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
}
/**
* @internal
*/
export function validateComponentName(
name: string,
{ isNativeTag }: AppConfig,

View File

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

View File

@ -1,15 +1,15 @@
/* eslint-disable no-restricted-globals */
import {
type ComponentInternalInstance,
formatComponentName,
} from './component'
import { type GenericComponentInstance, formatComponentName } from './component'
import { devtoolsPerfEnd, devtoolsPerfStart } from './devtools'
let supported: boolean
let perf: Performance
/**
* @internal
*/
export function startMeasure(
instance: ComponentInternalInstance,
instance: GenericComponentInstance,
type: string,
): void {
if (instance.appContext.config.performance && isSupported()) {
@ -21,8 +21,11 @@ export function startMeasure(
}
}
/**
* @internal
*/
export function endMeasure(
instance: ComponentInternalInstance,
instance: GenericComponentInstance,
type: string,
): void {
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
msg + args.map(a => a.toString?.() ?? JSON.stringify(a)).join(''),
instance && instance.proxy,
(instance && instance.proxy) || instance,
trace
.map(
({ ctx }) =>

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import {
type CreateAppFunction,
createAppAPI,
normalizeContainer,
warn,
} from '@vue/runtime-dom'
import type { RawProps } from './componentProps'
@ -21,7 +22,13 @@ const mountApp: AppMountFn<ParentNode> = (app, container) => {
if (container.nodeType === 1 /* Node.ELEMENT_NODE */) {
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)
return instance
}
@ -36,6 +43,19 @@ export const createVaporApp: CreateAppFunction<ParentNode, VaporComponent> = (
) => {
if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, i => i)
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
app.mount = (container, ...args: any[]) => {
container = normalizeContainer(container) as ParentNode

View File

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

View File

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