webpack2 终极优化

webpack是当下最流行的js打包工具,这得益于网页应用日益复杂和js模块化的流行。webpack2增长了一些新特性也正式发布了一段时间,是时候告诉你们如何用webpack2优化你的构建让它构建出更小的文件尺寸和更好的开发体验。css

优化输出

打包结果更小可让网页打开速度更快以及简约宽带。能够经过这如下几点作到html

压缩css

css-loader 在webpack2里默认是没有开启压缩的,最后生成的css文件里有不少空格和tab,经过配置
css-loader?minimize参数能够开启压缩输出最小的css。css的压缩实际是是经过cssnano实现的。node

tree-shaking

tree-shaking 是指借助es6 import export 语法静态性的特色来删掉export可是没有import过的东西。要让tree-shaking工做须要注意如下几点:react

  • 配置babel让它在编译转化es6代码时不把import export转换为cmd的module.export,配置以下:
"presets": [
    [
      "es2015",
      {
        "modules": false
      }
    ]
]
  • 大多数分布到npm的库里的代码都是es5的,可是也有部分库(redux,react-router等等)开始支持tree-shaking。这些库发布到npm里的代码即包含es5的又包含全采用了es6 import export 语法的代码。

拿redux库来讲,npm下载到的目录结构以下:webpack

├── es
│   └── utils
├── lib
│   └── utils

其中lib目录里是编译出的es5代码,es目录里是编译出的采用import export 语法的es5代码,在redux的package.json文件里有这两个配置:git

"main": "lib/index.js",
"jsnext:main": "es/index.js",

这是指这个库的入口文件的位置,因此要让webpack去读取es目录下的代码须要使用jsnext:main字段配置的入口,要作到这点webpack须要这样配置:es6

module.exports = {
    resolve: {
            mainFields: ['jsnext:main','main'],
        }
};

这会让webpack先使用jsnext:main字段,在没有时使用main字段。这样就能够优化支持tree-shaking的库。github

优化 UglifyJsPlugin

webpack --optimize-minimize 选项会开启 UglifyJsPlugin来压缩输出的js,可是默认的UglifyJsPlugin配置并无把代码压缩到最小输出的js里仍是有注释和空格,须要覆盖默认的配置:web

new UglifyJsPlugin({
    // 最紧凑的输出
    beautify: false,
    // 删除全部的注释
    comments: false,
    compress: {
      // 在UglifyJs删除没有用到的代码时不输出警告  
      warnings: false,
      // 删除全部的 `console` 语句
      // 还能够兼容ie浏览器
      drop_console: true,
      // 内嵌定义了可是只用到一次的变量
      collapse_vars: true,
      // 提取出出现屡次可是没有定义成变量去引用的静态值
      reduce_vars: true,
    }
})

定义环境变量 NODE_ENV=production

不少库里(好比react)有部分代码是这样的:正则表达式

if(process.env.NODE_ENV !== 'production'){
// 不是生产环境才须要用到的代码,好比控制台里看到的警告    
}

在环境变量 NODE_ENV 等于 production 的时候UglifyJs会认为if语句里的是死代码在压缩代码时删掉。

使用 CommonsChunkPlugin 抽取公共代码

CommonsChunkPlugin能够提取出多个代码块都依赖的模块造成一个单独的模块。要发挥CommonsChunkPlugin的做用还须要浏览器缓存机制的配合。在应用有多个页面的场景下提取出全部页面公共的代码减小单个页面的代码,在不一样页面之间切换时全部页面公共的代码以前被加载过而没必要从新加载。这个方法能够很是有效的提高应用性能。

在生产环境按照文件内容md5打hash

webpack编译在生产环境出来的js、css、图片、字体这些文件应该放到CDN上,再根据文件内容的md5命名文件,利用缓存机制用户只须要加载一次,第二次加载时就直接访问缓存。若是你以后有修改就会为对应的文件生产新的md5值。作到以上你须要这样配置:

