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