Просмотр исходного кода

fix(compiler-vapor): preserve v-for selector source offsets (#14816)

edison 1 месяц назад
Родитель
Сommit
211caf8f92

+ 18 - 0
packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap

@@ -332,6 +332,24 @@ exports[`compile > expression parsing > interpolation 1`] = `
 "
 `;
 
+exports[`compile > expression parsing > keeps member selector source offsets after prefixing 1`] = `
+"import { createSelector as _createSelector, setClassName as _setClassName, createFor as _createFor, template as _template } from 'vue';
+const t0 = _template("<tr>")
+
+export function render(_ctx) {
+  const _selector0 = _createSelector(() => _ctx.state.selected)
+  const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
+    const n2 = t0()
+    _selector0(_for_item0.value.id, () => {
+      _setClassName(n2, (_for_item0.value.id === _ctx.state.selected ? 1 : 0), "danger")
+    })
+    return n2
+  }, (row) => (row.id))
+  n0.onReset(_selector0.reset)
+  return n0
+}"
+`;
+
 exports[`compile > expression parsing > v-bind 1`] = `
 "
   const n0 = t0()

+ 14 - 0
packages/compiler-vapor/__tests__/compile.spec.ts

@@ -215,6 +215,20 @@ describe('compile', () => {
       })
       expect(code).matchSnapshot()
     })
+
+    test('keeps member selector source offsets after prefixing', () => {
+      const code = compile(
+        `<tr
+          v-for="row in rows"
+          :key="row.id"
+          :class="row.id === state.selected ? 'danger' : ''"
+        ></tr>`,
+      )
+      expect(code).matchSnapshot()
+      expect(code).contains(
+        `const _selector0 = _createSelector(() => _ctx.state.selected)`,
+      )
+    })
   })
 
   describe('custom directive', () => {

+ 18 - 0
packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap

@@ -62,6 +62,24 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`compiler: v-for > key only binding pattern 2`] = `
+"import { createSelector as _createSelector, setClassName as _setClassName, createFor as _createFor, template as _template } from 'vue';
+const t0 = _template("<tr>")
+
+export function render(_ctx) {
+  const _selector0 = _createSelector(() => _ctx.state.selected)
+  const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
+    const n2 = t0()
+    _selector0(_for_item0.value.id, () => {
+      _setClassName(n2, (_for_item0.value.id === _ctx.state.selected ? 1 : 0), "danger")
+    })
+    return n2
+  }, (row) => (row.id))
+  n0.onReset(_selector0.reset)
+  return n0
+}"
+`;
+
 exports[`compiler: v-for > multi className helper with repeated v-for value 1`] = `
 "import { setClassName as _setClassName, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
 const t0 = _template("<div>")

+ 14 - 0
packages/compiler-vapor/__tests__/transforms/vFor.spec.ts

