The monorepo code organization approach
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:
- Modify code in
packages/reactivity/src/ref.ts
- Write test cases in
packages/runtime-core/src/apiSetup.ts
- Build all affected packages using the root directory's
pnpm build
command - 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:
- Build Caching: Skips rebuilding unchanged packages
- Incremental Compilation: Uses
tsc --build --incremental
- 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:
- Build Caching System: Records file hashes to skip unchanged builds
- Change Impact Analysis: Determines which packages need retesting based on git history
- 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:
- Compiler and runtime can be modified synchronously
- Reactivity system can be referenced by all packages instantly
- 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:
- Functional Partitioning: Organize packages into subdirectories by function
- Tiered Building: Separate base libraries from higher-level implementations
- 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:
- Quick validation of fixes when debugging issues
- Real-time collaboration when developing new features
- Ensuring compatibility when contributing PRs
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Vue3整体架构概述
下一篇:响应式系统的核心思想