-
速度分析
- webpack有时侯打包很慢,而我们在项目中可能用了很多的
plugin
和loader
,想知道那个环节慢,下面这个插件可以计算plugin
和loader
的耗时
yarn add -D speed-measure-webpack-plugin
- webpack有时侯打包很慢,而我们在项目中可能用了很多的
-
体积分析
- 打包后的体积优化是一个很重要的点,譬如引入的一些第三方组件库过大,这是就要考虑是否需要寻找替代品了。这里可以采用
webpack-bundle-analyzer
,它可以用交互式可缩放树形图显示webpack
输出文件的大小。
yarn add -D webpack-bundle-analyzer
- 安装完后,配置
webpcak.config.js
,配置好后,yarn run dev
启动后,会默认起一个端口号为8888的本地服务器。这样就能看到每个模块的的文件大小,进行针对性的优化。
- 打包后的体积优化是一个很重要的点,譬如引入的一些第三方组件库过大,这是就要考虑是否需要寻找替代品了。这里可以采用
-
多线程/多实例构建
-
大家都知道
webpack
是运行在node
环境中,二node
,是单线程的。webpack
的打包过程是io
密集和计算机密集型操作,如果能fork
,多个进程并行处理各个任务,将会有效缩短构建时间。一般使用较多的两个是thread-loader
和HappyPack
。- thread-loader(官方推荐)
yarn add -D thread-loader
thread-loader
会将你的loader
放置在一个worker
池里面运行,以达到多线程构建。
注:放置在这个thread-loader
之后的loader会在一个单独的worker
池(worker pool
)中运行。- HappyPack(该插件作者不在维护)
yarn add - D happypack
HappyPack可以让Webpack同一时间处理多个任务,发挥多喝CPU的能力,将任务分解给多个子进程去并发的执行,子进程处理完成后,再把结果发送给主进程。通过多进程模型,来加速代码的构建。
-
-
多进程并行压缩代码
- 通常我们在开发环境,代码构建时间比较快,而构建用于发布到线上的代码时会添加压缩代码这一流程,则会道济计算量大耗时多
-
webpack
默认提供了UglifyJS
插件来压缩JS
代码,但是使用的是单线程压缩代码,也就是说多个js文件需要被压缩,它需要一个个文件进行压缩。所以说在正式环境打包压缩代码速度非常慢(因为压缩JS
代码需要先把代码解析成用Object
抽象表示的AST
语法树,再应用各种规则分析处理AST
,导致这个过程耗时非常大)。所以我们要对压缩代码这里一步骤进行优化,常用的做法是多进程并行压缩,目前主要有三种主流的压缩方案。paralle-uglify-plugin
uglifyjs-webpack-plugin
-
terser-webpack-plugin
-
parallel-uglify-plugin
上面介绍的HappyPack
的思想是使用多个子进程去解析和编译JS
,CSS
等,这样就可以并行处理多个子任务,多个子任务完成后,再将结果发到主进程中,有了这个思想后,ParallelUglifyPlugin
插件就产生了。当
webpack
有多个JS
文件需要输出和压缩时,原来会使用UglifyJS
去一个个压缩并且输出,而ParallelUglifyPlugin
插件则会开启多个子进程,把对多个文件压缩的工作分给多个子进程去完成,但是每个子进程还是通过UglifyJS
去压缩代码。并行压缩可以显著的提升效率yarn add -D webpack-parallel-uglify-plugin
注:
webpack-parallel-uglify-plugin
已不再维护,这里不推荐使用 -
uglifyjs-webpack-plugin
yarn add -D uglifyjs-webpack-plugin
注:其实它和上面的
parallel-uglify-plugin
类似,也可通过设置parallel: true
开启多进程压缩。 terser-webpack-plugin
不知道你有没有发现:webpack4 已经默认支持 ES6语法的压缩。而这离不开terser-webpack-plugin.
yarn add -D uglifyjs-webpack-plugin
注:其实它和上面的
parallel-uglify-plugin
类似,也可通过设置parallel: true
开启多进程压缩。 -
-
预编译资源模块
- 什么是预编译资源模块?
在使用webpack
进行打包时候,对于依赖的第三方库,比如vue
,vuex
等这些不会修改的依赖,我们可以让它和我们自己编写的代码分开打包,这样做的好处是每次更改我本地代码的文件的时候,webpack
只需要打包我项目本身的文件代码,而不会再去编译第三方库。那么第三方库在第一次打包的时候只打包一次,以后只要我们不升级第三方包的时候,那么webpack
就不会对这些库去打包,这样的可以快速的提高打包的速度。其实也就是预编译资源模块。webpack
中,我们可以结合DllPlugin
和DllReferencePlugin
插件来实现。 - DllPlugin是什么?
它能把第三方库代码分离开,并且每次文件更改的时候,它只会打包该项目自身的代码。所以打包速度会更快。DLLPlugin
插件是在一个额外独立的webpack
设置中创建一个只有dll
的bundle
,也就是说我们在项目根目录下除了有webpack.config.js
,还会新建一个webpack.dll.js
文件。webpack.dll.js
的作用是把所有的第三方库依赖打包到一个bundle
的dll
文件里面,还会生成一个名为manifest.json
文件。该manifest.json
的作用是用来让DllReferencePlugin
映射到相关的依赖上去的。 - DllReferencePlugin又是什么?
这个插件是在webpack.config.js
中使用的,该插件的作用是把刚刚在webpack.dll.js
中打包生成的dll
文件引用到需要的预编译的依赖上来。什么意思呢?就是说在webpack.dll.js
中打包后比如会生成vendor.dll.js
文件和vendor-manifest.json
文件,vendor.dll.js
文件包含了所有的第三方库文件,vendor-manifest.json
文件会包含所有库代码的一个索引,当在使用webpack.config.js
文件打包DllReferencePlugin
插件的时候,会使用该DllReferencePlugin
插件读取vendor-manifest.json
文件,看看是否有该第三方库。vendor-manifest.json
文件就是一个第三方库的映射而已。 - 这么在项目中使用
- 上面说了这么多,主要是为了方便大家对于预编译资源模块和
DllPlugin
和DllReferencePlugin
插件作用的理解。先来看下完成的项目目录结构,主要在两块配置,分别是webpack.dll.js
和webpack.config.js
(对应这里我是webpack.base.js
)
- webpack.dll.js
const path = require('path'); const webpack = require('webpack'); module.exports = { mode: 'production', entry: { vendors: ['lodash', 'jquery'], react: ['react', 'react-dom'] }, output: { filename: '[name].dll.js', path: path.resolve(__dirname, './dll'), library: '[name]' }, plugins: [ new webpack.DllPlugin({ name: '[name]', path: path.resolve(__dirname, './dll/[name].manifest.json') }) ] }
这里我拆了两部分:
vendors
(存放了lodash
、jquery
等)和react
(存放了react
相关的库,react
、react-dom
等)- webpack.config.js(对应我这里就是webpack.base.js)
const path = require("path"); const fs = require('fs'); // ... const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); const webpack = require('webpack'); const plugins = [ // ... ]; const files = fs.readdirSync(path.resolve(__dirname, './dll')); files.forEach(file => { if(/.*\.dll.js/.test(file)) { plugins.push(new AddAssetHtmlWebpackPlugin({ filepath: path.resolve(__dirname, './dll', file) })) } if(/.*\.manifest.json/.test(file)) { plugins.push(new webpack.DllReferencePlugin({ manifest: path.resolve(__dirname, './dll', file) })) } }) module.exports = { entry: { main: "./src/index.js" }, module: { rules: [] }, plugins, output: { // publicPath: "./", path: path.resolve(__dirname, "dist") } }
由于上面我把第三方库做了一个拆分,所以对应生成也就会是多个文件,这里读取了一下文件,做了一层遍历。最后在
package.json
里面再添加一条脚本就可以了:"scripts": { "build:dll": "webpack --config ./webpack.dll.js", },
运行
yarn build:dll
就会生成本小节开头贴的那张项目结构图了~ - 什么是预编译资源模块?
-
利用缓存提升二次构建速度
- 一般来说,对于静态资源,我们都希望浏览器能够进行缓存,那样以后进入页面就可以直接使用缓存资源,页面打开速度会显著加快,既提高了用户的体验也节省了宽带资源。当然浏览器缓存方法有很多种,这里只简单讨论下在
webpack
中如何利用缓存来提升二次构建速度。在webpack
中利用缓存一般有以下几种思路:-
babel-loader
开启缓存 - 使用
cache-loader
- 使用
hard-source-webpack-plugin
-
- babel-loader
-
babel-loader
在执行的时候,可能会产生一些运行期间重复的公共文件,造成代码体积冗余,同时也会减慢编译效率。可以加上cacheDirectory
参数开启缓存:
{ test: /\.js$/, exclude: /node_modules/, use: [{ loader: "babel-loader", options: { cacheDirectory: true } }], },
-
- cache-loader
- 在一些性能开销较大的
loader
之前添加此loader
,以将结果缓存到磁盘里。
yarn add -D cache-loader
- 使用
cache-loader
的配置很简单,放在其他loader
之前即可。修改Webpack
的配置如下:
注:请注意,保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的// webpack.config.js module.exports = { module: { rules: [ { test: /\.ext$/, use: [ 'cache-loader', ...loaders ], include: path.resolve('src') } ] } }
loader
使用此loader
。 - 在一些性能开销较大的
- hard-source-webpack-plugin
-
HardSourceWebpackPlugin
为模块提供了中间缓存,缓存默认的存放路径是:node_modules/.cache/hard-source
。配置hard-source-webpack-plugin
后,首次构建时间并不会有太大的变化,但是从第二次开始,构建时间大约可以减少 80%左右。yarn add -D hard-source-webpack-plugin
- 使用
// webpack.config.js var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); module.exports = { entry: // ... output: // ... plugins: [ new HardSourceWebpackPlugin() ] }
webpack5
中会内置hard-source-webpack-plugin
。 -
- 一般来说,对于静态资源,我们都希望浏览器能够进行缓存,那样以后进入页面就可以直接使用缓存资源,页面打开速度会显著加快,既提高了用户的体验也节省了宽带资源。当然浏览器缓存方法有很多种,这里只简单讨论下在
-
缩小构建目标
- 有时候我们的项目中会用到很多模块,但有些模块其实是不需要被解析的。这时我们就可以通过缩小构建目标或者减少文件搜索范围的方式来对构建做适当的优化。
- 主要是exclude 与 include的使用:
- exclude: 不需要被解析的模块
- include: 需要被解析的模块
这里const path = require('path'); module.exports = { ... module: { rules: [ { test: /\.js$/, exclude: /node_modules/, // include: path.resolve('src'), use: ['babel-loader'] } ] }
babel-loader
就会排除对node_modules
下对应js
的解析,提升构建速度。
-
减少文件搜索范围
- 这个主要是resolve相关的配置,用来设置模块如何被解析。通过resolve的配置,可以帮助Webpack快速查找依赖,也可以替换对应的依赖。
- resolve.modules:告诉
webpack
解析模块时应该搜索的目录 - resolve.mainFields:当从
npm
包中导入模块时(例如,import * as React from 'react'
),此选项将 决定在package.json
中使用哪个字段导入模块。根据webpack
配置中指定的target
不同,默认值也会有所不同 - resolve.mainFiles:解析目录时要使用的文件名,默认是
index
- resolve.extensions:文件扩展名
// webpack.config.js const path = require('path'); module.exports = { ... resolve: { alias: { react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js') }, //直接指定react搜索模块,不设置默认会一层层的搜寻 modules: [path.resolve(__dirname, 'node_modules')], //限定模块路径 extensions: ['.js'], //限定文件扩展名 mainFields: ['main'] //限定模块入口文件名 } ... }
- resolve.modules:告诉
- 这个主要是resolve相关的配置,用来设置模块如何被解析。通过resolve的配置,可以帮助Webpack快速查找依赖,也可以替换对应的依赖。
-
动态 Polyfill 服务
- 什么是
babel-polyfill
?
babel
只负责语法转换,比如将ES6
的语法转换成ES5
。但如果有些对象、方法,浏览器本身不支持,比如:- 全局对象:
Promise
、WeakMap
等。 - 全局静态函数:
Array.from
、Object.assign
等。 - 实例方法:比如
Array.prototype.includes
等。
此时,需要引入babel-polyfill
来模拟实现这些对象、方法。这种一般也称为垫片
。
- 全局对象:
- 怎么使用
babel-polyfill
?- 使用也非常简单,在webpack.config.js文件作如下配置就可以了:
module.exports = { entry: ["@babel/polyfill", "./app/js"], };
- 使用也非常简单,在webpack.config.js文件作如下配置就可以了:
- 动态
Polyfill
服务
每次打开页面,浏览器都会向Polyfill Service发送请求,Polyfill Service识别 User Agent,下发不同的 Polyfill,做到按需加载Polyfill的效果。 - 怎么使用动态
Polyfill
服务?//访问url,根据User Agent 直接返回浏览器所需的 polyfills https://polyfill.io/v3/polyfill.min.js
- 什么是
-
Scope Hoisting
什么是Scope Hoisting?
Scope hoisting
直译过来就是「作用域提升」。熟悉JavaScript
都应该知道「函数提升」和「变量提升」,JavaScript
会把函数和变量声明提升到当前作用域的顶部。「作用域提升」也类似于此,webpack
会把引入的js
文件“提升到”它的引入者顶部。Scope Hoisting
可以让Webpack
打包出来的代码文件更小、运行的更快。-
启用
Scope Hoisting
- 要在
Webpack
中使用Scope Hoisting
非常简单,因为这是Webpack
内置的功能,只需要配置一个插件,相关代码如下:
// webpack.config.js const webpack = require('webpack') module.exports = mode => { if (mode === 'production') { return {} } return { devtool: 'source-map', plugins: [new webpack.optimize.ModuleConcatenationPlugin()], } }
- 好处
- 代码体积更小,因为函数申明语句会产生大量代码;
- 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小。
Scope Hoisting
的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。因此只有那些被引用了一次的模块才能被合并。注意:由于
Scope Hoisting
需要分析出模块之间的依赖关系,因此源码必须采用ES6
模块化语句,不然它将无法生效。 - 要在
文章转自:https://mp.weixin.qq.com/s?__biz=MzU2MzM2NzU0NA==&mid=2247486539&idx=1&sn=7484798f6c42611c1e81447fc5a51739&chksm=fc5a175ccb2d9e4aa4f33f049bee8fa18e5a1c27ed315be2911ca5c8a1802760701b818c500e&mpshare=1&scene=1&srcid=0915Ld1r2gSXJFB9I7tNdaa5&sharer_sharetime=1600137668291&sharer_shareid=a8f4498c49789458cc5918ae43d0aed2&key=7f54b3443b683033173cffe97dc7047f2921f7d585f87442c5b17eb942738599ea5f40012f9783eacf8c4f1a5789472fca6fa37449cd0fcbeb9dbd7bd6c184e8c6a24f652623e6d12b6b414b310815b80f59947220ee0919dbd2ec4e0a9f3b463e09cebee1ab37d6d7ccbc29b4def16a8854304e29ba208adbd3b98dbe475920&ascene=1&uin=MjIxNzU2Mjg4NA%3D%3D&devicetype=Windows+10+x64&version=62090538&lang=zh_CN&exportkey=A4LNOZ5CDhlUAgdwZU%2Bg4Vc%3D&pass_ticket=sU%2F%2FRSgUTGKQVQgrUbMNdKmZ6urS7BLB4AhRGceH6Mq8KLX6W06k8Zdvf2hhC9RU&wx_header=0