]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: support reusing template ast from sfc descriptor
authorEvan You <yyx990803@gmail.com>
Mon, 20 Nov 2023 14:05:27 +0000 (22:05 +0800)
committerEvan You <yyx990803@gmail.com>
Sat, 25 Nov 2023 08:18:29 +0000 (16:18 +0800)
packages/compiler-dom/src/index.ts
packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap
packages/compiler-sfc/__tests__/compileTemplate.spec.ts
packages/compiler-sfc/src/compileTemplate.ts
packages/compiler-sfc/src/parse.ts
packages/compiler-sfc/src/script/importUsageCheck.ts

index a2f4aff2e4c397240619ae68c904c286474f4bb2..2fc43b242019b48dfa06ddc14b4a20cfb7eb21c9 100644 (file)
@@ -38,11 +38,11 @@ export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {
 }
 
 export function compile(
-  template: string,
+  src: string | RootNode,
   options: CompilerOptions = {}
 ): CodegenResult {
   return baseCompile(
-    template,
+    src,
     extend({}, parserOptions, options, {
       nodeTransforms: [
         // ignore <script> and <tag>
index f97eef6094ea1b3ef6b3361d7b68abe32680eebc..1106ca7ad4037b7f20134596f11c66a51e236139 100644 (file)
@@ -58,24 +58,6 @@ export function ssrRender(_ctx, _push, _parent, _attrs) {
 }"
 `;
 
-exports[`source map 1`] = `
-{
-  "mappings": ";;;wBACE,oBAA8B;IAAzB,oBAAmB,4BAAbA,WAAM",
-  "names": [
-    "render",
-  ],
-  "sources": [
-    "example.vue",
-  ],
-  "sourcesContent": [
-    "
-  <div><p>{{ render }}</p></div>
-",
-  ],
-  "version": 3,
-}
-`;
-
 exports[`template errors 1`] = `
 [
   [SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
index 5e1867f26ea1c68eb2955df29b5e8181c0534723..9b57c1230627d9703e23800821ab004e0d65160a 100644 (file)
@@ -1,3 +1,4 @@
+import { RawSourceMap, SourceMapConsumer } from 'source-map-js'
 import {
   compileTemplate,
   SFCTemplateCompileOptions
@@ -107,18 +108,54 @@ test('source map', () => {
   const template = parse(
     `
 <template>
-  <div><p>{{ render }}</p></div>
+  <div><p>{{ foobar }}</p></div>
 </template>
 `,
     { filename: 'example.vue', sourceMap: true }
-  ).descriptor.template as SFCTemplateBlock
+  ).descriptor.template!
 
-  const result = compile({
+  const { code, map } = compile({
     filename: 'example.vue',
     source: template.content
   })
 
-  expect(result.map).toMatchSnapshot()
+  expect(map!.sources).toEqual([`example.vue`])
+  expect(map!.sourcesContent).toEqual([template.content])
+
+  const consumer = new SourceMapConsumer(map as RawSourceMap)
+  expect(
+    consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
+  ).toMatchObject(getPositionInCode(template.content, `foobar`))
+})
+
+test('should work w/ AST from descriptor', () => {
+  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: template.content,
+    ast: template.ast
+  })
+
+  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`))
 })
 
 test('template errors', () => {
@@ -199,3 +236,36 @@ test('dynamic v-on + static v-on should merged', () => {
 
   expect(result.code).toMatchSnapshot()
 })
+
+interface Pos {
+  line: number
+  column: number
+  name?: string
+}
+
+function getPositionInCode(
+  code: string,
+  token: string,
+  expectName: string | boolean = false
+): Pos {
+  const generatedOffset = code.indexOf(token)
+  let line = 1
+  let lastNewLinePos = -1
+  for (let i = 0; i < generatedOffset; i++) {
+    if (code.charCodeAt(i) === 10 /* newline char code */) {
+      line++
+      lastNewLinePos = i
+    }
+  }
+  const res: Pos = {
+    line,
+    column:
+      lastNewLinePos === -1
+        ? generatedOffset
+        : generatedOffset - lastNewLinePos - 1
+  }
+  if (expectName) {
+    res.name = typeof expectName === 'string' ? expectName : token
+  }
+  return res
+}
index 8b1adea8e2a24bb394dd2db591dc0ae14d8abf0d..8c5e492fabd7d8a371af501a4122f8cf954306d5 100644 (file)
@@ -30,7 +30,7 @@ import { warnOnce } from './warn'
 import { genCssVarsFromList } from './style/cssVars'
 
 export interface TemplateCompiler {
-  compile(template: string, options: CompilerOptions): CodegenResult
+  compile(source: string | RootNode, options: CompilerOptions): CodegenResult
   parse(template: string, options: ParserOptions): RootNode
 }
 
@@ -46,6 +46,7 @@ export interface SFCTemplateCompileResults {
 
 export interface SFCTemplateCompileOptions {
   source: string
+  ast?: RootNode
   filename: string
   id: string
   scoped?: boolean
@@ -164,6 +165,7 @@ function doCompileTemplate({
   slotted,
   inMap,
   source,
+  ast: inAST,
   ssr = false,
   ssrCssVars,
   isProd = false,
@@ -199,7 +201,7 @@ function doCompileTemplate({
   const shortId = id.replace(/^data-v-/, '')
   const longId = `data-v-${shortId}`
 
-  let { code, ast, preamble, map } = compiler.compile(source, {
+  let { code, ast, preamble, map } = compiler.compile(inAST || source, {
     mode: 'module',
     prefixIdentifiers: true,
     hoistStatic: true,
@@ -235,7 +237,7 @@ function doCompileTemplate({
     let msg = w.message
     if (w.loc) {
       msg += `\n${generateCodeFrame(
-        source,
+        inAST?.source || source,
         w.loc.start.offset,
         w.loc.end.offset
       )}`
index 31087394a370640b81b21868e3f85efa6c90386a..22fde21550a85131e19879b6a87c243a2c058b9a 100644 (file)
@@ -3,7 +3,9 @@ import {
   ElementNode,
   SourceLocation,
   CompilerError,
-  BindingMetadata
+  BindingMetadata,
+  RootNode,
+  createRoot
 } from '@vue/compiler-core'
 import * as CompilerDOM from '@vue/compiler-dom'
 import { RawSourceMap, SourceMapGenerator } from 'source-map-js'
@@ -36,7 +38,7 @@ export interface SFCBlock {
 
 export interface SFCTemplateBlock extends SFCBlock {
   type: 'template'
-  ast: ElementNode
+  ast: RootNode
 }
 
 export interface SFCScriptBlock extends SFCBlock {
@@ -154,7 +156,7 @@ export function parse(
             source,
             false
           ) as SFCTemplateBlock)
-          templateBlock.ast = node
+          templateBlock.ast = createRoot(node.children, source)
 
           // warn against 2.x <template functional>
           if (templateBlock.attrs.functional) {
@@ -243,7 +245,8 @@ export function parse(
         )
       }
     }
-    genMap(descriptor.template)
+    // no need to genMap for template as its AST already accounts for the
+    // position in the SFC
     genMap(descriptor.script)
     descriptor.styles.forEach(genMap)
     descriptor.customBlocks.forEach(genMap)
index 34e95f1918b40decd1735a9a64710e94e6165f2a..c3c51185c5645bb48788900e0d32f4168ae04dc1 100644 (file)
@@ -3,7 +3,6 @@ import { SFCDescriptor } from '../parse'
 import {
   NodeTypes,
   SimpleExpressionNode,
-  createRoot,
   forAliasRE,
   parserOptions,
   transform,
@@ -35,7 +34,7 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
   }
 
   let code = ''
-  transform(createRoot([ast]), {
+  transform(ast, {
     nodeTransforms: [
       node => {
         if (node.type === NodeTypes.ELEMENT) {