七、开发和生产环境的构建配置差异
- 我们在日常的前端开发工作中,一般都会有两套构建环境:一套开发时使用,构建结果用于本地开发调试,不进行代码压缩,打印
debug
信息,包含sourcemap
文件 - 另外一套构建后的结果是直接应用于线上的,即代码都是压缩后,运行时不打印
debug
信息,静态文件不包括sourcemap
的。有的时候可能还需要多一套测试环境,在运行时直接进行请求mock
等工作 webpack 4.x
版本引入了mode
的概念,在运行webpack
时需要指定使用production
或development
两个mode
其中一个,这个功能也就是我们所需要的运行两套构建环境的能力。
7.1 在配置文件中区分 mode
之前我们的配置文件都是直接对外暴露一个 JS
对象,这种方式暂时没有办法获取到 webpack
的 mode
参数,我们需要更换一种方式来处理配置。根据官方的文档多种配置类型,配置文件可以对外暴露一个函数,因此我们可以这样做
module.exports = (env, argv) => ({ // ... 其他配置 optimization: { minimize: false, // 使用 argv 来获取 mode 参数的值 minimizer: argv.mode === 'production' ? [ new UglifyJsPlugin({ /* 你自己的配置 */ }), // 仅在我们要自定义压缩配置时才需要这么做 // mode 为 production 时 webpack 会默认使用压缩 JS 的 plugin ] : [], }, })...
这样获取 mode
之后,我们就能够区分不同的构建环境,然后根据不同环境再对特殊的 loader
或 plugin
做额外的配置就可以了
- 以上是
webpack 4.x
的做法,由于有了mode
参数,区分环境变得简单了。不过在当前业界,估计还是使用webpack 3.x
版本的居多,所以这里也简单介绍一下3.x
如何区分环境
webpack
的运行时环境是 Node.js
,我们可以通过 Node.js
提供的机制给要运行的 webpack
程序传递环境变量,来控制不同环境下的构建行为。例如,我们在 npm
中的 scripts
字段添加一个用于生产环境的构建命令…
{ "scripts": { "build": "NODE_ENV=production webpack", "develop": "NODE_ENV=development webpack-dev-server" } }...
然后在 webpack.config.js
文件中可以通过 process.env.NODE_ENV
来获取命令传入的环境变量
const config = { // ... webpack 配置 } if (process.env.NODE_ENV === 'production') { // 生产环境需要做的事情,如使用代码压缩插件等 config.plugins.push(new UglifyJsPlugin()) } module.exports = config...
7.2 运行时的环境变量
我们使用 webpack 时传递的 mode 参数,是可以在我们的应用代码运行时,通过 process.env.NODE_ENV
这个变量获取的。这样方便我们在运行时判断当前执行的构建环境,使用最多的场景莫过于控制是否打印 debug
信息…
- 下面这个简单的例子,在应用开发的代码中实现一个简单的
console
打印封装
export default function log(...args) { if (process.env.NODE_ENV === 'development' && console && console.log) { console.log.apply(console, args) } }...
同样,以上是 webpack 4.x
的做法,下面简单介绍一下 3.x
版本应该如何实现。这里需要用到 DefinePlugin
插件,它可以帮助我们在构建时给运行时定义变量,那么我们只要在前面 webpack 3.x
版本区分构建环境的例子的基础上,再使用 DefinePlugin
添加环境变量即可影响到运行时的代码…
module.exports = { // ... // webpack 的配置 plugins: [ new webpack.DefinePlugin({ // webpack 3.x 的 process.env.NODE_ENV 是通过手动在命令行中指定 NODE_ENV=... 的方式来传递的 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), }), ], }...
7.3 常见的环境差异配置
常见的 webpack 构建差异配置
- 生产环境可能需要分离
CSS
成单独的文件,以便多个页面共享同一个CSS
文件 - 生产环境需要压缩
HTML/CSS/JS
代码 - 生产环境需要压缩图片
- 开发环境需要生成
sourcemap
文件 - 开发环境需要打印
debug
信息 - 开发环境需要
live reload
或者hot reload
的功能…
webpack 4.x
的 mode
已经提供了上述差异配置的大部分功能, mode
为 production
时默认使用 JS
代码压缩,而 mode
为 development
时默认启用 hot
reload
,等等。这样让我们的配置更为简洁,我们只需要针对特别使用的 loader
和 plugin
做区分配置就可以了…
webpack 3.x
版本还是只能自己动手修改配置来满足大部分环境差异需求,所以如果你要开始一个新的项目,建议直接使用webpack 4.x
版本
7.4 拆分配置
前面我们列出了几个环境差异配置,可能这些构建需求就已经有点多了,会让整个 webpack
的配置变得复杂,尤其是有着大量环境变量判断的配置。我们可以把 webpack
的配置按照不同的环境拆分成多个文件,运行时直接根据环境变量加载对应的配置即可。基本的划分如下…
webpack.base.js
:基础部分,即多个文件中共享的配置webpack.development.js
:开发环境使用的配置webpack.production.js
:生产环境使用的配置webpack.test.js
:测试环境使用的配置…
如何处理这样的配置拆分
首先我们要明白,对于 webpack
的配置,其实是对外暴露一个 JS
对象,所以对于这个对象,我们都可以用 JS
代码来修改它,例如
const config = { // ... webpack 配置 } // 我们可以修改这个 config 来调整配置,例如添加一个新的插件 config.plugins.push(new YourPlugin()); module.exports = config;...
因此,只要有一个工具能比较智能地合并多个配置对象,我们就可以很轻松地拆分 webpack 配置,然后通过判断环境变量,使用工具将对应环境的多个配置对象整合后提供给 webpack 使用。这个工具就是 webpack-merge
- 我们的 webpack 配置基础部分,即
webpack.base.js
应该大致是这样的
module.exports = { entry: '...', output: { // ... }, resolve: { // ... }, module: { // 这里是一个简单的例子,后面介绍 API 时会用到 rules: [ { test: /\.js$/, use: ['babel'], }, ], // ... }, plugins: [ // ... ], }...
然后 webpack.development.js
需要添加 loader
或 plugin
,就可以使用 webpack-merge
的 API
,例如
const { smart } = require('webpack-merge') const webpack = require('webpack') const base = require('./webpack.base.js') module.exports = smart(base, { module: { rules: [ // 用 smart API,当这里的匹配规则相同且 use 值都是数组时,smart 会识别后处理 // 和上述 base 配置合并后,这里会是 { test: /\.js$/, use: ['babel', 'coffee'] } // 如果这里 use 的值用的是字符串或者对象的话,那么会替换掉原本的规则 use 的值 { test: /\.js$/, use: ['coffee'], }, // ... ], }, plugins: [ // plugins 这里的数组会和 base 中的 plugins 数组进行合并 new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), }), ], })...
可见 webpack-merge
提供的 smart
方法,可以帮助我们更加轻松地处理 loader
配置的合并。 webpack-merge
还有其他 API
可以用于自定义合并行为 https://github.com/survivejs/webpack-merge
7.5 完整代码
webpack.config.js
module.exports = function(env, argv) { return argv.mode === 'production' ? require('./configs/webpack.production') : require('./configs/webpack.development') }
configs/webpack.base.js
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js', }, module: { rules: [ { test: /\.jsx?/, include: [ path.resolve(__dirname, '../src'), ], use: 'babel-loader', }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'file-loader' }, ], }, ], }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', // 配置输出文件名和路径 template: 'src/index.html', // 配置文件模板 }), ], }
configs/webpack.development.js
const webpack = require('webpack') const merge = require('webpack-merge') const baseConfig = require('./webpack.base') const config = merge.smart(baseConfig, { module: { rules: [ { enforce: 'pre', test: /\.jsx?$/, exclude: /node_modules/, loader: "eslint-loader", }, { test: /\.less$/, use: [ 'style-loader', 'css-loader', 'less-loader' ], }, ], }, devServer: { port: '1234', before(app){ app.get('/api/test.json', function(req, res) { res.json({ code: 200, message: 'hello world' }) }) }, }, }) config.plugins.push( new webpack.DefinePlugin({ __DEV__: JSON.stringify(true), }) ) module.exports = config
configs/webpack.production.js
const merge = require('webpack-merge') const ExtractTextPlugin = require('extract-text-webpack-plugin') const baseConfig = require('./webpack.base') const config = merge.smart(baseConfig, { module: { rules: [ { test: /\.less$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ { loader: 'css-loader', options: { minimize: true } }, 'less-loader', ], }), }, ], } }) config.plugins.push(new ExtractTextPlugin('[name].css')) module.exports = config
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论