webpack 是什麼?
- 官方網站:https://webpack.js.org/
- 中文網站:https://www.webpackjs.com/
本質上,`webpack` 是一個現代 `JavaScript` 應用程序的靜態模塊打包器(module bundler)。當 webpack 處理應用程序時,它會遞歸地
構建一個依賴關係圖(dependency graph),其中包含應用程序需要的每個模塊,然後將所有這些模塊打包成一個或多個 bundle。
安裝(兩個工具webpack、webpack-cli)
`webpack` 是一個使用 `Node.js` 實現的一個模塊化代碼打包工具。所以,我們需要先安裝 webpack,安裝之前需要搭建好 `Node.js` 環境
npm install -D webpack webpack-cli
注:不推薦全局安裝
`webpack-cli` : 提供 webpack 命令、工具,類似 `create-react-app`
`webpack` : webpack 代碼,類似 `react`
使用
./node_modules/.bin/webpack
查看版本
./node_modules/.bin/webpack -v
也可以編輯 `package.json` 的 `scripts` 來簡化輸入
// package.json
{
...,
"scripts": {
"start": "webpack" // scripts 中可以定位到 ./node_modules/.bin/ 目錄下
}
}
當然,還可以使用更加方便的方式:
npx webpack
通過 `npx` 也可以幫助我們定位命令到 `./node_modules/.bin/` 目錄下
注:npm5.2+ 增加,如果沒有,可以使用 npm i -g npx 來安裝
打包模塊
打包之前,我們需要了解一個概念,入口文件
入口文件
入口文件就是我們項目中加載的第一個文件,比如上面的 `main.js` 文件,其它文件都是通過 `import` 等方式引入的,`webpack` 會從
我們指定的入口文件開始分析所有需要依賴的文件,然後把打包成一個完整文件。
打包命令
webpack ./js/index.js
上面命令會使用 `webpack` 默認的一些配置對模塊文件進行打包,並把打包後的文件輸出到默認創建的 `./dist` 目錄下,打包後的
文件名稱默認爲 `main.js`。
模塊文件打包以後,就可以在不支持 es6 模塊語法的瀏覽器環境下引入使用了。
打包文件分析
- 把分散的模塊文件打包到一個文件中,不需要外部引入了
- 內置了一個小型模塊加載器(類似 `requireJS`),實現了打包後的代碼隔離與引用
以上就是 webpack 最基礎的使用於基本原理,當然強大的 `webpack` 遠遠不止這些功能。
打包配置
雖然,我們可以直接通過命令的來打包,但是推薦創建一個 `webpack.config.js` 的配置文件來實現更方便和強大的功能。
`webpack` 命令在運行的時候,默認會讀取運行命令所在的目錄下的 `webpack.config.js` 文件,通常我們會在項目的根目錄下運行命令
和創建配置文件。
我們也可以通過 `—config` 選項來指定配置文件路徑:
webpack --config ./configs/my_webpack.config.js
通常情況下,我們的項目目錄大致如下:
核心配置
mode模式
`"production" | "development" | "none"`
在webpack.config.js添加就可變成開發環境
module.exports = {
mode: 'production'
}
entry入口---分析的第一個文件
output出口
打包後的文件位置
多出口模式
- 可以指定一個固定的文件名稱,如果是多入口多出口(`entry` 爲對象),則不能使用單文件出口,需要使用下面的方式
- 通過 `webpack` 內置的變量佔位符:`[name]`
深入功能
在 `webpack` 中,有一個很重要的特性:模塊不僅僅只是 `js` 的文件,`webpack` 可以把任意文件數據作爲模塊進行處理,
包括:非 js 文本、css、圖片等等
但是 `webpack` 默認情況下只能處理 `js` 模塊,如果需要處理其它類型的模塊,則需要使用它提供的一些其它功能
執行簡要流程
Loaders
https://webpack.js.org/loaders/
當 `webpack` 碰到不識別的模塊的時候,`webpack` 會在配置的 `module` 中進行該文件解析規則的查找
- `rules` 就是我們爲不同類型的文件定義的解析規則對應的 loader,它是一個數組
- 每一種類型規則通過 test 選項來定義,通過正則進行匹配,通常我們會通過正則的方式來匹配文件後綴類型
- `use` 針對匹配到文件類型,調用對應的 `loader` 進行處理
從一個簡單的案例來了解 loader
raw-loader
在 webpack 中通過 import 方式導入文件內容,loader 並不是 webpack 內置的,所以首先要安裝
npm install --save-dev raw-loader
然後在 webpack.config.js 中進行配置
file-loader
把識別出的資源模塊,移動到指定的輸出目錄,並且返回這個資源在輸出目錄的地址(字符串)
npm install --save-dev file-loader
佔位符:https://webpack.js.org/loaders/file-loader#placeholders
url-loader
可以處理理 `file-loader` 所有的事情,但是遇到圖片格式的模塊,可以選擇性的把圖片轉成 `base64` 格式的字符串,並打包到 `js` 中,
對小體積的圖片比較合適,大圖片不合適。
npm install --save-dev url-loader
css-loader
分析 `css` 模塊之間的關係,併合成一個 `css`
npm install --save-dev css-loader
style-loader
把 `css-loader` 生成的內容,用 `style` 標籤掛載到頁面的 `head` 中
npm install --save-dev style-loader
同一個任務的 `loader` 可以同時掛載多個,處理順序爲:從右到左,也就是先通過 `css-loader` 處理,然後把處理後的 `css` 字符串
交給 `style-loader` 進行處理
sass-loader
把 `sass` 語法轉換成 `css` ,依賴 `node-sass` 模塊
npm install --save-dev sass-loader node-sass
Plugins(插件)
擴展 `webpack` 本身的一些功能,它們會運行在各種模塊解析完成以後的打包編譯階段,比如對解析後的模塊文件進行壓縮等
HtmlWebpackPlugin
在打包結束後,自動生成一個 `html` 文件,並把打包生成的 js 模塊引入到該 `html` 中
npm install --save-dev html-webpack-plugin
在 `html` 模板中,可以通過 `<%=htmlWebpackPlugin.options.XXX%>` 的方式獲取配置的值
clean-webpack-plugin
刪除(清理)構建目錄
npm install --save-dev clean-webpack-plugin
mini-css-extract-plugin
提取 `CSS` 到一個單獨的文件中
npm install --save-dev mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...,
module: {
rules: [
{
test: /\.s[ac]ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
...,
new MiniCssExtractPlugin({
filename: '[name].css'
}),
...
]
}
sourceMap
我們實際運行在瀏覽器的代碼是通過 `webpack` 打包合併甚至是壓縮混淆過的代碼,所生成的代碼並不利於我們的調試和錯誤定位,
我們可以通過 `sourceMap` 來解決這個問題,`sourceMap` 本質是一個記錄了編譯後代碼與源代碼的映射關係的文件,我們可以通過
`webpack` 的 `devtool` 選項來開啓 `sourceMap`
首先,編譯後會爲每一個編譯文件生成一個對應的 `.map` 文件,同時在編譯文件中添加一段對應的 `map` 文件引入代碼
同時,現代瀏覽器都能夠識別 `sourceMap` 文件,如 `chrome`,會在 `Sources` 面板中顯示根據編譯文件與對應的 `map` 文件定位到
源文件中,有利於我們的調試和錯誤定位
WebpackDevServer
每次的代碼修改都需要重新編譯打包,刷新瀏覽器,特別麻煩,我們可以通過安裝 `webpackDevServer` 來改善這方面的體驗
npm install --save-dev webpack-dev-server
啓動命令:
npx webpack-dev-server
或者,`package.json` 中添加 `scripts`
修改 `webpack.config.js`
啓動服務以後,`webpack` 不在會把打包後的文件生成到硬盤真實目錄中了,而是直接存在了內存中(同時虛擬了一個存放目錄路徑),
後期更新編譯打包和訪問速度大大提升
Proxy
當下前端的開發都是前後端分離開發的,前端開發過程中代碼會運行在一個服務器環境下(如當前的 `WebpackDevServer`),那麼在
處理一些後端請求的時候通常會出現跨域的問題。`WebpackDevServer` 內置了一個代理服務,通過內置代理就可以把我們的跨域請求
轉發目標服務器上(`WebpackDevServer` 內置的代理髮送的請求屬於後端 - `node`,不受同源策略限制),具體如下:
const Koa = require('koa');
const KoaRouter = require('koa-router');
const app = new Koa();
const router = new KoaRouter();
router.get('/api/info', async ctx => {
ctx.body = {
username: 'zMouse',
gender: 'male'
}
})
app.use( router.routes() );
app.listen(8787);
axios({
url: 'http://localhost:8787/api/info'
}).then(res => {
console.log('res',res.data);
})
默認情況下,該代碼運行以後會出現跨域請求錯誤,修改 `webpack` 配置
module.exports = {
...,
devServer: {
// 生成的虛擬目錄路徑
contentBase: "./dist",
// 自動開啓瀏覽器
open: true,
// 端口
port: 8081,
proxy: {
'/api': {
target: 'http://localhost:8787'
}
}
}
}
通過 `proxy` 設置,當我們在當前 `WebpackDevServer` 環境下發送以 `/api` 開頭的請求都會被轉發到 http://localhost:8787 目標服務器下
axios({
//url: 'http://locahost:8081/api/info',
url: '/api/info'
}).then(res => {
console.log('res',res.data);
})
Hot Module Replacement(熱重載)
在之前當代碼有變化,我們使用的 `live reload`,也就是刷新整個頁面,雖然這樣爲我們省掉了很多手動刷新頁面的麻煩,但是這樣
即使只是修改了很小的內容,也會刷新整個頁面,無法保持頁面操作狀態。`HMR` 隨之就出現了,它的核心的局部(模塊)更新,
也就是不刷新頁面,只更新變化的部分
開啓 `HMR` 以後,當代碼發生變化,`webpack` 即會進行編譯,並通過 `websocket` 通知客戶端(瀏覽器),我們需要監聽處理
來自 `webpack` 的通知,然後通過 `HMR` 提供的 `API` 來完成我們的局部更新邏輯
上面代碼就是 當 ./fn1.js 模塊代碼發生變化的時候,把最新的 fn1 函數綁定到 box1.onclick 上
從上面就可以看到,`HMR` 其實就是以模塊爲單位,當模塊代碼發生修改的時候,通知客戶端進行對應的更新,而客戶端則根據具體
的模塊來更新我們的頁面邏輯(這些邏輯需要自己去實現),好在當前一些常用的更新邏輯都有了現成的插件
https://github.com/gaearon/react-hot-loader
https://github.com/vuejs/vue-loader