Skip to content

Commit fb619cf

Browse files
committed
fix(compiler-sfc): fix ast reuse for ssr
1 parent 678378a commit fb619cf

File tree

5 files changed

+116
-8
lines changed

5 files changed

+116
-8
lines changed

packages/compiler-core/src/ast.ts

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export interface RootNode extends Node {
113113
temps: number
114114
ssrHelpers?: symbol[]
115115
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
116+
transformed?: boolean
116117

117118
// v2 compat only
118119
filters?: string[]

packages/compiler-core/src/transform.ts

+1
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ export function transform(root: RootNode, options: TransformOptions) {
334334
root.hoists = context.hoists
335335
root.temps = context.temps
336336
root.cached = context.cached
337+
root.transformed = true
337338

338339
if (__COMPAT__) {
339340
root.filters = [...context.filters!]

packages/compiler-sfc/__tests__/compileTemplate.spec.ts

+106
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,52 @@ test('should work w/ AST from descriptor', () => {
156156
expect(
157157
consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
158158
).toMatchObject(getPositionInCode(source, `foobar`))
159+
160+
expect(code).toBe(
161+
compile({
162+
filename: 'example.vue',
163+
source: template.content
164+
}).code
165+
)
166+
})
167+
168+
test('should work w/ AST from descriptor in SSR mode', () => {
169+
const source = `
170+
<template>
171+
<div><p>{{ foobar }}</p></div>
172+
</template>
173+
`
174+
const template = parse(source, {
175+
filename: 'example.vue',
176+
sourceMap: true
177+
}).descriptor.template!
178+
179+
expect(template.ast!.source).toBe(source)
180+
181+
const { code, map } = compile({
182+
filename: 'example.vue',
183+
source: '', // make sure it's actually using the AST instead of source
184+
ast: template.ast,
185+
ssr: true
186+
})
187+
188+
expect(map!.sources).toEqual([`example.vue`])
189+
// when reusing AST from SFC parse for template compile,
190+
// the source corresponds to the entire SFC
191+
expect(map!.sourcesContent).toEqual([source])
192+
193+
const consumer = new SourceMapConsumer(map as RawSourceMap)
194+
expect(
195+
consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
196+
).toMatchObject(getPositionInCode(source, `foobar`))
197+
198+
expect(code).toBe(
199+
compile({
200+
filename: 'example.vue',
201+
source: template.content,
202+
ssr: true
203+
}).code
204+
)
159205
})
160206

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

234+
test('should force re-parse on already transformed AST', () => {
235+
const source = `
236+
<template>
237+
<div><p>{{ foobar }}</p></div>
238+
</template>
239+
`
240+
const template = parse(source, {
241+
filename: 'example.vue',
242+
sourceMap: true
243+
}).descriptor.template!
244+
245+
// force set to empty, if this is reused then it won't generate proper code
246+
template.ast!.children = []
247+
template.ast!.transformed = true
248+
249+
const { code } = compile({
250+
filename: 'example.vue',
251+
source: '',
252+
ast: template.ast
253+
})
254+
255+
expect(code).toBe(
256+
compile({
257+
filename: 'example.vue',
258+
source: template.content
259+
}).code
260+
)
261+
})
262+
263+
test('should force re-parse with correct compiler in SSR mode', () => {
264+
const source = `
265+
<template>
266+
<div><p>{{ foobar }}</p></div>
267+
</template>
268+
`
269+
const template = parse(source, {
270+
filename: 'example.vue',
271+
sourceMap: true
272+
}).descriptor.template!
273+
274+
// force set to empty, if this is reused then it won't generate proper code
275+
template.ast!.children = []
276+
template.ast!.transformed = true
277+
278+
const { code } = compile({
279+
filename: 'example.vue',
280+
source: '',
281+
ast: template.ast,
282+
ssr: true
283+
})
284+
285+
expect(code).toBe(
286+
compile({
287+
filename: 'example.vue',
288+
source: template.content,
289+
ssr: true
290+
}).code
291+
)
292+
})
293+
188294
test('template errors', () => {
189295
const result = compile({
190296
filename: 'example.vue',

packages/compiler-sfc/src/compileTemplate.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,10 @@ function doCompileTemplate({
214214
inAST = undefined
215215
}
216216

217-
if (inAST?.codegenNode) {
218-
// input AST has codegenNode - it has already been transformed and cannot
219-
// be reused. We need to parse a fresh one. Can't just use `source` here
220-
// since we need the AST location info to be relative to the entire SFC.
217+
if (inAST?.transformed) {
218+
// If input AST has already been transformed, then it cannot be reused.
219+
// We need to parse a fresh one. Can't just use `source` here since we need
220+
// the AST location info to be relative to the entire SFC.
221221
const newAST = (ssr ? CompilerDOM : compiler).parse(inAST.source, {
222222
parseMode: 'sfc',
223223
onError: e => errors.push(e)

packages/compiler-ssr/src/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
noopDirectiveTransform,
1212
transformBind,
1313
transformStyle,
14-
transformOn
14+
transformOn,
15+
RootNode
1516
} from '@vue/compiler-dom'
1617
import { ssrCodegenTransform } from './ssrCodegenTransform'
1718
import { ssrTransformElement } from './transforms/ssrTransformElement'
@@ -28,12 +29,11 @@ import { ssrInjectFallthroughAttrs } from './transforms/ssrInjectFallthroughAttr
2829
import { ssrInjectCssVars } from './transforms/ssrInjectCssVars'
2930

3031
export function compile(
31-
template: string,
32+
source: string | RootNode,
3233
options: CompilerOptions = {}
3334
): CodegenResult {
3435
options = {
3536
...options,
36-
// apply DOM-specific parsing options
3737
...parserOptions,
3838
ssr: true,
3939
inSSR: true,
@@ -45,7 +45,7 @@ export function compile(
4545
hoistStatic: false
4646
}
4747

48-
const ast = baseParse(template, options)
48+
const ast = typeof source === 'string' ? baseParse(source, options) : source
4949

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

0 commit comments

Comments
 (0)