From 782d60475d71173e31771a5c99b586334df1c098 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, 20 Jan 2024 23:48:10 +0800 Subject: [PATCH] feat: basic template ref --- packages/compiler-vapor/src/compile.ts | 3 +- packages/compiler-vapor/src/generate.ts | 11 ++++ packages/compiler-vapor/src/ir.ts | 8 +++ .../src/transforms/transformElement.ts | 4 +- .../src/transforms/transformRef.ts | 31 +++++++++++ .../compiler-vapor/src/transforms/vBind.ts | 21 +++++--- packages/runtime-vapor/src/dom.ts | 1 + packages/runtime-vapor/src/dom/templateRef.ts | 52 +++++++++++++++++++ packages/runtime-vapor/src/render.ts | 7 ++- playground/src/todo-mvc.vue | 9 +++- 10 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 packages/compiler-vapor/src/transforms/transformRef.ts create mode 100644 packages/runtime-vapor/src/dom/templateRef.ts diff --git a/packages/compiler-vapor/src/compile.ts b/packages/compiler-vapor/src/compile.ts index 6d304ae20..ab74e5ffd 100644 --- a/packages/compiler-vapor/src/compile.ts +++ b/packages/compiler-vapor/src/compile.ts @@ -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, diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index d5f621510..b66a4629c 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -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, diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index ae76aa92a..20e0d14ac 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -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 diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 5b3787571..1e519935c 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -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, ): 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}"` diff --git a/packages/compiler-vapor/src/transforms/transformRef.ts b/packages/compiler-vapor/src/transforms/transformRef.ts new file mode 100644 index 000000000..141b01fbb --- /dev/null +++ b/packages/compiler-vapor/src/transforms/transformRef.ts @@ -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, + }) +} diff --git a/packages/compiler-vapor/src/transforms/vBind.ts b/packages/compiler-vapor/src/transforms/vBind.ts index 4e013c836..ff2799a15 100644 --- a/packages/compiler-vapor/src/transforms/vBind.ts +++ b/packages/compiler-vapor/src/transforms/vBind.ts @@ -4,10 +4,20 @@ 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 @@ -15,12 +25,9 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => { // 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')) { diff --git a/packages/runtime-vapor/src/dom.ts b/packages/runtime-vapor/src/dom.ts index 0c8a53222..2641a33d1 100644 --- a/packages/runtime-vapor/src/dom.ts +++ b/packages/runtime-vapor/src/dom.ts @@ -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) { diff --git a/packages/runtime-vapor/src/dom/templateRef.ts b/packages/runtime-vapor/src/dom/templateRef.ts new file mode 100644 index 000000000..fcedbd1e2 --- /dev/null +++ b/packages/runtime-vapor/src/dom/templateRef.ts @@ -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})`) + } + } +} diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index d422db08a..10b121a9e 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -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 diff --git a/playground/src/todo-mvc.vue b/playground/src/todo-mvc.vue index 6ebac01f5..e6228f8c4 100644 --- a/playground/src/todo-mvc.vue +++ b/playground/src/todo-mvc.vue @@ -1,5 +1,5 @@