
文章目录
- 一、痛点场景:前端开发的"至暗时刻"
-
- [1.1 你是否经历过这样的噩梦](#1.1 你是否经历过这样的噩梦)
- [1.2 传统开发模式的四大痛点](#1.2 传统开发模式的四大痛点)
- [1.3 生活案例:搬家的启示](#1.3 生活案例:搬家的启示)
- [二、解决方案:Webpack + Vuejs 黄金组合](#二、解决方案:Webpack + Vuejs 黄金组合)
-
- [2.1 什么是 Webpack + Vuejs](#2.1 什么是 Webpack + Vuejs)
- [2.2 专业解释](#2.2 专业解释)
- [2.3 大白话解释](#2.3 大白话解释)
- [三、是什么:揭开 Webpack + Vuejs 的神秘面纱](#三、是什么:揭开 Webpack + Vuejs 的神秘面纱)
-
- [3.1 Webpack 的核心概念](#3.1 Webpack 的核心概念)
- [3.2 Vue 单文件组件](#3.2 Vue 单文件组件)
- [3.3 生活案例:乐高积木](#3.3 生活案例:乐高积木)
- 四、为什么用:六大核心优势
-
- [4.1 优势一:模块化开发,代码可维护性大幅提升](#4.1 优势一:模块化开发,代码可维护性大幅提升)
- [4.2 优势二:热模块替换(HMR),开发体验极佳](#4.2 优势二:热模块替换(HMR),开发体验极佳)
- [4.3 优势三:代码分割,按需加载](#4.3 优势三:代码分割,按需加载)
- [4.4 优势四:资源一体化,万物皆模块](#4.4 优势四:资源一体化,万物皆模块)
- [4.5 优势五:生态丰富,插件体系完善](#4.5 优势五:生态丰富,插件体系完善)
- [4.6 优势六:企业级支持,久经考验](#4.6 优势六:企业级支持,久经考验)
- 五、演进之路:从手工拼接到工程化
-
- [5.1 第一阶段:石器时代(2005年以前)](#5.1 第一阶段:石器时代(2005年以前))
- [5.2 第二阶段:青铜时代(2009-2012)](#5.2 第二阶段:青铜时代(2009-2012))
- [5.3 第三阶段:白银时代(2012-2015)](#5.3 第三阶段:白银时代(2012-2015))
- [5.4 第四阶段:黄金时代(2016-2020)](#5.4 第四阶段:黄金时代(2016-2020))
- [5.5 第五阶段:白金时代(2020至今)](#5.5 第五阶段:白金时代(2020至今))
- [5.6 演进时间线总结](#5.6 演进时间线总结)
- [六、核心原理:Webpack 是怎么工作的](#六、核心原理:Webpack 是怎么工作的)
-
- [6.1 Webpack 的构建流程](#6.1 Webpack 的构建流程)
- [6.2 依赖图的构建](#6.2 依赖图的构建)
- [6.3 Loader 的工作原理](#6.3 Loader 的工作原理)
- [6.4 Plugin 的工作原理](#6.4 Plugin 的工作原理)
- [6.5 大白话解释](#6.5 大白话解释)
- 七、快速上手:从零搭建项目
-
- [7.1 环境准备](#7.1 环境准备)
- [7.2 使用 Vue CLI 创建项目](#7.2 使用 Vue CLI 创建项目)
- [7.3 项目结构解析](#7.3 项目结构解析)
- [7.4 核心文件解析](#7.4 核心文件解析)
- [7.5 启动开发服务器](#7.5 启动开发服务器)
- [7.6 打包部署](#7.6 打包部署)
- [八、企业项目实战:vue.config.js 深度配置](#八、企业项目实战:vue.config.js 深度配置)
-
- [8.1 vue.config.js 配置体系](#8.1 vue.config.js 配置体系)
- [8.2 基础配置](#8.2 基础配置)
- [8.3 开发服务器与代理配置](#8.3 开发服务器与代理配置)
- [8.4 环境变量管理](#8.4 环境变量管理)
- [8.5 路径别名配置](#8.5 路径别名配置)
- [8.6 代码分割与分包优化](#8.6 代码分割与分包优化)
- [8.7 CDN 外部扩展](#8.7 CDN 外部扩展)
- [8.8 打包体积分析](#8.8 打包体积分析)
- 九、性能优化:让项目飞起来
-
- [9.1 性能优化全景](#9.1 性能优化全景)
- [9.2 构建速度优化](#9.2 构建速度优化)
- [9.3 打包体积优化](#9.3 打包体积优化)
- [9.4 加载性能优化](#9.4 加载性能优化)
- [十、竞品对比:Webpack vs Vite vs Rollup vs Parcel](#十、竞品对比:Webpack vs Vite vs Rollup vs Parcel)
-
- [10.1 四大构建工具概览](#10.1 四大构建工具概览)
- [10.2 Webpack 的优劣势](#10.2 Webpack 的优劣势)
- [10.3 Vite 的优劣势](#10.3 Vite 的优劣势)
- [10.4 Rollup 的优劣势](#10.4 Rollup 的优劣势)
- [10.5 Parcel 的优劣势](#10.5 Parcel 的优劣势)
- [10.6 如何选择](#10.6 如何选择)
- 十一、常用场景教学
-
- [11.1 场景一:多环境 API 地址管理](#11.1 场景一:多环境 API 地址管理)
- [11.2 场景二:路由懒加载与权限控制](#11.2 场景二:路由懒加载与权限控制)
- [11.3 场景三:全局组件自动注册](#11.3 场景三:全局组件自动注册)
- [11.4 场景四:Mock 数据开发](#11.4 场景四:Mock 数据开发)
- 十二、面试官高频面试题
-
- [面试题一:说一下 Webpack 的构建流程](#面试题一:说一下 Webpack 的构建流程)
- [面试题二:Loader 和 Plugin 的区别是什么](#面试题二:Loader 和 Plugin 的区别是什么)
- [面试题三:说一下 Webpack 热更新(HMR)的原理](#面试题三:说一下 Webpack 热更新(HMR)的原理)
- [面试题四:什么是 Tree Shaking?如何生效?](#面试题四:什么是 Tree Shaking?如何生效?)
- [面试题五:代码分割(Code Splitting)有哪些方式?](#面试题五:代码分割(Code Splitting)有哪些方式?)
- [面试题六:Vue CLI 中 configureWebpack 和 chainWebpack 的区别?](#面试题六:Vue CLI 中 configureWebpack 和 chainWebpack 的区别?)
- [面试题七:Webpack 5 有哪些新特性?](#面试题七:Webpack 5 有哪些新特性?)
- [面试题八:如何优化 Webpack 的构建速度?](#面试题八:如何优化 Webpack 的构建速度?)
- 十三、总结
一、痛点场景:前端开发的"至暗时刻"
1.1 你是否经历过这样的噩梦
作为一名前端开发者,你一定经历过这样的场景:项目里有几十个 JavaScript 文件,每个页面都要手动写一堆 <script> 标签,而且顺序还不能错------jQuery 必须在 bootstrap 之前加载,工具函数必须在业务代码之前引入,一旦顺序搞错,控制台立刻报红 Uncaught ReferenceError: $ is not defined。
更可怕的是,当项目规模达到上百个文件时,依赖关系变成了一团乱麻。你改了一个工具函数,根本不知道哪些页面引用了它,只能全项目搜索,然后逐个检查。部署的时候,要手动压缩、合并、重命名,一套流程走下来,半个小时就没了。

1.2 传统开发模式的四大痛点
痛点一:手动管理依赖,效率极低。 在没有构建工具的时代,前端项目的依赖管理完全靠人工。每个 HTML 文件里堆满了 <script> 和 <link> 标签,文件加载顺序必须严格按照依赖关系排列。新人接手项目时,光是理清这些依赖关系就要花好几天。
痛点二:代码无法模块化,维护困难。 原生 JavaScript 长期没有模块系统,所有代码都暴露在全局作用域下。变量名冲突、函数覆盖是家常便饭。为了避免冲突,大家只能用命名空间、立即执行函数等奇技淫巧,但这只是治标不治本。
痛点三:部署流程繁琐,容易出错。 上线前需要手动压缩 JS、CSS,手动合并文件,手动替换引用路径。任何一步出错,线上就会出问题。而且这个过程完全不可复现,换个人操作可能结果就不一样。
痛点四:开发体验差,没有热更新。 改一行代码,要手动刷新浏览器,然后重新操作到刚才的页面状态。如果是一个多步骤的表单页面,每次改完代码都要重新填一遍表单,开发效率极其低下。
1.3 生活案例:搬家的启示
想象一下你要搬家:没有搬家公司的时候,你得自己找纸箱、自己打包、自己找车、自己搬上搬下,一趟跑不完还要跑第二趟,中间还可能把东西弄丢或者打碎。这就像传统前端开发,所有事情都要自己手动处理。
而 Webpack 就像一家专业的搬家公司:你只需要告诉它哪些东西要搬(入口文件),它会自动帮你打包、分类、运输,最后整齐地摆到新家(输出目录)。你不需要关心中间过程,只需要享受结果。
二、解决方案:Webpack + Vuejs 黄金组合
2.1 什么是 Webpack + Vuejs
Webpack 是一个现代 JavaScript 应用程序的静态模块打包器。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
Vue.js 是一套用于构建用户界面的渐进式 JavaScript 框架。与其他大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
当 Webpack 遇上 Vue.js,就产生了化学反应:Vue 负责视图层的组件化开发,Webpack 负责模块打包和工程化构建。两者结合,形成了一套完整的前端工程化解决方案。

2.2 专业解释
从专业角度来看,Webpack + Vuejs 的组合本质上是"声明式组件开发"与"模块化构建系统"的完美结合:
- Vue.js 提供了组件化开发范式,让开发者可以用
.vue单文件组件的方式,把模板、逻辑、样式封装在一个文件中,实现高内聚低耦合。 - Webpack 提供了模块打包能力,通过
vue-loader将.vue文件转译为浏览器可识别的 JavaScript、CSS 和 HTML,同时处理各种资源依赖。 - Vue CLI 则是基于 Webpack 的脚手架工具,把 Webpack 的复杂配置封装起来,让开发者可以零配置启动项目,同时保留了深度定制的能力。
2.3 大白话解释
说人话就是:以前你写网页,要自己管所有 JS 文件的加载顺序,要自己压缩代码,要自己处理兼容问题。现在有了 Webpack + Vuejs,你只需要专心写业务代码,剩下的脏活累活全部交给工具链自动处理。
就像你开餐厅,以前既要买菜、又要切菜、还要炒菜、还要端盘子、还要收银,一个人忙不过来。现在你雇了一个后厨团队(Webpack),你只需要负责设计菜品(写 Vue 组件),后厨自动帮你把食材处理好、烹饪好、装盘好,你直接端给客人就行。
三、是什么:揭开 Webpack + Vuejs 的神秘面纱
3.1 Webpack 的核心概念
要理解 Webpack,必须掌握它的五大核心概念:
Entry(入口):指示 Webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,Webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
javascript
// webpack.config.js
module.exports = {
entry: './src/main.js'
};
Output(输出):告诉 Webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件。
javascript
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};
Loader(加载器):让 Webpack 能够去处理那些非 JavaScript 文件(Webpack 自身只理解 JavaScript)。Loader 可以将所有类型的文件转换为 Webpack 能够处理的有效模块。
javascript
// webpack.config.js
module.exports = {
module: {
rules: [
{ test: /\.vue$/, use: 'vue-loader' },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
]
}
};
Plugin(插件):用于执行范围更广的任务,包括打包优化、资源管理、注入环境变量等。
javascript
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
};
Mode(模式) :通过选择 development、production 或 none 之中的一个,来设置 Webpack 内置的优化。
3.2 Vue 单文件组件
Vue 的单文件组件(.vue 文件)是 Webpack + Vuejs 组合的核心魅力所在。一个 .vue 文件包含三个部分:
vue
<!-- src/components/HelloWorld.vue -->
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button @click="count++">点击了 {{ count }} 次</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
msg: '欢迎使用 Webpack + Vuejs',
count: 0
};
}
};
</script>
<style scoped>
.hello {
text-align: center;
color: #42b983;
}
</style>
<template>:组件的 HTML 模板,最终会被编译为渲染函数。<script>:组件的 JavaScript 逻辑,包含数据、方法、生命周期等。<style>:组件的样式,scoped属性表示样式只作用于当前组件,不会污染全局。
Webpack 通过 vue-loader 来处理 .vue 文件,把它拆分成三个部分分别处理,最后合并成一个可执行的 JavaScript 模块。
3.3 生活案例:乐高积木
你可以把 Webpack 想象成一个乐高积木工厂:
- Entry 入口 就是你要拼的那个成品的说明书首页,告诉你从哪块积木开始。
- Module 模块 就是每一块乐高积木,有不同的形状和颜色。
- Loader 加载器 就是加工机器,把特殊形状的积木(比如 Vue 组件、SCSS 样式)转换成标准积木。
- Plugin 插件 就是工厂里的各种辅助设备,比如打包机、贴标机、质检机。
- Output 输出 就是最终拼好的成品,整齐地包装好放在仓库里。
而 Vue.js 就是一套设计精美的乐高积木套装,每块积木都有标准的接口,可以随意组合。Webpack 工厂帮你把这些积木按照图纸组装成最终的作品。
四、为什么用:六大核心优势
4.1 优势一:模块化开发,代码可维护性大幅提升
Webpack 支持 ES Module、CommonJS、AMD 等多种模块规范,你可以放心地使用 import 和 export 来组织代码,不用担心浏览器兼容性问题。每个模块都有独立的作用域,变量名冲突的问题彻底消失。
javascript
// utils/request.js
import axios from 'axios';
export function get(url, params) {
return axios.get(url, { params });
}
export function post(url, data) {
return axios.post(url, data);
}
javascript
// views/UserList.vue
import { get } from '@/utils/request';
export default {
data() {
return { userList: [] };
},
created() {
get('/api/users').then(res => {
this.userList = res.data;
});
}
};
4.2 优势二:热模块替换(HMR),开发体验极佳
Webpack 的热模块替换(Hot Module Replacement)功能,可以在应用程序运行时交换、添加或删除模块,而无需重新加载整个页面。这意味着你改了代码,浏览器立刻更新,而且页面状态不会丢失。
javascript
// webpack.config.js
module.exports = {
devServer: {
hot: true,
open: true,
port: 8080
}
};
在 Vue 项目中,配合 vue-loader,HMR 可以做到修改组件后只更新该组件,不影响其他组件的状态。比如你正在填一个很长的表单,突然发现某个按钮样式不对,改完保存后,表单里填的数据还在,不用重新填写。
4.3 优势三:代码分割,按需加载
Webpack 支持代码分割(Code Splitting),可以把代码拆分成多个 chunk,实现按需加载。这对于大型单页应用来说至关重要------用户不需要一次性下载所有代码,只下载当前页面需要的代码即可。
javascript
// router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
},
{
path: '/user/:id',
name: 'User',
component: () => import('@/views/User.vue')
}
];
export default new VueRouter({
mode: 'history',
routes
});
上面的代码中,每个路由组件都使用了动态 import() 语法,Webpack 会自动把它们拆分成独立的 chunk,只有当用户访问对应路由时才会加载。
4.4 优势四:资源一体化,万物皆模块
在 Webpack 中,一切皆模块。不仅 JavaScript 文件可以被 import,CSS、图片、字体、甚至数据文件都可以作为模块引入。
javascript
// main.js
import Vue from 'vue';
import App from './App.vue';
import './assets/styles/global.css';
import logo from './assets/images/logo.png';
new Vue({
render: h => h(App)
}).$mount('#app');
vue
<!-- 组件中使用图片 -->
<template>
<div>
<img :src="logo" alt="logo" />
</div>
</template>
<script>
import logo from '@/assets/images/logo.png';
export default {
data() {
return { logo };
}
};
</script>
Webpack 会自动处理图片的压缩、Base64 内联、文件重命名等工作,你不需要手动管理图片路径。
4.5 优势五:生态丰富,插件体系完善
Webpack 拥有最庞大的插件生态,几乎你能想到的构建需求,都有对应的插件或 loader。
| 类别 | 常用工具 | 作用 |
|---|---|---|
| JS 转译 | babel-loader | 将 ES6+ 转译为 ES5 |
| Vue 处理 | vue-loader | 处理 .vue 单文件组件 |
| CSS 处理 | css-loader、style-loader、sass-loader | 处理样式文件 |
| 图片处理 | url-loader、file-loader | 处理图片资源 |
| HTML 生成 | html-webpack-plugin | 自动生成 HTML 文件 |
| 代码压缩 | terser-webpack-plugin | 压缩 JavaScript |
| CSS 提取 | mini-css-extract-plugin | 提取 CSS 到独立文件 |
| 体积分析 | webpack-bundle-analyzer | 分析打包体积 |
4.6 优势六:企业级支持,久经考验
Webpack 从 2012 年诞生至今,已经经历了十多年的发展,被无数大型企业项目验证过。Vue CLI 基于 Webpack 构建,提供了标准化的项目结构和配置,团队协作时不需要每个人都去研究 Webpack 配置,开箱即用。
五、演进之路:从手工拼接到工程化
5.1 第一阶段:石器时代(2005年以前)
在 Web 开发的早期,前端就是写 HTML、CSS、JavaScript,所有代码都写在一个文件里。随着页面越来越复杂,代码开始拆分到多个文件,但文件之间的依赖完全靠人工管理。
html
<!-- 传统方式:手动引入所有JS文件 -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="app"></div>
<script src="js/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/utils.js"></script>
<script src="js/user.js"></script>
<script src="js/main.js"></script>
</body>
</html>
这个阶段的特点是:简单直接,但项目一大就乱套。
5.2 第二阶段:青铜时代(2009-2012)
Node.js 的出现带来了 CommonJS 模块规范,前端开始有了模块化的概念。RequireJS、SeaJS 等模块加载器应运而生,实现了浏览器端的异步模块加载。
javascript
// AMD 规范(RequireJS)
define(['jquery', 'utils'], function($, utils) {
function init() {
$('#app').text(utils.formatDate(new Date()));
}
return { init: init };
});
但模块加载器只是解决了"怎么加载"的问题,没有解决"怎么打包"的问题。生产环境还是需要手动合并压缩。
5.3 第三阶段:白银时代(2012-2015)
2012 年,Tobias Koppers 开始开发 Webpack。Webpack 的核心理念是"一切皆模块",不仅 JS 可以模块化,CSS、图片等所有资源都可以模块化。同时 Webpack 支持代码分割,可以按需加载。
2014 年,Vue.js 发布。Vue 的渐进式设计理念和简单易用的 API,迅速获得了开发者的喜爱。早期的 Vue 项目可以直接通过 <script> 标签引入使用,但随着项目复杂度增加,工程化构建成为刚需。
5.4 第四阶段:黄金时代(2016-2020)
2016 年,Vue 2.0 发布,同时 Vue CLI 工具链逐步完善。Vue CLI 基于 Webpack,提供了零配置的项目脚手架,让开发者可以专注于业务代码,而不是构建配置。
bash
# Vue CLI 创建项目
npm install -g @vue/cli
vue create my-project
cd my-project
npm run serve
这个阶段,Webpack + Vuejs 成为了国内前端开发的主流技术栈,无数企业项目基于此构建。Webpack 也从 1.x 迭代到 4.x,性能和功能不断提升。
5.5 第五阶段:白金时代(2020至今)
2020 年,Webpack 5 发布,带来了持久化缓存、模块联邦、更好的 Tree Shaking 等特性。同时,Vite 等新一代构建工具开始崛起,利用浏览器原生 ES Module 实现了极快的冷启动和热更新。
但 Webpack 并没有被淘汰。凭借十年的生态积累和企业级项目的验证,Webpack 仍然是大型项目的首选。Vue CLI 也持续更新,保持与 Webpack 最新版本的兼容。
5.6 演进时间线总结
| 时间 | 里程碑 | 意义 |
|---|---|---|
| 2009 | Node.js 发布 | 前端有了模块规范的基础 |
| 2012 | Webpack 开始开发 | 模块打包器的革命 |
| 2014 | Vue.js 首次发布 | 渐进式框架诞生 |
| 2016 | Vue 2.0 + Vue CLI | Webpack + Vue 组合成熟 |
| 2020 | Webpack 5 发布 | 持久化缓存、模块联邦 |
| 2021 | Vite 2.0 发布 | 新一代构建工具挑战 |
| 至今 | 多元并存 | Webpack 仍是企业项目主力 |
六、核心原理:Webpack 是怎么工作的
6.1 Webpack 的构建流程
Webpack 的构建过程可以分为五个核心阶段:

第一步:初始化参数。 从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
第二步:开始编译。 用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。
第三步:确定入口。 根据配置中的 entry 找出所有入口文件。
第四步:编译模块。 从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
第五步:完成模块编译。 经过第四步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
第六步:输出资源。 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表。
第七步:输出完成。 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
6.2 依赖图的构建
Webpack 的核心是依赖图(Dependency Graph)。从入口文件开始,Webpack 会解析每个文件中的 import 和 require 语句,递归地构建出整个项目的依赖关系图。
main.js
├── App.vue
│ ├── components/Header.vue
│ ├── components/Sidebar.vue
│ └── views/Home.vue
│ └── components/ProductList.vue
├── router/index.js
├── store/index.js
└── assets/styles/global.css
Webpack 会为每个模块分配一个唯一的 ID,然后把所有模块打包到一个或多个 bundle 文件中。浏览器加载 bundle 后,通过模块 ID 来查找和执行对应的代码。
6.3 Loader 的工作原理
Loader 本质上是一个函数,接收源文件内容作为参数,返回转换后的内容。Loader 可以链式调用,从右到左依次执行。
javascript
// 一个简单的自定义 loader
module.exports = function(source) {
// source 是源文件内容
const result = source.replace(/foo/g, 'bar');
return result;
};
以处理 CSS 为例,css-loader 负责解析 CSS 中的 @import 和 url() 语句,style-loader 负责把 CSS 注入到页面的 <style> 标签中。两者配合,就实现了在 JS 中 import './style.css' 的功能。
6.4 Plugin 的工作原理
Plugin 是基于 Webpack 的事件流机制工作的。Webpack 在构建的各个阶段会广播特定的事件,Plugin 可以监听这些事件,在合适的时机执行自定义逻辑。
Webpack 使用了 Tapable 库来实现事件的发布和订阅。Compiler 和 Compilation 是两个核心对象,它们都继承自 Tapable,可以注册和触发事件。
javascript
// 一个简单的自定义 plugin
class MyPlugin {
apply(compiler) {
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('构建完成!');
});
}
}
module.exports = MyPlugin;
6.5 大白话解释
你可以把 Webpack 的构建过程想象成做菜:
- Entry 入口 就是菜谱,告诉你第一道菜做什么。
- 依赖图构建 就是根据菜谱列出所有需要的食材。
- Loader 加载器 就是食材处理,把生肉切成肉片,把蔬菜洗干净。
- Plugin 插件 就是各种厨房电器,比如烤箱、榨汁机,在特定步骤帮你完成特定任务。
- Chunk 代码块 就是把食材组合成一道道菜。
- Output 输出 就是把做好的菜装盘端上桌。
七、快速上手:从零搭建项目
7.1 环境准备
在开始之前,你需要安装 Node.js。推荐使用 NVM(Node Version Manager)来管理 Node 版本,可以方便地切换不同版本。
bash
# 安装 NVM(Mac/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# 安装 Node.js 18.x
nvm install 18
nvm use 18
# 验证安装
node -v
npm -v
7.2 使用 Vue CLI 创建项目
Vue CLI 是官方提供的脚手架工具,基于 Webpack 构建,提供了标准化的项目模板。

bash
# 全局安装 Vue CLI
npm install -g @vue/cli
# 验证安装
vue --version
# 创建项目
vue create my-vue-app
执行 vue create 后,会出现交互式选择界面:
? Please pick a preset:
Default ([Vue 2] babel, eslint)
Default (Vue 3) ([Vue 3] babel, eslint)
> Manually select features
选择 Manually select features 手动配置,然后选择需要的功能:
? Check the features needed for your project:
(*) Babel
(*) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router
(*) Vuex
(*) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
7.3 项目结构解析
创建完成后,项目结构如下:
my-vue-app/
├── public/ # 静态资源目录(不经过webpack处理)
│ ├── favicon.ico
│ └── index.html # HTML模板
├── src/ # 源码目录
│ ├── assets/ # 资源文件(经过webpack处理)
│ │ └── logo.png
│ ├── components/ # 公共组件
│ │ └── HelloWorld.vue
│ ├── router/ # 路由配置
│ │ └── index.js
│ ├── store/ # Vuex状态管理
│ │ └── index.js
│ ├── views/ # 页面组件
│ │ ├── Home.vue
│ │ └── About.vue
│ ├── App.vue # 根组件
│ └── main.js # 入口文件
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── .eslintrc.js # ESLint配置
├── .gitignore
├── babel.config.js # Babel配置
├── package.json
├── README.md
└── vue.config.js # Vue CLI配置(webpack配置覆盖)
7.4 核心文件解析
入口文件 main.js:
javascript
// src/main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './assets/styles/global.css';
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
根组件 App.vue:
vue
<!-- src/App.vue -->
<template>
<div id="app">
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
</nav>
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
color: #2c3e50;
}
nav {
padding: 20px;
background: #f5f5f5;
}
nav a {
margin-right: 20px;
color: #42b983;
text-decoration: none;
}
</style>
路由配置 router/index.js:
javascript
// src/router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '@/views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
}
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
});
export default router;
7.5 启动开发服务器
bash
# 进入项目目录
cd my-vue-app
# 启动开发服务器
npm run serve
启动成功后,终端会输出:
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.1.100:8080/
Note that the development build is not optimized.
To create a production build, run npm run build.
打开浏览器访问 http://localhost:8080/,就可以看到你的 Vue 应用了。修改代码后,浏览器会自动热更新,无需手动刷新。
7.6 打包部署
bash
# 生产环境打包
npm run build
打包完成后,会在 dist 目录生成最终的产物:
dist/
├── css/
│ └── app.abc123.css
├── js/
│ ├── app.def456.js
│ ├── chunk-vendors.ghi789.js
│ └── about.jkl012.js
├── img/
│ └── logo.mno345.png
├── favicon.ico
└── index.html
可以看到,Webpack 自动帮我们做了:
- 文件名加上了 hash,解决浏览器缓存问题
- 第三方库(vue、vue-router、vuex)被单独打包到
chunk-vendors.js - 路由组件
About.vue被单独打包,实现按需加载 - CSS 被提取到独立文件
- 图片被处理并放到
img目录
把 dist 目录部署到 Nginx 或其他静态服务器上即可上线。
八、企业项目实战:vue.config.js 深度配置
8.1 vue.config.js 配置体系
在企业项目中,vue.config.js 是最重要的配置文件。它是 Vue CLI 对 Webpack 配置的封装,让你可以不用直接写复杂的 webpack.config.js,就能完成大部分定制需求。

8.2 基础配置
javascript
// vue.config.js
module.exports = {
// 部署应用包时的基本 URL
publicPath: process.env.NODE_ENV === 'production' ? '/my-app/' : '/',
// 生产环境构建文件的目录
outputDir: 'dist',
// 放置生成的静态资源 (js、css、img、fonts) 的目录
assetsDir: 'static',
// 指定生成的 index.html 的输出路径
indexPath: 'index.html',
// 生成的静态资源在它们的文件名中包含 hash 以便更好的控制缓存
filenameHashing: true,
// 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码
lintOnSave: process.env.NODE_ENV === 'development',
// 生产环境是否生成 sourceMap 文件
productionSourceMap: false
};
8.3 开发服务器与代理配置
在企业开发中,前端和后端通常是分离的,开发时前端请求后端 API 会遇到跨域问题。通过 devServer.proxy 可以轻松解决。
javascript
// vue.config.js
module.exports = {
devServer: {
port: 8080,
open: true,
hot: true,
proxy: {
// 代理 /api 开头的请求
'/api': {
target: 'http://localhost:3000', // 后端服务地址
changeOrigin: true, // 改变请求头中的 origin
pathRewrite: {
'^/api': '' // 重写路径,去掉 /api 前缀
}
},
// 代理上传接口
'/upload': {
target: 'http://localhost:3001',
changeOrigin: true
}
}
}
};
配置后,前端代码中请求 /api/users 会被代理到 http://localhost:3000/users,浏览器不会报跨域错误。
8.4 环境变量管理
企业项目通常有多个环境:开发环境、测试环境、预发布环境、生产环境。通过 .env.* 文件可以管理不同环境的变量。
bash
# .env.development(开发环境)
NODE_ENV=development
VUE_APP_API_BASE_URL=http://dev-api.example.com
VUE_APP_TITLE=开发环境
bash
# .env.production(生产环境)
NODE_ENV=production
VUE_APP_API_BASE_URL=https://api.example.com
VUE_APP_TITLE=生产环境
bash
# .env.test(测试环境,需要配合 --mode test 使用)
NODE_ENV=production
VUE_APP_API_BASE_URL=http://test-api.example.com
VUE_APP_TITLE=测试环境
在代码中使用环境变量:
javascript
// src/utils/request.js
import axios from 'axios';
const service = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL,
timeout: 10000
});
export default service;
在 package.json 中配置不同环境的构建命令:
json
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build:test": "vue-cli-service build --mode test",
"build:prod": "vue-cli-service build --mode production"
}
}
8.5 路径别名配置
在企业项目中,文件层级可能很深,用相对路径引用文件会很麻烦(比如 ../../../../utils/request)。通过配置别名可以简化引用。
javascript
// vue.config.js
const path = require('path');
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
configureWebpack: {
resolve: {
alias: {
'@': resolve('src'),
'@components': resolve('src/components'),
'@views': resolve('src/views'),
'@utils': resolve('src/utils'),
'@api': resolve('src/api'),
'@assets': resolve('src/assets')
}
}
}
};
配置后,在代码中可以这样引用:
javascript
// 以前
import request from '../../../../utils/request';
import UserList from '../../../../components/UserList.vue';
// 现在
import request from '@utils/request';
import UserList from '@components/UserList.vue';
8.6 代码分割与分包优化
大型项目中,第三方依赖体积往往很大。通过 splitChunks 可以把第三方库单独打包,利用浏览器缓存提升加载速度。
javascript
// vue.config.js
module.exports = {
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// Vue 核心库单独打包
vue: {
name: 'chunk-vue',
test: /[\\/]node_modules[\\/](vue|vue-router|vuex)[\\/]/,
priority: 20,
chunks: 'all'
},
// UI 组件库单独打包
elementUI: {
name: 'chunk-element-ui',
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
priority: 20,
chunks: 'all'
},
// 工具库单独打包
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/](lodash|axios|moment|echarts)[\\/]/,
priority: 10,
chunks: 'all'
},
// 公共组件单独打包
commons: {
name: 'chunk-commons',
test: resolve('src/components'),
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
}
}
};
8.7 CDN 外部扩展
对于一些体积较大的第三方库(如 Vue、Element UI、ECharts),可以通过 CDN 引入,不打包进 bundle,减小打包体积。
javascript
// vue.config.js
const isProduction = process.env.NODE_ENV === 'production';
const cdn = {
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
'element-ui': 'ELEMENT'
},
css: [
'https://cdn.jsdelivr.net/npm/element-ui@2.15.14/lib/theme-chalk/index.css'
],
js: [
'https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.min.js',
'https://cdn.jsdelivr.net/npm/vue-router@3.6.5/dist/vue-router.min.js',
'https://cdn.jsdelivr.net/npm/vuex@3.6.2/dist/vuex.min.js',
'https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js',
'https://cdn.jsdelivr.net/npm/element-ui@2.15.14/lib/index.js'
]
};
module.exports = {
configureWebpack: config => {
if (isProduction) {
config.externals = cdn.externals;
}
},
chainWebpack: config => {
config.plugin('html').tap(args => {
if (isProduction) {
args[0].cdn = cdn;
}
return args;
});
}
};
然后在 public/index.html 中注入 CDN 链接:
html
<!DOCTYPE html>
<html>
<head>
<% if (htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<% htmlWebpackPlugin.options.cdn.css.forEach(css => { %>
<link rel="stylesheet" href="<%= css %>">
<% }) %>
<% } %>
</head>
<body>
<div id="app"></div>
<% if (htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<% htmlWebpackPlugin.options.cdn.js.forEach(js => { %>
<script src="<%= js %>"></script>
<% }) %>
<% } %>
</body>
</html>
8.8 打包体积分析
使用 webpack-bundle-analyzer 可以可视化分析打包后的文件体积,找出优化空间。
bash
# 安装
npm install webpack-bundle-analyzer --save-dev
javascript
// vue.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
chainWebpack: config => {
if (process.env.npm_config_report) {
config.plugin('webpack-bundle-analyzer')
.use(BundleAnalyzerPlugin)
.end();
}
}
};
bash
# 执行分析
npm run build --report
执行后会自动打开一个网页,展示每个模块的体积占比,你可以直观地看到哪些库占用空间最大,然后针对性优化。
九、性能优化:让项目飞起来
9.1 性能优化全景
Webpack 性能优化可以分为四大方向:构建速度优化、打包体积优化、开发体验优化、加载性能优化。

9.2 构建速度优化
1. 开启持久化缓存(Webpack 5 特性)
javascript
// vue.config.js
module.exports = {
configureWebpack: {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
}
}
};
开启文件系统缓存后,第二次构建的速度可以提升 90% 以上。Webpack 会把编译结果缓存到磁盘,下次构建时只重新编译发生变化的模块。
2. 缩小 Loader 处理范围
javascript
// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('js')
.include
.add(resolve('src'))
.end()
.exclude
.add(/node_modules/)
.end();
}
};
通过 include 和 exclude 限定 Loader 的处理范围,避免对 node_modules 中的文件做不必要的转译。
3. 多进程构建
bash
# 安装 thread-loader
npm install thread-loader --save-dev
javascript
// vue.config.js
module.exports = {
chainWebpack: config => {
const jsRule = config.module.rule('js');
jsRule.uses.clear();
jsRule
.use('thread-loader')
.loader('thread-loader')
.options({ workers: 3 })
.end()
.use('babel-loader')
.loader('babel-loader')
.end();
}
};
thread-loader 会把耗时的 Loader 放到独立的 worker 进程中执行,充分利用多核 CPU。
9.3 打包体积优化
1. Tree Shaking(摇树优化)
Tree Shaking 可以移除 JavaScript 上下文中未引用的代码(dead code)。Webpack 在生产模式下默认开启。
要让 Tree Shaking 生效,需要确保:
- 使用 ES2015 模块语法(
import/export) - 在
package.json中添加sideEffects字段
json
{
"sideEffects": [
"*.css",
"*.scss",
"*.vue"
]
}
2. 按需引入第三方库
以 Element UI 为例,全量引入会打包所有组件,体积很大。使用 babel-plugin-component 可以实现按需引入。
bash
npm install babel-plugin-component -D
javascript
// babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
]
]
};
javascript
// main.js - 按需引入,只引入需要的组件
import { Button, Input, Table, Dialog } from 'element-ui';
Vue.use(Button);
Vue.use(Input);
Vue.use(Table);
Vue.use(Dialog);
3. 图片优化
javascript
// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.tap(options => {
options.limit = 8192; // 小于8KB的图片转base64
options.name = 'img/[name].[hash:8].[ext]';
return options;
});
}
};
小于 8KB 的图片会被转成 Base64 内联到 CSS 中,减少 HTTP 请求;大于 8KB 的图片会被单独输出,并加上 hash 文件名。
9.4 加载性能优化
1. 预加载(Preload / Prefetch)
html
<!-- preload:当前页面需要的资源,提前加载 -->
<link rel="preload" href="/js/chunk-vendors.js" as="script">
<!-- prefetch:未来页面可能需要的资源,空闲时加载 -->
<link rel="prefetch" href="/js/about.js" as="script">
Vue CLI 默认会为初始路由的资源添加 preload,为其他路由的资源添加 prefetch。可以在 vue.config.js 中控制:
javascript
// vue.config.js
module.exports = {
chainWebpack: config => {
// 移除 prefetch 插件
config.plugins.delete('prefetch');
// 或者修改它的选项
config.plugin('prefetch').tap(options => {
options[0].fileBlacklist = options[0].fileBlacklist || [];
options[0].fileBlacklist.push(/myasyncRoute(.)+?\.js$/);
return options;
});
}
};
2. Gzip 压缩
bash
npm install compression-webpack-plugin --save-dev
javascript
// vue.config.js
const CompressionPlugin = require('compression-webpack-plugin');
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
configureWebpack: config => {
if (isProduction) {
config.plugins.push(
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240, // 大于10KB的文件压缩
minRatio: 0.8
})
);
}
}
};
配合 Nginx 的 gzip_static on 配置,可以直接返回预压缩的 .gz 文件,减少服务器 CPU 消耗。
十、竞品对比:Webpack vs Vite vs Rollup vs Parcel
10.1 四大构建工具概览

| 对比维度 | Webpack | Vite | Rollup | Parcel |
|---|---|---|---|---|
| 定位 | 全能型模块打包器 | 下一代前端构建工具 | 专注库打包的模块打包器 | 零配置Web应用打包器 |
| 发布时间 | 2012年 | 2020年 | 2015年 | 2017年 |
| 开发启动 | 较慢(需全量构建) | 极快(原生ESM按需编译) | 中等 | 快 |
| HMR热更新 | 支持(随项目变大变慢) | 支持(毫秒级,与项目大小无关) | 需插件支持 | 支持 |
| 生产构建 | 成熟稳定,优化完善 | 使用Rollup构建,速度快 | 极快,Tree Shaking最强 | 较快 |
| 配置复杂度 | 高(灵活强大) | 低(开箱即用) | 中 | 零配置 |
| 生态成熟度 | 最成熟(十年沉淀) | 快速增长 | 成熟 | 一般 |
| 插件生态 | 最丰富 | 丰富(兼容Rollup插件) | 丰富 | 较少 |
| 代码分割 | 强大完善 | 支持 | 较弱 | 支持 |
| 适用场景 | 大型企业级应用 | 现代前端新项目 | 组件库/SDK/工具库 | 小型项目/原型验证 |
10.2 Webpack 的优劣势
优势:
- 生态最成熟,插件和 Loader 最丰富,几乎所有需求都有现成方案
- 功能最全面,代码分割、懒加载、模块联邦等高级特性完善
- 久经考验,被无数大型企业项目验证,稳定性高
- Vue CLI、Create React App 等主流脚手架都基于 Webpack
- 社区资源最多,遇到问题容易找到解决方案
劣势:
- 配置复杂,学习曲线陡峭,新手容易被配置劝退
- 开发服务器启动慢,项目越大启动越慢
- HMR 热更新速度随项目规模增长而下降
- 构建产物中包含较多运行时代码,有一定体积开销
10.3 Vite 的优劣势
优势:
- 开发服务器启动极快,利用浏览器原生 ES Module,不需要全量打包
- HMR 热更新毫秒级响应,与项目大小无关
- 配置简单,开箱即用,学习成本低
- 生产构建使用 Rollup,Tree Shaking 效果好
- Vue 官方推荐,Vue 3 项目的默认构建工具
劣势:
- 生态不如 Webpack 成熟,一些特殊需求可能找不到对应插件
- 对 CommonJS 模块的支持需要预构建处理,偶尔会有兼容性问题
- 企业级项目的最佳实践还在积累中
- 部分 Webpack 插件无法直接迁移
10.4 Rollup 的优劣势
优势:
- Tree Shaking 最强,打包产物最干净,体积最小
- 配置简洁,API 设计优雅
- 支持输出多种格式(ES Module、CommonJS、UMD)
- 被 Vite 用作生产构建工具,性能优秀
劣势:
- 主要面向库打包,应用级项目的代码分割等功能较弱
- 开发服务器需要额外插件
- 对 CommonJS 模块的处理需要插件支持
- 不适合复杂的多页面应用
10.5 Parcel 的优劣势
优势:
- 零配置,开箱即用,不需要写配置文件
- 自动检测和处理各种资源类型
- 开发体验好,启动速度快
- 适合快速原型验证和小型项目
劣势:
- 自定义能力弱,复杂需求难以满足
- 生态较小,插件数量有限
- 企业级项目的可配置性不足
- 社区活跃度不如 Webpack 和 Vite
10.6 如何选择
| 场景 | 推荐工具 | 理由 |
|---|---|---|
| 大型企业级 Vue 2 项目 | Webpack + Vue CLI | 生态成熟,稳定性高,团队熟悉 |
| 全新 Vue 3 项目 | Vite | 官方推荐,开发体验极佳 |
| 开发组件库/SDK/工具库 | Rollup | Tree Shaking 强,产物干净 |
| 小型项目/原型验证 | Parcel / Vite | 零配置,快速启动 |
| 需要模块联邦的微前端 | Webpack 5 | 模块联邦是 Webpack 5 原生特性 |
| 老旧项目维护 | Webpack | 迁移成本高,保持稳定 |
十一、常用场景教学
11.1 场景一:多环境 API 地址管理
痛点: 项目有开发、测试、预发布、生产四个环境,每个环境的 API 地址不同,手动改代码容易出错。
解决方案: 使用 .env.* 文件 + process.env 管理环境变量。
bash
# .env.development
VUE_APP_BASE_API=http://dev-api.company.com
VUE_APP_ENV=development
bash
# .env.test
VUE_APP_BASE_API=http://test-api.company.com
VUE_APP_ENV=test
bash
# .env.production
VUE_APP_BASE_API=https://api.company.com
VUE_APP_ENV=production
javascript
// src/utils/request.js
import axios from 'axios';
import { Message } from 'element-ui';
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 15000
});
// 请求拦截器
service.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
},
error => Promise.reject(error)
);
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data;
if (res.code !== 200) {
Message.error(res.message || '请求失败');
return Promise.reject(new Error(res.message || 'Error'));
}
return res;
},
error => {
Message.error(error.message);
return Promise.reject(error);
}
);
export default service;
11.2 场景二:路由懒加载与权限控制
痛点: 单页应用首屏加载慢,所有页面代码一次性下载。
解决方案: 路由懒加载 + 动态路由权限控制。
javascript
// src/router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Layout from '@/layout/index.vue';
Vue.use(VueRouter);
// 公共路由,不需要权限
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index.vue'),
meta: { title: '首页', icon: 'dashboard' }
}
]
}
];
// 动态路由,根据用户权限加载
export const asyncRoutes = [
{
path: '/system',
component: Layout,
redirect: '/system/user',
meta: { title: '系统管理', icon: 'system', roles: ['admin'] },
children: [
{
path: 'user',
name: 'UserManage',
component: () => import('@/views/system/user.vue'),
meta: { title: '用户管理', roles: ['admin'] }
},
{
path: 'role',
name: 'RoleManage',
component: () => import('@/views/system/role.vue'),
meta: { title: '角色管理', roles: ['admin'] }
}
]
}
];
const createRouter = () => new VueRouter({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
});
const router = createRouter();
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher;
}
export default router;
javascript
// src/permission.js(路由守卫)
import router from './router';
import store from './store';
import { Message } from 'element-ui';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
NProgress.configure({ showSpinner: false });
const whiteList = ['/login'];
router.beforeEach(async (to, from, next) => {
NProgress.start();
const hasToken = localStorage.getItem('token');
if (hasToken) {
if (to.path === '/login') {
next({ path: '/' });
NProgress.done();
} else {
const hasRoles = store.getters.roles && store.getters.roles.length > 0;
if (hasRoles) {
next();
} else {
try {
const { roles } = await store.dispatch('user/getInfo');
const accessRoutes = await store.dispatch('permission/generateRoutes', roles);
router.addRoutes(accessRoutes);
next({ ...to, replace: true });
} catch (error) {
await store.dispatch('user/resetToken');
Message.error(error.message || '验证失败,请重新登录');
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
});
router.afterEach(() => {
NProgress.done();
});
11.3 场景三:全局组件自动注册
痛点: 项目中有很多通用组件(按钮、输入框、表格等),每个页面都要手动 import,很繁琐。
解决方案: 使用 Webpack 的 require.context 自动注册全局组件。
javascript
// src/components/global/index.js
import Vue from 'vue';
// 自动加载 global 目录下的 .vue 文件
const componentsContext = require.context('./', false, /\.vue$/);
componentsContext.keys().forEach(fileName => {
const componentConfig = componentsContext(fileName);
const componentName = fileName
.replace(/^\.\//, '')
.replace(/\.\w+$/, '');
// 全局注册组件
Vue.component(
componentName,
componentConfig.default || componentConfig
);
});
javascript
// src/main.js
import '@/components/global';
之后在任何组件中都可以直接使用,无需手动引入:
vue
<template>
<div>
<app-button type="primary">提交</app-button>
<app-input v-model="name" placeholder="请输入姓名" />
<app-table :data="tableData" />
</div>
</template>
11.4 场景四:Mock 数据开发
痛点: 后端接口还没开发完,前端无法联调,阻塞开发进度。
解决方案: 使用 webpack-dev-server 的 before 钩子搭建 Mock 服务。
javascript
// vue.config.js
module.exports = {
devServer: {
before: function(app) {
// Mock 用户列表接口
app.get('/api/users', function(req, res) {
res.json({
code: 200,
message: 'success',
data: [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 28 }
]
});
});
// Mock 登录接口
app.post('/api/login', function(req, res) {
res.json({
code: 200,
message: 'success',
data: {
token: 'mock-token-abc123',
userInfo: { id: 1, name: '管理员', role: 'admin' }
}
});
});
}
}
};
更专业的做法是使用 mockjs 库生成随机数据:
bash
npm install mockjs --save-dev
javascript
// mock/index.js
const Mock = require('mockjs');
const userList = Mock.mock({
'list|10-20': [
{
'id|+1': 1,
'name': '@cname',
'age|18-60': 1,
'email': '@email',
'createTime': '@datetime'
}
]
});
module.exports = {
'GET /api/users': userList,
'POST /api/login': {
code: 200,
data: { token: '@string(32)' }
}
};
十二、面试官高频面试题
面试题一:说一下 Webpack 的构建流程
参考答案:
Webpack 的构建流程可以分为以下几个阶段:
- 初始化参数:从配置文件和 Shell 语句中读取并合并参数,得到最终的配置对象。
- 初始化 Compiler:用配置对象初始化 Compiler 编译器对象,加载所有配置的插件。
- 确定入口:根据配置中的 entry 找出所有入口文件。
- 编译模块:从入口文件出发,调用对应的 Loader 对模块进行转译,同时解析模块的依赖,递归处理所有依赖模块。
- 完成编译:所有模块转译完成后,得到每个模块的最终内容和依赖关系图。
- 输出资源:根据依赖关系组装成 Chunk,再把 Chunk 转换成文件加入输出列表。
- 写入文件:根据 output 配置,把输出列表中的文件写入到指定目录。
整个过程中,Webpack 会在各个阶段广播事件,Plugin 可以监听这些事件执行自定义逻辑。
面试题二:Loader 和 Plugin 的区别是什么
参考答案:
Loader 和 Plugin 是 Webpack 中两个核心概念,它们的区别如下:
| 维度 | Loader | Plugin |
|---|---|---|
| 作用 | 转换模块内容,处理非JS文件 | 执行更广泛的任务,如打包优化、资源管理 |
| 运行时机 | 在模块加载时执行 | 在构建的各个阶段执行 |
| 本质 | 一个函数,接收源码返回转换结果 | 一个带有 apply 方法的类,基于事件流工作 |
| 配置位置 | module.rules 中配置 | plugins 数组中配置 |
| 举例 | vue-loader、babel-loader、css-loader | HtmlWebpackPlugin、TerserPlugin |
简单来说,Loader 是"翻译官",负责把各种文件翻译成 Webpack 能理解的模块;Plugin 是"全能助手",在构建的各个环节帮你完成各种额外任务。
面试题三:说一下 Webpack 热更新(HMR)的原理
参考答案:
HMR(Hot Module Replacement)即模块热替换,可以在不刷新整个页面的情况下更新变更的模块。
HMR 的工作原理如下:
- 建立连接:Webpack Dev Server 和浏览器之间通过 WebSocket 建立长连接。
- 监听变化:Webpack 监听文件系统的变化,当文件修改后,Webpack 重新编译该模块。
- 推送通知:编译完成后,Dev Server 通过 WebSocket 向浏览器推送更新消息,消息中包含新的 hash 值。
- 请求更新:浏览器收到消息后,对比 hash 值,然后通过 Ajax 请求更新清单(manifest),获取需要更新的模块列表。
- 热替换:浏览器下载变更的模块代码,通过 HMR Runtime 替换旧模块,同时保留应用状态。
- 失败兜底:如果热替换失败,则自动降级为刷新整个页面。
在 Vue 项目中,vue-loader 提供了组件级的 HMR 支持,修改 .vue 文件后可以只更新该组件,不影响其他组件状态。
面试题四:什么是 Tree Shaking?如何生效?
参考答案:
Tree Shaking(摇树优化)是一种移除未使用代码(dead code)的技术,就像摇树一样把枯叶子摇掉,只保留实际用到的代码。
Tree Shaking 生效的条件:
- 使用 ES Module 语法 :必须使用
import和export,不能使用 CommonJS 的require。因为 ES Module 是静态的,依赖关系在编译时就能确定,而 CommonJS 是动态的,无法在编译时分析。 - 标记副作用 :在
package.json中配置sideEffects字段,告诉 Webpack 哪些文件有副作用,不能被 Tree Shaking。 - 生产模式 :Webpack 在
production模式下默认开启 Tree Shaking 和代码压缩。
json
{
"sideEffects": [
"*.css",
"*.scss",
"*.vue",
"./src/polyfill.js"
]
}
需要注意的是,Tree Shaking 只能处理 ES Module,对于有副作用的代码(如修改全局变量、在模块顶层执行操作),Webpack 不会移除。
面试题五:代码分割(Code Splitting)有哪些方式?
参考答案:
Webpack 实现代码分割有三种方式:
- 入口起点 :使用
entry配置手动拆分代码。
javascript
module.exports = {
entry: {
main: './src/main.js',
vendor: './src/vendor.js'
}
};
- 动态导入 :使用
import()语法,在代码中按需加载模块。这是最常用的方式,Vue 路由懒加载就是基于这个特性。
javascript
// 路由懒加载
const About = () => import('./views/About.vue');
// 条件加载
if (needChart) {
import('echarts').then(echarts => {
echarts.init(document.getElementById('chart'));
});
}
- SplitChunksPlugin :通过
optimization.splitChunks配置,自动提取公共代码和第三方库。
javascript
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10
},
common: {
minChunks: 2,
name: 'common',
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
面试题六:Vue CLI 中 configureWebpack 和 chainWebpack 的区别?
参考答案:
两者都可以用来修改 Webpack 配置,但方式不同:
- configureWebpack :可以是一个对象或函数。对象形式会被
webpack-merge合并到最终配置中;函数形式可以直接修改配置对象,或者返回一个对象进行合并。适合简单的配置修改。
javascript
// 对象形式
module.exports = {
configureWebpack: {
resolve: {
alias: { '@': path.resolve(__dirname, 'src') }
}
}
};
// 函数形式
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.optimization.minimizer.push(new MyPlugin());
}
}
};
- chainWebpack:使用 webpack-chain 的链式 API 来修改配置,可以更精细地控制配置的每个部分。适合复杂的、需要精确修改 Loader 或 Plugin 的场景。
javascript
module.exports = {
chainWebpack: config => {
config.module
.rule('images')
.use('url-loader')
.tap(options => {
options.limit = 8192;
return options;
});
config.plugin('html').tap(args => {
args[0].title = '我的应用';
return args;
});
}
};
简单来说,简单的配置用 configureWebpack,复杂的精细操作用 chainWebpack。两者可以同时使用,chainWebpack 会先执行,然后是 configureWebpack。
面试题七:Webpack 5 有哪些新特性?
参考答案:
Webpack 5 于 2020 年发布,主要新特性包括:
- 持久化缓存 :新增
cache: { type: 'filesystem' }配置,将编译结果缓存到磁盘,二次构建速度提升 90% 以上。 - 模块联邦(Module Federation):允许多个独立构建的应用在运行时共享模块,是微前端架构的重要技术支撑。
- 更好的 Tree Shaking:改进了对嵌套模块、CommonJS 模块的分析能力,移除更多无用代码。
- 资源模块(Asset Modules) :内置了
asset/resource、asset/inline、asset/source等模块类型,替代了file-loader、url-loader、raw-loader。 - Node.js Polyfill 移除:默认不再自动注入 Node.js 核心模块的 polyfill,减小打包体积。
- 优化的持久化缓存 ID:使用内容哈希代替数字 ID,改善长期缓存。
面试题八:如何优化 Webpack 的构建速度?
参考答案:
可以从以下几个方面优化构建速度:
- 缩小文件搜索范围 :配置
include和exclude,减少 Loader 处理的文件数量。 - 开启持久化缓存 :Webpack 5 使用
cache: { type: 'filesystem' },Webpack 4 使用cache-loader或hard-source-webpack-plugin。 - 多进程构建 :使用
thread-loader或HappyPack,把耗时的 Loader 放到多进程中执行。 - 利用 DLL 预编译:把不常变化的第三方库提前打包成 DLL,避免重复编译。
- 合理配置 resolve :减少
extensions数组长度,避免过多的文件查找。 - 使用更快的压缩工具 :生产环境使用
esbuild或swc替代terser进行代码压缩。 - 升级到 Webpack 5:Webpack 5 在构建性能上有显著提升。
十三、总结
Webpack + Vuejs 的组合,是前端工程化发展史上的经典搭配。Webpack 提供了强大的模块打包能力,Vue.js 提供了优雅的组件化开发范式,两者结合让前端开发从"刀耕火种"进入了"工业化时代"。
虽然近年来 Vite 等新一代构建工具发展迅速,但 Webpack 凭借十年的生态积累和企业级项目的验证,仍然是大型项目的可靠选择。掌握 Webpack + Vuejs,不仅能让你高效完成日常开发,更能让你理解前端工程化的底层原理,在面试和工作中脱颖而出。
本文从痛点场景出发,系统讲解了 Webpack + Vuejs 的核心概念、工作原理、项目搭建、企业级配置、性能优化、竞品对比和高频面试题,希望能帮助你从入门到精通,真正掌握这门技术。
本文为原创文章,如需转载,请联系作者获得授权,并注明出处。