基于Webpack5的打包构建优化

2,297 阅读6分钟

一、量化工具

1. speed-measure-webpack-plugin 

测量webpack构建速度

下载量:(100w/weeks)

使用方式:

//webpack.config.js 
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin(); 
const config = {     //...webpack配置 }  
module.exports = smp.wrap(config);

效果:

Speed Measure Plugin 输出预览

2. webpack-bundle-analyzer 

创建所有捆绑包内容的交互式树形图可视化

下载量:(500w/weeks)

使用方式:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const config = {     
   plugins: [         
       //...         
       new BundleAnalyzerPlugin(),     
   ]    
}  

效果:

image.png

二、速度优化

1. exclude/include

我们可以通过 excludeinclude 配置来确保转译尽可能少的文件。顾名思义,exclude 指定要排除的文件,include 指定要包含的文件。

exclude: /node_modules/, // 不检查node_modules里的代码 黑名单 
include: [path.resolve(__dirname, 'src')], // 只检查src里面的代码 白名单

2.  cache: boolean|object

(在构建及打包过程中都生效,但如果文件有修改则无法使用缓存)

缓存生成的 webpack 模块和 chunk,来改善构建速度。cache 会在开发模式被设置成 type: 'memory' 而且在 生产模式 中被禁用。 cache: true 与 cache: { type: 'memory' } 配置作用一致。 传入 false 会禁用缓存

memory 内存中缓存,不允许额外配置;

filesystem文件系统中缓存,可以额外配置;

cache: {     
    type: 'filesystem', //开放更多的可配置项      
    cacheDirectory: path.resolve(__dirname, '.temp_cache'), //缓存位置 
},

其他缓存相关优化:cache-loader ;给 babel-loader 增加选项 cacheDirectory;require('hard-source-webpack-plugin')(52w/weeks)[注意:不能和speed-measure-webpack-plugin插件公用]

3. thread-loader

使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。

在 worker 池中运行的 loader 是受到限制的。例如:

  • 这些 loader 不能生成新的文件。
  • 这些 loader 不能使用自定义的 loader API(也就是说,不能通过插件来自定义)。
  • 这些 loader 无法获取 webpack 的配置。

每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右。同时会限制跨进程的数据交换。

请仅在耗时的操作中使用此 loader!

4.module.noParse

防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中 不应该含有 importrequiredefine 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。

module.exports = {   
   //...   
   module: {     
       noParse:/jquery|lodash/,   
   }, 
};

5.resolve.alias

alias通过创建import或者require的别名,把原来导入模块的路径映射成一个新的导入路径,这样的好处就是webpack直接会去对应别名的目录查找模块,减少了搜索时间。

module.exports = {  
    resolve: {     
        alias: {       
            '@': path.resolve(__dirname, 'src'),     
        },   
    }, 
};

6.resolve.extensions

extensions字段用来在导入模块时,自动带入后缀尝试去匹配对应的文件

extensions数组越长,或者正确后缀的文件越靠后,匹配的次数越多也就越耗时

module.exports = {   
   resolve: {         
       extensions: ['.js', '.json']     
   } 
}

7. 模块懒加载

如果不进行模块懒加载的话,最后整个项目代码都会被打包到一个js文件里,单个js文件体积非常大,那么当用户网页请求的时候,首屏加载时间会比较长,使用模块懒加载之后,大js文件会分成多个小js文件,网页加载时会按需加载,大大提升首屏加载速度

 const routes = [   
     {     
         path: '/login',     
         name: 'login',     
         component: login   
     },   
     {   
         path: '/home',     
         name: 'home',     // 懒加载     
         component: () => import('../views/home/home.vue'),   
     }, 
]

8. hash

hash 计算与整个项目的构建相关;

chunkhash 计算与同一 chunk 内容相关; 根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。

contenthash 计算与文件内容本身相关。 将根据资源内容创建出唯一 hash,也就是说文件内容不变,hash 就不变。

我们要保证,改过的文件需要更新hash值,而没改过的文件依然保持原本的hash值,这样才能保证在上线后,浏览器访问时没有改变的文件会命中缓存,从而达到性能优化的目的

   output: {     
        path: path.resolve(__dirname, '../dist'),  // 给js文件加上 contenthash 
        filename: 'js/chunk-[contenthash].js',     
       clean: true,   
  },

三、大小优化

1. terser-webpack-plugin

该插件使用 terser 来压缩 JavaScript。

参数: parallel: boolean | number;  使用多进程并行运行来提高构建速度,默认并发运行数:os.cpus().length - 1。并行化可以显着加快构建速度,因此强烈推荐

module.exports = {   
    optimization: {     
        minimize: true, //开发环境不压缩,能显著提高编译速度      
        minimizer: [       
            new TerserPlugin({         
                parallel: 4,       
            }),     
        ],   
    }, 
};

2. IgnorePlugin

webpack 的内置插件,作用是忽略第三方包指定目录。

例如: moment (2.24.0版本) 会将所有本地化内容和核心功能一起打包,我们就可以使用 IgnorePlugin 在打包时忽略本地化内容。

module.exports = {    
    //忽略 moment 下的 ./locale 目录    
    new webpack.IgnorePlugin({          
        resourceRegExp: /^./locale$/,          
        contextRegExp: /moment$/,    
    }),
}

3. externals及html-webpack-externals-plugin

我们希望在使用时,仍然可以通过 import 的方式去引用(如 import $ from 'jquery'),并且希望 webpack 不会对其进行打包,此时就可以配置 externals

module.exports = {     
    //...     
    externals: {         
        //jquery通过script引入之后,全局中即有了 jQuery 变量         
        'jquery': 'jQuery'     
    } 
}

webpack 中公共模块,基础库, 多次使用的公共方法单独抽离,以减少包体积和打包时间

new HtmlWebpackExternalsPlugin({     
    externals: [          
        {               
            module: 'lodash',               
            entry: 'https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js',                 global: '_',          
        },     
   ], 
}),

4. optimization.runtimeChunk

设置为 true 或 'multiple',会为每个入口添加一个只含有 runtime 的额外 chunk。

设置为 object,对象中可以设置只有 name 属性,其中属性值可以是名称或者返回名称的函数,用于为 runtime chunks 命名。

module.exports = {   
    //...   
    optimization: {     
        runtimeChunk: {       
            name: 'runtime',     
        },   
     }, 
};

5. optimization.splitChunks

抽离公共代码是对于多页应用来说的,如果多个页面引入了一些公共模块,那么可以把这些公共的模块抽离出来,单独打包。公共代码只需要下载一次就缓存起来了,避免了重复下载。

抽离公共代码对于单页应用和多页应该在配置上没有什么区别,都是配置在 optimization.splitChunks 中。

optimization: {
    runtimeChunk: true,
    // 分割代码块
    splitChunks: {
        chunks: 'all',
        /**
         * initial 入口chunk,对于异步导入的文件不处理
         *  针对直接引入的代码
            async 异步chunk,只对异步导入的文件处理
            all 全部chunk 一般用这个
        */
        // 缓存分组
        cacheGroups: {
            // 第三方模块
            vendor: {
                name: 'vendor', // chunk 名称 即打包后所有第三方库都叫vendor
                // 拆分的时候 第三方和功能代码有可能冲突,比如第三方库也作为公共代码在多个地方引用
                // 此时用priority 先按第三方模块进行抽离,未命中在按公共模块抽离
                // priority越大 优先级越大
                priority: 1, // 权限更高,优先抽离,重要!!!
                // 通过test 来检测是否命中 通过检测路径
                test: /node_modules/,
                // 最小分组尺寸 小于此值不抽离 如果有些文件比较小 没有必要抽离
                minSize: 0, // 大小限制
                // 只要复用超过1次 就抽离
                minChunks: 1, // 最少复用过几次
            },

            // 公共的模块
            common: {
                name: 'common', // chunk 名称
                priority: 0, // 优先级
                minSize: 0, // 公共模块的大小限制
                // 只要复用超过2次 就抽离
                minChunks: 2, // 公共模块最少复用过几次
            },
        },
    },
},

6. mini-css-extract-plugin

本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。

不可与speed-measure-webpack-plugin 共用,参考"You forgot to add 'mini-css-extract-plugin' plugin"

const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 
module.exports = {
   ``````
    module:{ 		
        rules:[ 			
            {             	
                test: /.less$/,             	
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'], 		} 		
        ]      
    },      
    plugins: [             
        new MiniCssExtractPlugin(),     
    ] 
}

7. compression-webpack-plugin

nginx配置gzip压缩

对于静态资源,有两种开启压缩的方式,一种是compress in time,另一种是precompression。

v2-9c3947e9a122d2a9daba1bee1d7ef59c_r.jpeg

对于第二种,因为静态资源已经提前进行了压缩处理,当HTTP请求到达之后,可以直接响应已经压缩过的文件,所以可以节约服务器的CPU。

所以就算不配置compression-webpack-plugin插件也会使用gzip压缩,只是配置插件及nginx后可以节约cpu。

const CompressionPlugin = require('compression-webpack-plugin'); 
module.exports = { 	
   plugins: [             
       new CompressionPlugin(), 	
   ] 
}

8.url-loader

url-loader 可以识别图片的大小,然后把图片转换成base64,从而减少代码的体积 (url-loader内置了file-loader)

{     
    test: /.(jpg|png|gif|bmp)$/,     
    use: [        
        {             
            loader: 'url-loader',            
            options: {                 
                name: '[hash:10].[ext]',                 
                esModule: false,                 
                limit: 8 * 1024, // 如果文件的体积小于limit,则转为base64内置到html                     },       
        },     
    ], 
},

四、webpack5下的过时优化

  1. 除cache外的其他缓存优化,
    包括cache-loader ;
    给 babel-loader 增加选项 cacheDirectory;
    require('hard-source-webpack-plugin');
    [webpack5貌似内置了hard-source-webpack-plugin的技术]

  2. happypack 用来设置多线程,但是在 webpack5 就不要再使用 happypack 了,官方也已经不再维护了,推荐使用  thread-loader

  3. webpack-parallel-uglify-plugin 或者是 uglifyjs-webpack-plugin 配置 parallel来进行多进程压缩的优化,Webpack 默认使用的是 TerserWebpackPlugin,默认就开启了多进程和缓存。

  4.  dll 动态链接库(事先把常用但又构建时间长的代码提前打包好(例如 react、react-dom),取个名字叫 dll。后面再打包的时候就跳过原来的未打包代码,直接用 dll。这样一来,构建时间就会缩短,提高 webpack 打包速度。[其实就是缓存])

参考

辛辛苦苦学会的 webpack dll 配置,可能已经过时了

Webpack打包速度优化实践

webpack文档

前端访问优化之gzip压缩

带你深度解锁Webpack系列(优化篇)