Skip to content

Commit

Permalink
wip(vapor): createDynamicComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 31, 2025
1 parent cad7f0e commit 9f1025d
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -270,19 +270,19 @@ export function render(_ctx) {
`;

exports[`compiler: element transform > dynamic component > dynamic binding 1`] = `
"import { resolveDynamicComponent as _resolveDynamicComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
"import { createDynamicComponent as _createDynamicComponent } from 'vue';
export function render(_ctx) {
const n0 = _createComponentWithFallback(_resolveDynamicComponent(_ctx.foo), null, null, true)
const n0 = _createDynamicComponent(() => (_ctx.foo), null, null, true)
return n0
}"
`;

exports[`compiler: element transform > dynamic component > dynamic binding shorthand 1`] = `
"import { resolveDynamicComponent as _resolveDynamicComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
"import { createDynamicComponent as _createDynamicComponent } from 'vue';
export function render(_ctx) {
const n0 = _createComponentWithFallback(_resolveDynamicComponent(_ctx.is), null, null, true)
const n0 = _createDynamicComponent(() => (_ctx.is), null, null, true)
return n0
}"
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ describe('compiler: element transform', () => {
`<component :is="foo" />`,
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
Expand All @@ -546,7 +546,7 @@ describe('compiler: element transform', () => {
const { code, ir, helpers } =
compileWithElementTransform(`<component :is />`)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
Expand Down
20 changes: 13 additions & 7 deletions packages/compiler-vapor/src/generators/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ export function genCreateComponent(
...inlineHandlers,
`const n${operation.id} = `,
...genCall(
operation.asset
? helper('createComponentWithFallback')
: helper('createComponent'),
operation.dynamic && !operation.dynamic.isStatic
? helper('createDynamicComponent')
: operation.asset
? helper('createComponentWithFallback')
: helper('createComponent'),
tag,
rawProps,
rawSlots,
Expand All @@ -78,10 +80,14 @@ export function genCreateComponent(

function genTag() {
if (operation.dynamic) {
return genCall(
helper('resolveDynamicComponent'),
genExpression(operation.dynamic, context),
)
if (operation.dynamic.isStatic) {
return genCall(
helper('resolveDynamicComponent'),
genExpression(operation.dynamic, context),
)
} else {
return ['() => (', ...genExpression(operation.dynamic, context), ')']
}
} else if (operation.asset) {
return toValidAssetId(operation.tag, 'component')
} else {
Expand Down
57 changes: 57 additions & 0 deletions packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { shallowRef } from '@vue/reactivity'
import { nextTick } from '@vue/runtime-dom'
import { createDynamicComponent } from '../src'
import { makeRender } from './_utils'

const define = makeRender()

describe('api: createDynamicComponent', () => {
const A = () => document.createTextNode('AAA')
const B = () => document.createTextNode('BBB')

test('direct value', async () => {
const val = shallowRef<any>(A)

const { html } = define({
setup() {
return createDynamicComponent(() => val.value)
},
}).render()

expect(html()).toBe('AAA<!--dynamic-component-->')

val.value = B
await nextTick()
expect(html()).toBe('BBB<!--dynamic-component-->')

// fallback
val.value = 'foo'
await nextTick()
expect(html()).toBe('<foo></foo><!--dynamic-component-->')
})

test('global registration', async () => {
const val = shallowRef('foo')

const { app, html, mount } = define({
setup() {
return createDynamicComponent(() => val.value)
},
}).create()

app.component('foo', A)
app.component('bar', B)

mount()
expect(html()).toBe('AAA<!--dynamic-component-->')

val.value = 'bar'
await nextTick()
expect(html()).toBe('BBB<!--dynamic-component-->')

// fallback
val.value = 'baz'
await nextTick()
expect(html()).toBe('<baz></baz><!--dynamic-component-->')
})
})
31 changes: 31 additions & 0 deletions packages/runtime-vapor/src/apiCreateDynamicComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { resolveDynamicComponent } from '@vue/runtime-dom'
import { DynamicFragment, type Fragment } from './block'
import { createComponentWithFallback } from './component'
import { renderEffect } from './renderEffect'
import type { RawProps } from './componentProps'
import type { RawSlots } from './componentSlots'

export function createDynamicComponent(
getter: () => any,
rawProps?: RawProps | null,
rawSlots?: RawSlots | null,
isSingleRoot?: boolean,
): Fragment {
const frag = __DEV__
? new DynamicFragment('dynamic-component')
: new DynamicFragment()
renderEffect(() => {
const value = getter()
frag.update(
() =>
createComponentWithFallback(
resolveDynamicComponent(value) as any,
rawProps,
rawSlots,
isSingleRoot,
),
value,
)
})
return frag
}
6 changes: 3 additions & 3 deletions packages/runtime-vapor/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ export class DynamicFragment extends Fragment {
document.createTextNode('')
}

update(render?: BlockFn): void {
if (render === this.current) {
update(render?: BlockFn, key: any = render): void {
if (key === this.current) {
return
}
this.current = render
this.current = key

pauseTracking()
const parent = this.anchor.parentNode
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-vapor/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ export function isVaporComponent(
*/
export function createComponentWithFallback(
comp: VaporComponent | string,
rawProps: RawProps | null | undefined,
rawSlots: RawSlots | null | undefined,
rawProps?: RawProps | null,
rawSlots?: RawSlots | null,
isSingleRoot?: boolean,
): HTMLElement | VaporComponentInstance {
if (!isString(comp)) {
Expand Down
1 change: 1 addition & 0 deletions packages/runtime-vapor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ export {
getDefaultValue,
} from './apiCreateFor'
export { createTemplateRefSetter } from './apiTemplateRef'
export { createDynamicComponent } from './apiCreateDynamicComponent'

0 comments on commit 9f1025d

Please sign in to comment.