簡介
透過具體的示例,結合 Bootstrap 4.x、Vue.js 3.x 和 Electron 11.x,全面介紹 webpack 5.x 的配置、用法。
本文原始碼 在此。
起步
檢查 node.js 和 npm 的版本。
$ node -v
v15.10.0
$ npm -v
7.6.0
新建一個目錄,進入。新建 package.json
檔案。
$ mkdir webpack-demo
$ cd webpack-demo
$ npm init -y
為了提高下載速度,安裝 cnpm。
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
用 cnpm 安裝 webpack 及其命令列工具。檢查安裝的版本。
$ cnpm i -D webpack webpack-cli
$ npx webpack -v
webpack 5.24.2
webpack-cli 4.5.0
在 package.json
的 scripts
中新增 "build": "webpack"
。
"scripts": {
"build": "webpack"
}
這樣就可以用 npm run build
命令來執行一次打包。
新建兩個子目錄 src
和 dist
,分別用來放置原始檔和打包輸出。
$ mkdir src dist
新建 ./src/app.js
檔案,作為打包入口。
$ touch ./src/app.js
alert('Hello webpack!');
新建 index.html
檔案,假設打包輸出為 ./dist/bundle.js
。
$ touch index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Hello World!</title>
</head>
<body>
<p>Hello webpack, Bootstrap 4.x, Vue.js 3.x and Electron 11.x!</p>
<script src="./dist/bundle.js"></script>
</body>
</html>
最終的目錄結構:
$ tree -L 2 -I "node_modules"
.
|-- dist
|-- index.html
|-- package.json
`-- src
`-- app.js
2 directories, 3 files
基本配置
習慣上將 webpack 的配置檔案命名為 webpack.config.js
,須手動建立,放在專案根目錄下,webpack 會自動引用此配置檔案。
$ touch webpack.config.js
const path = require('path');
module.exports = {
// production 生產環境
// 或 development 開發環境
mode: 'production',
// 打包的起點
entry: './src/app.js',
// 打包的輸出
output: {
filename: 'bundle.js',
// 輸出目錄的絕對路徑和相對路徑(相對網站目錄)
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
},
// 模組,裝載器等,見下文
module: {},
// 外掛,`.css` 的剝離和壓縮、`.html` 的動態生成等,見下文
plugins: []
};
嘗試進行一次打包:
$ npm run build
> webpack-demo@1.0.0 build
> npx webpack
asset bundle.js 24 bytes [compared for emit] [minimized] (name: main)
./src/app.js 28 bytes [built] [code generated]
webpack 5.24.2 compiled successfully in 278 ms
$ tree -L 2 -I "node_modules"
.
|-- dist
| `-- bundle.js
|-- index.html
|-- package.json
|-- src
| `-- app.js
`-- webpack.config.js
2 directories, 5 files
嘗試用瀏覽器開啟 index.html
,看是否有彈窗。
認識裝載器
裝載器在 webpack.config.js
的 module.rules
中列出。
module: {
rules: [
]
}
rules
中的元素是物件型別,通常包含 test
use
兩個屬性。test
匹配檔名,use
列出裝載器,由此形成一條流水線(呼叫順序與書寫順序相反)。
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1 }
},
{
loader: 'less-loader',
options: { noIeCompat: true }
}
]
}
]
}
上例表示以 .less
結尾的檔案依次用 less-loader
css-loader
style-loader
處理。
只需一個裝載器時,用 loader
代替 use
。
{
test: /\.(png|svg|jpg|gif)$/,
loader: 'file-loader'
}
新增轉載器
安裝上述提到的 3 個的裝載器:
$ cnpm i -D style-loader css-loader file-loader
css-loader
負責解析.js
檔案中的import '.css'
以及.css
檔案中的@import '.css'
。style-loader
負責生成<style>
標籤並追加到<head>
標籤中。
在 webpack.config.js
的 module.rules
中新增:
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
loader: 'file-loader'
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
loader: 'file-loader'
}
]
}
新建 ./src/app.css
檔案。
$ touch ./src/app.css
p {
background-color: blue;
}
在 ./src/app.js
中引入:
import './app.css';
$ rm -f ./dist/* && npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
| `-- bundle.js
|-- index.html
|-- package.json
|-- src
| |-- app.css
| `-- app.js
`-- webpack.config.js
2 directories, 6 files
檢查 <p>
標籤的背景色。
使用 Bootstrap 4.x
安裝 Bootstrap 及其依賴包。
$ cnpm i -D bootstrap jquery popper.js
在 ./src/app.js
的頂部插入:
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
在 index.html
中使用 Bootstrap 的樣式類 text-success
。
<p class="text-danger">Hello webpack, Bootstrap 4.x, Vue.js 3.x and Electron 11.x!</p>
重新打包,檢查字型的顏色。
省略字尾名
在 .js
檔案中用 import
匯入模組時,可以省略模組的字尾名,但必須在 webpack.config.js
的 resolve.extensions
中列出。
resolve: {
extensions: ['.js', '.json', '.css', '.sass', '.vue']
}
多入口
新建 JS 檔案。
$ touch ./src/admin.js
alert('Hello admin!');
webpack.config.js
中,entry
改成物件型別,output.filename
用 [name]
拼接。
entry: {
app: './src/app.js',
admin: './src/admin.js'
},
output: {
filename: '[name].bundle.js',
},
相應修改 index.html
:
<script src="./dist/app.bundle.js"></script>
<script src="./dist/admin.bundle.js"></script>
$ rm -f ./dist/* && npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
| |-- admin.bundle.js
| |-- app.bundle.js
| |-- app.bundle.js.LICENSE.txt
|-- index.html
|-- package.json
|-- src
| |-- admin.js
| |-- app.css
| `-- app.js
`-- webpack.config.js
2 directories, 9 files
重新打包,檢查是否有兩次彈窗。
分離第三方庫
可以單獨打包第三方庫,而不在 .js
檔案中引入,只須在 entry.vendors
中列出第三方庫,這會得到 vendors.bundle.js
檔案(檔名取決於 output.filename
)。
entry: {
vendors: ['bootstrap', 'jquery', 'popper.js']
}
在 index.html
中新增:
<script src="./dist/vendors.bundle.js"></script>
註釋 ./src/app.js
中的 import 'bootstrap'
。
// import 'bootstrap';
剝離 CSS
剝離 .js
引入的 .css
,合併成單獨的 .css
檔案,每個入口對應一個。
安裝外掛 mini-css-extract-plugin
。
$ cnpm i -D mini-css-extract-plugin
在 plugins
中例項化外掛,在 module.rules
中應用外掛。同時,為方便演示:
- 刪除
./src/admin.js
,相應修改entry
。 - 修改
output.filename
。
$ rm -f ./src/admin.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
entry: {
// admin: './src/admin.js',
},
output: {
// filename: '[name].bundle.js',
filename: '[name].js',
},
module: {
rules: [
{
test: /\.css/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
]
},
plugins: [
// 將輸出 `[name].css`
new MiniCssExtractPlugin({ filename: '[name].css' })
]
修改 index.html
:
<link rel="stylesheet" href="./dist/app.css">
<script src="./dist/vendors.js"></script>
<script src="./dist/app.js"></script>
$ rm -f ./dist/* && npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
| |-- app.css
| |-- app.js
| |-- vendors.js
| `-- vendors.js.LICENSE.txt
|-- index.html
|-- package.json
|-- src
| |-- app.css
| `-- app.js
`-- webpack.config.js
2 directories, 9 files
壓縮 CSS
安裝外掛 css-minimizer-webpack-plugin
。
$ cnpm i -D css-minimizer-webpack-plugin
在 optimization.minimizer
中例項化即可。
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
optimization: {
minimizer: ['...', new CssMinimizerPlugin()]
}
...
表示對 minimizer
進行擴充套件而不是覆蓋,以保留內建的 .js
壓縮外掛。
自動清理打包輸出
安裝外掛 clean-webpack-plugin
。
$ cnpm i -D clean-webpack-plugin
在 plugins
中例項化即可。
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
new CleanWebpackPlugin()
]
帶 hash 的檔名、自動生成 index.html
安裝外掛 html-webpack-plugin
。
$ cnpm i -D html-webpack-plugin
檔名用 [fullhash]
拼接。在 plugins
中呼叫 HtmlWebpackPlugin
指定模板 .ejs
、輸出檔名、是否將 .css
.js
直接嵌入模板而不透過 <link>
<script>
引用。
const HtmlWebpackPlugin = require('html-webpack-plugin');
output: {
filename: '[name].[fullhash].js',
},
plugins: [
new MiniCssExtractPlugin({ filename: '[name].[fullhash].css' }),
new HtmlWebpackPlugin({
template: './src/index.ejs',
filename: 'index.html',
inject: false
})
]
刪除沒用的 index.html
,新建模板 ./src/index.ejs
。
$ rm -f ./index.html
$ touch ./src/index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Hello World!</title>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
</head>
<body>
<p class="text-danger">Hello webpack, Bootstrap 4.x, Vue.js 3.x and Electron 11.x!</p>
<script src="<%= htmlWebpackPlugin.files.js[0] %>"></script>
<script src="<%= htmlWebpackPlugin.files.js[1] %>"></script>
</body>
</html>
重新打包,檢查是否正確生成 ./dist/index.html
。
$ npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
| |-- app.7faf852820591e38bbd0.css
| |-- app.7faf852820591e38bbd0.js
| |-- index.html
| |-- vendors.7faf852820591e38bbd0.js
| `-- vendors.7faf852820591e38bbd0.js.LICENSE.txt
|-- package.json
|-- src
| |-- app.css
| |-- app.js
| `-- index.ejs
`-- webpack.config.js
2 directories, 10 files
開發時配置
安裝 webpack-merge
。
$ cnpm i -D webpack-merge
新增配置檔案 webpack.dev.config.js
。
$ touch webpack.dev.config.js
const { merge } = require('webpack-merge');
const common = require('./webpack.config.js');
module.exports = merge(common, {
// 開發環境
mode: 'development',
// 到原始碼的對映
devtool: 'inline-source-map'
});
用處見下文。
使用 webpack-dev-server
webpack-dev-server
可以快速開啟一個 Web 服務,並在檔案發生改變時重新編譯並通知瀏覽器。
安裝 webpack-dev-server
。
$ cnpm i -D webpack-dev-server
在 webpack.dev.config.js
中新增配置項 devServer
。
devServer: {
host: "127.0.0.1",
port: 8080,
contentBase: __dirname + '/assets', // 圖示、圖片等靜態資源的位置
historyApiFallback: true // 相容 HTML5 history API
}
修改 package.json
的 scripts
。
"scripts": {
"dev": "webpack serve -c webpack.dev.config.js --open"
}
執行 npm run dev
檢視效果。
$ tree -L 2 -I "node_modules"
.
|-- dist
|-- package.json
|-- src
| |-- app.css
| |-- app.js
| `-- index.ejs
|-- webpack.config.js
`-- webpack.dev.config.js
2 directories, 6 files
使用 Vue.js 3.x
安裝。
$ cnpm i -D vue@next vue-loader@next @vue/compiler-sfc
修改 webpack.config.js
的 module.rules
和 plugins
。
const webpack = require('webpack');
const { VueLoaderPlugin } = require("vue-loader");
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
]
},
plugins: [
new VueLoaderPlugin(),
new webpack.DefinePlugin({
"__VUE_OPTIONS_API__": true,
"__VUE_PROD_DEVTOOLS__": false,
})
],
修改 ./src/index.ejs
。
<body>
<div id="app" v-cloak></div>
</body>
修改 ./src/app.css
。
[v-cloak] {
display: none;
}
新增檔案 ./src/app.vue
。
$ touch ./src/app.vue
<template>
<p @click="onClick" class="text-danger">
Hello webpack, Bootstrap 4.x, Vue.js 3.x and Electron 11.x!
</p>
</template>
<script>
export default {
methods: {
onClick() {
alert('clicked');
}
}
};
</script>
在 ./src/app.js
中新增:
import { createApp } from 'vue';
import app from './app.vue';
createApp(app).mount('#app');
$ tree -L 2 -I "node_modules"
.
|-- dist
|-- package.json
|-- src
| |-- app.css
| |-- app.js
| |-- app.vue
| `-- index.ejs
|-- webpack.config.js
`-- webpack.dev.config.js
2 directories, 7 files
執行 npm run dev
檢視效果。
使用 Electron 11.x
在 webpack.config.js
中增加 target
並修改 output
。
target: 'electron-renderer',
output: {
// filename: '[name].[fullhash].js',
filename: '[name].js',
// publicPath: '/'
publicPath: './'
plugins: [
// new MiniCssExtractPlugin({ filename: '[name].[fullhash].css' }),
new MiniCssExtractPlugin({ filename: '[name].css' }),
必須設定 target
,否則不能在渲染程式中使用 fs
path
等模組。
安裝 Electron。
$ cnpm i -D electron electron-packager
$ npx electron -v
v11.3.0
$ npx electron-packager --version
Electron Packager 15.2.0
Node v15.10.0
Host Operating system: win32 10.0.19042 (x64)
新建 main.js
作為主程式。
$ touch main.js
const { app, BrowserWindow } = require('electron');
function createWindow() {
const options = {
width: 400,
height: 300,
webPreferences: { nodeIntegration: true }
};
const win = new BrowserWindow(options);
win.loadFile('./dist/index.html');
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
win.loadFile('./dist/index.html');
取決於 electron
的工作目錄。
修改 package.json
。
"main": "main.js",
"scripts": {
"build": "webpack",
"dev": "electron .",
"test": "webpack && electron .",
"make": "electron-packager . --ignore='\\.gitignore|webpack*\\.js|node_modules|src' --overwrite --download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/"
}
--ignore
忽略.gitignore
webpack*.js
node_modules/
src/
等檔案。--overwrite
如存在舊的軟體包,直接覆蓋。--download
從淘寶 NPM 映象下載。
在 ./src/index.ejs
新增:
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';">
用 webpack 打包,用 Electron 執行。
$ npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
| |-- app.css
| |-- app.js
| |-- index.html
| |-- vendors.js
| `-- vendors.js.LICENSE.txt
|-- main.js
|-- package.json
|-- src
| |-- app.css
| |-- app.js
| |-- app.vue
| `-- index.ejs
|-- webpack.config.js
`-- webpack.dev.config.js
2 directories, 13 files
$ npm run dev
可以用 npm test
代替以上兩個命令。
打包成 .exe
檔案。
$ npm run make
> webpack-demo@1.0.0 make
> electron-packager . --ignore='\\.gitignore|webpack*\\.js|node_modules|src' --overwrite --download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/
Packaging app for platform win32 x64 using electron v11.3.0
Wrote new app to...
本作品採用《CC 協議》,轉載必須註明作者和本文連結