From: 三咲智子 Kevin Deng Date: Sun, 3 Dec 2023 10:36:01 +0000 (+0800) Subject: feat: custom directlve X-Git-Tag: v3.6.0-alpha.1~16^2~756 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=db151e1b43c39fb8a7866b7bdf95933bb0630fc2;p=thirdparty%2Fvuejs%2Fcore.git feat: custom directlve closes #19 --- diff --git a/packages/compiler-vapor/package.json b/packages/compiler-vapor/package.json index 2f57f3777f..adc2681c84 100644 --- a/packages/compiler-vapor/package.json +++ b/packages/compiler-vapor/package.json @@ -37,8 +37,8 @@ }, "homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-vapor#readme", "dependencies": { - "@vue/compiler-dom": "3.3.8", - "@vue/shared": "3.3.8", + "@vue/compiler-dom": "workspace:*", + "@vue/shared": "workspace:*", "source-map-js": "^1.0.2" } } diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 07011c9737..0b84f86387 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -9,6 +9,7 @@ import { NodeTypes, BindingTypes, isSimpleIdentifier, + createSimpleExpression, } from '@vue/compiler-dom' import { type IRDynamicChildren, @@ -20,7 +21,7 @@ import { SetEventIRNode, } from './ir' import { SourceMapGenerator } from 'source-map-js' -import { isString } from '@vue/shared' +import { camelize, capitalize, isString } from '@vue/shared' // remove when stable // @ts-expect-error @@ -361,6 +362,23 @@ function genOperation(oper: OperationNode, context: CodegenContext) { ) return } + case IRNodeTypes.WITH_DIRECTIVE: { + // TODO merge directive for the same node + pushWithNewline(`${vaporHelper('withDirectives')}(n${oper.element}, [[`) + + // TODO resolve directive + const directiveReference = camelize(`v-${oper.name}`) + if (context.bindingMetadata[directiveReference]) { + genExpression(createSimpleExpression(directiveReference), context) + } + + if (oper.binding) { + push(', ') + genExpression(oper.binding, context) + } + push(']])') + return + } default: return checkNever(oper) } @@ -406,6 +424,7 @@ function genExpression( vaporHelper, push, }: CodegenContext, + { unref = true }: { unref?: boolean } = {}, ) { if (isString(exp)) return push(exp) @@ -418,14 +437,15 @@ function genExpression( if (exp.isStatic) { content = JSON.stringify(content) } else { - switch (bindingMetadata[content]) { - case BindingTypes.SETUP_REF: - content += '.value' - break - case BindingTypes.SETUP_MAYBE_REF: - content = `${vaporHelper('unref')}(${content})` - break - } + if (unref) + switch (bindingMetadata[content]) { + case BindingTypes.SETUP_REF: + content += '.value' + break + case BindingTypes.SETUP_MAYBE_REF: + content = `${vaporHelper('unref')}(${content})` + break + } if (prefixIdentifiers && !inline) { if (isSimpleIdentifier(content)) name = content content = `_ctx.${content}` diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 4e4c06e52b..0ed0ebfb3f 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -20,6 +20,8 @@ export enum IRNodeTypes { PREPEND_NODE, APPEND_NODE, CREATE_TEXT_NODE, + + WITH_DIRECTIVE, } export interface BaseIRNode { @@ -110,6 +112,13 @@ export interface AppendNodeIRNode extends BaseIRNode { parent: number } +export interface WithDirectiveIRNode extends BaseIRNode { + type: IRNodeTypes.WITH_DIRECTIVE + element: number + name: string + binding: IRExpression | undefined +} + export type IRNode = | OperationNode | RootIRNode @@ -124,6 +133,7 @@ export type OperationNode = | InsertNodeIRNode | PrependNodeIRNode | AppendNodeIRNode + | WithDirectiveIRNode export interface IRDynamicInfo { id: number | null diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 9a144ac8f1..9ac60cd9ba 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -7,7 +7,7 @@ import { createCompilerError, ElementTypes, } from '@vue/compiler-dom' -import { isVoidTag } from '@vue/shared' +import { isBuiltInDirective, isVoidTag } from '@vue/shared' import { NodeTransform, TransformContext } from '../transform' import { IRNodeTypes } from '../ir' @@ -74,6 +74,14 @@ function transformProp( const directiveTransform = context.options.directiveTransforms[name] if (directiveTransform) { directiveTransform(prop, node, context) + } else if (!isBuiltInDirective(name)) { + context.registerOperation({ + type: IRNodeTypes.WITH_DIRECTIVE, + element: context.reference(), + name, + binding: prop.exp, + loc: prop.loc, + }) } switch (name) { diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 05310689ed..c6195f36ef 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -1,6 +1,6 @@ import { EffectScope } from '@vue/reactivity' - import { Block, BlockFn } from './render' +import { DirectiveBinding } from './directives' export interface ComponentInternalInstance { uid: number @@ -11,9 +11,29 @@ export interface ComponentInternalInstance { component: BlockFn isMounted: boolean + /** directives */ + dirs: Map // TODO: registory of provides, appContext, lifecycles, ... } +// TODO +export let currentInstance: ComponentInternalInstance | null = null + +export const getCurrentInstance: () => ComponentInternalInstance | null = () => + currentInstance + +export const setCurrentInstance = (instance: ComponentInternalInstance) => { + currentInstance = instance + instance.scope.on() +} + +export const unsetCurrentInstance = () => { + currentInstance && currentInstance.scope.off() + currentInstance = null +} + +export interface ComponentPublicInstance {} + let uid = 0 export const createComponentInstance = ( component: BlockFn, @@ -26,6 +46,8 @@ export const createComponentInstance = ( component, isMounted: false, + + dirs: new Map(), // TODO: registory of provides, appContext, lifecycles, ... } return instance diff --git a/packages/runtime-vapor/src/directives.ts b/packages/runtime-vapor/src/directives.ts new file mode 100644 index 0000000000..03d8c62df0 --- /dev/null +++ b/packages/runtime-vapor/src/directives.ts @@ -0,0 +1,81 @@ +import { isFunction } from '@vue/shared' +import { currentInstance, type ComponentPublicInstance } from './component' + +export interface DirectiveBinding { + instance: ComponentPublicInstance | null + value: V + oldValue: V | null + arg?: string + // TODO: should we support modifiers for custom directives? + // modifiers: DirectiveModifiers + dir: ObjectDirective +} + +export type DirectiveHook = ( + node: T, + binding: DirectiveBinding, +) => void + +// create node -> `created` -> node operation -> `beforeMount` -> node mounted -> `mounted` +// effect update -> `beforeUpdate` -> node updated -> `updated` +// `beforeUnmount`-> node unmount -> `unmounted` +export interface ObjectDirective { + created?: DirectiveHook + // beforeMount?: DirectiveHook + // mounted?: DirectiveHook + // beforeUpdate?: DirectiveHook + // updated?: DirectiveHook + // beforeUnmount?: DirectiveHook + // unmounted?: DirectiveHook + // getSSRProps?: SSRDirectiveHook + deep?: boolean +} + +export type FunctionDirective = DirectiveHook +export type Directive = + | ObjectDirective + | FunctionDirective + +export type DirectiveArguments = Array< + | [Directive | undefined] + | [Directive | undefined, value: any] + | [Directive | undefined, value: any, argument: string] +> + +export function withDirectives( + node: T, + directives: DirectiveArguments, +): T { + if (!currentInstance) { + // TODO warning + return node + } + + if (!currentInstance.dirs.has(node)) currentInstance.dirs.set(node, []) + const bindings = currentInstance.dirs.get(node)! + + // TODO public instance + const instance = currentInstance as any + for (const directive of directives) { + let [dir, value, arg] = directive + if (!dir) continue + if (isFunction(dir)) { + // TODO function directive + dir = { + created: dir, + } satisfies ObjectDirective + } + + const binding: DirectiveBinding = { + dir, + instance, + value, + oldValue: void 0, + arg, + } + if (dir.created) dir.created(node, binding) + bindings.push(binding) + } + + return node +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index cf6be7c44d..5533dfc826 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -35,9 +35,10 @@ export { getCurrentScope, onScopeDispose, } from '@vue/reactivity' -export { effect } from './scheduler' +export { withModifiers, withKeys } from '@vue/runtime-dom' + export * from './on' export * from './render' export * from './template' export * from './scheduler' -export { withModifiers, withKeys } from '@vue/runtime-dom' +export * from './directives' diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index 49c30e6e10..82a3bfde7e 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -5,7 +5,11 @@ import { toDisplayString, } from '@vue/shared' -import { ComponentInternalInstance, createComponentInstance } from './component' +import { + ComponentInternalInstance, + createComponentInstance, + setCurrentInstance, +} from './component' export type Block = Node | Fragment | Block[] export type ParentBlock = ParentNode | Node[] @@ -17,6 +21,7 @@ export function render( container: string | ParentNode, ): ComponentInternalInstance { const instance = createComponentInstance(comp) + setCurrentInstance(instance) mountComponent(instance, (container = normalizeContainer(container))) return instance } diff --git a/playground/src/directive.vue b/playground/src/directive.vue new file mode 100644 index 0000000000..74bb387ef6 --- /dev/null +++ b/playground/src/directive.vue @@ -0,0 +1,12 @@ + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82ca550ff8..4b45499bde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -269,11 +269,11 @@ importers: packages/compiler-vapor: dependencies: '@vue/compiler-dom': - specifier: 3.3.8 - version: 3.3.8 + specifier: workspace:* + version: link:../compiler-dom '@vue/shared': - specifier: 3.3.8 - version: 3.3.8 + specifier: workspace:* + version: link:../shared source-map-js: specifier: ^1.0.2 version: 1.0.2 @@ -1825,22 +1825,6 @@ packages: pretty-format: 29.7.0 dev: true - /@vue/compiler-core@3.3.8: - resolution: {integrity: sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==} - dependencies: - '@babel/parser': 7.23.5 - '@vue/shared': 3.3.8 - estree-walker: 2.0.2 - source-map-js: 1.0.2 - dev: false - - /@vue/compiler-dom@3.3.8: - resolution: {integrity: sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==} - dependencies: - '@vue/compiler-core': 3.3.8 - '@vue/shared': 3.3.8 - dev: false - /@vue/consolidate@0.17.3: resolution: {integrity: sha512-nl0SWcTMzaaTnJ5G6V8VlMDA1CVVrNnaQKF1aBZU3kXtjgU9jtHMsEAsgjoRUx+T0EVJk9TgbmxGhK3pOk22zw==} engines: {node: '>= 0.12.0'} @@ -1850,10 +1834,6 @@ packages: resolution: {integrity: sha512-zzyb+tVvzmOePv8Gp4sefP/7CKidx4WiJDfKPP698b9bN5jSFtmSOg4nvPoJEE1ICKeAEgdRKVneYJ8Mp7C/WA==} dev: false - /@vue/shared@3.3.8: - resolution: {integrity: sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==} - dev: false - /@zeit/schemas@2.29.0: resolution: {integrity: sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==} dev: true