目錄
1.webpack是什麼?
本質上,webpack
是一個現代 JavaScript
應用程序的靜態模塊打包器(module bundler)。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關係圖(dependency graph),其中包含應用程序需要的每個模塊,然後將所有這些模塊打包成一個或多個 bundle。
2.webpack安裝
webpack
是一個使用 Node.js
實現的一個模塊化代碼打包工具。所以,我們需要先安裝 webpack,安裝之前需要搭建好 Node.js
環境 。
npm install -D webpack webpack-cli
注:不推薦全局安裝
webpack-cli
: 提供 webpack 命令、工具,類似create-react-app
webpack
: webpack 代碼,類似react
3.webpack使用
注意:在window下查看命令需要使用反斜槓,否則會報錯,不是內部或外部命令。
.\node_modules\.bin\webpack
// 查看版本
.\node_modules\.bin\webpack -v
也可以編輯 package.json
的 scripts
來簡化輸入 :
scripts 中可以自動定位到 ./node_modules/.bin/ 目錄下;且只能匹配test
、start
、restart
、stop,不能自定義其他命令
// package.json
{
...,
"scripts": {
"start": "webpack" // scripts 中可以定位到 ./node_modules/.bin/ 目錄下
// "start": "webpack -v"
}
}
scripts
中使用test
、start
、restart
、stop
命名的時候,可以在調用的時候省略run
,即直接npm start
。
當然,還可以使用更加方便的方式:
npx webpack
npx webpack -v
通過 npx
也可以幫助我們直接定位命令到 ./node_modules/.bin/
目錄下 :
注:npm5.2+ 增加,如果沒有,可以使用 npm i -g npx 來安裝。
4.webpack打包模塊
打包之前,我們需要了解一個概念,入口文件 。
4.1入口文件
入口文件就是我們項目中加載的第一個文件,比如上面的 main.js
文件,其它文件都是通過 import
等方式引入的,webpack
會從我們指定的入口文件開始分析所有需要依賴的文件,然後把打包成一個完整文件。
4.2打包命令
webpack ./js/index.js
上面命令會使用 webpack
默認的一些配置對模塊文件進行打包,並把打包後的文件輸出到默認創建的 ./dist
目錄下,打包後的文件名稱默認爲 main.js
。
模塊文件打包以後,就可以在不支持 es6 模塊語法的瀏覽器環境下引入使用了。
打包文件分析 :
-
把分散的模塊文件打包到一個文件中,不需要外部引入了
-
內置了一個小型模塊加載器(類似
requireJS
),實現了打包後的代碼隔離與引用
以上就是 webpack 最基礎的使用於基本原理,當然強大的 webpack
遠遠不止這些功能。
示例:
- 直接在package.json文件中設置入口文件;
- 或者直接使用命令.\node_modules\.bin\webpack入口文件;
使用webpack對模塊進行打包,通常會對源代碼的目錄進行分類,如src。
項目路徑:
index.html:
<body>
<script type="module" src="js/main.js"></script>
</body>
m1.js:
let a = 100;
export default a;
mian.js:
import a from './m1.js';
console.log(a);
執行命令:npm start ./js/main.js後報錯
報錯原因:沒有指定打包的模式
在package.json文件中修改入口文件及模式:
"start": "webpack src/js/main.js --mode=development"
再次執行命令後:在項目路徑下自動生成了dist文件夾,且生成了打包後的main.js文件
並且將index.html中js文件,更改爲打包生成後的main.js文件:在瀏覽器中打開頁面打印正確值a爲100。
<body>
<script type="module" src="../dist/main.js"></script>
</body>
5.打包配置webpack.config.js
雖然,我們可以直接通過命令的來打包,但是推薦創建一個 webpack.config.js
的配置文件來實現更方便和強大的功能。
webpack
命令在運行的時候,默認會讀取運行命令所在的目錄下的 webpack.config.js
文件,通常我們會在項目的根目錄下運行命令和創建配置文件。
我們也可以通過 —config
選項來指定配置文件路徑:
webpack --config ./configs/my_webpack.config.js
通常情況下,我們的項目目錄大致如下:
/
-- /dist - 項目打包後存放目錄
-- /node_modules - 第三方模塊
-- /src
------ css/
------ images/
------ js/
------ index.js
-- webpack.config.js
-- package.json
配置文件webpack.config.js
:
module.exports = {
... //配置項
}
6.核心配置
6.1mode——打包模式
模式 : "production" | "development" | "none"
。分別表示用於生產環境(打包文件會進行壓縮),開發環境,不指定。
module.exports = {
mode: 'production'
}
6.2entry——入口文件形式
指定打包的入口文件,有三種不同的形式:string | object | array
- 一對一:一個入口文件、一個打包文件
module.exports = {
entry: './src/index.js'
}
- 多對一(數組方式):多個入口文件、一個打包文件
module.exports = {
entry: [
'./src/index1.js',
'./src/index2.js',
]
}
- 多對多(對象方式):多個入口、多打包文件
module.exports = {
entry: {
'index1': "./src/index1.js",
'index2': "./src/index2.js"
}
}
6.3output——打包後文件存放位置
打包後的文件位置 :
module.exports = {
...,
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
//多入口多出口(`entry` 爲對象)
// filename: "[name].js"
}
}
- 可以指定一個固定的文件名稱,如果是多入口多出口(
entry
爲對象),則不能使用單文件出口,需要使用下面的方式 - 通過
webpack
內置的變量佔位符:[name]
7.深入——執行簡要流程
在 webpack
中,有一個很重要的特性:模塊不僅僅只是 js
的文件,webpack
可以把任意文件數據作爲模塊進行處理,包括:非 js 文本、css、圖片等等 。
import txt from './a.txt';
console.log(txt);
但是 webpack
默認情況下只能處理 js
模塊,如果需要處理其它類型的模塊,則需要使用它提供的一些其它功能 。
執行簡要流程:
loaders
:webpack
中非常核心的內容之一,前面我們說的非 js 類型的模塊處理就靠它了,不同類型的模塊的解析就是依賴不同的loader
來實現的plugins
:webpack
中另外一個核心的內容,它主要是擴展webpack
本身的一些功能,它們會運行在各種模塊解析完成以後的打包編譯階段,比如對解析後的模塊文件進行壓縮等。
8.Loaders
https://webpack.js.org/loaders/
module.exports = {
...,
module: {
rules:[
{
test:/\.xxx$/,
use:{
loader: 'xxx-load'
}
}
]
}
}
當 webpack
碰到不識別的模塊的時候,webpack
會在配置的 module
中進行該文件解析規則的查找:
rules
就是我們爲不同類型的文件定義的解析規則對應的 loader,它是一個數組- 每一種類型規則通過 test 選項來定義,通過正則進行匹配,通常我們會通過正則的方式來匹配文件後綴類型。如/\.css$/。
use
針對匹配到文件類型,調用對應的loader
進行處理
從一個簡單的案例來了解 loader :
src/datas/data.txt :
我是 txt 的內容
src/datas/data.md :
# 我是 md 的內容
src/raw-loader.js:
import txtData from './datas/data.txt';
import mdData from './datas/data.md';
console.log('txtData: ', txtData);
console.log('mdData: ', mdData);
默認情況下,webpack 會報錯,因爲 webpack 處理不了 txt 和 md 這樣的非 js 的模塊,但是我們可以通過不同的loader專門來處理純文本內容(不同的 loader 有不同的作用) 。
8.1raw-loader
在 webpack 中通過 import 方式導入文件內容,loader 並不是 webpack 內置的,所以首先要安裝 :
npm install --save-dev raw-loader
然後在 webpack.config.js 中進行配置 :
module.exports = {
...,
module: {
rules: [
{
test: /\.(txt|md)$/,
use: 'raw-loader'
}
]
}
}
8.2file-loader
把識別出的資源模塊,移動到指定的輸出目錄,並且返回這個資源在輸出目錄的地址(字符串) 。
npm install --save-dev file-loader
rules: [
...,
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: "file-loader",
options: {
// placeholder 佔位符 [name] 源資源模塊的名稱
// [ext] 源資源模塊的後綴
name: "[name]_[hash].[ext]",
//打包後的存放位置
outputPath: "./images"
// 打包後文件的 url
publicPath: './images',
}
}
}
]
8.3url-loader
可以處理理 file-loader
所有的事情,但是遇到圖片格式的模塊,可以選擇性的把圖片轉成 base64
格式的字符串,並打包到 js
中,對小體積的圖片比較合適,大圖不合適。
npm install --save-dev url-loader
rules: [
...,
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: "url-loader",
options: {
// placeholder 佔位符 [name] 源資源模塊的名稱
// [ext] 源資源模塊的後綴
name: "[name]_[hash].[ext]",
//打包後的存放位置
outputPath: "./images"
// 打包後文件的 url
publicPath: './images',
// 小於 100 字節轉成 base64 格式
limit: 100
}
}
}
]
8.4css-loader
分析 css
模塊之間的關係,併合成一個 css
。
npm install --save-dev css-loader
rules: [
...,
{
test: /\.css$/,
use: {
loader: "css-loader",
options: {
// 啓用/禁用 url() 處理
url: true,
// 啓用/禁用 @import 處理
import: true,
// 啓用/禁用 Sourcemap
sourceMap: false
}
}
}
]
8.5style-loader
把 css-loader
生成的內容,用 style
標籤掛載到 head
中
npm install --save-dev style-loader
rules: [
...,
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
同一個任務的 loader
可以同時掛載多個,處理順序爲:從右到左,也就是先通過 css-loader
處理,然後把處理後的 css
字符串交給 style-loader
進行處理。
rules: [
...,
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
options: {}
},
'css-loader'
]
}
]
8.6sass-loader
把 sass
語法轉換成 css
,依賴 node-sass
模塊 。
npm install --save-dev sass-loader node-sass
9.Plugins
擴展 webpack
本身的一些功能,它們會運行在各種模塊解析完成以後的打包編譯階段,比如對解析後的模塊文件進行壓縮等 。
9.1HtmlWebpackPlugin
在打包結束後,自動生成一個 html
文件,並把打包生成的 js 模塊引入到該 html
中 。
npm install --save-dev html-webpack-plugin
// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
title: "My App",
filename: "app.html",
template: "./src/html/index.html"
})
]
};
在 html
模板中,可以通過 <%=htmlWebpackPlugin.options.XXX%>
的方式獲取配置的值 :
<!--./src/html/index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=htmlWebpackPlugin.options.title%></title>
</head>
<body>
<h1>html-webpack-plugin</h1>
</body>
</html>
更多的配置 :
title
: 用來生成裏面的title
元素filename
: 輸出的HTML
文件名,默認是index.html
, 也可以直接配置子目錄template
: 模板文件路徑,支持加載器(loader
),例如html!./index.html
inject
:true | 'head' | 'body' | false
,注入所有的資源到特定的template
或者templateContent
中,如果設置爲true
或者body
,所有的javascript
資源將被放置到body
元素的底部,'head'
將放置到head
元素中favicon
: 添加特定的favicon
路徑到輸出的HTML
文件中minify
:{} | false
, 傳遞html-minifier
選項給minify
輸出hash
:true | false
,如果爲true
,將添加webpack
編譯生成的hash
到所有包含的腳本和CSS
文件,對於解除cache
很有用cache
:true | false
,如果爲true
,這是默認值,僅在文件修改之後纔會發佈文件showErrors
:true | false
,如果爲true
,這是默認值,錯誤信息會寫入到HTML
裏面chunks
: 允許只添加某些塊 (例如,僅 unit test 塊)chunksSortMode
: 允許控制塊在添加到裏面之前的排序方式,支持的值:'none' | 'default' |{function}-default:'auto'
excludeChunks
: 允許跳過某些塊,(例如,跳過單元測試的塊)
9.2clean-webpack-plugin
刪除(清理)構建目錄
npm install --save-dev clean-webpack-plugin
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
...
plugins: [
...,
new CleanWebpackPlugin(),
...
]
}
9.3mini-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
},
'style-loader',
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
...,
new MiniCssExtractPlugin({
filename: '[name].css'
}),
...
]
}
9.4sourceMap
我們實際運行在瀏覽器的代碼是通過 webpack
打包合併甚至是壓縮混淆過的代碼,所生成的代碼並不利於我們的調試和錯誤定位,我們可以通過 sourceMap
來解決這個問題,sourceMap
本質是一個記錄了編譯後代碼與源代碼的映射關係的文件,我們可以通過 webpack
的 devtool
選項來開啓 sourceMap
。
module.exports = {
devtool: 'source-map',
...
}
首先,編譯後會爲每一個編譯文件生成一個對應的 .map
文件,同時在編譯文件中添加一段對應的 map
文件引入代碼 。
...
//# sourceMappingURL=xx.js.map
...
/*# sourceMappingURL=xx.css.map*/
同時,現代瀏覽器都能夠識別 sourceMap
文件,如 chrome
,會在 Sources
面板中顯示根據編譯文件與對應的 map
文件定位到源文件中,有利於我們的調試和錯誤定位。
10.WebpackDevServer
每次的代碼修改都需要重新編譯打包,刷新瀏覽器,特別麻煩,我們可以通過安裝 webpackDevServer
來改善這方面的體驗 。
npm install --save-dev webpack-dev-server
啓動命令:
npx webpack-dev-server
或者,package.json
中添加 scripts
:
...,
"scripts": {
"server": "webpack-dev-server"
}
修改 webpack.config.js
:
module.exports = {
...,
devServer: {
// 生成的虛擬目錄路徑
contentBase: "./dist",
// 自動開啓瀏覽器
open: true,
// 端口
port: 8081
}
}
啓動服務以後,webpack
不在會把打包後的文件生成到硬盤真實目錄中了,而是直接存在了內存中(同時虛擬了一個存放目錄路徑),後期更新編譯打包和訪問速度大大提升。
11.Proxy
當下前端的開發都是前後端分離開發的,前端開發過程中代碼會運行在一個服務器環境下(如當前的 WebpackDevServer
),那麼在處理一些後端請求的時候通常會出現跨域的問題。WebpackDevServer
內置了一個代理服務,通過內置代理就可以把我們的跨域請求轉發目標服務器上(WebpackDevServer
內置的代理髮送的請求屬於後端 - node
,不受同源策略限制),具體如下:
後端代碼,以 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);
})
注意 url
地址要填寫 WebpackDevServer
域,比如當前 WebpackDevServer
開啓的是 http://localhost:8081,也就是我們當前前端代碼運行的環境,那麼請求的 url
也必須發送到這裏,當我們的請求滿足了 proxy
中設置的 /api
開頭,那麼就會把請求轉發到 target
,所以最後的實際請求是:http://lcoahost:8787/api/info 。
12.Hot Module Replacement
在之前當代碼有變化,我們使用的 live reload
,也就是刷新整個頁面,雖然這樣爲我們省掉了很多手動刷新頁面的麻煩,但是這樣即使只是修改了很小的內容,也會刷新整個頁面,無法保持頁面操作狀態。HMR
隨之就出現了,它的核心的局部(模塊)更新,也就是不刷新頁面,只更新變化的部分 。
module.exports = {
...,
devServer: {
// 生成的虛擬目錄路徑
contentBase: "./dist",
// 自動開啓瀏覽器
open: true,
// 端口
port: 8081,
// 開啓熱更新
hot:true,
// 即使 HMR 不生效,也不去刷新整個頁面(選擇開啓)
hotOnly:true,
proxy: {
'/api': {
target: 'http://localhost:8787'
}
}
}
}
開啓 HMR
以後,當代碼發生變化,webpack
即會進行編譯,並通過 websocket
通知客戶端(瀏覽器),我們需要監聽處理來自 webpack
的通知,然後通過 HMR
提供的 API
來完成我們的局部更新邏輯 。
./fn1.js :
export default function() {
console.log('start1!');
}
index.js :
import fn1 from './fn1.js';
box1.onclick = fn1;
if (module.hot) {//如果開啓 HMR
module.hot.accept('./fn1.js', function() {
// 更新邏輯
box1.onclick = fn1;
})
}
上面代碼就是 當 ./fn1.js 模塊代碼發生變化的時候,把最新的 fn1 函數綁定到 box1.onclick 上
從上面就可以看到,HMR
其實就是以模塊爲單位,當模塊代碼發生修改的時候,通知客戶端進行對應的更新,而客戶端則根據具體的模塊來更新我們的頁面邏輯(這些邏輯需要自己去實現),好在當前一些常用的更新邏輯都有了現成的插件。
css熱更新
樣式熱更新比較簡單,style-loader
中就已經集成實現了,我們只需要在 use
中使用就可以了
react 熱更新
-
react 腳手架中也有集成
vue 熱更新
-
vue 腳手架中也有集成