development、production拆分
根據檔案拆分
webpack打包時分為開發模式(development)和釋出模式(production),在前面使用命令引數做了簡單區分。
但這種方式區分在做定製化模式時有些不太方便,所以需要對兩種模式做徹底拆分。
之前介紹過,webpack可以使用 --config 引數指定配置檔案,所以可以不同模式使用不同配置檔案。
? 將webpack配置檔案放入一個指定的目錄,方便管理,
/buiild/config.js 是控制路徑的物件,在此進行管理, config.js 檔案位於 /build 下,所以所以 root 屬性則改為指向上級目錄
const path = require('path')
module.exports.config = {
root: path.join(__dirname, '../'),
}
此時可以在兩個配置檔案中設定做定製化配置項
webpack.dev.js
const path = require('path')
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// 引用config物件,因匯出時為 module.exports.config 所以在此使用 { config }匯入
const { config } = require('./config');
// browserslist環境變數
process.env.BROWSERSLIST_ENV = 'development'
const modules = {
mode:'development',
entry: path.join(config.root, 'src/index.js') ,
output: {
path: path.join(config.root, 'dist') ,
// dev模式下不需要快取
filename: '[name].js'
},
module:{
rules:[
{
// 所有的.js(x?)檔案都走babel-loader
test:/\.js(x?)$/,
include: path.join(config.root, 'src'),
loader: "babel-loader"
}
]
},
// dev不需要壓縮
optimization: {
minimize: false,
},
plugins: [
new HtmlWebpackPlugin({
// HTML的標題,
// template的title優先順序大於當前資料
title: 'my-cli',
// 輸出的html檔名稱
filename: 'index.html',
// 本地HTML模板檔案地址
template: path.join(config.root, 'src/index.html'),
// 引用JS檔案的目錄路徑
publicPath: './',
// 引用JS檔案的位置
// true或者body將打包後的js指令碼放入body元素下,head則將指令碼放到中
// 預設為true
inject: 'body',
// 載入js方式,值為defer/blocking
// 預設為blocking, 如果設定了defer,則在js引用標籤上加上此屬性,進行非同步載入
scriptLoading: 'blocking',
// 是否進行快取,預設為true,在開發環境可以設定成false
cache: false,
// 新增mate屬性
meta: {}
}),
new CleanWebpackPlugin({
// 是否假裝刪除檔案
// 如果為false則代表真實刪除,如果為true,則代表不刪除
dry: false,
// 是否將刪除日誌列印到控制檯 預設為false
verbose: true,
// 允許保留本次打包的檔案
// true為允許,false為不允許,保留本次打包結果,也就是會刪除本次打包的檔案
// 預設為true
protectWebpackAssets: true,
// 每次打包之前刪除匹配的檔案
cleanOnceBeforeBuildPatterns: ['**/*'],
// 每次打包之後刪除匹配的檔案
cleanAfterEveryBuildPatterns:["*.js"],
}),
new webpack.DefinePlugin({ "global_a": JSON.stringify("我是一個打包配置的全域性變數") }),
],
resolve: {
alias:{
// 設定路徑別名
'@': path.join(config.root, 'src'),
'~': path.join(config.root, 'src/assets'),
},
// 可互忽略的字尾
extensions:['.jsx', '.js', '.json'],
// 預設讀取的檔名
mainFiles:['index', 'main'],
}
}
// 使用node.js的匯出,將配置進行匯出
module.exports = modules
webpack.pro.js
const path = require('path')
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
// 引用config物件,因匯出時為 module.exports.config 所以在此使用 { config }匯入
const { config } = require('./config');
// browserslist環境變數
process.env.BROWSERSLIST_ENV = 'production'
const modules = {
mode: 'production',
entry: path.join(config.root, 'src/index.js'),
// prod需要快取js
output: {
path: path.join(config.root, 'dist') ,
filename: '[name]_[contenthash].js'
},
module:{
rules:[
{
// 所有的.js(x?)檔案都走babel-loader
test:/\.js(x?)$/,
include: path.join(config.root, 'src') ,
loader: "babel-loader"
}
]
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
// 指定壓縮的檔案
include: /\.js(\?.*)?$/i,
// 排除壓縮的檔案
// exclude:/\.js(\?.*)?$/i,
// 是否啟用多執行緒執行,預設為true,開啟,預設併發數量為os.cpus()-1
// 可以設定為false(不使用多執行緒)或者數值(併發數量)
parallel: true,
// 可以設定一個function,使用其它壓縮外掛覆蓋預設的壓縮外掛,預設為undefined,
minify: undefined,
// 是否將程式碼註釋提取到一個單獨的檔案。
// 屬性值:Boolean | String | RegExp | Function<(node, comment) -> Boolean|Object> | Object
// 預設為true, 只提取/^\**!|@preserve|@license|@cc_on/i註釋
// 感覺沒什麼特殊情況直接設定為false即可
extractComments: false,
// 壓縮時的選項設定
terserOptions: {
// 是否保留原始函式名稱,true代表保留,false即保留
// 此屬性對使用Function.prototype.name
// 預設為false
keep_fnames: false,
// 是否保留原始類名稱
keep_classnames: false,
// format和output是同一個屬性值,,名稱不一致,output不建議使用了,被放棄
// 指定壓縮格式。例如是否保留*註釋*,是否始終為*if*、*for*等設定大括號。
format: {
comments: false,
},
output: undefined,
// 是否支援IE8,預設不支援
ie8: false,
compress: {
// 是否使用預設配置項,這個屬性當只啟用指定某些選項時可以設定為false
defaults: false,
// 是否移除無法訪問的程式碼
dead_code: false,
// 是否優化只使用一次的變數
collapse_vars: true,
warnings: true,
// 是否刪除所有 console.*語句,預設為false,這個可以線上上設定為true
drop_console: false,
// 是否刪除所有debugger語句,預設為true
drop_debugger: true,
// 移除指定func,這個屬性假定函式沒有任何副作用,可以使用此屬性移除所有指定func
// pure_funcs: ['console.log'], //移除console
},
},
})
]
},
plugins: [
new HtmlWebpackPlugin({
// template的title優先順序大於當前資料
title:'my-cli',
// 檔名稱
// 模板路徑
template:path.join(__dirname, 'src/index.html'),
// 用於打包後引用指令碼時的路徑
publicPath:'./',
// 是否將打包的資源引用到當前HTML, false代表不引用
// true或者body將打包後的js指令碼放入body元素下,head則將指令碼放到中
// 預設為true
inject:'body',
// 載入js方式,值為defer/blocking
// 預設為blocking, 如果設定了defer,則在js引用標籤上加上此屬性,進行非同步載入
scriptLoading:'blocking',
// 是否進行快取,預設為true,在開發環境可以設定成false
cache:false,
// 新增mate屬性
meta:{}
}),
new CleanWebpackPlugin({
dry:false,
// 是否列印日誌到控制檯 預設為false
verbose: true,
// 允許保留本次打包的檔案
// true為允許,false為不允許,保留本次打包結果,也就是會刪除本次打包的檔案
// 預設為true
protectWebpackAssets:true,
// 每次打包之前刪除匹配的檔案
cleanOnceBeforeBuildPatterns:[],
// 每次打包之後刪除匹配的檔案
cleanAfterEveryBuildPatterns:["*.js"],
}),
new webpack.DefinePlugin({ "global_a": JSON.stringify("我是一個打包配置的全域性變數") }),
],
resolve: {
alias:{
// 設定路徑別名
'@': path.join(config.root, 'src'),
'~': path.join(config.root, 'src/assets')
},
// 可互忽略的字尾
extensions:['.jsx', '.js', '.json'],
// 預設讀取的檔名
mainFiles:['index', 'main'],
}
}
// 使用node.js的匯出,將配置進行匯出
module.exports = modules
webpack-merge
根據檔案拆分之後,發現兩個檔案中具有好多共同的配置資訊。這樣寫兩份反而不太方便管理了。
此時就需要一種能提供出一個 公共配置檔案模組(common) 和兩個定製化配置檔案。在定製化配置檔案中匯入 公共配置檔案模組(common) 並進行合併配置
webpack社群中提供了一個webpack-merge庫,就是允許我們對webpack配置屬性合併。
yarn add -D webpack-merge@5.7.3
抽出一個 webpack.common.js 檔案,將公共配置配置在此
const path = require('path')
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
// 引用config物件,因匯出時為 module.exports.config 所以在此使用 { config }匯入
const { config } = require('./config');
// isDev boolean
// 使用node。js的匯出,將配置進行匯出
module.exports = (isDev = true) => {
// 根據引數設定browserslist環境變數
process.env.BROWSERSLIST_ENV = isDev ? 'development' : 'production'
return {
// 入口檔案
// 字串形式
entry: path.join(config.root, 'src/index.js'),
// 根據isDev引數判斷是否快取JS
// 輸出檔案
output: {
// 輸出檔案的目錄地址
path: path.join(config.root, 'dist'),
// 輸出檔名稱,contenthash代表一種快取,只有檔案更改才會更新hash值,重新打包
filename: `[name]${isDev ? '' : '_[contenthash]'}.js`,
},
//devtool:false, //'eval'
module: {
rules: [
{
// 所有的.js(x?)檔案都走babel-loader
test: /\.js(x?)$/,
include: path.join(config.root, 'src'),
loader: "babel-loader"
},
]
},
optimization: {
// 根據當前模式進行判斷是否需要壓縮
minimize: !isDev,
minimizer: [
new TerserPlugin({
// 指定壓縮的檔案
include: /\.js(\?.*)?$/i,
// 排除壓縮的檔案
// exclude:/\.js(\?.*)?$/i,
// 是否啟用多執行緒執行,預設為true,開啟,預設併發數量為os.cpus()-1
// 可以設定為false(不使用多執行緒)或者數值(併發數量)
parallel: true,
// 可以設定一個function,使用其它壓縮外掛覆蓋預設的壓縮外掛,預設為undefined,
minify: undefined,
// 是否將程式碼註釋提取到一個單獨的檔案。
// 屬性值:Boolean | String | RegExp | Function<(node, comment) -> Boolean|Object> | Object
// 預設為true, 只提取/^\**!|@preserve|@license|@cc_on/i註釋
// 感覺沒什麼特殊情況直接設定為false即可
extractComments: false,
// 壓縮時的選項設定
terserOptions: {
// 是否保留原始函式名稱,true代表保留,false即保留
// 此屬性對使用Function.prototype.name
// 預設為false
keep_fnames: false,
// 是否保留原始類名稱
keep_classnames: false,
// format和output是同一個屬性值,,名稱不一致,output不建議使用了,被放棄
// 指定壓縮格式。例如是否保留*註釋*,是否始終為*if*、*for*等設定大括號。
format: {
comments: false,
},
output: undefined,
// 是否支援IE8,預設不支援
ie8: false,
compress: {
// 是否使用預設配置項,這個屬性當只啟用指定某些選項時可以設定為false
defaults: false,
// 是否移除無法訪問的程式碼
dead_code: false,
// 是否優化只使用一次的變數
collapse_vars: true,
warnings: true,
// 是否刪除所有 console.*語句,預設為false,這個可以線上上設定為true
drop_console: false,
// 是否刪除所有debugger語句,預設為true
drop_debugger: true,
// 移除指定func,這個屬性假定函式沒有任何副作用,可以使用此屬性移除所有指定func
// pure_funcs: ['console.log'], //移除console
},
},
})
]
},
plugins: [
new HtmlWebpackPlugin({
// HTML的標題,
// template的title優先順序大於當前資料
title: 'my-cli',
// 輸出的html檔名稱
filename: 'index.html',
// 本地HTML模板檔案地址
template: path.join(config.root, 'src/index.html'),
// 引用JS檔案的目錄路徑
publicPath: './',
// 引用JS檔案的位置
// true或者body將打包後的js指令碼放入body元素下,head則將指令碼放到中
// 預設為true
inject: 'body',
// 載入js方式,值為defer/blocking
// 預設為blocking, 如果設定了defer,則在js引用標籤上加上此屬性,進行非同步載入
scriptLoading: 'blocking',
// 是否進行快取,預設為true,在開發環境可以設定成false
cache: false,
// 新增mate屬性
meta: {}
}),
new CleanWebpackPlugin({
// 是否假裝刪除檔案
// 如果為false則代表真實刪除,如果為true,則代表不刪除
dry: false,
// 是否將刪除日誌列印到控制檯 預設為false
verbose: true,
// 允許保留本次打包的檔案
// true為允許,false為不允許,保留本次打包結果,也就是會刪除本次打包的檔案
// 預設為true
protectWebpackAssets: true,
// 每次打包之前刪除匹配的檔案
cleanOnceBeforeBuildPatterns: ['**/*'],
// 每次打包之後刪除匹配的檔案
cleanAfterEveryBuildPatterns:["*.js"],
}),
new webpack.DefinePlugin({ "global_a": JSON.stringify("我是一個打包配置的全域性變數") }),
],
resolve: {
alias: {
// 設定路徑別名
'@': path.join(config.root, 'src'),
'~': path.join(config.root, 'src/assets'),
},
// 可互忽略的字尾
extensions: ['.jsx', '.js', '.json'],
// 預設讀取的檔名
mainFiles: ['index', 'main'],
}
}
}
webpack.common.js 檔案匯出的是一個函式 ,函式引數是一個是一個boolean型別的isDev,判斷當前模式。將細小化的差異直接 webpack.common.js 檔案中。
例如: output.filename 中是否快取和 optimization.minimize 屬性
??? 在webpack.common.js內部根據isDev設定了browserslist使用的環境變數
然後,就可以在 webpack.dev.js 和 webpack.pro.js 檔案中使用webpack-merge 進行合併 webpack.common.js 檔案中模組
webpack.dev.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
// 使用node.js的匯出,將配置進行匯出
module.exports = merge([
common(true),
{
mode:'development',
}
])
webpack.pro.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
// 使用node.js的匯出,將配置進行匯出
module.exports = merge([
common(false),
{
mode:'production',
}
])
可以看到 程式碼中直接呼叫了webpack-merge提供的一個 merge 函式合併然後匯出。 merge 函式會將傳入的多個物件屬性進行合併,然後返回合併後物件。
merge 函式其實與 Object.assign() 功能一樣,對多個JS物件屬性進行合併。只不過 merge 功能較為強大一些。
??? webpack配置匯出的是一個JS物件,在程式碼中可以以任意形式組織此物件,只要最後進行匯出就行。
? merge函式引數可以使用陣列形式傳遞,並且支援可變引數。
總結
???
- 對開發模式(development)和釋出模式(production)進行拆分主要是為了更方便管理程式碼,其具體結構依照每個人編碼習慣具有差異性
- webpack-merge 是一個將多個JS物件屬性合併的庫,功能與 Object.assign() 一致,只是功能更為強大一些。
如果此篇對您有所幫助,在此求一個star。專案地址: OrcasTeam/my-cli
本文參考
本文依賴
package.json
{
"name": "my-cli",
"version": "1.0.0",
"main": "index.js",
"author": "mowenjinzhao<yanzhangshuai@126.com>",
"license": "MIT",
"devDependencies": {
"@babel/core": "7.13.1",
"@babel/plugin-transform-runtime": "7.13.7",
"@babel/preset-env": "7.13.5",
"@babel/preset-react": "7.12.13",
"@babel/runtime-corejs3": "7.13.7",
"babel-loader": "8.2.2",
"clean-webpack-plugin": "3.0.0",
"html-webpack-plugin": "5.2.0",
"webpack": "5.24.0",
"webpack-cli": "4.5.0",
"webpack-merge": "5.7.3"
},
"dependencies": {
"react": "17.0.1",
"react-dom": "17.0.1",
},
"scripts": {
"start": "webpack-dev-server --config build/webpack.dev.js",
"build": "webpack --config build/webpack.pro.js",
},
"browserslist": {
"development": [
"chrome > 75"
],
"production": [
"ie 9"
]
}
}