const t0 = _template("<div></div>")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _withDirectives(n1, [[_ctx.vExample, _ctx.msg]])
+ _withDirectives(n1, [[_ctx.vExample, () => _ctx.msg]])
return n0
}"
`;
const t0 = _template("<div></div>")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _withDirectives(n1, [[_ctx.vExample, _ctx.msg, _ctx.foo]])
+ _withDirectives(n1, [[_ctx.vExample, () => _ctx.msg, _ctx.foo]])
return n0
}"
`;
const t0 = _template("<div></div>")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _withDirectives(n1, [[_ctx.vExample, _ctx.msg, void 0, { bar: true }]])
+ _withDirectives(n1, [[_ctx.vExample, () => _ctx.msg, void 0, { bar: true }]])
return n0
}"
`;
const t0 = _template("<div></div>")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _withDirectives(n1, [[_ctx.vExample, _ctx.msg, "foo"]])
+ _withDirectives(n1, [[_ctx.vExample, () => _ctx.msg, "foo"]])
return n0
}"
`;
const t0 = _template("<div></div>")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _withDirectives(n1, [[_ctx.vExample, _ctx.msg, "foo", { bar: true }]])
+ _withDirectives(n1, [[_ctx.vExample, () => _ctx.msg, "foo", { bar: true }]])
return n0
}"
`;
import { transformVText } from './transforms/vText'
import { transformVBind } from './transforms/vBind'
import { transformVOn } from './transforms/vOn'
+import { transformVShow } from './transforms/vShow'
import { transformInterpolation } from './transforms/transformInterpolation'
import type { HackOptions } from './ir'
on: transformVOn,
html: transformVHtml,
text: transformVText,
+ show: transformVShow,
},
]
}
IRNodeTypes,
} from './ir'
import { SourceMapGenerator } from 'source-map-js'
-import { camelize, isString } from '@vue/shared'
+import { camelize, isString, makeMap } from '@vue/shared'
import type { Identifier } from '@babel/types'
// remove when stable
// TODO merge directive for the same node
pushWithNewline(`${vaporHelper('withDirectives')}(n${oper.element}, [[`)
- // TODO resolve directive
- const directiveReference = camelize(`v-${dir.name}`)
- if (bindingMetadata[directiveReference]) {
- const directiveExpression = createSimpleExpression(directiveReference)
- directiveExpression.ast = null
- genExpression(directiveExpression, context)
+ if (dir.name === 'show') {
+ push(vaporHelper('vShow'))
+ } else {
+ const directiveReference = camelize(`v-${dir.name}`)
+ // TODO resolve directive
+ if (bindingMetadata[directiveReference]) {
+ const directiveExpression = createSimpleExpression(directiveReference)
+ directiveExpression.ast = null
+ genExpression(directiveExpression, context)
+ }
}
if (dir.exp) {
- push(', ')
+ push(', () => ')
genExpression(dir.exp, context)
} else if (dir.arg || dir.modifiers.length) {
push(', void 0')
return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
}
+const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
+
function genExpression(node: IRExpression, context: CodegenContext): void {
const { push } = context
if (isString(node)) return push(node)
!context.prefixIdentifiers ||
!node.content.trim() ||
// there was a parsing error
- ast === false
+ ast === false ||
+ isLiteralWhitelisted(rawExpr)
) {
return push(rawExpr, NewlineType.None, loc)
}
type: IRNodeTypes.WITH_DIRECTIVE,
element: context.reference(),
dir: prop,
- loc: loc,
+ loc,
})
}
}
--- /dev/null
+import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
+import type { DirectiveTransform } from '../transform'
+import { IRNodeTypes } from '../ir'
+
+export const transformVShow: DirectiveTransform = (dir, node, context) => {
+ const { exp, loc } = dir
+ if (!exp) {
+ context.options.onError(
+ createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, loc),
+ )
+ }
+
+ context.registerOperation({
+ type: IRNodeTypes.WITH_DIRECTIVE,
+ element: context.reference(),
+ dir,
+ loc,
+ })
+}
-import { EffectScope } from '@vue/reactivity'
-import { Block } from './render'
-import { DirectiveBinding } from './directives'
+import { type Ref, EffectScope, ref } from '@vue/reactivity'
+import type { Block } from './render'
+import type { DirectiveBinding } from './directive'
import type { Data } from '@vue/shared'
export type SetupFn = (props: any, ctx: any) => Block | Data
scope: EffectScope
component: FunctionalComponent | ObjectComponent
- isMounted: boolean
+ get isMounted(): boolean
+ isMountedRef: Ref<boolean>
/** directives */
dirs: Map<Node, DirectiveBinding[]>
export const createComponentInstance = (
component: ObjectComponent | FunctionalComponent,
): ComponentInternalInstance => {
+ const isMountedRef = ref(false)
const instance: ComponentInternalInstance = {
uid: uid++,
block: null,
scope: new EffectScope(true /* detached */)!,
component,
- isMounted: false,
+ get isMounted() {
+ return isMountedRef.value
+ },
+ isMountedRef,
dirs: new Map(),
// TODO: registory of provides, appContext, lifecycles, ...
import { isFunction } from '@vue/shared'
import { currentInstance, type ComponentInternalInstance } from './component'
+import { effect } from './scheduler'
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
M extends string = string,
> {
instance: ComponentInternalInstance | null
+ source?: () => V
value: V
oldValue: V | null
arg?: A
// create node -> `created` -> node operation -> `beforeMount` -> node mounted -> `mounted`
// effect update -> `beforeUpdate` -> node updated -> `updated`
// `beforeUnmount`-> node unmount -> `unmounted`
-export interface ObjectDirective<
+export type DirectiveHookName =
+ | 'created'
+ | 'beforeMount'
+ | 'mounted'
+ // | 'beforeUpdate'
+ | 'updated'
+ | 'beforeUnmount'
+ | 'unmounted'
+export type ObjectDirective<
T = any,
V = any,
A = string,
M extends string = string,
-> {
- created?: DirectiveHook<T, V, A, M>
- beforeMount?: DirectiveHook<T, V, A, M>
- mounted?: DirectiveHook<T, V, A, M>
- // beforeUpdate?: DirectiveHook<T, V,A,M>
- // updated?: DirectiveHook<T, V,A,M>
- beforeUnmount?: DirectiveHook<T, V, A, M>
- unmounted?: DirectiveHook<T, V, A, M>
- // getSSRProps?: SSRDirectiveHook
- // deep?: boolean
+> = {
+ [K in DirectiveHookName]?: DirectiveHook<T, V, A, M> | undefined
+} & {
+ deep?: boolean
}
-export type DirectiveHookName = Exclude<keyof ObjectDirective, 'deep'>
export type FunctionDirective<
T = any,
export type DirectiveArguments = Array<
| [Directive | undefined]
- | [Directive | undefined, value: any]
- | [Directive | undefined, value: any, argument: string]
+ | [Directive | undefined, () => any]
+ | [Directive | undefined, () => any, argument: string]
| [
Directive | undefined,
- value: any,
+ value: () => any,
argument: string,
modifiers: DirectiveModifiers,
]
const bindings = currentInstance.dirs.get(node)!
for (const directive of directives) {
- let [dir, value, arg, modifiers] = directive
+ let [dir, source, arg, modifiers] = directive
if (!dir) continue
if (isFunction(dir)) {
// TODO function directive
const binding: DirectiveBinding = {
dir,
instance: currentInstance,
- value,
- oldValue: void 0,
+ source,
+ value: null, // set later
+ oldValue: null,
arg,
modifiers,
}
- if (dir.created) dir.created(node, binding)
bindings.push(binding)
+
+ callDirectiveHook(node, binding, 'created')
+ effect(() => {
+ if (!currentInstance!.isMountedRef.value) return
+ callDirectiveHook(node, binding, 'updated')
+ })
}
return node
nodes?: IterableIterator<Node>,
) {
if (!instance) return
- if (!nodes) {
- nodes = instance.dirs.keys()
- }
+ nodes = nodes || instance.dirs.keys()
for (const node of nodes) {
const directives = instance.dirs.get(node) || []
for (const binding of directives) {
- const hook = binding.dir[name]
- hook && hook(node, binding)
+ callDirectiveHook(node, binding, name)
}
}
}
+
+function callDirectiveHook(
+ node: Node,
+ binding: DirectiveBinding,
+ name: DirectiveHookName,
+) {
+ const { dir } = binding
+ const hook = dir[name]
+ if (!hook) return
+
+ const newValue = binding.source ? binding.source() : undefined
+ if (name === 'updated' && binding.value === newValue) return
+
+ binding.oldValue = binding.value
+ binding.value = newValue
+ hook(node, binding)
+}
--- /dev/null
+import type { ObjectDirective } from '../directive'
+
+const vShowMap = new WeakMap<HTMLElement, string>()
+
+export const vShow: ObjectDirective<HTMLElement> = {
+ beforeMount(node, { source: value }) {
+ vShowMap.set(node, node.style.display === 'none' ? '' : node.style.display)
+ setDisplay(node, value)
+ },
+
+ updated(node, { value, oldValue }) {
+ if (!value === !oldValue) return
+ setDisplay(node, value)
+ },
+
+ beforeUnmount(node, { source: value }) {
+ setDisplay(node, value)
+ },
+}
+
+function setDisplay(el: HTMLElement, value: unknown): void {
+ el.style.display = value ? vShowMap.get(el)! : 'none'
+}
export * from './render'
export * from './template'
export * from './scheduler'
-export * from './directives'
+export * from './directive'
export * from './dom'
+export * from './directives/vShow'
setCurrentInstance,
unsetCurrentInstance,
} from './component'
-import { invokeDirectiveHook } from './directives'
+import { invokeDirectiveHook } from './directive'
import { insert, remove } from './dom'
export type Block = Node | Fragment | Block[]
invokeDirectiveHook(instance, 'beforeMount')
insert(block, instance.container)
- instance.isMounted = true
+ instance.isMountedRef.value = true
invokeDirectiveHook(instance, 'mounted')
// TODO: lifecycle hooks (mounted, ...)
invokeDirectiveHook(instance, 'beforeUnmount')
scope.stop()
block && remove(block, container)
- instance.isMounted = false
+ instance.isMountedRef.value = false
invokeDirectiveHook(instance, 'unmounted')
unsetCurrentInstance()
--- /dev/null
+<script setup lang="ts">
+import { ref } from '@vue/vapor'
+
+const visible = ref(true)
+function handleClick() {
+ visible.value = !visible.value
+}
+</script>
+
+<template>
+ <button @click="handleClick">toggle</button>
+ <h1 v-show="visible">hello world</h1>
+</template>