Skip to content

Commit

Permalink
feat(runtime-vapor): resolve-components resolve-directives
Browse files Browse the repository at this point in the history
close #209
  • Loading branch information
Doctor-wu authored and sxzz committed May 28, 2024
1 parent 4ed4fb6 commit 857dbc0
Show file tree
Hide file tree
Showing 8 changed files with 442 additions and 13 deletions.
3 changes: 1 addition & 2 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import {
extend,
getGlobalThis,
isArray,
isBuiltInTag,
isFunction,
isObject,
isPromise,
Expand Down Expand Up @@ -761,8 +762,6 @@ export const unsetCurrentInstance = () => {
internalSetCurrentInstance(null)
}

const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component')

export function validateComponentName(
name: string,
{ isNativeTag }: AppConfig,
Expand Down
213 changes: 213 additions & 0 deletions packages/runtime-vapor/__tests__/helpers/resolveAssets.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// import {
// type Component,
// type Directive,
// createVaporApp,
// resolveComponent,
// resolveDirective,
// } from '@vue/runtime-vapor'
// import { makeRender } from '../_utils'

// const define = makeRender()

describe('resolveAssets', () => {
// test('should work', () => {
// const FooBar = () => null
// const BarBaz = { mounted: () => null }
// let component1: Component | string
// let component2: Component | string
// let component3: Component | string
// let component4: Component | string
// let directive1: Directive
// let directive2: Directive
// let directive3: Directive
// let directive4: Directive
// const Root = define({
// // comps: {
// // FooBar: FooBar,
// // },
// // dirs: {
// // BarBaz: BarBaz,
// // },
// render() {
// component1 = resolveComponent('FooBar')!
// directive1 = resolveDirective('BarBaz')!
// // camelize
// component2 = resolveComponent('Foo-bar')!
// directive2 = resolveDirective('Bar-baz')!
// // capitalize
// component3 = resolveComponent('fooBar')!
// directive3 = resolveDirective('barBaz')!
// // camelize and capitalize
// component4 = resolveComponent('foo-bar')!
// directive4 = resolveDirective('bar-baz')!
// return []
// },
// })
// const app = createVaporApp(Root)
// const root = document.createElement('div')
// app.mount(root)
// expect(component1!).toBe(FooBar)
// expect(component2!).toBe(FooBar)
// expect(component3!).toBe(FooBar)
// expect(component4!).toBe(FooBar)
// expect(directive1!).toBe(BarBaz)
// expect(directive2!).toBe(BarBaz)
// expect(directive3!).toBe(BarBaz)
// expect(directive4!).toBe(BarBaz)
// })
// test('maybeSelfReference', async () => {
// let component1: Component | string
// let component2: Component | string
// let component3: Component | string
// const Foo = () => null
// const Root = {
// name: 'Root',
// components: {
// Foo,
// Root: Foo,
// },
// setup() {
// return () => {
// component1 = resolveComponent('Root', true)
// component2 = resolveComponent('Foo', true)
// component3 = resolveComponent('Bar', true)
// }
// },
// }
// const app = createApp(Root)
// const root = nodeOps.createElement('div')
// app.mount(root)
// expect(component1!).toMatchObject(Root) // explicit self name reference
// expect(component2!).toBe(Foo) // successful resolve take higher priority
// expect(component3!).toMatchObject(Root) // fallback when resolve fails
// })
// describe('warning', () => {
// test('used outside render() or setup()', () => {
// resolveComponent('foo')
// expect(
// 'resolveComponent can only be used in render() or setup().',
// ).toHaveBeenWarned()
// resolveDirective('foo')
// expect(
// 'resolveDirective can only be used in render() or setup().',
// ).toHaveBeenWarned()
// })
// test('not exist', () => {
// const Root = {
// setup() {
// resolveComponent('foo')
// resolveDirective('bar')
// return () => null
// },
// }
// const app = createApp(Root)
// const root = nodeOps.createElement('div')
// app.mount(root)
// expect('Failed to resolve component: foo').toHaveBeenWarned()
// expect('Failed to resolve directive: bar').toHaveBeenWarned()
// })
// test('resolve dynamic component', () => {
// const dynamicComponents = {
// foo: () => 'foo',
// bar: () => 'bar',
// baz: { render: () => 'baz' },
// }
// let foo, bar, baz // dynamic components
// let dynamicVNode: VNode
// const Child = {
// render(this: any) {
// return this.$slots.default()
// },
// }
// const Root = {
// components: { foo: dynamicComponents.foo },
// setup() {
// return () => {
// foo = resolveDynamicComponent('foo') // <component is="foo"/>
// bar = resolveDynamicComponent(dynamicComponents.bar) // <component :is="bar"/>, function
// dynamicVNode = createVNode(resolveDynamicComponent(null)) // <component :is="null"/>
// return h(Child, () => {
// // check inside child slots
// baz = resolveDynamicComponent(dynamicComponents.baz) // <component :is="baz"/>, object
// })
// }
// },
// }
// const app = createApp(Root)
// const root = nodeOps.createElement('div')
// app.mount(root)
// expect(foo).toBe(dynamicComponents.foo)
// expect(bar).toBe(dynamicComponents.bar)
// expect(baz).toBe(dynamicComponents.baz)
// // should allow explicit falsy type to remove the component
// expect(dynamicVNode!.type).toBe(Comment)
// })
// test('resolve dynamic component should fallback to plain element without warning', () => {
// const Root = {
// setup() {
// return () => {
// return createVNode(resolveDynamicComponent('div') as string, null, {
// default: () => 'hello',
// })
// }
// },
// }
// const app = createApp(Root)
// const root = nodeOps.createElement('div')
// app.mount(root)
// expect(serializeInner(root)).toBe('<div>hello</div>')
// })
// })
// test('resolving from mixins & extends', () => {
// const FooBar = () => null
// const BarBaz = { mounted: () => null }
// let component1: Component | string
// let component2: Component | string
// let component3: Component | string
// let component4: Component | string
// let directive1: Directive
// let directive2: Directive
// let directive3: Directive
// let directive4: Directive
// const Base = {
// components: {
// FooBar: FooBar,
// },
// }
// const Mixin = {
// directives: {
// BarBaz: BarBaz,
// },
// }
// const Root = {
// extends: Base,
// mixins: [Mixin],
// setup() {
// return () => {
// component1 = resolveComponent('FooBar')!
// directive1 = resolveDirective('BarBaz')!
// // camelize
// component2 = resolveComponent('Foo-bar')!
// directive2 = resolveDirective('Bar-baz')!
// // capitalize
// component3 = resolveComponent('fooBar')!
// directive3 = resolveDirective('barBaz')!
// // camelize and capitalize
// component4 = resolveComponent('foo-bar')!
// directive4 = resolveDirective('bar-baz')!
// }
// },
// }
// const app = createApp(Root)
// const root = nodeOps.createElement('div')
// app.mount(root)
// expect(component1!).toBe(FooBar)
// expect(component2!).toBe(FooBar)
// expect(component3!).toBe(FooBar)
// expect(component4!).toBe(FooBar)
// expect(directive1!).toBe(BarBaz)
// expect(directive2!).toBe(BarBaz)
// expect(directive3!).toBe(BarBaz)
// expect(directive4!).toBe(BarBaz)
// })
})
57 changes: 55 additions & 2 deletions packages/runtime-vapor/src/apiCreateVaporApp.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { isFunction, isObject } from '@vue/shared'
import { NO, isFunction, isObject } from '@vue/shared'
import {
type Component,
type ComponentInternalInstance,
createComponentInstance,
validateComponentName,
} from './component'
import { warn } from './warning'
import { version } from '.'
import { type Directive, version } from '.'
import { render, setupComponent, unmountComponent } from './apiRender'
import type { InjectionKey } from './apiInject'
import type { RawProps } from './componentProps'
import { validateDirectiveName } from './directives'

export function createVaporApp(
rootComponent: Component,
Expand Down Expand Up @@ -60,6 +62,35 @@ export function createVaporApp(
return app
},

component(name: string, component?: Component): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
if (!component) {
return context.comps[name]
}
if (__DEV__ && context.comps[name]) {
warn(`Component "${name}" has already been registered in target app.`)
}
context.comps[name] = component
return app
},

directive(name: string, directive?: Directive) {
if (__DEV__) {
validateDirectiveName(name)
}

if (!directive) {
return context.dirs[name] as any
}
if (__DEV__ && context.dirs[name]) {
warn(`Directive "${name}" has already been registered in target app.`)
}
context.dirs[name] = directive
return app
},

mount(rootContainer): any {
if (!instance) {
instance = createComponentInstance(
Expand Down Expand Up @@ -119,11 +150,14 @@ export function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
errorHandler: undefined,
warnHandler: undefined,
globalProperties: {},
},
provides: Object.create(null),
comps: {},
dirs: {},
}
}

Expand Down Expand Up @@ -151,6 +185,11 @@ export interface App {
): this
use<Options>(plugin: Plugin<Options>, options: Options): this

component(name: string): Component | undefined
component<T extends Component>(name: string, component: T): this
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this

mount(
rootContainer: ParentNode | string,
isHydrate?: boolean,
Expand All @@ -163,6 +202,9 @@ export interface App {
}

export interface AppConfig {
// @private
readonly isNativeTag: (tag: string) => boolean

errorHandler?: (
err: unknown,
instance: ComponentInternalInstance | null,
Expand All @@ -180,6 +222,17 @@ export interface AppContext {
app: App // for devtools
config: AppConfig
provides: Record<string | symbol, any>

/**
* Resolved component registry, only for components with mixins or extends
* @internal
*/
comps: Record<string, Component>
/**
* Resolved directive registry, only for components with mixins or extends
* @internal
*/
dirs: Record<string, Directive>
}

/**
Expand Down
Loading

0 comments on commit 857dbc0

Please sign in to comment.