├── .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('')) {
1178 | for (let i = ancestors.length - 1; i >= 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('') &&
1191 | s.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
1192 | )
1193 | }
1194 | function parseinterpolation(context) {
1195 | const openDelimiter = '{{'
1196 | const closeDelimiter = '}}'
1197 | const closeIndex = context.source.indexOf(
1198 | closeDelimiter,
1199 | openDelimiter.length
1200 | )
1201 | advanceBy(context, openDelimiter.length)
1202 | const rawContentLength = closeIndex - openDelimiter.length
1203 | const rawContent = parseTextData(context, rawContentLength)
1204 | const content = rawContent.trim()
1205 | advanceBy(context, closeDelimiter.length)
1206 | return {
1207 | type: 0 /* INTERPOLATION */,
1208 | content: {
1209 | type: 1 /* SIMPLE_EXPRESSION */,
1210 | content
1211 | }
1212 | }
1213 | }
1214 | function advanceBy(context, length) {
1215 | context.source = context.source.slice(length)
1216 | }
1217 | function createRoot(children) {
1218 | return {
1219 | children,
1220 | type: 4 /* ROOT */
1221 | }
1222 | }
1223 | function createParserContext(content) {
1224 | return {
1225 | source: content
1226 | }
1227 | }
1228 | function parseElement(context, ancestors) {
1229 | // 1.解析tag
1230 | const element = parseTag(context, 0 /* Start */)
1231 | ancestors.push(element)
1232 | element.children = parseChildren(context, ancestors)
1233 | ancestors.pop()
1234 | if (startsWithEndTagOpen(context.source, element.tag)) {
1235 | parseTag(context, 1 /* End */)
1236 | } else {
1237 | throw new Error(`丢失结束标签:${element.tag}`)
1238 | }
1239 | // 2.删除解析过的字符串
1240 | return element
1241 | }
1242 | function parseTag(context, type) {
1243 | const match = /^<\/?([a-z]*)/i.exec(context.source)
1244 | const tag = match[1]
1245 | advanceBy(context, match[0].length)
1246 | advanceBy(context, 1)
1247 | if (type === 1 /* End */) {
1248 | return
1249 | }
1250 | return {
1251 | type: 2 /* ELEMENT */,
1252 | tag
1253 | }
1254 | }
1255 | function parseText(context) {
1256 | let endIndex = context.source.length
1257 | let endTokens = ['<', '{{']
1258 | for (let i = 0; i < endTokens.length; i++) {
1259 | const index = context.source.indexOf(endTokens[i])
1260 | if (~index && endIndex > 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('')
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('')) {
40 | for (let i = ancestors.length - 1; i >= 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('') &&
54 | s.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
55 | )
56 | }
57 |
58 | function parseinterpolation(context) {
59 | const openDelimiter = '{{'
60 | const closeDelimiter = '}}'
61 |
62 | const closeIndex = context.source.indexOf(
63 | closeDelimiter,
64 | openDelimiter.length
65 | )
66 | advanceBy(context, openDelimiter.length)
67 |
68 | const rawContentLength = closeIndex - openDelimiter.length
69 | const rawContent = parseTextData(context, rawContentLength)
70 | const content = rawContent.trim()
71 |
72 | advanceBy(context, closeDelimiter.length)
73 |
74 | return {
75 | type: NodeTypes.INTERPOLATION,
76 | content: {
77 | type: NodeTypes.SIMPLE_EXPRESSION,
78 | content
79 | }
80 | }
81 | }
82 |
83 | function advanceBy(context: any, length: number) {
84 | context.source = context.source.slice(length)
85 | }
86 |
87 | function createRoot(children) {
88 | return {
89 | children,
90 | type: NodeTypes.ROOT
91 | }
92 | }
93 |
94 | function createParserContext(content: string) {
95 | return {
96 | source: content
97 | }
98 | }
99 |
100 | function parseElement(context: any, ancestors): any {
101 | // 1.解析tag
102 | const element: any = parseTag(context, TagType.Start)
103 | ancestors.push(element)
104 | element.children = parseChildren(context, ancestors)
105 | ancestors.pop()
106 |
107 | if (startsWithEndTagOpen(context.source, element.tag)) {
108 | parseTag(context, TagType.End)
109 | } else {
110 | throw new Error(`丢失结束标签:${element.tag}`)
111 | }
112 | // 2.删除解析过的字符串
113 | return element
114 | }
115 |
116 | function parseTag(context: any, type) {
117 | const match: any = /^<\/?([a-z]*)/i.exec(context.source)
118 | const tag = match[1]
119 | advanceBy(context, match[0].length)
120 | advanceBy(context, 1)
121 |
122 | if (type === TagType.End) {
123 | return
124 | }
125 |
126 | return {
127 | type: NodeTypes.ELEMENT,
128 | tag
129 | }
130 | }
131 |
132 | function parseText(context: any): any {
133 | let endIndex = context.source.length
134 | let endTokens = ['<', '{{']
135 |
136 | for (let i = 0; i < endTokens.length; i++) {
137 | const index = context.source.indexOf(endTokens[i])
138 | if (~index && endIndex > 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 |
--------------------------------------------------------------------------------