mirror of https://github.com/vuejs/core.git
refactor: id rewrite of vapor v-for
This commit is contained in:
parent
757af933dc
commit
31f497b1d1
|
@ -1,29 +1,31 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`compiler: v-for > basic v-for 1`] = `
|
||||
"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';
|
||||
"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';
|
||||
|
||||
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(_block.s[0])))
|
||||
const _updateEffect = () => {
|
||||
const [item] = _block.s
|
||||
_on(n3, "click", $event => (_ctx.remove(item)))
|
||||
_renderEffect(() => {
|
||||
_setText(n3, item)
|
||||
}
|
||||
_renderEffect(_updateEffect)
|
||||
return [n2, _updateEffect]
|
||||
})
|
||||
return n2
|
||||
})
|
||||
_append(n0, n1)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > basic v-for 2`] = `
|
||||
exports[`compiler: v-for > no value 1`] = `
|
||||
"import { template as _template, fragment as _fragment, createFor as _createFor, append as _append } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
|
@ -32,7 +34,37 @@ export function render(_ctx) {
|
|||
const n0 = t1()
|
||||
const n1 = _createFor(() => (_ctx.items), (_block) => {
|
||||
const n2 = t0()
|
||||
return [n2, () => {}]
|
||||
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
|
||||
})
|
||||
_append(n0, n1)
|
||||
return n0
|
||||
|
|
|
@ -66,8 +66,15 @@ describe('compiler: v-for', () => {
|
|||
expect((ir.operation[0] as ForIRNode).render.effect).lengthOf(1)
|
||||
})
|
||||
|
||||
test('basic v-for', () => {
|
||||
test('no value', () => {
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -7,9 +7,9 @@ import {
|
|||
advancePositionWithMutation,
|
||||
locStub,
|
||||
} from '@vue/compiler-dom'
|
||||
import type { IREffect, RootIRNode, VaporHelper } from './ir'
|
||||
import type { RootIRNode, VaporHelper } from './ir'
|
||||
import { SourceMapGenerator } from 'source-map-js'
|
||||
import { extend, isString, remove } from '@vue/shared'
|
||||
import { extend, isString } from '@vue/shared'
|
||||
import type { ParserPlugin } from '@babel/parser'
|
||||
import { genTemplate } from './generators/template'
|
||||
import { genBlockFunctionContent } from './generators/block'
|
||||
|
@ -69,22 +69,19 @@ export class CodegenContext {
|
|||
return `_${name}`
|
||||
}
|
||||
|
||||
identifiers: Record<string, string[]> = Object.create(null)
|
||||
withId = <T>(fn: () => T, map: Record<string, string | null>): T => {
|
||||
identifiers: Record<string, number> = Object.create(null)
|
||||
withId = <T>(fn: () => T, ids: string[]): T => {
|
||||
const { identifiers } = this
|
||||
const ids = Object.keys(map)
|
||||
|
||||
for (const id of ids) {
|
||||
identifiers[id] ||= []
|
||||
identifiers[id].unshift(map[id] || id)
|
||||
if (identifiers[id] === undefined) identifiers[id] = 0
|
||||
identifiers[id]!++
|
||||
}
|
||||
|
||||
const ret = fn()
|
||||
ids.forEach(id => remove(identifiers[id], map[id] || id))
|
||||
ids.forEach(id => identifiers[id]!--)
|
||||
|
||||
return ret
|
||||
}
|
||||
genEffect?: (effects: IREffect[]) => CodeFragment[]
|
||||
|
||||
constructor(
|
||||
public ir: RootIRNode,
|
||||
|
|
|
@ -21,13 +21,15 @@ export function genBlockFunction(
|
|||
oper: BlockFunctionIRNode,
|
||||
context: CodegenContext,
|
||||
args: CodeFragment[] = [],
|
||||
returnValue?: () => CodeFragment[],
|
||||
preamble: CodeFragment[] = [],
|
||||
returnValue?: CodeFragment[],
|
||||
): CodeFragment[] {
|
||||
return [
|
||||
'(',
|
||||
...args,
|
||||
') => {',
|
||||
INDENT_START,
|
||||
...preamble,
|
||||
...genBlockFunctionContent(oper, context, returnValue),
|
||||
INDENT_END,
|
||||
NEWLINE,
|
||||
|
@ -38,7 +40,7 @@ export function genBlockFunction(
|
|||
export function genBlockFunctionContent(
|
||||
ir: BlockFunctionIRNode | RootIRNode,
|
||||
context: CodegenContext,
|
||||
returnValue?: () => CodeFragment[],
|
||||
returnValue?: CodeFragment[],
|
||||
): CodeFragment[] {
|
||||
const { vaporHelper } = context
|
||||
const [frag, push] = buildCodeFragment(
|
||||
|
@ -65,11 +67,7 @@ export function genBlockFunctionContent(
|
|||
push(...genOperations(ir.operation, context))
|
||||
push(...genEffects(ir.effect, context))
|
||||
|
||||
push(
|
||||
NEWLINE,
|
||||
'return ',
|
||||
...(returnValue ? returnValue() : [`n${ir.dynamic.id}`]),
|
||||
)
|
||||
push(NEWLINE, 'return ', ...(returnValue || [`n${ir.dynamic.id}`]))
|
||||
|
||||
return frag
|
||||
}
|
||||
|
|
|
@ -45,9 +45,10 @@ export function genSetEvent(
|
|||
const hasMultipleStatements = exp.content.includes(`;`)
|
||||
|
||||
if (isInlineStatement) {
|
||||
const expr = context.withId(() => genExpression(exp, context), {
|
||||
$event: null,
|
||||
})
|
||||
const expr = context.withId(
|
||||
() => genExpression(exp, context),
|
||||
['$event'],
|
||||
)
|
||||
return [
|
||||
'$event => ',
|
||||
hasMultipleStatements ? '{' : '(',
|
||||
|
|
|
@ -88,9 +88,8 @@ function genIdentifier(
|
|||
const { inline, bindingMetadata } = options
|
||||
let name: string | undefined = id
|
||||
|
||||
const idMap = identifiers[id]
|
||||
if (idMap && idMap.length) {
|
||||
return [idMap[0], NewlineType.None, loc]
|
||||
if (identifiers[id]) {
|
||||
return [id, NewlineType.None, loc]
|
||||
}
|
||||
|
||||
if (inline) {
|
||||
|
|
|
@ -6,11 +6,14 @@ import {
|
|||
INDENT_END,
|
||||
INDENT_START,
|
||||
NEWLINE,
|
||||
buildCodeFragment,
|
||||
} from '../generate'
|
||||
import type { ForIRNode, IREffect } from '../ir'
|
||||
import { genOperations } from './operation'
|
||||
import { NewlineType } from '@vue/compiler-dom'
|
||||
import type { ForIRNode } from '../ir'
|
||||
import {
|
||||
NewlineType,
|
||||
type SimpleExpressionNode,
|
||||
walkIdentifiers,
|
||||
} from '@vue/compiler-dom'
|
||||
import type { ArrowFunctionExpression } from '@babel/types'
|
||||
|
||||
export function genFor(
|
||||
oper: ForIRNode,
|
||||
|
@ -19,73 +22,63 @@ export function genFor(
|
|||
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), ')']
|
||||
let updateFn = '_updateEffect'
|
||||
context.genEffect = genEffectInFor
|
||||
const valueIds = value ? extractParams(value) : new Set<string>()
|
||||
const keyIds = key ? extractParams(key) : new Set<string>()
|
||||
const ids = [...valueIds, ...keyIds]
|
||||
|
||||
const idMap: Record<string, string> = {}
|
||||
if (rawValue) idMap[rawValue] = `_block.s[0]`
|
||||
if (rawKey) idMap[rawKey] = `_block.s[1]`
|
||||
let preamble: CodeFragment[] = []
|
||||
if (value || rawKey) {
|
||||
const assignment: CodeFragment[] = ['let ', ids.join(', ')]
|
||||
|
||||
const blockRet = (): CodeFragment[] => [
|
||||
`[n${render.dynamic.id!}, ${updateFn}]`,
|
||||
]
|
||||
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!}`]
|
||||
const blockFn = context.withId(
|
||||
() => genBlockFunction(render, context, ['_block'], blockRet),
|
||||
idMap,
|
||||
() => genBlockFunction(render, context, ['_block'], preamble, blockRet),
|
||||
ids,
|
||||
)
|
||||
|
||||
context.genEffect = undefined
|
||||
|
||||
return [
|
||||
NEWLINE,
|
||||
`const n${oper.id} = `,
|
||||
...call(vaporHelper('createFor'), sourceExpr, blockFn),
|
||||
]
|
||||
|
||||
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})`,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export function genOperations(opers: OperationNode[], context: CodegenContext) {
|
|||
return frag
|
||||
}
|
||||
|
||||
function genOperation(
|
||||
export function genOperation(
|
||||
oper: OperationNode,
|
||||
context: CodegenContext,
|
||||
): CodeFragment[] {
|
||||
|
@ -60,9 +60,6 @@ function genOperation(
|
|||
}
|
||||
|
||||
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))
|
||||
|
@ -70,7 +67,7 @@ export function genEffects(effects: IREffect[], context: CodegenContext) {
|
|||
return frag
|
||||
}
|
||||
|
||||
function genEffect({ operations }: IREffect, context: CodegenContext) {
|
||||
export function genEffect({ operations }: IREffect, context: CodegenContext) {
|
||||
const { vaporHelper } = context
|
||||
const [frag, push] = buildCodeFragment(
|
||||
NEWLINE,
|
||||
|
|
|
@ -15,7 +15,7 @@ interface ForBlock extends Fragment {
|
|||
|
||||
export const createFor = (
|
||||
src: () => any[] | Record<string, string> | Set<any> | Map<any, any>,
|
||||
renderItem: (block: ForBlock) => [Block, () => void],
|
||||
renderItem: (block: ForBlock) => Block,
|
||||
getKey: ((item: any, index: number) => any) | null,
|
||||
getMemo?: (item: any) => any[],
|
||||
hydrationNode?: Node,
|
||||
|
@ -47,9 +47,8 @@ export const createFor = (
|
|||
memo: getMemo && getMemo(item),
|
||||
[fragmentKey]: true,
|
||||
})
|
||||
const res = scope.run(() => renderItem(block))!
|
||||
block.nodes = res[0]
|
||||
block.update = res[1]
|
||||
block.nodes = scope.run(() => renderItem(block))!
|
||||
block.update = () => scope.effects.forEach(effect => effect.run())
|
||||
if (getMemo) block.update()
|
||||
if (parent) insert(block.nodes, parent, anchor)
|
||||
return block
|
||||
|
|
Loading…
Reference in New Issue