本文 GitHub 代码.
通常来讲, production 环境与 development 的配置有不一样的地方, 比如 production 环境要求更小的代码量以加快网页的加载速度, 因此通常都会对源代码进行压缩, 比如使用 UglifyJsPlugin 插件等; 而对于 development 环境来说, 我们希望有热加载 (HMR) 功能等.
webpack4 默认支持 mode 参数用于区分 production 环境和 development 环境. 由于 2 个环境的配置有相同部分又有差异部分, 因此通常的做法是提供 3 个 webpack 配置文件, 一个是公有的配置文件, 一个是针对 production 的配置文件, 一个针对 development, 后两个配置文件通过 webpack-merge 引入公有的配置文件.
在默认情况下, Webpack4 已经对两种 mode 分别有不同的配置了, 比较明显的不同便是 production 环境引入了 UglifyJsPlugin 插件, 而 development 没有.
具体来讲, webpack 在默认情况下对 production 和 development 有以下不同配置:
模式 | 默认配置 |
---|---|
production | 通过 DefinePlugin 设置 process.env.NODE_ENV=production。启用 FlagDependencyUsagePlugin、FlagIncludedChunksPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、OccurrenceOrderPlugin、SideEffectsFlagPlugin 和 UglifyJsPlugin 插件。 |
development | 通过 DefinePlugin 设置 process.env.NODE_ENV=development。启用 NamedChunksPlugin 和 NamedModulesPlugin 插件。 |
分离配置文件
多数时候, webpack 默认的配置是无法满足我们的需求的, 我们希望对于不同的环境进行不同的配置, 此时一种常用的做法是维护一个作用于所有环境的公有配置文件, 然后针对每个环境再创建单独的配置文件, 环境配置文件通过 https://www.npmjs.com/package/webpack-merge 引用公有配置文件中的内容.
创建 3 个配置文件:
- webpack.base.conf.JS (公有配置文件)
- webpack.prod.conf.JS(production 环境配置文件)
- webpack.dev.conf.JS (development 环境配置文件)
创建 webpack.base.conf.JS 如下:
- const path = require('path');
- var htmlWebpackPlugin = require('html-webpack-plugin');
- const CleanWebpackPlugin = require('clean-webpack-plugin');
- const webpack = require('webpack');
- function resolve(dir) {
- return path.join(__dirname, '..', dir)
- }
- module.exports = {
- entry: {
- 'index': './src/index.js',
- },
- output: {
- filename: '[name].[contenthash].js',
- chunkFilename: '[name].[contenthash].js',
- path: path.resolve(__dirname, 'distribution')
- },
- module: {
- rules: [
- {
- test: /\.JS$/,
- loader: 'babel-loader',
- include: [resolve('src')],
- exclude: [resolve('node_modules')]
- },
- {
- test: /\.CSS$/,
- use: [
- 'style-loader',
- {
- loader: 'css-loader',
- options: {
- sourceMap: true,
- modules: true,
- localIdentName: '[name]---[local]---[hash:base64:5]'
- }
- }
- ]
- }
- ]
- },
- plugins: [
- new CleanWebpackPlugin(['distribution']),
- new HtmlWebpackPlugin({
- template: './src/index.html',
- filename: 'index.html'
- })
- ]
- };
webpack.prod.conf.JS 与 webpack.dev.conf.JS 使用 webpack-merge 引用 webpack.base.conf.JS, 以 webpack.dev.conf.JS 为例:
- const merge = require('webpack-merge');
- const baseWebpackConfig = require('./webpack.base.conf');
- const webpackConfig = merge(baseWebpackConfig, {
- mode:'development'
- //environment specific config goes here
- });
- module.exports = webpackConfig;
此时, 我们通过配置 mode 参数声明当前配置所处的环境. 另外, 还需要修改一下 package.JSON 中的 scripts:
- ...
- "scripts": {
- "build": "webpack --config webpack.prod.conf.js",
- "start": "webpack-dev-server --open --config webpack.dev.conf.js"
- },
- ...
- source map
source map 有多种风格 https://webpack.js.org/configuration/devtool/ 配置, 不同风格各有利弊, 对于不同的环境推荐配置如下:
- development:inline-source-map
- production:source-map
配置 webpack.dev.conf.JS:
- ...
- const webpackConfig = merge(baseWebpackConfig, {
- mode:'development',
- devtool: 'inline-source-map'
- //environment specific config goes here
- });
- ...
配置 webpack.prod.config.JS 如下:
- ...
- const webpackConfig = merge(baseWebpackConfig, {
- mode:'production',
- devtool:'source-map'
- //environment specific config goes here
- });
- ...
本地调试
对于 development 环境来说, 需要配置本地调试服务器, 以及 HMR.
配置 webpack-dev-server
webpack-dev-server 用于本地调试, 配置 webpack.dev.conf.JS:
- ...
- const webpackConfig = merge(baseWebpackConfig, {
- mode:'development',
- devtool: 'inline-source-map',
- devServer: {
- contentBase: './distribution',
- inline:true,//do not use iframe for the page, true is default
- open: true,//open browser after dev server starts, true is default
- port: 8080,//8080 is default
- proxy: {//proxy backend API
- '/api': 'http://localhost:3000'
- }
- }
- //environment specific config goes here
- });
- ...
devServer 下的很多配置项都可以通过命令行参数的形式替代, 具体请参考 webpack-dev-server 官方文档 https://webpack.js.org/configuration/dev-server/ .
配置 HMR
在 webpack.dev.conf.JS, 在其中加入 HotModuleReplacementPlugin 插件:
- ...
- plugins: [
- new webpack.HotModuleReplacementPlugin()
- ]
- ...
另外, 需要在 devServer 中配置 hot:true:
- devServer: {
- contentBase: './distribution',
- inline: true,//do not use iframe for the page, true is default
- open: true,//open browser after dev server starts
- port: 8080,//8080 is default
- proxy: {//proxy backend API
- '/api': 'http://localhost:3000'
- },
- hot: true
- },
此时报错:
Cannot use [chunkhash] or [contenthash] for chunk in '[name].[contenthash].js' (use [hash] instead)
解决方法: 在 webpack.dev.conf.JS 中重新配置 output:
- ...
- output: {
- filename: '[name].js',
- chunkFilename: '[name].js',
- },
- ...
代码分割
production 环境通常需要进行代码分割, 而 development 环境则没有这样的需求.
分割 JavaScript 代码
对 webpack 的运行时代码进行抽离:
- ...
- optimization: {
- runtimeChunk: {
- "name": "manifest"
- },
- ...
另外, 所有第三方库单独抽离:
- ...
- splitChunks: {
- cacheGroups: {
- default: false,
- vendors: false,
- vendor: {
- test: /[\\/]node_modules[\\/]/,
- name: 'vendors',
- chunks: 'all'
- }
- }
- }
- ...
为了支持缓存, 加入 HashedModuleIdsPlugin 插件:
- ...
- plugins: [
- new webpack.HashedModuleIdsPlugin()
- ]
- ...
分割 CSS 代码
当前, CSS 代码被 style-loader 直接添加到 JS 文件里面了, 在 production 环境中, 我们通常希望将 CSS 文件单独抽离出来, 此时可以采用 mini-CSS-extract-plugin 插件.
不过, 当前 CSS 的 loader 配置是放到 webpack.base.conf.JS 中的, 因此我们需要将其搬出到每个环境各自的配置文件中, 此时 webpack.base.conf.JS 文件如下:
- const path = require('path');
- var HtmlWebpackPlugin = require('html-webpack-plugin');
- const CleanWebpackPlugin = require('clean-webpack-plugin');
- const webpack = require('webpack');
- function resolve(dir) {
- return path.join(__dirname, '..', dir)
- }
- module.exports = {
- entry: {
- 'index': './src/index.js',
- },
- output: {
- filename: '[name].[contenthash].js',
- chunkFilename: '[name].[contenthash].js',
- path: path.resolve(__dirname, 'distribution')
- },
- module: {
- rules: [
- {
- test: /\.JS$/,
- loader: 'babel-loader',
- include: [resolve('src')],
- exclude: [resolve('node_modules')]
- }
- ]
- },
- plugins: [
- new CleanWebpackPlugin(['distribution']),
- new HtmlWebpackPlugin({
- template: './src/index.html',
- filename: 'index.html'
- })
- ]
- };
对于 production 环境, 我们将使用 MiniCssExtractPlugin.loader 替换 style-loader:
- ...
- module: {
- rules: [
- {
- test: /\.CSS$/,
- use: [
- MiniCssExtractPlugin.loader,
- {
- loader: 'css-loader',
- options: {
- sourceMap: true,
- modules: true,
- localIdentName: '[name]---[local]---[hash:base64:5]'
- }
- }
- ]
- }
- ]
- }
- ...
另外需要配置 MiniCssExtractPlugin 插件:
- ...
- new MiniCssExtractPlugin({
- filename: "[name].[contenthash].css",
- chunkFilename: "[name].[contenthash].css"
- })
- ...
还需要将 CSS 文件分割到单独的 CSS chunk 中:
- ...
- splitChunks: {
- cacheGroups: {
- default: false,
- vendors: false,
- vendor: {
- test: /[\\/]node_modules[\\/]/,
- name: 'vendors',
- chunks: 'all'
- },
- styles: {
- name: 'styles',
- test: /\.CSS$/,
- chunks: 'all',
- enforce: true
- }
- }
- }
- ...
对于 development 环境, 原封不动地将 webpack.base.conf.JS 中的 CSS module 配置搬过来, 此时的 webpack.dev.conf.JS 文件为:
- const merge = require('webpack-merge');
- const webpack = require('webpack');
- const baseWebpackConfig = require('./webpack.base.conf');
- const webpackConfig = merge(baseWebpackConfig, {
- //environment specific config goes here
- mode: 'development',
- output: {
- filename: '[name].js',
- chunkFilename: '[name].js'
- },
- devtool: 'inline-source-map',
- devServer: {
- contentBase: './distribution',
- inline: true,//do not use iframe for the page, true is default
- open: true,//open browser after dev server starts
- port: 8080,//8080 is default
- proxy: {//proxy backend API
- '/api': 'http://localhost:3000'
- },
- hot: true
- },
- module: {
- rules: [
- {
- test: /\.CSS$/,
- use: [
- 'style-loader',
- {
- loader: 'css-loader',
- options: {
- sourceMap: true,
- modules: true,
- localIdentName: '[name]---[local]---[hash:base64:5]'
- }
- }
- ]
- }
- ]
- },
- plugins: [
- new webpack.HotModuleReplacementPlugin()
- ]
- });
- module.exports = webpackConfig;
来源: http://www.jianshu.com/p/5e6e63813fc3