TOC
基本介紹
- Webpack 可以做到的事
- 將相依的 js 檔打包在一起,降低請求次數
- 將 js 檔案 Bundle 變成單一的檔案
- 在前端程式碼中使用 npm packages
- 撰寫 JavaScript ES6 或 ES7(需要透過 babel 來幫助)
- Minify 或優化程式碼
- 類似 gulp plugin 做 pipeline 的文件轉換動作
- 使用 HMR(Hot Module Replacement)
基本語法
webpack --config webpack.config.js
Config
基本屬性
context
- 基礎目錄,預設是檔案所在的目錄
mode
- webpack4 加入的元素
- 在 production 模式下會默認開啟 UglifyJsPlugin 等等一些內建優化 plugins
- 可以不用配置 alias
- 比如 react 即可依照 mode 決定載入
production.min.js
還是development.js
- but 大部分的第三方 library 並沒有做入口環境判斷
- 比如 react 即可依照 mode 決定載入
optimization
- 優化項目,這裡可以客製化 webpack 內建優化 plugin 的參數
resolve
- extensions
- 可以在引入時不用帶副檔名
import Comp from '../path/to/component';
- alias
- 直接指定別名,不用更改相對路徑
import $ from '../path/to/jquery';
- 可搭配
ProvidePlugin
助攻
- extensions
devtools
- 有 7 種 sourcemap 模式
- 打包方式不同,決定是否壓縮或節省時間
建議用
module.exports = { devtool: "source-map", }
四大核心
Entry
: 一個可執行模塊或庫的入口文件
Single Entry Syntax
- 全部都將檔案打包成一個檔案
開發 library 時或 single page 時,才會比較常用
module.exports = { entry: { main: './app/index.js', }, }
Object Syntax
- 將 vendors 統一打包成另一個檔案
- 減少網路 requests 的次數,快取過就不 load 了
- 可以各自引入想要的 js 減輕網路負擔
當然
output
也要跟著調整module.exports = { entry: { app: './app/index.js', vendors: './app/vendors.js', pageOne: './src/pageOne/index.js', pageTwo: './src/pageTwo/index.js', pageThree: './src/pageThree/index.js' }, }
Output
: 輸出
output.filename
可以按照 entry 輸出對應的 output
其中 name 為 entry 的 key
module.exports = { output: { filename: '[name].js', }, }
output.path
指定輸出到哪個資料夾
module.exports = { output: { path: './dist' }, }
Loaders
: 文件轉換器
- 為了要讓
webpack
也能理解並處理css
圖片
等等非js
的文件並轉譯為 js 形式 - 用
test: /\.<型別>$/
來決定用什麼 loader
Plugins
: 用於擴展 webpack 的功能
- 可在 webpack 構建生命週期的節點上加入擴展 hook,為 webpack 加入客製化功能
基本格式
function ConsoleLogOnBuildWebpackPlugin() { }; ConsoleLogOnBuildWebpackPlugin.prototype.apply = function(compiler) { compiler.plugin('run', function(compiler, callback) { console.log("The webpack build process is starting!!!"); callback(); }); }; module.exports = ConsoleLogOnBuildWebpackPlugin;
優化
原則
- 縮小打包體積
- 減少請求數
- 使用快取策略
- 規劃開發階段打包策略
方向
Code Splitting
新版本的webpack會預設對程式碼碼進行拆分,拆分的規則是:
模塊被重複引用或者來自 node_modules 中的模塊
在壓縮前為 30kb
在按需加載時,請求數量小於等於 5
在初始化加載時,請求數量小於等於 3
把打包文件分割成為更小的文件,允許使用者在他們需要的時候才下載需要的程式碼
主要做法
分離第三方 library( vendor )
為非同步載入的程式碼打包成一個公共的模組 (Lazy load)
為 Manifest 單獨打包 (Webpack 的 Runtime 程式碼)
依功能模組打包程式碼,並在需要特定功能時才載入對應的模組
- 為不同入口的公共業務代碼打包(同理,也是為了緩存和加載速度)
- 比如說
app.js
拆分成home.js
和profile.js
各自依頁面載入 - 畢竟使用者不是一開始就會用到所有的功能
Manifest
- 當
./app/index.js
有任何變化時,執行打包會包出一個全新的檔案 👎 - 希望
./dist/[hash].vendor.js
不要一直變動,瀏覽器會重複載入 - 起因:webpack 本身有自己的 runtime code,造成 [hash] 數值不一樣
- 解決方法
- 執行 webpack 時,額外產生
./dist/[hash].manifest.js
檔 - 這樣打包出來的檔案就不會變,達到
vendor cache
的效果
new webpack.optimize.CommonsChunkPlugin({ name: ['vendor', 'manifest'] // Specify the common bundle's name. })
Tree Shaking
- webpack 2 引入,但好像不是很好用
實踐 DCE 的方式之一
DCE: Dead code elimination 編譯最佳化技術之一
把項目中沒必要的模塊全部抖掉,用於在不同的模塊之間消除無用的代碼
因為 ES6 的 import 跟 export 是完全靜態化的,所以可以藉此剔除無用的程式碼
靜態化: 在運行前就明確指定依賴關係 像 CommonJS 就是屬於動態載入
具體 shake shake 規則 (符合以下規則才不會被 shake 掉)
- 兩個關鍵字只能作為模塊頂層的語句出現,不能出現在函數或者其他的塊語句裡面
- import 的模塊名只能是字符串常量,不能使用變量
- 不管 import 的語句的出現位置在哪裡,模塊在初始化的所有的 import 都必須已經導入完成
- import 的綁定是不可變的,類似於 const
Lazy Load
舉例來說一個情境
載入需看到的
- 導航列
- 最外層骨架的 code
- 內容最上層
不需看到的
- 其他頁面
- 未點開的 popup
- 在可視範圍之外的元件
webpack 會將此 import 視為可分割點,並切割成
x.bundle.min.js
需要安裝 babel plugins 才可以 動態載入
"plugins": [ "@babel/plugin-syntax-dynamic-import" ],
- 支持零配置使用,可以從命令行指定 entry 的位置
- 不指定就是
src/index.js
- mode 參數也可以透過命令行傳入
- 但如果需要多個 entry 還是需要一個配置文件
常用 Loaders
css-loader
- 以下為有順序性的
sass-loader
- 將 scss/sass 轉譯成一般 css
css-loader
- 單純載入 css 用,瀏覽器尚無法解析
style-loader
- 將載入的 css 用
<style></style>
包起來並引入至<head>
裡
切記: loader 順序是不能變的,是
由後往前
一層一層轉譯module: { rules: [ { test: /\.css$/, use: [ { loader: 'style-loader'}, { loader: 'css-loader', options: { modules: true } }, { loader: 'sass-loader' } ] } ] }
- 最終效果 (有效簡化引入寫法)
require("!style-loader!style-loader!css-loader!./file.css");
➡️require("./file.scss");
vue-loader
搭配 vue-html-loader vue-style-loader vue-template-compiler 都是 fork 來的
- 可以處理 vue 元件且各自解析對應的 tag
<style lang="sass"> /* write sass here */ </style>
- 將載入的 css 用
babel-loader
babel-loader babel-core
{ test: /.js$/, exclude: /node_modules/, use: [ { loader: ‘babel-loader’ } ] }
file-loader
- 可以指定要複製和放置資源文件的位置,以及如何使用版本 hash 命名以獲得更好的 cache
- 可以使用相對路徑而不用擔心部署時 URL 的問題
- webpack 將會在打包輸出中自動重寫文件路徑為正確的 URL。
url-loader
- 允許你有條件地將文件轉換為內聯的 base-64 URL,這會減少小文件的 HTTP 請求數。
- 如果文件大於 threshold,會自動的交給 file-loader 處理。
適用小圖,C/P 值的概念
```js
{
loader: 'url-loader',
options: {
limit: 8192,
name: 'img/[name].[hash].[ext]',
publicPath: '../'
}
}
```
大於 limit —> file-loader
- 轉換後還是好幾 MB,就該改用 lazy load 解決
小於 limit —> url-loader
- 壓成 base64 文字可以減少請求數
優點
- 減少網路請求
缺點
- 為降低 html 可讀性
- 無法 cache 起來,但寫在 css 裡就可以 cache 囉
resolve-url-loader
- 可以解決在 css 裡的 url 引入
background-image: url(asset/img/bla bla bla.png)
好用 Plugins
Webpack 4 內建
webpack.optimization.UglifyJsPlugin
- 將模組化好的程式,直接整個壓成一行
去除空格並置換字元
module.exports = { plugins: [ new webpack.optimize.UglifyJsPlugin() ] }
webpack.DefinePlugin
- 可以定義 global 變數且在元件內直接使用
webpack.ProvidePlugin
- 讓相關 libs 不用
require
就直接用
new webpack.ProvidePlugin({ $: "jquery" }) // 有用 resolve.alias 的話
webpack.optimization.SplitChunksPlugin
- 可依據檔案大小(minSize)
- module 引用次數(minChunks)
- 同步/非同步(chunks)
- priority(優先級)等自訂優化決定打包檔案的邏輯。
獨立 plugins
mini-css-extract-plugin
- 非同步載入
- 不重複編譯,性能更好
- 只針對 css (所以順序性有差)
- 抽離出來的有一個通用的 css 和每個頁面一個 css
- 訪問對應的頁面才會加載(Async loading)
VueLoaderPlugin
vue-loader/lib/plugin
- 用於打包 vue 相關檔案
- 可處理 SFC 檔案
- 需搭配
vue-template-compiler
一起安裝
ProgressBarPlugin
- 打包時顯示進度條
NamedModulesPlugin
- 熱加載時直接返回更新文件名,而不是文件的id
html-webpack-plugin
- 可以幫忙建一個 index.html 骨架
- 並可以加
meta
favicon
gacode
之類的 - 幫忙生出
./dist/index.html
檔案 - 且自動將 bundle 好的所有 css、js 等相關路徑,會直接都塞進
index.html
裡 - 為html文件中引入的外部資源如script、link動態添加每次compile後的hash,防止引用緩存的外部文件問題
- 可以生成創建html入口文件,比如單頁面可以生成一個html文件入口,配置N個html-webpack-plugin可以生成N個頁面入口
將 webpack中
entry
配置的相關入口 chunk 和extract-text-webpack-plugin
抽取的css樣式 插入到該插件提供的template
或者templateContent
配置項指定的內容基礎上生成一個html文件,具體插入方式是將樣式link
插入到head
元素中,script
插入到head
或者body
中。module.exports = { plugins: [ new HtmlWebpackPlugin() ] }
webpack-merge
- 將不同情境的 config 檔跟 base.config 合起來
clean-webpack-plugin
- 讓 webpack 每次打包前都清除特定資料夾
CommonsChunkPlugin
- 通常是配置 code spiliting
- 分別為 vendor、manifest 和 vendor-async 配置
- 有
name
,async
,children
,minChunks
OptimizeCSSAssetsPlugin
- 最後會含 autoprefixer
- 但會把一些
他認為
不需要的 prefix 拿掉QQ
DefinePlugin
可以對專案程式碼注入全域環境變數
new webpack.DefinePlugin({ DESCRIPTION: 'Oh Yah' }) console.log(DESCRIPTION); // Oh Yah
ProvidePlugin
- 可以在無需引入的情況下,在全域的模式直接使用 lib 的變數
new webpack.ProvidePlugin({ 'React': 'react' // 這是有先用 resolve.alias }) import React,{Component} from 'react'; // 之後檔案的這一行可以拿掉了
extract-text-webpack-plugin
CopyWebpackPlugin
分析工具
webpack-bundle-analyzer
- 統計和優化webpack日誌的工具
安裝 & 啟動
npm i -g webpack-bundle-analyzer webpack-bundle-analyzer stats.json -p 8888
webpack-bundle-size-analyzer
- 在終端機
webpack-dashboard
```js
npm install webpack-dashboard --save-dev
```
webpack-jarvis
- 精美 dashboard
- 不過是在 build 時另起 server…
- 所以只適合 development 模式 @@?
自問自答
那多少 k 才算高效呢?
- 似乎在單檔超過 500k 就會被 alert 了
Loader 跟 Plugin 所作用的時間不一樣?
- Plugin 的作用時間會是在 Webpack 編譯之前、或者編譯完成之後
- Loader 則是 Webpack 編譯時與 Webpack 一起協作。
如果是已有既有的 html 要怎麼動態引入 code spilit 後的 js 檔呢?
- 可以用 template 的方式引入?
哎呀有坑
- 打包後
webkit-
前綴會被拔掉?- 設定到
minimize: true
在匹配到css後直接壓縮 - 用了autoprefix自動添加前綴,這樣壓縮,會導致添加的前綴丟失
- 解法
- 使用
optimize-css-assets-webpack-plugin
解掉?
- 使用
- 設定到
遇到問題
如果是 vue 專案,會遇到 SFC 失靈的問題
- 安裝
vue-style-loader
- 安裝
接著導致
ReferenceError: document is not defined
- 靠北,結果是因為
minifyPlugin
在前面的關係 - 對調順序即可
- 靠北,結果是因為
TypeError: Cannot read property 'parseComponent' of undefined
版本要一樣
"dependencies": { "vue": "2.5.21", "vue-template-compiler": "2.5.21" }
Error: Can't resolve '/Users/toby/work/pixnet/pixpixnetid/node_modules/html-webpack-template/index.ejs
- 依賴到
html-webpack-template
依賴的 index - 拔掉 option 即可
- 依賴到
vue-loader was used without the corresponding plugin
- 缺對應的 plugin
Module not found: Error: Can't resolve '../../helpers/click-i13n'
- 可能是 resolve 裡的 alias 沒有加
- 因為沒有加
.vue
啦…
assertInputSourceMap option-assertions.js
- babel-loader 需升級到
8.0.0-beta.1
- babel-loader 需升級到
Error: Unexpected '/'. Escaping special characters with \ may help
- 需把註解
//
拿掉
- 需把註解
Module not found: Error: Can't resolve
- 缺少
sass-loader
- 缺少
輸出沒有 css ?
- 原因
- 在 loader 裡需要引入
MiniCssExtractPlugin.loader
- 在 loader 裡需要引入
解決
module: { rules: [ { test: /.(scss|sass)$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', ], }, ] }
- 原因
參考連結
- webpack 之前端性能優化
- webpack 優化總結
- Webpack性能優化整理
- 三十分鐘掌握Webpack性能優化
- Multiple CSS bundles with webpack
- check for webpack 4
- webpack筆記
- 使用 webpack 模組化你的程式碼,讓人生更美好。
- 淺談大型 React 專案的 Code Splitting
- 中文文檔
- CommonsChunkPlugin
- 模塊系統比較
- 從範例看 webpack 加載
- 分析列表
- webpack-awesome
- 从实践中寻找webpack4最优配置
- 使用 webpack 进行 web 性能优化
- 請 Toby 喝珍奶,你請我就喝 -
YA~大杯還小杯~看你誠意 ❤ ️
使用手機掃描 QRCODE 完成 pay 下去就對