Przeglądaj źródła

fix(compiler-sfc): fix ast reuse for ssr

Evan You 2 lat temu
rodzic
commit
fb619cf9a4

+ 1 - 0
packages/compiler-core/src/ast.ts

@@ -113,6 +113,7 @@ export interface RootNode extends Node {
   temps: number
   ssrHelpers?: symbol[]
   codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
+  transformed?: boolean
 
   // v2 compat only
   filters?: string[]

+ 1 - 0
packages/compiler-core/src/transform.ts

@@ -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!]

+ 106 - 0
packages/compiler-sfc/__tests__/compileTemplate.spec.ts

@@ -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', () => {
@@ -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',

+ 4 - 4
packages/compiler-sfc/src/compileTemplate.ts

@@ -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)

+ 4 - 4
packages/compiler-ssr/src/index.ts

@@ -11,7 +11,8 @@ import {
   noopDirectiveTransform,
   transformBind,
   transformStyle,
-  transformOn
+  transformOn,
+  RootNode
 } from '@vue/compiler-dom'
 import { ssrCodegenTransform } from './ssrCodegenTransform'
 import { ssrTransformElement } from './transforms/ssrTransformElement'
@@ -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,
@@ -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.