其显示出的pnpm与npm所依赖的node_modules大小几乎一致 原因是二者的vue项目目前都是空的 依赖包数量少 体积小
如果创建了n个npm项目 那么内存就会成为n倍 而同样的情况下 pnpm的所占内存不变
不修改它,Vite 就用默认配置:
1.端口 5173 2.不自动打开浏览器3.不生成 sourcemap 4.不区分环境加载变量(只能用默认的 development/production)
修改它,你可以定制 Vite 的行为:
1.改端口2.自动打开浏览器3.生成 sourcemap 4.根据 command 和 mode 做条件判断5.配置代理、别名、插件等
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import fs from 'fs';
import path from 'path';
// ============================================================
// 这部分代码在 Node 环境执行(终端里看输出)
// ============================================================
console.log('\n========== Vite 配置文件加载(pnpm 环境)==========');
console.log('当前命令:', process.argv[2]); // 'serve' 或 'build'
console.log('当前工作目录:', process.cwd());
// 验证 pnpm 的 .pnpm 目录是否存在
const pnpmDir = path.join(process.cwd(), 'node_modules', '.pnpm');
if (fs.existsSync(pnpmDir)) {
console.log('✅ 检测到 pnpm 的 .pnpm 目录(严格隔离模式)');
// 读取 .pnpm 目录下的内容,展示 pnpm 的存储结构
const items = fs.readdirSync(pnpmDir).slice(0, 6);
console.log(' .pnpm 目录内容(前6个):', items.join(', '));
} else {
console.log('❌ 未检测到 .pnpm 目录,当前可能不是 pnpm 项目');
}
// 验证 node_modules 顶层只有声明过的依赖
const nodeModulesDir = path.join(process.cwd(), 'node_modules');
const topLevelDeps = fs.readdirSync(nodeModulesDir).filter((name) => !name.startsWith('.'));
console.log(' node_modules 顶层依赖:', topLevelDeps.join(', '));
console.log('==================================================\n');
// ============================================================
// Vite 配置导出
// ============================================================
export default defineConfig(({ command, mode }) => {
console.log('>> defineConfig 接收到的 command:', command);
console.log('>> defineConfig 接收到的 mode:', mode);
console.log('>> 将加载 .env.' + mode + ' 文件');
return {
plugins: [vue()],
// 开发服务器配置
server: {
port: mode === 'test' ? 3000 : 5173,
open: true, // 自动打开浏览器
},
// 构建配置
build: {
sourcemap: true, // 生成 sourcemap 便于调试
},
};
});
拓展工具:fs+path 是为了做路径处理、本地文件读写,绝大多数用来配置路径别名、本地 https 证书
console.log('当前命令:', process.argv[2]); // 'serve' 或 'build' process.argv[]是node函数内置数组,记录终端启动的整条命令
console.log('当前工作目录:', process.cwd()); //获取你执行命令的文件夹路径(项目根目录)
const pnpmDir = path.join(process.cwd(), 'node_modules', '.pnpm'); if (fs.existsSync(pnpmDir)) { //fs.existsSync:Node 文件 API,判断这个文件夹是否存在 // 找到node_modules/.pnpm,判定是pnpm const items = fs.readdirSync(pnpmDir).slice(0,6); console.log('✅ 检测到 pnpm 的 .pnpm 目录(严格隔离模式)'); console.log(' .pnpm 目录内容(前6个):', items.join(',')); } else { console.log('❌ 未检测到 .pnpm 目录,当前可能不是 pnpm 项目'); }
const nodeModulesDir = path.join(process.cwd(), 'node_modules'); // 拼接路径:获取当前命令行所在根目录下 node_modules 文件夹的绝对路径
// 读取 node_modules 整个目录下所有文件/文件夹名称 // filter 过滤规则:保留「不以英文点 . 开头」的内容,剔除 .pnpm、.DS_Store 这类隐藏文件/隐藏目录 const topLevelDeps = fs.readdirSync(nodeModulesDir).filter(name => !name.startsWith('.'));
// 将过滤后的顶层依赖数组用英文逗号拼接为字符串,打印到终端 console.log(' node_modules 顶层依赖:', topLevelDeps.join(','));
return { // 注册vue插件,让Vite支持解析.vue单文件组件 plugins: [vue()],
server: {
port: mode === 'test' ? 3000 : 5173, // 固定本地开发端口为5173,不使用随机端口
<!-- defineConfig 回调写法优势:可以根据mode/command做动态配置 -->
open: true, // 启动服务后自动打开默认浏览器访问项目地址
},
build.sourcemap: true 生成 .map 文件,方便调试时看到源码位置(工程化实战中,这用于线上报错定位)
在项目根目录执行:
pnpm add -D eslint eslint-plugin-vue @vue/eslint-config-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser 包的作用:
pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier
pnpm add -D husky lint-staged
为什么在ESModule 项目中要使用CommonJS的写法? :如果起名为xxxx.js Node会把它当成ESM ESM不可以直接写module.exports 语法报错 所以说要用xxx.cjs 强制走CommonJS 写法固定成熟 几乎不会出现解析BUG 稳定性更高
项目根目录创建 .prettierrc.cjs:
在该文件中声明的文件/目录 Prettier 会跳过这些目录/文件,不进行格式化。 一般就是dist/ node*modules/ *.log _.lock _.yaml _.yml _.html _.svg 这些文件 总而言之 就是Prettier 只管手写的业务源码(ts、js、vue、css 等);
打开 package.json,在 "scripts" 中添加以下命令,并在根节点添加 "lint-staged" 字段:
具体操作:只改 3 个地方
- 在 "scripts" 里添加 5 个新命令(原有的 dev、build、preview 不要动) json { "scripts": { "lint": "eslint . --ext .vue,.js,.ts --fix", // ← 新增 "format": "prettier --write .", // ← 新增 "lint:check": "eslint . --ext .vue,.js,.ts", // ← 新增 "format:check": "prettier --check .", // ← 新增 "prepare": "husky" // ← 新增 //"prepare": "husky" 的作用是让 pnpm install 时自动初始化 Husky,这样团队成员克隆项目后只需 pnpm install,Git Hooks 就会自动配置好 } }
- 在 package.json 的根节点(和 "scripts" 平级)添加 "lint-staged" 字段 json { "name": "demo2-env", "private": true, "version": "0.0.0", "scripts": { /_ ... / }, "lint-staged": { // ← 整个字段都是新增的 ".{js,ts,vue}": [ "eslint --fix", "prettier --write" ], ".{css,scss}": [ "prettier --write" ], ".{json,md}": [ "prettier --write" ] }, "dependencies": { /_ ... / }, "devDependencies": { / ... */ } // ← 新增的 devDependencies 也在这里 }
- 在 "devDependencies" 中添加新包的列表(原有的 @vitejs/plugin-vue、vite、vue 等都要保留) json { "devDependencies": { "typescript": "^5.5.3", "vue-eslint-parser": "^9.4.3" } }
Husky 9.x 的初始化方式和使用 package.json 中的 "prepare": "husky" 脚本自动触发。为了让 Husky 立即生效,手动执行一次: pnpm run prepare
这会在项目根目录创建 .husky/ 文件夹。然后手动创建 pre-commit 钩子文件:
查看 .husky/pre-commit 文件内容,确保它是这样的:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh" //里面没有\
pnpm lint-staged
验证 1:ESLint 能正常运行 pnpm lint
验证 2:Prettier 能正常运行 pnpm format
验证 3:lint-staged 能正常运行
git add src/App.vue //先 add 一个文件到暂存区
pnpm lint-staged
发现代码报错 原因是 eslint版本过新 不支持原本的.eslintrc.cjs文件 需要改用eslint.config.js
方案一:(最简单推荐:降级 ESLint 到 8.x,适配你现有的.eslintrc.cjs) 新版本 ESLint 改动极大,大量规则写法全部重写,新手最稳妥是降级回稳定的 v8 版本,兼容你现在所有配置: 卸载现有高版本 eslint 全套 powershell pnpm remove eslint eslint-plugin-vue @vue/eslint-config-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser 固定安装 8 系列稳定版 powershell pnpm add -D eslint@8 eslint-plugin-vue @vue/eslint-config-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser 再次执行 powershell pnpm lint .eslintrc.cjs 可以正常被识别,不用改配置代码。 方案二:(保留 ESLint 10,全面迁移新版配置) 需要做 3 件事: 把根目录的 .eslintrc.cjs 重命名为 eslint.config.js 把配置语法从 CommonJS module.exports 改成 ESM export default,重写整套规则格式(语法变动非常多) 适配插件导入写法,大部分旧规则名失效需要替换 pnpm add -D @eslint/js typescript-eslint eslint-config-prettier //10代需要添加的扁平包 缺点:改动量大,你整套 Vue+TS 规则都要重写,不适合现阶段。
运行正常 但是一堆报错 是因为他扫描了一堆本不该扫描的文档/文件
可以改为"lint": "eslint src --ext .vue,.js,.ts --fix",
可以发现 改完不扫描无关文档/文件 就不会报错了
如:故意用双引号,故意不加分号等
#将坏代码添加到暂存区
git add src/App.vue
#尝试提交
git commit -m "test: 故意提交坏代码验证 ESLint 拦截"
而后就会看到Husky的日志 报错 阻止提交
lint-staged 配置里从左到右依次执行:["eslint --fix", "prettier --write"]。先 ESLint 修代码风格,再 Prettier 格式化,避免相互覆盖
自动修复失败时,lint-staged 会终止提交,你需要手动修复后再 add 提交。这是故意的——宁可不让提交,也不能让坏代码进仓库。
pnpm add vue-router
在router/index.ts里加上
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
// ⭐ 关键:所有页面都用 () => import() 实现懒加载
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue'),
},
{
path: '/user',
name: 'User',
component: () => import('@/views/User.vue'),
},
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
import router from './router';
app.use(router);
当前环境:{{ mode }}
//import.meta.env.MODE无法在非js/ts我机组生效 此处现在替换为了mode 同时在下方声明
<nav style="margin: 20px 0; padding: 12px; background: #f5f5f5; border-radius: 6px;">
<router-link to="/" style="margin-right: 20px;">🏠 首页</router-link>
<router-link to="/user" style="margin-right: 20px;">👤 用户中心</router-link>
<router-link to="/admin">🔐 管理后台</router-link>
</nav>
<hr />
<!-- 路由出口:当前页面的内容会渲染在这里 -->
<router-view />
<script setup>
console.log('App.vue 加载了(主入口)');
const mode =import.meta.env.MODE
</script>
这一步是关键——让 Vite 在打包时主动拆分代码: 修改vite.config.ts
保留所有原有配置,只新增 resolve 和 build.rollupOptions
export default defineConfig({
plugins: [vue()],
resolve: {
// 配置路径别名,让 @ 指向 src/
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
build: {
// 生成 sourcemap,便于调试时定位源码位置
sourcemap: true,
rollupOptions: {
output: {
manualChunks(id) {
// 所有以 'vue' 开头的包(vue、vue-router 等)都打包到 vue-vendor 中
if (id.includes('vue') || id.includes('vue-router')) {
return 'vue-vendor'
}
},
// 每个 chunk 的文件名格式,[name] 会自动取 manualChunks 里定义的 key
chunkFileNames: 'assets/[name]-[hash].js',
},
},
},
});
可以看出:所有 .vue 组件是通过 ?import 请求分别加载的(因为 Vite 在开发模式下不做打包合并)
再次观察:打开其他界面时(切换路由),浏览器network面板可以看见该界面的动态加载
1.读取环境变量2.解析入口3.构建依赖图谱4.插件转换(把输入的.vue,.ts,.less转换为.js,.css 5.摇树优化6.代码拆分7.压缩混淆8.输出dist/目录+清单(manifest)

明显的发现Network面板少了不少加载的东西
没有出现 User-xxx.js 或 Admin-xxx.js。说明它们没有被加载,实现了“按需加载”
然后点击导航栏的“用户中心”,观察 Network 面板,或者也可以观察console面板
发现这时出现了我们点击对应的路由的名字

manualChunks的功能:分包规范,Rollup 原生提供的手动分包配置项,Vue 核心库只加载一次,后续页面直接读浏览器缓存,减少重复下载,这个文件的 hash 只在这些库的版本号变化时才会改变——你改业务代码不会让 vendor 的 hash 变化,用户不需要重新下载 85KB 的 Vue 核心库,只下载被改动的业务 chunk
用 Node.js 脚本,通过命令行交互询问用户,然后自动创建 view、api、store 三个目录下的文件骨架 inquirer 从 v9 开始是纯 ESM 包,如果项目是 ESM("type": "module"),直接 import 即可。但我们这里用 CommonJS 方式写脚本(方便在终端直接运行),所以使用 [email protected](最后一个支持 CommonJS 的版本) 最新版本: pnpm add -D inquirer @types/inquirer 支持cjs写法的8.2.6版本: pnpm add -D [email protected]
在项目根目录下创建scripts文件夹,然后创建 generate.ts 解决了:日常开发每个新业务模块都要手动新建 3 个文件、重复写基础模板,效率低且格式不统一: 不用反复复制粘贴 Vue、API、Pinia 样板代码 强制统一全项目代码格式、命名规范(大驼峰接口 / 小驼峰文件) 自带 TS 类型模板,不用从零手写 interface 内置文件覆盖保护,防止误删已有代码,支持--force强制覆盖
运行使用方式: 常规唤起交互 ts-node generate.ts
"gen": "node scripts/generate.js"
在 src/ 下创建 api/、stores/modules/、views/ 目录(如果还没有的话): //mkdir = make directory -p 父目录不存在时自动逐级创建;文件夹已经存在也不会报错,静默跳过
mkdir -p src/api mkdir -p src/stores/modules mkdir -p src/views
同时需要创建 src/utils/request.ts(API 模板里引用了它):
// src/utils/request.ts
import axios from 'axios';
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
});
// 响应拦截器:直接返回 data
request.interceptors.response.use(
(response) => response.data,
(error) => Promise.reject(error)
);
export default request;
在src/views内部新生成了一个新的文件夹product 里面有新生成的index.vue 以及在api/中生成的product.ts
打开 src/router/index.ts,在 routes 数组中新增一个路由:
// ========== 新增:自动生成的 product 路由 ==========
{
path: '/product',
name: 'Product',
component: () => import('@/views/product/index.vue'),
},
打开views/Home.vue 在导航区添加 /product 的链接 去产品页
pnpm dev
真正作用:大量的模块只需要在终端输入pnpm gen输入多次 输入多次模块名,就可以省去大量时间。并且这个脚本“目录分层规范”变成了一条自动化生产线。 新人入职第一天跑一遍 pnpm gen product,生成的文件结构跟团队老员工的一模一样,不需要有人口头告诉他“你要在 api 下建文件、还要按这个格式写”————让机器帮你执行规范,而不是靠人脑记
git push 代码到 GitHub 时,GitHub 自动拉取代码 → 安装依赖 → 代码校验(ESLint) → 执行测试(Vitest) → 打包构建。任何一步失败,流水线标红,阻止代码合并。
pnpm add -D vitest @vue/test-utils jsdom @vitest/coverage-v8 : vitest:测试运行器(Vite 原生集成,启动极快) @vue/test-utils:Vue 组件测试工具库,用于挂载和交互 Vue 组件 jsdom:在 Node 环境里模拟浏览器 DOM API(如 document、window),让组件测试能在 Node 中运行 @vitest/coverage-v8:测试覆盖率统计工具(V8 引擎原生支持)
// src/utils/__tests__/math.test.ts
import { describe, it, expect } from 'vitest';
// 一个简单的加法函数(直接写在测试里,不用单独建文件)
function add(a: number, b: number): number {
return a + b;
}
describe('add 函数', () => {
it('1 + 2 应该等于 3', () => {
expect(add(1, 2)).toBe(3);
});
it('-1 + 1 应该等于 0', () => {
expect(add(-1, 1)).toBe(0);
});
});
"test": "vitest run", "test:coverage": "vitest run --coverage"
pnpm test
预期结果:
如果显示 Test Files 1 passed,说明测试跑通了。如果没有输出或报错,检查是否安装了 vitest,以及 tests 目录是否在 src/utils/ 下
这一步先确认你已经把代码推到 GitHub 了: git remote -v 然后把代码推送上去,确保 GitHub 仓库里有你的代码。
创建 .github/workflows/ci.yml:
name: CI 流水线
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test: # 运行环境:最新 Ubuntu
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: 安装 Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'pnpm'
- name: 安装依赖
run: pnpm install
- name: 代码规范检查
run: pnpm lint
- name: 单元测试
run: pnpm test
- name: 构建项目
run: pnpm build
提交并推送:
git add .github/workflows/ci.yml
git commit -m "feat: 添加 GitHub Actions CI 流水线"
git push
打开你的 GitHub 仓库页面
点击 Actions 标签页
你会看到 CI 流水线 正在运行(黄色圆点表示运行中)
点击进去,可以看到每一步的执行日志
报错详细说明
流水线标红 ❌,阻止了这次提交的合并(如果你配置了分支保护规则,这个 PR 会被自动标记为不可合并)
验证: 用 Playwright 自动打开浏览器,模拟用户访问首页 → 点击“用户页” → 检查页面是否正常。
对应你导图中的: Vitest, Playwright E2E 测试 自动执行冒烟测试,验证页面可以正常访问,接口 500 无报错
pnpm add -D @playwright/test npx playwright install:npx playwright install 会下载 Chromium、Firefox 和 WebKit 三个浏览器内核,用于在不同浏览器中运行测试,下载时间可能较长 可以只安装 Chromium 节省时间:npx playwright install Chromium 也可以安装edge:Playwright 里 Edge 命名为 msedge :npx playwright install msedge
创建 e2e/homepage.spec.ts:
// e2e/homepage.spec.ts
import { test, expect } from '@playwright/test';
test.describe('首页冒烟测试', () => {
test('首页应该正常加载', async ({ page }) => {
// 访问首页
await page.goto('http://localhost:5173/');
// 检查页面标题是否包含 "Vite 分包验证 Demo"
await expect(page.locator('h1')).toContainText('Vite 分包验证');
});
test('点击用户页链接应该跳转到 /user', async ({ page }) => {
await page.goto('http://localhost:5173/');
// 点击 "用户中心" 链接
await page.click('text=用户中心');
// 验证 URL 变成了 /user
await expect(page).toHaveURL(/.*\/user/);
// 验证页面出现了 "用户中心" 标题
await expect(page.locator('h1')).toContainText('用户中心');
});
});
{ "scripts": { "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui" } }
终端 1(启动开发服务器):
pnpm dev
终端 2(运行 E2E 测试):
pnpm test:e2e 预期: Running 2 tests using 1 worker
✓ e2e/homepage.spec.ts:4:7 › 首页冒烟测试 › 首页应该正常加载 (2.1s) ✓ e2e/homepage.spec.ts:10:7 › 首页冒烟测试 › 点击用户页链接应该跳转到 /user (1.5s)
2 passed (4.2s)
修改 .github/workflows/ci.yml,在 build 步骤之后添加 E2E 测试步骤:
- name: 构建项目
run: pnpm build
-
name: 安装 Playwright 浏览器 run: npx playwright install --with-deps msedge
name: 运行 E2E 测试 run: | pnpm preview & npx wait-on http://localhost:4173 pnpm test:e2e env: CI: true
git add . git commit -m "feat: 添加 Playwright E2E 测试并集成到 CI" git push
打开 GitHub Actions,会看到流水线中新增了 “安装 Playwright 浏览器” 和 “运行 E2E 测试” 两个步骤,全部通过后流水线变绿。
Demo 8:性能监控 SDK(对应导图中的“运行时监控层”)











