webpack4性能优化小结——HMR、source-map、oneOf、babel缓存、 多进程打包、文件缓存、tree shaking、code split、dll、pwa、externals

webpack性能优化:

开发环境性能优化

1)优化打包构建速度 —— HMR
2)优化代码调试—— source-map

生产环境性能优化

1)优化打包构建速度——oneOf、babel缓存、 多进程打包
2)优化代码运行的性能——文件缓存、tree shaking、code split、dll、懒加载和预加载、pwa、externals


开发环境配置–优化打包构建的速度

使用HMR(hot module repalcement),热模块替换,作用是一个模块发生改变,只会重新打包这个模块,而不是全部更新。极大地提升了构建速度。

**样式文件:**不必做特殊处理,使用的style-loader内部实现了热模块替换功能。
**js文件:**在默认情况下不会使用HMR功能,需要在js代码中添加支持HMR功能的代码。

if (module.hot) {
  module.hot.accept('./logon.js', () => {
    logon();
  });
}

以上代码表示只有在logon模块发生改变时内部函数才会执行处理。其他模块不会重新打包构建,这段代码在那个中监听的logon模块在哪个js文件中引入,就在哪个js文件中添加即可。注意:HMR功能只能对非入口文件进行处理,入口文件无法处理。一旦入口文件被修改,整个项目代码都会被重新构建。
**html文件:**不能使用hot功能,需要在webpack配置入口修改配置,将html文件作为入口文件引入。但是修改完成之后,一旦html文件发生变化,所有文件都会被重新编译加载。

entry:["./src/js/index.js","./src/index.html"],

开发环境配置–优化代码调试

使用工具:source-map——提供一种源代码到构建后代码的映射技术,如果构建后代码出错,可以通过追踪源代码错误信息的位置。需要在webpack.config.js中新增配置项:

devtool:"source-map"

新增的配置会在构建后的输文件夹中输出对应的sourceMap文件,以方便在浏览器中通过源码进行调试,devtool取值的不同输出的sourceMap输出的方式也不同,关于devtool的取值如下如所示:

devtool含义
不生成 Source Map,错误代码提示准确,没有任何的源代码信息
eval每个 module 会封装到 eval 里包裹起来执行,并且会在每个 eval 语句的末尾追加注释 //# sourceURL=webpack:///./main.js
source-map会额外生成一个单独 Source Map 文件,并且会在 JavaScript 文件末尾追加 //# sourceMappingURL=bundle.js.map错误代码提示准确,并且提示源代码的错误位置。
hidden-source-map和 source-map 类似,但不会在 JavaScript 文件末尾追加 //# sourceMappingURL=bundle.js.map不能追踪源代码的错误位置,只能提示到构建后代码的错误位置
inline-source-map和 source-map 类似,但不会额外生成一个单独 Source Map 文件,而是把 Source Map 转换成 base64 编码内嵌到 JavaScript 中。错误代码提示准确,并且提示源代码的错误位置。
eval-source-map和 eval 类似,但会把每个模块的 Source Map 转换成 base64 编码内嵌到 eval 语句的末尾,例如 //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW...错误代码提示准确,并且提示源代码的错误位置。
cheap-source-map和 source-map 类似,但生成的 Source Map 文件中没有列信息,因此生成速度更快。错误代码提示准确,并且提示源代码的错误位置,只能精确到行,不能精确到列。
cheap-module-source-map和 cheap-source-map 类似,但会包含 Loader 生成的 Source Map错误代码提示准确,并且提示源代码的错误位置。

根据以上的配置,或生成独立的sourceMap文件或直接内敛至js文件的末尾。除了生成的sourceMap文件的不同之外,对源代码的映射也不同,即有错误可提示到源代码的具体位置,或是提示至打包构建后代码的位置。

打包后浏览器中sourceMap映射的源码:

在这里插入图片描述

生成单独map文件:

在这里插入图片描述

