├── .gitignore
├── LICENSE
├── README.md
├── README.zh-CN.md
├── babel.config.js
├── note
├── reactivity
│ ├── assets
│ │ ├── computed_flow.png
│ │ ├── init_flow.png
│ │ ├── readonly_flow.png
│ │ ├── ref_flow.png
│ │ ├── scheduler_flow.png
│ │ └── shallowReactive_flow.png
│ ├── computed.md
│ ├── index.md
│ ├── readonly.md
│ ├── ref.md
│ ├── scheduler.md
│ └── shallowReadonly.md
└── runtime-core
│ ├── assets
│ └── createApp_flow.png
│ └── createApp.md
├── package.json
├── packages
├── compiler-core
│ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── codegen.spec.ts.snap
│ │ ├── codegen.spec.ts
│ │ ├── parse.spec.ts
│ │ └── transform.spec.ts
│ ├── package.json
│ └── src
│ │ ├── ast.ts
│ │ ├── codegen.ts
│ │ ├── compile.ts
│ │ ├── index.ts
│ │ ├── parse.ts
│ │ ├── runtimeHelpers.ts
│ │ ├── transform.ts
│ │ ├── transforms
│ │ ├── transformElement.ts
│ │ ├── transformExpression.ts
│ │ └── transformText.ts
│ │ └── utils.ts
├── reactivity
│ ├── __tests__
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ ├── shallowReactive.spec.ts
│ │ ├── shallowReadonly.spec.ts
│ │ └── shallowRef.spec.ts
│ ├── package.json
│ └── src
│ │ ├── baseHandlers.ts
│ │ ├── computed.ts
│ │ ├── effect.ts
│ │ ├── index.ts
│ │ ├── reactive.ts
│ │ └── ref.ts
├── runtime-core
│ ├── package.json
│ └── 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
│ └── src
│ │ └── index.ts
├── shared
│ ├── package.json
│ └── src
│ │ ├── ShapeFlags.ts
│ │ ├── index.ts
│ │ └── toDisplayString.ts
└── vue
│ ├── dist
│ ├── tiny-vue3.cjs.js
│ └── tiny-vue3.esm.js
│ ├── examples
│ ├── apiInject
│ │ ├── App.js
│ │ └── index.html
│ ├── compiler-base
│ │ ├── App.js
│ │ ├── index.html
│ │ └── main.js
│ ├── componentEmit
│ │ ├── App.js
│ │ ├── Foo.js
│ │ ├── index.html
│ │ └── main.js
│ ├── componentSlot
│ │ ├── App.js
│ │ ├── Foo.js
│ │ ├── index.html
│ │ └── main.js
│ ├── componentUpdate
│ │ ├── App.js
│ │ ├── Child.js
│ │ ├── index.html
│ │ └── main.js
│ ├── currentInstance
│ │ ├── App.js
│ │ ├── Foo.js
│ │ ├── index.html
│ │ └── main.js
│ ├── customRenderer
│ │ ├── App.js
│ │ ├── index.html
│ │ └── main.js
│ ├── helloworld
│ │ ├── App.js
│ │ ├── Foo.js
│ │ ├── index.html
│ │ └── main.js
│ ├── nextTicker
│ │ ├── App.js
│ │ ├── index.html
│ │ └── main.js
│ ├── patchChildren
│ │ ├── App.js
│ │ ├── ArrayToArray.js
│ │ ├── ArrayToText.js
│ │ ├── TextToArray.js
│ │ ├── TextToText.js
│ │ ├── index.html
│ │ └── main.js
│ └── update
│ │ ├── App.js
│ │ ├── index.html
│ │ └── main.js
│ ├── package.json
│ └── src
│ └── index.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── rollup.config.js
├── tsconfig.json
└── vitest.config.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Facebook, Inc. and its affiliates.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
tiny vue3
2 |
3 |
4 |
5 | **English** | [中文](./README.zh-CN.md)
6 |
7 |
8 |
9 | ### Overview
10 |
11 | Tiny version of the Vue3 based on TDD, in order to learning deeply Vue3 and clarify the core logic
12 |
13 | ### Feature
14 |
15 | **reactivity**
16 |
17 | - [x] implement reactive
18 | - [x] effect.scheduler
19 | - [x] effect.stop
20 | - [x] track dependency collection
21 | - [x] trigger triggering dependencies
22 | - [x] implement readonly
23 | - [x] isReactive
24 | - [x] isReadonly
25 | - [x] nested reactive
26 | - [x] shallowReadonly
27 | - [x] shallowReactive
28 | - [x] isProxy
29 | - [x] implement ref
30 | - [x] shallowRef
31 | - [x] proxyRefs
32 | - [x] implement computed
33 |
34 | **runtime-core**
35 |
36 | - [x] component type
37 | - [x] element type
38 | - [x] props
39 | - [x] props 和 context
40 | - [x] component emit
41 | - [x] proxy
42 | - [x] setup
43 | - [x] nextTick
44 | - [x] getCurrentInstance
45 | - [x] provide/inject
46 | - [x] slots
47 | - [x] Fragment node component
48 | - [x] Text node component
49 | - [x] $el
50 |
51 | **compiler-core**
52 |
53 | - [x] parse slots
54 | - [x] parse element
55 | - [x] parse text
56 |
57 | **runtime-dom**
58 |
59 | - [x] custom renderer
60 |
61 | **monorepo & vitest**
62 |
63 | - [x] refactor by pnpm monorepo
64 | - [x] vitest replace jest
65 |
66 | ### License
67 |
68 | Tiny-vue3 is [MIT licensed](./LICENSE).
69 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | tiny vue3
2 |
3 |
4 |
5 | [English](./README.md) | **中文**
6 |
7 |
8 |
9 | ### 概述
10 |
11 | 基于 TDD 实现 vue3 模型简易版本,用于深入学习 vue3,理清 vue3 的核心逻辑
12 |
13 | > `note` 目录下为学习笔记,用于加深各个环节的重点印象
14 |
15 | ### 功能实现
16 |
17 | **reactivity**
18 |
19 | - [x] reactive 的实现
20 | - [x] 支持 effect.scheduler
21 | - [x] 支持 effect.stop
22 | - [x] track 依赖收集
23 | - [x] trigger 触发依赖
24 | - [x] readonly 的实现
25 | - [x] 支持 isReactive
26 | - [x] 支持 isReadonly
27 | - [x] 支持嵌套 reactive
28 | - [x] 支持 shallowReadonly
29 | - [x] 支持 shallowReactive
30 | - [x] 支持 isProxy
31 | - [x] ref 的实现
32 | - [x] 支持 shallowRef
33 | - [x] 支持 proxyRefs
34 | - [x] computed 的实现
35 |
36 | **runtime-core**
37 |
38 | - [x] 支持组件类型
39 | - [x] 支持 element 类型
40 | - [x] 初始化 props
41 | - [x] setup 可获取 props 和 context
42 | - [x] 支持 component emit
43 | - [x] 支持 proxy
44 | - [x] 可以在 render 函数中获取 setup 返回的对象
45 | - [x] nextTick 的实现
46 | - [x] 支持 getCurrentInstance
47 | - [x] 支持 provide/inject
48 | - [x] 支持最基础的 slots
49 | - [x] 支持 Fragment 类型节点
50 | - [x] 支持 Text 类型节点
51 | - [x] 支持 $el api
52 |
53 | **compiler-core**
54 |
55 | - [x] 解析插值
56 | - [x] 解析 element
57 | - [x] 解析 text
58 |
59 | **runtime-dom**
60 |
61 | - [x] 支持 custom renderer
62 |
63 | **monorepo & vitest**
64 |
65 | - [x] pnpm monorepo 改造
66 | - [x] vitest 替换 jest
67 |
68 | ### License
69 |
70 | 本项目基于 [MIT licensed](./LICENSE).
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ["@babel/preset-env", { targets: { node: "current" } }],
4 | "@babel/preset-typescript"
5 | ]
6 | }
--------------------------------------------------------------------------------
/note/reactivity/assets/computed_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Psilocine/tiny-vue3/771f80fd685be15a33472e9d8e48a31eb73e7e6c/note/reactivity/assets/computed_flow.png
--------------------------------------------------------------------------------
/note/reactivity/assets/init_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Psilocine/tiny-vue3/771f80fd685be15a33472e9d8e48a31eb73e7e6c/note/reactivity/assets/init_flow.png
--------------------------------------------------------------------------------
/note/reactivity/assets/readonly_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Psilocine/tiny-vue3/771f80fd685be15a33472e9d8e48a31eb73e7e6c/note/reactivity/assets/readonly_flow.png
--------------------------------------------------------------------------------
/note/reactivity/assets/ref_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Psilocine/tiny-vue3/771f80fd685be15a33472e9d8e48a31eb73e7e6c/note/reactivity/assets/ref_flow.png
--------------------------------------------------------------------------------
/note/reactivity/assets/scheduler_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Psilocine/tiny-vue3/771f80fd685be15a33472e9d8e48a31eb73e7e6c/note/reactivity/assets/scheduler_flow.png
--------------------------------------------------------------------------------
/note/reactivity/assets/shallowReactive_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Psilocine/tiny-vue3/771f80fd685be15a33472e9d8e48a31eb73e7e6c/note/reactivity/assets/shallowReactive_flow.png
--------------------------------------------------------------------------------
/note/reactivity/computed.md:
--------------------------------------------------------------------------------
1 | # computed
2 |
3 | ## 流程
4 | 
5 |
6 | computed 计算属性的特征就是缓存,在 get value 的时候才去考虑是否需要更新值。
7 | keypiont 是 ReactiveEffect 类传入 scheduler 函数代替 getter 函数执行,
8 | ```typescript
9 | class ComputedRefImpl {
10 | ...
11 | private _effect: ReactiveEffect;
12 | constructor(getter) {
13 | this._effect = new ReactiveEffect(getter, () => {
14 | if (!this._dirty) {
15 | this._dirty = true;
16 | }
17 | });
18 | }
19 | ...
20 | }
21 | ```
22 | 在 getter 依赖的属性或值发生变化时,触发的是 scheduler 函数,标记 _dirty 为 true 则在下一轮 get value 的时候会重新计算 _value 的值
--------------------------------------------------------------------------------
/note/reactivity/index.md:
--------------------------------------------------------------------------------
1 | # reactivity 笔记
2 |
3 | ## 流程
4 | 
5 |
6 | ## reactive
7 |
8 | 响应式的入口,本质是通过 Proxy 返回一个新的响应式对象,该对象在 getter 和 setter 里分别做依赖收集(track)和依赖触发(trigger)
9 |
10 | ## effect
11 |
12 | 接受一个 function 函数,调用时会实例化一个 ReactiveEffect,并立即触发该函数,如果该函数里触发 getter,那么会把该实例收集起来
13 |
14 | update(返回值): ReactiveEffect 初始化时立即触发会把当前 funciton 函数返回,称为 runner,后续可以再 ReactiveEffect 外部主动触发;同时也可以把 function 的值返回
15 |
16 | update(scheduler): effect 可以有第二个参数 options 对象,传入 scheduler 函数可以在触发依赖的时候执行 scheduler 而不是执行 runner
17 |
18 | update(stop/onStop): stop 可以在外部把 runner 函数停止,即在依赖触发的时不运行 function 函数。逻辑是 track 的时候 ReactiveEffect 也把 deps 收集起来、runner 挂载 ReactiveEffect 实例。在调用 stop 的时候通过 runner -> ReactiveEffect -> deps -> 遍历 delete effect(还增加一个 active 锁防止 stop 多次调用);
19 | effect 第二个参数 options 对象可以传入 onStop 函数,可以在外部执行 stop 的时候执行
20 |
21 | ## track
22 |
23 | 在 getter 的时候,分别通过 target 和 key 作唯一的 Map 映射,把当前 ReactiveEffect 实例收集到 dep 里
24 |
25 | ## trigger
26 |
27 | 在 setter 的时候,通过 target 和 key 的唯一映射拿到依赖收集的 dep,触发每一个 ReactiveEffect 实例的 run 方法
28 |
29 | update(scheduler): 当 ReactiveEffect 实例有 scheduler 函数时,scheduler 函数代替 run 函数执行
--------------------------------------------------------------------------------
/note/reactivity/readonly.md:
--------------------------------------------------------------------------------
1 | # readonly
2 |
3 | ## 流程
4 | 
5 |
6 | 如命名 readonly 一样言简意赅,readonly 创建一个不能修改内部属性的响应式对象。
7 |
8 | 介绍一下 TDD 的流程:
9 | 1. 先写测试用例
10 | 2. 实现测试用例的功能
11 | 3. 确保测试用例通过的情况下重构
12 |
13 | 实现 readonly 功能的同时,把 reactive 和 readonly 的代码复用重构到 baseHandlers 文件里。
14 | 优化点:get 和 set 函数在初始化时创建,后续再调用 reactive/readonly 都是同一个对象,不用重新创建(readonlyGet 同理)。
--------------------------------------------------------------------------------
/note/reactivity/ref.md:
--------------------------------------------------------------------------------
1 | # ref
2 |
3 | ## 流程
4 | 
5 |
6 | ref 可以接受基本类型也可以接受引用类型。我们知道 Proxy 返回响应式对象的前提是需要参数是对象,基本类型如何监听呢?
7 | 所以 ref 才会以 `.value` 的形式来生成,在 get value 的时候收集依赖,在 set value 的时候触发依赖
--------------------------------------------------------------------------------
/note/reactivity/scheduler.md:
--------------------------------------------------------------------------------
1 | # scheduler
2 |
3 | ## 流程
4 | 
5 |
6 |
7 | 谈谈 constructor 参数里 scheduler 的修饰符,这节实现的逻辑如下
8 | ```typescript
9 | class ReactiveEffect {
10 | private fn;
11 |
12 | constructor(_fn, public scheduler?) {
13 | this.fn = _fn;
14 | }
15 | ...
16 | }
17 | ```
18 |
19 | 1. `?` 代表可选属性大家都很常见,补充另一个操作符 `!`,其代表非空断言,表示强制解析,使用后类型推断会排除 `null` 和 `undefined`
20 | ```typscript
21 | function foo (maybeString: string | undefined | null) {
22 | const res: string = maybeString; // Error
23 | cosnt res2: string = maybeString!; // Ok
24 | }
25 | ```
26 |
27 | 2. `public` 相当于创建了同名的成员变量,即
28 | ```typescript
29 | class ReactiveEffect {
30 | private fn;
31 | public scheduler;
32 |
33 | constructor(_fn, scheduler?) {
34 | this.fn = _fn;
35 | this.scheduler = scheduler;
36 | }
37 | ...
38 | }
39 | ```
40 |
41 | 以此类推,`private`、`readonly`、`protected` 也是可行的
42 | ```typescript
43 | class ReactiveEffect {
44 | // private fn; // 可省略
45 |
46 | constructor(private fn, public scheduler?) {
47 | // this.fn = _fn; // 可省略
48 | }
49 | ...
50 | }
51 | ```
--------------------------------------------------------------------------------
/note/reactivity/shallowReadonly.md:
--------------------------------------------------------------------------------
1 | # shallowReactive
2 |
3 |
4 | ## 流程
5 | 
6 |
7 | shallowReactive 和 shallowReadonly 的子对象不会转换为响应式,适用于只会变化外层属性的场景,可以减少多次 Proxy,减少性能开销
--------------------------------------------------------------------------------
/note/runtime-core/assets/createApp_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Psilocine/tiny-vue3/771f80fd685be15a33472e9d8e48a31eb73e7e6c/note/runtime-core/assets/createApp_flow.png
--------------------------------------------------------------------------------
/note/runtime-core/createApp.md:
--------------------------------------------------------------------------------
1 | # createApp
2 |
3 | ## 流程
4 |
5 | 
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "version": "1.0.0",
4 | "description": "A tiny vue3 library.",
5 | "author": "psilolau@foxmail.com",
6 | "scripts": {
7 | "test": "vitest",
8 | "build": "rollup -c rollup.config.js"
9 | },
10 | "keywords": [
11 | "vue3",
12 | "vue",
13 | "simplify vue",
14 | "mini vue",
15 | "tiny vue"
16 | ],
17 | "devDependencies": {
18 | "@babel/core": "^7.17.8",
19 | "@babel/preset-env": "^7.16.11",
20 | "@babel/preset-typescript": "^7.16.7",
21 | "@rollup/plugin-typescript": "^8.3.1",
22 | "rollup": "^2.70.1",
23 | "tslib": "^2.3.1",
24 | "typescript": "^4.6.3",
25 | "vitest": "^0.22.1"
26 | },
27 | "dependencies": {
28 | "@types/jest": "^29.0.3"
29 | },
30 | "license": "MIT"
31 | }
32 |
--------------------------------------------------------------------------------
/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1
2 |
3 | exports[`codegen element:
4 | "const { createElementVNode: _createElementVNode } = Vue
5 | return function render(_ctx, _cache){return }"
6 | 1`] = `
7 | "const { createElementVNode: _createElementVNode } = Vue
8 | return function render(_ctx, _cache){return _createElementVNode(\\"div\\"), null, 'hi,' + _toDisplayString(message))}"
9 | `;
10 |
--------------------------------------------------------------------------------
/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 { transformElement } from "../src/transforms/transformElement";
5 | import { transformExpression } from "../src/transforms/transformExpression";
6 | import { transformText } from "../src/transforms/transformText";
7 |
8 | describe('codegen', () => {
9 | it('string', () => {
10 | const ast = baseParse('hi');
11 |
12 | transform(ast)
13 |
14 | const { code } = generate(ast);
15 | // 快照
16 | // 1. 抓 bug
17 | // 2. 有意
18 | expect(code).toMatchInlineSnapshot(`
19 | "const { createElementVNode: _createElementVNode } = Vue
20 | return function render(_ctx, _cache){return 'hi'}"
21 | `)
22 | });
23 |
24 | it('interpolation', () => {
25 | const ast = baseParse('{{message}}')
26 | transform(ast, {
27 | nodeTransforms: [transformExpression]
28 | });
29 | const { code } = generate(ast);
30 | expect(code).toMatchInlineSnapshot(`
31 | "const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue
32 | return function render(_ctx, _cache){return _toDisplayString(_ctx.message)}"
33 | `);
34 | });
35 |
36 |
37 | it("element", () => {
38 | const ast: any = baseParse("hi,{{message}}
");
39 | transform(ast, {
40 | nodeTransforms: [transformExpression, transformElement, transformText],
41 | });
42 |
43 | const { code } = generate(ast);
44 | expect(code).toMatchInlineSnapshot(`
45 | "const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = Vue
46 | return function render(_ctx, _cache){return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))}"
47 | `);
48 | });
49 | });
--------------------------------------------------------------------------------
/packages/compiler-core/__tests__/parse.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../src/ast";
2 | import { baseParse } from "../src/parse";
3 |
4 | describe("Parse", () => {
5 | describe("interpolation", () => {
6 | test("simple interpolation", () => {
7 | const ast = baseParse("{{ message }}");
8 |
9 | // root
10 | expect(ast.children[0]).toStrictEqual({
11 | type: NodeTypes.INTERPOLATION,
12 | content: {
13 | type: NodeTypes.SIMPLE_EXPRESSION,
14 | content: "message",
15 | },
16 | });
17 | });
18 | });
19 | describe("element", () => {
20 | test("simple element div", () => {
21 | const ast = baseParse("");
22 |
23 | expect(ast.children[0]).toStrictEqual({
24 | type: NodeTypes.ELEMENT,
25 | tag: "div",
26 | children: [],
27 | });
28 | });
29 | });
30 |
31 | describe("text", () => {
32 | it("simple text", () => {
33 | const ast = baseParse("some text");
34 |
35 | expect(ast.children[0]).toStrictEqual({
36 | type: NodeTypes.TEXT,
37 | content: "some text",
38 | });
39 | });
40 | });
41 |
42 | test("hello world", () => {
43 | const ast = baseParse("hi,{{message}}
");
44 |
45 | expect(ast.children[0]).toStrictEqual({
46 | type: NodeTypes.ELEMENT,
47 | tag: "div",
48 | children: [
49 | {
50 | type: NodeTypes.TEXT,
51 | content: "hi,",
52 | },
53 | {
54 | type: NodeTypes.INTERPOLATION,
55 | content: {
56 | type: NodeTypes.SIMPLE_EXPRESSION,
57 | content: "message",
58 | },
59 | },
60 | ],
61 | });
62 | });
63 |
64 |
65 | test("Nested element", () => {
66 | const ast = baseParse("");
67 |
68 | expect(ast.children[0]).toStrictEqual({
69 | type: NodeTypes.ELEMENT,
70 | tag: "div",
71 | children: [
72 | {
73 | type: NodeTypes.ELEMENT,
74 | tag: "p",
75 | children: [
76 | {
77 | type: NodeTypes.TEXT,
78 | content: "hi",
79 | },
80 | ],
81 | },
82 | {
83 | type: NodeTypes.INTERPOLATION,
84 | content: {
85 | type: NodeTypes.SIMPLE_EXPRESSION,
86 | content: "message",
87 | },
88 | },
89 | ],
90 | });
91 | });
92 |
93 | test("should throw error when lack end tag", () => {
94 | expect(() => {
95 | baseParse("
");
96 | }).toThrow(`缺少结束标签:span`);
97 | });
98 | });
99 |
--------------------------------------------------------------------------------
/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 | const plugin = (node) => {
9 | if (node.type === NodeTypes.TEXT) {
10 | node.content = node.content + " mini-vue";
11 | }
12 | }
13 | transform(ast, {
14 | nodeTransforms: [plugin],
15 | });
16 | const nodeText = ast.children[0].children[0]
17 | expect(nodeText.content).toBe('hi, mini-vue')
18 | });
19 | });
--------------------------------------------------------------------------------
/packages/compiler-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tiny-vue3/compiler-core",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "keywords": [],
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@tiny-vue3/shared": "workspace:^1.0.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/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 |
15 | return {
16 | type: NodeTypes.ELEMENT,
17 | tag,
18 | props,
19 | children,
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/codegen.ts:
--------------------------------------------------------------------------------
1 | import { isString } from "@tiny-vue3/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();
11 | const { push } = context;
12 |
13 | genFunctionPreamble(ast, context);
14 |
15 | const functionName = "render";
16 | const args = ["_ctx", "_cache"];
17 | const signature = args.join(", ");
18 |
19 | push(`function ${functionName}(${signature}){`);
20 | push(`return `);
21 | genNode(ast.codegenNode, context);
22 | push("}");
23 |
24 | return {
25 | code: context.code,
26 | };
27 | }
28 |
29 | function genFunctionPreamble(ast: any, context: any) {
30 | const { push } = context;
31 | const VueBinging = "Vue";
32 | const aliasHelper = (s) => `${helperMapName[s]}: _${helperMapName[s]}`;
33 | if (ast.helpers.length > 0) {
34 | push(
35 | `const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`
36 | );
37 | }
38 | push("\n");
39 | push("return ");
40 | }
41 |
42 | function createCodegenContext(): any {
43 | const context = {
44 | code: "",
45 | push(source) {
46 | context.code += source;
47 | },
48 | helper(key) {
49 | return `_${helperMapName[key]}`;
50 | },
51 | };
52 |
53 | return context;
54 | }
55 |
56 | function genNode(node: any, context) {
57 | switch (node.type) {
58 | case NodeTypes.TEXT:
59 | genText(node, context);
60 | break;
61 |
62 | case NodeTypes.INTERPOLATION:
63 | genInterpolation(node, context);
64 | break;
65 |
66 | case NodeTypes.SIMPLE_EXPRESSION:
67 | genExpression(node, context);
68 | break;
69 |
70 | case NodeTypes.ELEMENT:
71 | genElement(node, context);
72 |
73 | break;
74 | case NodeTypes.COMPOUND_EXPRESSION:
75 | genCompoundExpression(node, context);
76 | break;
77 | default:
78 | break;
79 | }
80 | }
81 |
82 | function genCompoundExpression(node, context) {
83 | const { push } = context;
84 | const { children } = node;
85 |
86 | for (let i = 0; i < children.length; i++) {
87 | const child = children[i];
88 | if (isString(child)) {
89 | push(child);
90 | } else {
91 | genNode(child, context);
92 | }
93 | }
94 | }
95 |
96 | function genElement(node, context) {
97 | const { push, helper } = context;
98 | const { tag, children, props } = node;
99 | push(`${helper(CREATE_ELEMENT_VNODE)}(`);
100 | genNodeList(genNullable([tag, props, children]), context);
101 | push(")");
102 | }
103 |
104 | function genNodeList(nodes, context) {
105 | const { push } = context;
106 | for (let i = 0; i < nodes.length; i++) {
107 | const node = nodes[i];
108 | if (isString(node)) {
109 | push(node);
110 | } else {
111 | genNode(node, context);
112 | }
113 |
114 | if (i < nodes.length - 1) {
115 | push(", ");
116 | }
117 | }
118 | }
119 |
120 | function genNullable(args: any) {
121 | return args.map((arg) => arg || "null");
122 | }
123 |
124 | function genText(node: any, context: any) {
125 | const { push } = context;
126 | push(`'${node.content}'`);
127 | }
128 |
129 | function genInterpolation(node: any, context: any) {
130 | const { push, helper } = context;
131 | push(`${helper(TO_DISPLAY_STRING)}(`);
132 | genNode(node.content, context);
133 | push(")");
134 | }
135 |
136 | function genExpression(node: any, context: any) {
137 | const { push } = context;
138 | push(`${node.content}`);
139 | }
140 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/compile.ts:
--------------------------------------------------------------------------------
1 | import { generate } from "./codegen";
2 | import { baseParse } from "./parse";
3 | import { transform } from "./transform";
4 | import { transformElement } from "./transforms/transformElement";
5 | import { transformExpression } from "./transforms/transformExpression";
6 | import { transformText } from "./transforms/transformText";
7 |
8 | export function baseCompile(template) {
9 | const ast: 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'
--------------------------------------------------------------------------------
/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 = createParserContent(content);
10 |
11 | return createRoot(parseChildren(context, []));
12 | }
13 | function parseChildren(context, ancestors) {
14 | const nodes: any = [];
15 | while (!isEnd(context, ancestors)) {
16 | let node;
17 | const s = context.source;
18 | if (s.startsWith("{{")) {
19 | node = parseInterpolation(context);
20 | } else if (s[0] === "<") {
21 | if (/[a-z]/i.test(s[1])) {
22 | node = parseElement(context, ancestors);
23 | }
24 | }
25 |
26 | if (!node) {
27 | node = parseText(context);
28 | }
29 |
30 | nodes.push(node);
31 | }
32 | return nodes;
33 | }
34 |
35 | function isEnd(context, ancestors) {
36 | // 2. 遇到结束标签
37 | const s = context.source;
38 | if (s.startsWith("")) {
39 | for (let i = ancestors.length - 1; i >= 0; --i) {
40 | const tag = ancestors[i].tag;
41 | if (startsWithEndTagOpen(s, tag)) {
42 | return true;
43 | }
44 | }
45 | }
46 | // 1. source 有值
47 | return !context.source;
48 | }
49 | function parseText(context: any) {
50 | let endIndex = context.source.length;
51 | let endTokens = ["<", "{{"];
52 |
53 | for (let i = 0; i < endTokens.length; i++) {
54 | const index = context.source.indexOf(endTokens[i]);
55 | if (index !== -1 && endIndex > index) {
56 | endIndex = index;
57 | }
58 | }
59 | // 1. 获取 context
60 | const content = parseTextData(context, endIndex);
61 |
62 | console.log("---------", content);
63 |
64 | return {
65 | type: NodeTypes.TEXT,
66 | content,
67 | };
68 | }
69 |
70 | function parseTextData(context: any, length) {
71 | const content = context.source.slice(0, length);
72 | // 2. 推进
73 | advanceBy(context, length);
74 |
75 | return content;
76 | }
77 |
78 | function parseElement(context: any, ancestors) {
79 | // Implement
80 | // 1. 解析 tag
81 | const element: any = parseTag(context, TagType.Start);
82 | ancestors.push(element);
83 | element.children = parseChildren(context, ancestors);
84 | ancestors.pop();
85 |
86 | if (startsWithEndTagOpen(context.source, element.tag)) {
87 | parseTag(context, TagType.End);
88 | } else {
89 | throw new Error(`缺少结束标签:${element.tag}`);
90 | }
91 |
92 | return element;
93 | }
94 |
95 | function startsWithEndTagOpen(source, tag) {
96 | return (
97 | source.startsWith("") &&
98 | source.slice(2, 2 + tag.length).toLowerCase() === tag
99 | );
100 | }
101 |
102 | function parseTag(context, type: TagType) {
103 | const match: any = /^<\/?([a-z]*)/i.exec(context.source);
104 | const tag = match[1];
105 | // 2. 删除处理完成的代码
106 | advanceBy(context, match[0].length);
107 | advanceBy(context, 1);
108 |
109 | if (type === TagType.End) return;
110 | console.log(context);
111 |
112 | return {
113 | type: NodeTypes.ELEMENT,
114 | tag,
115 | };
116 | }
117 |
118 | function parseInterpolation(context) {
119 | // {{ message }}
120 |
121 | const openDelimiter = "{{";
122 | const closeDelimiter = "}}";
123 |
124 | const closeIndex = context.source.indexOf(
125 | closeDelimiter,
126 | openDelimiter.length
127 | );
128 |
129 | advanceBy(context, openDelimiter.length);
130 |
131 | const rawContentLength = closeIndex - openDelimiter.length;
132 |
133 | const rawContent = parseTextData(context, rawContentLength);
134 | const content = rawContent.trim();
135 | advanceBy(context, closeDelimiter.length);
136 |
137 | return {
138 | type: NodeTypes.INTERPOLATION,
139 | content: {
140 | type: NodeTypes.SIMPLE_EXPRESSION,
141 | content,
142 | },
143 | };
144 | }
145 |
146 | function advanceBy(context: any, length: number) {
147 | context.source = context.source.slice(length);
148 | }
149 |
150 | function createRoot(children) {
151 | return {
152 | children,
153 | type: NodeTypes.ROOT
154 | };
155 | }
156 | function createParserContent(content: string): any {
157 | return {
158 | source: content,
159 | };
160 | }
161 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/runtimeHelpers.ts:
--------------------------------------------------------------------------------
1 | export const TO_DISPLAY_STRING = Symbol("toDisplayString");
2 | export const CREATE_ELEMENT_VNODE = Symbol('createElementVNode');
3 |
4 | export const helperMapName = {
5 | [TO_DISPLAY_STRING]: "toDisplayString",
6 | [CREATE_ELEMENT_VNODE]: 'createElementVNode'
7 | };
8 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/transform.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 | import { TO_DISPLAY_STRING, CREATE_ELEMENT_VNODE } from "./runtimeHelpers";
3 |
4 | export function transform(root, options = {}) {
5 | const context = createTransformContext(root, options);
6 | // 1. dfs
7 | traverseNode(root, context);
8 | // 2. 修改 text content
9 |
10 | // root.codegenNode
11 | createRootCodegen(root);
12 |
13 | root.helpers = [...context.helpers.keys()];
14 | }
15 |
16 | function createRootCodegen(root: any) {
17 | const child = root.children[0];
18 | if (child.type === NodeTypes.ELEMENT) {
19 | root.codegenNode = child.codegenNode;
20 | } else {
21 | root.codegenNode = root.children[0];
22 | }
23 | }
24 |
25 | function createTransformContext(root: any, options: any) {
26 | const context = {
27 | root,
28 | nodeTransforms: options.nodeTransforms || [],
29 | helpers: new Map(),
30 | helper(key) {
31 | context.helpers.set(key, 1);
32 | },
33 | };
34 |
35 | return context;
36 | }
37 |
38 | function traverseNode(node: any, context) {
39 | const nodeTransforms = context.nodeTransforms;
40 | const exitFns: any = [];
41 | for (let i = 0; i < nodeTransforms.length; i++) {
42 | const transform = nodeTransforms[i];
43 | const onExit = transform(node, context);
44 | if (onExit) exitFns.push(onExit);
45 | }
46 |
47 | switch (node.type) {
48 | case NodeTypes.INTERPOLATION:
49 | context.helper(TO_DISPLAY_STRING);
50 | break;
51 |
52 | case NodeTypes.ROOT:
53 | case NodeTypes.ELEMENT:
54 | traverseChildren(node, context);
55 | context.helper(CREATE_ELEMENT_VNODE);
56 | break;
57 |
58 | default:
59 | break;
60 | }
61 |
62 | let i = exitFns.length;
63 | while (i--) {
64 | exitFns[i]();
65 | }
66 | }
67 |
68 | function traverseChildren(node, context) {
69 | const children = node.children;
70 |
71 | for (let i = 0; i < children.length; i++) {
72 | const node = children[i];
73 |
74 | traverseNode(node, context);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/transforms/transformElement.ts:
--------------------------------------------------------------------------------
1 | import { createVNodeCall, NodeTypes } from "../ast";
2 |
3 | export function transformElement(node, context) {
4 | if (node.type === NodeTypes.ELEMENT) {
5 | return () => {
6 | // tag
7 | const vnodeTag = `'${node.tag}'`;
8 |
9 | // props
10 | let vnodeProps;
11 |
12 | // children
13 | const children = node.children;
14 | let vnodeChildren = children[0];
15 |
16 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);
17 | };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/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 | for (let i = 0; i < children.length; i++) {
9 | const child = children[i];
10 | let currentContainer;
11 | if (isText(child)) {
12 | for (let j = i + 1; j < children.length; j++) {
13 | const next = children[j];
14 | if (isText(next)) {
15 | if (!currentContainer) {
16 | currentContainer = children[i] = {
17 | type: NodeTypes.COMPOUND_EXPRESSION,
18 | children: [child],
19 | };
20 | }
21 | currentContainer.children.push(" + ");
22 | currentContainer.children.push(next);
23 | children.splice(j, 1);
24 | j--;
25 | } else {
26 | currentContainer = undefined;
27 | break;
28 | }
29 | }
30 | }
31 | }
32 | };
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | export function isText(node) {
4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { vi } from "vitest";
2 |
3 | import { computed } from "../src/computed";
4 | import { reactive } from "../src/reactive";
5 |
6 | describe("computed", () => {
7 | it("happy path", () => {
8 | const user = reactive({
9 | age: 1,
10 | });
11 |
12 | const age = computed(() => {
13 | return user.age;
14 | });
15 |
16 | expect(age.value).toBe(1);
17 | });
18 |
19 | it("should compute lazily", () => {
20 | const value = reactive({
21 | foo: 1,
22 | });
23 | const getter = vi.fn(() => {
24 | return value.foo;
25 | });
26 | const cValue = computed(getter);
27 |
28 | // lazy
29 | expect(getter).not.toHaveBeenCalled();
30 |
31 | expect(cValue.value).toBe(1);
32 | expect(getter).toHaveBeenCalledTimes(1);
33 |
34 | // should not compute again
35 | cValue.value; // get
36 | expect(getter).toHaveBeenCalledTimes(1);
37 |
38 | // // should not compute until needed
39 | value.foo = 2;
40 | expect(getter).toHaveBeenCalledTimes(1);
41 |
42 | // // now it should compute
43 | expect(cValue.value).toBe(2);
44 | expect(getter).toHaveBeenCalledTimes(2);
45 |
46 | // // should not compute again
47 | cValue.value;
48 | expect(getter).toHaveBeenCalledTimes(2);
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { vi } from "vitest";
2 |
3 | import { reactive } from "../src/reactive";
4 | import { effect, stop } from "../src/effect";
5 |
6 | describe("effect", () => {
7 | it("happy path", () => {
8 | const user = reactive({
9 | age: 10,
10 | });
11 |
12 | let nextAge;
13 | effect(() => {
14 | nextAge = user.age + 1;
15 | });
16 |
17 | expect(nextAge).toBe(11);
18 |
19 | // update
20 | user.age++;
21 | expect(nextAge).toBe(12);
22 | });
23 |
24 | it("should return runner when call effect", () => {
25 | // 当调用 runner 的时候可以重新执行 effect.run
26 | // runner 的返回值就是用户给的 fn 的返回值
27 | let foo = 0;
28 | const runner = effect(() => {
29 | foo++;
30 | return foo;
31 | });
32 |
33 | expect(foo).toBe(1);
34 | runner();
35 | expect(foo).toBe(2);
36 | expect(runner()).toBe(3);
37 | });
38 |
39 | it("scheduler", () => {
40 | let dummy;
41 | let run: any;
42 | const scheduler = vi.fn(() => {
43 | run = runner;
44 | });
45 | const obj = reactive({ foo: 1 });
46 | const runner = effect(
47 | () => {
48 | dummy = obj.foo;
49 | },
50 | { scheduler }
51 | );
52 | expect(scheduler).not.toHaveBeenCalled();
53 | expect(dummy).toBe(1);
54 | // should be called on first trigger
55 | obj.foo++;
56 | expect(scheduler).toHaveBeenCalledTimes(1);
57 | // // should not run yet
58 | expect(dummy).toBe(1);
59 | // // manually run
60 | run();
61 | // // should have run
62 | expect(dummy).toBe(2);
63 | });
64 |
65 | it("stop", () => {
66 | let dummy;
67 | const obj = reactive({ prop: 1 });
68 | const runner = effect(() => {
69 | dummy = obj.prop;
70 | });
71 | obj.prop = 2;
72 | expect(dummy).toBe(2);
73 | stop(runner);
74 | // obj.prop = 3;
75 | obj.prop++;
76 | expect(dummy).toBe(2);
77 |
78 | // stopped effect should still be manually callable
79 | runner();
80 | expect(dummy).toBe(3);
81 | });
82 |
83 | it("onStop", () => {
84 | const obj = reactive({
85 | foo: 1,
86 | });
87 | const onStop = vi.fn();
88 | let dummy;
89 | const runner = effect(
90 | () => {
91 | dummy = obj.foo;
92 | },
93 | {
94 | onStop,
95 | }
96 | );
97 |
98 | stop(runner);
99 | expect(onStop).toBeCalledTimes(1);
100 | });
101 | });
102 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReactive, reactive, isProxy } from "../src/reactive";
2 | describe("reactive", () => {
3 | it("happy path", () => {
4 | const original = { foo: 1 };
5 | const observed = reactive(original);
6 | expect(observed).not.toBe(original);
7 | expect(observed.foo).toBe(1);
8 | expect(isReactive(observed)).toBe(true);
9 | expect(isReactive(original)).toBe(false);
10 | expect(isProxy(observed)).toBe(true);
11 | });
12 |
13 | test("nested reactives", () => {
14 | const original = {
15 | nested: {
16 | foo: 1,
17 | },
18 | array: [{ bar: 2 }],
19 | };
20 | const observed = reactive(original);
21 | expect(isReactive(observed.nested)).toBe(true);
22 | expect(isReactive(observed.array)).toBe(true);
23 | expect(isReactive(observed.array[0])).toBe(true);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { vi } from "vitest";
2 |
3 | import { isReadonly, readonly, isProxy } from "../src/reactive";
4 |
5 | describe("readonly", () => {
6 | it("should make nested values readonly", () => {
7 | const original = { foo: 1, bar: { baz: 2 } };
8 | const wrapped = readonly(original);
9 | expect(wrapped).not.toBe(original);
10 | expect(isReadonly(wrapped)).toBe(true);
11 | expect(isReadonly(original)).toBe(false);
12 | expect(isReadonly(wrapped.bar)).toBe(true);
13 | expect(isReadonly(original.bar)).toBe(false);
14 | expect(isProxy(wrapped)).toBe(true);
15 |
16 | expect(wrapped.foo).toBe(1);
17 | });
18 |
19 | it("should call console.warn when set", () => {
20 | console.warn = vi.fn();
21 | const user = readonly({
22 | age: 10,
23 | });
24 |
25 | user.age = 11;
26 | expect(console.warn).toHaveBeenCalled();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../src/effect";
2 | import { reactive } from "../src/reactive";
3 | import { ref, isRef, unref, proxyRefs } from "../src/ref";
4 | describe("ref", () => {
5 | it("happy path", () => {
6 | const a: any = ref(1);
7 | expect(a.value).toBe(1);
8 | });
9 |
10 | it("should be reactive", () => {
11 | const a:any = ref(1);
12 | let dummy;
13 | let calls = 0;
14 | effect(() => {
15 | calls++;
16 | dummy = a.value;
17 | });
18 | expect(calls).toBe(1);
19 | expect(dummy).toBe(1);
20 | a.value = 2;
21 | expect(calls).toBe(2);
22 | expect(dummy).toBe(2);
23 | // same value should not trigger
24 | a.value = 2;
25 | expect(calls).toBe(2);
26 | expect(dummy).toBe(2);
27 | });
28 |
29 | it("should make nested properties reactive", () => {
30 | const a:any = ref({
31 | count: 1,
32 | });
33 | let dummy;
34 | effect(() => {
35 | dummy = a.value.count;
36 | });
37 | expect(dummy).toBe(1);
38 | a.value.count = 2;
39 | expect(dummy).toBe(2);
40 | });
41 |
42 | it("isRef", () => {
43 | const a = ref(1);
44 | const user = reactive({
45 | age: 1,
46 | });
47 | expect(isRef(a)).toBe(true);
48 | expect(isRef(1)).toBe(false);
49 | expect(isRef(user)).toBe(false);
50 | });
51 |
52 | it("unref", () => {
53 | const a = ref(1);
54 | expect(unref(a)).toBe(1);
55 | expect(unref(1)).toBe(1);
56 | });
57 |
58 |
59 | it("proxyRefs", () => {
60 | const user: any = {
61 | age: ref(10),
62 | name: "xiaohong",
63 | };
64 |
65 | const proxyUser = proxyRefs(user);
66 | expect(user.age.value).toBe(10);
67 | expect(proxyUser.age).toBe(10);
68 | expect(proxyUser.name).toBe("xiaohong");
69 |
70 | proxyUser.age = 20;
71 |
72 | expect(proxyUser.age).toBe(20);
73 | expect(user.age.value).toBe(20);
74 |
75 | proxyUser.age = ref(10);
76 | expect(proxyUser.age).toBe(10);
77 | expect(user.age.value).toBe(10);
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/shallowReactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReactive, shallowReactive } from "../src/reactive";
2 | import { effect } from '../src/effect'
3 |
4 | describe("shallowReactive", () => {
5 | test("should not make non-reactive properties reactive", () => {
6 | let dummy;
7 | let dummy2;
8 | const props = shallowReactive({ foo: 1, nested: { bar: 1 } });
9 |
10 | effect(() => {
11 | dummy = props.foo;
12 | dummy2 = props.nested.bar;
13 | });
14 | expect(isReactive(props)).toBe(true);
15 | expect(isReactive(props.nested)).toBe(false);
16 |
17 | props.foo = 2;
18 | expect(dummy).toBe(2);
19 |
20 | props.nested.bar++;
21 | expect(dummy2).toBe(1);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { vi } from "vitest";
2 |
3 | import { isReadonly, shallowReadonly } from "../src/reactive";
4 |
5 | describe("shallowReadonly", () => {
6 | test("should not make non-reactive properties reactive", () => {
7 | const props = shallowReadonly({ n: { foo: 1 } });
8 | expect(isReadonly(props)).toBe(true);
9 | expect(isReadonly(props.n)).toBe(false);
10 | });
11 |
12 | it("should call console.warn when set", () => {
13 | console.warn = vi.fn();
14 | const user = shallowReadonly({
15 | age: 10,
16 | });
17 |
18 | user.age = 11;
19 | expect(console.warn).toHaveBeenCalled();
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/shallowRef.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReactive } from "../src/reactive";
2 | import { shallowRef, isRef } from "../src/ref";
3 | import { effect } from "../src/effect";
4 |
5 | describe("shallowRef", () => {
6 | test("should not make non-reactive properties reactive", () => {
7 | let dummy: any;
8 | const props: any = shallowRef({ foo: 1 });
9 |
10 | effect(() => {
11 | dummy = props.value.foo || "can't find";
12 | });
13 | expect(isRef(props)).toBe(true);
14 | expect(isReactive(props.value)).toBe(false);
15 | expect(isReactive(props.value.foo)).toBe(false);
16 |
17 | props.value.foo = 2;
18 | expect(dummy).toBe(1);
19 |
20 | props.value = {};
21 | expect(dummy).toBe("can't find");
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/packages/reactivity/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tiny-vue3/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 | "@tiny-vue3/shared": "workspace:^1.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/reactivity/src/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { extend, isObject } from "@tiny-vue3/shared";
2 |
3 | import { reactive, ReactiveFlags, readonly } from "./reactive";
4 | import { track, trigger } from "./effect";
5 |
6 | const get = createGetter();
7 | const set = createSetter();
8 | const readonlyGet = createGetter(true);
9 | const shallowReactiveGet = createGetter(false, true)
10 | const shallowReadonlyGet = createGetter(true, true);
11 |
12 | function createGetter(isReadonly = false, shallow = false) {
13 | return function (target, key) {
14 | if (key === ReactiveFlags.IS_REACTIVE) {
15 | return !isReadonly;
16 | } else if (key === ReactiveFlags.IS_READONLY) {
17 | return isReadonly;
18 | }
19 |
20 | const res = Reflect.get(target, key);
21 |
22 | if (!isReadonly) {
23 | // 依赖收集
24 | track(target, key);
25 | }
26 |
27 | if (shallow) {
28 | return res;
29 | }
30 |
31 | if (isObject(res)) {
32 | return isReadonly ? readonly(res) : reactive(res);
33 | }
34 |
35 | return res;
36 | };
37 | }
38 |
39 | function createSetter() {
40 | return function set(target, key, value) {
41 | const res = Reflect.set(target, key, value);
42 |
43 | // 依赖触发
44 | trigger(target, key);
45 | return res;
46 | };
47 | }
48 |
49 | export const mutableHandlers = {
50 | get,
51 | set,
52 | };
53 |
54 | export const shallowReactiveHandlers = extend({}, mutableHandlers, {
55 | get: shallowReactiveGet,
56 | });
57 |
58 | export const readonlyHandlers = {
59 | get: readonlyGet,
60 | set(target, key) {
61 | console.warn(
62 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`,
63 | target
64 | );
65 |
66 | return true;
67 | },
68 | };
69 |
70 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
71 | get: shallowReadonlyGet,
72 | });
73 |
--------------------------------------------------------------------------------
/packages/reactivity/src/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 | class ComputedRefImpl {
4 | private _dirty: boolean = true;
5 | private _value: any;
6 | private _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 |
24 | export function computed(getter) {
25 | return new ComputedRefImpl(getter);
26 | }
27 |
--------------------------------------------------------------------------------
/packages/reactivity/src/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "@tiny-vue3/shared";
2 |
3 | let activeEffect;
4 | let shouldTrack = false;
5 | export class ReactiveEffect {
6 | private _fn;
7 | deps = [];
8 | active = true;
9 | onStop?: () => void;
10 |
11 | constructor(fn, public scheduler?) {
12 | this._fn = fn;
13 | }
14 |
15 | run() {
16 | if (!this.active) {
17 | return this._fn();
18 | }
19 | // 应该收集
20 | activeEffect = this;
21 | shouldTrack = true;
22 | const r = this._fn();
23 |
24 | // 重置
25 | shouldTrack = false;
26 |
27 | return r;
28 | }
29 |
30 | stop() {
31 | if (this.active) {
32 | cleanupEffect(this);
33 | if (this.onStop) {
34 | this.onStop();
35 | }
36 | this.active = false;
37 | }
38 | }
39 | }
40 |
41 | function cleanupEffect(effect) {
42 | effect.deps.forEach((dep: any) => {
43 | dep.delete(effect);
44 | });
45 |
46 | // 把 effect.deps 清空
47 | effect.deps.length = 0;
48 | }
49 |
50 | const targetMap = new Map();
51 | export function track(target, key) {
52 | if (!isTracking()) return;
53 |
54 | // target -> key -> dep
55 | let depsMap = targetMap.get(target);
56 | if (!depsMap) {
57 | depsMap = new Map();
58 | targetMap.set(target, depsMap);
59 | }
60 |
61 | let dep = depsMap.get(key);
62 | if (!dep) {
63 | dep = new Set();
64 | depsMap.set(key, dep);
65 | }
66 | trackEffects(dep);
67 | }
68 |
69 | export function trackEffects(dep) {
70 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
71 | if (dep.has(activeEffect)) return;
72 |
73 | dep.add(activeEffect);
74 | activeEffect.deps.push(dep);
75 | }
76 |
77 | export function isTracking() {
78 | return shouldTrack && activeEffect !== undefined;
79 | }
80 |
81 | export function trigger(target, key) {
82 | const depsMap = targetMap.get(target);
83 | const dep = depsMap.get(key);
84 | triggerEffects(dep);
85 | }
86 |
87 | export function triggerEffects(dep) {
88 | for (const effect of dep) {
89 | if (effect.scheduler) {
90 | effect.scheduler();
91 | } else {
92 | effect.run();
93 | }
94 | }
95 | }
96 |
97 | export function effect(fn, options: any = {}) {
98 | const _effect = new ReactiveEffect(fn, options.scheduler);
99 | extend(_effect, options);
100 |
101 | _effect.run();
102 |
103 | const runner: any = _effect.run.bind(_effect);
104 | runner.effect = _effect;
105 | return runner;
106 | }
107 |
108 | export function stop(runner) {
109 | runner.effect.stop();
110 | }
111 |
--------------------------------------------------------------------------------
/packages/reactivity/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ref, proxyRefs, unref, isRef } from "./ref";
2 |
3 | export {
4 | reactive,
5 | readonly,
6 | shallowReactive,
7 | shallowReadonly,
8 | isReactive,
9 | isReadonly,
10 | isProxy,
11 | } from "./reactive";
12 |
13 | export { effect, stop } from './effect'
--------------------------------------------------------------------------------
/packages/reactivity/src/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "@tiny-vue3/shared";
2 | import {
3 | mutableHandlers,
4 | readonlyHandlers,
5 | shallowReactiveHandlers,
6 | shallowReadonlyHandlers,
7 | } from "./baseHandlers";
8 |
9 | export const enum ReactiveFlags {
10 | IS_REACTIVE = "__v_isReactive",
11 | IS_READONLY = "__v_isReadonly",
12 | }
13 |
14 | export function reactive(raw) {
15 | return createReactiveObject(raw, mutableHandlers);
16 | }
17 |
18 | export function shallowReactive(raw) {
19 | return createReactiveObject(raw, shallowReactiveHandlers);
20 | }
21 |
22 | export function readonly(raw) {
23 | return createReactiveObject(raw, readonlyHandlers);
24 | }
25 |
26 | export function shallowReadonly(raw) {
27 | return createReactiveObject(raw, shallowReadonlyHandlers);
28 | }
29 |
30 | function createReactiveObject(target, baseHandlerss) {
31 | if (!isObject(target)) {
32 | console.warn(`target ${target} 必须是一个对象`);
33 | return target;
34 | }
35 | return new Proxy(target, baseHandlerss);
36 | }
37 |
38 | export function isReactive(value) {
39 | return !!value[ReactiveFlags.IS_REACTIVE];
40 | }
41 |
42 | export function isReadonly(value) {
43 | return !!value[ReactiveFlags.IS_READONLY];
44 | }
45 |
46 | export function isProxy(value) {
47 | return isReactive(value) || isReadonly(value);
48 | }
49 |
50 | export const toReactive = (value) => {
51 | return isObject(value) ? reactive(value) : value;
52 | };
53 |
54 | export const toReadonly = (value) => {
55 | return isObject(value) ? readonly(value) : value;
56 | };
57 |
--------------------------------------------------------------------------------
/packages/reactivity/src/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from "@tiny-vue3/shared";
2 |
3 | import { reactive } from "./reactive";
4 | import { isTracking, trackEffects, triggerEffects } from "./effect";
5 |
6 | class RefImpl {
7 | private _value;
8 | private dep;
9 | private _rawValue: any;
10 | public readonly __v_isRef = true;
11 | constructor(value, public readonly __v_isShallow) {
12 | this._value = __v_isShallow ? value : convert(value);
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(newValue) {
23 | if (hasChanged(this._rawValue, newValue)) {
24 | this._value = this.__v_isShallow ? newValue : convert(newValue);
25 | this._rawValue = newValue;
26 | triggerEffects(this.dep);
27 | }
28 | }
29 | }
30 |
31 | function createRef(rawValue: unknown, shallow:boolean) {
32 | if (isRef(rawValue)) {
33 | return rawValue
34 | }
35 | return new RefImpl(rawValue, shallow);
36 | }
37 |
38 | export function ref(value) {
39 | return createRef(value, false);
40 | }
41 |
42 | export function shallowRef(value) {
43 | return createRef(value, true);
44 | }
45 |
46 | function convert(value) {
47 | return isObject(value) ? reactive(value) : value;
48 | }
49 |
50 | function trackRefValue(ref) {
51 | if (isTracking()) {
52 | trackEffects(ref.dep);
53 | }
54 | }
55 |
56 | export function isRef(ref) {
57 | return !!ref.__v_isRef;
58 | }
59 |
60 | export function unref(ref) {
61 | return isRef(ref) ? ref.value : ref;
62 | }
63 |
64 | export function proxyRefs(objectWithRefs) {
65 | return new Proxy(objectWithRefs, {
66 | get(target, key) {
67 | return unref(Reflect.get(target, key));
68 | },
69 | set(target, key, value) {
70 | if (isRef(target[key]) && !isRef(value)) {
71 | return (target[key].value = value);
72 | } else {
73 | return Reflect.set(target, key, value);
74 | }
75 | },
76 | });
77 | }
78 |
--------------------------------------------------------------------------------
/packages/runtime-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tiny-vue3/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 | "@tiny-vue3/reactivity": "workspace:^1.0.0",
14 | "@tiny-vue3/shared": "workspace:^1.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 |
3 | export function provide(key, value) {
4 | // 存
5 | const currentInstance: any = getCurrentInstance();
6 |
7 | if (currentInstance) {
8 | let { provides } = currentInstance;
9 | const parentProvides = currentInstance.parent.provides;
10 |
11 | // init
12 | if (provides === parentProvides) {
13 | provides = currentInstance.provides = Object.create(parentProvides);
14 | }
15 |
16 | provides[key] = value;
17 | }
18 | }
19 |
20 | export function inject(key, defaultValue) {
21 | // 取
22 | const currentInstance: any = getCurrentInstance();
23 |
24 | if (currentInstance) {
25 | const parentProvides = currentInstance.parent.provides;
26 | if (key in parentProvides) {
27 | return parentProvides[key];
28 | } else if (typeof defaultValue === "function") {
29 | return defaultValue();
30 | }
31 |
32 | return defaultValue;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRefs, shallowReadonly } from "@tiny-vue3/reactivity";
2 |
3 | import { emit } from "./componentEmit";
4 | import { initProps } from "./componentProps";
5 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance";
6 | import { initSlots } from "./componentSlots";
7 |
8 | export function createComponentInstance(vnode, parent) {
9 | console.log("createComponentInstance", parent);
10 | const component = {
11 | vnode,
12 | type: vnode.type,
13 | next: null,
14 | setupState: {},
15 | props: {},
16 | slots: {},
17 | provides: parent ? parent.provides : {},
18 | parent,
19 | isMounted: false,
20 | subTree: {},
21 | emit: () => {},
22 | };
23 |
24 | component.emit = emit.bind(null, component) as any;
25 |
26 | return component;
27 | }
28 |
29 | export function setupComponent(instance) {
30 | initProps(instance, instance.vnode.props);
31 | initSlots(instance, instance.vnode.children);
32 |
33 | setupStatefulComponent(instance);
34 | }
35 |
36 | function setupStatefulComponent(instance: any) {
37 | const Component = instance.type;
38 |
39 | // ctx
40 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
41 |
42 | const { setup } = Component;
43 |
44 | if (setup) {
45 | setCurrentInstance(instance);
46 | const setupResult = setup(shallowReadonly(instance.props), {
47 | emit: instance.emit,
48 | });
49 |
50 | setCurrentInstance(null);
51 |
52 | handleSetupResult(instance, setupResult);
53 | }
54 | }
55 |
56 | function handleSetupResult(instance, setupResult: any) {
57 | // function Object
58 | // TODO function 渲染函数
59 |
60 | if (typeof setupResult === "object") {
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 | instance.render = Component.render;
77 | }
78 |
79 | let currentInstance = null;
80 | export function getCurrentInstance() {
81 | return currentInstance;
82 | }
83 |
84 | export function setCurrentInstance(instance) {
85 | currentInstance = instance;
86 | }
87 |
88 | let compiler;
89 |
90 | export function registerRuntimeCompiler(_compiler) {
91 | compiler = _compiler;
92 | }
93 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerKey } from "@tiny-vue3/shared";
2 |
3 | export function emit(instance, event, ...args) {
4 | console.log("emit", event);
5 |
6 | // instance.props -> event
7 | const { props } = instance;
8 |
9 | // TPP
10 | // 先去写特定行为 -> 重构成通用行为
11 | // add
12 | const handlerName = toHandlerKey(camelize(event));
13 | const handler = props[handlerName];
14 |
15 | handler && handler(...args);
16 | }
17 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance, rawProps) {
2 |
3 | instance.props = rawProps || {};
4 | // attrs
5 | }
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from "@tiny-vue3/shared";
2 |
3 | const publicPropertiesMap = {
4 | $el: (i) => i.vnode.el,
5 | $slots: (i) => i.slots,
6 | $props: (i) => i.props,
7 | };
8 |
9 | export const PublicInstanceProxyHandlers = {
10 | get({ _: instance }, key) {
11 | // setupState
12 | const { setupState, props } = instance;
13 | // if (key in setupState) {
14 | // return setupState[key];
15 | // }
16 |
17 | if (hasOwn(setupState, key)) {
18 | return setupState[key];
19 | } else if (hasOwn(props, key)) {
20 | return props[key];
21 | }
22 |
23 | const publicGetter = publicPropertiesMap[key];
24 | if (publicGetter) {
25 | return publicGetter(instance);
26 | }
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "@tiny-vue3/shared";
2 |
3 | export function initSlots(instance, children) {
4 | // children object
5 | // instance.slots = Array.isArray(children) ? children : [children];
6 | const { vnode } = instance;
7 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) {
8 | normalizeObjectSlots(children, instance.slots);
9 | }
10 | }
11 |
12 | function normalizeObjectSlots(children, slots) {
13 | for (const key in children) {
14 | const value = children[key];
15 |
16 | slots[key] = (props) => normalizeSlotValue(value(props));
17 | }
18 | }
19 |
20 | function normalizeSlotValue(value) {
21 | return Array.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 | // 先 vode
8 | // 基于 vode 做处理
9 |
10 | const vnode = createVNode(rootComponent);
11 |
12 | render(vnode, rootContainer);
13 | },
14 | };
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/packages/runtime-core/src/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from "../vnode";
2 |
3 | export function renderSlots(slots, name, props) {
4 | // vnode
5 | const slot = slots[name];
6 |
7 | if (slot) {
8 | console.log("slot", slot);
9 | if (typeof slot === 'function') {
10 | return createVNode(Fragment, {}, slot(props));
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/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 { getCurrentInstance, registerRuntimeCompiler } from "./component";
5 | export { provide, inject } from './apiInject';
6 | export { createRenderer } from './renderer';
7 | export { nextTick } from './scheduler';
8 | export { toDisplayString } from '@tiny-vue3/shared'
9 | export * from "@tiny-vue3/reactivity";
10 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "@tiny-vue3/reactivity";
2 | import { EMPTY_OBJ, ShapeFlags } from "@tiny-vue3/shared";
3 | import { createComponentInstance, setupComponent } from "./component";
4 | import { shouldUpdateComponent } from "./componentUpdateUtils";
5 | import { createAppAPI } from "./createApp";
6 | import { queueJobs } from "./scheduler";
7 | import { Fragment, Text } from "./vnode";
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 | // n1 -> old
23 | // n2 -> new
24 | function patch(n1, n2, container, parentComponent, anchor) {
25 | console.log('n1', n1)
26 | console.log("n2", n2);
27 | const { type, shapeFlag } = n2;
28 |
29 | switch (type) {
30 | case Fragment:
31 | processFragment(n1, n2, container, parentComponent, anchor);
32 | break;
33 | case Text:
34 | processText(n1, n2, container);
35 | break;
36 |
37 | default:
38 | if (shapeFlag & ShapeFlags.ELEMENT) {
39 | processElement(n1, n2, container, parentComponent, anchor);
40 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
41 | processComponent(n1, n2, container, parentComponent, anchor);
42 | }
43 | break;
44 | }
45 | }
46 |
47 | function processText(n1, n2: any, container: any) {
48 | const { children } = n2;
49 | const textNode = (n2.el = document.createTextNode(children));
50 | container.append(textNode);
51 | }
52 |
53 | function processFragment(
54 | n1,
55 | n2: any,
56 | container: any,
57 | parentComponent,
58 | anchor
59 | ) {
60 | mountChildren(n2.children, container, parentComponent, anchor);
61 | }
62 |
63 | function processElement(
64 | n1,
65 | n2: any,
66 | container: any,
67 | parentComponent,
68 | anchor
69 | ) {
70 | if (!n1) {
71 | mountElement(n1, n2, container, parentComponent, anchor);
72 | } else {
73 | patchElement(n1, n2, container, parentComponent, anchor);
74 | }
75 | }
76 |
77 | function patchElement(n1, n2, container, parentComponent, anchor) {
78 | const oldProps = n1.props || EMPTY_OBJ;
79 | const newProps = n2.props || EMPTY_OBJ;
80 |
81 | const el = (n2.el = n1.el);
82 | patchChildren(n1, n2, el, parentComponent, anchor);
83 | patchProps(el, oldProps, newProps);
84 | }
85 |
86 | function patchChildren(n1, n2, container, parentComponent, anchor) {
87 | const { shapeFlag, children: c2 } = n2;
88 | const { shapeFlag: prevShapeFlag, children: c1 } = n1;
89 |
90 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
91 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
92 | // 1. 把老的 children 清空
93 | unmountChildren(n1.children);
94 | }
95 | if (c1 !== c2) {
96 | hostSetElementText(container, c2);
97 | }
98 | } else {
99 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
100 | hostSetElementText(container, "");
101 | mountChildren(c2, container, parentComponent, anchor);
102 | } else {
103 | // array diff array
104 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
105 | }
106 | }
107 | }
108 |
109 | function patchKeyedChildren(
110 | c1,
111 | c2,
112 | container,
113 | parentComponent,
114 | parentAnchor
115 | ) {
116 | let i = 0;
117 | const l2 = c2.length;
118 | let e1 = c1.length - 1;
119 | let e2 = l2 - 1;
120 |
121 | function isSomeVNodeType(n1, n2) {
122 | return n1.type === n2.type && n1.key === n2.key;
123 | }
124 |
125 | // 左侧
126 | while (i <= e1 && i <= e2) {
127 | const n1 = c1[i];
128 | const n2 = c2[i];
129 |
130 | if (isSomeVNodeType(n1, n2)) {
131 | patch(n1, n2, container, parentComponent, parentAnchor);
132 | } else {
133 | break;
134 | }
135 | i++;
136 | }
137 |
138 | // 右侧
139 | while (i <= e1 && i <= e2) {
140 | const n1 = c1[e1];
141 | const n2 = c2[e2];
142 |
143 | if (isSomeVNodeType(n1, n2)) {
144 | patch(n1, n2, container, parentComponent, parentAnchor);
145 | } else {
146 | break;
147 | }
148 | e1--;
149 | e2--;
150 | }
151 |
152 | // 新的比老的多 创建
153 | if (i > e1) {
154 | // 左侧右侧逻辑共用
155 | if (i <= e2) {
156 | const nextPos = e2 + 1;
157 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
158 | while (i <= e2) {
159 | patch(null, c2[i], container, parentComponent, anchor);
160 | i++;
161 | }
162 | }
163 | } else if (i > e2) {
164 | // 新的比老的少 删除 左侧右侧逻辑共用
165 | while (i <= e1) {
166 | hostRemove(c1[i].el);
167 | i++;
168 | }
169 | } else {
170 | // 中间对比
171 | let s1 = i;
172 | let s2 = i;
173 |
174 | const toBePatched = e2 - s2 + 1;
175 | let patched = 0;
176 | const keyToNewIndexMap = new Map();
177 | const newIndexToOldIndexMap = new Array(toBePatched);
178 | let moved = false;
179 | let maxNewIndexSoFar = 0;
180 |
181 | for (let i = 0; i < toBePatched; i++) {
182 | newIndexToOldIndexMap[i] = 0;
183 | }
184 |
185 | for (let i = s2; i <= e2; i++) {
186 | const nextChild = c2[i];
187 | keyToNewIndexMap.set(nextChild.key, i);
188 | }
189 |
190 | for (let i = s1; i <= e1; i++) {
191 | const prevChild = c1[i];
192 |
193 | if (patched >= toBePatched) {
194 | // patched 的数量大于等于 新的中间节点数量,直接把剩余的老的中间节点删除
195 | hostRemove(prevChild.el);
196 | continue;
197 | }
198 |
199 | let newIndex;
200 | // null undefined
201 | if (prevChild.key != null) {
202 | newIndex = keyToNewIndexMap.get(prevChild.key);
203 | } else {
204 | for (let j = s2; j <= e2; j++) {
205 | if (isSomeVNodeType(prevChild, c2[j])) {
206 | newIndex = j;
207 |
208 | break;
209 | }
210 | }
211 | }
212 |
213 | if (newIndex === undefined) {
214 | hostRemove(prevChild.el);
215 | } else {
216 | if (newIndex >= maxNewIndexSoFar) {
217 | maxNewIndexSoFar = newIndex;
218 | } else {
219 | moved = true;
220 | }
221 |
222 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
223 | patch(prevChild, c2[newIndex], container, parentComponent, null);
224 | patched++;
225 | }
226 | }
227 |
228 | const increasingNewIndexSequence = moved
229 | ? getSequence(newIndexToOldIndexMap)
230 | : [];
231 | let j = increasingNewIndexSequence.length - 1;
232 |
233 | for (let i = toBePatched - 1; i >= 0; i--) {
234 | const nextIndex = i + s2;
235 | const nextChild = c2[nextIndex];
236 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
237 |
238 | if (newIndexToOldIndexMap[i] === 0) {
239 | patch(null, nextChild, container, parentComponent, anchor);
240 | } else if (moved) {
241 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
242 | hostInsert(nextChild.el, container, anchor);
243 | } else {
244 | j--;
245 | }
246 | }
247 | }
248 | }
249 | }
250 |
251 | function unmountChildren(children) {
252 | for (let i = 0; i < children.length; i++) {
253 | const el = children[i].el;
254 | // remove
255 | hostRemove(el);
256 | }
257 | }
258 |
259 | function patchProps(el, oldProps, newProps) {
260 | if (oldProps !== newProps) {
261 | for (const key in newProps) {
262 | const prevProp = oldProps[key];
263 | const nextProp = newProps[key];
264 |
265 | if (prevProp !== nextProp) {
266 | hostPatchProp(el, key, prevProp, nextProp);
267 | }
268 | }
269 |
270 | if (oldProps !== EMPTY_OBJ) {
271 | for (const key in oldProps) {
272 | if (!(key in newProps)) {
273 | hostPatchProp(el, key, oldProps[key], null);
274 | }
275 | }
276 | }
277 | }
278 | }
279 |
280 | function mountElement(n1, n2: any, container: any, parentComponent, anchor) {
281 | //canvas
282 | // new Element()
283 | const el = (n2.el = hostCreateElement(n2.type));
284 |
285 | const { children, shapeFlag } = n2;
286 |
287 | // children
288 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
289 | el.textContent = children;
290 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
291 | mountChildren(n2.children, el, parentComponent, anchor);
292 | }
293 |
294 | // props
295 | const { props } = n2;
296 | for (const key in props) {
297 | const val = props[key];
298 | hostPatchProp(el, key, null, val);
299 | }
300 | // canvs
301 | // el.x = 10
302 |
303 | // container.append(el);
304 | // addChild()
305 | hostInsert(el, container, anchor);
306 | }
307 |
308 | function mountChildren(children, container, parentComponent, anchor) {
309 | children.forEach((v) => {
310 | patch(null, v, container, parentComponent, anchor);
311 | });
312 | }
313 |
314 | function processComponent(
315 | n1,
316 | n2: any,
317 | container: any,
318 | parentComponent,
319 | anchor
320 | ) {
321 | if (!n1) {
322 | mountComponent(n2, container, parentComponent, anchor);
323 | } else {
324 | updateComponent(n1, n2);
325 | }
326 | }
327 |
328 | function updateComponent(n1, n2) {
329 | const instance = (n2.component = n1.component);
330 | if (shouldUpdateComponent(n1, n2)) {
331 | instance.next = n2;
332 | instance.update();
333 | } else {
334 | n2.el = n1.el;
335 | instance.vnode = n2;
336 | }
337 | }
338 |
339 | function mountComponent(
340 | initialVNode: any,
341 | container,
342 | parentComponent,
343 | anchor
344 | ) {
345 | const instance = (initialVNode.component = createComponentInstance(
346 | initialVNode,
347 | parentComponent
348 | ));
349 |
350 | setupComponent(instance);
351 | setupRenderEffect(instance, initialVNode, container, anchor);
352 | }
353 |
354 | function setupRenderEffect(instance: any, initialVNode, container, anchor) {
355 | instance.update = effect(() => {
356 | if (!instance.isMounted) {
357 | console.log("init");
358 | const { proxy } = instance;
359 | const subTree = (instance.subTree = instance.render.call(proxy, proxy));
360 |
361 | patch(null, subTree, container, instance, anchor);
362 |
363 | initialVNode.el = subTree.el;
364 |
365 | instance.isMounted = true;
366 | } else {
367 | console.log("update");
368 | // 需要一个 vnode
369 | const { next, vnode } = instance;
370 | if (next) {
371 | next.el = vnode.el;
372 | updateComponentPreRender(instance, next);
373 | }
374 | const { proxy } = instance;
375 | const subTree = instance.render.call(proxy, proxy);
376 | const prevSubTree = instance.subTree;
377 | instance.subTree = subTree;
378 |
379 | patch(prevSubTree, subTree, container, instance, anchor);
380 | }
381 | }, {
382 | scheduler() {
383 | console.log('update scheduler')
384 | queueJobs(instance.update);
385 | }
386 | });
387 | }
388 |
389 | return {
390 | createApp: createAppAPI(render),
391 | };
392 | }
393 |
394 | function updateComponentPreRender(instance, nextVNode) {
395 | instance.vnode = nextVNode;
396 | instance.next = null;
397 | instance.props = nextVNode.props;
398 | }
399 |
400 | // https://en.wikipedia.org/wiki/Longest_increasing_subsequence
401 | function getSequence(arr: number[]): number[] {
402 | const p = arr.slice();
403 | const result = [0];
404 | let i, j, u, v, c;
405 | const len = arr.length;
406 | for (i = 0; i < len; i++) {
407 | const arrI = arr[i];
408 | if (arrI !== 0) {
409 | j = result[result.length - 1];
410 | if (arr[j] < arrI) {
411 | p[i] = j;
412 | result.push(i);
413 | continue;
414 | }
415 | u = 0;
416 | v = result.length - 1;
417 | while (u < v) {
418 | c = (u + v) >> 1;
419 | if (arr[result[c]] < arrI) {
420 | u = c + 1;
421 | } else {
422 | v = c;
423 | }
424 | }
425 | if (arrI < arr[result[u]]) {
426 | if (u > 0) {
427 | p[i] = result[u - 1];
428 | }
429 | result[u] = i;
430 | }
431 | }
432 | }
433 | u = result.length;
434 | v = result[u - 1];
435 | while (u-- > 0) {
436 | result[u] = v;
437 | v = p[v];
438 | }
439 | return result;
440 | }
441 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/scheduler.ts:
--------------------------------------------------------------------------------
1 | const queue: any[] = [];
2 |
3 | const p = Promise.resolve();
4 | let isFlushPending = false;
5 |
6 | export function nextTick(fn) {
7 | return fn ? p.then(fn) : p;
8 | }
9 |
10 | export function queueJobs(job) {
11 | if (!queue.includes(job)) {
12 | queue.push(job);
13 | }
14 |
15 | queueFlush();
16 | }
17 |
18 | function queueFlush() {
19 | if (isFlushPending) return;
20 | isFlushPending = true;
21 |
22 | nextTick(flushJob);
23 | }
24 |
25 | function flushJob() {
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 { ShapeFlags } from "@tiny-vue3/shared";
2 |
3 | export const Fragment = Symbol("Fragment");
4 | export const Text = Symbol("Text");
5 |
6 | export {
7 | createVNode as createElementVNode
8 | }
9 |
10 | export function createVNode(type, props?, children?) {
11 | const vnode = {
12 | type,
13 | props,
14 | children,
15 | component: null,
16 | key: props && props.key,
17 | shapeFlag: getShapeFlag(type),
18 | el: null,
19 | };
20 |
21 | // chilren
22 | if (typeof children === "string") {
23 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
24 | } else if (Array.isArray(children)) {
25 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
26 | }
27 |
28 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
29 | if (typeof children === "object") {
30 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
31 | }
32 | }
33 |
34 | return vnode;
35 | }
36 |
37 | export function createTextVNode(text: string) {
38 | return createVNode(Text, {}, text);
39 | }
40 |
41 | function getShapeFlag(type) {
42 | return typeof type === "string"
43 | ? ShapeFlags.ELEMENT
44 | : ShapeFlags.STATEFUL_COMPONENT;
45 | }
46 |
--------------------------------------------------------------------------------
/packages/runtime-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tiny-vue3/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 | "@tiny-vue3/runtime-core": "workspace:^1.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/runtime-dom/src/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "@tiny-vue3/runtime-core";
2 |
3 | function createElement(type) {
4 | console.log("createElement ----------");
5 | return document.createElement(type);
6 | }
7 | function patchProp(el, key, prevVal, nextVal) {
8 | console.log("patchProp ----------");
9 | const isOn = (key: string) => /^on[A-Z]/.test(key);
10 | if (isOn(key)) {
11 | const event = key.slice(2).toLowerCase();
12 | el.addEventListener(event, nextVal);
13 | } else {
14 | if (nextVal === undefined || nextVal === null) {
15 | el.removeAttribute(key);
16 | } else {
17 | el.setAttribute(key, nextVal);
18 | }
19 | }
20 | }
21 | function insert(child, parent, anchor) {
22 | console.log("insert ----------", anchor);
23 | parent.insertBefore(child, anchor || null);
24 | }
25 |
26 | function remove(child) {
27 | console.log("remove ----------", child);
28 | const parent = child.parentNode;
29 |
30 | if (parent) {
31 | parent.removeChild(child);
32 | }
33 | }
34 |
35 | function setElementText(el, text) {
36 | el.textContent = text;
37 | }
38 |
39 | const renderer: any = createRenderer({
40 | createElement,
41 | patchProp,
42 | insert,
43 | remove,
44 | setElementText,
45 | });
46 |
47 | export function createApp(...args) {
48 | return renderer.createApp(...args);
49 | }
50 |
51 | export * from "@tiny-vue3/runtime-core";
52 |
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tiny-vue3/shared",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/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/ShapeFlags.ts:
--------------------------------------------------------------------------------
1 | // 0000
2 | // 0001 -> element
3 | // 0010 -> stateful_component
4 | // 0100 -> text_children
5 | // 1000 -> array_children
6 | export const enum ShapeFlags {
7 | ELEMENT = 1,
8 | STATEFUL_COMPONENT = 1 << 1,
9 | TEXT_CHILDREN = 1 << 2,
10 | ARRAY_CHILDREN = 1 << 3,
11 | SLOT_CHILDREN = 1 << 4
12 | }
13 |
--------------------------------------------------------------------------------
/packages/shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './toDisplayString'
2 |
3 | export const extend = Object.assign;
4 | export const EMPTY_OBJ = {};
5 |
6 | export const isObject = (value) => {
7 | return value !== null && typeof value === "object";
8 | };
9 |
10 | export const isString = (value) => typeof value === "string";
11 |
12 | export const hasChanged = (val, newValue) => {
13 | return !Object.is(val, newValue);
14 | };
15 |
16 | export const hasOwn = (val, key) =>
17 | Object.prototype.hasOwnProperty.call(val, key);
18 |
19 | export const camelize = (str: string) => {
20 | return str.replace(/-(\w)/g, (_, c: string) => {
21 | return c ? c.toUpperCase() : "";
22 | });
23 | };
24 | export const capitalize = (str: string) => {
25 | return str.charAt(0).toUpperCase() + str.slice(1);
26 | };
27 |
28 | export const toHandlerKey = (str: string) => {
29 | return str ? "on" + capitalize(str) : "";
30 | };
31 |
32 | export { ShapeFlags } from './ShapeFlags'
--------------------------------------------------------------------------------
/packages/shared/src/toDisplayString.ts:
--------------------------------------------------------------------------------
1 | export function toDisplayString(value) {
2 | return String(value)
3 | }
--------------------------------------------------------------------------------
/packages/vue/dist/tiny-vue3.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | const Fragment = Symbol("Fragment");
6 | const Text = Symbol("Text");
7 | function createVNode(type, props, children) {
8 | const vnode = {
9 | type,
10 | props,
11 | children,
12 | component: null,
13 | key: props && props.key,
14 | shapeFlag: getShapeFlag(type),
15 | el: null,
16 | };
17 | // chilren
18 | if (typeof children === "string") {
19 | vnode.shapeFlag |= 4 /* ShapeFlags.TEXT_CHILDREN */;
20 | }
21 | else if (Array.isArray(children)) {
22 | vnode.shapeFlag |= 8 /* ShapeFlags.ARRAY_CHILDREN */;
23 | }
24 | if (vnode.shapeFlag & 2 /* ShapeFlags.STATEFUL_COMPONENT */) {
25 | if (typeof children === "object") {
26 | vnode.shapeFlag |= 16 /* ShapeFlags.SLOT_CHILDREN */;
27 | }
28 | }
29 | return vnode;
30 | }
31 | function createTextVNode(text) {
32 | return createVNode(Text, {}, text);
33 | }
34 | function getShapeFlag(type) {
35 | return typeof type === "string"
36 | ? 1 /* ShapeFlags.ELEMENT */
37 | : 2 /* ShapeFlags.STATEFUL_COMPONENT */;
38 | }
39 |
40 | function h(type, props, children) {
41 | return createVNode(type, props, children);
42 | }
43 |
44 | function renderSlots(slots, name, props) {
45 | // vnode
46 | const slot = slots[name];
47 | if (slot) {
48 | console.log("slot", slot);
49 | if (typeof slot === 'function') {
50 | return createVNode(Fragment, {}, slot(props));
51 | }
52 | }
53 | }
54 |
55 | function toDisplayString(value) {
56 | return String(value);
57 | }
58 |
59 | const extend = Object.assign;
60 | const EMPTY_OBJ = {};
61 | const isObject = (value) => {
62 | return value !== null && typeof value === "object";
63 | };
64 | const isString = (value) => typeof value === "string";
65 | const hasChanged = (val, newValue) => {
66 | return !Object.is(val, newValue);
67 | };
68 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
69 | const camelize = (str) => {
70 | return str.replace(/-(\w)/g, (_, c) => {
71 | return c ? c.toUpperCase() : "";
72 | });
73 | };
74 | const capitalize = (str) => {
75 | return str.charAt(0).toUpperCase() + str.slice(1);
76 | };
77 | const toHandlerKey = (str) => {
78 | return str ? "on" + capitalize(str) : "";
79 | };
80 |
81 | let activeEffect;
82 | let shouldTrack = false;
83 | class ReactiveEffect {
84 | constructor(fn, scheduler) {
85 | this.scheduler = scheduler;
86 | this.deps = [];
87 | this.active = true;
88 | this._fn = fn;
89 | }
90 | run() {
91 | if (!this.active) {
92 | return this._fn();
93 | }
94 | // 应该收集
95 | activeEffect = this;
96 | shouldTrack = true;
97 | const r = this._fn();
98 | // 重置
99 | shouldTrack = false;
100 | return r;
101 | }
102 | stop() {
103 | if (this.active) {
104 | cleanupEffect(this);
105 | if (this.onStop) {
106 | this.onStop();
107 | }
108 | this.active = false;
109 | }
110 | }
111 | }
112 | function cleanupEffect(effect) {
113 | effect.deps.forEach((dep) => {
114 | dep.delete(effect);
115 | });
116 | // 把 effect.deps 清空
117 | effect.deps.length = 0;
118 | }
119 | const targetMap = new Map();
120 | function track(target, key) {
121 | if (!isTracking())
122 | return;
123 | // target -> key -> dep
124 | let depsMap = targetMap.get(target);
125 | if (!depsMap) {
126 | depsMap = new Map();
127 | targetMap.set(target, depsMap);
128 | }
129 | let dep = depsMap.get(key);
130 | if (!dep) {
131 | dep = new Set();
132 | depsMap.set(key, dep);
133 | }
134 | trackEffects(dep);
135 | }
136 | function trackEffects(dep) {
137 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
138 | if (dep.has(activeEffect))
139 | return;
140 | dep.add(activeEffect);
141 | activeEffect.deps.push(dep);
142 | }
143 | function isTracking() {
144 | return shouldTrack && activeEffect !== undefined;
145 | }
146 | function trigger(target, key) {
147 | const depsMap = targetMap.get(target);
148 | const dep = depsMap.get(key);
149 | triggerEffects(dep);
150 | }
151 | function triggerEffects(dep) {
152 | for (const effect of dep) {
153 | if (effect.scheduler) {
154 | effect.scheduler();
155 | }
156 | else {
157 | effect.run();
158 | }
159 | }
160 | }
161 | function effect(fn, options = {}) {
162 | const _effect = new ReactiveEffect(fn, options.scheduler);
163 | extend(_effect, options);
164 | _effect.run();
165 | const runner = _effect.run.bind(_effect);
166 | runner.effect = _effect;
167 | return runner;
168 | }
169 | function stop(runner) {
170 | runner.effect.stop();
171 | }
172 |
173 | const get = createGetter();
174 | const set = createSetter();
175 | const readonlyGet = createGetter(true);
176 | const shallowReactiveGet = createGetter(false, true);
177 | const shallowReadonlyGet = createGetter(true, true);
178 | function createGetter(isReadonly = false, shallow = false) {
179 | return function (target, key) {
180 | if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
181 | return !isReadonly;
182 | }
183 | else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
184 | return isReadonly;
185 | }
186 | const res = Reflect.get(target, key);
187 | if (!isReadonly) {
188 | // 依赖收集
189 | track(target, key);
190 | }
191 | if (shallow) {
192 | return res;
193 | }
194 | if (isObject(res)) {
195 | return isReadonly ? readonly(res) : reactive(res);
196 | }
197 | return res;
198 | };
199 | }
200 | function createSetter() {
201 | return function set(target, key, value) {
202 | const res = Reflect.set(target, key, value);
203 | // 依赖触发
204 | trigger(target, key);
205 | return res;
206 | };
207 | }
208 | const mutableHandlers = {
209 | get,
210 | set,
211 | };
212 | const shallowReactiveHandlers = extend({}, mutableHandlers, {
213 | get: shallowReactiveGet,
214 | });
215 | const readonlyHandlers = {
216 | get: readonlyGet,
217 | set(target, key) {
218 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target);
219 | return true;
220 | },
221 | };
222 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
223 | get: shallowReadonlyGet,
224 | });
225 |
226 | function reactive(raw) {
227 | return createReactiveObject(raw, mutableHandlers);
228 | }
229 | function shallowReactive(raw) {
230 | return createReactiveObject(raw, shallowReactiveHandlers);
231 | }
232 | function readonly(raw) {
233 | return createReactiveObject(raw, readonlyHandlers);
234 | }
235 | function shallowReadonly(raw) {
236 | return createReactiveObject(raw, shallowReadonlyHandlers);
237 | }
238 | function createReactiveObject(target, baseHandlerss) {
239 | if (!isObject(target)) {
240 | console.warn(`target ${target} 必须是一个对象`);
241 | return target;
242 | }
243 | return new Proxy(target, baseHandlerss);
244 | }
245 | function isReactive(value) {
246 | return !!value["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */];
247 | }
248 | function isReadonly(value) {
249 | return !!value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */];
250 | }
251 | function isProxy(value) {
252 | return isReactive(value) || isReadonly(value);
253 | }
254 |
255 | class RefImpl {
256 | constructor(value, __v_isShallow) {
257 | this.__v_isShallow = __v_isShallow;
258 | this.__v_isRef = true;
259 | this._value = __v_isShallow ? value : convert(value);
260 | this._rawValue = value;
261 | this.dep = new Set();
262 | }
263 | get value() {
264 | trackRefValue(this);
265 | return this._value;
266 | }
267 | set value(newValue) {
268 | if (hasChanged(this._rawValue, newValue)) {
269 | this._value = this.__v_isShallow ? newValue : convert(newValue);
270 | this._rawValue = newValue;
271 | triggerEffects(this.dep);
272 | }
273 | }
274 | }
275 | function createRef(rawValue, shallow) {
276 | if (isRef(rawValue)) {
277 | return rawValue;
278 | }
279 | return new RefImpl(rawValue, shallow);
280 | }
281 | function ref(value) {
282 | return createRef(value, false);
283 | }
284 | function convert(value) {
285 | return isObject(value) ? reactive(value) : value;
286 | }
287 | function trackRefValue(ref) {
288 | if (isTracking()) {
289 | trackEffects(ref.dep);
290 | }
291 | }
292 | function isRef(ref) {
293 | return !!ref.__v_isRef;
294 | }
295 | function unref(ref) {
296 | return isRef(ref) ? ref.value : ref;
297 | }
298 | function proxyRefs(objectWithRefs) {
299 | return new Proxy(objectWithRefs, {
300 | get(target, key) {
301 | return unref(Reflect.get(target, key));
302 | },
303 | set(target, key, value) {
304 | if (isRef(target[key]) && !isRef(value)) {
305 | return (target[key].value = value);
306 | }
307 | else {
308 | return Reflect.set(target, key, value);
309 | }
310 | },
311 | });
312 | }
313 |
314 | function emit(instance, event, ...args) {
315 | console.log("emit", event);
316 | // instance.props -> event
317 | const { props } = instance;
318 | // TPP
319 | // 先去写特定行为 -> 重构成通用行为
320 | // add
321 | const handlerName = toHandlerKey(camelize(event));
322 | const handler = props[handlerName];
323 | handler && handler(...args);
324 | }
325 |
326 | function initProps(instance, rawProps) {
327 | instance.props = rawProps || {};
328 | // attrs
329 | }
330 |
331 | const publicPropertiesMap = {
332 | $el: (i) => i.vnode.el,
333 | $slots: (i) => i.slots,
334 | $props: (i) => i.props,
335 | };
336 | const PublicInstanceProxyHandlers = {
337 | get({ _: instance }, key) {
338 | // setupState
339 | const { setupState, props } = instance;
340 | // if (key in setupState) {
341 | // return setupState[key];
342 | // }
343 | if (hasOwn(setupState, key)) {
344 | return setupState[key];
345 | }
346 | else if (hasOwn(props, key)) {
347 | return props[key];
348 | }
349 | const publicGetter = publicPropertiesMap[key];
350 | if (publicGetter) {
351 | return publicGetter(instance);
352 | }
353 | },
354 | };
355 |
356 | function initSlots(instance, children) {
357 | // children object
358 | // instance.slots = Array.isArray(children) ? children : [children];
359 | const { vnode } = instance;
360 | if (vnode.shapeFlag & 16 /* ShapeFlags.SLOT_CHILDREN */) {
361 | normalizeObjectSlots(children, instance.slots);
362 | }
363 | }
364 | function normalizeObjectSlots(children, slots) {
365 | for (const key in children) {
366 | const value = children[key];
367 | slots[key] = (props) => normalizeSlotValue(value(props));
368 | }
369 | }
370 | function normalizeSlotValue(value) {
371 | return Array.isArray(value) ? value : [value];
372 | }
373 |
374 | function createComponentInstance(vnode, parent) {
375 | console.log("createComponentInstance", parent);
376 | const component = {
377 | vnode,
378 | type: vnode.type,
379 | next: null,
380 | setupState: {},
381 | props: {},
382 | slots: {},
383 | provides: parent ? parent.provides : {},
384 | parent,
385 | isMounted: false,
386 | subTree: {},
387 | emit: () => { },
388 | };
389 | component.emit = emit.bind(null, component);
390 | return component;
391 | }
392 | function setupComponent(instance) {
393 | initProps(instance, instance.vnode.props);
394 | initSlots(instance, instance.vnode.children);
395 | setupStatefulComponent(instance);
396 | }
397 | function setupStatefulComponent(instance) {
398 | const Component = instance.type;
399 | // ctx
400 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
401 | const { setup } = Component;
402 | if (setup) {
403 | setCurrentInstance(instance);
404 | const setupResult = setup(shallowReadonly(instance.props), {
405 | emit: instance.emit,
406 | });
407 | setCurrentInstance(null);
408 | handleSetupResult(instance, setupResult);
409 | }
410 | }
411 | function handleSetupResult(instance, setupResult) {
412 | // function Object
413 | // TODO function 渲染函数
414 | if (typeof setupResult === "object") {
415 | instance.setupState = proxyRefs(setupResult);
416 | }
417 | finishComponentSetup(instance);
418 | }
419 | function finishComponentSetup(instance) {
420 | const Component = instance.type;
421 | if (compiler && !Component.render) {
422 | if (Component.template) {
423 | Component.render = compiler(Component.template);
424 | }
425 | }
426 | instance.render = Component.render;
427 | }
428 | let currentInstance = null;
429 | function getCurrentInstance() {
430 | return currentInstance;
431 | }
432 | function setCurrentInstance(instance) {
433 | currentInstance = instance;
434 | }
435 | let compiler;
436 | function registerRuntimeCompiler(_compiler) {
437 | compiler = _compiler;
438 | }
439 |
440 | function provide(key, value) {
441 | // 存
442 | const currentInstance = getCurrentInstance();
443 | if (currentInstance) {
444 | let { provides } = currentInstance;
445 | const parentProvides = currentInstance.parent.provides;
446 | // init
447 | if (provides === parentProvides) {
448 | provides = currentInstance.provides = Object.create(parentProvides);
449 | }
450 | provides[key] = value;
451 | }
452 | }
453 | function inject(key, defaultValue) {
454 | // 取
455 | const currentInstance = getCurrentInstance();
456 | if (currentInstance) {
457 | const parentProvides = currentInstance.parent.provides;
458 | if (key in parentProvides) {
459 | return parentProvides[key];
460 | }
461 | else if (typeof defaultValue === "function") {
462 | return defaultValue();
463 | }
464 | return defaultValue;
465 | }
466 | }
467 |
468 | function shouldUpdateComponent(prevVNode, nextVNode) {
469 | const { props: prevProps } = prevVNode;
470 | const { props: nextProps } = nextVNode;
471 | for (const key in nextProps) {
472 | if (nextProps[key] !== prevProps[key]) {
473 | return true;
474 | }
475 | }
476 | return false;
477 | }
478 |
479 | function createAppAPI(render) {
480 | return function createApp(rootComponent) {
481 | return {
482 | mount(rootContainer) {
483 | // 先 vode
484 | // 基于 vode 做处理
485 | const vnode = createVNode(rootComponent);
486 | render(vnode, rootContainer);
487 | },
488 | };
489 | };
490 | }
491 |
492 | const queue = [];
493 | const p = Promise.resolve();
494 | let isFlushPending = false;
495 | function nextTick(fn) {
496 | return fn ? p.then(fn) : p;
497 | }
498 | function queueJobs(job) {
499 | if (!queue.includes(job)) {
500 | queue.push(job);
501 | }
502 | queueFlush();
503 | }
504 | function queueFlush() {
505 | if (isFlushPending)
506 | return;
507 | isFlushPending = true;
508 | nextTick(flushJob);
509 | }
510 | function flushJob() {
511 | isFlushPending = false;
512 | let job;
513 | while ((job = queue.shift())) {
514 | job && job();
515 | }
516 | }
517 |
518 | function createRenderer(options) {
519 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
520 | function render(vnode, container) {
521 | patch(null, vnode, container, null, null);
522 | }
523 | // n1 -> old
524 | // n2 -> new
525 | function patch(n1, n2, container, parentComponent, anchor) {
526 | console.log('n1', n1);
527 | console.log("n2", n2);
528 | const { type, shapeFlag } = n2;
529 | switch (type) {
530 | case Fragment:
531 | processFragment(n1, n2, container, parentComponent, anchor);
532 | break;
533 | case Text:
534 | processText(n1, n2, container);
535 | break;
536 | default:
537 | if (shapeFlag & 1 /* ShapeFlags.ELEMENT */) {
538 | processElement(n1, n2, container, parentComponent, anchor);
539 | }
540 | else if (shapeFlag & 2 /* ShapeFlags.STATEFUL_COMPONENT */) {
541 | processComponent(n1, n2, container, parentComponent, anchor);
542 | }
543 | break;
544 | }
545 | }
546 | function processText(n1, n2, container) {
547 | const { children } = n2;
548 | const textNode = (n2.el = document.createTextNode(children));
549 | container.append(textNode);
550 | }
551 | function processFragment(n1, n2, container, parentComponent, anchor) {
552 | mountChildren(n2.children, container, parentComponent, anchor);
553 | }
554 | function processElement(n1, n2, container, parentComponent, anchor) {
555 | if (!n1) {
556 | mountElement(n1, n2, container, parentComponent, anchor);
557 | }
558 | else {
559 | patchElement(n1, n2, container, parentComponent, anchor);
560 | }
561 | }
562 | function patchElement(n1, n2, container, parentComponent, anchor) {
563 | const oldProps = n1.props || EMPTY_OBJ;
564 | const newProps = n2.props || EMPTY_OBJ;
565 | const el = (n2.el = n1.el);
566 | patchChildren(n1, n2, el, parentComponent, anchor);
567 | patchProps(el, oldProps, newProps);
568 | }
569 | function patchChildren(n1, n2, container, parentComponent, anchor) {
570 | const { shapeFlag, children: c2 } = n2;
571 | const { shapeFlag: prevShapeFlag, children: c1 } = n1;
572 | if (shapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) {
573 | if (prevShapeFlag & 8 /* ShapeFlags.ARRAY_CHILDREN */) {
574 | // 1. 把老的 children 清空
575 | unmountChildren(n1.children);
576 | }
577 | if (c1 !== c2) {
578 | hostSetElementText(container, c2);
579 | }
580 | }
581 | else {
582 | if (prevShapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) {
583 | hostSetElementText(container, "");
584 | mountChildren(c2, container, parentComponent, anchor);
585 | }
586 | else {
587 | // array diff array
588 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
589 | }
590 | }
591 | }
592 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
593 | let i = 0;
594 | const l2 = c2.length;
595 | let e1 = c1.length - 1;
596 | let e2 = l2 - 1;
597 | function isSomeVNodeType(n1, n2) {
598 | return n1.type === n2.type && n1.key === n2.key;
599 | }
600 | // 左侧
601 | while (i <= e1 && i <= e2) {
602 | const n1 = c1[i];
603 | const n2 = c2[i];
604 | if (isSomeVNodeType(n1, n2)) {
605 | patch(n1, n2, container, parentComponent, parentAnchor);
606 | }
607 | else {
608 | break;
609 | }
610 | i++;
611 | }
612 | // 右侧
613 | while (i <= e1 && i <= e2) {
614 | const n1 = c1[e1];
615 | const n2 = c2[e2];
616 | if (isSomeVNodeType(n1, n2)) {
617 | patch(n1, n2, container, parentComponent, parentAnchor);
618 | }
619 | else {
620 | break;
621 | }
622 | e1--;
623 | e2--;
624 | }
625 | // 新的比老的多 创建
626 | if (i > e1) {
627 | // 左侧右侧逻辑共用
628 | if (i <= e2) {
629 | const nextPos = e2 + 1;
630 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
631 | while (i <= e2) {
632 | patch(null, c2[i], container, parentComponent, anchor);
633 | i++;
634 | }
635 | }
636 | }
637 | else if (i > e2) {
638 | // 新的比老的少 删除 左侧右侧逻辑共用
639 | while (i <= e1) {
640 | hostRemove(c1[i].el);
641 | i++;
642 | }
643 | }
644 | else {
645 | // 中间对比
646 | let s1 = i;
647 | let s2 = i;
648 | const toBePatched = e2 - s2 + 1;
649 | let patched = 0;
650 | const keyToNewIndexMap = new Map();
651 | const newIndexToOldIndexMap = new Array(toBePatched);
652 | let moved = false;
653 | let maxNewIndexSoFar = 0;
654 | for (let i = 0; i < toBePatched; i++) {
655 | newIndexToOldIndexMap[i] = 0;
656 | }
657 | for (let i = s2; i <= e2; i++) {
658 | const nextChild = c2[i];
659 | keyToNewIndexMap.set(nextChild.key, i);
660 | }
661 | for (let i = s1; i <= e1; i++) {
662 | const prevChild = c1[i];
663 | if (patched >= toBePatched) {
664 | // patched 的数量大于等于 新的中间节点数量,直接把剩余的老的中间节点删除
665 | hostRemove(prevChild.el);
666 | continue;
667 | }
668 | let newIndex;
669 | // null undefined
670 | if (prevChild.key != null) {
671 | newIndex = keyToNewIndexMap.get(prevChild.key);
672 | }
673 | else {
674 | for (let j = s2; j <= e2; j++) {
675 | if (isSomeVNodeType(prevChild, c2[j])) {
676 | newIndex = j;
677 | break;
678 | }
679 | }
680 | }
681 | if (newIndex === undefined) {
682 | hostRemove(prevChild.el);
683 | }
684 | else {
685 | if (newIndex >= maxNewIndexSoFar) {
686 | maxNewIndexSoFar = newIndex;
687 | }
688 | else {
689 | moved = true;
690 | }
691 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
692 | patch(prevChild, c2[newIndex], container, parentComponent, null);
693 | patched++;
694 | }
695 | }
696 | const increasingNewIndexSequence = moved
697 | ? getSequence(newIndexToOldIndexMap)
698 | : [];
699 | let j = increasingNewIndexSequence.length - 1;
700 | for (let i = toBePatched - 1; i >= 0; i--) {
701 | const nextIndex = i + s2;
702 | const nextChild = c2[nextIndex];
703 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
704 | if (newIndexToOldIndexMap[i] === 0) {
705 | patch(null, nextChild, container, parentComponent, anchor);
706 | }
707 | else if (moved) {
708 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
709 | hostInsert(nextChild.el, container, anchor);
710 | }
711 | else {
712 | j--;
713 | }
714 | }
715 | }
716 | }
717 | }
718 | function unmountChildren(children) {
719 | for (let i = 0; i < children.length; i++) {
720 | const el = children[i].el;
721 | // remove
722 | hostRemove(el);
723 | }
724 | }
725 | function patchProps(el, oldProps, newProps) {
726 | if (oldProps !== newProps) {
727 | for (const key in newProps) {
728 | const prevProp = oldProps[key];
729 | const nextProp = newProps[key];
730 | if (prevProp !== nextProp) {
731 | hostPatchProp(el, key, prevProp, nextProp);
732 | }
733 | }
734 | if (oldProps !== EMPTY_OBJ) {
735 | for (const key in oldProps) {
736 | if (!(key in newProps)) {
737 | hostPatchProp(el, key, oldProps[key], null);
738 | }
739 | }
740 | }
741 | }
742 | }
743 | function mountElement(n1, n2, container, parentComponent, anchor) {
744 | //canvas
745 | // new Element()
746 | const el = (n2.el = hostCreateElement(n2.type));
747 | const { children, shapeFlag } = n2;
748 | // children
749 | if (shapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) {
750 | el.textContent = children;
751 | }
752 | else if (shapeFlag & 8 /* ShapeFlags.ARRAY_CHILDREN */) {
753 | mountChildren(n2.children, el, parentComponent, anchor);
754 | }
755 | // props
756 | const { props } = n2;
757 | for (const key in props) {
758 | const val = props[key];
759 | hostPatchProp(el, key, null, val);
760 | }
761 | // canvs
762 | // el.x = 10
763 | // container.append(el);
764 | // addChild()
765 | hostInsert(el, container, anchor);
766 | }
767 | function mountChildren(children, container, parentComponent, anchor) {
768 | children.forEach((v) => {
769 | patch(null, v, container, parentComponent, anchor);
770 | });
771 | }
772 | function processComponent(n1, n2, container, parentComponent, anchor) {
773 | if (!n1) {
774 | mountComponent(n2, container, parentComponent, anchor);
775 | }
776 | else {
777 | updateComponent(n1, n2);
778 | }
779 | }
780 | function updateComponent(n1, n2) {
781 | const instance = (n2.component = n1.component);
782 | if (shouldUpdateComponent(n1, n2)) {
783 | instance.next = n2;
784 | instance.update();
785 | }
786 | else {
787 | n2.el = n1.el;
788 | instance.vnode = n2;
789 | }
790 | }
791 | function mountComponent(initialVNode, container, parentComponent, anchor) {
792 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));
793 | setupComponent(instance);
794 | setupRenderEffect(instance, initialVNode, container, anchor);
795 | }
796 | function setupRenderEffect(instance, initialVNode, container, anchor) {
797 | instance.update = effect(() => {
798 | if (!instance.isMounted) {
799 | console.log("init");
800 | const { proxy } = instance;
801 | const subTree = (instance.subTree = instance.render.call(proxy, proxy));
802 | patch(null, subTree, container, instance, anchor);
803 | initialVNode.el = subTree.el;
804 | instance.isMounted = true;
805 | }
806 | else {
807 | console.log("update");
808 | // 需要一个 vnode
809 | const { next, vnode } = instance;
810 | if (next) {
811 | next.el = vnode.el;
812 | updateComponentPreRender(instance, next);
813 | }
814 | const { proxy } = instance;
815 | const subTree = instance.render.call(proxy, proxy);
816 | const prevSubTree = instance.subTree;
817 | instance.subTree = subTree;
818 | patch(prevSubTree, subTree, container, instance, anchor);
819 | }
820 | }, {
821 | scheduler() {
822 | console.log('update scheduler');
823 | queueJobs(instance.update);
824 | }
825 | });
826 | }
827 | return {
828 | createApp: createAppAPI(render),
829 | };
830 | }
831 | function updateComponentPreRender(instance, nextVNode) {
832 | instance.vnode = nextVNode;
833 | instance.next = null;
834 | instance.props = nextVNode.props;
835 | }
836 | // https://en.wikipedia.org/wiki/Longest_increasing_subsequence
837 | function getSequence(arr) {
838 | const p = arr.slice();
839 | const result = [0];
840 | let i, j, u, v, c;
841 | const len = arr.length;
842 | for (i = 0; i < len; i++) {
843 | const arrI = arr[i];
844 | if (arrI !== 0) {
845 | j = result[result.length - 1];
846 | if (arr[j] < arrI) {
847 | p[i] = j;
848 | result.push(i);
849 | continue;
850 | }
851 | u = 0;
852 | v = result.length - 1;
853 | while (u < v) {
854 | c = (u + v) >> 1;
855 | if (arr[result[c]] < arrI) {
856 | u = c + 1;
857 | }
858 | else {
859 | v = c;
860 | }
861 | }
862 | if (arrI < arr[result[u]]) {
863 | if (u > 0) {
864 | p[i] = result[u - 1];
865 | }
866 | result[u] = i;
867 | }
868 | }
869 | }
870 | u = result.length;
871 | v = result[u - 1];
872 | while (u-- > 0) {
873 | result[u] = v;
874 | v = p[v];
875 | }
876 | return result;
877 | }
878 |
879 | function createElement(type) {
880 | console.log("createElement ----------");
881 | return document.createElement(type);
882 | }
883 | function patchProp(el, key, prevVal, nextVal) {
884 | console.log("patchProp ----------");
885 | const isOn = (key) => /^on[A-Z]/.test(key);
886 | if (isOn(key)) {
887 | const event = key.slice(2).toLowerCase();
888 | el.addEventListener(event, nextVal);
889 | }
890 | else {
891 | if (nextVal === undefined || nextVal === null) {
892 | el.removeAttribute(key);
893 | }
894 | else {
895 | el.setAttribute(key, nextVal);
896 | }
897 | }
898 | }
899 | function insert(child, parent, anchor) {
900 | console.log("insert ----------", anchor);
901 | parent.insertBefore(child, anchor || null);
902 | }
903 | function remove(child) {
904 | console.log("remove ----------", child);
905 | const parent = child.parentNode;
906 | if (parent) {
907 | parent.removeChild(child);
908 | }
909 | }
910 | function setElementText(el, text) {
911 | el.textContent = text;
912 | }
913 | const renderer = createRenderer({
914 | createElement,
915 | patchProp,
916 | insert,
917 | remove,
918 | setElementText,
919 | });
920 | function createApp(...args) {
921 | return renderer.createApp(...args);
922 | }
923 |
924 | var runtimeDom = /*#__PURE__*/Object.freeze({
925 | __proto__: null,
926 | createApp: createApp,
927 | h: h,
928 | renderSlots: renderSlots,
929 | createTextVNode: createTextVNode,
930 | createElementVNode: createVNode,
931 | getCurrentInstance: getCurrentInstance,
932 | registerRuntimeCompiler: registerRuntimeCompiler,
933 | provide: provide,
934 | inject: inject,
935 | createRenderer: createRenderer,
936 | nextTick: nextTick,
937 | toDisplayString: toDisplayString,
938 | ref: ref,
939 | proxyRefs: proxyRefs,
940 | unref: unref,
941 | isRef: isRef,
942 | reactive: reactive,
943 | readonly: readonly,
944 | shallowReactive: shallowReactive,
945 | shallowReadonly: shallowReadonly,
946 | isReactive: isReactive,
947 | isReadonly: isReadonly,
948 | isProxy: isProxy,
949 | effect: effect,
950 | stop: stop
951 | });
952 |
953 | const TO_DISPLAY_STRING = Symbol("toDisplayString");
954 | const CREATE_ELEMENT_VNODE = Symbol('createElementVNode');
955 | const helperMapName = {
956 | [TO_DISPLAY_STRING]: "toDisplayString",
957 | [CREATE_ELEMENT_VNODE]: 'createElementVNode'
958 | };
959 |
960 | function generate(ast) {
961 | const context = createCodegenContext();
962 | const { push } = context;
963 | genFunctionPreamble(ast, context);
964 | const functionName = "render";
965 | const args = ["_ctx", "_cache"];
966 | const signature = args.join(", ");
967 | push(`function ${functionName}(${signature}){`);
968 | push(`return `);
969 | genNode(ast.codegenNode, context);
970 | push("}");
971 | return {
972 | code: context.code,
973 | };
974 | }
975 | function genFunctionPreamble(ast, context) {
976 | const { push } = context;
977 | const VueBinging = "Vue";
978 | const aliasHelper = (s) => `${helperMapName[s]}: _${helperMapName[s]}`;
979 | if (ast.helpers.length > 0) {
980 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`);
981 | }
982 | push("\n");
983 | push("return ");
984 | }
985 | function createCodegenContext() {
986 | const context = {
987 | code: "",
988 | push(source) {
989 | context.code += source;
990 | },
991 | helper(key) {
992 | return `_${helperMapName[key]}`;
993 | },
994 | };
995 | return context;
996 | }
997 | function genNode(node, context) {
998 | switch (node.type) {
999 | case 3 /* NodeTypes.TEXT */:
1000 | genText(node, context);
1001 | break;
1002 | case 0 /* NodeTypes.INTERPOLATION */:
1003 | genInterpolation(node, context);
1004 | break;
1005 | case 1 /* NodeTypes.SIMPLE_EXPRESSION */:
1006 | genExpression(node, context);
1007 | break;
1008 | case 2 /* NodeTypes.ELEMENT */:
1009 | genElement(node, context);
1010 | break;
1011 | case 5 /* NodeTypes.COMPOUND_EXPRESSION */:
1012 | genCompoundExpression(node, context);
1013 | break;
1014 | }
1015 | }
1016 | function genCompoundExpression(node, context) {
1017 | const { push } = context;
1018 | const { children } = node;
1019 | for (let i = 0; i < children.length; i++) {
1020 | const child = children[i];
1021 | if (isString(child)) {
1022 | push(child);
1023 | }
1024 | else {
1025 | genNode(child, context);
1026 | }
1027 | }
1028 | }
1029 | function genElement(node, context) {
1030 | const { push, helper } = context;
1031 | const { tag, children, props } = node;
1032 | push(`${helper(CREATE_ELEMENT_VNODE)}(`);
1033 | genNodeList(genNullable([tag, props, children]), context);
1034 | push(")");
1035 | }
1036 | function genNodeList(nodes, context) {
1037 | const { push } = context;
1038 | for (let i = 0; i < nodes.length; i++) {
1039 | const node = nodes[i];
1040 | if (isString(node)) {
1041 | push(node);
1042 | }
1043 | else {
1044 | genNode(node, context);
1045 | }
1046 | if (i < nodes.length - 1) {
1047 | push(", ");
1048 | }
1049 | }
1050 | }
1051 | function genNullable(args) {
1052 | return args.map((arg) => arg || "null");
1053 | }
1054 | function genText(node, context) {
1055 | const { push } = context;
1056 | push(`'${node.content}'`);
1057 | }
1058 | function genInterpolation(node, context) {
1059 | const { push, helper } = context;
1060 | push(`${helper(TO_DISPLAY_STRING)}(`);
1061 | genNode(node.content, context);
1062 | push(")");
1063 | }
1064 | function genExpression(node, context) {
1065 | const { push } = context;
1066 | push(`${node.content}`);
1067 | }
1068 |
1069 | function baseParse(content) {
1070 | const context = createParserContent(content);
1071 | return createRoot(parseChildren(context, []));
1072 | }
1073 | function parseChildren(context, ancestors) {
1074 | const nodes = [];
1075 | while (!isEnd(context, ancestors)) {
1076 | let node;
1077 | const s = context.source;
1078 | if (s.startsWith("{{")) {
1079 | node = parseInterpolation(context);
1080 | }
1081 | else if (s[0] === "<") {
1082 | if (/[a-z]/i.test(s[1])) {
1083 | node = parseElement(context, ancestors);
1084 | }
1085 | }
1086 | if (!node) {
1087 | node = parseText(context);
1088 | }
1089 | nodes.push(node);
1090 | }
1091 | return nodes;
1092 | }
1093 | function isEnd(context, ancestors) {
1094 | // 2. 遇到结束标签
1095 | const s = context.source;
1096 | if (s.startsWith("")) {
1097 | for (let i = ancestors.length - 1; i >= 0; --i) {
1098 | const tag = ancestors[i].tag;
1099 | if (startsWithEndTagOpen(s, tag)) {
1100 | return true;
1101 | }
1102 | }
1103 | }
1104 | // 1. source 有值
1105 | return !context.source;
1106 | }
1107 | function parseText(context) {
1108 | let endIndex = context.source.length;
1109 | let endTokens = ["<", "{{"];
1110 | for (let i = 0; i < endTokens.length; i++) {
1111 | const index = context.source.indexOf(endTokens[i]);
1112 | if (index !== -1 && endIndex > index) {
1113 | endIndex = index;
1114 | }
1115 | }
1116 | // 1. 获取 context
1117 | const content = parseTextData(context, endIndex);
1118 | console.log("---------", content);
1119 | return {
1120 | type: 3 /* NodeTypes.TEXT */,
1121 | content,
1122 | };
1123 | }
1124 | function parseTextData(context, length) {
1125 | const content = context.source.slice(0, length);
1126 | // 2. 推进
1127 | advanceBy(context, length);
1128 | return content;
1129 | }
1130 | function parseElement(context, ancestors) {
1131 | // Implement
1132 | // 1. 解析 tag
1133 | const element = parseTag(context, 0 /* TagType.Start */);
1134 | ancestors.push(element);
1135 | element.children = parseChildren(context, ancestors);
1136 | ancestors.pop();
1137 | if (startsWithEndTagOpen(context.source, element.tag)) {
1138 | parseTag(context, 1 /* TagType.End */);
1139 | }
1140 | else {
1141 | throw new Error(`缺少结束标签:${element.tag}`);
1142 | }
1143 | return element;
1144 | }
1145 | function startsWithEndTagOpen(source, tag) {
1146 | return (source.startsWith("") &&
1147 | source.slice(2, 2 + tag.length).toLowerCase() === tag);
1148 | }
1149 | function parseTag(context, type) {
1150 | const match = /^<\/?([a-z]*)/i.exec(context.source);
1151 | const tag = match[1];
1152 | // 2. 删除处理完成的代码
1153 | advanceBy(context, match[0].length);
1154 | advanceBy(context, 1);
1155 | if (type === 1 /* TagType.End */)
1156 | return;
1157 | console.log(context);
1158 | return {
1159 | type: 2 /* NodeTypes.ELEMENT */,
1160 | tag,
1161 | };
1162 | }
1163 | function parseInterpolation(context) {
1164 | // {{ message }}
1165 | const openDelimiter = "{{";
1166 | const closeDelimiter = "}}";
1167 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length);
1168 | advanceBy(context, openDelimiter.length);
1169 | const rawContentLength = closeIndex - openDelimiter.length;
1170 | const rawContent = parseTextData(context, rawContentLength);
1171 | const content = rawContent.trim();
1172 | advanceBy(context, closeDelimiter.length);
1173 | return {
1174 | type: 0 /* NodeTypes.INTERPOLATION */,
1175 | content: {
1176 | type: 1 /* NodeTypes.SIMPLE_EXPRESSION */,
1177 | content,
1178 | },
1179 | };
1180 | }
1181 | function advanceBy(context, length) {
1182 | context.source = context.source.slice(length);
1183 | }
1184 | function createRoot(children) {
1185 | return {
1186 | children,
1187 | type: 4 /* NodeTypes.ROOT */
1188 | };
1189 | }
1190 | function createParserContent(content) {
1191 | return {
1192 | source: content,
1193 | };
1194 | }
1195 |
1196 | function transform(root, options = {}) {
1197 | const context = createTransformContext(root, options);
1198 | // 1. dfs
1199 | traverseNode(root, context);
1200 | // 2. 修改 text content
1201 | // root.codegenNode
1202 | createRootCodegen(root);
1203 | root.helpers = [...context.helpers.keys()];
1204 | }
1205 | function createRootCodegen(root) {
1206 | const child = root.children[0];
1207 | if (child.type === 2 /* NodeTypes.ELEMENT */) {
1208 | root.codegenNode = child.codegenNode;
1209 | }
1210 | else {
1211 | root.codegenNode = root.children[0];
1212 | }
1213 | }
1214 | function createTransformContext(root, options) {
1215 | const context = {
1216 | root,
1217 | nodeTransforms: options.nodeTransforms || [],
1218 | helpers: new Map(),
1219 | helper(key) {
1220 | context.helpers.set(key, 1);
1221 | },
1222 | };
1223 | return context;
1224 | }
1225 | function traverseNode(node, context) {
1226 | const nodeTransforms = context.nodeTransforms;
1227 | const exitFns = [];
1228 | for (let i = 0; i < nodeTransforms.length; i++) {
1229 | const transform = nodeTransforms[i];
1230 | const onExit = transform(node, context);
1231 | if (onExit)
1232 | exitFns.push(onExit);
1233 | }
1234 | switch (node.type) {
1235 | case 0 /* NodeTypes.INTERPOLATION */:
1236 | context.helper(TO_DISPLAY_STRING);
1237 | break;
1238 | case 4 /* NodeTypes.ROOT */:
1239 | case 2 /* NodeTypes.ELEMENT */:
1240 | traverseChildren(node, context);
1241 | context.helper(CREATE_ELEMENT_VNODE);
1242 | break;
1243 | }
1244 | let i = exitFns.length;
1245 | while (i--) {
1246 | exitFns[i]();
1247 | }
1248 | }
1249 | function traverseChildren(node, context) {
1250 | const children = node.children;
1251 | for (let i = 0; i < children.length; i++) {
1252 | const node = children[i];
1253 | traverseNode(node, context);
1254 | }
1255 | }
1256 |
1257 | function createVNodeCall(context, tag, props, children) {
1258 | context.helper(CREATE_ELEMENT_VNODE);
1259 | return {
1260 | type: 2 /* NodeTypes.ELEMENT */,
1261 | tag,
1262 | props,
1263 | children,
1264 | };
1265 | }
1266 |
1267 | function transformElement(node, context) {
1268 | if (node.type === 2 /* NodeTypes.ELEMENT */) {
1269 | return () => {
1270 | // tag
1271 | const vnodeTag = `'${node.tag}'`;
1272 | // props
1273 | let vnodeProps;
1274 | // children
1275 | const children = node.children;
1276 | let vnodeChildren = children[0];
1277 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);
1278 | };
1279 | }
1280 | }
1281 |
1282 | function transformExpression(node) {
1283 | if (node.type === 0 /* NodeTypes.INTERPOLATION */) {
1284 | node.content = processExpression(node.content);
1285 | }
1286 | }
1287 | function processExpression(node) {
1288 | node.content = `_ctx.${node.content}`;
1289 | return node;
1290 | }
1291 |
1292 | function isText(node) {
1293 | return node.type === 3 /* NodeTypes.TEXT */ || node.type === 0 /* NodeTypes.INTERPOLATION */;
1294 | }
1295 |
1296 | function transformText(node) {
1297 | if (node.type === 2 /* NodeTypes.ELEMENT */) {
1298 | return () => {
1299 | const { children } = node;
1300 | for (let i = 0; i < children.length; i++) {
1301 | const child = children[i];
1302 | let currentContainer;
1303 | if (isText(child)) {
1304 | for (let j = i + 1; j < children.length; j++) {
1305 | const next = children[j];
1306 | if (isText(next)) {
1307 | if (!currentContainer) {
1308 | currentContainer = children[i] = {
1309 | type: 5 /* NodeTypes.COMPOUND_EXPRESSION */,
1310 | children: [child],
1311 | };
1312 | }
1313 | currentContainer.children.push(" + ");
1314 | currentContainer.children.push(next);
1315 | children.splice(j, 1);
1316 | j--;
1317 | }
1318 | else {
1319 | currentContainer = undefined;
1320 | break;
1321 | }
1322 | }
1323 | }
1324 | }
1325 | };
1326 | }
1327 | }
1328 |
1329 | function baseCompile(template) {
1330 | const ast = baseParse(template);
1331 | transform(ast, {
1332 | nodeTransforms: [transformExpression, transformElement, transformText],
1333 | });
1334 | return generate(ast);
1335 | }
1336 |
1337 | function compileToFunction(template) {
1338 | const { code } = baseCompile(template);
1339 | const render = new Function("Vue", code)(runtimeDom);
1340 | return render;
1341 | }
1342 | registerRuntimeCompiler(compileToFunction);
1343 |
1344 | exports.createApp = createApp;
1345 | exports.createElementVNode = createVNode;
1346 | exports.createRenderer = createRenderer;
1347 | exports.createTextVNode = createTextVNode;
1348 | exports.effect = effect;
1349 | exports.getCurrentInstance = getCurrentInstance;
1350 | exports.h = h;
1351 | exports.inject = inject;
1352 | exports.isProxy = isProxy;
1353 | exports.isReactive = isReactive;
1354 | exports.isReadonly = isReadonly;
1355 | exports.isRef = isRef;
1356 | exports.nextTick = nextTick;
1357 | exports.provide = provide;
1358 | exports.proxyRefs = proxyRefs;
1359 | exports.reactive = reactive;
1360 | exports.readonly = readonly;
1361 | exports.ref = ref;
1362 | exports.registerRuntimeCompiler = registerRuntimeCompiler;
1363 | exports.renderSlots = renderSlots;
1364 | exports.shallowReactive = shallowReactive;
1365 | exports.shallowReadonly = shallowReadonly;
1366 | exports.stop = stop;
1367 | exports.toDisplayString = toDisplayString;
1368 | exports.unref = unref;
1369 |
--------------------------------------------------------------------------------
/packages/vue/dist/tiny-vue3.esm.js:
--------------------------------------------------------------------------------
1 | const Fragment = Symbol("Fragment");
2 | const Text = Symbol("Text");
3 | function createVNode(type, props, children) {
4 | const vnode = {
5 | type,
6 | props,
7 | children,
8 | component: null,
9 | key: props && props.key,
10 | shapeFlag: getShapeFlag(type),
11 | el: null,
12 | };
13 | // chilren
14 | if (typeof children === "string") {
15 | vnode.shapeFlag |= 4 /* ShapeFlags.TEXT_CHILDREN */;
16 | }
17 | else if (Array.isArray(children)) {
18 | vnode.shapeFlag |= 8 /* ShapeFlags.ARRAY_CHILDREN */;
19 | }
20 | if (vnode.shapeFlag & 2 /* ShapeFlags.STATEFUL_COMPONENT */) {
21 | if (typeof children === "object") {
22 | vnode.shapeFlag |= 16 /* ShapeFlags.SLOT_CHILDREN */;
23 | }
24 | }
25 | return vnode;
26 | }
27 | function createTextVNode(text) {
28 | return createVNode(Text, {}, text);
29 | }
30 | function getShapeFlag(type) {
31 | return typeof type === "string"
32 | ? 1 /* ShapeFlags.ELEMENT */
33 | : 2 /* ShapeFlags.STATEFUL_COMPONENT */;
34 | }
35 |
36 | function h(type, props, children) {
37 | return createVNode(type, props, children);
38 | }
39 |
40 | function renderSlots(slots, name, props) {
41 | // vnode
42 | const slot = slots[name];
43 | if (slot) {
44 | console.log("slot", slot);
45 | if (typeof slot === 'function') {
46 | return createVNode(Fragment, {}, slot(props));
47 | }
48 | }
49 | }
50 |
51 | function toDisplayString(value) {
52 | return String(value);
53 | }
54 |
55 | const extend = Object.assign;
56 | const EMPTY_OBJ = {};
57 | const isObject = (value) => {
58 | return value !== null && typeof value === "object";
59 | };
60 | const isString = (value) => typeof value === "string";
61 | const hasChanged = (val, newValue) => {
62 | return !Object.is(val, newValue);
63 | };
64 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
65 | const camelize = (str) => {
66 | return str.replace(/-(\w)/g, (_, c) => {
67 | return c ? c.toUpperCase() : "";
68 | });
69 | };
70 | const capitalize = (str) => {
71 | return str.charAt(0).toUpperCase() + str.slice(1);
72 | };
73 | const toHandlerKey = (str) => {
74 | return str ? "on" + capitalize(str) : "";
75 | };
76 |
77 | let activeEffect;
78 | let shouldTrack = false;
79 | class ReactiveEffect {
80 | constructor(fn, scheduler) {
81 | this.scheduler = scheduler;
82 | this.deps = [];
83 | this.active = true;
84 | this._fn = fn;
85 | }
86 | run() {
87 | if (!this.active) {
88 | return this._fn();
89 | }
90 | // 应该收集
91 | activeEffect = this;
92 | shouldTrack = true;
93 | const r = this._fn();
94 | // 重置
95 | shouldTrack = false;
96 | return r;
97 | }
98 | stop() {
99 | if (this.active) {
100 | cleanupEffect(this);
101 | if (this.onStop) {
102 | this.onStop();
103 | }
104 | this.active = false;
105 | }
106 | }
107 | }
108 | function cleanupEffect(effect) {
109 | effect.deps.forEach((dep) => {
110 | dep.delete(effect);
111 | });
112 | // 把 effect.deps 清空
113 | effect.deps.length = 0;
114 | }
115 | const targetMap = new Map();
116 | function track(target, key) {
117 | if (!isTracking())
118 | return;
119 | // target -> key -> dep
120 | let depsMap = targetMap.get(target);
121 | if (!depsMap) {
122 | depsMap = new Map();
123 | targetMap.set(target, depsMap);
124 | }
125 | let dep = depsMap.get(key);
126 | if (!dep) {
127 | dep = new Set();
128 | depsMap.set(key, dep);
129 | }
130 | trackEffects(dep);
131 | }
132 | function trackEffects(dep) {
133 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
134 | if (dep.has(activeEffect))
135 | return;
136 | dep.add(activeEffect);
137 | activeEffect.deps.push(dep);
138 | }
139 | function isTracking() {
140 | return shouldTrack && activeEffect !== undefined;
141 | }
142 | function trigger(target, key) {
143 | const depsMap = targetMap.get(target);
144 | const dep = depsMap.get(key);
145 | triggerEffects(dep);
146 | }
147 | function triggerEffects(dep) {
148 | for (const effect of dep) {
149 | if (effect.scheduler) {
150 | effect.scheduler();
151 | }
152 | else {
153 | effect.run();
154 | }
155 | }
156 | }
157 | function effect(fn, options = {}) {
158 | const _effect = new ReactiveEffect(fn, options.scheduler);
159 | extend(_effect, options);
160 | _effect.run();
161 | const runner = _effect.run.bind(_effect);
162 | runner.effect = _effect;
163 | return runner;
164 | }
165 | function stop(runner) {
166 | runner.effect.stop();
167 | }
168 |
169 | const get = createGetter();
170 | const set = createSetter();
171 | const readonlyGet = createGetter(true);
172 | const shallowReactiveGet = createGetter(false, true);
173 | const shallowReadonlyGet = createGetter(true, true);
174 | function createGetter(isReadonly = false, shallow = false) {
175 | return function (target, key) {
176 | if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
177 | return !isReadonly;
178 | }
179 | else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
180 | return isReadonly;
181 | }
182 | const res = Reflect.get(target, key);
183 | if (!isReadonly) {
184 | // 依赖收集
185 | track(target, key);
186 | }
187 | if (shallow) {
188 | return res;
189 | }
190 | if (isObject(res)) {
191 | return isReadonly ? readonly(res) : reactive(res);
192 | }
193 | return res;
194 | };
195 | }
196 | function createSetter() {
197 | return function set(target, key, value) {
198 | const res = Reflect.set(target, key, value);
199 | // 依赖触发
200 | trigger(target, key);
201 | return res;
202 | };
203 | }
204 | const mutableHandlers = {
205 | get,
206 | set,
207 | };
208 | const shallowReactiveHandlers = extend({}, mutableHandlers, {
209 | get: shallowReactiveGet,
210 | });
211 | const readonlyHandlers = {
212 | get: readonlyGet,
213 | set(target, key) {
214 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target);
215 | return true;
216 | },
217 | };
218 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
219 | get: shallowReadonlyGet,
220 | });
221 |
222 | function reactive(raw) {
223 | return createReactiveObject(raw, mutableHandlers);
224 | }
225 | function shallowReactive(raw) {
226 | return createReactiveObject(raw, shallowReactiveHandlers);
227 | }
228 | function readonly(raw) {
229 | return createReactiveObject(raw, readonlyHandlers);
230 | }
231 | function shallowReadonly(raw) {
232 | return createReactiveObject(raw, shallowReadonlyHandlers);
233 | }
234 | function createReactiveObject(target, baseHandlerss) {
235 | if (!isObject(target)) {
236 | console.warn(`target ${target} 必须是一个对象`);
237 | return target;
238 | }
239 | return new Proxy(target, baseHandlerss);
240 | }
241 | function isReactive(value) {
242 | return !!value["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */];
243 | }
244 | function isReadonly(value) {
245 | return !!value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */];
246 | }
247 | function isProxy(value) {
248 | return isReactive(value) || isReadonly(value);
249 | }
250 |
251 | class RefImpl {
252 | constructor(value, __v_isShallow) {
253 | this.__v_isShallow = __v_isShallow;
254 | this.__v_isRef = true;
255 | this._value = __v_isShallow ? value : convert(value);
256 | this._rawValue = value;
257 | this.dep = new Set();
258 | }
259 | get value() {
260 | trackRefValue(this);
261 | return this._value;
262 | }
263 | set value(newValue) {
264 | if (hasChanged(this._rawValue, newValue)) {
265 | this._value = this.__v_isShallow ? newValue : convert(newValue);
266 | this._rawValue = newValue;
267 | triggerEffects(this.dep);
268 | }
269 | }
270 | }
271 | function createRef(rawValue, shallow) {
272 | if (isRef(rawValue)) {
273 | return rawValue;
274 | }
275 | return new RefImpl(rawValue, shallow);
276 | }
277 | function ref(value) {
278 | return createRef(value, false);
279 | }
280 | function convert(value) {
281 | return isObject(value) ? reactive(value) : value;
282 | }
283 | function trackRefValue(ref) {
284 | if (isTracking()) {
285 | trackEffects(ref.dep);
286 | }
287 | }
288 | function isRef(ref) {
289 | return !!ref.__v_isRef;
290 | }
291 | function unref(ref) {
292 | return isRef(ref) ? ref.value : ref;
293 | }
294 | function proxyRefs(objectWithRefs) {
295 | return new Proxy(objectWithRefs, {
296 | get(target, key) {
297 | return unref(Reflect.get(target, key));
298 | },
299 | set(target, key, value) {
300 | if (isRef(target[key]) && !isRef(value)) {
301 | return (target[key].value = value);
302 | }
303 | else {
304 | return Reflect.set(target, key, value);
305 | }
306 | },
307 | });
308 | }
309 |
310 | function emit(instance, event, ...args) {
311 | console.log("emit", event);
312 | // instance.props -> event
313 | const { props } = instance;
314 | // TPP
315 | // 先去写特定行为 -> 重构成通用行为
316 | // add
317 | const handlerName = toHandlerKey(camelize(event));
318 | const handler = props[handlerName];
319 | handler && handler(...args);
320 | }
321 |
322 | function initProps(instance, rawProps) {
323 | instance.props = rawProps || {};
324 | // attrs
325 | }
326 |
327 | const publicPropertiesMap = {
328 | $el: (i) => i.vnode.el,
329 | $slots: (i) => i.slots,
330 | $props: (i) => i.props,
331 | };
332 | const PublicInstanceProxyHandlers = {
333 | get({ _: instance }, key) {
334 | // setupState
335 | const { setupState, props } = instance;
336 | // if (key in setupState) {
337 | // return setupState[key];
338 | // }
339 | if (hasOwn(setupState, key)) {
340 | return setupState[key];
341 | }
342 | else if (hasOwn(props, key)) {
343 | return props[key];
344 | }
345 | const publicGetter = publicPropertiesMap[key];
346 | if (publicGetter) {
347 | return publicGetter(instance);
348 | }
349 | },
350 | };
351 |
352 | function initSlots(instance, children) {
353 | // children object
354 | // instance.slots = Array.isArray(children) ? children : [children];
355 | const { vnode } = instance;
356 | if (vnode.shapeFlag & 16 /* ShapeFlags.SLOT_CHILDREN */) {
357 | normalizeObjectSlots(children, instance.slots);
358 | }
359 | }
360 | function normalizeObjectSlots(children, slots) {
361 | for (const key in children) {
362 | const value = children[key];
363 | slots[key] = (props) => normalizeSlotValue(value(props));
364 | }
365 | }
366 | function normalizeSlotValue(value) {
367 | return Array.isArray(value) ? value : [value];
368 | }
369 |
370 | function createComponentInstance(vnode, parent) {
371 | console.log("createComponentInstance", parent);
372 | const component = {
373 | vnode,
374 | type: vnode.type,
375 | next: null,
376 | setupState: {},
377 | props: {},
378 | slots: {},
379 | provides: parent ? parent.provides : {},
380 | parent,
381 | isMounted: false,
382 | subTree: {},
383 | emit: () => { },
384 | };
385 | component.emit = emit.bind(null, component);
386 | return component;
387 | }
388 | function setupComponent(instance) {
389 | initProps(instance, instance.vnode.props);
390 | initSlots(instance, instance.vnode.children);
391 | setupStatefulComponent(instance);
392 | }
393 | function setupStatefulComponent(instance) {
394 | const Component = instance.type;
395 | // ctx
396 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
397 | const { setup } = Component;
398 | if (setup) {
399 | setCurrentInstance(instance);
400 | const setupResult = setup(shallowReadonly(instance.props), {
401 | emit: instance.emit,
402 | });
403 | setCurrentInstance(null);
404 | handleSetupResult(instance, setupResult);
405 | }
406 | }
407 | function handleSetupResult(instance, setupResult) {
408 | // function Object
409 | // TODO function 渲染函数
410 | if (typeof setupResult === "object") {
411 | instance.setupState = proxyRefs(setupResult);
412 | }
413 | finishComponentSetup(instance);
414 | }
415 | function finishComponentSetup(instance) {
416 | const Component = instance.type;
417 | if (compiler && !Component.render) {
418 | if (Component.template) {
419 | Component.render = compiler(Component.template);
420 | }
421 | }
422 | instance.render = Component.render;
423 | }
424 | let currentInstance = null;
425 | function getCurrentInstance() {
426 | return currentInstance;
427 | }
428 | function setCurrentInstance(instance) {
429 | currentInstance = instance;
430 | }
431 | let compiler;
432 | function registerRuntimeCompiler(_compiler) {
433 | compiler = _compiler;
434 | }
435 |
436 | function provide(key, value) {
437 | // 存
438 | const currentInstance = getCurrentInstance();
439 | if (currentInstance) {
440 | let { provides } = currentInstance;
441 | const parentProvides = currentInstance.parent.provides;
442 | // init
443 | if (provides === parentProvides) {
444 | provides = currentInstance.provides = Object.create(parentProvides);
445 | }
446 | provides[key] = value;
447 | }
448 | }
449 | function inject(key, defaultValue) {
450 | // 取
451 | const currentInstance = getCurrentInstance();
452 | if (currentInstance) {
453 | const parentProvides = currentInstance.parent.provides;
454 | if (key in parentProvides) {
455 | return parentProvides[key];
456 | }
457 | else if (typeof defaultValue === "function") {
458 | return defaultValue();
459 | }
460 | return defaultValue;
461 | }
462 | }
463 |
464 | function shouldUpdateComponent(prevVNode, nextVNode) {
465 | const { props: prevProps } = prevVNode;
466 | const { props: nextProps } = nextVNode;
467 | for (const key in nextProps) {
468 | if (nextProps[key] !== prevProps[key]) {
469 | return true;
470 | }
471 | }
472 | return false;
473 | }
474 |
475 | function createAppAPI(render) {
476 | return function createApp(rootComponent) {
477 | return {
478 | mount(rootContainer) {
479 | // 先 vode
480 | // 基于 vode 做处理
481 | const vnode = createVNode(rootComponent);
482 | render(vnode, rootContainer);
483 | },
484 | };
485 | };
486 | }
487 |
488 | const queue = [];
489 | const p = Promise.resolve();
490 | let isFlushPending = false;
491 | function nextTick(fn) {
492 | return fn ? p.then(fn) : p;
493 | }
494 | function queueJobs(job) {
495 | if (!queue.includes(job)) {
496 | queue.push(job);
497 | }
498 | queueFlush();
499 | }
500 | function queueFlush() {
501 | if (isFlushPending)
502 | return;
503 | isFlushPending = true;
504 | nextTick(flushJob);
505 | }
506 | function flushJob() {
507 | isFlushPending = false;
508 | let job;
509 | while ((job = queue.shift())) {
510 | job && job();
511 | }
512 | }
513 |
514 | function createRenderer(options) {
515 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
516 | function render(vnode, container) {
517 | patch(null, vnode, container, null, null);
518 | }
519 | // n1 -> old
520 | // n2 -> new
521 | function patch(n1, n2, container, parentComponent, anchor) {
522 | console.log('n1', n1);
523 | console.log("n2", n2);
524 | const { type, shapeFlag } = n2;
525 | switch (type) {
526 | case Fragment:
527 | processFragment(n1, n2, container, parentComponent, anchor);
528 | break;
529 | case Text:
530 | processText(n1, n2, container);
531 | break;
532 | default:
533 | if (shapeFlag & 1 /* ShapeFlags.ELEMENT */) {
534 | processElement(n1, n2, container, parentComponent, anchor);
535 | }
536 | else if (shapeFlag & 2 /* ShapeFlags.STATEFUL_COMPONENT */) {
537 | processComponent(n1, n2, container, parentComponent, anchor);
538 | }
539 | break;
540 | }
541 | }
542 | function processText(n1, n2, container) {
543 | const { children } = n2;
544 | const textNode = (n2.el = document.createTextNode(children));
545 | container.append(textNode);
546 | }
547 | function processFragment(n1, n2, container, parentComponent, anchor) {
548 | mountChildren(n2.children, container, parentComponent, anchor);
549 | }
550 | function processElement(n1, n2, container, parentComponent, anchor) {
551 | if (!n1) {
552 | mountElement(n1, n2, container, parentComponent, anchor);
553 | }
554 | else {
555 | patchElement(n1, n2, container, parentComponent, anchor);
556 | }
557 | }
558 | function patchElement(n1, n2, container, parentComponent, anchor) {
559 | const oldProps = n1.props || EMPTY_OBJ;
560 | const newProps = n2.props || EMPTY_OBJ;
561 | const el = (n2.el = n1.el);
562 | patchChildren(n1, n2, el, parentComponent, anchor);
563 | patchProps(el, oldProps, newProps);
564 | }
565 | function patchChildren(n1, n2, container, parentComponent, anchor) {
566 | const { shapeFlag, children: c2 } = n2;
567 | const { shapeFlag: prevShapeFlag, children: c1 } = n1;
568 | if (shapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) {
569 | if (prevShapeFlag & 8 /* ShapeFlags.ARRAY_CHILDREN */) {
570 | // 1. 把老的 children 清空
571 | unmountChildren(n1.children);
572 | }
573 | if (c1 !== c2) {
574 | hostSetElementText(container, c2);
575 | }
576 | }
577 | else {
578 | if (prevShapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) {
579 | hostSetElementText(container, "");
580 | mountChildren(c2, container, parentComponent, anchor);
581 | }
582 | else {
583 | // array diff array
584 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
585 | }
586 | }
587 | }
588 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
589 | let i = 0;
590 | const l2 = c2.length;
591 | let e1 = c1.length - 1;
592 | let e2 = l2 - 1;
593 | function isSomeVNodeType(n1, n2) {
594 | return n1.type === n2.type && n1.key === n2.key;
595 | }
596 | // 左侧
597 | while (i <= e1 && i <= e2) {
598 | const n1 = c1[i];
599 | const n2 = c2[i];
600 | if (isSomeVNodeType(n1, n2)) {
601 | patch(n1, n2, container, parentComponent, parentAnchor);
602 | }
603 | else {
604 | break;
605 | }
606 | i++;
607 | }
608 | // 右侧
609 | while (i <= e1 && i <= e2) {
610 | const n1 = c1[e1];
611 | const n2 = c2[e2];
612 | if (isSomeVNodeType(n1, n2)) {
613 | patch(n1, n2, container, parentComponent, parentAnchor);
614 | }
615 | else {
616 | break;
617 | }
618 | e1--;
619 | e2--;
620 | }
621 | // 新的比老的多 创建
622 | if (i > e1) {
623 | // 左侧右侧逻辑共用
624 | if (i <= e2) {
625 | const nextPos = e2 + 1;
626 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
627 | while (i <= e2) {
628 | patch(null, c2[i], container, parentComponent, anchor);
629 | i++;
630 | }
631 | }
632 | }
633 | else if (i > e2) {
634 | // 新的比老的少 删除 左侧右侧逻辑共用
635 | while (i <= e1) {
636 | hostRemove(c1[i].el);
637 | i++;
638 | }
639 | }
640 | else {
641 | // 中间对比
642 | let s1 = i;
643 | let s2 = i;
644 | const toBePatched = e2 - s2 + 1;
645 | let patched = 0;
646 | const keyToNewIndexMap = new Map();
647 | const newIndexToOldIndexMap = new Array(toBePatched);
648 | let moved = false;
649 | let maxNewIndexSoFar = 0;
650 | for (let i = 0; i < toBePatched; i++) {
651 | newIndexToOldIndexMap[i] = 0;
652 | }
653 | for (let i = s2; i <= e2; i++) {
654 | const nextChild = c2[i];
655 | keyToNewIndexMap.set(nextChild.key, i);
656 | }
657 | for (let i = s1; i <= e1; i++) {
658 | const prevChild = c1[i];
659 | if (patched >= toBePatched) {
660 | // patched 的数量大于等于 新的中间节点数量,直接把剩余的老的中间节点删除
661 | hostRemove(prevChild.el);
662 | continue;
663 | }
664 | let newIndex;
665 | // null undefined
666 | if (prevChild.key != null) {
667 | newIndex = keyToNewIndexMap.get(prevChild.key);
668 | }
669 | else {
670 | for (let j = s2; j <= e2; j++) {
671 | if (isSomeVNodeType(prevChild, c2[j])) {
672 | newIndex = j;
673 | break;
674 | }
675 | }
676 | }
677 | if (newIndex === undefined) {
678 | hostRemove(prevChild.el);
679 | }
680 | else {
681 | if (newIndex >= maxNewIndexSoFar) {
682 | maxNewIndexSoFar = newIndex;
683 | }
684 | else {
685 | moved = true;
686 | }
687 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
688 | patch(prevChild, c2[newIndex], container, parentComponent, null);
689 | patched++;
690 | }
691 | }
692 | const increasingNewIndexSequence = moved
693 | ? getSequence(newIndexToOldIndexMap)
694 | : [];
695 | let j = increasingNewIndexSequence.length - 1;
696 | for (let i = toBePatched - 1; i >= 0; i--) {
697 | const nextIndex = i + s2;
698 | const nextChild = c2[nextIndex];
699 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
700 | if (newIndexToOldIndexMap[i] === 0) {
701 | patch(null, nextChild, container, parentComponent, anchor);
702 | }
703 | else if (moved) {
704 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
705 | hostInsert(nextChild.el, container, anchor);
706 | }
707 | else {
708 | j--;
709 | }
710 | }
711 | }
712 | }
713 | }
714 | function unmountChildren(children) {
715 | for (let i = 0; i < children.length; i++) {
716 | const el = children[i].el;
717 | // remove
718 | hostRemove(el);
719 | }
720 | }
721 | function patchProps(el, oldProps, newProps) {
722 | if (oldProps !== newProps) {
723 | for (const key in newProps) {
724 | const prevProp = oldProps[key];
725 | const nextProp = newProps[key];
726 | if (prevProp !== nextProp) {
727 | hostPatchProp(el, key, prevProp, nextProp);
728 | }
729 | }
730 | if (oldProps !== EMPTY_OBJ) {
731 | for (const key in oldProps) {
732 | if (!(key in newProps)) {
733 | hostPatchProp(el, key, oldProps[key], null);
734 | }
735 | }
736 | }
737 | }
738 | }
739 | function mountElement(n1, n2, container, parentComponent, anchor) {
740 | //canvas
741 | // new Element()
742 | const el = (n2.el = hostCreateElement(n2.type));
743 | const { children, shapeFlag } = n2;
744 | // children
745 | if (shapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) {
746 | el.textContent = children;
747 | }
748 | else if (shapeFlag & 8 /* ShapeFlags.ARRAY_CHILDREN */) {
749 | mountChildren(n2.children, el, parentComponent, anchor);
750 | }
751 | // props
752 | const { props } = n2;
753 | for (const key in props) {
754 | const val = props[key];
755 | hostPatchProp(el, key, null, val);
756 | }
757 | // canvs
758 | // el.x = 10
759 | // container.append(el);
760 | // addChild()
761 | hostInsert(el, container, anchor);
762 | }
763 | function mountChildren(children, container, parentComponent, anchor) {
764 | children.forEach((v) => {
765 | patch(null, v, container, parentComponent, anchor);
766 | });
767 | }
768 | function processComponent(n1, n2, container, parentComponent, anchor) {
769 | if (!n1) {
770 | mountComponent(n2, container, parentComponent, anchor);
771 | }
772 | else {
773 | updateComponent(n1, n2);
774 | }
775 | }
776 | function updateComponent(n1, n2) {
777 | const instance = (n2.component = n1.component);
778 | if (shouldUpdateComponent(n1, n2)) {
779 | instance.next = n2;
780 | instance.update();
781 | }
782 | else {
783 | n2.el = n1.el;
784 | instance.vnode = n2;
785 | }
786 | }
787 | function mountComponent(initialVNode, container, parentComponent, anchor) {
788 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));
789 | setupComponent(instance);
790 | setupRenderEffect(instance, initialVNode, container, anchor);
791 | }
792 | function setupRenderEffect(instance, initialVNode, container, anchor) {
793 | instance.update = effect(() => {
794 | if (!instance.isMounted) {
795 | console.log("init");
796 | const { proxy } = instance;
797 | const subTree = (instance.subTree = instance.render.call(proxy, proxy));
798 | patch(null, subTree, container, instance, anchor);
799 | initialVNode.el = subTree.el;
800 | instance.isMounted = true;
801 | }
802 | else {
803 | console.log("update");
804 | // 需要一个 vnode
805 | const { next, vnode } = instance;
806 | if (next) {
807 | next.el = vnode.el;
808 | updateComponentPreRender(instance, next);
809 | }
810 | const { proxy } = instance;
811 | const subTree = instance.render.call(proxy, proxy);
812 | const prevSubTree = instance.subTree;
813 | instance.subTree = subTree;
814 | patch(prevSubTree, subTree, container, instance, anchor);
815 | }
816 | }, {
817 | scheduler() {
818 | console.log('update scheduler');
819 | queueJobs(instance.update);
820 | }
821 | });
822 | }
823 | return {
824 | createApp: createAppAPI(render),
825 | };
826 | }
827 | function updateComponentPreRender(instance, nextVNode) {
828 | instance.vnode = nextVNode;
829 | instance.next = null;
830 | instance.props = nextVNode.props;
831 | }
832 | // https://en.wikipedia.org/wiki/Longest_increasing_subsequence
833 | function getSequence(arr) {
834 | const p = arr.slice();
835 | const result = [0];
836 | let i, j, u, v, c;
837 | const len = arr.length;
838 | for (i = 0; i < len; i++) {
839 | const arrI = arr[i];
840 | if (arrI !== 0) {
841 | j = result[result.length - 1];
842 | if (arr[j] < arrI) {
843 | p[i] = j;
844 | result.push(i);
845 | continue;
846 | }
847 | u = 0;
848 | v = result.length - 1;
849 | while (u < v) {
850 | c = (u + v) >> 1;
851 | if (arr[result[c]] < arrI) {
852 | u = c + 1;
853 | }
854 | else {
855 | v = c;
856 | }
857 | }
858 | if (arrI < arr[result[u]]) {
859 | if (u > 0) {
860 | p[i] = result[u - 1];
861 | }
862 | result[u] = i;
863 | }
864 | }
865 | }
866 | u = result.length;
867 | v = result[u - 1];
868 | while (u-- > 0) {
869 | result[u] = v;
870 | v = p[v];
871 | }
872 | return result;
873 | }
874 |
875 | function createElement(type) {
876 | console.log("createElement ----------");
877 | return document.createElement(type);
878 | }
879 | function patchProp(el, key, prevVal, nextVal) {
880 | console.log("patchProp ----------");
881 | const isOn = (key) => /^on[A-Z]/.test(key);
882 | if (isOn(key)) {
883 | const event = key.slice(2).toLowerCase();
884 | el.addEventListener(event, nextVal);
885 | }
886 | else {
887 | if (nextVal === undefined || nextVal === null) {
888 | el.removeAttribute(key);
889 | }
890 | else {
891 | el.setAttribute(key, nextVal);
892 | }
893 | }
894 | }
895 | function insert(child, parent, anchor) {
896 | console.log("insert ----------", anchor);
897 | parent.insertBefore(child, anchor || null);
898 | }
899 | function remove(child) {
900 | console.log("remove ----------", child);
901 | const parent = child.parentNode;
902 | if (parent) {
903 | parent.removeChild(child);
904 | }
905 | }
906 | function setElementText(el, text) {
907 | el.textContent = text;
908 | }
909 | const renderer = createRenderer({
910 | createElement,
911 | patchProp,
912 | insert,
913 | remove,
914 | setElementText,
915 | });
916 | function createApp(...args) {
917 | return renderer.createApp(...args);
918 | }
919 |
920 | var runtimeDom = /*#__PURE__*/Object.freeze({
921 | __proto__: null,
922 | createApp: createApp,
923 | h: h,
924 | renderSlots: renderSlots,
925 | createTextVNode: createTextVNode,
926 | createElementVNode: createVNode,
927 | getCurrentInstance: getCurrentInstance,
928 | registerRuntimeCompiler: registerRuntimeCompiler,
929 | provide: provide,
930 | inject: inject,
931 | createRenderer: createRenderer,
932 | nextTick: nextTick,
933 | toDisplayString: toDisplayString,
934 | ref: ref,
935 | proxyRefs: proxyRefs,
936 | unref: unref,
937 | isRef: isRef,
938 | reactive: reactive,
939 | readonly: readonly,
940 | shallowReactive: shallowReactive,
941 | shallowReadonly: shallowReadonly,
942 | isReactive: isReactive,
943 | isReadonly: isReadonly,
944 | isProxy: isProxy,
945 | effect: effect,
946 | stop: stop
947 | });
948 |
949 | const TO_DISPLAY_STRING = Symbol("toDisplayString");
950 | const CREATE_ELEMENT_VNODE = Symbol('createElementVNode');
951 | const helperMapName = {
952 | [TO_DISPLAY_STRING]: "toDisplayString",
953 | [CREATE_ELEMENT_VNODE]: 'createElementVNode'
954 | };
955 |
956 | function generate(ast) {
957 | const context = createCodegenContext();
958 | const { push } = context;
959 | genFunctionPreamble(ast, context);
960 | const functionName = "render";
961 | const args = ["_ctx", "_cache"];
962 | const signature = args.join(", ");
963 | push(`function ${functionName}(${signature}){`);
964 | push(`return `);
965 | genNode(ast.codegenNode, context);
966 | push("}");
967 | return {
968 | code: context.code,
969 | };
970 | }
971 | function genFunctionPreamble(ast, context) {
972 | const { push } = context;
973 | const VueBinging = "Vue";
974 | const aliasHelper = (s) => `${helperMapName[s]}: _${helperMapName[s]}`;
975 | if (ast.helpers.length > 0) {
976 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`);
977 | }
978 | push("\n");
979 | push("return ");
980 | }
981 | function createCodegenContext() {
982 | const context = {
983 | code: "",
984 | push(source) {
985 | context.code += source;
986 | },
987 | helper(key) {
988 | return `_${helperMapName[key]}`;
989 | },
990 | };
991 | return context;
992 | }
993 | function genNode(node, context) {
994 | switch (node.type) {
995 | case 3 /* NodeTypes.TEXT */:
996 | genText(node, context);
997 | break;
998 | case 0 /* NodeTypes.INTERPOLATION */:
999 | genInterpolation(node, context);
1000 | break;
1001 | case 1 /* NodeTypes.SIMPLE_EXPRESSION */:
1002 | genExpression(node, context);
1003 | break;
1004 | case 2 /* NodeTypes.ELEMENT */:
1005 | genElement(node, context);
1006 | break;
1007 | case 5 /* NodeTypes.COMPOUND_EXPRESSION */:
1008 | genCompoundExpression(node, context);
1009 | break;
1010 | }
1011 | }
1012 | function genCompoundExpression(node, context) {
1013 | const { push } = context;
1014 | const { children } = node;
1015 | for (let i = 0; i < children.length; i++) {
1016 | const child = children[i];
1017 | if (isString(child)) {
1018 | push(child);
1019 | }
1020 | else {
1021 | genNode(child, context);
1022 | }
1023 | }
1024 | }
1025 | function genElement(node, context) {
1026 | const { push, helper } = context;
1027 | const { tag, children, props } = node;
1028 | push(`${helper(CREATE_ELEMENT_VNODE)}(`);
1029 | genNodeList(genNullable([tag, props, children]), context);
1030 | push(")");
1031 | }
1032 | function genNodeList(nodes, context) {
1033 | const { push } = context;
1034 | for (let i = 0; i < nodes.length; i++) {
1035 | const node = nodes[i];
1036 | if (isString(node)) {
1037 | push(node);
1038 | }
1039 | else {
1040 | genNode(node, context);
1041 | }
1042 | if (i < nodes.length - 1) {
1043 | push(", ");
1044 | }
1045 | }
1046 | }
1047 | function genNullable(args) {
1048 | return args.map((arg) => arg || "null");
1049 | }
1050 | function genText(node, context) {
1051 | const { push } = context;
1052 | push(`'${node.content}'`);
1053 | }
1054 | function genInterpolation(node, context) {
1055 | const { push, helper } = context;
1056 | push(`${helper(TO_DISPLAY_STRING)}(`);
1057 | genNode(node.content, context);
1058 | push(")");
1059 | }
1060 | function genExpression(node, context) {
1061 | const { push } = context;
1062 | push(`${node.content}`);
1063 | }
1064 |
1065 | function baseParse(content) {
1066 | const context = createParserContent(content);
1067 | return createRoot(parseChildren(context, []));
1068 | }
1069 | function parseChildren(context, ancestors) {
1070 | const nodes = [];
1071 | while (!isEnd(context, ancestors)) {
1072 | let node;
1073 | const s = context.source;
1074 | if (s.startsWith("{{")) {
1075 | node = parseInterpolation(context);
1076 | }
1077 | else if (s[0] === "<") {
1078 | if (/[a-z]/i.test(s[1])) {
1079 | node = parseElement(context, ancestors);
1080 | }
1081 | }
1082 | if (!node) {
1083 | node = parseText(context);
1084 | }
1085 | nodes.push(node);
1086 | }
1087 | return nodes;
1088 | }
1089 | function isEnd(context, ancestors) {
1090 | // 2. 遇到结束标签
1091 | const s = context.source;
1092 | if (s.startsWith("")) {
1093 | for (let i = ancestors.length - 1; i >= 0; --i) {
1094 | const tag = ancestors[i].tag;
1095 | if (startsWithEndTagOpen(s, tag)) {
1096 | return true;
1097 | }
1098 | }
1099 | }
1100 | // 1. source 有值
1101 | return !context.source;
1102 | }
1103 | function parseText(context) {
1104 | let endIndex = context.source.length;
1105 | let endTokens = ["<", "{{"];
1106 | for (let i = 0; i < endTokens.length; i++) {
1107 | const index = context.source.indexOf(endTokens[i]);
1108 | if (index !== -1 && endIndex > index) {
1109 | endIndex = index;
1110 | }
1111 | }
1112 | // 1. 获取 context
1113 | const content = parseTextData(context, endIndex);
1114 | console.log("---------", content);
1115 | return {
1116 | type: 3 /* NodeTypes.TEXT */,
1117 | content,
1118 | };
1119 | }
1120 | function parseTextData(context, length) {
1121 | const content = context.source.slice(0, length);
1122 | // 2. 推进
1123 | advanceBy(context, length);
1124 | return content;
1125 | }
1126 | function parseElement(context, ancestors) {
1127 | // Implement
1128 | // 1. 解析 tag
1129 | const element = parseTag(context, 0 /* TagType.Start */);
1130 | ancestors.push(element);
1131 | element.children = parseChildren(context, ancestors);
1132 | ancestors.pop();
1133 | if (startsWithEndTagOpen(context.source, element.tag)) {
1134 | parseTag(context, 1 /* TagType.End */);
1135 | }
1136 | else {
1137 | throw new Error(`缺少结束标签:${element.tag}`);
1138 | }
1139 | return element;
1140 | }
1141 | function startsWithEndTagOpen(source, tag) {
1142 | return (source.startsWith("") &&
1143 | source.slice(2, 2 + tag.length).toLowerCase() === tag);
1144 | }
1145 | function parseTag(context, type) {
1146 | const match = /^<\/?([a-z]*)/i.exec(context.source);
1147 | const tag = match[1];
1148 | // 2. 删除处理完成的代码
1149 | advanceBy(context, match[0].length);
1150 | advanceBy(context, 1);
1151 | if (type === 1 /* TagType.End */)
1152 | return;
1153 | console.log(context);
1154 | return {
1155 | type: 2 /* NodeTypes.ELEMENT */,
1156 | tag,
1157 | };
1158 | }
1159 | function parseInterpolation(context) {
1160 | // {{ message }}
1161 | const openDelimiter = "{{";
1162 | const closeDelimiter = "}}";
1163 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length);
1164 | advanceBy(context, openDelimiter.length);
1165 | const rawContentLength = closeIndex - openDelimiter.length;
1166 | const rawContent = parseTextData(context, rawContentLength);
1167 | const content = rawContent.trim();
1168 | advanceBy(context, closeDelimiter.length);
1169 | return {
1170 | type: 0 /* NodeTypes.INTERPOLATION */,
1171 | content: {
1172 | type: 1 /* NodeTypes.SIMPLE_EXPRESSION */,
1173 | content,
1174 | },
1175 | };
1176 | }
1177 | function advanceBy(context, length) {
1178 | context.source = context.source.slice(length);
1179 | }
1180 | function createRoot(children) {
1181 | return {
1182 | children,
1183 | type: 4 /* NodeTypes.ROOT */
1184 | };
1185 | }
1186 | function createParserContent(content) {
1187 | return {
1188 | source: content,
1189 | };
1190 | }
1191 |
1192 | function transform(root, options = {}) {
1193 | const context = createTransformContext(root, options);
1194 | // 1. dfs
1195 | traverseNode(root, context);
1196 | // 2. 修改 text content
1197 | // root.codegenNode
1198 | createRootCodegen(root);
1199 | root.helpers = [...context.helpers.keys()];
1200 | }
1201 | function createRootCodegen(root) {
1202 | const child = root.children[0];
1203 | if (child.type === 2 /* NodeTypes.ELEMENT */) {
1204 | root.codegenNode = child.codegenNode;
1205 | }
1206 | else {
1207 | root.codegenNode = root.children[0];
1208 | }
1209 | }
1210 | function createTransformContext(root, options) {
1211 | const context = {
1212 | root,
1213 | nodeTransforms: options.nodeTransforms || [],
1214 | helpers: new Map(),
1215 | helper(key) {
1216 | context.helpers.set(key, 1);
1217 | },
1218 | };
1219 | return context;
1220 | }
1221 | function traverseNode(node, context) {
1222 | const nodeTransforms = context.nodeTransforms;
1223 | const exitFns = [];
1224 | for (let i = 0; i < nodeTransforms.length; i++) {
1225 | const transform = nodeTransforms[i];
1226 | const onExit = transform(node, context);
1227 | if (onExit)
1228 | exitFns.push(onExit);
1229 | }
1230 | switch (node.type) {
1231 | case 0 /* NodeTypes.INTERPOLATION */:
1232 | context.helper(TO_DISPLAY_STRING);
1233 | break;
1234 | case 4 /* NodeTypes.ROOT */:
1235 | case 2 /* NodeTypes.ELEMENT */:
1236 | traverseChildren(node, context);
1237 | context.helper(CREATE_ELEMENT_VNODE);
1238 | break;
1239 | }
1240 | let i = exitFns.length;
1241 | while (i--) {
1242 | exitFns[i]();
1243 | }
1244 | }
1245 | function traverseChildren(node, context) {
1246 | const children = node.children;
1247 | for (let i = 0; i < children.length; i++) {
1248 | const node = children[i];
1249 | traverseNode(node, context);
1250 | }
1251 | }
1252 |
1253 | function createVNodeCall(context, tag, props, children) {
1254 | context.helper(CREATE_ELEMENT_VNODE);
1255 | return {
1256 | type: 2 /* NodeTypes.ELEMENT */,
1257 | tag,
1258 | props,
1259 | children,
1260 | };
1261 | }
1262 |
1263 | function transformElement(node, context) {
1264 | if (node.type === 2 /* NodeTypes.ELEMENT */) {
1265 | return () => {
1266 | // tag
1267 | const vnodeTag = `'${node.tag}'`;
1268 | // props
1269 | let vnodeProps;
1270 | // children
1271 | const children = node.children;
1272 | let vnodeChildren = children[0];
1273 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);
1274 | };
1275 | }
1276 | }
1277 |
1278 | function transformExpression(node) {
1279 | if (node.type === 0 /* NodeTypes.INTERPOLATION */) {
1280 | node.content = processExpression(node.content);
1281 | }
1282 | }
1283 | function processExpression(node) {
1284 | node.content = `_ctx.${node.content}`;
1285 | return node;
1286 | }
1287 |
1288 | function isText(node) {
1289 | return node.type === 3 /* NodeTypes.TEXT */ || node.type === 0 /* NodeTypes.INTERPOLATION */;
1290 | }
1291 |
1292 | function transformText(node) {
1293 | if (node.type === 2 /* NodeTypes.ELEMENT */) {
1294 | return () => {
1295 | const { children } = node;
1296 | for (let i = 0; i < children.length; i++) {
1297 | const child = children[i];
1298 | let currentContainer;
1299 | if (isText(child)) {
1300 | for (let j = i + 1; j < children.length; j++) {
1301 | const next = children[j];
1302 | if (isText(next)) {
1303 | if (!currentContainer) {
1304 | currentContainer = children[i] = {
1305 | type: 5 /* NodeTypes.COMPOUND_EXPRESSION */,
1306 | children: [child],
1307 | };
1308 | }
1309 | currentContainer.children.push(" + ");
1310 | currentContainer.children.push(next);
1311 | children.splice(j, 1);
1312 | j--;
1313 | }
1314 | else {
1315 | currentContainer = undefined;
1316 | break;
1317 | }
1318 | }
1319 | }
1320 | }
1321 | };
1322 | }
1323 | }
1324 |
1325 | function baseCompile(template) {
1326 | const ast = baseParse(template);
1327 | transform(ast, {
1328 | nodeTransforms: [transformExpression, transformElement, transformText],
1329 | });
1330 | return generate(ast);
1331 | }
1332 |
1333 | function compileToFunction(template) {
1334 | const { code } = baseCompile(template);
1335 | const render = new Function("Vue", code)(runtimeDom);
1336 | return render;
1337 | }
1338 | registerRuntimeCompiler(compileToFunction);
1339 |
1340 | export { createApp, createVNode as createElementVNode, createRenderer, createTextVNode, effect, getCurrentInstance, h, inject, isProxy, isReactive, isReadonly, isRef, nextTick, provide, proxyRefs, reactive, readonly, ref, registerRuntimeCompiler, renderSlots, shallowReactive, shallowReadonly, stop, toDisplayString, unref };
1341 |
--------------------------------------------------------------------------------
/packages/vue/examples/apiInject/App.js:
--------------------------------------------------------------------------------
1 | import { h, provide, inject } from "../../dist/tiny-vue3.esm.js";
2 |
3 | const Provider = {
4 | name: "Provider",
5 | setup() {
6 | provide("foo", "fooVal");
7 | provide("bar", "barVal");
8 | },
9 | render() {
10 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]);
11 | },
12 | };
13 |
14 | const ProviderTwo = {
15 | name: "ProviderTwo",
16 | setup() {
17 | provide("foo", "fooValTwo");
18 | const foo = inject("foo");
19 |
20 | return {
21 | foo,
22 | };
23 | },
24 | render() {
25 | return h("div", {}, [
26 | h("p", {}, `ProviderTwo foo: ${this.foo}`),
27 | h(Consumer),
28 | ]);
29 | },
30 | };
31 |
32 | const Consumer = {
33 | name: "Comsumer",
34 | setup() {
35 | const foo = inject("foo");
36 | const bar = inject("bar");
37 | // const baz = inject("baz", "bazDefault");
38 | const baz = inject("baz", () => "bazDefault");
39 |
40 | return {
41 | foo,
42 | bar,
43 | baz,
44 | };
45 | },
46 |
47 | render() {
48 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar} - ${this.baz}`);
49 | },
50 | };
51 |
52 | export default {
53 | name: "App",
54 | setup() {},
55 | render() {
56 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]);
57 | },
58 | };
59 |
--------------------------------------------------------------------------------
/packages/vue/examples/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
16 |
17 |
18 |
19 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/packages/vue/examples/compiler-base/App.js:
--------------------------------------------------------------------------------
1 | import { ref } from "../../dist/tiny-vue3.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 | template: `hi,{{count}}
`,
6 | setup() {
7 | const count = (window.count = ref(1));
8 | return {
9 | count,
10 | };
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/packages/vue/examples/compiler-base/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/packages/vue/examples/compiler-base/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/tiny-vue3.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../dist/tiny-vue3.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | window.self = null;
5 | export const App = {
6 | name: "App",
7 | render() {
8 | // emit
9 | return h("div", {}, [
10 | h("div", {}, "App"),
11 | h(Foo, {
12 | // on + Event
13 | onAdd(a, b) {
14 | console.log("onAdd", a, b);
15 | },
16 | // add-foo
17 | onAddFoo(a, b) {
18 | console.log('onAdd', a, b);
19 | }
20 | }),
21 | ]);
22 | },
23 | setup() {
24 | return {};
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../dist/tiny-vue3.esm.js";
2 |
3 | export const Foo = {
4 | setup(props, { emit }) {
5 | const emitAdd = () => {
6 | console.log('emit add')
7 | emit('add', 1, 2)
8 | emit("add-foo", 1, 2);
9 | };
10 |
11 | return {
12 | emitAdd,
13 | };
14 | },
15 |
16 | render() {
17 | const btn = h(
18 | "button",
19 | {
20 | onClick: this.emitAdd,
21 | },
22 | "emitAdd"
23 | );
24 |
25 | const foo = h("p", {}, "foo");
26 | return h("div", {}, [foo, btn]);
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/tiny-vue3.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 |
6 | createApp(App).mount(rootContainer);
7 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVNode } from "../../dist/tiny-vue3.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | window.self = null;
5 | export const App = {
6 | name: "App",
7 | render() {
8 | // slot
9 | const app = h("div", {}, "App");
10 | // const foo = h(Foo, {}, h("p", {}, "123"));
11 | // const foo = h(Foo, {}, [h("p", {}, "123"), h("p", {}, "456")]);
12 | const foo = h(
13 | Foo,
14 | {},
15 | {
16 | header: ({ age }) => [
17 | h("p", {}, "header" + age),
18 | createTextVNode("你好呀"),
19 | ],
20 | footer: () => h("p", {}, "footer"),
21 | }
22 | );
23 |
24 | return h("div", {}, [app, foo]);
25 | },
26 | setup() {
27 | return {};
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../dist/tiny-vue3.esm.js";
2 |
3 | export const Foo = {
4 | setup(props, { emit }) {
5 | return {};
6 | },
7 |
8 | render() {
9 | const foo = h("p", {}, "foo");
10 |
11 | // Foo vnode children
12 | console.log(this.$slots);
13 | // child -> vnode
14 | // vnode
15 |
16 | // renderSlots
17 | // 1. 获取到要渲染的元素
18 | // 2. 获取到渲染的位置
19 | const age = 18;
20 | return h("div", {}, [
21 | renderSlots(this.$slots, "header", {
22 | age
23 | }),
24 | foo,
25 | renderSlots(this.$slots, "footer"),
26 | ]);
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/tiny-vue3.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 |
6 | createApp(App).mount(rootContainer);
7 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../dist/tiny-vue3.esm.js";
2 | import Child from "./Child.js";
3 |
4 | export const App = {
5 | name: "App",
6 | setup() {
7 | const msg = ref("123");
8 | const count = ref(1);
9 |
10 | window.msg = msg;
11 |
12 | const changeChildProps = () => {
13 | msg.value = "456";
14 | };
15 |
16 | const changeCount = () => {
17 | count.value++;
18 | };
19 |
20 | return { msg, changeChildProps, changeCount, count };
21 | },
22 |
23 | render() {
24 | return h("div", {}, [
25 | h("div", {}, "你好"),
26 | h(
27 | "button",
28 | {
29 | onClick: this.changeChildProps,
30 | },
31 | "change child props"
32 | ),
33 | h(Child, {
34 | msg: this.msg,
35 | }),
36 | h(
37 | "button",
38 | {
39 | onClick: this.changeCount,
40 | },
41 | "change self count"
42 | ),
43 | h("p", {}, "count: " + this.count),
44 | ]);
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../dist/tiny-vue3.esm.js";
2 | export default {
3 | name: "Child",
4 | setup(props, { emit }) {},
5 | render(proxy) {
6 | return h("div", {}, [h("div", {}, "child - props - msg: " + this.$props.msg)]);
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/tiny-vue3.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../dist/tiny-vue3.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | window.self = null;
5 | export const App = {
6 | name: "App",
7 | render() {
8 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]);
9 | },
10 | setup() {
11 | const instance = getCurrentInstance();
12 | console.log("App:", instance);
13 | return {};
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../dist/tiny-vue3.esm.js";
2 |
3 | export const Foo = {
4 | name: 'Foo',
5 | setup(props, { emit }) {
6 | const instance = getCurrentInstance();
7 | console.log("Foo:", instance);
8 | return {};
9 | },
10 |
11 | render() {
12 | return h("div", {}, "foo");
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/tiny-vue3.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 |
6 | createApp(App).mount(rootContainer);
7 |
--------------------------------------------------------------------------------
/packages/vue/examples/customRenderer/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../dist/tiny-vue3.esm.js";
2 |
3 | export const App = {
4 | setup() {
5 | return {
6 | x: 100,
7 | y: 100,
8 | };
9 | },
10 | render() {
11 | return h("rect", { x: this.x, y: this.y });
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/packages/vue/examples/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/packages/vue/examples/customRenderer/main.js:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../../dist/tiny-vue3.esm.js";
2 | import { App } from "./App.js";
3 | console.log(PIXI);
4 |
5 | const game = new PIXI.Application({
6 | width: 500,
7 | height: 500,
8 | });
9 |
10 | document.body.append(game.view);
11 |
12 | const renderer = createRenderer({
13 | createElement(type) {
14 | if (type === "rect") {
15 | const rect = new PIXI.Graphics();
16 | rect.beginFill(0xff0000);
17 | rect.drawRect(0, 0, 100, 100);
18 | rect.endFill();
19 |
20 | return rect;
21 | }
22 | },
23 | patchProp(el, key, val) {
24 | el[key] = val;
25 | },
26 | insert(el, parent) {
27 | parent.addChild(el);
28 | },
29 | });
30 |
31 | renderer.createApp(App).mount(game.stage);
32 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloworld/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../dist/tiny-vue3.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | window.self = null;
5 | export const App = {
6 | render() {
7 | window.self = this;
8 | // ui
9 | return h(
10 | "div",
11 | {
12 | id: "root",
13 | class: ["red", "hard"],
14 | onClick() {
15 | console.log("click");
16 | },
17 | },
18 | // setupState
19 | // this.$el
20 | [
21 | h("div", {}, "hi, " + this.msg),
22 | h(Foo, {
23 | count: 1,
24 | }),
25 | ]
26 | // string
27 | // "hi, tiny-vue3"
28 | // Array
29 | // [h("p", { class: "red" }, "hi"), h("p", { class: "blue" }, "tiny-vue3")]
30 | );
31 | },
32 | setup() {
33 | return {
34 | msg: "tiny-vue3-haha22",
35 | };
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloworld/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../dist/tiny-vue3.esm.js";
2 |
3 | export const Foo = {
4 | setup(props) {
5 | // props count
6 | // shallow readonly
7 | props.count++;
8 | console.log(props);
9 | },
10 |
11 | render() {
12 | return h("div", {}, "foo: " + this.count);
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloworld/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/tiny-vue3.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 |
6 | createApp(App).mount(rootContainer);
7 |
--------------------------------------------------------------------------------
/packages/vue/examples/nextTicker/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | ref,
4 | getCurrentInstance,
5 | nextTick,
6 | } from "../../dist/tiny-vue3.esm.js";
7 |
8 | export default {
9 | name: "App",
10 | setup() {
11 | const count = ref(1);
12 | const instance = getCurrentInstance();
13 |
14 | async function onClick() {
15 | for (let i = 0; i < 100; i++) {
16 | console.log("update");
17 | count.value = i;
18 | }
19 |
20 | // console.log(instance);
21 | // nextTick(() => {
22 | // console.log(instance);
23 | // });
24 |
25 | await nextTick()
26 | console.log(instance)
27 | }
28 |
29 | return {
30 | onClick,
31 | count,
32 | };
33 | },
34 | render() {
35 | const button = h("button", { onClick: this.onClick }, "update");
36 | const p = h("p", {}, "count:" + this.count);
37 |
38 | return h("div", {}, [button, p]);
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/packages/vue/examples/nextTicker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/packages/vue/examples/nextTicker/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/tiny-vue3.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../dist/tiny-vue3.esm.js";
2 |
3 | import ArrayToText from "./ArrayToText.js";
4 | import TextToText from "./TextToText.js";
5 | import TextToArray from "./TextToArray.js";
6 | import ArrayToArray from "./ArrayToArray.js";
7 |
8 | export default {
9 | name: "App",
10 | setup() {},
11 |
12 | render() {
13 | return h("div", { tId: 1 }, [
14 | h("p", {}, "主页"),
15 | // 老的是 array 新的是 text
16 | // h(ArrayToText),
17 | // 老的是 text 新的是 text
18 | // h(TextToText),
19 | // 老的是 text 新的是 array
20 | // h(TextToArray)
21 | // 老的是 array 新的是 array
22 | h(ArrayToArray),
23 | ]);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 array
3 |
4 | import { ref, h } from "../../dist/tiny-vue3.esm.js";
5 |
6 | // 1. 左侧的对比
7 | // (a b) c
8 | // (a b) d e
9 | // const prevChildren = [
10 | // h("p", { key: "A" }, "A"),
11 | // h("p", { key: "B" }, "B"),
12 | // h("p", { key: "C" }, "C"),
13 | // ];
14 | // const nextChildren = [
15 | // h("p", { key: "A" }, "A"),
16 | // h("p", { key: "B" }, "B"),
17 | // h("p", { key: "D" }, "D"),
18 | // h("p", { key: "E" }, "E"),
19 | // ];
20 |
21 | // 2. 右侧的对比
22 | // a (b c)
23 | // d e (b c)
24 | // const prevChildren = [
25 | // h("p", { key: "A" }, "A"),
26 | // h("p", { key: "B" }, "B"),
27 | // h("p", { key: "C" }, "C"),
28 | // ];
29 | // const nextChildren = [
30 | // h("p", { key: "D" }, "D"),
31 | // h("p", { key: "E" }, "E"),
32 | // h("p", { key: "B" }, "B"),
33 | // h("p", { key: "C" }, "C"),
34 | // ];
35 |
36 | // 3. 新的比老的长
37 | // 创建新的
38 | // 左侧
39 | // (a b)
40 | // (a b) c
41 | // i = 2, e1 = 1, e2 = 2
42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
43 | // const nextChildren = [
44 | // h("p", { key: "A" }, "A"),
45 | // h("p", { key: "B" }, "B"),
46 | // h("p", { key: "C" }, "C"),
47 | // h("p", { key: "D" }, "D"),
48 | // ];
49 |
50 | // 右侧
51 | // (a b)
52 | // c (a b)
53 | // i = 0, e1 = -1, e2 = 0
54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
55 | // const nextChildren = [
56 | // h("p", { key: "C" }, "C"),
57 | // h("p", { key: "D" }, "D"),
58 | // h("p", { key: "A" }, "A"),
59 | // h("p", { key: "B" }, "B"),
60 | // ];
61 |
62 | // 4. 老的比新的长
63 | // 删除老的
64 | // 左侧
65 | // (a b) c
66 | // (a b)
67 | // i = 2, e1 = 2, e2 = 1
68 | // const prevChildren = [
69 | // h("p", { key: "A" }, "A"),
70 | // h("p", { key: "B" }, "B"),
71 | // h("p", { key: "C" }, "C"),
72 | // ];
73 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
74 |
75 | // 右侧
76 | // a (b c)
77 | // (b c)
78 | // i = 0, e1 = 0, e2 = -1
79 |
80 | // const prevChildren = [
81 | // h("p", { key: "A" }, "A"),
82 | // h("p", { key: "B" }, "B"),
83 | // h("p", { key: "C" }, "C"),
84 | // ];
85 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
86 |
87 | // 5. 对比中间的部分
88 | // 删除老的 (在老的里面存在,新的里面不存在)
89 | // 5.1
90 | // a,b,(c,d),f,g
91 | // a,b,(e,c),f,g
92 | // D 节点在新的里面是没有的 - 需要删除掉
93 | // C 节点 props 也发生了变化
94 |
95 | // const prevChildren = [
96 | // h("p", { key: "A" }, "A"),
97 | // h("p", { key: "B" }, "B"),
98 | // h("p", { key: "C", id: "c-prev" }, "C"),
99 | // h("p", { key: "D" }, "D"),
100 | // h("p", { key: "F" }, "F"),
101 | // h("p", { key: "G" }, "G"),
102 | // ];
103 |
104 | // const nextChildren = [
105 | // h("p", { key: "A" }, "A"),
106 | // h("p", { key: "B" }, "B"),
107 | // h("p", { key: "E" }, "E"),
108 | // h("p", { key: "C", id:"c-next" }, "C"),
109 | // h("p", { key: "F" }, "F"),
110 | // h("p", { key: "G" }, "G"),
111 | // ];
112 |
113 | // 5.1.1
114 | // a,b,(c,e,d),f,g
115 | // a,b,(e,c),f,g
116 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
117 | // const prevChildren = [
118 | // h("p", { key: "A" }, "A"),
119 | // h("p", { key: "B" }, "B"),
120 | // h("p", { key: "C", id: "c-prev" }, "C"),
121 | // h("p", { key: "E" }, "E"),
122 | // h("p", { key: "D" }, "D"),
123 | // h("p", { key: "F" }, "F"),
124 | // h("p", { key: "G" }, "G"),
125 | // ];
126 |
127 | // const nextChildren = [
128 | // h("p", { key: "A" }, "A"),
129 | // h("p", { key: "B" }, "B"),
130 | // h("p", { key: "E" }, "E"),
131 | // h("p", { key: "C", id:"c-next" }, "C"),
132 | // h("p", { key: "F" }, "F"),
133 | // h("p", { key: "G" }, "G"),
134 | // ];
135 |
136 | // 2 移动 (节点存在于新的和老的里面,但是位置变了)
137 |
138 | // 2.1
139 | // a,b,(c,d,e),f,g
140 | // a,b,(e,c,d),f,g
141 | // 最长子序列: [1,2]
142 |
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 |
153 | // const nextChildren = [
154 | // h("p", { key: "A" }, "A"),
155 | // h("p", { key: "B" }, "B"),
156 | // h("p", { key: "E" }, "E"),
157 | // h("p", { key: "C" }, "C"),
158 | // h("p", { key: "D" }, "D"),
159 | // h("p", { key: "F" }, "F"),
160 | // h("p", { key: "G" }, "G"),
161 | // ];
162 |
163 | // 3. 创建新的节点
164 | // a,b,(c,e),f,g
165 | // a,b,(e,c,d),f,g
166 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
167 | // const prevChildren = [
168 | // h("p", { key: "A" }, "A"),
169 | // h("p", { key: "B" }, "B"),
170 | // h("p", { key: "C" }, "C"),
171 | // h("p", { key: "E" }, "E"),
172 | // h("p", { key: "F" }, "F"),
173 | // h("p", { key: "G" }, "G"),
174 | // ];
175 |
176 | // const nextChildren = [
177 | // h("p", { key: "A" }, "A"),
178 | // h("p", { key: "B" }, "B"),
179 | // h("p", { key: "E" }, "E"),
180 | // h("p", { key: "C" }, "C"),
181 | // h("p", { key: "D" }, "D"),
182 | // h("p", { key: "F" }, "F"),
183 | // h("p", { key: "G" }, "G"),
184 | // ];
185 |
186 | // 综合例子
187 | // a,b,(c,d,e,z),f,g
188 | // a,b,(d,c,y,e),f,g
189 |
190 | // const prevChildren = [
191 | // h("p", { key: "A" }, "A"),
192 | // h("p", { key: "B" }, "B"),
193 | // h("p", { key: "C" }, "C"),
194 | // h("p", { key: "D" }, "D"),
195 | // h("p", { key: "E" }, "E"),
196 | // h("p", { key: "Z" }, "Z"),
197 | // h("p", { key: "F" }, "F"),
198 | // h("p", { key: "G" }, "G"),
199 | // ];
200 |
201 | // const nextChildren = [
202 | // h("p", { key: "A" }, "A"),
203 | // h("p", { key: "B" }, "B"),
204 | // h("p", { key: "D" }, "D"),
205 | // h("p", { key: "C" }, "C"),
206 | // h("p", { key: "Y" }, "Y"),
207 | // h("p", { key: "E" }, "E"),
208 | // h("p", { key: "F" }, "F"),
209 | // h("p", { key: "G" }, "G"),
210 | // ];
211 |
212 | // fix c 节点应该是 move 而不是删除之后重新创建的
213 | const prevChildren = [
214 | h("p", { key: "A" }, "A"),
215 | h("p", {}, "C"),
216 | h("p", { key: "B" }, "B"),
217 | h("p", { key: "D" }, "D"),
218 | ];
219 |
220 | const nextChildren = [
221 | h("p", { key: "A" }, "A"),
222 | h("p", { key: "B" }, "B"),
223 | h("p", {}, "C"),
224 | h("p", { key: "D" }, "D"),
225 | ];
226 |
227 | export default {
228 | name: "ArrayToArray",
229 | setup() {
230 | const isChange = ref(false);
231 | window.isChange = isChange;
232 |
233 | return {
234 | isChange,
235 | };
236 | },
237 | render() {
238 | const self = this;
239 |
240 | return self.isChange === true
241 | ? h("div", {}, nextChildren)
242 | : h("div", {}, prevChildren);
243 | },
244 | };
245 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 text
3 |
4 | import { ref, h } from "../../dist/tiny-vue3.esm.js";
5 | const nextChildren = "newChildren";
6 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "ArrayToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | // 新的是 array
2 | // 老的是 text
3 | import { ref, h } from "../../dist/tiny-vue3.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "TextToArray",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | // 新的是 text
2 | // 老的是 text
3 | import { ref, h } from "../../dist/tiny-vue3.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = "newChild";
7 |
8 | export default {
9 | name: "TextToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/tiny-vue3.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../dist/tiny-vue3.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 |
6 | setup() {
7 | const count = ref(0);
8 |
9 | const onClick = () => {
10 | count.value++;
11 | };
12 |
13 | const props = ref({
14 | foo: "foo",
15 | bar: "bar",
16 | });
17 | const onChangePropsDemo1 = () => {
18 | props.value.foo = "new-foo";
19 | };
20 |
21 | const onChangePropsDemo2 = () => {
22 | props.value.foo = undefined;
23 | };
24 |
25 | const onChangePropsDemo3 = () => {
26 | props.value = {
27 | foo: "foo",
28 | };
29 | };
30 |
31 | return {
32 | count,
33 | onClick,
34 | onChangePropsDemo1,
35 | onChangePropsDemo2,
36 | onChangePropsDemo3,
37 | props,
38 | };
39 | },
40 | render() {
41 | return h(
42 | "div",
43 | {
44 | id: "root",
45 | ...this.props,
46 | },
47 | [
48 | h("div", {}, "count:" + this.count),
49 | h(
50 | "button",
51 | {
52 | onClick: this.onClick,
53 | },
54 | "click"
55 | ),
56 | h(
57 | "button",
58 | {
59 | onClick: this.onChangePropsDemo1,
60 | },
61 | "changeProps - 值改变了 - 修改"
62 | ),
63 |
64 | h(
65 | "button",
66 | {
67 | onClick: this.onChangePropsDemo2,
68 | },
69 | "changeProps - 值变成了 undefined - 删除"
70 | ),
71 |
72 | h(
73 | "button",
74 | {
75 | onClick: this.onChangePropsDemo3,
76 | },
77 | "changeProps - key 在新的里面没有了 - 删除"
78 | ),
79 | ]
80 | );
81 | },
82 | };
83 |
--------------------------------------------------------------------------------
/packages/vue/examples/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/tiny-vue3.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 |
6 | createApp(App).mount(rootContainer);
7 |
--------------------------------------------------------------------------------
/packages/vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tiny-vue3",
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 | "@tiny-vue3/compiler-core": "workspace:^1.0.0",
14 | "@tiny-vue3/runtime-dom": "workspace:^1.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vue/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "@tiny-vue3/runtime-dom";
2 | import { baseCompile } from "@tiny-vue3/compiler-core";
3 | import * as runtimeDom from "@tiny-vue3/runtime-dom";
4 | import { registerRuntimeCompiler } from "@tiny-vue3/runtime-dom";
5 |
6 | function compileToFunction(template) {
7 | const { code } = baseCompile(template);
8 | const render = new Function("Vue", code)(runtimeDom);
9 |
10 | return render;
11 | }
12 |
13 | registerRuntimeCompiler(compileToFunction);
14 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from "@rollup/plugin-typescript";
2 |
3 | export default {
4 | input: "./packages/vue/src/index.ts",
5 | output: [
6 | {
7 | format: "cjs",
8 | file: "packages/vue/dist/tiny-vue3.cjs.js",
9 | },
10 | {
11 | format: "es",
12 | file: "packages/vue/dist/tiny-vue3.esm.js",
13 | },
14 | ],
15 |
16 | plugins: [typescript()],
17 | };
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 | "lib": ["DOM", "ES6", "ES2016"], /* Specify library files to be included in the compilation. */
10 | // "allowJs": true, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | // "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | // "outDir": "./", /* Redirect output structure to the directory. */
18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | // "removeComments": true, /* Do not emit comments to output. */
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true, /* Enable all strict type-checking options. */
29 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
43 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
44 |
45 | /* Module Resolution Options */
46 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
50 | // "typeRoots": [], /* List of folders to include type definitions from. */
51 | "types": ["jest"], /* Type declaration files to be included in compilation. */
52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
56 |
57 | /* Source Map Options */
58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
62 |
63 | /* Experimental Options */
64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
66 |
67 | /* Advanced Options */
68 | "skipLibCheck": true, /* Skip type checking of declaration files. */
69 | "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
70 | "paths": {
71 | "@tiny-vue3/*": [
72 | "./packages/*/src"
73 | ]
74 | }
75 | },
76 | "include": [
77 | "packages/*/src",
78 | "packages/*/__tests__"
79 | ]
80 | }
81 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
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: /@guide-mini-vue\/(\w*)/,
12 | replacement: path.resolve(__dirname, "packages") + "/$1/src",
13 | },
14 | ],
15 | },
16 | });
17 |
--------------------------------------------------------------------------------