从webpack1.x 到 webpack2.x

从webpack1.x 到 webpack2.x

这周对去年年末做的一个产品准备发布出去,那时候写的脚手架工具是基于webpack1.13.2,还不支持动态导入import,之前都是将所有的代码打在一个bundle.js 中,首次加载的时间差不多要4-5s, 升级到2.0以后使用import方式,配合react-router的动态路由,将系统按照路由进行分割,这时候首页的加载速度差不多在2s左右,下面是我在升级过程中整理的一些笔记。

resolve

resolve.root, resolve.fallback, resolve.modulesDirectories

上面这些写法被一个单独的写法 resolve.modules 取代

before

1
2
3
4
5
6
module.exports = {
resolveLoader: {
fallback: 'path.join(__dirname, 'node_modules')',
modulesDirectories: [ path.join(__dirname, 'node_modules') ],
}
}

after

1
2
3
4
5
module.exports = {
resolve: {
modules: [ "node_modules"]
}
}

resolve.extensions

此选项不再需要传一个空字符串。此行为被迁移到 resolve.enforceExtension

before

1
2
3
4
5
module.exports = {
resolve: {
extensions: ['', '.js', '.jsx', '.ts', '.tsx', '.json'],
}
}

after

1
2
3
4
5
module.exports = {
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
}
}

module

module.loaders 改为 module.rules

旧的 loader 配置被更强大的 rules 系统取代,后者允许配置 loader 以及其他更多选项。 为了兼容旧版,module.loaders 语法仍然有效,旧的属性名依然可以被解析。 新的命名约定更易于理解,并且是升级配置使用 module.rules 的好理由

before

1
2
3
4
5
6
7
8
9
module.exports = {
module: {
loaders: [
{
test: /\.tsx?$/,
loader: 'ts-loader'
}
}
}

after

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'ts-loader'
}
}
}
}

链式loader

就像在 webpack 1 中,loader 可以链式调用,上一个 loader 的输出被作为输入传给下一个 loader。 使用 rule.use 配置选项,use 可以设置为一个 loader 数组。 在 webpack 1 中,loader 通常被用 ! 连写。这一写法在 webpack 2 中只在使用旧的选项 module.loaders 时才有效

before

1
2
3
4
5
6
7
8
9
10
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
loader: 'style-loader!css-loader!postcss-loader'
}
]
}
}

after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
}
}

取消「在模块名中自动添加 -loader 后缀」

before

1
2
3
4
5
6
7
8
9
10
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
loader: 'style!css!postcss'
}
]
}
}

after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
}
}

这么改的原因

json-loader 不再需要手动添加

如果没有为 JSON 文件配置 loader,webpack 将自动尝试通过 json-loader 加载 JSON 文件

这么做的目的主要是消除 webpack、 nodejs和 browserify之间的差异

配置中的 loader 默认相对于 context 进行解析

在 webpack 1 中,默认配置下 loader 解析相对于被匹配的文件。然而,在 webpack 2 中,默认配置下 loader 解析相对于 context 选项。

这解决了「在使用 npm link 或引用 context 上下文目录之外的模块时,loader 所导致的模块重复载入」的问题。

Plugin 插件

UglifyJsPlugin sourceMap

UglifyJsPlugin 的 sourceMap 选项现在默认为 false 而不是 true。这意味着如果你在压缩代码时启用了 source map,或者想要让 uglifyjs 的警告能够对应到正确的代码行,你需要将 UglifyJsPlugin 的 sourceMap 设为 true

before

1
2
3
4
5
6
module.exports = {
devtool: "source-map",
plugins: [
new UglifyJsPlugin()
]
}

after

1
2
3
4
5
6
7
8
module.exports = {
devtool: "source-map",
plugins: [
new UglifyJsPlugin({
sourceMap: true
})
]
}

UglifyJsPlugin warnings

UglifyJsPlugin 的 compress.warnings 选项现在默认为 false 而不是 true。 这意味着如果你想要看到 uglifyjs 的警告信息,你需要将 compress.warnings 设为 true

before

1
2
3
4
5
6
module.exports = {
devtool: "source-map",
plugins: [
new UglifyJsPlugin()
]
}

after