没有单独的sourceMap文件,内敛在js中的sourceMap:

在这里插入图片描述

在这里插入图片描述

那么问题来了,如何选择合适的source-map?

秉着开发环境构建速度更快,调试更加友好的原则,建议:
速度快(eval-source-map/eval-cheap-source-map),调试友好(source-map/cheap-module-source-map/cheap-sourcemap),所以在开发环境下把 devtool 设置成 cheap-module-eval-source-map,因为生成这种 Source Map 的速度最快,能加速构建。由于在开发环境下不会做代码压缩,Source Map 中即使没有列信息也不会影响断点调试;

生产环境主要是考虑代码要不要隐藏,要不要做调试,因为内敛的sourceMap会让代码的体积变得巨大,所以内敛的方式都不会被采用,建议:
hidden-source-map 隐藏源代码,会提示构建后的代码错误信息。
生产环境需要调试的则直接使用source-map或者cheap-module-source-map

优化的配置项:

oneOf:优化module中loader的执行方式

如果你的module中配置了多个loader,webpack打包每一个文件的时候都会有序执行每一个loader配置,相当于每一个文件都会被所有的loader过一遍,会减缓webpack的打包速度。使用oneOf配置项可以使每一个文件只会匹配一个loader,oneof里面不能有两项配置处理同一个类型的文件:如果需要,则可以将其中一个优先执行的提取到oneOf外面进行配置:

module: {
        //loader的详细规则
        rules: [
            {
                test: /\.js$/,
                loader: "eslint-loader",//js检查语法
                exclude: /node_modules/,
                enforce: 'pre',//优先执行
                options: {
                    fix: true

                }
            },
            {
                //每一个文件只会匹配一个loader
                oneOf: [
                    {
                        test: /\.css$/,
                        use: [{
                            loader: miniCssExtractPlugin.loader,
                            options: {
                                publicPath: "../",
                            },
                        },
                        {
                            loader: "css-loader"
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                ident: "postcss",
                                plugins: [
                                    require("postcss-preset-env")()
                                ]
                            }
                        }
                        ] 

                    },
                    {
                        test: /\.js$/,
                        loader: "babel-loader",//js检查语法兼容
                        exclude: /node_modules/,
                        options: {
                            presets: [
                                [
                                    "@babel/preset-env",
                                    {
                                        useBuiltIns: 'usage',
                                        corejs: {
                                            version: 3
                                        },
                                        targets: {
                                            chrome: '60',
                                            firefox: '60',
                                            ie: '9',
                                            safari: '10',
                                            edge: '17'
                                        }
                                    }
                                ]
                            ]
                        }
                    }
                ]
            },
        ],
    }

例如以上的module配置,js文件我们需要语法检查和语法兼容,但是配置里又有css-loader,js文件是不需要css-loader进行处理的,所以将配置放在oneOf里面,表示每一个文件只用匹配一个loader进行处理。但是js文件需要进行两次处理,即可把其中一个优先执行的配置放在外面,在进行oneOf处理,在文件很多的大项目里,会大大提高webpack的打包速度。

生产环境的缓存配置:

1)babel缓存:babel是对js文件进行处理编译处理、兼容性处理,编译成浏览器能够识别的语法。假设js有很多个模块,若开发过程中只改变其中一个模块,编译时会重新打包所有模块,降低webpack的打包速度,跟HMR功能类似,只不过HMR是浏览器刷新时的重新加载的模块,这个指的是webpack打包时的模块。

开启babel缓存只需要在babel-loader下的options下面添加:cacheDirectory: true

{
	test: /\.js$/,
    loader: "babel-loader",//js检查语法兼容
    exclude: /node_modules/,
    options: {
		presets: [
			[
          		"@babel/preset-env",
          		{
            		useBuiltIns: 'usage',
            		corejs: {
             			version: 3
            		},
            		targets: {
            			chrome: '60',
            			firefox: '60',
            			ie: '9',
            			safari: '10',
                		edge: '17'
            		}
           		}
           ]
		],
        //开启babel缓存
        cacheDirectory: true
	}
}

