| 维度 | v1(原始版) | v2(当前版) |
|---|---|---|
| Webpack | v5.x(通用配置) | v5.107(含 experiments.css / experiments.typescript) |
| Vue | Vue 3(Options API 示例) | Vue 3.4+(<script setup> + Composition API) |
| vue-loader | v15.x | v22+(CSS Modules 自定义注入选项) |
| 模板编译器 | vue-template-compiler | @vue/compiler-sfc(Vue 3 官方推荐) |
| TypeScript | ts-loader | ts-loader / swc-loader / experiments.typescript 三选一 |
| CSS 处理 | css-loader + style-loader | experiments.css(原生)或传统 Loader 链 |
| devServer | webpack-dev-server v4 | webpack-dev-server v5 + webpack-cli 7 |
| 脚手架 | Vue CLI | Vue CLI / create-vue(Vite) / create-webpack-app |
| 流程图 | 无 | Mermaid 构建流水线图 + SFC 编译流程图 |
| 方案对比 | 仅提及 Vite | 三种方案深度对比(决策矩阵) |
传统 Web 开发通常会强调将页面的 HTML、CSS、JavaScript 代码分开用不同文件承载,这种思维本质上是将页面层级的结构、样式、逻辑分离成不同关注点;而现代 MVVM 框架,包括 React、Vue、Angular,则强调在组件内完成所有结构、样式、逻辑定义,从而进一步缩小关注点粒度,降低开发者心智负担。
以 Vue 3.4+ 为例,通常可以选择使用 Vue SFC(Single File Component) 形式编写组件代码,并使用 <script setup> 语法糖:
<template>
<div class="hello">
<h3>{{ message }}</h3>
<button @click="increment">count: {{ count }}</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello World')
const count = ref(0)
const increment = () => count.value++
</script>
<style scoped>
h3 {
margin: 40px 0 0;
color: #42b983;
}
</style>v2 更新说明:上述示例已从 v1 的 Options API(
export default { data() {...} })升级为 Vue 3.4+ 推荐的<script setup>+ Composition API 写法。<script setup>是编译时宏,相比普通<script>具有更好的类型推导性能、更少的样板代码,且是当前 Vue 社区的最佳实践。
这种将整个页面拆解成组件的开发方式能够极大降低页面开发的复杂度,提升开发效率,但代价则是需要搭建一套适用的工程化环境,将 MVVM 框架组件转换为能够被普通浏览器兼容、运行的 HTML、CSS、JavaScript 代码。
本文将递进介绍使用 Webpack 5.107 搭建 Vue 3.4+ 应用开发环境的主要方法,包括:
vue-loader v22+ 处理 Vue SFC 文件?html-webpack-plugin、webpack-dev-server v5 运行 Vue 应用?在正式深入配置细节之前,我们先从宏观层面理解搭建 Vue 开发环境的三条主流路线:
| 对比维度 | Vue CLI(Webpack 底层) | 手动 Webpack 配置 | Vite(create-vue) |
|---|---|---|---|
| 底层构建工具 | Webpack 5 | Webpack 5.107 | 开发:esbuild;生产:Rollup |
| 配置难度 | ⭐ 低(交互式选择) | ⭐⭐⭐⭐⭐ 高(全手写) | ⭐ 低(交互式选择) |
| HMR 速度 | 较慢(秒级) | 取决于优化程度 | 极快(毫秒级) |
| 生产构建稳定性 | ✅ 成熟稳定 | ✅ 完全可控 | ✅ 稳定(Rollup) |
| 插件生态丰富度 | ⭐⭐⭐⭐⭐ 最丰富 | ⭐⭐⭐⭐⭐ 最丰富 | ⭐⭐⭐ 快速增长中 |
| 深度定制灵活性 | ⭐⭐⭐ 中等(受限于 CLI 抽象) | ⭐⭐⭐⭐⭐ 最高 | ⭐⭐⭐ 中等(插件 API) |
| 学习价值 | ⭐⭐ 偏黑盒 | ⭐⭐⭐⭐⭐ 最高 | ⭐⭐⭐ 较高 |
| 维护状态 | 默认进入维护模式(Vue 官方推荐转向 Vite) | 持续演进(Webpack 5.107+) | 活跃开发(Vue 官方首推) |
| 适用场景 | 遗留项目迁移、团队已有 CLI 规范 | 需要极致定制、学习原理、monorepo 统一构建 | 新项目首选、原型开发、中小型应用 |
核心结论:虽然 Vite 已成为 Vue 官方推荐的构建工具,但 Webpack 在以下场景仍具有不可替代的价值:
- 大型企业应用的深度构建优化(code splitting 策略、module federation)
- Monorepo 下统一构建工具链的需求
- 需要与公司内部 Webpack 插件生态集成的场景
- 对构建产物有精细控制需求的场景
- 学习前端工程化原理的必经之路
形态上,Vue SFC(Single File Component) 文件(*.vue)是使用类 HTML 语法描述 Vue 组件的自定义文件格式,文件由四种类型的顶层语法块组成:
<template>:用于指定 Vue 组件模板内容,支持类 HTML、Pug 等语法,其内容会被预编译为 JavaScript 渲染函数;<script> / <script setup>:用于定义组件逻辑。Vue 3.4+ 推荐 <script setup> 语法糖,编译时会自动将内容包装为 setup() 函数;也支持传统的 Options API 写法;<style>:用于定义组件样式,通过配置适当 Loader 可实现 Less、Sass、Stylus 等预处理器语法支持;也可通过添加 scoped、module 属性将样式封装在当前组件内;<docs>;Custom Block 通常需要搭配特定工具使用,详情可参考 Custom Blocks | Vue Loader。v2 重要变更:Vue 3 不再使用
vue-template-compiler,而是使用@vue/compiler-sfc作为模板编译器。这是 Vue 3 的架构性变更——编译器与运行时完全解耦。
yarn add -D webpack webpack-cli vue-loader@^22 @vue/compiler-sfc
npm install -D webpack webpack-cli vue-loader@^22 @vue/compiler-sfc// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader'],
},
],
},
plugins: [
// vue-loader v22+ 必须配合此插件使用
new VueLoaderPlugin(),
],
}提示:
vue-loader库同时提供用于处理 SFC 代码转译的 Loader 组件,与用于处理上下文兼容性的 Plugin 组件,两者需要同时配置才能正常运行。这是 vue-loader 的经典"Loader + Plugin"配对模式——Plugin 负责把配置中的其它模块处理规则(如 CSS、预处理器规则)关联应用到 SFC 拆分出的各个语言块上,这是二者必须同时配置的原因。
经过 vue-loader 处理后,SFC 各个模块会被等价转译为普通 JavaScript 模块。下面用 Mermaid 图详细展示这一过程:
关键要点解析:
<template> 编译路径:vue-loader 将 <template> 内容提取后,交给 @vue/compiler-sfc 编译为 render 函数。这个 render 函数返回 Virtual DOM 的创建指令(即 h() / createElement() 调用),而非 HTML 字符串。<script setup> 编译路径:<script setup> 是编译时宏,vue-loader + @vue/compiler-sfc 会将其内容自动包装为 setup() 函数,并在编译阶段自动处理 ref、reactive 等响应式 API 的导入,无需手动 import。<style> 编译路径:CSS 内容会根据 lang 属性分发给对应的预处理 Loader 链,最终通过 css-loader 解析 @import / url(),再由 style-loader 注入到页面中(开发模式)或由 MiniCssExtractPlugin 提取为独立文件(生产模式)。注意,上例 Webpack 配置还无法处理 CSS 代码,若此时添加 <style> 模块将导致报错。为此需要添加处理 CSS 的规则:
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'development',
module: {
rules: [
{ test: /\.vue$/, use: ['vue-loader'] },
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [new VueLoaderPlugin()],
}v2 新增:Webpack 5.107+ 提供了实验性的原生 CSS 支持,无需 css-loader 和 style-loader:
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'development',
experiments: {
// 启用原生 CSS 支持(Webpack 5.107+ 实验特性)
css: true,
},
module: {
rules: [
{ test: /\.vue$/, use: ['vue-loader'] },
],
},
plugins: [new VueLoaderPlugin()],
}注意:
experiments.css目前仍是实验性功能,生产环境建议继续使用方案 A 的传统 Loader 链以确保稳定性。启用后,Webpack 会原生解析 CSS 的@import和url()语法,并将 CSS 作为独立的 asset module 类型处理。
上例接入的 vue-loader 使得 Webpack 能够正确理解、翻译 Vue SFC 文件的内容,接下来我们还需要让页面真正运行起来,这里会用到:
html-webpack-plugin 自动生成 HTML 页面;html-webpack-plugin 是一款根据编译产物自动生成 HTML 文件的 Webpack 插件,借助这一插件我们无需手动维护产物数量、路径、hash 值更新等问题。
yarn add -D html-webpack-plugin// webpack.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
clean: true,
},
module: {
rules: [{ test: /\.vue$/, use: ['vue-loader'] }],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
title: 'Vue + Webpack App',
templateContent: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webpack App</title>
</head>
<body>
<div id="app" />
</body>
</html>
`,
}),
],
resolve: {
extensions: ['.js', '.vue', '.json'],
},
}v2 重要变更:webpack-dev-server v5 配置方式有显著变化,且需要配合 webpack-cli v7 使用。
yarn add -D webpack-dev-server@^5 webpack-cli@^7// webpack.config.js(追加 devServer 配置)
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
clean: true,
},
devServer: {
// webpack-dev-server v5 配置
port: 3000,
hot: true,
open: true,
// 开发服务器静态文件目录
static: {
directory: path.join(__dirname, 'public'),
},
// 历史模式 SPA fallback
historyApiFallback: true,
// 客户端日志级别
client: {
logging: 'info',
overlay: {
errors: true,
warnings: false,
},
},
},
module: {
rules: [
{ test: /\.vue$/, use: ['vue-loader'] },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
templateContent: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack App</title>
</head>
<body>
<div id="app" />
</body>
</html>
`,
}),
],
resolve: {
extensions: ['.js', '.vue', '.json'],
},
}v5 主要配置变化说明:
| 配置项 | v4 用法 | v5 用法 | 说明 |
|---|---|---|---|
contentBase | contentBase: './public' | 已移除,改用 static.directory | 静态文件目录配置重构 |
overlay | overlay: true | 移入 client.overlay | 错误遮罩层配置位置变更 |
publicPath | 顶层配置 | 移入 client 或 devMiddleware | 公共路径配置重构 |
historyApiFallback | 顶层布尔值 | 顶层对象或布尔值 | 支持更精细的 rewrite 规则 |
| 启动命令 | npx webpack serve | npx webpack serve(需 webpack-cli 7) | 命令不变,但依赖版本要求更高 |
之后,运行 npx webpack serve 命令,即可自动打开带热更新功能的开发服务器页面。
不要忘记创建 Vue 应用的入口文件:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')到这里,我们还是在用原生 JavaScript、CSS 编写 Vue 组件代码,在现代 Web 开发中,我们通常还会搭配其它工程化工具力求提升开发效率、质量。好消息是经过精妙的设计,vue-loader 能够复合使用其它 Webpack Loader 的能力处理各个模块内容,包括:
swc-loader、ts-loader 等处理 SFC 的 <script> / <script setup> 模块;less-loader、sass-loader 等处理 <style> 模块;pug-plain-loader 等处理 <template> 模块。为了达到这种效果,用法上我们需要为每种模块配置相应的 Webpack 处理规则,并正确设置模块的 lang 属性值。
npm install -D typescript ts-loadermodule.exports = {
module: {
rules: [
{ test: /\.vue$/, use: ['vue-loader'] },
{
test: /\.ts$/,
use: ['ts-loader'],
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.ts', '.js', '.vue', '.json'],
},
}<script setup lang="ts">
import { ref } from 'vue'
interface MessageProps {
text: string
count: number
}
const message = ref<string>('Hello World')
const count = ref<number>(0)
const props = defineProps<MessageProps>()
</script>v2 新增:swc 是基于 Rust 编写的 JavaScript/TypeScript 编译器,比 ts-loader 快 20 倍以上,是当前主流的高性能替代方案。
npm install -D swc-loader @swc/coremodule.exports = {
module: {
rules: [
{ test: /\.vue$/, use: ['vue-loader'] },
{
test: /\.(tsx?|jsx?)$/,
use: {
loader: 'swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
target: 'es2020',
},
},
},
exclude: /node_modules/,
},
],
},
}v2 新增:Webpack 5.107+ 内置了实验性的 TypeScript 支持,无需额外安装 Loader:
module.exports = {
experiments: {
// Webpack 5.107+ 原生 TypeScript 支持
typescript: {
// 配置选项(可选)
configFile: path.resolve(__dirname, 'tsconfig.json'),
},
},
module: {
rules: [
{ test: /\.vue$/, use: ['vue-loader'] },
],
},
}三种 TS 方案对比:
方案 编译速度 类型检查 成熟度 推荐场景 ts-loader 慢(tsc 基准) ✅ 完整 ⭐⭐⭐⭐⭐ 需要完整类型检查的项目 swc-loader 极快(20x+) ⚠️ 仅擦除类型 ⭐⭐⭐⭐ 追求构建速度的首选 experiments.ts 快(内置) ⚠️ 仅擦除类型 ⭐⭐(实验中) 尝试新特性的前沿项目
以 Less 为例,首先安装依赖:
yarn add -D less less-loader css-loader style-loader其次,修改 Webpack 配置,添加 Less 文件相关处理规则:
module.exports = {
module: {
rules: [
{ test: /\.vue$/, use: ['vue-loader'] },
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader'],
},
],
},
plugins: [new VueLoaderPlugin()],
}之后,设置 <style> 标签的 lang="less":
<style lang="less" scoped>
h3 {
margin: 40px 0 0;
color: #42b983;
span {
font-weight: normal;
}
}
</style>v2 补充:
scoped属性会为当前组件的样式添加data-v-xxxxx哈希属性选择器,实现样式隔离。此外还可以使用module属性启用 CSS Modules:
<!-- CSS Modules 模式 -->
<style lang="less" module>
.title {
color: #42b983;
& .subtitle {
font-weight: normal;
}
}
</style>
<script setup>
import { useCssModule } from 'vue'
// 默认样式模块
const style = useCssModule()
</script>
<template>
<h3 :class="style.title">{{ message }}</h3>
</template><template> 的处理规则会稍微不同,因为绝大部分 Webpack 模板类 Loader 都会返回一个模板函数,而不是编译好的 HTML 片段,这与 Vue SFC 将 <template> 编译为 render 函数的规则相冲突,此时通常需要使用一个返回原始的 HTML 字符串的 loader,例如使用 pug-plain-loader,而不是 pug-loader。
yarn add -D pug pug-plain-loadermodule.exports = {
module: {
rules: [
{ test: /\.pug$/, use: ['pug-plain-loader'] },
{ test: /\.vue$/, use: ['vue-loader'] },
],
},
plugins: [new VueLoaderPlugin()],
}<template lang="pug">
div.hello
h3 {{ message }}
button(@click="increment") count: {{ count }}
</template>// webpack.config.js — 完整版
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
mode: 'production',
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash:8].js',
clean: true,
},
resolve: {
extensions: ['.js', '.ts', '.vue', '.json', '.less'],
},
module: {
rules: [
// Vue SFC 处理
{
test: /\.vue$/,
use: ['vue-loader'],
},
// TypeScript(swc-loader 高性能方案)
{
test: /\.ts$/,
use: {
loader: 'swc-loader',
options: {
jsc: {
parser: { syntax: 'typescript', tsx: false },
target: 'es2020',
},
},
},
exclude: /node_modules/,
},
// Less 样式处理(生产模式提取为独立文件)
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true,
},
},
},
],
},
// Pug 模板处理
{
test: /\.pug$/,
use: ['pug-plain-loader'],
},
// 静态资源处理(Webpack 5 Asset Modules)
{
test: /\.(png|jpe?g|gif|svg|webp)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // 8KB 以下转为 base64 inline
},
},
generator: {
filename: 'images/[name].[contenthash:8][ext]',
},
},
],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
title: 'Vue 3 + Webpack 5 App',
template: './public/index.html',
}),
new MiniCssExtractPlugin({
filename: 'styles/[name].[contenthash:8].css',
}),
],
// 优化配置
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all',
},
},
},
},
}通常,Vue.js 程序会被构建为一套纯客户端运行的 SPA(Single Page Application) 应用,相比于传统的服务端渲染技术,SPA 已经能解决许多前后端协作的开发效率、性能问题,但却很自然地引入一些新的问题:
SSR (Server Side Render) 正是为了解决这些问题而出现的技术。本质上,SSR 是一种在服务端将组件渲染为 HTML 字符串并发送到浏览器,最后在浏览器上将这些 HTML 片段 "激活(Hydrate)" 为可交互应用的技术。
在 Vue 场景下,通常可以选择 Nuxt、Quasar、@vue/server-renderer 等方案实现 SSR。
接下来,我们使用 Webpack 5.107、Vue 3.4+、Express、@vue/server-renderer 搭建一套完备的 Vue SSR 应用。
├─ package.json
├─ server.js # Express 服务端入口
├─ src/
│ ├─ App.vue # 根组件
│ ├─ main.js # 客户端入口
│ └─ entry-server.js # 服务端入口
├─ webpack.base.js # 公共 Webpack 配置
├─ webpack.client.js # 客户端构建配置
└─ webpack.server.js # 服务端构建配置需要为客户端、服务端环境分别准备项目 Entry 文件:
src/main.js(客户端入口):
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')src/entry-server.js(服务端入口):
import { createSSRApp } from 'vue'
import App from './App.vue'
export default function () {
const app = createSSRApp(App)
return { app }
}关键区别:客户端使用
createApp并立即mount;服务端使用createSSRApp并返回工厂函数(不执行 mount)。createSSRApp会禁用客户端特有的激活逻辑(如beforeMount/mounted中对window/document的直接访问),使组件在 Node.js 环境安全运行。
// webpack.base.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'production',
module: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader'],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: 'swc-loader',
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader'],
},
],
},
plugins: [new VueLoaderPlugin()],
resolve: {
extensions: ['.js', '.vue', '.json'],
},
}// webpack.client.js
const path = require('path')
const { merge } = require('webpack-merge') // 或 webpack-merge v6+
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
const base = require('./webpack.base')
module.exports = merge(base, {
entry: {
client: path.join(__dirname, './src/main.js'),
},
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/',
filename: '[name].[contenthash:8].js',
},
plugins: [
new WebpackManifestPlugin({ fileName: 'manifest-client.json' }),
new HtmlWebpackPlugin({
templateContent: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue SSR Example</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
`,
}),
],
})// webpack.server.js
const path = require('path')
const { merge } = require('webpack-merge')
const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
const base = require('./webpack.base')
module.exports = merge(base, {
entry: {
server: path.join(__dirname, './src/entry-server.js'),
},
target: 'node',
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].js',
libraryTarget: 'commonjs2',
clean: true,
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 服务端必须使用 vue-style-loader(而非 style-loader)
// 因为 vue-style-loader 能在 SSR 模式下收集 CSS 字符串
'vue-style-loader',
{
loader: 'css-loader',
options: {
esModule: false, // SSR 模式下必须关闭 ES Module 化
},
},
],
},
],
},
plugins: [
new WebpackManifestPlugin({ fileName: 'manifest-server.json' }),
],
})为什么服务端要用
vue-style-loader?
style-loader在浏览器环境下通过动态插入<style>标签来注入样式,但在 Node.js 环境中没有 DOM,无法工作。vue-style-loader是专为 SSR 设计的变体,它在服务端渲染时会将 CSS 收集为字符串(配合@vue/server-renderer使用时可自动收集并注入到 HTML 中),方便后续做样式注入或 CSS 外链处理。
// server.js
const express = require('express')
const path = require('path')
const { renderToString } = require('@vue/server-renderer')
const clientManifest = require('./dist/manifest-client.json')
const serverManifest = require('./dist/manifest-server.json')
const serverBundle = path.join(
__dirname,
'./dist',
serverManifest['server.js']
)
const createApp = require(serverBundle).default
const server = express()
server.use(express.static('./dist'))
server.get('/', async (req, res) => {
const { app } = createApp()
try {
const html = await renderToString(app)
const clientBundle = clientManifest['client.js']
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Vue SSR Example</title>
</head>
<body>
<!-- 服务端渲染的 HTML -->
<div id="app">${html}</div>
<!-- 客户端 Hydrate 脚本 -->
<script src="${clientBundle}"></script>
</body>
</html>
`)
} catch (err) {
console.error(err)
res.status(500).send('Internal Server Error')
}
})
server.listen(3000, () => {
console.log('SSR server running at http://localhost:3000')
})yarn add express @vue/server-renderer
yarn add -D webpack-merge webpack-manifest-plugin vue-style-loader
npx webpack --config ./webpack.client.js
npx webpack --config ./webpack.server.js
node server.js<template>
<div :class="['main', cls]">
<h3>{{ message }}</h3>
<button @click="toggle">Toggle</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const isActive = ref(false)
const message = ref('Hello World')
const cls = computed(() => (isActive.value ? 'activate' : 'deactivate'))
function toggle() {
isActive.value = !isActive.value
}
</script>
<style>
h3 {
color: #42b983;
}
.main {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
padding: 20px 12px;
transition: background 0.3s linear;
}
.activate {
background: #000;
color: #fff;
}
.deactivate {
background: #fff;
}
</style>提示:
- 这里选择
@vue/server-renderer仅出于演示目的,实际生产环境建议使用更成熟、完备的框架方案,如 Nuxt 3、Quasar。- SSR 不是银弹,带来了更高的架构复杂度和服务端负载压力。对于 SEO 需求但不要求实时动态内容的场景(如官网、文档站),可以考虑 SSG(Static Site Generation) 方案。
SSR 不是银弹,依然带来了不少新问题:
因此,对于内容相对静态的应用(如公司官网、营销活动页、文档站等),可以使用 SSG(Static Site Generation) 方式,在编译构建阶段提前生成各页面的静态 HTML 代码,既能满足 SEO 需求,又能尽可能降低架构复杂度。
| 方案 | 底层 | 适用场景 | 备注 |
|---|---|---|---|
| Nuxt 3 (nitro) | Rollup / Webpack | 全栈 Vue 应用 | 最推荐,内置 SSG/SSR/SPA 模式切换 |
Vite SSG (vite-plugin-ssg) | Vite (esbuild) | 轻量级静态站点 | 基于 Vite,构建速度极快 |
| prerender-spa-plugin | Puppeteer | 遗留 SPA 项目预渲染 | 不兼容 Webpack 5,不推荐新项目使用 |
| 自定义脚本 | @vue/server-renderer | 完全自定义需求 | 参考 SSR 章节,在构建时遍历路由批量渲染 |
v2 说明:在 Webpack 4 时代常用的
prerender-spa-plugin由于依赖 Puppeteer 且内部机制与 Webpack 5 的 chunk 图不兼容,目前无法正常工作。对于 Webpack 5 项目,建议迁移到 Nuxt 3 或自行基于@vue/server-renderer实现 SSG 脚本。
综上,手动配置 Vue + Webpack 开发环境的过程是非常复杂且繁琐的。社区已经将大量重复、被验证有效的模式封装成开箱即用的脚手架工具:
npx @vue/cli create my-appVue CLI 是基于 Webpack 搭建的 Vue 项目脚手架工具,提供多媒体资源处理、SFC 文件处理、dev-server、HMR、自动化测试、ESLint、Babel 等功能。
重要提示:Vue CLI 已于 2024 年底正式进入维护模式(Maintenance Mode),Vue 官方明确推荐新项目使用 create-vue(Vite) 作为脚手架。但现有的大量企业项目仍然运行在 Vue CLI + Webpack 技术栈上,理解和掌握其底层 Webpack 配置仍有重要价值。
通过 vue.config.js 可以修改底层 Webpack 配置:
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => {
// 自定义 vue-loader 选项
return options
})
},
configureWebpack: {
// 直接合并 Webpack 配置
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
},
}npm create vue@latest my-app这是 Vue 官方目前主推的脚手架工具,基于 Vite 构建,提供:
v2 新增:Webpack 官方提供了自己的脚手架工具,可以快速创建包含 Vue 支持的 Webpack 项目:
npm create webpack-app@latest my-app -- --template vue这会生成一个开箱即用的 Webpack 5 + Vue 3 项目,包含:
生成的项目结构清晰,适合作为手动定制的基础起点。
本文详细介绍了如何使用 Webpack 5.107 开发 Vue 3.4+ 应用,涵盖以下核心内容:
| 主题 | 关键技术点 |
|---|---|
| SFC 编译 | vue-loader v22+ + @vue/compiler-sfc + VueLoaderPlugin |
| 开发服务器 | webpack-dev-server v5 + webpack-cli 7 + HMR |
| TypeScript | ts-loader / swc-loader / experiments.typescript 三选一 |
| CSS 处理 | 传统 Loader 链 或 experiments.css(实验特性) |
| 预处理器集成 | Less / Sass / Pug 通过 lang 属性 + 对应 Loader |
| SSR | 双 Entry + @vue/server-renderer + Express + vue-style-loader |
| 方案选择 | Vue CLI / 手动 Webpack / Vite 三者各有适用场景 |
核心观点:
vue-loader 的 VueLoaderPlugin 为什么是必须的?如果去掉它会发生什么?尝试从源码角度分析其作用。vue-style-loader 替代 style-loader?两者的实现机制有何不同?