Webpack 2 中一些常见的优化措施

NO然后 2年前
   <p><a href="/misc/goto?guid=4959732858242557553" rel="nofollow,noindex">Webpack</a> 是一款强大的前端构建工具, 社区对其介绍的相关文章已经很多了, 本文不再赘述. 基于 Webpack 2, 本文是对我在搭建团队前端脚手架的过程中, 搜罗的 Webpack 2 常见的优化措施的一个总结.</p>    <p>如果你还不了解 Webpack 2, 可以先看下 <a href="/misc/goto?guid=4959749507153188745" rel="nofollow,noindex">Webpack 2 快速入门</a></p>    <h3>1. 分离第三方依赖</h3>    <p>在 <strong>开发环境</strong> 下, 通常会采取 HMR 模式来提高开发效率. 但一般情况下, 我们只会更改自身的业务文件, 不会去更改第三方的依赖, 但 webpack 在 rebuild 的时候, 依旧会 build 所有的依赖. 因而, 为减少 rebuild 的时间, 我们可以分离第三方依赖, 在项目启动之前, 将其单独打包和引入.</p>    <p>这要借助 <a href="/misc/goto?guid=4959749507236305083" rel="nofollow,noindex">DllPlugin</a> 插件.</p>    <p>我们定义一份生成 dll 的配置文件:</p>    <pre>  <code class="language-javascript">## webpacl.dll.config.js  module.exports = {      entry: {          vendor: [              'vue', 'vuex', 'vue-router', 'vuex-router-sync', 'babel-polyfill', '...'          ]      },      output: {          path: path.join(__dirname, './public/', 'dist'),          filename: '[name].dll.js',          library: '[name]_library'      },      plugins: [          new webpack.DllPlugin({            path: path.join(__dirname, './public/', 'dist', '[name]-manifest.json'),            name: '[name]_library'        })      ]  }  </code></pre>    <p>生成 dll 文件之后, 可以根据环境变量在页面的静态文件中引入:</p>    <p><img src="https://simg.open-open.com/show/f051e1fbe3bd1752c0d26d9c4c3c1cf5.png"></p>    <p>这样, 在每次 rebuild 的时候, webpack 都不会去重新 build vendor, 能极大减少 rebuild 的时间, 提升开发效率.</p>    <p>仅在开发环境下使用</p>    <h3>2. 多进程构建</h3>    <p>Webpack的构建过程是单进程的, 利用 <a href="/misc/goto?guid=4959737624901788269" rel="nofollow,noindex">HappyPack</a> 可让 loader 对文件进行多进程处理, 其原理图如下:</p>    <p><img src="https://simg.open-open.com/show/81725db4e298f4a5fd371898b268dcfc.png"></p>    <p>在业务文件依赖越多和复杂的情况下, HappyPack 对 Webpack 构建效率的提升会越明显. 下图是我在项目使用 HappyPack 前后的一张构建时间对比图:</p>    <p><img src="https://simg.open-open.com/show/fe147bdd6f7882c35394a73027e507d2.png"></p>    <p>HappyPack 会充分利用系统的资源来提升 Webpack 的构建效率, 所以系统本身的硬件配置会对 HappyPack 的使用有一定的影响.</p>    <p>HappyPack 不限于处理 js 文件, 也可以同时处理 css/vue 等其它类型文件. HappyPack 支持多个实例, 可以创建多个实例来分别处理不同的类型文件:</p>    <pre>  <code class="language-javascript">let HappyPack = require('happypack');  let os = require('os');  let happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });     module.exports = {      ...      plugins: [   new HappyPack({   id: 'vue',            threadPool: happyThreadPool,            cache: true,            verbose: true,            loaders: ['vue-loader'],   }),      new HappyPack({   id: 'js',            threadPool: happyThreadPool,            cache: true,            verbose: true,            loaders: ['babel-loader'],   })      # others   ]      ...     }  </code></pre>    <p>此外, HappyPack 同时还利用缓存来使得 rebuild 更快.</p>    <p>开发环境和生产环境下均可使用. 关于其原理分析, 请看 <a href="/misc/goto?guid=4959749507350234658" rel="nofollow,noindex">HappyPack 原理解析</a></p>    <h3>3. 提取公共的依赖模块</h3>    <p>无论是单页还是多页应用, 在 <strong>生产环境</strong> 下, 通常都会利用 <a href="/misc/goto?guid=4959749507439025172" rel="nofollow,noindex">CommonsChunkPlugin</a> 插件来提供公共的依赖模块:</p>    <pre>  <code class="language-javascript">new webpack.optimize.CommonsChunkPlugin({      name: "vendor",      minChunks: ({resource}) => {       resource &&        resource.indexOf('node_modules') &&       resource.match(/.js$/)      }  }),  </code></pre>    <p>上述的配置会提取 node_modules 下的所有模块, 打包出来的结果可能是这样的:</p>    <p><img src="https://simg.open-open.com/show/fb88d0d4e1f68fd36f7a721c2c6449b6.png"></p>    <p>打包结果分析图由 <a href="/misc/goto?guid=4959741211529732867" rel="nofollow,noindex">webpack-bundle-analyzer</a> 提供</p>    <p>这样提取了公共模块之后, 的确会减少业务包的大小, 但是, 这种方式会导致两个问题:</p>    <ul>     <li>业务越复杂, 三方依赖会越多, vendor 包会越大</li>     <li>没有隔离业务路由组件, 所有的路由都有可能会去加载 vendor, 但并不是所有的路由组件都依赖 node_modules 下的所有模块</li>    </ul>    <p>所以, 上述提取公共依赖的方式不可取. 我们应该去分析业务依赖和路由, 尽可能将所有路由组件的公共依赖提取出来:</p>    <pre>  <code class="language-javascript">entry: {      app: path.resolve(__dirname, '../src/page/index.js'),      vendor: [          'vue', 'vuex', 'vue-router', 'vuex-router-sync', 'babel-polyfill',          'axios', '....'      ]  },     new webpack.optimize.CommonsChunkPlugin({      name: "vendor",      filename: "vendor.js"  }),  </code></pre>    <p>前后两种方式打包出来的 vendor 大小对比:</p>    <p><img src="https://simg.open-open.com/show/1361f47ae7262f9aa4194e5d16e6d239.png"></p>    <p>既要去提取公共依赖, 也要避免 vendor 包过于太大.</p>    <h3>4. 文件分离</h3>    <p>文件分离主要是将图片和 CSS 从 js 中分离. 图片和 CSS 都是 Webpack 需要构建的资源, 通过某种配置, 图片可以以 base64 的方式混淆在 js 文件中, 这会增加最终的 bundle 文件的大小. 在 <strong>生产环境</strong> 下, 应该将图片和 CSS 从 js 中分离:</p>    <ul>     <li>在生产环境下, 通过自定义插件, 将图片的本地引用替换为 CDN 的链接</li>     <li>在生产环境下, 通过 <a href="/misc/goto?guid=4959749507542694236" rel="nofollow,noindex">ExtractTextPlugin</a> 来 提取 CSS.</li>    </ul>    <h3>5. 资源混淆和压缩</h3>    <p>Webpack提供的 <a href="/misc/goto?guid=4959749507630258920" rel="nofollow,noindex">UglifyJS</a> 插件由于采用单线程压缩, 速度比较慢,</p>    <p>可以使用 <a href="/misc/goto?guid=4959749507716716988" rel="nofollow,noindex">Parallel</a> 插件进行优化:</p>    <pre>  <code class="language-javascript">let ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');  let os = require('os');     new ParallelUglifyPlugin({      workerCount: os.cpus().length,      cacheDir: '.cache/',      uglifyJS: {          compress: {              warnings: false,              drop_debugger: true,              drop_console: true          },          comments: false,          sourceMap: true,          mangle: true      }  })  </code></pre>    <h3>6. Gzip 压缩</h3>    <p>在 <strong>生产环境</strong> 下, 如果想进一步减小 bundle 文件的大小, 可以使用 <a href="/misc/goto?guid=4959749507799793494" rel="nofollow,noindex">Gzip</a> 压缩.</p>    <pre>  <code class="language-javascript">let CompressionPlugin = require("compression-webpack-plugin");     module.exports = {      plugins: [          new CompressionPlugin({              asset: "[path].gz[query]",              algorithm: "gzip",              test: /.(js|html)$/,              threshold: 10240,              minRatio: 0.8          })      ]  }  </code></pre>    <p>Gzip 压缩能有效减少 bundle 的文件大小:</p>    <p><img src="https://simg.open-open.com/show/34a0a4a5c75db57f831e64ec03345958.png"></p>    <p>部署上线时, 服务端也需要开启 gzip 压缩</p>    <h3>7. 按需加载</h3>    <p>在单页应用中, 一个应用可能会对应很多路由, 每个路由都会对应一个组件; 如果将这些组件全部全部放进一个 bundle, 会导致最终的 bundle 文件比较大(看上图的 app bundle 文件). 因而, 我们需要利用 Webpack 的 <a href="/misc/goto?guid=4959749507871813473" rel="nofollow,noindex">Code Splitting</a> 功能, 将代码进行分割, 实现路由的按需加载.</p>    <p>在 Vue 中, 利用 vue-router 的 <a href="/misc/goto?guid=4959749507960053131" rel="nofollow,noindex">懒加载</a> 功能, 是比较容易实现按需加载的:</p>    <p><img src="https://simg.open-open.com/show/280909473066fc5ec4bb8adbce9d6a14.png"></p>    <p>当访问首页时, 会去加载 Index 组件, 此时并不会加载 Info 组件; 只有当路由切换为 /info 时, Info 组件才会被加载.</p>    <p>以上是个人的一些总结, 如有不足请指正, 如有遗漏, 欢迎补充.</p>    <p><a href="/misc/goto?guid=4959749508041860066" rel="nofollow,noindex">vue-startup</a> 是基于上述的一些优化措施写的一个 Vue 的脚手架, 欢迎 star.</p>    <p> </p>    <p>来自:http://web.jobbole.com/91414/</p>    <p> </p>