This guide is for the people who build, ship, and contribute to Vue itself — maintainers, contributors, framework integrators, and architects who need to understand how the vuejs/core monorepo is structured, how it builds, and how its internals actually work. It is written from the source at version 3.5.35, commit 86ad076.
If you are using Vue to build an application, read the User Guide instead. This document assumes you are working on Vue or integrating its packages at a deep level.
1. Executive Summary
vuejs/core is a pnpm monorepo that produces the vue npm package and a family of @vue/* packages. The codebase is written in TypeScript, built with Rollup + esbuild, tested with Vitest, and organized so that each layer of the framework — reactivity, runtime, compiler, server renderer — is an independently publishable package with a clear dependency direction.
| Property | Value |
|---|---|
| Repository | vuejs/core (the Vue 3 core) |
| Version | 3.5.35 |
| License | MIT (© 2013–present, Yuxi "Evan" You) |
| Language | TypeScript (compiled to ES2016 for browser builds, ES2019 for Node/SSR) |
| Package manager | pnpm 11.3.0 (enforced via only-allow) |
| Module system | ESM-first ("type": "module") |
| Build tool | Rollup 4.x with rollup-plugin-esbuild; SWC for minification |
| Test runner | Vitest 4.x (jsdom for DOM tests, Puppeteer for e2e) |
| Node requirement | >= 20.0.0 |
| Total source | ~1.69M chars across 677 files, 42 high-priority source folders |
The architectural throughline: the compiler turns templates into render functions; the reactivity system tracks state; the runtime renders and patches the DOM (or any custom target) in response to reactive change. Each concern is a separate package so it can be tree-shaken, reused (e.g. @vue/reactivity standalone), or retargeted (custom renderers, SSR, the test renderer).
2. Repository Architecture
2.1 Monorepo layout
core/
├── packages/ Published packages
│ ├── reactivity/ @vue/reactivity — standalone reactivity system
│ ├── shared/ @vue/shared — internal utilities (private)
│ ├── runtime-core/ @vue/runtime-core — renderer, component model (platform-agnostic)
│ ├── runtime-dom/ @vue/runtime-dom — DOM-specific renderer + browser APIs
│ ├── runtime-test/ @vue/runtime-test — test renderer (private)
│ ├── compiler-core/ @vue/compiler-core— platform-agnostic template compiler
│ ├── compiler-dom/ @vue/compiler-dom — DOM-specific compiler transforms
│ ├── compiler-sfc/ @vue/compiler-sfc — single-file-component compiler
│ ├── compiler-ssr/ @vue/compiler-ssr — SSR codegen (private)
│ ├── server-renderer/ @vue/server-renderer — SSR runtime
│ ├── vue/ vue — the umbrella package (full + runtime builds)
│ └── vue-compat/ @vue/compat — Vue 2 migration build
├── packages-private/ Non-published tooling & tests
│ ├── sfc-playground/ The online SFC playground (Vite app)
│ ├── template-explorer/ Compiler output explorer (Monaco)
│ ├── dts-test/ Type-level tests (tsc-checked)
│ ├── dts-built-test/ Tests against rolled-up .d.ts
│ └── vite-debug/ Vite integration debug harness
├── scripts/ Build/dev/release Node scripts
├── changelogs/ Generated changelog history
├── rollup.config.js Main build configuration
├── rollup.dts.config.js .d.ts rollup configuration
├── tsconfig.json / .build.json TypeScript configuration
├── vitest.config.ts Test configuration (4 projects)
└── eslint.config.js Lint rules (with codebase-specific bans)
2.2 Package dependency graph
The dependency direction is strictly one-way (lower layers never import higher layers):
shared (leaf — depended on by everything)
▲
┌──────────────────┼──────────────────────────┐
reactivity compiler-core │
▲ ▲ │
runtime-core compiler-dom │
▲ ▲ ▲ │
runtime-dom compiler-ssr compiler-sfc ──────┤
▲ ▲ ▲ │
└──────► vue ◄─────┴──────────┘ │
▲ │
server-renderer ────────────────────────────┘
| Package | Depends on (workspace) | Role |
|---|---|---|
@vue/shared |
— | Pure utility functions, shared constants, type helpers. |
@vue/reactivity |
shared |
The reactivity engine (ref, reactive, effect, computed, watch). |
@vue/runtime-core |
reactivity, shared |
Virtual DOM, component lifecycle, the platform-agnostic renderer (createRenderer). |
@vue/runtime-dom |
runtime-core, reactivity, shared, csstype |
DOM node ops, attribute/prop patching, createApp, Transition, custom elements. |
@vue/compiler-core |
shared, @babel/parser, estree-walker, source-map-js, entities |
Parse → transform → codegen for templates. |
@vue/compiler-dom |
compiler-core, shared |
DOM-specific parser options and directive transforms. |
@vue/compiler-ssr |
compiler-dom, shared |
Generates SSR render functions. |
@vue/compiler-sfc |
compiler-core, compiler-dom, compiler-ssr, shared, @babel/parser, magic-string, postcss, estree-walker, source-map-js |
Compiles .vue files (template + script + style). |
@vue/server-renderer |
compiler-ssr, shared, vue |
Renders apps to strings/streams. |
vue |
compiler-dom, compiler-sfc, runtime-dom, server-renderer, shared |
Umbrella package; full + runtime-only builds. |
@vue/vue-compat |
@babel/parser, entities, estree-walker, source-map-js, vue |
Vue 2 compatibility build. |
Note
@vue/sharedis private to the framework (it has nodependenciesof its own and is never meant to be installed directly by users). It contains the small utilities (extend,isObject,hasChanged,NOOP,camelize,normalizeClass, patch/shape flags, etc.) reused across every layer to keep bundle size down — note the ESLint rule that bans object spread in favor ofextendfrom this package.
2.3 The vue umbrella package and its entries
The vue package re-exports @vue/runtime-dom and adds the in-browser compiler. Its exports map publishes several subpaths:
| Subpath | Resolves to | Purpose |
|---|---|---|
vue |
dist/vue.runtime.esm-bundler.js (import) / index.js (require) |
Main entry (runtime-only by default). |
vue/server-renderer |
@vue/server-renderer |
SSR functions. |
vue/compiler-sfc |
@vue/compiler-sfc (browser variant available) |
SFC compiler, for tooling and the playground. |
vue/jsx-runtime / vue/jsx-dev-runtime |
jsx-runtime/index.js |
Automatic JSX runtime (h-based). |
vue/jsx |
jsx.d.ts |
JSX type definitions. |
The umbrella's packages/vue/src/index.ts registers the runtime compiler via registerRuntimeCompiler(compileToFunction) — this is the mechanism that lets the full build compile string templates on the fly, while the runtime-only build omits it entirely.
3. Build System
3.1 Build formats
Each package declares its target formats in package.json's buildOptions.formats. The vue package builds all seven:
| Format | Output suffix | Module type | Notes |
|---|---|---|---|
esm-bundler |
.esm-bundler.js |
ES | For bundlers; leaves __DEV__ etc. as flags for the bundler to define. |
esm-bundler-runtime |
.runtime.esm-bundler.js |
ES | Runtime-only bundler build (the default vue entry). |
esm-browser |
.esm-browser.js |
ES | Self-contained for native <script type=module>. |
esm-browser-runtime |
.runtime.esm-browser.js |
ES | Runtime-only native ESM. |
cjs |
.cjs.js / .cjs.prod.js |
CommonJS | Node (SSR, tests). Targets ES2019. |
global |
.global.js / .global.prod.js |
IIFE | Exposes window.Vue. |
global-runtime |
.runtime.global.js |
IIFE | Runtime-only global. |
3.2 rollup.config.js internals
rollup.config.js is the single source of build truth. Key behaviors:
- Target selection. It requires the
TARGETenvironment variable (the package name) and throwsTARGET package must be specified via --environment flag.if missing.scripts/build.jssets it per-package. - Compile-time defines (
resolveDefine). Every build inlines a map of constants via esbuild'sdefine(and the@rollup/plugin-replaceplugin where esbuild's literal-only rules fall short). These constants drive dead-code elimination. - Enum inlining.
scripts/inline-enums.jsrewritesconst enum-style values to literals at build time (the project bansconst enumin source — see Coding Standards — and inlines plain enums instead). - Externalization (
resolveExternal). For Node/bundler builds, all direct deps and peer deps are externalized; for browser/global builds, deps are inlined (tree-shaken deps likesource-map-js,@babel/parser,estree-walker,entities/decodeare only listed to suppress warnings). - Minification. Production browser/global builds run through an SWC-based
renderChunkminify pass (compress.ecma: 2016,mangle: true, Safari 10 workaround), re-prepending the license banner. - Tree-shaking.
treeshake.moduleSideEffects: falselets bundlers drop unused exports; circular-dependency warnings are suppressed (they're expected and benign in the runtime).
3.3 Compile-time feature flags
These constants are replaced at build time. They are the closest thing Vue has to a "configuration reference" — they shape what code ends up in each bundle.
| Constant | Meaning |
|---|---|
__DEV__ |
Development build (enables warnings, dev-only checks). In bundler ESM builds it becomes !!(process.env.NODE_ENV !== 'production'). |
__TEST__ |
True only during Vue's own tests. |
__BROWSER__ |
Build targets the browser. |
__GLOBAL__ |
IIFE/global build. |
__ESM_BUNDLER__ / __ESM_BROWSER__ |
ESM bundler / browser build. |
__CJS__ |
CommonJS build (Node). |
__SSR__ |
Build includes SSR branches (all non-global builds). |
__COMPAT__ |
Vue 2 compat build. |
__VERSION__ / __COMMIT__ |
Inlined version and commit. |
__FEATURE_SUSPENSE__ |
Always true — <Suspense> support. |
__FEATURE_OPTIONS_API__ |
__VUE_OPTIONS_API__ in bundler builds (user-tunable); true otherwise. |
__FEATURE_PROD_DEVTOOLS__ |
__VUE_PROD_DEVTOOLS__ in bundler builds; false otherwise. |
__FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__ |
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ in bundler builds; false otherwise. |
Important
The three
__VUE_*flags are the only ones intended for downstream consumers to define in their bundler. The rest are resolved entirely within Vue's own build. If you ship a library that bundles Vue, define__VUE_OPTIONS_API__,__VUE_PROD_DEVTOOLS__, and__VUE_PROD_HYDRATION_MISMATCH_DETAILS__to avoid "feature flag not defined" warnings.
3.4 Type declaration build
build-dts runs tsc -p tsconfig.build.json --noCheck to emit raw .d.ts into temp/, then rollup.dts.config.js rolls each package's declarations into a single dist/{pkg}.d.ts using rollup-plugin-dts. A custom patchTypes plugin (AST-driven, via @babel/parser + magic-string) converts re-exported types to inline exports (so VitePress/defineComponent inference works) and appends per-package type augmentations (JSX, macros). For the vue package, a copyMts plugin duplicates vue.d.ts to vue.d.mts so Node ESM and CJS both get correct types.
4. Development & Operations
4.1 npm scripts (the operational CLI surface)
The root package.json scripts are how everything is built, tested, and released:
| Script | Command | Purpose |
|---|---|---|
dev |
node scripts/dev.js |
Watch-build a single package for development. |
build |
node scripts/build.js |
Production build of all (or selected) packages. |
build-dts |
tsc … && rollup -c rollup.dts.config.js |
Build rolled-up type declarations. |
clean |
rimraf … |
Remove dist/, temp/, eslint cache. |
check |
tsc --incremental --noEmit |
Full type check. |
lint |
eslint --cache . |
Lint the codebase. |
format / format-check |
prettier --write/--check |
Code formatting. |
test |
vitest |
Run all tests (watch by default). |
test-unit |
vitest --project unit* |
Unit tests only. |
test-e2e |
build global + vitest --project e2e |
Puppeteer end-to-end tests. |
test-dts |
run-s build-dts test-dts-only |
Type-level tests against built .d.ts. |
test-coverage |
vitest run --project unit* --coverage |
Coverage report (v8 provider). |
size |
run-s size-* && node scripts/usage-size.js |
Measure bundle sizes. |
bench / bench-compare |
vitest bench … |
Reactivity/runtime benchmarks. |
release |
node scripts/release.js |
Cut a release. |
changelog |
conventional-changelog -p angular … |
Generate the changelog. |
dev-sfc |
run-s dev-sfc-prepare dev-sfc-run |
Run the SFC playground against local source. |
Tip
pnpm dev <package>(e.g.pnpm dev reactivity) rebuilds one package on change — the fastest loop when working on a single layer.pnpm dev-sfcspins up the playground wired to your local build, which is the best way to exercise the full compile→render path interactively.
4.2 Git hooks and commit conventions
simple-git-hooks (installed via postinstall) wires:
- pre-commit:
pnpm lint-staged && pnpm check— lints staged files (ESLint + Prettier) and runs a full type check. - commit-msg:
node scripts/verify-commit.js— enforces Conventional Commits.
lint-staged runs Prettier on *.{js,json} and ESLint --fix + Prettier on *.ts(x).
Caution
The pre-commit hook runs a full
tsc --noEmit, which can take significant time on a cold cache. Do not bypass it with--no-verifyunless you have separately verified types — a type regression that lands onmainbreaks every downstream consumer's build.
4.3 Testing architecture
vitest.config.ts defines four test projects:
| Project | Environment | Scope |
|---|---|---|
unit |
node | Most packages; excludes DOM-coupled packages and e2e. |
unit-gc |
node (--expose-gc, forks pool) |
server-renderer's ssrWatch GC test. |
unit-jsdom |
jsdom | vue, vue-compat, runtime-dom tests that need a DOM. |
e2e |
jsdom | packages/vue/__tests__/e2e/* (Puppeteer-driven). |
The config also defines the same compile-time flags as the build (with __DEV__: true, __TEST__: true, __FEATURE_* enabled) and aliases workspace packages via scripts/aliases.js so tests run against source, not built output. Coverage uses the v8 provider over packages/*/src/**, excluding entries that are not meaningfully unit-testable (e.g. profiling.ts, featureFlags.ts, hydrationStrategies.ts, Transition*).
Note
Type-level correctness is tested separately from runtime behavior:
packages-private/dts-testcontains.test-d.ts(x)files checked bytsc, anddts-built-testverifies the rolled-up declarations. A change that compiles but breaks public types is caught here, not by Vitest.
5. Reactivity Internals
The reactivity system was substantially rewritten for Vue 3.5 to use a doubly-linked-list dependency graph with version counters, replacing the older Set-based dependency collection. This is the most performance-critical subsystem in the framework.
5.1 Core data structures (packages/reactivity/src/dep.ts, effect.ts)
| Type | Role |
|---|---|
Dep |
A dependency: one reactive property (or a computed/ref). Holds a version counter and a doubly-linked list of subscriber Links (subs). |
Subscriber |
Anything that consumes reactive state: a ReactiveEffect or a ComputedRefImpl. Holds a doubly-linked list of its dependencies (deps/depsTail) and a bitmask of flags. |
Link |
A node connecting one Dep to one Subscriber. Because the relationship is many-to-many, each link participates in two doubly-linked lists: the sub's dep-list (nextDep/prevDep) and the dep's sub-list (nextSub/prevSub). Each link carries a version. |
targetMap |
A WeakMap<object, Map<key, Dep>> storing the { target → key → Dep } graph for reactive objects. |
reactive object property effect / computed
(a Dep) (a Subscriber)
│ │
├── subs ──► Link ◄── deps ───────────┤
│ │ ▲ │
│ nextSub nextDep │
▼ ▼ │ ▼
(more subs) Link (more deps)
5.2 Tracking and triggering
track(target, type, key)(called from proxy getters) finds (or lazily creates) theDepfortarget[key]intargetMap, then callsdep.track(). If an effect is active (activeSub) andshouldTrackis on, it creates or reuses aLinkbetween the dep and the active subscriber, appending it to the sub's dep-list tail.trigger(target, type, key, …)finds affected deps and callsdep.trigger(), which bumpsdep.versionand the module-globalglobalVersion, then notifies subscribers. Arraylengthchanges,ADD/DELETE/CLEARoperations, and Map/Set iteration keys (ITERATE_KEY,MAP_KEY_ITERATE_KEY,ARRAY_ITERATE_KEY) are handled with special cases so iteration-dependent effects re-run correctly.
5.3 The globalVersion fast path
Every reactive mutation increments globalVersion. A ComputedRefImpl records the globalVersion at its last evaluation. When a computed is read, if globalVersion hasn't changed since, it can skip dependency-checking entirely — no reactive state changed anywhere, so the cache is definitely valid. This is a cheap global short-circuit before the finer-grained per-dep version check.
5.4 Batching (effect.ts)
Notifications are batched. startBatch()/endBatch() bracket a trigger; subscribers are collected into batchedSub/batchedComputed linked lists (marked with the NOTIFIED flag) and flushed at the end of the batch — effects run in original order, computeds are processed separately. Mutating methods that change array length (push, pop, splice, etc.) wrap their work in pauseTracking() + startBatch() to avoid the infamous length-tracking infinite loop (issue #2137).
5.5 Effect flags
ReactiveEffect and ComputedRefImpl carry a bitmask (EffectFlags) instead of multiple booleans, for compactness and speed:
| Flag | Meaning |
|---|---|
ACTIVE |
The effect has not been stopped. |
RUNNING |
Currently executing (used to detect/guard recursion). |
TRACKING |
Should record dependencies while running. |
NOTIFIED |
Already queued in the current batch. |
DIRTY |
Needs re-evaluation (a dep changed). |
ALLOW_RECURSE |
May re-trigger itself. |
PAUSED |
Paused via .pause() — triggers are deferred. |
EVALUATED |
(computed) Has been evaluated at least once. |
5.6 Proxy handlers
baseHandlers.ts—BaseReactiveHandler(get),MutableReactiveHandler(get/set/deleteProperty/has/ownKeys), andReadonlyReactiveHandler(blocks writes, warns in dev). The getter handles ref-unwrapping, the__v_*reactive flags (IS_REACTIVE,IS_READONLY,IS_SHALLOW,RAW,SKIP), array instrumentation, and lazy nested proxying. Four singleton handlers are exported:mutableHandlers,readonlyHandlers,shallowReactiveHandlers,shallowReadonlyHandlers.collectionHandlers.ts— Special handling forMap/Set/WeakMap/WeakSet, whose methods (get/set/add/delete/has/forEach/iterators) must be instrumented rather than proxied via property access. Identity-key consistency is checked in dev (the "raw and reactive versions of the same object" warning).arrayInstrumentations.ts— Reactive-aware versions of array methods. Iteration methods (map,filter,forEach,find,includes,indexOf, etc.) take anARRAY_ITERATEdependency and wrap returned items in reactive proxies as needed. Identity-sensitive searches (includes/indexOf/lastIndexOf) retry with raw values so you can search by either the reactive or raw form.
5.7 Public surface vs. internal
packages/reactivity/src/index.ts exports the public API (ref, reactive, computed, watch, effect, effectScope, the type set, and the TrackOpTypes/TriggerOpTypes/ReactiveFlags enums). The Dep, Link, Subscriber, targetMap, globalVersion, and batching internals are not part of the public contract — runtime-core augments RefUnwrapBailTypes via declaration merging to teach ref-unwrapping about VNodes and component instances without creating a runtime dependency cycle.
6. Compiler & Runtime Pipeline
6.1 Compiler stages
The template compiler is a classic three-stage pipeline, split across compiler-core (platform-agnostic) and compiler-dom (browser specifics):
template string
│ baseParse() ── parser.ts → AST (RootNode)
▼
AST
│ transform() ── transform.ts: node + directive transforms
│ nodeTransforms: transformElement, transformExpression, vIf, vFor,
│ vSlot, vOnce, vMemo, cacheStatic (static hoisting)…
│ directiveTransforms: vBind, vOn, vModel, vShow, vHtml, vText…
▼
transformed AST (with codegenNode)
│ generate() ── codegen.ts → render function source + sourcemap
▼
{ code, ast, map }
compiler-coreexposesbaseCompile,baseParse,transform,generate, plus every individual transform and theErrorCodes/createCompilerErrormachinery. It is target-agnostic.compiler-domsuppliesparserOptions(HTML namespaces, native tags), DOM directive transforms (v-html→transformVHtml,v-text,v-modeloverride,v-onoverride,v-show,<Transition>), andstringifyStatic(turns large static subtrees into a singleinnerHTMLstring for performance). Itscompile()composes these intobaseCompile.compiler-ssrreuses the DOM parser/transforms but swaps in SSR-specific transforms (ssrTransformElement,ssrTransformComponent,ssrVIf,ssrVFor,ssrVModel,ssrVShow, CSS-var injection) and emits SSR render functions viassrCodegenTransform.
6.2 SFC compilation (compiler-sfc)
@vue/compiler-sfc is the bridge that build tools (@vitejs/plugin-vue, vue-loader) call. Its public API:
| Function | Purpose |
|---|---|
parse(source) |
Split a .vue file into <template>, <script>, <script setup>, and <style> descriptor blocks (cached via parseCache). |
compileTemplate(options) |
Compile the template block to a render function. |
compileScript(descriptor, options) |
Process <script setup>, resolve the macros (defineProps/defineEmits/defineModel/…), and merge with <script>. |
compileStyle / compileStyleAsync |
Scoped CSS, CSS Modules, and v-bind() in CSS via PostCSS. |
rewriteDefault / rewriteDefaultAST |
Rewrite a module's default export for composition. |
resolveTypeElements / inferRuntimeType |
Type-only prop/emit resolution (turns TS types into runtime declarations). |
It bundles a forked @vue/consolidate for alternative template engines and exposes babelParse, MagicString, and walk for tooling.
Note
shouldTransformRefand the old reactivity-transform are gone —compiler-sfckeepsshouldTransformRef = () => falseonly to avoid breakingvite-plugin-vue < 5.0that passedreactivityTransform: true. Do not build new features on reactivity transform.
6.3 Runtime rendering (runtime-core → runtime-dom)
runtime-core is platform-agnostic. createRenderer(options) takes a set of node operations (create/insert/remove/patchProp/…) and returns a { render, createApp } pair. This is how Vue supports multiple targets:
| Renderer | Node ops source | Target |
|---|---|---|
runtime-dom |
nodeOps + patchProp (packages/runtime-dom/src) |
Real browser DOM. |
runtime-test |
nodeOps + patchProp (packages/runtime-test/src) |
An in-memory tree for assertions (serializeInner, triggerEvent). |
| Your custom renderer | your own ops | Canvas, native, terminal, etc. |
runtime-dom lazily instantiates the renderer (ensureRenderer) so apps that only use the reactivity APIs don't pull in renderer code. createApp wraps the core app to add DOM container normalization, in-DOM template handling (full build), and dev-only checks (isNativeTag, deprecated-isCustomElement/compilerOptions guards). createSSRApp uses the hydration renderer (createHydrationRenderer) to adopt server markup instead of creating new nodes.
6.4 Hydration strategies (lazy hydration)
runtime-core exports hydrateOnIdle, hydrateOnVisible, hydrateOnMediaQuery, and hydrateOnInteraction — strategy factories for deferring hydration of async components until the browser is idle, the element is visible, a media query matches, or the user interacts. These power "islands"-style performance patterns.
7. Coding Standards & Conventions
eslint.config.js enforces several codebase-specific rules that contributors must respect:
| Rule | Rationale |
|---|---|
No const enum |
The project inlines plain enums at build time; const enum breaks isolated modules. Use a regular enum. |
No object rest in destructuring ({ ...rest } patterns) |
Output target is ES2016; rest/spread produces verbose helpers. |
No object spread in expressions ({ ...obj }) |
esbuild transpiles it to verbose inline helpers — use extend() from @vue/shared. |
No async/await syntax |
ES2016 target; avoid the generated state machine. Use promises/.then or the compiler's async helpers. |
No optional chaining (?.) |
ES2016 target; it transpiles to verbose helpers. |
No raw Node.js modules (except node:-prefixed, and inside compiler-sfc) |
Most of the codebase must be environment-agnostic. |
No DOM globals (window, document) in env-agnostic packages |
Enforced per-package: DOM packages may use DOM globals, Node packages may use Node globals, shared may use neither restriction. |
import type required for type-only imports |
Keeps the runtime graph clean and tree-shakeable. |
Prefer @ts-expect-error over @ts-ignore |
Surfaces stale suppressions. |
Important
These bans exist because Vue's source is shipped to users essentially as-authored (after esbuild transpilation to ES2016). Convenience syntax that looks free in app code (spread,
async/await,?.) expands into helper functions that bloat the framework's own bundle. When in doubt, mirror the surrounding code and reach for@vue/sharedhelpers.
8. Security Analysis
Vue is a client/SSR library, so its security surface is about safe rendering, not network/auth.
| Area | Behavior | Guidance |
|---|---|---|
| HTML injection | v-html and the innerHTML-based stringifyStatic optimization set raw HTML. Text interpolation ({{ }} / toDisplayString) is always escaped. |
Never bind untrusted content to v-html. The compiler even refuses v-html with children and warns on v-text misuse. |
| In-DOM templates | The full build can compile a container's innerHTML as a template. The source explicitly marks these paths __UNSAFE__. |
Only mount over trusted in-DOM templates; never let user-controlled markup become a template. SSR output used as a template must not contain user data. |
| SSR escaping | @vue/shared's escapeHtml is used throughout the server renderer for interpolated content and attributes. |
Trust the renderer's escaping for interpolations; be careful with v-html and any manually injected SSRContext data. |
| XSS via attributes | runtime-dom's patchProp handles attribute vs. property assignment and supports Trusted Types (@types/trusted-types). |
In strict-CSP/Trusted-Types environments, configure a policy; Vue is Trusted-Types-aware. |
| Prototype pollution | Reactive proxy handlers explicitly skip __proto__ and non-trackable keys (isNonTrackableKeys). |
Don't rely on inherited/proto keys being reactive. |
| Dependency surface | Runtime deps are minimal and well-known (@babel/parser, magic-string, postcss, source-map-js, estree-walker, entities, csstype); most are compiler/build-time. |
Keep these patched; they affect build tooling more than runtime. |
Caution
The single most common Vue security mistake is
v-htmlwith user content. Treat everyv-html,innerHTML-equivalent render path, and selector-template mount (#id) as a potential XSS vector and audit accordingly.
9. Quality, Tech Debt & Improvement Areas
These are observations grounded in the current source and the community issue tracker; they are candidate areas for contributors.
- Vapor mode is in flight. The issue tracker shows active
[Vapor]/[runtime-vapor]work (e.g. #14844, #14787, #14851) — a compilation strategy that skips the virtual DOM. It is not yet a stable public API; expect churn in compiler and runtime internals around it. - Reactivity edge cases.
reactivesilently no-ops on non-extensible objects (#14893) — arguably should warn.watchwithimmediate: trueand a static source running only once (#14898) is a recurring source of confusion; documentation/DX could be improved. - Type-level scaling. Very large prop union types can trip
TS2590("union type too complex," #10514). Components with hundreds of literal-union prop values need workarounds. - Transition/animation gaps.
<Transition>doesn't animatevisibility(#2531);v-showenter animations differ fromv-if/v-forin<TransitionGroup>(#14894). - SSR hydration robustness. Recent fixes around Teleport + Suspense unmount (#14876) and redundant mismatch logging (#14855) indicate the Teleport/Suspense/hydration intersection is a complex, regression-prone area worth additional test coverage.
- Compiler crash hardening. Edge-case template inputs (
<template v-for>+ non-identifier:key+v-memo, #14859) have produced compiler crashes; fuzz-style template tests would help.
Note
Because Vue has no staging environment between
mainand published releases, the test matrix (unit + jsdom + e2e + dts) is the safety net. Any internals change to reactivity, the renderer, or the compiler should land with tests at the seam it touches.
10. Quick Reference
Package → npm name
| Folder | npm package | Published? |
|---|---|---|
packages/vue |
vue |
✅ |
packages/reactivity |
@vue/reactivity |
✅ |
packages/runtime-core |
@vue/runtime-core |
✅ |
packages/runtime-dom |
@vue/runtime-dom |
✅ |
packages/compiler-core |
@vue/compiler-core |
✅ |
packages/compiler-dom |
@vue/compiler-dom |
✅ |
packages/compiler-sfc |
@vue/compiler-sfc |
✅ |
packages/server-renderer |
@vue/server-renderer |
✅ |
packages/vue-compat |
@vue/compat |
✅ |
packages/shared |
@vue/shared |
✅ (internal) |
packages/compiler-ssr |
@vue/compiler-ssr |
internal |
packages/runtime-test |
@vue/runtime-test |
internal |
Common contributor commands
pnpm install # bootstrap (pnpm enforced)
pnpm dev reactivity # watch-build one package
pnpm dev-sfc # run the SFC playground on local source
pnpm test-unit # unit tests
pnpm test --project unit-jsdom # DOM tests
pnpm check # full type check
pnpm lint # eslint
pnpm build vue # production build of the vue package
pnpm build-dts # build rolled-up type declarations
pnpm size # measure bundle sizes
Key source files for orientation
| File | What it anchors |
|---|---|
packages/vue/src/index.ts |
Full build entry; registers the runtime compiler. |
packages/runtime-dom/src/index.ts |
createApp/createSSRApp, DOM renderer bootstrap. |
packages/runtime-core/src/index.ts |
The entire runtime public API surface. |
packages/reactivity/src/{dep,effect,computed,ref,reactive}.ts |
The reactivity engine. |
packages/compiler-core/src/{parser,transform,codegen}.ts |
The compiler pipeline. |
rollup.config.js |
Build formats, feature flags, externalization. |
vitest.config.ts |
Test projects and aliases. |