为什么我们要做三份 Webpack 配置文件
在知乎上我们常常会看到有同学发问:bat 等大型网站的前端工程是如何组织管理的?这的确是一个可以发散的很广的 q&a,我想如果要我回答这个问题,不如先从 webpack 配置说起。
时至今日,webpack 已经成为前端工程必备的基础工具之一,不仅被广泛用于前端工程发布前的打包,还在开发中担当本地前端资源服务器(assets server)、模块热更新(hot module replacement)、api proxy 等角色,结合 eslint 等代码检查工具,还可以实现在对源代码的严格校验检查。
正如上文中提到的,前端从开发到部署前都离不开 webpack 的参与,而 webpack 的默认配置文件只有一个,即 webpack.config.js,那么问题来了,开发期和部署前应该使用同一份 webpack 配置吗?答案肯定是否定的,既然 webpack.config.js 是一个 js 文件,我们当然可以在文件里写 javascript 业务逻辑,通过读取环境变量 node_env 来判断当前是在开发(dev)时还是最终的生产环境(production),然而很多同学习惯把这两者的配置都混写在根目录下的 webpack.config.js,通过很多零散的 if...else 来“临时”决定某一个 plugin 或者某一个 loader 的配置项,随着 loaders 和 plugins 的不断增加,久而久之 webpack.config.js 变得原来越隆长,代码的可读性和可维护性也大大下降。
我想通过本文来介绍一种用 3 个 js 文件来配置 webpack 的方法,这里借鉴了很多开源项目的配置,同时也结合了我们自己在开发中碰到的种种问题解决方案。
本文中提及的配置基于 webpack 2 或以上,建议使用 3.0 及以上版本
开发环境与生产环境的区别
开发环境
·node_env 为 development
·启用模块热更新(hot module replacement)
·额外的 webpack-dev-server 配置项,api proxy 配置项
·输出 sourcemap
生产环境
·node_env 为 production
· 将 react、jquery 等常用库设置为 external,直接采用 cdn 线上的版本
· 样式源文件(如 css、less、scss 等)需要通过 extracttextplugin 独立抽取成 css 文件
· 启用 post-css
· 启用 optimize-minimize(如 uglify 等)
·中大型的商业网站生产环境下,是绝对不能有 console.log() 的,所以要为 babel 配置remove console transform
这里需要说明的是因为开发环境下启用了 hot module replacement,为了让样式源文件的修改也同样能被热替换,不能使用 extracttextplugin,而转为随 js bundle 一起输出。
你需要三份配置文件
1. webpack.base.config.js
在 base 文件里,你需要将开发环境和生产环境中通用的配置集中放在这里:
const cleanwebpackplugin = require('clean-webpack-plugin'); const path = require('path'); const webpack = require('webpack'); // 配置常量 // 源代码的根目录(本地物理文件路径) const src_path = path.resolve('./src'); // 打包后的资源根目录(本地物理文件路径) const assets_build_path = path.resolve('./build'); // 资源根目录(可以是 cdn 上的绝对路径,或相对路径) const assets_public_path = '/assets/'; module.exports = { context: src_path, // 设置源代码的默认根路径 resolve: { extensions: ['.js', '.jsx'] // 同时支持 js 和 jsx }, entry: { // 注意 entry 中的路径都是相对于 src_path 的路径 vendor: './vendor', a: ['./entry-a'], b: ['./entry-b'], c: ['./entry-c'] }, output: { path: assets_build_path, publicpath: assets_public_path, filename: './[name].js' }, module: { rules: [ { enforce: 'pre', // eslint 优先级高于其他 js 相关的 loader test: /\.jsx?$/, exclude: /node_modules/, loader: 'eslint-loader' }, { test: /\.jsx?$/, exclude: /node_modules/, // 建议把 babel 的运行时配置放在 .babelrc 里,从而与 eslint-loader 等共享配置 loader: 'babel-loader' }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 8192, name: 'images/[name].[ext]' } } ] }, { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: [ { loader: 'url-loader', options: { limit: 8192, mimetype: 'application/font-woff', name: 'fonts/[name].[ext]' } } ] }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: [ { loader: 'file-loader', options: { limit: 8192, mimetype: 'application/font-woff', name: 'fonts/[name].[ext]' } } ] } ] }, plugins: [ // 每次打包前,先清空原来目录中的内容 new cleanwebpackplugin([assets_build_path], { verbose: false }), // 启用 commonchunkplugin new webpack.optimize.commonschunkplugin({ names: 'vendor', minchunks: infinity }) ] };
2. webpack.dev.config.js
这是用于开发环境的 webpack 配置,继承自 base:
const webpack = require('webpack'); // 读取同一目录下的 base config const config = require('./webpack.base.config'); // 添加 webpack-dev-server 相关的配置项 config.devserver = { contentbase: './', publicpath: '/assets/' }; // 有关 webpack 的 api 本地代理,另请参考 https://webpack.github.io/docs/webpack-dev-server.html#proxy config.module.rules.push( { test: /\.less$/, use: [ 'style-loader', 'css-loader', 'less-loader' ], exclude: /node_modules/ } ); // 真实场景中,react、jquery 等优先走全站的 cdn,所以要放在 externals 中 config.externals = { react: 'react', 'react-dom': 'reactdom' }; // 添加 sourcemap 支持 config.plugins.push( new webpack.sourcemapdevtoolplugin({ filename: '[file].map', exclude: ['vendor.js'] // vendor 通常不需要 sourcemap }) ); // hot module replacement object.keys(config.entry).foreach((key) => { // 这里有一个私有的约定,如果 entry 是一个数组,则证明它需要被 hot module replace if (array.isarray(config.entry[key])) { config.entry[key].unshift( 'webpack-dev-server/client?http://0.0.0.0:8080', 'webpack/hot/only-dev-server' ); } }); config.plugins.push( new webpack.hotmodulereplacementplugin() ); module.exports = config;
3. webpack.config.js
这是用于生产环境的 webpack 配置,同样继承自 base:
const webpack = require('webpack'); const extracttextplugin = require('extract-text-webpack-plugin'); // 读取同一目录下的 base config const config = require('./webpack.base.config'); config.module.rules.push( { test: /\.less$/, use: extracttextplugin.extract( { use: [ 'css-loader', 'less-loader' ], fallback: 'style-loader' } ), exclude: /node_modules/ } ); config.plugins.push( // 官方文档推荐使用下面的插件确保 node_env new webpack.defineplugin({ 'process.env.node_env': json.stringify(process.env.node_env || 'production') }), // 启动 minify new webpack.loaderoptionsplugin({ minimize: true }), // 抽取 css 文件 new extracttextplugin({ filename: '[name].css', allchunks: true, ignoreorder: true }) ); module.exports = config;
现在在你的工程文件夹里应该已经有三个 webpack 配置文件,它们分别是:
· webpack.base.config.js
· webpack.dev.config.js
· webpack.config.js
最后,你还需要在 package.json 里添加相应的配置:
{ ... "scripts": { "build": "webpack --optimize-minimize", "dev": "webpack-dev-server --config webpack.dev.config.js", "start": "npm run dev" // 或添加你自己的 start 逻辑 }, ... }
和很多项目一样,在开发环境下的时候,你需要使用 npm run dev 来启动,而在生产环境中,则用 npm run build 来发布。
题外话,在真实场景中,我们不会直接使用 webpack-dev-server,而采用 express + webpack/webpack-dev-middleware,配置方法与上面所述的完全相同。
关于专栏
如果你喜欢这篇文章,就请关注我的专栏在这里我们一起聊一聊前端技术和前端工程。
关于作者
henry,10 岁开始学习计算机编程,高二暑假获得江苏省青少年信息奥林匹克一等奖。2000 年开始自学 javascript 及网页制作,2006 年起正式开始从事前端开发工作,从此一干就是 10 多年。加入阿里巴巴前,曾在 sap 中国研究院担任智慧交通大数据产品经理。
github:magiccube (henry li)
小结
前端从开发到部署前都离不开 webpack 的参与,本文结合了我们自己在开发中碰到的种种问题解决方案,同时借鉴了很多开源项目的配置来介绍一种用 3 个 js 文件来配置 webpack 的方法。关于本文如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!