前言
其實整理下來,我發現webpack基礎的plugin大部分都是圍繞着檔案的壓縮,抽離等等進行處理。
loader則圍繞着這些檔案内容進行處理,比如把ts代碼轉化為js啊,把sass,less轉換為css啊,或者把es6+的文法轉換為低版本的文法啊等等。
然後由于項目越來越大,越來越複雜,用了越來越多的loader導緻打包速度變慢了,然後又加了一些用于優化的插件。然後webpack配置越來越複雜,丫整個一坨,更新個版本,之前用的某些插件或者loader還有可能會莫名其妙報錯。
一、通用環境
1、插件
1.1、HtmlWebpackPlugin
生成 html 檔案,并将打包生成的js,和css檔案,插入到該html中。
npm install --save-dev html-webpack-plugin
将 webpack 中entry配置的相關入口 chunk 和 extract-text-webpack-plugin抽取的 css 樣式 插入到該插件提供的template或者templateContent配置項指定的内容基礎上生成一個 html 檔案,具體插入方式是将樣式link插入到head元素中,script插入到head或者body中。
詳細的 options參考 傳送門
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(__dirname, '/index.html'),
minify: {
// 壓縮HTML檔案
removeComments: true, // 移除HTML中的注釋
collapseWhitespace: true, // 删除空白符與換行符
minifyCSS: true, // 壓縮内聯css
},
inject: true,
}),
]
多頁應用打包
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js',
login: './src/login.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:6].js',
},
//...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html', //打包後的檔案名
}),
new HtmlWebpackPlugin({
template: './public/login.html',
filename: 'login.html', //打包後的檔案名
}),
],
}
如果需要配置多個 HtmlWebpackPlugin,那麼 filename 字段不可預設,否則預設生成的都是 index.html。
但是有個問題,index.html 和 login.html 會發現,都同時引入了 index…js 和 login.js,通常這不是我們想要的,我們希望 index.html 中隻引入 index.js,login.html 隻引入 login.js。
HtmlWebpackPlugin 提供了一個 chunks 的參數,可以接受一個數組,配置此參數僅會将數組中指定的 js 引入到 html 檔案中。
module.exports = {
//...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html', //打包後的檔案名
chunks: ['index'],
}),
new HtmlWebpackPlugin({
template: './public/login.html',
filename: 'login.html', //打包後的檔案名
chunks: ['login'],
}),
],
}
1.2、CleanWebpackPlugin
npm install --save-dev clean-webpack-plugin
使用此插件,可以在每次打包之前,清理dist檔案夾。
由于過去每次不同的打包都會生成相應的檔案,導緻我們的 /dist 檔案夾相當雜亂。webpack 會生成檔案,然後将這些檔案放置在 /dist 檔案夾中,但是 webpack 無法追蹤到哪些檔案是實際在項目中用到的。通常,在每次建構前清理 /dist 檔案夾,是比較推薦的做法。
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
plugins: [
new CleanWebpackPlugin({
cleanAfterEveryBuildPatterns: ['dist'],
}),
]
1.3、ExtractTextPlugin
npm install --save-dev extract-text-webpack-plugin
将 css 單獨抽離出來,成生檔案,而非内聯。
該插件的主要是為了抽離 css 樣式,防止将樣式打包在 js 中引起頁面樣式加載錯亂的現象。
const ExtractTextPlugin = require('extract-text-webpack-plugin')
plugins: [
// 将css分離到/dist檔案夾下的css檔案夾中的index.css
new ExtractTextPlugin('css/index.css'),
]
在webpack4中有更好的替代者 mini-css-extract-plugin,具體可以參考本文目錄生産環境1.1
1.4、PurifyCSSPlugin
npm i -D purifycss-webpack purify-css
去除未被使用的css代碼
有時候我們 css 寫得多或者寫忘記了,會造成亢餘代碼,purifycss-webpack可以幫我們去除未被html頁面使用的css。
const path = require('path');
const glob = require('glob');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PurifyCSSPlugin = require('purifycss-webpack');
module.exports = {
entry: {...},
output: {...},
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: 'css-loader'
})
}
]
},
plugins: [
new ExtractTextPlugin('[name].[contenthash].css'),
// Make sure this is after ExtractTextPlugin!
new PurifyCSSPlugin({
// Give paths to parse for rules. These should be absolute!
paths: glob.sync(path.join(__dirname, 'app/*.html')),
})
]
};
1.5、UglifyJsPlugin
npm install uglifyjs-webpack-plugin --save-dev
uglifyjs 用于壓縮 js,支援檔案緩存,和多線程壓縮。
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new UglifyJsPlugin({
test: /\.js(\?.*)?$/i,
// 要處理的檔案
include: /\/includes/,
// 被排除的檔案
exclude: /\/excludes/,
// 判斷哪些 chunk 可以被壓縮(預設所有的 chunk 都會被壓縮)。
// 傳回值為 true 則會被壓縮,false 則不會被壓縮。
chunkFilter: (chunk) => {
// `vendor` 塊不壓縮
if (chunk.name === 'vendor') {
return false;
}
return true;
},
// 啟用檔案緩存。預設值為false。
// 預設的緩存目錄路徑:node_modules/.cache/uglifyjs-webpack-plugin。
// cache的值也可以設定為字元串類型,此時意為啟用檔案緩存并設定緩存目錄路徑。
// cache: 'path/to/cache'
cache: true,
// 啟用多程序并行運作。預設為false, 預設并發運作次數:os.cups().length - 1
// 該值設定為字元串時,表示啟用多程序并行運作,并設定并發運作的次數。
// parallel: 4
parallel: true,
// 使用源映射将錯誤資訊位置映射到子產品(這将會減慢編譯速度)
sourceMap: true,
// 壓縮選項配置,具體意思參考 https://github.com/mishoo/UglifyJS#minify-options
uglifyOptions: {
warnings: false,
parse: {},
compress: {},
mangle: true, // 注意 `mangle.properties` 的預設值是 `false`。
output: null,
toplevel: false,
nameCache: null,
ie8: false,
keep_fnames: false,
},
}),
],
},
};
還可以配置 extractComments 屬性提取注釋等,具體可以參考官方文檔 傳送門
1.6、CompressionPlugin
npm install compression-webpack-plugin --save-dev
可以把檔案進行GZip壓縮成更小的.gz檔案,優化首屏加載時間。
注意事項:
- 低版本浏覽器相容性,伺服器可以設定一些忽略規則忽略為浏覽器。
- 媒體檔案無需開啟:圖檔、音樂和視訊大多數都已壓縮過了。
為什麼可以優化首屏加載時間呢?基本原理:
- 浏覽器請求資源檔案時會自動帶一個Accept-Encoding的請求頭告訴伺服器支援的壓縮編碼類型,伺服器配置開啟gzip選項。
- 接收用戶端資源檔案請求,檢視請求頭Content-encoding支援的壓縮編碼格式,如果是包含gzip,那麼在每次響應資源請求之前進行gzip編碼壓縮後再響應傳回資源檔案(在響應頭會帶上Content-encoding: gzip)。
- 浏覽器接收到響應後檢視請求頭是否帶有Content-encoding:gzip,如果有進行對傳回的資源檔案進行解壓縮然後再進行解析渲染。
由于每次傳回請求需要壓縮檔案,增大了伺服器cpu壓力。但使用webpack提供的此插件,在打包後所有檔案已經被壓縮過了,伺服器查找到有與源檔案同名的.gz檔案就會直接讀取,不會主動壓縮。是以降低cpu了負載,也就不存在增加伺服器cpu壓力的情況了。
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
plugins: [
// 更多配置資訊可以參考連結 https://www.webpackjs.com/plugins/compression-webpack-plugin/
new CompressionPlugin({
// 比對檔案名
test: /\.js$|\.html$|\.css/,
// 對超過10kb的資料進行壓縮
threshold: 10240,
// 是否删除原檔案
deleteOriginalAssets: false,
}),
]
}
服務端啟用Gzip壓縮,參考 傳送門
1.7、DefinePlugin
DefinePlugin是webpack子產品自帶的,不需要安裝。
DefinePlugin 允許在 編譯時 建立配置的全局常量,這在需要區分開發模式與生産模式進行不同的操作時,非常有用。
例如,如果想在開發建構中進行日志記錄,而不在生産建構中進行,就可以定義一個全局常量去判斷是否記錄日志。
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('dev'),
}),
],
// 在代碼中可以直接使用
if (process.env.NODE_ENV !== 'production') {
console.log('Looks like we are in development mode!');
}
1.8、ProvidePlugin
ProvidePlugin是webpack子產品自帶的,不需要安裝。
自動加載子產品,而不必import或require它們。
每當identifier在代碼中被用到時,module都會自動加載,并用已加載identifier的導出内容填充module(或property,為了支援命名的導出内容)。
要導入ES2015子產品的預設導出,必須指定module的預設屬性。
// 預設子產品解析路徑為目前檔案夾(./**)和node_modules。
new webpack.ProvidePlugin({
identifier: 'module1',
// 或者指定具體屬性
identifier: ['module1', 'property1'],
// ...
});
// 或者指定完整路徑
new webpack.ProvidePlugin({
identifier: path.resolve(path.join(__dirname, 'src/module1'))
// ...
});
什麼意思呢?舉個栗子:
new webpack.ProvidePlugin({
$: 'jquery',
});
// 在任何代碼中就可以直接使用,而無需引入
$('#item')
1.9、DllPlugin和DllReferencePlugin
建議直接去看這篇文章: 傳送門。我剛開始看官方文檔的時候是懵逼的,直到看了這篇文章,才搞懂。
在使用webpack進行打包時候,對于依賴的第三方庫,比如vue,vuex等這些不會修改的依賴,我們可以讓它和我們自己編寫的代碼分開打包,這樣做的好處是每次更改我本地代碼的檔案的時候,webpack隻需要打包我項目本身的檔案代碼,而不會再去編譯第三方庫,那麼第三方庫在第一次打包的時候隻打包一次,以後隻要我們不更新第三方包的時候,那麼webpack就不會對這些庫去打包,這樣的可以快速的提高打包的速度。是以為了解決這個問題,DllPlugin 和 DllReferencePlugin插件就産生了。
DLLPlugin 這個插件是在一個額外獨立的webpack設定中建立一個隻有dll的bundle,也就是說我們在項目根目錄下除了有webpack.config.js,還會建立一個webpack.dll.config.js檔案。webpack.dll.config.js作用是把所有的第三方庫依賴打包到一個bundle的dll檔案裡面,還會生成一個名為 manifest.json清單檔案。
該manifest.json的作用是用來讓 DllReferencePlugin 映射到相關的依賴上去的。
DllReferencePlugin 插件是在webpack.config.js中使用的,該插件的作用是把剛剛在webpack.dll.config.js中打包生成的dll檔案引用到需要的預編譯的依賴上來。什麼意思呢?就是說在webpack.dll.config.js中打包後比如會生成 vendor.dll.js檔案和vendor-manifest.json檔案,vendor.dll.js檔案包含所有的第三方庫檔案,vendor-manifest.json檔案會包含所有庫代碼的一個索引,當在使用webpack.config.js檔案打包DllReferencePlugin插件的時候,會使用該DllReferencePlugin插件讀取vendor-manifest.json檔案,看看是否有該第三方庫。vendor-manifest.json檔案就是有一個第三方庫的一個映射而已。
是以說 第一次使用 webpack.dll.config.js 檔案會對第三方庫打包,打包完成後就不會再打包它了,然後每次運作 webpack.config.js檔案的時候,都會打包項目中本身的檔案代碼,當需要使用第三方依賴的時候,會使用 DllReferencePlugin插件去讀取第三方依賴庫。是以說它的打包速度會得到一個很大的提升。
具體使用:
首先需要在我們的項目根目錄下建立一個 webpack.dll.config.js 檔案。然後配置代碼如下:
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
// 入口檔案
entry: {
// 項目中用到該兩個依賴庫檔案
jquery: ['jquery'],
echarts: ['echarts']
},
// 輸出檔案
output: {
// 檔案名稱
filename: '[name].dll.js',
// 将輸出的檔案放到dist目錄下
path: path.resolve(__dirname, 'dist'),
/*
存放相關的dll檔案的全局變量名稱,比如對于jquery來說的話就是 _dll_jquery, 在前面加 _dll
是為了防止全局變量沖突。
*/
library: '_dll_[name]'
},
plugins: [
// 使用插件 DllPlugin
new DllPlugin({
/*
該插件的name屬性值需要和 output.library儲存一緻,該字段值,也就是輸出的 manifest.json檔案中name字段的值。
比如在jquery.manifest檔案中有 name: '_dll_jquery'
*/
name: '_dll_[name]',
/* 生成manifest檔案輸出的位置和檔案名稱 */
path: path.join(__dirname, 'dist', '[name].manifest.json')
})
]
};
DllPlugin 插件有三個配置項參數如下:
- context(可選): manifest檔案中請求的上下文,預設為該webpack檔案上下文。
- name: 公開的dll函數的名稱,和 output.library保持一緻。
- path: manifest.json 生成檔案的位置和檔案名稱。
下面我們繼續看下 webpack.config.js 配置代碼如下:
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = {
plugins: [
// 告訴webpack使用了哪些第三方庫代碼
new DllReferencePlugin({
// jquery 映射到json檔案上去
manifest: require('./dist/jquery.manifest.json')
}),
new DllReferencePlugin({
// echarts 映射到json檔案上去
manifest: require('./dist/echarts.manifest.json')
})
]
}
DllReferencePlugin項的參數有如下:
- context::(絕對路徑) manifest (或者是内容屬性)中請求的上下文
- extensions:用于解析 dll bundle 中子產品的擴充名 (僅在使用 ‘scope’ 時使用)。
- manifest: manifest :包含 content 和 name 的對象,或者是一個字元串 —— 編譯時用于加載 JSON manifest 的絕對路徑
- content:(可選):請求到子產品id的映射(預設值為 manifest.content)
- name(可選): dll暴露的地方的名稱(預設值為manifest.name) scope: dll中内容的字首。
- scope (可選):dll 中内容的字首
- sourceType(可選): dll是如何暴露的libraryTarget。
執行建構代碼
webpack --config webpack.dll.config.js
1.10、HappyPack
先放 傳送門
npm install --save-dev happypack
HappyPack 能讓 webpack 把任務分解給多個子程序去并發的執行,子程序處理完後再把結果發送給主程序。是以可以很大程度上優化打包速度。
要注意的是 HappyPack 對 file-loader、url-loader 支援的不友好,是以不建議對該 loader 使用。
另外當項目較小時,多線程打包反而會使打包速度變慢。
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
module: {
rules:[
{
test: /\.js$/,
use: 'happypack/loader?id=jsx'
},
{
test: /\.less$/,
use: 'happypack/loader?id=styles'
},
]
},
plugins: [
new HappyPack({
id: 'jsx',
threads: 4,
loaders: [ 'babel-loader' ],
//共享程序池
threadPool: happyThreadPool,
//允許 HappyPack 輸出日志
verbose: true,
}),
new HappyPack({
id: 'styles',
threads: 2,
loaders: [ 'style-loader', 'css-loader', 'less-loader' ],
//共享程序池
threadPool: happyThreadPool,
//允許 HappyPack 輸出日志
verbose: true,
})
];
}
- 在 Loader 配置中,所有檔案的處理都交給了 happypack/loader 去處理,使用緊跟其後的 querystring?id=babel 去告訴 happypack/loader 去選擇哪個 HappyPack 執行個體去處理檔案。
- 在 Plugin配置中,新增了兩個 HappyPack 執行個體分别用于告訴 happypack/loader 去如何處理 .js 和 .css檔案。選項中的 id 屬性的值和上面 querystring 中的 id相對應,選項中的 loaders配置,就是處理該類型檔案要使用的loader。
1.11、IgnorePlugin
IgnorePlugin用于忽略某些特定的子產品,讓 webpack 不把這些指定的子產品打包進去。
new webpack.IgnorePlugin({resourceRegExp, contextRegExp});
// 僅支援webpack4及之前版本, 在webpack5中不支援該寫法
new webpack.IgnorePlugin(resourceRegExp, [contextRegExp]);
- resourceRegExp 比對(test)資源請求路徑的正規表達式。
- contextRegExp(可選)比對(test)資源上下文(目錄)的正規表達式。
1.12、splitChunks (原CommonsChunkPlugin)
抽取代碼中公共子產品的插件,簡單來講也就是把很多個項目代碼中都引入了的子產品抽離出來,形成一個單獨的檔案。這樣能有效減少封包件大小
更詳細的配置項參考 官方文檔,這裡要吐槽下,有些屬性的意思文檔寫的也不全,完全了解不了。還得自行谷歌,後面有時間的話慢慢整理一下。 可以參考下這篇文章,有部分屬性解釋。
module.exports = {
//...
optimization: {
splitChunks: {
// 此屬性有三個值 ,async,initial,all,
// 反正一般用all,官方文檔說這個最強大嘛,其他兩個看不懂
chunks: 'all',
// 大于此值的才會被拆分,機關是位元組
minSize: 20000,
minRemainingSize: 0,
maxSize: 0,
// 入口點的最大并行請求數,看不懂我吐了
// 在有篇文章裡寫的大概意思是 被其他entry引用次數大于此值,預設1
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
automaticNameDelimiter: '~',
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
2、loader
更多 loader 參考 傳送門
2.1、css-loader + style-loader
npm install --save-dev css-loader style-loader
css-loader主要是幫我們解析css檔案内的css代碼,将 CSS 轉化成 CommonJS 子產品,而style-loader則幫我們将css-loader解析後的内容挂載到html頁面中。
具體的 options 配置,參考 css-loader , style-loader
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: 'style-loader',
options: {
...
},
},
{
loader: "css-loader",
options: {
...
},
},
],
},
],
},
};
2.2、file-loader
npm install --save-dev file-loader
解析項目中的檔案
詳細的options 配置參考 傳送門
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// 如果不配置 預設為檔案内容的 MD5 哈希值并會保留所引用資源的原始擴充名
name: '[path][name].[ext]'
}
}
]
}
]
}
}
// 關于name的配置還可以這麼寫
{
loader: 'file-loader',
options: {
name (file) {
if (process.env.NODE_ENV === 'development') {
return '[path][name].[ext]'
}
return '[hash].[ext]'
}
}
}
2.3、sass-loader
npm install sass-loader node-sass webpack --save-dev
将scss轉化為css代碼
更多options配置 傳送門
// webpack.config.js
module.exports = {
...
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader" // 将 JS 字元串生成為 style 節點
}, {
loader: "css-loader" // 将 CSS 轉化成 CommonJS 子產品
}, {
loader: "sass-loader" ,// 将 Sass 編譯成 CSS
options: {
...
}
}]
}]
}
};
通常,生産環境下比較推薦的做法是,使用 ExtractTextPlugin 将樣式表抽離成專門的單獨檔案。這樣,樣式表将不再依賴于 JavaScript:
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractSass = new ExtractTextPlugin({
filename: "[name].[contenthash].css",
disable: process.env.NODE_ENV === "development"
});
module.exports = {
...
module: {
rules: [{
test: /\.scss$/,
use: extractSass.extract({
use: [{
loader: "css-loader"
}, {
loader: "sass-loader"
}],
// 在開發環境使用 style-loader
fallback: "style-loader"
})
}]
},
plugins: [
extractSass
]
};
2.4、less-loader
直接參考 傳送門,用法跟sass-loader差不多。
2.5、babel-loader
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
Babel是一個工具鍊,主要用于在舊的浏覽器或環境中将ECMAScript 2015+代碼轉換為向後相容版本的JavaScript代碼。簡單來說就是把高階文法轉換為浏覽器支援的低階文法。
具體用法參考連結:傳送門,一般都是使用presets。起碼我自己很少去配置。
module: {
rules: [
//...
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
]
}
}
//...
]
}
更詳細的可以去babel官網看,知識點太多了,學不過來。
2.6、awesome-typescript-loader
npm install awesome-typescript-loader --save-dev
把 ts代碼 轉化為 js
這裡就不寫ts-loader了,相比之下awesome-typescript-loader更有優勢。
另外Webpack 轉譯 Typescript 現有方案 可以參考 這篇文章
const { CheckerPlugin } = require('awesome-typescript-loader')
module.exports = {
// Currently we need to add '.ts' to the resolve.extensions array.
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
// Source maps support ('inline-source-map' also works)
devtool: 'source-map',
// Add the loader for .ts files.
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'awesome-typescript-loader'
}
]
},
plugins: [
new CheckerPlugin()
]
};
awesome-typescript-loader與 ts-loader的差別
awesome-typescript-loader 不需要安裝額外的插件,可以通過内置的 CheckerPlugin 插件,把類型檢查放在獨立的程序中執行。
編譯時間對比:
如果都是用預設配置的話,awesome-typescript-loader 的速度相對快一些。
如果都設定了禁止類型檢查的選項,ts-loader 的速度相對快一些。
如果都設定了禁止類型檢查的選項并且将類型檢查放到獨立的程序中執行。
2.7、eslint-loader
npm install eslint-loader --save-dev
打包時通過 ESLint 檢查 JavaScript 代碼
這個可選可不選吧,因為可以在vscodd安裝eslint插件,編輯器自帶代碼檢查。當啟用了eslint-loader之後,會影響打包速度。
module.exports = {
// ...
module: {
rules: [
{
enforce: 'pre',
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
],
},
// ...
};
具體的options配置參考 傳送門
二、開發環境
1、插件
1.1、webpack dev serve
npm install --save-dev webpack-dev-server
webpack-dev-server實際上相當于啟用了一個express的Http伺服器+調用webpack-dev-middleware。它的作用主要是用來伺服資源檔案。這個Http伺服器和client使用了websocket通訊協定,原始檔案作出改動後,webpack-dev-server會用webpack實時的編譯,再用webpack-dev-middleware将webpack編譯後檔案會輸出到記憶體中。由于每次更新都會重新加載頁面,這時就需要用到熱更新了,具體熱更新的用法在後面有寫到。
如果需要對 webpack-dev-middleware 做更多配置,可以安裝這個插件
npm install --save-dev express webpack-dev-middleware
詳細用法參考 傳送門
1.2、熱更新 HotModuleReplacementPlugin
子產品熱更新插件。Hot-Module-Replacement 的熱更新是依賴于 webpack-dev-server,後者是在打封包件改變時更新打封包件或者 reload 重新整理整個頁面,HRM 是隻更新修改的部分。
詳細 參考 傳送門
HotModuleReplacementPlugin是webpack子產品自帶的,是以引入webpack後,在plugins配置項中直接使用即可。
const webpack = require('webpack')
devServer: {
contentBase: './dist',
hot: true, // 開啟熱更新
port: 8080,
compress: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
2、配置
2.1、devtool
此選項控制是否生成,以及如何生成 source map。
什麼是source map?
當 webpack 打包源代碼時,可能會很難追蹤到錯誤和警告在源代碼中的原始位置。例如,如果将三個源檔案(a.js, b.js 和 c.js)打包到一個 bundle(bundle.js)中,而其中一個源檔案包含一個錯誤,那麼堆棧跟蹤就會簡單地指向到 bundle.js。這并通常沒有太多幫助,因為你可能需要準确地知道錯誤來自于哪個源檔案。
為了更容易地追蹤錯誤和警告,JavaScript 提供了 source map 功能,将編譯後的代碼映射回原始源代碼。如果一個錯誤來自于 b.js,source map 就會明确的告訴你。
不同的值會明顯影響到建構(build)和重新建構(rebuild)的速度。
二、生産環境
1、插件
1.1、MiniCssExtractPlugin
将 CSS 提取為獨立的檔案的插件,對每個包含 css 的 js 檔案都會建立一個 CSS 檔案
支援按需加載 css 和 sourceMap。隻能用在 webpack4 中。
對比另一個插件 extract-text-webpack-plugin 有以下特點:
- 異步加載
- 不重複編譯,性能更好
- 更容易使用
- 隻針對 CSS
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /\.(le|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
},
},
'css-loader',
'postcss-loader',
'less-loader',
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[id].[contenthash:8].css',
}),
],
}
此插件不能跟style-loader一起使用。并且在開發環境中,更推薦使用style-loader,官方文檔中建議使用如下寫法
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';
const plugins = [];
if (!devMode) {
// enable in production only
plugins.push(new MiniCssExtractPlugin());
}
module.exports = {
plugins,
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
],
},
};
1.2、CssMinimizerPlugin
npm install css-minimizer-webpack-plugin --save-dev
該插件用于壓縮css檔案,并且可以使用緩存,以及開啟多線程壓縮
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
module: {
loaders: [
{
test: /.s?css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
],
},
optimization: {
minimize: true, // 設為true,表示在開發環境下也啟用壓縮
minimizer: [
new CssMinimizerPlugin({
// 設為true,則預設開啟的線程數為 os.cpus().length - 1 ,也可以設為任意數量,如 parallel: 4
parallel: true,
// 開啟源地圖映射,友善開發時調試代碼
sourceMap: true,
// 設為true,則開始緩存,也可直接設為字元串類型的路徑 如'path/to/cache',在webpack5中的配置發生了變化,可以參考
// https://webpack.js.org/configuration/other-options/#cache
cache: true,
],
},
};
使用此插件要注意,如果在開發環境使用此插件,則 loader 内不推薦使用 MiniCssExtractPlugin.loader,可以用style-loader。
三、其他問題
1、切換環境
先安裝用于合并config配置的工具 webpack-merge
npm install --save-dev webpack-merge
然後建立
- webpack.common.js
- webpack.dev.js
- webpack.prod.js
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()
]
});
最後在package.json中更新script腳本
"scripts": {
"start": "webpack-dev-server --open --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
2、webpack打包優化
直覺的檢視打包體積,部分參考自:傳送門
要想對打包體積進行優化,首先得找到體積大的子產品,在這裡我們可以使用webpack插件webpack-bundle-analyzer來檢視整個項目的體積結構對比。它是以treemap的形式展現出來,很形象直覺,還有一些具體的互動形式。既可以檢視你項目中用到的所有依賴,也可以直覺看到各個子產品體積在整個項目中的占比。
先安裝
npm install webpack-bundle-analyzer --save-dev
在config中配置
plugins: [
new BundleAnalyzerPlugin()
]
2.1、noParse:包名
webpack打包的時候,有時不需要解析某些子產品的依賴(這些子產品并沒有依賴别的子產品,或者根本就沒有子產品化),我們可以直接加上這個參數,直接跳過這個子產品的解析,如jquery 、lodash等。
在module中設定noParse屬性,值是一個正規表達式
//webpack.config.js
module.exports = {
//...
module: {
noParse: /jquery|lodash/
}
}
2.2、在loader配置中,使用exclude,和include
因為webpack會引入所有的包,但是有時我們沒用到
rules: [
{
test: /\.css$/,
exclude:/node_modules/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|gif|jpeg)$/,
use: ['file-loader'],
},
],
2.3、IgnorePlugin移除不必要的子產品
IgnorePlugin用于忽略某些特定的子產品,讓 webpack 不把這些指定的子產品打包進去
參考通用插件部分的1.11。
2.4、happypack 開始多程序打包
給loader開啟多個子程序去處理。具體使用參考通用插件部分的1.10
2.5、抽離公共代碼
簡單來講也就是把很多個項目代碼中都引入了的子產品抽離出來,形成一個單獨的檔案。這樣能有效減少封包件大小。參考 本文 通用插件部分1.12 splitChunks
2.6、子產品化引入
仔細想想,我們在使用這類工具庫的時候往往隻使用到了其中的很少的一部分功能,但卻把整個庫都引入了。是以這裡也可以進一步優化,隻引用需要的部分。
import {chain, cloneDeep} from 'lodash';
// 可以改寫為
import chain from 'lodash/chain';
import cloneDeep from 'lodash/cloneDeep';
2.7、通過CDN引用
對于一些必要的庫,但又無法對該庫進行更好的體積優化的話,可以嘗試通過外部引入的方式來減小打封包件的體積。采用該方法隻需要在cdn站點找到需要引用的庫的外部連結,以及對webpack進行簡單配置即可。
// 在html中添加script引用
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
// 這裡externals的key指的是使用時需要require的包名,value指的是該庫通過script引入後在全局注冊的變量名
// 看不懂的可以參考 https://webpack.docschina.org/configuration/externals/#root
module.exports = {
//...
externals: {
jquery: 'jQuery'
}
};
// 使用方法
import $ from 'jquery';
$('.my-element').animate(/* ... */);
2.8、通過DLLPlugin 和 DLLReferencePlugin 拆分依賴的第三方庫和項目代碼
參考 本文 通用插件部分1.9 DllPlugin和DllReferencePlugin
2.9、開啟Gzip壓縮
參考 本文 通用插件部分1.6 CompressionPlugin
2.10、壓縮混淆代碼
可以去掉不必要的空格、注釋、console資訊等,有效的減小代碼體積。參考 本文 通用插件部分1.5 UglifyJsPlugin
寫在最後
部分插件整理自: 傳送門,其他大部分來源于官方網站,還有一些來源于在某個地方意外看到的文章,找不到位址了。
我隻想說webpack 配起來是真要命啊。有空了得去github找找有沒有大佬配好的拆箱即用的那種配置,或者自己去嘗試配一套。