内容简介:本篇文章我们一起来看看
本篇文章我们一起来看看 Webpack
的性能优化相关内容。在这之前,再简单介绍下 Webpack
的一些相关概念。
一、webpack 是什么?
webpack
是一种前端资源构建工具,它是一个 静态模块打包器 ( module bundler
)。在 webpack
中,前端的所有资源文件( javascript/json/css/img/less/...
)都会作为模块处理,当 webpack
处理应用程序时,它会递归地构建一个依赖关系图( dependency graph
),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle
。
(图片来自网络)
1、基本功能
- 代码转换 :将
TypeScript
编译成JavaScript
、SCSS
编译成CSS
等。 - 文件优化 :压缩
JavaScript
、CSS
、HTML
代码,压缩合并图片等。 - 代码分割 :提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
- 模块合并 :在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
- 自动刷新 :监听本地源代码的变化,自动重新构建、刷新浏览器。
- 代码校验 :在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
- 自动发布 :更新完代码后,自动构建出线上发布代码并传输给发布系统。
2、打包原理
- 识别入口文件,分析代码,获取模块依赖,并且将代码打包为浏览器可以识别的代码;
- 递归地构建一个依赖关系图(
dependency graph
),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle
。
二、核心概念
1、entry
入口( Entry
):指示 webpack
应该以哪个文件(模块)为入口起点开始打包,分析构建内部依赖图。
2、output
输出( Output
):指示 Webpack
打包后的资源( bundles
)输出到哪里去,以及如何命名。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。
3、module
模块,在 Webpack
里一切皆模块, 一个模块对应着一个文件 。 Webpack
会从配置的 entry
开始递归找出所有依赖的模块。
4、chunk
代码块,一个 chunk
由多个模块组合而成,用于 代码合并与分割 。
5、loader
Loader
是模块转换器,用于把模块原内容按照需求转换成新内容 。例如:我们会使用 Loader
让 Webpack
能够去处理那些非 JavaScript
文件( Webpack 自身只理解 JavaScript
)。然后就可以利用 webpack
的打包能力,对它们进行处理。
6、plugins
用于扩展 Webpack
功能,在 Webpack
构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
7、mode
模式( Mode
)指示 Webpack
使用相应模式的配置,是使用开发模式( development
)还是生产模式( production
)。
- 开发模式
- 方便于浏览器调试的工具;
- 可以快速地对增加的内容进行编译;
- 提供了更精确、更有用的运行时错误提示机制。
- 生产模式
- 自动压缩构建输出的文件
- 快速的运行时处理
- 不暴露源代码和源文件的路径
- 快速的静态资源输出
8、devServer
通过 devServer
启动的 Webpack
会开启监听模式,当发生变化时重新执行构建,然后通知 devServer
会让 Webpack
在构建出的 JavaScript
代码里注入一个代理客户端用于控制网页,网页和 devServer
之间通过 WebSocket
协议通信,以方便 devServer
主动向客户端发送命令。 devServer
在收到来自 Webpack
的文件变化通知时,通过注入的客户端控制网页刷新。
HTTP SourceMap
三、构建流程
Webpack
的运行流程是一个串行的过程,从启动到结束会依次执行以下流程 :
-
从
entry
里配置的module
开始递归解析entry
依赖的所有module
-
每找到一个
module
,就会根据配置的loader
去找对应的转换规则 -
对
module
进行转换后,再解析出当前module
依赖的module
-
这些模块会以
entry
为单位分组,一个entry
和其所有依赖的module
被分到一个Chunk
中 -
最后
webpack
会把所有Chunk
转换成文件输出 -
在整个流程中
webpack
会在恰当的时机执行plugin
里定义的逻辑
四、开发环境性能优化
- 优化打包构建速度
-
HMR
-
- 优化代码调试
-
source-map
-
1、HMR
HMR
( hot module replacement
,热模块替换 / 模块热替换)的作用是:当一个模块发生变化,只会重新打包这一个模块,而不是打包所有模块,极大提升构建速度。
- 对于样式文件,我们不用手动再启动
HMR
功能,因为style-loader
内部已经实现了; - 对于
JavaScript
文件,默认是不能使用HMR
功能的,需要修改JavaScript
代码,添加支持HMR
功能的代码;注意:HMR
功能对JavaScript
的处理,只能处理非入口JavaScript
文件的其他文件。
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/HMR
2、source-map
source-map
是一种 提供源代码到构建后代码映射技术,如果构建后代码出错了,通过映射可以追踪源代码错误。
内联和外部的区别:
- 外部生成了文件,内联没有
- 内联构建速度更快
用法区别:
-
source-map
:外部,可以显示错误代码准确信息和源代码的错误位置; -
inline-source-map
:内联,只生成一个内联source-map
,可以显示错误代码准确信息和源代码的错误位置; -
hidden-source-map
:外部,显示错误代码错误原因,但是没有错误位置,不能追踪源代码错误,只能提示到构建后代码的错误位置; -
eval-source-map
:内联,每一个文件都生成对应的source-map
,都在eval
,错误代码准确信息 和 源代码的错误位置,只是多了一个hash
值; -
nosources-source-map
:外部,显示错误代码准确信息,但是没有任何源代码信息; -
cheap-source-map
:外部,显示错误代码准确信息和源代码的错误位置,只能精确到行; -
cheap-module-source-map
:外部,错误代码准确信息 和 源代码的错误位置,module
会将loader
的source map
加入。
如何选择
-
开发环境:速度快(
eval>inline>cheap>...
),调试更友好(可以显示源代码信息)- 速度快:
eval-cheap-source-map
、eval-source-map
- 调试更友好:
source-map
、cheap-module-source-map
、cheap-source-map
- 平衡点:
eval-source-map
/eval-cheap-module-source-map
- 速度快:
-
生产环境:隐藏源代码?调试要不要更友好?
- 内联会让代码体积变大,所以在生产环境不用内联
-
nosources-source-map
全部隐藏 -
hidden-source-map
只隐藏源代码,会提示构建后代码错误信息 - 也需要平衡:
source-map
/cheap-module-source-map
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/source-map
五、生产环境性能优化
- 优化打包构建速度
oneOf babel externals dll
- 优化代码运行的性能
hash-chunkhash-contenthash tree shaking code splitting pwa
1、oneOf
每个不同类型的文件在 loader
转换时,都会被命中,遍历 module
中 rules
中所有 loader
,这也会影响性能。使用 oneOf
之后,当规则匹配时,只使用第一个匹配规则。
module.exports = { //... module: { rules: [ { test: /\.css$/, oneOf: [ { resourceQuery: /inline/, // foo.css?inline use: 'url-loader' }, { resourceQuery: /external/, // foo.css?external use: 'file-loader' } ] } ] } };
注意:
- 使用
oneOf
根据文件类型加载对应的loader
时,只要能匹配一个即可退出; - 对于同一类型文件,比如处理
js
,如果需要多个loader
,可以单独抽离js
处理,确保oneOf
里面一个文件类型对应一个loader
; - 可以配置
enforce: 'pre',
指定优先执行。
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/oneOf
2、缓存
babel 缓存
因为在生产环境中不能使用 HMR
,要想也达到 HMR
的效果,在更改一个模块之后,不至于让别的模块也重新打包,可以使用 babel
缓存。
use: { loader: 'babel-loader', options: { cacheDirectory: true } }
这样在第二次构建的时候,会读取之前的缓存,加快打包速度。
文件资源缓存
一般情况下,对于前端静态资源,浏览器访问的时候希望资源都能够进行缓存,当第二次及以后进入页面的时候,页面就可以直接使用缓存资源,这样的话,页面打开速度很快,提高了用户体验同时也节省了带宽资源。而其中最为常见的一种最大化利用缓存的形式就是为静态资源加上 hash
,使用一个不会重复的标识符来达到资源可以永久缓存的目的。
在 Webpack
打包的时候,我们可以给打包的文件名加上一个 hash
值,有两种方式:
-
hash
:每次wepack
构建时会生成一个唯一的hash
值- 问题:因为
js
和css
同时使用一个hash
值;如果重新打包,会导致所有缓存失效(可能我却只改动一个文件)
- 问题:因为
-
chunkhash
:根据chunk
生成的hash
值。如果打包来源于同一个chunk
,那么hash
值就一样- 如何
css
是在js
中被引入的,所以同属于一个chunk
,js
和css
的hash
值还是一样的。
- 如何
-
contenthash
:根据文件的内容生成hash
值。不同文件hash
值一定不一样,这会让代码上线运行缓存更好使用(上线代码性能优化)。
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/cache
3、多进程打包
thread-loader
一般是给 loader
用的。下面是一个在 babel-loader
中使用多进程打包的例子:
{ test: /\.js$/, exclude: /node_modules/, use: [ { loader: 'thread-loader', options: { workers: 2 // 进程2个 } }, { loader: 'babel-loader', options: { // 预设:指示babel做怎么样的兼容性处理 presets: [ [ '@babel/preset-env', { // 按需加载 useBuiltIns: 'usage', // 指定core-js版本 corejs: { version: 3 }, // 指定兼容性做到哪个版本浏览器 targets: { chrome: '60', firefox: '60', ie: '9', safari: '10', edge: '17' } } ] ], // 开启babel缓存 // 第二次构建时,会读取之前的缓存 cacheDirectory: true } } ] },
但是要注意,启动进程是需要时间的,进程间的通信也是需要时间的,如果本来构建时间就很快,这种情况下,不需要使用 thread-loader
。
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/thread-loader
4、externals
externals
的作用是防止将某一些包打包到我们的输出中。假设我们的项目中使用了 JQuery
,我们不希望将它打包到输出文件中,而是使用外部的 CDN
链接。
// 忽略打包的文件 externals: { // 拒绝jQuery被打包进来 // 忽略库名 -- npm 包名 jquery: 'jQuery' }
注意,当我们忽略打包文件需要在 HTML
中使用 script
标签将其引入进来的。
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/externals
5、dll
使用 dll
技术,可以对某些库(一般是第三方库: jquery
、 react
、 vue
…)进行单独打包。
基本过程
-
第一次的时候把请求的内容存储起来存储在映射表中;
-
再次请求时,先从映射表中找请求的内容,看是否有缓存,有则加载缓存(类似浏览器的缓存策略命中缓存),没有就正常打包。
缺点:配置很麻烦。
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/dll
5、tree shaking
tree shaking
的作用是去除无用代码,让代码的体积更小。开启 production
环境后,默认就会启动 tree shaking
。
但是对于 JavaScript
文件来说,还有一个前提条件:必须使用 ES6
模块化。
最后还有一个问题需要注意,可能由于 Webpack
版本的原因,会把一些 css
资源给 shaking
掉,所以最好在 package.json
中配置一下:
"sideEffects": [ "*.css", "*.less" ]
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/tree-shaking
6、code splitting
在最开始使用 Webpack
的时候,都是将所有的 js
文件全部打包到一个 build.js
文件中,但是在大型项目中, build.js
可能过大, 导致页面加载时间过长。这个时候就需要 code splitting
。
code splitting
就是将文件分割成块( chunk
), 我们可以定义一些分割点( split point
),根据这些分割点对文件进行分块,并实现按需加载。
大致有三种方式来配置:
- 添加多个入口
entry
- 缺点:不好维护,每次都需要新添加入口配置
- 使用
splitChunks
配置- 可以将
node_modules
中代码单独打包一个chunk
最终输出 - 它也会自动分析多入口
chunk
中,有没有公共的文件,如果有则会打包成单独一个chunk
- 可以将
- 通过
js
代码,让某个文件被单独打包成一个chunk
-
import
动态导入语法:能将某个文件单独打包
-
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/code-splitting
7、懒加载和预加载
-
懒加载(延迟加载):当文件需要使用时才加载。
document.getElementById('btn').onclick = function() { import(/* webpackChunkName: 'test' */'./test').then(({ mul }) => { console.log(mul(4, 5)); }); };
-
预加载
prefetch
:会在使用之前,提前加载js
文件,等其他资源加载完毕,浏览器空闲了,再偷偷加载资源(兼容性比较差)。document.getElementById('btn').onclick = function() { import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => { console.log(mul(4, 5)); }); };
https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/lazy-loading
8、PWA
渐进式网络开发应用程序(离线可访问),使用 workbox-webpack-plugin
插件来实现。有两个步骤:
1、在 Webpack
中配置:
new WorkboxWebpackPlugin.GenerateSW({ /* 作用: 1. 帮助serviceworker快速启动 2. 删除旧的 serviceworker,生成一个 serviceworker 配置文件 */ clientsClaim: true, skipWaiting: true })
2、注册 serviceWorker
,并处理兼容性问题:
if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker .register('/service-worker.js') .then(() => { console.log('sw注册成功了~'); }) .catch(() => { console.log('sw注册失败了~'); }); }); }
另外, serviceWorker
必须在服务器上。
例子: https://github.com/IDeepspace/webpack-performance-enhancement-example/tree/master/pwa
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 性能优化第一课:性能指标
- 【前端性能优化】vue性能优化
- Golang 性能测试 (2) 性能分析
- 【前端性能优化】02--vue性能优化
- Java性能 -- 性能调优标准
- Java性能 -- 性能调优策略
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web前端开发最佳实践
党建 / 机械工业出版社 / 2015-1 / 59.00元
本书贴近Web前端标准来介绍前端开发相关最佳实践,目的在于让前端开发工程师提高编写代码的质量,重视代码的可维护性和执行性能,让初级工程师从入门开始就养成一个良好的编码习惯。本书总共分五个部分13章,第一部分包括第1章和第2章,介绍前端开发的基本范畴和现状,并综合介绍前端开发的一些最佳实践;第二部分为第3-5章,讲解HTML相关的最佳实践,并简单介绍HTML5中新标签的使用;第三部分为第6-8章,介......一起来看看 《Web前端开发最佳实践》 这本书的介绍吧!