diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
index 11119db64..5e3b020fa 100644
--- a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
+++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
@@ -35,7 +35,7 @@ export function render(_ctx) {
const t0 = _template("
")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _withDirectives(n1, [[_ctx.vExample, _ctx.msg]])
+ _withDirectives(n1, [[_ctx.vExample, () => _ctx.msg]])
return n0
}"
`;
@@ -47,7 +47,7 @@ export function render(_ctx) {
const t0 = _template("")
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
}"
`;
@@ -59,7 +59,7 @@ export function render(_ctx) {
const t0 = _template("")
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
}"
`;
@@ -83,7 +83,7 @@ export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _withDirectives(n1, [[_ctx.vExample, _ctx.msg, "foo"]])
+ _withDirectives(n1, [[_ctx.vExample, () => _ctx.msg, "foo"]])
return n0
}"
`;
@@ -95,7 +95,7 @@ export function render(_ctx) {
const t0 = _template("")
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
}"
`;
diff --git a/packages/compiler-vapor/src/compile.ts b/packages/compiler-vapor/src/compile.ts
index e386de3b3..0d37278d2 100644
--- a/packages/compiler-vapor/src/compile.ts
+++ b/packages/compiler-vapor/src/compile.ts
@@ -20,6 +20,7 @@ import { transformVHtml } from './transforms/vHtml'
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'
@@ -97,6 +98,7 @@ export function getBaseTransformPreset(
on: transformVOn,
html: transformVHtml,
text: transformVText,
+ show: transformVShow,
},
]
}
diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts
index fee38022b..bdb4f8820 100644
--- a/packages/compiler-vapor/src/generate.ts
+++ b/packages/compiler-vapor/src/generate.ts
@@ -30,7 +30,7 @@ import {
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
@@ -475,16 +475,20 @@ function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
// 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')
@@ -512,6 +516,8 @@ function genArrayExpression(elements: string[]) {
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)
@@ -525,7 +531,8 @@ function genExpression(node: IRExpression, context: CodegenContext): void {
!context.prefixIdentifiers ||
!node.content.trim() ||
// there was a parsing error
- ast === false
+ ast === false ||
+ isLiteralWhitelisted(rawExpr)
) {
return push(rawExpr, NewlineType.None, loc)
}
diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts
index ea468d29b..4bca80478 100644
--- a/packages/compiler-vapor/src/transforms/transformElement.ts
+++ b/packages/compiler-vapor/src/transforms/transformElement.ts
@@ -74,7 +74,7 @@ function transformProp(
type: IRNodeTypes.WITH_DIRECTIVE,
element: context.reference(),
dir: prop,
- loc: loc,
+ loc,
})
}
}
diff --git a/packages/compiler-vapor/src/transforms/vShow.ts b/packages/compiler-vapor/src/transforms/vShow.ts
new file mode 100644
index 000000000..1604b27e7
--- /dev/null
+++ b/packages/compiler-vapor/src/transforms/vShow.ts
@@ -0,0 +1,19 @@
+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,
+ })
+}
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 421b2bdfb..26518598d 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 } 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
@@ -19,7 +19,8 @@ export interface ComponentInternalInstance {
scope: EffectScope
component: FunctionalComponent | ObjectComponent
- isMounted: boolean
+ get isMounted(): boolean
+ isMountedRef: Ref
/** directives */
dirs: Map
@@ -44,6 +45,7 @@ let uid = 0
export const createComponentInstance = (
component: ObjectComponent | FunctionalComponent,
): ComponentInternalInstance => {
+ const isMountedRef = ref(false)
const instance: ComponentInternalInstance = {
uid: uid++,
block: null,
@@ -51,7 +53,10 @@ export const createComponentInstance = (
scope: new EffectScope(true /* detached */)!,
component,
- isMounted: false,
+ get isMounted() {
+ return isMountedRef.value
+ },
+ isMountedRef,
dirs: new Map(),
// TODO: registory of provides, appContext, lifecycles, ...
diff --git a/packages/runtime-vapor/src/directives.ts b/packages/runtime-vapor/src/directive.ts
similarity index 65%
rename from packages/runtime-vapor/src/directives.ts
rename to packages/runtime-vapor/src/directive.ts
index 70833e973..2eeb9277c 100644
--- a/packages/runtime-vapor/src/directives.ts
+++ b/packages/runtime-vapor/src/directive.ts
@@ -1,5 +1,6 @@
import { isFunction } from '@vue/shared'
import { currentInstance, type ComponentInternalInstance } from './component'
+import { effect } from './scheduler'
export type DirectiveModifiers = Record
@@ -9,6 +10,7 @@ export interface DirectiveBinding<
M extends string = string,
> {
instance: ComponentInternalInstance | null
+ source?: () => V
value: V
oldValue: V | null
arg?: A
@@ -26,23 +28,24 @@ export type DirectiveHook<
// 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
- beforeMount?: DirectiveHook
- mounted?: DirectiveHook
- // beforeUpdate?: DirectiveHook
- // updated?: DirectiveHook
- beforeUnmount?: DirectiveHook
- unmounted?: DirectiveHook
- // getSSRProps?: SSRDirectiveHook
- // deep?: boolean
+> = {
+ [K in DirectiveHookName]?: DirectiveHook | undefined
+} & {
+ deep?: boolean
}
-export type DirectiveHookName = Exclude
export type FunctionDirective<
T = any,
@@ -60,11 +63,11 @@ export type Directive<
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,
]
@@ -83,7 +86,7 @@ export function withDirectives(
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
@@ -95,13 +98,19 @@ export function withDirectives(
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
@@ -113,14 +122,28 @@ export function invokeDirectiveHook(
nodes?: IterableIterator,
) {
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)
+}
diff --git a/packages/runtime-vapor/src/directives/vShow.ts b/packages/runtime-vapor/src/directives/vShow.ts
new file mode 100644
index 000000000..f419053ec
--- /dev/null
+++ b/packages/runtime-vapor/src/directives/vShow.ts
@@ -0,0 +1,23 @@
+import type { ObjectDirective } from '../directive'
+
+const vShowMap = new WeakMap()
+
+export const vShow: ObjectDirective = {
+ 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'
+}
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index 63985f7ec..0c56476ad 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -41,5 +41,6 @@ export * from './on'
export * from './render'
export * from './template'
export * from './scheduler'
-export * from './directives'
+export * from './directive'
export * from './dom'
+export * from './directives/vShow'
diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts
index 74e88eace..b03e56ae8 100644
--- a/packages/runtime-vapor/src/render.ts
+++ b/packages/runtime-vapor/src/render.ts
@@ -7,7 +7,7 @@ import {
setCurrentInstance,
unsetCurrentInstance,
} from './component'
-import { invokeDirectiveHook } from './directives'
+import { invokeDirectiveHook } from './directive'
import { insert, remove } from './dom'
export type Block = Node | Fragment | Block[]
@@ -56,7 +56,7 @@ export function mountComponent(
invokeDirectiveHook(instance, 'beforeMount')
insert(block, instance.container)
- instance.isMounted = true
+ instance.isMountedRef.value = true
invokeDirectiveHook(instance, 'mounted')
// TODO: lifecycle hooks (mounted, ...)
@@ -70,7 +70,7 @@ export function unmountComponent(instance: ComponentInternalInstance) {
invokeDirectiveHook(instance, 'beforeUnmount')
scope.stop()
block && remove(block, container)
- instance.isMounted = false
+ instance.isMountedRef.value = false
invokeDirectiveHook(instance, 'unmounted')
unsetCurrentInstance()
diff --git a/playground/src/show.vue b/playground/src/show.vue
new file mode 100644
index 000000000..23b599121
--- /dev/null
+++ b/playground/src/show.vue
@@ -0,0 +1,13 @@
+
+
+
+
+ hello world
+