Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement setRef update #191

Merged
merged 10 commits into from
Apr 30, 2024
Original file line number Diff line number Diff line change
@@ -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("<div></div>")

export function render(_ctx) {
const n0 = t0()
_setRef(n0, _ctx.foo)
let r0
_renderEffect(() => r0 = _setRef(n0, _ctx.foo, r0))
return n0
}"
`;
Expand All @@ -18,7 +19,7 @@ const t0 = _template("<div></div>")
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
Expand Down
107 changes: 52 additions & 55 deletions packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,79 +25,78 @@ describe('compiler: template ref transform', () => {
test('static ref', () => {
const { ir, code } = compileWithTransformRef(`<div ref="foo" />`)

expect(code).matchSnapshot()
expect(ir.block.dynamic.children[0]).toMatchObject({
id: 0,
flags: DynamicFlag.REFERENCED,
})
expect(ir.template).toEqual(['<div></div>'])
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,
},
},
})

expect(code).matchSnapshot()
])
expect(code).contains('_setRef(n0, "foo")')
})

test('dynamic ref', () => {
const { ir, code } = compileWithTransformRef(`<div :ref="foo" />`)

expect(code).matchSnapshot()
expect(ir.block.dynamic.children[0]).toMatchObject({
id: 0,
flags: DynamicFlag.REFERENCED,
})
expect(ir.template).toEqual(['<div></div>'])
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(code).contains('_setRef(n0, _ctx.foo)')
])
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)')
})

test('ref + v-if', () => {
const { ir, code } = compileWithTransformRef(
`<div ref="foo" v-if="true" />`,
)

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")')
})

Expand All @@ -106,22 +105,20 @@ describe('compiler: template ref transform', () => {
`<div ref="foo" v-for="item in [1,2,3]" />`,
)

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", true)')
])
expect(code).contains('_setRef(n2, "foo", void 0, true)')
})
})
4 changes: 3 additions & 1 deletion packages/compiler-vapor/src/generators/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 []
Expand Down
8 changes: 7 additions & 1 deletion packages/compiler-vapor/src/generators/ref.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -10,11 +10,17 @@ export function genSetRef(
const { vaporHelper } = context
return [
NEWLINE,
oper.effect && `r${oper.element} = `,
...genCall(
vaporHelper('setRef'),
[`n${oper.element}`],
genExpression(oper.value, context),
oper.effect ? `r${oper.element}` : oper.refFor ? 'void 0' : undefined,
oper.refFor && 'true',
),
]
}

export function genDeclareOldRef(oper: DeclareOldRefIRNode): CodeFragment[] {
return [NEWLINE, `let r${oper.id}`]
}
8 changes: 8 additions & 0 deletions packages/compiler-vapor/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export enum IRNodeTypes {
CREATE_COMPONENT_NODE,

WITH_DIRECTIVE,
DECLARE_OLD_REF, // consider make it more general

IF,
FOR,
Expand Down Expand Up @@ -145,6 +146,7 @@ export interface SetRefIRNode extends BaseIRNode {
element: number
value: SimpleExpressionNode
refFor: boolean
effect: boolean
}

export interface SetModelValueIRNode extends BaseIRNode {
Expand Down Expand Up @@ -194,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
Expand All @@ -211,6 +218,7 @@ export type OperationNode =
| IfIRNode
| ForIRNode
| CreateComponentIRNode
| DeclareOldRefIRNode

export enum DynamicFlag {
NONE = 0,
Expand Down
30 changes: 22 additions & 8 deletions packages/compiler-vapor/src/transforms/transformRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -24,11 +24,25 @@ export const transformRef: NodeTransform = (node, context) => {
: EMPTY_EXPRESSION
}

return () =>
context.registerOperation({
type: IRNodeTypes.SET_REF,
element: context.reference(),
value,
refFor: !!context.inVFor,
})
return () => {
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,
},
],
)
}
}
73 changes: 72 additions & 1 deletion packages/runtime-vapor/__tests__/dom/templateRef.spec.ts
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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('<div></div>')
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('<div></div>')
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)
})
})
Loading
Loading