Webpack之恋(5)-代码拆分

楔子

Webpack可以把一切资源打包成一个文件,虽然这可以减少网络请求的次数,但有些场合需要拆分。比如把不常修改的第三方JavaScript类库拆分出来,让浏览器缓存减少网络请求,提高性能;把CSS拆分出来,让浏览器并行处理,也能提高性能。
webpack

Vendor(类库)拆分

不拆分使用

假如你的src/index.js内容如下

// src/index.js
import _ from 'lodash';
import moment from 'moment';

console.log(_.toUpper('hello world!'));
console.log(moment().format());

webpack配置文件webpack.config.js内容如下

// webpack.config.js
const path = require('path');
  module.exports = {
    entry: {
      index:'./src/index.js',
    },
  output: {
      path: path.resolve(__dirname, 'app/assets/js'),
      filename: '[name].js'
  }
};

使用npm安装lodash和comment类库后,通过webpack命令构建后,所有资源都打包到assets/js/index.js文件中了,html只需这样引用

<script src="assets/js/index.js"></script>

类库独立文件

那么我们来改下webpack.config.js,内容如下

// webpack.config.js
const path = require('path');
  module.exports = {
    entry: {
      index:'./src/index.js',
      vendor:['lodash','moment']
    },
  output: {
      path: path.resolve(__dirname, 'app/assets/js'),
      filename: '[name].js'
  }
};

添加了vendor,使用webpack构建后assets的结构如下

app/assets/
└── js
├── index.js
└── vendor.js

vendor.js包含lodash和moment,但index.js也包含lodash和moment。

拆分代码

因此我们还需要使用一个插件:CommonsChunkPlugin。它的作用是抽取出共用的部分,单独生成一个文件

const path = require('path');
const webpack = require('webpack');
module.exports = {
    entry: {
      index:'./src/index.js',
      vendor:['lodash','moment']
    },
  output: {
      path: path.resolve(__dirname, 'app/assets/js'),
      filename: '[name].js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'libs'
    })
  ]
};

记得npm本地安装webpack,执行webpack后的app/assets结构

app/assets/
└── js
├── index.js
├── libs.js
└── vendor.js

libs.js是lodash和coment内容,而vendor.js被抽取后内容并无实质内容,而index.js只包含其本身的业务代码。此时的html脚本引用为

<script src="assets/js/libs.js"></script>
<script src="assets/js/index.js"></script>

思考下如果把CommonsChunk的名字改为“vendor”,如何?是不是就把那个空的vendor.js给覆盖了,这样app/assets/中的文件就完美了,不会产生垃圾(如没有实质内容的vendor.js)。为了不产生垃圾,还有一张方式,把所有的第三方类库都抽出来

const path = require('path');
const webpack = require('webpack');
module.exports = {
    entry: {
      index:'./src/index.js'
    },
  output: {
      path: path.resolve(__dirname, 'app/assets/js'),
      filename: '[name].js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'libs',
      minChunks: function (module) {
      // 把存在于node_modules目录的抽出打成一个独立的文件
            return module.context && module.context.indexOf('node_modules') !== -1;
      }
    })
  ]
};

处理runtime信息

然而这里遇到一个非常尴尬的问题,webpack因为某些原因,它会向最后的产出物写入一些runtime信息,如果是单个产出物,会把runtime信息写进这个单个产出物;如果有抽出公共部分形成单独才文件,这些runtime信息会写入这个公共部分的文件。那么我们费尽心机抽出的公共文件并没有享受浏览器缓存的福利,因为每次runtime的信息是不一样的。我们可以再增加一个公共文件如manifest

const path = require('path');
const webpack = require('webpack');
module.exports = {
    entry: {
      index:'./src/index.js'
    },
  output: {
      path: path.resolve(__dirname, 'app/assets/js'),
      filename: '[name].js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: ['libs','manifest'],
      minChunks: function (module) {
// 把存在于node_modules目录的抽出打成一个独立的文件
            return module.context && module.context.indexOf('node_modules') !== -1;
      }
    })
  ]
};

这样虽然libs.js和manifest.js内容都是公共部分内容,但runtime信息写入了manifest.js,我们的html只需要引用libs.js即可。还有一种办法

const path = require('path');
const webpack = require('webpack');
module.exports = {
    entry: {
      index:'./src/index.js'
    },
  output: {
      path: path.resolve(__dirname, 'app/assets/js'),
      filename: '[name].js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: ['libs','manifest'],
      minChunks: function (module) {
// 把存在于node_modules目录的抽出打成一个独立的文件
            return module.context && module.context.indexOf('node_modules') !== -1;
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
// 已经没有什么公共部分了,只包含webpack的runtime信息
      name: 'empty-no-use'
    })
  ]
};

最后再加一个抽出公共部分的实例,由于没有公共部分无可抽取,但runtime信息会写入此文件。

Css拆分

不拆分使用

import css就像import其他模块一样,使用css-loader来来转换css为webpack能识别的JavaScript模块,可以使用style-loader来使用处理后的css。为了提高性能可以使用ExtractTextWebpackPlugin插件来把css拆分成独立文件。

示例代码结构如

├── app
│   └── index.html
├── src
│   ├── index.css
│   └── index.js
└── webpack.config.js

其中html引用webpack构建后的资源文件index.js

// app/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<div id="testdiv"></div>
</body>
<script src="assets/js/index.js"></script>
</html>

原生css文件

// src/index.css
#testdiv{
background:gray;
width:100px;
height:100px;
}

导入css文件到模块文件

// src/index.js
import './index.css';

webpack配置如下

// webpack.config.js
const path = require('path');
module.exports = {
  entry: {
     index:'./src/index.js'
  },
module: {
rules: [{
test: /\.css$/,
loader: 'style-loader!css-loader'
}]
},
  output: {
     path: path.resolve(__dirname, 'app/assets/js'),
     filename: '[name].js'
  },
  plugins: [
  ]
};

使用webpack构建即可预览效果,记得使用npm安装css-loader和style-loader。注意!代表管道,而loader是从右到做的顺序执行的。

拆分代码

安装ExtractTextWebpackPlugin

npm install extract-text-webpack-plugin

配置webpack.config.js

const path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
  entry: {
     index:'./src/index.js'
  },
module: {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: 'css-loader'
})
}]
},
  output: {
     path: path.resolve(__dirname, 'app/assets/js'),
     filename: '[name].js'
  },
  plugins: [
     new ExtractTextPlugin('styles.css'),
  ]
};

再次执行webpack构建,即可拆分出css了

app/assets/
└── js
├── index.js
└── styles.css

按需拆分

按需拆分将在下一篇博文中总结,敬请关注….

翟前锋 wechat
欢迎订阅我的微信公众号:zhaiqianfeng!