--- /dev/null
+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
+}
startMeasure(instance, `init`)
}
const reset = setCurrentInstance(instance)
- instance.scope.run(() => {
+ instance.scope.run(function componentSetupFn() {
const { type: component, props } = instance
if (__DEV__) {
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<ObjectComponent, 'setup'> & {
displayName?: string
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)
}
}
export { nextTick } from './scheduler'
export {
getCurrentInstance,
- type ComponentInternalInstance as ComponentInternalInstance,
+ ComponentInternalInstance,
type Component as Component,
type ObjectComponent,
type FunctionalComponent,
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'
<script setup lang="ts" vapor>
-import {
- ref,
- computed,
- onMounted,
- onBeforeMount,
- getCurrentInstance,
- onBeforeUpdate,
- onUpdated,
- onRenderTracked,
- onRenderTriggered,
-} from 'vue/vapor'
-
-const instance = getCurrentInstance()!
-const count = ref(1)
-const double = computed(() => count.value * 2)
-const html = computed(() => `<button>HTML! ${count.value}</button>`)
-
-const inc = () => count.value++
-const dec = () => count.value--
-
-onBeforeMount(() => {
- console.log('onBeforeMount', instance.isMounted)
-})
-onMounted(() => {
- console.log('onMounted', instance.isMounted)
-})
-onMounted(() => {
- setTimeout(() => {
- count.value++
- }, 1000)
-})
-
-onBeforeUpdate(() => {
- console.log('before updated')
-})
-onUpdated(() => {
- console.log('updated')
-})
-
-onRenderTracked(e => {
- console.log(`Render Tracked:`, e.target)
-})
-onRenderTriggered(e => {
- console.log(`Render trigger:`, e.target)
-})
-
-const log = (arg: any) => {
- console.log('callback in render effect')
- return arg
-}
+import Comp from './Comp.vue'
</script>
<template>
- <div>
- <h1 class="red">Counter</h1>
- <div>The number is {{ log(count) }}.</div>
- <div>{{ count }} * 2 = {{ double }}</div>
- <div style="display: flex; gap: 8px">
- <button @click="inc">inc</button>
- <button @click="dec">dec</button>
- </div>
- <div v-html="html" />
- <div v-text="html" />
- <div v-once>once: {{ count }}</div>
- <div v-pre>{{ count }}</div>
- <div v-cloak>{{ count }}</div>
- </div>
+ <h1>Vapor</h1>
+ <Comp />
</template>
-
-<style>
-.red {
- color: red;
-}
-
-html {
- padding: 10px;
-}
-</style>
-/// <reference types="vite/client" />
+import {
+ createComponentSimple,
+ // createFor,
+ createVaporApp,
+ delegate,
+ delegateEvents,
+ ref,
+ renderEffectSimple,
+ template,
+} from 'vue/vapor'
-import { createVaporApp } from 'vue/vapor'
-import { createApp } from 'vue'
-import './style.css'
+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 modules = import.meta.glob<any>('./**/*.(vue|js|ts)')
-const mod = (modules['.' + location.pathname] || modules['./App.vue'])()
+const t0 = template('<h1>Vapor</h1>')
+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]
+ })()
+ },
+}
-mod.then(({ default: mod }) => {
- const app = (mod.vapor ? createVaporApp : createApp)(mod)
- app.mount('#app')
+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
+ ]
+ },
+}
- // @ts-expect-error
- globalThis.unmount = () => {
- app.unmount()
- }
-})
+const t0$1 = template('<div></div>')
+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))
export default defineConfig({
build: {
target: 'esnext',
- minify: 'terser',
+ minify: false,
terserOptions: {
compress: {
pure_getters: true,