关于webpack:webpack4配置优化

5次阅读

共计 10585 个字符,预计需要花费 27 分钟才能阅读完成。

后面咱们说了 webpack 的一些根底,当初咱们来应用 webpack 理论来编译写个我的项目。
用 vue-cli 创立一个我的项目,而后把它的 vue-cli-service 以及 webpack 等黑盒工具移除,而后咱们来本人编译它。
首先咱们要创立三个文件

  • webpack.common.js 公共的 webpack 配置
  • webpack.dev.js 开发阶段的配置
  • webpack.prod.js 生产阶段的配置

首先咱们来编写 webpack.common 文件

const HtmlWebpackPlugin = require('html-webpack-plugin')// html 模板
const webpack = require('webpack')
const PreloadWebpackPlugin = require('preload-webpack-plugin')// 预加载
const path = require('path')
const os = require('os')
const VueLoaderPlugin = require('vue-loader/lib/plugin')// vue 对应插件
const workers = { // 多线程编译
  loader: 'thread-loader',
  options: {workers: os.cpus().length
  }
}
module.exports = {// entry: ['babel-polyfill', './src/main.js'],
  entry: './src/main.js', // 入口
  output: {filename: 'bundle.js' // 输入},
  optimization: {
    concatenateModules: true, // 尽可能合并模块到一个函数
    splitChunks: { // 公共代码拆分
      // include all types of chunks
      chunks: 'all'
    },
    runtimeChunk: true // 运行时代码  拆出来 缓存
  },
  resolve: {
    // modules: [// 解析时搜寻得模块, 配了导致 ie 报错
    //     path.resolve(__dirname, 'node_modules'),
    // ],
    alias: { // 配置别名
      components: path.resolve(__dirname, 'src/components'),
      src: path.resolve(__dirname, 'src')
    },
    extensions: ['.js', '.json', '.vue'] // 省略后缀
  },
  module: {
    rules: [

      { // babel
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          workers,
          {
            loader: 'babel-loader',
            options: {
              // babel-polyfill 按需引入, 差了 1m 左右 core polyfill 原型
              presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3}]],
              cacheDirectory: true, // 开启缓存 第二次构建时,会读取之前的缓存, 吃内存,不开发了记得清下占用
              // presets: ['@babel/preset-env']
              plugins: [// import()语法反对
                '@babel/plugin-syntax-dynamic-import'
              ]
            }
          }

        ]
      },

      {test: /\.(gif|png|jpe?g|svg)$/i, // 不超过 10kb 转为 data:urlbase64
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10 * 1024, // 10 KB
              name: 'image/[name]-[contenthash:8].[ext]', // 输入 hash
              esModule: false
            }
          }]
      },

      {
        test: /\.vue$/, // 解析 vue 文件
        use: [
          workers,
          'vue-loader'
        ]
      }

    ]
  },
  plugins: [
    new PreloadWebpackPlugin({ // 退出预解析和预加载 webpack4 之后下载 preload-webpack-plugin@next 最新版
      rel: 'preload',
      as (entry) {console.log(entry)
        if (/\.css$/.test(entry)) return 'style'
        if (/\.woff$/.test(entry)) return 'font'
        if (/\.(png|jpe?g|gif)$/.test(entry)) return 'image'
        return 'script'
      },
      include: 'allChunks',
      fileBlacklist: [/\.(map)$/, /runtime~.+\.js$/]
      // 把 runtime 文件抽离进去,因为 import()运行时文件变动了,runtime 治理运行时 babel 导入文件的 hash 也会变动,// 默认 webpack 打包进去的依赖入口文件会把 runtime 导入,这样会导致它的 hash 也会变动,这样就会导致缓存生效,// 所以把 runtime 这个文件加载到 html 中,从以来入口文件中抽离,来防止依赖入口的 hash 变动。这样它就不须要进行预加载了。}),
    new HtmlWebpackPlugin({ // html 模板
      title: 'Vue dev App',
      template: path.resolve(__dirname, 'public/index.html')
    }),

    new VueLoaderPlugin(),
    new webpack.DefinePlugin({
      // 值要求的是一个代码片段
      BASE_URL: '"/"'
    })
  ]
}

代码里都有正文,而后咱们做下思路剖析:

  • 确定打包造成依赖图的入口文件和输入文件名字
  • 配置别名 resolve 不便我的项目开始时快捷引入
  • 配置 loader,首先解析 vue 文件须要 vue-loader 和 vue-loader/lib/plugin 插件, 解析图片应用 url-loader 配置大小(10kb 以下转换为 base64 超出的 copy 图片)和文件门路以及文件名 contenthash:8 内容级别的 hash,vue 的图片导入默认应用的 CommonJs 标准,所以标注 esModule 不转为 esm, 最初配置 babel-loader 转换 js 个性,首先下载 babel-core 外围模块和 env 编译所有新个性,指定不须要编译的文件夹,babel-polyfill 配置上兼容新的 api(Iterator、Generator、Set、Maps、Proxy、Reflect),而后配置 useBuiltIns 按浏览器短少的 polyfill 按需加载,而后配置 core:3 兼容 原型的办法(array.includes)(https://www.cnblogs.com/dh-dh/p/10071312.html

  • 而后咱们配置了 workers,在第一次编译后开启多线程打包,只对可能影响编译速度的 loader 增加。
  • 而后配置 optimization 属性,尽可能合并模块到一个函数,拆分公共代码,而后把运行时得代码拆分进去(import()的代码)
  • 而后配置插件,首先是预加载插件,html 模板,vueLoader 的插件,设置门路变量。

而后再配置开发阶段配置 webpack.dev


const {merge} = require('webpack-merge')// 合并配置插件
const common = require('./webpack.common')// 公共配置
const path = require('path')
const os = require('os')
const webpack = require('webpack')
const workers = { // 多线程编译
  loader: 'thread-loader',
  options: {workers: os.cpus().length
  }
}
module.exports = merge(common, {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  // devtool:'source-map',
  devServer: {
    hot: true,
    contentBase: path.join(__dirname, 'public'),
    open: true,
    stats: 'errors-only',
    clientLogLevel: 'none',
    disableHostCheck: true
  },
  module: {
    rules: [
      {test: /\.(js|vue)$/, // eslint
        enforce: 'pre',
        exclude: /node_modules/,
        include: path.resolve(__dirname, 'src'),
        loader: [workers, 'eslint-loader']
      },
      {
        test: /\.less$/, // 解析引入得 less
        use: [
          workers,
          'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
      {
        test: /\.css$/, // 解析组件外部 style
        use: [
          workers,
          'style-loader',
          'css-loader',
          'postcss-loader'
        ]
      }
    ]
  },
  plugins: [new webpack.HotModuleReplacementPlugin() // 热加载
  ]
})
  • 这个文件很简略,咱们就须要合并下公共配置,而后加一些开发阶段的非凡配置
  • 配置模式开发,配置 source-map,配置 devServer 服务热加载,读取资源目录
  • 配置 loader,首先下载 eslint 相干依赖,生产 eslint 配置,配置 eslint-loader,less 和 css 的 loader,咱们这里应用了 postcss 以及它的相干插件

postcss.config.js


const postcssConfig = {
  plugins: [ // 配合 package 的 browserslist
    require('postcss-import'),
    require('postcss-cssnext'),
    require('cssnano')
  ]
}
console.log(process.env.NODE_ENV)
if (process.env.NODE_ENV === 'production') { // 应用 postcss 插件形式实现,webpack 插件形式太麻烦了
  const purgecss = require('@fullhuman/postcss-purgecss')
  const purgecssConfig = require('./purgecss.config')
  postcssConfig.plugins.push(purgecss(purgecssConfig))
}
module.exports = postcssConfig

这个外面引入了一个生产阶段对 vue 外部 style css 摇树的插件配置,配置我贴出来
purgecss.config



module.exports = {content: ['./public/**/*.html', './src/**/*.vue'], /// / 清理范畴,所有的 vue component 和和入口的 html,js 里引入的默认是全局应用的
  defaultExtractor (content) {const contentWithoutStyleBlocks = content.replace(/<style[^]+?<\/style>/gi, '')
    return contentWithoutStyleBlocks.match(/[A-Za-z0-9-_/:]*[A-Za-z0-9-_/]+/g) || []},
  whitelist: [/el-.+$/], // 将 elementuijs 应用上的类也放在白名单不作解决
  whitelistPatterns: [/-(leave|enter|appear)(|-(to|from|active))$/, /^(?!(|.*?:)cursor-move).+-move$/, /^router-link(|-exact)-active$/, /data-v-.*/, /el-.+$/]
}
  • 最初配置一个热加载的插件

最初咱们配置生产文件
webpack.prod

const {merge} = require('webpack-merge')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')// 打包之前清空
const CopyWebpackPlugin = require('copy-webpack-plugin')// copy 文件插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')// 把 css 抽成 link 的形式
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')// 压缩 css
const TerserWebpackPlugin = require('terser-webpack-plugin')// 压缩 js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')// 匹配 runtime.chunk 文件写到 html 中
const common = require('./webpack.common')
const path = require('path')
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer') // webpack 打包后果剖析
console.log(common)
// common.module.rules.push({//     test: /\.(gif|png|jpe?g|svg)$/i,
//     // 这个依赖下载要用 cnpm 淘宝源,并且用管理员权限下载, 下不下来把依赖都删了,而后从新下载,其余都下不来,亲身经历
//    // 算了不必它了,通过它压缩过后的图片包含 base64,在 ie 中不能解析
//     loader: 'image-webpack-loader',
//     options: {
//         mozjpeg: {
//             progressive: true,
//         },
//         // optipng.enabled: false will disable optipng
//         optipng: {
//             enabled: false,
//         },
//         pngquant: {//             quality: [0.65, 0.90],
//             speed: 4
//         },
//         gifsicle: {
//             interlaced: false,
//         },
//         // the webp option will enable WEBP
//         webp: {
//             quality: 75
//         }
//     }
// })
module.exports = merge(common, {
  mode: 'production',
  output: {path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].bundle.js', // 输入 name hash
    chunkFilename: '[name].[contenthash:8].chunk.js' // chunk 文件 hash
  },
  devtool: 'none',
  module: {
    rules: [
      {test: /\.(less)$/,
        use: [
          MiniCssExtractPlugin.loader, // 生成环境,最初不实用 style 插入,应用 link 形式插入
          // 'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
      {
        test: /\.css$/, // 解析组件外部 style
        use: [
          MiniCssExtractPlugin.loader,
          // 'style-loader',
          'css-loader',
          'postcss-loader'
        ]
      }
    ]
  },
  optimization: {
    minimizer: [
      new TerserWebpackPlugin({parallel: true// 开启多线程}), // 压缩 js
      new OptimizeCssAssetsWebpackPlugin({
        cssProcessPluginOptions: {preset: ['default', { discardComments: { removeAll: true} }]
        }
      }) // 压缩 css
    ],

    // 模块只导出被应用的成员
    usedExports: true,
    sideEffects: true // 标记无副作用,配合 package js 摇树应用
  },
  plugins: [new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Vue prod App',
      template: path.resolve(__dirname, 'public/index.html'),
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      }
    }),
    new MiniCssExtractPlugin({ // 生成 link 的 css 文件, 名称 hash
      filename: '[name].[contenthash:8].css'
    }),
    new CopyWebpackPlugin([ // 复制文件
      'public'
    ]),
    new ScriptExtHtmlWebpackPlugin({ // 写入 html 中
      inline: [/runtime~.+\.js$/] // 正则匹配 runtime 文件名
    }),

    new BundleAnalyzerPlugin() // webpack 打包后果剖析]
})
// css 摇树应用 postcss 插件实现了
  • 老样子配置模式,和输入地址,这会的文件名称就须要用到 contenthash 文件级别的 hash 生成,不便咱们做浏览器缓存(chunkFilename 文件的名字也做了 hash)。
  • 去除 source-map
  • 配置 css 相干 loader 最初转换的时候咱们不应用 style-loader 形式用 style 标签插入,而是应用 link 文件生成 css 文件引入
  • optimization 配置压缩 js 和 css,usedExports 标识导出应用的模块,标记 sideEffects 无副作用配合 package 文件标识局部右副作用文件,来实现 js 摇树
  • 最初就是插件,清空文件夹,html 模板,生成 css 文件(不反对热加载,生产阶段应用,该插件的次要是为了抽离 css 款式, 避免将款式打包在 js 中文件过大和因为文件大网络申请超时的状况), 复制文件,而后把 runtime 结尾的治理 (import()babel) 异步治理导入的文件,写入到 html 中(为什么这样 common 配置里有写,webpack 相干的 runtimeChunk 又讲相干常识)。上线优化计划
  • 最初就是增加一个 webpack 后果打包剖析的插件

babel.config.js 咱们有一个兼容[vue-cli 的浏览器兼容 babel 的配置]

module.exports = {
  presets: ['@vue/cli-plugin-babel/preset']
}

最初贴的就是 package.json

{
  "name": "vue-app-base",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config webpack.prod.js",
    "lint": "eslint --ext .js,.vue src/",
    "all-lint-fix": "eslint --fix --ext .js,.vue src/",
    "precommit": "lint-staged"
  },
  "dependencies": {
    "core-js": "3",
    "vue": "^2.6.11"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/preset-env": "^7.12.7",
    "@fullhuman/postcss-purgecss": "^3.0.0",
    "@vue/cli-plugin-babel": "^4.5.9",
    "babel-loader": "^8.2.1",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^5.0.4",
    "cross-env": "^7.0.3",
    "css-loader": "^5.0.1",
    "cssnano": "^4.1.10",
    "eslint": "^7.14.0",
    "eslint-config-standard": "^16.0.2",
    "eslint-loader": "^4.0.2",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-vue": "^7.1.0",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^4.5.0",
    "husky": "^4.3.0",
    "image-webpack-loader": "^7.0.1",
    "less": "^3.12.2",
    "less-loader": "^7.1.0",
    "lint-staged": "^10.5.2",
    "mini-css-extract-plugin": "^1.3.1",
    "optimize-css-assets-webpack-plugin": "^5.0.4",
    "postcss": "^8.1.10",
    "postcss-cssnext": "^3.1.0",
    "postcss-import": "^13.0.0",
    "postcss-loader": "^4.1.0",
    "preload-webpack-plugin": "^3.0.0-beta.4",
    "script-ext-html-webpack-plugin": "^2.1.5",
    "style-loader": "^2.0.0",
    "terser-webpack-plugin": "2.2.1",
    "thread-loader": "^3.0.1",
    "url-loader": "^4.1.1",
    "vue-loader": "^15.9.5",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^4.41.2",
    "webpack-bundle-analyzer": "^4.1.0",
    "webpack-cli": "3.3",
    "webpack-dev-server": "^3.9.0",
    "webpack-merge": "^5.4.0"
  },
  "lint-staged": {"src/**/*.(js|vue)": [
      "eslint --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {"pre-commit": "npm run precommit"}
  },
  "eslintConfig": {
    "root": true,
    "env": {"node": true},
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {"parser": "babel-eslint"},
    "rules": {}},
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "IE 10"
  ],
  "sideEffects": [
    "*.css",
    "*.less",
    "*.vue"
  ]
}

这样就配置实现了,这一套配置其实去除掉 vue 相干的就是一套通用配置。
做下 webpack 的 loader 和 plugin 区别

  • loader: loader 尽管是扩大了 webpack,然而它只专一于转化文件(transform)这一个畛域,实现压缩,打包,语言翻译。
    loader 是运行在 NodeJS 中。
    仅仅只是为了打包,仅仅只是为了打包,仅仅只是为了打包,重要 的话说三遍!!!
  • plugin:plugin 也是为了扩大 webpack 的性能,然而 plugin 是作用于 webpack 自身上的。而且 plugin 不仅只局限在打包,资源的加载上,它的性能要更加丰盛。从打包优化和压缩,到从新定义环境变量,功能强大到能够用来解决各种各样的工作。

webpack 的执行流程咱们能够间接来参考这张图来看。

正文完
 0