Skip to content

Commit ce906b5

Browse files
committed
feat: add x-jsx x-tpl components
1 parent 6394ee2 commit ce906b5

File tree

10 files changed

+217
-16
lines changed

10 files changed

+217
-16
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,6 @@
211211
"activityBar.background": "#580744",
212212
"titleBar.activeBackground": "#7B0A5F",
213213
"titleBar.activeForeground": "#FFFBFE"
214-
}
214+
},
215+
"commentTranslate.source": "Google"
215216
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export {default as XJsx} from './x-jsx'
2+
export {default as XTpl} from './x-tpl'
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import {FunctionComponent, JsxFn, JsxNode} from '@/utils/types-helper'
3+
import {defineComponent, VNode, h, PropType} from 'vue-demi'
4+
5+
const valueIsFunctionComponent = (value: any): value is FunctionComponent => {
6+
return typeof value === 'object' && value.functional && typeof value.render === 'function'
7+
}
8+
9+
/**
10+
* jsx render component
11+
*
12+
* @example
13+
* ```jsx
14+
* <x-jsx :jsx="yourJsx">
15+
* i am child text
16+
* </x-jsx>
17+
*
18+
* const yourJsx = <div>hello</div>
19+
* const yourJsx = (props) => <div>hello, {props.children}</div>
20+
* ```
21+
*/
22+
const vm = defineComponent({
23+
name: 'XJsx',
24+
props: {
25+
jsx: {
26+
type: [Function, Object, Array, String, Number, Boolean] as PropType<JsxNode | JsxFn | FunctionComponent>
27+
}
28+
},
29+
render(): VNode {
30+
const {$slots, $attrs, $props} = this as InstanceType<typeof vm>
31+
const {jsx} = $props
32+
33+
const children = typeof $slots.default === 'function' ? $slots.default() : $slots.default
34+
const props = {...$attrs, children}
35+
36+
let result: VNode
37+
38+
if (valueIsFunctionComponent(jsx)) {
39+
result = jsx.render(h, props)
40+
} else if (typeof jsx === 'function') {
41+
result = jsx(props, h) as VNode
42+
} else {
43+
result = jsx as VNode
44+
}
45+
46+
return result
47+
}
48+
})
49+
50+
export default vm
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import {Vue2CompileResult} from '@/utils/types-helper'
3+
import {Vue, defineComponent, VNode, h, PropType, isVue2} from 'vue-demi'
4+
5+
// Use weak map to store the mapping relationship between ctx and {[template]: renderFunction}
6+
// This memory will be automatically reclaimed when other references to ctx are destroyed (related to the browser memory reclamation mechanism)
7+
const compileMap = new WeakMap<Object, Record<string, Function>>()
8+
9+
/**
10+
* compile a template to render function
11+
* @param template template string
12+
* @param ctx context
13+
* @returns render function
14+
*/
15+
const compile = (template: string, ctx: Object): Function => {
16+
const historyResults = compileMap.get(ctx)
17+
if (historyResults && historyResults[template]) return historyResults[template].bind(ctx)
18+
19+
let renderFunc: Function
20+
if (isVue2) {
21+
const compileResult = Vue.compile(template) as unknown as Vue2CompileResult
22+
renderFunc = compileResult.staticRenderFns?.[0] ?? compileResult.render
23+
} else {
24+
renderFunc = Vue.compile(template)
25+
}
26+
27+
const results = {
28+
...historyResults,
29+
[template]: renderFunc
30+
}
31+
compileMap.set(ctx, results)
32+
return renderFunc.bind(ctx)
33+
}
34+
35+
/**
36+
* template render component
37+
*
38+
* @example
39+
* ```jsx
40+
* <x-tpl :tpl="yourTemplate" :ctx="this" />
41+
*
42+
* export default {
43+
* data() {
44+
* return {
45+
* name: 'xiao ming',
46+
* yourTemplate: '<div>hello, {{name}}</div>'
47+
* }
48+
* }
49+
* ```
50+
*/
51+
const vm = defineComponent({
52+
name: 'XTpl',
53+
props: {
54+
tpl: {
55+
type: String
56+
},
57+
ctx: {
58+
type: Object as PropType<Record<string, any>>
59+
}
60+
},
61+
render() {
62+
const {$props, $parent} = this as InstanceType<typeof vm>
63+
const {tpl, ctx} = $props
64+
const _ctx = ctx || $parent || {}
65+
66+
// console.log('render', isVue2, tpl, _ctx)
67+
68+
if (!tpl) return null
69+
70+
const renderFunc = compile(tpl, _ctx)
71+
const result = isVue2 ? renderFunc(h) : renderFunc(ctx)
72+
73+
return result as VNode
74+
}
75+
})
76+
77+
export default vm

