wip: save

This commit is contained in:
Evan You 2024-12-02 09:36:49 +08:00
parent a3edc274e4
commit 0acafc7b4d
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
8 changed files with 179 additions and 97 deletions

View File

@ -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
}

View File

@ -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__) {

View File

@ -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<ObjectComponent, 'setup'> & {
displayName?: string

View File

@ -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)
}
}

View File

@ -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'

View File

@ -1,79 +1,8 @@
<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>

View File

@ -1,18 +1,79 @@
/// <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'
const modules = import.meta.glob<any>('./**/*.(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('<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]
})()
},
}
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('<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))

View File

@ -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,