2)文件资源缓存:

从服务请求回来得到的资源一般都是有强制缓存的,这样会提高用户浏览网页的速度,当用户再次进入该页面时不用从服务器获取相应的资源,而是从缓存中获取。但是假设有网页有缺陷,开发人员紧急修复,但是用户有缓存效果没有得到最新的资源。则这不会是我们想要的效果。

基本思路:每个文件附带版本号,每次请求若版本号变化时,会重新获取资源而不是从缓存中获取。每一次webpack打包都会产生一个hash值,将此hash值作为输出文件的名称的拼接可以有效解决这个问题。其中hash值包括:
(1)hash:每次打包构建生成的唯一hash值。
(2)chunkhash:根据chunk生产的hash值,如果打包来源是属于同一个chunk,则它们使用的hash值就会是一样的。
(3) contenthash:根据文件内容产生的hash值,不同文件的hash值是不一样的。
这三个hash也是面试之中面试官常问的问题。那么我们应该用哪一个?用contenthash!
使用hash值每次打包,所有的缓存都会失效,但是发生改变的文件可能只有几个,但是又要从服务器中重新获取。
使用chunkhash和hash是一样的,若不同的文件来自同一个chunk,则修改其中一个重新打包,其他文件的缓存也会失效。所以真相只有一个,使用contenthash相对较好

output: {
        //输出的文件名字
        filename: "js/built-[contenthash:12].js",
        path: resolve(__dirname, "build")
    },

总结:babel缓存让第二次打包构建的速度更快,文件资源缓存让上线运行环境的缓存更好使用。

tree shaking:去除无用代码,忽略一些无用文件(css\js)减小构建后文件的体积。

使用方式:使用ES6模块化,开启production模式。
在默认情况使用tree shaking时,不同的版本可能会忽略掉一些有用的文件,例如你在js中import了css文件,但是这个js文件的代码里并不会用到这个css文件,此时打包会误认为是无用的文件。所以必须在package.js中配置sideEffects

"sideEffects":["*.css"]//多个需要配置的文件就写在数组中。

code split 代码分割

将一个大的文件分割打包成多个文件,加载时可以进行并行加载速度更快,对于按需加载功能才可以实现。

实现方式:
1)多入口文件配置:

//使用多入口文件可以实现不同模块单独输出js文件
entry: {
        index:"./src/js/index.js",
        logon: "./src/js/logon.js"
    },

2)使用optimization配置:可以将第三方文件也就是node_modules中的文件单独打包出来成为一个js文件,例如上述中有index.js文件和logon.js文件,加入两个文件都引入了同一个jquery模块,这是可以将jQuery单独打包成一个文件。如果不单独打包,两个文件内部都引入了jQuery,文件非常大,相当于打包了两次jQuery。在浏览页面的时候,jq文件存在的缓存也不会重复加载

在这里插入图片描述

 optimization:{
        splitChunks:{
            chunks: "all"
        }
    }
//自动分析多入口文件中有没有公共的依赖,如果有,将这个依赖单独打包处理

3)使用js代码完成单独打包:

可以在js使用import引入文件的时候,通过内部js代码将引入的文件单独打包成一个js,例如你在index.js文件里引入了logon,但是你是单入口配置,在不修改配置的情况下,可以

import(/* webpackChunkName: 'logon' */'./logon').then(({logon})=>{
  console.log(logon);
}).catch(()=>{
  console.log("logon加载失败");
});

/* webpackChunkName: 'logon' */表示输出文件的名字为logon

PWA 渐进式网络开发应用程序——离线访问,兼容性问题,还没完全普及。

使用workbox插件 workbox-webpack-plugin 按照惯先npm安装插件,然后引入并且在plugins配置项里new 一个:

