From dcaad1d50648a130cc677c1fdbc98a23d31e313e Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Fri, 26 Apr 2024 23:46:07 +0800 Subject: [PATCH 1/8] feat(runtime-vapor, compiler-vapor): impl setRef update close #190 --- .../__snapshots__/transformRef.spec.ts.snap | 11 +-- .../__tests__/transforms/transformRef.spec.ts | 8 +- packages/compiler-vapor/src/generators/ref.ts | 8 ++ packages/compiler-vapor/src/ir.ts | 1 + packages/compiler-vapor/src/transform.ts | 3 + .../src/transforms/transformRef.ts | 7 +- .../__tests__/dom/templateRef.spec.ts | 73 ++++++++++++++++++- packages/runtime-vapor/src/dom/templateRef.ts | 20 ++++- 8 files changed, 118 insertions(+), 13 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap index ba867bd75..cc0147649 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap @@ -1,12 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: template ref transform > dynamic ref 1`] = ` -"import { setRef as _setRef, template as _template } from 'vue/vapor'; +"import { renderEffect as _renderEffect, setRef as _setRef, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() - _setRef(n0, _ctx.foo) + let r0 + _renderEffect(() => r0 = _setRef(n0, _ctx.foo, r0)) return n0 }" `; @@ -18,7 +19,7 @@ const t0 = _template("
") export function render(_ctx) { const n0 = _createFor(() => ([1,2,3]), (_block) => { const n2 = t0() - _setRef(n2, "foo", true) + _setRef(n2, "foo", undefined, true) return [n2, () => {}] }) return n0 @@ -32,7 +33,7 @@ const t0 = _template("
") export function render(_ctx) { const n0 = _createIf(() => (true), () => { const n2 = t0() - _setRef(n2, "foo") + _setRef(n2, "foo", undefined) return n2 }) return n0 @@ -45,7 +46,7 @@ const t0 = _template("
") export function render(_ctx) { const n0 = t0() - _setRef(n0, "foo") + _setRef(n0, "foo", undefined) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts index 78b7694ad..83d19816f 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts @@ -45,7 +45,7 @@ describe('compiler: template ref transform', () => { }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n0, "foo")') + expect(code).contains('_setRef(n0, "foo", undefined)') }) test('dynamic ref', () => { @@ -70,7 +70,7 @@ describe('compiler: template ref transform', () => { }, }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n0, _ctx.foo)') + expect(code).contains('_setRef(n0, _ctx.foo, r0)') }) test('ref + v-if', () => { @@ -98,7 +98,7 @@ describe('compiler: template ref transform', () => { }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n2, "foo")') + expect(code).contains('_setRef(n2, "foo", undefined)') }) test('ref + v-for', () => { @@ -122,6 +122,6 @@ describe('compiler: template ref transform', () => { refFor: true, }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n2, "foo", true)') + expect(code).contains('_setRef(n2, "foo", undefined, true)') }) }) diff --git a/packages/compiler-vapor/src/generators/ref.ts b/packages/compiler-vapor/src/generators/ref.ts index 248221844..753a861bf 100644 --- a/packages/compiler-vapor/src/generators/ref.ts +++ b/packages/compiler-vapor/src/generators/ref.ts @@ -8,13 +8,21 @@ export function genSetRef( context: CodegenContext, ): CodeFragment[] { const { vaporHelper } = context + const dynamicExp = oper.refCount !== -1 return [ NEWLINE, + dynamicExp && `let r${oper.refCount}`, + dynamicExp && NEWLINE, + ...(!!dynamicExp + ? [`${vaporHelper('renderEffect')}(() => `, `r${oper.refCount} = `] + : []), ...genCall( vaporHelper('setRef'), [`n${oper.element}`], genExpression(oper.value, context), + [dynamicExp ? `r${oper.refCount}` : 'undefined'], oper.refFor && 'true', ), + !!dynamicExp && ')', ] } diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 40ccf3bf1..5a68ce0fb 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -145,6 +145,7 @@ export interface SetRefIRNode extends BaseIRNode { element: number value: SimpleExpressionNode refFor: boolean + refCount: number } export interface SetModelValueIRNode extends BaseIRNode { diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index bc2de550a..27aa62d2f 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -79,6 +79,8 @@ export interface TransformContext { component: Set + refCount: number + enterBlock(ir: TransformContext['block'], isVFor?: boolean): () => void reference(): number increaseId(): number @@ -152,6 +154,7 @@ function createRootContext( inVFor: 0, comment: [], component: root.component, + refCount: 0, increaseId: () => globalId++, reference() { diff --git a/packages/compiler-vapor/src/transforms/transformRef.ts b/packages/compiler-vapor/src/transforms/transformRef.ts index 7da99ebfc..402999026 100644 --- a/packages/compiler-vapor/src/transforms/transformRef.ts +++ b/packages/compiler-vapor/src/transforms/transformRef.ts @@ -6,7 +6,7 @@ import { import type { NodeTransform } from '../transform' import { IRNodeTypes } from '../ir' import { normalizeBindShorthand } from './vBind' -import { findProp } from '../utils' +import { findProp, isConstantExpression } from '../utils' import { EMPTY_EXPRESSION } from './utils' export const transformRef: NodeTransform = (node, context) => { @@ -24,11 +24,14 @@ export const transformRef: NodeTransform = (node, context) => { : EMPTY_EXPRESSION } - return () => + return () => { + const dynamicExp = !isConstantExpression(value) context.registerOperation({ type: IRNodeTypes.SET_REF, element: context.reference(), value, refFor: !!context.inVFor, + refCount: dynamicExp ? context.refCount++ : -1, }) + } } diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts index 6dc10eff3..7ae406723 100644 --- a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -1,4 +1,12 @@ -import { ref, setRef, template } from '../../src' +import type { NodeRef } from 'packages/runtime-vapor/src/dom/templateRef' +import { + createIf, + nextTick, + ref, + renderEffect, + setRef, + template, +} from '../../src' import { makeRender } from '../_utils' const define = makeRender() @@ -23,4 +31,67 @@ describe('api: template ref', () => { const { host } = render() expect(el.value).toBe(host.children[0]) }) + + it('string ref update', async () => { + const t0 = template('
') + const fooEl = ref(null) + const barEl = ref(null) + const refKey = ref('foo') + + const { render } = define({ + setup() { + return { + foo: fooEl, + bar: barEl, + } + }, + render() { + const n0 = t0() + let r0: NodeRef | undefined + renderEffect(() => { + r0 = setRef(n0 as Element, refKey.value, r0) + }) + return n0 + }, + }) + const { host } = render() + expect(fooEl.value).toBe(host.children[0]) + expect(barEl.value).toBe(null) + + refKey.value = 'bar' + await nextTick() + expect(barEl.value).toBe(host.children[0]) + expect(fooEl.value).toBe(null) + }) + + it('string ref unmount', async () => { + const t0 = template('
') + const el = ref(null) + const toggle = ref(true) + + const { render } = define({ + setup() { + return { + refKey: el, + } + }, + render() { + const n0 = createIf( + () => toggle.value, + () => { + const n1 = t0() + setRef(n1 as Element, 'refKey') + return n1 + }, + ) + return n0 + }, + }) + const { host } = render() + expect(el.value).toBe(host.children[0]) + + toggle.value = false + await nextTick() + expect(el.value).toBe(null) + }) }) diff --git a/packages/runtime-vapor/src/dom/templateRef.ts b/packages/runtime-vapor/src/dom/templateRef.ts index 25a9b014b..b3770739a 100644 --- a/packages/runtime-vapor/src/dom/templateRef.ts +++ b/packages/runtime-vapor/src/dom/templateRef.ts @@ -27,7 +27,12 @@ export type RefEl = Element | ComponentInternalInstance /** * Function for handling a template ref */ -export function setRef(el: RefEl, ref: NodeRef, refFor = false) { +export function setRef( + el: RefEl, + ref: NodeRef, + oldRef?: NodeRef, + refFor = false, +) { if (!currentInstance) return const { setupState, isUnmounted } = currentInstance @@ -42,6 +47,18 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) { ? (currentInstance.refs = {}) : currentInstance.refs + // dynamic ref changed. unset old ref + if (oldRef != null && oldRef !== ref) { + if (isString(oldRef)) { + refs[oldRef] = null + if (hasOwn(setupState, oldRef)) { + setupState[oldRef] = null + } + } else if (isRef(oldRef)) { + oldRef.value = null + } + } + if (isFunction(ref)) { const invokeRefSetter = (value?: Element | Record) => { callWithErrorHandling( @@ -117,4 +134,5 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) { warn('Invalid template ref type:', ref, `(${typeof ref})`) } } + return ref } From 4141c160f84a54cb610f835fc219d4c9ea95324a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sat, 27 Apr 2024 01:45:35 +0800 Subject: [PATCH 2/8] refactor: omit undefined --- .../transforms/__snapshots__/transformRef.spec.ts.snap | 6 +++--- .../__tests__/transforms/transformRef.spec.ts | 6 +++--- packages/compiler-vapor/src/generators/ref.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap index cc0147649..bf50a8e82 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap @@ -19,7 +19,7 @@ const t0 = _template("
") export function render(_ctx) { const n0 = _createFor(() => ([1,2,3]), (_block) => { const n2 = t0() - _setRef(n2, "foo", undefined, true) + _setRef(n2, "foo", void 0, true) return [n2, () => {}] }) return n0 @@ -33,7 +33,7 @@ const t0 = _template("
") export function render(_ctx) { const n0 = _createIf(() => (true), () => { const n2 = t0() - _setRef(n2, "foo", undefined) + _setRef(n2, "foo") return n2 }) return n0 @@ -46,7 +46,7 @@ const t0 = _template("
") export function render(_ctx) { const n0 = t0() - _setRef(n0, "foo", undefined) + _setRef(n0, "foo") return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts index 83d19816f..f942eb1d6 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts @@ -45,7 +45,7 @@ describe('compiler: template ref transform', () => { }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n0, "foo", undefined)') + expect(code).contains('_setRef(n0, "foo")') }) test('dynamic ref', () => { @@ -98,7 +98,7 @@ describe('compiler: template ref transform', () => { }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n2, "foo", undefined)') + expect(code).contains('_setRef(n2, "foo")') }) test('ref + v-for', () => { @@ -122,6 +122,6 @@ describe('compiler: template ref transform', () => { refFor: true, }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n2, "foo", undefined, true)') + expect(code).contains('_setRef(n2, "foo", void 0, true)') }) }) diff --git a/packages/compiler-vapor/src/generators/ref.ts b/packages/compiler-vapor/src/generators/ref.ts index 753a861bf..c66220a6f 100644 --- a/packages/compiler-vapor/src/generators/ref.ts +++ b/packages/compiler-vapor/src/generators/ref.ts @@ -20,7 +20,7 @@ export function genSetRef( vaporHelper('setRef'), [`n${oper.element}`], genExpression(oper.value, context), - [dynamicExp ? `r${oper.refCount}` : 'undefined'], + dynamicExp ? `r${oper.refCount}` : oper.refFor ? 'void 0' : undefined, oper.refFor && 'true', ), !!dynamicExp && ')', From 6b8cd6b8570a4ef366ce2fb39965fd3547675d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sat, 27 Apr 2024 02:15:45 +0800 Subject: [PATCH 3/8] refactor --- .../__tests__/transforms/transformRef.spec.ts | 107 +++++++++--------- .../src/generators/operation.ts | 4 +- packages/compiler-vapor/src/generators/ref.ts | 16 ++- packages/compiler-vapor/src/ir.ts | 9 +- packages/compiler-vapor/src/transform.ts | 3 - .../src/transforms/transformRef.ts | 27 +++-- 6 files changed, 91 insertions(+), 75 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts index f942eb1d6..8a1de25f3 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts @@ -25,51 +25,58 @@ describe('compiler: template ref transform', () => { test('static ref', () => { const { ir, code } = compileWithTransformRef(`
`) + expect(code).matchSnapshot() expect(ir.block.dynamic.children[0]).toMatchObject({ id: 0, flags: DynamicFlag.REFERENCED, }) expect(ir.template).toEqual(['
']) - expect(ir.block.operation).lengthOf(1) - expect(ir.block.operation[0]).toMatchObject({ - type: IRNodeTypes.SET_REF, - element: 0, - value: { - content: 'foo', - isStatic: true, - loc: { - start: { line: 1, column: 10, offset: 9 }, - end: { line: 1, column: 15, offset: 14 }, + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.SET_REF, + element: 0, + value: { + content: 'foo', + isStatic: true, + loc: { + start: { line: 1, column: 10, offset: 9 }, + end: { line: 1, column: 15, offset: 14 }, + }, }, }, - }) - - expect(code).matchSnapshot() + ]) expect(code).contains('_setRef(n0, "foo")') }) test('dynamic ref', () => { const { ir, code } = compileWithTransformRef(`
`) + expect(code).matchSnapshot() expect(ir.block.dynamic.children[0]).toMatchObject({ id: 0, flags: DynamicFlag.REFERENCED, }) expect(ir.template).toEqual(['
']) - expect(ir.block.operation).lengthOf(1) - expect(ir.block.operation[0]).toMatchObject({ - type: IRNodeTypes.SET_REF, - element: 0, - value: { - content: 'foo', - isStatic: false, - loc: { - start: { line: 1, column: 12, offset: 11 }, - end: { line: 1, column: 15, offset: 14 }, - }, + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.DECLARE_OLD_REF, + id: 0, }, - }) - expect(code).matchSnapshot() + ]) + expect(ir.block.effect).toMatchObject([ + { + operations: [ + { + type: IRNodeTypes.SET_REF, + element: 0, + value: { + content: 'foo', + isStatic: false, + }, + }, + ], + }, + ]) expect(code).contains('_setRef(n0, _ctx.foo, r0)') }) @@ -78,26 +85,22 @@ describe('compiler: template ref transform', () => { `
`, ) + expect(code).matchSnapshot() expect(ir.block.operation).lengthOf(1) expect(ir.block.operation[0].type).toBe(IRNodeTypes.IF) const { positive } = ir.block.operation[0] as IfIRNode - - expect(positive.operation).lengthOf(1) - expect(positive.operation[0]).toMatchObject({ - type: IRNodeTypes.SET_REF, - element: 2, - value: { - content: 'foo', - isStatic: true, - loc: { - start: { line: 1, column: 10, offset: 9 }, - end: { line: 1, column: 15, offset: 14 }, + expect(positive.operation).toMatchObject([ + { + type: IRNodeTypes.SET_REF, + element: 2, + value: { + content: 'foo', + isStatic: true, }, + effect: false, }, - }) - - expect(code).matchSnapshot() + ]) expect(code).contains('_setRef(n2, "foo")') }) @@ -106,22 +109,20 @@ describe('compiler: template ref transform', () => { `
`, ) + expect(code).matchSnapshot() const { render } = ir.block.operation[0] as ForIRNode - expect(render.operation).lengthOf(1) - expect(render.operation[0]).toMatchObject({ - type: IRNodeTypes.SET_REF, - element: 2, - value: { - content: 'foo', - isStatic: true, - loc: { - start: { line: 1, column: 10, offset: 9 }, - end: { line: 1, column: 15, offset: 14 }, + expect(render.operation).toMatchObject([ + { + type: IRNodeTypes.SET_REF, + element: 2, + value: { + content: 'foo', + isStatic: true, }, + refFor: true, + effect: false, }, - refFor: true, - }) - expect(code).matchSnapshot() + ]) expect(code).contains('_setRef(n2, "foo", void 0, true)') }) }) diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index 59b0816d4..9fd77bd81 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -7,7 +7,7 @@ import { genSetHtml } from './html' import { genIf } from './if' import { genSetModelValue } from './modelValue' import { genDynamicProps, genSetProp } from './prop' -import { genSetRef } from './ref' +import { genDeclareOldRef, genSetRef } from './ref' import { genCreateTextNode, genSetText } from './text' import { type CodeFragment, @@ -59,6 +59,8 @@ export function genOperation( return genFor(oper, context) case IRNodeTypes.CREATE_COMPONENT_NODE: return genCreateComponent(oper, context) + case IRNodeTypes.DECLARE_OLD_REF: + return genDeclareOldRef(oper) } return [] diff --git a/packages/compiler-vapor/src/generators/ref.ts b/packages/compiler-vapor/src/generators/ref.ts index c66220a6f..e51957844 100644 --- a/packages/compiler-vapor/src/generators/ref.ts +++ b/packages/compiler-vapor/src/generators/ref.ts @@ -1,6 +1,6 @@ import { genExpression } from './expression' import type { CodegenContext } from '../generate' -import type { SetRefIRNode } from '../ir' +import type { DeclareOldRefIRNode, SetRefIRNode } from '../ir' import { type CodeFragment, NEWLINE, genCall } from './utils' export function genSetRef( @@ -8,21 +8,19 @@ export function genSetRef( context: CodegenContext, ): CodeFragment[] { const { vaporHelper } = context - const dynamicExp = oper.refCount !== -1 return [ NEWLINE, - dynamicExp && `let r${oper.refCount}`, - dynamicExp && NEWLINE, - ...(!!dynamicExp - ? [`${vaporHelper('renderEffect')}(() => `, `r${oper.refCount} = `] - : []), + oper.effect && `r${oper.element} = `, ...genCall( vaporHelper('setRef'), [`n${oper.element}`], genExpression(oper.value, context), - dynamicExp ? `r${oper.refCount}` : oper.refFor ? 'void 0' : undefined, + oper.effect ? `r${oper.element}` : oper.refFor ? 'void 0' : undefined, oper.refFor && 'true', ), - !!dynamicExp && ')', ] } + +export function genDeclareOldRef(oper: DeclareOldRefIRNode): CodeFragment[] { + return [NEWLINE, `let r${oper.id}`] +} diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 5a68ce0fb..412ba3811 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -32,6 +32,7 @@ export enum IRNodeTypes { CREATE_COMPONENT_NODE, WITH_DIRECTIVE, + DECLARE_OLD_REF, // consider make it more general IF, FOR, @@ -145,7 +146,7 @@ export interface SetRefIRNode extends BaseIRNode { element: number value: SimpleExpressionNode refFor: boolean - refCount: number + effect: boolean } export interface SetModelValueIRNode extends BaseIRNode { @@ -195,6 +196,11 @@ export interface CreateComponentIRNode extends BaseIRNode { root: boolean } +export interface DeclareOldRefIRNode extends BaseIRNode { + type: IRNodeTypes.DECLARE_OLD_REF + id: number +} + export type IRNode = OperationNode | RootIRNode export type OperationNode = | SetPropIRNode @@ -212,6 +218,7 @@ export type OperationNode = | IfIRNode | ForIRNode | CreateComponentIRNode + | DeclareOldRefIRNode export enum DynamicFlag { NONE = 0, diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 27aa62d2f..bc2de550a 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -79,8 +79,6 @@ export interface TransformContext { component: Set - refCount: number - enterBlock(ir: TransformContext['block'], isVFor?: boolean): () => void reference(): number increaseId(): number @@ -154,7 +152,6 @@ function createRootContext( inVFor: 0, comment: [], component: root.component, - refCount: 0, increaseId: () => globalId++, reference() { diff --git a/packages/compiler-vapor/src/transforms/transformRef.ts b/packages/compiler-vapor/src/transforms/transformRef.ts index 402999026..a7f9482e5 100644 --- a/packages/compiler-vapor/src/transforms/transformRef.ts +++ b/packages/compiler-vapor/src/transforms/transformRef.ts @@ -25,13 +25,24 @@ export const transformRef: NodeTransform = (node, context) => { } return () => { - const dynamicExp = !isConstantExpression(value) - context.registerOperation({ - type: IRNodeTypes.SET_REF, - element: context.reference(), - value, - refFor: !!context.inVFor, - refCount: dynamicExp ? context.refCount++ : -1, - }) + const id = context.reference() + const effect = !isConstantExpression(value) + effect && + context.registerOperation({ + type: IRNodeTypes.DECLARE_OLD_REF, + id, + }) + context.registerEffect( + [value], + [ + { + type: IRNodeTypes.SET_REF, + element: context.reference(), + value, + refFor: !!context.inVFor, + effect, + }, + ], + ) } } From 521f3c77ba5c7b53ddbd89fa6390020dcfe59738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sat, 27 Apr 2024 02:16:25 +0800 Subject: [PATCH 4/8] test: remove loc --- .../compiler-vapor/__tests__/transforms/transformRef.spec.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts index 8a1de25f3..2e3432a19 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts @@ -38,10 +38,6 @@ describe('compiler: template ref transform', () => { value: { content: 'foo', isStatic: true, - loc: { - start: { line: 1, column: 10, offset: 9 }, - end: { line: 1, column: 15, offset: 14 }, - }, }, }, ]) From 31a96146d63247cf767add5425b241927f872e65 Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Sat, 27 Apr 2024 13:08:43 +0800 Subject: [PATCH 5/8] chore(compiler-vapor): use id instead of reference --- packages/compiler-vapor/src/transforms/transformRef.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler-vapor/src/transforms/transformRef.ts b/packages/compiler-vapor/src/transforms/transformRef.ts index a7f9482e5..cac393a80 100644 --- a/packages/compiler-vapor/src/transforms/transformRef.ts +++ b/packages/compiler-vapor/src/transforms/transformRef.ts @@ -37,7 +37,7 @@ export const transformRef: NodeTransform = (node, context) => { [ { type: IRNodeTypes.SET_REF, - element: context.reference(), + element: id, value, refFor: !!context.inVFor, effect, From 43fd261c9a57975f3e2ff073bb3a1371a890e1a1 Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Sun, 28 Apr 2024 18:31:22 +0800 Subject: [PATCH 6/8] feat(runtime-vapor): align template ref test case with core --- .../__tests__/dom/templateRef.spec.ts | 532 ++++++++++++++++++ 1 file changed, 532 insertions(+) diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts index 7ae406723..41997b31a 100644 --- a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -1,11 +1,17 @@ import type { NodeRef } from 'packages/runtime-vapor/src/dom/templateRef' import { + createFor, createIf, + getCurrentInstance, + insert, nextTick, + reactive, ref, renderEffect, setRef, + setText, template, + watchEffect, } from '../../src' import { makeRender } from '../_utils' @@ -94,4 +100,530 @@ describe('api: template ref', () => { await nextTick() expect(el.value).toBe(null) }) + + it('function ref mount', () => { + const fn = vi.fn() + const t0 = template('
') + const { render } = define({ + render() { + const n0 = t0() + setRef(n0 as Element, fn) + return n0 + }, + }) + + const { host } = render() + expect(fn.mock.calls[0][0]).toBe(host.children[0]) + }) + + it('function ref update', async () => { + const fn1 = vi.fn() + const fn2 = vi.fn() + const fn = ref(fn1) + + const t0 = template('
') + const { render } = define({ + render() { + const n0 = t0() + let r0: NodeRef | undefined + renderEffect(() => { + r0 = setRef(n0 as Element, fn.value, r0) + }) + return n0 + }, + }) + + const { host } = render() + + expect(fn1.mock.calls).toHaveLength(1) + expect(fn1.mock.calls[0][0]).toBe(host.children[0]) + expect(fn2.mock.calls).toHaveLength(0) + + fn.value = fn2 + await nextTick() + expect(fn1.mock.calls).toHaveLength(1) + expect(fn2.mock.calls).toHaveLength(1) + expect(fn2.mock.calls[0][0]).toBe(host.children[0]) + }) + + it('function ref unmount', async () => { + const fn = vi.fn() + const toggle = ref(true) + + const t0 = template('
') + const { render } = define({ + render() { + const n0 = createIf( + () => toggle.value, + () => { + const n1 = t0() + setRef(n1 as Element, fn) + return n1 + }, + ) + return n0 + }, + }) + const { host } = render() + expect(fn.mock.calls[0][0]).toBe(host.children[0]) + toggle.value = false + await nextTick() + expect(fn.mock.calls[1][0]).toBe(undefined) + }) + + it('should work with direct reactive property', () => { + const state = reactive({ + refKey: null, + }) + + const t0 = template('
') + const { render } = define({ + setup() { + return state + }, + render() { + const n0 = t0() + setRef(n0 as Element, 'refKey') + return n0 + }, + }) + const { host } = render() + expect(state.refKey).toBe(host.children[0]) + }) + + test('multiple root refs', () => { + const refKey1 = ref(null) + const refKey2 = ref(null) + const refKey3 = ref(null) + + const t0 = template('
') + const t1 = template('
') + const t2 = template('
') + const { render } = define({ + setup() { + return { + refKey1, + refKey2, + refKey3, + } + }, + render() { + const n0 = t0() + const n1 = t1() + const n2 = t2() + setRef(n0 as Element, 'refKey1') + setRef(n1 as Element, 'refKey2') + setRef(n2 as Element, 'refKey3') + return [n0, n1, n2] + }, + }) + const { host } = render() + // Note: toBe Condition is different from core test case + // Core test case is expecting refKey1.value to be host.children[1] + expect(refKey1.value).toBe(host.children[0]) + expect(refKey2.value).toBe(host.children[1]) + expect(refKey3.value).toBe(host.children[2]) + }) + + // #1505 + test('reactive template ref in the same template', async () => { + const t0 = template('
') + const el = ref() + const { render } = define({ + render() { + const n0 = t0() + setRef(n0 as Element, el) + renderEffect(() => { + setText(n0, el.value && el.value.getAttribute('id')) + }) + return n0 + }, + }) + + const { host } = render() + // ref not ready on first render, but should queue an update immediately + expect(host.innerHTML).toBe(`
`) + await nextTick() + // ref should be updated + expect(host.innerHTML).toBe(`
foo
`) + }) + + // #1834 + test('exchange refs', async () => { + const refToggle = ref(false) + const spy = vi.fn() + + const t0 = template('

') + const t1 = template('') + const { render } = define({ + render() { + const instance = getCurrentInstance()! + const n0 = t0() + const n1 = t1() + let r0: NodeRef | undefined + let r1: NodeRef | undefined + renderEffect(() => { + r0 = setRef(n0 as Element, refToggle.value ? 'foo' : 'bar', r0) + }) + renderEffect(() => { + r1 = setRef(n1 as Element, refToggle.value ? 'bar' : 'foo', r1) + }) + watchEffect( + () => { + refToggle.value + spy( + (instance.refs.foo as HTMLElement).tagName, + (instance.refs.bar as HTMLElement).tagName, + ) + }, + { + flush: 'post', + }, + ) + return [n0, n1] + }, + }) + + render() + + expect(spy.mock.calls[0][0]).toBe('I') + expect(spy.mock.calls[0][1]).toBe('P') + refToggle.value = true + await nextTick() + expect(spy.mock.calls[1][0]).toBe('P') + expect(spy.mock.calls[1][1]).toBe('I') + }) + + // #1789 + test('toggle the same ref to different elements', async () => { + const refToggle = ref(false) + const spy = vi.fn() + + const t0 = template('

') + const t1 = template('') + const { render } = define({ + render() { + const instance = getCurrentInstance()! + const n0 = createIf( + () => refToggle.value, + () => { + const n1 = t0() + setRef(n1 as Element, 'foo') + return n1 + }, + () => { + const n1 = t1() + setRef(n1 as Element, 'foo') + return n1 + }, + ) + watchEffect( + () => { + refToggle.value + spy((instance.refs.foo as HTMLElement).tagName) + }, + { + flush: 'post', + }, + ) + return [n0] + }, + }) + + render() + + expect(spy.mock.calls[0][0]).toBe('I') + refToggle.value = true + await nextTick() + expect(spy.mock.calls[1][0]).toBe('P') + }) + + // compiled output of v-for + template ref + test('ref in v-for', async () => { + const show = ref(true) + const list = reactive([1, 2, 3]) + const listRefs = ref([]) + const mapRefs = () => listRefs.value.map((n: HTMLElement) => n.innerHTML) + + const t0 = template('
    ') + const t1 = template('
  • ') + const { render } = define({ + render() { + const n0 = createIf( + () => show.value, + () => { + const n1 = t0() + const n2 = createFor( + () => list, + _block => { + const n1 = t1() + setRef(n1 as Element, listRefs, undefined, true) + const updateEffect = () => { + const [item] = _block.s + setText(n1, item) + } + renderEffect(updateEffect) + return [n1, updateEffect] + }, + ) + insert(n2, n1 as ParentNode) + return n1 + }, + ) + return n0 + }, + }) + render() + + expect(mapRefs()).toMatchObject(['1', '2', '3']) + + list.push(4) + await nextTick() + expect(mapRefs()).toMatchObject(['1', '2', '3', '4']) + + list.shift() + await nextTick() + expect(mapRefs()).toMatchObject(['2', '3', '4']) + + show.value = !show.value + await nextTick() + + expect(mapRefs()).toMatchObject([]) + + show.value = !show.value + await nextTick() + expect(mapRefs()).toMatchObject(['2', '3', '4']) + }) + + test('named ref in v-for', async () => { + const show = ref(true) + const list = reactive([1, 2, 3]) + const listRefs = ref([]) + const mapRefs = () => listRefs.value.map((n: HTMLElement) => n.innerHTML) + + const t0 = template('
      ') + const t1 = template('
    • ') + const { render } = define({ + setup() { + return { listRefs } + }, + render() { + const n0 = createIf( + () => show.value, + () => { + const n1 = t0() + const n2 = createFor( + () => list, + _block => { + const n1 = t1() + setRef(n1 as Element, 'listRefs', undefined, true) + const updateEffect = () => { + const [item] = _block.s + setText(n1, item) + } + renderEffect(updateEffect) + return [n1, updateEffect] + }, + ) + insert(n2, n1 as ParentNode) + return n1 + }, + ) + return n0 + }, + }) + render() + + expect(mapRefs()).toMatchObject(['1', '2', '3']) + + list.push(4) + await nextTick() + expect(mapRefs()).toMatchObject(['1', '2', '3', '4']) + + list.shift() + await nextTick() + expect(mapRefs()).toMatchObject(['2', '3', '4']) + + show.value = !show.value + await nextTick() + + expect(mapRefs()).toMatchObject([]) + + show.value = !show.value + await nextTick() + expect(mapRefs()).toMatchObject(['2', '3', '4']) + }) + + // #6697 v-for ref behaves differently under production and development + test('named ref in v-for , should be responsive when rendering', async () => { + const list = ref([1, 2, 3]) + const listRefs = ref([]) + + const t0 = template('
        ') + const t1 = template('
      • ') + const { render } = define({ + setup() { + return { listRefs } + }, + render() { + const n0 = t0() + const n1 = n0.firstChild + const n2 = n1!.nextSibling! + const n3 = createFor( + () => list.value, + _block => { + const n4 = t1() + setRef(n4 as Element, 'listRefs', undefined, true) + const updateEffect = () => { + const [item] = _block.s + setText(n4, item) + } + renderEffect(updateEffect) + return [n4, updateEffect] + }, + ) + insert(n3, n2 as unknown as ParentNode) + renderEffect(() => { + setText(n1!, String(listRefs.value)) + }) + return n0 + }, + }) + + const { host } = render() + + await nextTick() + expect(String(listRefs.value)).toBe( + '[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]', + ) + expect(host.innerHTML).toBe( + '
        [object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]
        • 1
        • 2
        • 3
        ', + ) + + list.value.splice(0, 1) + await nextTick() + expect(String(listRefs.value)).toBe( + '[object HTMLLIElement],[object HTMLLIElement]', + ) + expect(host.innerHTML).toBe( + '
        [object HTMLLIElement],[object HTMLLIElement]
        • 2
        • 3
        ', + ) + }) + + // TODO: need to implement Component slots + // test('string ref inside slots', async () => { + // const spy = vi.fn() + // const { component: Child } = define({ + // render(this: any) { + // return this.$slots.default() + // }, + // }) + // const { render } = define({ + // render() { + // onMounted(function (this: any) { + // spy(this.$refs.foo.tag) + // }) + // const n0 = createComponent(Child) + // setRef(n0, 'foo') + // return n0 + // }, + // }) + // const { host } = render() + + // expect(spy).toHaveBeenCalledWith('div') + // }) + + //TODO: need setup return render function + // it('render function ref mount', () => { + // const el = ref(null) + + // const Comp = define({ + // setup() { + // return () => h('div', { ref: el }) + // }, + // }) + // render(h(Comp), root) + // expect(el.value).toBe(root.children[0]) + // }) + + // it('render function ref update', async () => { + // const root = nodeOps.createElement('div') + // const refs = { + // foo: ref(null), + // bar: ref(null), + // } + // const refKey = ref('foo') + + // const Comp = { + // setup() { + // return () => h('div', { ref: refs[refKey.value] }) + // }, + // } + // render(h(Comp), root) + // expect(refs.foo.value).toBe(root.children[0]) + // expect(refs.bar.value).toBe(null) + + // refKey.value = 'bar' + // await nextTick() + // expect(refs.foo.value).toBe(null) + // expect(refs.bar.value).toBe(root.children[0]) + // }) + + // it('render function ref unmount', async () => { + // const root = nodeOps.createElement('div') + // const el = ref(null) + // const toggle = ref(true) + + // const Comp = { + // setup() { + // return () => (toggle.value ? h('div', { ref: el }) : null) + // }, + // } + // render(h(Comp), root) + // expect(el.value).toBe(root.children[0]) + + // toggle.value = false + // await nextTick() + // expect(el.value).toBe(null) + // }) + + // TODO: can not reproduce in Vapor + // // #2078 + // test('handling multiple merged refs', async () => { + // const Foo = { + // render: () => h('div', 'foo'), + // } + // const Bar = { + // render: () => h('div', 'bar'), + // } + + // const viewRef = shallowRef(Foo) + // const elRef1 = ref() + // const elRef2 = ref() + + // const App = { + // render() { + // if (!viewRef.value) { + // return null + // } + // const view = h(viewRef.value, { ref: elRef1 }) + // return h(view, { ref: elRef2 }) + // }, + // } + // const root = nodeOps.createElement('div') + // render(h(App), root) + + // expect(serializeInner(elRef1.value.$el)).toBe('foo') + // expect(elRef1.value).toBe(elRef2.value) + + // viewRef.value = Bar + // await nextTick() + // expect(serializeInner(elRef1.value.$el)).toBe('bar') + // expect(elRef1.value).toBe(elRef2.value) + + // viewRef.value = null + // await nextTick() + // expect(elRef1.value).toBeNull() + // expect(elRef1.value).toBe(elRef2.value) + // }) }) From 5c86b2af649f4e9773732292ef2c9392c4856cb3 Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Sun, 28 Apr 2024 19:00:46 +0800 Subject: [PATCH 7/8] chore(compiler-vapor): update snapshot --- .../__snapshots__/transformTemplateRef.spec.ts.snap | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap index ba867bd75..bf50a8e82 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap @@ -1,12 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: template ref transform > dynamic ref 1`] = ` -"import { setRef as _setRef, template as _template } from 'vue/vapor'; +"import { renderEffect as _renderEffect, setRef as _setRef, template as _template } from 'vue/vapor'; const t0 = _template("
        ") export function render(_ctx) { const n0 = t0() - _setRef(n0, _ctx.foo) + let r0 + _renderEffect(() => r0 = _setRef(n0, _ctx.foo, r0)) return n0 }" `; @@ -18,7 +19,7 @@ const t0 = _template("
        ") export function render(_ctx) { const n0 = _createFor(() => ([1,2,3]), (_block) => { const n2 = t0() - _setRef(n2, "foo", true) + _setRef(n2, "foo", void 0, true) return [n2, () => {}] }) return n0 From fedc46fdab4e188a26a49c98cb98de8f405588fa Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Sun, 28 Apr 2024 22:16:24 +0800 Subject: [PATCH 8/8] test(compiler-vapor): update test --- .../__tests__/transforms/transformTemplateRef.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts index c4e134a82..22f68f953 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts @@ -25,7 +25,6 @@ describe('compiler: template ref transform', () => { test('static ref', () => { const { ir, code } = compileWithTransformRef(`
        `) - expect(code).matchSnapshot() expect(ir.block.dynamic.children[0]).toMatchObject({ id: 0, flags: DynamicFlag.REFERENCED, @@ -44,13 +43,13 @@ describe('compiler: template ref transform', () => { }, }, }) + expect(code).matchSnapshot() expect(code).contains('_setRef(n0, "foo")') }) test('dynamic ref', () => { const { ir, code } = compileWithTransformRef(`
        `) - expect(code).matchSnapshot() expect(ir.block.dynamic.children[0]).toMatchObject({ id: 0, flags: DynamicFlag.REFERENCED, @@ -76,6 +75,7 @@ describe('compiler: template ref transform', () => { ], }, ]) + expect(code).matchSnapshot() expect(code).contains('_setRef(n0, _ctx.foo, r0)') }) @@ -84,7 +84,6 @@ describe('compiler: template ref transform', () => { `
        `, ) - expect(code).matchSnapshot() expect(ir.block.operation).lengthOf(1) expect(ir.block.operation[0].type).toBe(IRNodeTypes.IF) @@ -100,6 +99,7 @@ describe('compiler: template ref transform', () => { effect: false, }, ]) + expect(code).matchSnapshot() expect(code).contains('_setRef(n2, "foo")') }) @@ -108,7 +108,6 @@ describe('compiler: template ref transform', () => { `
        `, ) - expect(code).matchSnapshot() const { render } = ir.block.operation[0] as ForIRNode expect(render.operation).toMatchObject([ { @@ -122,6 +121,7 @@ describe('compiler: template ref transform', () => { effect: false, }, ]) + expect(code).matchSnapshot() expect(code).contains('_setRef(n2, "foo", void 0, true)') }) })