{
  output: {
    publicPath: CND_URL,
    filename: '[name]_[chunkhash].js',
  },
}

知道以上原理后咱们还能够进一步优化:利用CommonsChunkPlugin提取出使用页面都依赖的基础运行环境。好比对于最多见的react体系你能够抽出基础库react react-dom redux react-redux到一个单独的文件而不是和其它文件放在一块儿打包为一个文件,这样作的好处是只要你不升级他们的版本这个文件永远不会被刷新。若是你把这些基础库和业务代码打包在一个文件里每次改动业务代码都会致使浏览器重复下载这些包含基础库的代码。以上的配置为:

// vender.js 文件抽离基础库到单独的一个文件里防止跟随业务代码被刷新
// 全部页面都依赖的第三方库
// react基础
import 'react';
import 'react-dom';
import 'react-redux';
// redux基础
import 'redux';
import 'redux-thunk';
// webpack配置
{
  entry: {
    vendor: './path/to/vendor.js',
  },
}

DedupePlugin 和 OccurrenceOrderPlugin

在webpack1里常常会使用 DedupePlugin 插件来消除重复的模块以及使用 OccurrenceOrderPlugin 插件让被依赖次数更高的模块靠前分到更小的id 来达到输出更少的代码,在webpack2里这些已经这两个插件已经被移除了由于这些功能已经被内置了。

除了压缩文本代码外还能够:

以上优化点只须要在构建用于生产环境代码的时候才使用,在开发环境时最好关闭由于它们很耗时。

优化开发体验

优化开发体验主要从更快的构建和更方便的功能入手。

更快的构建

缩小文件搜索范围

webpack的resolve.modules配置模块库(一般是指node_modules)所在的位置,在js里出现import 'redux'这样不是相对也不是绝对路径的写法时会去node_modules目录下找。可是默认的配置会采用向上递归搜索的方式去寻找node_modules,但一般项目目录里只有一个node_modules在项目根目录,为了减小搜索咱们直接写明node_modules的全路径:

module.exports = {
    resolve: {
        modules: [path.resolve(__dirname, 'node_modules')]
    }
};

除此以外webpack配置loader时也能够缩小文件搜索范围。

  • loader的test正则表达式也应该尽量的简单,好比在你的项目里只有.js文件时就不要把test写成/\.jsx?$/
  • loader使用include命中只须要处理的文件,好比babel-loader的这两个配置:

只对项目目录下src目录里的代码进行babel编译

{
    test: /\.js$/,
    loader: 'babel-loader',
    include: path.resolve(__dirname, 'src')
}

项目目录下的全部js都会进行babel编译,包括庞大的node_modules下的js

{
    test: /\.js$/,
    loader: 'babel-loader'
}

开启 babel-loader 缓存

babel编译过程很耗时,好在babel-loader提供缓存编译结果选项,在重启webpack时不须要创新编译而是复用缓存结果减小编译流程。babel-loader缓存机制默认是关闭的,打开的配置以下:

module.exports = {
    module: {
         loaders: [{
                test: /\.js$/,
                loader: 'babel-loader?cacheDirectory',
         }]
  }
};

使用 alias

resolve.alias 配置路径映射。
发布到npm的库大多数都包含两个目录,一个是放着cmd模块化的lib目录,一个是把全部文件合成一个文件的dist目录,多数的入口文件是指向lib里面下的。
默认状况下webpack会去读lib目录下的入口文件再去递归加载其它依赖的文件这个过程很耗时,alias配置可让webpack直接使用dist目录的总体文件减小文件递归解析。配置以下:

module.exports = {
  resolve: {
    alias: {
      'moment': 'moment/min/moment.min.js',
      'react': 'react/dist/react.js',
      'react-dom': 'react-dom/dist/react-dom.js'
    }
  }
};

使用 noParse

