// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-for > basic v-for 1`] = `
-"import { template as _template, fragment as _fragment, renderEffect as _renderEffect, children as _children, on as _on, setText as _setText, createFor as _createFor, append as _append } from 'vue/vapor';
+"import { template as _template, fragment as _fragment, children as _children, on as _on, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, append as _append } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
const t1 = _fragment()
const n0 = t1()
const n1 = _createFor(() => (_ctx.items), (_block) => {
- let item
- _renderEffect(() => {
- ([item] = _block.s);
- })
const n2 = t0()
const { 0: [n3],} = _children(n2)
- _on(n3, "click", $event => (_ctx.remove(item)))
- _renderEffect(() => {
+ _on(n3, "click", $event => (_ctx.remove(_block.s[0])))
+ const _updateEffect = () => {
+ const [item] = _block.s
_setText(n3, item)
- })
- return n2
+ }
+ _renderEffect(_updateEffect)
+ return [n2, _updateEffect]
})
_append(n0, n1)
return n0
}"
`;
-exports[`compiler: v-for > no value 1`] = `
+exports[`compiler: v-for > basic v-for 2`] = `
"import { template as _template, fragment as _fragment, createFor as _createFor, append as _append } from 'vue/vapor';
export function render(_ctx) {
const n0 = t1()
const n1 = _createFor(() => (_ctx.items), (_block) => {
const n2 = t0()
- return n2
- })
- _append(n0, n1)
- return n0
-}"
-`;
-
-exports[`compiler: v-for > object de-structured value 1`] = `
-"import { template as _template, fragment as _fragment, renderEffect as _renderEffect, children as _children, createTextNode as _createTextNode, append as _append, setText as _setText, createFor as _createFor } from 'vue/vapor';
-
-export function render(_ctx) {
- const t0 = _template("<span></span>")
- const t1 = _fragment()
- const n0 = t1()
- const n1 = _createFor(() => (_ctx.items), (_block) => {
- let id, value
- _renderEffect(() => {
- ([{ id, value }] = _block.s);
- })
- const n2 = t0()
- const { 0: [n5],} = _children(n2)
- const n3 = _createTextNode()
- const n4 = _createTextNode()
- _append(n5, n3, n4)
- _renderEffect(() => {
- _setText(n3, id)
- })
- _renderEffect(() => {
- _setText(n4, value)
- })
- return n2
+ return [n2, () => {}]
})
_append(n0, n1)
return n0
expect((ir.operation[0] as ForIRNode).render.effect).lengthOf(1)
})
- test('no value', () => {
+ test('basic v-for', () => {
const { code } = compileWithVFor(`<div v-for=" of items">item</div>`)
expect(code).matchSnapshot()
})
-
- test('object de-structured value', () => {
- const { code } = compileWithVFor(
- '<span v-for="({ id, value }) in items">{{ id }}{{ value }}</span>',
- )
- expect(code).matchSnapshot()
- })
})
advancePositionWithMutation,
locStub,
} from '@vue/compiler-dom'
-import type { RootIRNode, VaporHelper } from './ir'
+import type { IREffect, RootIRNode, VaporHelper } from './ir'
import { SourceMapGenerator } from 'source-map-js'
-import { extend, isString } from '@vue/shared'
+import { extend, isString, remove } from '@vue/shared'
import type { ParserPlugin } from '@babel/parser'
import { genTemplate } from './generators/template'
import { genBlockFunctionContent } from './generators/block'
return `_${name}`
}
- identifiers: Record<string, number> = Object.create(null)
- withId = <T>(fn: () => T, ids: string[]): T => {
+ identifiers: Record<string, string[]> = Object.create(null)
+ withId = <T>(fn: () => T, map: Record<string, string | null>): T => {
const { identifiers } = this
+ const ids = Object.keys(map)
+
for (const id of ids) {
- if (identifiers[id] === undefined) identifiers[id] = 0
- identifiers[id]!++
+ identifiers[id] ||= []
+ identifiers[id].unshift(map[id] || id)
}
const ret = fn()
- ids.forEach(id => identifiers[id]!--)
+ ids.forEach(id => remove(identifiers[id], map[id] || id))
return ret
}
+ genEffect?: (effects: IREffect[]) => CodeFragment[]
constructor(
public ir: RootIRNode,
oper: BlockFunctionIRNode,
context: CodegenContext,
args: CodeFragment[] = [],
- preamble: CodeFragment[] = [],
- returnValue?: CodeFragment[],
+ returnValue?: () => CodeFragment[],
): CodeFragment[] {
return [
'(',
...args,
') => {',
INDENT_START,
- ...preamble,
...genBlockFunctionContent(oper, context, returnValue),
INDENT_END,
NEWLINE,
export function genBlockFunctionContent(
ir: BlockFunctionIRNode | RootIRNode,
context: CodegenContext,
- returnValue?: CodeFragment[],
+ returnValue?: () => CodeFragment[],
): CodeFragment[] {
const { vaporHelper } = context
const [frag, push] = buildCodeFragment(
push(...genOperations(ir.operation, context))
push(...genEffects(ir.effect, context))
- push(NEWLINE, 'return ', ...(returnValue || [`n${ir.dynamic.id}`]))
+ push(
+ NEWLINE,
+ 'return ',
+ ...(returnValue ? returnValue() : [`n${ir.dynamic.id}`]),
+ )
return frag
}
const hasMultipleStatements = exp.content.includes(`;`)
if (isInlineStatement) {
- const expr = context.withId(
- () => genExpression(exp, context),
- ['$event'],
- )
+ const expr = context.withId(() => genExpression(exp, context), {
+ $event: null,
+ })
return [
'$event => ',
hasMultipleStatements ? '{' : '(',
const { inline, bindingMetadata } = options
let name: string | undefined = id
- if (identifiers[id]) {
- return [id, NewlineType.None, loc]
+ const idMap = identifiers[id]
+ if (idMap && idMap.length) {
+ return [idMap[0], NewlineType.None, loc]
}
if (inline) {
INDENT_END,
INDENT_START,
NEWLINE,
+ buildCodeFragment,
} from '../generate'
-import type { ForIRNode } from '../ir'
-import {
- NewlineType,
- type SimpleExpressionNode,
- walkIdentifiers,
-} from '@vue/compiler-dom'
-import type { ArrowFunctionExpression } from '@babel/types'
+import type { ForIRNode, IREffect } from '../ir'
+import { genOperations } from './operation'
+import { NewlineType } from '@vue/compiler-dom'
export function genFor(
oper: ForIRNode,
const { call, vaporHelper } = context
const { source, value, key, render } = oper
+ const rawValue = value && value.content
const rawKey = key && key.content
+
const sourceExpr = ['() => (', ...genExpression(source, context), ')']
- const valueIds = value ? extractParams(value) : new Set<string>()
- const keyIds = key ? extractParams(key) : new Set<string>()
- const ids = [...valueIds, ...keyIds]
+ let updateFn = '_updateEffect'
+ context.genEffect = genEffectInFor
- let preamble: CodeFragment[] = []
- if (value || rawKey) {
- const assignment: CodeFragment[] = ['let ', ids.join(', ')]
+ const idMap: Record<string, string> = {}
+ if (rawValue) idMap[rawValue] = `_block.s[0]`
+ if (rawKey) idMap[rawKey] = `_block.s[1]`
- preamble = [
- NEWLINE,
- ...assignment,
- NEWLINE,
- ...call(vaporHelper('renderEffect'), [
- '() => {',
- INDENT_START,
- NEWLINE,
- '(',
- '[',
- value && [value.content, NewlineType.None, value.loc],
- rawKey && ', ',
- rawKey && [rawKey, NewlineType.None, key.loc],
- '] = _block.s',
- ');',
- INDENT_END,
- NEWLINE,
- '}',
- ]),
- ]
- }
+ const blockRet = (): CodeFragment[] => [
+ `[n${render.dynamic.id!}, ${updateFn}]`,
+ ]
- const blockRet: CodeFragment[] = [`n${render.dynamic.id!}`]
const blockFn = context.withId(
- () => genBlockFunction(render, context, ['_block'], preamble, blockRet),
- ids,
+ () => genBlockFunction(render, context, ['_block'], blockRet),
+ idMap,
)
+ context.genEffect = undefined
+
return [
NEWLINE,
`const n${oper.id} = `,
...call(vaporHelper('createFor'), sourceExpr, blockFn),
]
-}
-function extractParams(node: SimpleExpressionNode) {
- const ids = new Set<string>()
- if (node.ast === null || node.ast === false) {
- ids.add(node.content)
- } else {
- walkIdentifiers(
- node.ast as ArrowFunctionExpression,
- (id, parent, parentStack, isReference, isLocal) => {
- if (isLocal) ids.add(id.name)
- },
- true,
- )
+ function genEffectInFor(effects: IREffect[]): CodeFragment[] {
+ if (!effects.length) {
+ updateFn = '() => {}'
+ return []
+ }
+
+ const [frag, push] = buildCodeFragment(INDENT_START)
+ // const [value, key] = _block.s
+ if (rawValue || rawKey) {
+ push(
+ NEWLINE,
+ 'const ',
+ '[',
+ rawValue && [rawValue, NewlineType.None, value.loc],
+ rawKey && ', ',
+ rawKey && [rawKey, NewlineType.None, key.loc],
+ '] = _block.s',
+ )
+ }
+
+ const idMap: Record<string, string | null> = {}
+ if (value) idMap[value.content] = null
+ if (key) idMap[key.content] = null
+ context.withId(() => {
+ effects.forEach(effect =>
+ push(...genOperations(effect.operations, context)),
+ )
+ }, idMap)
+
+ push(INDENT_END)
+
+ return [
+ NEWLINE,
+ `const ${updateFn} = () => {`,
+ ...frag,
+ NEWLINE,
+ '}',
+ NEWLINE,
+ `${vaporHelper('renderEffect')}(${updateFn})`,
+ ]
}
- return ids
}
return frag
}
-export function genOperation(
+function genOperation(
oper: OperationNode,
context: CodegenContext,
): CodeFragment[] {
}
export function genEffects(effects: IREffect[], context: CodegenContext) {
+ if (context.genEffect) {
+ return context.genEffect(effects)
+ }
const [frag, push] = buildCodeFragment()
for (const effect of effects) {
push(...genEffect(effect, context))
return frag
}
-export function genEffect({ operations }: IREffect, context: CodegenContext) {
+function genEffect({ operations }: IREffect, context: CodegenContext) {
const { vaporHelper } = context
const [frag, push] = buildCodeFragment(
NEWLINE,
export const createFor = (
src: () => any[] | Record<string, string> | Set<any> | Map<any, any>,
- renderItem: (block: ForBlock) => Block,
+ renderItem: (block: ForBlock) => [Block, () => void],
getKey: ((item: any, index: number) => any) | null,
getMemo?: (item: any) => any[],
hydrationNode?: Node,
memo: getMemo && getMemo(item),
[fragmentKey]: true,
})
- block.nodes = scope.run(() => renderItem(block))!
- block.update = () => scope.effects.forEach(effect => effect.run())
+ const res = scope.run(() => renderItem(block))!
+ block.nodes = res[0]
+ block.update = res[1]
if (getMemo) block.update()
if (parent) insert(block.nodes, parent, anchor)
return block