生产环境构建
在本指南中,我们将深入一些最佳实践,并且使用工具,将网站或应用程序构建到生产环境中。
配置
开发环境(development)和生产环境(production)的构建目标差异很大。
在开发环境中,我们需要具有强大的、实时加载或模块热替换能力的 source map 和 localhost server。
在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。
在编写 webpack 配置文件时,为了遵循不重复原则(Don't repeat yourself - DRY),需要保留一个通用(common)配置。有了通用配置,我们就不必在特定环境的配置文件中重复相同的配置代码。
为了将这些配置合并在一起,我们使用 webpack-merge 的工具。
安装 webpack-merge:
npm install --save-dev webpack-merge
然后,将项目根目录下的 webpack.config.js(webpack 默认配置文件)删除,并新建三个配置文件:
- webpack.common.js(通用的 webpack 配置)
- webpack.dev.js(开发环境的webpack 配置)
- webpack.prod.js(生产环境的webpack 配置)
它们的具体内容如下:
webpack.common.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js'
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Production'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
}
});
webpack.prod.js
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
plugins: [
new UglifyJSPlugin()
]
});
NPM Scripts
现在,我们修改项目目录中的 package.json 文件,来设定 Scripts 的内容(即设置 npm script 脚本,方便快速运行命令)。
- 开发环境脚本:设定 npm start 为开发环境脚本,并在其中使用 webpack-dev-server。
- 生产环境脚本:设定 npm run build 为生产环境脚本。
package.json
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --open --mode development --config webpack.dev.js",
"build": "webpack --mode production --config webpack.prod.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"clean-webpack-plugin": "^0.1.18",
"css-loader": "^0.28.10",
"csv-loader": "^2.1.1",
"express": "^4.16.2",
"file-loader": "^1.1.9",
"html-webpack-plugin": "github:jantimon/html-webpack-plugin",
"style-loader": "^0.20.2",
"uglifyjs-webpack-plugin": "^1.2.2",
"webpack": "^4.0.0",
"webpack-cli": "^2.0.9",
"webpack-dev-middleware": "^2.0.6",
"webpack-dev-server": "^3.0.0",
"webpack-merge": "^4.1.2",
"xml-loader": "^1.2.1"
},
"dependencies": {
"lodash": "^4.17.5"
}
}
现在,分别运行 npm start 和 npm run build,简单对比一下变化。下面,我们来继续添加一些生产环境的配置。
Minification(压缩)
虽然,UglifyJSPlugin 是代码压缩方面比较好的选择,但是还有一些其他好的选择。以下有几个同样很受欢迎的插件:
- BabelMinifyWebpackPlugin
- ClosureCompilerPlugin
source map
鼓励你在生产环境中启用 source map,因为它对调试源码和运行基准测试(benchmark tests)很有帮助。
对于 devtool 选项,在开发环境推荐用 inline-source-map,而在生产环境推荐用 source-map。
修改用于生产环境的 webpack 配置文件 webpack.prod.js。
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
devtool: 'source-map',
plugins: [
new UglifyJSPlugin({
sourceMap: true
})
]
});
注意: 避免在生产环境的 webpack 配置文件中使用 inline-*** 和 eval-***,因为它们会增加 bundle 的大小,并降低整体性能。
指定环境
许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。例如,当不处于生产环境中时,某些 library 为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。
当 process.env.NODE_ENV = 'production' 时,一些 library 可能针对会具体的环境进行代码优化。
我们可以使用 webpack 内置的 DefinePlugin 为所有的依赖,定义 process.env.NODE_ENV 这个变量。
webpack.prod.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
devtool: 'source-map',
plugins: [
new UglifyJSPlugin({
sourceMap: true
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
});
技术上讲,NODE_ENV 是一个由 Node.js 暴露给执行脚本的系统环境变量。通常用于决定在开发环境与生产环境(dev-vs-prod)下,服务器工具、构建脚本和客户端 library 的行为。
如果你正在使用像 react 这样的 library,那么在添加 DefinePlugin 插件后,你应该看到 bundle 大小显著下降。
任何位于 /src 的本地代码都可以关联到 process.env.NODE_ENV 环境变量,所以以下检查也是有效的:
src/index.js
import { cube } from './math.js';
if (process.env.NODE_ENV !== 'production') {
console.log('Looks like we are in development mode!');
}
function component() {
var element = document.createElement('pre');
element.innerHTML = [
'Hello webpack!',
'5 cubed is equal to ' + cube(5)
].join('\n\n');
return element;
}
document.body.appendChild(component());
分离 CSS
对于 CSS 样式的处理,最好的做法是使用 ExtractTextPlugin 将 CSS 分离成单独的文件(和 JS bundle 分离开来),以便在生产环境中节省加载时间。
使用 ExtractTextPlugin(extract-text-webpack-plugin 插件),分离出来的 CSS bundle,是一个独立的 CSS 文件,可以和 JS bundle 并行加载。
首先,需要安装 extract-text-webpack-plugin 插件
npm install --save-dev extract-text-webpack-plugin
修改 webpack.prod.js 配置文件。
const webpack = require('webpack');
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
devtool: 'source-map',
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new UglifyJSPlugin({
sourceMap: true
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new ExtractTextPlugin("styles.css")
]
});
然后,在 src 目录中,新增 test.css 文件。
.temp{
color: red;
}
在 src/index.js 入口文件中引入该 CSS 文件。
src/index.js
import { cube } from './math.js';
import './test.css';
if (process.env.NODE_ENV !== 'production') {
console.log('Looks like we are in development mode!');
}
function component() {
var element = document.createElement('pre');
element.innerHTML = [
'Hello webpack!',
'5 cubed is equal to ' + cube(5)
].join('\n\n');
element.classList.add('temp');
return element;
}
document.body.appendChild(component());
最后,运行 npm run build 进行构建。
如果没有报错,就说明分离 CSS 成功了。在 dist 输出目录中,生成了两个 bundle 文件(app.bundle.js 和 styles.css),这样就实现了分离 CSS。
说明: 使用 extract-text-webpack-plugin 插件,会将入口中引入的所有 css 文件,打包成一个独立的 CSS bundle,而不再是以前那种嵌入到 JS bundle 的方式了。
通过使用 ExtractTextPlugin 分离 CSS,是非常常用一个功能。