前言
Webpack 是 OneAPM 前端技术栈中非常重要的一部分。它非常好用,假设你还不了解它,建议你阅读这篇 Webpack 入门指迷 ,在 OneAPM 我们用它完毕静态资源打包。ES6 代码的转换 ,React 组件的组织等,在接下来的日子里,我们将通过一系列文章和业界分享我们在使用 Webpack 过程中关于性能方面的经验。
作为系列文章的第一篇。我们会重点介绍 Webpack 中的 resolve.alias
,也就是请求重定向。
只是请注意 Webpack 里的请求是对模块的依赖,也就是一个 require
语句,而不是一个 HTTP 请求。
必要的准备
- 须要你有一定的 Node.js 基础
- 电脑上装有最新版的 Webpack (npm install webpack -g)
- 了解 Webpack 配置文件的格式
样例:本地时钟
要实现的功能非常easy。就是在页面上用中文显示当前时间,须要用到 moment 这个库,这个库封装了非常多和日期相关的函数,并且自带了国际化的支持。
新建一个 Node.js 项目
使用 npm init
初始化你的项目,然后通过npm install moment -D
加上 moment
的开发人员依赖。
新建一个entry.js
作为入口文件,当然你也能够用 app.js
这种名字,仅仅是大部分的 Webpack 演示样例都是用的是 entry.js
。
var moment = require('moment');
document.write(moment().locale('zh-cn').format('LLLL'));
新建一个页面index.html
, 引用 bundle.js
:
<body>
<h5>当前时间:</h5>
<script src="dist/bundle.js"></script>
</body>
此时的文件文件夹看起来是这种:
index.html
package.json
entry.js
node_modules/moment
到眼下为止 bundle.js
这个文件还不存在,只是别着急,接下来的工作就交给 Webpack 来完毕。
index.html ------------------------+
package.json |
+--> <Clock App>
entry.js --------+ |
+-->bundle.js+--+
node_modules/moment-+
如图,Webpack 会把 entry.js
和 moment
模块一起打包成一个 bundle.js
文件,和 index.html
一起构成了我们的 Clock App。怎么样,是不是已经听到 Clock App 滴答作响了?
使用 webpack 打包代码
在命令行执行:
webpack --entry ./entry.js --output-path dist --output-file bundle.js
你会看到相似以下的输出结果:
Hash: bf9007fb1e0cb30e3ef7
Version: webpack 1.10.0
Time: 650ms
Asset Size Chunks Chunk Names
bundle.js 378 kB 0 [emitted] null
[0] ./entry.js 125 bytes {0} [built]
+ 86 hidden modules
能够看到,耗时 650ms,这么慢着实让人意外,一定要想办法提高“新一代神器”速度;还有一方面,最后一行的 + 86 hidden modules 非常让人怀疑:明明是一个简单的 Clock App,怎么会有这么多的依赖。
怎样高速定位 Webpack 速度慢的原因
再一次。在命令行输入:
webpack --entry ./entry.js --output-path dist --output-file bundle.js
--colors
--profile
--display-modules
只是这次新添加了三个參数,这三个參数的含义各自是:
--colors
输出结果带彩色,比方:会用红色显示耗时较长的步骤--profile
输出性能数据,能够看到每一步的耗时--display-modules
默认情况下node_modules
下的模块会被隐藏,加上这个參数能够显示这些被隐藏的模块
这次命令行的结果已经非常有參考价值,能够帮助我们定位耗时比較长的步骤
Hash: bf9007fb1e0cb30e3ef7
Version: webpack 1.10.0
Time: 650ms
Asset Size Chunks Chunk Names
bundle.js 378 kB 0 [emitted] null
[0] ./entry.js 125 bytes {0} [built]
factory:11ms building:8ms = 19ms
[1] ../~/moment/moment.js 102 kB {0} [built]
[0] 19ms -> factory:7ms building:141ms = 167ms
[2] (webpack)/buildin/module.js 251 bytes {0} [built]
[0] 19ms -> [1] 148ms -> factory:132ms building:159ms = 458ms
[3] ../~/moment/locale ^./.*$ 2.01 kB {0} [optional] [built]
[0] 19ms -> [1] 148ms -> factory:6ms building:10ms dependencies:113ms = 296ms
[4] ../~/moment/locale/af.js 2.57 kB {0} [optional] [built]
[0] 19ms -> [1] 148ms -> [3] 16ms -> factory:52ms building:65ms dependencies:138ms = 438ms
..... 广告切割线,Node.js project师简历请发 nodejs@oneapm.com ......
[85] ../~/moment/locale/zh-cn.js 4.31 kB {0} [optional] [built]
[0] 22ms -> [1] 162ms -> [3] 18ms -> factory:125ms building:145ms dependencies:22ms = 494ms
[86] ../~/moment/locale/zh-tw.js 3.07 kB {0} [optional] [built]
[0] 22ms -> [1] 162ms -> [3] 18ms -> factory:126ms building:146ms dependencies:21ms = 495ms
从命令行的结果里能够看到从 Request[4] 到 Request[86] 都是在解析 moment.js
附带的大量本地化文件。所以我们遇到的速度慢的问题事实上是由 moment
引起的。
假设你想知道为什么 Webpack 会载入这么多的模块。能够參考这篇文章 Why Enormous Locales During Webpack MomentJS
我们再来看看 entry.js
代码的第一行,标准的 CommonJS
写法:
var moment = require('moment');
也就是说。请求的是 moment
的源代码。实际上。通过 NPM 安装moment
的时候会同一时候安装 moment
的源代码和压缩后的代码,试验证明以下这种写法也是可行的:
var moment = require('moment/min/moment-with-locales.min.js');
仅仅只是这样改,可读性会有所下降,并且每个用到moment
的地方都得这么写。另外,假设相同的问题出如今第三方模块中,改动别人代码就不那么方便了。
以下来看看用 Webpack 怎么解决问题。
在 Webpack 中使用别名
别名(resolve.alias
) 是 Webpack 的一个配置项,它的作用是把用户的一个请求重定向到还有一个路径,比如通过改动 webpack.config.js
配置文件,添加:
resolve: {
alias: {
moment: "moment/min/moment-with-locales.min.js"
}
}
这样待打包的脚本中的 require('moment')
; 事实上就等价于 require('moment/min/moment-with-locales.min.js');
。通过别名的使用在本例中能够降低差点儿一半的时间。
Hash: cdea65709b783ee0741a
Version: webpack 1.10.0
Time: 320ms
Asset Size Chunks Chunk Names
bundle.js 148 kB 0 [emitted] main
[0] ./entry.js 125 bytes {0} [built]
factory:11ms building:9ms = 20ms
[1] ../~/moment/min/moment-with-locales.min.js 146 kB {0} [built] [1 warning]
[0] 20ms -> factory:8ms building:263ms = 291ms
[2] (webpack)/buildin/module.js 251 bytes {0} [built]
[0] 20ms -> [1] 271ms -> factory:3ms building:1ms = 295ms
WARNING in ../~/moment/min/moment-with-locales.min.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./locale in */webpack_performance/node_modules/moment/min
@ ../~/moment/min/moment-with-locales.min.js 1:2731-2753
在 Webpack
中忽略对已知文件的解析
module.noParse
是 webpack
的还有一个非常实用的配置项,假设你 确定一个模块中没有其他新的依赖 就能够配置这项,webpack
将不再扫描这个文件里的依赖。
module: {
noParse: [/moment-with-locales/]
}
这样改动。再结合前面重命名的样例,更新后的流程是:
webpack
检查到entry.js
文件对moment
的请求;- 请求被
alias
重定向,转而请求moment/min/moment-with-locales.min.js;
noParse
规则中的/moment-with-locales/
一条生效,所以webpack
就直接把依赖打包进了bundle.js
。
Hash: 907880ed7638b4ed70b9
Version: webpack 1.10.0
Time: 76ms
Asset Size Chunks Chunk Names
bundle.js 147 kB 0 [emitted] main
[0] ./entry.js 125 bytes {0} [built]
factory:13ms building:13ms = 26ms
[1] ../~/moment/min/moment-with-locales.min.js 146 kB {0} [built]
[0] 26ms -> factory:13ms building:5ms = 44ms
时间进一步被压缩,仅仅须要 76ms,比前一步还降低了 75%。
在 Webpack 中使用公用 CDN
Webpack 是如此的强大。用其打包的脚本能够执行在多种环境下。Web 环境仅仅是其默认的一种,也是最经常使用的一种。考虑到 Web 上有非常多的公用 CDN 服务。那么 怎么将 Webpack 和公用的 CDN 结合使用呢?方法是使用 externals
声明一个外部依赖。
externals: {
moment: true
}
当然了 HTML 代码里须要加上一行
<script src="//apps.bdimg.com/libs/moment/2.8.3/moment-with-locales.min.js"></script>
这次打包。结果仅仅用了 49 ms。差点儿达到了极限。
总结
本文结合本地时钟的样例,展示了定位 Webpack 性能问题的步骤,以及所须要的两个參数 :--display-modules
和 --profile
。然后,重点介绍了 resolve.alias
即利用别名做重定向的方法和场景。在此基础上,配合module.noParse
忽略某些模块的解析能够进一步加高速度。最后介绍了用 externals
定义外部依赖方法来使用公用 CDN。
关于
本文相关的源代码在: https://github.com/wyvernnot/webpack_performance/tree/master/moment-example;
本文系OneAPMproject师原创文章。
OneAPM是中国基础软件领域的新兴领军企业,能帮助企业用户和开发人员轻松实现:缓慢的程序代码和SQL语句的实时抓取。想阅读很多其他技术文章。请訪问OneAPM官方技术博客。