阿里云主机折上折
  • 微信号
Current Site:Index > The monorepo code organization approach

The monorepo code organization approach

Author:Chuan Chen 阅读数:41989人阅读 分类: Vue.js

Monorepo Code Organization Approach

Monorepo is a code organization method where multiple projects or modules are stored in the same code repository. The Vue 3 source code adopts this structure, centralizing the management of core libraries, compilers, runtime modules, etc., and enabling collaboration between modules through the workspace mechanism.

Advantages of Monorepo

Easier Code Sharing
In Vue 3's monorepo, different packages can directly reference code from other packages. For example, @vue/runtime-core can directly use utility functions from @vue/shared:

// packages/runtime-core/src/component.ts
import { isFunction } from '@vue/shared'

export function defineComponent(options: unknown) {
  if (isFunction(options)) {
    // ...
  }
}

Simpler Dependency Management
All packages share a top-level node_modules, with dependencies managed via pnpm or yarn workspace. Vue 3's package.json configures the workspace like this:

{
  "workspaces": [
    "packages/*"
  ]
}

Unified Build and Testing
Unified script commands can be run from the root directory:

# Build all packages
pnpm run build

# Run all tests
pnpm test

Vue 3's Directory Structure

Vue 3's monorepo has the following typical structure:

vue-next/
├── packages/
│   ├── compiler-core/     # Core compiler
│   ├── compiler-dom/      # DOM platform compiler
│   ├── reactivity/        # Reactivity system
│   ├── runtime-core/      # Core runtime
│   ├── runtime-dom/       # DOM platform runtime
│   ├── shared/            # Shared utilities
│   └── vue/               # Full build entry
├── scripts/              # Build scripts
├── package.json          # Root package.json
└── pnpm-workspace.yaml   # Workspace configuration

Dependency Relationships Between Modules

Vue 3's internal modules declare dependencies via the workspace protocol:

// packages/runtime-core/package.json
{
  "dependencies": {
    "@vue/shared": "workspace:*",
    "@vue/reactivity": "workspace:*"
  }
}

This declaration ensures the latest local code is always used, rather than published versions on npm.

Example Development Workflow

Cross-Package Modification Example
Suppose you need to modify the reactivity system and test its impact on the runtime:

  1. Modify code in packages/reactivity/src/ref.ts
  2. Write test cases in packages/runtime-core/src/apiSetup.ts
  3. Build all affected packages using the root directory's pnpm build command
  4. Run pnpm test to validate the changes

Version Release Process
Vue 3 uses the changesets tool to manage multi-package versions:

# Interactively select packages to release
pnpm changeset

# Generate version change files
pnpm changeset version

# Publish to npm
pnpm release

Build System Design

Vue 3's build scripts are located in scripts/build.js, with key build logic including:

// Build all packages in parallel
async function buildAll(targets) {
  await runParallel(require('os').cpus().length, targets, build)
}

// Build process for a single package
async function build(target) {
  const pkgDir = path.resolve(`packages/${target}`)
  const pkg = require(`${pkgDir}/package.json`)
  
  // Determine build format based on package.json's buildOptions
  if (pkg.buildOptions?.formats) {
    await execa('rollup', ['-c', '--environment', `TARGET:${target}`], {
      stdio: 'inherit'
    })
  }
}

Debugging Tips

To debug a specific package in the monorepo, you can use pnpm link:

cd packages/reactivity
pnpm link --global

cd ../your-project
pnpm link @vue/reactivity

Or configure a compound launch in VS Code:

{
  "compounds": [{
    "name": "Debug Vue Core",
    "configurations": [
      "Debug Runtime Core",
      "Debug Reactivity"
    ]
  }]
}

Multi-Package Testing Strategy

Vue 3 uses Jest for cross-package testing, with test files able to reference other packages' source code:

// packages/runtime-core/__tests__/component.spec.ts
import { ref } from '@vue/reactivity'
import { h } from '@vue/runtime-core'

test('should work with ref', () => {
  const count = ref(0)
  const Comp = {
    render: () => h('div', count.value)
  }
  // ...
})

