目前
vue3
官网推荐的工具链已经是vite
了,就算是要使用webpack
甚至是webpack5
,也可以直接使用vue-cli
。然而之所以写这个,可以当是webpack5
的一个学习文章。同时也是因为之前有个项目是在vue3
刚出来的时候使用vue-cli
(那是官网还是推荐使用webpack
,以及但是得版本为webpack4
)开发的,手动改成webpack5
,想记录下。
Webpack
是什么
本质上,webpack
是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles
,它们均为静态资源,用于展示你的内容。
这个话是官网的原话,官网地址:webpack 中文
核心内容
- Entry
- Output
- Loader
其作用是让
webpack
处理那些非 js, json 文件。由于webpack
自身只能试别 js, json,其他后缀的文件需要经过loader
处理,将他们转化成有效模块。loader
可以是同步的,也可以是异步的,支持链式调用。
- Plugins
loader
用于转换某些类型的模块,而插件则可以用于执行范围更广的任务:打包优化,资源管理,注入环境变量。plugin
会运行在webpack
的不同阶段,贯穿整个编译周期,目的在于解决loader
无法实现的其他事。
- Module
vue3 项目搭建
首先创建项目名称,以及下载相关的库。
# 创建项目
mkdir webpack-vue3
cd webpack-vue3
# 初始化 npm
npm init -y
# 下载 webpack
npm install webpack webpack-cli webpack-dev-server -D
# 下载 vue
npm install vue
# 下载 css 相关 loader
npm install css-loader style-loader vue-loader postcss-loader postcss autoprefixer -D
# 下载 vue 相关
npm install vue-loader @vue/compiler-sfc -D
# 下载 babel 相关
npm install babel-loader @babel/core @babel/preset-env -D
# 下载 plugin
npm install html-webpack-plugin -D
首先根目录创建 src
、public
文件夹,文件夹内容如下所示:
<!-- public > index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
// src > main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
<!-- src > App.vue -->
<template>
<div class="container">
<p>Vue3 Project</p>
<p class="text">count: {{ count }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(1)
</script>
<style>
.text {
color: blue;
}
</style>
初始化 vue
单页面程序后,下面开始配置 webpack
,在根目录下创建 webpack.config.js
文件。
// . > webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'development',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
devtool: 'source-map',
devServer: {
static: './dist',
hot: true
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer'), // 使用 autoprefixer 插件
]
}
}
}
]
},
{
test: /\.(png|jpg|gif|svg|jpeg)$/,
type: 'asset/resource', // 使用 asset/resource 类型
generator: {
filename: 'public/images/[name].[contenthash:8][ext]', // 输出目录和文件名格式
},
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html'
})
],
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js'
},
extensions: ['.js', '.vue', '.json']
}
}
然后 package.json
配置脚本
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "webpack server",
"build": "webpack"
},
完成到这一步,我们就可以在终端运行 npm run server
来跑起项目,也可以通过 npm run build
打包项目。这个只是一个简单的 webpack
配置,运行环境 mode
在正式打包的时候还要手动修改成 production
,以及 source-map
移除等。对于要正式上线的项目,这样的配置往往是不够的。
优化 webpack 配置
拆分配置,配置运行环境
首先,我们先对项目进行环境的划分。
npm install cross-env -D
下载 cross-env
库,用于设置环境变量。下载完后,修改 package.json
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "webpack server --config build/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js",
"build:test": "cross-env NODE_ENV=test webpack --config build/webpack.prod.js"
},
}
--config
后面是指定的配置文件,这个我们待会创建。build
这个指令是用来打包正式上线的,build:test
这个是用来测试打包效果的,用来展示打包量化工具等。这个你也可以用来用作 测试服的打包。就比如有些公司会分内测,外测以及正式服。你就可以不写 build:test
,直接用 inner
、outer
、formal
代替。
然后就是创建对应的配置文件了。根据上面的路径,在根目录创建 build
文件夹,里面创建 webpack.common.js
、webpack.dev.js
、webpack.prod.js
三个文件。
// build > webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
entry: path.resolve(__dirname, '../src/main.js'),
output: {
filename: 'public/js/bundle.js',
path: path.resolve(__dirname, '../dist'),
clean: true
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
},
{
test: /\.(png|jpg|gif|svg|jpeg)$/,
type: 'asset/resource', // 使用 asset/resource 类型
generator: {
filename: 'public/images/[name].[contenthash:8][ext]', // 输出目录和文件名格式
},
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, '../src'),
vue: 'vue/dist/vue.esm-bundler.js',
},
extensions: ['.js', '.vue', '.json']
}
}
webpack.common.js
中的配置和 webpack.config.js
中的大体一致,移除了 devtool
和 devServer
的配置,其他的也只是写小修改,例如 output
的时候输出位置以及添加了@
别名。babel-loader
和 postcss-loader
的配置也提取出去了,这里要在根目录下创建 .babelrc
和 postcss.config.js
。
// . > babelrc
{
"presets": ["@babel/preset-env"]
}
// . > postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
接下来就是 webpack.dev.js
// build > webpack.dev.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common')
module.exports = merge(commonConfig, {
mode: 'development',
devtool: 'source-map',
devServer: {
static: '../dist',
hot: true,
open: true
},
})
dev
配置就是 devtool
以及 devServer
了,然后通过 webpack-merge
将 common
和 dev
合并起来。
// build > webpack.prod.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common')
module.exports = merge(commonConfig, {
mode: 'production'
})
prod
当前来看则是这样,相对简单。
添加 sass 预处理器
npm install sass sass-loader -D
然后在 webpack.common.js
的 module.rules
中添加 scss
的规则
// build > webpack.common.js
moudle.exports = {
// ...
module: {
rules: {
//...
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
}
}
}
}
然后 .vue
中就能使用 lang="scss"
以及引入 .scss
文件。
拆分 chunk
拆分之前,我们先安装一下 webpack-bundle-analyzer
,它是一个用于分析 Webpack 打包结果的工具。它可以生成一个可视化的报告,帮助开发者理解各个模块的大小以及它们在最终打包文件中的占比。
npm i -D webpack-bundle-analyzer
然后我们在 webpack.prod.js
中配置一下,这里要用到我们前面做的环境区分。我们只在 build:test
的时候才使用 webpack-bundle-analyzer
。
// build > webpack.prod.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const ENV = process.env.NODE_ENV
console.log('运行环境:' + ENV)
function getPlugins() {
const plugins = []
if (ENV === 'test') {
plugins.push(new BundleAnalyzerPlugin({
analyzerMode: 'static', // 生成静态报告
openAnalyzer: true,
}))
}
return plugins
}
module.exports = merge(commonConfig, {
mode: 'production',
plugins: getPlugins()
})
这时候我们执行 npm run build:test
就可以看到打包出的 bundle.js
大小以及分布图。
接下来就是对 chunk 拆分了
通过 optimization.splitChunks
来进行拆分
// build > webpack.prod.js
module.exports = merge(commonConfig, {
mode: 'production',
plugins: getPlugins(),
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 1,
chunks: 'all',
minChunks: 1,
minSize: 0
},
common: {
name: 'common',
minChunks: 2,
priority: 0,
chunks: 'all',
reuseExistingChunk: true,
minChunks: 2,
minSize: 0
}
}
}
},
})
配置完这个后,如果你直接执行 build
测试的话,会报错。因为这里你 output
的配置中的 filename
是定死的文件名。这里需要修改一下 webpack.common.js
中的配置。
// build > webpack.common.js
module.exports = {
entry: path.resolve(__dirname, '../src/main.js'),
output: {
// 修改部分
filename: 'public/js/[name].[contenthash:8].js',
path: path.resolve(__dirname, '../dist'),
clean: true
},
// ... 其他配置
}
这时在进行 npm run build:test
,就可以发现会打包出两个 js 文件
。同时可以看到解析图也发生了改变。
对于某个路由的拆分
路由的拆分比较简单,只需要动态引入组件的时候使用 webpackChunkName
。
首先实现路由环境。
npm i vue-router
然后在 src
下创建 routers
和 views
文件夹。一个用来存路由配置,一个用来存具体的路由组件。
// src > routers > index.js
import { createWebHashHistory, createRouter } from "vue-router"
const Home = () => import(/* webpackChunkName: "home" */ '@/views/home/index.vue')
const Detail = () => import(/* webpackChunkName: "detail" */ '@/views/detail/index.vue')
const routes = [
{
path: '/',
component: Home
},
{
path: '/detail',
component: Detail
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
// src > main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from "./routers"
const app = createApp(App)
app.use(router).mount('#app')
<!-- src > views > detail > index.vue -->
<template>
<div class="detail-container">
detail
{{ count }}
</div>
</template>
<script setup>
const count = ref(1)
</script>
<style lang="scss">
.detail-container {
color: yellow;
}
</style>
<!-- src > views > home > index.vue -->
<template>
<div class="main-container">
home
{{ count }}
</div>
</template>
<script setup>
const count = ref(1)
</script>
<style lang="scss">
.main-container {
color: blue;
}
</style>
<!-- src > App.vue -->
<template>
<div class="container">
<p>Vue3 Project</p>
<p class="text">count: {{ count }}</p>
<!-- 新增部分 -->
<RouterView></RouterView>
</div>
</template>
<script setup>
// 新增部分
import { RouterView } from 'vue-router'
import { ref } from 'vue'
const count = ref(1)
</script>
<style lang="scss">
.container {
.text {
color: blue;
}
}
</style>
运行 build:test
会发现,这时候会多出 detail.[base:8]
和 home.[base:8]
。即说明拆分成功了。
提取 css
目前我们打包出来的都是 js
文件,样式也是在 js
文件中的。这里我们需要 mini-css-extract-plugin
库来将 css
样式从 javascrip
中提取出来。
npm install mini-css-extract-plugin -D
配置如下
// build > webpack.common.js
// ... 其他已有引用
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
// ... 其他loader
// 修改部分
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
},
]
},
plugins: [
// ... 其他 plugins
new MiniCssExtractPlugin({
filename: 'public/css/[name].[contenthash:8].css'
}),
]
}
配置完成后打包,就会生成对应的 css 文件。
添加缓存
webpack5
自己提供了 cache
功能
// build > webpack.prod.js
module.exports = {
cache: {
type: 'filesystem'
}
}
打包后在 node_modules
中可以看到 .cache
文件夹。
其他配置
利用 progress-bar-webpack-plugin
库,显示打包进度。
// build > webpack.prod.js
// ... 其他引入
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
function getPlugins() {
const plugins = [
new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`
})
]
if (ENV === 'test') {
// ... test 配置
}
return plugins
}
同时可以关闭自带的显示信息
module.exports = {
// ...
stats: {
// 显示详细信息
all: false,
assets: true, // 显示打包的文件
timings: true, // 显示构建时间
modules: false,
chunks: false,
version: true, // 显示 Webpack 版本
errors: true, // 显示错误
},
}
最终打包的时候可以看到具体进度以及耗时。