]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): fix ast reuse for ssr
authorEvan You <yyx990803@gmail.com>
Mon, 27 Nov 2023 07:38:30 +0000 (15:38 +0800)
committerEvan You <yyx990803@gmail.com>
Mon, 27 Nov 2023 07:38:30 +0000 (15:38 +0800)
packages/compiler-core/src/ast.ts
packages/compiler-core/src/transform.ts
packages/compiler-sfc/__tests__/compileTemplate.spec.ts
packages/compiler-sfc/src/compileTemplate.ts
packages/compiler-ssr/src/index.ts

index 117fef835aaa8240b5520b86f07a437045ae91ea..05c187feec0db82b7f23ec92ceee71e724601c7f 100644 (file)
@@ -113,6 +113,7 @@ export interface RootNode extends Node {
   temps: number
   ssrHelpers?: symbol[]
   codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
+  transformed?: boolean
 
   // v2 compat only
   filters?: string[]
index 2053e26ce92652dc6b6a9f626f4e66e7bfcec1ad..f128b3b6e9df0275e4f74a882e5e71b2af610b64 100644 (file)
@@ -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!]
index 3b74dd66ada11762d2fa44ba32948f2a4cd7bf37..29be21da89a83750178e3935dd1d6de0e8050673 100644 (file)
@@ -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',
index 1afdc74bf22a0e3aff40695365ee3bedbf5182bb..e5f38c5baca6f2ced31c31d4274f10bc29403038 100644 (file)
@@ -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)
index 1e5f9055064906cab459c27be7d1857fcfa1f846..cae8cf711a8a40bbfd420736f0503b223687934f 100644 (file)
@@ -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.