Skip to content

Commit 107569b

Browse files
authored
feat(runtime-vapor): resolve assets of components & directives (#214)
1 parent 4ed4fb6 commit 107569b

File tree

7 files changed

+285
-14
lines changed

7 files changed

+285
-14
lines changed

packages/runtime-core/src/component.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ import {
6767
extend,
6868
getGlobalThis,
6969
isArray,
70+
isBuiltInTag,
7071
isFunction,
7172
isObject,
7273
isPromise,
73-
makeMap,
7474
} from '@vue/shared'
7575
import type { Data } from '@vue/runtime-shared'
7676
import type { SuspenseBoundary } from './components/Suspense'
@@ -761,8 +761,6 @@ export const unsetCurrentInstance = () => {
761761
internalSetCurrentInstance(null)
762762
}
763763

764-
const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component')
765-
766764
export function validateComponentName(
767765
name: string,
768766
{ isNativeTag }: AppConfig,
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {
2+
type Component,
3+
type Directive,
4+
createVaporApp,
5+
resolveComponent,
6+
resolveDirective,
7+
} from '@vue/runtime-vapor'
8+
import { makeRender } from '../_utils'
9+
10+
const define = makeRender()
11+
12+
describe('resolveAssets', () => {
13+
test('todo', () => {
14+
expect(true).toBeTruthy()
15+
})
16+
test('should work', () => {
17+
const FooBar = () => []
18+
const BarBaz = { mounted: () => null }
19+
let component1: Component | string
20+
let component2: Component | string
21+
let component3: Component | string
22+
let component4: Component | string
23+
let directive1: Directive
24+
let directive2: Directive
25+
let directive3: Directive
26+
let directive4: Directive
27+
const Root = define({
28+
render() {
29+
component1 = resolveComponent('FooBar')!
30+
directive1 = resolveDirective('BarBaz')!
31+
// camelize
32+
component2 = resolveComponent('Foo-bar')!
33+
directive2 = resolveDirective('Bar-baz')!
34+
// capitalize
35+
component3 = resolveComponent('fooBar')!
36+
directive3 = resolveDirective('barBaz')!
37+
// camelize and capitalize
38+
component4 = resolveComponent('foo-bar')!
39+
directive4 = resolveDirective('bar-baz')!
40+
return []
41+
},
42+
})
43+
const app = createVaporApp(Root.component)
44+
app.component('FooBar', FooBar)
45+
app.directive('BarBaz', BarBaz)
46+
const root = document.createElement('div')
47+
app.mount(root)
48+
expect(component1!).toBe(FooBar)
49+
expect(component2!).toBe(FooBar)
50+
expect(component3!).toBe(FooBar)
51+
expect(component4!).toBe(FooBar)
52+
expect(directive1!).toBe(BarBaz)
53+
expect(directive2!).toBe(BarBaz)
54+
expect(directive3!).toBe(BarBaz)
55+
expect(directive4!).toBe(BarBaz)
56+
})
57+
test('maybeSelfReference', async () => {
58+
let component1: Component | string
59+
let component2: Component | string
60+
let component3: Component | string
61+
const Foo = () => []
62+
const Root = define({
63+
name: 'Root',
64+
render() {
65+
component1 = resolveComponent('Root', true)
66+
component2 = resolveComponent('Foo', true)
67+
component3 = resolveComponent('Bar', true)
68+
return []
69+
},
70+
})
71+
const app = createVaporApp(Root.component)
72+
app.component('Foo', Foo)
73+
const root = document.createElement('div')
74+
app.mount(root)
75+
expect(component1!).toMatchObject(Root.component) // explicit self name reference
76+
expect(component2!).toBe(Foo) // successful resolve take higher priority
77+
expect(component3!).toMatchObject(Root.component) // fallback when resolve fails
78+
})
79+
describe('warning', () => {
80+
test('used outside render() or setup()', () => {
81+
resolveComponent('foo')
82+
expect(
83+
'[Vue warn]: resolveComponent can only be used in render() or setup().',
84+
).toHaveBeenWarned()
85+
resolveDirective('foo')
86+
expect(
87+
'[Vue warn]: resolveDirective can only be used in render() or setup().',
88+
).toHaveBeenWarned()
89+
})
90+
test('not exist', () => {
91+
const Root = define({
92+
setup() {
93+
resolveComponent('foo')
94+
resolveDirective('bar')
95+
},
96+
})
97+
const app = createVaporApp(Root.component)
98+
const root = document.createElement('div')
99+
app.mount(root)
100+
expect('Failed to resolve component: foo').toHaveBeenWarned()
101+
expect('Failed to resolve directive: bar').toHaveBeenWarned()
102+
})
103+
})
104+
})

packages/runtime-vapor/src/apiCreateVaporApp.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { isFunction, isObject } from '@vue/shared'
1+
import { NO, isFunction, isObject } from '@vue/shared'
22
import {
33
type Component,
44
type ComponentInternalInstance,
55
createComponentInstance,
6+
validateComponentName,
67
} from './component'
78
import { warn } from './warning'
8-
import { version } from '.'
9+
import { type Directive, version } from '.'
910
import { render, setupComponent, unmountComponent } from './apiRender'
1011
import type { InjectionKey } from './apiInject'
1112
import type { RawProps } from './componentProps'
13+
import { validateDirectiveName } from './directives'
1214

1315
export function createVaporApp(
1416
rootComponent: Component,
@@ -60,6 +62,35 @@ export function createVaporApp(
6062
return app
6163
},
6264

65+
component(name: string, component?: Component): any {
66+
if (__DEV__) {
67+
validateComponentName(name, context.config)
68+
}
69+
if (!component) {
70+
return context.components[name]
71+
}
72+
if (__DEV__ && context.components[name]) {
73+
warn(`Component "${name}" has already been registered in target app.`)
74+
}
75+
context.components[name] = component
76+
return app
77+
},
78+
79+
directive(name: string, directive?: Directive) {
80+
if (__DEV__) {
81+
validateDirectiveName(name)
82+
}
83+
84+
if (!directive) {
85+
return context.directives[name] as any
86+
}
87+
if (__DEV__ && context.directives[name]) {
88+
warn(`Directive "${name}" has already been registered in target app.`)
89+
}
90+
context.directives[name] = directive
91+
return app
92+
},
93+
6394
mount(rootContainer): any {
6495
if (!instance) {
6596
instance = createComponentInstance(
@@ -119,11 +150,14 @@ export function createAppContext(): AppContext {
119150
return {
120151
app: null as any,
121152
config: {
153+
isNativeTag: NO,
122154
errorHandler: undefined,
123155
warnHandler: undefined,
124156
globalProperties: {},
125157
},
126158
provides: Object.create(null),
159+
components: {},
160+
directives: {},
127161
}
128162
}
129163

@@ -151,6 +185,11 @@ export interface App {
151185
): this
152186
use<Options>(plugin: Plugin<Options>, options: Options): this
153187

188+
component(name: string): Component | undefined
189+
component<T extends Component>(name: string, component: T): this
190+
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
191+
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
192+
154193
mount(
155194
rootContainer: ParentNode | string,
156195
isHydrate?: boolean,
@@ -163,6 +202,9 @@ export interface App {
163202
}
164203

165204
export interface AppConfig {
205+
// @private
206+
readonly isNativeTag: (tag: string) => boolean
207+
166208
errorHandler?: (
167209
err: unknown,
168210
instance: ComponentInternalInstance | null,
@@ -180,6 +222,17 @@ export interface AppContext {
180222
app: App // for devtools
181223
config: AppConfig
182224
provides: Record<string | symbol, any>
225+
226+
/**
227+
* Resolved component registry, only for components with mixins or extends
228+
* @internal
229+
*/
230+
components: Record<string, Component>
231+
/**
232+
* Resolved directive registry, only for components with mixins or extends
233+
* @internal
234+
*/
235+
directives: Record<string, Directive>
183236
}
184237

185238
/**

packages/runtime-vapor/src/component.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { isRef } from '@vue/reactivity'
2-
import { EMPTY_OBJ, hasOwn, isArray, isFunction } from '@vue/shared'
2+
import {
3+
EMPTY_OBJ,
4+
hasOwn,
5+
isArray,
6+
isBuiltInTag,
7+
isFunction,
8+
} from '@vue/shared'
39
import type { Block } from './apiRender'
410
import {
511
type ComponentPropsOptions,
@@ -24,7 +30,11 @@ import {
2430
} from './componentSlots'
2531
import { VaporLifecycleHooks } from './apiLifecycle'
2632
import { warn } from './warning'
27-
import { type AppContext, createAppContext } from './apiCreateVaporApp'
33+
import {
34+
type AppConfig,
35+
type AppContext,
36+
createAppContext,
37+
} from './apiCreateVaporApp'
2838
import type { Data } from '@vue/runtime-shared'
2939
import { BlockEffectScope } from './blockEffectScope'
3040

@@ -233,7 +243,6 @@ export interface ComponentInternalInstance {
233243
// [VaporLifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise<unknown>>
234244
}
235245

236-
// TODO
237246
export let currentInstance: ComponentInternalInstance | null = null
238247

239248
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
@@ -256,7 +265,7 @@ const emptyAppContext = createAppContext()
256265

257266
let uid = 0
258267
export function createComponentInstance(
259-
component: ObjectComponent | FunctionalComponent,
268+
component: Component,
260269
rawProps: RawProps | null,
261270
slots: Slots | null,
262271
dynamicSlots: DynamicSlots | null,
@@ -367,6 +376,17 @@ export function isVaporComponent(
367376
return !!val && hasOwn(val, componentKey)
368377
}
369378

379+
export function validateComponentName(
380+
name: string,
381+
{ isNativeTag }: AppConfig,
382+
) {
383+
if (isBuiltInTag(name) || isNativeTag(name)) {
384+
warn(
385+
'Do not use built-in or reserved HTML elements as component id: ' + name,
386+
)
387+
}
388+
}
389+
370390
function getAttrsProxy(instance: ComponentInternalInstance): Data {
371391
return (
372392
instance.attrsProxy ||

packages/runtime-vapor/src/directives.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { invokeArrayFns, isFunction } from '@vue/shared'
1+
import { invokeArrayFns, isBuiltInDirective, isFunction } from '@vue/shared'
22
import {
33
type ComponentInternalInstance,
44
currentInstance,
@@ -72,6 +72,12 @@ export type Directive<T = any, V = any, M extends string = string> =
7272
| ObjectDirective<T, V, M>
7373
| FunctionDirective<T, V, M>
7474

75+
export function validateDirectiveName(name: string) {
76+
if (isBuiltInDirective(name)) {
77+
warn('Do not use built-in directive ids as custom directive id: ' + name)
78+
}
79+
}
80+
7581
export type DirectiveArguments = Array<
7682
| [Directive | undefined]
7783
| [Directive | undefined, () => any]

0 commit comments

Comments
 (0)