Vue3源码学习笔记-工程化

6 阅读5分钟

Vue 中的工程化设计

Vue 中有很多优秀的工程化设计,有很多是可以应用到日常开发中的,下面盘点一下 Vue 中的工程化设计有哪些可以学习的思想和技巧。


一、分层架构与依赖治理

用物理边界约束架构,防止代码腐化

Vue 具体做法

实践具体做法可借鉴点
单向依赖图编译器 → 共享包 ← 运行时,二者互不引用数据流单向流动
共享包收敛所有跨包工具收敛到 @vue/shared禁止同层级相互引用
私有包隔离packages-private/ 物理隔离防止误发布

前端项目三层划分

┌─────────────────────────────────────┐
│     视图层 (UI Layer)               │  页面组件、通用组件
├─────────────────────────────────────┤
│     业务层 (Business Layer)          │  状态管理、业务逻辑
├─────────────────────────────────────┤
│     基础层 (Infrastructure Layer)    │  工具函数、API 封装、类型定义
└─────────────────────────────────────┘

依赖规则:上层可引用下层,下层禁止引用上层,禁止同层级相互引用。

工程实现示例

eslint-plugin-import/no-restricted-paths 限制跨包引用:

module.exports = {
  rules: {
    'import/no-restricted-paths': [{
      map: [
        // 禁止 ui-components 导入业务逻辑
        { target: 'packages/ui-components', from: 'packages/business-logic' },
        // 禁止业务包直接引用平台层
        { target: 'packages/platform', from: 'packages/*' },
      ],
      base: process.cwd(),
    }],
  },
};

二、条件编译与环境标志

Vue 中使用了较多环境变量控制分支代码:__DEV____TEST____SSR__

使用环境变量隔离开发、测试和生产代码,避免将不必要的代码打包到生产环境。

Vue 实现

通过 esbuild define 选项在构建时替换,核心环境变量包括 __DEV____SSR____TEST__(见 rollup.config.js:170-218)。条件分支在编译后彻底消失。

前端项目实践

Vite 项目中,环境变量通过 .env 文件定义:

# .env.development
VITE_API_BASE_URL=http://localhost:3000
VITE_ENABLE_MOCK=true

# .env.production
VITE_API_BASE_URL=https://api.example.com
VITE_ENABLE_MOCK=false

在代码中使用:

// 根据环境决定是否启用 Mock
if (import.meta.env.VITE_ENABLE_MOCK) {
  import('./mock').then(({ setupMock }) => setupMock());
}

// 移除日志(生产环境自动清空)
if (import.meta.env.DEV) {
  console.log('Debug info:', data);
}

配合 rollup-plugin-visualizer 可视化产物大小,确保无用代码被彻底清除。


三、平台无关的测试策略

核心思想:测试粒度与运行时环境解耦

Vue 三层测试设计

层级环境测什么速度
单元测试Node逻辑、算法、编译器最快
单元测试jsdomDOM 操作、事件中等
E2EPuppeteer真实浏览器行为最慢

自建 runtime-test 包:用纯 JS 对象模拟 DOM,让 VDOM 相关测试不依赖任何浏览器环境。

前端项目实践

Vitest 统一测试框架:

// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
  },
})

分层测试示例:

// 纯逻辑测试(Node 环境,最快)
describe('useOrderList', () => {
  it('should filter by status', () => {
    const { filteredOrders } = useOrderList({ status: 'pending' });
    expect(filteredOrders.value).toHaveLength(3);
  });
});

// 组件测试(jsdom,测试 DOM 交互)
import { mount } from '@vue/test-utils'

describe('OrderList.vue', () => {
  it('should emit select event', async () => {
    const wrapper = mount(OrderList);
    await wrapper.find('.order-item').trigger('click');
    expect(wrapper.emitted('select')).toBeTruthy();
  });
});

关键原则:测试下沉(能用 Node 就不走 jsdom)、80/20 法则(80% 单元测试,E2E 只覆盖核心流程)。

runtime-test 核心原理

const node = {
  nodeType: 1,
  tagName: 'DIV',
  children: [],
  props: {},
  eventListeners: {},
};

function createElement(tag, props, children) {
  return { nodeType: 1, tagName: tag, props, children };
}

function patch(old, new) {
  // 简单的 vdom diff 逻辑
}

这样 VDOM 的 mountpatch 等逻辑完全可以在 Node 环境中测试,不依赖 JSDOM 或 Puppeteer。


四、代码规范约束

Vue 的 ESLint 配置不是"建议",而是编译时强制执行。

前端项目实践

1. pre-commit Hook
pnpm add -D husky lint-staged
{
  "lint-staged": {
    "*.{ts,vue}": ["eslint --fix", "git add"]
  }
}
npx husky add .husky/pre-commit "npx lint-staged"
2. CI 强制检查
# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install
      - run: pnpm lint
3. Vite 构建时强制检查
// vite.config.ts
import eslint from 'vite-plugin-eslint2'

export default defineConfig({
  plugins: [
    eslint({
      failOnWarning: true,
    }),
  ],
})
4. 最佳实践链路
编辑器(提示) → pre-commit(兜底) → CI(强制)
  • 编辑器:VSCode + ESLint 插件,实时提示
  • pre-commit:lint-staged 只检查暂存区,快速反馈
  • CI:最终防线,无法绕过

五、Commit 规范与自动流水线

Angular Commit Convention 在 Vue 中的价值不仅是"提交好看":

feat(compiler): add template optimization
fix(reactivity): patch array mutation tracking

衍生的工程能力

  • 自动生成 CHANGELOG:按 feat/fix/perf 分类,版本发布零人工整理
  • 语义化版本推断:feat → minor,fix → patch,breaking → major,减少人为判断失误
  • CI 条件执行:通过 scope 判断只跑受影响包的测试(Monorepo 可借此优化)

前端项目实践

1. Commit 规范

格式:type(scope): subject,用正则校验:

// scripts/verify-commit.js
const commitRE = /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,50}/

if (!commitRE.test(msg)) {
  console.error('invalid commit message format');
  process.exit(1);
}
# .husky/commit-msg
node scripts/verify-commit.js
2. 自动流水线

核心流程:pnpm release → 选择版本 → 更新版本号 → 生成 CHANGELOG → git commit/tag/push → CI 触发

详细配置见:自动流水线配置指南.md

3. 语义化版本
  • feat(xxx): → minor 版本
  • fix(xxx): → patch 版本
  • BREAKING CHANGE: → major 版本

打标签发布:git tag v1.2.0 && git push


总结:在自己项目中最值得考虑的 3 件事

  • 画好"地图":明确每个包/目录的职责和依赖方向,用 ESLint 的 import/no-restricted-paths 或自定义规则强制执行
  • 测试分层守护:从单元测试(Node)→ 组件测试(jsdom)→ E2E(浏览器),按速度由快到慢、覆盖范围由小到大递进,CI 中分层执行,PR 阶段只跑必要测试
  • 规范焊死在工具链:能自动化的不要靠人记,能编译时拦截的不要留到运行时