├── .eslintrc.js ├── .gitignore ├── .husky ├── commit-msg ├── lintstagedrc.js └── pre-commit ├── .prettierrc ├── README.md ├── babel.config.js ├── commitlint.config.js ├── example ├── ApiInject │ ├── App.js │ ├── index.html │ └── main.js ├── ComponentEvent │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── ComponentSlot │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── CurrentInstance │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── CustomRenderer │ ├── App.js │ ├── index.html │ └── main.js ├── HelloWord │ ├── app.js │ ├── foo.js │ ├── index.html │ └── main.js ├── PatchChildren │ ├── App.js │ ├── ArrayToArray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js ├── Update │ ├── App.js │ ├── App1.js │ ├── index.html │ └── main.js ├── compiler-base │ ├── App.js │ ├── index.html │ └── main.js ├── componentUpdate │ ├── App.js │ ├── Child.js │ ├── index.html │ └── main.js └── nextTick │ ├── App.js │ ├── index.html │ └── main.js ├── lib ├── ai-vue-next.bundle.cjs.js └── ai-vue-next.bundle.esm.js ├── package-lock.json ├── package.json ├── packages ├── .pnpm-debug.log ├── compiler-core │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── codegen.spec.ts.snap │ │ ├── codegen.spec.ts │ │ ├── parse.spec.ts │ │ └── transform.spec.ts │ ├── package.json │ ├── pnpm-lock.yaml │ └── src │ │ ├── ast.ts │ │ ├── codegen.ts │ │ ├── compile.ts │ │ ├── index.ts │ │ ├── parse.ts │ │ ├── runtimeHelpers.ts │ │ ├── transform.ts │ │ ├── transforms │ │ ├── transformExpression.ts │ │ ├── transformText.ts │ │ └── transfromElement.ts │ │ └── utils.ts ├── reactivity │ ├── __tests__ │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts │ ├── package.json │ ├── pnpm-lock.yaml │ └── src │ │ ├── baseHandles.ts │ │ ├── computed.ts │ │ ├── effect.ts │ │ ├── index.ts │ │ ├── reactive.ts │ │ └── ref.ts ├── runtime-core │ ├── package.json │ ├── pnpm-lock.yaml │ └── src │ │ ├── apiInject.ts │ │ ├── component.ts │ │ ├── componentEmit.ts │ │ ├── componentProps.ts │ │ ├── componentPublicInstance.ts │ │ ├── componentSlots.ts │ │ ├── componentUpdateUtils.ts │ │ ├── createApp.ts │ │ ├── h.ts │ │ ├── helpers │ │ └── renderSlots.ts │ │ ├── index.ts │ │ ├── renderer.ts │ │ ├── scheduler.ts │ │ └── vnode.ts ├── runtime-dom │ ├── package.json │ ├── pnpm-lock.yaml │ └── src │ │ └── index.ts ├── shared │ ├── package.json │ └── src │ │ ├── index.ts │ │ ├── shapeFlags.ts │ │ └── toDisplayString.ts └── vue │ ├── .pnpm-debug.log │ ├── dist │ ├── ai-vue-next.cjs.js │ └── ai-vue-next.esm.js │ ├── examples │ ├── ApiInject │ │ ├── App.js │ │ ├── index.html │ │ └── main.js │ ├── ComponentEvent │ │ ├── App.js │ │ ├── Foo.js │ │ ├── index.html │ │ └── main.js │ ├── ComponentSlot │ │ ├── App.js │ │ ├── Foo.js │ │ ├── index.html │ │ └── main.js │ ├── CurrentInstance │ │ ├── App.js │ │ ├── Foo.js │ │ ├── index.html │ │ └── main.js │ ├── CustomRenderer │ │ ├── App.js │ │ ├── index.html │ │ └── main.js │ ├── HelloWord │ │ ├── app.js │ │ ├── foo.js │ │ ├── index.html │ │ └── main.js │ ├── PatchChildren │ │ ├── App.js │ │ ├── ArrayToArray.js │ │ ├── ArrayToText.js │ │ ├── TextToArray.js │ │ ├── TextToText.js │ │ ├── index.html │ │ └── main.js │ ├── Update │ │ ├── App.js │ │ ├── App1.js │ │ ├── index.html │ │ └── main.js │ ├── compiler-base │ │ ├── App.js │ │ ├── index.html │ │ └── main.js │ ├── componentUpdate │ │ ├── App.js │ │ ├── Child.js │ │ ├── index.html │ │ └── main.js │ └── nextTick │ │ ├── App.js │ │ ├── index.html │ │ └── main.js │ ├── package.json │ ├── pnpm-lock.yaml │ └── src │ └── index.ts ├── pnpm-lock.yaml ├── pnpm.workspace.yaml ├── rollup.config.ts ├── scripts └── verifyCommit.js ├── tsconfig.json ├── vitest.config.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | sourceType: 'module' 5 | }, 6 | rules: { 7 | 'no-unused-vars': [ 8 | 'error', 9 | // we are only using this rule to check for unused arguments since TS 10 | // catches unused variables but not args. 11 | { varsIgnorePattern: '.*', args: 'none' } 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | .vscode -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run verify-commit 5 | # npx --no-install commitlint --edit $1 6 | -------------------------------------------------------------------------------- /.husky/lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'], 3 | '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [ 4 | 'prettier --write--parser json' 5 | ], 6 | 'package.json': ['prettier --write'], 7 | '*.vue': ['eslint --fix', 'prettier --write', 'stylelint --fix'], 8 | // '*.{scss,less,styl,html}': ['stylelint --fix', 'prettier --write'], 9 | '*.md': ['prettier --write'] 10 | } 11 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | [ -n "$CI" ] && exit 0 5 | 6 | # Format and submit code according to lintstagedrc.js configuration 7 | pnpm run lint:lint-staged 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 80, 5 | "trailingComma": "none", 6 | "arrowParens": "avoid" 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ai-vue-next 2 | 3 | 实现 vue3 进行源码学习 4 | 5 | #### 实现的模块 6 | 7 | **reactivity 模块** 8 | 9 | - [x] reactive 10 | - [x] effect 11 | - [x] track 依赖收集 12 | - [x] trigger 依赖触发 13 | - [x] ref 14 | - [x] computed 15 | - [x] readonly 16 | - [x] 支持嵌套 reactive 17 | - [x] 支持 effectScheduler 18 | - [x] 支持 effect.stop 19 | - [x] 支持 isReactive 20 | - [x] 支持 isReadonly 21 | - [x] 支持 isProxy 22 | - [x] 支持 shallowReadonly 23 | - [x] 支持 isref 24 | - [x] 支持 unref 25 | - [x] 支持 proxyRefs 26 | 27 | **runtime-core 模块** 28 | 29 | - [x] 支持 element 类型 30 | - [x] 初始化 props 31 | - [x] setup 收集 props 和 context 32 | - [x] 支持 proxy 获取数据 33 | - [x] 实现挂载 rendecompiler 象 34 | - [x] 实现$el 35 | 36 | **runtime-dom 模块** 37 | 38 | - [x] 实现自定义渲染器 39 | - [x] 双端对比 diff 算法 40 | - [x] 实现组件更新功能 41 | - [x] 实现 nextTick 功能 42 | 43 | **compiler-core 模块** 44 | 45 | - [x] 实现解析插值功能 46 | - [x] 实现解析 interpolation/element/text 三种类型 47 | - [x] 实现 transform 功能 48 | - [x] 实现代码生成 interpolation/element/text 三种类型 49 | - [x] 实现 template 编译成 render 函数 50 | - [x] 实现 monorepo 架构+vitest 替换 jest 51 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // babel.config.js 2 | module.exports = { 3 | presets: [ 4 | [ 5 | '@babel/preset-env', {targets: {node: 'current'}} 6 | ], 7 | '@babel/preset-typescript', 8 | ], 9 | }; -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignores: [commit => commit.includes('init')], 3 | extends: ['@commitlint/config-conventional'], 4 | rules: { 5 | 'body-leading-blank': [2, 'always'], 6 | 'footer-leading-blank': [1, 'always'], 7 | 'header-max-length': [2, 'always', 1], 8 | 'subject-empty': [2, 'never'], 9 | 'type-empty': [2, 'never'], 10 | 'type-enum': [ 11 | 2, 12 | 'always', 13 | [ 14 | 'feat', 15 | 'fix', 16 | 'perf', 17 | 'style', 18 | 'docs', 19 | 'test', 20 | 'refactor', 21 | 'build', 22 | 'ci', 23 | 'chore', 24 | 'revert', 25 | 'wip', 26 | 'workflow', 27 | 'types', 28 | 'release' 29 | ] 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/ApiInject/App.js: -------------------------------------------------------------------------------- 1 | import { h, provide, inject } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'App', 5 | setup() {}, 6 | render() { 7 | return h('div', {}, [h('p', {}, 'ApiInject'), h(Provider)]) 8 | } 9 | } 10 | 11 | const Provider = { 12 | name: 'Provider', 13 | setup() { 14 | provide('foo', 'fooVal') 15 | provide('bar', 'barVal') 16 | // provide('baz', 'bazVal') 17 | }, 18 | render() { 19 | return h('div', {}, [h('p', {}, 'Provider'), h(ProviderTwo)]) 20 | } 21 | } 22 | 23 | const ProviderTwo = { 24 | name: 'ProviderTwo', 25 | setup() { 26 | provide('foo', 'fooTwo') 27 | const foo = inject('foo') 28 | 29 | return { 30 | foo 31 | } 32 | }, 33 | render() { 34 | return h('div', {}, [h('p', {}, `ProviderTwo: ${this.foo}`), h(Consumer)]) 35 | } 36 | } 37 | 38 | const Consumer = { 39 | name: 'Consumer', 40 | setup() { 41 | // provide('foo', 'fooTwo1') 42 | const foo = inject('foo') 43 | const bar = inject('bar') 44 | // const baz = inject('baz', 'bazDefault') 45 | const baz = inject('baz', () => 'bazDefault') 46 | 47 | return { 48 | foo, 49 | bar, 50 | baz 51 | } 52 | }, 53 | render() { 54 | return h('div', {}, `Consumer-${this.foo}-${this.bar}-${this.baz}`) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/ApiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/ApiInject/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /example/ComponentEvent/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import Foo from './Foo.js' 3 | 4 | export default { 5 | name: 'App', 6 | setup() {}, 7 | render() { 8 | return h( 9 | 'div', 10 | { 11 | class: 'app' 12 | }, 13 | [ 14 | h('p', {}, 'App组件'), 15 | h(Foo, { 16 | onAdd: (...args) => { 17 | console.log('App组件调用了onAdd方法', ...args) 18 | }, 19 | onAddFoo: (...args) => { 20 | console.log('App组件调用了onAddFoo方法', ...args) 21 | } 22 | }) 23 | ] 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/ComponentEvent/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'Foo', 5 | setup(props, { emit }) { 6 | const emitAdd = () => { 7 | console.log('emitAdd') 8 | emit('add', 1, 2) 9 | emit('add-foo', 3, 4) 10 | } 11 | 12 | return { 13 | emitAdd 14 | } 15 | }, 16 | render() { 17 | const emitBtn = h( 18 | 'button', 19 | { class: 'emit-btn', onClick: this.emitAdd }, 20 | 'emitBtn' 21 | ) 22 | const foo = h('p', {}, 'foo组件') 23 | return h('div', { class: 'foo' }, [foo, emitBtn]) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/ComponentEvent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/ComponentEvent/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /example/ComponentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { h, createTextVNode } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import Foo from './Foo.js' 3 | 4 | export default { 5 | name: 'App', 6 | setup() {}, 7 | render() { 8 | return h('div', {}, [ 9 | h('p', {}, 'App'), 10 | h( 11 | Foo, 12 | {}, 13 | // h('p', {class: 'custom-element1'}, 'slot-element'), 14 | { 15 | header: props => [ 16 | h( 17 | 'p', 18 | { class: 'custom-element1' }, 19 | 'slot-element-header' + props.name 20 | ), 21 | createTextVNode('我是textNode节点') 22 | ], 23 | footer: () => 24 | h('p', { class: 'custom-element1' }, 'slot-element-footer') 25 | } 26 | ) 27 | ]) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/ComponentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'Foo', 5 | setup() {}, 6 | render() { 7 | const name = '作用域插槽' 8 | return h('div', {}, [ 9 | renderSlots(this.$slots, 'header', { 10 | name 11 | }), 12 | h('p', {}, 'foo'), 13 | renderSlots(this.$slots, 'footer') 14 | ]) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/ComponentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/ComponentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /example/CurrentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstace } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import Foo from './Foo.js' 3 | 4 | export default { 5 | name: 'App', 6 | setup() { 7 | const instance = getCurrentInstace() 8 | console.log('App', instance) 9 | }, 10 | render() { 11 | return h('div', {}, [h('p', {}, 'app'), h(Foo)]) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/CurrentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstace } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'Foo', 5 | setup() { 6 | const instance = getCurrentInstace() 7 | console.log('Foo', instance) 8 | }, 9 | render() { 10 | return h('div', {}, 'Foo') 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/CurrentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/CurrentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /example/CustomRenderer/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'App', 5 | setup() { 6 | const x = 100 7 | const y = 100 8 | 9 | return { 10 | x, 11 | y 12 | } 13 | }, 14 | render() { 15 | return h('rect', { x: this.x, y: this.y }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/CustomRenderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/CustomRenderer/main.js: -------------------------------------------------------------------------------- 1 | import { createRenderer } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | // @ts-ignore 5 | const game = new PIXI.Application({ 6 | width: 500, 7 | height: 500 8 | }) 9 | document.body.append(game.view) 10 | 11 | const renderer = createRenderer({ 12 | createElement: tag => { 13 | if (tag === 'rect') { 14 | // @ts-ignore 15 | const rect = new PIXI.Graphics() 16 | rect.beginFill(0xff0000) 17 | rect.drawRect(0, 0, 100, 100) 18 | rect.endFill() 19 | return rect 20 | } 21 | }, 22 | patchProp: (el, key, value) => { 23 | el[key] = value 24 | }, 25 | insert: (el, parent) => { 26 | parent.addChild(el) 27 | } 28 | }) 29 | 30 | renderer.createApp(App).mount(game.stage) 31 | -------------------------------------------------------------------------------- /example/HelloWord/app.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import foo from './foo.js' 3 | // @ts-ignore 4 | window.self = null 5 | export default { 6 | render() { 7 | // @ts-ignore 8 | window.self = this 9 | return h( 10 | 'div', 11 | { 12 | class: ['vue-next'], 13 | onClick: () => { 14 | console.log('ai-vue-next') 15 | } 16 | }, 17 | [ 18 | h('p', { class: ['red', 'title'] }, 'hi'), 19 | h( 20 | 'p', 21 | { class: 'blue', onMousedown: () => console.log('mousedown') }, 22 | this.msg 23 | ), 24 | h(foo, { count: this.count }) 25 | ] 26 | ) 27 | }, 28 | setup() { 29 | return { 30 | msg: 'ai-vue-next', 31 | count: 1 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/HelloWord/foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'Foo', 5 | setup(props) { 6 | props.count++ 7 | console.log(props) 8 | }, 9 | render() { 10 | return h('div', { class: 'foo' }, `foo: ${this.count}`) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/HelloWord/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/HelloWord/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './app.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /example/PatchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import ArrayToText from './ArrayToText.js' 3 | import TextToArray from './TextToArray.js' 4 | import TextToText from './TextToText.js' 5 | import ArrayToArray from './ArrayToArray.js' 6 | 7 | export default { 8 | name: 'App', 9 | setup() {}, 10 | render() { 11 | return h('div', { id: 'root' }, [ 12 | h('p', {}, '主页'), 13 | // 老的是array 新的text 14 | // h(ArrayToText) 15 | 16 | // 老的是text 新的text 17 | // h(TextToText) 18 | 19 | // 老的是text 新的是Array 20 | // h(TextToArray) 21 | 22 | // 新老都是array 23 | h(ArrayToArray) 24 | ]) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/PatchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | // 新老节点都是 array 2 | 3 | import { h, ref, proxyRefs } from '../../lib/ai-vue-next.bundle.esm.js' 4 | 5 | // 1. 左侧的对比 6 | // (a b) c 7 | // (a b) d e 8 | // const prevChildren = [ 9 | // h('p', {key: 'A'}, 'A'), 10 | // h('p', {key: 'B'}, 'B'), 11 | // h('p', {key: 'C'}, 'C') 12 | // ] 13 | // const nextChildren = [ 14 | // h('p', {key: 'A'}, 'A'), 15 | // h('p', {key: 'B'}, 'B'), 16 | // h('p', {key: 'D'}, 'D'), 17 | // h('p', {key: 'E'}, 'E') 18 | // ] 19 | 20 | // 2. 右侧的对比 21 | // a (b c) 22 | // d e (b c) 23 | // const prevChildren = [ 24 | // h('p', {key: 'A'}, 'A'), 25 | // h('p', {key: 'B'}, 'B'), 26 | // h('p', {key: 'C'}, 'C') 27 | // ] 28 | // const nextChildren = [ 29 | // h('p', {key: 'D'}, 'D'), 30 | // h('p', {key: 'E'}, 'E'), 31 | // h('p', {key: 'B'}, 'B'), 32 | // h('p', {key: 'C'}, 'C') 33 | // ] 34 | 35 | // 3. 普通序列+挂载 36 | // (a b) 37 | // (a b) c d e 38 | // const prevChildren = [ 39 | // h('p', {key: 'A'}, 'A'), 40 | // h('p', {key: 'B'}, 'B') 41 | // ] 42 | // const nextChildren = [ 43 | // h('p', {key: 'A'}, 'A'), 44 | // h('p', {key: 'B'}, 'B'), 45 | // h('p', {key: 'C'}, 'C'), 46 | // h('p', {key: 'D'}, 'D'), 47 | // h('p', {key: 'E'}, 'E'), 48 | // ] 49 | // (a b) 50 | // c d e (a b) 51 | // const prevChildren = [ 52 | // h('p', {key: 'A'}, 'A'), 53 | // h('p', {key: 'B'}, 'B') 54 | // ] 55 | // const nextChildren = [ 56 | // h('p', {key: 'C'}, 'C'), 57 | // h('p', {key: 'D'}, 'D'), 58 | // h('p', {key: 'E'}, 'E'), 59 | // h('p', {key: 'A'}, 'A'), 60 | // h('p', {key: 'B'}, 'B'), 61 | // ] 62 | 63 | // 3. 普通序列+卸载 64 | // (a b) c d e 65 | // (a b) 66 | // const prevChildren = [ 67 | // h('p', {key: 'A'}, 'A'), 68 | // h('p', {key: 'B'}, 'B'), 69 | // h('p', {key: 'C'}, 'C'), 70 | // h('p', {key: 'D'}, 'D'), 71 | // h('p', {key: 'E'}, 'E'), 72 | // ] 73 | // const nextChildren = [ 74 | // h('p', {key: 'A'}, 'A'), 75 | // h('p', {key: 'B'}, 'B'), 76 | // ] 77 | 78 | // a d e (b c) 79 | // (b c) 80 | // const prevChildren = [ 81 | // h('p', {key: 'A'}, 'A'), 82 | // h('p', {key: 'D'}, 'D'), 83 | // h('p', {key: 'E'}, 'E'), 84 | // h('p', {key: 'B'}, 'B'), 85 | // h('p', {key: 'C'}, 'C'), 86 | // ] 87 | // const nextChildren = [ 88 | // h('p', {key: 'B'}, 'B'), 89 | // h('p', {key: 'C'}, 'C'), 90 | // ] 91 | 92 | // 5. 未知序列 93 | // [i ... e1 + 1]: a b [c d e] f g 94 | // [i ... e2 + 1]: a b [e c h] f g 95 | // D 在新节点中是没有的 会被卸载 96 | // C 节点的props会发生变化 97 | // const prevChildren = [ 98 | // h('p', {key: 'A'}, 'A'), 99 | // h('p', {key: 'B'}, 'B'), 100 | // h('p', {key: 'C'}, 'C'), 101 | // h('p', {key: 'D'}, 'D'), 102 | // h('p', {key: 'E'}, 'E'), 103 | // h('p', {key: 'F'}, 'F'), 104 | // h('p', {key: 'G'}, 'G'), 105 | // ] 106 | // const nextChildren = [ 107 | // h('p', {key: 'A'}, 'A'), 108 | // h('p', {key: 'B'}, 'B'), 109 | // h('p', {key: 'E'}, 'E'), 110 | // h('p', {key: 'C', id: 'c-id'}, 'C'), 111 | // h('p', {key: 'H'}, 'H'), 112 | // h('p', {key: 'F'}, 'F'), 113 | // h('p', {key: 'G'}, 'G'), 114 | // ] 115 | 116 | // [i ... e1 + 1]: a b [c d e h i] f g 117 | // [i ... e2 + 1]: a b [e c] f g 118 | // 中间部分 老的比心的多 那么多的部分会被直接卸载 119 | // const prevChildren = [ 120 | // h('p', { key: 'A' }, 'A'), 121 | // h('p', { key: 'B' }, 'B'), 122 | // h('p', { key: 'C' }, 'C'), 123 | // h('p', { key: 'D' }, 'D'), 124 | // h('p', { key: 'E' }, 'E'), 125 | // h('p', { key: 'H' }, 'H'), 126 | // h('p', { key: 'I' }, 'I'), 127 | // h('p', { key: 'F' }, 'F'), 128 | // h('p', { key: 'G' }, 'G') 129 | // ] 130 | // const nextChildren = [ 131 | // h('p', { key: 'A' }, 'A'), 132 | // h('p', { key: 'B' }, 'B'), 133 | // h('p', { key: 'E' }, 'E'), 134 | // h('p', { key: 'C', id: 'c-id' }, 'C'), 135 | // h('p', { key: 'F' }, 'F'), 136 | // h('p', { key: 'G' }, 'G') 137 | // ] 138 | 139 | // [i ... e1 + 1]: a b [c d e] f g 140 | // [i ... e2 + 1]: a b [e c d] f g 141 | // 移动 节点存在于新旧节点中,但是位置变了 142 | // 最长递增子序列为[1, 2] 即[e c d]中c d的下标 c d不要移动 只需将e插入到c前面即可 移动一次 143 | // const prevChildren = [ 144 | // h('p', { key: 'A' }, 'A'), 145 | // h('p', { key: 'B' }, 'B'), 146 | // h('p', { key: 'C' }, 'C'), 147 | // h('p', { key: 'D' }, 'D'), 148 | // h('p', { key: 'E' }, 'E'), 149 | // h('p', { key: 'F' }, 'F'), 150 | // h('p', { key: 'G' }, 'G') 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 | // [i ... e1 + 1]: a b [d] f g 163 | // [i ... e2 + 1]: a b [c d e] f g 164 | // 新增 同时也体现了源码的move优化点 165 | // const prevChildren = [ 166 | // h('p', { key: 'A' }, 'A'), 167 | // h('p', { key: 'B' }, 'B'), 168 | // h('p', { key: 'D' }, 'D'), 169 | // h('p', { key: 'F' }, 'F'), 170 | // h('p', { key: 'G' }, 'G') 171 | // ] 172 | // const nextChildren = [ 173 | // h('p', { key: 'A' }, 'A'), 174 | // h('p', { key: 'B' }, 'B'), 175 | // h('p', { key: 'C' }, 'C'), 176 | // h('p', { key: 'D' }, 'D'), 177 | // h('p', { key: 'E' }, 'E'), 178 | // h('p', { key: 'F' }, 'F'), 179 | // h('p', { key: 'G' }, 'G') 180 | // ] 181 | 182 | // 综合例子 183 | // a b (c d e x) f g 184 | // a b (d c y e) f g 185 | const prevChildren = [ 186 | h('p', { key: 'A' }, 'A'), 187 | h('p', { key: 'B' }, 'B'), 188 | h('p', { key: 'C' }, 'C'), 189 | h('p', { key: 'D' }, 'D'), 190 | h('p', { key: 'E' }, 'E'), 191 | h('p', { key: 'X' }, 'X'), 192 | h('p', { key: 'F' }, 'F'), 193 | h('p', { key: 'G' }, 'G') 194 | ] 195 | const nextChildren = [ 196 | h('p', { key: 'A' }, 'A'), 197 | h('p', { key: 'B' }, 'B'), 198 | h('p', { key: 'D' }, 'D'), 199 | h('p', { key: 'C' }, 'C'), 200 | h('p', { key: 'Y' }, 'Y'), 201 | h('p', { key: 'F' }, 'F'), 202 | h('p', { key: 'G' }, 'G') 203 | ] 204 | 205 | // 新节点 text 206 | export default { 207 | name: 'ArrayToArray', 208 | setup() { 209 | const isChange = ref(false) 210 | // @ts-ignore 211 | window.isChange = isChange 212 | 213 | const obj = proxyRefs({ 214 | a: { 215 | b: { 216 | c: 1 217 | } 218 | } 219 | }) 220 | console.log(obj.a.b.c) 221 | return { 222 | isChange 223 | } 224 | }, 225 | render() { 226 | return h('div', {}, this.isChange ? nextChildren : prevChildren) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /example/PatchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | // 老节点 array 2 | 3 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 4 | 5 | // 新节点 text 6 | export default { 7 | name: 'ArrayToText', 8 | setup() { 9 | const isChange = ref(false) 10 | // @ts-ignore 11 | window.isChange = isChange 12 | 13 | const obj = ref({ 14 | a: ref({ 15 | b: ref({ 16 | c: 1 17 | }) 18 | }) 19 | }) 20 | console.log(obj.value.a.value) 21 | return { 22 | isChange 23 | } 24 | }, 25 | render() { 26 | const nextChildren = 'newChildren' 27 | const prevChildren = [h('p', {}, 'A'), h('p', {}, 'B')] 28 | return h('div', {}, this.isChange ? nextChildren : prevChildren) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/PatchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | // 老节点是text 2 | // 新节点是array 3 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 4 | 5 | // 新节点 text 6 | export default { 7 | name: 'ArrayToText', 8 | setup() { 9 | const isChange = ref(false) 10 | // @ts-ignore 11 | window.isChange = isChange 12 | 13 | const obj = ref({ 14 | a: ref({ 15 | b: ref({ 16 | c: 1 17 | }) 18 | }) 19 | }) 20 | console.log(obj.value.a.b) 21 | return { 22 | isChange 23 | } 24 | }, 25 | render() { 26 | const prevChildren = 'oldChildren' 27 | const nextChildren = [h('p', {}, 'A'), h('p', {}, 'B')] 28 | return h('div', {}, this.isChange ? nextChildren : prevChildren) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/PatchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | // 老节点Text 2 | 3 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 4 | 5 | // 新节点Text 6 | export default { 7 | name: 'TextToText', 8 | setup() { 9 | const isChange = ref(false) 10 | 11 | // @ts-ignore 12 | window.isChange = isChange 13 | 14 | return { 15 | isChange 16 | } 17 | }, 18 | render() { 19 | const oldChild = 'oldChild' 20 | const newChild = 'newChild' 21 | return h('div', {}, this.isChange ? newChild : oldChild) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/PatchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/PatchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | 6 | function getSequence(arr) { 7 | const p = arr.slice() 8 | const result = [0] 9 | let i, j, u, v, c 10 | const len = arr.length 11 | // 遍历数组 12 | debugger 13 | for (i = 0; i < len; i++) { 14 | const arrI = arr[i] 15 | // 此算法中排除了等于0的情况,原因是0成为了diff算法中的占位符,在上面的流程图中已经忽略了,不影响对算法的了解 16 | if (arrI !== 0) { 17 | j = result[result.length - 1] 18 | // 用当前num与result中的最后一项对比 19 | if (arr[j] < arrI) { 20 | // 当前数值大于result子序列最后一项时,直接往后新增,并将当前数值的前一位result保存 21 | p[i] = j 22 | result.push(i) 23 | continue 24 | } 25 | u = 0 26 | v = result.length - 1 27 | // 当前数值小于result子序列最后一项时,使用二分法找到第一个大于当前数值的下标 28 | while (u < v) { 29 | c = ((u + v) / 2) | 0 30 | if (arr[result[c]] < arrI) { 31 | u = c + 1 32 | } else { 33 | v = c 34 | } 35 | } 36 | if (arrI < arr[result[u]]) { 37 | // 找到下标,将当前下标对应的前一位result保存(如果找到的是第一位,不需要操作,第一位前面没有了) 38 | if (u > 0) { 39 | p[i] = result[u - 1] 40 | } 41 | // 找到下标,直接替换result中的数值 42 | result[u] = i 43 | } 44 | } 45 | } 46 | u = result.length 47 | v = result[u - 1] 48 | // 回溯,直接从最后一位开始,将前面的result全部覆盖,如果不需要修正,则p中记录的每一项都是对应的前一位,不会有任何影响 49 | while (u-- > 0) { 50 | result[u] = v 51 | v = p[v] 52 | } 53 | return result 54 | } 55 | 56 | getSequence([10, 9, 2, 5, 3, 7, 101, 18, 1]) 57 | -------------------------------------------------------------------------------- /example/Update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'App', 5 | setup() { 6 | const props = ref({ 7 | foo: 'foo', 8 | bar: 'bar' 9 | }) 10 | 11 | const onChangePropsDemo1 = () => { 12 | props.value.foo = 'new-foo' 13 | } 14 | 15 | const onChangePropsDemo2 = () => { 16 | props.value.foo = undefined 17 | } 18 | 19 | const onChangePropsDemo3 = () => { 20 | props.value = { 21 | foo: 'foo' 22 | } 23 | } 24 | 25 | return { 26 | props, 27 | onChangePropsDemo1, 28 | onChangePropsDemo2, 29 | onChangePropsDemo3 30 | } 31 | }, 32 | render() { 33 | return h('div', { id: 'root', ...this.props }, [ 34 | h( 35 | 'button', 36 | { onClick: this.onChangePropsDemo1 }, 37 | '改变props.foo为new-foo' 38 | ), 39 | h( 40 | 'button', 41 | { onClick: this.onChangePropsDemo2 }, 42 | '改变props.foo为undefined' 43 | ), 44 | h('button', { onClick: this.onChangePropsDemo3 }, '删除props.bar') 45 | ]) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/Update/App1.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'App', 5 | setup() { 6 | const count = ref(0) 7 | const clickHandle = () => { 8 | count.value++ 9 | } 10 | 11 | return { 12 | count, 13 | clickHandle 14 | } 15 | }, 16 | render() { 17 | return h('div', { id: 'root' }, [ 18 | h('p', {}, `count: ${this.count}`), 19 | h('button', { onClick: this.clickHandle }, '点击+1') 20 | ]) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/Update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/Update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /example/compiler-base/App.js: -------------------------------------------------------------------------------- 1 | import { ref } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export const App = { 4 | name: 'App', 5 | template: '
hi,{{count}}
', 6 | setup() { 7 | const count = (window.count = ref(0)) 8 | return { 9 | message: 'mini-vue123', 10 | count 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/compiler-base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/compiler-base/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import { App } from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /example/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import Child from './Child.js' 3 | 4 | const App = { 5 | name: 'App', 6 | setup() { 7 | const msg = ref('123') 8 | const count = ref(1) 9 | 10 | // @ts-ignore 11 | window.msg = msg 12 | 13 | const changeChildProps = () => { 14 | msg.value = '456' 15 | } 16 | 17 | const changeCount = () => { 18 | count.value++ 19 | } 20 | 21 | return { 22 | msg, 23 | count, 24 | changeChildProps, 25 | changeCount 26 | } 27 | }, 28 | render() { 29 | return h('div', {}, [ 30 | h('div', {}, '您好'), 31 | 32 | h( 33 | 'button', 34 | { 35 | onClick: this.changeChildProps 36 | }, 37 | 'change child props' 38 | ), 39 | 40 | h(Child, { 41 | msg: this.msg 42 | }), 43 | 44 | h( 45 | 'button', 46 | { 47 | onClick: this.changeCount 48 | }, 49 | 'change self count' 50 | ), 51 | 52 | h('p', {}, 'count: ' + this.count) 53 | ]) 54 | } 55 | } 56 | export default App 57 | -------------------------------------------------------------------------------- /example/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'Child', 5 | setup() {}, 6 | render() { 7 | return h('div', {}, [ 8 | h('div', {}, 'child - props -msg: ' + this.$props.msg) 9 | ]) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/componentUpdate/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /example/nextTick/App.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | ref, 4 | getCurrentInstace, 5 | nextTick 6 | } from '../../lib/ai-vue-next.bundle.esm.js' 7 | 8 | export default { 9 | name: 'App', 10 | setup() { 11 | const count = ref(1) 12 | const instance = getCurrentInstace() 13 | async function onClick() { 14 | for (let i = 0; i < 10; i++) { 15 | console.log('update--' + i) 16 | count.value = i 17 | } 18 | debugger 19 | console.log(instance) 20 | // nextTick(() => { 21 | // console.log(instance) 22 | // }) 23 | await nextTick() 24 | console.log(instance) 25 | } 26 | 27 | return { 28 | count, 29 | onClick 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 | -------------------------------------------------------------------------------- /example/nextTick/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/nextTick/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /lib/ai-vue-next.bundle.esm.js: -------------------------------------------------------------------------------- 1 | function toDisplayString(value) { 2 | return String(value) 3 | } 4 | 5 | const extend = Object.assign 6 | const EMPTY_OBJ = {} 7 | const isString = value => typeof value === 'string' 8 | const isObject = value => typeof value === 'object' && value !== null 9 | const hasChanged = (value, oldValue) => !Object.is(value, oldValue) 10 | const isFunction = val => typeof val === 'function' 11 | const isArray = Array.isArray 12 | const isOn = val => /^on[A-Z]/.test(val) 13 | const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key) 14 | // add => Add 15 | const capitalize = val => val.charAt(0).toUpperCase() + val.slice(1) 16 | // add-foo => addFoo 17 | const camelize = val => 18 | val.replace(/-(\w)/g, (_, c) => { 19 | return c ? c.toUpperCase() : '' 20 | }) 21 | const toHandlerKey = val => 'on' + val 22 | 23 | const Fragment = Symbol('Fragment') 24 | const Text = Symbol('Text') 25 | function createVNode(type, props, children) { 26 | const vnode = { 27 | type, 28 | key: props === null || props === void 0 ? void 0 : props.key, 29 | props, 30 | children, 31 | component: null, 32 | el: null, 33 | shapeFlag: getShapeFlag(type) 34 | } 35 | if (typeof children === 'string') { 36 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */ 37 | } else if (isArray(children)) { 38 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */ 39 | } 40 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */ && isObject(children)) { 41 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */ 42 | } 43 | return vnode 44 | } 45 | function createTextVNode(text) { 46 | return createVNode(Text, {}, text) 47 | } 48 | function isSameVNodeType(n1, n2) { 49 | return n1.type === n2.type && n1.key === n2.key 50 | } 51 | function getShapeFlag(type) { 52 | if (isObject(type)) { 53 | return 2 /* STATEFUL_COMPONENT */ 54 | } else { 55 | return 1 /* ELEMENT */ 56 | } 57 | } 58 | 59 | function h(type, props, children) { 60 | return createVNode(type, props, children) 61 | } 62 | 63 | function renderSlots(slots, name, props) { 64 | const slot = slots[name] 65 | if (slot) { 66 | if (isFunction(slot)) { 67 | return slot ? createVNode(Fragment, {}, slot(props)) : '' 68 | } 69 | } 70 | } 71 | 72 | const targetMap = new WeakMap() 73 | let activeEffect 74 | class ReactiveEffect { 75 | constructor(fn, scheduler = null) { 76 | this.fn = fn 77 | this.scheduler = scheduler 78 | this.deps = [] 79 | this.active = true 80 | this.fn = fn 81 | } 82 | run() { 83 | if (!this.active) { 84 | // 当使用stop(effect)后,我们在去执行runner就是执行run方法,因为进行了stop所以要在这里返回this.fn() 不能让activeEffect在赋值,不然会继续收集依赖 85 | return this.fn() 86 | } 87 | try { 88 | activeEffect = this 89 | return this.fn() 90 | } finally { 91 | // 当fn执行完之后要把activeEffect清空 如果不清除下次访问属性的时候会把之前的fn收集进去 92 | activeEffect = undefined 93 | } 94 | } 95 | stop() { 96 | if (this.active) { 97 | cleanupEffect(this) 98 | if (this.onStop) { 99 | this.onStop() 100 | } 101 | this.active = false 102 | } 103 | } 104 | } 105 | function cleanupEffect(effect) { 106 | const { deps } = effect 107 | if (deps.length) { 108 | for (let i = 0; i < deps.length; i++) { 109 | deps[i].delete(effect) 110 | } 111 | // 这里将deps.length设置为0是优化 112 | // 原本删除数组下标每一个set集合的成员,deps.length长度不变 113 | // 这里作用是将每一个key的set集合中存放的当前effect删除 即属性值更新不会执行effect函数 114 | // 当把每一个key的当前effect删除掉之后 就认为依赖清除完了,下次没必要再执行一遍 115 | deps.length = 0 116 | } 117 | } 118 | // { 119 | // target: { // new WeakMap 120 | // key: { // new Map() 121 | // dep: new Set() 122 | // } 123 | // } 124 | // } 125 | function track(target, key) { 126 | let depsMap = targetMap.get(target) 127 | if (!depsMap) { 128 | targetMap.set(target, (depsMap = new Map())) 129 | } 130 | let dep = depsMap.get(key) 131 | if (!dep) { 132 | depsMap.set(key, (dep = new Set())) 133 | } 134 | trackEffects(dep) 135 | } 136 | function trackEffects(dep) { 137 | // 这里的判断 activeEffect存在时才会收集依赖 因为每次属性被访问都会出发track函数 比如 a=obj.b也会触发 138 | if (isTracking()) { 139 | dep.add(activeEffect) 140 | // 用户写的每一个effect函数都会new一个新的effect对象 里面各自有自己的deps 141 | // dep里存放的是每个对象key的effect回调函数fn 142 | // deps里面是当前effect回调函数里面所有被访问key的回调函数set集合(dep)的数组 143 | // egg: effect(() => { sum = obj.a + user.name + foo.b}) 144 | // 那么当执行用户写的这个effect时候会new一个effect对象 并有一个deps=[]的属性 145 | // 然后将关于a的set集合,name的set集合,b的set集合全部放入deps 146 | // 此时deps里面就包含了用户写的effect函数中所被访问到的所有对象属性的set集合 147 | // obj.a user.name foo.b每一个属性更新都会执行effect 148 | // 也就是他们的set集合中都会有一个相同的effect即当前的effect对象 149 | // 上面stop方法就是将当前effect中所有依赖属性的set集合中删除当前effect 这样当obj.a user.name foo.b更新时因为effect被删除 所以就不会执行了 150 | activeEffect.deps.push(dep) 151 | } 152 | } 153 | function trigger(target, key) { 154 | const depsMap = targetMap.get(target) 155 | if (!depsMap) { 156 | // never been tracked 157 | return 158 | } 159 | const dep = depsMap.get(key) 160 | triggerEffects(dep) 161 | } 162 | function triggerEffects(dep) { 163 | for (const effect of dep) { 164 | if (effect.scheduler) { 165 | effect.scheduler() 166 | } else { 167 | effect.run() 168 | } 169 | } 170 | } 171 | function isTracking() { 172 | return activeEffect !== undefined 173 | } 174 | function effect(fn, options) { 175 | const _effect = new ReactiveEffect(fn) 176 | options && extend(_effect, options) 177 | _effect.run() 178 | const runner = _effect.run.bind(_effect) 179 | runner.effect = _effect 180 | return runner 181 | } 182 | 183 | const get = createGetter() 184 | const set = createSetter() 185 | const readonlyGet = createGetter(true) 186 | const shallowReadonlyGet = createGetter(true, true) 187 | function createGetter(isReadonly = false, shallow = false) { 188 | return function get(target, key) { 189 | if (key === '__v_isReactive' /* IS_REACTIVE */) { 190 | // 注意:这里返回的是!isReadonly而不是true的原因是 191 | // 当对象是readonly的时候就不是reactive 192 | return !isReadonly 193 | } else if (key === '__v_isReadonly' /* IS_READONLY */) { 194 | // 注意:这里返回的是isReadonly而不是true的原因是 195 | // 当对象是reactive的时候就不是readonly 196 | return isReadonly 197 | } 198 | const res = Reflect.get(target, key) 199 | if (!isReadonly) { 200 | track(target, key) 201 | } 202 | if (shallow) { 203 | return res 204 | } 205 | if (isObject(res)) { 206 | return isReadonly ? readonly(res) : reactive(res) 207 | } 208 | console.log('触发了getter', key, res) 209 | return res 210 | } 211 | } 212 | function createSetter() { 213 | return function set(target, key, value) { 214 | const res = Reflect.set(target, key, value) 215 | trigger(target, key) 216 | return res 217 | } 218 | } 219 | const mutableHandlers = { 220 | get, 221 | set 222 | } 223 | const readonlyHandlers = { 224 | get: readonlyGet, 225 | set(target, key) { 226 | console.warn( 227 | `Set operation on key "${String(key)}" failed: target is readonly.`, 228 | target 229 | ) 230 | return true 231 | } 232 | } 233 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 234 | get: shallowReadonlyGet 235 | }) 236 | 237 | function reactive(raw) { 238 | return createReactiveObject(raw, mutableHandlers) 239 | } 240 | function readonly(raw) { 241 | return createReactiveObject(raw, readonlyHandlers) 242 | } 243 | function shallowReadonly(raw) { 244 | return createReactiveObject(raw, shallowReadonlyHandlers) 245 | } 246 | function createReactiveObject(target, baseHandles) { 247 | if (!isObject(target)) { 248 | console.warn(`target ${target} must be is Object`) 249 | return 250 | } 251 | return new Proxy(target, baseHandles) 252 | } 253 | 254 | class RefImpl { 255 | constructor(value) { 256 | this.__v_isRef = true 257 | this._value = isObject(value) ? reactive(value) : value 258 | // 存一下原生的值 有可能是被代理过得对象 用于下面set的时候看是否变化 259 | this._rawValue = value 260 | this.dep = new Set() 261 | } 262 | get value() { 263 | trackRefValue(this) 264 | return this._value 265 | } 266 | set value(newVal) { 267 | if (hasChanged(newVal, this._rawValue)) { 268 | this._rawValue = newVal 269 | this._value = isObject(newVal) ? reactive(newVal) : newVal 270 | triggerRefValue(this) 271 | } 272 | } 273 | } 274 | function trackRefValue(ref) { 275 | if (isTracking()) { 276 | trackEffects(ref.dep) 277 | } 278 | } 279 | function triggerRefValue(ref) { 280 | triggerEffects(ref.dep) 281 | } 282 | function ref(value) { 283 | return new RefImpl(value) 284 | } 285 | function isRef(r) { 286 | return Boolean(r && r.__v_isRef) 287 | } 288 | function unref(r) { 289 | return isRef(r) ? r.value : r 290 | } 291 | function proxyRefs(objectWithRefs) { 292 | return new Proxy(objectWithRefs, { 293 | get(target, key) { 294 | return unref(Reflect.get(target, key)) 295 | }, 296 | set(target, key, value) { 297 | const oldValue = target[key] 298 | if (isRef(oldValue) && !isRef(value)) { 299 | oldValue.value = value 300 | return true 301 | } else { 302 | return Reflect.set(target, key, value) 303 | } 304 | } 305 | }) 306 | } 307 | 308 | function emit(instance, event, ...args) { 309 | const { props } = instance 310 | event = toHandlerKey(camelize(capitalize(event))) 311 | const handle = props[event] 312 | handle && handle(...args) 313 | } 314 | 315 | function initProps(instance, rawProps) { 316 | instance.props = rawProps || {} 317 | } 318 | 319 | const publicPropertiesMap = { 320 | $el: i => i.vnode.el, 321 | $slots: i => i.slots, 322 | $props: i => i.props 323 | } 324 | const PublicInstanceProxyHandlers = { 325 | get({ _: instance }, key) { 326 | const { setupState, props } = instance 327 | if (hasOwn(setupState, key)) { 328 | return setupState[key] 329 | } else if (hasOwn(props, key)) { 330 | return props[key] 331 | } 332 | const publicGetter = publicPropertiesMap[key] 333 | if (publicGetter) { 334 | return publicGetter(instance) 335 | } 336 | } 337 | } 338 | 339 | function initSlots(instance, children) { 340 | const { shapeFlag } = instance.vnode 341 | if (shapeFlag & 16 /* SLOT_CHILDREN */) { 342 | normalizeObjectSlots(children, instance) 343 | } 344 | } 345 | function normalizeObjectSlots(children, instance) { 346 | const slots = {} 347 | for (const name in children) { 348 | if (hasOwn(children, name)) { 349 | const slot = children[name] 350 | slots[name] = props => normalizeSlotValue(slot(props)) 351 | } 352 | } 353 | instance.slots = slots 354 | } 355 | function normalizeSlotValue(value) { 356 | return isArray(value) ? value : [value] 357 | } 358 | 359 | let currentInstance = null 360 | function createComponentInstance(vnode, parent) { 361 | const component = { 362 | vnode, 363 | type: vnode.type, 364 | setupState: {}, 365 | props: {}, 366 | slots: {}, 367 | next: null, 368 | provides: parent ? parent.provides : {}, 369 | parent, 370 | isMounted: false, 371 | subTree: {}, 372 | emit: () => {} 373 | } 374 | component.emit = emit.bind(null, component) 375 | // console.log('createComponentInstance', parent, component) 376 | return component 377 | } 378 | function setupComponent(instance) { 379 | // TODO: 380 | initProps(instance, instance.vnode.props) 381 | initSlots(instance, instance.vnode.children) 382 | setupStatefulComponent(instance) 383 | } 384 | function setupStatefulComponent(instance) { 385 | const Component = instance.type 386 | // 声依永proxy将setupState挂在到组建实例上 387 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers) 388 | // call setup() 389 | const { setup } = Component 390 | if (setup) { 391 | setCurrentInstance(instance) 392 | const setupResult = setup(shallowReadonly(instance.props), { 393 | emit: instance.emit 394 | }) 395 | setCurrentInstance(null) 396 | handleSetupResult(instance, setupResult) 397 | } 398 | } 399 | function handleSetupResult(instance, setupResult) { 400 | if (isFunction(setupResult)); 401 | else if (isObject(setupResult)) { 402 | instance.setupState = proxyRefs(setupResult) 403 | } 404 | finishComponentSetup(instance) 405 | } 406 | function finishComponentSetup(instance) { 407 | const Component = instance.type 408 | if (compiler && !Component.render) { 409 | if (Component.template) { 410 | Component.render = compiler(Component.template) 411 | } 412 | } 413 | if (Component.render) { 414 | instance.render = Component.render 415 | } 416 | } 417 | function setCurrentInstance(instance) { 418 | currentInstance = instance 419 | } 420 | function getCurrentInstace() { 421 | return currentInstance 422 | } 423 | let compiler 424 | function registerRuntimeCompiler(_compiler) { 425 | compiler = _compiler 426 | } 427 | 428 | function provide(key, value) { 429 | const currentInstance = getCurrentInstace() 430 | debugger 431 | if (currentInstance) { 432 | let { provides } = currentInstance 433 | const parentProvides = currentInstance.parent.provides 434 | // 只有初始化的时候才为provides改变原型 435 | // 否则每次provide执行的时候之前赋值全被清空了 436 | if (provides === parentProvides) { 437 | // 将自己的provides的原型改为父级的provides原型对象 438 | provides = currentInstance.provides = Object.create(parentProvides) 439 | } 440 | provides[key] = value 441 | } 442 | } 443 | function inject(key, defaultValue) { 444 | const currentInstance = getCurrentInstace() 445 | if (currentInstance) { 446 | // inject每次都是去父级上的provides上找 如果找不到就接着向上找 447 | const { provides } = currentInstance.parent 448 | // 先去原型链上看看有没有key 有的滑直接取 没有的滑看看是否给有默认值 并返回 449 | if (key in provides) { 450 | return provides[key] 451 | } else if (defaultValue) { 452 | // 支持默认值是函数 453 | if (isFunction(defaultValue)) { 454 | return defaultValue() 455 | } 456 | return defaultValue 457 | } 458 | } 459 | } 460 | 461 | function createAppAPI(render) { 462 | return function createApp(rootComponent) { 463 | return { 464 | mount(rootContainer) { 465 | // 生成vnode 466 | const vnode = createVNode(rootComponent) 467 | // 渲染vnode 468 | render(vnode, rootContainer) 469 | } 470 | } 471 | } 472 | } 473 | 474 | function shouldUpdateComponent(prevVNode, nextVNode) { 475 | const { props: prevProps } = prevVNode 476 | const { props: nextProps } = nextVNode 477 | for (const key in nextProps) { 478 | if (nextProps[key] !== prevProps[key]) { 479 | return true 480 | } 481 | } 482 | return false 483 | } 484 | 485 | const p = Promise.resolve() 486 | const queue = [] 487 | let isFlushPending = false 488 | function queueJobs(job) { 489 | if (!queue.includes(job)) { 490 | queue.push(job) 491 | } 492 | queueFlush() 493 | } 494 | function nextTick(fn) { 495 | typeof fn ? p.then(fn) : p 496 | } 497 | function queueFlush() { 498 | if (isFlushPending) { 499 | return 500 | } 501 | isFlushPending = true 502 | nextTick(flushJobs) 503 | } 504 | function flushJobs() { 505 | isFlushPending = false 506 | let job 507 | while ((job = queue.shift())) { 508 | job && job() 509 | } 510 | } 511 | 512 | function createRenderer(options) { 513 | const { 514 | createElement: hostCreateElement, 515 | patchProp: hostPatchProp, 516 | insert: hostInsert, 517 | remove: hostRemove, 518 | setElementText: hostSetElementText 519 | } = options 520 | function render(vnode, container) { 521 | patch(null, vnode, container, null, null) 522 | } 523 | function patch(n1, n2, container, parentComponent, anchor) { 524 | // 处理组件 525 | const { type, shapeFlag } = n2 526 | switch (type) { 527 | case Fragment: 528 | processFragment(n1, n2, container, parentComponent, anchor) 529 | break 530 | case Text: 531 | processText(n1, n2, container) 532 | break 533 | default: 534 | if (shapeFlag & 2 /* STATEFUL_COMPONENT */) { 535 | processComponent(n1, n2, container, parentComponent, anchor) 536 | } else if (shapeFlag & 1 /* ELEMENT */) { 537 | processElement(n1, n2, container, parentComponent, anchor) 538 | } 539 | break 540 | } 541 | } 542 | function processFragment(n1, n2, container, parentComponent, anchor) { 543 | mountChildren(n2.children, container, parentComponent, anchor) 544 | } 545 | function processText(n1, n2, container) { 546 | const text = (n2.el = document.createTextNode(n2.children)) 547 | container.append(text) 548 | } 549 | function processComponent(n1, n2, container, parentComponent, anchor) { 550 | if (!n1) { 551 | mountComponent(n2, container, parentComponent, anchor) 552 | } else { 553 | updateComponent(n1, n2) 554 | } 555 | } 556 | function processElement(n1, n2, container, parentComponent, anchor) { 557 | if (!n1) { 558 | mountElement(n2, container, parentComponent, anchor) 559 | } else { 560 | patchElement(n1, n2, container, parentComponent, anchor) 561 | } 562 | } 563 | // mount 564 | function mountComponent(initialVNode, container, parentComponent, anchor) { 565 | const instance = (initialVNode.component = createComponentInstance( 566 | initialVNode, 567 | parentComponent 568 | )) 569 | setupComponent(instance) 570 | setupRenderEffect(instance, initialVNode, container, anchor) 571 | } 572 | function mountElement(n2, container, parentComponent, anchor) { 573 | const { type, props, children, shapeFlag } = n2 574 | // 生成标签 575 | // const el = (vnode.el = document.createElement(type)) 576 | const el = (n2.el = hostCreateElement(type)) 577 | // 生成属性 578 | for (const key in props) { 579 | if (hasOwn(props, key)) { 580 | const value = props[key] 581 | hostPatchProp(el, key, null, value) 582 | } 583 | } 584 | // 生成子节点 585 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 586 | // 文本节点 587 | el.textContent = children 588 | } else if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 589 | mountChildren(children, el, parentComponent, anchor) 590 | } 591 | // container.append(el) 592 | hostInsert(el, container, anchor) 593 | } 594 | function mountChildren(children, container, parentComponent, anchor) { 595 | children.forEach(vnode => { 596 | patch(null, vnode, container, parentComponent, anchor) 597 | }) 598 | } 599 | // update component 600 | function updateComponent(n1, n2) { 601 | // 每次属性更新 都会触发patch 如果不是props更新就没必要更新组件 602 | const instance = (n2.component = n1.component) 603 | if (shouldUpdateComponent(n1, n2)) { 604 | instance.next = n2 605 | instance.update() 606 | } else { 607 | // 不需要更新的时候 需要把n1.el 给到n2 608 | n2.el = n1.el 609 | n2.vnode = n2 610 | } 611 | } 612 | // patch 613 | function patchElement(n1, n2, container, parentComponent, anchor) { 614 | console.log('patchElement') 615 | console.log('n1', n1) 616 | console.log('n2', n2) 617 | const el = (n2.el = n1.el) 618 | const prevProps = n1.props || EMPTY_OBJ 619 | const nextProps = n2.props || EMPTY_OBJ 620 | patchChildren(n1, n2, el, parentComponent, anchor) 621 | patchProp(el, prevProps, nextProps) 622 | } 623 | function patchProp(el, oldProps, newProps) { 624 | if (oldProps !== newProps) { 625 | for (const key in newProps) { 626 | const next = newProps[key] 627 | const prev = oldProps[key] 628 | if (next !== prev) { 629 | hostPatchProp(el, key, prev, next) 630 | } 631 | } 632 | if (oldProps !== EMPTY_OBJ) { 633 | for (const key in oldProps) { 634 | if (!(key in newProps)) { 635 | hostPatchProp(el, key, oldProps[key], null) 636 | } 637 | } 638 | } 639 | } 640 | } 641 | function patchChildren(n1, n2, container, parentComponent, anchor) { 642 | const prevShapeFlag = n1.shapeFlag 643 | const { shapeFlag } = n2 644 | const c1 = n1.children 645 | const c2 = n2.children 646 | // 自节点有三种可能情况: text array 没有节点 null 647 | // 新节点为文本节点 648 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 649 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) { 650 | // 如果老节点是数组节点 新节点是文本节点 卸载老节点 651 | unmountChildren(c1) 652 | } 653 | // 设置文本节点 654 | if (c1 !== c2) { 655 | // 新老节点不一样的时候触发设置文本节点 656 | hostSetElementText(container, c2) 657 | } 658 | } else { 659 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) { 660 | // 新节点为 array | no children 661 | if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 662 | // 新节点为 array 新老节点都是array 进行diff 663 | patchKeyedChildren(c1, c2, container, parentComponent, anchor) 664 | } else { 665 | // 老节点是数组 没有新的孩子节点 卸载旧节点 666 | unmountChildren(c1) 667 | } 668 | } else { 669 | // 老的节点是文本或空 670 | // 新的节点是数组或空 671 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) { 672 | // 老的节点是文本 673 | // 删除老节点 674 | hostSetElementText(container, '') 675 | } 676 | if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 677 | // 添加新节点 678 | mountChildren(c2, container, parentComponent, anchor) 679 | } 680 | } 681 | } 682 | } 683 | function unmountChildren(children) { 684 | for (let i = 0; i < children.length; i++) { 685 | const el = children[i].el 686 | hostRemove(el) 687 | } 688 | } 689 | function unmount(el) { 690 | hostRemove(el) 691 | } 692 | function patchKeyedChildren( 693 | c1, 694 | c2, 695 | container, 696 | parentComponent, 697 | parentAnchor 698 | ) { 699 | const l2 = c2.length 700 | let i = 0 701 | let e1 = c1.length - 1 702 | let e2 = l2 - 1 703 | // 1. 从头同步 704 | // (a b) c 705 | // (a b) d e 706 | while (i <= e1 && i <= e2) { 707 | const n1 = c1[i] 708 | const n2 = c2[i] 709 | if (isSameVNodeType(n1, n2)) { 710 | patch(n1, n2, container, parentComponent, parentAnchor) 711 | } else { 712 | break 713 | } 714 | i++ 715 | } 716 | // 2. 从尾同步 717 | // a (b c) 718 | // d e (b c) 719 | while (i <= e1 && i <= e2) { 720 | const n1 = c1[e1] 721 | const n2 = c2[e2] 722 | if (isSameVNodeType(n1, n2)) { 723 | patch(n1, n2, container, parentComponent, parentAnchor) 724 | } else { 725 | break 726 | } 727 | e1-- 728 | e2-- 729 | } 730 | // 3. 普通序列+挂载 731 | // (a b) 732 | // (a b) c 733 | // i = 2, e1 = 1, e2 = 2 734 | // (a b) 735 | // c (a b) 736 | // i = 0, e1 = -1, e2 = 0 737 | if (i > e1) { 738 | if (i <= e2) { 739 | const nextPos = e2 + 1 740 | const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor 741 | while (i <= e2) { 742 | patch(null, c2[i], container, parentComponent, anchor) 743 | i++ 744 | } 745 | } 746 | } 747 | // 4. 常用序列+卸载 748 | // (a b) c 749 | // (a b) 750 | // i = 2, e1 = 2, e2 = 1 751 | // a (b c) 752 | // (b c) 753 | // i = 0, e1 = 0, e2 = -1 754 | else if (i > e2) { 755 | while (i <= e1) { 756 | unmount(c1[i].el) 757 | i++ 758 | } 759 | } 760 | // 5. 未知序列 761 | // [i ... e1 + 1]: a b [c d e] f g 762 | // [i ... e2 + 1]: a b [e d c h] f g 763 | // i = 2, e1 = 4, e2 = 5 764 | else { 765 | debugger 766 | const s1 = i // 老的起始下标 767 | const s2 = i // 新的起始下标 768 | // 5.1 为 newChildren 构建 key:index map 769 | const keyToNewIndexMap = new Map() 770 | for (let i = s2; i <= e2; i++) { 771 | const nextChild = c2[i] 772 | if (nextChild.key != null) { 773 | // != null 包含了 null 和 undefined 774 | keyToNewIndexMap.set(nextChild.key, i) 775 | } 776 | } 777 | // 5.2 循环遍历老节点 进行打补丁 778 | // 匹配节点并删除不再存在的节点 779 | let patched = 0 // 已经打过补丁的个数 780 | const toBePatch = e2 - s2 + 1 // 将要被打补丁的总数 781 | let maxNewIndexSoFar = 0 // 用于跟踪是否有任何节点移动 782 | let move = false // 是否需要移动 783 | const newIndexToOldIndexMap = new Array(toBePatch) // 创建一个以新节点个数为长度的数组 数组中每一项存放老节点的下标 784 | // 初始化都为0 这里初始化不是设置0 不使用fill是性能问题 785 | for (let i = 0; i < toBePatch; i++) { 786 | newIndexToOldIndexMap[i] = 0 787 | } 788 | for (let i = s1; i <= e1; i++) { 789 | const preChild = c1[i] 790 | if (patched >= toBePatch) { 791 | // [i ... e1 + 1]: a b [c d e h i] f g 792 | // [i ... e2 + 1]: a b [e c] f g 793 | // 如果打过补丁的个数 >= 将要被打补丁的总数 说明剩下的老节点都要被卸载 794 | // h i将被直接卸载 795 | unmount(preChild.el) 796 | continue 797 | } 798 | let newIndex 799 | if (preChild.key != null) { 800 | // 如果老节点有key的话 直接在map中取值 801 | newIndex = keyToNewIndexMap.get(preChild.key) 802 | } else { 803 | // 老节点没有key 就尝试定位同类型的无键节点 804 | for (let j = 0; j <= e2; j++) { 805 | if (isSameVNodeType(preChild, c2[j])) { 806 | // 如果找到相同的节点 给newIndex赋值 直接跳出循环 807 | newIndex = j 808 | break 809 | } 810 | } 811 | } 812 | if (newIndex === undefined) { 813 | // 新的节点中没有这个老节点 进行卸载 814 | unmount(preChild.el) 815 | } else { 816 | // 这个老节点在新节点中也存在 记录下新节点下标newIndex对应的老节点的下标i 817 | // 由于初始化为0 而0在后面会被处理为新增 所以这里进行了i+1 818 | // 解决下面的情况 会出现的问题 819 | // [c d e] f g 820 | // [e d c h] f g 821 | // 当处理c的时候发现c在新节点中也存在 走到这块如果设置为i就是0 那么后面处理新增的时候就会把c在创建一份 而c只是要移动位置 822 | newIndexToOldIndexMap[newIndex - s2] = i + 1 823 | // 判断是否需要移动 824 | // 解决一下情况 825 | // a b [d] f g 826 | // a b [c d e] f g 827 | // 上面的d不需要移动 只需要创建c e节点 828 | if (newIndex >= maxNewIndexSoFar) { 829 | maxNewIndexSoFar = newIndex 830 | } else { 831 | move = true 832 | } 833 | // 再次进行patch 834 | patch( 835 | preChild, 836 | c2[newIndex], 837 | container, 838 | parentComponent, 839 | parentAnchor 840 | ) 841 | patched++ 842 | } 843 | } 844 | // 5.2 流程走完 卸载了新节点没有的老节点 新节点中有的老节点会再次打补丁 所以这个时候新节点中所有的老节点都是有el属性的 845 | // 5.3 移动和挂载 846 | // 仅在节点移动时生成最长稳定子序列 847 | const increasingNewIndexSequence = move 848 | ? getSequence(newIndexToOldIndexMap) 849 | : [] // 生成最长递增子序列的下标 850 | let j = increasingNewIndexSequence.length - 1 // 最长递增子序列数组的索引 851 | for (let i = toBePatch - 1; i >= 0; i--) { 852 | const nextIndex = s2 + i 853 | const nextChild = c2[nextIndex] 854 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor 855 | if (newIndexToOldIndexMap[i] === 0) { 856 | // 新增 857 | patch(null, nextChild, container, parentComponent, anchor) 858 | } else if (move) { 859 | if (increasingNewIndexSequence[j] !== i) { 860 | // 需要移动 861 | console.log('需要移动', j) 862 | hostInsert(nextChild.el, container, anchor) 863 | } else { 864 | // 不需要移动 最长递增子序列数组的索引前移 865 | j-- 866 | } 867 | } 868 | } 869 | } 870 | } 871 | function setupRenderEffect(instance, initialVNode, container, anchor) { 872 | instance.update = effect( 873 | () => { 874 | if (!instance.isMounted) { 875 | console.log('init') 876 | const { proxy } = instance 877 | const subTree = (instance.subTree = instance.render.call( 878 | proxy, 879 | proxy 880 | )) 881 | patch(null, subTree, container, instance, anchor) 882 | // patch之后将跟节点挂在到vnode上 就是createVNode创建出来的对象中的el 883 | initialVNode.el = subTree.el 884 | instance.isMounted = true 885 | } else { 886 | console.log('update') 887 | // 找出下次更新的vnode(next) vnode是之前的 888 | // 先改props然后在进行patch更新 889 | const { next, vnode } = instance 890 | if (next) { 891 | next.el = vnode.el 892 | updateComponentPreRender(instance, next) 893 | } 894 | const { proxy } = instance 895 | const subTree = instance.render.call(proxy, proxy) 896 | const prevSubtree = instance.subTree 897 | instance.subTree = subTree 898 | console.log('current', subTree) 899 | console.log('prev', prevSubtree) 900 | patch(prevSubtree, subTree, container, instance, anchor) 901 | } 902 | }, 903 | { 904 | scheduler: () => { 905 | console.log('update--scheduler') 906 | queueJobs(instance.update) 907 | } 908 | } 909 | ) 910 | } 911 | return { 912 | createApp: createAppAPI(render) 913 | } 914 | } 915 | function updateComponentPreRender(instance, nextVNode) { 916 | instance.vnode = nextVNode 917 | instance.next = null 918 | instance.props = nextVNode.props 919 | } 920 | // https://en.wikipedia.org/wiki/Longest_increasing_subsequence 921 | function getSequence(arr) { 922 | const p = arr.slice() 923 | const result = [0] 924 | let i, j, u, v, c 925 | const len = arr.length 926 | for (i = 0; i < len; i++) { 927 | const arrI = arr[i] 928 | // 此算法中排除了等于0的情况,原因是0成为了diff算法中的占位符 929 | if (arrI !== 0) { 930 | // 用当前num与result中的最后一项对比 931 | j = result[result.length - 1] 932 | // 当前数值大于result子序列最后一项时,直接往后新增,并将当前数值的前一位result保存 933 | if (arr[j] < arrI) { 934 | p[i] = j // 最后一项与 p 对应的索引进行对应 935 | result.push(i) 936 | continue 937 | } 938 | u = 0 939 | v = result.length - 1 940 | // 当前数值小于result子序列最后一项时,使用二分法找到第一个大于当前数值的下标 941 | while (u < v) { 942 | // 除2 并向下取整 943 | c = (u + v) >> 1 944 | if (arr[result[c]] < arrI) { 945 | u = c + 1 946 | } else { 947 | v = c 948 | } 949 | } 950 | // 比较 => 替换 951 | if (arrI < arr[result[u]]) { 952 | // 找到下标,将当前下标对应的前一位result保存(如果找到的是第一位,不需要操作,第一位前面没有了) 953 | if (u > 0) { 954 | p[i] = result[u - 1] // 正确的结果 955 | } 956 | // 找到下标,直接替换result中的数值 957 | result[u] = i // 有可能替换会导致结果不正确,需要一个新数组 p 记录正确的结果 958 | } 959 | } 960 | } 961 | u = result.length 962 | v = result[u - 1] 963 | // 回溯,直接从最后一位开始,将前面的result全部覆盖,如果不需要修正,则p中记录的每一项都是对应的前一位,不会有任何影响 964 | while (u-- > 0) { 965 | result[u] = v 966 | v = p[v] 967 | } 968 | return result 969 | } 970 | 971 | function createElement(tag) { 972 | return document.createElement(tag) 973 | } 974 | function patchProp(el, key, oldValue, newValue) { 975 | newValue = isArray(newValue) ? newValue.join(' ') : newValue 976 | if (isOn(key)) { 977 | // 处理事件 978 | const event = key.slice(2).toLowerCase() 979 | el.addEventListener(event, newValue) 980 | } else { 981 | // 处理属性 982 | if (newValue === undefined || newValue === null) { 983 | el.removeAttribute(key) 984 | } else { 985 | el.setAttribute(key, newValue) 986 | } 987 | } 988 | } 989 | function insert(el, parent, anchor = null) { 990 | parent.insertBefore(el, anchor) 991 | } 992 | function remove(child) { 993 | const parentNode = child.parentNode 994 | if (parentNode) { 995 | parentNode.removeChild(child) 996 | } 997 | } 998 | function setElementText(el, text) { 999 | el.textContent = text 1000 | } 1001 | const renderer = createRenderer({ 1002 | createElement, 1003 | patchProp, 1004 | insert, 1005 | remove, 1006 | setElementText 1007 | }) 1008 | const createApp = (...args) => { 1009 | return renderer.createApp(...args) 1010 | } 1011 | 1012 | var runtimeDom = /*#__PURE__*/ Object.freeze({ 1013 | __proto__: null, 1014 | createApp: createApp, 1015 | h: h, 1016 | renderSlots: renderSlots, 1017 | createTextVNode: createTextVNode, 1018 | createElementVNode: createVNode, 1019 | getCurrentInstace: getCurrentInstace, 1020 | registerRuntimeCompiler: registerRuntimeCompiler, 1021 | provide: provide, 1022 | inject: inject, 1023 | createRenderer: createRenderer, 1024 | nextTick: nextTick, 1025 | toDisplayString: toDisplayString, 1026 | ref: ref, 1027 | proxyRefs: proxyRefs, 1028 | reactive: reactive 1029 | }) 1030 | 1031 | const TO_DISPLAY_STRING = Symbol('toDisplayString') 1032 | const CREATE_ELEMENT_VNODE = Symbol('createElementVNode') 1033 | const helperMapName = { 1034 | [TO_DISPLAY_STRING]: 'toDisplayString', 1035 | [CREATE_ELEMENT_VNODE]: 'createElementVNode' 1036 | } 1037 | 1038 | function generate(ast) { 1039 | const context = createCodegenContext() 1040 | const { push } = context 1041 | // import { toDisplayString as _toDisplayString } from "vue" 1042 | genFunctionPreamble(ast, context) 1043 | const functionName = 'render' 1044 | const args = ['_ctx', '_ceche'] 1045 | const signature = args.join(', ') 1046 | // console.log(ast) 1047 | push(`function ${functionName}(${signature}) {return `) 1048 | genNode(ast.codegenNode, context) 1049 | push('}') 1050 | return { 1051 | code: context.code 1052 | } 1053 | } 1054 | function genFunctionPreamble(ast, context) { 1055 | const { push } = context 1056 | const VueBinging = 'Vue' 1057 | const aliasHelper = s => `${helperMapName[s]}:_${helperMapName[s]}` 1058 | if (ast.helpers.length) { 1059 | push(`const {${ast.helpers.map(aliasHelper).join(', ')}} = ${VueBinging}`) 1060 | } 1061 | push('\n') 1062 | push('return ') 1063 | } 1064 | function createCodegenContext(ast) { 1065 | const context = { 1066 | code: '', 1067 | push(source) { 1068 | context.code += source 1069 | }, 1070 | helper(key) { 1071 | return `_${helperMapName[key]}` 1072 | } 1073 | } 1074 | return context 1075 | } 1076 | function genNode(node, context) { 1077 | switch (node.type) { 1078 | case 3 /* TEXT */: 1079 | genText(node, context) 1080 | break 1081 | case 0 /* INTERPOLATION */: 1082 | genInterpolation(node, context) 1083 | break 1084 | case 1 /* SIMPLE_EXPRESSION */: 1085 | genExpression(node, context) 1086 | break 1087 | case 2 /* ELEMENT */: 1088 | genElement(node, context) 1089 | break 1090 | case 5 /* COMPOUND_EXPRESSION */: 1091 | genCompoundExpression(node, context) 1092 | break 1093 | } 1094 | } 1095 | function genCompoundExpression(node, context) { 1096 | const { children } = node 1097 | const { push } = context 1098 | for (let i = 0; i < children.length; i++) { 1099 | const child = children[i] 1100 | if (isString(child)) { 1101 | push(child) 1102 | } else { 1103 | genNode(child, context) 1104 | } 1105 | } 1106 | } 1107 | function genElement(node, context) { 1108 | const { push, helper } = context 1109 | const { tag, children, props } = node 1110 | push(`${helper(CREATE_ELEMENT_VNODE)}(`) 1111 | // for (let i = 0; i < children.length; i++) { 1112 | // const child = children[i]; 1113 | // genNode(child, context) 1114 | // } 1115 | genNodeList(genNullable([tag, props, children]), context) 1116 | // genNode(children, context) 1117 | push(')') 1118 | } 1119 | function genNodeList(nodes, context) { 1120 | const { push } = context 1121 | for (let i = 0; i < nodes.length; i++) { 1122 | const node = nodes[i] 1123 | if (isString(node)) { 1124 | push(node) 1125 | } else { 1126 | genNode(node, context) 1127 | } 1128 | if (i < nodes.length - 1) { 1129 | push(', ') 1130 | } 1131 | } 1132 | } 1133 | function genExpression(node, context) { 1134 | const { push } = context 1135 | push(`${node.content}`) 1136 | } 1137 | function genInterpolation(node, context) { 1138 | const { push, helper } = context 1139 | push(`${helper(TO_DISPLAY_STRING)}(`) 1140 | genNode(node.content, context) 1141 | push(')') 1142 | } 1143 | function genText(node, context) { 1144 | const { push } = context 1145 | push(`'${node.content}'`) 1146 | } 1147 | function genNullable(args) { 1148 | return args.map(arg => arg || 'null') 1149 | } 1150 | 1151 | function baseParse(content) { 1152 | const context = createParserContext(content) 1153 | return createRoot(parseChildren(context, [])) 1154 | } 1155 | function parseChildren(context, ancestors) { 1156 | const nodes = [] 1157 | while (!isEnd(context, ancestors)) { 1158 | let node 1159 | const s = context.source 1160 | if (s.startsWith('{{')) { 1161 | node = parseinterpolation(context) 1162 | } else if (s[0] === '<') { 1163 | if (/[a-z]/g.test(s[1])) { 1164 | node = parseElement(context, ancestors) 1165 | } 1166 | } 1167 | if (!node) { 1168 | node = parseText(context) 1169 | } 1170 | nodes.push(node) 1171 | } 1172 | return nodes 1173 | } 1174 | function isEnd(context, ancestors) { 1175 | const s = context.source 1176 | // 1. 是不是结束标签 1177 | if (s.startsWith('= 0; i--) { 1179 | const { tag } = ancestors[i] 1180 | if (startsWithEndTagOpen(s, tag)) { 1181 | return true 1182 | } 1183 | } 1184 | } 1185 | // 2. context.source是不是为空 1186 | return !s 1187 | } 1188 | function startsWithEndTagOpen(s, tag) { 1189 | return ( 1190 | s.startsWith(' index) { 1261 | endIndex = index 1262 | } 1263 | } 1264 | // 1. 获取context 1265 | const content = parseTextData(context, endIndex) 1266 | return { 1267 | type: 3 /* TEXT */, 1268 | content 1269 | } 1270 | } 1271 | function parseTextData(context, length) { 1272 | const content = context.source.slice(0, length) 1273 | // 2. 推进 1274 | advanceBy(context, content.length) 1275 | return content 1276 | } 1277 | 1278 | function transform(root, options = {}) { 1279 | const context = createTransformContext(root, options) 1280 | traverseNode(root, context) 1281 | createRootCodegen(root) 1282 | root.helpers = [...context.helpers.keys()] 1283 | } 1284 | function createRootCodegen(root) { 1285 | const child = root.children[0] 1286 | if (child.type === 2 /* ELEMENT */) { 1287 | root.codegenNode = child.codegenNode 1288 | } else { 1289 | root.codegenNode = root.children[0] 1290 | } 1291 | } 1292 | function createTransformContext(root, options) { 1293 | const context = { 1294 | root, 1295 | nodeTransforms: options.nodeTransforms || [], 1296 | helpers: new Map(), 1297 | helper(key) { 1298 | context.helpers.set(key, 1) 1299 | } 1300 | } 1301 | return context 1302 | } 1303 | function traverseNode(node, context) { 1304 | const { nodeTransforms } = context 1305 | const exitFns = [] 1306 | for (let i = 0; i < nodeTransforms.length; i++) { 1307 | const transform = nodeTransforms[i] 1308 | const onExit = transform(node, context) 1309 | if (onExit) { 1310 | exitFns.push(onExit) 1311 | } 1312 | } 1313 | switch (node.type) { 1314 | case 0 /* INTERPOLATION */: 1315 | context.helper(TO_DISPLAY_STRING) 1316 | break 1317 | case 4 /* ROOT */: 1318 | case 2 /* ELEMENT */: 1319 | traverseChildren(node, context) 1320 | break 1321 | } 1322 | let i = exitFns.length 1323 | while (i--) { 1324 | exitFns[i]() 1325 | } 1326 | } 1327 | function traverseChildren(node, context) { 1328 | const { children } = node 1329 | for (let i = 0; i < children.length; i++) { 1330 | const node = children[i] 1331 | // 深度优先搜索 1332 | traverseNode(node, context) 1333 | } 1334 | } 1335 | 1336 | function transformExpression(node) { 1337 | if (node.type === 0 /* INTERPOLATION */) { 1338 | node.content = processExpression(node.content) 1339 | } 1340 | } 1341 | function processExpression(node) { 1342 | node.content = `_ctx.${node.content}` 1343 | return node 1344 | } 1345 | 1346 | function isText(node) { 1347 | return node.type === 3 /* TEXT */ || node.type === 0 /* INTERPOLATION */ 1348 | } 1349 | 1350 | function transformText(node) { 1351 | if (node.type === 2 /* ELEMENT */) { 1352 | return () => { 1353 | const { children } = node 1354 | let currentContainer 1355 | for (let i = 0; i < children.length; i++) { 1356 | const child = children[i] 1357 | if (isText(child)) { 1358 | for (let j = i + 1; j < children.length; j++) { 1359 | const next = children[j] 1360 | if (isText(next)) { 1361 | if (!currentContainer) { 1362 | currentContainer = children[i] = { 1363 | type: 5 /* COMPOUND_EXPRESSION */, 1364 | children: [child] 1365 | } 1366 | } 1367 | currentContainer.children.push(' + ') 1368 | currentContainer.children.push(next) 1369 | children.splice(j, 1) 1370 | j-- 1371 | } else { 1372 | currentContainer = undefined 1373 | break 1374 | } 1375 | } 1376 | } 1377 | } 1378 | } 1379 | } 1380 | } 1381 | 1382 | function createVNodeCall(context, tag, props, children) { 1383 | context.helper(CREATE_ELEMENT_VNODE) 1384 | return { 1385 | type: 2 /* ELEMENT */, 1386 | tag, 1387 | props, 1388 | children 1389 | } 1390 | } 1391 | 1392 | function transformElement(node, context) { 1393 | if (node.type === 2 /* ELEMENT */) { 1394 | return () => { 1395 | context.helper(CREATE_ELEMENT_VNODE) 1396 | // tag 1397 | const vnodeTag = `'${node.tag}'` 1398 | // props 1399 | let vnodeProps 1400 | // children 1401 | const { children } = node 1402 | let vnodeChildren = children[0] 1403 | node.codegenNode = createVNodeCall( 1404 | context, 1405 | vnodeTag, 1406 | vnodeProps, 1407 | vnodeChildren 1408 | ) 1409 | } 1410 | } 1411 | } 1412 | 1413 | function baseCompile(template) { 1414 | const ast = baseParse(template) 1415 | transform(ast, { 1416 | nodeTransforms: [transformExpression, transformElement, transformText] 1417 | }) 1418 | return generate(ast) 1419 | } 1420 | 1421 | // 出口文件 1422 | function compileToFunction(template) { 1423 | const { code } = baseCompile(template) 1424 | const render = new Function('Vue', code)(runtimeDom) 1425 | return render 1426 | } 1427 | registerRuntimeCompiler(compileToFunction) 1428 | 1429 | export { 1430 | createApp, 1431 | createVNode as createElementVNode, 1432 | createRenderer, 1433 | createTextVNode, 1434 | getCurrentInstace, 1435 | h, 1436 | inject, 1437 | nextTick, 1438 | provide, 1439 | proxyRefs, 1440 | reactive, 1441 | ref, 1442 | registerRuntimeCompiler, 1443 | renderSlots, 1444 | toDisplayString 1445 | } 1446 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-vue-next", 3 | "version": "1.0.0", 4 | "description": "vue-next mini version", 5 | "main": "lib/ai-vue-next.bundle.cjs.js", 6 | "module": "lib/ai-vue-next.bundle.esm.js", 7 | "scripts": { 8 | "test": "vitest", 9 | "rollup": "rollup -c rollup.config.ts --watch", 10 | "build": "rollup -c rollup.config.ts", 11 | "prepare": "husky install", 12 | "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", 13 | "verify-commit": "node scripts/verifyCommit.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/MissFlower/ai-vue-next.git" 18 | }, 19 | "keywords": [], 20 | "author": "DoveyLoveyCora", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/MissFlower/ai-vue-next/issues" 24 | }, 25 | "homepage": "https://github.com/MissFlower/ai-vue-next#readme", 26 | "devDependencies": { 27 | "@babel/core": "^7.16.0", 28 | "@babel/preset-env": "^7.16.0", 29 | "@babel/preset-typescript": "^7.16.0", 30 | "@commitlint/cli": "^14.1.0", 31 | "@commitlint/config-conventional": "^14.1.0", 32 | "@rollup/plugin-typescript": "^8.3.0", 33 | "@typescript-eslint/parser": "^5.3.1", 34 | "chalk": "^4.1.0", 35 | "eslint": "^8.2.0", 36 | "husky": "^7.0.4", 37 | "jest": "^27.3.1", 38 | "lint-staged": "^11.2.6", 39 | "prettier": "^2.4.1", 40 | "rollup": "^2.60.0", 41 | "tslib": "^2.3.1", 42 | "typescript": "^4.4.4", 43 | "vitest": "^0.23.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/.pnpm-debug.log: -------------------------------------------------------------------------------- 1 | { 2 | "0 debug pnpm:scope": { 3 | "selected": 1, 4 | "total": 6 5 | }, 6 | "1 debug pnpm:package-manifest": { 7 | "initial": { 8 | "name": "@ai-vue-next/runtime-dom", 9 | "version": "1.0.0", 10 | "description": "", 11 | "main": "index.js", 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC" 18 | }, 19 | "prefix": "/Users/aidongyang/code/DoveyLoveyCora/ai-vue-next/packages/runtime-dom" 20 | }, 21 | "2 debug pnpm:context": { 22 | "currentLockfileExists": false, 23 | "storeDir": "/Users/aidongyang/.pnpm-store/v3", 24 | "virtualStoreDir": "/Users/aidongyang/code/DoveyLoveyCora/ai-vue-next/packages/runtime-dom/node_modules/.pnpm" 25 | }, 26 | "3 debug pnpm:stage": { 27 | "prefix": "/Users/aidongyang/code/DoveyLoveyCora/ai-vue-next/packages/runtime-dom", 28 | "stage": "resolution_started" 29 | }, 30 | "4 info pnpm": { 31 | "err": { 32 | "name": "Error", 33 | "message": "GET https://registry.npmjs.org/@ai-vue-runtime%2Fcore: Not Found - 404", 34 | "code": "ERR_PNPM_FETCH_404", 35 | "stack": "Error: GET https://registry.npmjs.org/@ai-vue-runtime%2Fcore: Not Found - 404\n at RetryOperation._fn (/Users/aidongyang/.nvm/versions/node/v14.15.1/lib/node_modules/pnpm/dist/pnpm.cjs:85659:19)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)" 36 | } 37 | }, 38 | "5 error pnpm": { 39 | "code": "ERR_PNPM_FETCH_404", 40 | "hint": "@ai-vue-runtime/core is not in the npm registry, or you have no permission to fetch it.\n\nNo authorization header was set for the request.", 41 | "request": { 42 | "url": "https://registry.npmjs.org/@ai-vue-runtime%2Fcore" 43 | }, 44 | "response": { 45 | "size": 0 46 | }, 47 | "pkgName": "@ai-vue-runtime/core", 48 | "pkgsStack": [], 49 | "prefix": "/Users/aidongyang/code/DoveyLoveyCora/ai-vue-next/packages/runtime-dom", 50 | "err": { 51 | "name": "pnpm", 52 | "message": "GET https://registry.npmjs.org/@ai-vue-runtime%2Fcore: Not Found - 404", 53 | "code": "ERR_PNPM_FETCH_404", 54 | "stack": "Error: GET https://registry.npmjs.org/@ai-vue-runtime%2Fcore: Not Found - 404\n at RetryOperation._fn (/Users/aidongyang/.nvm/versions/node/v14.15.1/lib/node_modules/pnpm/dist/pnpm.cjs:85659:19)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)" 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`codegen > element 1`] = ` 4 | "const {toDisplayString:_toDisplayString, createElementVNode:_createElementVNode} = Vue 5 | return function render(_ctx, _ceche) {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, _ceche) {return _toDisplayString(_ctx.message)}" 11 | `; 12 | 13 | exports[`codegen > string 1`] = ` 14 | " 15 | return function render(_ctx, _ceche) {return 'hi'}" 16 | `; 17 | 18 | exports[`codegen element 1`] = ` 19 | "const {toDisplayString:_toDisplayString, createElementVNode:_createElementVNode} = Vue 20 | return function render(_ctx, _ceche) {return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))}" 21 | `; 22 | 23 | exports[`codegen interpolation 1`] = ` 24 | "const {toDisplayString:_toDisplayString} = Vue 25 | return function render(_ctx, _ceche) {return _toDisplayString(_ctx.message)}" 26 | `; 27 | 28 | exports[`codegen string 1`] = ` 29 | " 30 | return function render(_ctx, _ceche) {return 'hi'}" 31 | `; 32 | -------------------------------------------------------------------------------- /packages/compiler-core/__tests__/codegen.spec.ts: -------------------------------------------------------------------------------- 1 | import { generate } from '../src/codegen' 2 | import { baseParse } from '../src/parse' 3 | import { transform } from '../src/transform' 4 | import { transformExpression } from '../src/transforms/transformExpression' 5 | import { transformText } from '../src/transforms/transformText' 6 | import { transformElement } from '../src/transforms/transfromElement' 7 | 8 | describe('codegen', () => { 9 | it('string', () => { 10 | const ast = baseParse('hi') 11 | transform(ast) 12 | const { code } = generate(ast) 13 | 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 | 24 | expect(code).toMatchSnapshot() 25 | }) 26 | 27 | it('element', () => { 28 | const ast: any = baseParse('
hi,{{message}}
') 29 | transform(ast, { 30 | nodeTransforms: [transformExpression, transformElement, transformText] 31 | }) 32 | // console.log('ast-------', ast, ast.codegenNode.children); 33 | 34 | const { code } = generate(ast) 35 | 36 | expect(code).toMatchSnapshot() 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/compiler-core/__tests__/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from '../src/ast' 2 | import { baseParse } from '../src/parse' 3 | describe('parse', () => { 4 | describe('interpolation', () => { 5 | test('simple interpolation', () => { 6 | const ast = baseParse('{{ message }}') 7 | 8 | expect(ast.children[0]).toStrictEqual({ 9 | type: NodeTypes.INTERPOLATION, 10 | content: { 11 | type: NodeTypes.SIMPLE_EXPRESSION, 12 | content: 'message' 13 | } 14 | }) 15 | }) 16 | }) 17 | 18 | describe('element', () => { 19 | it('simple element div', () => { 20 | const ast = baseParse('
') 21 | 22 | expect(ast.children[0]).toStrictEqual({ 23 | type: NodeTypes.ELEMENT, 24 | tag: 'div', 25 | children: [] 26 | }) 27 | }) 28 | }) 29 | 30 | describe('text', () => { 31 | it('simple text', () => { 32 | const ast = baseParse('some text') 33 | 34 | expect(ast.children[0]).toStrictEqual({ 35 | type: NodeTypes.TEXT, 36 | content: 'some text' 37 | }) 38 | }) 39 | }) 40 | 41 | test('hello world', () => { 42 | const ast = baseParse('
hi,{{message}}
') 43 | 44 | expect(ast.children[0]).toStrictEqual({ 45 | type: NodeTypes.ELEMENT, 46 | tag: 'div', 47 | children: [ 48 | { 49 | type: NodeTypes.TEXT, 50 | content: 'hi,' 51 | }, 52 | { 53 | type: NodeTypes.INTERPOLATION, 54 | content: { 55 | type: NodeTypes.SIMPLE_EXPRESSION, 56 | content: 'message' 57 | } 58 | }, 59 | { 60 | type: NodeTypes.TEXT, 61 | content: ' ' 62 | } 63 | ] 64 | }) 65 | }) 66 | 67 | test('Nest Element', () => { 68 | const ast = baseParse('

