]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(vapor): support rest elements in v-for destructure
authorEvan You <evan@vuejs.org>
Thu, 30 Jan 2025 13:15:25 +0000 (21:15 +0800)
committerEvan You <evan@vuejs.org>
Thu, 30 Jan 2025 13:15:25 +0000 (21:15 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/vFor.spec.ts
packages/compiler-vapor/src/generators/for.ts
packages/runtime-vapor/src/apiCreateFor.ts
packages/runtime-vapor/src/index.ts

index d67038943bee732a6cfe60703d8a555498d41d10..93be46fc7edf28632f48ec9acf49d15900e4e671 100644 (file)
@@ -1,5 +1,19 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
+exports[`compiler: v-for > array de-structured value (with rest) 1`] = `
+"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
+const t0 = _template("<div></div>", true)
+
+export function render(_ctx) {
+  const n0 = _createFor(() => (_ctx.list), (_ctx0) => {
+    const n2 = t0()
+    _renderEffect(() => _setText(n2, _ctx0[0].value[0] + _ctx0[0].value.slice(1) + _ctx0[1].value))
+    return n2
+  }, ([id, ...other], index) => (id))
+  return n0
+}"
+`;
+
 exports[`compiler: v-for > array de-structured value 1`] = `
 "import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
 const t0 = _template("<div></div>", true)
@@ -30,20 +44,6 @@ export function render(_ctx) {
 }"
 `;
 
-exports[`compiler: v-for > function params w/ prefixIdentifiers: false 1`] = `
-"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("<div></div>", true)
-
-export function render(_ctx) {
-  const n0 = _createFor(() => (items), ([item, __, k]) => {
-    const n2 = t0()
-    _renderEffect(() => _setText(n2, item))
-    return n2
-  }, (item, __, k) => (k))
-  return n0
-}"
-`;
-
 exports[`compiler: v-for > multi effect 1`] = `
 "import { setProp as _setProp, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
 const t0 = _template("<div></div>", true)
@@ -82,13 +82,13 @@ export function render(_ctx) {
 `;
 
 exports[`compiler: v-for > object de-structured value (with rest) 1`] = `
-"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
+"import { getRestElement as _getRestElement, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
 const t0 = _template("<div></div>", true)
 
 export function render(_ctx) {
   const n0 = _createFor(() => (_ctx.list), (_ctx0) => {
     const n2 = t0()
-    _renderEffect(() => _setText(n2, _ctx0[0].value.id + _ctx0[0].value + _ctx0[1].value))
+    _renderEffect(() => _setText(n2, _ctx0[0].value.id + _getRestElement(_ctx0[0].value, ["id"]) + _ctx0[1].value))
     return n2
   }, ({ id, ...other }, index) => (id))
   return n0
@@ -109,34 +109,6 @@ export function render(_ctx) {
 }"
 `;
 
-exports[`compiler: v-for > object de-structured value 2`] = `
-"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("<div></div>", true)
-
-export function render(_ctx) {
-  const n0 = _createFor(() => (_ctx.list), (_ctx0) => {
-    const n2 = t0()
-    _renderEffect(() => _setText(n2, _ctx0[0].value.id + _ctx0[0].value + _ctx0[1].value))
-    return n2
-  }, ({ id, ...other }, index) => (id))
-  return n0
-}"
-`;
-
-exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = `
-"import { setText as _setText, renderEffect as _renderEffect, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("<div></div>", true)
-
-export function render(_ctx) {
-  const n0 = _createFor(() => (_ctx.list), _withDestructure(([{ foo = bar, baz: [qux = quux] }]) => [foo, qux], (_ctx0) => {
-    const n2 = t0()
-    _renderEffect(() => _setText(n2, _ctx0[0] + _ctx.bar + _ctx.baz + _ctx0[1] + _ctx.quux))
-    return n2
-  }))
-  return n0
-}"
-`;
-
 exports[`compiler: v-for > w/o value 1`] = `
 "import { createFor as _createFor, template as _template } from 'vue';
 const t0 = _template("<div>item</div>", true)
index e14380d766668c205b0a88b68964df3f29051d7c..59998d844ba8325aa4e60cf18d609664f6a3f8a8 100644 (file)
@@ -144,11 +144,12 @@ describe('compiler: v-for', () => {
     })
   })
 
-  test.todo('object de-structured value (with rest)', () => {
+  test('object de-structured value (with rest)', () => {
     const { code, ir } = compileWithVFor(
       `<div v-for="(  { id, ...other }, index) in list" :key="id">{{ id + other + index }}</div>`,
     )
     expect(code).matchSnapshot()
+    expect(code).toContain('_getRestElement(_ctx0[0].value, ["id"])')
     expect(ir.block.operation[0]).toMatchObject({
       type: IRNodeTypes.FOR,
       source: {
@@ -206,11 +207,12 @@ describe('compiler: v-for', () => {
     })
   })
 
-  test.todo('array de-structured value (with rest)', () => {
+  test('array de-structured value (with rest)', () => {
     const { code, ir } = compileWithVFor(
       `<div v-for="([id, ...other], index) in list" :key="id">{{ id + other + index }}</div>`,
     )
     expect(code).matchSnapshot()
+    expect(code).toContain('_ctx0[0].value.slice(1)')
     expect(ir.block.operation[0]).toMatchObject({
       type: IRNodeTypes.FOR,
       source: {
index af429c0e673d4c596fc07f00ed976f9c8e4ef66a..1d2b379e7b7922082d5823d4437cb2910ccc2889 100644 (file)
@@ -31,13 +31,20 @@ export function genFor(
   const idMap: Record<string, string | SimpleExpressionNode | null> = {}
 
   idToPathMap.forEach((pathInfo, id) => {
-    const path = `${propsName}[0].value${pathInfo ? pathInfo.path : ''}`
-    if (pathInfo && pathInfo.dynamic) {
-      const node = (idMap[id] = createSimpleExpression(path))
-      const plugins = context.options.expressionPlugins
-      node.ast = parseExpression(`(${path})`, {
-        plugins: plugins ? [...plugins, 'typescript'] : ['typescript'],
-      })
+    let path = `${propsName}[0].value${pathInfo ? pathInfo.path : ''}`
+    if (pathInfo) {
+      if (pathInfo.helper) {
+        path = `${pathInfo.helper}(${path}, ${pathInfo.helperArgs})`
+      }
+      if (pathInfo.dynamic) {
+        const node = (idMap[id] = createSimpleExpression(path))
+        const plugins = context.options.expressionPlugins
+        node.ast = parseExpression(`(${path})`, {
+          plugins: plugins ? [...plugins, 'typescript'] : ['typescript'],
+        })
+      } else {
+        idMap[id] = path
+      }
     } else {
       idMap[id] = path
     }
@@ -68,7 +75,15 @@ export function genFor(
   // construct a id -> accessor path map.
   // e.g. `{ x: { y: [z] }}` -> `Map{ 'z' => '.x.y[0]' }`
   function parseValueDestructure() {
-    const map = new Map<string, { path: string; dynamic: boolean } | null>()
+    const map = new Map<
+      string,
+      {
+        path: string
+        dynamic: boolean
+        helper?: string
+        helperArgs?: string
+      } | null
+    >()
     if (value) {
       rawValue = value && value.content
       if (value.ast) {
@@ -78,32 +93,62 @@ export function genFor(
             if (isLocal) {
               let path = ''
               let isDynamic = false
+              let helper
+              let helperArgs
               for (let i = 0; i < parentStack.length; i++) {
                 const parent = parentStack[i]
                 const child = parentStack[i + 1] || id
+
                 if (
                   parent.type === 'ObjectProperty' &&
                   parent.value === child
                 ) {
-                  if (parent.computed && parent.key.type !== 'StringLiteral') {
+                  if (parent.key.type === 'StringLiteral') {
+                    path += `[${JSON.stringify(parent.key.value)}]`
+                  } else if (parent.computed) {
                     isDynamic = true
                     path += `[${value.content.slice(
                       parent.key.start! - 1,
                       parent.key.end! - 1,
                     )}]`
-                  } else if (parent.key.type === 'StringLiteral') {
-                    path += `[${JSON.stringify(parent.key.value)}]`
                   } else {
                     // non-computed, can only be identifier
                     path += `.${(parent.key as Identifier).name}`
                   }
                 } else if (parent.type === 'ArrayPattern') {
                   const index = parent.elements.indexOf(child as any)
-                  path += `[${index}]`
+                  if (child.type === 'RestElement') {
+                    path += `.slice(${index})`
+                  } else {
+                    path += `[${index}]`
+                  }
+                } else if (
+                  parent.type === 'ObjectPattern' &&
+                  child.type === 'RestElement'
+                ) {
+                  helper = context.helper('getRestElement')
+                  helperArgs =
+                    '[' +
+                    parent.properties
+                      .filter(p => p.type === 'ObjectProperty')
+                      .map(p => {
+                        if (p.key.type === 'StringLiteral') {
+                          return JSON.stringify(p.key.value)
+                        } else if (p.computed) {
+                          isDynamic = true
+                          return value.content.slice(
+                            p.key.start! - 1,
+                            p.key.end! - 1,
+                          )
+                        } else {
+                          return JSON.stringify((p.key as Identifier).name)
+                        }
+                      })
+                      .join(', ') +
+                    ']'
                 }
-                // TODO handle rest spread
               }
-              map.set(id.name, { path, dynamic: isDynamic })
+              map.set(id.name, { path, dynamic: isDynamic, helper, helperArgs })
             }
           },
           true,
@@ -115,7 +160,6 @@ export function genFor(
     return map
   }
 
-  // TODO this should be looked at for destructure cases
   function genCallback(expr: SimpleExpressionNode | undefined) {
     if (!expr) return false
     const res = context.withId(
index 2f27fe6b5e961eff0434e6932df2fefe89446240..ceff769669ee7a62f9e5bb9f2e23be79990b04aa 100644 (file)
@@ -380,3 +380,12 @@ function normalizeAnchor(node: Block): Node {
     return normalizeAnchor(node.nodes!)
   }
 }
+
+// runtime helper for rest element destructure
+export function getRestElement(val: any, keys: string[]): any {
+  const res: any = {}
+  for (const key in val) {
+    if (!keys.includes(key)) res[key] = val[key]
+  }
+  return res
+}
index 4646f4ebec8260ce31d488271130e93260a46db1..8e4679a33498eef4b0d256b67637f7f94c10b642 100644 (file)
@@ -22,5 +22,5 @@ export {
 } from './dom/prop'
 export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
 export { createIf } from './apiCreateIf'
-export { createFor, createForSlots } from './apiCreateFor'
+export { createFor, createForSlots, getRestElement } from './apiCreateFor'
 export { createTemplateRefSetter } from './apiTemplateRef'