1. Tree Shaking
1.1 JS Tree Shaking
1.1.1 本地代碼Tree Shaking
- 一個簡單的打包示例
(1) 打包入口代碼
src/index.js
import { add } from './math'
add(1,5)
src/math.js
export const add = (a, b) => {
console.log(a + b)
}
export const minus = (a, b) => {
console.log(a - b)
}
(2) 打包輸出
npm run bundle
//...
/*! exports provided: add, minus */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "add", function() { return add; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "minus", function() { return minus; });
const add = (a, b) => {
console.log(a + b);
};
const minus = (a, b) => {
console.log(a - b);
};
//...
(3) 問題分析
src/index.js
僅引入了add
方法汉矿,但是卻打包了add
方法和minus
方法崎坊。
-
Tree Shaking
tree shaking 是一個術(shù)語,通常用于描述移除JavaScript
上下文中的未引用代碼(dead-code
)洲拇。
webpack 2.0
及之后版本支持Tree Shaking
奈揍。
webpack 3.X
版本開啟Tree Shaking
方式與webpack 4.X
不同。
Tree Shaking
只支持ES Module
模塊引入方式赋续。不支持commonjs
模塊引入方式男翰。
-
development
模式開啟Tree Shaking
(1) 編輯打包配置文件
webpack.dev.config.js
optimization: {
usedExports: true
}
(2) 將文件標(biāo)記為side-effect-free
(無副作用)
編輯package.json
"sideEffects": ["*.css"]
side-effect-free
數(shù)組中標(biāo)記的文件即使沒有通過ES Module
,也會被打包輸出纽乱。如果沒有文件設(shè)置為side-effect-free
蛾绎,則sideEffects
值設(shè)置為false
。
(3) 打包輸出
/*! exports provided: add, minus */
/*! exports used: add */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return add; });
/* unused harmony export minus */
const add = (a, b) => {
console.log(a + b);
};
const minus = (a, b) => {
console.log(a - b);
};
/***/
-
production
模式開啟Tree Shaking
生產(chǎn)模式自動開啟Tree Shaking
鸦列,無需設(shè)置optimization
租冠。
Tree Shaking
開啟的關(guān)鍵在于JavaScript
代碼壓縮。在webpack3.X
版本中薯嗤,通過UglifyJsPlugin
插件進行JavaScript
代碼壓縮顽爹。在webpack4.X
版本中,mode: production
生產(chǎn)模式默認進行JavaScript
代碼壓縮骆姐。
- 結(jié)論
你可以將應(yīng)用程序想象成一棵樹镜粤。綠色表示實際用到的source code
(源碼) 和library
(庫),是樹上綠色的樹葉玻褪∪饪剩灰色表示未引用代碼,是秋天樹上枯萎的樹葉归园。為了除去死去的樹葉黄虱,你必須搖動這棵樹,使它們落下庸诱。
在以
import { add } from './math'
的方式引入模塊時捻浦,Tree Shaking
能夠?qū)?code>'./math'中未被引入的模塊過濾掉。
1.1.2 Lodash Tree Shaking
- 編輯打包入口文件
src/index.js
import { join } from 'lodash';
console.log(_.join(['1','2', '3'], '-'))
- 打包輸出
Asset Size Chunks Chunk Names
index.html 199 bytes [emitted]
main.js 70.3 KiB 0 [emitted] app
只用到了lodash
中的join
方法桥爽,main.js
包大小為0.3 KiB
朱灿。很明顯。Tree Shaking
并沒有生效钠四。
- 安裝依賴
npm i babel-plugin-lodash -D
- 編輯打包配置文件
webapck.dev.config.js
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
[ "@babel/preset-env", {"useBuiltIns": "usage", "corejs": 2}]
],
plugins: [
"lodash" //對lodash進行Tree Shaking
]
}
}
- 打包輸出
Asset Size Chunks Chunk Names
index.html 199 bytes [emitted]
main.js 1.08 KiB 0 [emitted] app
經(jīng)過Tree Shaking
后盗扒,main.js
包大小為1.08 KiB
跪楞。
使用
babel-plugin-lodash
插件后,即使使用import lodash from 'lodash'
方式引入lodash
侣灶,Tree Shaking
仍然生效甸祭。
1.2 CSS Tree Shaking
- 安裝依賴
npm i -D purifycss-webpack purify-css glob-all
- 編輯打包配置文件
webpack.dev.config.js
const PurifyCSS = require('purifycss-webpack');
const glob = require('glob-all');
module.exports = {
//...
plugins: [
new PurifyCSS({
paths: glob.sync([
path.join(__dirname, './src/*.js')
])
})
]
}
- 打包輸出
生成的css
文件不包含./src/*.js
中使用不到的樣式。
purify-css
和css modules
不可以同時使用褥影。
2. webpack-merge
- 安裝依賴
npm i webpack-merge -D
- 打包配置文件
(1)build/webpack.base.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
entry: {
app: path.resolve(__dirname, '../src/index.js')
},
output: {
publicPath: '',
filename: '[name].bundle.js',
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [ require('autoprefixer')]
}
}
]
},
{
test: /\.scss$/,
use: [
"style-loader",
"css-loader",
"sass-loader",
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [ require('autoprefixer')]
}
}
]
},
{
test: /\.html$/,
use: [
{
loader: "html-loader",
options: {
attrs: [':src', ':data-src']
}
}
]
},
{
test: /\.(eot|ttf|svg|woff)$/,
use: {
loader: "file-loader",
options: {
name: '[name]-[hash:5].[ext]',
outputPath: 'font/'
}
}
},
{
test: /\.(png|svg|jpg|gif)$/,
use: {
loader:'url-loader',
options: {
name: '[name]-[hash:5].[ext]',
outputPath: 'images/',
limit: 4096
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/index.html')
}),
new CleanWebpackPlugin()
]
}
(2) build/webpack.dev.config.js
const webpack = require('webpack');
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config')
const devConfig = {
mode: "development",
devtool: 'cheap-module-eval-source-map',
optimization: {
usedExports: true
},
devServer: {
open: true, //瀏覽器自動打開
port: 9000,
contentBase: './dist',
hot: true,
//hotOnly: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
}
module.exports = merge(baseConfig, devConfig)
(3) build/webpack.prod.config.js
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config')
const prodConfig = {
mode: "production",
devtool: 'cheap-module-source-map',
}
module.exports = merge(baseConfig, prodConfig)
webpack-merge
可以對module.rules
進行合并池户,但無法對單個rule
中的loader
進行合并。
- 創(chuàng)建打包命令
package.json
"scripts": {
"build": "webpack --config ./build/webpack.prod.config.js",
"dev": "webpack --config ./build/webpack.dev.config.js",
"start": "webpack-dev-server --config ./build/webpack.dev.config.js",
}
3. js代碼分割(Code Splitting)
3.1 單獨文件打包輸出的缺點
- 安裝
lodash
npm i --save lodash
- 編輯
src/index.js
import _ from 'lodash'
console.log(_.join(['1','2', '3'], '-'))
- 打包分析
Asset Size Chunks Chunk Names
app.bundle.js 1.38 MiB app [emitted] app
index.html 221 bytes [emitted]
Entrypoint app = app.bundle.js
將
lodash
和業(yè)務(wù)代碼打包到一個文件app.bundle.js
凡怎。頁面加載js
耗時時間久校焦。頁面代碼更新時,app.bundle.js
全量更新统倒。
3.2 多入口實現(xiàn)分包
- 編輯打包配置文件
entry: {
lodash: path.resolve(__dirname, '../src/lodash.js'),
app: path.resolve(__dirname, '../src/index.js')
}
- 編輯
src/lodash.js
import lodash from 'lodash'
window.lodash = lodash
- 編輯
src/index.js
console.log(window.lodash.join(['1','2', '3'], '-'))
- 打包分析
Asset Size Chunks Chunk Names
app.bundle.js 29.1 KiB app [emitted] app
index.html 284 bytes [emitted]
lodash.bundle.js 1.38 MiB lodash [emitted] lodash
Entrypoint lodash = lodash.bundle.js
Entrypoint app = app.bundle.js
entry
為多入口時寨典,入口文件順序即是html
模板引入對應(yīng)輸出文件的順序。不同入口文件之間沒有依賴關(guān)系房匆。
3.3 SplitChunksPlugin配置
3.3.1 同步代碼分割
- 通過
SplitChunksPlugin
實現(xiàn)同步代碼分割招盲。
webpack 4+
支持SplitChunksPlugin
畜份。
- 編輯打包配置文件
webpack.base.config.js
optimization: {
splitChunks: {
chunks: "all"
}
}
- 編輯
src/index.js
import _ from 'lodash'
console.log(_.join(['1','2', '3'], '-'))
- 打包分析
npm run dev
Built at: 04/12/2019 9:37:25 AM
Asset Size Chunks Chunk Names
app.bundle.js 32.4 KiB app [emitted] app
index.html 289 bytes [emitted]
vendors~app.bundle.js 1.35 MiB vendors~app [emitted] vendors~app
Entrypoint app = vendors~app.bundle.js app.bundle.js
lodash
打包輸出代碼被分割到vendors~app.bundle.js
文件中柏蘑。
chunk
表示打包輸出模塊重抖,打包輸出幾個文件姨夹,chunks
就有幾個米诉。
同步代碼分割可以通過瀏覽器緩存功能提升第二次頁面加載速度威酒。
- 指定代碼分割打包輸出文件名
(1) 編輯打包配置文件
output: {
//...
chunkFilename: '[name].chunk.js',
//...
}
在
html
頁面中直接引入的資源文件(js
煌恢、css
)命名以filename
為規(guī)則骗卜。間接引用的資源文件命名以chunkFilename
為規(guī)則
(2) 打包分析
npm run dev
Built at: 04/12/2019 9:39:07 AM
Asset Size Chunks Chunk Names
app.bundle.js 32.4 KiB app [emitted] app
index.html 288 bytes [emitted]
vendors~app.chunk.js 1.35 MiB vendors~app [emitted] vendors~app
Entrypoint app = vendors~app.chunk.js app.bundle.js
3.3.2 異步代碼分割
- 使用
@babel/plugin-syntax-dynamic-import
實現(xiàn)代碼分割 - 安裝依賴
npm i @babel/plugin-syntax-dynamic-import -D
- 編輯
babel
配置
"plugins": [
"@babel/plugin-syntax-dynamic-import"
]
- 編輯
src/index.js
import('lodash').then(({default : _}) => {
console.log(_.join(['1','2', '3'], '-'))
})
- 打包分析
npm run dev
Built at: 04/12/2019 9:29:30 AM
Asset Size Chunks Chunk Names
0.bundle.js 1.35 MiB 0 [emitted]
app.bundle.js 33.8 KiB app [emitted] app
index.html 221 bytes [emitted]
webpack
會自動對通過import()
方法異步加載的模塊進行代碼分割宠页。
異步代碼分割既可以通過瀏覽器緩存功能提升第二次頁面加載速度,又可以通過懶加載的方式提升首次頁面加載速度寇仓。
- 指定代碼分割打包輸出文件名
import(/* webpackChunkName: "lodash" */'lodash').then(({default : _}) => {
//...
import()
方法代碼分割的底層還是通過SplitChunksPlugin
實現(xiàn)的举户,splitChunks
配置參數(shù)同樣會影響import()
方法代碼分割情況。
3.3.3 SplitChunksPlugin配置參數(shù)
-
optimization.splitChunks
默認配置項
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
webpack4.X
版本才支持SplitChunksPlugin
遍烦。在webpack3.X
中俭嘁,使用CommonsChunkPlugin
進行代碼分割。
-
optimization.splitChunks
配置項說明
optimization: {
splitChunks: {
chunks: 'all',
//initial只對同步代碼分割服猪,async(默認)只對異步代碼分割供填、all所有代碼都做代碼分割
minSize: 30000,
//大于30000Bit的模塊才做代碼分割
maxSize: 0,
//當(dāng)模塊大于maxSize時,會對模塊做二次代碼分割罢猪。當(dāng)設(shè)置為0時近她,不做二次分割。
minChunks: 1,
//當(dāng)打包輸出chunks文件引用該模塊的次數(shù)達到一定數(shù)目時才做代碼分割膳帕。
maxAsyncRequests: 5,
//異步加載的js文件最大數(shù)目為邊界條件進行代碼分割
maxInitialRequests: 3,
//以初始加載的js文件最大數(shù)目為邊界條件進行代碼分割
automaticNameDelimiter: '~',
//代碼分割生成文件連接符
name: true,
//代碼分割生成文件自動生成文件名
cacheGroups: {
//代碼分割緩存組粘捎,被分割代碼文件通過緩存組輸出為一個文件。
vendors: {
test: /[\\/]node_modules[\\/]/,
//模塊路徑正則表達式
priority: -10,
//緩存組優(yōu)先級,一個模塊優(yōu)先打包輸出到優(yōu)先級高的緩存組中攒磨。
name: 'vendor'
//代碼分割打包輸出文件名
},
lodash: {
test: /[\\/]lodash[\\/]/,
priority: -5,
},
jquery: {
test: /[\\/]jquery[\\/]/,
priority: -5,
},
default: {
//默認緩存組泳桦,一般設(shè)置priority優(yōu)先級數(shù)值最小
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
//代碼分割的模塊A引用其他模塊B,B已經(jīng)被打包輸出娩缰,則不再重新打包進入A
name: 'common',
chunks: 'all'
}
}
}
}
optimization.splitChunks.chunks
可設(shè)置灸撰,默認值為async
,表示默認只對動態(tài)import()
做代碼分割漆羔。splitChunks.cacheGroups.{cacheGroup}.chunks
同樣可以設(shè)置梧奢,默認值為all
,表示cacheGroups
分組代碼分割優(yōu)先級高于import()
。
3.4 懶加載(Lazy Loading)
-
Lazy Loading
文檔演痒。 -
import()
實現(xiàn)懶加載
const lazyConsole = async () => {
const {default : _} = await import(/* webpackChunkName: "lodash" */'lodash');
console.log(_.join(['1','2', '3'], '-'))
};
document.addEventListener('click', lazyConsole)
import()
動態(tài)加載不僅可以實現(xiàn)代碼分割亲轨,還可以實現(xiàn)懶加載。
lodash
模塊生成的vendors~lodash.bundle.js
文件在點擊頁面時才加載鸟顺。
只有配置
chunkFilename
之后惦蚊,webpackChunkName
才生效。
如果多個import()
的魔法注釋webpackChunkName
指定同一個名字讯嫂,則這多個import()
模塊會打包成一個bundle
蹦锋。
如果外部也引入了import()
方法中引入的模塊,則該模塊不會分割單獨打包欧芽。
3.5 預(yù)取/預(yù)加載模塊(prefetch/preload module)
3.5.1 查看頁面代碼利用率
-
chrome
瀏覽器打開網(wǎng)頁 - 打開調(diào)試面板
-
commond + shift + p
-show coverage
-instrument coverage
-
刷新網(wǎng)頁
-
分析結(jié)果
紅色表示加載并運行代碼莉掂,綠色表示只加載未運行代碼。
可以看到該頁面加載的每一個文件的利用率以及綜合利用率千扔。
點擊右側(cè)橫條憎妙,可以看到具體文件代碼利用情況。
3.5.2 提高代碼利用率
- 通過
import()
異步模塊懶加載的方式可以提高首屏代碼利用率曲楚。 - 未使用懶加載
src/index.js
document.addEventListener('click', () => {
const element = document.createElement('div');
element.innerHTML = 'Dell Li';
document.body.appendChild(element)
});
代碼利用率為:77%
- 通過懶加載
src/index.js
document.addEventListener('click', () => {
import('./click').then(({default: click}) => {
click && click()
})
});
src/click.js
const handleClick = () => {
const element = document.createElement('div');
element.innerHTML = 'Dell Li';
document.body.appendChild(element)
};
export default handleClick
代碼利用率為:81.5%
異步模塊懶加載雖然可以減少首屏代碼量厘唾,縮短網(wǎng)頁首次加載時間,但等待用戶交互后才請求對應(yīng)
js
文件龙誊,會影響用戶體驗抚垃。可以通過prefetch/preload
方式解決該問題趟大。
3.5.3 prefetch/preload module
-
webpack v4.6.0+
添加了預(yù)取和預(yù)加載(prefetch/preload module)的支持鹤树。 - 使用
prefetch
src/index.js
document.addEventListener('click', () => {
import(/* webpackPrefetch: true */ './click').then(({default: click}) => {
click && click()
})
});
這會生成 <link rel="prefetch" href="1.bundle.js">
并追加到頁面頭部,指示著瀏覽器在閑置時間預(yù)取1.bundle.js
文件护昧。
-
prefetch
/preload
指令對比
-
preload chunk
會在父chunk
加載時魂迄,以并行方式開始加載。prefetch chunk
會在父chunk
加載結(jié)束后開始加載惋耙。 -
preload chunk
具有中等優(yōu)先級捣炬,并立即下載熊昌。prefetch chunk
在瀏覽器閑置時下載。 -
preload chunk
會在父chunk
中立即請求湿酸,用于當(dāng)下時刻婿屹。prefetch chunk
會用于未來的某個時刻。 - 瀏覽器支持程度不同推溃。
4. CSS文件的代碼分割
4.1 現(xiàn)有CSS打包分析
- 打包配置
webpack.base.config.js
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [ require('autoprefixer')]
}
}
]
},
{
test: /\.scss$/,
use: [
"style-loader",
"css-loader",
"sass-loader",
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [ require('autoprefixer')]
}
}
]
}
]
}
- 入口文件
src/index.js
import './style.css'
src/style.css
body {
background: yellow;
}
- 打包輸出
npm run build
Built at: 04/12/2019 3:53:13 PM
Asset Size Chunks Chunk Names
app.bundle.js 6.79 KiB 0 [emitted] app
app.bundle.js.map 3.04 KiB 0 [emitted] app
index.html 221 bytes [emitted]
Entrypoint app = app.bundle.js app.bundle.js.map
- 存在的問題
沒有打包輸出css
文件昂利,css
代碼被打包到js
中。
4.2 MiniCssExtractPlugin
-
MiniCssExtractPlugin
文檔介紹
該插件將CSS
分割到文件中铁坎。對每個js
文件中的css
代碼創(chuàng)建一個css
文件蜂奸。支持css
按需加載和sourcemap
。
MiniCssExtractPlugin
不支持HMR
(模塊熱更新)硬萍,建議在生產(chǎn)環(huán)境中使用扩所。
在
webpack4
版本中,我們之前首選使用的extract-text-webpack-plugin完成了其歷史使命朴乖。推薦使用mini-css-extract-plugin祖屏。
- 安裝
MiniCssExtractPlugin
npm install --save-dev mini-css-extract-plugin
-
webpack.base.config.js
中對css
和scss
文件的loader
處理移動到webpack.dev.config.js
中。 - 修改打包配置文件
webpack.pro.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
//...
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [ require('autoprefixer')]
}
}
]
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader",
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [ require('autoprefixer')]
}
}
]
},
]
},
plugins: [
new MiniCssExtractPlugin({})
]
與之前的配置相比做了兩點修改买羞,一個是引入
new MiniCssExtractPlugin({})
插件袁勺,一個是MiniCssExtractPlugin.loader
替換style-loader
。
由于webpack-merge
可以對module.rules
進行合并畜普,但無法對單個rule
中的loader
進行合并期丰。所以在webpack.pro.config.js
里寫了完整的處理css
和sass
文件的rule
。也可以在webpack.base.config.js
通過環(huán)境變量的邏輯進行判斷添加MiniCssExtractPlugin.loader
或者style-loader
吃挑。
- 打包輸出
npm run build
Built at: 04/12/2019 4:16:56 PM
Asset Size Chunks Chunk Names
app.bundle.js 1010 bytes 0 [emitted] app
app.bundle.js.map 3.04 KiB 0 [emitted] app
app.css 66 bytes 0 [emitted] app
app.css.map 170 bytes 0 [emitted] app
index.html 259 bytes [emitted]
Entrypoint app = app.css app.bundle.js app.css.map app.bundle.js.map
打包輸出了css
文件咐汞。
如果沒有打包輸出
css
文件。原因可能是production
自動開啟Tree Shaking
儒鹿,需要將css
文件標(biāo)記為side-effect-free
(無副作用)。
"sideEffects"
:["*.css"]
-
css
文件命名規(guī)則
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].chunk.css"
})
]
-
css
文件壓縮
(1) 安裝依賴
npm install --save-dev optimize-css-assets-webpack-plugin
(2) 編輯配置文件
webpack.prod.config.js
var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const prodConfig = {
//...
optimization: {
minimizer: [
new OptimizeCssAssetsPlugin({})
]
}
//...
}
(3) 打包輸出
Built at: 04/12/2019 4:50:40 PM
Asset Size Chunks Chunk Names
app.bundle.js 4.1 KiB 0 [emitted] app
app.bundle.js.map 3.66 KiB 0 [emitted] app
app.css 56 bytes 0 [emitted] app
index.html 259 bytes [emitted]
Entrypoint app = app.css app.bundle.js app.bundle.js.map
- 可通過
cacheGroups
實現(xiàn)將所有js
文件中的css
打包到一個css
文件中(Extracting all CSS in a single file)和將一個入口文件對應(yīng)的所有css
打包到一個css
文件中(Extracting CSS based on entry)几晤。
5. 打包分析(bundle analysis)
5.1 打包分析工具介紹
- 如果我們以分離代碼作為開始约炎,那么就應(yīng)該以檢查模塊的輸出結(jié)果作為結(jié)束,對其進行分析是很有用處的蟹瘾。
-
官方提供分析工具 是一個好的初始選擇圾浅。下面是一些可選擇的社區(qū)支持工具:
(1) webpack-chart:webpack stats
可交互餅圖。
(2) webpack-visualizer:可視化并分析你的bundle
憾朴,檢查哪些模塊占用空間狸捕,哪些可能是重復(fù)使用的。
(3) webpack-bundle-analyzer:一個plugin
和CLI
工具众雷,它將bundle
內(nèi)容展示為便捷的灸拍、交互式做祝、可縮放的樹狀圖形式。
(4) webpack bundle optimize helper:此工具會分析你的bundle
鸡岗,并為你提供可操作的改進措施建議混槐,以減少bundle
體積大小。
5.2 官方分析工具
-
analyse
文檔 - 編輯打包命令
package.json
"scripts": {
"dev": "webpack --profile --json > stats.json --config ./build/webpack.dev.config.js"
}
- 打包輸出
npm run dev
生成stats.json
文件轩性,該文件中包含打包信息声登。 - 使用
analyse
分析打包結(jié)果
將stats.json
文件上傳到analyse
分析地址,即可看到打包細節(jié)信息揣苏。
5.3 webpack-bundle-analyzer
- 安裝依賴
npm install --save-dev webpack-bundle-analyzer
- 編輯打包配置文件
webpack.base.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
- 打包結(jié)果分析
如果打包生成的不同Asset
引入了相同的js
文件悯嗓,則說明該js
文件被重復(fù)打包進兩個不同的資源,需要修改配置將該js
文件進行分割卸察。
6. Shimming
shimming
文檔
6.1 shimming 全局變量
- 一些第三方的庫(
library
)可能會引用一些全局依賴(例如jQuery
中的$
)脯厨。這些“不符合規(guī)范的模塊”就是shimming
發(fā)揮作用的地方。 - 安裝
jquery
npm i jquery lodash --save
- 修改打包配置文件
webpack.base.config.js
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
join: ['lodash', 'join']
})
]
當(dāng)有模塊使用
$
時蛾派,會自動import $ from 'jquery'
- 可直接使用
$
src/index.js
const dom = $('div')
dom.html(join(['hello', 'world'], ' '))
$('body').append(dom)
shimming
和alias
對比:alias
的作用是創(chuàng)建import
或require
的別名俄认,來確保模塊引入變得更簡單。shimming
的作用是解決一些第三方的庫(library
)可能會引用的一些全局依賴洪乍。即:alias
使模塊引入更簡單眯杏,不用寫復(fù)雜路徑;shimming
使模塊不用引入壳澳,使用全局變量的方式岂贩。
6.2 imports-loader
- 打印現(xiàn)在模塊中
this
指向
src/index.js
console.log(this === window); //false
- 安裝依賴
npm i imports-loader -D
- 編輯打包配置文件
webpack.base.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{loader: "babel-loader"},
{loader: "imports-loader?this=>window"}
]
}
]
}
- 打印現(xiàn)在模塊中
this
指向
src/index.js
console.log(this === window); //true
項目中配置
imports-loader?this=>window
可能導(dǎo)致打包錯誤,'import' and 'export' may only appear at the top level (4:0)
巷波。
7. 環(huán)境變量
- 修改打包配置文件
webpack.dev.config.js
const devConfig = {
//...
}
module.exports = devConfig
webpack.prod.config.js
const prodConfig = {
//...
}
module.exports = prodConfig
webpack.base.config.js
const merge = require('webpack-merge')
const devConfig = require('./webpack.dev.config')
const prodConfig = require('./webpack.prod.config')
const baseConfig = {
//...
}
module.exports = (env) => {
if(env && env.production) {
return merge(baseConfig, prodConfig)
} else {
return merge(baseConfig, devConfig)
}
}
- 修改打包命令
package.json
"scripts": {
"build": "webpack --env.production --config ./build/webpack.base.config.js",
"dev": "webpack --config ./build/webpack.base.config.js",
"start": "webpack-dev-server --config ./build/webpack.base.config.js"
}
這里的
--env.production
與打包配置文件中的env && env.production
對應(yīng)萎津。
如果使用--env.production=abc
,則打包配置文件中需要使用env && env.production==='abc'
的寫法抹镊。
如果使用--env production
锉屈,則打包配置文件中需要使用env === 'production'
的寫法。
8. TypeScript
8.1 引入TypeScript
- 安裝依賴
? webpack-operate npm i ts-loader typescript -D
- 項目根目錄創(chuàng)建
TypeScript
配置文件
tsconfig.json
{
"compilerOptions": {
"module": "commonjs", //模塊引入機制
"target": "es5", //轉(zhuǎn)化為es5
"sourceMap": true, //支持sourceMap
"allowJs": true //支持js引入
},
"exclude": [
"node_modules"
]
}
- 創(chuàng)建入口文件
src/index.ts
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message
}
greet() {
return 'Hello' + this.greeting;
}
}
let greeter = new Greeter('world')
alert(greeter.greet())
- 編輯打包配置文件
webpack.config.base.js
entry: {
app: path.resolve(__dirname, '../src/index.ts'),
}
//...
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: "ts-loader"
}
]
//...
}
- 打包輸出
npm run bundle
8.2 對庫代碼進行編譯檢查
-
查詢
TypeScript
支持編譯檢查的庫垮耳。 - 對庫代碼進行編譯檢查——以
lodash
為例
(1) 安裝依賴
? webpack-operate npm i @types/lodash --save-dev
(2) 修改ts
文件
src/index.ts
import * as _ from 'lodash'
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message
}
greet() {
//return _.join(123) //傳參不是數(shù)組颈渊,標(biāo)紅報錯
return _.join([ 'Hello', this.greeting], ' ');
}
}
let greeter = new Greeter('world')
alert(greeter.greet())
9. Eslint
9.1 使用eslint
- 安裝依賴
? webpack-operate npm i eslint -D
- 初始化
eslint
配置文件
npx eslint --init
自動生成.eslintrc.js
文件。
? webpack-operate npx eslint --init
? How would you like to use ESLint? To check syntax and find problems
? What type of modules does your project use? JavaScript modules (import/export)
? Which framework does your project use? React
? Where does your code run? (Press <space> to select, <a> to toggle all, <i> to invert selection)Browser
? What format do you want your config file to be in? JavaScript
The config that you've selected requires the following dependencies:
eslint-plugin-react@latest
? Would you like to install them now with npm? Yes
- 使用
airbnb
規(guī)則
(1) 安裝依賴
? webpack-operate npm install eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y babel-eslint -D
(2) 修改.eslintrc.js
配置文件
"extends": "airbnb",
"parser": "babel-eslint"
- 檢查代碼
(1) 命令行檢查方式
npx eslint XXX(文件夾名字)
(2) 編輯器檢查方式
- 使某些規(guī)則失效
編輯.eslintrc.js
規(guī)則文件
"rules": {
"no-unused-vars": 0
}
以上是在項目中使用
eslint
终佛,與Webpack
無關(guān)俊嗽。
9.2 Webpack中配置eslint
-
eslint-loader
文檔 - 安裝依賴
? webpack-operate npm i eslint-loader -D
- 編輯打包配置文件
webpack.base.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{loader: "babel-loader"},
{loader: "eslint-loader"}
]
}
]
}
webpack.dev.config.js
devServer: {
overlay: true
}
eslint-loader
的作用是打包時先使用eslint
檢查規(guī)則,應(yīng)該放在babel-loader
之后铃彰。overlay
的作用是使用webpack-dev-server
打包時绍豁,將報錯信息顯示在頁面上。
- 對不符合規(guī)范的代碼進行簡單修復(fù)
{
loader: "eslint-loader",
options: {
fix: true
}
}
使用
eslint-loader
會在打包前對代碼進行檢查牙捉,降低打包效率竹揍。在實際項目開發(fā)中敬飒,一般使用eslint
與git
結(jié)合,在代碼提交到git
倉庫時對代碼進行檢查鬼佣。
10. PWA
- 安裝依賴
? webpack-operate npm i workbox-webpack-plugin -D
- 編輯生產(chǎn)環(huán)境打包配置文件
webpack.prod.config.js
const WorkBoxPlugin = require('workbox-webpack-plugin')
var prodConfig = {
//...
plugins: [
new WorkBoxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
]
//...
}
生產(chǎn)環(huán)境才需要使用
PWA
驶拱。
- 編輯入口文件
src/index.js
//業(yè)務(wù)代碼
import('lodash').then(({default : _}) => {
console.log(_.join(['1','2', '3'], '-'))
})
//使用serviceWorker
if('serviceWorker' in navigator) { //如果瀏覽器支持serviceWorker
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(res => {
console.log('serviceWorker registed')
})
.catch(err => {
console.log('serviceWorker registe err')
})
})
}
service-worker.js
文件在打包時生成。
- 打包輸出
npm run build
Asset Size Chunks Chunk Names
2.6c02624b28028221db11.chunk.js 529 KiB 2 [emitted]
2.6c02624b28028221db11.chunk.js.map 630 KiB 2 [emitted]
app.051fb24e16eb3c7493d6.chunk.js 812 bytes 0 [emitted] app
app.051fb24e16eb3c7493d6.chunk.js.map 783 bytes 0 [emitted] app
index.html 326 bytes [emitted]
precache-manifest.a8a4feb9efc884fe5d31eed9b7b76ac0.js 445 bytes [emitted]
runtime.a366ecc84e6df04acf79.bundle.js 8.81 KiB 1 [emitted] runtime
runtime.a366ecc84e6df04acf79.bundle.js.map 8.8 KiB 1 [emitted] runtime
service-worker.js 927 bytes [emitted]
Entrypoint app = runtime.a366ecc84e6df04acf79.bundle.js runtime.a366ecc84e6df04acf79.bundle.js.map app.051fb24e16eb3c7493d6.chunk.js app.051fb24e16eb3c7493d6.chunk.js.map
- 本地開啟一個服務(wù)
? webpack-operate cd dist
? dist http-server
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8080
http://192.168.1.3:8080
http://192.168.57.1:8080
Hit CTRL-C to stop the server
- 測試
PWA
打開http://127.0.0.1:8080
晶衷,可以看到html
頁面蓝纲。
關(guān)閉服務(wù),刷新瀏覽器晌纫,仍然可以正常訪問頁面税迷。
11. 編寫并發(fā)布一個npm包
- 創(chuàng)建文件夾
nmw-lodash
- 將項目初始化為一個
npm
包
? nmw-lodash npm init -y
- 安裝依賴
? nmw-lodash npm i webpack webpack-cli --save
? nmw-lodash npm i lodash --save
- 編寫代碼
src/index.js
import * as math from './math'
import * as string from './string'
export default {
math,
string
}
src/math.js
export function add(a, b) {
return a + b;
}
src/string.js
import _ from 'lodash'
export function join(a, b) {
return _.join([a, b], ' ')
}
- 創(chuàng)建并編輯打包配置文件
webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'nmw-lodash.js',
library: "nmwLodash", //以script標(biāo)簽引入時,支持nmwLodash全局變量
libraryTarget: "umd" //支持umd規(guī)范引入
//libraryTarget: "global" nmwLodash掛載在global上锹漱。
},
externals: [
"lodash" //不打包lodash
]
}
- 創(chuàng)建打包命令
package.json
"scripts": {
"build": "webpack"
}
- 修改
npm
包入口文件
package.json
"main": "./dist/nmw-lodash.js",
- 打包輸出
npm run build
- 在
npm
官網(wǎng)注冊賬號 - 添加賬號密碼
? nmw-lodash npm adduser
- 發(fā)布項目
npm publish
12. 打包性能優(yōu)化
12.1 優(yōu)化配置
- 跟上技術(shù)的迭代
Node
箭养、Npm
、Yarn
- 在盡可能少的模塊上應(yīng)用
Loader
例如:使用exclude
和include
哥牍。
{
test: /\.js$/,
exclude: /node_modules/,
use: [{loader: "babel-loader"}]
}
exclude
表示不進行loader
編譯的路徑毕泌。
include
表示只進行loader
編譯的路徑。
-
Plugin
盡可能精簡并確毙崂保可靠
例如:只在生產(chǎn)環(huán)境使用MiniCssExtractPlugin
對CSS
進行分割撼泛。
var MiniCssExtractPlugin = require("mini-css-extract-plugin");
//...
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].chunk.css"
})
]
-
resolve
參數(shù)合理配置 (文檔)
(1)resolve.alias
:創(chuàng)建import
或require
的別名,來確保模塊引入變得更簡單澡谭。
例如:import Utility from 'Utilities';
(2)resolve.extensions
:自動解析確定的擴展愿题。能夠使用戶在引入模塊時不帶擴展。
例如:import File from '../path/to/file';
(3)resolve.mainFiles
解析目錄時要使用的文件名蛙奖。
例如:resolve
配置如下
resolve: {
extensions: ['.js', '.jsx'],
mainFiles: ['index'], //默認配置
alias: {
child: path.resolve(__dirname, '../src/components/child')
}
}
模塊引入方式如下:
import Child from 'child';
resolve
配置不宜過于復(fù)雜潘酗,否則會使模塊查找時間增加,降低webpack
打包速度雁仲。
- 控制包文件大小
(1) 使用Tree Shaking
(2)Code Splitting
代碼分割 -
thread-loader
仔夺、parallel-webpack
、happypack
多進程打包 - 合理使用
SourceMap
- 結(jié)合
state
分析打包結(jié)果(bundle analysis
) - 開發(fā)環(huán)境內(nèi)存編譯(
webpack-dev-server
) - 開發(fā)環(huán)境無用插件剔除
12.2 DIIPlugin
12.2.1 使用DIIPlugin
- 測試打包速度
npm run build
打包耗時約950ms
- 第三方模塊沒有必要頻繁重新打包攒砖∏糇疲可以將第三方模塊打包輸出,
webpack
進行項目打包時祭衩,直接使用已經(jīng)被打包的第三方模塊,不再重新打包阅签。 - 創(chuàng)建并編輯打包配置文件
webpack.dll.config.js
const path = require('path');
const webpack = require('webpack')
module.exports = {
mode: 'production',
entry: {
vendors: ['react', 'react-dom', 'lodash'],
},
output: {
filename: "[name].dll.js",
path: path.resolve(__dirname, '../dll'),
library: "[name]" //以vendors全局變量的方式暴露
},
plugins: [
new webpack.DllPlugin({ //生成vendors.manifest.json映射文件
name: '[name]',
path: path.resolve(__dirname, '../dll/[name].manifest.json')
})
]
}
- 創(chuàng)建打包命令
package.json
"scripts": {
//...
"build:dll": "webpack --config ./build/webpack.dll.config.js",
//...
}
- 打包生成
vendors
包
npm run build:dll
生成vendors.dll.js
以及vendors.manifest.json
映射文件掐暮。 - 安裝依賴
npm i add-asset-html-webpack-plugin -D
- 編輯打包配置文件
webpack.base.config.js
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
//...
plugins: [
//...
new AddAssetHtmlPlugin({
//將vendors.dll.js插入html模板中
filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
}),
//打包代碼時,第三方模塊如果在vendors.manifest.json有映射政钟,則直接在vendors全局變量中取路克。
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll/vendors.manifest.json')
})
],
AddAssetHtmlPlugin
插件必須放在HtmlWebpackPlugin
后面樟结。
- 測試打包速度
npm run build
打包耗時約670ms
- 總結(jié)
生成vendors
包及映射 - 將vendors
包插入html
模板 - 以vendors
全局變量暴露 - 使用vendors
包
12.2.2 多個DIIPlugin
- 編輯
dll
包打包配置文件
webpack.dll.config.js
//...
entry: {
vendors: ['lodash'],
react: ['react', 'react-dom']
}
//...
- 編輯打包配置文件
webpack.base.config.js
動態(tài)生成plugins
數(shù)組。
const fs = require('fs')
const plugins =[
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/index.html')
}),
new CleanWebpackPlugin()
//...
]
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
//根據(jù) dll 目錄中生成的文件精算,添加對應(yīng)插件瓢宦。
files.forEach(file => {
if(/.*\.dll\.js/.test(file)) {
//XXX.dll.js插入html模板中
plugins.push(new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if(/.*\.manifest\.json/.test(file)) {
//根據(jù)XXX.manifest.json映射,直接在XXX全局變量中獲取第三方模塊灰羽。
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
})
13. 多頁面打包配置
13.1 介紹
- 多頁面應(yīng)用
① 生成多個html
文件驮履。
② 各個html
文件引入對應(yīng)的jsbundle
。 - 多頁面應(yīng)用的實現(xiàn)方式
(1) 多配置
對多個webpack
配置分別打包廉嚼,生成多個html
頁面玫镐。
(2) 單配置
對一個webpack
配置進行打包,生成多個html
頁面怠噪。
html-webpack-plugin
文檔
13.2 多配置
- 技術(shù)基礎(chǔ)
(1)webpack
打包可以接收一個配置數(shù)組恐似。
(2)parallel-webpack
提高打包速度。
直接使用
webpack
也可以接收一個配置數(shù)組傍念,但串行打包過程速度比較慢矫夷。parallel-webpack
可以并行打包,提高打包速度憋槐。
- 特點
(1) 優(yōu)點
可以使用parallel-webpack
提高打包速度双藕。
配置之間更加獨立、靈活秦陋。
(2) 缺點
不能多頁面之間共享代碼蔓彩。 - 創(chuàng)建編輯模板文件
src/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>
如果打包配置文件添加了
html-loader
,會正常解析html
文件作為模版驳概,就會直接把<%= htmlWebpackPlugin.options.title %>
解析成字符串赤嚼。
- 創(chuàng)建編輯入口文件
src/index.js
console.log('this is index.js');
src/list.js
console.log('this list.js');
- 編輯打包配置文件
webpack.pro.config.js
const baseConfig = require('./webpack.base.config');
//...
const prodConfig = {
//...
}
const buildConfig = merge(baseConfig, prodConfig);
const generatePage = function (
{ entry = '',
title = '',
name = '',
chunks = [],
template = path.resolve(__dirname, '../src/index.html')
} = {}) {
return {
entry,
plugins: [
new HtmlWebpackPlugin({
chunks,
template,
title,
filename: name + '.html'
})
]
}
};
const indexConfig = generatePage({
entry: {
app: path.resolve(__dirname, '../src/index.js')
},
title: 'page index',
name: 'index',
chunks: ['runtime','vendors','app']
});
const listConfig = generatePage({
entry: {
list: path.resolve(__dirname, '../src/list.js')
},
title: 'page list',
name: 'list',
chunks: ['runtime','vendors','list']
});
const pagesConfig = [indexConfig, listConfig];
module.exports = pagesConfig.map(pageConfig => merge(pageConfig, buildConfig));
多配置在同一個文件中,生成一個配置數(shù)組顺又。
????這里的chunks: ['runtime','vendors','app'/'list']
可以省略更卒,因為是多配置,默認會插入所有chunks
稚照。如果是13.3
中的單配置蹂空,入口有多個,那么就必須指定插入的chunks
果录。
- 打包
npm run build
Hash: 10dc11f107c648d35db3659186349535b844a395
Version: webpack 4.30.0
Child
Hash: 10dc11f107c648d35db3
Time: 876ms
Built at: 06/01/2019 4:38:36 PM
Asset Size Chunks Chunk Names
app.bundle.js 963 bytes 0 [emitted] app
index.html 190 bytes [emitted]
Child
Hash: 659186349535b844a395
Time: 850ms
Built at: 06/01/2019 4:38:36 PM
Asset Size Chunks Chunk Names
list.bundle.js 962 bytes 0 [emitted] list
list.html 191 bytes [emitted]
多配置打包不可以使用
clean-webpack-plugin
上枕,否則后一個打包會清除前一個打包結(jié)果。
- 使用
parallel-webpack
打包
(1) 安裝
npm i parallel-webpack -D
(2) 打包
./node_modules/parallel-webpack/bin/run.js --config=build/webpack.prod.config.js
13.3 單配置
- 特點
(1) 優(yōu)點
可以共享各個entry
之間的公用代碼弱恒。
(2) 缺點
打包比較慢辨萍。
輸出的內(nèi)容比較復(fù)雜。
配置不夠獨立返弹,相互耦合锈玉。例如:無法實現(xiàn)對不同入口設(shè)置不同的splitChunks
代碼分割規(guī)則爪飘、無法實現(xiàn)對不同入口設(shè)置不同的動態(tài)路由(splitChunks
會將公共代碼提出來,提前加載)拉背。
單配置時师崎,
webpack
打包配置對不同入口的所有chunks
都生效。只要有一個入口的同步代碼依賴樹中含有某一個模塊椅棺,該模塊就不會被動態(tài)路由異步加載犁罩。
- 編輯打包配置文件
webpack.pro.config.js
//...
const buildConfig = merge(baseConfig, prodConfig);
//...
const pagesConfig = [indexConfig, listConfig];
module.exports = merge(pagesConfig.concat(buildConfig));
webpack-merge
可以接收多個參數(shù)merge(object1, object2, object3, ...)
,也可以接收一個數(shù)組merge([object1, object2, object3, ...])
土陪。
- 打包
npm run build
Version: webpack 4.30.0
Time: 668ms
Built at: 06/01/2019 4:54:57 PM
Asset Size Chunks Chunk Names
app.bundle.js 963 bytes 0 [emitted] app
index.html 190 bytes [emitted]
list.bundle.js 963 bytes 1 [emitted] list
list.html 191 bytes [emitted]
Entrypoint app = app.bundle.js
Entrypoint list = list.bundle.js