webpack常用优化

如何加快构建速度

  • 通过externals配置来提取常用库
  • 利用DllPlugin和DllReferencePlugin预编译资源模块
  • HappyPack它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
  • 缩小范围
    • module.rules
      • exclude、include
    • resolve.modules
      默认是当前目录下的node_modules,而依赖库却在项目根目录下的node_modules
    • resolve.mainFields
    • resolve.extension
      extensions默认是[js,json]
  • babel-plugin-import

优化代码

  • splitChunks
  • 代码分割
  • contentHash长缓存
  • prefetch/preload
  • css/js压缩,postcss去重,去掉无用css
  • 文件放入cdn(多域名)
  • tree-shaking
    • es6模块导入,静态,不能是require

第三方库处理-externals

防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。参见externals

include exclude 常用于loader

  • 首先,不配置这两个属性你引入的模块还是会被打包。
  • 但是,很多第三方模块是不需要再被处理的,比如jQuery,不需要再被babel处理,因为jQuery已经是es5,浏览器直接可以识别。这个时候,你不设置exclude,jQuery就会被处理,这样就增加了打包时间。
  • 所以,设置好exclude和include可以优化打包时间。

babel-plugin-import

实现模块按需加载、而不是引一个模块将整个库打包进来

scope-hoisting

Scope Hoisting 它可以让webpack打包出来的代码文件更小,运行更快,它可以被称作为 “作用域提升”。是在webpack3中提出来的,当然现在webpack4也是支持的。

  • 代码体积更小,因为函数申明语句会产生大量代码;
  • 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小。
  • 由于 Scope Hoisting 需要分析出模块之间的依赖关系,因此源码必须采用 ES6 模块化语句,不然它将无法生效。
    • ES2015 的模块语法规定 import 和 export 关键字必须在顶层、模块路径只能用字符串字面量,这种“强制静态化”的做法使代码在编译时就能确定模块的依赖关系,以及输入和输出的变量,所以这种功能实现起来会更加简便。
      暂不支持 CommonJS 模块语法的原因是,这种模块语法中的模块是可以动态加载的,很难分析出模块之间的依赖关系及输出的变量。
  • 预先计算部分值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // d.js
    let a = 1;
    let b = 2;
    let c = 3;
    let d = a + b + c;

    export default d;

    // index.js
    import d from './d';
    console.log(d)
  • prod环境开启了后部分代码

    1
    ([function (e, t, r) { "use strict"; r.r(t); console.log("1111111"), console.log("result is", 6) }]);
  • dev环境未开启bundle.js部分代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    /******/ ({

    /***/ "./src/d.js":
    /*!******************!*\
    !*** ./src/d.js ***!
    \******************/
    /*! exports provided: default */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    __webpack_require__.r(__webpack_exports__);
    var a = 1;
    var b = 2;
    var c = 3;
    var d = a + b + c;
    /* harmony default export */ __webpack_exports__["default"] = (d);

    /***/ }),

    /***/ "./src/index.js":
    /*!**********************!*\
    !*** ./src/index.js ***!
    \**********************/
    /*! no exports provided */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _d__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./d */ "./src/d.js");

    console.log('1111111');
    console.log('result is', _d__WEBPACK_IMPORTED_MODULE_0__["default"]);

    /***/ })

    /******/ });

webpackBundle-analyzer

通过本插件分析打包文件

tree-shaking

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import 和 export。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。
webpack 2 正式版本内置支持 ES2015 模块(也叫做 harmony modules)和未使用模块检测能力。新的 webpack 4 正式版本扩展了此检测能力,通过 package.json 的 “sideEffects” 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。

  • tree-shaking 默认只支持 es6语法静态导入,不支持require等
  • production环境默认支持dev环境不支持
  • 确保没有 compiler 将 ES2015 模块语法转换为 CommonJS 模块
  • 在项目 package.json 文件中,添加一个 “sideEffects” 属性。
    • sideEffects
      • package.json里sideEffects默认是true
        • 即使没使用test变量,但是import test.js后仍然会打包到bundle
      • package.json的sideEffects为false的话
        • test文件不会被打包到bundle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//package.json