@@ -80,6 +80,20 @@ describe('compiler: v-for', () => {
       `,
       ).code,
     ).matchSnapshot()
+
+    const reverseMemberSelector = compileWithVFor(
+      `
+          <tr
+            v-for="row of rows"
+            :key="row.id"
+            :class="row.id === state.selected ? 'danger' : ''"
+          ></tr>
+      `,
+    ).code
+    expect(reverseMemberSelector).matchSnapshot()
+    expect(reverseMemberSelector).contains(
+      `const _selector0 = _createSelector(() => _ctx.state.selected)`,
+    )
   })
 
   test('selector pattern', () => {

+ 11 - 7
packages/compiler-vapor/src/generators/expression.ts

@@ -19,8 +19,13 @@ import {
 import type { Identifier, Node } from '@babel/types'
 import type { CodegenContext } from '../generate'
 import { isConstantExpression } from '../utils'
-import { type CodeFragment, NEWLINE, buildCodeFragment } from './utils'
-import { type ParserOptions, parseExpression } from '@babel/parser'
+import {
+  type CodeFragment,
+  NEWLINE,
+  buildCodeFragment,
+  getParserOptions,
+} from './utils'
+import { parseExpression } from '@babel/parser'
 
 export function genExpression(
   node: SimpleExpressionNode,
@@ -669,11 +674,10 @@ function escapeRegExp(string: string) {
 }
 
 function parseExp(context: CodegenContext, content: string): Node {
-  const plugins = context.options.expressionPlugins
-  const options: ParserOptions = {
-    plugins: plugins ? [...plugins, 'typescript'] : ['typescript'],
-  }
-  return parseExpression(`(${content})`, options)
+  return parseExpression(
+    `(${content})`,
+    getParserOptions(context.options.expressionPlugins),
+  )
 }
 
 export function genVarName(exp: string): string {

+ 22 - 15
packages/compiler-vapor/src/generators/for.ts

@@ -20,12 +20,13 @@ import {
   NEWLINE,
   genCall,
   genMulti,
+  getParserOptions,
 } from './utils'
 import type { Expression, Identifier, Node } from '@babel/types'
 import { parseExpression } from '@babel/parser'
 import { walk } from 'estree-walker'
 import { genOperation } from './operation'
-import { VaporVForFlags, extend, isGloballyAllowed } from '@vue/shared'
+import { VaporVForFlags, isGloballyAllowed } from '@vue/shared'
 
 export function genFor(
   oper: ForIRNode,
@@ -79,6 +80,7 @@ export function genFor(
     render,
     keyProp,
     idMap,
+    context,
   )
   const selectorDeclarations: CodeFragment[] = []
   const selectorName = (i: number) =>
@@ -315,9 +317,7 @@ export function buildDestructureIdMap(
 
       if (pathInfo.dynamic) {
         const node = (idMap[id] = createSimpleExpression(path))
-        node.ast = parseExpression(`(${path})`, {
-          plugins: plugins ? [...plugins, 'typescript'] : ['typescript'],
-        })
+        node.ast = parseExpression(`(${path})`, getParserOptions(plugins))
       } else {
         idMap[id] = path
       }
@@ -332,6 +332,7 @@ function matchPatterns(
   render: BlockIRNode,
   keyProp: SimpleExpressionNode | undefined,
   idMap: Record<string, string | SimpleExpressionNode | null>,
+  context: CodegenContext,
 ) {
   const selectorPatterns: NonNullable<
     ReturnType<typeof matchSelectorPattern>
@@ -343,7 +344,12 @@ function matchPatterns(
 
   render.effect = render.effect.filter((effect, index) => {
     if (keyProp !== undefined) {
-      const selector = matchSelectorPattern(effect, keyProp.content, idMap)
+      const selector = matchSelectorPattern(
+        effect,
+        keyProp.content,
+        idMap,
+        context,
+      )
       if (selector) {
         selectorPatterns.push(selector)
         removedEffectIndexes.push(index)
@@ -419,6 +425,7 @@ function matchSelectorPattern(
   effect: IREffect,
   key: string,
   idMap: Record<string, string | SimpleExpressionNode | null>,
+  context: CodegenContext,
 ):
   | {
       effect: IREffect
@@ -473,18 +480,18 @@ function matchSelectorPattern(
 
         if (!hasExtraId) {
           const name = content.slice(selector.start! - 1, selector.end! - 1)
+          const selectorExpression = createSimpleExpression(
+            name,
+            false,
+            selector.loc as any,
+          )
+          selectorExpression.ast = parseExpression(
+            `(${name})`,
+            getParserOptions(context.options.expressionPlugins),
+          )
           return {
             effect,
-            // @ts-expect-error
-            selector: {
-              content: name,
-              ast: extend({}, selector, {
-                start: 1,
-                end: name.length + 1,
-              }),
-              loc: selector.loc as any,
-              isStatic: false,
-            },
+            selector: selectorExpression,
           }
         }
       }

+ 6 - 13
packages/compiler-vapor/src/generators/prop.ts

@@ -21,6 +21,7 @@ import {
   NEWLINE,
   genCall,
   genMulti,
+  getParserOptions,
 } from './utils'
 import {
   camelize,
@@ -33,7 +34,7 @@ import {
   toHandlerKey,
 } from '@vue/shared'
 import { getLiteralExpressionValue } from '../utils'
-import { type ParserOptions, parseExpression } from '@babel/parser'
+import { parseExpression } from '@babel/parser'
 import type {
   ConditionalExpression,
   Expression,
@@ -323,21 +324,13 @@ function createSubExpression(
   })
   expression.ast = isSimpleIdentifier(content)
     ? null
-    : parseExpression(`(${content})`, getParserOptions(context))
+    : parseExpression(
+        `(${content})`,
+        getParserOptions(context.options.expressionPlugins),
+      )
   return expression
 }
 
-function getParserOptions(context: CodegenContext): ParserOptions {
-  const plugins = context.options.expressionPlugins
-  return {
-    plugins: plugins
-      ? plugins.some(plugin => plugin === 'typescript')
-        ? plugins
-        : [...plugins, 'typescript']
-      : ['typescript'],
-  }
-}
-
 // dynamic key props and v-bind="{}" will reach here
 export function genDynamicProps(
   oper: SetDynamicPropsIRNode,

+ 13 - 0
packages/compiler-vapor/src/generators/utils.ts

@@ -9,6 +9,7 @@ import {
 } from '@vue/compiler-dom'
 import { isArray, isString } from '@vue/shared'
 import type { CodegenContext } from '../generate'
+import type { ParserOptions } from '@babel/parser'
 
 export const IMPORT_EXP_START = '__IMPORT_EXP_START__'
 export const IMPORT_EXP_END = '__IMPORT_EXP_END__'
@@ -105,6 +106,18 @@ export function genCall(
   return [fnName, ...genMulti(['(', ')', ', ', placeholder], ...frags)]
 }
 
+export function getParserOptions(
+  plugins: CodegenContext['options']['expressionPlugins'],
+): ParserOptions {
+  return {
+    plugins: plugins
+      ? plugins.some(plugin => plugin === 'typescript')
+        ? plugins
+        : [...plugins, 'typescript']
+      : ['typescript'],
+  }
+}
+
 export function codeFragmentToString(
   code: CodeFragment[],
   context: CodegenContext,

+ 0 - 4
packages/runtime-vapor/src/apiCreateFor.ts

@@ -122,10 +122,6 @@ export const createFor = (
       if (newBlocks && newBlocks !== oldBlocks) {
         stopBlockScopes(newBlocks)
       }
-      for (const selector of selectors) {
-        selector.cleanup()
-      }
-      selectors.length = 0
       oldBlocks = []
       newBlocks = []
     }, true)