Skip to content

Commit

Permalink
feat: basic template ref
Browse files Browse the repository at this point in the history
sxzz committed Jan 20, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 6a26db2 commit 782d604
Showing 10 changed files with 135 additions and 12 deletions.
3 changes: 2 additions & 1 deletion packages/compiler-vapor/src/compile.ts
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ import { transformVText } from './transforms/vText'
import { transformVBind } from './transforms/vBind'
import { transformVOn } from './transforms/vOn'
import { transformVShow } from './transforms/vShow'
import { transformRef } from './transforms/transformRef'
import { transformInterpolation } from './transforms/transformInterpolation'
import type { HackOptions } from './ir'

@@ -95,7 +96,7 @@ export function getBaseTransformPreset(
prefixIdentifiers?: boolean,
): TransformPreset {
return [
[transformOnce, transformInterpolation, transformElement],
[transformOnce, transformRef, transformInterpolation, transformElement],
{
bind: transformVBind,
on: transformVOn,
11 changes: 11 additions & 0 deletions packages/compiler-vapor/src/generate.ts
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ import {
type SetEventIRNode,
type SetHtmlIRNode,
type SetPropIRNode,
type SetRefIRNode,
type SetTextIRNode,
type VaporHelper,
type WithDirectiveIRNode,
@@ -386,6 +387,8 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
return genSetEvent(oper, context)
case IRNodeTypes.SET_HTML:
return genSetHtml(oper, context)
case IRNodeTypes.SET_REF:
return genSetRef(oper, context)
case IRNodeTypes.CREATE_TEXT_NODE:
return genCreateTextNode(oper, context)
case IRNodeTypes.INSERT_NODE:
@@ -442,6 +445,14 @@ function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
)
}

function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setRef'), `n${oper.element}`, () =>
genExpression(oper.value, context),
)
}

