webpack 优化

webpack特点:
对 CommonJS 、 AMD 、ES6 的语法做了兼容
对 js、css、图片等资源文件都支持打包
串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,
例如提供对 CoffeeScript、ES6的支持
有独立的配置文件 webpack.config.js
可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间
支持 SourceUrls 和 SourceMaps,易于调试
具有强大的 Plugin 接口,大多是内部插件,使用起来比较灵活
使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快

webpack 的核心在于静态资源打包。gulp 的核心在于任务集成。

常用Loaders介绍
处理样式,转成css,如:less-loader, sass-loader
图片处理,如: url-loader, file-loader。两个都必须用上。
否则超过大小限制的图片无法生成到目标文件夹中
处理js,将es6或更高级的代码转成es5的代码。如:
babel-loader,babel-preset-es2015,babel-preset-react
将js模块暴露到全局,如果expose-loader

常用Plugins介绍
代码热替换, HotModuleReplacementPlugin
生成html文件,HtmlWebpackPlugin
将css成生文件,而非内联,ExtractTextPlugin
报错但不退出webpack进程,NoErrorsPlugin
代码丑化,UglifyJsPlugin,开发过程中不建议打开
多个 html共用一个js文件(chunk),可用CommonsChunkPlugin
清理文件夹,Clean
调用模块的别名ProvidePlugin,
例如想在js中用$,如果通过webpack加载,需要将$与jQuery对应起来

webpack.optimize.CommonsChunkPlugin
提取公共代码
提取第三方库
配置manifest,不用改变hash值,直接浏览器缓存
将webpack的运行时代码提取到独立的manifest文件中,
这样每次修改业务代码只重新打包生成业务代码模块 main.js 和运行时代码模块 manifest.js 就实现了业务模块和公共基础库模块的分离。

对「低频库/工具/代码」的处理,对于这类代码最好的办法是做代码分割(Code Splitting)
做到按需加载,进一步加速应用

webpack 提供了几种添加分割点的方法:
CommonJs: require.ensure
AMD: require
ES6 Modules import

分割点可以主动将指定的模块分离成另一个 chunk,而不是随当前 chunk 一起打包

code split 按需,import, require.ensure(),加载需要的JS代码,减少首次请求的时间
webpack提供的require.ensure可以定义分割点来打包独立的chunk

1
// 同步加载的写法,如:
var TopicItem = require('../topic/topicitem');

// 异步加载的写法
require.ensure([], function(){
	var dialog = require('...');
	var $ = require('....')
	var util = require('...')
})

首屏需要的同步加载,首屏过后才需要的则按需加载(异步)

提取频繁共用的模块,将 webpack runtime 构建为内联 script。
使用 Commons-chunk-plugin,我们只需要将共用的模块打包到 libs/vendor 中即可,
minChunks 是指限定模块被 chunks 依赖的最少次数,低于设定值(2 ≤ n ≤ chunks 总数)将不会被提取到公共 chunk 中。如果 chunks 太多,又不想让所有公共模块被分离到 vendor 中,可以将 minChunks 设为 Infinity,则公共 chunk 仅仅包含在 entry 中指定的模块,而不会把其他共用的模块提取进去。

1
new webpack.optimize.CommonsChunkPlugin({  
  names: ['libs', 'vendor'].reverse(),
  minChunks: Infinity
})

尽管已经划分好了 chunks,也提取了公共的模块,但仅改动一个模块的代码还是会造成 Initial chunk (libs) 的变化。原因是这个初始块包含着 webpack runtime,而 runtime 还包含 chunks ID 及其对应 chunkhash 的对象。因此当任何 chunks 内容发生变化,webpack runtime 均会随之改变。
可以通过增加一个指定的公共 chunk 来提取 runtime
manifest 只是个特定的名字(可能是包含了 chunks 清单,所以起名 manifest),如果仅仅是为了分离 webpack runtime,可以将 manifest 替换成任意你想要的名字。

manifest.js 实在是太小了,以至于不值得再为一个小 js 增加资源请求数量。

这时候我们可以引入另一个插件:
inline-manifest-webpack-plugin。
它可以将 manifest 转为内联在 html 内的 inline script,因为 manifest 经常随着构建而变化,写入到 html 中便不需要每次构建再下载新的 manifest 了,从而减少了一个小文件请求。此插件依赖 html-webpack-plugin 和 manifest 公共块

UglifyJsPlugin压缩代码
webpack -p 也是压缩
生产环境版本时使用如下命令:
env NODE_ENV=production webpack -p
不仅可以将未使用的 exports 清除,还能去掉很多不必要的代码,如无用的条件代码、未使用的变量、不可达代码等。

webpack-uglify-parallel的是实现原理是采用了多核并行压缩的方式来提升我们的压缩速度。

1
const os = require('os');
const UglifyJsParallelPlugin = require('webpack-uglify-parallel');

new UglifyJsParallelPlugin({
  workers: os.cpus().length,
  mangle: true,
  compressor: {
    warnings: false,
    drop_console: true,
    drop_debugger: true
   }
})

利用 DllPlugin 和 DllReferencePlugin 预编译资源模块
dll,完全不管第三方库,webpack只打包项目的业务代码。
DllPlugin的作用是预先编译一些模块,而DllReferencePlugin则是把这些预先编译好的模块引用起来。这边需要注意的是DllPlugin必须要在DllReferencePlugin执行前先执行一次,dll这个概念应该也是借鉴了windows程序开发中的dll文件的设计理念。

