Skip to content

Commit

Permalink
fix(compiler-sfc): fix ast reuse for ssr
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Nov 27, 2023
1 parent 678378a commit fb619cf
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 8 deletions.
1 change: 1 addition & 0 deletions packages/compiler-core/src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export interface RootNode extends Node {
temps: number
ssrHelpers?: symbol[]
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
transformed?: boolean

// v2 compat only
filters?: string[]
Expand Down
1 change: 1 addition & 0 deletions packages/compiler-core/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ export function transform(root: RootNode, options: TransformOptions) {
root.hoists = context.hoists
root.temps = context.temps
root.cached = context.cached
root.transformed = true

if (__COMPAT__) {
root.filters = [...context.filters!]
Expand Down
106 changes: 106 additions & 0 deletions packages/compiler-sfc/__tests__/compileTemplate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,52 @@ test('should work w/ AST from descriptor', () => {
expect(
consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
).toMatchObject(getPositionInCode(source, `foobar`))

expect(code).toBe(
compile({
filename: 'example.vue',
source: template.content
}).code
)
})

test('should work w/ AST from descriptor in SSR mode', () => {
const source = `
<template>
<div><p>{{ foobar }}</p></div>
</template>
`
const template = parse(source, {
filename: 'example.vue',
sourceMap: true
}).descriptor.template!

expect(template.ast!.source).toBe(source)

const { code, map } = compile({
filename: 'example.vue',
source: '', // make sure it's actually using the AST instead of source
ast: template.ast,
ssr: true
})

expect(map!.sources).toEqual([`example.vue`])
// when reusing AST from SFC parse for template compile,
// the source corresponds to the entire SFC
expect(map!.sourcesContent).toEqual([source])

const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
).toMatchObject(getPositionInCode(source, `foobar`))

expect(code).toBe(
compile({
filename: 'example.vue',
source: template.content,
ssr: true
}).code
)
})

test('should not reuse AST if using custom compiler', () => {
Expand Down Expand Up @@ -185,6 +231,66 @@ test('should not reuse AST if using custom compiler', () => {
expect(code).toBe(template.content)
})

test('should force re-parse on already transformed AST', () => {
const source = `
<template>
<div><p>{{ foobar }}</p></div>
</template>
`
const template = parse(source, {
filename: 'example.vue',
sourceMap: true
}).descriptor.template!

// force set to empty, if this is reused then it won't generate proper code
template.ast!.children = []
template.ast!.transformed = true

const { code } = compile({
filename: 'example.vue',
source: '',
ast: template.ast
})

expect(code).toBe(
compile({
filename: 'example.vue',
source: template.content
}).code
)
})

test('should force re-parse with correct compiler in SSR mode', () => {
const source = `
<template>
<div><p>{{ foobar }}</p></div>
</template>
`
const template = parse(source, {
filename: 'example.vue',
sourceMap: true
}).descriptor.template!

// force set to empty, if this is reused then it won't generate proper code
template.ast!.children = []
template.ast!.transformed = true

const { code } = compile({
filename: 'example.vue',
source: '',
ast: template.ast,
ssr: true
})

expect(code).toBe(
compile({
filename: 'example.vue',
source: template.content,
ssr: true
}).code
)
})

test('template errors', () => {
const result = compile({
filename: 'example.vue',
Expand Down
8 changes: 4 additions & 4 deletions packages/compiler-sfc/src/compileTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,10 @@ function doCompileTemplate({
inAST = undefined
}

if (inAST?.codegenNode) {
// input AST has codegenNode - it has already been transformed and cannot
// be reused. We need to parse a fresh one. Can't just use `source` here
// since we need the AST location info to be relative to the entire SFC.
if (inAST?.transformed) {
// If input AST has already been transformed, then it cannot be reused.
// We need to parse a fresh one. Can't just use `source` here since we need
// the AST location info to be relative to the entire SFC.
const newAST = (ssr ? CompilerDOM : compiler).parse(inAST.source, {
parseMode: 'sfc',
onError: e => errors.push(e)
Expand Down
8 changes: 4 additions & 4 deletions packages/compiler-ssr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
noopDirectiveTransform,
transformBind,
transformStyle,
transformOn
transformOn,
RootNode
} from '@vue/compiler-dom'
import { ssrCodegenTransform } from './ssrCodegenTransform'
import { ssrTransformElement } from './transforms/ssrTransformElement'
Expand All @@ -28,12 +29,11 @@ import { ssrInjectFallthroughAttrs } from './transforms/ssrInjectFallthroughAttr
import { ssrInjectCssVars } from './transforms/ssrInjectCssVars'

export function compile(
template: string,
source: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
options = {
...options,
// apply DOM-specific parsing options
...parserOptions,
ssr: true,
inSSR: true,
Expand All @@ -45,7 +45,7 @@ export function compile(
hoistStatic: false
}

const ast = baseParse(template, options)
const ast = typeof source === 'string' ? baseParse(source, options) : source

// Save raw options for AST. This is needed when performing sub-transforms
// on slot vnode branches.
Expand Down

0 comments on commit fb619cf

Please sign in to comment.