function genCreateTextNode(
oper: CreateTextNodeIRNode,
context: CodegenContext,
8 changes: 8 additions & 0 deletions packages/compiler-vapor/src/ir.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ export enum IRNodeTypes {
SET_TEXT,
SET_EVENT,
SET_HTML,
SET_REF,

INSERT_NODE,
PREPEND_NODE,
@@ -93,6 +94,12 @@ export interface SetHtmlIRNode extends BaseIRNode {
value: IRExpression
}

export interface SetRefIRNode extends BaseIRNode {
type: IRNodeTypes.SET_REF
element: number
value: IRExpression
}

export interface CreateTextNodeIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_TEXT_NODE
id: number
@@ -134,6 +141,7 @@ export type OperationNode =
| SetTextIRNode
| SetEventIRNode
| SetHtmlIRNode
| SetRefIRNode
| CreateTextNodeIRNode
| InsertNodeIRNode
| PrependNodeIRNode
4 changes: 3 additions & 1 deletion packages/compiler-vapor/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import {
ElementTypes,
NodeTypes,
} from '@vue/compiler-dom'
import { isBuiltInDirective, isVoidTag } from '@vue/shared'
import { isBuiltInDirective, isReservedProp, isVoidTag } from '@vue/shared'
import type { NodeTransform, TransformContext } from '../transform'
import { IRNodeTypes, type VaporDirectiveNode } from '../ir'

@@ -60,6 +60,8 @@ function transformProp(
context: TransformContext<ElementNode>,
): void {
const { name, loc } = prop
if (isReservedProp(name)) return

if (prop.type === NodeTypes.ATTRIBUTE) {
context.template += ` ${name}`
if (prop.value) context.template += `="${prop.value.content}"`
31 changes: 31 additions & 0 deletions packages/compiler-vapor/src/transforms/transformRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
NodeTypes,
type SimpleExpressionNode,
findProp,
} from '@vue/compiler-dom'
import type { NodeTransform } from '../transform'
import { type IRExpression, IRNodeTypes } from '../ir'
import { normalizeBindShorthand } from './vBind'

export const transformRef: NodeTransform = (node, context) => {
if (node.type !== NodeTypes.ELEMENT) return
const dir = findProp(node, 'ref', false, true)

if (!dir) return

let value: IRExpression
if (dir.type === NodeTypes.DIRECTIVE) {
value =
(dir.exp as SimpleExpressionNode | undefined) ||
normalizeBindShorthand(dir.arg as SimpleExpressionNode)
} else {
value = dir.value ? JSON.stringify(dir.value.content) : '""'
}

context.registerOperation({
type: IRNodeTypes.SET_REF,
element: context.reference(),
value,
loc: dir.loc,
})
}
21 changes: 14 additions & 7 deletions packages/compiler-vapor/src/transforms/vBind.ts
Original file line number Diff line number Diff line change
@@ -4,23 +4,30 @@ import {
createCompilerError,
createSimpleExpression,
} from '@vue/compiler-core'
import { camelize } from '@vue/shared'
import { camelize, isReservedProp } from '@vue/shared'
import { IRNodeTypes } from '../ir'
import type { DirectiveTransform } from '../transform'

export function normalizeBindShorthand(
arg: SimpleExpressionNode,
): SimpleExpressionNode {
// shorthand syntax https://github.com/vuejs/core/pull/9451
const propName = camelize(arg.content)
const exp = createSimpleExpression(propName, false, arg.loc)
exp.ast = null
return exp
}

export const transformVBind: DirectiveTransform = (dir, node, context) => {
let { arg, exp, loc, modifiers } = dir

if (!arg) {
// TODO support v-bind="{}"
return
}
if (!exp) {
// shorthand syntax https://github.com/vuejs/core/pull/9451
const propName = camelize(arg.content)
exp = createSimpleExpression(propName, false, arg.loc)
exp.ast = null
}
if (arg.isStatic && isReservedProp(arg.content)) return

if (!exp) exp = normalizeBindShorthand(arg)

let camel = false
if (modifiers.includes('camel')) {
1 change: 1 addition & 0 deletions packages/runtime-vapor/src/dom.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import { isArray, toDisplayString } from '@vue/shared'
import type { Block, ParentBlock } from './render'

export * from './dom/patchProp'
export * from './dom/templateRef'

export function insert(block: Block, parent: Node, anchor: Node | null = null) {
// if (!isHydrating) {
52 changes: 52 additions & 0 deletions packages/runtime-vapor/src/dom/templateRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { type Ref, type SchedulerJob, isRef } from '@vue/reactivity'
import { currentInstance } from '../component'
import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling'
import { hasOwn, isFunction, isString } from '@vue/shared'
import { warn } from '../warning'
import { queuePostRenderEffect } from '../scheduler'

export type NodeRef = string | Ref | ((ref: Element) => void)

/**
* Function for handling a template ref
*/
export function setRef(el: Element, ref: NodeRef) {
if (!currentInstance) return
const { setupState, isUnmounted } = currentInstance

if (isFunction(ref)) {
callWithErrorHandling(ref, currentInstance, VaporErrorCodes.FUNCTION_REF, [
el,
// refs,
])
} else {
const _isString = isString(ref)
const _isRef = isRef(ref)

if (_isString || _isRef) {
const doSet = () => {
if (_isString) {
if (hasOwn(setupState, ref)) {
setupState[ref] = el
}
} else if (_isRef) {
ref.value = el
} else if (__DEV__) {
warn('Invalid template ref type:', ref, `(${typeof ref})`)
}
}
// #9908 ref on v-for mutates the same array for both mount and unmount
// and should be done together
if (isUnmounted /* || isVFor */) {
doSet()
} else {
// #1789: set new refs in a post job so that they don't get overwritten
// by unmounting ones.
;(doSet as SchedulerJob).id = -1
queuePostRenderEffect(doSet)
}
} else if (__DEV__) {
warn('Invalid template ref type:', ref, `(${typeof ref})`)
}
}
}
7 changes: 5 additions & 2 deletions packages/runtime-vapor/src/render.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import {
import { initProps } from './componentProps'
import { invokeDirectiveHook } from './directive'
import { insert, remove } from './dom'
import { queuePostRenderEffect } from './scheduler'

export type Block = Node | Fragment | Block[]
export type ParentBlock = ParentNode | Node[]
@@ -78,8 +79,10 @@ export function mountComponent(
instance.isMounted = true

// hook: mounted
invokeDirectiveHook(instance, 'mounted')
m && invokeArrayFns(m)
queuePostRenderEffect(() => {
invokeDirectiveHook(instance, 'mounted')
m && invokeArrayFns(m)
})
reset()

return instance
9 changes: 8 additions & 1 deletion playground/src/todo-mvc.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script setup lang="ts">
import { ref } from 'vue/vapor'
import { onMounted, ref } from 'vue/vapor'
interface Task {
title: string
completed: boolean
}
const tasks = ref<Task[]>([])
const value = ref('hello')
const inputRef = ref<HTMLInputElement>()
function handleAdd() {
tasks.value.push({
@@ -16,6 +17,11 @@ function handleAdd() {
// TODO: clear input
value.value = ''
}
onMounted(() => {
console.log('onMounted')
console.log(inputRef.value)
})
</script>

<template>
@@ -41,6 +47,7 @@ function handleAdd() {
<li>
<input
type="text"
:ref="el => (inputRef = el)"
:value="value"
@input="evt => (value = evt.target.value)"
/>

0 comments on commit 782d604

Please sign in to comment.