Skip to content

Commit c4e0bbe

Browse files
committed
feat(vue-jsx-vapor): support h and Fragment
1 parent 39898b9 commit c4e0bbe

File tree

5 files changed

+250
-18
lines changed

5 files changed

+250
-18
lines changed

packages/babel/src/index.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,13 @@ export default (): {
9797
statements.unshift(preambleResult)
9898
}
9999

100-
const helpers = [
101-
'setNodes',
102-
'createNodes',
103-
state.opts.interop ? 'createComponent' : '',
104-
].filter((helper) => {
105-
const result = importSet.has(helper)
106-
result && importSet.delete(helper)
107-
return result
108-
})
100+
const helpers = ['setNodes', 'createNodes', 'createComponent'].filter(
101+
(helper) => {
102+
const result = importSet.has(helper)
103+
result && importSet.delete(helper)
104+
return result
105+
},
106+
)
109107
if (helpers.length) {
110108
statements.unshift(
111109
`import { ${helpers

packages/vue-jsx-vapor/src/core/h.ts

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/* eslint-disable prefer-rest-params */
2+
import {
3+
createTextNode,
4+
type Block,
5+
type Component,
6+
type ComponentOptions,
7+
type ConcreteComponent,
8+
type DefineComponent,
9+
type EmitsOptions,
10+
type Fragment,
11+
type FunctionalComponent,
12+
type RawSlots,
13+
type Suspense,
14+
type SuspenseProps,
15+
type Teleport,
16+
type TeleportProps,
17+
type VNodeRef,
18+
} from 'vue'
19+
import { createComponentWithFallback, isBlock } from './runtime'
20+
21+
type NodeChildAtom = Block | string | number | boolean | null | undefined | void
22+
23+
type NodeArrayChildren = Array<NodeArrayChildren | NodeChildAtom>
24+
25+
type RawChildren = NodeChildAtom | NodeArrayChildren
26+
27+
function normalizeNode(node: RawChildren): Block {
28+
if (node == null || typeof node === 'boolean') {
29+
return []
30+
} else if (Array.isArray(node) && node.length) {
31+
return node.map(normalizeNode)
32+
} else if (isBlock(node)) {
33+
return node
34+
} else {
35+
// strings and numbers
36+
return createTextNode(String(node))
37+
}
38+
}
39+
40+
// fake constructor type returned from `defineComponent`
41+
interface Constructor<P = any> {
42+
__isFragment?: never
43+
__isTeleport?: never
44+
__isSuspense?: never
45+
new (...args: any[]): { $props: P }
46+
}
47+
48+
type HTMLElementEventHandler = {
49+
[K in keyof HTMLElementEventMap as `on${Capitalize<K>}`]?: (
50+
ev: HTMLElementEventMap[K],
51+
) => any
52+
}
53+
54+
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N
55+
56+
type RawProps = Record<string, any>
57+
58+
// The following is a series of overloads for providing props validation of
59+
// manually written render functions.
60+
61+
// element
62+
export function h<K extends keyof HTMLElementTagNameMap>(
63+
type: K,
64+
children?: RawChildren,
65+
): Block
66+
export function h<K extends keyof HTMLElementTagNameMap>(
67+
type: K,
68+
props?: (RawProps & HTMLElementEventHandler) | null,
69+
children?: RawChildren | RawSlots,
70+
): Block
71+
72+
// custom element
73+
export function h(type: string, children?: RawChildren): Block
74+
export function h(
75+
type: string,
76+
props?: RawProps | null,
77+
children?: RawChildren | RawSlots,
78+
): Block
79+
80+
// text/comment
81+
export function h(
82+
type: typeof Text | typeof Comment,
83+
children?: string | number | boolean,
84+
): Block
85+
export function h(
86+
type: typeof Text | typeof Comment,
87+
props?: null,
88+
children?: string | number | boolean,
89+
): Block
90+
91+
// fragment
92+
export function h(type: typeof Fragment, children?: NodeArrayChildren): Block
93+
export function h(
94+
type: typeof Fragment,
95+
props?: { key?: PropertyKey; ref?: VNodeRef } | null,
96+
children?: NodeArrayChildren,
97+
): Block
98+
99+
// teleport (target prop is required)
100+
export function h(
101+
type: typeof Teleport,
102+
props: RawProps & TeleportProps,
103+
children: RawChildren | RawSlots,
104+
): Block
105+
106+
// suspense
107+
export function h(type: typeof Suspense, children?: RawChildren): Block
108+
export function h(
109+
type: typeof Suspense,
110+
props?: (RawProps & SuspenseProps) | null,
111+
children?: RawChildren | RawSlots,
112+
): Block
113+
114+
// functional component
115+
export function h(type: FunctionalComponent, children?: RawChildren): Block
116+
export function h<
117+
P,
118+
E extends EmitsOptions = {},
119+
S extends Record<string, any> = any,
120+
>(
121+
type: FunctionalComponent<P, E, S>,
122+
props?: (RawProps & P) | ({} extends P ? null : never),
123+
children?: RawChildren | IfAny<S, RawSlots, S>,
124+
): Block
125+
126+
// catch all types
127+
export function h(
128+
type:
129+
| string
130+
| ConcreteComponent
131+
| Component
132+
| ComponentOptions
133+
| Constructor
134+
| DefineComponent,
135+
children?: RawChildren,
136+
): Block
137+
export function h<P>(
138+
type:
139+
| string
140+
| ConcreteComponent<P>
141+
| Component<P>
142+
| ComponentOptions<P>
143+
| Constructor<P>
144+
| DefineComponent<P>,
145+
props?: (RawProps & P) | ({} extends P ? null : never),
146+
children?: RawChildren | RawSlots,
147+
): Block
148+
149+
export function h(type: any, propsOrChildren?: any, children?: any) {
150+
const l = arguments.length
151+
if (l === 2) {
152+
if (
153+
typeof propsOrChildren === 'object' &&
154+
!Array.isArray(propsOrChildren)
155+
) {
156+
// single block without props
157+
if (isBlock(propsOrChildren)) {
158+
return createComponentWithFallback(type, null, {
159+
default: () => normalizeNode(propsOrChildren),
160+
})
161+
}
162+
163+
// props without children
164+
return createComponentWithFallback(
165+
type,
166+
propsOrChildren ? { $: [() => propsOrChildren] } : null,
167+
)
168+
} else {
169+
// omit props
170+
return createComponentWithFallback(type, null, {
171+
default: () => normalizeNode(propsOrChildren),
172+
})
173+
}
174+
} else {
175+
if (l > 3) {
176+
children = Array.prototype.slice.call(arguments, 2)
177+
}
178+
return createComponentWithFallback(
179+
type,
180+
propsOrChildren ? { $: [() => propsOrChildren] } : null,
181+
children
182+
? typeof children === 'object' && !Array.isArray(children)
183+
? children
184+
: {
185+
default: () => normalizeNode(children),
186+
}
187+
: undefined,
188+
)
189+
}
190+
}

packages/vue-jsx-vapor/src/core/runtime.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
22
createComponent as _createComponent,
3+
createComponentWithFallback as _createComponentWithFallback,
34
EffectScope,
5+
Fragment,
46
insert,
57
isFragment,
68
isVaporComponent,
@@ -12,22 +14,58 @@ import {
1214
VaporFragment,
1315
type Block,
1416
type GenericComponentInstance,
17+
type VaporComponent,
1518
} from 'vue'
1619

1720
import * as Vue from 'vue'
1821

22+
export function isBlock(val: NonNullable<unknown>): val is Block {
23+
return (
24+
val instanceof Node ||
25+
Array.isArray(val) ||
26+
isVaporComponent(val) ||
27+
isFragment(val)
28+
)
29+
}
30+
1931
export function getCurrentInstance(): GenericComponentInstance | null {
2032
// @ts-ignore
2133
return Vue.currentInstance || Vue.getCurrentInstance()
2234
}
2335

24-
export { shallowRef as useRef } from 'vue'
36+
const createProxyComponent = (
37+
createComponent:
38+
| typeof _createComponent
39+
| typeof _createComponentWithFallback,
40+
type: VaporComponent | typeof Fragment,
41+
props: any,
42+
...args: any[]
43+
) => {
44+
if (type === Fragment) {
45+
type = (_, { slots }) => slots.default()
46+
props = null
47+
}
48+
// @ts-ignore
49+
if (Vue.currentInstance && Vue.currentInstance.appContext.vapor) {
50+
typeof type === 'function' && ((type as VaporComponent).__vapor = true)
51+
}
52+
return createComponent(type as VaporComponent, props, ...args)
53+
}
54+
55+
type Tail<T extends any[]> = T extends [any, ...infer R] ? R : never
2556

26-
export const createComponent: typeof _createComponent = (...args) => {
27-
typeof args[0] === 'function' && (args[0].__vapor = true)
28-
return _createComponent(...args)
57+
export const createComponent = (
58+
type: VaporComponent | typeof Fragment,
59+
...args: Tail<Parameters<typeof _createComponent>>
60+
) => {
61+
return createProxyComponent(_createComponent, type, ...args)
2962
}
3063

64+
export const createComponentWithFallback = (
65+
type: VaporComponent | typeof Fragment,
66+
...args: Tail<Parameters<typeof _createComponentWithFallback>>
67+
) => createProxyComponent(_createComponentWithFallback, type, ...args)
68+
3169
/**
3270
* Returns the props of the current component instance.
3371
*

packages/vue-jsx-vapor/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
export { shallowRef as useRef } from 'vue'
2+
13
export * from './core/runtime'
24

35
export * from './jsx-runtime/dom'
6+
7+
export * from './core/h'

packages/vue-jsx-vapor/src/jsx-runtime.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
import { Fragment, h, type VNode } from 'vue'
2-
import type { NativeElements, ReservedProps } from 'vue-jsx-vapor'
1+
import { Fragment, type Block } from 'vue'
2+
import { h, type NativeElements, type ReservedProps } from 'vue-jsx-vapor'
33

44
function jsx(type: any, props: any, key: any): ReturnType<typeof h> {
5-
const { children } = props
5+
const { children, ['v-slots']: vSlots } = props
66
delete props.children
7+
delete props['v-slots']
78
if (arguments.length > 2) {
89
props.key = key
910
}
10-
return h(type, props, children)
11+
return h(type, props, vSlots || children)
1112
}
1213

1314
export { Fragment, jsx, jsx as jsxDEV, jsx as jsxs }
1415

1516
declare global {
1617
// eslint-disable-next-line @typescript-eslint/no-namespace
1718
namespace JSX {
18-
export interface Element extends VNode {}
19+
// @ts-ignore
20+
export type Element = Block
1921
export interface ElementClass {
2022
$props: {}
2123
}

0 commit comments

Comments
 (0)