The root directory's Jest configuration collects tests from all packages:

// jest.config.js
module.exports = {
  projects: ['<rootDir>/packages/*/jest.config.js']
}

Unified Code Standards

Shared ESLint configurations ensure consistent code style:

// packages/eslint-config/index.js
module.exports = {
  extends: ['eslint:recommended'],
  rules: {
    'no-debugger': 'error',
    '@typescript-eslint/no-unused-vars': 'error'
  }
}

// Subpackage .eslintrc.js
module.exports = {
  extends: ['@vue/eslint-config']
}

Performance Optimization Techniques

Vue 3's monorepo employs the following optimizations:

  1. Build Caching: Skips rebuilding unchanged packages
  2. Incremental Compilation: Uses tsc --build --incremental
  3. Task Parallelization: Uses worker thread pools for builds and tests
// Example parallel task execution
async function runParallel(maxConcurrency, source, iteratorFn) {
  const executing = []
  for (const item of source) {
    const p = Promise.resolve().then(() => iteratorFn(item))
    executing.push(p)
    if (executing.length >= maxConcurrency) {
      await Promise.race(executing)
    }
  }
  return Promise.all(executing)
}

Comparison with Multirepo

Feature Monorepo Multirepo
Code Sharing Direct references Requires published versions
Dependency Mgmt Unified updates Independently maintained
Cross-Package Changes Atomic commits Multi-repo coordination
Toolchain Unified configuration Potentially inconsistent
Repository Size Potentially large Smaller and distributed
Permission Control Coarse-grained Fine-grained per repo

Solutions to Common Problems

Circular Dependency Detection
Add a dependency check script to package.json:

{
  "scripts": {
    "check:cycles": "madge --circular packages/*/src/index.ts"
  }
}

Dependency Version Conflicts
Use pnpm's resolutions field to enforce uniform versions:

{
  "resolutions": {
    "typescript": "4.9.5"
  }
}

IDE Support
Configure project references for proper VS Code navigation:

// tsconfig.json
{
  "references": [
    { "path": "./packages/compiler-core" },
    { "path": "./packages/runtime-core" }
  ]
}

Custom Toolchain Extensions

Vue 3 has developed dedicated tools to support monorepo workflows:

  1. Build Caching System: Records file hashes to skip unchanged builds
  2. Change Impact Analysis: Determines which packages need retesting based on git history
  3. Preview Server: Loads development builds of multiple packages simultaneously
// Simplified change analysis implementation
function getChangedPackages() {
  const changedFiles = execSync('git diff --name-only HEAD~1').toString().split('\n')
  return Array.from(new Set(
    changedFiles
      .filter(f => f.startsWith('packages/'))
      .map(f => f.split('/')[1])
  ))
}

Historical Evolution

Vue 2 used a multirepo structure, with key splits including:

  • vuejs/vue: Core library
  • vuejs/vue-router: Routing
  • vuejs/vuex: State management

After switching to monorepo, Vue 3 achieved these improvements:

  1. Compiler and runtime can be modified synchronously
  2. Reactivity system can be referenced by all packages instantly
  3. Full test suite runs in a single CI job

Scaling Strategies

As the number of packages grows, Vue 3 employs these methods to maintain maintainability:

  1. Functional Partitioning: Organize packages into subdirectories by function
  2. Tiered Building: Separate base libraries from higher-level implementations
  3. Automation Tools: Code generation, documentation sync, etc.
# Partitioned directory structure
packages/
├── core/
│   ├── reactivity/
│   └── runtime/
├── compilers/
│   ├── core/
│   └── dom/
└── ecosystems/
    ├── router/
    └── devtools/

Integration with Other Ecosystems

External projects can depend on Vue 3's local development version using the workspace protocol:

{
  "dependencies": {
    "@vue/reactivity": "workspace:../vue-next/packages/reactivity"
  }
}

This integration facilitates:

  1. Quick validation of fixes when debugging issues
  2. Real-time collaboration when developing new features
  3. Ensuring compatibility when contributing PRs

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.