//排除css,否则css也会被排除在外
"sideEffects": [
"**/*.css"
],
// index.js
import test from './test'//代码没用test变量

//test.js,有副作用,不是纯函数
function test(){
return 'hello'
}
console.log('llloooootesttest') //副作用effects
test();

export default test;

第三方库处理-dll(动态链接库)

DLLPlugin 和 DLLReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。

  • dll.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // dll.config.js
    const path = require('path');
    const DllPlugin = require('webpack').DllPlugin;
    // 需要产生一个缓存列表
    module.exports = {
    mode: 'development',
    entry : ['react','react-dom'], // add minus
    output:{
    library:'react',//放全局变量
    // libraryTarget:'commonjs2', //默认是var,commonjs, commonjs2, umd this
    filename : 'react.dll.js',
    path: path.resolve(__dirname,'dll')
    },
    plugins:[
    new DllPlugin({
    name : 'react',
    path : path.resolve(__dirname,'dll/manifest.json')
    })
    ]
    }
  • webpack.config配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // webpack.config.js
    // // dll去找manifest.json文件
    new DLLReferencePlugin({
    manifest : path.resolve(__dirname,'./dll/manifest.json')
    }),
    // //将dllreact.js引入到html
    new AddAssetHtmlPlugin({
    filepath : path.resolve(__dirname,'./dll/react.dll.js')
    }),
  • html模板
    需要单独引用打好的dll文件,或者通过AddAssetHtmlPlugin自动引入

构建速度-splitChunks

webpack 总共提供了三种办法来实现 Code Splitting,

  • 入口配置:entry 入口使用多个入口文件;
  • 抽取公有代码:使用 SplitChunks 抽取公有代码;
  • 动态加载 :动态加载一些代码。
  • 多entry时,相同文件文件提取
  • 被不同文件引用过N次以上时提取
  • chunks参数
    • async(默认)
      • 只有通过import(‘aaaa.js’).then动态语法会提取公共文件
    • initial/all
      • 动态导入或静态导入多次的都会提取为公共文件
  • minChunks
    模块被不同的 chunk 引入超过 1 次的抽取为 common
  • catchGroup
    它可以继承/覆盖上面 splitChunks 中所有的参数值,除此之外还额外提供了三个配置,分别为:test, priority 和 reuseExistingChunk。
    test: 表示要过滤 modules,默认为所有的 modules,可匹配模块路径或 chunk 名字,当匹配的是 chunk 名字的时候,其里面的所有 modules 都会选中;
    priority:表示抽取权重,数字越大表示优先级越高。因为一个 module 可能会满足多个 cacheGroups 的条件,那么抽取到哪个就由权重最高的说了算;
    reuseExistingChunk:表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 生产环境下第三方模块进行抽离
optimization:{
// usedExports:true,
splitChunks: {
chunks: 'initial',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 6,
maxInitialRequests: 4,
automaticNameDelimiter: '~',
automaticNameMaxLength: 30,
cacheGroups: {
react:{
test : /[\\/]node_modules[\\/](react)|(react-dom)/,
priority:-2
},
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},

动态加载-代码分割

  • 动态导入 类比路由的懒加载 import
  • 默认会产生代码分割
  • 使用jsonp异步加载
  • 魔术字符串修改分割文件name
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    button.addEventListener('click',()=>{
    // 动态导入 类比路由的懒加载 import
    // 默认会产生代码分割
    // 使用jsonp异步加载 ./calc
    // 魔术字符串修改分割文件name
    import(/* wepbackChunkName:'video' */'./calc').then(res=>{
    console.log(res.add(23,32))
    })
    });
    //修改异步chunk名字,chunkFilename:'[name].min.js'
    // 0.min.js
    // 8.bundle.js