├── .gitignore ├── pnpm-workspace.yaml ├── packages ├── compiler-core │ ├── src │ │ ├── index.ts │ │ ├── utils.ts │ │ ├── runtimeHelpers.ts │ │ ├── transforms │ │ │ ├── transformExpression.ts │ │ │ ├── transformElement.ts │ │ │ └── transformText.ts │ │ ├── ast.ts │ │ ├── compile.ts │ │ ├── transform.ts │ │ ├── codegen.ts │ │ └── parse.ts │ ├── package.json │ └── __test__ │ │ ├── __snapshots__ │ │ └── codegen.test.ts.snap │ │ ├── transform.test.ts │ │ ├── codegen.test.ts │ │ └── parse.test.ts ├── shared │ ├── src │ │ ├── toDisplayString.ts │ │ ├── shapeFlags.ts │ │ └── index.ts │ └── package.json ├── runtime-core │ ├── src │ │ ├── h.ts │ │ ├── componentProps.ts │ │ ├── index.ts │ │ ├── createApp.ts │ │ ├── componentEmit.ts │ │ ├── apiWatch.ts │ │ ├── componentsSlots.ts │ │ ├── componentPublicInstance.ts │ │ ├── scheduler.ts │ │ ├── componentUpdateUtils.ts │ │ ├── vnode.ts │ │ ├── apiInject.ts │ │ ├── helps │ │ │ └── renderSlots.ts │ │ ├── component.ts │ │ └── renderer.ts │ ├── package.json │ └── __tests__ │ │ └── apiWatch.test.ts ├── vue │ ├── examples │ │ ├── update │ │ │ ├── main.js │ │ │ ├── index.html │ │ │ └── App.js │ │ ├── apiInject │ │ │ ├── main.js │ │ │ ├── index.html │ │ │ └── App.js │ │ ├── helloWorld │ │ │ ├── main.js │ │ │ ├── Foo.js │ │ │ ├── index.html │ │ │ └── App.js │ │ ├── nextTicker │ │ │ ├── main.js │ │ │ ├── index.html │ │ │ └── App.js │ │ ├── compiler-base │ │ │ ├── main.js │ │ │ ├── App.js │ │ │ └── index.html │ │ ├── componentEmit │ │ │ ├── main.js │ │ │ ├── App.js │ │ │ ├── index.html │ │ │ └── Foo.js │ │ ├── componentSlot │ │ │ ├── main.js │ │ │ ├── index.html │ │ │ ├── Foo.js │ │ │ └── App.js │ │ ├── componentUpdate │ │ │ ├── main.js │ │ │ ├── Child.js │ │ │ ├── index.html │ │ │ └── App.js │ │ ├── currentInstance │ │ │ ├── main.js │ │ │ ├── Foo.js │ │ │ ├── App.js │ │ │ └── index.html │ │ ├── patchChildren │ │ │ ├── main.js │ │ │ ├── index.html │ │ │ ├── TextToText.js │ │ │ ├── App.js │ │ │ ├── ArrayToText.js │ │ │ ├── TextToArray.js │ │ │ └── ArrayToArray.js │ │ └── customRenderer │ │ │ ├── App.js │ │ │ ├── index.html │ │ │ └── main.js │ ├── package.json │ └── src │ │ └── index.ts ├── reactivity │ ├── src │ │ ├── index.ts │ │ ├── computed.ts │ │ ├── reactive.ts │ │ ├── ref.ts │ │ ├── baseHandlers.ts │ │ └── effect.ts │ ├── package.json │ └── __test__ │ │ ├── shallowReadonly.test.ts │ │ ├── reactive.test.ts │ │ ├── readonly.test.ts │ │ ├── computed.test.ts │ │ ├── ref.test.ts │ │ └── effect.test.ts └── runtime-dom │ ├── package.json │ └── src │ └── index.ts ├── .eslintrc ├── vitest.config.ts ├── rollup.config.js ├── package.json ├── README.md ├── tsconfig.json ├── yarn.lock └── yarn-error.log /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /packages/compiler-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './compile' 2 | -------------------------------------------------------------------------------- /packages/shared/src/toDisplayString.ts: -------------------------------------------------------------------------------- 1 | export function toDisplayString(value) { 2 | return String(value) 3 | } 4 | -------------------------------------------------------------------------------- /packages/runtime-core/src/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from './vnode' 2 | 3 | export function h(type, props?, children?) { 4 | return createVNode(type, props, children) 5 | } 6 | -------------------------------------------------------------------------------- /packages/compiler-core/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from './ast' 2 | 3 | export function isText(node) { 4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION 5 | } 6 | -------------------------------------------------------------------------------- /packages/vue/examples/update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /packages/vue/examples/apiInject/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /packages/vue/examples/helloWorld/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /packages/vue/examples/nextTicker/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /packages/vue/examples/compiler-base/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /packages/vue/examples/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /packages/vue/examples/componentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /packages/vue/examples/componentUpdate/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /packages/vue/examples/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /packages/vue/examples/patchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/vue3-mini.esm.js' 2 | import { App } from './App.js' 3 | 4 | const rootContainer = document.querySelector('#app') 5 | createApp(App).mount(rootContainer) 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@antfu", 3 | "rules": { 4 | "@typescript-eslint/no-use-before-define": "off", 5 | "no-console": "off", 6 | "no-cond-assign": "off", 7 | "@typescript-eslint/no-this-alias": "off", 8 | "no-new-func": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /packages/vue/examples/customRenderer/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../dist/vue3-mini.esm.js' 2 | 3 | export const App = { 4 | setup() { 5 | return { 6 | x: 100, 7 | y: 100, 8 | } 9 | }, 10 | render() { 11 | return h('rect', { x: this.x, y: this.y }) 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /packages/shared/src/shapeFlags.ts: -------------------------------------------------------------------------------- 1 | // | 两位都为0 才为0 用作赋值 2 | // & 两位都为1 才为1 用作判断 3 | 4 | export enum ShapeFlags { 5 | ELEMENT = 1, // 0001 6 | STATEFUL_COMPONENT = 1 << 1, // 0010 7 | TEXT_CHILDREN = 1 << 2, // 0100 8 | ARRAY_CHILDREN = 1 << 3, // 1000 9 | SLOT_CHILDREN = 1 << 4, // 1000 10 | } 11 | -------------------------------------------------------------------------------- /packages/compiler-core/src/runtimeHelpers.ts: -------------------------------------------------------------------------------- 1 | export const TO_DISPLAY_STRING = Symbol('toDisplayString') 2 | export const CREATE_ELEMENT_VNODE = Symbol('createElementVNode') 3 | 4 | export const helperMapName = { 5 | [TO_DISPLAY_STRING]: 'toDisplayString', 6 | [CREATE_ELEMENT_VNODE]: 'createElementVNode', 7 | } 8 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue3-mini/shared", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "keywords": [], 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/vue/examples/compiler-base/App.js: -------------------------------------------------------------------------------- 1 | import { ref } from '../../dist/vue3-mini.esm.js' 2 | 3 | export const App = { 4 | name: 'App', 5 | template: '
hi,{{count}}
', 6 | setup() { 7 | const count = (window.count = ref(1)) 8 | return { 9 | count, 10 | } 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /packages/vue/examples/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../dist/vue3-mini.esm.js' 2 | export default { 3 | name: 'Child', 4 | setup(props, { emit }) {}, 5 | render(proxy) { 6 | return h('div', {}, [ 7 | h('div', {}, `child - props - msg: ${this.$props.msg}`), 8 | ]) 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /packages/vue/examples/helloWorld/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../dist/vue3-mini.esm.js' 2 | 3 | export const Foo = { 4 | setup(props) { 5 | console.log(props) 6 | // shallowReadonly -> warn 7 | props.count++ 8 | }, 9 | 10 | render() { 11 | return h('div', {}, `foo:${this.count}`) 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance, rawProps) { 2 | // 将props赋值给instance.props 3 | console.log('初始化 props') 4 | // TODO 5 | // 应该还有 attrs 的概念 6 | // attrs 7 | // 如果组件声明了 props 的话,那么才可以进入 props 属性内 8 | // 不然的话是需要存储在 attrs 内 9 | instance.props = rawProps || {} 10 | } 11 | -------------------------------------------------------------------------------- /packages/vue/examples/currentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, h } from '../../dist/vue3-mini.esm.js' 2 | 3 | export const Foo = { 4 | name: 'Foo', 5 | setup() { 6 | const instance = getCurrentInstance() 7 | console.log('Foo:', instance) 8 | return {} 9 | }, 10 | render() { 11 | return h('div', {}, 'foo') 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /packages/reactivity/src/index.ts: -------------------------------------------------------------------------------- 1 | export { effect, track, trigger, stop, trackEffects, triggerEffects, isTracking } from './effect' 2 | export { computed } from './computed' 3 | export { ref, isRef, unRef, proxyRefs } from './ref' 4 | export { 5 | reactive, 6 | readonly, 7 | isReactive, 8 | isReadonly, 9 | shallowReadonly, 10 | isProxy, 11 | } from './reactive' 12 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transforms/transformExpression.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from '../ast' 2 | 3 | export function transformExpression(node) { 4 | if (node.type === NodeTypes.INTERPOLATION) 5 | node.content = processExpression(node.content) 6 | } 7 | function processExpression(node: any) { 8 | node.content = `_ctx.${node.content}` 9 | 10 | return node 11 | } 12 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { defineConfig } from 'vitest/config' 3 | 4 | export default defineConfig({ 5 | test: { 6 | globals: true, 7 | }, 8 | resolve: { 9 | alias: [ 10 | { 11 | find: /@vue3-mini\/(\w*)/, 12 | replacement: `${path.resolve(__dirname, 'packages')}/$1/src`, 13 | }, 14 | ], 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/reactivity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue3-mini/reactivity", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "keywords": [], 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "dependencies": { 13 | "@vue3-mini/shared": "workspace:^1.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/compiler-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue3-mini/compiler-core", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "keywords": [], 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "dependencies": { 13 | "@vue3-mini/shared": "workspace:^1.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/vue/examples/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, h } from '../../dist/vue3-mini.esm.js' 2 | import { Foo } from './Foo.js' 3 | 4 | export const App = { 5 | name: 'App', 6 | render() { 7 | return h('div', {}, [h('p', {}, 'currentInstanceDemo'), h(Foo)]) 8 | }, 9 | 10 | setup() { 11 | const instance = getCurrentInstance() 12 | console.log('App:', instance) 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /packages/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-mini", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "keywords": [], 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "dependencies": { 13 | "@vue3-mini/compiler-core": "workspace:^1.0.0", 14 | "@vue3-mini/runtime-dom": "workspace:^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/runtime-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue3-mini/runtime-core", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "keywords": [], 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "dependencies": { 13 | "@vue3-mini/reactivity": "workspace:^1.0.0", 14 | "@vue3-mini/shared": "workspace:^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/runtime-dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue3-mini/runtime-dom", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "keywords": [], 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "dependencies": { 13 | "@vue3-mini/runtime-core": "workspace:^1.0.0", 14 | "@vue3-mini/shared": "workspace:^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript' 2 | 3 | export default { 4 | input: './packages/vue/src/index.ts', 5 | output: [ 6 | // 1. cjs -> commonjs规范 7 | // 2. esm -> 标准化模块规范 8 | { 9 | format: 'cjs', 10 | file: 'packages/vue/dist/vue3-mini.cjs.js', 11 | }, 12 | { 13 | format: 'es', 14 | file: 'packages/vue/dist/vue3-mini.esm.js', 15 | }, 16 | ], 17 | plugins: [ 18 | typescript(), 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /packages/compiler-core/src/ast.ts: -------------------------------------------------------------------------------- 1 | import { CREATE_ELEMENT_VNODE } from './runtimeHelpers' 2 | 3 | export const enum NodeTypes { 4 | INTERPOLATION, 5 | SIMPLE_EXPRESSION, 6 | ELEMENT, 7 | TEXT, 8 | ROOT, 9 | COMPOUND_EXPRESSION, 10 | } 11 | 12 | export function createVNodeCall(context, tag, props, children) { 13 | context.helper(CREATE_ELEMENT_VNODE) 14 | const vnodeElement = { 15 | type: NodeTypes.ELEMENT, 16 | tag, 17 | props, 18 | children, 19 | } 20 | return vnodeElement 21 | } 22 | -------------------------------------------------------------------------------- /packages/vue/examples/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../dist/vue3-mini.esm.js' 2 | import { Foo } from './Foo.js' 3 | 4 | export const App = { 5 | render() { 6 | return h('div', {}, [ 7 | h('div', {}, 'App'), 8 | h(Foo, { 9 | onAdd(a, b) { 10 | console.log('onAdd', a, b) 11 | }, 12 | onAddFoo(a, b) { 13 | console.log('onAddFoo', a, b) 14 | }, 15 | }), 16 | ]) 17 | }, 18 | 19 | setup() { 20 | return {} 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /packages/vue/examples/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/vue/examples/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/vue/examples/helloWorld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/vue/examples/nextTicker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/vue/examples/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/vue/examples/componentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/vue/examples/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/vue/examples/currentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/vue/examples/patchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/runtime-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export { h } from './h' 2 | export { renderSlots } from './helps/renderSlots' 3 | export { createTextVnode, createElementVNode } from './vnode' 4 | export { getCurrentInstance, registerRuntimeCompiler } from './component' 5 | export { provide, inject } from './apiInject' 6 | export { createRenderer } from './renderer' 7 | export { nextTick } from './scheduler' 8 | export { Text, Fragment } from './vnode' 9 | export { toDisplayString } from '@vue3-mini/shared' 10 | export * from '@vue3-mini/reactivity' 11 | -------------------------------------------------------------------------------- /packages/vue/examples/componentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from '../../dist/vue3-mini.esm.js' 2 | 3 | export const Foo = { 4 | setup() { 5 | return {} 6 | }, 7 | 8 | render() { 9 | const foo = h('p', {}, 'foo') 10 | // 具名插槽 11 | // 1. 获取到渲染的元素 12 | // 2. 获取到渲染的位置 13 | // 作用域插槽 14 | const age = 18 15 | return h('div', {}, [ 16 | renderSlots(this.$slots, 'header', { 17 | age, 18 | }), 19 | foo, 20 | renderSlots(this.$slots, 'footer'), 21 | ]) 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /packages/vue/examples/patchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../dist/vue3-mini.esm.js' 2 | 3 | const prevChildren = 'oldChild' 4 | const nextChildren = 'newChild' 5 | 6 | export default { 7 | name: 'ArrayToText', 8 | 9 | setup() { 10 | const isChange = ref(false) 11 | window.isChange = isChange 12 | 13 | return { 14 | isChange, 15 | } 16 | }, 17 | 18 | render() { 19 | const self = this 20 | return self.isChange === true 21 | ? h('div', {}, nextChildren) 22 | : h('div', {}, prevChildren) 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /packages/vue/src/index.ts: -------------------------------------------------------------------------------- 1 | import { baseCompile } from '@vue3-mini/compiler-core' 2 | import * as runtimeDom from '@vue3-mini/runtime-dom' 3 | import { registerRuntimeCompiler } from '@vue3-mini/runtime-dom' 4 | // 出口文件 5 | export * from '@vue3-mini/runtime-dom' 6 | 7 | function compileToFunction(template) { 8 | const { code } = baseCompile(template) 9 | const render = new Function('Vue', code)(runtimeDom) 10 | console.log('生成render函数---------------------------------------', render) 11 | return render 12 | } 13 | 14 | registerRuntimeCompiler(compileToFunction) 15 | -------------------------------------------------------------------------------- /packages/vue/examples/componentEmit/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../dist/vue3-mini.esm.js' 2 | 3 | export const Foo = { 4 | setup(props, { emit }) { 5 | const emitAdd = () => { 6 | console.log('emitAdd') 7 | emit('add', 1, 2) 8 | emit('add-foo', 1, 2) 9 | } 10 | return { 11 | emitAdd, 12 | } 13 | }, 14 | 15 | render() { 16 | const btn = h('button', 17 | { 18 | onClick: this.emitAdd, 19 | }, 20 | 'emitAdd') 21 | const foo = h('p', {}, 'foo') 22 | return h('div', {}, [foo, btn]) 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /packages/runtime-core/src/createApp.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from './vnode' 2 | 3 | export function createAppAPI(render) { 4 | return function createApp(rootComponent) { 5 | // rootComponent根组件 6 | return { 7 | mount(rootContainer) { 8 | // component -> vnode 9 | // 所有的逻辑操作 都会基于vnode 10 | // 创建根组件vnode 11 | console.log('基于根组件创建 vnode') 12 | const vnode = createVNode(rootComponent) 13 | // 渲染 14 | console.log('调用 render,基于 vnode 进行开箱') 15 | render(vnode, rootContainer) 16 | }, 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/compiler-core/src/compile.ts: -------------------------------------------------------------------------------- 1 | import { generate } from './codegen' 2 | import { baseParse } from './parse' 3 | import { transform } from './transform' 4 | import { transformElement } from './transforms/transformElement' 5 | import { transformExpression } from './transforms/transformExpression' 6 | import { transformText } from './transforms/transformText' 7 | 8 | export function baseCompile(template) { 9 | const ast = baseParse(template) 10 | transform(ast, { 11 | nodeTransforms: [transformExpression, transformElement, transformText], 12 | }) 13 | return generate(ast) 14 | } 15 | -------------------------------------------------------------------------------- /packages/vue/examples/compiler-base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/vue/examples/customRenderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /packages/vue/examples/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../dist/vue3-mini.esm.js' 2 | import ArrayToArray from './ArrayToArray.js' 3 | 4 | export const App = { 5 | name: 'App', 6 | 7 | setup() { 8 | return {} 9 | }, 10 | 11 | render() { 12 | return h('div', { tId: 1 }, [ 13 | h('p', {}, '主页'), 14 | // 老的是array, 新的是text 15 | // h(ArrayToText), 16 | // 老的是text, 新的是text 17 | // h(TextToText), 18 | // 老的是text, 新的是array 19 | // h(TextToArray), 20 | // 老的是array, 新的是array 21 | h(ArrayToArray), 22 | ]) 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /packages/vue/examples/patchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../dist/vue3-mini.esm.js' 2 | 3 | const nextChildren = 'newChildren' 4 | const prevChildren = [h('div', {}, 'A'), h('div', {}, 'B')] 5 | 6 | export default { 7 | name: 'ArrayToText', 8 | 9 | setup() { 10 | const isChange = ref(false) 11 | window.isChange = isChange 12 | 13 | return { 14 | isChange, 15 | } 16 | }, 17 | 18 | render() { 19 | const self = this 20 | return self.isChange === true 21 | ? h('div', {}, nextChildren) 22 | : h('div', {}, prevChildren) 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /packages/vue/examples/patchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../dist/vue3-mini.esm.js' 2 | 3 | const prevChildren = 'newChildren' 4 | const nextChildren = [h('div', {}, 'A'), h('div', {}, 'B')] 5 | 6 | export default { 7 | name: 'ArrayToText', 8 | 9 | setup() { 10 | const isChange = ref(false) 11 | window.isChange = isChange 12 | 13 | return { 14 | isChange, 15 | } 16 | }, 17 | 18 | render() { 19 | const self = this 20 | return self.isChange === true 21 | ? h('div', {}, nextChildren) 22 | : h('div', {}, prevChildren) 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transforms/transformElement.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes, createVNodeCall } from '../ast' 2 | 3 | export function transformElement(node, context) { 4 | if (node.type === NodeTypes.ELEMENT) { 5 | return () => { 6 | // 中间处理层 7 | // tag 8 | const vnodeTag = `'${node.tag}'` 9 | // props 10 | let vnodeProps 11 | const children = node.children 12 | const vnodeChildren = children[0] 13 | 14 | node.codegenNode = createVNodeCall( 15 | context, 16 | vnodeTag, 17 | vnodeProps, 18 | vnodeChildren, 19 | ) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/vue/examples/componentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { createTextVnode, h } from '../../dist/vue3-mini.esm.js' 2 | import { Foo } from './Foo.js' 3 | 4 | export const App = { 5 | name: 'App', 6 | render() { 7 | const app = h('div', {}, 'App') 8 | const foo = h( 9 | Foo, 10 | {}, 11 | { 12 | header: ({ age }) => [ 13 | h('p', {}, `header${age}`), 14 | createTextVnode('你好'), 15 | ], 16 | footer: () => h('p', {}, 'footer'), 17 | }, 18 | ) 19 | 20 | return h('div', {}, [app, foo]) 21 | }, 22 | 23 | setup() { 24 | return {} 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /packages/compiler-core/__test__/__snapshots__/codegen.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`codegen > element 1`] = ` 4 | "const { toDisplayString:_toDisplayString, createElementVNode:_createElementVNode } = Vue 5 | return function render(_ctx, _cache){return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))}" 6 | `; 7 | 8 | exports[`codegen > interpolation 1`] = ` 9 | "const { toDisplayString:_toDisplayString } = Vue 10 | return function render(_ctx, _cache){return _toDisplayString(_ctx.message)}" 11 | `; 12 | 13 | exports[`codegen > string 1`] = ` 14 | " 15 | return function render(_ctx, _cache){return 'hi'}" 16 | `; 17 | -------------------------------------------------------------------------------- /packages/reactivity/__test__/shallowReadonly.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | import { isReadonly, shallowReadonly } from '../src/index' 3 | 4 | describe('shallowReadonly', () => { 5 | it('should not make non-reactive properties reactive', () => { 6 | const props = shallowReadonly({ n: { foo: 1 } }) 7 | expect(isReadonly(props)).toBe(true) 8 | expect(isReadonly(props.n)).toBe(false) 9 | }) 10 | it('warn when call set', () => { 11 | console.warn = vi.fn() 12 | const user = shallowReadonly({ 13 | age: 10, 14 | }) 15 | user.age = 11 16 | expect(console.warn).toBeCalledTimes(1) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { camelize, toHandlerKey } from '@vue3-mini/shared' 2 | 3 | /** 4 | * description 5 | * emit方法 6 | * 7 | * @param instance 当前实例 8 | * @param event 事件名 9 | * @param args 传递的参数 10 | * @return void 11 | * 12 | */ 13 | export function emit(instance, event, ...args) { 14 | // 如果要调用emit 则props里需要有onXXX这个Prop 15 | // 找到instance.props -> 寻找event 16 | const { props } = instance 17 | 18 | // 格式化event名称 将add-two / addTwo转化成 onAddTwo 19 | const handlerName = toHandlerKey(camelize(event)) 20 | // 在Props里寻找转化后的propName 21 | const handler = props[handlerName] 22 | // 如果寻找到了就去执行 23 | handler && handler(...args) 24 | } 25 | -------------------------------------------------------------------------------- /packages/runtime-core/src/apiWatch.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from './../../reactivity/src/effect' 2 | import { queuePreFlushCb } from './scheduler' 3 | 4 | export function watchEffect(source) { 5 | function job() { 6 | effect.run() 7 | } 8 | 9 | let cleanup 10 | 11 | const onCleanup = (fn) => { 12 | cleanup = effect.onStop = () => { 13 | fn() 14 | } 15 | } 16 | function getter() { 17 | // 初始化的时候不调用 18 | if (cleanup) 19 | cleanup() 20 | 21 | source(onCleanup) 22 | } 23 | 24 | const effect = new ReactiveEffect(getter, () => { 25 | queuePreFlushCb(job) 26 | }) 27 | 28 | effect.run() 29 | 30 | return () => { 31 | effect.stop() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/compiler-core/__test__/transform.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { NodeTypes } from '../src/ast' 3 | import { baseParse } from '../src/parse' 4 | import { transform } from '../src/transform' 5 | 6 | describe('transform', () => { 7 | it('happy path', () => { 8 | const ast = baseParse('
hi,{{message}}
') 9 | 10 | const plugin = (node) => { 11 | if (node.type === NodeTypes.TEXT) 12 | node.content = `${node.content} mini-vue` 13 | } 14 | 15 | transform(ast, { 16 | nodeTransforms: [plugin], 17 | }) 18 | 19 | const nodeText = ast.children[0].children[0] 20 | expect(nodeText.content).toBe('hi, mini-vue') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/vue/examples/helloWorld/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../dist/vue3-mini.esm.js' 2 | import { Foo } from './Foo.js' 3 | 4 | window.self = null 5 | export const App = { 6 | render() { 7 | window.self = this 8 | return h( 9 | 'div', 10 | { 11 | id: 'root', 12 | class: ['red', 'hard'], 13 | 14 | onClick() { 15 | console.log('click') 16 | }, 17 | onMouseDown() { 18 | console.log('down') 19 | }, 20 | }, 21 | [h('div', {}, `hi${this.msg}`), h(Foo, { count: 1 })], 22 | // 'hello' + this.msg 23 | // [ 24 | // h('p', { class: 'red' }, 'HELLO'), 25 | // h('p', { class: 'blue' }, 'WORLD') 26 | // ] 27 | ) 28 | }, 29 | 30 | setup() { 31 | // composition api 32 | return { 33 | msg: 'hello world', 34 | } 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /packages/vue/examples/nextTicker/App.js: -------------------------------------------------------------------------------- 1 | import { 2 | getCurrentInstance, 3 | h, 4 | nextTick, 5 | ref, 6 | } from '../../dist/vue3-mini.esm.js' 7 | 8 | export const App = { 9 | name: 'App', 10 | setup() { 11 | const count = ref(1) 12 | const instance = getCurrentInstance() 13 | 14 | function onClick() { 15 | for (let i = 0; i < 100; i++) { 16 | console.log('update') 17 | count.value = i 18 | } 19 | 20 | // 拿不到视图的变更 21 | console.log(instance) 22 | nextTick(() => { 23 | console.log(instance) 24 | }) 25 | } 26 | 27 | return { 28 | onClick, 29 | count, 30 | } 31 | }, 32 | render() { 33 | const button = h('button', { onClick: this.onClick }, 'update') 34 | const p = h('p', {}, `count:${this.count}`) 35 | 36 | return h('div', {}, [button, p]) 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /packages/reactivity/src/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from './effect' 2 | 3 | class ComputedRefImpl { 4 | private _getter: any 5 | private _dirty = true // 初始化dirty时为true 6 | private _value: any 7 | private _effect: any 8 | constructor(getter) { 9 | this._getter = getter 10 | // 当依赖的响应式对象的值发生改变的时候 会通过scheduler 将dirty设置成true 11 | this._effect = new ReactiveEffect(getter, () => { 12 | if (!this._dirty) 13 | this._dirty = true 14 | }) 15 | } 16 | 17 | get value() { 18 | // 如果dirty为true 则说明依赖发生过改变 或初始化状态 19 | // 这个时候需要重新执行effect.run() 获取最新的值 20 | if (this._dirty) { 21 | this._dirty = false 22 | // 第一次run的时候会触发响应式对象 get -> 收集依赖 23 | this._value = this._effect.run() 24 | } 25 | return this._value 26 | } 27 | } 28 | 29 | export function computed(getter) { 30 | return new ComputedRefImpl(getter) 31 | } 32 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentsSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from '@vue3-mini/shared' 2 | export function initSlots(instance, children) { 3 | // 初始化slots 4 | const { vnode } = instance 5 | console.log('初始化 slots') 6 | // 判断当前节点类型是否为slotChildren 7 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) 8 | normalizeObjectSlots(children, instance.slots) 9 | } 10 | 11 | function normalizeSlotValue(value) { 12 | // 把 function 返回的值转换成 array ,这样 slot 就可以支持多个元素了 13 | return Array.isArray(value) ? value : [value] 14 | } 15 | 16 | function normalizeObjectSlots(children, slots) { 17 | for (const key in children) { 18 | const value = children[key] 19 | // 父组件是 header: (age) => h('div', {}, age) 传参 20 | // value需要执行下value(props)才能得到返回结果 21 | // 在renderSlots时需要用函数调用的方式获取prop 所以slots[key]也是一个函数的形式 22 | slots[key] = props => normalizeSlotValue(value(props)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from '@vue3-mini/shared' 2 | 3 | const publicPropertiesMap = { 4 | $el: i => i.vnode.el, // 当前组件vnode实例 5 | $slots: i => i.slots, // 当前组件slots 6 | $props: i => i.props, 7 | } 8 | 9 | export const PublicInstanceProxyHandlers = { 10 | get({ _: instance }, key) { 11 | const { setupState, props } = instance 12 | console.log(`触发 proxy hook , key -> : ${key}`) 13 | 14 | // 通过this可以获取setup返回值或prop 15 | if (key[0] !== '$') { 16 | // 说明不是访问 public api 17 | // 先检测访问的 key 是否存在于 setupState 中, 是的话直接返回 18 | if (hasOwn(setupState, key)) 19 | return setupState[key] 20 | else if (hasOwn(props, key)) 21 | return props[key] 22 | } 23 | const publicGetter = publicPropertiesMap[key] 24 | // 其他代理对象 $el等 25 | if (publicGetter) 26 | return publicGetter(instance) 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /packages/vue/examples/customRenderer/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { createRenderer } from '../../dist/vue3-mini.esm.js' 3 | import { App } from './App.js' 4 | 5 | console.log(PIXI) 6 | const game = new PIXI.Application({ 7 | width: 500, 8 | height: 500, 9 | }) 10 | 11 | document.body.append(game.view) 12 | 13 | const renderer = createRenderer({ 14 | createElement(type) { 15 | if (type === 'rect') { 16 | const rect = new PIXI.Graphics() 17 | rect.beginFill(0xFF0000) 18 | rect.drawRect(0, 0, 100, 100) 19 | rect.endFill() 20 | return rect 21 | } 22 | }, 23 | patchProp(el, key, val) { 24 | el[key] = val 25 | }, 26 | 27 | insert(el, parent) { 28 | parent.addChild(el) 29 | }, 30 | }) 31 | 32 | renderer.createApp(App).mount(game.stage) 33 | 34 | // const rootContainer = document.querySelector('#app') 35 | // createApp(App).mount(rootContainer) 36 | -------------------------------------------------------------------------------- /packages/reactivity/__test__/reactive.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { isProxy, isReactive, reactive } from '../src/index' 3 | 4 | describe('reactive', () => { 5 | it('happy path', () => { 6 | const original = { 7 | foo: 1, 8 | } 9 | const observed = reactive(original) 10 | expect(observed).not.toBe(original) 11 | expect(observed.foo).toBe(1) 12 | expect(isReactive(observed)).toBe(true) 13 | expect(isReactive(original)).toBe(false) 14 | expect(isProxy(observed)).toBe(true) 15 | }) 16 | 17 | it('nested reactive', () => { 18 | const original = { 19 | nested: { 20 | foo: 1, 21 | }, 22 | array: [{ bar: 2 }], 23 | } 24 | 25 | const observed = reactive(original) 26 | expect(isReactive(observed.nested)).toBe(true) 27 | expect(isReactive(observed.array)).toBe(true) 28 | expect(isReactive(observed.array[0])).toBe(true) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /packages/reactivity/__test__/readonly.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | import { isProxy, isReadonly, readonly } from '../src/index' 3 | 4 | describe('readonly', () => { 5 | it('should make nested values readonly', () => { 6 | const original = { 7 | foo: 1, 8 | bar: { 9 | baz: 2, 10 | }, 11 | } 12 | const wrapped = readonly(original) 13 | expect(wrapped).not.toBe(original) 14 | expect(isReadonly(wrapped)).toBe(true) 15 | expect(isReadonly(original)).toBe(false) 16 | expect(isReadonly(wrapped.bar)).toBe(true) 17 | expect(isReadonly(original.bar)).toBe(false) 18 | expect(isProxy(wrapped)).toBe(true) 19 | expect(wrapped.foo).toBe(1) 20 | }) 21 | 22 | it('warn when call set', () => { 23 | console.warn = vi.fn() 24 | const user = readonly({ 25 | age: 10, 26 | }) 27 | user.age = 11 28 | expect(console.warn).toBeCalledTimes(1) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-proxy", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "cuiyiming1998", 6 | "license": "ISC", 7 | "homepage": "https://github.com/cuiyiming1998/Vue3-mini/#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/cuiyiming1998/Vue3-mini.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/cuiyiming1998/Vue3-mini/issues" 14 | }, 15 | "keywords": [], 16 | "main": "lib/vue3-mini.cjs.js", 17 | "module": "lib/vue3-mini.esm.js", 18 | "scripts": { 19 | "test": "vitest", 20 | "build": "rollup -c rollup.config.js", 21 | "lint": "eslint .", 22 | "lint:fix": "eslint . --fix" 23 | }, 24 | "devDependencies": { 25 | "@antfu/eslint-config": "^0.36.0", 26 | "@rollup/plugin-typescript": "^8.4.0", 27 | "eslint": "^8.36.0", 28 | "rollup": "^2.79.0", 29 | "tslib": "^2.4.0", 30 | "typescript": "^4.7.3", 31 | "vitest": "^0.22.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './toDisplayString' 2 | export * from './shapeFlags' 3 | 4 | export const extend = Object.assign 5 | export const EMPTY_OBJ = {} 6 | 7 | export const isObject = (val) => { 8 | return val !== null && typeof val === 'object' 9 | } 10 | 11 | export const hasChanged = (value, newValue) => { 12 | return !Object.is(value, newValue) 13 | } 14 | 15 | export const hasOwn = (val, key) => { 16 | return Object.prototype.hasOwnProperty.call(val, key) 17 | } 18 | 19 | // 横杠转驼峰add-foo -> addFoo 20 | export const camelize = (str: string) => { 21 | return str.replace(/-(\w)/g, (_, c: string) => { 22 | return c ? c.toUpperCase() : '' 23 | }) 24 | } 25 | 26 | // 首字母大写 27 | export const capitalize = (str: string) => { 28 | return str.charAt(0).toUpperCase() + str.slice(1) 29 | } 30 | 31 | // 驼峰加on 32 | export const toHandlerKey = (str: string) => { 33 | return str ? `on${capitalize(str)}` : '' 34 | } 35 | 36 | export const isString = (val) => { 37 | return typeof val === 'string' 38 | } 39 | -------------------------------------------------------------------------------- /packages/vue/examples/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../dist/vue3-mini.esm.js' 2 | import Child from './Child.js' 3 | 4 | export const App = { 5 | name: 'App', 6 | setup() { 7 | const msg = ref('123') 8 | const count = ref(1) 9 | 10 | window.msg = msg 11 | 12 | const changeChildProps = () => { 13 | msg.value = '456' 14 | } 15 | 16 | const changeCount = () => { 17 | count.value++ 18 | } 19 | 20 | return { msg, changeChildProps, changeCount, count } 21 | }, 22 | 23 | render() { 24 | return h('div', {}, [ 25 | h('div', {}, '你好'), 26 | h( 27 | 'button', 28 | { 29 | onClick: this.changeChildProps, 30 | }, 31 | 'change child props', 32 | ), 33 | h(Child, { 34 | msg: this.msg, 35 | }), 36 | h( 37 | 'button', 38 | { 39 | onClick: this.changeCount, 40 | }, 41 | 'change self count', 42 | ), 43 | h('p', {}, `count: ${this.count}`), 44 | ]) 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /packages/reactivity/src/reactive.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '@vue3-mini/shared' 2 | import { mutableHandlers, readonlyHandlers, shallowReadonlyHandlers } from './baseHandlers' 3 | 4 | export const enum ReactiveFlags { 5 | IS_REACTIVE = '__v_isReactive', 6 | IS_READONLY = '__v_isReadonly', 7 | } 8 | 9 | export function reactive(raw) { 10 | return createActiveObject(raw, mutableHandlers) 11 | } 12 | 13 | export function isReactive(value) { 14 | return !!value[ReactiveFlags.IS_REACTIVE] 15 | } 16 | 17 | export function readonly(raw) { 18 | return createActiveObject(raw, readonlyHandlers) 19 | } 20 | 21 | export function shallowReadonly(raw) { 22 | return createActiveObject(raw, shallowReadonlyHandlers) 23 | } 24 | 25 | export function isReadonly(value) { 26 | return !!value[ReactiveFlags.IS_READONLY] 27 | } 28 | 29 | export function isProxy(value) { 30 | return isReactive(value) || isReadonly(value) 31 | } 32 | 33 | export function createActiveObject(target: any, baseHandlers) { 34 | if (!isObject(target)) { 35 | console.warn(`target ${target} 必须是一个对象`) 36 | return 37 | } 38 | return new Proxy(target, baseHandlers) 39 | } 40 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transforms/transformText.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from '../ast' 2 | import { isText } from '../utils' 3 | 4 | export function transformText(node) { 5 | let currentContainer 6 | if (node.type === NodeTypes.ELEMENT) { 7 | return () => { 8 | const { children } = node 9 | for (let i = 0; i < children.length; i++) { 10 | const child = children[i] 11 | if (isText(child)) { 12 | for (let j = i + 1; j < children.length; j++) { 13 | const next = children[j] 14 | if (isText(next)) { 15 | if (!currentContainer) { 16 | currentContainer = children[i] = { 17 | type: NodeTypes.COMPOUND_EXPRESSION, 18 | children: [child], 19 | } 20 | } 21 | currentContainer.children.push(' + ') 22 | currentContainer.children.push(next) 23 | children.splice(j, 1) 24 | j-- 25 | } 26 | else { 27 | currentContainer = undefined 28 | break 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/runtime-core/src/scheduler.ts: -------------------------------------------------------------------------------- 1 | const queue: any[] = [] 2 | const activePrefFlushCbs: any[] = [] 3 | 4 | const p = Promise.resolve() 5 | let isFlushPending = false 6 | 7 | export function nextTick(fn?) { 8 | return fn ? p.then(fn) : p 9 | } 10 | 11 | export function queueJobs(job) { 12 | if (!queue.includes(job)) 13 | queue.push(job) 14 | 15 | // 执行所有的job 16 | queueFlush() 17 | } 18 | 19 | function queueFlush() { 20 | // 如果同时触发了两个组件的更新的话 21 | // 这里就会触发两次 then (微任务逻辑) 22 | // 但是着是没有必要的 23 | // 我们只需要触发一次即可处理完所有的 job 调用 24 | // 所以需要判断一下 如果已经触发过 nextTick 了 25 | // 那么后面就不需要再次触发一次 nextTick 逻辑了 26 | if (isFlushPending) 27 | return 28 | 29 | isFlushPending = true 30 | // 创建微任务 31 | nextTick(flushJobs) 32 | } 33 | 34 | export function queuePreFlushCb(job) { 35 | activePrefFlushCbs.push(job) 36 | queueFlush() 37 | } 38 | 39 | function flushJobs() { 40 | // isFlushPending 置为false 重新记录job 41 | isFlushPending = false 42 | 43 | flushPreFlushCbs() 44 | 45 | let job 46 | 47 | while ((job = queue.shift())) 48 | job && job() 49 | } 50 | function flushPreFlushCbs() { 51 | for (let i = 0; i < activePrefFlushCbs.length; i++) 52 | activePrefFlushCbs[i]() 53 | } 54 | -------------------------------------------------------------------------------- /packages/compiler-core/__test__/codegen.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { generate } from '../src/codegen' 3 | import { baseParse } from '../src/parse' 4 | import { transform } from '../src/transform' 5 | import { transformElement } from '../src/transforms/transformElement' 6 | import { transformExpression } from '../src/transforms/transformExpression' 7 | import { transformText } from '../src/transforms/transformText' 8 | 9 | describe('codegen', () => { 10 | it('string', () => { 11 | const ast = baseParse('hi') 12 | transform(ast) 13 | const { code } = generate(ast) 14 | expect(code).toMatchSnapshot() 15 | }) 16 | 17 | it('interpolation', () => { 18 | const ast = baseParse('{{message}}') 19 | transform(ast, { 20 | nodeTransforms: [transformExpression], 21 | }) 22 | const { code } = generate(ast) 23 | expect(code).toMatchSnapshot() 24 | }) 25 | 26 | it('element', () => { 27 | const ast = baseParse('
hi,{{message}}
') 28 | transform(ast, { 29 | nodeTransforms: [transformExpression, transformElement, transformText], 30 | }) 31 | const { code } = generate(ast) 32 | expect(code).toMatchSnapshot() 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentUpdateUtils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUpdateComponent(prevVNode, nextVNode) { 2 | const { props: prevProps } = prevVNode 3 | const { props: nextProps } = nextVNode 4 | // const emits = component!.emitsOptions; 5 | 6 | // 这里主要是检测组件的 props 7 | // 核心:只要是 props 发生改变了,那么这个 component 就需要更新 8 | 9 | // 1. props 没有变化,那么不需要更新 10 | if (prevProps === nextProps) 11 | return false 12 | 13 | // 如果之前没有 props,那么就需要看看现在有没有 props 了 14 | // 所以这里基于 nextProps 的值来决定是否更新 15 | if (!prevProps) 16 | return !!nextProps 17 | 18 | // 之前有值,现在没值,那么肯定需要更新 19 | if (!nextProps) 20 | return true 21 | 22 | // 以上都是比较明显的可以知道 props 是否是变化的 23 | // 在 hasPropsChanged 会做更细致的对比检测 24 | return hasPropsChanged(prevProps, nextProps) 25 | } 26 | 27 | function hasPropsChanged(prevProps, nextProps): boolean { 28 | // 依次对比每一个 props.key 29 | 30 | // 提前对比一下 length ,length 不一致肯定是需要更新的 31 | const nextKeys = Object.keys(nextProps) 32 | if (nextKeys.length !== Object.keys(prevProps).length) 33 | return true 34 | 35 | // 只要现在的 prop 和之前的 prop 不一样那么就需要更新 36 | for (let i = 0; i < nextKeys.length; i++) { 37 | const key = nextKeys[i] 38 | if (nextProps[key] !== prevProps[key]) 39 | return true 40 | } 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /packages/reactivity/__test__/computed.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | import { computed, reactive } from '../src/index' 3 | 4 | describe('computed', () => { 5 | it('happy path', () => { 6 | const user = reactive({ 7 | age: 1, 8 | }) 9 | 10 | const age = computed(() => { 11 | return user.age 12 | }) 13 | 14 | expect(age.value).toBe(1) 15 | }) 16 | 17 | it('should compute lazily', () => { 18 | const value = reactive({ 19 | foo: 1, 20 | }) 21 | const getter = vi.fn(() => { 22 | return value.foo 23 | }) 24 | const cValue = computed(getter) 25 | 26 | // lazy 27 | expect(getter).not.toHaveBeenCalled() 28 | expect(cValue.value).toBe(1) 29 | expect(getter).toHaveBeenCalledTimes(1) 30 | 31 | // should not compute again 32 | cValue.value 33 | expect(getter).toHaveBeenCalledTimes(1) 34 | 35 | // should not compute until needed 36 | value.foo = 2 // 这里会调用trigger 37 | expect(getter).toHaveBeenCalledTimes(1) 38 | 39 | // now it should compute 40 | expect(cValue.value).toBe(2) 41 | expect(getter).toHaveBeenCalledTimes(2) 42 | 43 | // should not compute again 44 | cValue.value 45 | expect(getter).toHaveBeenCalledTimes(2) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /packages/vue/examples/apiInject/App.js: -------------------------------------------------------------------------------- 1 | import { h, inject, provide } from '../../dist/vue3-mini.esm.js' 2 | 3 | const Provider = { 4 | name: 'Provider', 5 | setup() { 6 | provide('foo', 'fooVal') 7 | provide('bar', 'barVal') 8 | }, 9 | render() { 10 | return h('div', {}, [h('p', {}, 'Provider'), h(ProviderTwo)]) 11 | }, 12 | } 13 | 14 | const ProviderTwo = { 15 | name: 'Provider', 16 | setup() { 17 | provide('foo', 'fooTwo') 18 | const foo = inject('foo') 19 | return { foo } 20 | }, 21 | render() { 22 | return h('div', {}, [h('p', {}, `ProviderTwo foo: ${this.foo}`), h(Consumer)]) 23 | }, 24 | } 25 | 26 | const Consumer = { 27 | name: 'Consumer', 28 | setup() { 29 | const foo = inject('foo') 30 | const bar = inject('bar') 31 | const baz = inject('baz', () => 'bazDefault') 32 | return { 33 | foo, 34 | bar, 35 | baz, 36 | } 37 | }, 38 | 39 | template: '
Consumer: xxx
', 40 | 41 | render() { 42 | return h('div', {}, `Consumer: - ${this.foo} - ${this.bar} - ${this.baz}`) 43 | }, 44 | } 45 | 46 | export const App = { 47 | name: 'App', 48 | render() { 49 | return h('div', {}, [h('p', {}, 'apiInject'), h(Provider)]) 50 | }, 51 | 52 | setup() { 53 | return {} 54 | }, 55 | } 56 | -------------------------------------------------------------------------------- /packages/vue/examples/update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../dist/vue3-mini.esm.js' 2 | 3 | export const App = { 4 | name: 'App', 5 | 6 | setup() { 7 | const count = ref(0) 8 | 9 | const onClick = () => { 10 | count.value++ 11 | } 12 | 13 | const props = ref({ 14 | foo: 'foo', 15 | bar: 'bar', 16 | }) 17 | 18 | const onChangePropsDemo1 = () => { 19 | props.value.foo = 'new-foo' 20 | } 21 | 22 | const onChangePropsDemo2 = () => { 23 | props.value.foo = undefined 24 | } 25 | 26 | const onChangePropsDemo3 = () => { 27 | props.value = { 28 | foo: 'foo', 29 | } 30 | } 31 | 32 | return { 33 | count, 34 | onClick, 35 | onChangePropsDemo1, 36 | onChangePropsDemo2, 37 | onChangePropsDemo3, 38 | props, 39 | } 40 | }, 41 | 42 | render() { 43 | return h('div', { id: 'root', ...this.props }, [ 44 | h('button', { onClick: this.onClick }, 'click'), 45 | h('button', { onClick: this.onChangePropsDemo1 }, 'changeProps - 值改变了 - 修改'), 46 | h('button', { onClick: this.onChangePropsDemo2 }, 'changeProps - 值变成了undefined - 删除'), 47 | h('button', { onClick: this.onChangePropsDemo3 }, 'changeProps - key在新的里面没有了 - 删除'), 48 | ]) 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Vue3-mini 3 | 4 | 手写一遍Vue3! 5 | 6 | #### runtime-core 7 | 8 | - [x] 支持组件类型 9 | - [x] 支持 element 类型 10 | - [x] 初始化 props 11 | - [x] setup 可获取 props 和 context 12 | - [x] 支持 component emit 13 | - [x] 支持 proxy 14 | - [x] 可以在 render 函数中获取 setup 返回的对象 15 | - [x] nextTick 的实现 16 | - [x] 支持 getCurrentInstance 17 | - [x] 支持 provide/inject 18 | - [x] 支持最基础的 slots 19 | - [x] 支持 Text 类型节点 20 | - [x] 支持 $el api 21 | - [x] 支持 watchEffect 22 | 23 | 24 | #### reactivity 25 | 26 | 目标是用自己的 reactivity 支持现有的 demo 运行 27 | 28 | - [x] reactive 的实现 29 | - [x] ref 的实现 30 | - [x] readonly 的实现 31 | - [x] computed 的实现 32 | - [x] track 依赖收集 33 | - [x] trigger 触发依赖 34 | - [x] 支持 isReactive 35 | - [x] 支持嵌套 reactive 36 | - [x] 支持 toRaw 37 | - [x] 支持 effect.scheduler 38 | - [x] 支持 effect.stop 39 | - [x] 支持 isReadonly 40 | - [x] 支持 isProxy 41 | - [x] 支持 shallowReadonly 42 | - [x] 支持 proxyRefs 43 | 44 | ### compiler-core 45 | - [x] 解析插值 46 | - [x] 解析 element 47 | - [x] 解析 text 48 | 49 | ### runtime-dom 50 | - [x] 支持 custom renderer 51 | 52 | ### runtime-test 53 | - [x] 支持测试 runtime-core 的逻辑 54 | 55 | ### infrastructure 56 | - [x] support monorepo with pnpm 57 | ### build 58 | 59 | ```shell 60 | pnpm build 61 | ``` 62 | 63 | ### example 64 | 65 | 通过 server 的方式打开 packages/vue/example/\* 下的 index.html 即可 66 | 67 | >  推荐使用 [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) 68 | -------------------------------------------------------------------------------- /packages/runtime-core/__tests__/apiWatch.test.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from '@vue3-mini/reactivity' 2 | import { describe, expect, it, vi } from 'vitest' 3 | import { nextTick } from '../src/scheduler' 4 | import { watchEffect } from '../src/apiWatch' 5 | 6 | describe('api: watch', () => { 7 | it('effect', async () => { 8 | const state = reactive({ count: 0 }) 9 | let dummy 10 | watchEffect(() => { 11 | dummy = state.count 12 | }) 13 | expect(dummy).toBe(0) 14 | 15 | state.count++ 16 | await nextTick() 17 | expect(dummy).toBe(1) 18 | }) 19 | 20 | it('stopping the watcher (effect)', async () => { 21 | const state = reactive({ count: 0 }) 22 | let dummy 23 | const stop: any = watchEffect(() => { 24 | dummy = state.count 25 | }) 26 | expect(dummy).toBe(0) 27 | 28 | stop() 29 | state.count++ 30 | await nextTick() 31 | // should not update 32 | expect(dummy).toBe(0) 33 | }) 34 | 35 | it('cleanup registration (effect)', async () => { 36 | const state = reactive({ count: 0 }) 37 | const cleanup = vi.fn() 38 | let dummy 39 | const stop: any = watchEffect((onCleanup) => { 40 | onCleanup(cleanup) 41 | dummy = state.count 42 | }) 43 | expect(dummy).toBe(0) 44 | 45 | state.count++ 46 | await nextTick() 47 | expect(cleanup).toHaveBeenCalledTimes(1) 48 | expect(dummy).toBe(1) 49 | 50 | stop() 51 | expect(cleanup).toHaveBeenCalledTimes(2) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /packages/runtime-core/src/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from '@vue3-mini/shared' 2 | 3 | export const Fragment = Symbol('Fragment') 4 | export const Text = Symbol('Text') 5 | 6 | export { 7 | createVNode as createElementVNode, 8 | } 9 | /* 10 | type -> 'div' / 'span' 11 | props -> attribute 12 | children 13 | */ 14 | export function createVNode(type, props?, children?) { 15 | const vnode = { 16 | type, // 组件类型 17 | props, // props 18 | component: null, 19 | children, // 孩子节点, 可以是text(文本节点) 或者 array(嵌套子节点) 20 | shapeFlag: getShapeFlag(type), // 类型标识 21 | key: props && props.key, 22 | el: null, // 当前实例 23 | } 24 | if (typeof children === 'string') 25 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN 26 | else if (Array.isArray(children)) 27 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN 28 | 29 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 30 | if (typeof children === 'object') { 31 | // 如果是组件类型 且children为object 32 | // 则当前节点属于slotChildren 33 | // 暂时只有 element 类型和 component 类型的组件 34 | // 所以这里除了 element ,那么只要是 component 的话,那么children 肯定就是 slots 了 35 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN 36 | } 37 | } 38 | return vnode 39 | } 40 | 41 | export function createTextVnode(text: string) { 42 | return createVNode(Text, {}, text) 43 | } 44 | 45 | function getShapeFlag(type) { 46 | // 如果类型是string则为element类型 47 | // 否则为component类型 48 | return typeof type === 'string' 49 | ? ShapeFlags.ELEMENT 50 | : ShapeFlags.STATEFUL_COMPONENT 51 | } 52 | -------------------------------------------------------------------------------- /packages/runtime-core/src/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from './component' 2 | 3 | export function provide(key, value) { 4 | // 存 5 | // 获取当前组件实例 6 | const currentInstance: any = getCurrentInstance() 7 | 8 | if (currentInstance) { 9 | let { provides } = currentInstance 10 | 11 | // 当父级 key 和 爷爷级别的 key 重复的时候,对于子组件来讲,需要取最近的父级别组件的值 12 | // 那这里的解决方案就是利用原型链来解决 13 | // provides 初始化的时候是在 createComponent 时处理的,当时是直接把 parent.provides 赋值给组件的 provides 的 14 | // 所以,如果说这里发现 provides 和 parentProvides 相等的话,那么就说明是第一次做 provide(对于当前组件来讲) 15 | // 我们就可以把 parent.provides 作为 currentInstance.provides 的原型重新赋值 16 | // 至于为什么不在 createComponent 的时候做这个处理,可能的好处是在这里初始化的话,是有个懒执行的效果(优化点,只有需要的时候在初始化) 17 | 18 | // 更改prototype 使provide能够嵌套使用 19 | // 如果parent没有 则向上寻找 20 | const parentProvides = currentInstance.parent.provides 21 | if (provides === parentProvides) { 22 | // 如果第一次 则通过继承父provides进行初始化 23 | provides = currentInstance.provides = Object.create(parentProvides) 24 | } 25 | provides[key] = value 26 | } 27 | } 28 | 29 | export function inject(key, defaultValue) { 30 | // 取 31 | const currentInstance: any = getCurrentInstance() 32 | if (currentInstance) { 33 | // 因为provides是层层继承的 所以只需找到parent的provide 即为所有的provides 34 | const parentProvides = currentInstance.parent.provides 35 | if (key in parentProvides) { 36 | return parentProvides[key] 37 | } 38 | else if (defaultValue) { 39 | // 如果没有找到 且有default 则赋值 40 | if (typeof defaultValue === 'function') 41 | return defaultValue() 42 | 43 | return defaultValue 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/runtime-core/src/helps/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { Fragment, createVNode } from '../vnode' 2 | 3 | /** 4 | * Compiler runtime helper for rendering `` 5 | * 用来 render slot 的 6 | * 之前是把 slot 的数据都存在 instance.slots 内(可以看 componentSlot.ts), 7 | * 这里就是取数据然后渲染出来的点 8 | * 这个是由 compiler 模块直接渲染出来的 -可以参看这个 demo https://vue-next-template-explorer.netlify.app/#%7B%22src%22%3A%22%3Cdiv%3E%5Cn%20%20%3Cslot%3E%3C%2Fslot%3E%5Cn%3C%2Fdiv%3E%22%2C%22ssr%22%3Afalse%2C%22options%22%3A%7B%22mode%22%3A%22module%22%2C%22prefixIdentifiers%22%3Afalse%2C%22optimizeImports%22%3Afalse%2C%22hoistStatic%22%3Afalse%2C%22cacheHandlers%22%3Afalse%2C%22scopeId%22%3Anull%2C%22inline%22%3Afalse%2C%22ssrCssVars%22%3A%22%7B%20color%20%7D%22%2C%22bindingMetadata%22%3A%7B%22TestComponent%22%3A%22setup-const%22%2C%22setupRef%22%3A%22setup-ref%22%2C%22setupConst%22%3A%22setup-const%22%2C%22setupLet%22%3A%22setup-let%22%2C%22setupMaybeRef%22%3A%22setup-maybe-ref%22%2C%22setupProp%22%3A%22props%22%2C%22vMySetupDir%22%3A%22setup-const%22%7D%2C%22optimizeBindings%22%3Afalse%7D%7D 9 | * 其最终目的就是在 render 函数中调用 renderSlot 取 instance.slots 内的数据 10 | * TODO 这里应该是一个返回一个 block ,但是暂时还没有支持 block ,所以这个暂时只需要返回一个 vnode 即可 11 | * 因为 block 的本质就是返回一个 vnode 12 | * 13 | * @private 14 | */ 15 | export function renderSlots(slots, name, props) { 16 | // 寻找slots[name] 17 | // 如果存在 则使用Fragment创建虚拟节点 传入Props 18 | const slot = slots[name] 19 | if (slot) { 20 | // 因为 slot 是一个返回 vnode 的函数,我们只需要把这个结果返回出去即可 21 | // slot 就是一个函数,所以就可以把当前组件的一些数据给传出去,这个就是作用域插槽 22 | // 参数就是 props 23 | if (typeof slot === 'function') 24 | return createVNode(Fragment, {}, slot(props)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/runtime-dom/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from '@vue3-mini/runtime-core' 2 | 3 | function createElement(type) { 4 | // 创建element 5 | return document.createElement(type) 6 | } 7 | 8 | function patchProp(el, key, prevVal, nextVal) { 9 | // 处理prop 10 | // 如果是on开头的 则添加eventListener 11 | 12 | console.log(`PatchProp 设置属性:${key} 值:${nextVal}`) 13 | console.log(`key: ${key} 之前的值是:${prevVal}`) 14 | 15 | const isOn = (key: string) => /^on[A-Z]/.test(key) 16 | if (isOn(key)) { 17 | const event = key.slice(2).toLocaleLowerCase() 18 | el.addEventListener(event, nextVal) 19 | } 20 | else { 21 | // 其余的是attribute 22 | if (undefined === nextVal || nextVal === null) { 23 | // 如果prop删除了(赋值undefined或null) 则removeAttr 24 | el.removeAttribute(key, nextVal) 25 | } 26 | else { 27 | el.setAttribute(key, nextVal) 28 | } 29 | } 30 | } 31 | 32 | function insert(child, parent, anchor) { 33 | // 添加到DOM的方法 34 | parent.insertBefore(child, anchor || null) 35 | } 36 | 37 | function remove(child) { 38 | // 删除的方法 39 | // 寻找parentNode 40 | // 通过parentNode.removeChild删除 41 | const parent = child.parentNode 42 | if (parent) 43 | parent.removeChild(child) 44 | } 45 | 46 | function setElementText(el, text) { 47 | // 设置text节点 48 | el.textContent = text 49 | } 50 | 51 | function createText(text) { 52 | return document.createTextNode(text) 53 | } 54 | 55 | function setText(node, text) { 56 | node.nodeValue = text 57 | } 58 | 59 | // 默认HTML DOM中的render 60 | const renderer: any = createRenderer({ 61 | createElement, 62 | patchProp, 63 | insert, 64 | remove, 65 | setElementText, 66 | createText, 67 | setText, 68 | }) 69 | 70 | export function createApp(...args) { 71 | // main.js使用的createApp在这里 72 | return renderer.createApp(...args) 73 | } 74 | 75 | export * from '@vue3-mini/runtime-core' 76 | -------------------------------------------------------------------------------- /packages/reactivity/src/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged, isObject } from '@vue3-mini/shared' 2 | import { isTracking, reactive, trackEffects, triggerEffects } from './index' 3 | 4 | class RefImpl { 5 | private _value: any 6 | public dep 7 | public _rawValue 8 | public __v_ifRef = true 9 | constructor(value) { 10 | this._rawValue = value 11 | // 如果value是对象 --> 转成reactive 12 | this._value = convert(value) 13 | this.dep = new Set() 14 | } 15 | 16 | get value() { 17 | // 这时被effect包裹时 访问.value 会触发track收集依赖 18 | trackRefValue(this) 19 | return this._value 20 | } 21 | 22 | set value(newValue) { 23 | // 如果ref是对象 则_value为proxy 需要用一个raw值来代替判断 24 | if (hasChanged(newValue, this._rawValue)) { 25 | // 先修改value的值 26 | this._rawValue = newValue 27 | this._value = convert(newValue) 28 | // 执行trigger更新 29 | triggerEffects(this.dep) 30 | } 31 | } 32 | } 33 | 34 | function convert(value) { 35 | return isObject(value) ? reactive(value) : value 36 | } 37 | 38 | function trackRefValue(ref) { 39 | if (isTracking()) 40 | trackEffects(ref.dep) 41 | } 42 | 43 | export function ref(value) { 44 | return new RefImpl(value) 45 | } 46 | 47 | export function isRef(ref) { 48 | return !!ref?.__v_ifRef 49 | } 50 | 51 | export function unRef(ref) { 52 | // 判断是否是ref 如果是返回ref.value 否则直接返回值 53 | return isRef(ref) ? ref.value : ref 54 | } 55 | 56 | export function proxyRefs(objectWithRefs) { 57 | return new Proxy(objectWithRefs, { 58 | get(target, key) { 59 | // get -> 如果是ref 返回.value 60 | return unRef(Reflect.get(target, key)) 61 | }, 62 | 63 | set(target, key, value) { 64 | // 如果原来的值是ref 且新值不是ref 则替换原来的值的.value 65 | if (isRef(target[key]) && !isRef(value)) 66 | return target[key].value = value 67 | else 68 | return Reflect.set(target, key, value) 69 | }, 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transform.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from './ast' 2 | import { TO_DISPLAY_STRING } from './runtimeHelpers' 3 | 4 | export function transform(root, options = {}) { 5 | // 1. 遍历 6 | // 深度优先搜索ast树 7 | const context = createTransformContext(root, options) 8 | traverseNode(root, context) 9 | // 2. 修改 10 | // root.codegenNode 11 | createRootCodegen(root) 12 | 13 | root.helpers = [...context.helpers.keys()] 14 | } 15 | 16 | function traverseNode(node: any, context) { 17 | // 由外部传入的transforms来驱动 18 | const nodeTransforms = context.nodeTransforms 19 | const exitFns: any = [] 20 | for (let i = 0; i < nodeTransforms.length; i++) { 21 | const transform = nodeTransforms[i] 22 | const onExit = transform(node, context) 23 | if (onExit) 24 | exitFns.push(onExit) 25 | } 26 | 27 | switch (node.type) { 28 | case NodeTypes.INTERPOLATION: 29 | context.helper(TO_DISPLAY_STRING) 30 | break 31 | case NodeTypes.ROOT: 32 | case NodeTypes.ELEMENT: 33 | traverseChildren(node, context) 34 | break 35 | default: 36 | break 37 | } 38 | 39 | let i = exitFns.length 40 | while (i--) 41 | exitFns[i]() 42 | } 43 | function traverseChildren(node: any, context: any) { 44 | const children = node.children 45 | 46 | for (let i = 0; i < children.length; i++) { 47 | const node = children[i] 48 | traverseNode(node, context) 49 | } 50 | } 51 | 52 | function createTransformContext(root: any, options: any) { 53 | const context = { 54 | root, 55 | nodeTransforms: options.nodeTransforms || [], 56 | helpers: new Map(), 57 | helper(key) { 58 | context.helpers.set(key, 1) 59 | }, 60 | } 61 | 62 | return context 63 | } 64 | function createRootCodegen(root: any) { 65 | const child = root.children[0] 66 | if (child.type === NodeTypes.ELEMENT) 67 | root.codegenNode = child.codegenNode 68 | else 69 | root.codegenNode = root.children[0] 70 | } 71 | -------------------------------------------------------------------------------- /packages/reactivity/__test__/ref.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { effect, isRef, proxyRefs, reactive, ref, unRef } from '../src/index' 3 | 4 | describe('ref', () => { 5 | it('happy path', () => { 6 | const a = ref(1) 7 | expect(a.value).toBe(1) 8 | }) 9 | 10 | it('should be reactive', () => { 11 | const a = ref(1) 12 | let dummy 13 | let calls = 0 14 | effect(() => { 15 | calls++ 16 | dummy = a.value 17 | }) 18 | expect(calls).toBe(1) 19 | expect(dummy).toBe(1) 20 | a.value = 2 21 | expect(calls).toBe(2) 22 | expect(dummy).toBe(2) 23 | a.value = 2 24 | expect(calls).toBe(2) 25 | expect(dummy).toBe(2) 26 | }) 27 | 28 | it('should make nested properties reactive', () => { 29 | const a = ref({ 30 | count: 1, 31 | }) 32 | let dummy 33 | effect(() => { 34 | dummy = a.value.count 35 | }) 36 | expect(dummy).toBe(1) 37 | a.value.count = 2 38 | expect(dummy).toBe(2) 39 | }) 40 | 41 | it('isRef', () => { 42 | const a = ref(1) 43 | const user = reactive({ 44 | age: 1, 45 | }) 46 | expect(isRef(a)).toBe(true) 47 | expect(isRef(1)).toBe(false) 48 | expect(isRef(user)).toBe(false) 49 | }) 50 | 51 | it('unRef', () => { 52 | const a = ref(1) 53 | const user = reactive({ 54 | age: 1, 55 | }) 56 | expect(unRef(a)).toBe(1) 57 | expect(unRef(1)).toBe(1) 58 | }) 59 | 60 | it('proxyRefs', () => { 61 | // 通常使用在template里 调用ref而可以不用.value 62 | const user = { 63 | age: ref(10), 64 | name: 'zhangsan', 65 | } 66 | const proxyUser = proxyRefs(user) 67 | expect(user.age.value).toBe(10) 68 | expect(proxyUser.age).toBe(10) 69 | expect(proxyUser.name).toBe('zhangsan') 70 | 71 | proxyUser.age = 20 72 | expect(proxyUser.age).toBe(20) 73 | expect(user.age.value).toBe(20) 74 | 75 | proxyUser.age = ref(10) 76 | expect(proxyUser.age).toBe(10) 77 | expect(user.age.value).toBe(10) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /packages/reactivity/src/baseHandlers.ts: -------------------------------------------------------------------------------- 1 | import { extend, isObject } from '@vue3-mini/shared' 2 | import { ReactiveFlags } from './reactive' 3 | import { reactive, readonly, track, trigger } from './index' 4 | 5 | const get = createGetter() 6 | const readonlyGet = createGetter(true) // readonly的get 7 | const set = createSetter() 8 | const shallowReadonlyGet = createGetter(true, true) // shallowReadonly的get 9 | 10 | /** 11 | * description 12 | * 创建getter 13 | * 14 | * @param isReadonly 是否readonly 15 | * @param isShallow 是否shallow 16 | * @return 17 | * 18 | */ 19 | function createGetter(isReadonly = false, isShallow = false) { 20 | return function get(target, key) { 21 | if (ReactiveFlags.IS_REACTIVE === key) { 22 | // 如果调用isReactive 返回!isReadonly 23 | return !isReadonly 24 | } 25 | else if (ReactiveFlags.IS_READONLY === key) { 26 | // 如果调用isReadonly 返回isReadonly 27 | return isReadonly 28 | } 29 | const res = Reflect.get(target, key) 30 | 31 | // 如果是shallow 则直接返回res 32 | if (isShallow) 33 | return res 34 | 35 | // 如果res是对象 36 | // 则继续对res进行readonly或reactive操作 37 | if (isObject(res)) 38 | return isReadonly ? readonly(res) : reactive(res) 39 | 40 | // 如果不是readonly 进行track 41 | if (!isReadonly) 42 | track(target, key) 43 | 44 | return res 45 | } 46 | } 47 | 48 | /** 49 | * @description: 创建setter 50 | * @param { Void } 51 | * @return { set } 52 | */ 53 | function createSetter() { 54 | return function set(target, key, value) { 55 | const res = Reflect.set(target, key, value) 56 | // 触发trigger 57 | trigger(target, key) 58 | return res 59 | } 60 | } 61 | 62 | export const mutableHandlers = { 63 | get, 64 | set, 65 | } 66 | 67 | export const readonlyHandlers = { 68 | get: readonlyGet, 69 | set(target, key, value) { 70 | console.warn(`key: ${key} set 失败, 因为target为 readonly, ${target}`) 71 | return true 72 | }, 73 | } 74 | 75 | // shallowReadonly继承readonlyHandlers 76 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 77 | get: shallowReadonlyGet, 78 | }) 79 | -------------------------------------------------------------------------------- /packages/reactivity/__test__/effect.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | import { effect, reactive, stop } from '../src/index' 3 | 4 | describe('effect', () => { 5 | it('happy path', () => { 6 | const user = reactive({ 7 | age: 10, 8 | }) 9 | let nextAge 10 | effect(() => { 11 | nextAge = user.age + 1 12 | }) 13 | expect(nextAge).toBe(11) 14 | 15 | // update 16 | user.age++ 17 | expect(nextAge).toBe(12) 18 | }) 19 | 20 | it('should return runner when call effect', () => { 21 | // runner 22 | let foo = 10 23 | const runner = effect(() => { 24 | foo++ 25 | return 'foo' 26 | }) 27 | expect(foo).toBe(11) 28 | const r = runner() 29 | expect(foo).toBe(12) 30 | expect(r).toBe('foo') 31 | }) 32 | 33 | it('scheduler', () => { 34 | // 通过effect的第二个参数 给定一个scheduler 35 | // 当effect第一次执行的时候, 执行fn 36 | // 当响应式对象更新的时候 不会执行fn, 而是执行scheduler 37 | // 当执行runner的时候 会再次执行fn 38 | let dummy 39 | let run: any 40 | const scheduler = vi.fn(() => { 41 | run = runner 42 | }) 43 | const obj = reactive({ foo: 1 }) 44 | const runner = effect( 45 | () => { 46 | dummy = obj.foo 47 | }, 48 | { scheduler }, 49 | ) 50 | expect(scheduler).not.toHaveBeenCalled() 51 | obj.foo++ 52 | expect(scheduler).toHaveBeenCalledTimes(1) 53 | expect(dummy).toBe(1) 54 | run() 55 | expect(dummy).toBe(2) 56 | }) 57 | 58 | it('stop', () => { 59 | let dummy 60 | const obj = reactive({ prop: 1 }) 61 | const runner = effect(() => { 62 | dummy = obj.prop 63 | }) 64 | obj.prop = 2 65 | expect(dummy).toBe(2) 66 | stop(runner) 67 | // obj.prop = 3 68 | obj.prop++ 69 | expect(dummy).toBe(2) 70 | 71 | // stopped effect should still be manually callable 72 | runner() 73 | expect(dummy).toBe(3) 74 | }) 75 | 76 | it('onStop', () => { 77 | const obj = reactive({ 78 | foo: 1, 79 | }) 80 | const onStop = vi.fn() 81 | let dummy 82 | const runner = effect( 83 | () => { 84 | dummy = obj.foo 85 | }, 86 | { 87 | onStop, 88 | }, 89 | ) 90 | 91 | stop(runner) 92 | expect(onStop).toBeCalledTimes(1) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /packages/compiler-core/__test__/parse.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { NodeTypes } from '../src/ast' 3 | import { baseParse } from '../src/parse' 4 | 5 | describe('Parse', () => { 6 | describe('interpolation', () => { 7 | it('simple interpolation', () => { 8 | const ast = baseParse('{{ message }}') 9 | 10 | expect(ast.children[0]).toStrictEqual({ 11 | type: NodeTypes.INTERPOLATION, 12 | content: { 13 | type: NodeTypes.SIMPLE_EXPRESSION, 14 | content: 'message', 15 | }, 16 | }) 17 | }) 18 | }) 19 | 20 | describe('element', () => { 21 | it('simple element div', () => { 22 | const ast = baseParse('
') 23 | 24 | expect(ast.children[0]).toStrictEqual({ 25 | type: NodeTypes.ELEMENT, 26 | tag: 'div', 27 | children: [], 28 | }) 29 | }) 30 | }) 31 | 32 | describe('text', () => { 33 | it('simple test', () => { 34 | const ast = baseParse('some text') 35 | 36 | expect(ast.children[0]).toStrictEqual({ 37 | type: NodeTypes.TEXT, 38 | content: 'some text', 39 | }) 40 | }) 41 | }) 42 | 43 | it('hello world', () => { 44 | const ast = baseParse('
hi,{{message}}
') 45 | 46 | expect(ast.children[0]).toStrictEqual({ 47 | type: NodeTypes.ELEMENT, 48 | tag: 'div', 49 | children: [ 50 | { 51 | type: NodeTypes.TEXT, 52 | content: 'hi,', 53 | }, 54 | { 55 | type: NodeTypes.INTERPOLATION, 56 | content: { 57 | type: NodeTypes.SIMPLE_EXPRESSION, 58 | content: 'message', 59 | }, 60 | }, 61 | ], 62 | }) 63 | }) 64 | 65 | it('Nested ', () => { 66 | const ast = baseParse('

hi

{{message}}
') 67 | 68 | expect(ast.children[0]).toStrictEqual({ 69 | type: NodeTypes.ELEMENT, 70 | tag: 'div', 71 | children: [ 72 | { 73 | type: NodeTypes.ELEMENT, 74 | tag: 'p', 75 | children: [ 76 | { 77 | type: NodeTypes.TEXT, 78 | content: 'hi', 79 | }, 80 | ], 81 | }, 82 | { 83 | type: NodeTypes.INTERPOLATION, 84 | content: { 85 | type: NodeTypes.SIMPLE_EXPRESSION, 86 | content: 'message', 87 | }, 88 | }, 89 | ], 90 | }) 91 | }) 92 | 93 | it('should throw error when lack end tag', () => { 94 | expect(() => { 95 | baseParse('
') 96 | console.log(222222222222222) 97 | }).toThrow() 98 | }) 99 | }) 100 | -------------------------------------------------------------------------------- /packages/compiler-core/src/codegen.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '@vue3-mini/shared' 2 | import { NodeTypes } from './ast' 3 | import { 4 | CREATE_ELEMENT_VNODE, 5 | TO_DISPLAY_STRING, 6 | helperMapName, 7 | } from './runtimeHelpers' 8 | 9 | export function generate(ast) { 10 | const context = createCodegenContext() 11 | const { push } = context 12 | 13 | genFunctionPreamble(ast, context) 14 | 15 | const functionName = 'render' 16 | const args = ['_ctx', '_cache'] 17 | const signature = args.join(', ') 18 | 19 | push(`function ${functionName}(${signature}){`) 20 | 21 | push('return ') 22 | genNode(ast.codegenNode, context) 23 | push('}') 24 | 25 | return { code: context.code } 26 | } 27 | 28 | function genFunctionPreamble(ast, context: any) { 29 | const { push } = context 30 | 31 | const VueBinging = 'Vue' 32 | 33 | const aliasHelper = s => `${helperMapName[s]}:_${helperMapName[s]}` 34 | if (ast.helpers.length > 0) 35 | push(`const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinging}`) 36 | 37 | push('\n') 38 | 39 | push('return ') 40 | } 41 | 42 | function genNode(node: any, context: any) { 43 | switch (node.type) { 44 | case NodeTypes.TEXT: 45 | genText(node, context) 46 | break 47 | case NodeTypes.INTERPOLATION: 48 | genInterpolation(node, context) 49 | break 50 | case NodeTypes.SIMPLE_EXPRESSION: 51 | genExpression(node, context) 52 | break 53 | case NodeTypes.ELEMENT: 54 | genElement(node, context) 55 | break 56 | case NodeTypes.COMPOUND_EXPRESSION: 57 | genCompoundExpression(node, context) 58 | break 59 | default: 60 | break 61 | } 62 | } 63 | 64 | function genElement(node, context) { 65 | const { push, helper } = context 66 | const { tag, children, props } = node 67 | 68 | push(`${helper(CREATE_ELEMENT_VNODE)}(`) 69 | 70 | // genNode(children, context) 71 | 72 | genNodeList(genNullable([tag, props, children]), context) 73 | 74 | push(')') 75 | } 76 | 77 | function genNodeList(nodes: any, context) { 78 | const { push } = context 79 | for (let i = 0; i < nodes.length; i++) { 80 | const node = nodes[i] 81 | if (isString(node)) 82 | push(node) 83 | else 84 | genNode(node, context) 85 | 86 | if (i < nodes.length - 1) 87 | push(', ') 88 | } 89 | } 90 | 91 | function genNullable(args: any) { 92 | return args.map(arg => arg || 'null') 93 | } 94 | 95 | function genText(node, context) { 96 | const { push } = context 97 | push(`'${node.content}'`) 98 | } 99 | 100 | function createCodegenContext() { 101 | const context = { 102 | code: '', 103 | push(source) { 104 | context.code += source 105 | }, 106 | helper(key) { 107 | return `_${helperMapName[key]}` 108 | }, 109 | } 110 | 111 | return context 112 | } 113 | function genInterpolation(node: any, context: any) { 114 | const { push, helper } = context 115 | 116 | push(`${helper(TO_DISPLAY_STRING)}(`) 117 | genNode(node.content, context) 118 | push(')') 119 | } 120 | 121 | function genExpression(node: any, context: any) { 122 | const { push } = context 123 | 124 | push(`${node.content}`) 125 | } 126 | function genCompoundExpression(node: any, context: any) { 127 | const { push } = context 128 | const children = node.children 129 | for (let i = 0; i < children.length; i++) { 130 | const child = children[i] 131 | if (isString(child)) 132 | push(child) 133 | else 134 | genNode(child, context) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /packages/runtime-core/src/component.ts: -------------------------------------------------------------------------------- 1 | import { proxyRefs, shallowReadonly } from '@vue3-mini/reactivity' 2 | import { emit } from './componentEmit' 3 | import { initProps } from './componentProps' 4 | import { PublicInstanceProxyHandlers } from './componentPublicInstance' 5 | import { initSlots } from './componentsSlots' 6 | 7 | export function createComponentInstance(vnode, parent) { 8 | // 创建组件实例 9 | const component = { 10 | vnode, // 当前vnode 11 | type: vnode.type, // vnode的type 12 | next: null, // 需要更新的 vnode,用于更新 component 类型的组件 13 | setupState: {}, // setup的返回值 14 | props: {}, // 当前组件props 15 | slots: {}, // 当前组件的插槽 16 | provides: parent ? parent.provides : {}, // 获取 parent 的 provides 作为当前组件的初始化值 这样就可以继承 parent.provides 的属性了 17 | parent, // 父组件 18 | isMounted: false, // 是否被mount过, 在初始化后会被修改成true 19 | subTree: {}, // 组件的虚拟节点树 20 | emit: () => {}, // emit方法 21 | } 22 | 23 | // 赋值 emit 24 | // 这里使用 bind 把 instance 和 component 进行绑定 25 | // 后面用户使用的时候只需要给 event 和参数即可 26 | component.emit = emit.bind(null, component) as any 27 | return component 28 | } 29 | 30 | export function setupComponent(instance) { 31 | // 初始化props 32 | // 将instance.vnode.props赋值到instance上 33 | initProps(instance, instance.vnode.props) 34 | // 初始化slots 35 | initSlots(instance, instance.vnode.children) 36 | 37 | // 初始化有状态的component 38 | setupStatefulComponent(instance) 39 | } 40 | 41 | function setupStatefulComponent(instance: any) { 42 | const Component = instance.type 43 | 44 | // 创建代理对象 -> 通过this访问props/$el/$slots等 45 | // 1. 创建代理对象 46 | console.log('创建 proxy') 47 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers) 48 | 49 | // 2. 调用setup 50 | // 调用 setup 的时候传入 props 51 | const { setup } = Component 52 | if (setup) { 53 | // 赋值currentInstance 54 | // 必须要在调用 setup 之前 55 | setCurrentInstance(instance) 56 | // 调用setup 获取setup的返回值 57 | // 第一个参数为shallowReadonly属性的props 58 | // 第二个参数为 { emit } 59 | const setupResult = setup(shallowReadonly(instance.props), { 60 | emit: instance.emit, 61 | }) 62 | setCurrentInstance(null) 63 | 64 | handleSetupResult(instance, setupResult) 65 | } 66 | } 67 | 68 | function handleSetupResult(instance: any, setupResult: any) { 69 | // 处理setup的返回值 70 | // TODO 后续实现function 71 | if (typeof setupResult === 'object') { 72 | // setup的返回值用proxyRef包裹 73 | // proxyRefs 的作用就是把 setupResult 对象做一层代理 74 | // 方便用户直接访问 ref 类型的值 75 | // 比如 setupResult 里面有个 count 是个 ref 类型的对象,用户使用的时候就可以直接使用 count 了,而不需要在 count.value 76 | // 这里也就是官网里面说到的自动结构 Ref 类型 77 | instance.setupState = proxyRefs(setupResult) 78 | } 79 | 80 | // 完成组件setup 81 | finishComponentSetup(instance) 82 | } 83 | 84 | function finishComponentSetup(instance: any) { 85 | const Component = instance.type 86 | 87 | if (compiler && !Component.render) { 88 | if (Component.template) 89 | Component.render = compiler(Component.template) 90 | } 91 | // template 92 | if (!instance.render) { 93 | // render赋值 94 | instance.render = Component.render 95 | } 96 | } 97 | 98 | let currentInstance = null 99 | export function getCurrentInstance() { 100 | // 获取当前组件instance实例 101 | // 因为是在setup阶段赋值, 所以只能在setup阶段获取到currentInstance 102 | return currentInstance 103 | } 104 | 105 | export function setCurrentInstance(instance) { 106 | // 赋值currentInstance 107 | currentInstance = instance 108 | } 109 | 110 | let compiler 111 | 112 | export function registerRuntimeCompiler(_compiler) { 113 | compiler = _compiler 114 | } 115 | -------------------------------------------------------------------------------- /packages/reactivity/src/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '@vue3-mini/shared' 2 | 3 | let activeEffect // 代表当前的副作用对象 ReactiveEffect 4 | let shouldTrack = false // 代表当前是否需要 track 收集依赖 5 | const targetMap = new WeakMap() 6 | 7 | export class ReactiveEffect { 8 | private _fn: any 9 | public scheduler: Function | undefined 10 | public active = true 11 | public deps: any[] = [] 12 | public onStop?: () => void 13 | constructor(_fn, scheduler?) { 14 | console.log('创建 ReactiveEffect 对象') 15 | this._fn = _fn 16 | this.scheduler = scheduler 17 | } 18 | 19 | run() { 20 | // 将this赋值给activeEffect 当前正在执行的effect 21 | activeEffect = this 22 | if (!this.active) 23 | return this._fn() 24 | 25 | shouldTrack = true 26 | // shouldTrack为true 27 | // 执行fn收集依赖 28 | // 执行的时候给全局的 activeEffect 赋值 29 | // 利用全局属性来获取当前的 effect 30 | activeEffect = this 31 | const result = this._fn() 32 | // 收集完依赖将关闭shouldTrack 33 | shouldTrack = false 34 | 35 | return result 36 | } 37 | 38 | stop() { 39 | // 停止响应式 40 | if (this.active) { 41 | // 清空dep 42 | cleanupEffect(this) 43 | if (this.onStop) { 44 | // 执行生命周期函数 45 | this.onStop() 46 | } 47 | // 修改active状态为false 48 | this.active = false 49 | } 50 | } 51 | } 52 | 53 | function cleanupEffect(effect) { 54 | // 清除effect.deps 55 | // 找到所有依赖这个 effect 的响应式对象 56 | // 从这些响应式对象里面把 effect 给删除掉 57 | effect.deps.forEach((dep: any) => { 58 | dep.delete(effect) 59 | }) 60 | effect.deps.length = 0 61 | } 62 | 63 | export function track(target, key) { 64 | if (!isTracking()) 65 | return 66 | 67 | console.log(`触发 track -> target: ${target} key:${key}`) 68 | // 从targetMap中获取depsMap 69 | let depsMap = targetMap.get(target) 70 | if (!depsMap) 71 | targetMap.set(target, (depsMap = new Map())) 72 | 73 | let dep = depsMap.get(key) 74 | if (!dep) 75 | depsMap.set(key, (dep = new Set())) 76 | 77 | // 寻找到当前target, key的dep后添加effect 78 | trackEffects(dep) 79 | } 80 | 81 | export function trackEffects(dep) { 82 | if (dep.has(activeEffect)) 83 | return 84 | 85 | // dep添加effect 86 | // activeEffect的上层依赖中也添加dep 87 | dep.add(activeEffect) 88 | activeEffect.deps.push(dep) 89 | } 90 | /* 91 | * 没有被 effect 包裹时,由于没有副作用函数(即没有依赖,activeEffect === undefined),不应该收集依赖 92 | * 只有在 effect内部的情况下才会收集依赖 93 | * 某些特殊情况,即使包裹在 effect,也不应该收集依赖(即 shouldTrack === false)。如:组件生命周期执行、组件 setup 执行 94 | */ 95 | export function isTracking() { 96 | return shouldTrack && undefined !== activeEffect 97 | } 98 | 99 | export function trigger(target, key) { 100 | const depsMap = targetMap.get(target) 101 | const dep = depsMap.get(key) 102 | // 获取target key对应的dep 103 | triggerEffects(dep) 104 | } 105 | export function triggerEffects(dep) { 106 | // 循环dep 如果有scheduler 则执行 107 | // 否则run 108 | for (const effect of dep) { 109 | if (effect.scheduler) { 110 | // scheduler 可以让用户自己选择调用的时机 111 | // 这样就可以灵活的控制调用了 112 | // 在 runtime-core 中,就是使用了 scheduler 实现了在 next ticker 中调用的逻辑 113 | effect.scheduler() 114 | } 115 | else { 116 | effect.run() 117 | } 118 | } 119 | } 120 | export function effect(fn, options: any = {}) { 121 | const _effect = new ReactiveEffect(fn, options.scheduler) 122 | // 创建activeEffect 123 | extend(_effect, options) 124 | // 创建时执行一次 125 | _effect.run() 126 | 127 | // 创建返回值 effect会返回内部函数 128 | const runner: any = _effect.run.bind(_effect) 129 | runner.effect = _effect 130 | 131 | // 把 _effect.run 这个方法返回 132 | // 让用户可以自行选择调用的时机(调用 fn) 133 | return runner 134 | } 135 | 136 | export function stop(runner) { 137 | runner.effect.stop() 138 | } 139 | -------------------------------------------------------------------------------- /packages/compiler-core/src/parse.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from './ast' 2 | 3 | const enum TagType { 4 | Start, 5 | End, 6 | } 7 | 8 | export function baseParse(content: string) { 9 | const context = createParserContext(content) 10 | 11 | return createRoot(parseChildren(context, [])) 12 | } 13 | 14 | function parseChildren(context, ancestors) { 15 | const nodes: any = [] 16 | 17 | while (!isEnd(context, ancestors)) { 18 | let node 19 | const s = context.source 20 | if (s.startsWith('{{')) { 21 | // 插值类型 22 | node = parseInterpolation(context) 23 | } 24 | else if (s[0] === '<') { 25 | if (/[a-z]/i.test(s[1])) 26 | node = parseElement(context, ancestors) 27 | } 28 | // 如果node没有值 需要把它当成text来解析 29 | if (!node) 30 | node = parseText(context) 31 | 32 | nodes.push(node) 33 | } 34 | return nodes 35 | } 36 | 37 | function isEnd(context, ancestors) { 38 | const s = context.source 39 | if (s.startsWith('= 0; i--) { 41 | const tag = ancestors[i].tag 42 | if (startsWithEndTagOpen(s, tag)) 43 | return true 44 | } 45 | } 46 | return !s 47 | // 2. 当遇到结束标签的时候 48 | // 1. source有值的时候 49 | } 50 | 51 | function startsWithEndTagOpen(source, tag) { 52 | return ( 53 | source.startsWith(' index) 65 | endIndex = index 66 | } 67 | 68 | const content = parseTextData(context, endIndex) 69 | return { 70 | type: NodeTypes.TEXT, 71 | content, 72 | } 73 | } 74 | 75 | function parseTextData(context: any, length: number) { 76 | // 1. 获取content 77 | const content = context.source.slice(0, length) 78 | // 2. 推进 79 | advanceBy(context, length) 80 | return content 81 | } 82 | 83 | function parseElement(context: any, ancestors) { 84 | // 解析element 85 | // 这里删除了左侧的
86 | const element: any = parseTag(context, TagType.Start) 87 | // 收集tag名称 88 | ancestors.push(element) 89 | element.children = parseChildren(context, ancestors) 90 | // 再次调用删除
91 | // 弹出tag名称 92 | ancestors.pop() 93 | 94 | if (startsWithEndTagOpen(context.source, element.tag)) 95 | parseTag(context, TagType.End) 96 | else 97 | throw new Error(`缺少结束标签: ${element.tag}`) 98 | 99 | return element 100 | } 101 | 102 | function parseTag(context: any, type: TagType) { 103 | // 1. 解析tag 104 | // < 开头 a-z忽略大小写 105 | const match: any = /^<\/?([a-z]*)/i.exec(context.source) 106 | const tag = match[1] 107 | // 2. 删除处理完成的代码 108 | advanceBy(context, match[0].length) 109 | // 再删除剩下的 > 110 | advanceBy(context, 1) 111 | if (TagType.End === type) 112 | return 113 | 114 | return { 115 | type: NodeTypes.ELEMENT, 116 | tag, 117 | } 118 | } 119 | 120 | function parseInterpolation(context) { 121 | // 解析插值 122 | // {{ message }} 123 | const openDelimiter = '{{' 124 | const closeDelimiter = '}}' 125 | 126 | // 寻找后面的 }} 127 | const closeIndex = context.source.indexOf( 128 | closeDelimiter, 129 | closeDelimiter.length, 130 | ) 131 | // 推进指针 132 | // 也就是执行删除 133 | // 去除前面的 {{ 134 | advanceBy(context, openDelimiter.length) 135 | 136 | // 截取 137 | const rawContentLength = closeIndex - openDelimiter.length 138 | const rawContent = parseTextData(context, rawContentLength) 139 | const content = rawContent.trim() 140 | 141 | // 处理后需要删除掉 142 | advanceBy(context, closeDelimiter.length) 143 | return { 144 | type: NodeTypes.INTERPOLATION, 145 | content: { 146 | type: NodeTypes.SIMPLE_EXPRESSION, 147 | content, 148 | }, 149 | } 150 | } 151 | 152 | function advanceBy(context: any, length: number) { 153 | context.source = context.source.slice(length) 154 | } 155 | 156 | function createRoot(children) { 157 | return { 158 | children, 159 | type: NodeTypes.ROOT, 160 | } 161 | } 162 | 163 | function createParserContext(content: string): any { 164 | return { 165 | source: content, 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /packages/vue/examples/patchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | // 老的是 array 2 | // 新的是 array 3 | 4 | import { Text, h, ref } from '../../dist/vue3-mini.esm.js' 5 | 6 | // 1. 左侧的对比 7 | // (a b) c 8 | // (a b) d e 9 | // const prevChildren = [ 10 | // h("p", { key: "A" }, "A"), 11 | // h("p", { key: "B" }, "B"), 12 | // h("p", { key: "C" }, "C"), 13 | // ]; 14 | // const nextChildren = [ 15 | // h("p", { key: "A" }, "A"), 16 | // h("p", { key: "B" }, "B"), 17 | // h("p", { key: "D" }, "D"), 18 | // h("p", { key: "E" }, "E"), 19 | // ]; 20 | 21 | // 2. 右侧的对比 22 | // a (b c) 23 | // d e (b c) 24 | // const prevChildren = [ 25 | // h("p", { key: "A" }, "A"), 26 | // h("p", { key: "B" }, "B"), 27 | // h("p", { key: "C" }, "C"), 28 | // ]; 29 | // const nextChildren = [ 30 | // h("p", { key: "D" }, "D"), 31 | // h("p", { key: "E" }, "E"), 32 | // h("p", { key: "B" }, "B"), 33 | // h("p", { key: "C" }, "C"), 34 | // ]; 35 | 36 | // 3. 新的比老的长 37 | // 创建新的 38 | // 左侧 39 | // (a b) 40 | // (a b) c 41 | // i = 2, e1 = 1, e2 = 2 42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 43 | // const nextChildren = [ 44 | // h("p", { key: "A" }, "A"), 45 | // h("p", { key: "B" }, "B"), 46 | // h("p", { key: "C" }, "C"), 47 | // h("p", { key: "D" }, "D"), 48 | // ]; 49 | 50 | // 右侧 51 | // (a b) 52 | // c (a b) 53 | // i = 0, e1 = -1, e2 = 0 54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 55 | // const nextChildren = [ 56 | // h("p", { key: "D" }, "D"), 57 | // h("p", { key: "C" }, "C"), 58 | // h("p", { key: "A" }, "A"), 59 | // h("p", { key: "B" }, "B"), 60 | // ]; 61 | 62 | // 4. 老的比新的长 63 | // 删除老的 64 | // 左侧 65 | // (a b) c 66 | // (a b) 67 | // i = 2, e1 = 2, e2 = 1 68 | // const prevChildren = [ 69 | // h("p", { key: "A" }, "A"), 70 | // h("p", { key: "B" }, "B"), 71 | // h("p", { key: "C" }, "C"), 72 | // ]; 73 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 74 | 75 | // 右侧 76 | // a (b c) 77 | // (b c) 78 | // i = 0, e1 = 0, e2 = -1 79 | // const prevChildren = [ 80 | // h("p", { key: "A" }, "A"), 81 | // h("p", { key: "B" }, "B"), 82 | // h("p", { key: "C" }, "C"), 83 | // ]; 84 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 85 | 86 | // 5. 对比中间的部分 87 | // 删除老的 (在老的里面存在,新的里面不存在) 88 | // 5.1 89 | // a,b,(c,d),f,g 90 | // a,b,(e,c),f,g 91 | // D 节点在新的里面是没有的 - 需要删除掉 92 | // C 节点 props 也发生了变化 93 | 94 | // const prevChildren = [ 95 | // h("p", { key: "A" }, "A"), 96 | // h("p", { key: "B" }, "B"), 97 | // h("p", { key: "C", id: "c-prev" }, "C"), 98 | // h("p", { key: "D" }, "D"), 99 | // h("p", { key: "F" }, "F"), 100 | // h("p", { key: "G" }, "G"), 101 | // ]; 102 | 103 | // const nextChildren = [ 104 | // h("p", { key: "A" }, "A"), 105 | // h("p", { key: "B" }, "B"), 106 | // h("p", { key: "E" }, "E"), 107 | // h("p", { key: "C", id:"c-next" }, "C"), 108 | // h("p", { key: "F" }, "F"), 109 | // h("p", { key: "G" }, "G"), 110 | // ]; 111 | 112 | // 5.1.1 113 | // a,b,(c,e,d),f,g 114 | // a,b,(e,c),f,g 115 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑) 116 | // const prevChildren = [ 117 | // h("p", { key: "A" }, "A"), 118 | // h("p", { key: "B" }, "B"), 119 | // h("p", { key: "C", id: "c-prev" }, "C"), 120 | // h("p", { key: "E" }, "E"), 121 | // h("p", { key: "D" }, "D"), 122 | // h("p", { key: "F" }, "F"), 123 | // h("p", { key: "G" }, "G"), 124 | // ]; 125 | 126 | // const nextChildren = [ 127 | // h("p", { key: "A" }, "A"), 128 | // h("p", { key: "B" }, "B"), 129 | // h("p", { key: "E" }, "E"), 130 | // h("p", { key: "C", id:"c-next" }, "C"), 131 | // h("p", { key: "F" }, "F"), 132 | // h("p", { key: "G" }, "G"), 133 | // ]; 134 | 135 | // 2 移动 (节点存在于新的和老的里面,但是位置变了) 136 | 137 | // 2.1 138 | // a,b,(c,d,e),f,g 139 | // a,b,(e,c,d),f,g 140 | // 最长子序列: [1,2] 141 | 142 | // const prevChildren = [ 143 | // h("p", { key: "A" }, "A"), 144 | // h("p", { key: "B" }, "B"), 145 | // h("p", { key: "C" }, "C"), 146 | // h("p", { key: "D" }, "D"), 147 | // h("p", { key: "E" }, "E"), 148 | // h("p", { key: "F" }, "F"), 149 | // h("p", { key: "G" }, "G"), 150 | // ]; 151 | 152 | // const nextChildren = [ 153 | // h("p", { key: "A" }, "A"), 154 | // h("p", { key: "B" }, "B"), 155 | // h("p", { key: "E" }, "E"), 156 | // h("p", { key: "C" }, "C"), 157 | // h("p", { key: "D" }, "D"), 158 | // h("p", { key: "F" }, "F"), 159 | // h("p", { key: "G" }, "G"), 160 | // ]; 161 | 162 | // 3. 创建新的节点 163 | // a,b,(c,e),f,g 164 | // a,b,(e,c,d),f,g 165 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建 166 | // const prevChildren = [ 167 | // h("p", { key: "A" }, "A"), 168 | // h("p", { key: "B" }, "B"), 169 | // h("p", { key: "C" }, "C"), 170 | // h("p", { key: "E" }, "E"), 171 | // h("p", { key: "F" }, "F"), 172 | // h("p", { key: "G" }, "G"), 173 | // ]; 174 | 175 | // const nextChildren = [ 176 | // h("p", { key: "A" }, "A"), 177 | // h("p", { key: "B" }, "B"), 178 | // h("p", { key: "E" }, "E"), 179 | // h("p", { key: "C" }, "C"), 180 | // h("p", { key: "D" }, "D"), 181 | // h("p", { key: "F" }, "F"), 182 | // h("p", { key: "G" }, "G"), 183 | // ]; 184 | 185 | // 综合例子 186 | // a,b,(c,d,e,z),f,g 187 | // a,b,(d,c,y,e),f,g 188 | 189 | // const prevChildren = [ 190 | // h("p", { key: "A" }, "A"), 191 | // h("p", { key: "B" }, "B"), 192 | // h("p", { key: "C" }, "C"), 193 | // h("p", { key: "D" }, "D"), 194 | // h("p", { key: "E" }, "E"), 195 | // h("p", { key: "Z" }, "Z"), 196 | // h("p", { key: "F" }, "F"), 197 | // h("p", { key: "G" }, "G"), 198 | // ]; 199 | 200 | // const nextChildren = [ 201 | // h("p", { key: "A" }, "A"), 202 | // h("p", { key: "B" }, "B"), 203 | // h("p", { key: "D" }, "D"), 204 | // h("p", { key: "C" }, "C"), 205 | // h("p", { key: "Y" }, "Y"), 206 | // h("p", { key: "E" }, "E"), 207 | // h("p", { key: "F" }, "F"), 208 | // h("p", { key: "G" }, "G"), 209 | // ]; 210 | 211 | // fix c 节点应该是 move 而不是删除之后重新创建的 212 | const prevChildren = [ 213 | h(Text, { key: 'A' }, 'A'), 214 | h('p', {}, 'C'), 215 | h('p', { key: 'B' }, 'B'), 216 | h('p', { key: 'D' }, 'D'), 217 | ] 218 | 219 | const nextChildren = [ 220 | h(Text, { key: 'A' }, 'TEXT'), 221 | h('p', { key: 'B' }, 'B'), 222 | h('p', {}, 'C'), 223 | h('p', { key: 'D' }, 'D'), 224 | ] 225 | 226 | export default { 227 | name: 'ArrayToArray', 228 | setup() { 229 | const isChange = ref(false) 230 | window.isChange = isChange 231 | 232 | return { 233 | isChange, 234 | } 235 | }, 236 | render() { 237 | const self = this 238 | 239 | return self.isChange === true 240 | ? h('div', {}, nextChildren) 241 | : h('div', {}, prevChildren) 242 | }, 243 | } 244 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | "lib": ["DOM", "es6", "ES2016"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "esnext", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | "types": ["vitest/globals"], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true, /* Skip type checking all .d.ts files. */ 102 | "paths": { 103 | "@vue3-mini/*": [ 104 | "./packages/*/src" 105 | ] 106 | } 107 | }, 108 | "include": [ 109 | "packages/*/src", 110 | "packages/*/__test__" 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@esbuild/linux-loong64@0.14.54": 6 | version "0.14.54" 7 | resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" 8 | integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== 9 | 10 | "@rollup/plugin-typescript@^8.4.0": 11 | version "8.4.0" 12 | resolved "https://registry.npmmirror.com/@rollup/plugin-typescript/-/plugin-typescript-8.4.0.tgz#a8a384b6dbaab42b4cafb075278b15743c0f5ef8" 13 | integrity sha512-QssfoOP6V4/6skX12EfOW5UzJAv/c334F4OJWmQpe2kg3agEa0JwVCckwmfuvEgDixyX+XyxjFenH7M2rDKUyQ== 14 | dependencies: 15 | "@rollup/pluginutils" "^3.1.0" 16 | resolve "^1.17.0" 17 | 18 | "@rollup/pluginutils@^3.1.0": 19 | version "3.1.0" 20 | resolved "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" 21 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== 22 | dependencies: 23 | "@types/estree" "0.0.39" 24 | estree-walker "^1.0.1" 25 | picomatch "^2.2.2" 26 | 27 | "@types/chai-subset@^1.3.3": 28 | version "1.3.3" 29 | resolved "https://registry.npmmirror.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94" 30 | integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw== 31 | dependencies: 32 | "@types/chai" "*" 33 | 34 | "@types/chai@*": 35 | version "4.3.1" 36 | resolved "https://registry.npmmirror.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04" 37 | integrity sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ== 38 | 39 | "@types/chai@^4.3.3": 40 | version "4.3.3" 41 | resolved "https://registry.npmmirror.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07" 42 | integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g== 43 | 44 | "@types/estree@0.0.39": 45 | version "0.0.39" 46 | resolved "https://registry.npmmirror.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" 47 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== 48 | 49 | "@types/node@*": 50 | version "17.0.42" 51 | resolved "https://registry.npmmirror.com/@types/node/-/node-17.0.42.tgz#d7e8f22700efc94d125103075c074396b5f41f9b" 52 | integrity sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ== 53 | 54 | assertion-error@^1.1.0: 55 | version "1.1.0" 56 | resolved "https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 57 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 58 | 59 | chai@^4.3.6: 60 | version "4.3.6" 61 | resolved "https://registry.npmmirror.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" 62 | integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== 63 | dependencies: 64 | assertion-error "^1.1.0" 65 | check-error "^1.0.2" 66 | deep-eql "^3.0.1" 67 | get-func-name "^2.0.0" 68 | loupe "^2.3.1" 69 | pathval "^1.1.1" 70 | type-detect "^4.0.5" 71 | 72 | check-error@^1.0.2: 73 | version "1.0.2" 74 | resolved "https://registry.npmmirror.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 75 | integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== 76 | 77 | debug@^4.3.4: 78 | version "4.3.4" 79 | resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 80 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 81 | dependencies: 82 | ms "2.1.2" 83 | 84 | deep-eql@^3.0.1: 85 | version "3.0.1" 86 | resolved "https://registry.npmmirror.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 87 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== 88 | dependencies: 89 | type-detect "^4.0.0" 90 | 91 | esbuild-android-64@0.14.54: 92 | version "0.14.54" 93 | resolved "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" 94 | integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== 95 | 96 | esbuild-android-arm64@0.14.54: 97 | version "0.14.54" 98 | resolved "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" 99 | integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== 100 | 101 | esbuild-darwin-64@0.14.54: 102 | version "0.14.54" 103 | resolved "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25" 104 | integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== 105 | 106 | esbuild-darwin-arm64@0.14.54: 107 | version "0.14.54" 108 | resolved "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73" 109 | integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== 110 | 111 | esbuild-freebsd-64@0.14.54: 112 | version "0.14.54" 113 | resolved "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" 114 | integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== 115 | 116 | esbuild-freebsd-arm64@0.14.54: 117 | version "0.14.54" 118 | resolved "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" 119 | integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== 120 | 121 | esbuild-linux-32@0.14.54: 122 | version "0.14.54" 123 | resolved "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" 124 | integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== 125 | 126 | esbuild-linux-64@0.14.54: 127 | version "0.14.54" 128 | resolved "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652" 129 | integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== 130 | 131 | esbuild-linux-arm64@0.14.54: 132 | version "0.14.54" 133 | resolved "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" 134 | integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== 135 | 136 | esbuild-linux-arm@0.14.54: 137 | version "0.14.54" 138 | resolved "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" 139 | integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== 140 | 141 | esbuild-linux-mips64le@0.14.54: 142 | version "0.14.54" 143 | resolved "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" 144 | integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== 145 | 146 | esbuild-linux-ppc64le@0.14.54: 147 | version "0.14.54" 148 | resolved "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" 149 | integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== 150 | 151 | esbuild-linux-riscv64@0.14.54: 152 | version "0.14.54" 153 | resolved "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" 154 | integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== 155 | 156 | esbuild-linux-s390x@0.14.54: 157 | version "0.14.54" 158 | resolved "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" 159 | integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== 160 | 161 | esbuild-netbsd-64@0.14.54: 162 | version "0.14.54" 163 | resolved "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" 164 | integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== 165 | 166 | esbuild-openbsd-64@0.14.54: 167 | version "0.14.54" 168 | resolved "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" 169 | integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== 170 | 171 | esbuild-sunos-64@0.14.54: 172 | version "0.14.54" 173 | resolved "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" 174 | integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== 175 | 176 | esbuild-windows-32@0.14.54: 177 | version "0.14.54" 178 | resolved "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" 179 | integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== 180 | 181 | esbuild-windows-64@0.14.54: 182 | version "0.14.54" 183 | resolved "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" 184 | integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== 185 | 186 | esbuild-windows-arm64@0.14.54: 187 | version "0.14.54" 188 | resolved "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" 189 | integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== 190 | 191 | esbuild@^0.14.47: 192 | version "0.14.54" 193 | resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2" 194 | integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== 195 | optionalDependencies: 196 | "@esbuild/linux-loong64" "0.14.54" 197 | esbuild-android-64 "0.14.54" 198 | esbuild-android-arm64 "0.14.54" 199 | esbuild-darwin-64 "0.14.54" 200 | esbuild-darwin-arm64 "0.14.54" 201 | esbuild-freebsd-64 "0.14.54" 202 | esbuild-freebsd-arm64 "0.14.54" 203 | esbuild-linux-32 "0.14.54" 204 | esbuild-linux-64 "0.14.54" 205 | esbuild-linux-arm "0.14.54" 206 | esbuild-linux-arm64 "0.14.54" 207 | esbuild-linux-mips64le "0.14.54" 208 | esbuild-linux-ppc64le "0.14.54" 209 | esbuild-linux-riscv64 "0.14.54" 210 | esbuild-linux-s390x "0.14.54" 211 | esbuild-netbsd-64 "0.14.54" 212 | esbuild-openbsd-64 "0.14.54" 213 | esbuild-sunos-64 "0.14.54" 214 | esbuild-windows-32 "0.14.54" 215 | esbuild-windows-64 "0.14.54" 216 | esbuild-windows-arm64 "0.14.54" 217 | 218 | estree-walker@^1.0.1: 219 | version "1.0.1" 220 | resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" 221 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== 222 | 223 | fsevents@~2.3.2: 224 | version "2.3.2" 225 | resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 226 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 227 | 228 | function-bind@^1.1.1: 229 | version "1.1.1" 230 | resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 231 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 232 | 233 | get-func-name@^2.0.0: 234 | version "2.0.0" 235 | resolved "https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 236 | integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== 237 | 238 | has@^1.0.3: 239 | version "1.0.3" 240 | resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 241 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 242 | dependencies: 243 | function-bind "^1.1.1" 244 | 245 | is-core-module@^2.9.0: 246 | version "2.10.0" 247 | resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" 248 | integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== 249 | dependencies: 250 | has "^1.0.3" 251 | 252 | local-pkg@^0.4.2: 253 | version "0.4.2" 254 | resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.2.tgz#13107310b77e74a0e513147a131a2ba288176c2f" 255 | integrity sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg== 256 | 257 | loupe@^2.3.1: 258 | version "2.3.4" 259 | resolved "https://registry.npmmirror.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" 260 | integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== 261 | dependencies: 262 | get-func-name "^2.0.0" 263 | 264 | ms@2.1.2: 265 | version "2.1.2" 266 | resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 267 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 268 | 269 | nanoid@^3.3.4: 270 | version "3.3.4" 271 | resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" 272 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== 273 | 274 | path-parse@^1.0.7: 275 | version "1.0.7" 276 | resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 277 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 278 | 279 | pathval@^1.1.1: 280 | version "1.1.1" 281 | resolved "https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" 282 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== 283 | 284 | picocolors@^1.0.0: 285 | version "1.0.0" 286 | resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 287 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 288 | 289 | picomatch@^2.2.2: 290 | version "2.3.1" 291 | resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 292 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 293 | 294 | postcss@^8.4.16: 295 | version "8.4.16" 296 | resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" 297 | integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== 298 | dependencies: 299 | nanoid "^3.3.4" 300 | picocolors "^1.0.0" 301 | source-map-js "^1.0.2" 302 | 303 | resolve@^1.17.0, resolve@^1.22.1: 304 | version "1.22.1" 305 | resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" 306 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== 307 | dependencies: 308 | is-core-module "^2.9.0" 309 | path-parse "^1.0.7" 310 | supports-preserve-symlinks-flag "^1.0.0" 311 | 312 | "rollup@>=2.75.6 <2.77.0 || ~2.77.0": 313 | version "2.77.3" 314 | resolved "https://registry.npmmirror.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12" 315 | integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g== 316 | optionalDependencies: 317 | fsevents "~2.3.2" 318 | 319 | rollup@^2.79.0: 320 | version "2.79.0" 321 | resolved "https://registry.npmmirror.com/rollup/-/rollup-2.79.0.tgz#9177992c9f09eb58c5e56cbfa641607a12b57ce2" 322 | integrity sha512-x4KsrCgwQ7ZJPcFA/SUu6QVcYlO7uRLfLAy0DSA4NS2eG8japdbpM50ToH7z4iObodRYOJ0soneF0iaQRJ6zhA== 323 | optionalDependencies: 324 | fsevents "~2.3.2" 325 | 326 | source-map-js@^1.0.2: 327 | version "1.0.2" 328 | resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 329 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 330 | 331 | supports-preserve-symlinks-flag@^1.0.0: 332 | version "1.0.0" 333 | resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 334 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 335 | 336 | tinypool@^0.2.4: 337 | version "0.2.4" 338 | resolved "https://registry.npmmirror.com/tinypool/-/tinypool-0.2.4.tgz#4d2598c4689d1a2ce267ddf3360a9c6b3925a20c" 339 | integrity sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ== 340 | 341 | tinyspy@^1.0.2: 342 | version "1.0.2" 343 | resolved "https://registry.npmmirror.com/tinyspy/-/tinyspy-1.0.2.tgz#6da0b3918bfd56170fb3cd3a2b5ef832ee1dff0d" 344 | integrity sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q== 345 | 346 | tslib@^2.4.0: 347 | version "2.4.0" 348 | resolved "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" 349 | integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== 350 | 351 | type-detect@^4.0.0, type-detect@^4.0.5: 352 | version "4.0.8" 353 | resolved "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 354 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 355 | 356 | typescript@^4.7.3: 357 | version "4.7.3" 358 | resolved "https://registry.npmmirror.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" 359 | integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== 360 | 361 | "vite@^2.9.12 || ^3.0.0-0": 362 | version "3.0.9" 363 | resolved "https://registry.npmmirror.com/vite/-/vite-3.0.9.tgz#45fac22c2a5290a970f23d66c1aef56a04be8a30" 364 | integrity sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw== 365 | dependencies: 366 | esbuild "^0.14.47" 367 | postcss "^8.4.16" 368 | resolve "^1.22.1" 369 | rollup ">=2.75.6 <2.77.0 || ~2.77.0" 370 | optionalDependencies: 371 | fsevents "~2.3.2" 372 | 373 | vitest@^0.22.1: 374 | version "0.22.1" 375 | resolved "https://registry.npmmirror.com/vitest/-/vitest-0.22.1.tgz#3122e6024bf782ee9aca53034017af7adb009c32" 376 | integrity sha512-+x28YTnSLth4KbXg7MCzoDAzPJlJex7YgiZbUh6YLp0/4PqVZ7q7/zyfdL0OaPtKTpNiQFPpMC8Y2MSzk8F7dw== 377 | dependencies: 378 | "@types/chai" "^4.3.3" 379 | "@types/chai-subset" "^1.3.3" 380 | "@types/node" "*" 381 | chai "^4.3.6" 382 | debug "^4.3.4" 383 | local-pkg "^0.4.2" 384 | tinypool "^0.2.4" 385 | tinyspy "^1.0.2" 386 | vite "^2.9.12 || ^3.0.0-0" 387 | -------------------------------------------------------------------------------- /packages/runtime-core/src/renderer.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY_OBJ, ShapeFlags } from '@vue3-mini/shared' 2 | import { effect } from '@vue3-mini/reactivity' 3 | import { Fragment, Text } from './vnode' 4 | import { createComponentInstance, setupComponent } from './component' 5 | import { createAppAPI } from './createApp' 6 | import { shouldUpdateComponent } from './componentUpdateUtils' 7 | import { queueJobs } from './scheduler' 8 | 9 | export function createRenderer(options) { 10 | // 传入自定义渲染方法 11 | const { 12 | createElement: hostCreateElement, 13 | patchProp: hostPatchProp, 14 | insert: hostInsert, 15 | remove: hostRemove, 16 | setElementText: hostSetElementText, 17 | createText: hostCreateText, 18 | setText: hostSetText, 19 | } = options 20 | function render(vnode, container) { 21 | // 调用patch方法 22 | // 一开始没有n1新节点 传null 23 | patch(null, vnode, container, null, null) 24 | } 25 | 26 | // n1 老节点 27 | // n2 新节点 28 | function patch(n1, n2, container, parentComponent, anchor) { 29 | // 因为n2是新节点 30 | // 需要基于新节点的类型来判断 31 | const { type, shapeFlag } = n2 32 | // 其中还有几个类型比如: static fragment comment 33 | switch (type) { 34 | case Fragment: 35 | // Fragment -> 只渲染children 36 | processFragment(n1, n2, container, parentComponent, anchor) 37 | break 38 | case Text: 39 | // Text -> 渲染为textNode节点 40 | processText(n1, n2, container) 41 | break 42 | default: 43 | if (shapeFlag & ShapeFlags.ELEMENT) { 44 | // 处理element类型 45 | console.log('处理 Element 类型') 46 | processElement(n1, n2, container, parentComponent, anchor) 47 | } 48 | else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 49 | // 处理component类型 50 | console.log('处理 Component 类型') 51 | processComponent(n1, n2, container, parentComponent, anchor) 52 | } 53 | break 54 | } 55 | } 56 | 57 | function processText(n1, n2: any, container: any) { 58 | // 如果是text类型 则children为text: string 59 | console.log('处理 Text 节点') 60 | if (n1 === null) { 61 | // 如果n1没有 说明是初始化阶段 62 | // 使用hostCreateText创建text节点 然后insert 63 | console.log('初始化 Text 节点') 64 | hostInsert((n2.el = hostCreateText(n2.children as string)), container) 65 | } 66 | else { 67 | // update 68 | // 先对比一下 updated 之后的内容是否和之前的不一样 69 | // 在不一样的时候才需要 update text 70 | // 这里抽离出来的接口是 setText 71 | // 注意,这里一定要记得把 n1.el 赋值给 n2.el, 不然后续是找不到值的 72 | const el = (n2.el = n1.el!) 73 | if (n2.children !== n1.children) { 74 | console.log('更新 Text 节点') 75 | hostSetText(el, n2.children as string) 76 | } 77 | } 78 | } 79 | 80 | function processFragment( 81 | n1, 82 | n2: any, 83 | container: any, 84 | parentComponent, 85 | anchor, 86 | ) { 87 | // 如果是Fragment类型 就跳过当前节点 直接mount他的子节点children 88 | console.log('初始化 Fragment 类型的节点') 89 | mountChildren(n2.children, container, parentComponent, anchor) 90 | } 91 | 92 | function processElement( 93 | n1, 94 | n2: any, 95 | container: any, 96 | parentComponent, 97 | anchor, 98 | ) { 99 | if (!n1) { 100 | // 如果没有老节点 则直接mount新节点 101 | mountElement(n2, container, parentComponent, anchor) 102 | } 103 | else { 104 | // 有老节点 调用patchElement进行对比 105 | patchElement(n1, n2, container, parentComponent, anchor) 106 | } 107 | } 108 | 109 | function patchElement(n1, n2, container, parentComponent, anchor) { 110 | console.log('应该更新 element') 111 | console.log('旧的 vnode', n1) 112 | console.log('新的 vnode', n2) 113 | 114 | const oldProps = n1.props || EMPTY_OBJ 115 | const newProps = n2.props || EMPTY_OBJ 116 | 117 | // 获取el 供hostPatchProps使用 118 | const el = (n2.el = n1.el) 119 | 120 | // 更新props 121 | patchProps(el, oldProps, newProps) 122 | 123 | // 更新子节点children 124 | patchChildren(n1, n2, el, parentComponent, anchor) 125 | } 126 | 127 | function patchChildren(n1, n2, container, parentComponent, anchor) { 128 | const { shapeFlag: prevShapeFlag, children: c1 } = n1 129 | const { shapeFlag, children: c2 } = n2 130 | 131 | // 进行对比新老children 132 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 133 | // 如果 n2 的 children 是 text 类型的话 134 | // 就看看和之前的 n1 的 children 是不是一样的 135 | // 如果不一样的话直接重新设置一下 text 即可 136 | if (c1 !== c2) 137 | hostSetElementText(container, c2) 138 | 139 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { 140 | // 如果n1的children是array 那么需要清空children 141 | unmountChildren(n1.children) 142 | } 143 | } 144 | else { 145 | // 新节点为array类型 146 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { 147 | // 老节点为text 148 | // 1. 清空text 149 | hostSetElementText(container, '') 150 | // 2. mountChildren 151 | mountChildren(c2, container, parentComponent, anchor) 152 | } 153 | else { 154 | // 如果之前是 array_children 155 | // 现在还是 array_children 的话 156 | // 那么我们就需要对比两个 children 走diff算法了 157 | patchKeyedChildren(c1, c2, container, parentComponent, anchor) 158 | } 159 | } 160 | } 161 | 162 | function patchKeyedChildren( 163 | c1, 164 | c2, 165 | container, 166 | parentComponent, 167 | parentAnchor, 168 | ) { 169 | // i 头指针 170 | // e1 e2分别为两个数组的尾指针 171 | // 头部如果相同 i ++ 一直找到不同的位置为止 172 | let i = 0 173 | const l2 = c2.length 174 | const l1 = c1.length 175 | let e1 = l1 - 1 176 | let e2 = l2 - 1 177 | 178 | function isSameVNodeType(n1, n2) { 179 | // 基于type和key判断两个节点是否一样 180 | return n1.type === n2.type && n1.key === n2.key 181 | } 182 | 183 | // 左侧对比 184 | while (i <= e1 && i <= e2) { 185 | const n1 = c1[i] 186 | const n2 = c2[i] 187 | 188 | if (isSameVNodeType(n1, n2)) { 189 | // 如果type key相同 则调用patch去更新props和children 190 | console.log( 191 | '两个 child 相等,接下来对比这两个 child 节点(从左往右比对)', 192 | ) 193 | patch(n1, n2, container, parentComponent, parentAnchor) 194 | } 195 | else { 196 | console.log('两个 child 不相等(从左往右比对)') 197 | console.log(`prevChild:${n1}`) 198 | console.log(`nextChild:${n2}`) 199 | break 200 | } 201 | // 移动左侧指针 202 | i++ 203 | } 204 | 205 | // 右侧对比 206 | while (i <= e1 && i <= e2) { 207 | const n1 = c1[e1] 208 | const n2 = c2[e2] 209 | 210 | if (isSameVNodeType(n1, n2)) { 211 | // 如果type key相同 则调用patch去对比props和children 212 | console.log( 213 | '两个 child 相等,接下来对比这两个 child 节点(从右往左比对)', 214 | ) 215 | patch(n1, n2, container, parentComponent, parentAnchor) 216 | } 217 | else { 218 | console.log('两个 child 不相等(从右往左比对)') 219 | console.log(`prevChild:${n1}`) 220 | console.log(`nextChild:${n2}`) 221 | break 222 | } 223 | // 移动右侧指针 224 | e1-- 225 | e2-- 226 | } 227 | 228 | // 新的比老的长 创建 229 | // i > e1 230 | if (i > e1) { 231 | if (i <= e2) { 232 | // 如果是这种情况的话就说明 e2 也就是新节点的数量大于旧节点的数量 233 | // 也就是说新增了 vnode 234 | // 应该循环 c2 235 | // 锚点的计算:新的节点有可能需要添加到尾部,也可能添加到头部,所以需要指定添加的问题 236 | // 要添加的位置是当前的位置(e2 开始)+1 237 | // 因为对于往左侧添加的话,应该获取到 c2 的第一个元素 238 | // 所以我们需要从 e2 + 1 取到锚点的位置 239 | // 如果大于c2.length 则说明是右侧加 锚点为null 否则是左侧加 锚点为i + 1 240 | const nextPos = e2 + 1 241 | const anchor = nextPos < l2 ? c2[nextPos].el : null 242 | // 循环patch创建 243 | while (i <= e2) { 244 | // 创建新的节点 此时n1为null 245 | patch(null, c2[i], container, parentComponent, anchor) 246 | i++ 247 | } 248 | } 249 | } 250 | else if (i > e2) { 251 | // 新的比老的短 252 | // i > e2 253 | while (i <= e1) { 254 | hostRemove(c1[i].el) 255 | i++ 256 | } 257 | } 258 | else { 259 | // 乱序的部分 中间对比 260 | // 左右两边都比对完了,然后剩下的就是中间部位顺序变动的 261 | // 例如下面的情况 262 | // a,b,[c,d,e],f,g 263 | // a,b,[e,c,d],f,g 264 | // i -> 左侧 e1 -> 更新前的e e2 -> 更新后的e 265 | const s1 = i 266 | const s2 = i 267 | const toBePatched = e2 - s2 + 1 // 需要patch的新节点的数量 268 | let patched = 0 // 已经patch过的数量 269 | // 通过key建立映射表 270 | // key -> i 271 | const keyToNewIndex = new Map() 272 | // 初始化 从新的index映射为老的index 273 | // 创建数组的时候给定数组的长度,这个是性能最快的写法 274 | const newIndexToOldIndexMap = new Array(toBePatched) 275 | // 初始化为 0 , 后面处理的时候 如果发现是 0 的话,那么就说明新值在老的里面不存在 276 | for (let i = 0; i < toBePatched; i++) 277 | newIndexToOldIndexMap[i] = 0 278 | 279 | // 是否移动了位置 280 | let moved = false 281 | let maxNewIndexSoFar = 0 282 | 283 | for (let i = s2; i <= e2; i++) { 284 | const nextChild = c2[i] 285 | keyToNewIndex.set(nextChild.key, i) 286 | } 287 | 288 | // 去map里查找更新后是否存在 289 | // 遍历老节点 290 | // 1. 需要找出老节点有,而新节点没有的 -> 需要把这个节点删除掉 291 | // 2. 新老节点都有的,—> 需要 patch 292 | for (let i = s1; i <= e1; i++) { 293 | const prevChild = c1[i] 294 | // 如果patch过的数量 >= 需要patch的数量 295 | // 说明所有新节点都已经patch过了, 老节点直接删除掉 不需要再走下面的逻辑了 296 | if (patched >= toBePatched) { 297 | hostRemove(prevChild.el) 298 | continue 299 | } 300 | let newIndex 301 | if (prevChild.key != null) { 302 | // 这里就可以通过key快速的查找了, 看看在新的里面这个节点存在不存在 303 | newIndex = keyToNewIndex.get(prevChild.key) 304 | } 305 | else { 306 | // 如果没key 的话,那么只能是遍历所有的新节点来确定当前节点存在不存在了 307 | for (let j = s2; j <= e2; j++) { 308 | if (isSameVNodeType(prevChild, c2[j])) { 309 | // 如果是same 则说明此节点更新后也存在 310 | // 给newIndex的值更新成j 然后跳出循环 311 | newIndex = j 312 | break 313 | } 314 | } 315 | } 316 | 317 | // 因为有可能 nextIndex 的值为0(0也是正常值) 318 | // 所以需要通过值是不是 undefined 或者 null 来判断 319 | if (newIndex === undefined) { 320 | // 如果newIndex没有被赋值, 则说明节点被删除了 321 | hostRemove(prevChild.el) 322 | } 323 | else { 324 | // 老节点还存在 325 | console.log('新老节点都存在') 326 | 327 | // 这里是确定中间的节点是不是需要移动 328 | // 新的 newIndex 如果一直是升序的话,那么就说明没有移动 329 | // 所以我们可以记录最后一个节点在新的里面的索引,然后看看是不是升序 330 | // 不是升序的话,我们就可以确定节点移动过了 331 | if (newIndex >= maxNewIndexSoFar) { 332 | // 大于等于记录的点, 说明相对位置没有改变 333 | maxNewIndexSoFar = newIndex 334 | } 335 | else { 336 | // 新得到的index比记录的点小 说明需要移动位置 337 | moved = true 338 | } 339 | // 赋值新老节点位置的映射关系 从0开始 340 | // index -> 位置 341 | // 0 代表没有 所以等号右侧需要 + 1 342 | newIndexToOldIndexMap[newIndex - s2] = i + 1 343 | // 调用patch深度对比 344 | patch(prevChild, c2[newIndex], container, parentComponent, null) 345 | patched++ 346 | } 347 | } 348 | 349 | // 求最长递增子序列 350 | // 最长递增子序列为稳定的序列 不需要进行移动 351 | // [4, 2, 3] -> [1, 2] 352 | // 利用最长递增子序列来优化移动逻辑 353 | // 因为元素是升序的话,那么这些元素就是不需要移动的 354 | // 而我们就可以通过最长递增子序列来获取到升序的列表 355 | // 在移动的时候我们去对比这个列表,如果对比上的话,就说明当前元素不需要移动 356 | // 通过 moved 来进行优化,如果没有移动过的话 那么就不需要执行算法 357 | // getSequence 返回的是 newIndexToOldIndexMap 的索引值 358 | // 所以后面我们可以直接遍历索引值来处理,也就是直接使用 toBePatched 即可 359 | const increasingNewIndexSequence = moved 360 | ? getSequence(newIndexToOldIndexMap) 361 | : [] 362 | // 创建指针 363 | let j = increasingNewIndexSequence.length - 1 364 | 365 | // 遍历新节点 366 | // 1. 需要找出老节点没有,而新节点有的 -> 需要把这个节点创建 367 | // 2. 最后需要移动一下位置,比如 [c,d,e] -> [e,c,d] 368 | // 这里倒循环是因为在 insert 的时候,需要保证锚点是处理完的节点(也就是已经确定位置了) 369 | // 因为 insert 逻辑是使用的 insertBefore() 370 | for (let i = toBePatched - 1; i >= 0; i--) { 371 | const nextIndex = i + s2 372 | const nextChild = c2[nextIndex] 373 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null 374 | if (newIndexToOldIndexMap[i] === 0) { 375 | // 新节点新增的 376 | patch(null, nextChild, container, parentComponent, anchor) 377 | } 378 | if (moved) { 379 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 380 | // 需要移动 381 | // 1. j 已经没有了 说明剩下的都需要移动了 382 | // 2. 最长子序列里面的值和当前的值匹配不上, 说明当前元素需要移动 383 | hostInsert(nextChild.el, container, anchor) 384 | } 385 | else { 386 | // 在最长递增子序列内, 直接移动指针 387 | j-- 388 | } 389 | } 390 | } 391 | } 392 | } 393 | 394 | function unmountChildren(children) { 395 | // 循环children 删除子节点 396 | for (let i = 0; i < children.length; i++) { 397 | const el = children[i].el 398 | hostRemove(el) 399 | } 400 | } 401 | 402 | function patchProps(el, oldProps, newProps) { 403 | if (oldProps !== newProps) { 404 | // 对比 props 有以下几种情况 405 | // 1. oldProps 有,newProps 也有,但是 val 值变更了 406 | // 举个栗子 407 | // 之前: oldProps.id = 1 ,更新后:newProps.id = 2 408 | 409 | // key 存在 oldProps 里 也存在 newProps 内 410 | // 以 newProps 作为基准 411 | for (const key in newProps) { 412 | const prevProp = oldProps[key] 413 | const nextProp = newProps[key] 414 | // 获取改变前后的prop 进行更新 415 | if (prevProp !== nextProp) 416 | hostPatchProp(el, key, prevProp, nextProp) 417 | } 418 | // 2. oldProps 有,而 newProps 没有了 419 | // 之前: {id:1,tId:2} 更新后: {id:1} 420 | // 这种情况下我们就应该以 oldProps 作为基准,因为在 newProps 里面是没有的 tId 的 421 | if (oldProps !== EMPTY_OBJ) { 422 | for (const key in oldProps) { 423 | // 如果更新后key不在newProps里 删除 424 | if (!(key in newProps)) 425 | hostPatchProp(el, key, oldProps[key], null) 426 | } 427 | } 428 | } 429 | } 430 | 431 | function mountElement(n2: any, container: any, parentComponent, anchor) { 432 | // 创建element节点 433 | const el = (n2.el = hostCreateElement(n2.type)) 434 | const { children, props, shapeFlag } = n2 435 | 436 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 437 | // 举个栗子 438 | // render(){ 439 | // return h("div",{},"test") 440 | // } 441 | // 这里 children 就是 test ,只需要渲染一下就完事了 442 | console.log(`处理文本 ${children}`) 443 | hostSetElementText(el, children) 444 | } 445 | else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 446 | // 如果是array类型 调用mountChildren 447 | // 举个栗子 448 | // render(){ 449 | // Hello 是个 component 450 | // return h("div",{},[h("p"),h(Hello)]) 451 | // } 452 | // 这里 children 就是个数组了,就需要依次调用 patch 递归来处理 453 | mountChildren(n2.children, el, parentComponent, anchor) 454 | } 455 | if (props) { 456 | // 循环节点的props 进行props的赋值 457 | // TODO 458 | // 需要过滤掉vue自身用的key 459 | // 比如生命周期相关的 key: beforeMount、mounted 460 | for (const key in props) { 461 | const val = props[key] 462 | hostPatchProp(el, key, null, val) 463 | } 464 | } 465 | 466 | // 触发beforeMount() 467 | console.log('vnodeHook -> onVnodeBeforeMount') 468 | console.log('DirectiveHook -> beforeMount') 469 | console.log('transition -> beforeEnter') 470 | 471 | // 生成真实DOM 添加到页面中 472 | hostInsert(el, container, anchor) 473 | 474 | // 触发mounted() 475 | console.log('vnodeHook -> onVnodeMounted') 476 | console.log('DirectiveHook -> mounted') 477 | console.log('transition -> enter') 478 | } 479 | 480 | function mountChildren(children, container, parentComponent, anchor) { 481 | // 循环children, 再次调用patch 482 | children.forEach((v) => { 483 | patch(null, v, container, parentComponent, anchor) 484 | }) 485 | } 486 | 487 | function processComponent( 488 | n1, 489 | n2: any, 490 | container: any, 491 | parentComponent, 492 | anchor, 493 | ) { 494 | if (!n1) { 495 | // 如果没有n1 那么就是初始化mount 496 | mountComponent(n2, container, parentComponent, anchor) 497 | } 498 | else { 499 | updateComponent(n1, n2) 500 | } 501 | } 502 | 503 | function updateComponent(n1, n2) { 504 | const instance = (n2.component = n1.component) 505 | // 如果props相等 就不需要更新 506 | if (shouldUpdateComponent(n1, n2)) { 507 | console.log(`组件需要更新: ${instance}`) 508 | // 下次要更新的节点 509 | instance.next = n2 510 | // 这里的 update 是在 setupRenderEffect 里面初始化的,update 函数除了当内部的响应式对象发生改变的时候会调用 511 | // 还可以直接主动的调用(这是属于 effect 的特性) 512 | // 调用 update 再次更新调用 patch 逻辑 513 | // 在update 中调用的 next 就变成了 n2了 514 | // TODO 需要在 update 中处理支持 next 的逻辑 515 | instance.update() 516 | } 517 | else { 518 | // 不执行update的话 还是需要给el vnode重新赋值的 519 | console.log(`组件不需要更新: ${instance}`) 520 | n2.el = n1.el 521 | instance.vnode = n2 522 | } 523 | } 524 | 525 | function mountComponent( 526 | initialVNode: any, 527 | container: any, 528 | parentComponent, 529 | anchor, 530 | ) { 531 | // 创建组件实例 532 | const instance = (initialVNode.component = createComponentInstance( 533 | initialVNode, 534 | parentComponent, 535 | )) 536 | console.log(`创建组件实例:${instance.type.name}`) 537 | // 初始化组件 538 | setupComponent(instance) 539 | // 设置effect 收集依赖 获取subTree虚拟节点树 540 | setupRenderEffect(instance, initialVNode, container, anchor) 541 | } 542 | 543 | function setupRenderEffect( 544 | instance: any, 545 | initialVNode: any, 546 | container: any, 547 | anchor, 548 | ) { 549 | // 调用render时 会触发响应式对象ref/reactive的get收集依赖 550 | // 响应式对象改变了 会触发内部的函数 自动调用render生成新的subTree 551 | // 保存更新函数 552 | instance.update = effect( 553 | () => { 554 | if (!instance.isMounted) { 555 | // 组件初始化的时候会执行这里 556 | // 为什么要在这里调用 render 函数呢 557 | // 是因为在 effect 内调用 render 才能触发依赖收集 558 | // 等到后面响应式的值变更后会再次触发这个函数 559 | console.log(`setupRenderEffect -> ${instance.type.name} -> init`) 560 | console.log(`${instance.type.name}:调用 render,获取 subTree`) 561 | const { proxy } = instance 562 | // 可在 render 函数中通过 this 来使用 proxy 563 | const subTree = (instance.subTree = instance.render.call( 564 | proxy, 565 | proxy, 566 | )) 567 | 568 | // 这里触发beforeMount 569 | console.log(`${instance.type.name}:触发 beforeMount hook`) 570 | console.log(`${instance.type.name}:触发 onVnodeBeforeMount hook`) 571 | 572 | // 这里基于 subTree 再次调用 patch 573 | // 基于 render 返回的 vnode ,再次进行渲染 574 | // 这里我把这个行为隐喻成开箱 575 | // 一个组件就是一个箱子 576 | // 里面有可能是 element (也就是可以直接渲染的) 577 | // 也有可能还是 component 578 | // 这里就是递归的开箱 579 | // 而 subTree 就是当前的这个箱子(组件)装的东西 580 | // 箱子(组件)只是个概念,它实际是不需要渲染的 581 | // 要渲染的是箱子里面的 subTree 582 | patch(null, subTree, container, instance, anchor) 583 | 584 | // 更新el 585 | // 把 root element 赋值给 组件的vnode.el ,为后续调用 $el 的时候获取值 586 | initialVNode.el = subTree.el 587 | 588 | // 这里触发mounted 589 | console.log(`${instance.type.name}:触发 mounted hook`) 590 | // 更新instance的状态为isMounted 依赖变更时进入else分支 591 | instance.isMounted = true 592 | } 593 | else { 594 | // 响应式对象发生改变时会进到这里 595 | // 拿到新的 vnode ,然后和之前的 vnode 进行对比 596 | console.log(`setupRenderEffect -> ${instance.type.name} -> update`) 597 | console.log(`${instance.type.name}:调用更新逻辑`) 598 | // vnode是更新之前的 next是更新之后的 599 | const { proxy, next, vnode } = instance 600 | // 如果有 next 的话, 说明需要更新组件的数据(props,slots 等) 601 | // 先更新组件的数据,然后更新完成后,在继续对比当前组件的子元素 602 | // 更新el 603 | if (next) { 604 | next.el = vnode.el 605 | updateComponentPreRender(instance, next) 606 | } 607 | // 获取虚拟节点树 608 | const subTree = instance.render.call(proxy, proxy) 609 | const prevSubTree = instance.subTree 610 | // 更新subTree 611 | instance.subTree = subTree 612 | 613 | // 触发 beforeUpdated hook 614 | console.log(`${instance.type.name}:触发 beforeUpdated hook`) 615 | console.log(`${instance.type.name}:触发 onVnodeBeforeUpdate hook`) 616 | 617 | // 新旧虚拟节点数进行对比, 重新patch 618 | patch(prevSubTree, subTree, container, instance, anchor) 619 | 620 | // 触发 updated hook 621 | console.log(`${instance.type.name}:触发 updated hook`) 622 | console.log(`${instance.type.name}:触发 onVnodeUpdated hook`) 623 | } 624 | }, 625 | { 626 | // 如果组件中有循环的话, 那么就会多次update组件造成性能浪费 627 | // 将所有update作为job存储在一个队列中 628 | // 当所有的同步任务结束之后 再统一拿出来执行 629 | scheduler() { 630 | console.log('组件更新 ---- 执行scheduler储存jobs') 631 | queueJobs(instance.update) 632 | }, 633 | }, 634 | ) 635 | } 636 | 637 | function updateComponentPreRender(instance, nextVNode) { 638 | // 更新 nextVNode 的组件实例 639 | // 现在 instance.vnode 是组件实例更新前的 640 | // 所以之前的 props 就是基于 instance.vnode.props 来获取 641 | // 接着需要更新 vnode ,方便下一次更新的时候获取到正确的值 642 | instance.vnode = nextVNode 643 | instance.next = null 644 | // 更新props 645 | instance.props = nextVNode.props 646 | } 647 | 648 | return { 649 | createApp: createAppAPI(render), 650 | } 651 | } 652 | 653 | function getSequence(arr) { 654 | const p = arr.slice() 655 | const result = [0] 656 | let i, j, u, v, c 657 | const len = arr.length 658 | for (i = 0; i < len; i++) { 659 | const arrI = arr[i] 660 | if (arrI !== 0) { 661 | j = result[result.length - 1] 662 | if (arr[j] < arrI) { 663 | p[i] = j 664 | result.push(i) 665 | continue 666 | } 667 | u = 0 668 | v = result.length - 1 669 | while (u < v) { 670 | c = (u + v) >> 1 671 | if (arr[result[c]] < arrI) 672 | u = c + 1 673 | else 674 | v = c 675 | } 676 | if (arrI < arr[result[u]]) { 677 | if (u > 0) 678 | p[i] = result[u - 1] 679 | 680 | result[u] = i 681 | } 682 | } 683 | } 684 | u = result.length 685 | v = result[u - 1] 686 | while (u-- > 0) { 687 | result[u] = v 688 | v = p[v] 689 | } 690 | return result 691 | } 692 | -------------------------------------------------------------------------------- /yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | /usr/local/bin/node /usr/local/bin/yarn add vitest 3 | 4 | PATH: 5 | /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Users/mac/.fig/bin 6 | 7 | Yarn version: 8 | 1.22.11 9 | 10 | Node version: 11 | 14.17.1 12 | 13 | Platform: 14 | darwin x64 15 | 16 | Trace: 17 | SyntaxError: /Users/mac/OpenSource/vue3-proxy/package.json: Unexpected token } in JSON at position 214 18 | at JSON.parse () 19 | at /usr/local/lib/node_modules/yarn/lib/cli.js:1625:59 20 | at Generator.next () 21 | at step (/usr/local/lib/node_modules/yarn/lib/cli.js:310:30) 22 | at /usr/local/lib/node_modules/yarn/lib/cli.js:321:13 23 | 24 | npm manifest: 25 | { 26 | "name": "vue3-proxy", 27 | "version": "1.0.0", 28 | "main": "index.js", 29 | "devDependencies": { 30 | "@rollup/plugin-typescript": "^8.4.0", 31 | "rollup": "^2.79.0", 32 | "tslib": "^2.4.0", 33 | "typescript": "^4.7.3", 34 | }, 35 | "scripts": { 36 | "test": "vitest src/", 37 | "build": "rollup -c rollup.config.js" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/cuiyiming1998/vue3-proxy.git" 42 | }, 43 | "keywords": [], 44 | "author": "yiming.cui ", 45 | "license": "ISC", 46 | "bugs": { 47 | "url": "https://github.com/cuiyiming1998/vue3-proxy/issues" 48 | }, 49 | "homepage": "https://github.com/cuiyiming1998/vue3-proxy#readme", 50 | "description": "" 51 | } 52 | 53 | yarn manifest: 54 | No manifest 55 | 56 | Lockfile: 57 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 58 | # yarn lockfile v1 59 | 60 | 61 | "@rollup/plugin-typescript@^8.4.0": 62 | version "8.4.0" 63 | resolved "https://registry.npmmirror.com/@rollup/plugin-typescript/-/plugin-typescript-8.4.0.tgz#a8a384b6dbaab42b4cafb075278b15743c0f5ef8" 64 | integrity sha512-QssfoOP6V4/6skX12EfOW5UzJAv/c334F4OJWmQpe2kg3agEa0JwVCckwmfuvEgDixyX+XyxjFenH7M2rDKUyQ== 65 | dependencies: 66 | "@rollup/pluginutils" "^3.1.0" 67 | resolve "^1.17.0" 68 | 69 | "@rollup/pluginutils@^3.1.0": 70 | version "3.1.0" 71 | resolved "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" 72 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== 73 | dependencies: 74 | "@types/estree" "0.0.39" 75 | estree-walker "^1.0.1" 76 | picomatch "^2.2.2" 77 | 78 | "@types/chai-subset@^1.3.3": 79 | version "1.3.3" 80 | resolved "https://registry.npmmirror.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94" 81 | integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw== 82 | dependencies: 83 | "@types/chai" "*" 84 | 85 | "@types/chai@*", "@types/chai@^4.3.1": 86 | version "4.3.1" 87 | resolved "https://registry.npmmirror.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04" 88 | integrity sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ== 89 | 90 | "@types/estree@0.0.39": 91 | version "0.0.39" 92 | resolved "https://registry.npmmirror.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" 93 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== 94 | 95 | "@types/node@*": 96 | version "17.0.42" 97 | resolved "https://registry.npmmirror.com/@types/node/-/node-17.0.42.tgz#d7e8f22700efc94d125103075c074396b5f41f9b" 98 | integrity sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ== 99 | 100 | assertion-error@^1.1.0: 101 | version "1.1.0" 102 | resolved "https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 103 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 104 | 105 | chai@^4.3.6: 106 | version "4.3.6" 107 | resolved "https://registry.npmmirror.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" 108 | integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== 109 | dependencies: 110 | assertion-error "^1.1.0" 111 | check-error "^1.0.2" 112 | deep-eql "^3.0.1" 113 | get-func-name "^2.0.0" 114 | loupe "^2.3.1" 115 | pathval "^1.1.1" 116 | type-detect "^4.0.5" 117 | 118 | check-error@^1.0.2: 119 | version "1.0.2" 120 | resolved "https://registry.npmmirror.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 121 | integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== 122 | 123 | debug@^4.3.4: 124 | version "4.3.4" 125 | resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 126 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 127 | dependencies: 128 | ms "2.1.2" 129 | 130 | deep-eql@^3.0.1: 131 | version "3.0.1" 132 | resolved "https://registry.npmmirror.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 133 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== 134 | dependencies: 135 | type-detect "^4.0.0" 136 | 137 | esbuild-android-64@0.14.44: 138 | version "0.14.44" 139 | resolved "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.44.tgz#62f5cb563d0ba318d898b6eb230c61ad3dc93619" 140 | integrity sha512-dFPHBXmx385zuJULAD/Cmq/LyPRXiAWbf9ylZtY0wJ8iVyWfKYaCYxeJx8OAZUuj46ZwNa7MzW2GBAQLOeiemg== 141 | 142 | esbuild-android-arm64@0.14.44: 143 | version "0.14.44" 144 | resolved "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.44.tgz#ee7fcc9f47855b3395dfd1abcc2c43d8c53e57db" 145 | integrity sha512-qqaqqyxHXjZ/0ddKU3I3Nb7lAvVM69ELMhb8+91FyomAUmQPlHtxe+TTiWxXGHE72XEzcgTEGq4VauqLNkN22g== 146 | 147 | esbuild-darwin-64@0.14.44: 148 | version "0.14.44" 149 | resolved "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.44.tgz#75ea7f594687a7189a8ba62070d42f13178e68d0" 150 | integrity sha512-RBmtGKGY06+AW6IOJ1LE/dEeF7HH34C1/Ces9FSitU4bIbIpL4KEuQpTFoxwb4ry5s2hyw7vbPhhtyOd18FH9g== 151 | 152 | esbuild-darwin-arm64@0.14.44: 153 | version "0.14.44" 154 | resolved "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.44.tgz#550631fd135688abda042f4039b962a27f3a5f78" 155 | integrity sha512-Bmhx5Cfo4Hdb7WyyyDupTB8HPmnFZ8baLfPlzLdYvF6OzsIbV+CY+m/AWf0OQvY40BlkzCLJ/7Lfwbb71Tngmg== 156 | 157 | esbuild-freebsd-64@0.14.44: 158 | version "0.14.44" 159 | resolved "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.44.tgz#f98069b964266ca79bb361cfb9d7454a05b7e8ca" 160 | integrity sha512-O4HpWa5ZgxbNPQTF7URicLzYa+TidGlmGT/RAC3GjbGEQQYkd0R1Slyh69Yrmb2qmcOcPAgWHbNo1UhK4WmZ4w== 161 | 162 | esbuild-freebsd-arm64@0.14.44: 163 | version "0.14.44" 164 | resolved "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.44.tgz#247a8553d6033a58b6c3ba4d539216300497d7f6" 165 | integrity sha512-f0/jkAKccnDY7mg1F9l/AMzEm+VXWXK6c3IrOEmd13jyKfpTZKTIlt+yI04THPDCDZTzXHTRUBLozqp+m8Mg5Q== 166 | 167 | esbuild-linux-32@0.14.44: 168 | version "0.14.44" 169 | resolved "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.44.tgz#4b72747f9f367d3ee3c1d80bf75c617e6b109821" 170 | integrity sha512-WSIhzLldMR7YUoEL7Ix319tC+NFmW9Pu7NgFWxUfOXeWsT0Wg484hm6bNgs7+oY2pGzg715y/Wrqi1uNOMmZJw== 171 | 172 | esbuild-linux-64@0.14.44: 173 | version "0.14.44" 174 | resolved "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.44.tgz#6cd158fdd11f8d037c45139ccddb4fa5966f7ab6" 175 | integrity sha512-zgscTrCMcRZRIsVugqBTP/B5lPLNchBlWjQ8sQq2Epnv+UDtYKgXEq1ctWAmibZNy2E9QRCItKMeIEqeTUT5kA== 176 | 177 | esbuild-linux-arm64@0.14.44: 178 | version "0.14.44" 179 | resolved "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.44.tgz#f14f3c8c3501067f5b2cef7a79c9a0058092cb22" 180 | integrity sha512-H0H/2/wgiScTwBve/JR8/o+Zhabx5KPk8T2mkYZFKQGl1hpUgC+AOmRyqy/Js3p66Wim4F4Akv3I3sJA1sKg0w== 181 | 182 | esbuild-linux-arm@0.14.44: 183 | version "0.14.44" 184 | resolved "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.44.tgz#625478cc6ea4f64f5335e7fb07f80d6f659aeb5c" 185 | integrity sha512-laPBPwGfsbBxGw6F6jnqic2CPXLyC1bPrmnSOeJ9oEnx1rcKkizd4HWCRUc0xv+l4z/USRfx/sEfYlWSLeqoJQ== 186 | 187 | esbuild-linux-mips64le@0.14.44: 188 | version "0.14.44" 189 | resolved "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.44.tgz#593c909bb612998300af7f7db2809a63a0d62eec" 190 | integrity sha512-ri3Okw0aleYy7o5n9zlIq+FCtq3tcMlctN6X1H1ucILjBJuH8pan2trJPKWeb8ppntFvE28I9eEXhwkWh6wYKg== 191 | 192 | esbuild-linux-ppc64le@0.14.44: 193 | version "0.14.44" 194 | resolved "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.44.tgz#8fc2fcee671e322a5d44fcf8da6889095a187df9" 195 | integrity sha512-96TqL/MvFRuIVXz+GtCIXzRQ43ZwEk4XTn0RWUNJduXXMDQ/V1iOV28U6x6Oe3NesK4xkoKSaK2+F3VHcU8ZrA== 196 | 197 | esbuild-linux-riscv64@0.14.44: 198 | version "0.14.44" 199 | resolved "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.44.tgz#cc2b7158c8345b67e74458a0ec020c80ca777046" 200 | integrity sha512-rrK9qEp2M8dhilsPn4T9gxUsAumkITc1kqYbpyNMr9EWo+J5ZBj04n3GYldULrcCw4ZCHAJ+qPjqr8b6kG2inA== 201 | 202 | esbuild-linux-s390x@0.14.44: 203 | version "0.14.44" 204 | resolved "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.44.tgz#5a1e87d5d6236a8791026820936478f3b9cf8e35" 205 | integrity sha512-2YmTm9BrW5aUwBSe8wIEARd9EcnOQmkHp4+IVaO09Ez/C5T866x+ABzhG0bwx0b+QRo9q97CRMaQx2Ngb6/hfw== 206 | 207 | esbuild-netbsd-64@0.14.44: 208 | version "0.14.44" 209 | resolved "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.44.tgz#8afb16880b530264ce2ed9fb3df26071eb841312" 210 | integrity sha512-zypdzPmZTCqYS30WHxbcvtC0E6e/ECvl4WueUdbdWhs2dfWJt5RtCBME664EpTznixR3lSN1MQ2NhwQF8MQryw== 211 | 212 | esbuild-openbsd-64@0.14.44: 213 | version "0.14.44" 214 | resolved "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.44.tgz#a87455be446d6f5b07a60f060e1eafad454c9d4b" 215 | integrity sha512-8J43ab9ByYl7KteC03HGQjr2HY1ge7sN04lFnwMFWYk2NCn8IuaeeThvLeNjzOYhyT3I6K8puJP0uVXUu+D1xw== 216 | 217 | esbuild-sunos-64@0.14.44: 218 | version "0.14.44" 219 | resolved "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.44.tgz#74ce7d5fd815fa60fe0a85b9db7549b1dcb32804" 220 | integrity sha512-OH1/09CGUJwffA+HNM6mqPkSIyHVC3ZnURU/4CCIx7IqWUBn1Sh1HRLQC8/TWNgcs0/1u7ygnc2pgf/AHZJ/Ow== 221 | 222 | esbuild-windows-32@0.14.44: 223 | version "0.14.44" 224 | resolved "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.44.tgz#499e1c6cb46c216bdcb62f5b006fec3999bb06a7" 225 | integrity sha512-mCAOL9/rRqwfOfxTu2sjq/eAIs7eAXGiU6sPBnowggI7QS953Iq6o3/uDu010LwfN7zr18c/lEj6/PTwwTB3AA== 226 | 227 | esbuild-windows-64@0.14.44: 228 | version "0.14.44" 229 | resolved "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.44.tgz#a8dae69a4615b17f62375859ce9536dd96940a06" 230 | integrity sha512-AG6BH3+YG0s2Q/IfB1cm68FdyFnoE1P+GFbmgFO3tA4UIP8+BKsmKGGZ5I3+ZjcnzOwvT74bQRVrfnQow2KO5Q== 231 | 232 | esbuild-windows-arm64@0.14.44: 233 | version "0.14.44" 234 | resolved "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.44.tgz#db73bb68aa75a880bdaa938ccacc0f17e7a4bfea" 235 | integrity sha512-ygYPfYE5By4Sd6szsNr10B0RtWVNOSGmZABSaj4YQBLqh9b9i45VAjVWa8tyIy+UAbKF7WGwybi2wTbSVliO8A== 236 | 237 | esbuild@^0.14.27: 238 | version "0.14.44" 239 | resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.44.tgz#27fa3cd4f55d36780650c7f32cab581abc7a4473" 240 | integrity sha512-Rn+lRRfj60r/3svI6NgAVnetzp3vMOj17BThuhshSj/gS1LR03xrjkDYyfPmrYG/0c3D68rC6FNYMQ3yRbiXeQ== 241 | optionalDependencies: 242 | esbuild-android-64 "0.14.44" 243 | esbuild-android-arm64 "0.14.44" 244 | esbuild-darwin-64 "0.14.44" 245 | esbuild-darwin-arm64 "0.14.44" 246 | esbuild-freebsd-64 "0.14.44" 247 | esbuild-freebsd-arm64 "0.14.44" 248 | esbuild-linux-32 "0.14.44" 249 | esbuild-linux-64 "0.14.44" 250 | esbuild-linux-arm "0.14.44" 251 | esbuild-linux-arm64 "0.14.44" 252 | esbuild-linux-mips64le "0.14.44" 253 | esbuild-linux-ppc64le "0.14.44" 254 | esbuild-linux-riscv64 "0.14.44" 255 | esbuild-linux-s390x "0.14.44" 256 | esbuild-netbsd-64 "0.14.44" 257 | esbuild-openbsd-64 "0.14.44" 258 | esbuild-sunos-64 "0.14.44" 259 | esbuild-windows-32 "0.14.44" 260 | esbuild-windows-64 "0.14.44" 261 | esbuild-windows-arm64 "0.14.44" 262 | 263 | estree-walker@^1.0.1: 264 | version "1.0.1" 265 | resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" 266 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== 267 | 268 | fsevents@~2.3.2: 269 | version "2.3.2" 270 | resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 271 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 272 | 273 | function-bind@^1.1.1: 274 | version "1.1.1" 275 | resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 276 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 277 | 278 | get-func-name@^2.0.0: 279 | version "2.0.0" 280 | resolved "https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 281 | integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== 282 | 283 | has@^1.0.3: 284 | version "1.0.3" 285 | resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 286 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 287 | dependencies: 288 | function-bind "^1.1.1" 289 | 290 | is-core-module@^2.8.1: 291 | version "2.9.0" 292 | resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" 293 | integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== 294 | dependencies: 295 | has "^1.0.3" 296 | 297 | is-core-module@^2.9.0: 298 | version "2.10.0" 299 | resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" 300 | integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== 301 | dependencies: 302 | has "^1.0.3" 303 | 304 | local-pkg@^0.4.1: 305 | version "0.4.1" 306 | resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.1.tgz#e7b0d7aa0b9c498a1110a5ac5b00ba66ef38cfff" 307 | integrity sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw== 308 | 309 | loupe@^2.3.1: 310 | version "2.3.4" 311 | resolved "https://registry.npmmirror.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" 312 | integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== 313 | dependencies: 314 | get-func-name "^2.0.0" 315 | 316 | ms@2.1.2: 317 | version "2.1.2" 318 | resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 319 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 320 | 321 | nanoid@^3.3.4: 322 | version "3.3.4" 323 | resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" 324 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== 325 | 326 | path-parse@^1.0.7: 327 | version "1.0.7" 328 | resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 329 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 330 | 331 | pathval@^1.1.1: 332 | version "1.1.1" 333 | resolved "https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" 334 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== 335 | 336 | picocolors@^1.0.0: 337 | version "1.0.0" 338 | resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 339 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 340 | 341 | picomatch@^2.2.2: 342 | version "2.3.1" 343 | resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 344 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 345 | 346 | postcss@^8.4.13: 347 | version "8.4.14" 348 | resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" 349 | integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== 350 | dependencies: 351 | nanoid "^3.3.4" 352 | picocolors "^1.0.0" 353 | source-map-js "^1.0.2" 354 | 355 | resolve@^1.17.0: 356 | version "1.22.1" 357 | resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" 358 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== 359 | dependencies: 360 | is-core-module "^2.9.0" 361 | path-parse "^1.0.7" 362 | supports-preserve-symlinks-flag "^1.0.0" 363 | 364 | resolve@^1.22.0: 365 | version "1.22.0" 366 | resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" 367 | integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== 368 | dependencies: 369 | is-core-module "^2.8.1" 370 | path-parse "^1.0.7" 371 | supports-preserve-symlinks-flag "^1.0.0" 372 | 373 | rollup@^2.59.0: 374 | version "2.75.6" 375 | resolved "https://registry.npmmirror.com/rollup/-/rollup-2.75.6.tgz#ac4dc8600f95942a0180f61c7c9d6200e374b439" 376 | integrity sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA== 377 | optionalDependencies: 378 | fsevents "~2.3.2" 379 | 380 | rollup@^2.79.0: 381 | version "2.79.0" 382 | resolved "https://registry.npmmirror.com/rollup/-/rollup-2.79.0.tgz#9177992c9f09eb58c5e56cbfa641607a12b57ce2" 383 | integrity sha512-x4KsrCgwQ7ZJPcFA/SUu6QVcYlO7uRLfLAy0DSA4NS2eG8japdbpM50ToH7z4iObodRYOJ0soneF0iaQRJ6zhA== 384 | optionalDependencies: 385 | fsevents "~2.3.2" 386 | 387 | source-map-js@^1.0.2: 388 | version "1.0.2" 389 | resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 390 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 391 | 392 | supports-preserve-symlinks-flag@^1.0.0: 393 | version "1.0.0" 394 | resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 395 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 396 | 397 | tinypool@^0.1.3: 398 | version "0.1.3" 399 | resolved "https://registry.npmmirror.com/tinypool/-/tinypool-0.1.3.tgz#b5570b364a1775fd403de5e7660b325308fee26b" 400 | integrity sha512-2IfcQh7CP46XGWGGbdyO4pjcKqsmVqFAPcXfPxcPXmOWt9cYkTP9HcDmGgsfijYoAEc4z9qcpM/BaBz46Y9/CQ== 401 | 402 | tinyspy@^0.3.2: 403 | version "0.3.3" 404 | resolved "https://registry.npmmirror.com/tinyspy/-/tinyspy-0.3.3.tgz#8b57f8aec7fe1bf583a3a49cb9ab30c742f69237" 405 | integrity sha512-gRiUR8fuhUf0W9lzojPf1N1euJYA30ISebSfgca8z76FOvXtVXqd5ojEIaKLWbDQhAaC3ibxZIjqbyi4ybjcTw== 406 | 407 | tslib@^2.4.0: 408 | version "2.4.0" 409 | resolved "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" 410 | integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== 411 | 412 | type-detect@^4.0.0, type-detect@^4.0.5: 413 | version "4.0.8" 414 | resolved "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 415 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 416 | 417 | typescript@^4.7.3: 418 | version "4.7.3" 419 | resolved "https://registry.npmmirror.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" 420 | integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== 421 | 422 | vite@^2.9.12: 423 | version "2.9.12" 424 | resolved "https://registry.npmmirror.com/vite/-/vite-2.9.12.tgz#b1d636b0a8ac636afe9d83e3792d4895509a941b" 425 | integrity sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew== 426 | dependencies: 427 | esbuild "^0.14.27" 428 | postcss "^8.4.13" 429 | resolve "^1.22.0" 430 | rollup "^2.59.0" 431 | optionalDependencies: 432 | fsevents "~2.3.2" 433 | 434 | vitest@^0.15.1: 435 | version "0.15.1" 436 | resolved "https://registry.npmmirror.com/vitest/-/vitest-0.15.1.tgz#8bdb42544261fa95afe8ea10bae3f315ca7e4c74" 437 | integrity sha512-NaNFi93JKSuvV4YGnfQ0l0GKYxH0EsLcTrrXaCzd6qfVEZM/RJpjwSevg6waNFqu2DyN6e0aHHdrCZW5/vh5NA== 438 | dependencies: 439 | "@types/chai" "^4.3.1" 440 | "@types/chai-subset" "^1.3.3" 441 | "@types/node" "*" 442 | chai "^4.3.6" 443 | debug "^4.3.4" 444 | local-pkg "^0.4.1" 445 | tinypool "^0.1.3" 446 | tinyspy "^0.3.2" 447 | vite "^2.9.12" 448 | --------------------------------------------------------------------------------