codeFragmentToString,
genCall,
} from './generators/utils'
+import { setTemplateRefIdent } from './generators/templateRef'
export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'>
', ',
)
- if (inline) {
- // push(`((${signature}) => {`)
- } else {
+ if (!inline) {
push(NEWLINE, `export function ${functionName}(${signature}) {`)
}
push(INDENT_START)
+ if (ir.hasTemplateRef) {
+ push(
+ NEWLINE,
+ `const ${setTemplateRefIdent} = ${context.helper('createTemplateRefSetter')}()`,
+ )
+ }
push(...genBlockContent(ir.block, context, true))
push(INDENT_END, NEWLINE)
- if (inline) {
- // push('})()')
- } else {
+ if (!inline) {
push('}')
}
return genDeclareOldRef(oper)
case IRNodeTypes.SLOT_OUTLET_NODE:
return genSlotOutlet(oper, context)
+ case IRNodeTypes.WITH_DIRECTIVE:
+ return [] // TODO
+ default:
+ const exhaustiveCheck: never = oper
+ throw new Error(
+ `Unhandled operation type in genOperation: ${exhaustiveCheck}`,
+ )
}
-
- return []
}
export function genEffects(
import type { DeclareOldRefIRNode, SetTemplateRefIRNode } from '../ir'
import { type CodeFragment, NEWLINE, genCall } from './utils'
+export const setTemplateRefIdent = `_setTemplateRef`
+
export function genSetTemplateRef(
oper: SetTemplateRefIRNode,
context: CodegenContext,
): CodeFragment[] {
- const { helper } = context
return [
NEWLINE,
oper.effect && `r${oper.element} = `,
...genCall(
- helper('setRef'),
+ setTemplateRefIdent, // will be generated in root scope
`n${oper.element}`,
genExpression(oper.value, context),
oper.effect ? `r${oper.element}` : oper.refFor ? 'void 0' : undefined,
component: Set<string>
directive: Set<string>
block: BlockIRNode
+ hasTemplateRef: boolean
}
export interface IfIRNode extends BaseIRNode {
component: new Set(),
directive: new Set(),
block: newBlock(node),
+ hasTemplateRef: false,
}
const context = new TransformContext(ir, node, options)
const dir = findProp(node, 'ref', false, true)
if (!dir) return
+ context.ir.hasTemplateRef = true
+
let value: SimpleExpressionNode
if (dir.type === NodeTypes.DIRECTIVE) {
value = dir.exp || normalizeBindShorthand(dir.arg!, context)
import { type ShallowRef, readonly, shallowRef } from '@vue/reactivity'
-import { getCurrentInstance } from '../component'
+import { getCurrentGenericInstance } from '../component'
import { warn } from '../warning'
import { EMPTY_OBJ } from '@vue/shared'
export function useTemplateRef<T = unknown, Keys extends string = string>(
key: Keys,
): Readonly<ShallowRef<T | null>> {
- const i = getCurrentInstance()
+ const i = getCurrentGenericInstance()
const r = shallowRef(null)
if (i) {
const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs
} from '../directives/vShow'
import { CSS_VAR_TEXT } from '../helpers/useCssVars'
-type Style = string | Record<string, string | string[]> | null | undefined
+type Style = StyleValue | Record<string, StyleValue | StyleValue[]>
+type StyleValue = string | null | undefined
const displayRE = /(^|;)\s*display\s*:/
function setStyle(
style: CSSStyleDeclaration,
name: string,
- val: string | string[],
+ val: StyleValue | StyleValue[],
) {
if (isArray(val)) {
val.forEach(v => setStyle(style, name, v))
import { ref, shallowRef } from '@vue/reactivity'
import { type VaporComponentInstance, createComponent } from '../src/component'
-import { setRef } from '../src/dom/templateRef'
+import { setRef } from '../src/apiTemplateRef'
import { makeRender } from './_utils'
import { currentInstance } from '@vue/runtime-dom'
import { defineVaporComponent } from '../src/apiDefineComponent'
const define = makeRender()
describe('api: expose', () => {
- test.todo('via setup context + template ref', () => {
+ test('via setup context + template ref', () => {
+ let i: any
const Child = defineVaporComponent({
setup(_, { expose }) {
expose({
})
const childRef = ref()
define({
- render: () => {
- const n0 = createComponent(Child)
+ setup: () => {
+ const n0 = (i = createComponent(Child))
+ setRef(currentInstance as VaporComponentInstance, n0, childRef)
return n0
},
}).render()
- expect(childRef.value).toBeTruthy()
+ expect(childRef.value).toBe(i.exposeProxy)
expect(childRef.value.foo).toBe(1)
expect(childRef.value.bar).toBe(2)
expect(childRef.value.baz).toBeUndefined()
})
- test.todo('via setup context + template ref (expose empty)', () => {
+ test('via setup context + template ref (expose empty)', () => {
let childInstance: VaporComponentInstance | null = null
const Child = defineVaporComponent({
setup(_) {
})
const childRef = shallowRef()
define({
- render: () => {
+ setup: () => {
const n0 = createComponent(Child)
- setRef(n0, childRef)
+ setRef(currentInstance as VaporComponentInstance, n0, childRef)
return n0
},
}).render()
- expect(childInstance!.exposed).toBeUndefined()
+ expect(childInstance!.exposed).toBeNull()
expect(childRef.value).toBe(childInstance!)
})
-import type { NodeRef } from '../../src/dom/templateRef'
+import type { NodeRef } from '../../src/apiTemplateRef'
import {
createFor,
createIf,
+ createTemplateRefSetter,
insert,
renderEffect,
- setRef,
setText,
template,
} from '../../src'
const define = makeRender()
-describe.todo('api: template ref', () => {
+describe('api: template ref', () => {
test('string ref mount', () => {
const t0 = template('<div ref="refKey"></div>')
const el = ref(null)
},
render() {
const n0 = t0()
- setRef(n0 as Element, 'refKey')
+ createTemplateRefSetter()(n0 as Element, 'refKey')
return n0
},
})
const n0 = t0()
let r0: NodeRef | undefined
renderEffect(() => {
- r0 = setRef(n0 as Element, refKey.value, r0)
+ r0 = createTemplateRefSetter()(n0 as Element, refKey.value, r0)
})
return n0
},
expect(fooEl.value).toBe(null)
})
- it('string ref unmount', async () => {
+ it.todo('string ref unmount', async () => {
const t0 = template('<div></div>')
const el = ref(null)
const toggle = ref(true)
}
},
render() {
+ const setRef = createTemplateRefSetter()
const n0 = createIf(
() => toggle.value,
() => {
const { render } = define({
render() {
const n0 = t0()
- setRef(n0 as Element, fn)
+ createTemplateRefSetter()(n0 as Element, fn)
return n0
},
})
const n0 = t0()
let r0: NodeRef | undefined
renderEffect(() => {
- r0 = setRef(n0 as Element, fn.value, r0)
+ r0 = createTemplateRefSetter()(n0 as Element, fn.value, r0)
})
return n0
},
expect(fn2.mock.calls[0][0]).toBe(host.children[0])
})
- it('function ref unmount', async () => {
+ it.todo('function ref unmount', async () => {
const fn = vi.fn()
const toggle = ref(true)
() => toggle.value,
() => {
const n1 = t0()
- setRef(n1 as Element, fn)
+ createTemplateRefSetter()(n1 as Element, fn)
return n1
},
)
},
render() {
const n0 = t0()
- setRef(n0 as Element, 'refKey')
+ createTemplateRefSetter()(n0 as Element, 'refKey')
return n0
},
})
const n0 = t0()
const n1 = t1()
const n2 = t2()
- setRef(n0 as Element, 'refKey1')
- setRef(n1 as Element, 'refKey2')
- setRef(n2 as Element, 'refKey3')
+ createTemplateRefSetter()(n0 as Element, 'refKey1')
+ createTemplateRefSetter()(n1 as Element, 'refKey2')
+ createTemplateRefSetter()(n2 as Element, 'refKey3')
return [n0, n1, n2]
},
})
const { render } = define({
render() {
const n0 = t0()
- setRef(n0 as Element, el)
+ createTemplateRefSetter()(n0 as Element, el)
renderEffect(() => {
setText(n0, el.value && el.value.getAttribute('id'))
})
let r0: NodeRef | undefined
let r1: NodeRef | undefined
renderEffect(() => {
- r0 = setRef(n0 as Element, refToggle.value ? 'foo' : 'bar', r0)
+ r0 = createTemplateRefSetter()(
+ n0 as Element,
+ refToggle.value ? 'foo' : 'bar',
+ r0,
+ )
})
renderEffect(() => {
- r1 = setRef(n1 as Element, refToggle.value ? 'bar' : 'foo', r1)
+ r1 = createTemplateRefSetter()(
+ n1 as Element,
+ refToggle.value ? 'bar' : 'foo',
+ r1,
+ )
})
watchEffect(
() => {
})
// #1789
- test('toggle the same ref to different elements', async () => {
+ test.todo('toggle the same ref to different elements', async () => {
const refToggle = ref(false)
const spy = vi.fn()
const { render } = define({
render() {
const instance = currentInstance!
+ const setRef = createTemplateRefSetter()
const n0 = createIf(
() => refToggle.value,
() => {
})
// compiled output of v-for + template ref
- test('ref in v-for', async () => {
+ test.todo('ref in v-for', async () => {
const show = ref(true)
const list = reactive([1, 2, 3])
const listRefs = ref([])
() => list,
state => {
const n1 = t1()
- setRef(n1 as Element, listRefs, undefined, true)
+ createTemplateRefSetter()(
+ n1 as Element,
+ listRefs,
+ undefined,
+ true,
+ )
renderEffect(() => {
const [item] = state
setText(n1, item)
expect(mapRefs()).toMatchObject(['2', '3', '4'])
})
- test('named ref in v-for', async () => {
+ test.todo('named ref in v-for', async () => {
const show = ref(true)
const list = reactive([1, 2, 3])
const listRefs = ref([])
() => list,
state => {
const n1 = t1()
- setRef(n1 as Element, 'listRefs', undefined, true)
+ createTemplateRefSetter()(
+ n1 as Element,
+ 'listRefs',
+ undefined,
+ true,
+ )
renderEffect(() => {
const [item] = state
setText(n1, item)
})
// #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('<div><div></div><ul></ul></div>')
- const t1 = template('<li></li>')
- const { render } = define({
- setup() {
- return { listRefs }
- },
- render() {
- const n0 = t0()
- const n1 = n0.firstChild
- const n2 = n1!.nextSibling!
- const n3 = createFor(
- () => list.value,
- state => {
- const n4 = t1()
- setRef(n4 as Element, 'listRefs', undefined, true)
- renderEffect(() => {
- const [item] = state
- setText(n4, item)
- })
- return n4
- },
- )
- 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(
- '<div><div>[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>1</li><li>2</li><li>3</li><!--for--></ul></div>',
- )
-
- list.value.splice(0, 1)
- await nextTick()
- expect(String(listRefs.value)).toBe(
- '[object HTMLLIElement],[object HTMLLIElement]',
- )
- expect(host.innerHTML).toBe(
- '<div><div>[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>2</li><li>3</li><!--for--></ul></div>',
- )
- })
+ test.todo(
+ 'named ref in v-for , should be responsive when rendering',
+ async () => {
+ const list = ref([1, 2, 3])
+ const listRefs = ref([])
+
+ const t0 = template('<div><div></div><ul></ul></div>')
+ const t1 = template('<li></li>')
+ const { render } = define({
+ setup() {
+ return { listRefs }
+ },
+ render() {
+ const n0 = t0()
+ const n1 = n0.firstChild
+ const n2 = n1!.nextSibling!
+ const n3 = createFor(
+ () => list.value,
+ state => {
+ const n4 = t1()
+ createTemplateRefSetter()(
+ n4 as Element,
+ 'listRefs',
+ undefined,
+ true,
+ )
+ renderEffect(() => {
+ const [item] = state
+ setText(n4, item)
+ })
+ return n4
+ },
+ )
+ 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(
+ '<div><div>[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>1</li><li>2</li><li>3</li><!--for--></ul></div>',
+ )
+
+ list.value.splice(0, 1)
+ await nextTick()
+ expect(String(listRefs.value)).toBe(
+ '[object HTMLLIElement],[object HTMLLIElement]',
+ )
+ expect(host.innerHTML).toBe(
+ '<div><div>[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>2</li><li>3</li><!--for--></ul></div>',
+ )
+ },
+ )
// TODO: need to implement Component slots
// test('string ref inside slots', async () => {
// spy(this.$refs.foo.tag)
// })
// const n0 = createComponent(Child)
- // setRef(n0, 'foo')
+ // createTemplateRefSetter()(n0, 'foo')
// return n0
// },
// })
import { createComponent, setRef, template } from '../src'
import { makeRender } from './_utils'
import type { VaporComponent } from '../src/component'
-import type { RefEl } from '../src/dom/templateRef'
+import type { RefEl } from '../src/apiTemplateRef'
const define = makeRender()
currentInstance,
getExposed,
isVaporComponent,
-} from '../component'
+} from './component'
import {
type SchedulerJob,
callWithErrorHandling,
export type NodeRef = string | Ref | ((ref: Element) => void)
export type RefEl = Element | VaporComponentInstance
+export type setRefFn = (
+ el: RefEl,
+ ref: NodeRef,
+ oldRef?: NodeRef,
+ refFor?: boolean,
+) => NodeRef | undefined
+
+export function createTemplateRefSetter(): setRefFn {
+ const instance = currentInstance as VaporComponentInstance
+ return (...args) => setRef(instance, ...args)
+}
+
/**
* Function for handling a template ref
*/
export function setRef(
+ instance: VaporComponentInstance,
el: RefEl,
ref: NodeRef,
oldRef?: NodeRef,
refFor = false,
): NodeRef | undefined {
- if (!currentInstance || currentInstance.isUnmounted) return
+ if (!instance || instance.isUnmounted) return
- const setupState = currentInstance.setupState || {}
+ const setupState: any = __DEV__ ? instance.setupState || {} : null
const refValue = isVaporComponent(el) ? getExposed(el) || el : el
const refs =
- currentInstance.refs === EMPTY_OBJ
- ? (currentInstance.refs = {})
- : currentInstance.refs
+ instance.refs === EMPTY_OBJ ? (instance.refs = {}) : instance.refs
// dynamic ref changed. unset old ref
if (oldRef != null && oldRef !== ref) {
if (isString(oldRef)) {
refs[oldRef] = null
- if (hasOwn(setupState, oldRef)) {
+ if (__DEV__ && hasOwn(setupState, oldRef)) {
setupState[oldRef] = null
}
} else if (isRef(oldRef)) {
}
invokeRefSetter(refValue)
+ // TODO this gets called repeatedly in renderEffect when it's dynamic ref?
onScopeDispose(() => invokeRefSetter())
} else {
const _isString = isString(ref)
const doSet: SchedulerJob = () => {
if (refFor) {
existing = _isString
- ? hasOwn(setupState, ref)
+ ? __DEV__ && hasOwn(setupState, ref)
? setupState[ref]
: refs[ref]
: ref.value
existing = [refValue]
if (_isString) {
refs[ref] = existing
- if (hasOwn(setupState, ref)) {
+ if (__DEV__ && hasOwn(setupState, ref)) {
setupState[ref] = refs[ref]
// if setupState[ref] is a reactivity ref,
// the existing will also become reactivity too
}
} else if (_isString) {
refs[ref] = refValue
- if (hasOwn(setupState, ref)) {
+ if (__DEV__ && hasOwn(setupState, ref)) {
setupState[ref] = refValue
}
} else if (_isRef) {
doSet.id = -1
queuePostFlushCb(doSet)
+ // TODO this gets called repeatedly in renderEffect when it's dynamic ref?
onScopeDispose(() => {
queuePostFlushCb(() => {
if (isArray(existing)) {
remove(existing, refValue)
} else if (_isString) {
refs[ref] = null
- if (hasOwn(setupState, ref)) {
+ if (__DEV__ && hasOwn(setupState, ref)) {
setupState[ref] = null
}
} else if (_isRef) {
})
})
} else if (__DEV__) {
- // warn('Invalid template ref type:', ref, `(${typeof ref})`)
+ warn('Invalid template ref type:', ref, `(${typeof ref})`)
}
}
return ref
pauseTracking,
proxyRefs,
resetTracking,
+ unref,
} from '@vue/reactivity'
import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared'
import {
if (instance.exposed) {
return (
instance.exposeProxy ||
- (instance.exposeProxy = proxyRefs(markRaw(instance.exposed)))
+ (instance.exposeProxy = new Proxy(markRaw(instance.exposed), {
+ get: (target, key) => unref(target[key as any]),
+ }))
)
}
}
export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
export { createIf } from './apiCreateIf'
export { createFor } from './apiCreateFor'
-export { setRef } from './dom/templateRef'
+export { createTemplateRefSetter } from './apiTemplateRef'