module.noParse 配置哪些文件能够脱离webpack的解析。
有些库是自成一体不依赖其余库的没有使用模块化的,好比jquey、momentjs、chart.js,要使用它们必须总体所有引入。
webpack是模块化打包工具彻底没有必要去解析这些文件的依赖,由于它们都不依赖其它文件体积也很庞大,要忽略它们配置以下:

module.exports = {
  module: {
    noParse: /node_modules\/(jquey|moment|chart\.js)/
  }
};

除此之外还有不少能够加速的方法:

更方便的功能

模块热替换

模块热替换是指在开发的过程当中修改代码后不用刷新页面直接把变化的模块替换到老模块让页面呈现出最新的效果。
webpack-dev-server内置模块热替换,配置起来也很方便,下面以react应用为例,步骤以下:

  • 在启动webpack-dev-server的时候带上--hot参数开启模块热替换,在开启--hot后针对css的变化是会自动热替换的,可是js涉及到复杂的逻辑还须要进一步配置。
  • 配置页面入口文件
import App from './app';

function run(){
    render(<App/>,document.getElementById('app'));
}
run();

// 只在开发模式下配置模块热替换
if (process.env.NODE_ENV !== 'production') {
  module.hot.accept('./app', run);
}

当./app发生变化或者当./app依赖的文件发生变化时会把./app编译成一个模块去替换老的,替换完毕后从新执行run函数渲染出最新的效果。

自动生成html

webpack只作了资源打包的工做还缺乏把这些加载到html里运行的功能,在庞大的app里手写html去加载这些资源是很繁琐易错的,咱们须要自动正确的加载打包出的资源。
webpack原生不支持这个功能因而我作了一个插件 web-webpack-plugin
具体使用点开连接看详细文档,使用大概以下:

demo

webpack配置

module.exports = {
    entry: {
        A: './a',
        B: './b',
    },
    plugins: [
        new WebPlugin({
            // 输出的html文件名称,必填,注意不要重名,重名会覆盖相互文件。
            filename: 'index.html',
            // 该html文件依赖的entry,必须是一个数组。依赖的资源的注入顺序按照数组的顺序。
            requires: ['A', 'B'],
        }),
    ]
};

将会输出一个index.html文件,这个文件将会自动引入 entry AB 生成的js文件,

输出的html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
<script src="A.js"></script>
<script src="B.js"></script>
</body>
</html>

输出的目录结构

├── A.js
├── B.js
└── index.html

管理多页面

虽然webpack适用于单页应用,但复杂的系统常常是由多个单页应用组成,每一个页面一个功能模块。webpack给出了js打包方案但缺乏管理多个页面的功能。 web-webpack-pluginAutoWebPlugin会自动的为你的系统里每一个单页应用生成一个html入口页,这个入口会自动的注入当前单页应用依赖的资源,使用它你只需以下几行代码:

plugins: [
    // ./src/pages/ 表明存放全部页面的根目录,这个目录下的每个目录被看着是一个单页应用
    // 会为里面的每个目录生成一个html入口
    new AutoWebPlugin('./src/pages/', {
      //使用单页应用的html模版文件,这里你能够自定义配置
      template: './src/assets/template.html',
    }),
  ],

查看web-webpack-plugin的文档了解更多

分析输出结果

若是你对当前的配置输出或者构建速度不满意,webpack有一个工具叫作webpack analyze 以可视化的方式直观的分析构建,来进一步优化构建结果和速度。要使用它你须要在执行webpack的时候带上--json --profile2个参数,这表明让webpack把构建结果以json输出并带上构建性能信息,使用以下:

webpack --json --profile > stats.json

会生产一个stats.json 文件,再打开webpack analyze 上传这个文件开始分析。

最后附上这篇文章所讲到的webpack总体的配置,分为开发环境的webpack.config.js和生产环境的webpack-dist.config.js

阅读原文

相关文章
相关标签/搜索