淘先锋技术网

首页 1 2 3 4 5 6 7

前言

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.jsmoment模块一起打包成一个 bundle.js 文件,和 index.html 一起构成了我们的 Clock App。怎么样,是不是已经听到 Clock App 滴答作响了?

使用 webpack 打包代码

在命令行执行:

webpack --entry ./entry.js --output-path dist --output-file bundle.js

你会看到相似以下的输出结果:

Hash: bf9007fb1e0cb30e3ef7
Version: webpack 
Time: ms
    Asset    Size  Chunks             Chunk Names
bundle.js   kB         [emitted]  null
   [] ./entry.js  bytes {} [built]
    +  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 
Time: ms
    Asset    Size  Chunks             Chunk Names
bundle.js   kB         [emitted]  null
   [] ./entry.js  bytes {} [built]
       factory:ms building:ms = ms
   [] ../~/moment/moment.js  kB {} [built]
       [] ms -> factory:ms building:ms = ms
   [] (webpack)/buildin/module.js  bytes {} [built]
       [] ms -> [] ms -> factory:ms building:ms = ms
   [] ../~/moment/locale ^\.\/.*$  kB {} [optional] [built]
       [] ms -> [] ms -> factory:ms building:ms dependencies:ms = ms
   [] ../~/moment/locale/af.js  kB {} [optional] [built]
       [] ms -> [] ms -> [] ms -> factory:ms building:ms dependencies:ms = ms
                  ..... 广告切割线,Node.js project师简历请发 nodejs@oneapm.com ......
   [] ../~/moment/locale/zh-cn.js  kB {} [optional] [built]
        [] ms -> [] ms -> [] ms -> factory:ms building:ms dependencies:ms = ms
   [] ../~/moment/locale/zh-tw.js  kB {} [optional] [built]
        [] ms -> [] ms -> [] ms -> factory:ms building:ms dependencies:ms = ms

从命令行的结果里能够看到从 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 
Time: ms
    Asset    Size  Chunks             Chunk Names
bundle.js   kB         [emitted]  main
   [] ./entry.js  bytes {} [built]
       factory:ms building:ms = ms
   [] ../~/moment/min/moment-with-locales.min.js  kB {} [built] [ warning]
       [] ms -> factory:ms building:ms = ms
   [] (webpack)/buildin/module.js  bytes {} [built]
       [] ms -> [] ms -> factory:ms building:ms = ms

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 :-

Webpack中忽略对已知文件的解析

module.noParsewebpack 的还有一个非常实用的配置项,假设你 确定一个模块中没有其他新的依赖 就能够配置这项,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: ed7638b4ed70b9
Version: webpack 
Time: ms
    Asset    Size  Chunks             Chunk Names
bundle.js   kB         [emitted]  main
   [] ./entry.js  bytes {} [built]
       factory:ms building:ms = ms
   [] ../~/moment/min/moment-with-locales.min.js  kB {} [built]
       [] ms -> factory:ms building:ms = ms

时间进一步被压缩,仅仅须要 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官方技术博客