packages/vue-xrender/src/hooks/useJsx.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import {CreateElement, JsxNode, VueComponentConstructor} from '@/utils/types-helper'
2+
import {JsxDefaultProps, JsxFn, JsxNode, VueComponentConstructor} from '@/utils/types-helper'
33
import {defineComponent, getCurrentInstance, VNode, h} from 'vue-demi'
44

5-
export type JsxProps<T extends JsxNode = JsxNode> = Record<string, any> & {
6-
children: T
7-
}
8-
9-
export type JsxFn = (props: JsxProps, h?: CreateElement) => JsxNode
10-
115
/**
126
* create a vue component from jsx
137
* @param name component name in template
148
* @param jsx jsx node, if you want to reactive the component, you can use `() => jsx`
159
*/
16-
export function useJsx(name: string, jsx: JsxNode | JsxFn): VueComponentConstructor
10+
export function useJsx<P extends JsxDefaultProps = JsxDefaultProps, T extends JsxNode = JsxNode>(
11+
name: string,
12+
jsx: JsxNode | JsxFn<P, T>
13+
): VueComponentConstructor
1714

1815
/**
1916
* create a vue component from jsx
2017
* @param jsx jsx node, if you want to reactive the component, you can use `() => jsx`
2118
*/
22-
export function useJsx(jsx: JsxNode | JsxFn): VueComponentConstructor
19+
export function useJsx<P extends JsxDefaultProps = JsxDefaultProps, T extends JsxNode = JsxNode>(
20+
jsx: JsxNode | JsxFn<P, T>
21+
): VueComponentConstructor
2322

