Webpack
啟動(dòng)后會(huì)從配置的 Entry
出發(fā),解析出文件中的導(dǎo)入語句竖席,再遞歸的解析戴质。
縮小文件搜索范圍
- 優(yōu)化
loader
配置
為了盡可能少的讓文件被Loader
處理产舞,可以通過include
去命中只有哪些文件需要被處理。 - 優(yōu)化
resolve.modules
配置
可以指明存放第三方模塊的絕對(duì)路徑终抽,以減少尋找。 - 優(yōu)化
resolve.mainFields
配置
用于配置采用哪個(gè)字段作為入口文件的描述桶至。
使用本方法優(yōu)化時(shí)昼伴,需要考慮到所有運(yùn)行時(shí)依賴的第三方模塊的入口文件描述字段,就算有一個(gè)模塊搞錯(cuò)了都可能會(huì)造成構(gòu)建出的代碼無法正常運(yùn)行镣屹。
- 優(yōu)化
resolve.alias
配置
通過別名來把原導(dǎo)入路徑映射成一個(gè)新的導(dǎo)入路徑圃郊。 - 優(yōu)化
resolve.extensions
配置
在導(dǎo)入語句沒帶文件后綴時(shí),Webpack 會(huì)自動(dòng)帶上后綴后去嘗試詢問文件是否存在女蜈,resolve.extensions
用于配置在嘗試過程中用到的后綴列表持舆。 - 優(yōu)化
module.noParse
配置
讓Webpack
忽略對(duì)部分沒采用模塊化的文件的遞歸解析處理,提高構(gòu)建性能伪窖。
被忽略掉的文件里不應(yīng)該包含
import 逸寓、 require 狐援、 define
等模塊化語句芽腾,不然會(huì)導(dǎo)致構(gòu)建出的代碼中包含無法在瀏覽器環(huán)境下執(zhí)行的模塊化語句。
使用 DllPlugin(webpack4不建議使用)
把網(wǎng)頁依賴的基礎(chǔ)模塊抽離出來礼烈,打包到一個(gè)個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫中去簇宽。一個(gè)動(dòng)態(tài)鏈接庫中可以包含多個(gè)模塊勋篓。
Webpack
已經(jīng)內(nèi)置了對(duì)動(dòng)態(tài)鏈接庫的支持,需要通過2個(gè)內(nèi)置的插件接入晦毙,它們分別是:
-
DllPlugin
插件:用于打包出一個(gè)個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫文件生巡。 -
DllReferencePlugin
插件:用于在主要配置文件中去引入DllPlugin
插件打包好的動(dòng)態(tài)鏈接庫文件。
使用 HappyPack
運(yùn)行在 Node.js
之上的 Webpack
是單線程模型的见妒,
HappyPack
把任務(wù)分解給多個(gè)子進(jìn)程去并發(fā)的執(zhí)行孤荣,子進(jìn)程處理完后再把結(jié)果發(fā)送給主進(jìn)程,從而讓 Webpack
同一時(shí)刻處理多個(gè)任務(wù)须揣,發(fā)揮多核 CPU
電腦的威力盐股。
由于
JavaScript
是單線程模型,要想發(fā)揮多核CPU
的能力耻卡,只能通過多進(jìn)程去實(shí)現(xiàn)疯汁,而無法通過多線程實(shí)現(xiàn)。
使用 ParallelUglifyPlugin
當(dāng) Webpack
有多個(gè) JavaScript
文件需要輸出和壓縮時(shí)卵酪,會(huì)使用 UglifyJS
去一個(gè)個(gè)挨著壓縮再輸出幌蚊, 但是 ParallelUglifyPlugin
則會(huì)開啟多個(gè)子進(jìn)程谤碳,把對(duì)多個(gè)文件的壓縮工作分配給多個(gè)子進(jìn)程去完成,每個(gè)子進(jìn)程其實(shí)還是通過 UglifyJS
去壓縮代碼溢豆,但是變成了并行執(zhí)行蜒简。 所以 ParallelUglifyPlugin
能更快的完成對(duì)多個(gè)文件的壓縮工作。
使用自動(dòng)刷新
讓 Webpack
開啟監(jiān)聽模式漩仙,有兩種方式:
- 在配置文件
webpack.config.js
中設(shè)置watch: true
搓茬。 - 在執(zhí)行啟動(dòng)
Webpack
命令時(shí),帶上--watch
參數(shù)队他,完整命令是webpack --watch
卷仑。
文件監(jiān)聽工作原理:
定時(shí)獲取文件的最后編輯時(shí)間,并存儲(chǔ)麸折,如果發(fā)現(xiàn)當(dāng)前獲取的和最后一次保存的最后編輯時(shí)間不一致锡凝,就認(rèn)為該文件發(fā)生了變化。 watchOptions.poll
就是用于控制定時(shí)檢查的周期垢啼,具體含義是每隔多少毫秒檢查一次私爷。
當(dāng)發(fā)現(xiàn)某個(gè)文件發(fā)生了變化時(shí),并不會(huì)立刻告訴監(jiān)聽者膊夹,而是先緩存起來衬浑,收集一段時(shí)間的變化后,再一次性告訴監(jiān)聽者放刨。 watchOptions.aggregateTimeout
就是用于配置這個(gè)等待時(shí)間工秩。
默認(rèn)情況下 Webpack
會(huì)從配置的 Entry
文件出發(fā),遞歸解析出 Entry
文件所依賴的文件进统,并都加入到監(jiān)聽列表中去助币。
由于保存文件的路徑和最后編輯時(shí)間需要占用內(nèi)存,定時(shí)檢查周期檢查需要占用 CPU
以及文件 I/O
螟碎,所以最好減少需要監(jiān)聽的文件數(shù)量和降低檢查頻率眉菱。
優(yōu)化文件監(jiān)聽性能:ignored: /node_modules/
自動(dòng)刷新瀏覽器:
webpack
模塊負(fù)責(zé)監(jiān)聽文件,webpack-dev-server
模塊則負(fù)責(zé)刷新瀏覽器掉分。 在使用 webpack-dev-server
模塊去啟動(dòng) webpack
模塊時(shí)俭缓,webpack
模塊的監(jiān)聽模式默認(rèn)會(huì)被開啟。 webpack
模塊會(huì)在文件發(fā)生變化時(shí)告訴 webpack-dev-server
模塊酥郭。
自動(dòng)刷新的原理:
- 借助瀏覽器擴(kuò)展去通過瀏覽器提供的接口刷新华坦,WebStorm IDE 的 LiveEdit 功能就是這樣實(shí)現(xiàn)的。
- 往要開發(fā)的網(wǎng)頁中注入代理客戶端代碼不从,通過代理客戶端去刷新整個(gè)頁面惜姐。
- 把要開發(fā)的網(wǎng)頁裝進(jìn)一個(gè) iframe 中,通過刷新 iframe 去看到最新效果椿息。
DevServer
支持第2歹袁、3種方法坷衍,第2種是DevServer
默認(rèn)采用的刷新方法。
開啟模塊熱替換
當(dāng)一個(gè)源碼發(fā)生變化時(shí)条舔,只重新編譯發(fā)生變化的模塊惫叛,再用新輸出的模塊替換掉瀏覽器中對(duì)應(yīng)的老模塊。
DevServer
默認(rèn)不會(huì)開啟模塊熱替換模式逞刷,要開啟該模式,只需在啟動(dòng)時(shí)帶上參數(shù) --hot
妻熊,完整命令是 webpack-dev-server --hot
夸浅。
區(qū)分環(huán)境
當(dāng)你的代碼中出現(xiàn)了使用 process
模塊的語句時(shí),Webpack
就自動(dòng)打包進(jìn) process
模塊的代碼以支持非 Node.js
的運(yùn)行環(huán)境扔役。 這個(gè)注入的 process
模塊作用是為了模擬 Node.js
中的 process
帆喇,以支持上面使用的 process.env.NODE_ENV === 'production'
語句。
const DefinePlugin = require('webpack/lib/DefinePlugin');
module.exports = {
plugins: [
new DefinePlugin({
// 定義 NODE_ENV 環(huán)境變量為 production
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
],
};
注意在定義環(huán)境變量的值時(shí)用
JSON.stringify
包裹字符串的原因是環(huán)境變量的值需要是一個(gè)由雙引號(hào)包裹的字符串亿胸,而JSON.stringify('production')
的值正好等于'"production"'
坯钦。
壓縮代碼
壓縮
JavaScript
目前最成熟的JavaScript
代碼壓縮工具是UglifyJS
, 它會(huì)分析JavaScript
代碼語法樹侈玄,理解代碼含義婉刀,從而能做到諸如去掉無效代碼、去掉日志輸出代碼序仙、縮短變量名等優(yōu)化突颊。
直接在啟動(dòng)Webpack
時(shí)帶上--optimize-minimize
參數(shù),即webpack --optimize-minimize
潘悼, 這樣Webpack
會(huì)自動(dòng)為你注入一個(gè)帶有默認(rèn)配置的UglifyJSPlugin
律秃。壓縮
ES6
UglifyJS
只認(rèn)識(shí)ES5
語法的代碼。 為了壓縮ES6
代碼治唤,需要使用專門針對(duì)ES6
代碼的UglifyES
棒动。壓縮
CSS
要開啟cssnano
去壓縮代碼只需要開啟css-loader
的minimize
選項(xiàng)。
CDN 加速
CDN
又叫內(nèi)容分發(fā)網(wǎng)絡(luò)宾添,通過把資源部署到世界各地船惨,用戶在訪問時(shí)按照就近原則從離用戶最近的服務(wù)器獲取資源,從而加速資源的獲取速度缕陕。
CDN
其實(shí)是通過優(yōu)化物理鏈路層傳輸過程中的網(wǎng)速有限掷漱、丟包等問題來加速網(wǎng)絡(luò)傳輸?shù)摹?br>
過 publicPath
參數(shù)設(shè)置存放靜態(tài)資源的 CDN
目錄 URL
, 為了讓不同類型的資源輸出到不同的 CDN
榄檬,需要分別在:
-
output.publicPath
中設(shè)置JavaScript
的地址卜范。 -
css-loader.publicPath
中設(shè)置被CSS
導(dǎo)入的資源的的地址。 -
WebPlugin.stylePublicPath
中設(shè)置CSS
文件的地址鹿榜。
使用 Tree Shaking
Tree Shaking
可以用來剔除 JavaScript
中用不上的死代碼海雪。它依賴靜態(tài)的 ES6
模塊化語法锦爵。
提取公共代碼
Webpack
內(nèi)置了專門用于提取多個(gè) Chunk
中公共部分的插件 CommonsChunkPlugin
。
按需加載
使用 Prepack
Prepack
在保持運(yùn)行結(jié)果一致的情況下奥裸,改變?cè)创a的運(yùn)行邏輯险掀,輸出性能更高的 JavaScript
代碼。 實(shí)際上 Prepack
就是一個(gè)部分求值器湾宙,編譯代碼時(shí)提前將計(jì)算結(jié)果放到編譯后的代碼中樟氢,而不是在代碼運(yùn)行時(shí)才去求值,從而優(yōu)化代碼在運(yùn)行時(shí)的效率侠鳄。
Prepack
還處于初期的開發(fā)階段埠啃,局限性也很大。
開啟 Scope Hoisting伟恶,又叫作用域提升
原理:分析出模塊之間的依賴關(guān)系碴开,盡可能的把打散的模塊合并到一個(gè)函數(shù)中去,但前提是不能造成代碼冗余博秫。 因此只有那些被引用了一次的模塊才能被合并潦牛。
輸出分析
接入 webpack-bundle-analyzer
:
npm i -g webpack-bundle-analyzer
- 啟動(dòng)
Webpack
命令:webpack --profile --json > stats.json
- 在項(xiàng)目根目錄中執(zhí)行
webpack-bundle-analyzer
后,瀏覽器會(huì)打開對(duì)應(yīng)網(wǎng)頁看到分析結(jié)果挡育。
又話總結(jié)
- 側(cè)重優(yōu)化開發(fā)體驗(yàn)的配置文件
webpack.config.js
const path = require('path');
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const {AutoWebPlugin} = require('web-webpack-plugin');
const HappyPack = require('happypack');
// 自動(dòng)尋找 pages 目錄下的所有目錄巴碗,把每一個(gè)目錄看成一個(gè)單頁應(yīng)用
const autoWebPlugin = new AutoWebPlugin('./src/pages', {
// HTML 模版文件所在的文件路徑
template: './template.html',
// 提取出所有頁面公共的代碼
commonsChunk: {
// 提取出公共代碼 Chunk 的名稱
name: 'common',
},
});
module.exports = {
// AutoWebPlugin 會(huì)為尋找到的所有單頁應(yīng)用,生成對(duì)應(yīng)的入口配置即寒,
// autoWebPlugin.entry 方法可以獲取到生成入口配置
entry: autoWebPlugin.entry({
// 這里可以加入你額外需要的 Chunk 入口
base: './src/base.js',
}),
output: {
filename: '[name].js',
},
resolve: {
// 使用絕對(duì)路徑指明第三方模塊存放的位置良价,以減少搜索步驟
// 其中 __dirname 表示當(dāng)前工作目錄,也就是項(xiàng)目根目錄
modules: [path.resolve(__dirname, 'node_modules')],
// 針對(duì) Npm 中的第三方模塊優(yōu)先采用 jsnext:main 中指向的 ES6 模塊化語法的文件蒿叠,使用 Tree Shaking 優(yōu)化
// 只采用 main 字段作為入口文件描述字段明垢,以減少搜索步驟
mainFields: ['jsnext:main', 'main'],
},
module: {
rules: [
{
// 如果項(xiàng)目源碼中只有 js 文件就不要寫成 /\.jsx?$/,提升正則表達(dá)式性能
test: /\.js$/,
// 使用 HappyPack 加速構(gòu)建
use: ['happypack/loader?id=babel'],
// 只對(duì)項(xiàng)目根目錄下的 src 目錄中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
{
test: /\.js$/,
use: ['happypack/loader?id=ui-component'],
include: path.resolve(__dirname, 'src'),
},
{
// 增加對(duì) CSS 文件的支持
test: /\.css$/,
use: ['happypack/loader?id=css'],
},
]
},
plugins: [
autoWebPlugin,
// 使用 HappyPack 加速構(gòu)建
new HappyPack({
id: 'babel',
// babel-loader 支持緩存轉(zhuǎn)換出的結(jié)果市咽,通過 cacheDirectory 選項(xiàng)開啟
loaders: ['babel-loader?cacheDirectory'],
}),
new HappyPack({
// UI 組件加載拆分
id: 'ui-component',
loaders: [{
loader: 'ui-component-loader',
options: {
lib: 'antd',
style: 'style/index.css',
camel2: '-'
}
}],
}),
new HappyPack({
id: 'css',
// 如何處理 .css 文件痊银,用法和 Loader 配置中一樣
loaders: ['style-loader', 'css-loader'],
}),
// 提取公共代碼
new CommonsChunkPlugin({
// 從 common 和 base 兩個(gè)現(xiàn)成的 Chunk 中提取公共的部分
chunks: ['common', 'base'],
// 把公共的部分放到 base 中
name: 'base'
}),
],
watchOptions: {
// 使用自動(dòng)刷新:不監(jiān)聽的 node_modules 目錄下的文件
ignored: /node_modules/,
}
};
- 側(cè)重優(yōu)化輸出質(zhì)量的配置文件
webpack-dist.config.js
const path = require('path');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const {AutoWebPlugin} = require('web-webpack-plugin');
const HappyPack = require('happypack');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
// 自動(dòng)尋找 pages 目錄下的所有目錄,把每一個(gè)目錄看成一個(gè)單頁應(yīng)用
const autoWebPlugin = new AutoWebPlugin('./src/pages', {
// HTML 模版文件所在的文件路徑
template: './template.html',
// 提取出所有頁面公共的代碼
commonsChunk: {
// 提取出公共代碼 Chunk 的名稱
name: 'common',
},
// 指定存放 CSS 文件的 CDN 目錄 URL
stylePublicPath: '//css.cdn.com/id/',
});
module.exports = {
// AutoWebPlugin 會(huì)找為尋找到的所有單頁應(yīng)用施绎,生成對(duì)應(yīng)的入口配置溯革,
// autoWebPlugin.entry 方法可以獲取到生成入口配置
entry: autoWebPlugin.entry({
// 這里可以加入你額外需要的 Chunk 入口
base: './src/base.js',
}),
output: {
// 給輸出的文件名稱加上 Hash 值
filename: '[name]_[chunkhash:8].js',
path: path.resolve(__dirname, './dist'),
// 指定存放 JavaScript 文件的 CDN 目錄 URL
publicPath: '//js.cdn.com/id/',
},
resolve: {
// 使用絕對(duì)路徑指明第三方模塊存放的位置,以減少搜索步驟
// 其中 __dirname 表示當(dāng)前工作目錄谷醉,也就是項(xiàng)目根目錄
modules: [path.resolve(__dirname, 'node_modules')],
// 只采用 main 字段作為入口文件描述字段致稀,以減少搜索步驟
mainFields: ['jsnext:main', 'main'],
},
module: {
rules: [
{
// 如果項(xiàng)目源碼中只有 js 文件就不要寫成 /\.jsx?$/,提升正則表達(dá)式性能
test: /\.js$/,
// 使用 HappyPack 加速構(gòu)建
use: ['happypack/loader?id=babel'],
// 只對(duì)項(xiàng)目根目錄下的 src 目錄中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
{
test: /\.js$/,
use: ['happypack/loader?id=ui-component'],
include: path.resolve(__dirname, 'src'),
},
{
// 增加對(duì) CSS 文件的支持
test: /\.css$/,
// 提取出 Chunk 中的 CSS 代碼到單獨(dú)的文件中
use: ExtractTextPlugin.extract({
use: ['happypack/loader?id=css'],
// 指定存放 CSS 中導(dǎo)入的資源(例如圖片)的 CDN 目錄 URL
publicPath: '//img.cdn.com/id/'
}),
},
]
},
plugins: [
autoWebPlugin,
// 開啟ScopeHoisting
new ModuleConcatenationPlugin(),
// 使用HappyPack
new HappyPack({
// 用唯一的標(biāo)識(shí)符 id 來代表當(dāng)前的 HappyPack 是用來處理一類特定的文件
id: 'babel',
// babel-loader 支持緩存轉(zhuǎn)換出的結(jié)果俱尼,通過 cacheDirectory 選項(xiàng)開啟
loaders: ['babel-loader?cacheDirectory'],
}),
new HappyPack({
// UI 組件加載拆分
id: 'ui-component',
loaders: [{
loader: 'ui-component-loader',
options: {
lib: 'antd',
style: 'style/index.css',
camel2: '-'
}
}],
}),
new HappyPack({
id: 'css',
// 如何處理 .css 文件抖单,用法和 Loader 配置中一樣
// 通過 minimize 選項(xiàng)壓縮 CSS 代碼
loaders: ['css-loader?minimize'],
}),
new ExtractTextPlugin({
// 給輸出的 CSS 文件名稱加上 Hash 值
filename: `[name]_[contenthash:8].css`,
}),
// 提取公共代碼
new CommonsChunkPlugin({
// 從 common 和 base 兩個(gè)現(xiàn)成的 Chunk 中提取公共的部分
chunks: ['common', 'base'],
// 把公共的部分放到 base 中
name: 'base'
}),
new DefinePlugin({
// 定義 NODE_ENV 環(huán)境變量為 production 去除 react 代碼中的開發(fā)時(shí)才需要的部分
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
// 使用 ParallelUglifyPlugin 并行壓縮輸出的 JS 代碼
new ParallelUglifyPlugin({
// 傳遞給 UglifyJS 的參數(shù)
uglifyJS: {
output: {
// 最緊湊的輸出
beautify: false,
// 刪除所有的注釋
comments: false,
},
compress: {
// 在UglifyJs刪除沒有用到的代碼時(shí)不輸出警告
warnings: false,
// 刪除所有的 `console` 語句,可以兼容ie瀏覽器
drop_console: true,
// 內(nèi)嵌定義了但是只用到一次的變量
collapse_vars: true,
// 提取出出現(xiàn)多次但是沒有定義成變量去引用的靜態(tài)值
reduce_vars: true,
}
},
}),
]
};