相对于externals,dllPlugin有如下几点优势:

dll预编译出来的模块可以作为静态资源链接库可被重复使用,尤其适合多个项目之间的资源共享,如同一个站点pc和手机版等;
dll资源能有效地解决资源循环依赖的问题,
由于externals的配置项需要对每个依赖库进行逐个定制,所以每次增加一个组件都需要手动修改,略微繁琐,而通过dllPlugin则能完全通过配置读取,减少维护的成本;

热更新:
–watch cli 增量更新,缓存在内存中
监控目录下的文件变化并实时重新构建。但不会把结果通知给浏览器页面

hotdevserver, gzip压缩,开启服务热更新

1
devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    compress: true,
    historyApiFallback: true,
},

hotdevserver 会完全刷新,结合HMR, 可以实现局部刷新

1
if (module.hot) {
    module.hot.accept();
}

“inline”选项为整个页面添加了”Live Reloading”功能,而”hot”选项开启了”Hot Module Reloading”功能,这样就会尝试着重载发生变化的组件,而不是整个页面

sourcemap 配置,选择sourcemap类型,css 的sourcemap会增加编译时间,js sourcemap不会

profile true 优化
cache true 缓存临时文件

webpack的hash字段是根据每次编译compilation的内容计算所得,
也可以理解为项目总体文件的hash值,
而不是针对每个具体文件的。
当使用–hot参数时,只能使用hash,如果使用chunkhash会报错

chunkhash是根据模块内容计算出的hash值。
使用–inline时,hash和chunkhash都可以使用。

hash 与 chunkhash
文件的hash指纹通常作为前端静态资源实现增量更新的方案之一
chunkhash代表的是chunk的hash值
chunkhash是根据具体模块文件的内容计算所得的hash值,所以某个文件的改动只会影响它本身的hash
hash代表的是compilation的hash值

计算所有 chunks 的 hash —— [hash]
为每个 chunk 计算 hash —— [chunkhash]

new ExtractTextPlugin(“app.css”)
抽离样式,独立打包,利用缓存

npm script

1
"dev": "webpack-dev-server --config webpack.develop.config.js --devtool eval --progress --colors --hot --content-base src"
"dev": "webpack --config webpack.config.js --watch"

合理划分公共模块:
库和工具 - libs
定制 UI 库和工具 - vendor
业务模块 - entries
低频库/工具/代码 - 分割为 chunk

1
{
  entry: {
    libs: [
      'es6-promise/auto',
      'whatwg-fetch',
      'vue',
      'vue-router'
    ],
    vendor: [
      /*
       * vendor 中均是非 npm 模块,
       * 用 resolve.alias 修改路径,
       * 避免冗长的相对路径。
       */
      'assets/libs/fastclick',
      'components/request',
      'components/ui',
      'components/bootstrap' // 初始化脚本
    ],
    page1: 'src/pages/page1',
    page2: 'src/pages/page2'
},

DedupePlugin插件可以在打包的时候删除重复或者相似的文件,实际测试中应该是文件级别的重复的文件

happypack加速构建
webpack中为了方便各种资源和类型的加载,设计了以loader加载器的形式读取资源,但是受限于node的编程模型影响,所有的loader虽然以async的形式来并发调用,但是还是运行在单个 node的进程以及在同一个事件循环中,这就直接导致了当我们需要同时读取多个loader文件资源时,比如babel-loader需要transform各种jsx,es6的资源文件。在这种同步计算同时需要大量耗费cpu运算的过程中,node的单进程模型就无优势了,那么happypack就针对解决此类问题而生。

happypack的处理思路是将原有的webpack对loader的执行过程从单一进程的形式扩展多进程模式,原本的流程保持不变,这样可以在不修改原有配置的基础上来完成对编译过程的优化
happypack在编译过程中除了利用多进程的模式加速编译,还同时开启了cache计算,能充分利用缓存读取构建文件,对构建的速度提升也是非常明显的

resolve 加快webpack查找文件:
root 和 moduledirectories 如果只从用法上来看,似乎是可以互相替代的。但因为 moduledirectories 从设计上是取相对路径,所以比起 root ,所以会多 parse 很多路径。

1
resolve: {
    root: path.resolve('src/node_modules'),
    extensions: ['', '.js', '.jsx']
},
resolve: {
    modulesDirectories: ['node_modules', './src'],
    extensions: ['', '.js', '.jsx']
}

使用 include 来限定 babel 的使用范围,进一步提高效率。

1
var path = require('path');
module.exports = {
    module: {
        loaders: [
          {
              test: /\.js(x)*$/,
              loader: 'babel-loader',
              include: [
                // 只去解析运行目录下的 src 和 demo 文件夹
                path.join(process.cwd(), './src'),
                path.join(process.cwd(), './demo')
              ],
              query: {
                  presets: ['react', 'es2015-ie', 'stage-1']
              }
          }
        ]
    }
}

webpack 中对 chunks 做优化:
OccurrenceOrderPlugin (webpack 2 默认启用)
LimitChunkCountPlugin
MinChunkSizePlugin
UglifyJsPlugin
AggressiveMergingPlugin
DllPlugin
DllReferencePlugin