diff --git a/packages/runtime-vapor/src/apiCreateComponentSimple.ts b/packages/runtime-vapor/src/apiCreateComponentSimple.ts new file mode 100644 index 000000000..04ca9b052 --- /dev/null +++ b/packages/runtime-vapor/src/apiCreateComponentSimple.ts @@ -0,0 +1,69 @@ +import { + EffectScope, + ReactiveEffect, + pauseTracking, + proxyRefs, + resetTracking, +} from '@vue/reactivity' +import { + type Component, + type ComponentInternalInstance, + createSetupContext, +} from './component' +import { EMPTY_OBJ, isFunction } from '@vue/shared' +import { type SchedulerJob, queueJob } from '../../runtime-core/src/scheduler' + +export function createComponentSimple(component: any, rawProps?: any): any { + const instance = new ComponentInstance( + component, + rawProps, + ) as any as ComponentInternalInstance + pauseTracking() + let prevInstance = currentInstance + currentInstance = instance + instance.scope.on() + const setupFn = isFunction(component) ? component : component.setup + const setupContext = setupFn.length > 1 ? createSetupContext(instance) : null + const node = setupFn( + // TODO __DEV__ ? shallowReadonly(props) : + instance.props, + setupContext, + ) + instance.scope.off() + currentInstance = prevInstance + resetTracking() + node.__vue__ = instance + return node +} + +let uid = 0 +let currentInstance: ComponentInstance | null = null + +export class ComponentInstance { + type: any + uid: number = uid++ + scope: EffectScope = new EffectScope(true) + props: any + constructor(comp: Component, rawProps: any) { + this.type = comp + // init props + this.props = rawProps ? proxyRefs(rawProps) : EMPTY_OBJ + // TODO init slots + } +} + +export function renderEffectSimple(fn: () => void): void { + const updateFn = () => { + fn() + } + const effect = new ReactiveEffect(updateFn) + const job: SchedulerJob = effect.runIfDirty.bind(effect) + job.i = currentInstance as any + job.id = currentInstance!.uid + effect.scheduler = () => queueJob(job) + effect.run() + + // TODO lifecycle + // TODO recurse handling + // TODO measure +} diff --git a/packages/runtime-vapor/src/apiRender.ts b/packages/runtime-vapor/src/apiRender.ts index fe57ab0ef..9cf898e51 100644 --- a/packages/runtime-vapor/src/apiRender.ts +++ b/packages/runtime-vapor/src/apiRender.ts @@ -29,7 +29,7 @@ export function setupComponent(instance: ComponentInternalInstance): void { startMeasure(instance, `init`) } const reset = setCurrentInstance(instance) - instance.scope.run(() => { + instance.scope.run(function componentSetupFn() { const { type: component, props } = instance if (__DEV__) { diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 89f8ccc88..c91bcb77c 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -28,7 +28,10 @@ import type { Data } from '@vue/runtime-shared' export type Component = FunctionalComponent | ObjectComponent -export type SetupFn = (props: any, ctx: SetupContext) => Block | Data | void +export type SetupFn = ( + props: any, + ctx: SetupContext, +) => Block | Data | undefined export type FunctionalComponent = SetupFn & Omit & { displayName?: string diff --git a/packages/runtime-vapor/src/dom/element.ts b/packages/runtime-vapor/src/dom/element.ts index e3bd9ee5b..4aaf12dfe 100644 --- a/packages/runtime-vapor/src/dom/element.ts +++ b/packages/runtime-vapor/src/dom/element.ts @@ -3,14 +3,30 @@ import { renderEffect } from '../renderEffect' import { setText } from './prop' import { type Block, normalizeBlock } from '../block' +// export function insert( +// block: Block, +// parent: ParentNode, +// anchor: Node | null = null, +// ): void { +// const nodes = normalizeBlock(block) +// for (let i = 0; i < nodes.length; i++) { +// parent.insertBefore(nodes[i], anchor) +// } +// } + export function insert( block: Block, parent: ParentNode, anchor: Node | null = null, ): void { - const nodes = normalizeBlock(block) - for (let i = 0; i < nodes.length; i++) { - parent.insertBefore(nodes[i], anchor) + if (block instanceof Node) { + parent.insertBefore(block, anchor) + } else if (isArray(block)) { + for (let i = 0; i < block.length; i++) { + insert(block[i], parent, anchor) + } + } else if (block) { + insert(block.nodes, parent, anchor) } } diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index c03548be3..fa84dd514 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -77,7 +77,7 @@ export const warn = (__DEV__ ? _warn : NOOP) as typeof _warn export { nextTick } from './scheduler' export { getCurrentInstance, - type ComponentInternalInstance as ComponentInternalInstance, + ComponentInternalInstance, type Component as Component, type ObjectComponent, type FunctionalComponent, @@ -155,6 +155,10 @@ export { export { createBranch, createIf } from './apiCreateIf' export { createFor, createForSlots } from './apiCreateFor' export { createComponent } from './apiCreateComponent' +export { + createComponentSimple, + renderEffectSimple, +} from './apiCreateComponentSimple' export { createSelector } from './apiCreateSelector' export { setInheritAttrs } from './componentAttrs' diff --git a/playground/src/App.vue b/playground/src/App.vue index c826cd37b..5795cf47e 100644 --- a/playground/src/App.vue +++ b/playground/src/App.vue @@ -1,79 +1,8 @@ - - diff --git a/playground/src/main.ts b/playground/src/main.ts index d2999613d..1c4ecdfc1 100644 --- a/playground/src/main.ts +++ b/playground/src/main.ts @@ -1,18 +1,79 @@ -/// +import { + createComponentSimple, + // createFor, + createVaporApp, + delegate, + delegateEvents, + ref, + renderEffectSimple, + template, +} from 'vue/vapor' -import { createVaporApp } from 'vue/vapor' -import { createApp } from 'vue' -import './style.css' - -const modules = import.meta.glob('./**/*.(vue|js|ts)') -const mod = (modules['.' + location.pathname] || modules['./App.vue'])() - -mod.then(({ default: mod }) => { - const app = (mod.vapor ? createVaporApp : createApp)(mod) - app.mount('#app') - - // @ts-expect-error - globalThis.unmount = () => { - app.unmount() +function createForSimple(val: () => any, render: (i: number) => any) { + const l = val(), + arr = new Array(l) + for (let i = 0; i < l; i++) { + arr[i] = render(i) } -}) + return arr +} + +const t0 = template('

Vapor

') +const App = { + vapor: true, + __name: 'App', + setup() { + return (_ctx => { + const n0 = t0() + const n1 = createForSimple( + () => 10000, + (i: number) => createComponentSimple(Comp, { count: i }), + ) + return [n0, createComponentSimple(Counter), n1] + })() + }, +} + +const Counter = { + vapor: true, + __name: 'Counter', + setup() { + delegateEvents('click') + const count = ref(0) + const button = document.createElement('button') + button.textContent = '++' + delegate(button, 'click', () => () => count.value++) + return [ + button, + createComponentSimple(Comp, { + // if ref + count, + // if exp + get plusOne() { + return count.value + 1 + }, + }), + // TODO dynamic props: merge with Proxy that iterates sources on access + ] + }, +} + +const t0$1 = template('
') +const Comp = { + vapor: true, + __name: 'Comp', + setup(props: any) { + return (_ctx => { + const n = t0$1() + renderEffectSimple(() => { + n.textContent = props.count + ' / ' + props.plusOne + }) + return n + })() + }, +} + +const s = performance.now() +const app = createVaporApp(App) +app.mount('#app') +console.log((performance.now() - s).toFixed(2)) diff --git a/playground/vite.prod.config.ts b/playground/vite.prod.config.ts index 7e80b8173..2575d51fc 100644 --- a/playground/vite.prod.config.ts +++ b/playground/vite.prod.config.ts @@ -5,7 +5,7 @@ import * as CompilerSFC from '@vue/compiler-sfc' export default defineConfig({ build: { target: 'esnext', - minify: 'terser', + minify: false, terserOptions: { compress: { pure_getters: true,