Webpack 性能优化 (一)(使用别名做重定向)
2015-07-09 23:03
821 查看
前言
Webpack 是 OneAPM 前端技术栈中很重要的一部分,它非常好用,如果你还不了解它,建议你阅读这篇 Webpack 入门指迷 ,在 OneAPM 我们用它完成静态资源打包,ES6 代码的转换 ,React 组件的组织等,在接下来的日子里,我们将通过一系列文章和业界分享我们在使用 Webpack 过程中关于性能方面的经验。作为系列文章的第一篇,我们会重点介绍 Webpack 中的
resolve.alias,也就是请求重定向。不过请注意 Webpack 里的请求是对模块的依赖,也就是一个
require语句,而不是一个 HTTP 请求。
必要的准备
需要你有一定的 Node.js 基础电脑上装有最新版的 Webpack (npm install webpack -g)
了解 Webpack 配置文件的格式
例子:本地时钟
要实现的功能很简单,就是在页面上用中文显示当前时间,需要用到 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 工程师简历请发 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;本文系OneAPM工程师原创文章。OneAPM是中国基础软件领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和SQL语句的实时抓取。想阅读更多技术文章,请访问OneAPM官方技术博客。
相关文章推荐
- 使用ruby部署工具mina快速部署nodejs应用教程
- Google官方支持的NodeJS访问API,提供后台登录授权
- nodejs教程之环境安装及运行
- nodejs中的fiber(纤程)库详解
- 基于NodeJS的前后端分离的思考与实践(五)多终端适配
- 基于NodeJS的前后端分离的思考与实践(二)模版探索
- 我的NodeJs学习小结(一)
- nodejs中实现sleep功能实例
- Nodejs异步回调的优雅处理方法
- Windows系统下使用Sublime搭建nodejs环境
- nodejs实现获取某宝商品分类
- nodejs简单实现中英文翻译
- Node.js插件的正确编写方式
- 使用upstart把nodejs应用封装为系统服务实例
- NodeJS Web应用监听sock文件实例
- Nodejs学习笔记之测试驱动
- Nodejs学习笔记之Stream模块
- 如何正确使用Nodejs 的 c++ module 链接到 OpenSSL
- Nodejs为什么选择javascript为载体语言
- Nodejs极简入门教程(一):模块机制