webpack 5.x 快速入门
简介
通过具体的示例,结合 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 协议》,转载必须注明作者和本文链接