hi

{{message}}
') 69 | 70 | expect(ast.children[0]).toStrictEqual({ 71 | type: NodeTypes.ELEMENT, 72 | tag: 'div', 73 | children: [ 74 | { 75 | type: NodeTypes.ELEMENT, 76 | tag: 'p', 77 | children: [ 78 | { 79 | type: NodeTypes.TEXT, 80 | content: 'hi' 81 | } 82 | ] 83 | }, 84 | { 85 | type: NodeTypes.INTERPOLATION, 86 | content: { 87 | type: NodeTypes.SIMPLE_EXPRESSION, 88 | content: 'message' 89 | } 90 | } 91 | ] 92 | }) 93 | }) 94 | 95 | test.only('should throw error then lack end tag', () => { 96 | // baseParse('
') 97 | expect(() => { 98 | baseParse('
') 99 | }).toThrow() 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /packages/compiler-core/__tests__/transform.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from '../src/ast' 2 | import { baseParse } from '../src/parse' 3 | import { transform } from '../src/transform' 4 | 5 | describe('transform', () => { 6 | it('happy path', () => { 7 | const ast = baseParse('
hi,{{message}}
') 8 | 9 | const plugin = node => { 10 | if (node.type === NodeTypes.TEXT) { 11 | node.content += 'mini-vue' 12 | } 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/compiler-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ai-vue-next/compiler-core", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@ai-vue-next/shared": "workspace:^1.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/compiler-core/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | '@ai-vue-next/shared': workspace:^1.0.0 5 | 6 | dependencies: 7 | '@ai-vue-next/shared': link:../shared 8 | -------------------------------------------------------------------------------- /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 | return { 15 | type: NodeTypes.ELEMENT, 16 | tag, 17 | props, 18 | children 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/compiler-core/src/codegen.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '@ai-vue-next/shared' 2 | import { NodeTypes } from './ast' 3 | import { 4 | CREATE_ELEMENT_VNODE, 5 | helperMapName, 6 | TO_DISPLAY_STRING 7 | } from './runtimeHelpers' 8 | 9 | export function generate(ast) { 10 | const context = createCodegenContext(ast) 11 | const { push } = context 12 | 13 | // import { toDisplayString as _toDisplayString } from "vue" 14 | genFunctionPreamble(ast, context) 15 | 16 | const functionName = 'render' 17 | const args = ['_ctx', '_ceche'] 18 | const signature = args.join(', ') 19 | // console.log(ast) 20 | 21 | push(`function ${functionName}(${signature}) {return `) 22 | genNode(ast.codegenNode, context) 23 | push('}') 24 | 25 | return { 26 | code: context.code 27 | } 28 | } 29 | 30 | function genFunctionPreamble(ast, context) { 31 | const { push } = context 32 | const VueBinging = 'Vue' 33 | const aliasHelper = s => `${helperMapName[s]}:_${helperMapName[s]}` 34 | if (ast.helpers.length) { 35 | push(`const {${ast.helpers.map(aliasHelper).join(', ')}} = ${VueBinging}`) 36 | } 37 | push('\n') 38 | push('return ') 39 | } 40 | 41 | function createCodegenContext(ast: any) { 42 | const context = { 43 | code: '', 44 | push(source) { 45 | context.code += source 46 | }, 47 | helper(key) { 48 | return `_${helperMapName[key]}` 49 | } 50 | } 51 | 52 | return context 53 | } 54 | 55 | function genNode(node: any, context) { 56 | switch (node.type) { 57 | case NodeTypes.TEXT: 58 | genText(node, context) 59 | break 60 | 61 | case NodeTypes.INTERPOLATION: 62 | genInterpolation(node, context) 63 | break 64 | 65 | case NodeTypes.SIMPLE_EXPRESSION: 66 | genExpression(node, context) 67 | break 68 | 69 | case NodeTypes.ELEMENT: 70 | genElement(node, context) 71 | break 72 | 73 | case NodeTypes.COMPOUND_EXPRESSION: 74 | genCompoundExpression(node, context) 75 | break 76 | 77 | default: 78 | break 79 | } 80 | } 81 | 82 | function genCompoundExpression(node: any, context: any) { 83 | const { children } = node 84 | const { push } = context 85 | for (let i = 0; i < children.length; i++) { 86 | const child = children[i] 87 | if (isString(child)) { 88 | push(child) 89 | } else { 90 | genNode(child, context) 91 | } 92 | } 93 | } 94 | 95 | function genElement(node: any, context: any) { 96 | const { push, helper } = context 97 | const { tag, children, props } = node 98 | push(`${helper(CREATE_ELEMENT_VNODE)}(`) 99 | 100 | // for (let i = 0; i < children.length; i++) { 101 | // const child = children[i]; 102 | // genNode(child, context) 103 | // } 104 | genNodeList(genNullable([tag, props, children]), context) 105 | // genNode(children, context) 106 | push(')') 107 | } 108 | 109 | function genNodeList(nodes, context) { 110 | const { push } = context 111 | 112 | for (let i = 0; i < nodes.length; i++) { 113 | const node = nodes[i] 114 | if (isString(node)) { 115 | push(node) 116 | } else { 117 | genNode(node, context) 118 | } 119 | 120 | if (i < nodes.length - 1) { 121 | push(', ') 122 | } 123 | } 124 | } 125 | 126 | function genExpression(node: any, context: any) { 127 | const { push } = context 128 | push(`${node.content}`) 129 | } 130 | 131 | function genInterpolation(node: any, context: any) { 132 | const { push, helper } = context 133 | push(`${helper(TO_DISPLAY_STRING)}(`) 134 | genNode(node.content, context) 135 | push(')') 136 | } 137 | 138 | function genText(node: any, context: any) { 139 | const { push } = context 140 | push(`'${node.content}'`) 141 | } 142 | 143 | function genNullable(args) { 144 | return args.map(arg => arg || 'null') 145 | } 146 | -------------------------------------------------------------------------------- /packages/compiler-core/src/compile.ts: -------------------------------------------------------------------------------- 1 | import { generate } from './codegen' 2 | import { baseParse } from './parse' 3 | import { transform } from './transform' 4 | import { transformExpression } from './transforms/transformExpression' 5 | import { transformText } from './transforms/transformText' 6 | import { transformElement } from './transforms/transfromElement' 7 | 8 | export function baseCompile(template) { 9 | const ast: any = baseParse(template) 10 | transform(ast, { 11 | nodeTransforms: [transformExpression, transformElement, transformText] 12 | }) 13 | 14 | return generate(ast) 15 | } 16 | -------------------------------------------------------------------------------- /packages/compiler-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './compile' 2 | -------------------------------------------------------------------------------- /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 | return createRoot(parseChildren(context, [])) 11 | } 12 | 13 | function parseChildren(context, ancestors) { 14 | const nodes: any = [] 15 | 16 | while (!isEnd(context, ancestors)) { 17 | let node 18 | const s = context.source 19 | if (s.startsWith('{{')) { 20 | node = parseinterpolation(context) 21 | } else if (s[0] === '<') { 22 | if (/[a-z]/g.test(s[1])) { 23 | node = parseElement(context, ancestors) 24 | } 25 | } 26 | 27 | if (!node) { 28 | node = parseText(context) 29 | } 30 | 31 | nodes.push(node) 32 | } 33 | return nodes 34 | } 35 | 36 | function isEnd(context: any, ancestors) { 37 | const s = context.source 38 | // 1. 是不是结束标签 39 | if (s.startsWith('= 0; i--) { 41 | const { tag } = ancestors[i] 42 | if (startsWithEndTagOpen(s, tag)) { 43 | return true 44 | } 45 | } 46 | } 47 | // 2. context.source是不是为空 48 | return !s 49 | } 50 | 51 | function startsWithEndTagOpen(s: any, tag: any) { 52 | return ( 53 | s.startsWith(' index) { 139 | endIndex = index 140 | } 141 | } 142 | // 1. 获取context 143 | const content = parseTextData(context, endIndex) 144 | 145 | return { 146 | type: NodeTypes.TEXT, 147 | content 148 | } 149 | } 150 | 151 | function parseTextData(context: any, length) { 152 | const content = context.source.slice(0, length) 153 | // 2. 推进 154 | advanceBy(context, content.length) 155 | 156 | return content 157 | } 158 | -------------------------------------------------------------------------------- /packages/compiler-core/src/runtimeHelpers.ts: -------------------------------------------------------------------------------- 1 | export const TO_DISPLAY_STRING = Symbol('toDisplayString') 2 | export const CREATE_ELEMENT_VNODE = Symbol('createElementVNode') 3 | export const helperMapName = { 4 | [TO_DISPLAY_STRING]: 'toDisplayString', 5 | [CREATE_ELEMENT_VNODE]: 'createElementVNode' 6 | } 7 | -------------------------------------------------------------------------------- /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 | const context = createTransformContext(root, options) 6 | traverseNode(root, context) 7 | 8 | createRootCodegen(root) 9 | root.helpers = [...context.helpers.keys()] 10 | } 11 | 12 | function createRootCodegen(root: any) { 13 | const child = root.children[0] 14 | if (child.type === NodeTypes.ELEMENT) { 15 | root.codegenNode = child.codegenNode 16 | } else { 17 | root.codegenNode = root.children[0] 18 | } 19 | } 20 | 21 | function createTransformContext(root: any, options: any): any { 22 | const context = { 23 | root, 24 | nodeTransforms: options.nodeTransforms || [], 25 | helpers: new Map(), 26 | helper(key) { 27 | context.helpers.set(key, 1) 28 | } 29 | } 30 | 31 | return context 32 | } 33 | 34 | function traverseNode(node: any, context) { 35 | const { nodeTransforms } = context 36 | const exitFns: any = [] 37 | for (let i = 0; i < nodeTransforms.length; i++) { 38 | const transform = nodeTransforms[i] 39 | const onExit = transform(node, context) 40 | if (onExit) { 41 | exitFns.push(onExit) 42 | } 43 | } 44 | 45 | switch (node.type) { 46 | case NodeTypes.INTERPOLATION: 47 | context.helper(TO_DISPLAY_STRING) 48 | break 49 | case NodeTypes.ROOT: 50 | case NodeTypes.ELEMENT: 51 | traverseChildren(node, context) 52 | break 53 | 54 | default: 55 | break 56 | } 57 | 58 | let i = exitFns.length 59 | while (i--) { 60 | exitFns[i]() 61 | } 62 | } 63 | function traverseChildren(node: any, context: any) { 64 | const { children } = node 65 | for (let i = 0; i < children.length; i++) { 66 | const node = children[i] 67 | // 深度优先搜索 68 | traverseNode(node, context) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /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 | } 8 | function processExpression(node: any) { 9 | node.content = `_ctx.${node.content}` 10 | return node 11 | } 12 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transforms/transformText.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from '../ast' 2 | import { isText } from '../utils' 3 | 4 | export function transformText(node) { 5 | if (node.type === NodeTypes.ELEMENT) { 6 | return () => { 7 | const { children } = node 8 | let currentContainer 9 | 10 | for (let i = 0; i < children.length; i++) { 11 | const child = children[i] 12 | 13 | if (isText(child)) { 14 | for (let j = i + 1; j < children.length; j++) { 15 | const next = children[j] 16 | if (isText(next)) { 17 | if (!currentContainer) { 18 | currentContainer = children[i] = { 19 | type: NodeTypes.COMPOUND_EXPRESSION, 20 | children: [child] 21 | } 22 | } 23 | 24 | currentContainer.children.push(' + ') 25 | currentContainer.children.push(next) 26 | children.splice(j, 1) 27 | j-- 28 | } else { 29 | currentContainer = undefined 30 | break 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transforms/transfromElement.ts: -------------------------------------------------------------------------------- 1 | import { createVNodeCall, NodeTypes } from '../ast' 2 | import { CREATE_ELEMENT_VNODE } from '../runtimeHelpers' 3 | 4 | export function transformElement(node, context) { 5 | if (node.type === NodeTypes.ELEMENT) { 6 | return () => { 7 | context.helper(CREATE_ELEMENT_VNODE) 8 | 9 | // tag 10 | const vnodeTag = `'${node.tag}'` 11 | 12 | // props 13 | let vnodeProps 14 | 15 | // children 16 | const { children } = node 17 | let vnodeChildren = children[0] 18 | 19 | node.codegenNode = createVNodeCall( 20 | context, 21 | vnodeTag, 22 | vnodeProps, 23 | vnodeChildren 24 | ) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/compiler-core/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from './ast' 2 | 3 | export function isText(node: any) { 4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION 5 | } 6 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { computed } from '../src/computed' 2 | import { reactive } from '../src/reactive' 3 | import { vi } from 'vitest' 4 | 5 | describe('reactivity/computed', () => { 6 | it('should return updated value', () => { 7 | const value = reactive({ foo: 1 }) 8 | const cValue = computed(() => value.foo) 9 | expect(cValue.value).toBe(1) 10 | value.foo = 2 11 | expect(cValue.value).toBe(2) 12 | }) 13 | 14 | it('should compute lazily', () => { 15 | const value = reactive({ foo: 0 }) 16 | const getter = vi.fn(() => value.foo) 17 | const cValue = computed(getter) 18 | 19 | // lazy 20 | expect(getter).not.toHaveBeenCalled() 21 | 22 | expect(cValue.value).toBe(0) 23 | // 第一次访问 调用一次getter 24 | expect(getter).toHaveBeenCalledTimes(1) 25 | 26 | // should not compute again 27 | cValue.value 28 | // 再次访问获取缓存值 不被调用 29 | expect(getter).toHaveBeenCalledTimes(1) 30 | 31 | // should not compute until needed 32 | value.foo = 1 33 | // 响应式对象值改变了 只是触发trigger走schelder _dirty为true 34 | expect(getter).toHaveBeenCalledTimes(1) 35 | 36 | // now it should compute 37 | expect(cValue.value).toBe(1) 38 | // 再次访问 _dirty为true getter重新执行 并且_dirty为false 39 | expect(getter).toHaveBeenCalledTimes(2) 40 | 41 | // should not compute again 42 | cValue.value 43 | // 继续访问 依旧拿缓存 44 | expect(getter).toHaveBeenCalledTimes(2) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from '../src/reactive' 2 | import { effect, stop } from '../src/effect' 3 | import { vi } from 'vitest' 4 | 5 | describe('effect', () => { 6 | it('happy path', () => { 7 | const user = reactive({ 8 | age: 18 9 | }) 10 | 11 | expect(user.age).toBe(18) 12 | 13 | let nextAge 14 | effect(() => { 15 | nextAge = user.age + 1 16 | }) 17 | expect(nextAge).toBe(19) 18 | 19 | user.age++ 20 | expect(nextAge).toBe(20) 21 | }) 22 | it('effect should return runner function, call it again execute', () => { 23 | let foo = 1 24 | const runner = effect(() => { 25 | foo++ 26 | return 'foo' 27 | }) 28 | expect(foo).toBe(2) 29 | 30 | const r = runner() 31 | expect(foo).toBe(3) 32 | expect(r).toBe('foo') 33 | }) 34 | 35 | it('scheduler', () => { 36 | // scheduler使用和功能描述 37 | // 1. 通过effect的第二个参数传递 一个叫scheduler的函数 38 | // 2. effect初次执行的时候 仍然是执行fn 也就是第一个参数 39 | // 3. 当响应式对象 set函数被触发时 此时不会执行fn 而是会执行 scheduler函数 40 | // 4. 当执行effect的返回函数 runner时 会再次执行fn 41 | let dummy 42 | let run: any 43 | const scheduler = vi.fn(() => { 44 | run = runner 45 | }) 46 | const obj = reactive({ foo: 1 }) 47 | const runner = effect( 48 | () => { 49 | dummy = obj.foo 50 | }, 51 | { scheduler } 52 | ) 53 | expect(scheduler).not.toHaveBeenCalled() 54 | expect(dummy).toBe(1) 55 | // should be called on first trigger 56 | obj.foo++ 57 | expect(scheduler).toHaveBeenCalledTimes(1) 58 | // should not run yet 59 | expect(dummy).toBe(1) 60 | // manually run 61 | run() 62 | // should have run 63 | expect(dummy).toBe(2) 64 | }) 65 | 66 | it('stop', () => { 67 | let dummy 68 | const obj = reactive({ prop: 1, name: 10 }) 69 | const runner = effect(() => { 70 | dummy = obj.prop 71 | }) 72 | obj.prop = 2 73 | expect(dummy).toBe(2) 74 | stop(runner) 75 | obj.prop++ 76 | expect(dummy).toBe(2) 77 | 78 | // stopped effect should still be manually callable 79 | runner() 80 | obj.prop++ 81 | expect(dummy).toBe(3) 82 | }) 83 | 84 | it('events: onStop', () => { 85 | const onStop = vi.fn() 86 | const runner = effect(() => {}, { 87 | onStop 88 | }) 89 | 90 | stop(runner) 91 | expect(onStop).toHaveBeenCalled() 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReactive, reactive, readonly } from '../src/reactive' 2 | 3 | describe('happy path', () => { 4 | it('happy path', () => { 5 | const original = { 6 | foo: 1 7 | } 8 | const observed = reactive(original) 9 | 10 | expect(observed).not.toBe(original) 11 | expect(observed.foo).toBe(1) 12 | observed.foo = 5 13 | }) 14 | 15 | it('isReactive', () => { 16 | const original = { 17 | foo: 1 18 | } 19 | const observed = reactive(original) 20 | const readonlyObserved = readonly(original) 21 | expect(isReactive(observed)).toBe(true) 22 | expect(isReactive(readonlyObserved)).toBe(false) 23 | expect(isReactive(original)).toBe(false) 24 | expect(isProxy(original)).toBe(false) 25 | expect(isProxy(observed)).toBe(true) 26 | expect(isProxy(readonlyObserved)).toBe(true) 27 | }) 28 | 29 | test('nested reactives', () => { 30 | // reactive嵌套 31 | const original = { 32 | nested: { 33 | foo: 1 34 | }, 35 | array: [{ bar: 2 }] 36 | } 37 | const observed = reactive(original) 38 | expect(isReactive(observed.nested)).toBe(true) 39 | expect(isReactive(observed.array)).toBe(true) 40 | expect(isReactive(observed.array[0])).toBe(true) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isProxy, 3 | isReactive, 4 | isReadonly, 5 | reactive, 6 | readonly 7 | } from '../src/reactive' 8 | import { vi } from 'vitest' 9 | 10 | describe('readonly', () => { 11 | it('happy path', () => { 12 | const original = { 13 | foo: 1, 14 | bar: { 15 | baz: 2 16 | } 17 | } 18 | const wrap = readonly(original) 19 | expect(wrap).not.toBe(original) 20 | expect(wrap.foo).toBe(1) 21 | }) 22 | 23 | it('warn then set called', () => { 24 | console.warn = vi.fn() 25 | const user = readonly({ 26 | age: 10 27 | }) 28 | 29 | user.age = 11 30 | expect(user.age).toBe(10) 31 | expect(console.warn).toBeCalled() 32 | }) 33 | 34 | it('isReadonly', () => { 35 | const original = { 36 | name: 'july' 37 | } 38 | const reaciveObserved = reactive(original) 39 | const readonlyObserved = readonly(original) 40 | 41 | expect(isReadonly(readonlyObserved)).toBe(true) 42 | expect(isReadonly(reaciveObserved)).toBe(false) 43 | expect(isReadonly(original)).toBe(false) 44 | }) 45 | it('should make nested values readonly', () => { 46 | const original = { foo: 1, bar: { baz: 2 } } 47 | const wrapped = readonly(original) 48 | expect(wrapped).not.toBe(original) 49 | expect(isProxy(wrapped)).toBe(true) 50 | expect(isReactive(wrapped)).toBe(false) 51 | expect(isReadonly(wrapped)).toBe(true) 52 | expect(isReactive(original)).toBe(false) 53 | expect(isReadonly(original)).toBe(false) 54 | expect(isReactive(wrapped.bar)).toBe(false) 55 | expect(isReadonly(wrapped.bar)).toBe(true) 56 | expect(isReactive(original.bar)).toBe(false) 57 | expect(isReadonly(original.bar)).toBe(false) 58 | // get 59 | expect(wrapped.foo).toBe(1) 60 | // has 61 | expect('foo' in wrapped).toBe(true) 62 | // ownKeys 63 | expect(Object.keys(wrapped)).toEqual(['foo', 'bar']) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from '../src/reactive' 2 | import { effect } from '../src/effect' 3 | import { isRef, proxyRefs, ref, unref } from '../src/ref' 4 | 5 | describe('reactivity/ref', () => { 6 | it('should hold a value', () => { 7 | const a = ref(1) 8 | expect(a.value).toBe(1) 9 | a.value = 2 10 | expect(a.value).toBe(2) 11 | }) 12 | 13 | it('should be reactive', () => { 14 | const a = ref(1) 15 | let dummy 16 | let calls = 0 17 | effect(() => { 18 | calls++ 19 | dummy = a.value 20 | }) 21 | expect(calls).toBe(1) 22 | expect(dummy).toBe(1) 23 | a.value = 2 24 | expect(calls).toBe(2) 25 | expect(dummy).toBe(2) 26 | // same value should not trigger 27 | a.value = 2 28 | expect(calls).toBe(2) 29 | expect(dummy).toBe(2) 30 | }) 31 | 32 | it('should make nested properties reactive', () => { 33 | const a = ref({ 34 | count: 1 35 | }) 36 | let dummy 37 | effect(() => { 38 | dummy = a.value.count 39 | }) 40 | expect(dummy).toBe(1) 41 | a.value.count = 2 42 | expect(dummy).toBe(2) 43 | }) 44 | 45 | // it.skip('should work without initial value', () => { 46 | // const a = ref() 47 | // let dummy 48 | // effect(() => { 49 | // dummy = a.value 50 | // }) 51 | // expect(dummy).toBe(undefined) 52 | // a.value = 2 53 | // expect(dummy).toBe(2) 54 | // }) 55 | 56 | it.skip('should work like a normal property when nested in a reactive object', () => { 57 | const a = ref(1) 58 | const obj = reactive({ 59 | a, 60 | b: { 61 | c: a 62 | } 63 | }) 64 | 65 | let dummy1: number 66 | let dummy2: number 67 | 68 | effect(() => { 69 | dummy1 = obj.a 70 | dummy2 = obj.b.c 71 | }) 72 | 73 | const assertDummiesEqualTo = (val: number) => 74 | [dummy1, dummy2].forEach(dummy => expect(dummy).toBe(val)) 75 | 76 | assertDummiesEqualTo(1) 77 | a.value++ 78 | assertDummiesEqualTo(2) 79 | obj.a++ 80 | assertDummiesEqualTo(3) 81 | obj.b.c++ 82 | assertDummiesEqualTo(4) 83 | }) 84 | 85 | test('isRef', () => { 86 | expect(isRef(ref(1))).toBe(true) 87 | // expect(isRef(computed(() => 1))).toBe(true) 88 | 89 | expect(isRef(0)).toBe(false) 90 | expect(isRef(1)).toBe(false) 91 | // an object that looks like a ref isn't necessarily a ref 92 | expect(isRef({ value: 0 })).toBe(false) 93 | }) 94 | 95 | test('unref', () => { 96 | expect(unref(1)).toBe(1) 97 | expect(unref(ref(1))).toBe(1) 98 | }) 99 | 100 | test('proxyRefs', () => { 101 | const user = { 102 | age: ref(10), 103 | name: 'xiaoming' 104 | } 105 | 106 | const proxyRefsUser = proxyRefs(user) 107 | 108 | expect(proxyRefsUser.age).toBe(10) 109 | expect(proxyRefsUser.name).toBe('xiaoming') 110 | 111 | proxyRefsUser.age++ 112 | expect(proxyRefsUser.age).toBe(11) 113 | expect(user.age.value).toBe(11) 114 | 115 | proxyRefsUser.age = ref(20) 116 | expect(proxyRefsUser.age).toBe(20) 117 | expect(user.age.value).toBe(20) 118 | 119 | user.age = ref(30) 120 | expect(proxyRefsUser.age).toBe(30) 121 | expect(user.age.value).toBe(30) 122 | }) 123 | }) 124 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReactive, shallowReadonly } from '../src/reactive' 2 | 3 | describe('reactivity/shallowReadonly', () => { 4 | test('should not make non-reactive properties reactive', () => { 5 | const props = shallowReadonly({ n: { foo: 1 } }) 6 | expect(isReactive(props.n)).toBe(false) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /packages/reactivity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ai-vue-next/reactivity", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@ai-vue-next/shared": "workspace:^1.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/reactivity/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | '@ai-vue-next/shared': workspace:^1.0.0 5 | 6 | dependencies: 7 | '@ai-vue-next/shared': link:../shared 8 | -------------------------------------------------------------------------------- /packages/reactivity/src/baseHandles.ts: -------------------------------------------------------------------------------- 1 | import { extend, isObject } from '@ai-vue-next/shared' 2 | import { track, trigger } from './effect' 3 | import { reactive, ReactiveFlags, readonly } from './reactive' 4 | 5 | const get = createGetter() 6 | const set = createSetter() 7 | const readonlyGet = createGetter(true) 8 | const shallowReadonlyGet = createGetter(true, true) 9 | 10 | function createGetter(isReadonly = false, shallow = false) { 11 | return function get(target, key) { 12 | if (key === ReactiveFlags.IS_REACTIVE) { 13 | // 注意:这里返回的是!isReadonly而不是true的原因是 14 | // 当对象是readonly的时候就不是reactive 15 | return !isReadonly 16 | } else if (key === ReactiveFlags.IS_READONLY) { 17 | // 注意:这里返回的是isReadonly而不是true的原因是 18 | // 当对象是reactive的时候就不是readonly 19 | return isReadonly 20 | } 21 | const res = Reflect.get(target, key) 22 | 23 | if (shallow) { 24 | return res 25 | } 26 | 27 | if (isObject(res)) { 28 | return isReadonly ? readonly(res) : reactive(res) 29 | } 30 | console.log('触发了getter', key, res) 31 | 32 | if (!isReadonly) { 33 | track(target, key) 34 | } 35 | 36 | return res 37 | } 38 | } 39 | function createSetter() { 40 | return function set(target, key, value) { 41 | const res = Reflect.set(target, key, value) 42 | trigger(target, key) 43 | return res 44 | } 45 | } 46 | 47 | export const mutableHandlers = { 48 | get, 49 | set 50 | } 51 | 52 | export const readonlyHandlers = { 53 | get: readonlyGet, 54 | set(target, key) { 55 | console.warn( 56 | `Set operation on key "${String(key)}" failed: target is readonly.`, 57 | target 58 | ) 59 | return true 60 | } 61 | } 62 | 63 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 64 | get: shallowReadonlyGet 65 | }) 66 | -------------------------------------------------------------------------------- /packages/reactivity/src/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from './effect' 2 | 3 | class ComputedRefImpl { 4 | private _value: any 5 | private _dirty: boolean = true 6 | public readonly effect: ReactiveEffect 7 | constructor(getter) { 8 | this.effect = new ReactiveEffect(getter, () => { 9 | if (!this._dirty) { 10 | this._dirty = true 11 | } 12 | }) 13 | } 14 | 15 | get value() { 16 | if (this._dirty) { 17 | this._dirty = false 18 | this._value = this.effect.run() 19 | } 20 | return this._value 21 | } 22 | } 23 | export function computed(getter) { 24 | return new ComputedRefImpl(getter) 25 | } 26 | -------------------------------------------------------------------------------- /packages/reactivity/src/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '@ai-vue-next/shared' 2 | 3 | const targetMap = new WeakMap() 4 | let activeEffect 5 | 6 | export type EffectScheduler = (...args: any[]) => any 7 | export class ReactiveEffect { 8 | onStop?: () => void 9 | deps = [] 10 | active = true 11 | constructor(public fn, public scheduler: EffectScheduler | null = null) { 12 | this.fn = fn 13 | } 14 | 15 | run() { 16 | if (!this.active) { 17 | // 当使用stop(effect)后,我们在去执行runner就是执行run方法,因为进行了stop所以要在这里返回this.fn() 不能让activeEffect在赋值,不然会继续收集依赖 18 | return this.fn() 19 | } 20 | try { 21 | activeEffect = this 22 | return this.fn() 23 | } finally { 24 | // 当fn执行完之后要把activeEffect清空 如果不清除下次访问属性的时候会把之前的fn收集进去 25 | activeEffect = undefined 26 | } 27 | } 28 | 29 | stop() { 30 | if (this.active) { 31 | cleanupEffect(this) 32 | if (this.onStop) { 33 | this.onStop() 34 | } 35 | this.active = false 36 | } 37 | } 38 | } 39 | 40 | export function stop(runner) { 41 | runner.effect.stop() 42 | } 43 | 44 | function cleanupEffect(effect) { 45 | const { deps } = effect 46 | if (deps.length) { 47 | for (let i = 0; i < deps.length; i++) { 48 | deps[i].delete(effect) 49 | } 50 | // 这里将deps.length设置为0是优化 51 | // 原本删除数组下标每一个set集合的成员,deps.length长度不变 52 | // 这里作用是将每一个key的set集合中存放的当前effect删除 即属性值更新不会执行effect函数 53 | // 当把每一个key的当前effect删除掉之后 就认为依赖清除完了,下次没必要再执行一遍 54 | deps.length = 0 55 | } 56 | } 57 | // { 58 | // target: { // new WeakMap 59 | // key: { // new Map() 60 | // dep: new Set() 61 | // } 62 | // } 63 | // } 64 | export function track(target, key) { 65 | let depsMap = targetMap.get(target) 66 | if (!depsMap) { 67 | targetMap.set(target, (depsMap = new Map())) 68 | } 69 | 70 | let dep = depsMap.get(key) 71 | if (!dep) { 72 | depsMap.set(key, (dep = new Set())) 73 | } 74 | trackEffects(dep) 75 | } 76 | 77 | export function trackEffects(dep) { 78 | // 这里的判断 activeEffect存在时才会收集依赖 因为每次属性被访问都会出发track函数 比如 a=obj.b也会触发 79 | if (isTracking()) { 80 | dep.add(activeEffect) 81 | // 用户写的每一个effect函数都会new一个新的effect对象 里面各自有自己的deps 82 | // dep里存放的是每个对象key的effect回调函数fn 83 | // deps里面是当前effect回调函数里面所有被访问key的回调函数set集合(dep)的数组 84 | // egg: effect(() => { sum = obj.a + user.name + foo.b}) 85 | // 那么当执行用户写的这个effect时候会new一个effect对象 并有一个deps=[]的属性 86 | // 然后将关于a的set集合,name的set集合,b的set集合全部放入deps 87 | // 此时deps里面就包含了用户写的effect函数中所被访问到的所有对象属性的set集合 88 | // obj.a user.name foo.b每一个属性更新都会执行effect 89 | // 也就是他们的set集合中都会有一个相同的effect即当前的effect对象 90 | // 上面stop方法就是将当前effect中所有依赖属性的set集合中删除当前effect 这样当obj.a user.name foo.b更新时因为effect被删除 所以就不会执行了 91 | activeEffect.deps.push(dep) 92 | } 93 | } 94 | 95 | export function trigger(target, key) { 96 | const depsMap = targetMap.get(target) 97 | if (!depsMap) { 98 | // never been tracked 99 | return 100 | } 101 | const dep = depsMap.get(key) 102 | triggerEffects(dep) 103 | } 104 | 105 | export function triggerEffects(dep) { 106 | for (const effect of dep) { 107 | if (effect.scheduler) { 108 | effect.scheduler() 109 | } else { 110 | effect.run() 111 | } 112 | } 113 | } 114 | 115 | export function isTracking() { 116 | return activeEffect !== undefined 117 | } 118 | 119 | export function effect(fn, options?) { 120 | const _effect = new ReactiveEffect(fn) 121 | options && extend(_effect, options) 122 | _effect.run() 123 | const runner: any = _effect.run.bind(_effect) 124 | runner.effect = _effect 125 | return runner 126 | } 127 | -------------------------------------------------------------------------------- /packages/reactivity/src/index.ts: -------------------------------------------------------------------------------- 1 | export { ref, proxyRefs, isRef, unref } from './ref' 2 | export { 3 | reactive, 4 | readonly, 5 | shallowReadonly, 6 | isReactive, 7 | isReadonly, 8 | isProxy 9 | } from './reactive' 10 | export { effect, stop } from './effect' 11 | export { computed } from './computed' 12 | -------------------------------------------------------------------------------- /packages/reactivity/src/reactive.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '@ai-vue-next/shared' 2 | import { 3 | mutableHandlers, 4 | readonlyHandlers, 5 | shallowReadonlyHandlers 6 | } from './baseHandles' 7 | 8 | export const enum ReactiveFlags { 9 | IS_REACTIVE = '__v_isReactive', 10 | IS_READONLY = '__v_isReadonly' 11 | } 12 | export function reactive(raw) { 13 | return createReactiveObject(raw, mutableHandlers) 14 | } 15 | 16 | export function readonly(raw) { 17 | return createReactiveObject(raw, readonlyHandlers) 18 | } 19 | 20 | export function shallowReadonly(raw) { 21 | return createReactiveObject(raw, shallowReadonlyHandlers) 22 | } 23 | 24 | export function isReactive(value) { 25 | // 实现原理: 26 | // 如果value是一个reactive对象 那么访问其任意属性(不论属性是否存在)都会触发get函数 27 | // 在get函数中判断key值是否是当前访问的属性ReactiveFlags.IS_REACTIVE 如果触发了get并且key相等 那么就认为是reactive对象 28 | // 如果value本身不是一个reactive对象 那么访问其不存在的属性会返回undefined 使用!!对其进行boolean处理 29 | return !!value?.[ReactiveFlags.IS_REACTIVE] 30 | } 31 | 32 | export function isReadonly(value) { 33 | // 原理同isReactive 34 | return !!value?.[ReactiveFlags.IS_READONLY] 35 | } 36 | 37 | export function isProxy(value) { 38 | return isReactive(value) || isReadonly(value) 39 | } 40 | 41 | function createReactiveObject(target, baseHandles) { 42 | if (!isObject(target)) { 43 | console.warn(`target ${target} must be is Object`) 44 | return 45 | } 46 | return new Proxy(target, baseHandles) 47 | } 48 | -------------------------------------------------------------------------------- /packages/reactivity/src/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged, isObject } from '@ai-vue-next/shared' 2 | import { isTracking, trackEffects, triggerEffects } from './effect' 3 | import { reactive } from './reactive' 4 | 5 | class RefImpl { 6 | public dep 7 | private _value: any 8 | private _rawValue: any 9 | private readonly __v_isRef: Boolean = true 10 | constructor(value) { 11 | this._value = isObject(value) ? reactive(value) : value 12 | // 存一下原生的值 有可能是被代理过得对象 用于下面set的时候看是否变化 13 | this._rawValue = value 14 | this.dep = new Set() 15 | } 16 | 17 | get value() { 18 | trackRefValue(this) 19 | return this._value 20 | } 21 | 22 | set value(newVal) { 23 | if (hasChanged(newVal, this._rawValue)) { 24 | this._rawValue = newVal 25 | this._value = isObject(newVal) ? reactive(newVal) : newVal 26 | triggerRefValue(this) 27 | } 28 | } 29 | } 30 | 31 | function trackRefValue(ref) { 32 | if (isTracking()) { 33 | trackEffects(ref.dep) 34 | } 35 | } 36 | 37 | function triggerRefValue(ref) { 38 | triggerEffects(ref.dep) 39 | } 40 | 41 | export function ref(value) { 42 | return new RefImpl(value) 43 | } 44 | 45 | export function isRef(r) { 46 | return Boolean(r && r.__v_isRef) 47 | } 48 | 49 | export function unref(r) { 50 | return isRef(r) ? r.value : r 51 | } 52 | 53 | export function proxyRefs(objectWithRefs) { 54 | return new Proxy(objectWithRefs, { 55 | get(target, key) { 56 | return unref(Reflect.get(target, key)) 57 | }, 58 | set(target, key, value) { 59 | const oldValue = target[key] 60 | if (isRef(oldValue) && !isRef(value)) { 61 | oldValue.value = value 62 | return true 63 | } else { 64 | return Reflect.set(target, key, value) 65 | } 66 | } 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /packages/runtime-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ai-vue-next/runtime-core", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@ai-vue-next/reactivity": "workspace:^1.0.0", 14 | "@ai-vue-next/shared": "workspace:^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/runtime-core/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | '@ai-vue-next/reactivity': workspace:^1.0.0 5 | '@ai-vue-next/shared': workspace:^1.0.0 6 | 7 | dependencies: 8 | '@ai-vue-next/reactivity': link:../reactivity 9 | '@ai-vue-next/shared': link:../shared 10 | -------------------------------------------------------------------------------- /packages/runtime-core/src/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '@ai-vue-next/shared' 2 | import { getCurrentInstace } from './component' 3 | 4 | export function provide(key, value) { 5 | const currentInstance: any = getCurrentInstace() 6 | debugger 7 | if (currentInstance) { 8 | let { provides } = currentInstance 9 | const parentProvides = currentInstance.parent.provides 10 | // 只有初始化的时候才为provides改变原型 11 | // 否则每次provide执行的时候之前赋值全被清空了 12 | if (provides === parentProvides) { 13 | // 将自己的provides的原型改为父级的provides原型对象 14 | provides = currentInstance.provides = Object.create(parentProvides) 15 | } 16 | provides[key] = value 17 | } 18 | } 19 | 20 | export function inject(key, defaultValue) { 21 | const currentInstance: any = getCurrentInstace() 22 | if (currentInstance) { 23 | // inject每次都是去父级上的provides上找 如果找不到就接着向上找 24 | const { provides } = currentInstance.parent 25 | // 先去原型链上看看有没有key 有的滑直接取 没有的滑看看是否给有默认值 并返回 26 | if (key in provides) { 27 | return provides[key] 28 | } else if (defaultValue) { 29 | // 支持默认值是函数 30 | if (isFunction(defaultValue)) { 31 | return defaultValue() 32 | } 33 | return defaultValue 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/runtime-core/src/component.ts: -------------------------------------------------------------------------------- 1 | import { shallowReadonly } from '@ai-vue-next/reactivity' 2 | import { proxyRefs } from '@ai-vue-next/reactivity' 3 | import { isFunction, isObject } from '@ai-vue-next/shared' 4 | import { emit } from './componentEmit' 5 | import { initProps } from './componentProps' 6 | import { PublicInstanceProxyHandlers } from './componentPublicInstance' 7 | import { initSlots } from './componentSlots' 8 | 9 | let currentInstance = null 10 | 11 | export function createComponentInstance(vnode, parent) { 12 | const component = { 13 | vnode, 14 | type: vnode.type, 15 | setupState: {}, 16 | props: {}, 17 | slots: {}, 18 | next: null, 19 | provides: parent ? parent.provides : {}, 20 | parent, 21 | isMounted: false, 22 | subTree: {}, 23 | emit: () => {} 24 | } 25 | 26 | component.emit = emit.bind(null, component) as () => {} 27 | // console.log('createComponentInstance', parent, component) 28 | 29 | return component 30 | } 31 | 32 | export function setupComponent(instance) { 33 | // TODO: 34 | initProps(instance, instance.vnode.props) 35 | initSlots(instance, instance.vnode.children) 36 | setupStatefulComponent(instance) 37 | } 38 | 39 | function setupStatefulComponent(instance: any) { 40 | const Component = instance.type 41 | // 声依永proxy将setupState挂在到组建实例上 42 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers) 43 | 44 | // call setup() 45 | const { setup } = Component 46 | 47 | if (setup) { 48 | setCurrentInstance(instance) 49 | const setupResult = setup(shallowReadonly(instance.props), { 50 | emit: instance.emit 51 | }) 52 | setCurrentInstance(null) 53 | handleSetupResult(instance, setupResult) 54 | } 55 | } 56 | 57 | function handleSetupResult(instance: any, setupResult: any) { 58 | if (isFunction(setupResult)) { 59 | // TODO: 60 | } else if (isObject(setupResult)) { 61 | instance.setupState = proxyRefs(setupResult) 62 | } 63 | 64 | finishComponentSetup(instance) 65 | } 66 | 67 | function finishComponentSetup(instance: any) { 68 | const Component = instance.type 69 | 70 | if (compiler && !Component.render) { 71 | if (Component.template) { 72 | Component.render = compiler(Component.template) 73 | } 74 | } 75 | 76 | if (Component.render) { 77 | instance.render = Component.render 78 | } 79 | } 80 | 81 | function setCurrentInstance(instance) { 82 | currentInstance = instance 83 | } 84 | 85 | export function getCurrentInstace() { 86 | return currentInstance 87 | } 88 | 89 | let compiler 90 | export function registerRuntimeCompiler(_compiler) { 91 | compiler = _compiler 92 | } 93 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { camelize, toHandlerKey, capitalize } from '@ai-vue-next/shared' 2 | 3 | export function emit(instance, event, ...args) { 4 | const { props } = instance 5 | event = toHandlerKey(camelize(capitalize(event))) 6 | const handle = props[event] 7 | handle && handle(...args) 8 | } 9 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance, rawProps) { 2 | instance.props = rawProps || {} 3 | } 4 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from '@ai-vue-next/shared' 2 | 3 | export const publicPropertiesMap = { 4 | $el: i => i.vnode.el, 5 | $slots: i => i.slots, 6 | $props: i => i.props 7 | } 8 | export const PublicInstanceProxyHandlers = { 9 | get({ _: instance }, key) { 10 | const { setupState, props } = instance 11 | if (hasOwn(setupState, key)) { 12 | return setupState[key] 13 | } else if (hasOwn(props, key)) { 14 | return props[key] 15 | } 16 | const publicGetter = publicPropertiesMap[key] 17 | if (publicGetter) { 18 | return publicGetter(instance) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags, hasOwn, isArray } from '@ai-vue-next/shared' 2 | export function initSlots(instance, children) { 3 | const { shapeFlag } = instance.vnode 4 | if (shapeFlag & ShapeFlags.SLOT_CHILDREN) { 5 | normalizeObjectSlots(children, instance) 6 | } 7 | } 8 | 9 | function normalizeObjectSlots(children: any, instance: any) { 10 | const slots = {} 11 | for (const name in children) { 12 | if (hasOwn(children, name)) { 13 | const slot = children[name] 14 | slots[name] = props => normalizeSlotValue(slot(props)) 15 | } 16 | } 17 | instance.slots = slots 18 | } 19 | 20 | function normalizeSlotValue(value) { 21 | return isArray(value) ? value : [value] 22 | } 23 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentUpdateUtils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUpdateComponent(prevVNode, nextVNode) { 2 | const { props: prevProps } = prevVNode 3 | const { props: nextProps } = nextVNode 4 | 5 | for (const key in nextProps) { 6 | if (nextProps[key] !== prevProps[key]) { 7 | return true 8 | } 9 | } 10 | 11 | return false 12 | } 13 | -------------------------------------------------------------------------------- /packages/runtime-core/src/createApp.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from './vnode' 2 | 3 | export function createAppAPI(render) { 4 | return function createApp(rootComponent) { 5 | return { 6 | mount(rootContainer) { 7 | // 生成vnode 8 | const vnode = createVNode(rootComponent) 9 | // 渲染vnode 10 | render(vnode, rootContainer) 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /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/runtime-core/src/helpers/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '@ai-vue-next/shared' 2 | import { createVNode, Fragment } from '../vnode' 3 | 4 | export function renderSlots(slots, name, props) { 5 | const slot = slots[name] 6 | if (slot) { 7 | if (isFunction(slot)) { 8 | return slot ? createVNode(Fragment, {}, slot(props)) : '' 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/runtime-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export { h } from './h' 2 | export { renderSlots } from './helpers/renderSlots' 3 | export { createTextVNode, createElementVNode } from './vnode' 4 | export { getCurrentInstace, registerRuntimeCompiler } from './component' 5 | export { provide, inject } from './apiInject' 6 | export { createRenderer } from './renderer' 7 | export { nextTick } from './scheduler' 8 | export { toDisplayString } from '@ai-vue-next/shared' 9 | export * from '@ai-vue-next/reactivity' 10 | -------------------------------------------------------------------------------- /packages/runtime-core/src/renderer.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY_OBJ, hasOwn, ShapeFlags } from '@ai-vue-next/shared' 2 | import { createComponentInstance, setupComponent } from './component' 3 | import { Fragment, isSameVNodeType, Text } from './vnode' 4 | import { createAppAPI } from './createApp' 5 | import { effect } from '@ai-vue-next/reactivity' 6 | import { shouldUpdateComponent } from './componentUpdateUtils' 7 | import { queueJobs } from './scheduler' 8 | 9 | export function createRenderer(options) { 10 | const { 11 | createElement: hostCreateElement, 12 | patchProp: hostPatchProp, 13 | insert: hostInsert, 14 | remove: hostRemove, 15 | setElementText: hostSetElementText 16 | } = options 17 | 18 | function render(vnode, container) { 19 | patch(null, vnode, container, null, null) 20 | } 21 | 22 | function patch(n1, n2, container, parentComponent, anchor) { 23 | // 处理组件 24 | const { type, shapeFlag } = n2 25 | switch (type) { 26 | case Fragment: 27 | processFragment(n1, n2, container, parentComponent, anchor) 28 | break 29 | case Text: 30 | processText(n1, n2, container) 31 | break 32 | 33 | default: 34 | if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 35 | processComponent(n1, n2, container, parentComponent, anchor) 36 | } else if (shapeFlag & ShapeFlags.ELEMENT) { 37 | processElement(n1, n2, container, parentComponent, anchor) 38 | } 39 | break 40 | } 41 | } 42 | 43 | function processFragment(n1, n2, container: any, parentComponent, anchor) { 44 | mountChildren(n2.children, container, parentComponent, anchor) 45 | } 46 | 47 | function processText(n1, n2, container: any) { 48 | const text = (n2.el = document.createTextNode(n2.children)) 49 | container.append(text) 50 | } 51 | 52 | function processComponent(n1, n2, container: any, parentComponent, anchor) { 53 | if (!n1) { 54 | mountComponent(n2, container, parentComponent, anchor) 55 | } else { 56 | updateComponent(n1, n2) 57 | } 58 | } 59 | 60 | function processElement(n1, n2, container: any, parentComponent, anchor) { 61 | if (!n1) { 62 | mountElement(n2, container, parentComponent, anchor) 63 | } else { 64 | patchElement(n1, n2, container, parentComponent, anchor) 65 | } 66 | } 67 | 68 | // mount 69 | function mountComponent( 70 | initialVNode: any, 71 | container, 72 | parentComponent, 73 | anchor 74 | ) { 75 | const instance = (initialVNode.component = createComponentInstance( 76 | initialVNode, 77 | parentComponent 78 | )) 79 | 80 | setupComponent(instance) 81 | setupRenderEffect(instance, initialVNode, container, anchor) 82 | } 83 | 84 | function mountElement(n2, container: any, parentComponent, anchor) { 85 | const { type, props, children, shapeFlag } = n2 86 | // 生成标签 87 | // const el = (vnode.el = document.createElement(type)) 88 | const el = (n2.el = hostCreateElement(type)) 89 | // 生成属性 90 | for (const key in props) { 91 | if (hasOwn(props, key)) { 92 | const value = props[key] 93 | hostPatchProp(el, key, null, value) 94 | } 95 | } 96 | // 生成子节点 97 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 98 | // 文本节点 99 | el.textContent = children 100 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 101 | mountChildren(children, el, parentComponent, anchor) 102 | } 103 | 104 | // container.append(el) 105 | hostInsert(el, container, anchor) 106 | } 107 | 108 | function mountChildren( 109 | children: any[], 110 | container: any, 111 | parentComponent, 112 | anchor 113 | ) { 114 | children.forEach(vnode => { 115 | patch(null, vnode, container, parentComponent, anchor) 116 | }) 117 | } 118 | 119 | // update component 120 | function updateComponent(n1, n2) { 121 | // 每次属性更新 都会触发patch 如果不是props更新就没必要更新组件 122 | const instance = (n2.component = n1.component) 123 | if (shouldUpdateComponent(n1, n2)) { 124 | instance.next = n2 125 | instance.update() 126 | } else { 127 | // 不需要更新的时候 需要把n1.el 给到n2 128 | n2.el = n1.el 129 | n2.vnode = n2 130 | } 131 | } 132 | 133 | // patch 134 | function patchElement(n1, n2, container, parentComponent, anchor) { 135 | console.log('patchElement') 136 | console.log('n1', n1) 137 | console.log('n2', n2) 138 | const el = (n2.el = n1.el) 139 | const prevProps = n1.props || EMPTY_OBJ 140 | const nextProps = n2.props || EMPTY_OBJ 141 | 142 | patchChildren(n1, n2, el, parentComponent, anchor) 143 | patchProp(el, prevProps, nextProps) 144 | } 145 | 146 | function patchProp(el: any, oldProps: any, newProps: any) { 147 | if (oldProps !== newProps) { 148 | for (const key in newProps) { 149 | const next = newProps[key] 150 | const prev = oldProps[key] 151 | if (next !== prev) { 152 | hostPatchProp(el, key, prev, next) 153 | } 154 | } 155 | 156 | if (oldProps !== EMPTY_OBJ) { 157 | for (const key in oldProps) { 158 | if (!(key in newProps)) { 159 | hostPatchProp(el, key, oldProps[key], null) 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | function patchChildren(n1, n2, container, parentComponent, anchor) { 167 | const prevShapeFlag = n1.shapeFlag 168 | const { shapeFlag } = n2 169 | const c1 = n1.children 170 | const c2 = n2.children 171 | // 自节点有三种可能情况: text array 没有节点 null 172 | // 新节点为文本节点 173 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 174 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { 175 | // 如果老节点是数组节点 新节点是文本节点 卸载老节点 176 | unmountChildren(c1) 177 | } 178 | // 设置文本节点 179 | if (c1 !== c2) { 180 | // 新老节点不一样的时候触发设置文本节点 181 | hostSetElementText(container, c2) 182 | } 183 | } else { 184 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { 185 | // 新节点为 array | no children 186 | if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 187 | // 新节点为 array 新老节点都是array 进行diff 188 | patchKeyedChildren(c1, c2, container, parentComponent, anchor) 189 | } else { 190 | // 老节点是数组 没有新的孩子节点 卸载旧节点 191 | unmountChildren(c1) 192 | } 193 | } else { 194 | // 老的节点是文本或空 195 | // 新的节点是数组或空 196 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { 197 | // 老的节点是文本 198 | // 删除老节点 199 | hostSetElementText(container, '') 200 | } 201 | if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 202 | // 添加新节点 203 | mountChildren(c2, container, parentComponent, anchor) 204 | } 205 | } 206 | } 207 | } 208 | 209 | function unmountChildren(children) { 210 | for (let i = 0; i < children.length; i++) { 211 | const el = children[i].el 212 | hostRemove(el) 213 | } 214 | } 215 | 216 | function unmount(el) { 217 | hostRemove(el) 218 | } 219 | 220 | function patchKeyedChildren( 221 | c1, 222 | c2, 223 | container, 224 | parentComponent, 225 | parentAnchor 226 | ) { 227 | const l2 = c2.length 228 | let i = 0 229 | let e1 = c1.length - 1 230 | let e2 = l2 - 1 231 | 232 | // 1. 从头同步 233 | // (a b) c 234 | // (a b) d e 235 | while (i <= e1 && i <= e2) { 236 | const n1 = c1[i] 237 | const n2 = c2[i] 238 | if (isSameVNodeType(n1, n2)) { 239 | patch(n1, n2, container, parentComponent, parentAnchor) 240 | } else { 241 | break 242 | } 243 | i++ 244 | } 245 | // 2. 从尾同步 246 | // a (b c) 247 | // d e (b c) 248 | while (i <= e1 && i <= e2) { 249 | const n1 = c1[e1] 250 | const n2 = c2[e2] 251 | if (isSameVNodeType(n1, n2)) { 252 | patch(n1, n2, container, parentComponent, parentAnchor) 253 | } else { 254 | break 255 | } 256 | e1-- 257 | e2-- 258 | } 259 | // 3. 普通序列+挂载 260 | // (a b) 261 | // (a b) c 262 | // i = 2, e1 = 1, e2 = 2 263 | // (a b) 264 | // c (a b) 265 | // i = 0, e1 = -1, e2 = 0 266 | if (i > e1) { 267 | if (i <= e2) { 268 | const nextPos = e2 + 1 269 | const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor 270 | while (i <= e2) { 271 | patch(null, c2[i], container, parentComponent, anchor) 272 | i++ 273 | } 274 | } 275 | } 276 | // 4. 常用序列+卸载 277 | // (a b) c 278 | // (a b) 279 | // i = 2, e1 = 2, e2 = 1 280 | // a (b c) 281 | // (b c) 282 | // i = 0, e1 = 0, e2 = -1 283 | else if (i > e2) { 284 | while (i <= e1) { 285 | unmount(c1[i].el) 286 | i++ 287 | } 288 | } 289 | // 5. 未知序列 290 | // [i ... e1 + 1]: a b [c d e] f g 291 | // [i ... e2 + 1]: a b [e d c h] f g 292 | // i = 2, e1 = 4, e2 = 5 293 | else { 294 | debugger 295 | const s1 = i // 老的起始下标 296 | const s2 = i // 新的起始下标 297 | 298 | // 5.1 为 newChildren 构建 key:index map 299 | const keyToNewIndexMap = new Map() 300 | for (let i = s2; i <= e2; i++) { 301 | const nextChild = c2[i] 302 | if (nextChild.key != null) { 303 | // != null 包含了 null 和 undefined 304 | keyToNewIndexMap.set(nextChild.key, i) 305 | } 306 | } 307 | 308 | // 5.2 循环遍历老节点 进行打补丁 309 | // 匹配节点并删除不再存在的节点 310 | let patched = 0 // 已经打过补丁的个数 311 | const toBePatch = e2 - s2 + 1 // 将要被打补丁的总数 312 | let maxNewIndexSoFar = 0 // 用于跟踪是否有任何节点移动 313 | let move = false // 是否需要移动 314 | const newIndexToOldIndexMap = new Array(toBePatch) // 创建一个以新节点个数为长度的数组 数组中每一项存放老节点的下标 315 | // 初始化都为0 这里初始化不是设置0 不使用fill是性能问题 316 | for (let i = 0; i < toBePatch; i++) { 317 | newIndexToOldIndexMap[i] = 0 318 | } 319 | for (let i = s1; i <= e1; i++) { 320 | const preChild = c1[i] 321 | if (patched >= toBePatch) { 322 | // [i ... e1 + 1]: a b [c d e h i] f g 323 | // [i ... e2 + 1]: a b [e c] f g 324 | // 如果打过补丁的个数 >= 将要被打补丁的总数 说明剩下的老节点都要被卸载 325 | // h i将被直接卸载 326 | unmount(preChild.el) 327 | continue 328 | } 329 | let newIndex 330 | if (preChild.key != null) { 331 | // 如果老节点有key的话 直接在map中取值 332 | newIndex = keyToNewIndexMap.get(preChild.key) 333 | } else { 334 | // 老节点没有key 就尝试定位同类型的无键节点 335 | for (let j = 0; j <= e2; j++) { 336 | if (isSameVNodeType(preChild, c2[j])) { 337 | // 如果找到相同的节点 给newIndex赋值 直接跳出循环 338 | newIndex = j 339 | break 340 | } 341 | } 342 | } 343 | 344 | if (newIndex === undefined) { 345 | // 新的节点中没有这个老节点 进行卸载 346 | unmount(preChild.el) 347 | } else { 348 | // 这个老节点在新节点中也存在 记录下新节点下标newIndex对应的老节点的下标i 349 | // 由于初始化为0 而0在后面会被处理为新增 所以这里进行了i+1 350 | // 解决下面的情况 会出现的问题 351 | // [c d e] f g 352 | // [e d c h] f g 353 | // 当处理c的时候发现c在新节点中也存在 走到这块如果设置为i就是0 那么后面处理新增的时候就会把c在创建一份 而c只是要移动位置 354 | newIndexToOldIndexMap[newIndex - s2] = i + 1 355 | // 判断是否需要移动 356 | // 解决一下情况 357 | // a b [d] f g 358 | // a b [c d e] f g 359 | // 上面的d不需要移动 只需要创建c e节点 360 | if (newIndex >= maxNewIndexSoFar) { 361 | maxNewIndexSoFar = newIndex 362 | } else { 363 | move = true 364 | } 365 | // 再次进行patch 366 | patch( 367 | preChild, 368 | c2[newIndex], 369 | container, 370 | parentComponent, 371 | parentAnchor 372 | ) 373 | patched++ 374 | } 375 | } 376 | // 5.2 流程走完 卸载了新节点没有的老节点 新节点中有的老节点会再次打补丁 所以这个时候新节点中所有的老节点都是有el属性的 377 | 378 | // 5.3 移动和挂载 379 | // 仅在节点移动时生成最长稳定子序列 380 | const increasingNewIndexSequence = move 381 | ? getSequence(newIndexToOldIndexMap) 382 | : [] // 生成最长递增子序列的下标 383 | let j = increasingNewIndexSequence.length - 1 // 最长递增子序列数组的索引 384 | for (let i = toBePatch - 1; i >= 0; i--) { 385 | const nextIndex = s2 + i 386 | const nextChild = c2[nextIndex] 387 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor 388 | if (newIndexToOldIndexMap[i] === 0) { 389 | // 新增 390 | patch(null, nextChild, container, parentComponent, anchor) 391 | } else if (move) { 392 | if (increasingNewIndexSequence[j] !== i) { 393 | // 需要移动 394 | console.log('需要移动', j) 395 | hostInsert(nextChild.el, container, anchor) 396 | } else { 397 | // 不需要移动 最长递增子序列数组的索引前移 398 | j-- 399 | } 400 | } 401 | } 402 | } 403 | } 404 | 405 | function setupRenderEffect(instance, initialVNode, container, anchor) { 406 | instance.update = effect( 407 | () => { 408 | if (!instance.isMounted) { 409 | console.log('init') 410 | const { proxy } = instance 411 | const subTree = (instance.subTree = instance.render.call( 412 | proxy, 413 | proxy 414 | )) 415 | patch(null, subTree, container, instance, anchor) 416 | // patch之后将跟节点挂在到vnode上 就是createVNode创建出来的对象中的el 417 | initialVNode.el = subTree.el 418 | instance.isMounted = true 419 | } else { 420 | console.log('update') 421 | // 找出下次更新的vnode(next) vnode是之前的 422 | // 先改props然后在进行patch更新 423 | const { next, vnode } = instance 424 | if (next) { 425 | next.el = vnode.el 426 | updateComponentPreRender(instance, next) 427 | } 428 | const { proxy } = instance 429 | const subTree = instance.render.call(proxy, proxy) 430 | const prevSubtree = instance.subTree 431 | instance.subTree = subTree 432 | 433 | console.log('current', subTree) 434 | console.log('prev', prevSubtree) 435 | patch(prevSubtree, subTree, container, instance, anchor) 436 | } 437 | }, 438 | { 439 | scheduler: () => { 440 | console.log('update--scheduler') 441 | queueJobs(instance.update) 442 | } 443 | } 444 | ) 445 | } 446 | 447 | return { 448 | createApp: createAppAPI(render) 449 | } 450 | } 451 | 452 | function updateComponentPreRender(instance, nextVNode) { 453 | instance.vnode = nextVNode 454 | instance.next = null 455 | instance.props = nextVNode.props 456 | } 457 | 458 | // https://en.wikipedia.org/wiki/Longest_increasing_subsequence 459 | function getSequence(arr: number[]): number[] { 460 | const p = arr.slice() 461 | const result = [0] 462 | let i, j, u, v, c 463 | const len = arr.length 464 | for (i = 0; i < len; i++) { 465 | const arrI = arr[i] 466 | // 此算法中排除了等于0的情况,原因是0成为了diff算法中的占位符 467 | if (arrI !== 0) { 468 | // 用当前num与result中的最后一项对比 469 | j = result[result.length - 1] 470 | // 当前数值大于result子序列最后一项时,直接往后新增,并将当前数值的前一位result保存 471 | if (arr[j] < arrI) { 472 | p[i] = j // 最后一项与 p 对应的索引进行对应 473 | result.push(i) 474 | continue 475 | } 476 | u = 0 477 | v = result.length - 1 478 | // 当前数值小于result子序列最后一项时,使用二分法找到第一个大于当前数值的下标 479 | while (u < v) { 480 | // 除2 并向下取整 481 | c = (u + v) >> 1 482 | if (arr[result[c]] < arrI) { 483 | u = c + 1 484 | } else { 485 | v = c 486 | } 487 | } 488 | // 比较 => 替换 489 | if (arrI < arr[result[u]]) { 490 | // 找到下标,将当前下标对应的前一位result保存(如果找到的是第一位,不需要操作,第一位前面没有了) 491 | if (u > 0) { 492 | p[i] = result[u - 1] // 正确的结果 493 | } 494 | // 找到下标,直接替换result中的数值 495 | result[u] = i // 有可能替换会导致结果不正确,需要一个新数组 p 记录正确的结果 496 | } 497 | } 498 | } 499 | u = result.length 500 | v = result[u - 1] 501 | // 回溯,直接从最后一位开始,将前面的result全部覆盖,如果不需要修正,则p中记录的每一项都是对应的前一位,不会有任何影响 502 | while (u-- > 0) { 503 | result[u] = v 504 | v = p[v] 505 | } 506 | return result 507 | } 508 | -------------------------------------------------------------------------------- /packages/runtime-core/src/scheduler.ts: -------------------------------------------------------------------------------- 1 | const p = Promise.resolve() 2 | const queue: any[] = [] 3 | let isFlushPending = false 4 | export function queueJobs(job) { 5 | if (!queue.includes(job)) { 6 | queue.push(job) 7 | } 8 | 9 | queueFlush() 10 | } 11 | 12 | export function nextTick(fn) { 13 | typeof fn ? p.then(fn) : p 14 | } 15 | 16 | function queueFlush() { 17 | if (isFlushPending) { 18 | return 19 | } 20 | isFlushPending = true 21 | 22 | nextTick(flushJobs) 23 | } 24 | 25 | function flushJobs() { 26 | isFlushPending = false 27 | let job 28 | while ((job = queue.shift())) { 29 | job && job() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/runtime-core/src/vnode.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isObject, ShapeFlags } from '@ai-vue-next/shared' 2 | 3 | export const Fragment = Symbol('Fragment') 4 | export const Text = Symbol('Text') 5 | 6 | export { createVNode as createElementVNode } 7 | export function createVNode(type, props?, children?) { 8 | const vnode = { 9 | type, 10 | key: props?.key, 11 | props, 12 | children, 13 | component: null, 14 | el: null, 15 | shapeFlag: getShapeFlag(type) 16 | } 17 | if (typeof children === 'string') { 18 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN 19 | } else if (isArray(children)) { 20 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN 21 | } 22 | 23 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isObject(children)) { 24 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN 25 | } 26 | 27 | return vnode 28 | } 29 | 30 | export function createTextVNode(text: string) { 31 | return createVNode(Text, {}, text) 32 | } 33 | 34 | export function isSameVNodeType(n1, n2): boolean { 35 | return n1.type === n2.type && n1.key === n2.key 36 | } 37 | 38 | function getShapeFlag(type: any) { 39 | if (isObject(type)) { 40 | return ShapeFlags.STATEFUL_COMPONENT 41 | } else { 42 | return ShapeFlags.ELEMENT 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/runtime-dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ai-vue-next/runtime-dom", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@ai-vue-next/runtime-core": "workspace:^1.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/runtime-dom/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | '@ai-vue-next/runtime-core': workspace:^1.0.0 5 | 6 | dependencies: 7 | '@ai-vue-next/runtime-core': link:../runtime-core 8 | -------------------------------------------------------------------------------- /packages/runtime-dom/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from '@ai-vue-next/runtime-core' 2 | import { isArray, isOn } from '@ai-vue-next/shared' 3 | export * from '@ai-vue-next/runtime-core' 4 | 5 | function createElement(tag) { 6 | return document.createElement(tag) 7 | } 8 | 9 | function patchProp(el, key, oldValue, newValue) { 10 | newValue = isArray(newValue) ? newValue.join(' ') : newValue 11 | if (isOn(key)) { 12 | // 处理事件 13 | const event = key.slice(2).toLowerCase() 14 | el.addEventListener(event, newValue) 15 | } else { 16 | // 处理属性 17 | if (newValue === undefined || newValue === null) { 18 | el.removeAttribute(key) 19 | } else { 20 | el.setAttribute(key, newValue) 21 | } 22 | } 23 | } 24 | 25 | function insert(el, parent, anchor = null) { 26 | parent.insertBefore(el, anchor) 27 | } 28 | 29 | function remove(child) { 30 | const parentNode = child.parentNode 31 | if (parentNode) { 32 | parentNode.removeChild(child) 33 | } 34 | } 35 | 36 | function setElementText(el, text) { 37 | el.textContent = text 38 | } 39 | 40 | const renderer: any = createRenderer({ 41 | createElement, 42 | patchProp, 43 | insert, 44 | remove, 45 | setElementText 46 | }) 47 | 48 | export const createApp = (...args) => { 49 | return renderer.createApp(...args) 50 | } 51 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ai-vue-next/shared", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './toDisplayString' 2 | 3 | export * from './shapeFlags' 4 | 5 | export const extend = Object.assign 6 | 7 | export const EMPTY_OBJ = {} 8 | 9 | export const isString = value => typeof value === 'string' 10 | 11 | export const isObject = value => typeof value === 'object' && value !== null 12 | 13 | export const hasChanged = (value, oldValue) => !Object.is(value, oldValue) 14 | 15 | export const isFunction = (val: unknown): val is Function => 16 | typeof val === 'function' 17 | 18 | export const isArray = Array.isArray 19 | 20 | export const isOn = val => /^on[A-Z]/.test(val) 21 | 22 | export const hasOwn = (obj, key) => 23 | Object.prototype.hasOwnProperty.call(obj, key) 24 | 25 | // add => Add 26 | export const capitalize = (val: string) => 27 | val.charAt(0).toUpperCase() + val.slice(1) 28 | 29 | // add-foo => addFoo 30 | export const camelize = (val: string) => 31 | val.replace(/-(\w)/g, (_, c: string) => { 32 | return c ? c.toUpperCase() : '' 33 | }) 34 | 35 | export const toHandlerKey = (val: string) => 'on' + val 36 | -------------------------------------------------------------------------------- /packages/shared/src/shapeFlags.ts: -------------------------------------------------------------------------------- 1 | export const enum ShapeFlags { 2 | ELEMENT = 1, 3 | STATEFUL_COMPONENT = 1 << 1, 4 | TEXT_CHILDREN = 1 << 2, 5 | ARRAY_CHILDREN = 1 << 3, 6 | SLOT_CHILDREN = 1 << 4 7 | } 8 | -------------------------------------------------------------------------------- /packages/shared/src/toDisplayString.ts: -------------------------------------------------------------------------------- 1 | export function toDisplayString(value) { 2 | return String(value) 3 | } 4 | -------------------------------------------------------------------------------- /packages/vue/.pnpm-debug.log: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/vue/examples/ApiInject/App.js: -------------------------------------------------------------------------------- 1 | import { h, provide, inject } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'App', 5 | setup() {}, 6 | render() { 7 | return h('div', {}, [h('p', {}, 'ApiInject'), h(Provider)]) 8 | } 9 | } 10 | 11 | const Provider = { 12 | name: 'Provider', 13 | setup() { 14 | provide('foo', 'fooVal') 15 | provide('bar', 'barVal') 16 | // provide('baz', 'bazVal') 17 | }, 18 | render() { 19 | return h('div', {}, [h('p', {}, 'Provider'), h(ProviderTwo)]) 20 | } 21 | } 22 | 23 | const ProviderTwo = { 24 | name: 'ProviderTwo', 25 | setup() { 26 | provide('foo', 'fooTwo') 27 | const foo = inject('foo') 28 | 29 | return { 30 | foo 31 | } 32 | }, 33 | render() { 34 | return h('div', {}, [h('p', {}, `ProviderTwo: ${this.foo}`), h(Consumer)]) 35 | } 36 | } 37 | 38 | const Consumer = { 39 | name: 'Consumer', 40 | setup() { 41 | // provide('foo', 'fooTwo1') 42 | const foo = inject('foo') 43 | const bar = inject('bar') 44 | // const baz = inject('baz', 'bazDefault') 45 | const baz = inject('baz', () => 'bazDefault') 46 | 47 | return { 48 | foo, 49 | bar, 50 | baz 51 | } 52 | }, 53 | render() { 54 | return h('div', {}, `Consumer-${this.foo}-${this.bar}-${this.baz}`) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/vue/examples/ApiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/vue/examples/ApiInject/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /packages/vue/examples/ComponentEvent/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import Foo from './Foo.js' 3 | 4 | export default { 5 | name: 'App', 6 | setup() {}, 7 | render() { 8 | return h( 9 | 'div', 10 | { 11 | class: 'app' 12 | }, 13 | [ 14 | h('p', {}, 'App组件'), 15 | h(Foo, { 16 | onAdd: (...args) => { 17 | console.log('App组件调用了onAdd方法', ...args) 18 | }, 19 | onAddFoo: (...args) => { 20 | console.log('App组件调用了onAddFoo方法', ...args) 21 | } 22 | }) 23 | ] 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/vue/examples/ComponentEvent/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'Foo', 5 | setup(props, { emit }) { 6 | const emitAdd = () => { 7 | console.log('emitAdd') 8 | emit('add', 1, 2) 9 | emit('add-foo', 3, 4) 10 | } 11 | 12 | return { 13 | emitAdd 14 | } 15 | }, 16 | render() { 17 | const emitBtn = h( 18 | 'button', 19 | { class: 'emit-btn', onClick: this.emitAdd }, 20 | 'emitBtn' 21 | ) 22 | const foo = h('p', {}, 'foo组件') 23 | return h('div', { class: 'foo' }, [foo, emitBtn]) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/vue/examples/ComponentEvent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/vue/examples/ComponentEvent/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /packages/vue/examples/ComponentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { h, createTextVNode } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import Foo from './Foo.js' 3 | 4 | export default { 5 | name: 'App', 6 | setup() {}, 7 | render() { 8 | return h('div', {}, [ 9 | h('p', {}, 'App'), 10 | h( 11 | Foo, 12 | {}, 13 | // h('p', {class: 'custom-element1'}, 'slot-element'), 14 | { 15 | header: props => [ 16 | h( 17 | 'p', 18 | { class: 'custom-element1' }, 19 | 'slot-element-header' + props.name 20 | ), 21 | createTextVNode('我是textNode节点') 22 | ], 23 | footer: () => 24 | h('p', { class: 'custom-element1' }, 'slot-element-footer') 25 | } 26 | ) 27 | ]) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/vue/examples/ComponentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'Foo', 5 | setup() {}, 6 | render() { 7 | const name = '作用域插槽' 8 | return h('div', {}, [ 9 | renderSlots(this.$slots, 'header', { 10 | name 11 | }), 12 | h('p', {}, 'foo'), 13 | renderSlots(this.$slots, 'footer') 14 | ]) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/vue/examples/ComponentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/vue/examples/ComponentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /packages/vue/examples/CurrentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstace } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import Foo from './Foo.js' 3 | 4 | export default { 5 | name: 'App', 6 | setup() { 7 | const instance = getCurrentInstace() 8 | console.log('App', instance) 9 | }, 10 | render() { 11 | return h('div', {}, [h('p', {}, 'app'), h(Foo)]) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/vue/examples/CurrentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstace } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'Foo', 5 | setup() { 6 | const instance = getCurrentInstace() 7 | console.log('Foo', instance) 8 | }, 9 | render() { 10 | return h('div', {}, 'Foo') 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/vue/examples/CurrentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/vue/examples/CurrentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /packages/vue/examples/CustomRenderer/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'App', 5 | setup() { 6 | const x = 100 7 | const y = 100 8 | 9 | return { 10 | x, 11 | y 12 | } 13 | }, 14 | render() { 15 | return h('rect', { x: this.x, y: this.y }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/vue/examples/CustomRenderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/vue/examples/CustomRenderer/main.js: -------------------------------------------------------------------------------- 1 | import { createRenderer } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | // @ts-ignore 5 | const game = new PIXI.Application({ 6 | width: 500, 7 | height: 500 8 | }) 9 | document.body.append(game.view) 10 | 11 | const renderer = createRenderer({ 12 | createElement: tag => { 13 | if (tag === 'rect') { 14 | // @ts-ignore 15 | const rect = new PIXI.Graphics() 16 | rect.beginFill(0xff0000) 17 | rect.drawRect(0, 0, 100, 100) 18 | rect.endFill() 19 | return rect 20 | } 21 | }, 22 | patchProp: (el, key, value) => { 23 | el[key] = value 24 | }, 25 | insert: (el, parent) => { 26 | parent.addChild(el) 27 | } 28 | }) 29 | 30 | renderer.createApp(App).mount(game.stage) 31 | -------------------------------------------------------------------------------- /packages/vue/examples/HelloWord/app.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../dist/ai-vue-next.esm.js' 2 | import foo from './foo.js' 3 | // @ts-ignore 4 | window.self = null 5 | export default { 6 | render() { 7 | // @ts-ignore 8 | window.self = this 9 | return h( 10 | 'div', 11 | { 12 | class: ['vue-next'], 13 | onClick: () => { 14 | console.log('ai-vue-next') 15 | } 16 | }, 17 | [ 18 | h('p', { class: ['red', 'title'] }, 'hi'), 19 | h( 20 | 'p', 21 | { class: 'blue', onMousedown: () => console.log('mousedown') }, 22 | this.msg 23 | ), 24 | h(foo, { count: this.count }) 25 | ] 26 | ) 27 | }, 28 | setup() { 29 | return { 30 | msg: 'ai-vue-next', 31 | count: 1 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/vue/examples/HelloWord/foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../dist/ai-vue-next.esm.js' 2 | 3 | export default { 4 | name: 'Foo', 5 | setup(props) { 6 | props.count++ 7 | console.log(props) 8 | }, 9 | render() { 10 | return h('div', { class: 'foo' }, `foo: ${this.count}`) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/vue/examples/HelloWord/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/vue/examples/HelloWord/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/ai-vue-next.esm.js' 2 | import App from './app.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /packages/vue/examples/PatchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import ArrayToText from './ArrayToText.js' 3 | import TextToArray from './TextToArray.js' 4 | import TextToText from './TextToText.js' 5 | import ArrayToArray from './ArrayToArray.js' 6 | 7 | export default { 8 | name: 'App', 9 | setup() {}, 10 | render() { 11 | return h('div', { id: 'root' }, [ 12 | h('p', {}, '主页'), 13 | // 老的是array 新的text 14 | // h(ArrayToText) 15 | 16 | // 老的是text 新的text 17 | // h(TextToText) 18 | 19 | // 老的是text 新的是Array 20 | // h(TextToArray) 21 | 22 | // 新老都是array 23 | h(ArrayToArray) 24 | ]) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/vue/examples/PatchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | // 新老节点都是 array 2 | 3 | import { h, ref, proxyRefs } from '../../lib/ai-vue-next.bundle.esm.js' 4 | 5 | // 1. 左侧的对比 6 | // (a b) c 7 | // (a b) d e 8 | // const prevChildren = [ 9 | // h('p', {key: 'A'}, 'A'), 10 | // h('p', {key: 'B'}, 'B'), 11 | // h('p', {key: 'C'}, 'C') 12 | // ] 13 | // const nextChildren = [ 14 | // h('p', {key: 'A'}, 'A'), 15 | // h('p', {key: 'B'}, 'B'), 16 | // h('p', {key: 'D'}, 'D'), 17 | // h('p', {key: 'E'}, 'E') 18 | // ] 19 | 20 | // 2. 右侧的对比 21 | // a (b c) 22 | // d e (b c) 23 | // const prevChildren = [ 24 | // h('p', {key: 'A'}, 'A'), 25 | // h('p', {key: 'B'}, 'B'), 26 | // h('p', {key: 'C'}, 'C') 27 | // ] 28 | // const nextChildren = [ 29 | // h('p', {key: 'D'}, 'D'), 30 | // h('p', {key: 'E'}, 'E'), 31 | // h('p', {key: 'B'}, 'B'), 32 | // h('p', {key: 'C'}, 'C') 33 | // ] 34 | 35 | // 3. 普通序列+挂载 36 | // (a b) 37 | // (a b) c d e 38 | // const prevChildren = [ 39 | // h('p', {key: 'A'}, 'A'), 40 | // h('p', {key: 'B'}, 'B') 41 | // ] 42 | // const nextChildren = [ 43 | // h('p', {key: 'A'}, 'A'), 44 | // h('p', {key: 'B'}, 'B'), 45 | // h('p', {key: 'C'}, 'C'), 46 | // h('p', {key: 'D'}, 'D'), 47 | // h('p', {key: 'E'}, 'E'), 48 | // ] 49 | // (a b) 50 | // c d e (a b) 51 | // const prevChildren = [ 52 | // h('p', {key: 'A'}, 'A'), 53 | // h('p', {key: 'B'}, 'B') 54 | // ] 55 | // const nextChildren = [ 56 | // h('p', {key: 'C'}, 'C'), 57 | // h('p', {key: 'D'}, 'D'), 58 | // h('p', {key: 'E'}, 'E'), 59 | // h('p', {key: 'A'}, 'A'), 60 | // h('p', {key: 'B'}, 'B'), 61 | // ] 62 | 63 | // 3. 普通序列+卸载 64 | // (a b) c d e 65 | // (a b) 66 | // const prevChildren = [ 67 | // h('p', {key: 'A'}, 'A'), 68 | // h('p', {key: 'B'}, 'B'), 69 | // h('p', {key: 'C'}, 'C'), 70 | // h('p', {key: 'D'}, 'D'), 71 | // h('p', {key: 'E'}, 'E'), 72 | // ] 73 | // const nextChildren = [ 74 | // h('p', {key: 'A'}, 'A'), 75 | // h('p', {key: 'B'}, 'B'), 76 | // ] 77 | 78 | // a d e (b c) 79 | // (b c) 80 | // const prevChildren = [ 81 | // h('p', {key: 'A'}, 'A'), 82 | // h('p', {key: 'D'}, 'D'), 83 | // h('p', {key: 'E'}, 'E'), 84 | // h('p', {key: 'B'}, 'B'), 85 | // h('p', {key: 'C'}, 'C'), 86 | // ] 87 | // const nextChildren = [ 88 | // h('p', {key: 'B'}, 'B'), 89 | // h('p', {key: 'C'}, 'C'), 90 | // ] 91 | 92 | // 5. 未知序列 93 | // [i ... e1 + 1]: a b [c d e] f g 94 | // [i ... e2 + 1]: a b [e c h] f g 95 | // D 在新节点中是没有的 会被卸载 96 | // C 节点的props会发生变化 97 | // const prevChildren = [ 98 | // h('p', {key: 'A'}, 'A'), 99 | // h('p', {key: 'B'}, 'B'), 100 | // h('p', {key: 'C'}, 'C'), 101 | // h('p', {key: 'D'}, 'D'), 102 | // h('p', {key: 'E'}, 'E'), 103 | // h('p', {key: 'F'}, 'F'), 104 | // h('p', {key: 'G'}, 'G'), 105 | // ] 106 | // const nextChildren = [ 107 | // h('p', {key: 'A'}, 'A'), 108 | // h('p', {key: 'B'}, 'B'), 109 | // h('p', {key: 'E'}, 'E'), 110 | // h('p', {key: 'C', id: 'c-id'}, 'C'), 111 | // h('p', {key: 'H'}, 'H'), 112 | // h('p', {key: 'F'}, 'F'), 113 | // h('p', {key: 'G'}, 'G'), 114 | // ] 115 | 116 | // [i ... e1 + 1]: a b [c d e h i] f g 117 | // [i ... e2 + 1]: a b [e c] f g 118 | // 中间部分 老的比心的多 那么多的部分会被直接卸载 119 | // const prevChildren = [ 120 | // h('p', { key: 'A' }, 'A'), 121 | // h('p', { key: 'B' }, 'B'), 122 | // h('p', { key: 'C' }, 'C'), 123 | // h('p', { key: 'D' }, 'D'), 124 | // h('p', { key: 'E' }, 'E'), 125 | // h('p', { key: 'H' }, 'H'), 126 | // h('p', { key: 'I' }, 'I'), 127 | // h('p', { key: 'F' }, 'F'), 128 | // h('p', { key: 'G' }, 'G') 129 | // ] 130 | // const nextChildren = [ 131 | // h('p', { key: 'A' }, 'A'), 132 | // h('p', { key: 'B' }, 'B'), 133 | // h('p', { key: 'E' }, 'E'), 134 | // h('p', { key: 'C', id: 'c-id' }, 'C'), 135 | // h('p', { key: 'F' }, 'F'), 136 | // h('p', { key: 'G' }, 'G') 137 | // ] 138 | 139 | // [i ... e1 + 1]: a b [c d e] f g 140 | // [i ... e2 + 1]: a b [e c d] f g 141 | // 移动 节点存在于新旧节点中,但是位置变了 142 | // 最长递增子序列为[1, 2] 即[e c d]中c d的下标 c d不要移动 只需将e插入到c前面即可 移动一次 143 | // const prevChildren = [ 144 | // h('p', { key: 'A' }, 'A'), 145 | // h('p', { key: 'B' }, 'B'), 146 | // h('p', { key: 'C' }, 'C'), 147 | // h('p', { key: 'D' }, 'D'), 148 | // h('p', { key: 'E' }, 'E'), 149 | // h('p', { key: 'F' }, 'F'), 150 | // h('p', { key: 'G' }, 'G') 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 | // [i ... e1 + 1]: a b [d] f g 163 | // [i ... e2 + 1]: a b [c d e] f g 164 | // 新增 同时也体现了源码的move优化点 165 | // const prevChildren = [ 166 | // h('p', { key: 'A' }, 'A'), 167 | // h('p', { key: 'B' }, 'B'), 168 | // h('p', { key: 'D' }, 'D'), 169 | // h('p', { key: 'F' }, 'F'), 170 | // h('p', { key: 'G' }, 'G') 171 | // ] 172 | // const nextChildren = [ 173 | // h('p', { key: 'A' }, 'A'), 174 | // h('p', { key: 'B' }, 'B'), 175 | // h('p', { key: 'C' }, 'C'), 176 | // h('p', { key: 'D' }, 'D'), 177 | // h('p', { key: 'E' }, 'E'), 178 | // h('p', { key: 'F' }, 'F'), 179 | // h('p', { key: 'G' }, 'G') 180 | // ] 181 | 182 | // 综合例子 183 | // a b (c d e x) f g 184 | // a b (d c y e) f g 185 | const prevChildren = [ 186 | h('p', { key: 'A' }, 'A'), 187 | h('p', { key: 'B' }, 'B'), 188 | h('p', { key: 'C' }, 'C'), 189 | h('p', { key: 'D' }, 'D'), 190 | h('p', { key: 'E' }, 'E'), 191 | h('p', { key: 'X' }, 'X'), 192 | h('p', { key: 'F' }, 'F'), 193 | h('p', { key: 'G' }, 'G') 194 | ] 195 | const nextChildren = [ 196 | h('p', { key: 'A' }, 'A'), 197 | h('p', { key: 'B' }, 'B'), 198 | h('p', { key: 'D' }, 'D'), 199 | h('p', { key: 'C' }, 'C'), 200 | h('p', { key: 'Y' }, 'Y'), 201 | h('p', { key: 'F' }, 'F'), 202 | h('p', { key: 'G' }, 'G') 203 | ] 204 | 205 | // 新节点 text 206 | export default { 207 | name: 'ArrayToArray', 208 | setup() { 209 | const isChange = ref(false) 210 | // @ts-ignore 211 | window.isChange = isChange 212 | 213 | const obj = proxyRefs({ 214 | a: { 215 | b: { 216 | c: 1 217 | } 218 | } 219 | }) 220 | console.log(obj.a.b.c) 221 | return { 222 | isChange 223 | } 224 | }, 225 | render() { 226 | return h('div', {}, this.isChange ? nextChildren : prevChildren) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /packages/vue/examples/PatchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | // 老节点 array 2 | 3 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 4 | 5 | // 新节点 text 6 | export default { 7 | name: 'ArrayToText', 8 | setup() { 9 | const isChange = ref(false) 10 | // @ts-ignore 11 | window.isChange = isChange 12 | 13 | const obj = ref({ 14 | a: ref({ 15 | b: ref({ 16 | c: 1 17 | }) 18 | }) 19 | }) 20 | console.log(obj.value.a.value) 21 | return { 22 | isChange 23 | } 24 | }, 25 | render() { 26 | const nextChildren = 'newChildren' 27 | const prevChildren = [h('p', {}, 'A'), h('p', {}, 'B')] 28 | return h('div', {}, this.isChange ? nextChildren : prevChildren) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/vue/examples/PatchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | // 老节点是text 2 | // 新节点是array 3 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 4 | 5 | // 新节点 text 6 | export default { 7 | name: 'ArrayToText', 8 | setup() { 9 | const isChange = ref(false) 10 | // @ts-ignore 11 | window.isChange = isChange 12 | 13 | const obj = ref({ 14 | a: ref({ 15 | b: ref({ 16 | c: 1 17 | }) 18 | }) 19 | }) 20 | console.log(obj.value.a.b) 21 | return { 22 | isChange 23 | } 24 | }, 25 | render() { 26 | const prevChildren = 'oldChildren' 27 | const nextChildren = [h('p', {}, 'A'), h('p', {}, 'B')] 28 | return h('div', {}, this.isChange ? nextChildren : prevChildren) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/vue/examples/PatchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | // 老节点Text 2 | 3 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 4 | 5 | // 新节点Text 6 | export default { 7 | name: 'TextToText', 8 | setup() { 9 | const isChange = ref(false) 10 | 11 | // @ts-ignore 12 | window.isChange = isChange 13 | 14 | return { 15 | isChange 16 | } 17 | }, 18 | render() { 19 | const oldChild = 'oldChild' 20 | const newChild = 'newChild' 21 | return h('div', {}, this.isChange ? newChild : oldChild) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/vue/examples/PatchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/vue/examples/PatchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | 6 | function getSequence(arr) { 7 | const p = arr.slice() 8 | const result = [0] 9 | let i, j, u, v, c 10 | const len = arr.length 11 | // 遍历数组 12 | debugger 13 | for (i = 0; i < len; i++) { 14 | const arrI = arr[i] 15 | // 此算法中排除了等于0的情况,原因是0成为了diff算法中的占位符,在上面的流程图中已经忽略了,不影响对算法的了解 16 | if (arrI !== 0) { 17 | j = result[result.length - 1] 18 | // 用当前num与result中的最后一项对比 19 | if (arr[j] < arrI) { 20 | // 当前数值大于result子序列最后一项时,直接往后新增,并将当前数值的前一位result保存 21 | p[i] = j 22 | result.push(i) 23 | continue 24 | } 25 | u = 0 26 | v = result.length - 1 27 | // 当前数值小于result子序列最后一项时,使用二分法找到第一个大于当前数值的下标 28 | while (u < v) { 29 | c = ((u + v) / 2) | 0 30 | if (arr[result[c]] < arrI) { 31 | u = c + 1 32 | } else { 33 | v = c 34 | } 35 | } 36 | if (arrI < arr[result[u]]) { 37 | // 找到下标,将当前下标对应的前一位result保存(如果找到的是第一位,不需要操作,第一位前面没有了) 38 | if (u > 0) { 39 | p[i] = result[u - 1] 40 | } 41 | // 找到下标,直接替换result中的数值 42 | result[u] = i 43 | } 44 | } 45 | } 46 | u = result.length 47 | v = result[u - 1] 48 | // 回溯,直接从最后一位开始,将前面的result全部覆盖,如果不需要修正,则p中记录的每一项都是对应的前一位,不会有任何影响 49 | while (u-- > 0) { 50 | result[u] = v 51 | v = p[v] 52 | } 53 | return result 54 | } 55 | 56 | getSequence([10, 9, 2, 5, 3, 7, 101, 18, 1]) 57 | -------------------------------------------------------------------------------- /packages/vue/examples/Update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'App', 5 | setup() { 6 | const props = ref({ 7 | foo: 'foo', 8 | bar: 'bar' 9 | }) 10 | 11 | const onChangePropsDemo1 = () => { 12 | props.value.foo = 'new-foo' 13 | } 14 | 15 | const onChangePropsDemo2 = () => { 16 | props.value.foo = undefined 17 | } 18 | 19 | const onChangePropsDemo3 = () => { 20 | props.value = { 21 | foo: 'foo' 22 | } 23 | } 24 | 25 | return { 26 | props, 27 | onChangePropsDemo1, 28 | onChangePropsDemo2, 29 | onChangePropsDemo3 30 | } 31 | }, 32 | render() { 33 | return h('div', { id: 'root', ...this.props }, [ 34 | h( 35 | 'button', 36 | { onClick: this.onChangePropsDemo1 }, 37 | '改变props.foo为new-foo' 38 | ), 39 | h( 40 | 'button', 41 | { onClick: this.onChangePropsDemo2 }, 42 | '改变props.foo为undefined' 43 | ), 44 | h('button', { onClick: this.onChangePropsDemo3 }, '删除props.bar') 45 | ]) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/vue/examples/Update/App1.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'App', 5 | setup() { 6 | const count = ref(0) 7 | const clickHandle = () => { 8 | count.value++ 9 | } 10 | 11 | return { 12 | count, 13 | clickHandle 14 | } 15 | }, 16 | render() { 17 | return h('div', { id: 'root' }, [ 18 | h('p', {}, `count: ${this.count}`), 19 | h('button', { onClick: this.clickHandle }, '点击+1') 20 | ]) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/vue/examples/Update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/vue/examples/Update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /packages/vue/examples/compiler-base/App.js: -------------------------------------------------------------------------------- 1 | import { ref } from '../../dist/ai-vue-next.esm.js' 2 | 3 | export const App = { 4 | name: 'App', 5 | template: '
hi,{{count}}
', 6 | setup() { 7 | const count = (window.count = ref(0)) 8 | return { 9 | message: 'mini-vue123', 10 | count 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/vue/examples/compiler-base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/vue/examples/compiler-base/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../dist/ai-vue-next.esm.js' 2 | import { App } from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /packages/vue/examples/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import Child from './Child.js' 3 | 4 | const App = { 5 | name: 'App', 6 | setup() { 7 | const msg = ref('123') 8 | const count = ref(1) 9 | 10 | // @ts-ignore 11 | window.msg = msg 12 | 13 | const changeChildProps = () => { 14 | msg.value = '456' 15 | } 16 | 17 | const changeCount = () => { 18 | count.value++ 19 | } 20 | 21 | return { 22 | msg, 23 | count, 24 | changeChildProps, 25 | changeCount 26 | } 27 | }, 28 | render() { 29 | return h('div', {}, [ 30 | h('div', {}, '您好'), 31 | 32 | h( 33 | 'button', 34 | { 35 | onClick: this.changeChildProps 36 | }, 37 | 'change child props' 38 | ), 39 | 40 | h(Child, { 41 | msg: this.msg 42 | }), 43 | 44 | h( 45 | 'button', 46 | { 47 | onClick: this.changeCount 48 | }, 49 | 'change self count' 50 | ), 51 | 52 | h('p', {}, 'count: ' + this.count) 53 | ]) 54 | } 55 | } 56 | export default App 57 | -------------------------------------------------------------------------------- /packages/vue/examples/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/ai-vue-next.bundle.esm.js' 2 | 3 | export default { 4 | name: 'Child', 5 | setup() {}, 6 | render() { 7 | return h('div', {}, [ 8 | h('div', {}, 'child - props -msg: ' + this.$props.msg) 9 | ]) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/vue/examples/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/vue/examples/componentUpdate/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /packages/vue/examples/nextTick/App.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | ref, 4 | getCurrentInstace, 5 | nextTick 6 | } from '../../lib/ai-vue-next.bundle.esm.js' 7 | 8 | export default { 9 | name: 'App', 10 | setup() { 11 | const count = ref(1) 12 | const instance = getCurrentInstace() 13 | async function onClick() { 14 | for (let i = 0; i < 10; i++) { 15 | console.log('update--' + i) 16 | count.value = i 17 | } 18 | debugger 19 | console.log(instance) 20 | // nextTick(() => { 21 | // console.log(instance) 22 | // }) 23 | await nextTick() 24 | console.log(instance) 25 | } 26 | 27 | return { 28 | count, 29 | onClick 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/vue/examples/nextTick/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/vue/examples/nextTick/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/ai-vue-next.bundle.esm.js' 2 | import App from './App.js' 3 | 4 | createApp(App).mount(document.querySelector('#app')) 5 | -------------------------------------------------------------------------------- /packages/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-vue-next", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@ai-vue-next/compiler-core": "workspace:^1.0.0", 14 | "@ai-vue-next/runtime-dom": "workspace:^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/vue/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | '@ai-vue-next/compiler-core': workspace:^1.0.0 5 | '@ai-vue-next/runtime-dom': workspace:^1.0.0 6 | 7 | dependencies: 8 | '@ai-vue-next/compiler-core': link:../compiler-core 9 | '@ai-vue-next/runtime-dom': link:../runtime-dom 10 | -------------------------------------------------------------------------------- /packages/vue/src/index.ts: -------------------------------------------------------------------------------- 1 | // 出口文件 2 | export * from '@ai-vue-next/runtime-dom' 3 | import { baseCompile } from '@ai-vue-next/compiler-core' 4 | import * as runtimeDom from '@ai-vue-next/runtime-dom' 5 | import { registerRuntimeCompiler } from '@ai-vue-next/runtime-dom' 6 | 7 | function compileToFunction(template) { 8 | const { code } = baseCompile(template) 9 | const render = new Function('Vue', code)(runtimeDom) 10 | return render 11 | } 12 | 13 | registerRuntimeCompiler(compileToFunction) 14 | -------------------------------------------------------------------------------- /pnpm.workspace.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript' 2 | import pkg from './package.json' 3 | 4 | export default { 5 | input: './packages/vue/src/index.ts', 6 | output: [ 7 | { 8 | format: 'cjs', 9 | file: 'packages/vue/dist/ai-vue-next.cjs.js' 10 | }, 11 | { 12 | format: 'esm', 13 | file: 'packages/vue/dist/ai-vue-next.esm.js' 14 | } 15 | ], 16 | plugins: [typescript()], 17 | onwarn: (msg, warn) => { 18 | if (!/Circular/.test(msg)) { 19 | warn(msg) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/verifyCommit.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | chalk.level = 1 3 | // PWD|INIT_CWD是根路径 4 | const msg = require('fs') 5 | .readFileSync(`${process.env.INIT_CWD}/.git/COMMIT_EDITMSG`, 'utf-8') 6 | .trim() 7 | 8 | const commitRE = 9 | /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/ 10 | 11 | if (!commitRE.test(msg)) { 12 | console.error( 13 | ` ${chalk.bgRed.white(' 错误 ')} ${chalk.red( 14 | `无效的提交消息格式。` 15 | )}\n\n` + 16 | chalk.red(` 自动生成更改日志需要正确的提交消息格式。例子:\n\n`) + 17 | ` ${chalk.green(`feat(compiler): add 'comments' option`)}\n` + 18 | ` ${chalk.green( 19 | `fix(v-model): handle events on blur (close #28)` 20 | )}\n\n` + 21 | chalk.red(` See .github/commit-convention.md for more details.\n`) 22 | ) 23 | process.exit(1) 24 | } 25 | console.log( 26 | chalk.blue(`${msg}\n`) + chalk.green('commit信息格式正确,给予通过!') 27 | ) 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 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","esnext"], /* 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 | 26 | /* Modules */ 27 | "module": "esnext", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "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. */ 50 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | 68 | /* Interop Constraints */ 69 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 70 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 71 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 72 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 73 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 74 | 75 | /* Type Checking */ 76 | "strict": true, /* Enable all strict type-checking options. */ 77 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 78 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 79 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 80 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 81 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 82 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 83 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 84 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 85 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 86 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 87 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 88 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 89 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 90 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 91 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 92 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 93 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 94 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 95 | 96 | /* Completeness */ 97 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 98 | "skipLibCheck": true, /* Skip type checking all .d.ts files. */ 99 | "paths": { 100 | "@ai-vue-next/*": [ 101 | "./packages/*/src" 102 | ] 103 | } 104 | }, 105 | "include": [ 106 | "packages/*/src", 107 | "packages/*/__tests__" 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /vitest.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import path from 'path' 3 | 4 | export default defineConfig({ 5 | test: { 6 | globals: true 7 | }, 8 | resolve: { 9 | alias: [ 10 | { 11 | find: /@ai-vue-next\/(\w*)/, 12 | replacement: path.resolve(__dirname, 'packages') + '/$1/src' 13 | } 14 | ] 15 | } 16 | }) 17 | --------------------------------------------------------------------------------