test(vapor): apiCreateVaporApp

This commit is contained in:
Evan You 2024-12-10 12:49:47 +08:00
parent 443ac60394
commit 48fc65f25c
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
11 changed files with 154 additions and 76 deletions

View File

@ -785,6 +785,9 @@ export function createComponentInstance(
return instance return instance
} }
/**
* @internal
*/
export function validateComponentName( export function validateComponentName(
name: string, name: string,
{ isNativeTag }: AppConfig, { isNativeTag }: AppConfig,

View File

@ -500,6 +500,7 @@ export {
type GenericComponentInstance, type GenericComponentInstance,
type LifecycleHook, type LifecycleHook,
nextUid, nextUid,
validateComponentName,
} from './component' } from './component'
export { pushWarningContext, popWarningContext } from './warning' export { pushWarningContext, popWarningContext } from './warning'
export { export {
@ -512,3 +513,4 @@ export {
simpleSetCurrentInstance, simpleSetCurrentInstance,
} from './componentCurrentInstance' } from './componentCurrentInstance'
export { registerHMR, unregisterHMR } from './hmr' export { registerHMR, unregisterHMR } from './hmr'
export { startMeasure, endMeasure } from './profiling'

View File

@ -1,15 +1,15 @@
/* eslint-disable no-restricted-globals */ /* eslint-disable no-restricted-globals */
import { import { type GenericComponentInstance, formatComponentName } from './component'
type ComponentInternalInstance,
formatComponentName,
} from './component'
import { devtoolsPerfEnd, devtoolsPerfStart } from './devtools' import { devtoolsPerfEnd, devtoolsPerfStart } from './devtools'
let supported: boolean let supported: boolean
let perf: Performance let perf: Performance
/**
* @internal
*/
export function startMeasure( export function startMeasure(
instance: ComponentInternalInstance, instance: GenericComponentInstance,
type: string, type: string,
): void { ): void {
if (instance.appContext.config.performance && isSupported()) { if (instance.appContext.config.performance && isSupported()) {
@ -21,8 +21,11 @@ export function startMeasure(
} }
} }
/**
* @internal
*/
export function endMeasure( export function endMeasure(
instance: ComponentInternalInstance, instance: GenericComponentInstance,
type: string, type: string,
): void { ): void {
if (instance.appContext.config.performance && isSupported()) { if (instance.appContext.config.performance && isSupported()) {

View File

@ -57,7 +57,7 @@ export function warn(msg: string, ...args: any[]): void {
[ [
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
msg + args.map(a => a.toString?.() ?? JSON.stringify(a)).join(''), msg + args.map(a => a.toString?.() ?? JSON.stringify(a)).join(''),
instance && instance.proxy, (instance && instance.proxy) || instance,
trace trace
.map( .map(
({ ctx }) => ({ ctx }) =>

View File

@ -1,6 +1,8 @@
import { ref } from '@vue/reactivity' import { ref } from '@vue/reactivity'
import { makeRender } from './_utils' import { makeRender } from './_utils'
// import { createFor, createSelector, nextTick, renderEffect } from '../src' // @ts-expect-error
import { createFor, createSelector, renderEffect } from '../src'
import { nextTick } from '@vue/runtime-dom'
const define = makeRender() const define = makeRender()
@ -16,6 +18,7 @@ describe.todo('api: createSelector', () => {
const isSleected = createSelector(index) const isSleected = createSelector(index)
return createFor( return createFor(
() => list.value, () => list.value,
// @ts-expect-error
([item]) => { ([item]) => {
const span = document.createElement('li') const span = document.createElement('li')
renderEffect(() => { renderEffect(() => {
@ -25,6 +28,7 @@ describe.todo('api: createSelector', () => {
}) })
return span return span
}, },
// @ts-expect-error
item => item.id, item => item.id,
) )
}).render() }).render()
@ -66,10 +70,12 @@ describe.todo('api: createSelector', () => {
const { host } = define(() => { const { host } = define(() => {
const isSleected = createSelector( const isSleected = createSelector(
index, index,
// @ts-expect-error
(key, value) => key === value + 1, (key, value) => key === value + 1,
) )
return createFor( return createFor(
() => list.value, () => list.value,
// @ts-expect-error
([item]) => { ([item]) => {
const span = document.createElement('li') const span = document.createElement('li')
renderEffect(() => { renderEffect(() => {
@ -79,6 +85,7 @@ describe.todo('api: createSelector', () => {
}) })
return span return span
}, },
// @ts-expect-error
item => item.id, item => item.id,
) )
}).render() }).render()

View File

@ -1,25 +1,29 @@
import { import {
type ComponentInternalInstance,
type Plugin,
createComponent, createComponent,
createTextNode, createTextNode,
createVaporApp, createVaporApp,
defineComponent, defineVaporComponent,
getCurrentInstance, // @ts-expect-error
withDirectives,
} from '../src'
import {
type GenericComponentInstance,
type Plugin,
currentInstance,
inject, inject,
provide, provide,
resolveComponent, resolveComponent,
resolveDirective, resolveDirective,
withDirectives, warn,
} from '../src' } from '@vue/runtime-dom'
import { warn } from '@vue/runtime-dom'
import { makeRender } from './_utils' import { makeRender } from './_utils'
import type { VaporComponent } from '../src/component'
const define = makeRender() const define = makeRender()
describe.todo('api: createVaporApp', () => { describe('api: createVaporApp', () => {
test('mount', () => { test('mount', () => {
const Comp = defineComponent({ const Comp = defineVaporComponent({
props: { props: {
count: { default: 0 }, count: { default: 0 },
}, },
@ -51,7 +55,7 @@ describe.todo('api: createVaporApp', () => {
}) })
test('unmount', () => { test('unmount', () => {
const Comp = defineComponent({ const Comp = defineVaporComponent({
props: { props: {
count: { default: 0 }, count: { default: 0 },
}, },
@ -82,22 +86,22 @@ describe.todo('api: createVaporApp', () => {
}, },
}) })
const Child = defineComponent({ const Child = defineVaporComponent({
setup() { setup() {
const foo = inject('foo') const foo = inject('foo')
const bar = inject('bar') const bar = inject('bar')
try { try {
inject('__proto__') inject('__proto__')
} catch (e: any) {} } catch (e: any) {}
return createTextNode(() => [`${foo},${bar}`]) return createTextNode([`${foo},${bar}`])
}, },
}) })
const { app, mount, create, host } = Root.create(null) const { app, mount, create, html } = Root.create()
app.provide('foo', 1) app.provide('foo', 1)
app.provide('bar', 2) app.provide('bar', 2)
mount() mount()
expect(host.innerHTML).toBe(`3,2`) expect(html()).toBe(`3,2`)
expect('[Vue warn]: injection "__proto__" not found.').toHaveBeenWarned() expect('[Vue warn]: injection "__proto__" not found.').toHaveBeenWarned()
const { app: app2 } = create() const { app: app2 } = create()
@ -132,8 +136,8 @@ describe.todo('api: createVaporApp', () => {
test('component', () => { test('component', () => {
const { app, mount, host } = define({ const { app, mount, host } = define({
setup() { setup() {
const FooBar = resolveComponent('foo-bar') const FooBar = resolveComponent('foo-bar') as VaporComponent
const BarBaz = resolveComponent('bar-baz') const BarBaz = resolveComponent('bar-baz') as VaporComponent
return [createComponent(FooBar), createComponent(BarBaz)] return [createComponent(FooBar), createComponent(BarBaz)]
}, },
}).create() }).create()
@ -152,7 +156,7 @@ describe.todo('api: createVaporApp', () => {
expect(host.innerHTML).toBe(`foobar!barbaz!`) expect(host.innerHTML).toBe(`foobar!barbaz!`)
}) })
test('directive', () => { test.todo('directive', () => {
const spy1 = vi.fn() const spy1 = vi.fn()
const spy2 = vi.fn() const spy2 = vi.fn()
@ -229,7 +233,7 @@ describe.todo('api: createVaporApp', () => {
test('config.errorHandler', () => { test('config.errorHandler', () => {
const error = new Error() const error = new Error()
let instance: ComponentInternalInstance let instance: GenericComponentInstance
const handler = vi.fn((err, _instance, info) => { const handler = vi.fn((err, _instance, info) => {
expect(err).toBe(error) expect(err).toBe(error)
@ -239,7 +243,8 @@ describe.todo('api: createVaporApp', () => {
const { app, mount } = define({ const { app, mount } = define({
setup() { setup() {
instance = getCurrentInstance()! instance = currentInstance!
return {}
}, },
render() { render() {
throw error throw error
@ -251,7 +256,7 @@ describe.todo('api: createVaporApp', () => {
}) })
test('config.warnHandler', () => { test('config.warnHandler', () => {
let instance: ComponentInternalInstance let instance: GenericComponentInstance
const handler = vi.fn((msg, _instance, trace) => { const handler = vi.fn((msg, _instance, trace) => {
expect(msg).toMatch(`warn message`) expect(msg).toMatch(`warn message`)
@ -262,8 +267,9 @@ describe.todo('api: createVaporApp', () => {
const { app, mount } = define({ const { app, mount } = define({
name: 'Hello', name: 'Hello',
setup() { setup() {
instance = getCurrentInstance()! instance = currentInstance!
warn('warn message') warn('warn message')
return []
}, },
}).create() }).create()
@ -275,22 +281,23 @@ describe.todo('api: createVaporApp', () => {
describe('config.isNativeTag', () => { describe('config.isNativeTag', () => {
const isNativeTag = vi.fn(tag => tag === 'div') const isNativeTag = vi.fn(tag => tag === 'div')
test('Component.name', () => { // Not relevant for vapor
const { app, mount } = define({ // test('Component.name', () => {
name: 'div', // const { app, mount } = define({
render(): any {}, // name: 'div',
}).create() // render(): any {},
// }).create()
Object.defineProperty(app.config, 'isNativeTag', { // Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag, // value: isNativeTag,
writable: false, // writable: false,
}) // })
mount() // mount()
expect( // expect(
`Do not use built-in or reserved HTML elements as component id: div`, // `Do not use built-in or reserved HTML elements as component id: div`,
).toHaveBeenWarned() // ).toHaveBeenWarned()
}) // })
test('register using app.component', () => { test('register using app.component', () => {
const { app, mount } = define({ const { app, mount } = define({
@ -316,7 +323,7 @@ describe.todo('api: createVaporApp', () => {
}) })
test('with performance enabled', () => { test('with performance enabled', () => {
const { app, mount } = define({}).create() const { app, mount } = define({ setup: () => [] }).create()
app.config.performance = true app.config.performance = true
mount() mount()
@ -324,7 +331,7 @@ describe.todo('api: createVaporApp', () => {
}) })
test('with performance disabled', () => { test('with performance disabled', () => {
const { app, mount } = define({}).create() const { app, mount } = define({ setup: () => [] }).create()
app.config.performance = false app.config.performance = false
mount() mount()
@ -333,14 +340,16 @@ describe.todo('api: createVaporApp', () => {
}) })
test('config.globalProperty', () => { test('config.globalProperty', () => {
const { app, mount, html } = define({ const { app } = define({
render() { setup() {
const instance = getCurrentInstance()! return []
return createTextNode([instance.appContext.config.globalProperties.msg])
}, },
}).create() }).create()
try {
app.config.globalProperties.msg = 'hello world' app.config.globalProperties.msg = 'hello world'
mount() } catch (e) {}
expect(html()).toBe('hello world') expect(
`app.config.globalProperties is not supported in vapor mode`,
).toHaveBeenWarned()
}) })
}) })

View File

@ -1,11 +1,8 @@
import { ref, shallowRef } from '@vue/reactivity' import { ref, shallowRef } from '@vue/reactivity'
import { createComponent } from '../src/component' import { type VaporComponentInstance, createComponent } from '../src/component'
import { setRef } from '../src/dom/templateRef' import { setRef } from '../src/dom/templateRef'
import { makeRender } from './_utils' import { makeRender } from './_utils'
import { import { currentInstance } from '@vue/runtime-dom'
type ComponentInternalInstance,
getCurrentInstance,
} from '../src/component'
import { defineVaporComponent } from '../src/apiDefineComponent' import { defineVaporComponent } from '../src/apiDefineComponent'
const define = makeRender() const define = makeRender()
@ -39,10 +36,11 @@ describe.todo('api: expose', () => {
}) })
test('via setup context (expose empty)', () => { test('via setup context (expose empty)', () => {
let childInstance: ComponentInternalInstance | null = null let childInstance: VaporComponentInstance | null = null
const Child = defineVaporComponent({ const Child = defineVaporComponent({
setup(_) { setup(_) {
childInstance = getCurrentInstance() childInstance = currentInstance as VaporComponentInstance
return []
}, },
}) })
const childRef = shallowRef() const childRef = shallowRef()
@ -77,6 +75,7 @@ describe.todo('api: expose', () => {
define({ define({
setup(_, { expose }) { setup(_, { expose }) {
expose(ref(1)) expose(ref(1))
return []
}, },
}).render() }).render()
@ -89,6 +88,7 @@ describe.todo('api: expose', () => {
define({ define({
setup(_, { expose }) { setup(_, { expose }) {
expose(['focus']) expose(['focus'])
return []
}, },
}).render() }).render()
@ -101,6 +101,7 @@ describe.todo('api: expose', () => {
define({ define({
setup(_, { expose }) { setup(_, { expose }) {
expose(() => null) expose(() => null)
return []
}, },
}).render() }).render()

View File

@ -1,19 +1,23 @@
import type { NodeRef } from '../../src/dom/templateRef' import type { NodeRef } from '../../src/dom/templateRef'
import { import {
// @ts-expect-error
createFor, createFor,
// @ts-expect-error
createIf, createIf,
getCurrentInstance,
insert, insert,
nextTick,
reactive,
ref,
renderEffect, renderEffect,
setRef, setRef,
setText, setText,
template, template,
watchEffect,
} from '../../src' } from '../../src'
import { makeRender } from '../_utils' import { makeRender } from '../_utils'
import {
currentInstance,
nextTick,
reactive,
ref,
watchEffect,
} from '@vue/runtime-dom'
const define = makeRender() const define = makeRender()
@ -257,7 +261,7 @@ describe.todo('api: template ref', () => {
const t1 = template('<i></i>') const t1 = template('<i></i>')
const { render } = define({ const { render } = define({
render() { render() {
const instance = getCurrentInstance()! const instance = currentInstance!
const n0 = t0() const n0 = t0()
const n1 = t1() const n1 = t1()
let r0: NodeRef | undefined let r0: NodeRef | undefined
@ -303,7 +307,7 @@ describe.todo('api: template ref', () => {
const t1 = template('<i></i>') const t1 = template('<i></i>')
const { render } = define({ const { render } = define({
render() { render() {
const instance = getCurrentInstance()! const instance = currentInstance!
const n0 = createIf( const n0 = createIf(
() => refToggle.value, () => refToggle.value,
() => { () => {
@ -355,6 +359,7 @@ describe.todo('api: template ref', () => {
const n1 = t0() const n1 = t0()
const n2 = createFor( const n2 = createFor(
() => list, () => list,
// @ts-expect-error
state => { state => {
const n1 = t1() const n1 = t1()
setRef(n1 as Element, listRefs, undefined, true) setRef(n1 as Element, listRefs, undefined, true)
@ -413,6 +418,7 @@ describe.todo('api: template ref', () => {
const n1 = t0() const n1 = t0()
const n2 = createFor( const n2 = createFor(
() => list, () => list,
// @ts-expect-error
state => { state => {
const n1 = t1() const n1 = t1()
setRef(n1 as Element, 'listRefs', undefined, true) setRef(n1 as Element, 'listRefs', undefined, true)
@ -469,6 +475,7 @@ describe.todo('api: template ref', () => {
const n2 = n1!.nextSibling! const n2 = n1!.nextSibling!
const n3 = createFor( const n3 = createFor(
() => list.value, () => list.value,
// @ts-expect-error
state => { state => {
const n4 = t1() const n4 = t1()
setRef(n4 as Element, 'listRefs', undefined, true) setRef(n4 as Element, 'listRefs', undefined, true)

View File

@ -11,6 +11,7 @@ import {
type CreateAppFunction, type CreateAppFunction,
createAppAPI, createAppAPI,
normalizeContainer, normalizeContainer,
warn,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import type { RawProps } from './componentProps' import type { RawProps } from './componentProps'
@ -21,7 +22,13 @@ const mountApp: AppMountFn<ParentNode> = (app, container) => {
if (container.nodeType === 1 /* Node.ELEMENT_NODE */) { if (container.nodeType === 1 /* Node.ELEMENT_NODE */) {
container.textContent = '' container.textContent = ''
} }
const instance = createComponent(app._component, app._props as RawProps) const instance = createComponent(
app._component,
app._props as RawProps,
null,
false,
app._context,
)
mountComponent(instance, container) mountComponent(instance, container)
return instance return instance
} }
@ -36,6 +43,19 @@ export const createVaporApp: CreateAppFunction<ParentNode, VaporComponent> = (
) => { ) => {
if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, i => i) if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, i => i)
const app = _createApp(comp, props) const app = _createApp(comp, props)
if (__DEV__) {
app.config.globalProperties = new Proxy(
{},
{
set() {
warn(`app.config.globalProperties is not supported in vapor mode.`)
return false
},
},
)
}
const mount = app.mount const mount = app.mount
app.mount = (container, ...args: any[]) => { app.mount = (container, ...args: any[]) => {
container = normalizeContainer(container) as ParentNode container = normalizeContainer(container) as ParentNode

View File

@ -13,11 +13,13 @@ import {
type SuspenseBoundary, type SuspenseBoundary,
callWithErrorHandling, callWithErrorHandling,
currentInstance, currentInstance,
endMeasure,
nextUid, nextUid,
popWarningContext, popWarningContext,
pushWarningContext, pushWarningContext,
registerHMR, registerHMR,
simpleSetCurrentInstance, simpleSetCurrentInstance,
startMeasure,
unregisterHMR, unregisterHMR,
warn, warn,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
@ -100,6 +102,7 @@ export function createComponent(
rawProps?: RawProps | null, rawProps?: RawProps | null,
rawSlots?: RawSlots | null, rawSlots?: RawSlots | null,
isSingleRoot?: boolean, isSingleRoot?: boolean,
appContext?: GenericAppContext,
): VaporComponentInstance { ): VaporComponentInstance {
// check if we are the single root of the parent // check if we are the single root of the parent
// if yes, inject parent attrs as dynamic props source // if yes, inject parent attrs as dynamic props source
@ -117,15 +120,22 @@ export function createComponent(
} }
} }
const instance = new VaporComponentInstance(component, rawProps, rawSlots) const instance = new VaporComponentInstance(
const prev = currentInstance component,
simpleSetCurrentInstance(instance) rawProps,
rawSlots,
appContext,
)
pauseTracking()
if (__DEV__) { if (__DEV__) {
pushWarningContext(instance) pushWarningContext(instance)
startMeasure(instance, `init`)
} }
const prev = currentInstance
simpleSetCurrentInstance(instance)
pauseTracking()
const setupFn = isFunction(component) ? component : component.setup const setupFn = isFunction(component) ? component : component.setup
const setupContext = (instance.setupContext = const setupContext = (instance.setupContext =
setupFn && setupFn.length > 1 ? new SetupContext(instance) : null) setupFn && setupFn.length > 1 ? new SetupContext(instance) : null)
@ -177,12 +187,14 @@ export function createComponent(
}) })
} }
if (__DEV__) {
popWarningContext()
}
resetTracking() resetTracking()
simpleSetCurrentInstance(prev, instance) simpleSetCurrentInstance(prev, instance)
if (__DEV__) {
popWarningContext()
endMeasure(instance, 'init')
}
return instance return instance
} }
@ -280,6 +292,7 @@ export class VaporComponentInstance implements GenericComponentInstance {
comp: VaporComponent, comp: VaporComponent,
rawProps?: RawProps | null, rawProps?: RawProps | null,
rawSlots?: RawSlots | null, rawSlots?: RawSlots | null,
appContext?: GenericAppContext,
) { ) {
this.vapor = true this.vapor = true
this.uid = nextUid() this.uid = nextUid()
@ -295,7 +308,7 @@ export class VaporComponentInstance implements GenericComponentInstance {
this.provides = currentInstance.provides this.provides = currentInstance.provides
this.ids = currentInstance.ids this.ids = currentInstance.ids
} else { } else {
this.appContext = emptyContext this.appContext = appContext || emptyContext
this.provides = Object.create(this.appContext.provides) this.provides = Object.create(this.appContext.provides)
this.ids = ['', 0, 0] this.ids = ['', 0, 0]
} }
@ -417,6 +430,9 @@ export function mountComponent(
parent: ParentNode, parent: ParentNode,
anchor?: Node | null | 0, anchor?: Node | null | 0,
): void { ): void {
if (__DEV__) {
startMeasure(instance, `mount`)
}
if (!instance.isMounted) { if (!instance.isMounted) {
if (instance.bm) invokeArrayFns(instance.bm) if (instance.bm) invokeArrayFns(instance.bm)
insert(instance.block, parent, anchor) insert(instance.block, parent, anchor)
@ -427,6 +443,9 @@ export function mountComponent(
} else { } else {
insert(instance.block, parent, anchor) insert(instance.block, parent, anchor)
} }
if (__DEV__) {
endMeasure(instance, `mount`)
}
} }
export function unmountComponent( export function unmountComponent(

View File

@ -5,13 +5,14 @@ import {
queueJob, queueJob,
queuePostFlushCb, queuePostFlushCb,
simpleSetCurrentInstance, simpleSetCurrentInstance,
startMeasure,
warn, warn,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { type VaporComponentInstance, isVaporComponent } from './component' import { type VaporComponentInstance, isVaporComponent } from './component'
import { invokeArrayFns } from '@vue/shared' import { invokeArrayFns } from '@vue/shared'
export function renderEffect(fn: () => void, noLifecycle = false): void { export function renderEffect(fn: () => void, noLifecycle = false): void {
const instance = currentInstance as VaporComponentInstance const instance = currentInstance as VaporComponentInstance | null
const scope = getCurrentScope() const scope = getCurrentScope()
if (__DEV__ && !__TEST__ && !isVaporComponent(instance)) { if (__DEV__ && !__TEST__ && !isVaporComponent(instance)) {
warn('renderEffect called without active vapor instance.') warn('renderEffect called without active vapor instance.')
@ -20,6 +21,9 @@ export function renderEffect(fn: () => void, noLifecycle = false): void {
const renderEffectFn = noLifecycle const renderEffectFn = noLifecycle
? fn ? fn
: () => { : () => {
if (__DEV__ && instance) {
startMeasure(instance, `renderEffect`)
}
const prev = currentInstance const prev = currentInstance
simpleSetCurrentInstance(instance) simpleSetCurrentInstance(instance)
if (scope) scope.on() if (scope) scope.on()
@ -41,6 +45,9 @@ export function renderEffect(fn: () => void, noLifecycle = false): void {
} }
if (scope) scope.off() if (scope) scope.off()
simpleSetCurrentInstance(prev, instance) simpleSetCurrentInstance(prev, instance)
if (__DEV__ && instance) {
startMeasure(instance, `renderEffect`)
}
} }
const effect = new ReactiveEffect(renderEffectFn) const effect = new ReactiveEffect(renderEffectFn)