vuejs/core
compiler-ssr
Active contributors: Evan You, daiwei, edison
Purpose
@vue/compiler-ssr produces server-side render functions: instead of returning a vnode tree, the generated code pushes string fragments into a buffer that @vue/server-renderer consumes. It reuses @vue/compiler-dom's parser and most of @vue/compiler-core's transforms, then swaps element/component/slot transforms for SSR-aware versions and substitutes a different codegen pass (ssrCodegenTransform).
The package is small (~2,250 lines, 17 files) but tightly coupled to compiler-dom and server-renderer: the helpers it emits must exist in server-renderer/src/helpers/.
Directory layout
packages/compiler-ssr/src/
├── index.ts # compile()
├── runtimeHelpers.ts # SSR helper symbol IDs (ssrRenderAttrs, ssrInterpolate, …)
├── errors.ts # SSR-specific compiler errors
├── ssrCodegenTransform.ts # walks AST, rewrites vnode-style codegenNode → string-builder code
└── transforms/
├── ssrTransformElement.ts # element → ssrRenderAttr / template literal pieces
├── ssrTransformComponent.ts # component → ssrRenderComponent (with slot encoding)
├── ssrTransformSlotOutlet.ts # <slot> → ssrRenderSlot
├── ssrTransformSuspense.ts # render fallback synchronously
├── ssrTransformTeleport.ts # emit teleport markers
├── ssrTransformTransition.ts # render the BaseTransition default branch
├── ssrTransformTransitionGroup.ts # render the wrapper element + children
├── ssrVIf.ts / ssrVFor.ts # control-flow rewrites that emit string code
├── ssrVModel.ts / ssrVShow.ts # SSR-side directive renderers
├── ssrInjectFallthroughAttrs.ts # inject parent attrs into root element string
└── ssrInjectCssVars.ts # write style="--cssvar-foo: …" into root attrsThe pipeline
graph LR
src["template source"]
parse["compiler-dom baseParse"]
ast["AST"]
xform["transform with ssr* plugins"]
cgxform["ssrCodegenTransform<br/>(rewrites codegenNode)"]
gen["compiler-core generate()"]
out["ssrRender(_ctx, _push, _parent, …) {…}"]
src --> parse --> ast --> xform --> cgxform --> gen --> outTwo things happen in the transform:
- Standard transforms like
transformExpression,trackVForSlotScopes,trackSlotScopes,transformStyle,transformVBindShorthand— same logic as the client-side pipeline. - SSR-specific transforms like
ssrTransformElement,ssrTransformComponent,ssrInjectFallthroughAttrs,ssrInjectCssVars— replace or augment the AST nodes with SSR-flavored variants.
After both phases, ssrCodegenTransform walks the AST and rewrites each node's codegenNode into a sequence of _push("…literal…") and _push(_ssrRenderAttr(…)) calls that, in aggregate, emit the entire HTML string. Then the same compiler-core/codegen.ts writes the JavaScript.
ssrTransformElement
The biggest of the SSR transforms. For each element it:
- Resolves the runtime element name (handling
<component is="…">). - Iterates props, deciding for each one:
- Is it a static attribute? Append it to a literal template piece.
- Is it a dynamic class/style? Generate a call to
_ssrRenderClass/_ssrRenderStyle. - Is it
v-bind="object"? Generate_ssrRenderAttrs(_mergeProps(…)). - Is it an event handler or a DOM-only prop? Skip it (event handlers don't run on the server).
- Decides between writing the attributes into a static
<tag attr="…">…</tag>template, or splitting into_push(<tag)then a dynamic attributes call then_push(>)and so on. - Recurses into children, with text and interpolations becoming
_ssrInterpolatecalls.
ssrTransformComponent
Component vnodes can't simply be string-emitted because the server needs to await their setup, run their render function, etc. This transform:
- Computes the props arguments.
- Encodes scoped slots as functions that themselves push to an inner buffer.
- Emits a call to
_ssrRenderComponent(_component_X, props, slots, _parent, _scopeId)that the runtime will resolve and recurse into. - Tracks per-slot opt-in to async rendering.
The non-trivial bit is the slot encoding: a scoped slot in SSR context must produce its own string buffer, which then gets stitched into the parent's buffer at the right place. The transform builds a function whose body is a mini ssrRender pipeline.
Mode selection
The compiler is configured per-call via options.ssr. compileTemplate in @vue/compiler-sfc decides this and forwards to @vue/compiler-ssr when SSR is requested; otherwise to @vue/compiler-dom. The same .vue file therefore generates both a client render function and a ssrRender function in SSR-enabled builds; vite-plugin-vue's SSR mode flips the flag.
Differences from compiler-dom
| Concern | compiler-dom | compiler-ssr |
|---|---|---|
| Entry symbol | render(_ctx, _cache) |
ssrRender(_ctx, _push, _parent, _attrs) |
| Output | vnode tree via _createElementVNode |
string fragments via _push |
| Static hoisting | yes (cacheStatic) |
disabled (hoistStatic: false in options) |
prefixIdentifiers |
conditional | always true |
| Cache handlers | enabled in client | disabled (no event handlers in SSR) |
Integration points
- Imports
parserOptions,transformBind,transformOn,transformStyle,transformVBindShorthand,trackSlotScopes,trackVForSlotScopes,transformExpressionfrom@vue/compiler-domand@vue/compiler-core. - Emits calls to helpers defined in
@vue/server-renderer/src/helpers/. The names inruntimeHelpers.tshere must match those exports. - Invoked by
@vue/compiler-sfc.compileTemplatewhenoptions.ssris set. - The generated
ssrRenderis consumed by the renderer logic inpackages/server-renderer/src/render.ts.
Entry points for modification
- New SSR helper? Add it in
runtimeHelpers.tshere, implement it inserver-renderer/src/helpers/, export it fromserver-renderer/src/internal.ts. - Built-in component change? Update the matching
ssrTransformXand thehelpers/ssrRenderXin server-renderer in lockstep. - Hydration-mismatch bug from a particular template? Reproduce it in the Template Explorer with
ssr: trueto see what code SSR generates, then in client mode to compare.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.