├── .gitignore
├── README.md
├── babel.config.js
├── example
├── apiInject
│ ├── App.js
│ └── index.html
├── compiler-base
│ ├── App.js
│ └── index.html
├── 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
├── currentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── customRenderer
│ ├── App.js
│ ├── index.html
│ └── main.js
├── helloword
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── nextTicker
│ ├── App.js
│ ├── NextTicker.js
│ ├── index.html
│ └── main.js
├── patchChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
├── patchChildrenDiff
│ ├── App.js
│ ├── PatchChildren.js
│ ├── index.html
│ └── main.js
└── update
│ ├── App.js
│ ├── index.html
│ └── main.js
├── package.json
├── rollup.config.js
├── src
├── compiler-core
│ ├── src
│ │ ├── ast.ts
│ │ ├── codegen.ts
│ │ ├── compiler.ts
│ │ ├── index.ts
│ │ ├── parse.ts
│ │ ├── runtimeHelpers.ts
│ │ ├── transform.ts
│ │ ├── transforms
│ │ │ ├── transformElement.ts
│ │ │ ├── transformExpression.ts
│ │ │ └── transformText.ts
│ │ └── utisl.ts
│ └── tests
│ │ ├── __snapshots__
│ │ └── codegen.spec.ts.snap
│ │ ├── codegen.spec.ts
│ │ ├── parse.spec.ts
│ │ └── transform.spec.ts
├── index.ts
├── reactivity
│ ├── computed
│ │ └── computed.ts
│ ├── effect
│ │ └── effect.ts
│ ├── index.ts
│ ├── reactive
│ │ ├── README.md
│ │ ├── baseHandlers.ts
│ │ └── reactive.ts
│ ├── ref
│ │ └── index.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ └── shallowReadonly.spec.ts
├── runtime-core
│ ├── apiInject.ts
│ ├── compomentUpdateUtils.ts
│ ├── component.ts
│ ├── componentEmit.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlot.ts
│ ├── componentsProps.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helper
│ │ └── renderSlots.ts
│ ├── index.ts
│ ├── renderer.ts
│ ├── scheduler.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── ShapeFlags.ts
│ ├── index.ts
│ └── toDisplayString.ts
├── tsconfig.json
├── yarn-error.log
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /lib
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mini-vue
2 |
3 | ## 实现功能
4 |
5 | #### reactivity
6 |
7 | > - reactive
8 | > - effect
9 | > - readonly
10 | > - isReactive
11 | > - isReadonly
12 | > - isProxy
13 | > - ref
14 | > - isRef
15 | > - unRef
16 | > - proxyRef
17 | > - computed
18 |
19 | #### runtime-core
20 |
21 | > - component 主流程
22 | > - rollup 打包
23 | > - 组件代理对象
24 | > - shapeFlags
25 | > - 注册事件
26 | > - props 逻辑
27 | > - emit 逻辑
28 | > - slots 逻辑
29 | > - fragment text 逻辑
30 | > - getCurrentInstance 逻辑
31 | > - provide inject 逻辑
32 | > - custom renderer 逻辑
33 | > - 更新 element 初始化流程搭建
34 | > - 更新 element props 逻辑
35 | > - 更新 element children (text -> array 、array -> text、text -> text) 逻辑
36 | > - 更新 element diff 双端对比算法 逻辑(1) 双端对比
37 | > - 更新 element diff 双端对比算法 逻辑(2) 中间对比 删除 逻辑
38 | > - 更新 element diff 双端对比算法 逻辑(3) 中间对比 移动、新增 逻辑
39 | > - 组件更新 逻辑
40 | > - nextTick 逻辑
41 | > - 解析插值功能 逻辑
42 | > - 解析 element 逻辑
43 | > - 解析 text 逻辑
44 | > - 解析三种联合类型 逻辑
45 | > - 有限状态机 (parse 图解)
46 | > - transform 逻辑
47 | > - 实现代码生成 string 类型 逻辑
48 | > - 实现代码生成插值类型 逻辑
49 | > - 实现代码生成三种联合类型
50 | > - 实现编译 template 成 render 函数 逻辑
51 | > - 完结撒花
52 |
53 | > 课程学习地址
54 | > 
55 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ["@babel/preset-env", { targets: { node: "current" } }],
4 | "@babel/preset-typescript",
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/example/apiInject/App.js:
--------------------------------------------------------------------------------
1 | import { h, provide, inject } from "../../lib/my-mini-vue.esm.js";
2 |
3 | const ProviderOne = {
4 | name: "ProviderOne",
5 | setup() {
6 | provide("foo", "fooVal");
7 | provide("bar", "barVal");
8 | },
9 | render() {
10 | return h("div", {}, [h("p", {}, "ProviderOne"), h(ProviderTwo)]);
11 | },
12 | };
13 | const ProviderTwo = {
14 | name: "ProviderTwo",
15 | setup() {
16 | const foo = inject("foo");
17 | provide("foo", "fooTwo");
18 | // provide("bar", "barVal");
19 | return {
20 | foo,
21 | };
22 | },
23 | render() {
24 | return h("div", {}, [h("p", {}, `ProviderTwo - ${this.foo}`), h(Consumer)]);
25 | },
26 | };
27 |
28 | const Consumer = {
29 | name: "Consumer",
30 | setup() {
31 | const foo = inject("foo");
32 | const bar = inject("bar");
33 | // const baz = inject("baz", "defaultBaz");
34 | const baz = inject("baz", () => "defaultBazFunction");
35 | return {
36 | foo,
37 | bar,
38 | baz,
39 | };
40 | },
41 | render() {
42 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar} - ${this.baz}`);
43 | },
44 | };
45 | export default {
46 | name: "App",
47 | setup() {},
48 | render() {
49 | return h("div", {}, [h("p", {}, "apiInject"), h(ProviderOne)]);
50 | },
51 | };
52 |
--------------------------------------------------------------------------------
/example/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | mini-vue
8 |
9 |
10 |
11 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/example/compiler-base/App.js:
--------------------------------------------------------------------------------
1 | import { ref } from "../../lib/my-mini-vue.esm.js";
2 | // 最简单的情况
3 | // template 只有一个 interpolation
4 | // export default {
5 | // template: `{{msg}}`,
6 | // setup() {
7 | // return {
8 | // msg: "vue3 - compiler",
9 | // };
10 | // },
11 | // };
12 |
13 | // 复杂一点
14 | // template 包含 element 和 interpolation
15 | // export default {
16 | // template: `{{msg}}
`,
17 | // setup() {
18 | // return {
19 | // msg: "vue3 - compiler",
20 | // };
21 | // },
22 | // };
23 |
24 | export const App = {
25 | name: "App",
26 | template: `hi,{{message}}---{{count}}
`,
27 | setup() {
28 | const count = (window.count = ref(1));
29 | return {
30 | count,
31 | message: "mini-vue",
32 | };
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/example/compiler-base/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/my-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | window.self = null;
4 | export default {
5 | // ui
6 | render() {
7 | window.self = this;
8 | return h("div", {}, [
9 | h("p", {}, "hi, " + this.msg),
10 | h(Foo, {
11 | onAdd(a, b) {
12 | console.log("Is onAdd", a, b);
13 | },
14 | onAddFoo() {
15 | console.log("Is onAddFoo");
16 | },
17 | }),
18 | ]);
19 | },
20 | setup() {
21 | return {
22 | msg: "App",
23 | };
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/my-mini-vue.esm.js";
2 | export const Foo = {
3 | name: "Foo",
4 | setup(props, { emit }) {
5 | const emitAdd = () => {
6 | console.log("Is emitAdd");
7 | emit("add", 1, 2);
8 | emit("add-foo");
9 | };
10 | return {
11 | emitAdd,
12 | };
13 | },
14 | render() {
15 | const btn = h("button", { onClick: this.emitAdd }, "emitAdd");
16 | const foo = h("p", {}, "foo");
17 | return h("div", {}, [foo, btn]);
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | mini-vue
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/my-mini-vue.esm.js";
2 | import App from "./App.js";
3 | // 流程
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVNode } from "../../lib/my-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | export default {
4 | // ui
5 | render() {
6 | const app = h("p", {}, this.msg);
7 | // const foo = h(Foo, {}, h("p", {}, "123"));
8 | const foo = h(
9 | Foo,
10 | {},
11 | {
12 | header: ({ age }) => [
13 | h("p", {}, "header" + age),
14 | createTextVNode("你好啊!"),
15 | ],
16 | footer: () => h("p", {}, "footer"),
17 | }
18 | );
19 |
20 | return h("div", {}, [app, foo]);
21 | },
22 | setup() {
23 | return {
24 | msg: "App",
25 | };
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/example/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/my-mini-vue.esm.js";
2 | export const Foo = {
3 | name: "Foo",
4 | setup() {},
5 | render() {
6 | const foo = h("p", {}, "foo");
7 | const age = 18;
8 | return h("div", {}, [
9 | renderSlots(this.$slots, "header", { age }),
10 | foo,
11 | renderSlots(this.$slots, "footer"),
12 | ]);
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/example/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | mini-vue
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/my-mini-vue.esm.js";
2 | import App from "./App.js";
3 | // 流程
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | // 在 render 中使用 proxy 调用 emit 函数
2 | // 也可以直接使用 this
3 | // 验证 proxy 的实现逻辑
4 | import { h, ref } from "../../lib/my-mini-vue.esm.js";
5 | import Child from "./Child.js";
6 |
7 | export default {
8 | name: "App",
9 | setup() {
10 | const msg = ref("123");
11 | const count = ref(0);
12 | window.msg = msg;
13 |
14 | const changeChildProps = () => {
15 | msg.value = "456";
16 | };
17 | const changeCount = () => {
18 | count.value++;
19 | };
20 |
21 | return { msg, count, changeChildProps, changeCount };
22 | },
23 |
24 | render() {
25 | return h("div", {}, [
26 | h("div", {}, "你好"),
27 | h(
28 | "button",
29 | {
30 | onClick: this.changeChildProps,
31 | },
32 | "change child props"
33 | ),
34 | h(Child, {
35 | msg: this.msg,
36 | }),
37 | h("button", { onClick: this.changeCount }, "change self count"),
38 | h("p", {}, "count: " + this.count),
39 | ]);
40 | },
41 | };
42 |
--------------------------------------------------------------------------------
/example/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/my-mini-vue.esm.js";
2 | export default {
3 | name: "Child",
4 | setup(props, { emit }) {},
5 | render(proxy) {
6 | return h("div", {}, [h("div", {}, "child" + this.$props.msg)]);
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/example/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/my-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | export const App = {
4 | name: "App",
5 | render() {
6 | const app = h("p", {}, "app");
7 | const foo = h(Foo);
8 |
9 | return h("div", {}, [app, foo]);
10 | },
11 | setup() {
12 | const instance = getCurrentInstance();
13 | console.log("App", instance);
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/example/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/my-mini-vue.esm.js";
2 | export const Foo = {
3 | name: "Foo",
4 | setup() {
5 | const instance = getCurrentInstance();
6 | console.log("Foo", instance);
7 | },
8 | render() {
9 | const foo = h("p", { name: "Foo" }, "foo");
10 | return h("div", {}, [foo]);
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | mini-vue
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/my-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | // 流程
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/customRenderer/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/my-mini-vue.esm.js";
2 | export const App = {
3 | setup() {
4 | return {
5 | x: 100,
6 | y: 100,
7 | };
8 | },
9 | // ui
10 | render() {
11 | return h("rect", {
12 | x: this.x,
13 | y: this.y,
14 | });
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/example/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | mini-vue
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/customRenderer/main.js:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../../lib/my-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | console.log(PIXI);
4 | const game = new PIXI.Application({
5 | width: 666,
6 | height: 666,
7 | });
8 | document.body.append(game.view);
9 | const renderer = createRenderer({
10 | createElement(type) {
11 | if (type === "rect") {
12 | const rect = new PIXI.Graphics();
13 | rect.beginFill(0xff0000);
14 | rect.drawRect(0, 0, 100, 100);
15 | rect.endFill();
16 | return rect;
17 | }
18 | },
19 | patchProp(el, key, val) {
20 | el[key] = val;
21 | },
22 | insert(el, parent) {
23 | parent.addChild(el);
24 | },
25 | });
26 |
27 | renderer.createApp(App).mount(game.stage);
28 |
29 | // 流程
30 | // const rootContainer = document.querySelector("#app");
31 | // createApp(App).mount(rootContainer);
32 |
--------------------------------------------------------------------------------
/example/helloword/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/my-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | window.self = null;
4 | export default {
5 | // ui
6 | render() {
7 | window.self = this;
8 | return h(
9 | "div",
10 | {
11 | id: "h",
12 | class: "blue",
13 | onClick() {
14 | console.log("click");
15 | },
16 | },
17 | [h("p", {}, "hi, " + this.msg), h(Foo, { count: 1 })]
18 | );
19 | // return h("div", { id: "h", class: "red" }, [
20 | // h("p", { class: "red" }, "hi"),
21 | // h("p", { class: "blue" }, this.msg),
22 | // ]);
23 | },
24 | setup() {
25 | return {
26 | msg: "my-mini-vue",
27 | };
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/example/helloword/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/my-mini-vue.esm.js";
2 | export const Foo = {
3 | name: "Foo",
4 | setup(props) {
5 | console.log(props);
6 | props.count++;
7 | console.log(props);
8 | },
9 | render() {
10 | return h("div", {}, "Foo: " + this.count);
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/example/helloword/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | mini-vue
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/helloword/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/my-mini-vue.esm.js";
2 | import App from "./App.js";
3 | // 流程
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/nextTicker/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | ref,
4 | getCurrentInstance,
5 | nextTick,
6 | } from "../../lib/my-mini-vue.esm.js";
7 |
8 | export default {
9 | name: "App",
10 | setup() {
11 | const count = ref(0);
12 | const instance = getCurrentInstance();
13 | function onClick() {
14 | for (let i = 0; i < 100; i++) {
15 | count.value = i;
16 | }
17 | console.log(instance, "instance");
18 | nextTick(() => {
19 | console.log(instance, "instance");
20 | });
21 | }
22 |
23 | return { count, onClick };
24 | },
25 |
26 | render() {
27 | return h("div", { tId: 1 }, [
28 | // h("p", {}, "主页"),
29 | h("p", {}, "count: " + this.count),
30 | h("button", { onClick: this.onClick }, "update"),
31 | // h(NextTicker),
32 | ]);
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/example/nextTicker/NextTicker.js:
--------------------------------------------------------------------------------
1 | // 测试 nextTick 逻辑
2 | import { h, ref } from "../../lib/my-mini-vue.esm.js";
3 |
4 | // 如果 for 循环改变 count 的值 100 次的话
5 | // 会同时触发 100 次的 update 页面逻辑
6 | // 这里可以把 update 页面的逻辑放到微任务中执行
7 | // 避免更改了响应式对象就会执行 update 的逻辑
8 | // 因为只有最后一次调用 update 才是有价值的
9 | window.count = ref(1);
10 |
11 | // 如果一个响应式变量同时触发了两个组件的 update
12 | // 会发生什么有趣的事呢?
13 | const Child1 = {
14 | name: "NextTickerChild1",
15 | setup() {},
16 | render() {
17 | return h("div", {}, `child1 count: ${window.count.value}`);
18 | },
19 | };
20 |
21 | const Child2 = {
22 | name: "NextTickerChild2",
23 | setup() {},
24 | render() {
25 | return h("div", {}, `child2 count: ${window.count.value}`);
26 | },
27 | };
28 |
29 | export default {
30 | name: "NextTicker",
31 | setup() {},
32 | render() {
33 | return h(
34 | "div",
35 | { tId: "nextTicker" },
36 | [h(Child1), h(Child2)]
37 | // `for nextTick: count: ${window.count.value}`
38 | );
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/example/nextTicker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/nextTicker/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from "../../lib/my-mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/my-mini-vue.esm.js";
2 | import ArrayToText from "./ArrayToText.js";
3 | import TextToText from "./TextToText.js";
4 | import TextToArray from "./TextToArray.js";
5 |
6 | export default {
7 | name: "App",
8 | setup() {},
9 |
10 | render() {
11 | return h("div", { tId: 1 }, [
12 | h("p", {}, "主页"),
13 | // 老的是 Array 新的是 Text
14 | // h(ArrayToText),
15 | // 老的是 Text 新的是 Text
16 | // h(TextToText),
17 | // 老的是 Text 新的是 Array
18 | h(TextToArray),
19 | ]);
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/my-mini-vue.esm.js";
2 | const nextChildren = [h("div", {}, "C"), h("div", {}, "D")];
3 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
4 |
5 | export default {
6 | name: "ArrayToArray",
7 | setup() {
8 | const isChange = ref(false);
9 | window.isChange = isChange;
10 | return {
11 | isChange,
12 | };
13 | },
14 | render() {
15 | const self = this;
16 | return self.isChange === true
17 | ? h("div", {}, nextChildren)
18 | : h("div", {}, prevChildren);
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/my-mini-vue.esm.js";
2 | const nextChildren = "newChildren";
3 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
4 |
5 | export default {
6 | name: "ArrayToText",
7 | setup() {
8 | const isChange = ref(false);
9 | window.isChange = isChange;
10 | return {
11 | isChange,
12 | };
13 | },
14 | render() {
15 | const self = this;
16 | return self.isChange === true
17 | ? h("div", {}, nextChildren)
18 | : h("div", {}, prevChildren);
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/my-mini-vue.esm.js";
2 | const prevChildren = "newChildren";
3 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
4 |
5 | export default {
6 | name: "TextToArray",
7 | setup() {
8 | const isChange = ref(false);
9 | window.isChange = isChange;
10 | return {
11 | isChange,
12 | };
13 | },
14 | render() {
15 | const self = this;
16 | return self.isChange === true
17 | ? h("div", {}, nextChildren)
18 | : h("div", {}, prevChildren);
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/my-mini-vue.esm.js";
2 | const nextChildren = "newChildren";
3 | const prevChildren = "prevChildren";
4 |
5 | export default {
6 | name: "TextToText",
7 | setup() {
8 | const isChange = ref(false);
9 | window.isChange = isChange;
10 | return {
11 | isChange,
12 | };
13 | },
14 | render() {
15 | const self = this;
16 | return self.isChange === true
17 | ? h("div", {}, nextChildren)
18 | : h("div", {}, prevChildren);
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/example/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/my-mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/patchChildrenDiff/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/my-mini-vue.esm.js";
2 | import PatchChildren from "./PatchChildren.js";
3 |
4 | export default {
5 | name: "App",
6 | setup() {},
7 |
8 | render() {
9 | return h("div", { tId: 1 }, [h("p", {}, "主页"), h(PatchChildren)]);
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/example/patchChildrenDiff/PatchChildren.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/my-mini-vue.esm.js";
2 | import { ref } from "../../lib/my-mini-vue.esm.js";
3 |
4 | const isChange = ref(false);
5 |
6 | // 1. 左侧的对比
7 | // (a b) c
8 | // (a b) d e
9 | // const prevChildren = [
10 | // h("p", { key: "A" }, "A"),
11 | // h("p", { key: "B" }, "B"),
12 | // h("p", { key: "C" }, "C"),
13 | // ];
14 | // const nextChildren = [
15 | // h("p", { key: "A" }, "A"),
16 | // h("p", { key: "B" }, "B"),
17 | // h("p", { key: "D" }, "D"),
18 | // h("p", { key: "E" }, "E"),
19 | // ];
20 |
21 | // 2. 右侧的对比
22 | // a (b c)
23 | // d e (b c)
24 | // const prevChildren = [
25 | // h("p", { key: "A" }, "A"),
26 | // h("p", { key: "B" }, "B"),
27 | // h("p", { key: "C" }, "C"),
28 | // ];
29 | // const nextChildren = [
30 | // h("p", { key: "D" }, "D"),
31 | // h("p", { key: "E" }, "E"),
32 | // h("p", { key: "B" }, "B"),
33 | // h("p", { key: "C" }, "C"),
34 | // ];
35 |
36 | // 3. 新的比老的长
37 | // 创建新的
38 | // 左侧
39 | // (a b)
40 | // (a b) c
41 | // i = 2, e1 = 1, e2 = 2
42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
43 | // const nextChildren = [
44 | // h("p", { key: "A" }, "A"),
45 | // h("p", { key: "B" }, "B"),
46 | // h("p", { key: "C" }, "C"),
47 | // h("p", { key: "D" }, "D"),
48 | // ];
49 |
50 | // 右侧
51 | // (a b)
52 | // c (a b)
53 | // i = 0, e1 = -1, e2 = 0
54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
55 | // const nextChildren = [
56 | // h("p", { key: "D" }, "D"),
57 | // h("p", { key: "C" }, "C"),
58 | // h("p", { key: "A" }, "A"),
59 | // h("p", { key: "B" }, "B"),
60 | // ];
61 |
62 | // 4. 老的比新的长
63 | // 删除老的
64 | // 左侧
65 | // (a b) c
66 | // (a b)
67 | // i = 2, e1 = 2, e2 = 1
68 | // const prevChildren = [
69 | // h("p", { key: "A" }, "A"),
70 | // h("p", { key: "B" }, "B"),
71 | // h("p", { key: "C" }, "C"),
72 | // ];
73 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
74 |
75 | // 右侧
76 | // a (b c)
77 | // (b c)
78 | // i = 0, e1 = 0, e2 = -1
79 |
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 | // 2.2
164 | // a,b,(c,d,e,z),f,g
165 | // a,b,(d,c,y,e),f,g
166 | // 最长子序列: [1,3]
167 |
168 | // const prevChildren = [
169 | // h("p", { key: "A" }, "A"),
170 | // h("p", { key: "B" }, "B"),
171 | // h("p", { key: "C" }, "C"),
172 | // h("p", { key: "D" }, "D"),
173 | // h("p", { key: "E" }, "E"),
174 | // h("p", { key: "Z" }, "Z"),
175 | // h("p", { key: "F" }, "F"),
176 | // h("p", { key: "G" }, "G"),
177 | // ];
178 |
179 | // const nextChildren = [
180 | // h("p", { key: "A" }, "A"),
181 | // h("p", { key: "B" }, "B"),
182 | // h("p", { key: "D" }, "D"),
183 | // h("p", { key: "C" }, "C"),
184 | // h("p", { key: "Y" }, "Y"),
185 | // h("p", { key: "E" }, "E"),
186 | // h("p", { key: "F" }, "F"),
187 | // h("p", { key: "G" }, "G"),
188 | // ];
189 |
190 | // 3. 创建新的节点
191 | // a,b,(c,e),f,g
192 | // a,b,(e,c,d),f,g
193 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
194 | // const prevChildren = [
195 | // h("p", { key: "A" }, "A"),
196 | // h("p", { key: "B" }, "B"),
197 | // h("p", { key: "C" }, "C"),
198 | // h("p", { key: "E" }, "E"),
199 | // h("p", { key: "F" }, "F"),
200 | // h("p", { key: "G" }, "G"),
201 | // ];
202 |
203 | // const nextChildren = [
204 | // h("p", { key: "A" }, "A"),
205 | // h("p", { key: "B" }, "B"),
206 | // h("p", { key: "E" }, "E"),
207 | // h("p", { key: "C" }, "C"),
208 | // h("p", { key: "D" }, "D"),
209 | // h("p", { key: "F" }, "F"),
210 | // h("p", { key: "G" }, "G"),
211 | // ];
212 |
213 | // fix: 验证 没有设置 key 时 逻辑错误问题
214 | const prevChildren = [
215 | h("p", { key: "A" }, "A"),
216 | h("p", {}, "C"),
217 | h("p", { key: "B" }, "B"),
218 | h("p", { key: "D" }, "D"),
219 | ];
220 |
221 | const nextChildren = [
222 | h("p", { key: "A" }, "A"),
223 | h("p", { key: "B" }, "B"),
224 | h("p", {}, "C"),
225 | h("p", { key: "D" }, "D"),
226 | ];
227 |
228 | export default {
229 | name: "PatchChildren",
230 | setup() {},
231 | render() {
232 | return h("div", {}, [
233 | h(
234 | "button",
235 | {
236 | onClick: () => {
237 | isChange.value = !isChange.value;
238 | },
239 | },
240 | "测试子组件之间的 patch 逻辑"
241 | ),
242 | h("children", {}, isChange.value === true ? nextChildren : prevChildren),
243 | ]);
244 | },
245 | };
246 |
--------------------------------------------------------------------------------
/example/patchChildrenDiff/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/patchChildrenDiff/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/my-mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/my-mini-vue.esm.js";
2 | export default {
3 | name: "App",
4 | setup() {
5 | const count = ref(0);
6 | const props = ref({
7 | foo: "foo",
8 | bar: "bar",
9 | });
10 | const onClick = () => {
11 | count.value++;
12 | };
13 | const onChangeProps = () => {
14 | props.value.foo = "new-foo";
15 | };
16 | const onChangeProps1 = () => {
17 | props.value.foo = undefined;
18 | };
19 | const onChangeProps2 = () => {
20 | props.value = {
21 | foo: "foo",
22 | };
23 | };
24 | return {
25 | count,
26 | props,
27 | onClick,
28 | onChangeProps,
29 | onChangeProps1,
30 | onChangeProps2,
31 | };
32 | },
33 | // ui
34 | render() {
35 | return h(
36 | "div",
37 | {
38 | id: "root",
39 | foo: this.props.foo,
40 | bar: this.props.bar,
41 | },
42 | [
43 | h("div", {}, `count: ${this.count}`), // 依赖收集
44 | h(
45 | "button",
46 | {
47 | onClick: this.onClick,
48 | },
49 | "click"
50 | ),
51 | h(
52 | "button",
53 | {
54 | onClick: this.onChangeProps,
55 | },
56 | "修改 foo 值为 new-foo"
57 | ),
58 | h(
59 | "button",
60 | {
61 | onClick: this.onChangeProps1,
62 | },
63 | "修改 foo 值为 undefined"
64 | ),
65 | h(
66 | "button",
67 | {
68 | onClick: this.onChangeProps2,
69 | },
70 | "删除 bar"
71 | ),
72 | ]
73 | );
74 | },
75 | };
76 |
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | mini-vue
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/my-mini-vue.esm.js";
2 | import App from "./App.js";
3 | // 流程
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-mini-vue",
3 | "version": "1.0.0",
4 | "main": "lib/my-mini-vue.cjs.js",
5 | "module": "lib/my-mini-vue.esm.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "test": "jest",
9 | "build": "rollup -c rollup.config.js"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.17.4",
13 | "@babel/preset-env": "^7.16.11",
14 | "@babel/preset-typescript": "^7.16.7",
15 | "@rollup/plugin-typescript": "^8.3.2",
16 | "@types/jest": "^27.4.0",
17 | "babel-jest": "^27.5.1",
18 | "jest": "^27.5.1",
19 | "rollup": "^2.70.2",
20 | "tslib": "^2.3.1",
21 | "typescript": "^4.5.5"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from "./package.json";
2 | import typescript from "@rollup/plugin-typescript";
3 | export default {
4 | input: "./src/index.ts",
5 | output: [
6 | // 1. cjs -> common.js
7 | // 2. esm
8 | {
9 | format: "cjs",
10 | file: pkg.main,
11 | sourcemap: true,
12 | },
13 | {
14 | format: "es",
15 | file: pkg.module,
16 | sourcemap: true,
17 | },
18 | ],
19 | plugins: [typescript()],
20 | };
21 |
--------------------------------------------------------------------------------
/src/compiler-core/src/ast.ts:
--------------------------------------------------------------------------------
1 | import { CREATE_ELEMENT_VNODE } from "./runtimeHelpers";
2 |
3 | export const enum NodeTypes {
4 | INTERPOLATION,
5 | SIMPLE_EXPRESSION,
6 | ELEMENT,
7 | TEXT,
8 | ROOT,
9 | COMPOUND_EXPRESSION,
10 | }
11 |
12 | export function createVnodeCall(context, tag, props, children) {
13 | context.helper(CREATE_ELEMENT_VNODE);
14 | return {
15 | type: NodeTypes.ELEMENT,
16 | tag,
17 | props,
18 | children,
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/compiler-core/src/codegen.ts:
--------------------------------------------------------------------------------
1 | import { isString } from "../../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, context) {
30 | const { push, helper } = context;
31 | const VueBinging = "Vue";
32 | const aliasHelper = (s) => `${helperMapName[s]}:${helper(s)}`;
33 | if (ast.helpers.length) {
34 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")}} = ${VueBinging}`);
35 | }
36 | push(`\n`);
37 | push("return ");
38 | }
39 | function createCodegenContext() {
40 | const context = {
41 | code: "",
42 | push(source) {
43 | context.code += source;
44 | },
45 | helper(key) {
46 | return `_${helperMapName[key]}`;
47 | },
48 | };
49 | return context;
50 | }
51 | // 处理 code
52 | function genNode(node: any, context: any) {
53 | switch (node.type) {
54 | case NodeTypes.TEXT:
55 | genText(node, context);
56 | break;
57 | case NodeTypes.INTERPOLATION:
58 | genInterpolation(node, context);
59 | break;
60 | case NodeTypes.SIMPLE_EXPRESSION:
61 | genExpression(node, context);
62 | break;
63 | case NodeTypes.ELEMENT:
64 | genElement(node, context);
65 | break;
66 | case NodeTypes.COMPOUND_EXPRESSION:
67 | genCompoundExpression(node, context);
68 | break;
69 | default:
70 | break;
71 | }
72 | }
73 | // 处理 element
74 | function genElement(node, context) {
75 | const { push, helper } = context;
76 | const { tag, children, props } = node;
77 | push(`${helper(CREATE_ELEMENT_VNODE)}(`);
78 | genNodeList(genNullable([tag, props, children]), context);
79 | push(")");
80 | }
81 | // 处理转换 null 后的数组
82 | function genNodeList(nodes, context) {
83 | const { push } = context;
84 | for (let i = 0; i < nodes.length; i++) {
85 | const node = nodes[i];
86 | if (isString(node)) {
87 | push(node);
88 | } else {
89 | genNode(node, context);
90 | }
91 | if (i < nodes.length - 1) {
92 | push(", ");
93 | }
94 | }
95 | }
96 | // 将所有的 undefined 等转为 null
97 | function genNullable(args) {
98 | return args.map((arg) => arg || "null");
99 | }
100 | // 处理 string
101 | function genText(node, context) {
102 | const { push } = context;
103 | push(`'${node.content}'`);
104 | }
105 | // 处理 插值
106 | function genInterpolation(node: any, context: any) {
107 | const { push, helper } = context;
108 | push(`${helper(TO_DISPLAY_STRING)}(`);
109 | genNode(node.content, context);
110 | push(`)`);
111 | }
112 | // 处理 表达式
113 | function genExpression(node: any, context: any) {
114 | const { push } = context;
115 | push(`${node.content}`);
116 | }
117 | // 处理复合类型
118 | function genCompoundExpression(node: any, context: any) {
119 | const { push } = context;
120 | const { tag, children } = node;
121 | for (let i = 0; i < children.length; i++) {
122 | const child = children[i];
123 | if (isString(child)) {
124 | push(child);
125 | } else {
126 | genNode(child, context);
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/compiler-core/src/compiler.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 baseCompiler(template) {
9 | const ast: any = baseParse(template);
10 | transform(ast, {
11 | // 插件先调用 会后执行
12 | nodeTransforms: [transformExpression, transformElement, transformText],
13 | });
14 | return generate(ast);
15 | }
16 |
--------------------------------------------------------------------------------
/src/compiler-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./compiler";
2 |
--------------------------------------------------------------------------------
/src/compiler-core/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | const enum TagTypes {
4 | Start,
5 | End,
6 | }
7 |
8 | export function baseParse(content: string) {
9 | const context = createParserContext(content);
10 | return createRoot(parseChildren(context, []));
11 | }
12 | function parseChildren(context, ancestors) {
13 | let nodes: any = [];
14 | // 循环处理 在未达到结束他条件时 一直循环处理 内部所有的 数据
15 | while (!isEnd(context, ancestors)) {
16 | let node;
17 | const s = context.source;
18 | // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
19 | if (s.startsWith("{{")) {
20 | node = parseInterpolation(context);
21 | } else if (s[0] === "<") {
22 | if (/[a-z]/i.test(s[1])) {
23 | node = parseElement(context, ancestors);
24 | }
25 | } else {
26 | // if (/[a-z]|[0-9]|[\u4e00-\u9fa5]/i.test(s[0]))
27 | node = parseText(context);
28 | }
29 | nodes.push(node);
30 | }
31 | return nodes;
32 | }
33 |
34 | // text 处理
35 | function parseText(context) {
36 | const endTokens = ["<", "{{"];
37 | let endIndex = context.source.length;
38 | for (let i = 0; i <= endTokens.length; i++) {
39 | const index = context.source.indexOf(endTokens[i]);
40 | if (index !== -1 && endIndex > index) {
41 | endIndex = index;
42 | }
43 | }
44 | const content = parseTextData(context, endIndex);
45 | return {
46 | type: NodeTypes.TEXT,
47 | content,
48 | };
49 | }
50 |
51 | function parseTextData(context, length) {
52 | const content = context.source.slice(0, length);
53 | advanceBy(context, length);
54 | return content;
55 | }
56 | // element 处理
57 | function parseElement(context: any, ancestors) {
58 | // 获取tag
59 | const element: any = parseTag(context, TagTypes.Start);
60 | ancestors.push(element);
61 | element.children = parseChildren(context, ancestors);
62 | ancestors.pop();
63 | // 这一步是为了去除 闭合标签
64 | if (startsWithEndTagOpen(context.source, element.tag)) {
65 | parseTag(context, TagTypes.Start);
66 | } else {
67 | throw new Error(`缺少结束标签:${element.tag}`);
68 | }
69 |
70 | return element;
71 | }
72 |
73 | function startsWithEndTagOpen(source, tag) {
74 | return (
75 | source.startsWith("") &&
76 | source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
77 | );
78 | }
79 |
80 | function parseTag(context, type: TagTypes) {
81 | const match: any = /^<\/?([a-z]*)/i.exec(context.source);
82 | const tag = match[1];
83 |
84 | advanceBy(context, match[0].length);
85 | advanceBy(context, 1);
86 | if (type === TagTypes.End) return;
87 | return {
88 | type: NodeTypes.ELEMENT,
89 | tag,
90 | };
91 | }
92 | // 插值处理
93 | function parseInterpolation(context) {
94 | // 处理 message
95 | const openDelimiter = "{{"; // 分隔符
96 | const closeDelimiter = "}}";
97 | const closeIndex = context.source.indexOf("}}", openDelimiter.length);
98 | advanceBy(context, openDelimiter.length);
99 |
100 | const rawContentLength = closeIndex - openDelimiter.length;
101 | const rawContent = parseTextData(context, rawContentLength);
102 | const content = rawContent.trim();
103 | advanceBy(context, closeDelimiter.length);
104 | return {
105 | type: NodeTypes.INTERPOLATION,
106 | content: {
107 | type: NodeTypes.SIMPLE_EXPRESSION,
108 | content,
109 | },
110 | };
111 | }
112 |
113 | // 推进字符串
114 | function advanceBy(context: any, length: number) {
115 | context.source = context.source.slice(length);
116 | }
117 | function createRoot(children) {
118 | return { children, type: NodeTypes.ROOT };
119 | }
120 |
121 | function createParserContext(content) {
122 | return {
123 | source: content,
124 | };
125 | }
126 | // 判断是不是需要循环结束
127 | function isEnd(context, ancestors) {
128 | const s = context.source;
129 | // 2. 遇到结束标签 停止循环
130 | if (s.startsWith("<")) {
131 | for (let i = ancestors.length - 1; i >= 0; i--) {
132 | const tag = ancestors[i].tag;
133 | if (startsWithEndTagOpen(s, tag)) {
134 | return true;
135 | }
136 | }
137 | }
138 | if (ancestors && s.startsWith(`${ancestors}>`)) {
139 | return true;
140 | }
141 | // 1. context.source 有值时 继续循环
142 | return !s;
143 | }
144 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transform.ts:
--------------------------------------------------------------------------------
1 | // 1. 深度优先搜索
2 |
3 | import { NodeTypes } from "./ast";
4 | import { TO_DISPLAY_STRING } from "./runtimeHelpers";
5 |
6 | // 2. 修改 text content
7 | export function transform(root, options = {}) {
8 | const context = createTransformsContext(root, options);
9 | // 1. 深度优先搜索
10 | traverseNode(root, context);
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 | // 处理传入的 ast 树 深度遍历寻找需要修改的 值
25 | function traverseNode(node: any, context) {
26 | // console.log(node);
27 | // 为了达到测试效果 判断 node.type 是不是 text 如果是 就进行修改
28 | // 2. 修改 text content
29 | // if (node.type === NodeTypes.TEXT) {
30 | // node.content = node.content + "my-mini-vue";
31 | // }
32 | const nodeTransforms = context.transforms;
33 | const exitFns: any = []; // 插件的退出收集
34 | for (let i = 0; i < nodeTransforms.length; i++) {
35 | const transform = nodeTransforms[i];
36 | const onExit = transform(node, context);
37 | if (onExit) {
38 | exitFns.push(onExit);
39 | }
40 | }
41 |
42 | switch (node.type) {
43 | case NodeTypes.INTERPOLATION:
44 | context.helper(TO_DISPLAY_STRING);
45 | break;
46 | case NodeTypes.ROOT:
47 | case NodeTypes.ELEMENT:
48 | traverseChildren(node, context);
49 |
50 | default:
51 | break;
52 | }
53 | // 处理插件的退出逻辑
54 | let i = exitFns.length;
55 | while (i--) {
56 | exitFns[i]();
57 | }
58 | }
59 | // 处理 node children
60 | function traverseChildren(node, context) {
61 | const children = node.children;
62 | for (let i = 0; i < children.length; i++) {
63 | const node = children[i];
64 | traverseNode(node, context);
65 | }
66 | }
67 |
68 | function createTransformsContext(root, options) {
69 | const context = {
70 | root,
71 | transforms: options.nodeTransforms || [],
72 | helpers: new Map(),
73 | helper(key) {
74 | context.helpers.set(key, 1);
75 | },
76 | };
77 | return context;
78 | }
79 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformElement.ts:
--------------------------------------------------------------------------------
1 | import { createVnodeCall, NodeTypes } from "../ast";
2 | import { CREATE_ELEMENT_VNODE } from "../runtimeHelpers";
3 |
4 | export function transformElement(node, context) {
5 | if (node.type === NodeTypes.ELEMENT) {
6 | return () => {
7 | // 这里是中间处理层
8 | // tag 标签
9 | const vnodeTag = `'${node.tag}'`;
10 | // props 参数
11 | let vnoceProps;
12 | // children 节点
13 | const children = node.children;
14 | let vnodeChildren = children[0];
15 |
16 | const vnodeElement = createVnodeCall(
17 | context,
18 | vnodeTag,
19 | vnoceProps,
20 | vnodeChildren
21 | );
22 | node.codegenNode = vnodeElement;
23 | };
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformExpression.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 |
3 | // 处理表达式
4 | export function transformExpression(node) {
5 | if (node.type === NodeTypes.INTERPOLATION) {
6 | node.content = processExpression(node.content);
7 | }
8 | }
9 | // 因为 node.content.content 过长 进行封装优化处理
10 | function processExpression(node: any) {
11 | node.content = `_ctx.${node.content}`;
12 | return node;
13 | }
14 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformText.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 | import { isText } from "../utisl";
3 |
4 | export function transformText(node) {
5 | if (node.type === NodeTypes.ELEMENT) {
6 | return () => {
7 | const children = node.children;
8 | let currentContainer;
9 | for (let i = 0; i < children.length; i++) {
10 | const child = children[i];
11 | // 判断是不是一个text
12 | if (isText(child)) {
13 | for (let j = i + 1; j < children.length; j++) {
14 | const next = children[j];
15 |
16 | if (isText(next)) {
17 | if (!currentContainer) {
18 | currentContainer = children[i] = {
19 | type: NodeTypes.COMPOUND_EXPRESSION,
20 | children: [child],
21 | };
22 | }
23 | currentContainer.children.push(" + ");
24 | currentContainer.children.push(next);
25 | // 添加完成后将处理过的删除掉
26 | children.splice(j, 1);
27 | // 因为删除了元素 所以 会影响到 children 少了一个 所以这里需要进行 j--
28 | j--;
29 | } else {
30 | // 如果是 element 就返回
31 | currentContainer = undefined;
32 | }
33 | }
34 | }
35 | }
36 | };
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/compiler-core/src/utisl.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | export function isText(node) {
4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION;
5 | }
6 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/__snapshots__/codegen.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`codegen element 1`] = `
4 | "const { toDisplayString:_toDisplayString, createElementVNode:_createElementVNode} = Vue
5 | return function render (_ctx, _cache){return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))}"
6 | `;
7 |
8 | exports[`codegen interpolation 1`] = `
9 | "const { toDisplayString:_toDisplayString} = Vue
10 | return function render (_ctx, _cache){return _toDisplayString(_ctx.message)}"
11 | `;
12 |
13 | exports[`codegen string 1`] = `
14 | "
15 | return function render (_ctx, _cache){return 'hi'}"
16 | `;
17 |
--------------------------------------------------------------------------------
/src/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 | transform(ast);
12 | const { code } = generate(ast);
13 | expect(code).toMatchSnapshot();
14 | });
15 |
16 | it("interpolation", () => {
17 | const ast = baseParse("{{message}}");
18 | transform(ast, {
19 | nodeTransforms: [transformExpression],
20 | });
21 | const { code } = generate(ast);
22 | expect(code).toMatchSnapshot();
23 | });
24 |
25 | it("element", () => {
26 | const ast: any = baseParse("hi,{{message}}
");
27 | transform(ast, {
28 | // 插件先调用 会后执行
29 | nodeTransforms: [transformExpression, transformElement, transformText],
30 | });
31 | const { code } = generate(ast);
32 | expect(code).toMatchSnapshot();
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/parse.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../src/ast";
2 | import { baseParse } from "../src/parse";
3 |
4 | describe("Parse", () => {
5 | // 插值测试
6 | describe("interpolation", () => {
7 | test("simple interpolation", () => {
8 | const ast = baseParse("{{ message }}");
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 |
20 | // element 测试
21 | describe("element", () => {
22 | test("simple element div", () => {
23 | const ast = baseParse("");
24 | // root
25 | expect(ast.children[0]).toStrictEqual({
26 | type: NodeTypes.ELEMENT,
27 | tag: "div",
28 | children: [],
29 | });
30 | });
31 | });
32 |
33 | // text 测试
34 | describe("text", () => {
35 | test("simple text", () => {
36 | const ast = baseParse("some text");
37 | // root
38 | expect(ast.children[0]).toStrictEqual({
39 | type: NodeTypes.TEXT,
40 | content: "some text",
41 | });
42 | });
43 | });
44 |
45 | // 三种表达式混合测试
46 | test("hello world", () => {
47 | const ast = baseParse("hi,{{message}}
");
48 | expect(ast.children[0]).toStrictEqual({
49 | type: NodeTypes.ELEMENT,
50 | tag: "p",
51 | children: [
52 | {
53 | type: NodeTypes.TEXT,
54 | content: "hi,",
55 | },
56 | {
57 | type: NodeTypes.INTERPOLATION,
58 | content: {
59 | type: NodeTypes.SIMPLE_EXPRESSION,
60 | content: "message",
61 | },
62 | },
63 | ],
64 | });
65 | });
66 | // edge case
67 | test("hello world", () => {
68 | const ast = baseParse("");
69 | expect(ast.children[0]).toStrictEqual({
70 | type: NodeTypes.ELEMENT,
71 | tag: "div",
72 | children: [
73 | {
74 | type: NodeTypes.ELEMENT,
75 | tag: "p",
76 | children: [
77 | {
78 | content: "hi",
79 | type: NodeTypes.TEXT,
80 | },
81 | ],
82 | },
83 | {
84 | type: NodeTypes.INTERPOLATION,
85 | content: {
86 | type: NodeTypes.SIMPLE_EXPRESSION,
87 | content: "message",
88 | },
89 | },
90 | ],
91 | });
92 | });
93 | // 错误提示
94 | test("should throw error when lack end tag", () => {
95 | // baseParse("
");
96 | expect(() => {
97 | baseParse("
");
98 | }).toThrow(`缺少结束标签:span`);
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/src/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 + "my-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,my-mini-vue");
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 出口
2 | export * from "./runtime-dom";
3 | import { baseCompiler } from "./compiler-core/src";
4 | import * as runtimeDom from "./runtime-dom";
5 | import { registerRuntimeCompiler } from "./runtime-dom";
6 |
7 | function compilerToFunction(template) {
8 | // 调用 baseCompiler 返回 code 字符串
9 |
10 | const { code } = baseCompiler(template);
11 | const render = new Function("Vue", code)(runtimeDom);
12 | return render;
13 | // renderFunction(runtimeDom);
14 | // // 通过一个函数生成 接收一个 Vue 字段
15 | // function renderFunction(Vue) {
16 | // const {
17 | // toDisplayString: _toDisplayString,
18 | // createElementVNode: _createElementVNode,
19 | // } = Vue;
20 | // return function render(_ctx, _cache) {
21 | // return _createElementVNode(
22 | // "div",
23 | // null,
24 | // "hi," + _toDisplayString(_ctx.message)
25 | // );
26 | // };
27 | // }
28 | }
29 | // 调用 registerRuntimeCompiler 给到内部使用
30 | registerRuntimeCompiler(compilerToFunction);
31 |
--------------------------------------------------------------------------------
/src/reactivity/computed/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "../effect/effect";
2 |
3 | class ComputedRefImpl {
4 | private _dirty: boolean = true;
5 | private _value: any;
6 | private _effect: any;
7 | constructor(getter) {
8 | this._effect = new ReactiveEffect(getter, () => {
9 | // scheduler
10 | // 只要触发了这个函数说明响应式对象的值发生改变了
11 | // 那么就解锁,后续在调用 get 的时候就会重新执行,所以会得到最新的值
12 | if (this._dirty) return;
13 | this._dirty = true;
14 | });
15 | }
16 | get value() {
17 | // 当依赖的响应式对象的值改变时 _dirty 恢复 true
18 | if (this._dirty) {
19 | this._dirty = false;
20 | this._value = this._effect.run();
21 | }
22 | return this._value;
23 | }
24 | }
25 |
26 | export function computed(getter) {
27 | return new ComputedRefImpl(getter);
28 | }
29 |
--------------------------------------------------------------------------------
/src/reactivity/effect/effect.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * effect 方法
3 | *
4 | * 思路:
5 | * 1.接收一个fn
6 | * 2.直接触发fn
7 | * 3.提供收集依赖方法,当触发 get 方法时 调用 track 方法 进行依赖收集
8 | * 4.提供执行依赖方法,当触发 set 方法时 调用 trigger 方法 执行收集到的所有依赖
9 | * 5.提供stop方法,调用时停止传入runner的执行
10 | */
11 |
12 | import { extend } from "../../shared";
13 |
14 | let activeEffect;
15 | let shouldTrack;
16 |
17 | // 通过对象形式创建
18 | export class ReactiveEffect {
19 | private _fn: any;
20 | active = true;
21 | deps = [];
22 | onStop?: () => void;
23 | public scheduler: Function | undefined;
24 | constructor(fn, scheduler?: Function) {
25 | this._fn = fn;
26 | this.scheduler = scheduler;
27 | }
28 | run() {
29 | // 判断是不是被 stop 的状态
30 | if (!this.active) {
31 | return this._fn();
32 | }
33 | shouldTrack = true;
34 | activeEffect = this;
35 | const result = this._fn();
36 | shouldTrack = false;
37 | return result;
38 | }
39 | stop() {
40 | if (this.active) {
41 | cleanupEffect(this);
42 | if (this.onStop) {
43 | this.onStop();
44 | }
45 | this.active = false;
46 | }
47 | }
48 | }
49 |
50 | function cleanupEffect(effect) {
51 | effect.deps.forEach((dep: any) => {
52 | dep.delete(effect);
53 | });
54 | effect.deps.length = 0;
55 | }
56 |
57 | let targetMaps = new Map();
58 | /**
59 | *
60 | * 收集依赖方法
61 | *
62 | */
63 | export function track(target, key) {
64 | if (!isTrackIng()) return;
65 | // target -> key -> dep
66 | let depMaps = targetMaps.get(target);
67 | // 处理初始化逻辑, 当初始化时没有 depMaps 就创建一个添加到 targetMaps 中
68 | if (!depMaps) {
69 | depMaps = new Map();
70 | targetMaps.set(target, depMaps);
71 | }
72 | // 处理初始化逻辑, 当初始化时没有 dep 就创建 添加到 depMaps 中
73 | let dep = depMaps.get(key);
74 | if (!dep) {
75 | dep = new Set();
76 | depMaps.set(key, dep);
77 | }
78 | trackEffects(dep);
79 | }
80 |
81 | /**
82 | * 依赖收集封装
83 | */
84 | export function trackEffects(dep) {
85 | // 如果 activeEffect 已经被收集 就 return 不需要再次收集
86 | if (dep.has(activeEffect)) return;
87 | dep.add(activeEffect);
88 | // 为了在执行 stop 方法时可以取到当前 effect 所有的 dep
89 | activeEffect.deps.push(dep);
90 | }
91 |
92 | export function isTrackIng() {
93 | return shouldTrack && activeEffect !== undefined;
94 | }
95 |
96 | /**
97 | *
98 | * 依赖执行方法
99 | *
100 | */
101 | export function trigger(target, key) {
102 | // 根据 target 获取到depMaps
103 | let depMaps = targetMaps.get(target);
104 | // 根据 key 获取到 deps
105 | let dep = depMaps.get(key);
106 | triggerEffects(dep);
107 | }
108 |
109 | export function triggerEffects(dep) {
110 | // 循环 deps 执行每一个 fn => run
111 | for (const effect of dep) {
112 | if (effect.scheduler) {
113 | effect.scheduler();
114 | } else {
115 | effect.run();
116 | }
117 | }
118 | }
119 |
120 | export function effect(fn, options: any = {}) {
121 | const _effect = new ReactiveEffect(fn, options.scheduler);
122 | extend(_effect, options);
123 | // 开始就会执行一次
124 | _effect.run();
125 | const runner: any = _effect.run.bind(_effect);
126 | // runner 挂载 effect 实例
127 | runner.effect = _effect;
128 | return runner;
129 | }
130 |
131 | /**
132 | * stop
133 | * 参数:fn
134 | * 当调用此方法时,会停止 传入 fn 的响应式
135 | */
136 | export function stop(runner) {
137 | runner.effect.stop();
138 | }
139 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export { ref, proxyRefs } from "./ref";
2 |
--------------------------------------------------------------------------------
/src/reactivity/reactive/README.md:
--------------------------------------------------------------------------------
1 | ### reactive
2 |
3 | ### 功能
4 | 1.接收一个传入的参数
5 | 2.返回一个Proxy
6 |
7 | ### 实现思路
8 | 1.接收一个对象参数,返回一个 Proxy 对象
9 | 2.在 Proxy 的 get set 方法中对数据进行拦截处理
10 | 3.在 get 中进行依赖收集、在 set 中进行依赖触发
--------------------------------------------------------------------------------
/src/reactivity/reactive/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { track, trigger } from "../effect/effect";
2 | import { extend, isObject } from "../../shared";
3 | import { reactive, ReactiveFlags, readonly } from "./reactive";
4 |
5 | const get = createGetter();
6 | const set = createSetter();
7 | const readonlyGet = createGetter(true);
8 | const shallowReadonlyGet = createGetter(true, true);
9 |
10 | function createGetter(isReadonly?, shallow?) {
11 | return function get(target, key) {
12 | let res = Reflect.get(target, key);
13 | if (key === ReactiveFlags.IS_REACTIVE) {
14 | return !isReadonly;
15 | } else if (key === ReactiveFlags.IS_READONLY) {
16 | return isReadonly;
17 | }
18 | if (shallow) {
19 | return res;
20 | }
21 | if (isObject(res)) {
22 | return isReadonly ? readonly(res) : reactive(res);
23 | }
24 | // TODO: 进行依赖收集
25 | if (!isReadonly) {
26 | track(target, key);
27 | }
28 | return res;
29 | };
30 | }
31 |
32 | /**
33 | * 使用到的方法:
34 | * Proxy https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
35 | * Reflect https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
36 | * */
37 | function createSetter() {
38 | return function set(target, key, value) {
39 | let res = Reflect.set(target, key, value);
40 | // TODO: 进行依赖触发
41 | trigger(target, key);
42 | return res;
43 | };
44 | }
45 |
46 | export const mutableHandlers = {
47 | get,
48 | set,
49 | };
50 |
51 | export const readonlyHandlers = {
52 | get: readonlyGet,
53 | set(target, key, value) {
54 | console.warn(
55 | `key:${String(key)} 修改失败,因为${JSON.stringify(target)} 是 readonly`
56 | );
57 | return true;
58 | },
59 | };
60 |
61 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
62 | get: shallowReadonlyGet,
63 | });
64 |
--------------------------------------------------------------------------------
/src/reactivity/reactive/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "../../shared";
2 | import {
3 | mutableHandlers,
4 | readonlyHandlers,
5 | shallowReadonlyHandlers,
6 | } from "./baseHandlers";
7 |
8 | /**
9 | * reactive 方法
10 | *
11 | * 思路:
12 | * 接收一个对象参数,返回一个 Proxy 对象
13 | * 在 Proxy 的get set 方法中对数据进行拦截处理
14 | * 在 get 中进行依赖收集
15 | * 在 set 中进行依赖触发
16 | */
17 |
18 | export const enum ReactiveFlags {
19 | IS_REACTIVE = "__v_isReactive",
20 | IS_READONLY = "__v_isReadOnly",
21 | }
22 |
23 | export function reactive(raw) {
24 | return createReactiveObject(raw, mutableHandlers);
25 | }
26 |
27 | export function readonly(raw) {
28 | return createReactiveObject(raw, readonlyHandlers);
29 | }
30 |
31 | export function shallowReadonly(raw) {
32 | return createReactiveObject(raw, shallowReadonlyHandlers);
33 | }
34 | export function isReactive(value) {
35 | return !!value[ReactiveFlags.IS_REACTIVE];
36 | }
37 |
38 | export function isReadonly(value) {
39 | return !!value[ReactiveFlags.IS_READONLY];
40 | }
41 |
42 | export function isProxy(value) {
43 | return isReactive(value) || isReadonly(value);
44 | }
45 |
46 | function createReactiveObject(target: any, baseHandlers) {
47 | if (!isObject(target)) {
48 | console.warn(`target:${target} 必须是一个对象`);
49 | return target;
50 | }
51 | return new Proxy(target, baseHandlers);
52 | }
53 |
--------------------------------------------------------------------------------
/src/reactivity/ref/index.ts:
--------------------------------------------------------------------------------
1 | import { trackEffects, triggerEffects, isTrackIng } from "../effect/effect";
2 | import { reactive } from "../reactive/reactive";
3 | import { hasChanged, isObject } from "../../shared";
4 |
5 | /**
6 | * ref => 一个key 对应一个 dep 用来做依赖收集
7 | * get 时依赖收集 set 时 触发依赖
8 | * 传入的会是一个值 比如 true 1
9 | * get set 的时候进行获取 设置
10 | * proxy 用来包裹object
11 | */
12 |
13 | class Ref {
14 | private _value: any;
15 | // 为了收集依赖
16 | public dep;
17 | // 当ref设置对象时 需要使用基础值作为对比
18 | private _rawValue: any;
19 | public __v_isRef = true;
20 | constructor(value) {
21 | this._rawValue = value;
22 | this._value = convert(value);
23 | this.dep = new Set();
24 | }
25 | get value() {
26 | trackRefValue(this);
27 | return this._value;
28 | }
29 | set value(newValue) {
30 | // 一定是先修改值 再通知
31 | if (hasChanged(newValue, this._rawValue)) {
32 | this._rawValue = newValue;
33 | this._value = convert(newValue);
34 | triggerEffects(this.dep);
35 | }
36 | }
37 | }
38 |
39 | function convert(value) {
40 | return isObject(value) ? reactive(value) : value;
41 | }
42 |
43 | function trackRefValue(ref) {
44 | if (isTrackIng()) {
45 | trackEffects(ref.dep);
46 | }
47 | }
48 |
49 | export function ref(value) {
50 | return new Ref(value);
51 | }
52 |
53 | export function isRef(ref) {
54 | return !!ref.__v_isRef;
55 | }
56 |
57 | export function unRef(ref) {
58 | return isRef(ref) ? ref.value : ref;
59 | }
60 |
61 | /**
62 | * 主要用来在 template 中使用
63 | * example
64 | * const a = ref(1)
65 | * template = > a 不需要使用 a.value
66 | */
67 | export function proxyRefs(objectWithRefs) {
68 | return new Proxy(objectWithRefs, {
69 | get(target, key) {
70 | // get => 获取数据时 如果是 ref 类型,就返回 xxx.value 否则就返回 xxx 本身
71 | return unRef(Reflect.get(target, key));
72 | },
73 | set(target, key, value) {
74 | // 如果需要改变的 key 是 ref 类型,
75 | // 并且传入的 value 不是 ref 类型那么就替换 target[key].value
76 | if (isRef(target[key]) && !isRef(value)) {
77 | return (target[key].value = value);
78 | } else {
79 | return Reflect.set(target, key, value);
80 | }
81 | },
82 | });
83 | }
84 |
--------------------------------------------------------------------------------
/src/reactivity/tests/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { computed } from "../computed/computed";
2 | import { reactive } from "../reactive/reactive";
3 |
4 | describe("computed", () => {
5 | it("happy path", () => {
6 | const user = reactive({
7 | age: 1,
8 | });
9 | const age = computed(() => {
10 | return user.age;
11 | });
12 | expect(age.value).toBe(1);
13 | });
14 | it("should computed lazily", () => {
15 | const value = reactive({
16 | foo: 1,
17 | });
18 | const getter = jest.fn(() => {
19 | return value.foo;
20 | });
21 | const cValue = computed(getter);
22 | // lazy
23 | // 当没有调用computed 的 get 时 不执行传入的 fn
24 | expect(getter).not.toHaveBeenCalled();
25 | // 当调用 get 时 触发 传入的 fn
26 | expect(cValue.value).toBe(1);
27 | expect(getter).toBeCalledTimes(1);
28 | // should not compute again
29 | // 再次调用 get 时 fn 只会执行一次
30 | cValue.value;
31 | expect(getter).toBeCalledTimes(1);
32 | // should not compute until needed
33 | // 在需要之前不应该计算
34 | value.foo = 2;
35 | // 传入函数还是只执行一次
36 | expect(getter).toHaveBeenCalledTimes(1);
37 | // 当 cValue 被修改时 computed 返回新的值(这里使用 effect )
38 | // now it should compute
39 | // 现在它应该计算
40 | expect(cValue.value).toBe(2);
41 | expect(getter).toHaveBeenCalledTimes(2);
42 | // should not compute again
43 | cValue.value;
44 | expect(getter).toHaveBeenCalledTimes(2);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect, stop } from "../effect/effect";
2 | import { reactive } from "../reactive/reactive";
3 |
4 | describe("effect", () => {
5 | it("happy path", () => {
6 | const user = reactive({
7 | age: 10,
8 | });
9 | let nextAge;
10 | effect(() => {
11 | nextAge = user.age + 1;
12 | });
13 | expect(nextAge).toBe(11);
14 | user.age++;
15 | expect(nextAge).toBe(12);
16 | });
17 | it("should return runner when call effect", () => {
18 | // effect(fn) -> function (runner) -> fn() -> return
19 | // 调用 effect 返回一个 runner 函数 效用runner 函数 返回用户传入fn 的返回值
20 | let foo = 10;
21 | const runner = effect(() => {
22 | foo++;
23 | return "foo";
24 | });
25 | // 测试 effect 执行
26 | expect(foo).toBe(11);
27 | // 执行 effect 返回的函数
28 | const r = runner();
29 | expect(foo).toBe(12);
30 | expect(r).toBe("foo");
31 | });
32 | it("scheduler", () => {
33 | // 1. effect 接受第二个参数 是一个 options
34 | // 2. 第一次执行 effect 的时候 还会执行fn
35 | // 3. 当 set -> update 的时候,不会执行 fn 会执行 scheduler
36 | // 4. 执行 runner 的时候会再次执行 fn
37 | let dummy;
38 | let run: any;
39 | const scheduler = jest.fn(() => {
40 | run = runner;
41 | });
42 | const obj = reactive({
43 | foo: 1,
44 | });
45 | const runner = effect(
46 | () => {
47 | dummy = obj.foo;
48 | },
49 | { scheduler }
50 | );
51 | // 第一次不会执行 scheduler
52 | expect(scheduler).not.toHaveBeenCalled();
53 | // 但是会执行 fn 也就是 dummy 会被赋值 1
54 | expect(dummy).toBe(1);
55 | // set 更新数据时 fn 不会执行
56 | obj.foo++;
57 | // 但是会执行 scheduler
58 | expect(scheduler).toHaveBeenCalledTimes(1);
59 | expect(dummy).toBe(1);
60 | // 执行 runner 会执行 fn 也就是 foo 会改变
61 | run();
62 | expect(dummy).toBe(2);
63 | });
64 | // 测试 stop 功能
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 | stop(runner);
75 | // obj.prop 改变时 不会更新
76 | // obj.prop = 3;
77 | obj.prop++;
78 | // 所以结果 dummy 还是 2
79 | expect(dummy).toBe(2);
80 | // 重新执行 runner runner 重新打开响应式 可以更寻 dummy
81 | runner();
82 | expect(dummy).toBe(3);
83 | });
84 | // 测试 onStop 功能
85 | it("onStop", () => {
86 | let obj = reactive({
87 | foo: 1,
88 | });
89 | const onStop = jest.fn();
90 | let dummy;
91 | const runner = effect(
92 | () => {
93 | dummy = obj.foo;
94 | },
95 | {
96 | onStop,
97 | }
98 | );
99 | stop(runner);
100 | expect(onStop).toBeCalledTimes(1);
101 | });
102 | });
103 |
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReactive, reactive, readonly, isProxy } from "../reactive/reactive";
2 |
3 | describe("happy path", () => {
4 | it("reactive", () => {
5 | const original = { foo: 1 };
6 | const observed = reactive(original);
7 | const readOnlyObj = readonly({
8 | age: 10,
9 | });
10 | expect(observed).not.toBe(original);
11 | expect(observed.foo).toBe(1);
12 | expect(isReactive(observed)).toBe(true);
13 | expect(isReactive(original)).toBe(false);
14 | expect(isProxy(observed)).toBe(true);
15 | });
16 | it("nested reactive", () => {
17 | const original = {
18 | nested: {
19 | foo: 1,
20 | },
21 | array: [{ bar: 2 }],
22 | };
23 | const observed = reactive(original);
24 | expect(isReactive(observed.nested)).toBe(true);
25 | expect(isReactive(observed.array)).toBe(true);
26 | expect(isReactive(observed.array[0])).toBe(true);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, readonly, isProxy } from "../reactive/reactive";
2 |
3 | describe("readonly", () => {
4 | it("happy path", () => {
5 | const original = { foo: 1, bar: { baz: 2 } };
6 | const wrapped = readonly(original);
7 | expect(wrapped).not.toBe(original);
8 | expect(wrapped.foo).toBe(1);
9 | expect(isReadonly(wrapped.bar)).toBe(true);
10 | expect(isReadonly(original.bar.baz)).toBe(false);
11 | expect(isProxy(wrapped)).toBe(true);
12 | });
13 | it("not set", () => {
14 | console.warn = jest.fn();
15 | const user = readonly({
16 | age: 15,
17 | });
18 | user.age = 16;
19 | expect(console.warn).toBeCalled();
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect/effect";
2 | import { reactive } from "../reactive/reactive";
3 |
4 | import { isRef, proxyRefs, ref, unRef } from "../ref";
5 |
6 | describe("ref", () => {
7 | it("happy path", () => {
8 | const a = ref(1);
9 | expect(a.value).toBe(1);
10 | });
11 | it("should be reactive", () => {
12 | const a = ref(1);
13 | let dummy;
14 | let calls = 0;
15 | effect(() => {
16 | calls++;
17 | dummy = a.value;
18 | });
19 | expect(calls).toBe(1);
20 | expect(a.value).toBe(1);
21 | a.value = 2;
22 | expect(calls).toBe(2);
23 | expect(dummy).toBe(2);
24 | // same value should trigger
25 | a.value = 2;
26 | expect(calls).toBe(2);
27 | expect(dummy).toBe(2);
28 | });
29 | it("should make nested properties reactive", () => {
30 | const a = 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 | it("isRef", () => {
42 | const a = ref(1);
43 | const user = reactive({
44 | age: 1,
45 | });
46 | expect(isRef(a)).toBe(true);
47 | expect(isRef(1)).toBe(false);
48 | expect(isRef(user)).toBe(false);
49 | });
50 | it("unRef", () => {
51 | const a = ref(1);
52 | expect(unRef(a)).toBe(1);
53 | expect(unRef(1)).toBe(1);
54 | });
55 |
56 | it("proxyRefs", () => {
57 | const user = {
58 | age: ref(10),
59 | name: "莉莉娅",
60 | };
61 | const proxyUser = proxyRefs(user);
62 | expect(user.age.value).toBe(10);
63 | expect(proxyUser.age).toBe(10);
64 | expect(proxyUser.name).toBe("莉莉娅");
65 |
66 | proxyUser.age = 20;
67 | expect(user.age.value).toBe(20);
68 | expect(proxyUser.age).toBe(20);
69 |
70 | proxyUser.age = ref(10);
71 | expect(user.age.value).toBe(10);
72 | expect(proxyUser.age).toBe(10);
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, shallowReadonly } from "../reactive/reactive";
2 |
3 | describe("shallowReadonly", () => {
4 | test("should not make non-reactive properties reactive", () => {
5 | const props = shallowReadonly({ n: { foo: 1 } });
6 | expect(isReadonly(props)).toBe(true);
7 | expect(isReadonly(props.n)).toBe(false);
8 | });
9 | it("not set", () => {
10 | console.warn = jest.fn();
11 | const user = shallowReadonly({
12 | age: 15,
13 | });
14 | user.age = 16;
15 | expect(console.warn).toBeCalled();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 |
3 | export function provide(key, value) {
4 | // 通过 getCurrentInstance 获取当前实例
5 | // 只能在setup 中使用
6 | // 因为 getCurrentInstance 是在setup之后赋值的 components -> setCurrentInstance() 可以查看
7 | const currentInstance: any = getCurrentInstance();
8 | if (currentInstance) {
9 | let { provides } = currentInstance;
10 | const parentProvides = currentInstance.parent?.provides;
11 | // Object.create() -> https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
12 | // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
13 | // init provides 将 父组件的 provides
14 |
15 | // 判断 当前的 provides 是不是 与父级相等 如果相等 说明需要初始化
16 | if (parentProvides === provides) {
17 | // 原型绑定到 当前组件
18 | // 使用原型链解决 多层问题
19 | provides = currentInstance.provides = Object.create(parentProvides);
20 | }
21 | // 当 当前组件的 provides 被设置后 就不会再与父级的 provides 相等
22 | provides[key] = value;
23 | }
24 | }
25 | export function inject(key, defaultValue) {
26 | const currentInstance: any = getCurrentInstance();
27 | if (currentInstance) {
28 | const provides = currentInstance.parent?.provides;
29 | // 如果 key 在 parentProvides 中
30 | if (key in provides) {
31 | return provides[key];
32 | } else if (defaultValue) {
33 | // 如果 defaultValue 有值
34 | if (typeof defaultValue === "function") {
35 | return defaultValue();
36 | } else {
37 | return defaultValue;
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/runtime-core/compomentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | export function shouldUpdateComponent(prevVNode, nextVNode) {
2 | const { props: prevProps } = prevVNode;
3 | const { props: nextProps } = nextVNode;
4 | for (const key in nextProps) {
5 | if (nextProps[key] !== prevProps[key]) {
6 | return true;
7 | }
8 | }
9 | return false;
10 | }
11 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRefs } from "../reactivity";
2 | import { shallowReadonly } from "../reactivity/reactive/reactive";
3 | import { emit } from "./componentEmit";
4 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance";
5 | import { initSlots } from "./componentSlot";
6 | import { initProps } from "./componentsProps";
7 |
8 | export function createComponentInstance(vnode: any, parent) {
9 | const component = {
10 | vnode,
11 | type: vnode.type,
12 | setupState: {},
13 | provides: parent ? parent.provides : {},
14 | parent,
15 | next: null,
16 | props: {},
17 | isMounted: false,
18 | subTree: {},
19 | slots: {},
20 | emit: () => {},
21 | };
22 | // bind -> https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
23 | component.emit = emit.bind(null, component) as any;
24 | return component;
25 | }
26 |
27 | export function setupComponent(instance) {
28 | // TODO: 这里初始化props slots的方法占位 后续实现
29 | // 初始化props
30 | initProps(instance, instance.vnode.props);
31 | initSlots(instance, instance.vnode.children);
32 | // 处理有状态的组件
33 | setupStatefulComponent(instance);
34 | }
35 | function setupStatefulComponent(instance: any) {
36 | // 获取用户给到的配置
37 | const Component = instance.type;
38 | // ctx 给 instance 设置proxy 代理对象 解决this. 获取数据的问题
39 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
40 | const { setup } = Component;
41 | if (setup) {
42 | setCurrentInstance(instance);
43 | // setup 返回function(组件的render函数) 或者object(注入到组件上下文)
44 | const setupResult = setup(shallowReadonly(instance.props), {
45 | emit: instance.emit,
46 | });
47 | setCurrentInstance(null);
48 | handleSetupResult(instance, setupResult);
49 | }
50 | }
51 |
52 | function handleSetupResult(instance, setupResult: any) {
53 | // 处理function(组件的render函数) 或者object(注入到组件上下文)
54 | // TODO: 此处先实现了object 后续需要实现function 得逻辑
55 | if (typeof setupResult == "object") {
56 | // proxyRefs 处理 ref 返回值默认 不需要使用 value
57 | instance.setupState = proxyRefs(setupResult);
58 | }
59 | finishComponentSetup(instance);
60 | }
61 | // 处理 render 函数
62 | function finishComponentSetup(instance: any) {
63 | // 如果 用户写了就直接赋值 render 函数优先级最高
64 | // 如果没有赋值 则转换
65 | const Component = instance.type;
66 | if (compiler && !Component.render) {
67 | if (Component.template) {
68 | Component.render = compiler(Component.template);
69 | }
70 | }
71 | instance.render = Component.render;
72 | // if (!instance.render) {
73 | // instance.render = Component.render;
74 | // }
75 | }
76 | let currentInstance = null;
77 | export function getCurrentInstance() {
78 | return currentInstance;
79 | }
80 |
81 | function setCurrentInstance(instance) {
82 | currentInstance = instance;
83 | }
84 |
85 | let compiler;
86 | // 注册 compiler事件
87 | export function registerRuntimeCompiler(_compiler) {
88 | compiler = _compiler;
89 | }
90 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerKey } from "../shared";
2 |
3 | export function emit(instance, event, ...args) {
4 | console.log("emit", event);
5 | // instance.props -> event
6 | const { props } = instance;
7 | // TPP 先写特定行为 再重构为通用行为
8 | const handlerName = toHandlerKey(camelize(event));
9 | const handler = props[handlerName];
10 | handler && handler(...args);
11 | }
12 |
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from "../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 | const { setupState, props } = instance;
12 | if (hasOwn(setupState, key)) {
13 | return setupState[key];
14 | }
15 | // 处理 组件传入的 props
16 | if (hasOwn(props, key)) {
17 | return props[key];
18 | }
19 | // 判断key 是不是$el 如果是 就返回 vnode 的el
20 | const publicGetter = publicPropertiesMap[key];
21 | if (publicGetter) {
22 | return publicGetter(instance);
23 | }
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlot.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | export function initSlots(instance, children) {
4 | // 是不是需要 slots 处理
5 | const { vnode } = instance;
6 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) {
7 | normalizeObjectSlots(children, instance.slots);
8 | }
9 | }
10 |
11 | function normalizeObjectSlots(children: any, slots: any) {
12 | for (const key in children) {
13 | const value = children[key];
14 | slots[key] = (props) => normalSlotValue(value(props));
15 | }
16 | }
17 | function normalSlotValue(value) {
18 | return Array.isArray(value) ? value : [value];
19 | }
20 |
--------------------------------------------------------------------------------
/src/runtime-core/componentsProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance: any, rawProps: any) {
2 | instance.props = rawProps || {};
3 | // TODO: 后续处理attrs
4 | }
5 |
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | // 接收一个根组件 返回一个 App 对象
4 | export function createAppAPI(render) {
5 | return function createApp(rootComponent) {
6 | return {
7 | // 这时候是需要转换为 VNode
8 | // vue3 原本接收一个字符串 这里简化直接接收 dom
9 | mount(rootContainer) {
10 | // 先转换为虚拟节点 vnode
11 | // component -> vnode
12 | // 后续所有逻辑操作 都会基于 vnode 处理
13 | const vnode = createVNode(rootComponent);
14 | render(vnode, rootContainer);
15 | },
16 | };
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function h(type, props?, children?) {
4 | return createVNode(type, props, children);
5 | }
6 |
--------------------------------------------------------------------------------
/src/runtime-core/helper/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from "../vnode";
2 |
3 | export function renderSlots(slots, name, props) {
4 | const slot = slots[name];
5 | if (slot) {
6 | if (typeof slot === "function") {
7 | return createVNode(Fragment, {}, slot(props));
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | // runtime-core 出口
2 | export { createAppAPI } from "./createApp";
3 | export { h } from "./h";
4 | export { renderSlots } from "./helper/renderSlots";
5 | export { createTextVNode, createElementVNode } from "./vnode";
6 | export { getCurrentInstance, registerRuntimeCompiler } from "./component";
7 | export { provide, inject } from "./apiInject";
8 | export { createRenderer } from "./renderer";
9 | export { nextTick } from "./scheduler";
10 | export { toDisplayString } from "../shared";
11 | // 导出 reactivity 模块
12 | export * from "../reactivity";
13 |
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../reactivity/effect/effect";
2 | import { EMPTY_OBJ } from "../shared";
3 | import { ShapeFlags } from "../shared/ShapeFlags";
4 | import { shouldUpdateComponent } from "./compomentUpdateUtils";
5 | import { createComponentInstance, setupComponent } from "./component";
6 | import { createAppAPI } from "./createApp";
7 | import { queueJobs } from "./scheduler";
8 | import { Fragment, Text } from "./vnode";
9 |
10 | export function createRenderer(options) {
11 | const {
12 | createElement: hostCreateElement,
13 | patchProp: hostPatchProp,
14 | insert: hostInsert,
15 | remove: hostRemove,
16 | setElementText: hostSetElementText,
17 | } = options;
18 | function render(vnode, container) {
19 | // 调用 patch 方法,为了方便后续递归处理
20 | patch(null, vnode, container, null, null);
21 | }
22 |
23 | // n1 -> 旧的虚拟节点
24 | // n2 -> 新的虚拟节点
25 | function patch(n1, n2, container, parentComponent, anchor) {
26 | // 去处理组件
27 | // 判断是不是 element 类型
28 | // 如果是 element 就应该处理 element
29 | const { type, shapeFlag } = n2;
30 | switch (type) {
31 | case Fragment:
32 | processFragment(n1, n2, container, parentComponent, anchor);
33 | break;
34 | case Text:
35 | processText(n1, n2, container);
36 | break;
37 |
38 | default:
39 | if (shapeFlag & ShapeFlags.ELEMENT) {
40 | processElement(n1, n2, container, parentComponent, anchor);
41 | } else {
42 | processComponent(n1, n2, container, parentComponent, anchor);
43 | }
44 | break;
45 | }
46 | }
47 |
48 | // 组件初始化挂载
49 | function processComponent(
50 | n1,
51 | n2: any,
52 | container: any,
53 | parentComponent,
54 | anchor
55 | ) {
56 | if (!n1) {
57 | // 初始化 挂载 dom 组件
58 | mountComponent(n2, container, parentComponent, anchor);
59 | } else {
60 | // 组件更新逻辑
61 | updateComponent(n1, n2);
62 | }
63 | }
64 | function updateComponent(n1, n2) {
65 | // 获取到 instance 调用 effect 返回的 runner
66 | const instance = (n2.component = n1.component);
67 | // 判断是不是需要更新
68 | if (shouldUpdateComponent(n1, n2)) {
69 | instance.next = n2;
70 | instance.update();
71 | } else {
72 | n2.el = n1.el;
73 | n2.vnode = n2;
74 | }
75 | }
76 | // slot 只渲染 children 节点
77 | function processFragment(
78 | n1,
79 | n2: any,
80 | container: any,
81 | parentComponent,
82 | anchor
83 | ) {
84 | mountChildren(n2.children, container, parentComponent, anchor);
85 | }
86 | // slot 渲染 text 格式节点
87 | function processText(n1, n2: any, container: any) {
88 | const { children } = n2;
89 | const textNode = (n2.el = document.createTextNode(children));
90 | container.append(textNode);
91 | }
92 | // 将虚拟节点创建为真实 DOM
93 | function processElement(
94 | n1,
95 | n2: any,
96 | container: any,
97 | parentComponent,
98 | anchor
99 | ) {
100 | if (!n1) {
101 | mountElement(n2, container, parentComponent, anchor);
102 | } else {
103 | patchElement(n1, n2, container, parentComponent, anchor);
104 | }
105 | }
106 | // 处理 element 更新对比
107 | function patchElement(n1, n2, container, parentComponent, anchor) {
108 | // console.log("patchComponent-------");
109 | // console.log("n1:", n1);
110 | // console.log("n2:", n2);
111 | // console.log("我是更新");
112 | // 因为更新时 n2 是没有 el 的所有需要将 n1 的 el 赋值给他
113 | const el = (n2.el = n1.el);
114 | const oldProps = n1.props || EMPTY_OBJ;
115 | const newProps = n2.props || EMPTY_OBJ;
116 | patchChildren(n1, n2, el, parentComponent, anchor);
117 | patchProps(el, oldProps, newProps);
118 | }
119 | // 处理更新时的 children
120 | function patchChildren(n1, n2, container, parentComponent, anchor) {
121 | const { shapeFlag: prevShapeFlag } = n1;
122 | const c1 = n1.children;
123 | const { shapeFlag } = n2;
124 | const c2 = n2.children;
125 | // 判断新的 children 是 text
126 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
127 | // 再判断 旧的 children 是不是 array
128 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
129 | unmountChildren(c1);
130 | }
131 | if (c1 !== c2) {
132 | hostSetElementText(container, c2);
133 | }
134 | } else {
135 | // 如果新的 children 不是 text
136 | // 判断旧的 children 是不是 array
137 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
138 | hostSetElementText(container, "");
139 | mountChildren(c2, container, parentComponent, anchor);
140 | } else {
141 | // array diff array
142 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
143 | }
144 | }
145 | }
146 |
147 | // 处理数组对比数组
148 | function patchKeyedChildren(
149 | c1,
150 | c2,
151 | container,
152 | parentComponent,
153 | parentAnchor
154 | ) {
155 | const l2 = c2.length;
156 | let i = 0;
157 | let e1 = c1.length - 1;
158 | let e2 = l2 - 1;
159 | function isSomeVNodeType(n1, n2) {
160 | return n1.type === n2.type && n1.key === n2.key;
161 | }
162 | // 左侧对比
163 | while (i <= e1 && i <= e2) {
164 | const n1 = c1[i];
165 | const n2 = c2[i];
166 | // 如果相等 就执行 patch 判断下级是不是相等
167 | if (isSomeVNodeType(n1, n2)) {
168 | patch(n1, n2, container, parentComponent, parentAnchor);
169 | } else {
170 | break;
171 | }
172 | i++;
173 | }
174 |
175 | // 右侧
176 | while (i <= e1 && i <= e2) {
177 | const n1 = c1[e1];
178 | const n2 = c2[e2];
179 | // 如果相等 就执行 patch 判断下级是不是相等
180 | if (isSomeVNodeType(n1, n2)) {
181 | patch(n1, n2, container, parentComponent, parentAnchor);
182 | } else {
183 | break;
184 | }
185 | e1--;
186 | e2--;
187 | }
188 | // 新的比老的多 创建
189 | if (i > e1) {
190 | if (i <= e2) {
191 | const nextPos = e2 + 1;
192 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
193 | while (i <= e2) {
194 | patch(null, c2[i], container, parentComponent, anchor);
195 | i++;
196 | }
197 | }
198 | }
199 | // 老的比新的多 删除
200 | else if (i > e2) {
201 | while (i <= e1) {
202 | hostRemove(c1[i].el);
203 | i++;
204 | }
205 | }
206 | // 中间对比
207 | else {
208 | let s1 = i;
209 | let s2 = i;
210 | // 记录新数组的数量
211 | // 执行累加数
212 | // 当 patched 大于 等于 toBePatched 时 就相当于后边的都需要删除掉
213 | const toBePatched = e2 - s2 + 1;
214 | let patched = 0;
215 | // 存储新的 数组中不同的元素 的映射表
216 | const keyToNewIndexMap = new Map();
217 | // 建立新旧关系 映射表 设置固定长度 可以优化性能
218 | const newIndexToOldIndexMap = new Array(toBePatched);
219 | // 记录是否需要移动状态
220 | let moved = false;
221 | let maxNewIndexSoFar = 0;
222 | // 初始化所有的映射为 0 ,为 0 的时候代表还未建立映射
223 | for (let i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;
224 |
225 | // 遍历新的数组将 值添加至map key:i
226 | for (let i = s2; i <= e2; i++) {
227 | const nextChild = c2[i];
228 | keyToNewIndexMap.set(nextChild.key, i);
229 | }
230 |
231 | // 遍历旧的数组 新旧对比 处理逻辑
232 | for (let i = s1; i <= e1; i++) {
233 | const prevChild = c1[i];
234 | if (patched >= toBePatched) {
235 | hostRemove(prevChild.el);
236 | continue;
237 | }
238 | let newINdex;
239 | // 如果用户设置 key
240 | if (prevChild.key != null) {
241 | newINdex = keyToNewIndexMap.get(prevChild.key);
242 | } else {
243 | // 如果用户没有设置 key
244 | for (let j = s2; j <= e2; j++) {
245 | if (isSomeVNodeType(prevChild, c2[j])) {
246 | newINdex = j;
247 | break;
248 | }
249 | }
250 | }
251 | // 判断 旧的 在不在新的里边 不在就执行删除
252 | // 如果存就通过 patch 递归进行对比
253 | if (newINdex === undefined) {
254 | hostRemove(prevChild.el);
255 | } else {
256 | // 如果新的点 大于等于 maxNewIndexSoFar
257 | if (newINdex >= maxNewIndexSoFar) {
258 | maxNewIndexSoFar = newINdex;
259 | } else {
260 | moved = true;
261 | }
262 | // i + 1 是因为 初始化 newIndexToOldIndexMap 的时候为 0 所以需要进行 + 1
263 | newIndexToOldIndexMap[newINdex - s2] = i + 1;
264 | patch(prevChild, c2[newINdex], container, parentComponent, null);
265 | patched++;
266 | }
267 | }
268 | // 生成最长递增子序列
269 | const increasingNewIndexSequence = moved
270 | ? getSequence(newIndexToOldIndexMap)
271 | : [];
272 | let j = increasingNewIndexSequence.length - 1; // 最长递增子序列 指针
273 | // 使用倒叙处理 是为了保证 锚点的准确性
274 | for (let i = toBePatched - 1; i >= 0; i--) {
275 | const nextIndex = i + s2;
276 | const nextChild = c2[nextIndex];
277 | // 判断锚点是不是存在于 c2 如果不在 就是 null 追加在最后边
278 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
279 | // 如果新的不存在与映射表 那么就是新增
280 | if (newIndexToOldIndexMap[i] === 0) {
281 | patch(null, nextChild, container, parentComponent, anchor);
282 | } else if (moved) {
283 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
284 | hostInsert(nextChild.el, container, anchor);
285 | } else {
286 | j--;
287 | }
288 | }
289 | }
290 | }
291 | }
292 |
293 | // 删除 节点下的所有 children
294 | function unmountChildren(children) {
295 | for (let i = 0; i < children.length; i++) {
296 | const el = children[i].el;
297 | hostRemove(el);
298 | }
299 | }
300 | // 处理更新时的 props
301 | function patchProps(el, oldProps: any, newProps: any) {
302 | if (oldProps !== newProps) {
303 | // 遍历 newProps 与 oldProps 对比 判断是修改还是删除
304 | for (const key in newProps) {
305 | const prevProp: any = oldProps[key];
306 | const nextProp: any = newProps[key];
307 | if (prevProp !== newProps) {
308 | hostPatchProp(el, key, prevProp, nextProp);
309 | }
310 | }
311 | // 遍历 oldProps 判断 key 在 newProps 中是否存在
312 | // 如果不存在就删除
313 | if (oldProps !== EMPTY_OBJ) {
314 | for (const key in oldProps) {
315 | if (!(key in newProps)) {
316 | hostPatchProp(el, key, oldProps[key], null);
317 | }
318 | }
319 | }
320 | }
321 | }
322 | function mountElement(vnode: any, container: any, parentComponent, anchor) {
323 | // 创建 dom 添加至我们的视图
324 | const { type, props, children, shapeFlag } = vnode;
325 | // vnode -> element -> div
326 | let el = (vnode.el = hostCreateElement(type));
327 | // 判断 children 是不是数组 如果是数组就 遍历 重新执行 patch
328 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
329 | el.textContent = children;
330 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
331 | mountChildren(vnode.children, el, parentComponent, anchor);
332 | }
333 | // 处理所有的 props
334 | for (const key in props) {
335 | const val = props[key];
336 | hostPatchProp(el, key, null, val);
337 | }
338 | // container.append(el);
339 | hostInsert(el, container, anchor);
340 | }
341 | // 处理 children 的 dom
342 | function mountChildren(
343 | children: any,
344 | container: any,
345 | parentComponent,
346 | anchor
347 | ) {
348 | children.forEach((v) => {
349 | patch(null, v, container, parentComponent, anchor);
350 | });
351 | }
352 | function mountComponent(
353 | initialVNode: any,
354 | container: any,
355 | parentComponent,
356 | anchor
357 | ) {
358 | // 创建组件示例对象
359 | const instance = (initialVNode.component = createComponentInstance(
360 | initialVNode,
361 | parentComponent
362 | ));
363 | // 设置 component
364 | setupComponent(instance);
365 | setupRenderEffect(instance, initialVNode, container, anchor);
366 | }
367 | function setupRenderEffect(
368 | instance: any,
369 | initialVNode: any,
370 | container: any,
371 | anchor
372 | ) {
373 | // 通过使用 effect 依赖收集进行更新操作
374 | // 判断 instance 的 isMounted 状态 确定是否为初始化流程
375 | // 将 effect 返回的 runner 函数 赋值 给 instance 的 update 方法,在更新组件 props 时 再次执行
376 | instance.update = effect(
377 | () => {
378 | if (!instance.isMounted) {
379 | console.log("init-----");
380 | // 获取setup的数据 绑定到render this 上
381 | const { proxy } = instance;
382 | // call -> https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
383 | const subTree = (instance.subTree = renderCall(
384 | instance,
385 | proxy,
386 | proxy
387 | ));
388 | // subTree => 虚拟节点树 app.js 中设置的 h
389 | // vnode => path
390 | // vnode => element => mountElement
391 | patch(null, subTree, container, instance, anchor);
392 | // element -> mount
393 | initialVNode.el = subTree.el;
394 | instance.isMounted = true;
395 | } else {
396 | console.log("update");
397 | // 需要一个 vnode
398 | const { next, vnode } = instance;
399 | if (next) {
400 | next.el = vnode.el;
401 | updateComponentPreRender(instance, next);
402 | }
403 | // 获取到 旧的 subTree 以及新的subTree
404 | const { proxy } = instance;
405 | // 获取新的 subTree
406 | const subTree = renderCall(instance, proxy, proxy);
407 | // instance.render.call(proxy, proxy);
408 | const prevTree = instance.subTree;
409 | instance.subTree = subTree;
410 | console.log("update----");
411 | patch(prevTree, subTree, container, instance, anchor);
412 | }
413 | },
414 | {
415 | scheduler() {
416 | console.log("update -- scheduler");
417 | // 收集微任务的 jobs
418 | queueJobs(instance.update);
419 | },
420 | }
421 | );
422 | }
423 | return {
424 | createApp: createAppAPI(render),
425 | };
426 | }
427 |
428 | function updateComponentPreRender(instance: any, nextVNode: any) {
429 | instance.vnode = nextVNode;
430 | instance.next = null;
431 | instance.props = nextVNode.props;
432 | }
433 | // 获取最长递增子序列
434 | function getSequence(arr: number[]): number[] {
435 | const p = arr.slice();
436 | const result = [0];
437 | let i, j, u, v, c;
438 | const len = arr.length;
439 | for (i = 0; i < len; i++) {
440 | const arrI = arr[i];
441 | if (arrI !== 0) {
442 | j = result[result.length - 1];
443 | if (arr[j] < arrI) {
444 | p[i] = j;
445 | result.push(i);
446 | continue;
447 | }
448 | u = 0;
449 | v = result.length - 1;
450 | while (u < v) {
451 | c = (u + v) >> 1;
452 | if (arr[result[c]] < arrI) {
453 | u = c + 1;
454 | } else {
455 | v = c;
456 | }
457 | }
458 | if (arrI < arr[result[u]]) {
459 | if (u > 0) {
460 | p[i] = result[u - 1];
461 | }
462 | result[u] = i;
463 | }
464 | }
465 | }
466 | u = result.length;
467 | v = result[u - 1];
468 | while (u-- > 0) {
469 | result[u] = v;
470 | v = p[v];
471 | }
472 | return result;
473 | }
474 | // 处理 Tree 数据
475 | function renderCall(instance, ...args) {
476 | return instance.render.call(...args);
477 | }
478 |
--------------------------------------------------------------------------------
/src/runtime-core/scheduler.ts:
--------------------------------------------------------------------------------
1 | let queue: any = [];
2 | let isFlushPending = false;
3 | const p = Promise.resolve();
4 |
5 | export function nextTick(fn) {
6 | return fn ? p.then(fn) : p;
7 | }
8 |
9 | export function queueJobs(job) {
10 | if (!queue.includes(job)) {
11 | queue.push(job);
12 | }
13 | queueFlush();
14 | }
15 |
16 | function queueFlush() {
17 | if (isFlushPending) return;
18 | isFlushPending = true;
19 | nextTick(flushJobs);
20 | }
21 |
22 | function flushJobs() {
23 | isFlushPending = false;
24 | let job;
25 | while ((job = queue.shift())) {
26 | job && job();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | // 创建虚拟节点
2 | // 接收是三个参数 后两个可选
3 | import { ShapeFlags } from "../shared/ShapeFlags";
4 | // 用 symbol 作为唯一标识
5 | export const Fragment = Symbol("Fragment");
6 | export const Text = Symbol("Text");
7 |
8 | export { createVNode as createElementVNode };
9 | export function createVNode(type, props?, children?) {
10 | const vnode = {
11 | type,
12 | props,
13 | children,
14 | component: null,
15 | key: props && props.key,
16 | shapeFlag: getShapeFlag(type),
17 | el: null,
18 | };
19 | if (typeof children === "string") {
20 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
21 | } else if (Array.isArray(children)) {
22 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
23 | }
24 | // 如果是组件 并且children 是 object 那么就是 slot
25 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
26 | if (typeof children === "object") {
27 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
28 | }
29 | }
30 | return vnode;
31 | }
32 | function getShapeFlag(type: any) {
33 | return typeof type === "string"
34 | ? ShapeFlags.ELEMENT
35 | : ShapeFlags.STATEFUL_COMPONENT;
36 | }
37 |
38 | // 创建slot text 形式的虚拟节点
39 | export function createTextVNode(text: string) {
40 | return createVNode(Text, {}, text);
41 | }
42 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../runtime-core";
2 |
3 | function createElement(type) {
4 | // console.log("createElement----------");
5 | return document.createElement(type);
6 | }
7 | function patchProp(el, key, preValue, nextValue) {
8 | // console.log("PatchProp----------");
9 | // console.log("preValue:", preValue);
10 | const isOn = (key: string) => /^on[A-Z]/.test(key);
11 | if (isOn(key)) {
12 | const event = key.slice(2).toLocaleLowerCase();
13 | el.addEventListener(event, nextValue);
14 | } else {
15 | if (nextValue === undefined || nextValue == null) {
16 | el.removeAttribute(key, nextValue);
17 | } else {
18 | el.setAttribute(key, nextValue);
19 | }
20 | }
21 | }
22 | function insert(child, parent, anchor) {
23 | // console.log("insert----------");
24 | // parent.append(child);
25 | // https://developer.mozilla.org/zh-CN/docs/Web/API/Node/insertBefore
26 | // Node.insertBefore() 方法在参考节点之前插入一个拥有指定父节点的子节点
27 | parent.insertBefore(child, anchor || null);
28 | }
29 |
30 | function remove(child) {
31 | const parent = child.parentNode;
32 | if (parent) {
33 | parent.removeChild(child);
34 | }
35 | }
36 |
37 | function setElementText(el, text) {
38 | el.textContent = text;
39 | }
40 |
41 | const renderer: any = createRenderer({
42 | createElement,
43 | patchProp,
44 | insert,
45 | remove,
46 | setElementText,
47 | });
48 |
49 | export function createApp(...args) {
50 | return renderer.createApp(...args);
51 | }
52 | // 导出 runtime-core 模块
53 | export * from "../runtime-core";
54 |
--------------------------------------------------------------------------------
/src/shared/ShapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags {
2 | ELEMENT = 1, // 0001
3 | STATEFUL_COMPONENT = 1 << 1, // << === 左移 0010
4 | TEXT_CHILDREN = 1 << 2, // 0100
5 | ARRAY_CHILDREN = 1 << 3, // 1000
6 | SLOT_CHILDREN = 1 << 4, // 1000
7 | }
8 |
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./toDisplayString";
2 |
3 | export const extend = Object.assign;
4 |
5 | export const EMPTY_OBJ = {};
6 |
7 | export const isObject = (val) => {
8 | return val !== null && typeof val === "object";
9 | };
10 |
11 | export const isString = (value) => typeof value === "string";
12 |
13 | export const hasChanged = (value, newValue) => {
14 | return !Object.is(value, newValue);
15 | };
16 |
17 | export const hasOwn = (val, key) =>
18 | Object.prototype.hasOwnProperty.call(val, key);
19 |
20 | export const camelize = (str: string) => {
21 | return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ""));
22 | };
23 | const capitalize = (str: string) => {
24 | // charAt -> https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/charAt
25 | return str.charAt(0).toUpperCase() + str.slice(1);
26 | };
27 | export const toHandlerKey = (str: string) => {
28 | return str ? `on${capitalize(str)}` : "";
29 | };
30 |
--------------------------------------------------------------------------------
/src/shared/toDisplayString.ts:
--------------------------------------------------------------------------------
1 | export function toDisplayString(value) {
2 | return String(value);
3 | }
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 | /* Projects */
5 | // "incremental": true, /* Enable incremental compilation */
6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
7 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
11 | /* Language and Environment */
12 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
13 | "lib": [
14 | "DOM",
15 | "ES6",
16 | "ES2016"
17 | ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
18 | // "jsx": "preserve", /* Specify what JSX code is generated. */
19 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
20 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
21 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
22 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
23 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
24 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
25 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
26 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
27 | /* Modules */
28 | "module": "esnext", /* Specify what module code is generated. */
29 | // "rootDir": "./", /* Specify the root folder within your source files. */
30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
35 | "types": [
36 | "jest"
37 | ], /* Specify type package names to be included without being referenced in a source file. */
38 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
39 | // "resolveJsonModule": true, /* Enable importing .json files */
40 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
41 | /* JavaScript Support */
42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
45 | /* Emit */
46 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
47 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
48 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
49 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
50 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
51 | // "outDir": "./", /* Specify an output folder for all emitted files. */
52 | // "removeComments": true, /* Disable emitting comments. */
53 | // "noEmit": true, /* Disable emitting files from a compilation. */
54 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
55 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
56 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
57 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
59 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
60 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
61 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
62 | // "newLine": "crlf", /* Set the newline character for emitting files. */
63 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
64 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
65 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
66 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
67 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
68 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 | /* Type Checking */
76 | "strict": true, /* Enable all strict type-checking options. */
77 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
78 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
79 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
80 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
81 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
82 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
83 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
84 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
85 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
86 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
87 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
88 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
89 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
90 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
91 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
92 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
93 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
94 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
95 | /* Completeness */
96 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
97 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
98 | }
99 | }
--------------------------------------------------------------------------------