webpack3 webpack4 webpack5

919 阅读5分钟

背景

原想在网上搜一下webpack各版本分别升级了升级了什么,发生了什么变化,发现似乎都是都是1对1的比较没有一次性列出从3到4,从4到5的变化。于是便自己写一遍记录一下。

内容只做一个大概的引导,其中也有尚未解决的问题,有知道的大佬,还请不吝赐教。

webpack3

特性

  • Scope Hoisting (作用域提升):将所有模块打包后放置到一个闭包函数中,从而减少了打包后文件大小,减少了浏览器运行耗时。
  • Magic Comments (魔法注释):分割代码块,并实现懒加载

具体内容参照

webpack3 => webpack4

特性

  • 编译速度提升,but why and do what?
  • 0配置开箱即用
  • 提供了wasm的支持,(WebAssembly)了解我
  • 全新插件系统
    • 提供了针对插件和钩子的新API,如下:
      • 所有的 hook 由 hooks 对象统一管理,它将所有的hook作为可扩展的类属性
      • 添加插件时,你需要提供一个名字
      • 开发插件时,你可以选择插件的类型(sync/callback/promise之一)
      • 通过 this.hooks = { myHook: new SyncHook(…) } 来注册hook

配置

  • 提供mode属性:'production' | 'development' | 'none' ; development 有最好的开发体验,production 专注项目编译部署,比如开启 Scope hoisting 和 Tree-shaking 功能。
  • optimization
    • 由 optimization.splitChunks 和 optimization.runtimeChunk 替代CommonsChunkPlugin:前者拆分代码,后者提取runtime代码。原来的CommonsChunkPlugin产出模块时,会包含重复的代码,并且无法优化异步模块,minchunks的配置也较复杂,splitChunks解决了这个问题;另外,将 optimization.runtimeChunk 设置为true(或{name: “manifest”}),便能将入口模块中的runtime部分提取出来。
    • 由 optimization.noEmitOnErrors 替代NoEmitOnErrorsPlugin,生产环境默认开启。
    • 由 optimization.namedModules 替代NamedModulesPlugin,生产环境默认开启。
    • 由 optimization.concatenateModules 替代ModuleConcatenationPlugin,生产环境默认开启。
    • 由 optimization.minimize 替代optimize.UglifyJsPlugin ,生产环境默认开启。
optimization: {
    minimize: env === 'production' ? true : false, // 开发环境不压缩
    splitChunks: {
        chunks: "async", // 共有三个值可选:initial(初始模块)、async(按需加载模块)和all(全部模块)
        minSize: 30000, // 模块超过30k自动被抽离成公共模块
        minChunks: 1, // 模块被引用>=1次,便分割
        maxAsyncRequests: 5,  // 异步加载chunk的并发请求数量<=5
        maxInitialRequests: 3, // 一个入口并发加载的chunk数量<=3
        name: true, // 默认由模块名+hash命名,名称相同时多个模块将合并为1个,可以设置为function
        automaticNameDelimiter: '~', // 命名分隔符
        cacheGroups: { // 缓存组,会继承和覆盖splitChunks的配置
            default: { // 模块缓存规则,设置为false,默认缓存组将禁用
                minChunks: 2, // 模块被引用>=2次,拆分至vendors公共模块
                priority: -20, // 优先级
                reuseExistingChunk: true, // 默认使用已有的模块
            },
            vendors: {
                test: /[\/]node_modules[\/]/, // 表示默认拆分node_modules中的模块
                priority: -10
            }
        }
    }
}

避坑

  • mini-css-extract-plugin 替代 extract-text-webpack-plugin 做css打包
    • 优点
      1. 异步加载
      2. 不重复编译,性能更好
      3. 更容易使用
    • 缺点:不支持css热更新,需在开发环境引入 css-hot-loader
  • 使用 optimize-css-assets-webpack-plugin做生产环境css的优化压缩

性能优化

  1. 缩小编译范围,减少不必要的编译工作。即 modules、mainFields、noParse、includes、exclude、alias用起来。
const resolve = dir => path.join(__dirname, '..', dir);

// ...
resolve: {
    modules: [ // 指定以下目录寻找第三方模块,避免webpack往父级目录递归搜索
        resolve('src'),
        resolve('node_modules'),
        resolve(config.common.layoutPath)
    ],
    mainFields: ['main'], // 只采用main字段作为入口文件描述字段,减少搜索步骤
    alias: {
        vue$: "vue/dist/vue.common",
        "@": resolve("src") // 缓存src目录为@符号,避免重复寻址
    }
},
module: {
    noParse: /jquery|lodash/, // 忽略未采用模块化的文件,因此jquery或lodash将不会被下面的loaders解析
    // noParse: function(content) {
    //     return /jquery|lodash/.test(content)
    // },
    rules: [
        {
            test: /.js$/,
            include: [ // 表示只解析以下目录,减少loader处理范围
                resolve("src"),
                resolve(config.common.layoutPath)
            ],
            exclude: file => /test/.test(file), // 排除test目录文件
            loader: "happypack/loader?id=happy-babel" // 后面会介绍
        },
    ]
}
  1. 使用happypack提升loader的解析速度。使用子进程实现并发编译。
  2. 使用 webpack-parallel-uglify-plugin 插件提升AST还原成js速度。原理调用node=>fork子进程,把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,从而实现并发编译,进而大幅提升js压缩速度。
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

