},
"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"
}
}
NodeTypes,
BindingTypes,
isSimpleIdentifier,
+ createSimpleExpression,
} from '@vue/compiler-dom'
import {
type IRDynamicChildren,
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
)
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)
}
vaporHelper,
push,
}: CodegenContext,
+ { unref = true }: { unref?: boolean } = {},
) {
if (isString(exp)) return push(exp)
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}`
PREPEND_NODE,
APPEND_NODE,
CREATE_TEXT_NODE,
+
+ WITH_DIRECTIVE,
}
export interface BaseIRNode {
parent: number
}
+export interface WithDirectiveIRNode extends BaseIRNode {
+ type: IRNodeTypes.WITH_DIRECTIVE
+ element: number
+ name: string
+ binding: IRExpression | undefined
+}
+
export type IRNode =
| OperationNode
| RootIRNode
| InsertNodeIRNode
| PrependNodeIRNode
| AppendNodeIRNode
+ | WithDirectiveIRNode
export interface IRDynamicInfo {
id: number | null
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'
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) {
import { EffectScope } from '@vue/reactivity'
-
import { Block, BlockFn } from './render'
+import { DirectiveBinding } from './directives'
export interface ComponentInternalInstance {
uid: number
component: BlockFn
isMounted: boolean
+ /** directives */
+ dirs: Map<Node, DirectiveBinding[]>
// 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,
component,
isMounted: false,
+
+ dirs: new Map(),
// TODO: registory of provides, appContext, lifecycles, ...
}
return instance
--- /dev/null
+import { isFunction } from '@vue/shared'
+import { currentInstance, type ComponentPublicInstance } from './component'
+
+export interface DirectiveBinding<V = any> {
+ instance: ComponentPublicInstance | null
+ value: V
+ oldValue: V | null
+ arg?: string
+ // TODO: should we support modifiers for custom directives?
+ // modifiers: DirectiveModifiers
+ dir: ObjectDirective<any, V>
+}
+
+export type DirectiveHook<T = any | null, V = any> = (
+ node: T,
+ binding: DirectiveBinding<V>,
+) => void
+
+// create node -> `created` -> node operation -> `beforeMount` -> node mounted -> `mounted`
+// effect update -> `beforeUpdate` -> node updated -> `updated`
+// `beforeUnmount`-> node unmount -> `unmounted`
+export interface ObjectDirective<T = any, V = any> {
+ created?: DirectiveHook<T, V>
+ // beforeMount?: DirectiveHook<T, V>
+ // mounted?: DirectiveHook<T, V>
+ // beforeUpdate?: DirectiveHook<T, V>
+ // updated?: DirectiveHook<T, V>
+ // beforeUnmount?: DirectiveHook<T, V>
+ // unmounted?: DirectiveHook<T, V>
+ // getSSRProps?: SSRDirectiveHook
+ deep?: boolean
+}
+
+export type FunctionDirective<T = any, V = any> = DirectiveHook<T, V>
+export type Directive<T = any, V = any> =
+ | ObjectDirective<T, V>
+ | FunctionDirective<T, V>
+
+export type DirectiveArguments = Array<
+ | [Directive | undefined]
+ | [Directive | undefined, value: any]
+ | [Directive | undefined, value: any, argument: string]
+>
+
+export function withDirectives<T extends Node>(
+ 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
+}
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'
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[]
container: string | ParentNode,
): ComponentInternalInstance {
const instance = createComponentInstance(comp)
+ setCurrentInstance(instance)
mountComponent(instance, (container = normalizeContainer(container)))
return instance
}
--- /dev/null
+<script setup lang="ts">
+import { FunctionDirective } from '@vue/vapor'
+
+const vDirective: FunctionDirective<HTMLDivElement, undefined> = node => {
+ node.textContent = 'hello world'
+ node.style.color = 'red'
+}
+</script>
+
+<template>
+ <div v-directive />
+</template>
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
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'}
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