Webpack入門-學習總結
目錄【轉載】:本文源地址http://www.woc12138.com/article/45
- 一、Webpack 簡介
- 二、Webpack 初體驗
- 三、Webpack 開發環境的基本配置
- 四、Webpack 生產環境的基本配置
- 五、Webpack 優化配置
- 六、Webpack 配置詳情
- 七、Webpack5 介紹和使用
一、Webpack 簡介
1.1 webpack 是什麼
webpack 是一種前端資源構建工具,一個靜態模組打包器(module bundler)。
在webpack 看來, 前端的所有資原始檔(js/json/css/img/less/...)都會作為模組處理。
它將根據模組的依賴關係進行靜態分析,打包生成對應的靜態資源(bundle)。
1.2 webpack 五個核心概念
1.2.1 Entry
入口(Entry):指示 webpack 以哪個檔案為入口起點開始打包,分析構建內部依賴圖。
1.2.2 Output
輸出(Output):指示 webpack 打包後的資源 bundles 輸出到哪裡去,以及如何命名。
1.2.3 Loader
Loader:讓 webpack 能夠去處理那些非 JS 的檔案,比如樣式檔案、圖片檔案(webpack 自身只理解
JS)
1.2.4 Plugins
外掛(Plugins):可以用於執行範圍更廣的任務。外掛的範圍包括,從打包優化和壓縮,
一直到重新定義環境中的變數等。
1.2.5 Mode
模式(Mode):指示 webpack 使用相應模式的配置。
選項 | 描述 | 特點 |
---|---|---|
development | 會將 DefinePlugin 中 process.env.NODE_ENV 的值設定為 development。啟用 NamedChunksPlugin 和 NamedModulesPlugin。 | 能讓程式碼本地除錯執行的環境 |
production | 會將 DefinePlugin 中 process.env.NODE_ENV 的值設定為 production。啟用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin。 | 能讓程式碼優化上線執行的環境 |
二、Webpack 初體驗
2.1 初始化配置
-
初始化 package.json:npm init
-
下載安裝webpack:(webpack4以上的版本需要全域性/本地都安裝webpack-cli)
全域性安裝:cnpm i webpack webpack-cli -g
本地安裝:cnpm i webpack webpack-cli -D
2.2 編譯打包應用
建立 src 下的 js 等檔案後,不需要配置 webpack.config.js 檔案,在命令列就可以編譯打包。
指令:
-
開發環境:webpack ./src/index.js -o ./build/built.js --mode=development
webpack會以 ./src/index.js 為入口檔案開始打包,打包後輸出到 ./build/built.js 整體打包環境,是開發環境
-
生產環境:webpack ./src/index.js -o ./build/built.js --mode=production
webpack會以 ./src/index.js 為入口檔案開始打包,打包後輸出到 ./build/built.js 整體打包環境,是生產環境
結論:
- webpack 本身能處理 js/json 資源,不能處理 css/img 等其他資源
- 生產環境和開發環境將 ES6 模組化編譯成瀏覽器能識別的模組化,但是不能處理 ES6 的基本語法轉化為 ES5(需要藉助 loader)
- 生產環境比開發環境多一個壓縮 js 程式碼
三、Webpack 開發環境的基本配置
webpack.config.js 是 webpack 的配置檔案。
作用: 指示 webpack 幹哪些活(當你執行 webpack 指令時,會載入裡面的配置)
所有構建工具都是基於 nodejs 平臺執行的,模組化預設採用 commonjs。
開發環境配置主要是為了能讓程式碼執行。主要考慮以下幾個方面:
- 打包樣式資源
- 打包 html 資源
- 打包圖片資源
- 打包其他資源
- devServer
下面是一個簡單的開發環境webpack.confg.js配置檔案
// resolve用來拼接絕對路徑的方法
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin') // 引用plugin
module.exports = {
// webpack配置
entry: './src/js/index.js', // 入口起點
output: {
// 輸出
// 輸出檔名
filename: 'js/build.js',
// __dirname是nodejs的變數,代表當前檔案的目錄絕對路徑
path: resolve(__dirname, 'build'), // 輸出路徑,所有資源打包都會輸出到這個資料夾下
},
// loader配置
module: {
rules: [
// 詳細的loader配置
// 不同檔案必須配置不同loader處理
{
// 匹配哪些檔案
test: /\.less$/,
// 使用哪些loader進行處理
use: [
// use陣列中loader執行順序:從右到左,從下到上,依次執行(先執行css-loader)
// style-loader:建立style標籤,將js中的樣式資源插入進去,新增到head中生效
'style-loader',
// css-loader:將css檔案變成commonjs模組載入到js中,裡面內容是樣式字串
'css-loader',
// less-loader:將less檔案編譯成css檔案,需要下載less-loader和less
'less-loader'
],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
// url-loader:處理圖片資源,問題:預設處理不了html中的img圖片
test: /\.(jpg|png|gif)$/,
// 需要下載 url-loader file-loader
loader: 'url-loader',
options: {
// 圖片大小小於8kb,就會被base64處理,優點:減少請求數量(減輕伺服器壓力),缺點:圖片體積會更大(檔案請求速度更慢)
// base64在客戶端本地解碼所以會減少伺服器壓力,如果圖片過大還採用base64編碼會導致cpu呼叫率上升,網頁載入時變卡
limit: 8 * 1024,
// 給圖片重新命名,[hash:10]:取圖片的hash的前10位,[ext]:取檔案原來副檔名
name: '[hash:10].[ext]',
// 問題:因為url-loader預設使用es6模組化解析,而html-loader引入圖片是conmonjs,解析時會出問題:[object Module]
// 解決:關閉url-loader的es6模組化,使用commonjs解析
esModule: false,
outputPath: 'imgs',
},
},
{
test: /\.html$/,
// 處理html檔案的img圖片(負責引入img,從而能被url-loader進行處理)
loader: 'html-loader',
},
// 打包其他資源(除了html/js/css資源以外的資源)
{
// 排除html|js|css|less|jpg|png|gif檔案
exclude: /\.(html|js|css|less|jpg|png|gif)/,
// file-loader:處理其他檔案
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media',
},
},
],
},
// plugin的配置
plugins: [
// html-webpack-plugin:預設會建立一個空的html檔案,自動引入打包輸出的所有資源(JS/CSS)
// 需要有結構的HTML檔案可以加一個template
new HtmlWebpackPlugin({
// 複製這個./src/index.html檔案,並自動引入打包輸出的所有資源(JS/CSS)
template: './src/index.html',
}),
],
// 模式
mode: 'development', // 開發模式
// 開發伺服器 devServer:用來自動化,不用每次修改後都重新輸入webpack打包一遍(自動編譯,自動開啟瀏覽器,自動重新整理瀏覽器)
// 特點:只會在記憶體中編譯打包,不會有任何輸出(不會像之前那樣在外面看到打包輸出的build包,而是在記憶體中,關閉後會自動刪除)
// 啟動devServer指令為:npx webpack-dev-server
devServer: {
// 專案構建後路徑
contentBase: resolve(__dirname, 'build'),
// 啟動gzip壓縮
compress: true,
// 埠號
port: 3000,
// 自動開啟瀏覽器
open: true,
},
}
其中,大部分配置都在註釋中給出解釋。
-
執行專案的兩個指令:
webpack 會將打包結果輸出出去(build資料夾)
npx webpack-dev-server 只會在記憶體中編譯打包,沒有輸出 -
loader 和 plugin 的不同:(plugin 一定要先引入才能使用)
loader:1. 下載 2. 使用(配置 loader)
plugins:1.下載 2. 引入 3. 使用
四、Webpack 生產環境的基本配置
而生產環境的配置需要考慮以下幾個方面:
- 提取 css 成單獨檔案
- css 相容性處理
- 壓縮 css
- js 語法檢查
- js 相容性處理
- js 壓縮
- html 壓縮
下面是一個基本的生產環境下的webpack.config.js配置
const { resolve } = require('path')
const MiniCssExtractorPlugin = require('mini-css-extract-plugin')
const OptimiziCssAssetsWebpackPlugin = require('optimizi-css-assets-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 定義node.js的環境變數,決定使用browserslist的哪個環境
process.env.NODE_ENV = 'production'
// 複用loader的寫法
const commonCssLoader = [
// 這個loader取代style-loader。作用:提取js中的css成單獨檔案然後通過link載入
MiniCssExtractPlugin.loader,
// css-loader:將css檔案整合到js檔案中
// 經過css-loader處理後,樣式檔案是在js檔案中的
// 問題:1.js檔案體積會很大2.需要先載入js再動態建立style標籤,樣式渲染速度就慢,會出現閃屏現象
// 解決:用MiniCssExtractPlugin.loader替代style-loader
'css-loader',
/*
postcss-loader:css相容性處理:postcss --> 需要安裝:postcss-loader postcss-preset-env
postcss需要通過package.json中browserslist裡面的配置載入指定的css相容性樣式
在package.json中定義browserslist:
"browserslist": {
// 開發環境 --> 設定node環境變數:process.env.NODE_ENV = development
"development": [ // 只需要可以執行即可
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
// 生產環境。預設是生產環境
"production": [ // 需要滿足絕大多數瀏覽器的相容
">0.2%",
"not dead",
"not op_mini all"
]
},
*/
{
loader: 'postcss-loader',
options: {
ident: 'postcss', // 基本寫法
plugins: () => [
// postcss的外掛
require('postcss-preset-env')(),
],
},
},
]
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.css$/,
use: [...commonCssLoader],
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader'],
},
/*
正常來講,一個檔案只能被一個loader處理
當一個檔案要被多個loader處理,那麼一定要指定loader執行的先後順序
先執行eslint再執行babel(用enforce)
*/
{
/*
js的語法檢查: 需要下載 eslint-loader eslint
注意:只檢查自己寫的原始碼,第三方的庫是不用檢查的
airbnb(一個流行的js風格) --> 需要下載 eslint-config-airbnb-base eslint-plugin-import
設定檢查規則:
package.json中eslintConfig中設定
"eslintConfig": {
"extends": "airbnb-base", // 繼承airbnb的風格規範
"env": {
"browser": true // 可以使用瀏覽器中的全域性變數(使用window不會報錯)
}
}
*/
test: /\.js$/,
exclude: /node_modules/, // 忽略node_modules
enforce: 'pre', // 優先執行
loader: 'eslint-loader',
options: {
// 自動修復
fix: true,
},
},
/*
js相容性處理:需要下載 babel-loader @babel/core
1. 基本js相容性處理 --> @babel/preset-env
問題:只能轉換基本語法,如promise高階語法不能轉換
2. 全部js相容性處理 --> @babel/polyfill
問題:只要解決部分相容性問題,但是將所有相容性程式碼全部引入,體積太大了
3. 需要做相容性處理的就做:按需載入 --> core-js
*/
{
// 第三種方式:按需載入
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 預設:指示babel做怎樣的相容性處理
presets: [
'@babel/preset-env', // 基本預設
{
useBuiltIns: 'usage', //按需載入
corejs: { version: 3 }, // 指定core-js版本
targets: { // 指定相容到什麼版本的瀏覽器
chrome: '60',
firefox: '50',
ie: '9',
safari: '10',
edge: '17'
},
},
],
},
},
{
// 圖片處理
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false, // 關閉url-loader預設使用的es6模組化解析
},
},
// html中的圖片處理
{
test: /\.html$/,
loader: 'html-loader',
},
// 處理其他檔案
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media',
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
// 對輸出的css檔案進行重新命名
filename: 'css/built.css',
}),
// 壓縮css
new OptimiziCssAssetsWebpackPlugin(),
// HtmlWebpackPlugin:html檔案的打包和壓縮處理
// 通過這個外掛會自動將單獨打包的樣式檔案通過link標籤引入
new HtmlWebpackPlugin({
template: './src/index.html',
// 壓縮html程式碼
minify: {
// 移除空格
collapseWhitespace: true,
// 移除註釋
removeComments: true,
},
}),
],
// 生產環境下會自動壓縮js程式碼
mode: 'production',
}
五、Webpack 優化配置
5.1 開發環境效能優化
5.1.1 HMR(模組熱替換)
HMR: hot module replacement 熱模組替換 / 模組熱替換
作用:一個模組發生變化,只會重新打包構建這一個模組(而不是打包所有模組) ,極大提升構建速度
程式碼:只需要在 devServer 中設定 hot 為 true,就會自動開啟HMR功能(只能在開發模式下使用)
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 開啟HMR功能
// 當修改了webpack配置,新配置要想生效,必須重啟webpack服務
hot: true
}
每種檔案實現熱模組替換的情況:
-
樣式檔案:可以使用HMR功能,因為開發環境下使用的 style-loader 內部預設實現了熱模組替換功能
-
js 檔案:預設不能使用HMR功能(修改一個 js 模組所有 js 模組都會重新整理)
--> 實現 HMR 需要修改 js 程式碼(新增支援 HMR 功能的程式碼)
// 繫結 if (module.hot) { // 一旦 module.hot 為true,說明開啟了HMR功能。 --> 讓HMR功能程式碼生效 module.hot.accept('./print.js', function() { // 方法會監聽 print.js 檔案的變化,一旦發生變化,只有這個模組會重新打包構建,其他模組不會。 // 會執行後面的回撥函式 print(); }); }
注意:HMR 功能對 js 的處理,只能處理非入口 js 檔案的其他檔案。
-
html 檔案: 預設不能使用 HMR 功能(html 不用做 HMR 功能,因為只有一個 html 檔案,不需要再優化)
使用 HMR 會導致問題:html 檔案不能熱更新了(不會自動打包構建)
解決:修改 entry 入口,將 html 檔案引入(這樣 html 修改整體重新整理)
entry: ['./src/js/index.js', './src/index.html']
5.1.2 source-map
source-map:一種提供原始碼到構建後代碼的對映的技術 (如果構建後代碼出錯了,通過對映可以追蹤原始碼錯誤)
引數:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
程式碼:
devtool: 'eval-source-map'
可選方案:[生成source-map的位置|給出的錯誤程式碼資訊]
- source-map:外部,錯誤程式碼準確資訊 和 原始碼的錯誤位置
- inline-source-map:內聯,只生成一個內聯 source-map,錯誤程式碼準確資訊 和 原始碼的錯誤位置
- hidden-source-map:外部,錯誤程式碼錯誤原因,但是沒有錯誤位置(為了隱藏原始碼),不能追蹤原始碼錯誤,只能提示到構建後代碼的錯誤位置
- eval-source-map:內聯,每一個檔案都生成對應的 source-map,都在 eval 中,錯誤程式碼準確資訊 和 原始碼的錯誤位
- nosources-source-map:外部,錯誤程式碼準確資訊,但是沒有任何原始碼資訊(為了隱藏原始碼)
- cheap-source-map:外部,錯誤程式碼準確資訊 和 原始碼的錯誤位置,只能把錯誤精確到整行,忽略列
- cheap-module-source-map:外部,錯誤程式碼準確資訊 和 原始碼的錯誤位置,module 會加入 loader 的 source-map
內聯 和 外部的區別:1. 外部生成了檔案,內聯沒有 2. 內聯構建速度更快
開發/生產環境可做的選擇:
開發環境:需要考慮速度快,除錯更友好
- 速度快( eval > inline > cheap >... )
- eval-cheap-souce-map
- eval-source-map
- 除錯更友好
- souce-map
- cheap-module-souce-map
- cheap-souce-map
最終得出最好的兩種方案 --> eval-source-map(完整度高,內聯速度快) / eval-cheap-module-souce-map(錯誤提示忽略列但是包含其他資訊,內聯速度快)
生產環境:需要考慮原始碼要不要隱藏,除錯要不要更友好
- 內聯會讓程式碼體積變大,所以在生產環境不用內聯
- 隱藏原始碼
- nosources-source-map 全部隱藏
- hidden-source-map 只隱藏原始碼,會提示構建後代碼錯誤資訊
最終得出最好的兩種方案 --> source-map(最完整) / cheap-module-souce-map(錯誤提示一整行忽略列)
5.2 生產環境效能優化
5.2.1 優化打包構建速度
5.2.1.1 oneOf
oneOf:匹配到 loader 後就不再向後進行匹配,優化生產環境的打包構建速度
程式碼:
module: {
rules: [
{
// js 語法檢查
test: /\.js$/,
exclude: /node_modules/,
// 優先執行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
// oneOf 優化生產環境的打包構建速度
// 以下loader只會匹配一個(匹配到了後就不會再往下匹配了)
// 注意:不能有兩個配置處理同一種類型檔案(所以把eslint-loader提取出去放外面)
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
{
// js 相容性處理
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
}
]
},
5.2.1.2 babel 快取
babel 快取:類似 HMR,將 babel 處理後的資源快取起來(哪裡的 js 改變就更新哪裡,其他 js 還是用之前快取的資源),讓第二次打包構建速度更快
程式碼:
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 開啟babel快取
// 第二次構建時,會讀取之前的快取
cacheDirectory: true
}
},
檔案資源快取
檔名不變,就不會重新請求,而是再次用之前快取的資源
1.hash: 每次 wepack 打包時會生成一個唯一的 hash 值。
問題:重新打包,所有檔案的 hsah 值都改變,會導致所有快取失效。(可能只改動了一個檔案)
2.chunkhash:根據 chunk 生成的 hash 值。來源於同一個 chunk的 hash 值一樣
問題:js 和 css 來自同一個chunk,hash 值是一樣的(因為 css-loader 會將 css 檔案載入到 js 中,所以同屬於一個chunk)
3.contenthash: 根據檔案的內容生成 hash 值。不同檔案 hash 值一定不一樣(檔案內容修改,檔名裡的 hash 才會改變)
修改 css 檔案內容,打包後的 css 檔名 hash 值就改變,而 js 檔案沒有改變 hash 值就不變,這樣 css 和 js 快取就會分開判斷要不要重新請求資源 --> 讓程式碼上線執行快取更好使用
5.2.1.3 多程序打包
多程序打包:某個任務消耗時間較長會卡頓,多程序可以同一時間幹多件事,效率更高。
優點是提升打包速度,缺點是每個程序的開啟和交流都會有開銷(babel-loader消耗時間最久,所以使用thread-loader針對其進行優化)
{
test: /\.js$/,
exclude: /node_modules/,
use: [
/*
thread-loader會對其後面的loader(這裡是babel-loader)開啟多程序打包。
程序啟動大概為600ms,程序通訊也有開銷。(啟動的開銷比較昂貴,不要濫用)
只有工作消耗時間比較長,才需要多程序打包
*/
{
loader: 'thread-loader',
options: {
workers: 2 // 程序2個
}
},
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 開啟babel快取
// 第二次構建時,會讀取之前的快取
cacheDirectory: true
}
}
]
},
5.2.1.4 externals
externals:讓某些庫不打包,通過 cdn 引入
webpack.config.js 中配置:
externals: {
// 拒絕jQuery被打包進來(通過cdn引入,速度會快一些)
// 忽略的庫名 -- npm包名
jquery: 'jQuery'
}
需要在 index.html 中通過 cdn 引入:
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
5.2.1.5 dll
dll:讓某些庫單獨打包,後直接引入到 build 中。可以在 code split 分割出 node_modules 後再用 dll 更細的分割,優化程式碼執行的效能。
webpack.dll.js 配置:(將 jquery 單獨打包)
/*
node_modules的庫會打包到一起,但是很多庫的時候打包輸出的js檔案就太大了
使用dll技術,對某些庫(第三方庫:jquery、react、vue...)進行單獨打包
當執行webpack時,預設查詢webpack.config.js配置檔案
需求:需要執行webpack.dll.js檔案
--> webpack --config webpack.dll.js(執行這個指令表示以這個配置檔案打包)
*/
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最終打包生成的[name] --> jquery
// ['jquery] --> 要打包的庫是jquery
jquery: ['jquery']
},
output: {
// 輸出出口指定
filename: '[name].js', // name就是jquery
path: resolve(__dirname, 'dll'), // 打包到dll目錄下
library: '[name]_[hash]', // 打包的庫裡面向外暴露出去的內容叫什麼名字
},
plugins: [
// 打包生成一個manifest.json --> 提供jquery的對映關係(告訴webpack:jquery之後不需要再打包和暴露內容的名稱)
new webpack.DllPlugin({
name: '[name]_[hash]', // 對映庫的暴露的內容名稱
path: resolve(__dirname, 'dll/manifest.json') // 輸出檔案路徑
})
],
mode: 'production'
};
webpack.config.js 配置:(告訴 webpack 不需要再打包 jquery,並將之前打包好的 jquery 跟其他打包好的資源一同輸出到 build 目錄下)
// 引入外掛
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
// plugins中配置:
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
// 告訴webpack哪些庫不參與打包,同時使用時的名稱也得變
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 將某個檔案打包輸出到build目錄下,並在html中自動引入該資源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
5.2.2 優化程式碼執行的效能
5.2.2.1 快取
5.2.2.2 tree shaking(樹搖)
tree shaking:去除無用程式碼
前提:1. 必須使用 ES6 模組化 2. 開啟 production 環境 (這樣就自動會把無用程式碼去掉)
作用:減少程式碼體積
在 package.json 中配置:
"sideEffects": false
表示所有程式碼都沒有副作用(都可以進行 tree shaking)
這樣會導致的問題:可能會把 css / @babel/polyfill 檔案幹掉(副作用)
所以可以配置:"sideEffects": ["*.css", "*.less"]
不會對css/less檔案tree shaking處理
5.2.2.3 code split(程式碼分割)
程式碼分割。將打包輸出的一個大的 bundle.js 檔案拆分成多個小檔案,這樣可以並行載入多個檔案,比載入一個檔案更快。
1.多入口拆分
entry: {
// 多入口:有一個入口,最終輸出就有一個bundle
index: './src/js/index.js',
test: './src/js/test.js'
},
output: {
// [name]:取檔名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
2.optimization:
optimization: {
splitChunks: {
chunks: 'all'
}
},
- 將 node_modules 中的程式碼單獨打包(大小超過30kb)
- 自動分析多入口chunk中,有沒有公共的檔案。如果有會打包成單獨一個chunk(比如兩個模組中都引入了jquery會被打包成單獨的檔案)(大小超過30kb)
3.import 動態匯入語法:
/*
通過js程式碼,讓某個檔案被單獨打包成一個chunk
import動態匯入語法:能將某個檔案單獨打包(test檔案不會和index打包在同一個檔案而是單獨打包)
webpackChunkName:指定test單獨打包後文件的名字
*/
import(/* webpackChunkName: 'test' */'./test')
.then(({ mul, count }) => {
// 檔案載入成功~
// eslint-disable-next-line
console.log(mul(2, 5));
})
.catch(() => {
// eslint-disable-next-line
console.log('檔案載入失敗~');
});
5.2.2.4 lazy loading(懶載入/預載入)
1.懶載入:當檔案需要使用時才載入(需要程式碼分割)。但是如果資源較大,載入時間就會較長,有延遲。
2.正常載入:可以認為是並行載入(同一時間載入多個檔案)沒有先後順序,先載入了不需要的資源就會浪費時間。
3.預載入 prefetch(相容性很差):會在使用之前,提前載入。等其他資源載入完畢,瀏覽器空閒了,再偷偷載入這個資源。這樣在使用時已經載入好了,速度很快。所以在懶載入的基礎上加上預載入會更好。
程式碼:
document.getElementById('btn').onclick = function() {
// 將import的內容放在非同步回撥函式中使用,點選按鈕,test.js才會被載入(不會重複載入)
// webpackPrefetch: true表示開啟預載入
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
import('./test').then(({ mul }) => {
console.log(mul(2, 5))
})
};
5.2.2.5 pwa(離線可訪問技術)
pwa:離線可訪問技術(漸進式網路開發應用程式),使用 serviceworker 和 workbox 技術。優點是離線也能訪問,缺點是相容性差。
webpack.config.js 中配置:
const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); // 引入外掛
// plugins中加入:
new WorkboxWebpackPlugin.GenerateSW({
/*
1. 幫助serviceworker快速啟動
2. 刪除舊的 serviceworker
生成一個 serviceworker 配置檔案
*/
clientsClaim: true,
skipWaiting: true
})
index.js 中還需要寫一段程式碼來啟用它的使用:
/*
1. eslint不認識 window、navigator全域性變數
解決:需要修改package.json中eslintConfig配置
"env": {
"browser": true // 支援瀏覽器端全域性變數
}
2. sw程式碼必須執行在伺服器上
--> nodejs
或-->
npm i serve -g
serve -s build 啟動伺服器,將打包輸出的build目錄下所有資源作為靜態資源暴露出去
*/
if ('serviceWorker' in navigator) { // 處理相容性問題
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js') // 註冊serviceWorker
.then(() => {
console.log('sw註冊成功了~');
})
.catch(() => {
console.log('sw註冊失敗了~');
});
});
}
六、Webpack 配置詳情
6.1 entry
entry: 入口起點
-
string --> './src/index.js',單入口
打包形成一個 chunk。 輸出一個 bundle 檔案。此時 chunk 的名稱預設是 main
-
array --> ['./src/index.js', './src/add.js'],多入口
所有入口檔案最終只會形成一個 chunk,輸出出去只有一個 bundle 檔案。
(一般只用在 HMR 功能中讓 html 熱更新生效)
-
object,多入口
有幾個入口檔案就形成幾個 chunk,輸出幾個 bundle 檔案,此時 chunk 的名稱是 key 值
--> 特殊用法:
entry: {
// 最終只會形成一個chunk, 輸出出去只有一個bundle檔案。
index: ['./src/index.js', './src/count.js'],
// 形成一個chunk,輸出一個bundle檔案。
add: './src/add.js'
}
6.2 output
output: {
// 檔名稱(指定名稱+目錄)
filename: 'js/[name].js',
// 輸出檔案目錄(將來所有資源輸出的公共目錄)
path: resolve(__dirname, 'build'),
// 所有資源引入公共路徑字首 --> 'imgs/a.jpg' --> '/imgs/a.jpg'
publicPath: '/',
chunkFilename: 'js/[name]_chunk.js', // 指定非入口chunk的名稱
library: '[name]', // 打包整個庫後向外暴露的變數名
libraryTarget: 'window' // 變數名新增到哪個上 browser:window
// libraryTarget: 'global' // node:global
// libraryTarget: 'commonjs' // conmmonjs模組 exports
},
6.3 module
module: {
rules: [
// loader的配置
{
test: /\.css$/,
// 多個loader用use
use: ['style-loader', 'css-loader']
},
{
test: /\.js$/,
// 排除node_modules下的js檔案
exclude: /node_modules/,
// 只檢查src下的js檔案
include: resolve(__dirname, 'src'),
enforce: 'pre', // 優先執行
// enforce: 'post', // 延後執行
// 單個loader用loader
loader: 'eslint-loader',
options: {} // 指定配置選項
},
{
// 以下配置只會生效一個
oneOf: []
}
]
},
6.4 resolve
// 解析模組的規則
resolve: {
// 配置解析模組路徑別名: 優點:當目錄層級很複雜時,簡寫路徑;缺點:路徑不會提示
alias: {
$css: resolve(__dirname, 'src/css')
},
// 配置省略檔案路徑的字尾名(引入時就可以不寫檔案字尾名了)
extensions: ['.js', '.json', '.jsx', '.css'],
// 告訴 webpack 解析模組應該去找哪個目錄
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
}
這樣配置後,引入檔案就可以這樣簡寫:import '$css/index';
6.5 dev server
devServer: {
// 執行程式碼所在的目錄
contentBase: resolve(__dirname, 'build'),
// 監視contentBase目錄下的所有檔案,一旦檔案變化就會reload
watchContentBase: true,
watchOptions: {
// 忽略檔案
ignored: /node_modules/
},
// 啟動gzip壓縮
compress: true,
// 埠號
port: 5000,
// 域名
host: 'localhost',
// 自動開啟瀏覽器
open: true,
// 開啟HMR功能
hot: true,
// 不要顯示啟動伺服器日誌資訊
clientLogLevel: 'none',
// 除了一些基本資訊外,其他內容都不要顯示
quiet: true,
// 如果出錯了,不要全屏提示
overlay: false,
// 伺服器代理,--> 解決開發環境跨域問題
proxy: {
// 一旦devServer(5000)伺服器接收到/api/xxx的請求,就會把請求轉發到另外一個伺服器3000
'/api': {
target: 'http://localhost:3000',
// 傳送請求時,請求路徑重寫:將/api/xxx --> /xxx (去掉/api)
pathRewrite: {
'^/api': ''
}
}
}
}
其中,跨域問題:同源策略中不同的協議、埠號、域名就會產生跨域。
正常的瀏覽器和伺服器之間有跨域,但是伺服器之間沒有跨域。程式碼通過代理伺服器執行,所以瀏覽器和代理伺服器之間沒有跨域,瀏覽器把請求傳送到代理伺服器上,代理伺服器替你轉發到另外一個伺服器上,伺服器之間沒有跨域,所以請求成功。代理伺服器再把接收到的響應響應給瀏覽器。這樣就解決開發環境下的跨域問題。
6.6 optimization
contenthash 快取會導致一個問題:修改 a 檔案導致 b 檔案 contenthash 變化。
因為在 index.js 中引入 a.js,打包後 index.js 中記錄了 a.js 的 hash 值,而 a.js 改變,其重新打包後的 hash 改變,導致 index.js 檔案內容中記錄的 a.js 的 hash 也改變,從而重新打包後 index.js 的 hash 值也會變,這樣就會使快取失效。(改變的是a.js檔案但是 index.js 檔案的 hash 值也改變了)
解決辦法:runtimeChunk --> 將當前模組記錄其他模組的 hash 單獨打包為一個檔案 runtime,這樣 a.js 的 hash 改變只會影響 runtime 檔案,不會影響到 index.js 檔案
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
chunkFilename: 'js/[name].[contenthash:10]_chunk.js' // 指定非入口檔案的其他chunk的名字加_chunk
},
optimization: {
splitChunks: {
chunks: 'all',
/* 以下都是splitChunks預設配置,可以不寫
miniSize: 30 * 1024, // 分割的chunk最小為30kb(大於30kb的才分割)
maxSize: 0, // 最大沒有限制
minChunks: 1, // 要提取的chunk最少被引用1次
maxAsyncRequests: 5, // 按需載入時並行載入的檔案的最大數量為5
maxInitialRequests: 3, // 入口js檔案最大並行請求數量
automaticNameDelimiter: '~', // 名稱連線符
name: true, // 可以使用命名規則
cacheGroups: { // 分割chunk的組
vendors: {
// node_modules中的檔案會被打包到vendors組的chunk中,--> vendors~xxx.js
// 滿足上面的公共規則,大小超過30kb、至少被引用一次
test: /[\\/]node_modules[\\/]/,
// 優先順序
priority: -10
},
default: {
// 要提取的chunk最少被引用2次
minChunks: 2,
prority: -20,
// 如果當前要打包的模組和之前已經被提取的模組是同一個,就會複用,而不是重新打包
reuseExistingChunk: true
}
} */
},
// 將index.js記錄的a.js的hash值單獨打包到runtime檔案中
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: [
// 配置生產環境的壓縮方案:js/css
new TerserWebpackPlugin({
// 開啟快取
cache: true,
// 開啟多程序打包
parallel: true,
// 啟用sourceMap(否則會被壓縮掉)
sourceMap: true
})
]
}
七、Webpack5 介紹和使用
此版本重點關注以下內容:
- 通過持久快取提高構建效能.
- 使用更好的演算法和預設值來改善長期快取.
- 通過更好的樹搖和程式碼生成來改善捆綁包大小.
- 清除處於怪異狀態的內部結構,同時在 v4 中實現功能而不引入任何重大更改.
- 通過引入重大更改來為將來的功能做準備,以使我們能夠儘可能長時間地使用 v5.
下載
npm i webpack@next webpack-cli -D
自動刪除 Node.js Polyfills
早期,webpack 的目標是允許在瀏覽器中執行大多數 node.js 模組,但是模組格局發生了變化,許多模組用途現在主要是為前端目的而編寫的。webpack <= 4 附帶了許多 node.js 核心模組的 polyfill,一旦模組使用任何核心模組(即 crypto 模組),這些模組就會自動應用。
儘管這使使用為 node.js 編寫的模組變得容易,但它會將這些巨大的 polyfill 新增到包中。在許多情況下,這些 polyfill 是不必要的。
webpack 5 會自動停止填充這些核心模組,並專注於與前端相容的模組。
遷移:
- 儘可能嘗試使用與前端相容的模組。
- 可以為 node.js 核心模組手動新增一個 polyfill。錯誤訊息將提示如何實現該目標。
Chunk 和模組 ID
添加了用於長期快取的新演算法。在生產模式下預設情況下啟用這些功能。
chunkIds: "deterministic", moduleIds: "deterministic"
Chunk ID
你可以不用使用 import(/* webpackChunkName: "name" */ "module")
在開發環境來為 chunk 命名,生產環境還是有必要的
webpack 內部有 chunk 命名規則,不再是以 id(0, 1, 2)命名了
Tree Shaking
- webpack 現在能夠處理對巢狀模組的 tree shaking
// inner.js
export const a = 1;
export const b = 2;
// module.js
import * as inner from './inner';
export { inner };
// user.js
import * as module from './module';
console.log(module.inner.a);
在生產環境中, inner 模組暴露的 b
會被刪除
- webpack 現在能夠多個模組之前的關係
import { something } from './something';
function usingSomething() {
return something;
}
export function test() {
return usingSomething();
}
當設定了"sideEffects": false
時,一旦發現test
方法沒有使用,不但刪除test
,還會刪除"./something"
- webpack 現在能處理對 Commonjs 的 tree shaking
Output
webpack 4 預設只能輸出 ES5 程式碼
webpack 5 開始新增一個屬性 output.ecmaVersion, 可以生成 ES5 和 ES6 / ES2015 程式碼.
如:output.ecmaVersion: 2015
SplitChunk
// webpack4
minSize: 30000;
// webpack5
minSize: {
javascript: 30000,
style: 50000,
}
Caching
// 配置快取
cache: {
// 磁碟儲存
type: "filesystem",
buildDependencies: {
// 當配置修改時,快取失效
config: [__filename]
}
}
快取將儲存到 node_modules/.cache/webpack
監視輸出檔案
之前 webpack 總是在第一次構建時輸出全部檔案,但是監視重新構建時會只更新修改的檔案。
此次更新在第一次構建時會找到輸出檔案看是否有變化,從而決定要不要輸出全部檔案。
預設值
entry: "./src/index.js
output.path: path.resolve(__dirname, "dist")
output.filename: "[name].js"