在项目中使用了一段时间的 Webpack ,得益于其多元的功能支持和配置定制,得到了很多本地编译和依赖管理的帮助。在搭建好配置和架构之后,开发过程中可以不再关注模块的组织、载入、转义、合并、精简、兼容等各种方面的工程问题,全部交给 Webpack 来处理。效率和体验都得到了不小的提升。本篇文章就是在对使用 Webpack 过程中的关键配置和方法做一些总结和沉淀。
本文是一些零散的功能记录、关键点配置和 Tips,大部分从使用过程中总结而来,并不是手册翻译也不是入门讲解,正在入手 Webpack 或在使用中遇到问题的同学可以看看是否刚好解决到你的问题,如果有老司机也欢迎指出错误。
一、复杂项目配置正确姿势 - Node API:
Webpack 的配置方式,简单的项目通过一份 webpack.config.js 配置文件可以 hold 住了。并且 webpack.config.js 中可以以数组形式返回多份配置,执行打包命令时会遍历每个配置执行多次打包。
但在复杂项目中(例如同构项目)需要根据不同环境定制配置,写配置文件的方法可能捉襟见肘。这时可以直接用 Node API 来跑,从使用配置文件转为使用一个配置 Function 或者 Class 来灵活生成了。例如一个 build 脚本可以这样写 (文中部分代码为方便读者 Copy 未转图片,浏览折行请见谅):
./build.js:
var webpack = require('webpack');var configGen = require("./config.generator");
//通过参数生成定制配置,例如通过 process.argv 接收参数 var config = configGen(options); var compiler = webpack(config);
compiler.run(function(err, stats) { if(err){ console.err(err)
}else{ console.log(stats.toString({ //终端显示带上颜色 colors:true }))
}
});
然后使用 npm scripts 直接跑就很方便:
./package.json:
{ "scripts": { "build": "node ./build.js"
}
}
执行:
npm run build
或者开发时使用 webpack-dev-server 来做本地 server 动态更新, 非常灵活:
var webpack = require('webpack');var webpackDevServer = require('webpack-dev-server');var configGen = require("./config.generator");var config = configGen(options);var compiler = webpack(config);var server = new webpackDevServer(compiler,{
contentBase: __dirname,
stats: { colors: true }
});
server.listen(8081);
二、关于 loader 配置:
loader 可以写在代码里,也可以在配置里设置。建议通用的 loader 都放到配置里,减少代码中的特殊性。否则万一以后要迁移还麻烦。
例如配置 .jsx 文件使用 Babel-loader 支持 React 和 ES6,以及传递一些参数开启更多 Babel 插件:
module:{ loaders:[ {
test: /\.jsx$/,
include: path.resolve(__dirname, "lib"),
loader: "babel-loader", //query用于向loader传递参数,不同loader接收参数不一样
query: {
presets: ['react', 'es2015'],
plugins: [ "syntax-object-rest-spread", "transform-object-rest-spread" ],
cacheDirectory: true
}
}, ]
}
如果你有一些 loader 需要提前执行(例如CMD转AMD的兼容处理,不提前处理依赖解析就会有问题),可以使用 module.preLoaders ,配置和 module.loaders 相同。
如果你有用到一些自己写的 loader,想设置别名而不用直接写相对路径,和模块的别名(在resolve.alias 里设置)不同,需要在 resolveLoader.alias 里设置 loader 的别名:
resolveLoader: { alias: { "seajs-loader": path.resolve( __dirname, "./web_modules/seajs-loader.js" )
}
}
如果你的项目有引用根路径上级的模块(依赖路径在根路径之上),可能会出现找不到 loader 的情况,需要在 resolveLoader.root 中手动指定 loader 的默认位置:
resolveLoader: { //指定默认的loader路径,否则依赖走到上游会找不到loader root: path.resolve( __dirname, './node_modules' )
}
三、关于全局模块/全局变量/环境变量:
如果习惯了使用全局模块,例如 jQuery 的 $,而不想每次都写 $ = require('jquery'), 可以使用 ProvidePlugin 插件:
plugins: [ new webpack.ProvidePlugin({
$: "jquery", jQuery: "jquery"
})
]
如果代码中有需要插入静态的全局变量,或者需要根据环境变量来区分的分支,可以使用 DefinePlugin 插件来插入静态环境变量,插入的变量在编译时将被处理:
plugins: [ new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify( options.dev ? 'development' : 'production' )
}, "__SERVER__": isServer ? true : false })]
编译前:
编译后 (假设为 development 环境):
这时已经可以通过静态分析得到不可达的部分(console.log('prod')),再过 Uglify 压缩无用的代码就会被清除掉:
四、关于公共文件打包配置:
如果是多入口页面的项目,多个 Entry 之间可能会有一些公共的lib(基础库等),这时候就要用到公共文件提取打包,提高缓存的使用率。手册中写的很明白使用 CommonsChunkPlugin 插件来处理。这个插件支持很多种传参和设置,我比较喜欢下面这种对象传递,这样可以指定生成多个包:
entry: {
a:"./a.js",
b:"./b.js",
common1:[ //以下库文件及其下游依赖都会被打到 common1 中
"./lib/common.js", "react", "react-dom", "redux", "react-redux", "redux-thunk", "react-router", "react-router-redux"
],
},
plugins:[ new webpack.optimize.CommonsChunkPlugin({ //可以指定多个 entryName,打出多个 common 包
names: ['common1'],
minChunks: Infinity
}),
}
生成文件:
查看图片
这时再在 a.js 或 b.js 及其依赖中引用 common1 包中包含的库时,将不会再被重复打包到各自的 bundle 中。(注意:bundle 在页面中的载入顺序为: common1 => a/b )
五、关于 DllPlugin (manifest):
DllPlugin 相比 commonsChunkPlugin 是纯粹分离的一种更独立的打包方式。名副其实,相当于独立把文件打成第三方库来使用。这种方式适合用来处理一些不常修改的第三方库(尤其大型的框架源码等),将其独立打包,只通过生成的 manifest 文件对其中的模块进行引用,不用在每次项目编译时都把这些内容一起再编译打包一遍。因为这些通常都是不会被修改的。
使用 DllPlugin 打包分两步,一步是使用 DllPlugin 对需要独立出来的库文件进行独立打包。这里是一个独立的 webpack 打包过程和配置:
例:
./config.dll.js
var webpack = require('webpack');var path = require('path');module.exports = {
entry:{
vendor: [ "react", "react-dom", "redux", "react-redux", "redux-thunk", "react-router", "react-router-redux" ]
},
output:{
filename:'[name].dll.js',
path:path.resolve( __dirname, './output/dll' ),
library:"[name]"
},
plugins:[ new webpack.DllPlugin({
path:path.resolve( __dirname, './output/dll/[name]-manifest.json'),
name:"[name]"
}), new webpack.optimize.UglifyJsPlugin({ minimize: true, output: {comments: false} })
]
}
单独打包
webpack --config=config.dll.js
打包后除了生成所谓的 Dll 库文件,还生成一个指出 Dll 文件中包含的模块列表的 manifest.json 文件。
查看图片
vendor.dll.js:
查看图片
vendor-manifest.json:
第二步,使用 DllReferencrPlugin 在项目中引用 Dll 库文件:
plugins:[ new webpack.DllReferencePlugin({
context:__dirname,
manifest: require( './output/dll/vendor-manifest.json' )
})
]
这样只要遇到在 manifest.json 文件中存在的模块,都不会再打包进入项目中,而是运行时到指明的 Dll 库中寻找(页面中