// ...
optimization: {
    minimizer: [
        new ParallelUglifyPlugin({ // 多进程压缩
            cacheDir: '.cache/',
            uglifyJS: {
                output: {
                    comments: false,
                    beautify: false
                },
                compress: {
                    warnings: false,
                    drop_console: true,
                    collapse_vars: true,
                    reduce_vars: true
                }
            }
        }),
    ]
}
  1. 使用 DLLPlugin 和 DLLReferencePlugin 插件提前打包一些基本框架代码,不需每次编译都要加载一遍。比如:babel-polyfill、vue、vue-router、vuex、axios、element-ui、fastclick 等。
// webpack.dll.config.js

const webpack = require("webpack");
const path = require('path');
const CleanWebpackPlugin = require("clean-webpack-plugin");
const dllPath = path.resolve(__dirname, "../src/assets/dll"); // dll文件存放的目录

module.exports = {
    entry: {
        // 把 vue 相关模块的放到一个单独的动态链接库
        vue: ["babel-polyfill", "fastclick", "vue", "vue-router", "vuex", "axios", "element-ui"]
    },
    output: {
        filename: "[name]-[hash].dll.js", // 生成vue.dll.js
        path: dllPath,
        library: "_dll_[name]"
    },
    plugins: [
        new CleanWebpackPlugin(["*.js"], { // 清除之前的dll文件
            root: dllPath,
        }),
        new webpack.DllPlugin({
            name: "_dll_[name]",
            // manifest.json 描述动态链接库包含了哪些内容
            path: path.join(__dirname, "./", "[name].dll.manifest.json")
        }),
    ],
};

// webpack.config.js

externals: {
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'vuex': 'vuex',
    'elemenct-ui': 'ELEMENT',
    'axios': 'axios',
    'fastclick': 'FastClick'
},
plugins: [
    ...(config.common.needDll ? [
        new webpack.DllReferencePlugin({
            manifest: require("./vue.dll.manifest.json")
        })
    ] : [])
]

//   package.json
"scripts": {
    "dll": "webpack --mode production --config build/webpack.dll.config.js"
}
  • add-asset-html-webpack-plugin 插件帮我们自动引入 dll 文件。避免无法找到更新后的dll文件(新dll文件会加上新的hash)
//webpack.config.js

const autoAddDllRes = () => {
    const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
    return new AddAssetHtmlPlugin([{ // 往html中注入dll js
        publicPath: config.common.publicPath + "dll/",  // 注入到html中的路径
        outputPath: "dll", // 最终输出的目录
        filepath: resolve("src/assets/dll/*.js"),
        includeSourcemap: false,
        typeOfAsset: "js" // options js、css; default js
    }]);
};

// ...
plugins: [
    ...(config.common.needDll ? [autoAddDllRes()] : [])
]

webpack4 => webpack5

特性

  1. 剔除Node.js Polyfills
  2. 缓存
    • 浏览器长期缓存持久化:v4是根据代码的结构生成chunkhash,现在v5根据完全内容生成chunkhash,改了内容的注释或者变量则不会引起chunkhash的变化,让浏览器继续使用缓存

      • moduleId改为根据上下文模块路径计算,chunkId根据chunk内容计算,为module,chunk 分配确定的(3或5位)数字ID,这是包大小和长期缓存之间的一种权衡
    • 打包持久化缓存

      • 第一次构建是一次全量构建,它会利用磁盘模块缓存(以空间换时间),使得后续的构建从中获利。
      • 后续构建具体流程是:读取磁盘缓存 -> 校验模块 -> 解封模块内容。
// webpack.config.js
module.exports = { 
  cache: {
    // 1. 将缓存类型设置为文件系统
    type: 'filesystem', // 默认是memory
    // 2. 将缓存文件夹命名为 .temp_cache,
    // 默认路径是 node_modules/.cache/webpack
    cacheDirectory: path.resolve(__dirname, '.temp_cache')
  }
}
  1. 更好的Tree Shacking

    • 不仅仅支持ES module,v5开始支持commonjs规范的模块
    • 可标记更多没有使用的导出项
  2. 模板联邦——模块共享

    • 说明:直接将一个应用的包应用于另一个应用,同时具备整体应用一起打包的公共依赖抽取能力。
    • 精读

webpack打包结果分析

开发环境和生产环境都引入 webpack-bundle-analyzer 插件