new WorkboxWebpackPlugin.GenerateSW({
            clientsClaim:true,
            skipWaiting: true,
        })
//帮助serviceworker快速启动,删掉旧的serviceworker,生成一个serviceworker配置文件

兼容处理,在入口文件做处理,之后在服务器上打开即可

if("serviceWorker" in navigator){
  window.addEventListener("load",()=>{
    //  service-worker.js是打包生产的配置文件
    navigator.serviceWorker.register('/service-worker.js')
    .then(()=>{
      //注册成功处理函数
      console.log("1");
    })
    .catch(()=>{
      //注册失败处理函数
      console.log("2");
    })
  })
}

打开浏览器即可在开发工具里看到已经注册好的serviceWorker

在这里插入图片描述

多线程打包—— thread-loader

webpack单线程处理程序,如果打包的文件很多,工程量比较大,则打包速度就会降低,所以此时可以考虑多线程打包。多进程打包是需要开启时间的,启动时间大约为600ms,进程通信为也需要时间,只有打包时间需要较长的打包才会使用多进程打包。

{
  test: /\.js$/,
  exclude: /node_modules/,
  use:[
       {
         loader: "thread-loader",
         workers: 2 //进程数,可以和cpu进程数比较
        },
       {
        loader: "babel-loader",//js检查语法兼容
        options: {
         presets: [
             [
                "@babel/preset-env",
                 {
                 //......
                 }
             ]
          ],
         cacheDirectory: true
                                }
      }
    ],
}

externals——忽略某些包,不进行打包。

在打包的时候有些包是不需要打包到项目里面的,例如通过CDN进行引入的包,这个时候就可以使用externals

externals:{
        //忽略的npm包名 不打包 
        jquery: "jQuery"
    },
 //在webpack.config.js的entry同级下配置这个选项,此时jq不会被打包到项目中

dll 动态链接库

将一些需要单独打包的库(一般为第三方库)一次打包完成,可以将多个库打包成一个chunk,后续修改代码也不用重新打包这个库。即:优化第三方库的打包。

操作步骤:

(1)新建一个新的打包配置文件,名字随意。

const {resolve} = require("path");
const webpack = require("webpack");

module.exports= {
    entry:{
        //最终打包生成的name 和 需要单独打包的库
        jquery : ["jquery"] //如果有更多的库可以放在数组里面
    },
    output:{
        filename: "[name].js",
        path: resolve(__dirname,"dll"),
        library: "[name]_[hash]" //打包的库里面向外暴露的内容名字
    },
    plugins:[
        //打包生成一个manifest.json 停供映射的内容
        new webpack.DllPlugin({
            name: "[name]_[hash]", //映射的库暴露的名字
            path: resolve(__dirname,"dll/manifest.json"),
        })
    ],
    mode: "production"
}

(2)使用npm命令打包第三方库 webpack --config webpack.dll.js webpack.dll.js为新建的打包配置文件,此时输出目录下会有以上配置的输出文件manifest.json和jquery.js。

(3)在webpack.config.js里配置 告诉webpack不参与打包,同时哪些库的名字需要需改。同时需要使用add-asset-Html-webpack-plugin 插件,在打包后的html里自动引入这个第三方库。打包完成之后打开html文件会发现自动引入jquery.js文件

module.exports= {
    //入口起点
    entry:"./src/js/index.js",
    output:{
        filename: "js/built.js",
        path: resolve(__dirname,"build")
    },
    plugins:[
        new HtmlWbpackPlugin({
            template: './src/index.html'
        }),
        //告诉webpack哪些库不参与打包,同时使用的名字也得变化
        new webpack.DllReferencePlugin({
            manifest: resolve(__dirname,"/dll/manifest.json")
        }),
        // 将打包的文件输出并在html中自动引入该资源
        new addAssetHtmlWebpackPlugin({
            filepath: resolve(__dirname, "/dll/jquery.js")
        })
    ],
    mode: "production"
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值