## 1. 前言
以前本系列教程的其中一篇文章 [13. 生產環境 vs 開發環境](https://www.rails365.net/articles/webpack-3-ling-ji-chu-ru-men-jiao-cheng-13-sheng-chan-huan-jing-vs-kai-fa-huan-jing) 有寫過如何處理開發和線上環境的配置的差異性問題,之前我們是用一個環境變量 `NODE_ENV=production`,這個環境變量是放在命令行中,作為命令行的一部分,比如 `NODE_ENV=production webpack -p`,傳入到配置文件`webpack.config.js` 中,由它通過 `process.env.NODE_ENV` 這樣來引用,從而來判斷是開發環境或生產環境。
我們始終使用的是同一個配置文件,即 `webpack.config.js`,區別不同的環境是通過變量,這節我們來講下比較普遍使用的作法,也是官方推薦的作法,來實現配置文件的分離。
首先要明白的一點是,為什么要分離成不同的環境配置文件呢?主要是開發和生產環境的差異性比較大,比如我們開發環境需要一個 `webpack-dev-server` 而生產環境不需要,生產環境只需要保證編譯出來的文件體積足夠小,性能足夠好就可以,開發環境也不需要壓縮文件,不壓縮反而能看得清楚編譯后的源碼,而生產環境就要保證體積小,所以要壓縮編譯后的文件。
之前我們也寫了不少配置,現在我們就把它們整理一遍,主要是參照官方的這一篇文檔: [生產環境構建](https://doc.webpack-china.org/guides/production/),我也把這次的提交放到 [github](https://github.com/hfpp2012/hello-webpack/commit/f437da8069fed37bc934ee42e598e0e5ccaa2a5d) 上了,有興趣的可以直接來看。
我們根據文檔把這個操作實踐一遍,這邊主要是貼代碼加一些簡單的講解。
## 2. 操作
首先把 `webpack.config.js` 一分為二,開發環境的配置文件叫 `webpack.dev.js`,而生產環境的叫 `webpack.prod.js`,除此之外,還要多加一個文件,就是把他們共同的部分抽出來,單獨成一個文件,叫 `webpack.common.js`,這樣 `webpack.dev.js` 和 `webpack.prod.js` 就不會有太多相同或重復的內容,只要寫出他們不同的部分就好了,這樣也好維護啊。
```
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js
```
抽出來相同的內容為 `webpack.common.js` 再加上差異化的部分 `webpack.dev.js` 和 `webpack.prod.js`,那總要把它們合起來才是可讀取的真正的配置文件,如何合起來呢,官方提供了一個工具:webpack-merge。
首先來安裝這個工具。
``` bash
$ npm install --save-dev webpack-merge
```
接下來我就要來貼代碼啦,貼代碼之前,先列一下這次的一些改動:
1. 去掉了 HMR,之前臨時為了 HMR 而改的 [name].[hash].js 也改回來了 [name].[chunkhash].js,可以查看回這篇文章 [12. 如何使用模塊熱替換 HMR 來處理 CSS](https://www.rails365.net/articles/webpack-3-ling-ji-chu-ru-men-jiao-cheng-12-ru-he-shi-yong-mo-kuai-re-ti-huan-hmr-lai-chu-li-css)
2. 用了 DefinePlugin 這個插件 來定義全局變量,參見下面的 webpack.prod.js 文件,關于它的用法可以看這篇文章 [webpack.DefinePlugin使用介紹](https://juejin.im/post/5868985461ff4b0057794959)。
3. 其他一些細微的調整,比如 sass-loader 那部分,還有 svg 文件的處理等。
4. 開發環境使用了 `devtool: 'inline-source-map'`,編譯出來的 `bundle` 文件較大。
5. css 使用 contenthash 而不是 chunkhash,文件內容一改變,文件名才改變。
開始貼代碼。
**webpack.common.js**
``` javascript
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}),
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
minify: false,
hash: process.env.NODE_ENV === 'production',
excludeChunks: ['contact']
}),
new HtmlWebpackPlugin({
template: './src/contact.html',
filename: 'contact.html',
minify: false,
hash: process.env.NODE_ENV === 'production',
chunks: ['contact']
}),
new ExtractTextPlugin({
filename: '[name].[contenthash].css',
disable: false,
}),
],
module: {
rules: [
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader?sourceMap', 'sass-loader?sourceMap']
})
},
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test: /\.pug$/, loader: ['raw-loader', 'pug-html-loader'] },
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/'
}
},
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
},
{
test: /\.html$/,
use: [{
loader: 'html-loader',
}]
},
{ test: /\.woff2?$/, loader: 'url-loader?limit=10000&name=[name].[ext]&outputPath=fonts/' },
{ test: /\.(ttf|eot)$/, loader: 'file-loader?name=[name].[ext]&outputPath=fonts/' },
{ test:/bootstrap-sass[\/\\]assets[\/\\]javascripts[\/\\]/, loader: 'imports-loader?jQuery=jquery' },
]
}
};
```
**webpack.dev.js**
``` javascript
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const bootstrapEntryPoints = require('./webpack.bootstrap.config')
module.exports = merge(common, {
entry: {
"app.bundle": './src/app.js',
"contact": './src/contact.js',
"bootstrap": bootstrapEntryPoints.dev
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
inline: true,
port: 9000,
open: true,
}
});
```
**webpack.prod.js**
``` javascript
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const bootstrapEntryPoints = require('./webpack.bootstrap.config')
const webpack = require('webpack');
module.exports = merge(common, {
entry: {
"app.bundle": './src/app.js',
"contact": './src/contact.js',
"bootstrap": bootstrapEntryPoints.prod
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
})
]
});
```
最后記得修改一下 `package.json` 文件。
``` json
{
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"prod": "webpack -p --config webpack.prod.js"
}
}
```
`npm run dev` 的運行結果如下:


`npm run prod` 的運行結果如下:


先說這么多吧。
- 0. 開始
- 1. 介紹
- 2. 安裝
- 3. 實現 hello world
- 4. webpack 的配置文件 webpack.config.js
- 5. 使用第一個 webpack 插件 html-webpack-plugin
- 6. 使用 loader 處理 CSS 和 Sass
- 7. 初識 webpack-dev-server
- 8. 用 webpack 和 babel 配置 react 開發環境
- 9. 用 clean-webpack-plugin 來清除文件
- 10. 配置多個 HTML 文件
- 11. 如何使用 pug (jade) 作為 HTML 的模板
- 12. 如何使用模塊熱替換 HMR 來處理 CSS
- 13. 生產環境 vs 開發環境
- 14. 如何打包圖片
- 15. 加載和打包 Twitter Bootstrap 框架
- 16. 使用 ProvidePlugin 插件來處理像 jQuery 這樣的第三方包
- 17. 輕松通過兩個實例來理解 devtool: 'source-map' 是什么意思
- 18. 構建開發和生產環境-分離配置文件