1
2
3
4
5
6
7
8
9
10
module.exports = {
devtool: "source-map",
plugins: [
new UglifyJsPlugin({
compress: {
warnings: true
}
})
]
}

UglifyJsPlugin 不再压缩 loaders

UglifyJsPlugin 不再压缩 loaders。在未来很长一段时间里,需要通过设置 minimize:true 来压缩 loaders

为了兼容旧的 loaders,loaders 可以通过插件来切换到压缩模式:

before

1
2
3
4
5
6
7
8
9
module.exports = {
ts: {
compiler: 'ntypescript',
configFileName: '.tsconfig.json',
},
postcss: function () {
return [autoprefixer, precss, px2rem(PX2REM_OPTIONS)];
},
}

after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
minimize: true,
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: function() {
return [autoprefixer, precss, px2rem(PX2REM_OPTIONS)];
},
ts: {
compiler: 'ntypescript',
configFileName: '.tsconfig.json',
}
}
})
]
}

移除 DedupePlugin

BannerPlugin - 破坏性改动

BannerPlugin 不再接受两个参数,而是只接受单独的 options 对象
before

1
2
3
4
5
module.exports = {
plugins: [
new webpack.BannerPlugin('Banner', {raw: true, entryOnly: true});
]
}

after

1
2
3
4
5
module.exports = {
plugins: [
new webpack.BannerPlugin({banner: 'Banner', raw: true, entryOnly: true});
]
}

默认加载 OccurrenceOrderPlugin

before

1
2
3
4
5
6
7
module.exports = {
plugins: [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
]
}

after

1
2
3
4
5
6
7
module.exports = {
plugins: [
//new webpack.optimize.DedupePlugin(),
//new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
]
}

ExtractTextWebpackPlugin - 破坏性改动

ExtractTextPlugin 需要使用版本 2,才能在 webpack 2 下正常运行
这一插件的配置变化主要体现在语法上
before

1
2
3
4
5
6
7
8
9
10
module.exports = {
module: {
rules: [
{
test: /\.less$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader!less-loader")
}
]
}
}

after

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
}
}

在插件中
before

1
2
3
4
5
module.exports = {
plugins: [
new ExtractTextPlugin('[name].[contenthash].css'),
]
}

after

1
2
3
4
5
6
7
module.exports = {
plugins: [
new ExtractTextPlugin({
filename: '[name].[contenthash].css'
}),
]
}

通过 options 配置 loader

你不能再通过 webpack.config.js 的自定义属性来配置 loader。只能通过 options 来配置

before

1
2
3
4
5
6
7
8
9
module.exports = {
ts: {
compiler: 'ntypescript',
configFileName: '.tsconfig.json',
},
postcss: function () {
return [autoprefixer, precss, px2rem(PX2REM_OPTIONS)];
},
}

after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: function() {
return [autoprefixer, precss, px2rem(PX2REM_OPTIONS)];
},
ts: {
compiler: 'ntypescript',
configFileName: '.tsconfig.json',
}
}
})
]
}

ES2015 的代码分割

在 webpack 1 中,可以使用 require.ensure作为实现应用程序的懒加载 chunks 的一种方法

1
2
3
4
5
6
7
8
9
10
const routes = [
{
path: '/',
getIndexRoute (nextState, cb) {
require.ensure([], require => {
cb(null, { component: require('CONTAINERS/ProjectList) })
}, 'projectlist')
},
}
]

ES2015 模块加载规范定义了 import() 方法,可以在运行时(runtime)动态地加载 ES2015 模块。webpack 将 import() 作为分割点(split-point)并将所要请求的模块(requested module)放置到一个单独的 chunk 中。import() 接收模块名作为参数,并返回一个 Promise。

1
2
3
4
5
6
7
8
9
10
11
12
const routes = [{
component: ProjectHeader,
childRoutes: [
{
path: '/',
getComponent(location, cb) {
System.import('CONTAINERS/ProjectList')
.then(loadRoute(cb)) //为每个可能的路由创建独立的 chunk
.catch(errorLoading)
}
}
}]

好消息是:如果加载 chunk 失败,我们现在可以进行处理,因为现在它基于 Promise

可以传递部分表达式给 import()。这与 CommonJS 对表达式的处理方式一致(webpack 为所有可能匹配的文件创建 context)。

import() 为每一个可能的模块创建独立的 chunk。