2423
export function useJsx(...args: any[]) {
2524
const name: string | undefined = args[1] ? args[0] : undefined
2625
const jsx: JsxNode | JsxFn = args[1] ? args[1] : args[0]
2726

2827
const instance = getCurrentInstance()?.proxy
29-
if (!instance) throw new Error(`useJsx error: instance not foundname: ${name}`)
28+
if (!instance) throw new Error(`useJsx error: instance not found, name: ${name}`)
3029

3130
const jsxCom = defineComponent({
3231
name,

packages/vue-xrender/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from './hooks'
2+
export * from './components'
3+
export * from './utils/types-helper'

packages/vue-xrender/src/utils/types-helper.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
12
import {defineComponent, h, VNode} from 'vue-demi'
23

34
export type DefineComponent = ReturnType<typeof defineComponent>
@@ -10,3 +11,24 @@ export type CreateElement = typeof h
1011
export type JsxNode = JSX.Element | VNodeChild
1112

1213
export type VueComponentConstructor = DefineComponent
14+
15+
export type JsxDefaultProps = Record<string, any>
16+
17+
export type JsxProps<P extends JsxDefaultProps = JsxDefaultProps, T extends JsxNode = JsxNode> = P & {
18+
children: T
19+
}
20+
21+
export type JsxFn<P extends JsxDefaultProps = JsxDefaultProps, T extends JsxNode = JsxNode> = (
22+
props: JsxProps<P, T>,
23+
h?: CreateElement
24+
) => JsxNode
25+
26+
export type FunctionComponent = {
27+
functional: true
28+
render: (h: CreateElement, context: any) => VNode
29+
}
30+
31+
export type Vue2CompileResult = {
32+
render(h: CreateElement): VNode
33+
staticRenderFns: (() => VNode)[]
34+
}

playgrounds/vue2/src/App.vue

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="tsx" setup>
2-
import {useJsx} from 'vue-xrender'
2+
import {JsxFn, useJsx, XJsx, XTpl} from 'vue-xrender'
33
import {ref} from '@vue/composition-api'
44
55
const time = ref(0)
@@ -23,13 +23,34 @@ const Title = useJsx(props => (
2323
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2424
const Card2 = useJsx('Card', props => (
2525
<div>
26-
<h1>Card component, create in setup jsx, use in template</h1>
26+
<h1>Card component, create in setup useJsx, use in template</h1>
2727
<Title>
2828
<SubTitle></SubTitle>
2929
</Title>
3030
{props.children}
3131
</div>
3232
))
33+
34+
const CardJsx: JsxFn = props => (
35+
<div>
36+
<h1>CardJsx component, create in setup jsx function, use in template</h1>
37+
<Title>
38+
<SubTitle></SubTitle>
39+
</Title>
40+
{props.children}
41+
</div>
42+
)
43+
44+
const CardTpl = `
45+
<div>
46+
<h1>CardTpl component, create in setup template string, use in template</h1>
47+
<h2>CardTpl real Time: {{time}}s</h2>
48+
</div>
49+
`
50+
51+
defineExpose({
52+
time
53+
})
3354
</script>
3455

3556
<template>
@@ -40,5 +61,8 @@ const Card2 = useJsx('Card', props => (
4061
<card>
4162
<div>Card component's children from template</div>
4263
</card>
64+
65+
<x-jsx :jsx="CardJsx"> CardJsx component's children </x-jsx>
66+
<x-tpl :tpl="CardTpl" />
4367
</div>
4468
</template>

playgrounds/vue3/src/App.vue

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="tsx" setup>
2-
import {useJsx} from 'vue-xrender'
2+
import {JsxFn, useJsx, XJsx, XTpl} from 'vue-xrender'
33
import {ref} from 'vue'
44
55
const time = ref(0)
@@ -23,13 +23,34 @@ const Title = useJsx(props => (
2323
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2424
const Card2 = useJsx('Card', props => (
2525
<div>
26-
<h1>Card component, create in setup jsx, use in template</h1>
26+
<h1>Card component, create in setup useJsx, use in template</h1>
2727
<Title>
2828
<SubTitle></SubTitle>
2929
</Title>
3030
{props.children}
3131
</div>
3232
))
33+
34+
const CardJsx: JsxFn = props => (
35+
<div>
36+
<h1>CardJsx component, create in setup jsx function, use in template</h1>
37+
<Title>
38+
<SubTitle></SubTitle>
39+
</Title>
40+
{props.children}
41+
</div>
42+
)
43+
44+
const CardTpl = `
45+
<div>
46+
<h1>CardTpl component, create in setup template string, use in template</h1>
47+
<h2>CardTpl real Time: {{time}}s</h2>
48+
</div>
49+
`
50+
51+
defineExpose({
52+
time
53+
})
3354
</script>
3455

3556
<template>
@@ -40,5 +61,8 @@ const Card2 = useJsx('Card', props => (
4061
<card>
4162
<div>Card component's children from template</div>
4263
</card>
64+
65+
<x-jsx :jsx="CardJsx"> CardJsx component's children </x-jsx>
66+
<x-tpl :tpl="CardTpl" />
4367
</div>
4468
</template>

playgrounds/vue3/vite.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default defineConfig({
1616
resolve: {
1717
dedupe: ['vue', 'vue-demi', '@vue/runtime-core', '@vue/runtime-dom'], // use the same version
1818
alias: {
19-
vue: pathResolve('./node_modules/vue/dist/vue.runtime.esm-browser.js') // use the same version, also use runtime template compiler
19+
vue: pathResolve('./node_modules/vue/dist/vue.esm-browser.js') // use the same version, also use runtime template compiler
2020
}
2121
}
2222
})

0 commit comments

Comments
 (0)