Skip to content

Commit b685805

Browse files
committed
wip(ssr): ssr helper codegen
1 parent d1d81cf commit b685805

File tree

18 files changed

+372
-251
lines changed

18 files changed

+372
-251
lines changed

packages/compiler-core/src/ast.ts

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export interface RootNode extends Node {
103103
imports: ImportItem[]
104104
cached: number
105105
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
106+
ssrHelpers?: symbol[]
106107
}
107108

108109
export type ElementNode =

packages/compiler-core/src/codegen.ts

+80-53
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ function createCodegenContext(
7474
ast: RootNode,
7575
{
7676
mode = 'function',
77-
prefixIdentifiers = mode === 'module' || mode === 'cjs',
77+
prefixIdentifiers = mode === 'module',
7878
sourceMap = false,
7979
filename = `template.vue.html`,
8080
scopeId = null,
@@ -169,7 +169,6 @@ export function generate(
169169
const {
170170
mode,
171171
push,
172-
helper,
173172
prefixIdentifiers,
174173
indent,
175174
deindent,
@@ -182,58 +181,10 @@ export function generate(
182181
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
183182

184183
// preambles
185-
if (mode === 'function' || mode === 'cjs') {
186-
const VueBinding = mode === 'function' ? `Vue` : `require("vue")`
187-
// Generate const declaration for helpers
188-
// In prefix mode, we place the const declaration at top so it's done
189-
// only once; But if we not prefixing, we place the declaration inside the
190-
// with block so it doesn't incur the `in` check cost for every helper access.
191-
if (hasHelpers) {
192-
if (prefixIdentifiers) {
193-
push(
194-
`const { ${ast.helpers.map(helper).join(', ')} } = ${VueBinding}\n`
195-
)
196-
} else {
197-
// "with" mode.
198-
// save Vue in a separate variable to avoid collision
199-
push(`const _Vue = ${VueBinding}\n`)
200-
// in "with" mode, helpers are declared inside the with block to avoid
201-
// has check cost, but hoists are lifted out of the function - we need
202-
// to provide the helper here.
203-
if (ast.hoists.length) {
204-
const staticHelpers = [CREATE_VNODE, CREATE_COMMENT, CREATE_TEXT]
205-
.filter(helper => ast.helpers.includes(helper))
206-
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
207-
.join(', ')
208-
push(`const { ${staticHelpers} } = _Vue\n`)
209-
}
210-
}
211-
}
212-
genHoists(ast.hoists, context)
213-
newline()
214-
push(`return `)
184+
if (mode === 'module') {
185+
genModulePreamble(ast, context, genScopeId)
215186
} else {
216-
// generate import statements for helpers
217-
if (genScopeId) {
218-
ast.helpers.push(WITH_SCOPE_ID)
219-
if (ast.hoists.length) {
220-
ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
221-
}
222-
}
223-
if (hasHelpers) {
224-
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
225-
}
226-
if (ast.imports.length) {
227-
genImports(ast.imports, context)
228-
newline()
229-
}
230-
if (genScopeId) {
231-
push(`const withId = ${helper(WITH_SCOPE_ID)}("${scopeId}")`)
232-
newline()
233-
}
234-
genHoists(ast.hoists, context)
235-
newline()
236-
push(`export `)
187+
genFunctionPreamble(ast, context)
237188
}
238189

239190
// enter render function
@@ -315,6 +266,82 @@ export function generate(
315266
}
316267
}
317268

269+
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
270+
const { mode, helper, prefixIdentifiers, push, newline } = context
271+
const VueBinding = mode === 'function' ? `Vue` : `require("vue")`
272+
// Generate const declaration for helpers
273+
// In prefix mode, we place the const declaration at top so it's done
274+
// only once; But if we not prefixing, we place the declaration inside the
275+
// with block so it doesn't incur the `in` check cost for every helper access.
276+
if (ast.helpers.length > 0) {
277+
if (prefixIdentifiers) {
278+
push(`const { ${ast.helpers.map(helper).join(', ')} } = ${VueBinding}\n`)
279+
} else {
280+
// "with" mode.
281+
// save Vue in a separate variable to avoid collision
282+
push(`const _Vue = ${VueBinding}\n`)
283+
// in "with" mode, helpers are declared inside the with block to avoid
284+
// has check cost, but hoists are lifted out of the function - we need
285+
// to provide the helper here.
286+
if (ast.hoists.length) {
287+
const staticHelpers = [CREATE_VNODE, CREATE_COMMENT, CREATE_TEXT]
288+
.filter(helper => ast.helpers.includes(helper))
289+
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
290+
.join(', ')
291+
push(`const { ${staticHelpers} } = _Vue\n`)
292+
}
293+
}
294+
}
295+
// generate variables for ssr helpers
296+
if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
297+
// ssr guaruntees prefixIdentifier: true
298+
push(
299+
`const { ${ast.ssrHelpers
300+
.map(helper)
301+
.join(', ')} } = require("@vue/server-renderer")\n`
302+
)
303+
}
304+
genHoists(ast.hoists, context)
305+
newline()
306+
push(`return `)
307+
}
308+
309+
function genModulePreamble(
310+
ast: RootNode,
311+
context: CodegenContext,
312+
genScopeId: boolean
313+
) {
314+
const { push, helper, newline, scopeId } = context
315+
// generate import statements for helpers
316+
if (genScopeId) {
317+
ast.helpers.push(WITH_SCOPE_ID)
318+
if (ast.hoists.length) {
319+
ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
320+
}
321+
}
322+
if (ast.helpers.length) {
323+
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
324+
}
325+
if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
326+
push(
327+
`import { ${ast.ssrHelpers
328+
.map(helper)
329+
.join(', ')} } from "@vue/server-renderer"\n`
330+
)
331+
}
332+
if (ast.imports.length) {
333+
genImports(ast.imports, context)
334+
newline()
335+
}
336+
if (genScopeId) {
337+
push(`const withId = ${helper(WITH_SCOPE_ID)}("${scopeId}")`)
338+
newline()
339+
}
340+
genHoists(ast.hoists, context)
341+
newline()
342+
push(`export `)
343+
}
344+
318345
function genAssets(
319346
assets: string[],
320347
type: 'component' | 'directive',

packages/compiler-core/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export { transformOn } from './transforms/vOn'
3333

3434
// exported for compiler-ssr
3535
export { processIfBranches } from './transforms/vIf'
36+
export { processForNode } from './transforms/vFor'
3637
export {
3738
transformExpression,
3839
processExpression

packages/compiler-core/src/options.ts

+6-9
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export interface ParserOptions {
2323
// this number is based on the map above, but it should be pre-computed
2424
// to avoid the cost on every parse() call.
2525
maxCRNameLength?: number
26-
2726
onError?: (error: CompilerError) => void
2827
}
2928

@@ -32,6 +31,8 @@ export interface TransformOptions {
3231
directiveTransforms?: { [name: string]: DirectiveTransform | undefined }
3332
isBuiltInComponent?: (tag: string) => symbol | void
3433
// Transform expressions like {{ foo }} to `_ctx.foo`.
34+
// If this option is false, the generated code will be wrapped in a
35+
// `with (this) { ... }` block.
3536
// - This is force-enabled in module mode, since modules are by default strict
3637
// and cannot use `with`
3738
// - Default: mode === 'module'
@@ -48,6 +49,7 @@ export interface TransformOptions {
4849
// analysis to determine if a handler is safe to cache.
4950
// - Default: false
5051
cacheHandlers?: boolean
52+
ssr?: boolean
5153
onError?: (error: CompilerError) => void
5254
}
5355

@@ -61,13 +63,6 @@ export interface CodegenOptions {
6163
// `require('vue')`.
6264
// - Default: 'function'
6365
mode?: 'module' | 'function' | 'cjs'
64-
// Prefix suitable identifiers with _ctx.
65-
// If this option is false, the generated code will be wrapped in a
66-
// `with (this) { ... }` block.
67-
// - This is force-enabled in module mode, since modules are by default strict
68-
// and cannot use `with`
69-
// - Default: mode === 'module'
70-
prefixIdentifiers?: boolean
7166
// Generate source map?
7267
// - Default: false
7368
sourceMap?: boolean
@@ -76,7 +71,9 @@ export interface CodegenOptions {
7671
filename?: string
7772
// SFC scoped styles ID
7873
scopeId?: string | null
79-
// generate SSR specific code?
74+
// we need to know about this to generate proper preambles
75+
prefixIdentifiers?: boolean
76+
// generate ssr-specific code?
8077
ssr?: boolean
8178
}
8279

packages/compiler-core/src/transform.ts

+21-13
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ function createTransformContext(
115115
nodeTransforms = [],
116116
directiveTransforms = {},
117117
isBuiltInComponent = NOOP,
118+
ssr = false,
118119
onError = defaultOnError
119120
}: TransformOptions
120121
): TransformContext {
@@ -126,6 +127,7 @@ function createTransformContext(
126127
nodeTransforms,
127128
directiveTransforms,
128129
isBuiltInComponent,
130+
ssr,
129131
onError,
130132

131133
// state
@@ -256,10 +258,19 @@ export function transform(root: RootNode, options: TransformOptions) {
256258
if (options.hoistStatic) {
257259
hoistStatic(root, context)
258260
}
259-
finalizeRoot(root, context)
261+
if (!options.ssr) {
262+
createRootCodegen(root, context)
263+
}
264+
// finalize meta information
265+
root.helpers = [...context.helpers]
266+
root.components = [...context.components]
267+
root.directives = [...context.directives]
268+
root.imports = [...context.imports]
269+
root.hoists = context.hoists
270+
root.cached = context.cached
260271
}
261272

262-
function finalizeRoot(root: RootNode, context: TransformContext) {
273+
function createRootCodegen(root: RootNode, context: TransformContext) {
263274
const { helper } = context
264275
const { children } = root
265276
const child = children[0]
@@ -304,13 +315,6 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
304315
} else {
305316
// no children = noop. codegen will return null.
306317
}
307-
// finalize meta information
308-
root.helpers = [...context.helpers]
309-
root.components = [...context.components]
310-
root.directives = [...context.directives]
311-
root.imports = [...context.imports]
312-
root.hoists = context.hoists
313-
root.cached = context.cached
314318
}
315319

316320
export function traverseChildren(
@@ -359,13 +363,17 @@ export function traverseNode(
359363

360364
switch (node.type) {
361365
case NodeTypes.COMMENT:
362-
// inject import for the Comment symbol, which is needed for creating
363-
// comment nodes with `createVNode`
364-
context.helper(CREATE_COMMENT)
366+
if (!context.ssr) {
367+
// inject import for the Comment symbol, which is needed for creating
368+
// comment nodes with `createVNode`
369+
context.helper(CREATE_COMMENT)
370+
}
365371
break
366372
case NodeTypes.INTERPOLATION:
367373
// no need to traverse, but we need to inject toString helper
368-
context.helper(TO_DISPLAY_STRING)
374+
if (!context.ssr) {
375+
context.helper(TO_DISPLAY_STRING)
376+
}
369377
break
370378

371379
// for container types, further traverse downwards

0 commit comments

Comments
 (0)