webpack 基本應用
【注意】只過一遍知識點,不再詳細代碼演示了。
安裝和配置 —— 拆分 dev prod 配置雹拄,然后 merge
安裝 nodejs
初始化
npm init -y
安裝插件
npm i webpack webpack-cli webpack-merge --save-dev
新建
src
及其測試 js 代碼(包含 ES6 模塊化)創(chuàng)建配置文件
增加
scripts
蜈项,運行安裝
npm i clean-webpack-plugin --save-dev
配置 prod
本地服務和代理
- 新建
index.html
- 安裝
npm i html-webpack-plugin --save-dev
瘪菌,并配置 - 安裝
npm i webpack-dev-server --save-dev
,并配置 - 修改
scripts
的dev
毫目,運行
處理 ES6
先引入簡單的 babel 蔬啡,polyfill 以后會單獨講
- 安裝
npm i @babel/core @babel/preset-env babel-loader --save-dev
- 配置 webpack module
- 配置
.babelrc
- 運行 dev
處理樣式
基本使用
- 安裝
npm i style-loader css-loader less-loader less --save-dev
(注意要安裝 less) - 配置 webpack module
- 新建 css less 文件诲侮,引入 index.js
- 運行 dev
postcss
- 安裝
npm i postcss-loader autoprefixer -D
- 新建
postcss.config.js
- 配置 webpack module ,增加
postcss-loader
- 增加 css
transform: rotate(-45deg);
- 運行 dev
處理圖片
考慮 base64 格式
- 安裝
npm i file-loader url-loader --save-dev
- 分別配置 webpack.dev 和 webpack.prod
- 新建圖片文件箱蟆,并引入到 js 中沟绪,插入頁面
- 運行 dev
- 運行 build
webpack 題目
webpack 面試題
- 前端代碼為何要進行構架和打包?
- module chunk bundle 的區(qū)別
- loader 和 plugin 的區(qū)別
- 常用的 loader 和 plugin 有哪些
- webpack 性能優(yōu)化(如上)
- webpack 構建流程簡述
- 如何產出多頁空猜,如何產出 lib
babel 面試題
- babel 和 webpack 的區(qū)別
- babel-runtime 和 babel-polyfill 區(qū)別
- 為何 Proxy 無法 polyfill
webpack 高級應用
多入口
- 在src目錄下新建
other.html
和other.js
-
修改 entry
-
修改 output
-
修改 HtmlWebpackPlugin
- 運行 dev
- 運行 build
抽離 & 壓縮 css 文件
抽離
把原common里面的css绽慈、less配置移到dev下,prod的需要配置module和plugin辈毯,如下所示:
- 安裝
npm i mini-css-extract-plugin -D
- 將之前 common 中的 css 處理坝疼,移動到 dev 配置中
-
配置 prod (配置 module ,配置 plugin)
-
運行 build:dist目錄下抽離出對應的css文件
壓縮
- 安裝
npm i terser-webpack-plugin optimize-css-assets-webpack-plugin -D
- 配置 prod
抽離公共代碼
場景:一般是公共模塊或者第三方模塊谆沃,多處引用如果不提取钝凶,可能會存在代碼重復冗余。
common和dev基本不用改動唁影,dev能快速用即可耕陷,提取可能還稍顯麻煩了
- 配置
splitChunks
test:/node-module/,表示會進行匹配。比如lodash會安裝到node-module里面据沈,當匹配到就符合這個條件
minSize:表示大小限制哟沫。比如有的文件很小就幾行代碼,直接引用即可沒必要抽取卓舵,所以需要限制一下
minChunks:表示最少復用幾次南用。比如第三方模塊,屬于可能會在多處引用的情況掏湾,所以引用超過一次就提取肿嘲;但公共模塊可能需要2次融击,如果只在某個地方用到1次就沒必要提取
-
修改 HtmlWebpackPlugin 中的 chunks 。重要v摺W鹄恕!
安裝 lodash
npm i lodash --save
做第三方模塊的測試封救,引用 lodash運行 build
懶加載
webpack不需要做配置拇涤,只需要import引入即可
-
增加
dynamic-data.js
并動態(tài)引入
運行 dev 查看效果(看加載 js)
[圖片上傳中...(image.png-241244-1709365460497-0)]
1.5s之后出現(xiàn)0.js文件運行 build 看打包效果
至此,可以總結一下:
- module:就是js的模塊化webpack支持commonJS誉结、ES6等模塊化規(guī)范鹅士,簡單來說就是你通過import語句引入的代碼。
- chunk: chunk是webpack根據(jù)功能拆分出來的惩坑,包含三種情況:
- 你的項目入口(entry)
- 通過import()動態(tài)引入的代碼
- 通過splitChunks拆分出來的代碼
- (chunk包含著module掉盅,可能是一對多也可能是一對一)
- bundle:bundle是webpack打包之后的各個文件也拜,一般就是和chunk是一對一的關系,bundle就是對chunk進行編譯壓縮打包等處理之后的產出趾痘。
處理 React 和 vue
處理react:在.babelrc加上"@babel/preset-env"即可
- vue-loader
- jsx 的編譯慢哈,babel 已經支持,配置
@babel/preset-react
module永票、chunk卵贱、bundle區(qū)別:
- module:各個源碼文件,webpack中一切皆模塊侣集。比如js键俱、css文件
- chunk:多模塊合并成的,如可以通過entry肚吏、 import() 方妖、splitChunk來定義輸出
- bundle: 最終輸出的文件
常見 loader 和 plugin
webpack 性能優(yōu)化
注意:
- 以下知識點不再一行一行演示
- 面試的時候也不會問到很細節(jié)
- 但不要死記硬背一個概念,一定要知道它的基本原理和配置方式
打包效率
1)優(yōu)化 babel-loader
- babel-loader cache 未修改的不重新編譯
- babel-loader include 明確范圍
在 module下的rules配置
{
test: /\.js$/,
use: ['babel-loader?cacheDirectory'], // 開啟緩存
include: path.resolve(__dirname, 'src'), // 明確范圍
// // 排除范圍罚攀,include 和 exclude 兩者選一個即可
// exclude: path.resolve(__dirname, 'node_modules')
},
2)IgnorePlugin 避免引入哪些模塊
以常用的 moment 為例党觅。安裝 npm i moment -d
并且 import moment from 'moment'
之后,monent 默認將所有語言的 js 都加載進來斋泄,使得打包文件過大杯瞻。可以通過 ignorePlugin 插件忽略 locale 下的語言文件炫掐,不打包進來魁莉。
plugins: [
// 忽略 moment 下的 /locale 目錄
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
]
import moment from 'moment'
import 'moment/locale/zh-cn' // 這里手動引入中文語言包
moment.locale('zh-cn')
noParse 避免重復打包
module.noParse
配置項可以讓 Webpack 忽略對部分沒采用模塊化的文件的遞歸解析處理,這樣做的好處是能提高構建性能募胃。 原因是一些庫旗唁,例如 jQuery 、ChartJS痹束, 它們龐大又沒有采用模塊化標準检疫,讓 Webpack 去解析這些文件耗時又沒有意義。
module.exports = {
module: {
// 獨完整的 `react.min.js` 文件就沒有采用模塊化
// 忽略對 `react.min.js` 文件的遞歸解析處理
noParse: [/react\.min\.js$/],
},
};
.min.js基本都是一家模塊化處理過的祷嘶,不需要重新再打包
兩者對比一下:
-
IgnorePlugin
直接不引入屎媳,代碼中不存在 -
noParse
引入,但不再打包編譯论巍。比如xxx.min
happyPack 多進程打包
【注意】大型項目烛谊,構建速度明顯變慢時,作用才能明顯嘉汰。否則丹禀,反而會有副作用。
webpack 是基于 nodejs 運行,nodejs 是單線程的湃崩,happyPack 可以開啟多個進程來進行構建荧降,發(fā)揮多核 CPU 的優(yōu)勢。
const path = require('path')
const HappyPack = require('happypack')
module.exports = {
module: {
rules: [
{
test: /\.js$/,
// 把對 .js 文件的處理轉交給 id 為 babel 的 HappyPack 實例
use: ['happypack/loader?id=babel'],
exclude: path.resolve(__dirname, 'node_modules')
}
]
},
plugins: [
new HappyPack({
// 用唯一的標識符 id 來代表當前的 HappyPack 是用來處理一類特定的文件
id: 'babel',
// 如何處理 .js 文件攒读,用法和 Loader 配置中一樣
loaders: ['babel-loader?cacheDirectory'],
// ... 其它配置項
})
]
}
ParallelUglifyPlugin 多進程壓縮 js
webpack 默認用內置的 uglifyJS 壓縮 js 代碼朵诫。
大型項目壓縮 js 代碼時,也可能會慢薄扁〖舴担可以開啟多進程壓縮,和 happyPack 同理邓梅。
const path = require('path')
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
module.exports = {
plugins: [
// 使用 ParallelUglifyPlugin 并行壓縮輸出的 JS 代碼
new ParallelUglifyPlugin({
// 傳遞給 UglifyJS 的參數(shù)
// (還是使用 UglifyJS 壓縮脱盲,只不過幫助開啟了多進程)
uglifyJS: {
output: {
beautify: false, // 最緊湊的輸出
comments: false, // 刪除所有的注釋
},
compress: {
// 在UglifyJs刪除沒有用到的代碼時不輸出警告
warnings: false,
// 刪除所有的 `console` 語句,可以兼容ie瀏覽器
drop_console: true,
// 內嵌定義了但是只用到一次的變量
collapse_vars: true,
// 提取出出現(xiàn)多次但是沒有定義成變量去引用的靜態(tài)值
reduce_vars: true,
}
},
}),
],
};
適用場景:項目較大日缨,打包較慢钱反,開啟多進程能提高速度。項目較小的情況下匣距,不建議面哥,
因為相對而言進程有開銷,反而降低了打包速度
自動刷新
watch 默認關閉毅待。但 webpack-dev-server 和 webpack-dev-middleware 里 Watch 模式默認開啟尚卫。
先驗證下 webpack 是否能默認自動刷新頁面 ?尸红?吱涉?
module.export = {
watch: true, // 開啟監(jiān)聽,默認為 false
// 注意外里,開啟監(jiān)聽之后怎爵,webpack-dev-server 會自動開啟刷新瀏覽器!V鸦取疙咸!
// 監(jiān)聽配置
watchOptions: {
ignored: /node_modules/, // 忽略哪些
// 監(jiān)聽到變化發(fā)生后會等300ms再去執(zhí)行動作,防止文件更新太快導致重新編譯頻率太高
// 默認為 300ms
aggregateTimeout: 300,
// 判斷文件是否發(fā)生變化是通過不停的去詢問系統(tǒng)指定文件有沒有變化實現(xiàn)的
// 默認每隔1000毫秒詢問一次
poll: 1000
}
}
自動更新
一般開啟devServer就會自動更新(頁面會自動刷新)
熱更新
- 速度更慢
- 網頁當前的狀態(tài)會丟失风科,如 input 輸入的文字,圖片要重新加載乞旦,vuex 和 redux 中的數(shù)據(jù)
操作步驟
- 把現(xiàn)有的 watch 注釋掉
- 增加以下代碼
- 修改 css less 實驗 —— 熱替換生效
- 修改 js 實驗 —— 熱替換不生效
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
module.exports = {
entry:{
// 為每個入口都注入代理客戶端
index:[
'webpack-dev-server/client?http://localhost:8080/',
'webpack/hot/dev-server',
path.join(srcPath, 'index.js')
],
// other 先不改了
},
plugins: [
// 該插件的作用就是實現(xiàn)模塊熱替換贼穆,實際上當啟動時帶上 `--hot` 參數(shù),會注入該插件兰粉,生成 .hot-update.json 文件故痊。
new HotModuleReplacementPlugin(),
],
devServer:{
// 告訴 DevServer 要開啟模塊熱替換模式
hot: true,
}
};
js 熱替換不生效,是因為我們要自己增加代碼邏輯玖姑。
// 增加愕秫,開啟熱更新之后的代碼邏輯
if (module.hot) {
module.hot.accept(['./math'], () => {
const sumRes = sum(10, 20)
console.log('sumRes in hot', sumRes)
})
}
最后慨菱,熱替換切勿用于 prod 環(huán)境!4魉Α符喝!
DllPlugin
Dll 動態(tài)鏈接庫,其中可以包含給其他模塊調用的函數(shù)和數(shù)據(jù)甜孤。
要給 Web 項目構建接入動態(tài)鏈接庫的思想协饲,需要完成以下事情:
- 把網頁依賴的基礎模塊抽離出來,打包到一個個單獨的動態(tài)鏈接庫中去缴川。一個動態(tài)鏈接庫中可以包含多個模塊茉稠。
- 當需要導入的模塊存在于某個動態(tài)鏈接庫中時,這個模塊不能被再次被打包把夸,而是去動態(tài)鏈接庫中獲取而线。
- 頁面依賴的所有動態(tài)鏈接庫需要被加載。
為什么給 Web 項目構建接入動態(tài)鏈接庫的思想后恋日,會大大提升構建速度呢膀篮?
- 前端依賴于第三方庫
vue
react
等 - 其特點是:體積大,構建速度慢谚鄙,版本升級慢
- 同一個版本各拷,只需要編譯一次,之后直接引用即可 —— 不用每次重復構建闷营,提高構建速度
烤黍??傻盟?速蕊??娘赴?规哲?
Webpack 已經內置了對動態(tài)鏈接庫的支持,需要通過2個內置的插件接入诽表,它們分別是:
- DllPlugin 插件:打包出 dll 文件
- DllReferencePlugin 插件:使用 dll 文件
打包出 dll 的過程
- 增加 webpack.dll.js
- 修改 package.json scripts
"dll": "webpack --config build/webpack.dll.js"
-
npm run dll
并查看輸出結果
使用 dll
- 引入
DllReferencePlugin
- babel-loader 中排除
node_modules
- 配置
new DllReferencePlugin({...})
- index.html 中引入
react.dll.js
- 運行 dev
總結 - 提高構建效率的方法
哪些可用于線上唉锌,哪些用于線下
優(yōu)化 babel-loader(可用于線上)
IgnorePlugin 避免引入哪些模塊(可用于線上)
noParse 避免重復打包(可用于線上)
happyPack 多進程打包(可用于線上)
ParallelUglifyPlugin 多進程壓縮 js(可用于線上)
自動刷新(僅開發(fā)環(huán)境)
熱更新(僅開發(fā)環(huán)境)
DllPlugin(僅開發(fā)環(huán)境)
webpack 性能優(yōu)化
產出代碼優(yōu)化
使用 production
- 開啟壓縮代碼
- 開啟 tree shaking:用來去除無用代碼的代碼(必須是 ES6 Module 語法才行)
ES6 Module 和 commonjs 的區(qū)別
- ES6 Module 是靜態(tài)引入竿奏,編譯時引入
- commonjs 是動態(tài)引入袄简,執(zhí)行時引入
// commonjs
let apiList = require('../config/api.js')
if (isDev) {
// 可以動態(tài)引入,執(zhí)行時引入
apiList = require('../config/api_dev.js')
}
import apiList from '../config/api.js'
if (isDev) {
// 編譯時報錯泛啸,只能靜態(tài)引入
import apiList from '../config/api_dev.js'
}
小圖片 base64 編碼
bundle 加 hash
使用 CDN加速
1绿语、配置 publicPath
產物dist是cdn開頭:
2、將產物地址上傳到cdn,成功后就能訪問了
提取公共改代碼
懶加載
使用production
開啟mode:production
scope hosting 將多個函數(shù)合并到一個函數(shù)中
作用:代碼體積更小吕粹、創(chuàng)建函數(shù)作用域更少种柑、代碼可讀性更好
使用前后的對比,使用的好處匹耕、
優(yōu)化前:
源碼:
優(yōu)化前打包后產物:
會各自創(chuàng)建函數(shù)作用域聚请,代碼量較大
優(yōu)化后打包產物:
體積更小
配置如下:
//1.引入插件
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')
module.exports = {
resolve: {
// 針對 Npm 中的第三方模塊優(yōu)先采用 jsnext:main 中指向的 ES6 模塊化語法的文件
mainFields: ['jsnext:main', 'browser', 'main']
},
plugins: [
// 開啟 Scope Hoisting
new ModuleConcatenationPlugin(),
]
}
同時,考慮到 Scope Hoisting 依賴源碼需采用 ES6 模塊化語法泌神,還需要配置 mainFields
良漱。因為大部分 Npm 中的第三方庫采用了 CommonJS 語法,但部分庫會同時提供 ES6 模塊化的代碼欢际,為了充分發(fā)揮 Scope Hoisting 的作用母市。
webpack 原理和二次開發(fā)
這部分是面試的加分項,大家不要深究細節(jié)损趋。即便面試被問到患久,實際工作中應用的概率也非常小。
webpack 本身就是一個工具浑槽,只是用來打包構建蒋失。而且發(fā)展多年已經成熟完善,日常的開發(fā)場景都能滿足桐玻。
webpack 構建流程
幾個核心概念
- Entry:入口篙挽,Webpack 執(zhí)行構建的第一步將從 Entry 開始,可抽象成輸入镊靴。
- Module:模塊铣卡,在 Webpack 里一切皆模塊,一個模塊對應著一個文件偏竟。Webpack 會從配置的 Entry 開始遞歸找出所有依賴的模塊煮落。
- Chunk:代碼塊,一個 Chunk 由多個模塊組合而成踊谋,用于代碼合并與分割蝉仇。
- Loader:模塊轉換器,用于把模塊原內容按照需求轉換成新內容殖蚕。
- Plugin:擴展插件轿衔,在 Webpack 構建流程中的特定時機會廣播出對應的事件,插件可以監(jiān)聽這些事件的發(fā)生睦疫,在特定時機做對應的事情呀枢。
Webpack 的構建流程可以分為以下三大階段:
- 初始化:啟動構建,讀取與合并配置參數(shù)笼痛,加載 Plugin,實例化 Compiler。
- 編譯:從 Entry 發(fā)出缨伊,針對每個 Module 串行調用對應的 Loader 去翻譯文件內容摘刑,再找到該 Module 依賴的 Module,遞歸地進行編譯處理刻坊。
- 輸出:對編譯后的 Module 組合成 Chunk枷恕,把 Chunk 轉換成文件,輸出到文件系統(tǒng)谭胚。
開發(fā) loader
以 less-loader
為例徐块,回顧一下使用規(guī)則
{
test: /\.less$/,
// 注意順序
loader: ['style-loader', 'css-loader', 'less-loader']
}
所以,loader 的作用:
- 一個代碼轉換器灾而,將 less 代碼轉換為 css 代碼
- 再例如 vue-template-compiler
- 再例如 babel 編譯 jsx
所以一個 loader 的基本開發(fā)模式:
const less = require('node-less')
module.exports = function(source) {
// source 即 less 代碼胡控,需要返回 css 代碼
return less(source)
}
以上是 loader 的基本開發(fā)方式,實際開發(fā)中可能還會有更多要求
- 支持 options 旁趟,如
url-loader
的使用 - 支持異步 loader
- 緩存策略
// options
const loaderUtils = require('loader-utils')
module.exports = function(source) {
// 獲取 loader 的 options
const options = loaderUtils.getOptions(this)
return source // 示例昼激,直接返回 source 了
}
// 異步 loader
module.exports = function(source) {
// 使用異步 loader
const callback = this.async()
// 執(zhí)行異步函數(shù),如讀取文件
someAsyncFn(source, function(err, result, sourceMaps, ast) {
// 通過 callback 返回異步執(zhí)行后的結果
callback(err, result, sourceMaps, ast)
})
}
// 緩存
module.exports = function(source) {
// 關閉該 Loader 的緩存功能(webpack 默認開啟緩存)
this.cacheable(false)
return source
}
babel
本節(jié)主要解決以下問題锡搜,相信很多同學都很懵
- babel-polyfill —— 7.4 之后棄用橙困,推薦直接使用 corejs 和 regenerator ?耕餐?
- babel-runtime
初始化環(huán)境
- 安裝 babel 插件
npm i @babel/cli @babel/core @babel/preset-env -D
- 新建
.babelrc
凡傅,配置 preset-env - 新建
src/index.js
,寫一個箭頭函數(shù) - 運行
npx babel src/index.js
肠缔,看結果
使用 polyfill
什么是 babel-polyfill
什么是 polyfill 夏跷?—— 即一個補丁,引入以兼容新 API(注意不是新語法桩砰,如箭頭函數(shù))拓春,如搜索“Object.keys polyfill” 和 “Promise polyfill”
core-js 集合了所有新 API 的 polyfill 。https://github.com/zloirock/core-js
regenerator 是 generator 的 polyfill 亚隅。 https://github.com/facebook/regenerator
babel-polyfill 即 core-js 和 regenerator 的集合硼莽,它只做了一層封裝而已。
基本使用
-
src/index.js
中寫一個 Promise煮纵,打包看結果 -
npm install --save @babel/polyfill
【注意】要--save
- 然后引入
import '@babel/polyfill'
- 再打包懂鸵,看結果
- 解釋:babel 僅僅是處理 ES6 語法,并不關心模模塊化的事情行疏。模塊化歸 webpack 管理
- 全部引入 polyfill 匆光,體積很大
按需加載
- 新增
"useBuiltIns": "usage"
(注意要改寫 preset 的 json 結構) - 刪掉入口的
import '@babel/polyfill'
- 再打包,看結果
- 提示選擇 core-js 的版本酿联,增加
"corejs": 3
- 只引入了 promise 的 polyfill
- 提示選擇 core-js 的版本酿联,增加
使用 runtime
babel-polyfill 的問題 —— 會污染全局變量
- 如果是一個網站或者系統(tǒng)终息,無礙
- 如果是做一個第三方工具夺巩,給其他系統(tǒng)使用,則會有問題
- 不能保證其他系統(tǒng)會用 Promise 和 includes 做什么周崭,即便他們用錯了柳譬,那你也不能偷偷的給更改了
// 源代碼
Promise.resolve(100).then(data => data);
[10, 20, 30].includes(20);
// 結果 —— 可以看到,Promise 和 includes 都未改動续镇,因為以注入全局變量了
"use strict";
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
Promise.resolve(100).then(function (data) {
return data;
});
[10, 20, 30].includes(20);
使用 babel-runtime
npm install --save-dev @babel/plugin-transform-runtime
-
npm install --save @babel/runtime
美澳,注意是--save
- 配置
"plugins": ["@babel/plugin-transform-runtime"]
- 其中
"corejs": 3,
v3 支持 API 如數(shù)組 includes ,v2.x 不支持
- 其中
- 刪掉
"useBuiltIns": "usage"
- 運行代碼
總結
babel-polyfill 是什么摸航,core-js 是什么
babel-polyfill是core-js和regenerate的合集制跟,但目前已經廢棄babel-polyfill 按需加載的配置
babel-polyfill 和 babel-runtime 的不同應用場景
前端為什么要進行打包和構建?
代碼層面:
1)體積更薪椿ⅰ(Tree-Sharking雨膨、壓縮、合并逢净,加載更快)
2)編譯高級語言或語法(TS哥放、ES6+、模塊化爹土、scss)
3)兼容性和錯誤檢查(Polyfill甥雕、postcss、eslint)
工程化/團隊規(guī)范方面:
統(tǒng)一高效的開發(fā)環(huán)境
統(tǒng)一的構建流程和產出標準
集成公司構建規(guī)范(提測胀茵、上線等)
loader和plugin的區(qū)別社露?
loader模塊轉換器,如less-》css
plugin擴展插件琼娘,如HtmlWebpackPlugin
常用loader和plugin有哪些峭弟?
babel和webpack的區(qū)別?
- babel-js新語法編譯工具脱拼,不關心模塊化
-webpack-打包工具瞒瘸,是多個loader、plugin的集合
如何產出一個lib熄浓?
babel-polyfill和babel-runtime的區(qū)別
-babel-polyfill會污染全局,而babel-runtime不會污染全局
產出第三方lib要用babel-runtime
webpack如何實現(xiàn)懶加載
import()