webpack 是什么邪铲?
中文網(wǎng)站:https://webpack.docschina.org
本質(zhì)上,webpack
是一個(gè)現(xiàn)代 JavaScript
應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) webpack 處理應(yīng)用程序時(shí)掂之,它會(huì)遞歸地構(gòu)建一個(gè)依賴關(guān)系圖(dependency graph)店诗,其中包含應(yīng)用程序需要的每個(gè)模塊,然后將所有這些模塊打包成一個(gè)或多個(gè) bundle盏混。
安裝
npm install -D webpack webpack-cli
npm install -D webpack@4 webpack-cli@3
webpack-cli : 提供 webpack 命令蔚鸥、工具
webpack : webpack 代碼
使用
方法一:
./node_modules/.bin/webpack
// 查看版本
./node_modules/.bin/webpack -v
方法二:
編輯 package.json
的 scripts
來(lái)簡(jiǎn)化輸入
// package.json
{
...,
"scripts": {
"start": "webpack" // scripts 中可以定位到 ./node_modules/.bin/ 目錄下
}
}
//scripts 中使用 test、start许赃、restart止喷、stop 命名的時(shí)候,可以在調(diào)用的時(shí)候省略 run图焰,即直接 npm start
npm start
方法三:
通過(guò) npx
也可以幫助我們定位命令到 ./node_modules/.bin/
目錄下
注:npm5.2+ 增加启盛,如果沒(méi)有,可以使用 npm i -g npx 來(lái)安裝
npx webpack
打包模塊
默認(rèn)打包
跟上一個(gè)入口文件路徑webpack
會(huì)從指定的入口文件開(kāi)始分析所有需要依賴的文件技羔,然后把打包成一個(gè)完整文件僵闯。
會(huì)使用 webpack
默認(rèn)的配置對(duì)模塊文件進(jìn)行打包,并把打包后的文件輸出到默認(rèn)創(chuàng)建的 ./dist
目錄下藤滥,打包后的文件名稱默認(rèn)為 main.js
鳖粟。
npx webpack ./js/index.js
打包配置
webpack
命令在運(yùn)行的時(shí)候,默認(rèn)會(huì)讀取運(yùn)行命令所在的目錄下的 webpack.config.js
文件拙绊。
也可以通過(guò) —config
選項(xiàng)來(lái)指定配置文件路徑:
webpack --config ./configs/my_webpack.config.js
module.exports = {
... //配置項(xiàng)
}
mode
模式 : "production" | "development" | "none"
不同的模式下向图,webpack設(shè)置了不同的默認(rèn)配置,打包的時(shí)候進(jìn)行一些對(duì)應(yīng)的優(yōu)化标沪。
module.exports = {
mode: 'production'
}
entry
指定打包?口?文件榄攀,有三種不同的形式
一個(gè)入口、輸出一個(gè)打包文件
module.exports = {
entry: './src/index.js'
}
多個(gè)入口金句、輸出一個(gè)打包文件
module.exports = {
entry: [
'./src/index1.js',
'./src/index2.js',
]
}
多個(gè)入口檩赢、輸出多打包文件,文件名為對(duì)象的key值违寞。
module.exports = {
entry: {
'index1': "./src/index1.js",
'index2': "./src/index2.js"
}
}
output
默認(rèn)放在dist目錄下贞瞒,可以通過(guò)output修改打包后的文件位置
module.exports = {
...,
output: {
//path必須是絕對(duì)路徑偶房,__dirname當(dāng)前目錄
path: path.resolve(__dirname, "./build"),
filename: "bundle.js", //單文件出口可以指定固定文件名
//多文件出口,name從entry的key取
//filename: "[name].js"
}
}
WebpackDevServer
啟動(dòng)服務(wù)以后军浆,webpack
不在會(huì)把打包后的文件生成到硬盤(pán)真實(shí)目錄中棕洋,而是直接存在了內(nèi)存中(同時(shí)虛擬了一個(gè)目錄路徑)
npm install --save-dev webpack-dev-server
啟動(dòng)命令:
npx webpack-dev-server
或者,package.json
中添加 scripts
...,
"scripts": {
"server": "webpack-dev-server"
}
webpack五可以使用webpack server
...,
"scripts": {
"server": "webpack server"
}
修改 webpack.config.js
module.exports = {
...,
devServer: {
// 自動(dòng)開(kāi)啟瀏覽器
open: true,
// 端口
port: 8081
}
}
Proxy
后端代碼
const Koa = require('koa');
const KoaRouter = require('koa-router');
const app = new Koa();
const router = new KoaRouter();
router.get('/info', async ctx => {
ctx.body = {
username: 'zMouse',
}
})
app.use( router.routes() );
app.listen(8787);
前端代碼
//會(huì)產(chǎn)生跨域請(qǐng)求
axios({
url: 'http://localhost:8787/info'
}).then(res => {
console.log('res',res.data);
})
//webpack配置后代碼
axios({
url: 'http://localhost:8787/api/info'
}).then(res => {
console.log('res',res.data);
})
module.exports = {
...,
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8787',
pathRewrite: {
'^/api': ''
}
}
}
}
}
Hot Module Replacement
實(shí)現(xiàn)不刷新頁(yè)面乒融,只更新變化的部分
開(kāi)啟 HMR
以后掰盘,當(dāng)代碼發(fā)生變化,webpack
即會(huì)進(jìn)行編譯簇抵,并通過(guò) websocket
通知客戶端(瀏覽器)庆杜,我們需要監(jiān)聽(tīng)處理來(lái)自 webpack
的通知,然后通過(guò) HMR
提供的 API
來(lái)完成我們的局部更新邏輯
module.exports = {
...,
devServer: {
// 開(kāi)啟熱更新,如果沒(méi)寫(xiě)moudle.hot會(huì)回退為`live reload`
hot:true,
// 即使 HMR 不生效,也不去刷新整個(gè)頁(yè)面(選擇開(kāi)啟)
hotOnly:true,
}
}
一個(gè)js代碼熱更新示例:
當(dāng) fn1.js 模塊代碼發(fā)生變化的時(shí)候阿迈,把最新的 fn1 函數(shù)綁定到 box1.onclick 上
//fn1.js
export default function() {
console.log('start1!');
}
import fn1 from './fn1.js';
box1.onclick = fn1;
if (module.hot) {//如果開(kāi)啟 HMR
module.hot.accept('./fn1.js', function() {
// 更新邏輯
box1.onclick = fn1;
})
}
HMR
以模塊為單位,當(dāng)模塊代碼發(fā)生修改的時(shí)候断盛,通知客戶端進(jìn)行對(duì)應(yīng)的更新,而客戶端則根據(jù)具體的模塊來(lái)更新我們的頁(yè)面邏輯
當(dāng)前一些常用的更新邏輯都有了現(xiàn)成的插件
css熱更新
樣式熱更新愉舔,style-loader
中就已經(jīng)集成實(shí)現(xiàn)
react 熱更新
react 腳手架中已經(jīng)集成
https://github.com/gaearon/react-hot-loader
vue 熱更新
vue 腳手架中已經(jīng)集成
https://github.com/vuejs/vue-loader
sourceMap
https://webpack.docschina.org/configuration/devtool/#devtool
webpack
會(huì)打包合并甚至是壓縮混淆代碼钢猛,所生成的代碼運(yùn)行在瀏覽器時(shí)不利于我們的調(diào)試和定位錯(cuò)誤,
sourceMap
是 記錄了編譯后代碼與源代碼的映射關(guān)系的文件
通過(guò) webpack
的 devtool
選項(xiàng)來(lái)開(kāi)啟 sourceMap
轩缤。
module.exports = {
mode: 'production',
devtool: 'source-map',
...
}
編譯后會(huì)為每一個(gè)編譯文件生成一個(gè)對(duì)應(yīng)的 .map
文件命迈,同時(shí)在編譯文件中添加一段對(duì)應(yīng)的注釋,和對(duì)應(yīng)的 map
文件關(guān)聯(lián)
...
//# sourceMappingURL=main.js.map
...
/*# sourceMappingURL=main.css.map*/
配置推薦:
devtool:"eval-cheap-module-source-map",// 開(kāi)發(fā)環(huán)境
devtool:"source-map"||false, // 生產(chǎn)環(huán)境
Code Spliting
將代碼分割到多個(gè)不同的bundle(打包后)文件中火的,可以通過(guò)按需加載等方式對(duì)資源進(jìn)行加載
入口起點(diǎn)
通過(guò)設(shè)置多個(gè)入口文件的方式實(shí)現(xiàn)最簡(jiǎn)單的代碼分割
entry: {
index: "./src/index.js",
list: "./src/list.js",
},
output: {
path: resolve(__dirname, "../dist"),
// 多入口文件的filename不能寫(xiě)死名稱壶愤,需要通過(guò)[name]配置
filename: "js/[name].js",
}
防止重復(fù)
如果只按上述配置,若index和list都使用了axios馏鹤,都會(huì)把a(bǔ)xios代碼打包其中征椒。
可以通過(guò)設(shè)置dependOn配置多個(gè)模塊之間的共享文件,將共享的axios代碼提取成一個(gè)單獨(dú)文件湃累。
entry: {
index: {
import: "./src/index.js",
dependOn: "axios",
},
list: {
import: "./src/list.js",
dependOn: "axios",
},
axios: "axios",
},
//需要設(shè)置runtimeChunk為single勃救,在所有生成 chunk 之間共享運(yùn)行時(shí)文件,否則axios會(huì)生成兩個(gè)實(shí)例
optimization: {
runtimeChunk: 'single',
},
SplitChunksPlugin
將公共的依賴模塊提取到已有的入口chunk文件或新的chunk文件當(dāng)中
entry: {
index: "./src/index.js",
list: "./src/list.js",
},
optimization: {
splitChunks: {
// async表示只從異步加載得模塊(動(dòng)態(tài)加載import())里面進(jìn)行拆分
// initial表示只從入口模塊進(jìn)行拆分
// all表示以上兩者都包括
chunks: "all",
},
}
動(dòng)態(tài)導(dǎo)入
通過(guò)import()
動(dòng)態(tài)導(dǎo)入模塊治力,可以通過(guò)內(nèi)聯(lián)注釋對(duì)chunk進(jìn)行一些配置
模塊方法 | webpack 中文文檔 (docschina.org)
import(/* webpackChunkName: 'data', webpackPreload: true*/ './data')
.then(data => {
console.log(data);
})
Prefetch/Preload
通過(guò)內(nèi)聯(lián)注釋webpackPrefetch
和webpackPreload
兩種資源提示告知瀏覽器對(duì)資源進(jìn)行不同的加載處理
const data = import(/* webpackChunkName: 'data', webpackPreload: true */ './data.js')
const data = import(/* webpackChunkName: 'data', webpackPrefetch: true */ './data.js')
與 prefetch 指令相比蒙秒,preload 指令有許多不同之處:
preload chunk 會(huì)在父 chunk 加載時(shí),以并行方式開(kāi)始加載宵统。prefetch chunk 會(huì)在父 chunk 加載結(jié)束后開(kāi)始加載税肪。
preload chunk 具有中等優(yōu)先級(jí),并立即下載。prefetch chunk 在瀏覽器閑置時(shí)下載益兄。
preload chunk 會(huì)在父 chunk 中立即請(qǐng)求,用于當(dāng)下時(shí)刻箭券。prefetch chunk 會(huì)用于未來(lái)的某個(gè)時(shí)刻净捅。
外部擴(kuò)展
通過(guò)externals配置在輸出的bundle中排除某些依賴,這些依賴將會(huì)在用戶環(huán)境中所包含辩块。
externals:
lodash: '_'
},
tree shaking
將上下文中的dead-code移除蛔六,就像搖樹(shù)上的枯葉使其掉落一樣
optimization: {
usedExports: true,
}
配置合并
npm install webpack-merge -D
const merge = require("webpack-merge")
const commonConfig = require("./webpack.common.js")
const devConfig = { ... }
module.exports = merge(commonConfig,devConfig)