Webpack4學習筆記(五)——分離production和development環境
本文ofollow,noindex">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預設的配置是無法滿足我們的需求的,我們希望對於不同的環境進行不同的配置,此時一種常用的做法是維護一個作用於所有環境的公有配置檔案,然後針對每個環境再建立單獨的配置檔案,環境配置檔案通過
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有多種風格 配置,不同風格各有利弊,對於不同的環境推薦配置如下:
- 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官方文件 。
配置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;