Webpack 進階用法
當前構建時的問題
- 每次構建的時候不會清理目錄,造成構建的輸出目錄
output
文件越來越多
通過npm script
清理構建目錄(並不優雅的處理方式)
rm -rf ./dist && webpack
rimraf ./dist && webpack
自動清理構建目錄
-
避免構建前每次都需要手動刪除
dist
-
使用
clean-webpack-plugin
- 默認會刪除
output
指定的輸出目錄
- 默認會刪除
-
plugins:[ new CleanWebpackPlugin() ]
CSS
增強
CSS
兼容添加前綴
IE
==>Trident(-ms)
Geko(-moz)
火狐WebKit(-webkit)
谷歌Presto(-0)
歐鵬
使用PostCSS
插件autoprefixer
自動補齊CSS3
前綴
-
npm i postcss-loader autoprefixer -D
-
使用
autoprefixer
插件 -
根據
Can I Use規則
https://caniuse.com/
-
module :{ rules:[ { test:/.less$/, use:[ 'style-loader', 'css-loader', 'less-loader', { loader:'postcss-loader', //參數 options:{ plugins:()=>{ //引入 require('autoprefixer')({ //可以設置 autoprefixer 兼容瀏覽器版本 //>1% 使用人數佔比 //ios7 兼容ios7版本以上 版本 browsers:['last 2 version','>1%,'ios7'] }) } } } ] }, ] }
將移動端CSS
px
自動轉換成rem
CSS
媒體查詢實現 響應式佈局
- 缺陷:需要寫多套適配樣式代碼
使用px2rem-loader
- 頁面渲染時計算根元素的
font-size
值- 可以使用手淘的
lib-flexible
github.com/amfe/lib-flexible
- 可以使用手淘的
module :{
rules:[
{
test:/.less$/,
use:[
'style-loader',
'css-loader',
'less-loader',
{
loader:'postcss-loader',
//參數
options:{
remUnit:75,
remPrecision:8//轉換成rem 後面的小數點位數
}
}
]
},
]
}
資源內聯的意義
- 代碼層面
- 頁面框架的初始化腳本
- 上報相關打點
css
內聯避免頁面閃動
請求層面:減少http
網絡請求數
- 小圖片或者字體內聯(
url-loader
)
HTML 和 JS
內聯
raw-loader
內聯html
與js
安裝版本爲:npm i [email protected] -D
css
內聯
- 方案一:藉助
style-loader
- 方案二:
html-inline-css-webpack-pulugin
module :{
rules:[
{
test:/.less$/,
use:[
{
loader:'style-loader',
//參數
options:{
inserAt:'top',//樣式插入到`<head>`
singleton:true,//將所有的`style`標籤合併成一個
}
}
'css-loader',
'less-loader',
]
},
]
}
多頁面應用(MPA
)概念
每一次頁面跳轉得時候,後臺服務器都會返回一個新的html
文檔,這種類型得網站也就是多頁網站,也叫做多頁應用
多頁面打包基本思路
每個頁面對應一個entry
,一個html-webpack-plugin
缺點:每次新增或刪除頁面需要修改webpack
配置
module.exports = {
entry :{
index:'./src/index.js',
search:'./src/search.js'
}
}
動態獲取entry
和設置 html-webpack-plugin
數量
利用 glob.sync
entry:glob.sync(path.join(_dirname,'./src/*/index.js'))
module.exports = {
entry :{
index:'./src/index/index.js',
search:'./src/search/index.js'
}
}
//約定:把所有得頁面都放在 src 目錄下面,每個頁面得人口文件都命名爲index.js文件
- 通過
glob
多頁面打包
npm i glob
const glob = require('glob')
const setMPA = () =>{
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname,'./src/*/'));//匹配src目錄下所有頁面內容
Object.keys(entryFiles).map(index=>{
const entryFire = entryFiles[index];
const match = entryFiles.mach(/src\(.*)\/index\.js/)
const pageName = match && match[1]
entryFiles[pageName] = entryFiles;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
template:path.join(_dirname,`src/${pageName}/index.html`),
filename:`${pageName}.html`, //指定打包出來之後得文件名稱
chunks:['search'],
inject:true,
minify:{
html:true,
collapseWhitespace:true,
preserveLineBreaks:false,
minifyCSS:true,
minifyJS:true,
removeComments:false
}
})
})
return {
entry,
htmlWebpackPlugins
}
}
使用source map
作用:通過source map
定位到源代碼
開發環境開啓,線上環境關閉
- 線上排查問題得時候可以將
sourcemap
上傳到錯誤監控系統
source map
關鍵字
eval
:使用eval
包裹模塊代碼source map
:產生.map
文件cheap
:不包含列信息inline
:將.map
作爲DataURL
嵌入,不單獨生成.map
文件module
:包含loader
的sourcemap
提取頁面公共資源
基礎庫基礎
思路:將react 、 react-dom
基礎包通過引入cdn
引入,不打入bundle
中
方法:使用html-webpack-externals-plugin
利用SplitChunksPlugin
進行公共腳本分離
Webpack4
內置的,替代CommonsChunkPlugin
插件chunks
參數說明:async
異步引入的庫進行分離(默認)initial
:同步引入的庫進行分離all
:所有引入的庫進行分離(推薦)
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
}
}
}
}
}
利用**SplitChunksPlugin
**分離基礎包
test
:匹配出需要分離的包
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name:'vendors',
chunks: 'all'
}
}
}
}
};
tree shaking
(搖樹優化)
概念:1個模塊可能有多個方法,只要把其中的某個方法使用到了,則整個文件都會被達到bundle
裏面去,tree shaking
就是隻把用到的方法打入bundle
,沒用到的方法會在uglify
階段被擦除掉
使用:webpack
默認支持,在.babelrc
裏設置modules:false
即可.production mode
的情況下默認開啓
要求:必須是ES6
的語法,CJS
的方式不支持
DCE (Elimination)
代碼不會被執行,不可到達
代碼執行的結果不會被用到
代碼只會影響死變量(只寫不讀)
tree shaking
原理
利用ES6
模塊的特點:
- 只能作爲模塊頂層的語句出現
import
模塊名只能是字符串常量import binding
是immutable
的
代碼擦除:ugkify
階段刪除無用代碼
ScopeHoisting
使用和原理分析
現象:構建後代碼存在大量閉包代碼
模塊轉換分析
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eoaFSTUg-1586446111477)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200408234303736.png)]
結論:
- 被
webpack
轉換後的模塊會帶上一層包裹 import
會被轉換成__webpack_require
進一步分析webpack
的模塊機制
分析:
- 打包出來的是一個
llFE
(匿名閉包) modules
是一個數組,每一項是一個模塊初始化函數__webpack_require
用來加載模塊,返回module.exports
- 通過
WEBPACK_REQUIRE_METHOD(0)
啓動程序
scope hoisting
原理
原理:將所有模塊的代碼按照引用順序放在一個函數作用域裏,然後適當的重命名一些變量以防止變量名衝突
對比:通過scope hoisting
可以減少函數聲明代碼和內存開銷
scope hoisting
webpack mode
爲production
默認開啓
必須是ES6
語法,CJS
不支持
代碼分割得意義
對於大得web
應用來說,將所有的代碼都放在一個文件中顯然是不夠有效的,特別是當你的某些代碼塊實在某些特殊的時候纔會被用到,webpack
有有一個功能就是將你的代碼庫分割成chunks
(語塊),當代碼運行到需要它們的時候在在加載
適用的場景:
- 抽離相同代碼到一個共享塊
- 腳本懶加載,是的初始下載的代碼更小
懶加載JS
腳本的方式
ComonJS
:require.ensure
ES6
:動態import
(目前還沒有原生支持,需要babel
轉換)
如何使用動態import
安裝babel
插件
npm install @babel/plugin-syntax-dynamic-import --save-dev
{
"plugins":["@babel/plugin-syntax-dynamic-import"]
}
webpack
和ESLint
結合
安裝husky
npm i husky --seve-dev
增加npm script
,通過lint-staged
增量檢查修改的文件
"script":{
"precommit":"lint-staged"
},
"lint-staged":{
"linters":{
"*.{js.scss}":["eslint--fix","git add"]
}
}
新建項目 時候 webpack
與 ESLint
集成
- 使用
eslint-loader
,構建時檢查JS
規範
module.exports = {
//...
module: {
rules: {
{
test:/\.js$/,
exclude:/node_modules/,
use:[
"babel-loader",
"eslint-loader"
]
}
}
}
};
webpack
打包庫和組件
webpack
除了可以用來打包應用,也可以用來打包JS
庫
實現一個大整數加法庫的打包
- 需要打包壓縮版和非壓縮版本
- 支持
AMD / CJS / ESM
模塊引入ESM=>import * as test from 'test'
CJS =>const test = require('test')
AMD =>require(['test'],function(test){ // ...})
如何將庫暴露出去
library:
指定庫的全局變量libraryTarget:
支持庫引入的方式
module.exports = {
mode:'none',
entry :{
"large-number":'./src/index/index.js',
"large-number.min":'./src/search/index.js'
},
output:{
filename:"[name].js",
library:'largeNumber',
libraryExport:'default',
libraryTarget:'umd'
},
optimization:{
minimize:true,
minimizer:[
new TerserPlugin({
include:/\.min\.js$/ //通過include 設置只壓縮 min.js 結尾的文件
})
]
}
}
設置人口文件
package.json 的 main字段爲 index.js main:index.js
if(process.env.NODE_ENV === "production"){
module.exports = require("./dist/large-number.min.js")
}else{
module.exports = require("./dist/large-number.js")
}
webpack
實現ssr
打包
服務端渲染ssr
是什麼
渲染:HTML + CSS + JS +Data==>
渲染後的HTML
服務端:
- 所有模板等資源都存儲在服務端
- 內網機器拉取數據更快
- 一個
HTML
返回所有數據
瀏覽器和服務器交互流程
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lqQvoiaX-1586446111479)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409222304222.png)]
客戶端渲染 vs
服務端渲染
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oMGMtNN9-1586446111480)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409222548020.png)]
總結:服務端渲染(SSR)的核心是減少請求
ssr
的優勢
- 減少白屏的時間
- 對於
SEO
友好
SSR
代碼實現思路
- 服務端
- 使用
react-dom / server
的renderToString
方法將React
組件渲染成字符串,因爲在服務端是沒有window
這些對象 - 服務端路由返回對應的模板
- 使用
- 客戶端
- 打包出針對服務端的組件
webpack ssr
打包存在的問題
瀏覽器的全局變量(Node.js
中沒有document
,window
)
- 組件適配:將不兼容的組件根據打包環境進行適配
- 請求適配:將
fetch
或者ajax
發送請求的寫法改成isomorphic-fetch
或者axios
樣式問題(Node.js
無法解析css
)
- 方案一:服務端打包通過
ignore-loader
忽略掉css
的解析 - 方案二:將
style-loader
替換成isomorphic-style-loader
如何解決SSR
打包之後樣式不顯示的問題
使用打包出來的瀏覽器端html
爲模板
設置佔位符,動態插入組件
比如 iview table
在查看網頁源代碼之後會出現很多<!---->
,就是佔位從而顯示樣式
首屏數據如何處理
服務端獲取數據
替換佔位符
優化構建時命令行的顯示日誌
統計信息 stats
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-g0kvygEF-1586446111481)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409230846573.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Q84SZkaE-1586446111482)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409231203671.png)]
如何判斷構建是否成功
在CI / CD
的 pipline
或者發佈系統需要知道當前構建狀態
每次構建完成後輸入echo $
獲取錯誤嗎
構建異常和中斷處理
webpack4
之前的版本構建失敗不會拋出錯誤碼(error code
)
Node.js
中的process.exit
規範
-
0 表示成功完成,回調函數中,
err
爲null -
非0 表示執行失敗,回調函數中,
err
不爲null
,err.code
就是傳給exit
的數字
如何主動捕獲並處理構建錯誤
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OPuMUzFL-1586446111483)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200409232612853.png)]