├── .gitignore
├── README.md
├── babel.config.js
├── example
├── apiInject
│ ├── App.js
│ ├── index.html
│ └── main.js
├── compiler-base
│ ├── App.js
│ ├── index.html
│ └── main.js
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlot
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentUpdate
│ ├── App.js
│ ├── Child.js
│ ├── index.html
│ └── main.js
├── constomRender
│ ├── App.js
│ ├── index.html
│ └── main.js
├── currentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── helloWorld
│ ├── App.js
│ ├── Props.js
│ ├── index.html
│ └── main.js
├── nextTick
│ ├── App.js
│ ├── index.html
│ └── main.js
├── patchChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
└── update
│ ├── App.js
│ ├── Props.js
│ ├── index.html
│ └── main.js
├── lib
├── mini-vue.cjs.js
└── mini-vue.esm.js
├── package.json
├── rollup.config.js
├── src
├── compiler-core
│ ├── src
│ │ ├── ast.ts
│ │ ├── codegen.ts
│ │ ├── complie.ts
│ │ ├── index.ts
│ │ ├── parse.ts
│ │ ├── runtimeHelpers.ts
│ │ ├── transform.ts
│ │ ├── transforms
│ │ │ ├── transformElement.ts
│ │ │ ├── transformText.ts
│ │ │ └── transfromExpression.ts
│ │ └── utils.ts
│ └── tests
│ │ ├── __snapshots__
│ │ └── codegen.spec.ts.snap
│ │ ├── codegen.spec.ts
│ │ ├── parse.spec.ts
│ │ └── transform.spec.ts
├── index.ts
├── reactivity
│ ├── baseHandler.ts
│ ├── computed.ts
│ ├── effect.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ └── shallowReadonly.spec.ts
├── runtime-core
│ ├── apiInject.ts
│ ├── component.ts
│ ├── componentProps.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── componentUpdateUtils.ts
│ ├── componetEmit.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── index.ts
│ ├── render.ts
│ ├── scheduler.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── index.ts
│ ├── shapeFlags.ts
│ └── toDisplayString.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # my_vue_project
2 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {targets: {node: 'current'}}],
4 | '@babel/preset-typescript',
5 | ],
6 | };
--------------------------------------------------------------------------------
/example/apiInject/App.js:
--------------------------------------------------------------------------------
1 | import { h, provide, inject } from "../../lib/mini-vue.esm.js";
2 |
3 | const Provider = {
4 | name: "Provider",
5 | render() {
6 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]);
7 | },
8 | setup() {
9 | provide("foo", "fooVal");
10 | provide("bar", "barVal");
11 | },
12 | };
13 |
14 | const ProviderTwo = {
15 | name: "Provider",
16 | render() {
17 | return h("div", {}, [h("p", {}, `Provider - ${this.foo}`), h(Consumer)]);
18 | },
19 | setup() {
20 | provide("foo", "fooValTwo");
21 | const foo = inject("foo");
22 | return { foo };
23 | },
24 | };
25 |
26 | const Consumer = {
27 | name: "Comsumer",
28 | setup() {
29 | const foo = inject("foo");
30 | const bar = inject("bar");
31 | const baz = inject("baz", "bazDefault");
32 | const bazFun = inject("bazFun", () => "bazFun");
33 | return { foo, bar, baz, bazFun };
34 | },
35 | render() {
36 | return h(
37 | "div",
38 | {},
39 | `Consumer: - ${this.foo} - ${this.bar} - ${this.baz} - ${this.bazFun}`
40 | );
41 | },
42 | };
43 |
44 | const App = {
45 | name: "app",
46 | render() {
47 | return h("div", {}, [h(Provider)]);
48 | },
49 | setup() {},
50 | };
51 | export { App };
52 |
--------------------------------------------------------------------------------
/example/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/apiInject/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | createApp(App).mount(rootContainer);
5 |
--------------------------------------------------------------------------------
/example/compiler-base/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/mini-vue.esm.js";
2 | const App = {
3 | name: "app",
4 | template: `hi,{{count}}
`,
5 | setup() {
6 | const count = (window.count = ref(1));
7 | return {
8 | count,
9 | };
10 | },
11 | };
12 | export { App };
13 |
--------------------------------------------------------------------------------
/example/compiler-base/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/compiler-base/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | createApp(App).mount(rootContainer);
5 |
--------------------------------------------------------------------------------
/example/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | window.self = null;
4 | const App = {
5 | name: "app",
6 | render() {
7 | return h("div", {}, [
8 | h("div", {}, "App"),
9 | h(Foo, {
10 | //on + 事件名
11 | onAdd(a, b) {
12 | console.log("onAdd", a, b);
13 | },
14 | onAddFoo() {
15 | console.log("onAddFoo");
16 | },
17 | }),
18 | ]);
19 | },
20 |
21 | setup() {
22 | return {};
23 | },
24 | };
25 | export { App };
26 |
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | export const Foo = {
3 | name: "props",
4 | setup(props, { emit }) {
5 | const emitAdd = () => {
6 | console.log("emit add");
7 | emit("add", 1, 2);
8 | emit("add-foo");
9 | };
10 | return { emitAdd };
11 | },
12 | render() {
13 | const btn = h("button", { onClick: this.emitAdd }, "emitAdd");
14 | const foo = h("p", {}, "Foo");
15 | return h("p", {}, [foo, btn]);
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | createApp(App).mount(rootContainer);
5 |
--------------------------------------------------------------------------------
/example/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVNode } from "../../lib/mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | window.self = null;
4 | const App = {
5 | name: "app",
6 | render() {
7 | const app = h("div", {}, "App");
8 | const foo = h(
9 | Foo,
10 | {},
11 | {
12 | header: ({ age }) => [
13 | h("p", {}, "header" + age),
14 | createTextVNode("textNode"),
15 | ],
16 | footer: ({ age }) => h("p", {}, "footer" + age),
17 | }
18 | );
19 | return h("div", {}, [app, foo]);
20 | },
21 |
22 | setup() {
23 | return {};
24 | },
25 | };
26 | export { App };
27 |
--------------------------------------------------------------------------------
/example/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/mini-vue.esm.js";
2 | export const Foo = {
3 | name: "props",
4 | setup() {},
5 | render() {
6 | const age = 18;
7 | const foo = h("p", {}, "foo");
8 |
9 | //Foo .vnode.children
10 | // children isArray ? children : [children];
11 | // 1.获取到要渲染的元素
12 | // 2.获取到要渲染的位置
13 | // 3.作用域插槽
14 | console.log(this.$slots);
15 | return h("p", {}, [
16 | renderSlots(this.$slots, "header", { age }),
17 | foo,
18 | renderSlots(this.$slots, "footer", { age }),
19 | ]);
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/example/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | createApp(App).mount(rootContainer);
5 |
--------------------------------------------------------------------------------
/example/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/mini-vue.esm.js";
2 | import { Child } from "./Child.js";
3 | window.self = null;
4 | const App = {
5 | name: "app",
6 | setup() {
7 | const msg = ref("123");
8 | const count = ref(1);
9 | window.msg = msg;
10 |
11 | const changeChildProps = () => {
12 | msg.value = "456";
13 | };
14 | const changeCount = () => {
15 | count.value++;
16 | };
17 | return {
18 | msg,
19 | count,
20 | changeChildProps,
21 | changeCount,
22 | };
23 | },
24 | render() {
25 | // this.count get this.count.value
26 | return h("div", {}, [
27 | h("div", {}, "哈喽"),
28 | h("button", { onClick: this.changeChildProps }, "change child props"),
29 | h(Child, { msg: this.msg }),
30 | h("button", { onClick: this.changeCount }, "change count"),
31 | h("p", {}, "count:" + this.count),
32 | ]);
33 | },
34 | };
35 | export { App };
36 |
--------------------------------------------------------------------------------
/example/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | export const Child = {
3 | name: "child",
4 | setup(props) {},
5 | render() {
6 | return h("div", { class: "red" }, "child-porps-msg:" + this.$props.msg);
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/example/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/componentUpdate/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | createApp(App).mount(rootContainer);
5 |
--------------------------------------------------------------------------------
/example/constomRender/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | export const App = {
3 | setup() {
4 | return {
5 | x: 100,
6 | y: 100,
7 | };
8 | },
9 | render() {
10 | return h("rect", { x: this.x, y: this.y });
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/example/constomRender/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/constomRender/main.js:
--------------------------------------------------------------------------------
1 | import { createRender } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const containerBox = new PIXI.Application({
4 | width: 500,
5 | height: 500,
6 | });
7 | document.body.append(containerBox.view);
8 | const renderer = createRender({
9 | createElement(type) {
10 | if (type === "rect") {
11 | const rect = new PIXI.Graphics();
12 | rect.beginFill(0xfff0000);
13 | rect.drawRect(0, 0, 100, 100);
14 | rect.endFill();
15 | rect.beginFill(0xfff0000);
16 | rect.drawRect(200, 0, 100, 100);
17 | rect.endFill();
18 | rect.beginFill(0xfff0000);
19 | rect.drawRect(50, 200, 200, 50);
20 | rect.endFill();
21 | return rect;
22 | }
23 | },
24 | patchProp(el, key, val) {
25 | el[key] = val;
26 | },
27 | insert(el, parent) {
28 | parent.addChild(el);
29 | },
30 | });
31 |
32 | renderer.createApp(App).mount(containerBox.stage);
33 | // pixiJS
34 | // const rootContainer = document.querySelector("#app");
35 |
36 | // createApp(App).mount(rootContainer);
37 |
--------------------------------------------------------------------------------
/example/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | window.self = null;
4 | const App = {
5 | name: "app",
6 | render() {
7 | return h("div", {}, [h("p", {}, "currentInstance"), h(Foo, { count: 1 })]);
8 | },
9 |
10 | setup() {
11 | const instance = getCurrentInstance();
12 | console.log("App:", instance);
13 | },
14 | };
15 | export { App };
16 |
--------------------------------------------------------------------------------
/example/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/mini-vue.esm.js";
2 | export const Foo = {
3 | name: "Foo",
4 | setup() {},
5 | setup(props) {
6 | // 1.setup传入
7 | // 2.通过this访问到props的值
8 | // 3.不可以被修改 readonly:shadowReadonly
9 | // props.count
10 | props.count++;
11 | console.log(props);
12 | const instance = getCurrentInstance();
13 | console.log(instance);
14 | },
15 | render() {
16 | return h("div", { class: "red" }, "count:" + this.count);
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | createApp(App).mount(rootContainer);
5 |
--------------------------------------------------------------------------------
/example/helloWorld/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | import { Foo } from "./Props.js";
3 | window.self = null;
4 | const App = {
5 | name: "app",
6 | render() {
7 | window.self = this;
8 | return h(
9 | "div",
10 | {
11 | id: "root",
12 | class: ["red", "hard"],
13 | },
14 | [
15 | h("p", { class: "blue" }, "hello"),
16 | h(
17 | "button",
18 | {
19 | class: "green",
20 | onClick() {
21 | window.alert("I'm clicked");
22 | },
23 | },
24 | "minivue"
25 | ),
26 | // this.$el => get root element
27 | h("div", { class: "red" }, "hi," + this.msg),
28 | h(Foo, { count: 1 }),
29 | ]
30 | );
31 | },
32 |
33 | setup() {
34 | // composition Api
35 | return {
36 | msg: "mini-vue start",
37 | };
38 | },
39 | };
40 | export { App };
41 |
--------------------------------------------------------------------------------
/example/helloWorld/Props.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | export const Foo = {
3 | name: "props",
4 | setup(props) {
5 | // 1.setup传入
6 | // 2.通过this访问到props的值
7 | // 3.不可以被修改 readonly:shadowReadonly
8 | // props.count
9 | props.count++;
10 | console.log(props);
11 | },
12 | render() {
13 | return h("div", { class: "red" }, "count:" + this.count);
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/example/helloWorld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/helloWorld/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | console.log("app vnode", App);
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/nextTick/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | ref,
4 | getCurrentInstance,
5 | nextTick,
6 | } from "../../lib/mini-vue.esm.js";
7 | window.self = null;
8 | const App = {
9 | name: "app",
10 | setup() {
11 | const count = ref(1);
12 | const instance = getCurrentInstance();
13 | function onClick() {
14 | for (let i = 0; i < 100; i++) {
15 | console.log("update");
16 | count.value = i;
17 | }
18 | nextTick(() => {
19 | console.log(instance);
20 | });
21 | // await nextTick()
22 | // console.log(instance);
23 | }
24 | return {
25 | onClick,
26 | count,
27 | };
28 | },
29 | render() {
30 | const btn = h("button", { onClick: this.onClick }, "update");
31 | const p = h("p", {}, "count:" + this.count);
32 | return h(
33 | "div",
34 | {
35 | id: "root",
36 | },
37 | [btn, p]
38 | );
39 | },
40 | };
41 | export { App };
42 |
--------------------------------------------------------------------------------
/example/nextTick/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/nextTick/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | createApp(App).mount(rootContainer);
5 |
--------------------------------------------------------------------------------
/example/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | import ArrayToText from "./ArrayToText.js";
3 | import TextToText from "./TextToText.js";
4 | import TextToArray from "./TextToArray.js";
5 | import ArrayToArray from "./ArrayToArray.js";
6 | const App = {
7 | name: "app",
8 | render() {
9 | window.self = this;
10 | return h(
11 | "div",
12 | {
13 | tId: 1,
14 | },
15 | [h("p", {}, "主页"), h(ArrayToText)]
16 | );
17 | },
18 |
19 | setup() {
20 | // composition Api
21 | return {
22 | msg: "mini-vue start",
23 | };
24 | },
25 | };
26 | export { App };
27 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/mini-vue.esm.js";
2 |
3 | // 1.左侧的对比
4 | // (a b) c
5 | // (a b) d e
6 | // const prevChildren = [
7 | // h("p", { key: "A" }, "A"),
8 | // h("p", { key: "B" }, "B"),
9 | // h("p", { key: "C" }, "C"),
10 | // ];
11 | // const nextChildren = [
12 | // h("p", { key: "A" }, "A"),
13 | // h("p", { key: "B" }, "B"),
14 | // h("p", { key: "D" }, "D"),
15 | // h("p", { key: "E" }, "E"),
16 | // ];
17 |
18 | // 2.右侧的对比
19 | // a (b c)
20 | // d e (b c)
21 | // const prevChildren = [
22 | // h("p", { key: "A" }, "A"),
23 | // h("p", { key: "B" }, "B"),
24 | // h("p", { key: "C" }, "C"),
25 | // ];
26 | // const nextChildren = [
27 | // h("p", { key: "D" }, "D"),
28 | // h("p", { key: "E" }, "E"),
29 | // h("p", { key: "B" }, "B"),
30 | // h("p", { key: "C" }, "C"),
31 | // ];
32 |
33 | // 3.新的比老的长
34 | // 左侧对比
35 | // (a b)
36 | // (a b) c d
37 | const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
38 | const nextChildren = [
39 | h("p", { key: "A" }, "A"),
40 | h("p", { key: "B" }, "B"),
41 | h("p", { key: "C" }, "C"),
42 | h("p", { key: "D" }, "D"),
43 | ];
44 |
45 | // 右侧对比
46 | // (a b)
47 | // c d (a b)
48 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
49 | // const nextChildren = [
50 | // h("p", { key: "C" }, "C"),
51 | // h("p", { key: "D" }, "D"),
52 | // h("p", { key: "A" }, "A"),
53 | // h("p", { key: "B" }, "B"),
54 | // ];
55 | // 新的比老的少
56 | // (a b) c d
57 | // (a b)
58 | // 2 3 1
59 | // const prevChildren = [
60 | // h("p", { key: "A" }, "A"),
61 | // h("p", { key: "B" }, "B"),
62 | // h("p", { key: "C" }, "C"),
63 | // h("p", { key: "D" }, "D"),
64 | // ];
65 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
66 | // a b (c d)
67 | // (c d)
68 | // 0 1 -1
69 | // const prevChildren = [
70 | // h("p", { key: "A" }, "A"),
71 | // h("p", { key: "B" }, "B"),
72 | // h("p", { key: "C" }, "C"),
73 | // h("p", { key: "D" }, "D"),
74 | // ];
75 | // const nextChildren = [h("p", { key: "C" }, "C"), h("p", { key: "D" }, "D")];
76 | // (a,b),c,d,(f, g)
77 | // (a,b),e,c,(f,g)
78 | // const prevChildren = [
79 | // h("p", { key: "A" }, "A"),
80 | // h("p", { key: "B" }, "B"),
81 | // h("p", { key: "C", id: "c-prev" }, "C"),
82 | // h("p", { key: "D" }, "D"),
83 | // h("p", { key: "F" }, "F"),
84 | // h("p", { key: "G" }, "G"),
85 | // ];
86 | // const nextChildren = [
87 | // h("p", { key: "A" }, "A"),
88 | // h("p", { key: "B" }, "B"),
89 | // h("p", { key: "E" }, "E"),
90 | // h("p", { key: "C", id: "c-prev" }, "C"),
91 | // h("p", { key: "F" }, "F"),
92 | // h("p", { key: "G" }, "G"),
93 | // ];
94 | // a b (c d) e f g
95 | // a b e (c d) f g LIS [2,3]
96 | // const prevChildren = [
97 | // h("p", { key: "A" }, "A"),
98 | // h("p", { key: "B" }, "B"),
99 | // h("p", { key: "C", id: "c-prev" }, "C"),
100 | // h("p", { key: "D" }, "D"),
101 | // h("p", { key: "E" }, "E"),
102 | // h("p", { key: "F" }, "F"),
103 | // h("p", { key: "G" }, "G"),
104 | // ];
105 | // const nextChildren = [
106 | // h("p", { key: "A" }, "A"),
107 | // h("p", { key: "B" }, "B"),
108 | // h("p", { key: "E" }, "E"),
109 | // h("p", { key: "C", id: "c-prev" }, "C"),
110 | // h("p", { key: "D" }, "D"),
111 | // h("p", { key: "F" }, "F"),
112 | // h("p", { key: "G" }, "G"),
113 | // ];
114 | // 创建新的节点
115 | // (a b c)(d e f g)
116 | // (a b c) n (d e f g)
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: "Old" }, "Old"),
122 | // h("p", { key: "E" }, "E"),
123 | // h("p", { key: "F" }, "F"),
124 | // h("p", { key: "G" }, "G"),
125 | // ];
126 | // const nextChildren = [
127 | // h("p", { key: "A" }, "A"),
128 | // h("p", { key: "B" }, "B"),
129 | // h("p", { key: "C", id: "c-prev" }, "C"),
130 | // h("p", { key: "New" }, "New"),
131 | // h("p", { key: "E" }, "E"),
132 | // h("p", { key: "F" }, "F"),
133 | // h("p", { key: "G" }, "G"),
134 | // ];
135 | export default {
136 | name: "ArrayToArray",
137 | setup() {
138 | const isChange = ref(false);
139 | window.isChange = isChange;
140 |
141 | return {
142 | isChange,
143 | };
144 | },
145 | render() {
146 | const self = this;
147 | return self.isChange === true
148 | ? h("div", {}, nextChildren)
149 | : h("div", {}, prevChildren);
150 | },
151 | };
152 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/mini-vue.esm.js";
2 |
3 | const nextChildren = "newChildren";
4 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
5 |
6 | export default {
7 | name: "ArrayToText",
8 | setup() {
9 | const isChange = ref(false);
10 | window.isChange = isChange;
11 |
12 | return {
13 | isChange,
14 | };
15 | },
16 | render() {
17 | const self = this;
18 | return self.isChange === true
19 | ? h("div", {}, nextChildren)
20 | : h("div", {}, prevChildren);
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/mini-vue.esm.js";
2 |
3 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
4 | const prevChildren = "oldChildren";
5 |
6 | export default {
7 | name: "TextToArray",
8 | setup() {
9 | const isChange = ref(false);
10 | window.isChange = isChange;
11 |
12 | return {
13 | isChange,
14 | };
15 | },
16 | render() {
17 | const self = this;
18 | console.log(self.isChange);
19 | return self.isChange === true
20 | ? h("div", {}, nextChildren)
21 | : h("div", {}, prevChildren);
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/mini-vue.esm.js";
2 |
3 | const nextChildren = "newChildren";
4 | const prevChildren = "oldChildren";
5 |
6 | export default {
7 | name: "TextToText",
8 | setup() {
9 | const isChange = ref(false);
10 | window.isChange = isChange;
11 |
12 | return {
13 | isChange,
14 | };
15 | },
16 | render() {
17 | const self = this;
18 | console.log(self.isChange);
19 | return self.isChange === true
20 | ? h("div", {}, nextChildren)
21 | : h("div", {}, prevChildren);
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/example/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | console.log("app vnode", App);
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/mini-vue.esm.js";
2 | import { Foo } from "./Props.js";
3 | window.self = null;
4 | const App = {
5 | name: "app",
6 | setup() {
7 | const count = ref(0);
8 |
9 | const onClick = () => {
10 | count.value++;
11 | };
12 | const props = ref({
13 | foo: "foo",
14 | bar: "bar",
15 | });
16 | const onChangePropsDemo1 = () => {
17 | props.value.foo = "new foo";
18 | };
19 | const onChangePropsDemo2 = () => {
20 | props.value.foo = undefined;
21 | };
22 | const onChangePropsDemo3 = () => {
23 | props.value = { foo: "foo" };
24 | };
25 | return {
26 | count,
27 | onClick,
28 | onChangePropsDemo1,
29 | onChangePropsDemo2,
30 | onChangePropsDemo3,
31 | props,
32 | };
33 | },
34 | render() {
35 | // this.count get this.count.value
36 | return h(
37 | "div",
38 | {
39 | id: "root",
40 | ...this.props,
41 | },
42 | [
43 | h("div", {}, "count:" + this.count),
44 | h("p", {}, this.props),
45 | h("button", { onClick: this.onClick }, "Click"),
46 | h(
47 | "button",
48 | { onClick: this.onChangePropsDemo1 },
49 | "ChangePropsDemo1 修改值"
50 | ),
51 | h(
52 | "button",
53 | { onClick: this.onChangePropsDemo2 },
54 | "ChangePropsDemo2 修改成undifined"
55 | ),
56 | h(
57 | "button",
58 | { onClick: this.onChangePropsDemo3 },
59 | "ChangePropsDemo3 删除值"
60 | ),
61 | ]
62 | );
63 | },
64 | };
65 | export { App };
66 |
--------------------------------------------------------------------------------
/example/update/Props.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | export const Foo = {
3 | name: "props",
4 | setup(props) {
5 | // 1.setup传入
6 | // 2.通过this访问到props的值
7 | // 3.不可以被修改 readonly:shadowReadonly
8 | // props.count
9 | props.count++;
10 | console.log(props);
11 | },
12 | render() {
13 | return h("div", { class: "red" }, "count:" + this.count);
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | minivue
8 |
9 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.querySelector("#app");
4 | createApp(App).mount(rootContainer);
5 |
--------------------------------------------------------------------------------
/lib/mini-vue.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | var ShapeFlags;
6 | (function (ShapeFlags) {
7 | ShapeFlags[ShapeFlags["ELEMENT"] = 1] = "ELEMENT";
8 | ShapeFlags[ShapeFlags["STATEFUL_COMPONENT"] = 2] = "STATEFUL_COMPONENT";
9 | ShapeFlags[ShapeFlags["TEXT_CHILDREN"] = 4] = "TEXT_CHILDREN";
10 | ShapeFlags[ShapeFlags["ARRAY_CHILDREN"] = 8] = "ARRAY_CHILDREN";
11 | ShapeFlags[ShapeFlags["SLOT_CHILDREN"] = 16] = "SLOT_CHILDREN";
12 | })(ShapeFlags || (ShapeFlags = {}));
13 | function getShapeFlag(type) {
14 | return typeof type === "string" ? 1 /* ELEMENT */ : 2 /* STATEFUL_COMPONENT */;
15 | }
16 |
17 | const Fragment = Symbol("Fragment");
18 | const Text = Symbol("Text");
19 | function createVNode(type, props, children) {
20 | const VNode = {
21 | el: null,
22 | type,
23 | props: props || {},
24 | children,
25 | component: null,
26 | next: null,
27 | key: props === null || props === void 0 ? void 0 : props.key,
28 | shapeFlag: getShapeFlag(type),
29 | };
30 | // children?
31 | if (typeof children === "string") {
32 | VNode.shapeFlag |= 4 /* TEXT_CHILDREN */;
33 | }
34 | else if (Array.isArray(children)) {
35 | VNode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
36 | }
37 | if (VNode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
38 | if (typeof children === "object") {
39 | VNode.shapeFlag |= 16 /* SLOT_CHILDREN */;
40 | }
41 | }
42 | return VNode;
43 | }
44 | function createTextVNode(text) {
45 | return createVNode(Text, {}, text);
46 | }
47 |
48 | function createAppAPI(render) {
49 | return function createApp(rootComponent) {
50 | return {
51 | mount(rootContainer) {
52 | //先转化为虚拟节点vnode
53 | // component => vnode
54 | const vnode = createVNode(rootComponent);
55 | render(vnode, rootContainer);
56 | }
57 | };
58 | };
59 | }
60 |
61 | function h(type, props, children) {
62 | return createVNode(type, props, children);
63 | }
64 |
65 | function renderSlots(slots, name, props) {
66 | const slot = slots[name];
67 | if (slot) {
68 | // function
69 | if (typeof slot === 'function') {
70 | //children 不可以有 array
71 | // 只需要把 children
72 | return createVNode(Fragment, {}, slot(props));
73 | }
74 | }
75 | }
76 |
77 | function initProps(instance, rawProps) {
78 | // attrs
79 | instance.props = rawProps || {};
80 | }
81 |
82 | function initSlots(instance, children) {
83 | // instance.slots = Array.isArray(children) ? children : [children];
84 | const { vnode } = instance;
85 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
86 | normalizeObjectSlots(instance.slots, children);
87 | }
88 | }
89 | function normalizeObjectSlots(slots, children) {
90 | for (const key in children) {
91 | const value = children[key];
92 | slots[key] = (props) => normalizeSlotValue(value(props));
93 | }
94 | }
95 | function normalizeSlotValue(value) {
96 | return Array.isArray(value) ? value : [value];
97 | }
98 |
99 | let activeEffect;
100 | let shouldTrack = false;
101 | const targetMap = new Map();
102 | class ReactiveEffect {
103 | constructor(fn, scheduler) {
104 | this.deps = [];
105 | this.active = true;
106 | this._fn = fn;
107 | this.scheduler = scheduler;
108 | }
109 | run() {
110 | if (!this.active) {
111 | return this._fn();
112 | }
113 | shouldTrack = true;
114 | activeEffect = this;
115 | const result = this._fn();
116 | shouldTrack = false;
117 | activeEffect = undefined;
118 | return result;
119 | }
120 | stop() {
121 | if (this.active) {
122 | cleanupEffect(this);
123 | if (this.onStop) {
124 | this.onStop();
125 | }
126 | this.active = false;
127 | }
128 | }
129 | }
130 | function cleanupEffect(effect) {
131 | effect.deps.forEach((dep) => {
132 | dep.delete(effect);
133 | });
134 | effect.deps.length = 0;
135 | }
136 | function track(target, key) {
137 | if (!isTracking()) {
138 | return;
139 | }
140 | // target => key => dep
141 | let depsMap = targetMap.get(target);
142 | if (!depsMap) {
143 | depsMap = new Map();
144 | targetMap.set(target, depsMap);
145 | }
146 | let dep = depsMap.get(key);
147 | if (!dep) {
148 | dep = new Set();
149 | depsMap.set(key, dep);
150 | }
151 | trackEffects(dep);
152 | }
153 | function trackEffects(dep) {
154 | // 判断dep是不是已经添加了这个activeEffect
155 | if (dep.has(activeEffect))
156 | return;
157 | dep.add(activeEffect);
158 | activeEffect.deps.push(dep);
159 | }
160 | function trigger(target, key) {
161 | let depsMap = targetMap.get(target);
162 | let dep = depsMap.get(key);
163 | triggerEffects(dep);
164 | }
165 | function triggerEffects(dep) {
166 | for (const effect of dep) {
167 | if (effect.scheduler) {
168 | effect.scheduler();
169 | }
170 | else {
171 | effect.run();
172 | }
173 | }
174 | }
175 | function effect(fn, options = {}) {
176 | //fn
177 | const _effect = new ReactiveEffect(fn, options.scheduler);
178 | // options
179 | // Object.assign(_effect,options);
180 | //extend
181 | extend(_effect, options);
182 | _effect.run();
183 | const runner = _effect.run.bind(_effect);
184 | runner.effect = _effect;
185 | return runner;
186 | }
187 | function isTracking() {
188 | return shouldTrack && activeEffect !== undefined;
189 | }
190 |
191 | class RefImp {
192 | constructor(value) {
193 | this.__v_isRef = true;
194 | this._rawValue = value;
195 | // 判断value是不是 一个对象
196 | this._value = convert(value);
197 | this.dep = new Set();
198 | }
199 | get value() {
200 | trackRefValue(this);
201 | return this._value;
202 | }
203 | set value(newValue) {
204 | // 判断value的值是否修改 对比的时候应该是两个Object对象而不是Proxy对象
205 | if (!hasChanged(this._rawValue, newValue))
206 | return;
207 | this._rawValue = newValue;
208 | this._value = convert(newValue);
209 | triggerEffects(this.dep);
210 | return;
211 | }
212 | }
213 | function trackRefValue(ref) {
214 | if (isTracking()) {
215 | trackEffects(ref.dep);
216 | }
217 | }
218 | function ref(value) {
219 | return new RefImp(value);
220 | }
221 | function isRef(ref) {
222 | return !!ref.__v_isRef;
223 | }
224 | function unRef(ref) {
225 | return isRef(ref) ? ref.value : ref;
226 | }
227 | function proxyRefs(objectWithRef) {
228 | return new Proxy(objectWithRef, withRefHandlers);
229 | }
230 |
231 | const get = createGetter();
232 | const set = createSetter();
233 | const readonlyGet = createGetter(true);
234 | const shallowReadonlyGet = createGetter(true, true);
235 | function createGetter(isReadonly = false, isShallow = false) {
236 | return function get(target, key) {
237 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
238 | return !isReadonly;
239 | }
240 | else if (key === "__v_isReadOnly" /* IS_READONLY */) {
241 | return isReadonly;
242 | }
243 | const res = Reflect.get(target, key);
244 | // 判断shallow 直接返回res
245 | if (isShallow)
246 | return res;
247 | // 判断 res是不是一个Object
248 | if (isObject(res)) {
249 | return isReadonly ? readonly(res) : reactive(res);
250 | }
251 | if (!isReadonly) {
252 | track(target, key);
253 | }
254 | return res;
255 | };
256 | }
257 | function createSetter(isReadonly = false) {
258 | return function set(target, key, value) {
259 | const res = Reflect.set(target, key, value);
260 | if (!isReadonly) {
261 | // trigger 触发依赖
262 | trigger(target, key);
263 | }
264 | return res;
265 | };
266 | }
267 | const mutableHandlers = {
268 | get,
269 | set
270 | };
271 | const readonlyHandlers = {
272 | get: readonlyGet,
273 | set(target, key) {
274 | console.warn(`${target} is readonly,could't set ${key}!`, target);
275 | return true;
276 | }
277 | };
278 | const shallowReadonlyHandlers = Object.assign({}, readonlyHandlers, {
279 | get: shallowReadonlyGet
280 | });
281 | // get => age(ref包裹) 返回 .value
282 | // 如果没有被ref包裹 返回 本身的值
283 | const withRefHandlers = {
284 | get(target, key) {
285 | return unRef(Reflect.get(target, key));
286 | },
287 | set(target, key, value) {
288 | if (isRef(target[key]) && !isRef(value)) {
289 | return target[key].value = value;
290 | }
291 | else {
292 | return Reflect.set(target, key, value);
293 | }
294 | }
295 | };
296 |
297 | var ReactiveFlags;
298 | (function (ReactiveFlags) {
299 | ReactiveFlags["IS_REACTIVE"] = "__v_isReactive";
300 | ReactiveFlags["IS_READONLY"] = "__v_isReadOnly";
301 | })(ReactiveFlags || (ReactiveFlags = {}));
302 | function reactive(raw) {
303 | return createActiveObject(raw, mutableHandlers);
304 | }
305 | function readonly(raw) {
306 | return createActiveObject(raw, readonlyHandlers);
307 | }
308 | function shallowReadonly(raw) {
309 | return createActiveObject(raw, shallowReadonlyHandlers);
310 | }
311 | function createActiveObject(target, baseHandlers) {
312 | if (!isObject(target)) {
313 | console.warn(`target:${target} must be a Object!`);
314 | }
315 | return new Proxy(target, baseHandlers);
316 | }
317 |
318 | function toDisplayString(value) {
319 | return String(value);
320 | }
321 |
322 | const extend = Object.assign;
323 | const isObject = (val) => {
324 | return val !== null && typeof val === "object";
325 | };
326 | const isString = (val) => typeof val === "string";
327 | const hasChanged = (val, newVal) => {
328 | return !Object.is(val, newVal);
329 | };
330 | const convert = (newValue) => {
331 | return isObject(newValue) ? reactive(newValue) : newValue;
332 | };
333 | const isOn = (event) => {
334 | return /^on[A-Z]/.test(event);
335 | };
336 | const hasOwn = (properties, key) => {
337 | return Object.prototype.hasOwnProperty.call(properties, key);
338 | };
339 | // TPP 先写一个特定的行为 再重构成一个通用的行为
340 | // add => Add
341 | const capitalize = (str) => {
342 | return str.charAt(0).toUpperCase() + str.slice(1);
343 | };
344 | const toHandleKey = (str) => {
345 | return str ? "on" + capitalize(str) : "";
346 | };
347 | // add-foo => addFoo
348 | const cameLize = (str) => {
349 | return str.replace(/-(\w)/g, (_, c) => {
350 | return c ? c.toUpperCase() : "";
351 | });
352 | };
353 |
354 | const publicPropertiesMap = {
355 | $el: (i) => i.vnode.el,
356 | $slots: i => i.slots,
357 | $props: i => i.props
358 | };
359 | const PublicInstanceProxyHandlers = {
360 | get({ _: instance }, key) {
361 | // setupState
362 | const { setupState, props } = instance;
363 | if (hasOwn(setupState, key)) {
364 | return setupState[key];
365 | }
366 | else if (hasOwn(props, key)) {
367 | return props[key];
368 | }
369 | const publicGetter = publicPropertiesMap[key];
370 | if (publicGetter) {
371 | return publicGetter(instance);
372 | }
373 | }
374 | };
375 |
376 | function emit(instance, event, ...args) {
377 | // instance.props => event
378 | const { props } = instance;
379 | const handlerName = toHandleKey(event);
380 | const handler = props[cameLize(handlerName)];
381 | handler && handler(...args);
382 | }
383 |
384 | function createComponentInstance(vnode, parent) {
385 | const instance = {
386 | vnode,
387 | type: vnode.type,
388 | setupState: {},
389 | props: {},
390 | slots: {},
391 | provides: parent ? parent.provides : {},
392 | parent,
393 | isMounted: false,
394 | emit: () => { },
395 | };
396 | // 初始化赋值emit
397 | instance.emit = emit.bind(null, instance);
398 | return instance;
399 | }
400 | function setupInstance(instance) {
401 | initProps(instance, instance.vnode.props);
402 | initSlots(instance, instance.vnode.children);
403 | setupStatefulComponent(instance);
404 | }
405 | function setupStatefulComponent(instance) {
406 | const Component = instance.type;
407 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
408 | const { setup } = Component;
409 | if (setup) {
410 | setCurrentInstance(instance);
411 | const setupResult = setup && setup(shallowReadonly(instance.props), { emit: instance.emit });
412 | setCurrentInstance(null);
413 | handleSetupResult(instance, setupResult);
414 | }
415 | }
416 | function handleSetupResult(instance, setupResult) {
417 | // Object
418 | if (typeof setupResult === "object") {
419 | instance.setupState = proxyRefs(setupResult);
420 | }
421 | finishCompoentSetup(instance);
422 | // function
423 | }
424 | function finishCompoentSetup(instance) {
425 | const Component = instance.type;
426 | // compile
427 | if (compiler && !Component.render) {
428 | if (Component.template) {
429 | Component.render = compiler(Component.template);
430 | }
431 | }
432 | instance.render = Component.render;
433 | }
434 | let currentInstance = null;
435 | // getCurrentInstance
436 | function getCurrentInstance() {
437 | return currentInstance;
438 | }
439 | function setCurrentInstance(instance) {
440 | currentInstance = instance;
441 | }
442 | let compiler;
443 | function registerRuntimeComplier(_compiler) {
444 | compiler = _compiler;
445 | }
446 |
447 | function provide(key, value) {
448 | // set
449 | const currentInstance = getCurrentInstance();
450 | if (currentInstance) {
451 | let { provides } = currentInstance;
452 | const parentProvides = currentInstance.parent.provides;
453 | // 把provide的原型指向parentProvides
454 | // init 的时候执行
455 | if (provides === parentProvides) {
456 | provides = currentInstance.provides = Object.create(parentProvides);
457 | }
458 | provides[key] = value;
459 | }
460 | }
461 | function inject(key, defaultValue) {
462 | // get
463 | const currentInstance = getCurrentInstance();
464 | if (currentInstance) {
465 | const parentProvides = currentInstance.parent.provides;
466 | if (key in parentProvides) {
467 | return parentProvides[key];
468 | }
469 | else if (defaultValue) {
470 | if (typeof defaultValue === 'function') {
471 | return defaultValue();
472 | }
473 | return defaultValue;
474 | }
475 | }
476 | }
477 |
478 | function shouldUpdateComponent(prevVNode, nextVNode) {
479 | const { props: prevProps } = prevVNode;
480 | const { props: nextProps } = nextVNode;
481 | for (const key in nextProps) {
482 | if (nextProps[key] !== prevProps[key]) {
483 | return true;
484 | }
485 | }
486 | return false;
487 | }
488 |
489 | const queue = [];
490 | let isFlushPending = false;
491 | const p = Promise.resolve();
492 | function nextTick(fn) {
493 | return fn ? p.then(fn) : p;
494 | }
495 | function queueJob(job) {
496 | if (!queue.includes(job)) {
497 | queue.push(job);
498 | }
499 | queueFlash();
500 | }
501 | function queueFlash() {
502 | if (isFlushPending)
503 | return;
504 | isFlushPending = true;
505 | nextTick(flushJobs);
506 | }
507 | function flushJobs() {
508 | isFlushPending = false;
509 | let job;
510 | while (job = queue.shift()) {
511 | job && job();
512 | }
513 | }
514 |
515 | function createRender(options) {
516 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, setElementArray: hostSetElementArray, } = options;
517 | function render(vnode, container) {
518 | // shapeFlags
519 | // patch递归
520 | // 判断是不是一个element类型
521 | patch(null, vnode, container);
522 | }
523 | function patch(n1, n2, container, parentComponent = null, anchor = null) {
524 | // todo 判断是不是一个element
525 | // 处理组件
526 | // shapeflag 判断
527 | const { type, shapeFlag } = n2;
528 | // Fragment => 只渲染children
529 | switch (type) {
530 | case Fragment:
531 | processFragment(n1, n2, container, parentComponent);
532 | break;
533 | case Text:
534 | processText(n1, n2, container);
535 | break;
536 | default:
537 | if (shapeFlag & 1 /* ELEMENT */) {
538 | processElement(n1, n2, container, parentComponent, anchor);
539 | }
540 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
541 | processComponent(n1, n2, container, parentComponent);
542 | }
543 | break;
544 | }
545 | }
546 | function processElement(n1, n2, container, parentComponent, anchor) {
547 | if (!n1) {
548 | // init
549 | mountElement(n2, container, parentComponent, anchor);
550 | }
551 | else {
552 | // update
553 | patchElement(n1, n2, container, parentComponent, anchor);
554 | }
555 | }
556 | const EMPTY_OBJ = {};
557 | function patchElement(n1, n2, container, parentComponent, anchor) {
558 | const oldProps = n1.props || EMPTY_OBJ;
559 | const newProps = n2.props || EMPTY_OBJ;
560 | const el = (n2.el = n1.el);
561 | patchChildren(n1, n2, el, parentComponent, anchor);
562 | patchProps(el, oldProps, newProps);
563 | }
564 | function patchChildren(n1, n2, container, parentComponent, anchor) {
565 | const prevShapeFlag = n1.shapeFlag;
566 | const nextShapeFlag = n2.shapeFlag;
567 | const c1 = n1.children;
568 | const c2 = n2.children;
569 | if (nextShapeFlag & 4 /* TEXT_CHILDREN */) {
570 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
571 | // array => text
572 | // 1.把老的children清空
573 | unmountChildren(c1);
574 | // 2.set 新的textchildren
575 | // 如果 n1.children !== n2.children 则直接把n2的文本值设置为container的textContent
576 | // text => text 同时需执行以下
577 | hostSetElementText(container, c2);
578 | }
579 | else {
580 | hostSetElementText(container, c2);
581 | }
582 | }
583 | else {
584 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
585 | // array => text
586 | // 1.把容器的文本内容清空
587 | hostSetElementText(container, "");
588 | mountChildren(c2, container, parentComponent);
589 | }
590 | else {
591 | // array diff => array
592 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
593 | }
594 | }
595 | }
596 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
597 | console.log("patch child");
598 | let i = 0;
599 | let e1 = c1.length - 1;
600 | let e2 = c2.length - 1;
601 | const l2 = c2.length;
602 | // 左侧对比
603 | while (i <= e1 && i <= e2) {
604 | const n1 = c1[i];
605 | const n2 = c2[i];
606 | if (isSameVNodeType(n1, n2)) {
607 | patch(n1, n2, container, parentComponent);
608 | }
609 | else {
610 | break;
611 | }
612 | i++;
613 | }
614 | // 右侧对比
615 | while (i <= e1 && i <= e2) {
616 | const n1 = c1[e1];
617 | const n2 = c2[e2];
618 | if (isSameVNodeType(n1, n2)) {
619 | patch(n1, n2, container, parentComponent);
620 | }
621 | else {
622 | break;
623 | }
624 | e1--;
625 | e2--;
626 | }
627 | if (i > e1) {
628 | // 新的比老的多
629 | if (i <= e2) {
630 | // 如果是这种情况的话就说明 e2 也就是新节点的数量大于旧节点的数量
631 | // 也就是说新增了 vnode
632 | // 应该循环 c2
633 | // 锚点的计算:新的节点有可能需要添加到尾部,也可能添加到头部,所以需要指定添加的问题
634 | // 要添加的位置是当前的位置(e2 开始)+1
635 | // 因为对于往左侧添加的话,应该获取到 c2 的第一个元素
636 | // 所以我们需要从 e2 + 1 取到锚点的位置
637 | const nextPos = e2 + 1;
638 | const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;
639 | while (i <= e2) {
640 | console.log(`需要新创建一个 vnode: ${c2[i].key}`);
641 | patch(null, c2[i], container, parentComponent, anchor);
642 | i++;
643 | }
644 | }
645 | }
646 | else if (i > e2) {
647 | // 新的比老的少
648 | const anchorIndex = i + e1 - e2;
649 | while (i < anchorIndex) {
650 | hostRemove(c1[i].el);
651 | i++;
652 | }
653 | }
654 | else {
655 | // 中间对比
656 | let s1 = i;
657 | let s2 = i;
658 | let isPatchedCount = 0;
659 | const toBePatched = e2 - s2 + 1;
660 | const keyToNewIndexMap = new Map();
661 | const newIndexToOldIndexMap = new Array(toBePatched);
662 | let moved = false;
663 | let maxNewIndexSoFar = 0;
664 | for (let i = 0; i < toBePatched; i++)
665 | newIndexToOldIndexMap[i] = 0;
666 | for (let i = s2; i <= e2; i++) {
667 | const nextChild = c2[i];
668 | keyToNewIndexMap.set(nextChild.key, i);
669 | }
670 | for (let i = s1; i <= e1; i++) {
671 | const prevChild = c1[i];
672 | if (isPatchedCount >= toBePatched) {
673 | hostRemove(prevChild.el);
674 | continue;
675 | }
676 | let newIndex;
677 | if (prevChild.key != null) {
678 | newIndex = keyToNewIndexMap.get(prevChild.key);
679 | }
680 | else {
681 | for (let j = s2; j <= e2; j++) {
682 | if (isSameVNodeType(prevChild, c2[j])) {
683 | newIndex = j;
684 | break;
685 | }
686 | }
687 | }
688 | if (newIndex === undefined) {
689 | hostRemove(prevChild.el);
690 | }
691 | else {
692 | if (newIndex >= maxNewIndexSoFar) {
693 | maxNewIndexSoFar = newIndex;
694 | }
695 | else {
696 | moved = true;
697 | }
698 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
699 | patch(prevChild, c2[newIndex], container, parentComponent, null);
700 | isPatchedCount++;
701 | }
702 | }
703 | const increasingNewIndexSequence = moved
704 | ? getSequence(newIndexToOldIndexMap)
705 | : [];
706 | let j = increasingNewIndexSequence.length - 1;
707 | for (let i = toBePatched - 1; i >= 0; i--) {
708 | const nextIndex = i + s2;
709 | const nextChild = c2[nextIndex];
710 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
711 | if (newIndexToOldIndexMap[i] === 0) {
712 | patch(null, nextChild, container, parentComponent, anchor);
713 | }
714 | else if (moved) {
715 | // 如果判断需要moved
716 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
717 | console.log(anchor);
718 | hostInsert(nextChild.el, container, anchor);
719 | }
720 | else {
721 | j--;
722 | }
723 | }
724 | }
725 | }
726 | }
727 | // 获取最长递增子序列算法
728 | function getSequence(arr) {
729 | const p = arr.slice();
730 | const result = [0];
731 | let i, j, u, v, c;
732 | const len = arr.length;
733 | for (i = 0; i < len; i++) {
734 | const arrI = arr[i];
735 | if (arrI !== 0) {
736 | j = result[result.length - 1];
737 | if (arr[j] < arrI) {
738 | p[i] = j;
739 | result.push(i);
740 | continue;
741 | }
742 | u = 0;
743 | v = result.length - 1;
744 | while (u < v) {
745 | c = (u + v) >> 1;
746 | if (arr[result[c]] < arrI) {
747 | u = c + 1;
748 | }
749 | else {
750 | v = c;
751 | }
752 | }
753 | if (arrI < arr[result[u]]) {
754 | if (u > 0) {
755 | p[i] = result[u - 1];
756 | }
757 | result[u] = i;
758 | }
759 | }
760 | }
761 | u = result.length;
762 | v = result[u - 1];
763 | while (u-- > 0) {
764 | result[u] = v;
765 | v = p[v];
766 | }
767 | return result;
768 | }
769 | function isSameVNodeType(n1, n2) {
770 | return n1.type === n2.type && n1.key === n2.key;
771 | }
772 | function unmountChildren(children) {
773 | for (let i = 0; i < children.length; i++) {
774 | const el = children[i].el;
775 | // remove
776 | hostRemove(el);
777 | }
778 | }
779 | function patchProps(el, oldProps, newProps) {
780 | // 两个props Object判断不相等?
781 | if (oldProps !== newProps) {
782 | for (const key in newProps) {
783 | const prevProp = oldProps[key] ? oldProps[key] : null;
784 | const nextProp = newProps[key];
785 | if (prevProp !== nextProp) {
786 | hostPatchProp(el, key, prevProp, nextProp);
787 | }
788 | }
789 | if (oldProps !== EMPTY_OBJ) {
790 | for (const key in oldProps) {
791 | if (!(key in newProps)) {
792 | hostPatchProp(el, key, oldProps[key], null);
793 | }
794 | }
795 | }
796 | }
797 | }
798 | function processComponent(n1, n2, container, parentComponent) {
799 | if (!n1) {
800 | mountComponent(n2, container, parentComponent);
801 | }
802 | else {
803 | updateComponent(n1, n2);
804 | }
805 | }
806 | function updateComponent(n1, n2) {
807 | const instance = (n2.component = n1.component);
808 | if (shouldUpdateComponent(n1, n2)) {
809 | console.log("组件更新", n1, n2);
810 | instance.next = n2;
811 | instance.update();
812 | }
813 | else {
814 | n2.el = n1.el;
815 | n2.vnode = n2;
816 | }
817 | }
818 | function updateComponentPreRender(instance, nextVNode) {
819 | instance.vnode = nextVNode;
820 | instance.next = null;
821 | instance.props = nextVNode.props;
822 | }
823 | function processFragment(n1, n2, container, parentComponent) {
824 | // Implement
825 | mountChildren(n2.children, container, parentComponent);
826 | }
827 | function processText(n1, n2, container) {
828 | const { children } = n2;
829 | const textNode = (n2.el = document.createTextNode(children));
830 | container.append(textNode);
831 | }
832 | function mountElement(vnode, container, parentComponent, anchor) {
833 | // canvas
834 | // new Element()
835 | // createElement()
836 | const el = (vnode.el = hostCreateElement(vnode.type));
837 | // const el = (vnode.el = document.createElement(vnode.type));
838 | //children: string array
839 | const { children, shapeFlag } = vnode;
840 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
841 | el.textContent = children;
842 | }
843 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
844 | mountChildren(children, el, parentComponent);
845 | }
846 | // props
847 | const { props } = vnode;
848 | // patch Prop
849 | for (const key in props) {
850 | const val = props[key];
851 | hostPatchProp(el, key, null, val);
852 | }
853 | // container.append(el);
854 | hostInsert(el, container, anchor);
855 | }
856 | function mountChildren(children, container, parentComponent) {
857 | children.forEach((child) => {
858 | patch(null, child, container, parentComponent);
859 | });
860 | }
861 | function mountComponent(initalVNode, container, parentComponent) {
862 | const instance = (initalVNode.component = createComponentInstance(initalVNode, parentComponent));
863 | setupInstance(instance);
864 | setupRenderEffect(instance, initalVNode, container);
865 | }
866 | function setupRenderEffect(instance, initalVNode, container) {
867 | // 拆分更新与初始化
868 | instance.update = effect(() => {
869 | if (!instance.isMounted) {
870 | console.log("init");
871 | const { proxy } = instance;
872 | const subTree = (instance.subTree = instance.render.call(proxy, proxy));
873 | // vnode => patch
874 | // vnode => element => mountElement
875 | patch(null, subTree, container, instance);
876 | // element => mounted
877 | initalVNode.el = subTree.el;
878 | instance.isMounted = true;
879 | }
880 | else {
881 | console.log("update");
882 | const { proxy, next, vnode } = instance;
883 | if (next) {
884 | next.el = vnode.el;
885 | updateComponentPreRender(instance, next);
886 | }
887 | const prevSubTree = instance.subTree;
888 | const subTree = (instance.subTree = instance.render.call(proxy, proxy));
889 | // vnode => patch
890 | // vnode => element => mountElement
891 | patch(prevSubTree, subTree, container, instance);
892 | // element => mounted
893 | initalVNode.el = subTree.el;
894 | }
895 | }, {
896 | scheduler() {
897 | console.log("update-----scheduler");
898 | queueJob(instance.update);
899 | },
900 | });
901 | }
902 | return {
903 | createApp: createAppAPI(render),
904 | };
905 | }
906 |
907 | function createElement(type) {
908 | return document.createElement(type);
909 | }
910 | function patchProp(el, key, prevVal, nextVal) {
911 | if (isOn(key)) {
912 | const event = key.slice(2).toLocaleLowerCase();
913 | el.addEventListener(event, () => {
914 | nextVal();
915 | });
916 | }
917 | else {
918 | if (nextVal === undefined || nextVal === null) {
919 | el.removeAttribute(key);
920 | }
921 | else {
922 | el.setAttribute(key, nextVal);
923 | }
924 | }
925 | }
926 | function insert(child, parent, anchor = null) {
927 | console.log('insert', child);
928 | parent.insertBefore(child, anchor);
929 | }
930 | function remove(child) {
931 | const parent = child.parentNode;
932 | if (parent) {
933 | parent.removeChild(child);
934 | }
935 | }
936 | function setElementText(el, text) {
937 | el.textContent = text;
938 | }
939 | function setElementArray(el, children) {
940 | console.log(el);
941 | console.log(children);
942 | }
943 | const render = createRender({
944 | createElement,
945 | patchProp,
946 | insert,
947 | remove,
948 | setElementText,
949 | setElementArray
950 | });
951 | function createApp(...args) {
952 | return render.createApp(...args);
953 | }
954 |
955 | var runtimeDom = /*#__PURE__*/Object.freeze({
956 | __proto__: null,
957 | createApp: createApp,
958 | createAppAPI: createAppAPI,
959 | h: h,
960 | renderSlots: renderSlots,
961 | createTextVNode: createTextVNode,
962 | createElementVNode: createVNode,
963 | getCurrentInstance: getCurrentInstance,
964 | registerRuntimeComplier: registerRuntimeComplier,
965 | inject: inject,
966 | provide: provide,
967 | createRender: createRender,
968 | nextTick: nextTick,
969 | toDisplayString: toDisplayString,
970 | ref: ref,
971 | proxyRefs: proxyRefs
972 | });
973 |
974 | const TO_DISPLAY_STRING = Symbol("toDisplayString");
975 | const CREATE_ELEMENT_VNODE = Symbol("createElementVNode");
976 | const helperMapName = {
977 | [TO_DISPLAY_STRING]: "toDisplayString",
978 | [CREATE_ELEMENT_VNODE]: "createElementVNode",
979 | };
980 |
981 | var NodeTypes;
982 | (function (NodeTypes) {
983 | NodeTypes["INTERPOLATION"] = "interpolation";
984 | NodeTypes["SIMPLE_EXPRESSION"] = "simple_expression";
985 | NodeTypes["ELEMENT"] = "element";
986 | NodeTypes["TEXT"] = "text";
987 | NodeTypes["ROOT"] = "root";
988 | NodeTypes["COMPOUND_EXPRESSION"] = "compound_expression";
989 | })(NodeTypes || (NodeTypes = {}));
990 | function createVNodeCall(context, tag, props, children) {
991 | context.helper(CREATE_ELEMENT_VNODE);
992 | return {
993 | type: NodeTypes.ELEMENT,
994 | tag,
995 | props,
996 | children,
997 | };
998 | }
999 |
1000 | var TagsType;
1001 | (function (TagsType) {
1002 | TagsType["START"] = "start";
1003 | TagsType["END"] = "end";
1004 | })(TagsType || (TagsType = {}));
1005 | function baseParse(content) {
1006 | const context = createParserContext(content);
1007 | return createRoot(parseChildren(context, []));
1008 | }
1009 | function parseChildren(context, ancestors) {
1010 | const nodes = [];
1011 | while (!isLoopEnd(context, ancestors)) {
1012 | let node;
1013 | const s = context.source;
1014 | if (s.startsWith("{{")) {
1015 | node = parseInterpolation(context);
1016 | }
1017 | else if (s[0] === "<") {
1018 | if (/[a-z]/.test(s[1])) {
1019 | node = parseElement(context, ancestors);
1020 | }
1021 | }
1022 | else {
1023 | node = parseText(context);
1024 | }
1025 | nodes.push(node);
1026 | }
1027 | return nodes;
1028 | }
1029 | function isLoopEnd(context, ancestors) {
1030 | //1.source 为空
1031 | const s = context.source;
1032 | if (s.startsWith("")) {
1033 | for (let i = ancestors.length - 1; i >= 0; i--) {
1034 | const tag = ancestors[i].tag;
1035 | if (startsWithEndTagOpen(s, tag)) {
1036 | return true;
1037 | }
1038 | }
1039 | return true;
1040 | }
1041 | //2.碰到结束标签
1042 | return !s;
1043 | }
1044 | function parseText(context) {
1045 | let endTokens = ["{{", "<"];
1046 | let endIndex = context.source.length;
1047 | for (let i = 0; i < endTokens.length; i++) {
1048 | const index = context.source.indexOf(endTokens[i]);
1049 | if (index !== -1 && index < endIndex) {
1050 | endIndex = index;
1051 | }
1052 | }
1053 | const content = parseTextData(context, endIndex);
1054 | return {
1055 | type: NodeTypes.TEXT,
1056 | content,
1057 | };
1058 | }
1059 | function parseTextData(context, length) {
1060 | const rawText = context.source.slice(0, length);
1061 | advanceBy(context, length);
1062 | return rawText;
1063 | }
1064 | function parseElement(context, ancestors) {
1065 | //Implement
1066 | //1.解析
1067 | const element = parseTag(context, "start" /* START */);
1068 | ancestors.push(element);
1069 | element.children = parseChildren(context, ancestors);
1070 | ancestors.pop();
1071 | if (startsWithEndTagOpen(context.source, element.tag)) {
1072 | parseTag(context, "end" /* END */);
1073 | }
1074 | else {
1075 | throw new Error(`缺少结束标签:${element.tag}`);
1076 | }
1077 | return element;
1078 | }
1079 | function startsWithEndTagOpen(source, tag) {
1080 | return (source.startsWith("") &&
1081 | source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase());
1082 | }
1083 | function parseTag(context, type) {
1084 | const match = /^<\/?([a-z]*)/i.exec(context.source);
1085 | const tag = match[1];
1086 | //2.删除处理完成的代码
1087 | advanceBy(context, match[0].length);
1088 | advanceBy(context, 1);
1089 | if (type === "end" /* END */)
1090 | return;
1091 | return {
1092 | type: NodeTypes.ELEMENT,
1093 | tag,
1094 | };
1095 | }
1096 | function parseInterpolation(context) {
1097 | // {{message}}
1098 | const openDelimiter = "{{";
1099 | const closeDelimiter = "}}";
1100 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length);
1101 | advanceBy(context, openDelimiter.length);
1102 | const rawContentLength = closeIndex - openDelimiter.length;
1103 | const rawContent = context.source.slice(0, rawContentLength);
1104 | const content = rawContent.trim();
1105 | advanceBy(context, rawContentLength + closeDelimiter.length);
1106 | return {
1107 | type: NodeTypes.INTERPOLATION,
1108 | content: {
1109 | type: NodeTypes.SIMPLE_EXPRESSION,
1110 | content,
1111 | },
1112 | };
1113 | }
1114 | function advanceBy(context, length) {
1115 | context.source = context.source.slice(length);
1116 | }
1117 | function createRoot(children) {
1118 | return {
1119 | children,
1120 | type: NodeTypes.ROOT,
1121 | };
1122 | }
1123 | function createParserContext(content) {
1124 | return {
1125 | source: content,
1126 | };
1127 | }
1128 |
1129 | function transform(root, options = {}) {
1130 | const context = createTransformContext(root, options);
1131 | // 1.深度优先搜索
1132 | traverseNode(root, context);
1133 | createRootCodegen(root);
1134 | root.helpers = [...context.helpers.keys()];
1135 | }
1136 | function createRootCodegen(root) {
1137 | const child = root.children[0];
1138 | if (child.type === NodeTypes.ELEMENT && child.codegenNode) {
1139 | root.codegenNode = child.codegenNode;
1140 | }
1141 | else {
1142 | root.codegenNode = child;
1143 | }
1144 | }
1145 | function createTransformContext(root, options) {
1146 | const context = {
1147 | root,
1148 | nodeTransforms: options.nodeTransforms || {},
1149 | helpers: new Map(),
1150 | helper(key) {
1151 | context.helpers.set(key, 1);
1152 | },
1153 | };
1154 | return context;
1155 | }
1156 | function traverseNode(node, context) {
1157 | const nodeTransforms = context.nodeTransforms;
1158 | const exitFns = [];
1159 | for (let i = 0; i < nodeTransforms.length; i++) {
1160 | const transform = nodeTransforms[i];
1161 | const onExit = transform(node, context);
1162 | if (onExit)
1163 | exitFns.push(onExit);
1164 | }
1165 | switch (node.type) {
1166 | case NodeTypes.INTERPOLATION:
1167 | context.helper(TO_DISPLAY_STRING);
1168 | break;
1169 | case NodeTypes.ROOT:
1170 | tranverseChildren(node, context);
1171 | break;
1172 | case NodeTypes.ELEMENT:
1173 | tranverseChildren(node, context);
1174 | break;
1175 | }
1176 | // 退出流程
1177 | let i = exitFns.length;
1178 | while (i--) {
1179 | exitFns[i]();
1180 | }
1181 | }
1182 | function tranverseChildren(node, context) {
1183 | const children = node.children;
1184 | for (let i = 0; i < children.length; i++) {
1185 | const node = children[i];
1186 | traverseNode(node, context);
1187 | }
1188 | }
1189 |
1190 | function generate(ast) {
1191 | const context = codegenContext();
1192 | const { push } = context;
1193 | getFunctionPreamble(context, ast);
1194 | push("return ");
1195 | const functionName = "render";
1196 | const args = ["_ctx,_cache"];
1197 | const signature = args.join(", ");
1198 | push(`function ${functionName}(${signature}){return `);
1199 | genNode(ast.codegenNode, context);
1200 | push("}");
1201 | return {
1202 | code: context.code,
1203 | };
1204 | }
1205 | function codegenContext() {
1206 | const context = {
1207 | code: "",
1208 | push(source) {
1209 | context.code += source;
1210 | },
1211 | helper(key) {
1212 | return `${helperMapName[key]}`;
1213 | },
1214 | };
1215 | return context;
1216 | }
1217 | function genNode(node, context) {
1218 | switch (node.type) {
1219 | case NodeTypes.TEXT:
1220 | genText(node, context);
1221 | break;
1222 | case NodeTypes.INTERPOLATION:
1223 | genInterpolation(node, context);
1224 | break;
1225 | case NodeTypes.SIMPLE_EXPRESSION:
1226 | getExpression(node, context);
1227 | break;
1228 | case NodeTypes.ELEMENT:
1229 | getElement(node, context);
1230 | break;
1231 | case NodeTypes.COMPOUND_EXPRESSION:
1232 | getCompoundExpression(node, context);
1233 | break;
1234 | }
1235 | }
1236 | function getFunctionPreamble(context, ast) {
1237 | const { push, helper } = context;
1238 | const vueBinging = "vue";
1239 | const aliasHelpers = (s) => `${helper(s)}:_${helper(s)}`;
1240 | if (ast.helpers.length > 0) {
1241 | push(`const { ${ast.helpers.map(aliasHelpers).join(", ")} } = ${vueBinging}`);
1242 | }
1243 | push("\n");
1244 | }
1245 | function genText(node, context) {
1246 | const { push } = context;
1247 | push(`"${node.content}"`);
1248 | }
1249 | function genInterpolation(node, context) {
1250 | const { push, helper } = context;
1251 | push(`_${helper(TO_DISPLAY_STRING)}(`);
1252 | genNode(node.content, context);
1253 | push(")");
1254 | }
1255 | function getExpression(node, context) {
1256 | const { push } = context;
1257 | push(`${node.content}`);
1258 | }
1259 | function getElement(node, context) {
1260 | const { push, helper } = context;
1261 | const { tag, children, props } = node;
1262 | push(`_${helper(CREATE_ELEMENT_VNODE)}(`);
1263 | genNodeList(genNullalbe([tag, props, children]), context);
1264 | // genNode(children, context);
1265 | push(")");
1266 | }
1267 | function getCompoundExpression(node, context) {
1268 | const { children } = node;
1269 | const { push } = context;
1270 | for (let i = 0; i < children.length; i++) {
1271 | const child = children[i];
1272 | if (isString(child)) {
1273 | push(child);
1274 | }
1275 | else {
1276 | genNode(child, context);
1277 | }
1278 | }
1279 | }
1280 | function genNullalbe(args) {
1281 | return args.map((arg) => arg || "null");
1282 | }
1283 | function genNodeList(nodes, context) {
1284 | const { push } = context;
1285 | for (let i = 0; i < nodes.length; i++) {
1286 | const node = nodes[i];
1287 | if (isString(node)) {
1288 | push(node);
1289 | }
1290 | else {
1291 | genNode(node, context);
1292 | }
1293 | if (i < nodes.length - 1) {
1294 | push(",");
1295 | }
1296 | }
1297 | }
1298 |
1299 | function transformElement(node, context) {
1300 | if (node.type === NodeTypes.ELEMENT) {
1301 | return () => {
1302 | // 中间处理层
1303 | // tag
1304 | const vnodeTag = `"${node.tag}"`;
1305 | // props
1306 | let vnodeProps;
1307 | //children
1308 | const { children } = node;
1309 | let vnodeChildren = children[0];
1310 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);
1311 | };
1312 | }
1313 | }
1314 |
1315 | function transformExpression(node) {
1316 | if (node.type === NodeTypes.INTERPOLATION) {
1317 | return () => {
1318 | node.content = processExpression(node.content);
1319 | };
1320 | }
1321 | }
1322 | function processExpression(node) {
1323 | node.content = `_ctx.${node.content}`;
1324 | return node;
1325 | }
1326 |
1327 | function isText(node) {
1328 | return node.type === NodeTypes.TEXT || NodeTypes.INTERPOLATION;
1329 | }
1330 |
1331 | function transformText(node) {
1332 | const { children } = node;
1333 | if (node.type === NodeTypes.ELEMENT) {
1334 | return () => {
1335 | let currentContainer;
1336 | for (let i = 0; i < children.length; i++) {
1337 | const child = children[i];
1338 | if (isText(child)) {
1339 | for (let j = i + 1; j < children.length; j++) {
1340 | const next = children[j];
1341 | if (isText(next)) {
1342 | if (!currentContainer) {
1343 | currentContainer = children[i] = {
1344 | type: NodeTypes.COMPOUND_EXPRESSION,
1345 | children: [child],
1346 | };
1347 | }
1348 | currentContainer.children.push(" + ");
1349 | currentContainer.children.push(next);
1350 | children.splice(j, 1);
1351 | j--;
1352 | }
1353 | else {
1354 | currentContainer = undefined;
1355 | break;
1356 | }
1357 | }
1358 | }
1359 | }
1360 | };
1361 | }
1362 | }
1363 |
1364 | function baseCompile(template) {
1365 | const ast = baseParse(template);
1366 | transform(ast, {
1367 | nodeTransforms: [transformExpression, transformElement, transformText],
1368 | });
1369 | return generate(ast);
1370 | }
1371 |
1372 | // mini-vue 出口
1373 | function complieToFunction(template) {
1374 | const { code } = baseCompile(template);
1375 | const render = new Function("vue", code)(runtimeDom);
1376 | return render;
1377 | }
1378 | registerRuntimeComplier(complieToFunction);
1379 |
1380 | exports.createApp = createApp;
1381 | exports.createAppAPI = createAppAPI;
1382 | exports.createElementVNode = createVNode;
1383 | exports.createRender = createRender;
1384 | exports.createTextVNode = createTextVNode;
1385 | exports.getCurrentInstance = getCurrentInstance;
1386 | exports.h = h;
1387 | exports.inject = inject;
1388 | exports.nextTick = nextTick;
1389 | exports.provide = provide;
1390 | exports.proxyRefs = proxyRefs;
1391 | exports.ref = ref;
1392 | exports.registerRuntimeComplier = registerRuntimeComplier;
1393 | exports.renderSlots = renderSlots;
1394 | exports.toDisplayString = toDisplayString;
1395 |
--------------------------------------------------------------------------------
/lib/mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | var ShapeFlags;
2 | (function (ShapeFlags) {
3 | ShapeFlags[ShapeFlags["ELEMENT"] = 1] = "ELEMENT";
4 | ShapeFlags[ShapeFlags["STATEFUL_COMPONENT"] = 2] = "STATEFUL_COMPONENT";
5 | ShapeFlags[ShapeFlags["TEXT_CHILDREN"] = 4] = "TEXT_CHILDREN";
6 | ShapeFlags[ShapeFlags["ARRAY_CHILDREN"] = 8] = "ARRAY_CHILDREN";
7 | ShapeFlags[ShapeFlags["SLOT_CHILDREN"] = 16] = "SLOT_CHILDREN";
8 | })(ShapeFlags || (ShapeFlags = {}));
9 | function getShapeFlag(type) {
10 | return typeof type === "string" ? 1 /* ELEMENT */ : 2 /* STATEFUL_COMPONENT */;
11 | }
12 |
13 | const Fragment = Symbol("Fragment");
14 | const Text = Symbol("Text");
15 | function createVNode(type, props, children) {
16 | const VNode = {
17 | el: null,
18 | type,
19 | props: props || {},
20 | children,
21 | component: null,
22 | next: null,
23 | key: props === null || props === void 0 ? void 0 : props.key,
24 | shapeFlag: getShapeFlag(type),
25 | };
26 | // children?
27 | if (typeof children === "string") {
28 | VNode.shapeFlag |= 4 /* TEXT_CHILDREN */;
29 | }
30 | else if (Array.isArray(children)) {
31 | VNode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
32 | }
33 | if (VNode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
34 | if (typeof children === "object") {
35 | VNode.shapeFlag |= 16 /* SLOT_CHILDREN */;
36 | }
37 | }
38 | return VNode;
39 | }
40 | function createTextVNode(text) {
41 | return createVNode(Text, {}, text);
42 | }
43 |
44 | function createAppAPI(render) {
45 | return function createApp(rootComponent) {
46 | return {
47 | mount(rootContainer) {
48 | //先转化为虚拟节点vnode
49 | // component => vnode
50 | const vnode = createVNode(rootComponent);
51 | render(vnode, rootContainer);
52 | }
53 | };
54 | };
55 | }
56 |
57 | function h(type, props, children) {
58 | return createVNode(type, props, children);
59 | }
60 |
61 | function renderSlots(slots, name, props) {
62 | const slot = slots[name];
63 | if (slot) {
64 | // function
65 | if (typeof slot === 'function') {
66 | //children 不可以有 array
67 | // 只需要把 children
68 | return createVNode(Fragment, {}, slot(props));
69 | }
70 | }
71 | }
72 |
73 | function initProps(instance, rawProps) {
74 | // attrs
75 | instance.props = rawProps || {};
76 | }
77 |
78 | function initSlots(instance, children) {
79 | // instance.slots = Array.isArray(children) ? children : [children];
80 | const { vnode } = instance;
81 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
82 | normalizeObjectSlots(instance.slots, children);
83 | }
84 | }
85 | function normalizeObjectSlots(slots, children) {
86 | for (const key in children) {
87 | const value = children[key];
88 | slots[key] = (props) => normalizeSlotValue(value(props));
89 | }
90 | }
91 | function normalizeSlotValue(value) {
92 | return Array.isArray(value) ? value : [value];
93 | }
94 |
95 | let activeEffect;
96 | let shouldTrack = false;
97 | const targetMap = new Map();
98 | class ReactiveEffect {
99 | constructor(fn, scheduler) {
100 | this.deps = [];
101 | this.active = true;
102 | this._fn = fn;
103 | this.scheduler = scheduler;
104 | }
105 | run() {
106 | if (!this.active) {
107 | return this._fn();
108 | }
109 | shouldTrack = true;
110 | activeEffect = this;
111 | const result = this._fn();
112 | shouldTrack = false;
113 | activeEffect = undefined;
114 | return result;
115 | }
116 | stop() {
117 | if (this.active) {
118 | cleanupEffect(this);
119 | if (this.onStop) {
120 | this.onStop();
121 | }
122 | this.active = false;
123 | }
124 | }
125 | }
126 | function cleanupEffect(effect) {
127 | effect.deps.forEach((dep) => {
128 | dep.delete(effect);
129 | });
130 | effect.deps.length = 0;
131 | }
132 | function track(target, key) {
133 | if (!isTracking()) {
134 | return;
135 | }
136 | // target => key => dep
137 | let depsMap = targetMap.get(target);
138 | if (!depsMap) {
139 | depsMap = new Map();
140 | targetMap.set(target, depsMap);
141 | }
142 | let dep = depsMap.get(key);
143 | if (!dep) {
144 | dep = new Set();
145 | depsMap.set(key, dep);
146 | }
147 | trackEffects(dep);
148 | }
149 | function trackEffects(dep) {
150 | // 判断dep是不是已经添加了这个activeEffect
151 | if (dep.has(activeEffect))
152 | return;
153 | dep.add(activeEffect);
154 | activeEffect.deps.push(dep);
155 | }
156 | function trigger(target, key) {
157 | let depsMap = targetMap.get(target);
158 | let dep = depsMap.get(key);
159 | triggerEffects(dep);
160 | }
161 | function triggerEffects(dep) {
162 | for (const effect of dep) {
163 | if (effect.scheduler) {
164 | effect.scheduler();
165 | }
166 | else {
167 | effect.run();
168 | }
169 | }
170 | }
171 | function effect(fn, options = {}) {
172 | //fn
173 | const _effect = new ReactiveEffect(fn, options.scheduler);
174 | // options
175 | // Object.assign(_effect,options);
176 | //extend
177 | extend(_effect, options);
178 | _effect.run();
179 | const runner = _effect.run.bind(_effect);
180 | runner.effect = _effect;
181 | return runner;
182 | }
183 | function isTracking() {
184 | return shouldTrack && activeEffect !== undefined;
185 | }
186 |
187 | class RefImp {
188 | constructor(value) {
189 | this.__v_isRef = true;
190 | this._rawValue = value;
191 | // 判断value是不是 一个对象
192 | this._value = convert(value);
193 | this.dep = new Set();
194 | }
195 | get value() {
196 | trackRefValue(this);
197 | return this._value;
198 | }
199 | set value(newValue) {
200 | // 判断value的值是否修改 对比的时候应该是两个Object对象而不是Proxy对象
201 | if (!hasChanged(this._rawValue, newValue))
202 | return;
203 | this._rawValue = newValue;
204 | this._value = convert(newValue);
205 | triggerEffects(this.dep);
206 | return;
207 | }
208 | }
209 | function trackRefValue(ref) {
210 | if (isTracking()) {
211 | trackEffects(ref.dep);
212 | }
213 | }
214 | function ref(value) {
215 | return new RefImp(value);
216 | }
217 | function isRef(ref) {
218 | return !!ref.__v_isRef;
219 | }
220 | function unRef(ref) {
221 | return isRef(ref) ? ref.value : ref;
222 | }
223 | function proxyRefs(objectWithRef) {
224 | return new Proxy(objectWithRef, withRefHandlers);
225 | }
226 |
227 | const get = createGetter();
228 | const set = createSetter();
229 | const readonlyGet = createGetter(true);
230 | const shallowReadonlyGet = createGetter(true, true);
231 | function createGetter(isReadonly = false, isShallow = false) {
232 | return function get(target, key) {
233 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
234 | return !isReadonly;
235 | }
236 | else if (key === "__v_isReadOnly" /* IS_READONLY */) {
237 | return isReadonly;
238 | }
239 | const res = Reflect.get(target, key);
240 | // 判断shallow 直接返回res
241 | if (isShallow)
242 | return res;
243 | // 判断 res是不是一个Object
244 | if (isObject(res)) {
245 | return isReadonly ? readonly(res) : reactive(res);
246 | }
247 | if (!isReadonly) {
248 | track(target, key);
249 | }
250 | return res;
251 | };
252 | }
253 | function createSetter(isReadonly = false) {
254 | return function set(target, key, value) {
255 | const res = Reflect.set(target, key, value);
256 | if (!isReadonly) {
257 | // trigger 触发依赖
258 | trigger(target, key);
259 | }
260 | return res;
261 | };
262 | }
263 | const mutableHandlers = {
264 | get,
265 | set
266 | };
267 | const readonlyHandlers = {
268 | get: readonlyGet,
269 | set(target, key) {
270 | console.warn(`${target} is readonly,could't set ${key}!`, target);
271 | return true;
272 | }
273 | };
274 | const shallowReadonlyHandlers = Object.assign({}, readonlyHandlers, {
275 | get: shallowReadonlyGet
276 | });
277 | // get => age(ref包裹) 返回 .value
278 | // 如果没有被ref包裹 返回 本身的值
279 | const withRefHandlers = {
280 | get(target, key) {
281 | return unRef(Reflect.get(target, key));
282 | },
283 | set(target, key, value) {
284 | if (isRef(target[key]) && !isRef(value)) {
285 | return target[key].value = value;
286 | }
287 | else {
288 | return Reflect.set(target, key, value);
289 | }
290 | }
291 | };
292 |
293 | var ReactiveFlags;
294 | (function (ReactiveFlags) {
295 | ReactiveFlags["IS_REACTIVE"] = "__v_isReactive";
296 | ReactiveFlags["IS_READONLY"] = "__v_isReadOnly";
297 | })(ReactiveFlags || (ReactiveFlags = {}));
298 | function reactive(raw) {
299 | return createActiveObject(raw, mutableHandlers);
300 | }
301 | function readonly(raw) {
302 | return createActiveObject(raw, readonlyHandlers);
303 | }
304 | function shallowReadonly(raw) {
305 | return createActiveObject(raw, shallowReadonlyHandlers);
306 | }
307 | function createActiveObject(target, baseHandlers) {
308 | if (!isObject(target)) {
309 | console.warn(`target:${target} must be a Object!`);
310 | }
311 | return new Proxy(target, baseHandlers);
312 | }
313 |
314 | function toDisplayString(value) {
315 | return String(value);
316 | }
317 |
318 | const extend = Object.assign;
319 | const isObject = (val) => {
320 | return val !== null && typeof val === "object";
321 | };
322 | const isString = (val) => typeof val === "string";
323 | const hasChanged = (val, newVal) => {
324 | return !Object.is(val, newVal);
325 | };
326 | const convert = (newValue) => {
327 | return isObject(newValue) ? reactive(newValue) : newValue;
328 | };
329 | const isOn = (event) => {
330 | return /^on[A-Z]/.test(event);
331 | };
332 | const hasOwn = (properties, key) => {
333 | return Object.prototype.hasOwnProperty.call(properties, key);
334 | };
335 | // TPP 先写一个特定的行为 再重构成一个通用的行为
336 | // add => Add
337 | const capitalize = (str) => {
338 | return str.charAt(0).toUpperCase() + str.slice(1);
339 | };
340 | const toHandleKey = (str) => {
341 | return str ? "on" + capitalize(str) : "";
342 | };
343 | // add-foo => addFoo
344 | const cameLize = (str) => {
345 | return str.replace(/-(\w)/g, (_, c) => {
346 | return c ? c.toUpperCase() : "";
347 | });
348 | };
349 |
350 | const publicPropertiesMap = {
351 | $el: (i) => i.vnode.el,
352 | $slots: i => i.slots,
353 | $props: i => i.props
354 | };
355 | const PublicInstanceProxyHandlers = {
356 | get({ _: instance }, key) {
357 | // setupState
358 | const { setupState, props } = instance;
359 | if (hasOwn(setupState, key)) {
360 | return setupState[key];
361 | }
362 | else if (hasOwn(props, key)) {
363 | return props[key];
364 | }
365 | const publicGetter = publicPropertiesMap[key];
366 | if (publicGetter) {
367 | return publicGetter(instance);
368 | }
369 | }
370 | };
371 |
372 | function emit(instance, event, ...args) {
373 | // instance.props => event
374 | const { props } = instance;
375 | const handlerName = toHandleKey(event);
376 | const handler = props[cameLize(handlerName)];
377 | handler && handler(...args);
378 | }
379 |
380 | function createComponentInstance(vnode, parent) {
381 | const instance = {
382 | vnode,
383 | type: vnode.type,
384 | setupState: {},
385 | props: {},
386 | slots: {},
387 | provides: parent ? parent.provides : {},
388 | parent,
389 | isMounted: false,
390 | emit: () => { },
391 | };
392 | // 初始化赋值emit
393 | instance.emit = emit.bind(null, instance);
394 | return instance;
395 | }
396 | function setupInstance(instance) {
397 | initProps(instance, instance.vnode.props);
398 | initSlots(instance, instance.vnode.children);
399 | setupStatefulComponent(instance);
400 | }
401 | function setupStatefulComponent(instance) {
402 | const Component = instance.type;
403 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
404 | const { setup } = Component;
405 | if (setup) {
406 | setCurrentInstance(instance);
407 | const setupResult = setup && setup(shallowReadonly(instance.props), { emit: instance.emit });
408 | setCurrentInstance(null);
409 | handleSetupResult(instance, setupResult);
410 | }
411 | }
412 | function handleSetupResult(instance, setupResult) {
413 | // Object
414 | if (typeof setupResult === "object") {
415 | instance.setupState = proxyRefs(setupResult);
416 | }
417 | finishCompoentSetup(instance);
418 | // function
419 | }
420 | function finishCompoentSetup(instance) {
421 | const Component = instance.type;
422 | // compile
423 | if (compiler && !Component.render) {
424 | if (Component.template) {
425 | Component.render = compiler(Component.template);
426 | }
427 | }
428 | instance.render = Component.render;
429 | }
430 | let currentInstance = null;
431 | // getCurrentInstance
432 | function getCurrentInstance() {
433 | return currentInstance;
434 | }
435 | function setCurrentInstance(instance) {
436 | currentInstance = instance;
437 | }
438 | let compiler;
439 | function registerRuntimeComplier(_compiler) {
440 | compiler = _compiler;
441 | }
442 |
443 | function provide(key, value) {
444 | // set
445 | const currentInstance = getCurrentInstance();
446 | if (currentInstance) {
447 | let { provides } = currentInstance;
448 | const parentProvides = currentInstance.parent.provides;
449 | // 把provide的原型指向parentProvides
450 | // init 的时候执行
451 | if (provides === parentProvides) {
452 | provides = currentInstance.provides = Object.create(parentProvides);
453 | }
454 | provides[key] = value;
455 | }
456 | }
457 | function inject(key, defaultValue) {
458 | // get
459 | const currentInstance = getCurrentInstance();
460 | if (currentInstance) {
461 | const parentProvides = currentInstance.parent.provides;
462 | if (key in parentProvides) {
463 | return parentProvides[key];
464 | }
465 | else if (defaultValue) {
466 | if (typeof defaultValue === 'function') {
467 | return defaultValue();
468 | }
469 | return defaultValue;
470 | }
471 | }
472 | }
473 |
474 | function shouldUpdateComponent(prevVNode, nextVNode) {
475 | const { props: prevProps } = prevVNode;
476 | const { props: nextProps } = nextVNode;
477 | for (const key in nextProps) {
478 | if (nextProps[key] !== prevProps[key]) {
479 | return true;
480 | }
481 | }
482 | return false;
483 | }
484 |
485 | const queue = [];
486 | let isFlushPending = false;
487 | const p = Promise.resolve();
488 | function nextTick(fn) {
489 | return fn ? p.then(fn) : p;
490 | }
491 | function queueJob(job) {
492 | if (!queue.includes(job)) {
493 | queue.push(job);
494 | }
495 | queueFlash();
496 | }
497 | function queueFlash() {
498 | if (isFlushPending)
499 | return;
500 | isFlushPending = true;
501 | nextTick(flushJobs);
502 | }
503 | function flushJobs() {
504 | isFlushPending = false;
505 | let job;
506 | while (job = queue.shift()) {
507 | job && job();
508 | }
509 | }
510 |
511 | function createRender(options) {
512 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, setElementArray: hostSetElementArray, } = options;
513 | function render(vnode, container) {
514 | // shapeFlags
515 | // patch递归
516 | // 判断是不是一个element类型
517 | patch(null, vnode, container);
518 | }
519 | function patch(n1, n2, container, parentComponent = null, anchor = null) {
520 | // todo 判断是不是一个element
521 | // 处理组件
522 | // shapeflag 判断
523 | const { type, shapeFlag } = n2;
524 | // Fragment => 只渲染children
525 | switch (type) {
526 | case Fragment:
527 | processFragment(n1, n2, container, parentComponent);
528 | break;
529 | case Text:
530 | processText(n1, n2, container);
531 | break;
532 | default:
533 | if (shapeFlag & 1 /* ELEMENT */) {
534 | processElement(n1, n2, container, parentComponent, anchor);
535 | }
536 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
537 | processComponent(n1, n2, container, parentComponent);
538 | }
539 | break;
540 | }
541 | }
542 | function processElement(n1, n2, container, parentComponent, anchor) {
543 | if (!n1) {
544 | // init
545 | mountElement(n2, container, parentComponent, anchor);
546 | }
547 | else {
548 | // update
549 | patchElement(n1, n2, container, parentComponent, anchor);
550 | }
551 | }
552 | const EMPTY_OBJ = {};
553 | function patchElement(n1, n2, container, parentComponent, anchor) {
554 | const oldProps = n1.props || EMPTY_OBJ;
555 | const newProps = n2.props || EMPTY_OBJ;
556 | const el = (n2.el = n1.el);
557 | patchChildren(n1, n2, el, parentComponent, anchor);
558 | patchProps(el, oldProps, newProps);
559 | }
560 | function patchChildren(n1, n2, container, parentComponent, anchor) {
561 | const prevShapeFlag = n1.shapeFlag;
562 | const nextShapeFlag = n2.shapeFlag;
563 | const c1 = n1.children;
564 | const c2 = n2.children;
565 | if (nextShapeFlag & 4 /* TEXT_CHILDREN */) {
566 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
567 | // array => text
568 | // 1.把老的children清空
569 | unmountChildren(c1);
570 | // 2.set 新的textchildren
571 | // 如果 n1.children !== n2.children 则直接把n2的文本值设置为container的textContent
572 | // text => text 同时需执行以下
573 | hostSetElementText(container, c2);
574 | }
575 | else {
576 | hostSetElementText(container, c2);
577 | }
578 | }
579 | else {
580 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
581 | // array => text
582 | // 1.把容器的文本内容清空
583 | hostSetElementText(container, "");
584 | mountChildren(c2, container, parentComponent);
585 | }
586 | else {
587 | // array diff => array
588 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
589 | }
590 | }
591 | }
592 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
593 | console.log("patch child");
594 | let i = 0;
595 | let e1 = c1.length - 1;
596 | let e2 = c2.length - 1;
597 | const l2 = c2.length;
598 | // 左侧对比
599 | while (i <= e1 && i <= e2) {
600 | const n1 = c1[i];
601 | const n2 = c2[i];
602 | if (isSameVNodeType(n1, n2)) {
603 | patch(n1, n2, container, parentComponent);
604 | }
605 | else {
606 | break;
607 | }
608 | i++;
609 | }
610 | // 右侧对比
611 | while (i <= e1 && i <= e2) {
612 | const n1 = c1[e1];
613 | const n2 = c2[e2];
614 | if (isSameVNodeType(n1, n2)) {
615 | patch(n1, n2, container, parentComponent);
616 | }
617 | else {
618 | break;
619 | }
620 | e1--;
621 | e2--;
622 | }
623 | if (i > e1) {
624 | // 新的比老的多
625 | if (i <= e2) {
626 | // 如果是这种情况的话就说明 e2 也就是新节点的数量大于旧节点的数量
627 | // 也就是说新增了 vnode
628 | // 应该循环 c2
629 | // 锚点的计算:新的节点有可能需要添加到尾部,也可能添加到头部,所以需要指定添加的问题
630 | // 要添加的位置是当前的位置(e2 开始)+1
631 | // 因为对于往左侧添加的话,应该获取到 c2 的第一个元素
632 | // 所以我们需要从 e2 + 1 取到锚点的位置
633 | const nextPos = e2 + 1;
634 | const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;
635 | while (i <= e2) {
636 | console.log(`需要新创建一个 vnode: ${c2[i].key}`);
637 | patch(null, c2[i], container, parentComponent, anchor);
638 | i++;
639 | }
640 | }
641 | }
642 | else if (i > e2) {
643 | // 新的比老的少
644 | const anchorIndex = i + e1 - e2;
645 | while (i < anchorIndex) {
646 | hostRemove(c1[i].el);
647 | i++;
648 | }
649 | }
650 | else {
651 | // 中间对比
652 | let s1 = i;
653 | let s2 = i;
654 | let isPatchedCount = 0;
655 | const toBePatched = e2 - s2 + 1;
656 | const keyToNewIndexMap = new Map();
657 | const newIndexToOldIndexMap = new Array(toBePatched);
658 | let moved = false;
659 | let maxNewIndexSoFar = 0;
660 | for (let i = 0; i < toBePatched; i++)
661 | newIndexToOldIndexMap[i] = 0;
662 | for (let i = s2; i <= e2; i++) {
663 | const nextChild = c2[i];
664 | keyToNewIndexMap.set(nextChild.key, i);
665 | }
666 | for (let i = s1; i <= e1; i++) {
667 | const prevChild = c1[i];
668 | if (isPatchedCount >= toBePatched) {
669 | hostRemove(prevChild.el);
670 | continue;
671 | }
672 | let newIndex;
673 | if (prevChild.key != null) {
674 | newIndex = keyToNewIndexMap.get(prevChild.key);
675 | }
676 | else {
677 | for (let j = s2; j <= e2; j++) {
678 | if (isSameVNodeType(prevChild, c2[j])) {
679 | newIndex = j;
680 | break;
681 | }
682 | }
683 | }
684 | if (newIndex === undefined) {
685 | hostRemove(prevChild.el);
686 | }
687 | else {
688 | if (newIndex >= maxNewIndexSoFar) {
689 | maxNewIndexSoFar = newIndex;
690 | }
691 | else {
692 | moved = true;
693 | }
694 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
695 | patch(prevChild, c2[newIndex], container, parentComponent, null);
696 | isPatchedCount++;
697 | }
698 | }
699 | const increasingNewIndexSequence = moved
700 | ? getSequence(newIndexToOldIndexMap)
701 | : [];
702 | let j = increasingNewIndexSequence.length - 1;
703 | for (let i = toBePatched - 1; i >= 0; i--) {
704 | const nextIndex = i + s2;
705 | const nextChild = c2[nextIndex];
706 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
707 | if (newIndexToOldIndexMap[i] === 0) {
708 | patch(null, nextChild, container, parentComponent, anchor);
709 | }
710 | else if (moved) {
711 | // 如果判断需要moved
712 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
713 | console.log(anchor);
714 | hostInsert(nextChild.el, container, anchor);
715 | }
716 | else {
717 | j--;
718 | }
719 | }
720 | }
721 | }
722 | }
723 | // 获取最长递增子序列算法
724 | function getSequence(arr) {
725 | const p = arr.slice();
726 | const result = [0];
727 | let i, j, u, v, c;
728 | const len = arr.length;
729 | for (i = 0; i < len; i++) {
730 | const arrI = arr[i];
731 | if (arrI !== 0) {
732 | j = result[result.length - 1];
733 | if (arr[j] < arrI) {
734 | p[i] = j;
735 | result.push(i);
736 | continue;
737 | }
738 | u = 0;
739 | v = result.length - 1;
740 | while (u < v) {
741 | c = (u + v) >> 1;
742 | if (arr[result[c]] < arrI) {
743 | u = c + 1;
744 | }
745 | else {
746 | v = c;
747 | }
748 | }
749 | if (arrI < arr[result[u]]) {
750 | if (u > 0) {
751 | p[i] = result[u - 1];
752 | }
753 | result[u] = i;
754 | }
755 | }
756 | }
757 | u = result.length;
758 | v = result[u - 1];
759 | while (u-- > 0) {
760 | result[u] = v;
761 | v = p[v];
762 | }
763 | return result;
764 | }
765 | function isSameVNodeType(n1, n2) {
766 | return n1.type === n2.type && n1.key === n2.key;
767 | }
768 | function unmountChildren(children) {
769 | for (let i = 0; i < children.length; i++) {
770 | const el = children[i].el;
771 | // remove
772 | hostRemove(el);
773 | }
774 | }
775 | function patchProps(el, oldProps, newProps) {
776 | // 两个props Object判断不相等?
777 | if (oldProps !== newProps) {
778 | for (const key in newProps) {
779 | const prevProp = oldProps[key] ? oldProps[key] : null;
780 | const nextProp = newProps[key];
781 | if (prevProp !== nextProp) {
782 | hostPatchProp(el, key, prevProp, nextProp);
783 | }
784 | }
785 | if (oldProps !== EMPTY_OBJ) {
786 | for (const key in oldProps) {
787 | if (!(key in newProps)) {
788 | hostPatchProp(el, key, oldProps[key], null);
789 | }
790 | }
791 | }
792 | }
793 | }
794 | function processComponent(n1, n2, container, parentComponent) {
795 | if (!n1) {
796 | mountComponent(n2, container, parentComponent);
797 | }
798 | else {
799 | updateComponent(n1, n2);
800 | }
801 | }
802 | function updateComponent(n1, n2) {
803 | const instance = (n2.component = n1.component);
804 | if (shouldUpdateComponent(n1, n2)) {
805 | console.log("组件更新", n1, n2);
806 | instance.next = n2;
807 | instance.update();
808 | }
809 | else {
810 | n2.el = n1.el;
811 | n2.vnode = n2;
812 | }
813 | }
814 | function updateComponentPreRender(instance, nextVNode) {
815 | instance.vnode = nextVNode;
816 | instance.next = null;
817 | instance.props = nextVNode.props;
818 | }
819 | function processFragment(n1, n2, container, parentComponent) {
820 | // Implement
821 | mountChildren(n2.children, container, parentComponent);
822 | }
823 | function processText(n1, n2, container) {
824 | const { children } = n2;
825 | const textNode = (n2.el = document.createTextNode(children));
826 | container.append(textNode);
827 | }
828 | function mountElement(vnode, container, parentComponent, anchor) {
829 | // canvas
830 | // new Element()
831 | // createElement()
832 | const el = (vnode.el = hostCreateElement(vnode.type));
833 | // const el = (vnode.el = document.createElement(vnode.type));
834 | //children: string array
835 | const { children, shapeFlag } = vnode;
836 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
837 | el.textContent = children;
838 | }
839 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
840 | mountChildren(children, el, parentComponent);
841 | }
842 | // props
843 | const { props } = vnode;
844 | // patch Prop
845 | for (const key in props) {
846 | const val = props[key];
847 | hostPatchProp(el, key, null, val);
848 | }
849 | // container.append(el);
850 | hostInsert(el, container, anchor);
851 | }
852 | function mountChildren(children, container, parentComponent) {
853 | children.forEach((child) => {
854 | patch(null, child, container, parentComponent);
855 | });
856 | }
857 | function mountComponent(initalVNode, container, parentComponent) {
858 | const instance = (initalVNode.component = createComponentInstance(initalVNode, parentComponent));
859 | setupInstance(instance);
860 | setupRenderEffect(instance, initalVNode, container);
861 | }
862 | function setupRenderEffect(instance, initalVNode, container) {
863 | // 拆分更新与初始化
864 | instance.update = effect(() => {
865 | if (!instance.isMounted) {
866 | console.log("init");
867 | const { proxy } = instance;
868 | const subTree = (instance.subTree = instance.render.call(proxy, proxy));
869 | // vnode => patch
870 | // vnode => element => mountElement
871 | patch(null, subTree, container, instance);
872 | // element => mounted
873 | initalVNode.el = subTree.el;
874 | instance.isMounted = true;
875 | }
876 | else {
877 | console.log("update");
878 | const { proxy, next, vnode } = instance;
879 | if (next) {
880 | next.el = vnode.el;
881 | updateComponentPreRender(instance, next);
882 | }
883 | const prevSubTree = instance.subTree;
884 | const subTree = (instance.subTree = instance.render.call(proxy, proxy));
885 | // vnode => patch
886 | // vnode => element => mountElement
887 | patch(prevSubTree, subTree, container, instance);
888 | // element => mounted
889 | initalVNode.el = subTree.el;
890 | }
891 | }, {
892 | scheduler() {
893 | console.log("update-----scheduler");
894 | queueJob(instance.update);
895 | },
896 | });
897 | }
898 | return {
899 | createApp: createAppAPI(render),
900 | };
901 | }
902 |
903 | function createElement(type) {
904 | return document.createElement(type);
905 | }
906 | function patchProp(el, key, prevVal, nextVal) {
907 | if (isOn(key)) {
908 | const event = key.slice(2).toLocaleLowerCase();
909 | el.addEventListener(event, () => {
910 | nextVal();
911 | });
912 | }
913 | else {
914 | if (nextVal === undefined || nextVal === null) {
915 | el.removeAttribute(key);
916 | }
917 | else {
918 | el.setAttribute(key, nextVal);
919 | }
920 | }
921 | }
922 | function insert(child, parent, anchor = null) {
923 | console.log('insert', child);
924 | parent.insertBefore(child, anchor);
925 | }
926 | function remove(child) {
927 | const parent = child.parentNode;
928 | if (parent) {
929 | parent.removeChild(child);
930 | }
931 | }
932 | function setElementText(el, text) {
933 | el.textContent = text;
934 | }
935 | function setElementArray(el, children) {
936 | console.log(el);
937 | console.log(children);
938 | }
939 | const render = createRender({
940 | createElement,
941 | patchProp,
942 | insert,
943 | remove,
944 | setElementText,
945 | setElementArray
946 | });
947 | function createApp(...args) {
948 | return render.createApp(...args);
949 | }
950 |
951 | var runtimeDom = /*#__PURE__*/Object.freeze({
952 | __proto__: null,
953 | createApp: createApp,
954 | createAppAPI: createAppAPI,
955 | h: h,
956 | renderSlots: renderSlots,
957 | createTextVNode: createTextVNode,
958 | createElementVNode: createVNode,
959 | getCurrentInstance: getCurrentInstance,
960 | registerRuntimeComplier: registerRuntimeComplier,
961 | inject: inject,
962 | provide: provide,
963 | createRender: createRender,
964 | nextTick: nextTick,
965 | toDisplayString: toDisplayString,
966 | ref: ref,
967 | proxyRefs: proxyRefs
968 | });
969 |
970 | const TO_DISPLAY_STRING = Symbol("toDisplayString");
971 | const CREATE_ELEMENT_VNODE = Symbol("createElementVNode");
972 | const helperMapName = {
973 | [TO_DISPLAY_STRING]: "toDisplayString",
974 | [CREATE_ELEMENT_VNODE]: "createElementVNode",
975 | };
976 |
977 | var NodeTypes;
978 | (function (NodeTypes) {
979 | NodeTypes["INTERPOLATION"] = "interpolation";
980 | NodeTypes["SIMPLE_EXPRESSION"] = "simple_expression";
981 | NodeTypes["ELEMENT"] = "element";
982 | NodeTypes["TEXT"] = "text";
983 | NodeTypes["ROOT"] = "root";
984 | NodeTypes["COMPOUND_EXPRESSION"] = "compound_expression";
985 | })(NodeTypes || (NodeTypes = {}));
986 | function createVNodeCall(context, tag, props, children) {
987 | context.helper(CREATE_ELEMENT_VNODE);
988 | return {
989 | type: NodeTypes.ELEMENT,
990 | tag,
991 | props,
992 | children,
993 | };
994 | }
995 |
996 | var TagsType;
997 | (function (TagsType) {
998 | TagsType["START"] = "start";
999 | TagsType["END"] = "end";
1000 | })(TagsType || (TagsType = {}));
1001 | function baseParse(content) {
1002 | const context = createParserContext(content);
1003 | return createRoot(parseChildren(context, []));
1004 | }
1005 | function parseChildren(context, ancestors) {
1006 | const nodes = [];
1007 | while (!isLoopEnd(context, ancestors)) {
1008 | let node;
1009 | const s = context.source;
1010 | if (s.startsWith("{{")) {
1011 | node = parseInterpolation(context);
1012 | }
1013 | else if (s[0] === "<") {
1014 | if (/[a-z]/.test(s[1])) {
1015 | node = parseElement(context, ancestors);
1016 | }
1017 | }
1018 | else {
1019 | node = parseText(context);
1020 | }
1021 | nodes.push(node);
1022 | }
1023 | return nodes;
1024 | }
1025 | function isLoopEnd(context, ancestors) {
1026 | //1.source 为空
1027 | const s = context.source;
1028 | if (s.startsWith("")) {
1029 | for (let i = ancestors.length - 1; i >= 0; i--) {
1030 | const tag = ancestors[i].tag;
1031 | if (startsWithEndTagOpen(s, tag)) {
1032 | return true;
1033 | }
1034 | }
1035 | return true;
1036 | }
1037 | //2.碰到结束标签
1038 | return !s;
1039 | }
1040 | function parseText(context) {
1041 | let endTokens = ["{{", "<"];
1042 | let endIndex = context.source.length;
1043 | for (let i = 0; i < endTokens.length; i++) {
1044 | const index = context.source.indexOf(endTokens[i]);
1045 | if (index !== -1 && index < endIndex) {
1046 | endIndex = index;
1047 | }
1048 | }
1049 | const content = parseTextData(context, endIndex);
1050 | return {
1051 | type: NodeTypes.TEXT,
1052 | content,
1053 | };
1054 | }
1055 | function parseTextData(context, length) {
1056 | const rawText = context.source.slice(0, length);
1057 | advanceBy(context, length);
1058 | return rawText;
1059 | }
1060 | function parseElement(context, ancestors) {
1061 | //Implement
1062 | //1.解析
1063 | const element = parseTag(context, "start" /* START */);
1064 | ancestors.push(element);
1065 | element.children = parseChildren(context, ancestors);
1066 | ancestors.pop();
1067 | if (startsWithEndTagOpen(context.source, element.tag)) {
1068 | parseTag(context, "end" /* END */);
1069 | }
1070 | else {
1071 | throw new Error(`缺少结束标签:${element.tag}`);
1072 | }
1073 | return element;
1074 | }
1075 | function startsWithEndTagOpen(source, tag) {
1076 | return (source.startsWith("") &&
1077 | source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase());
1078 | }
1079 | function parseTag(context, type) {
1080 | const match = /^<\/?([a-z]*)/i.exec(context.source);
1081 | const tag = match[1];
1082 | //2.删除处理完成的代码
1083 | advanceBy(context, match[0].length);
1084 | advanceBy(context, 1);
1085 | if (type === "end" /* END */)
1086 | return;
1087 | return {
1088 | type: NodeTypes.ELEMENT,
1089 | tag,
1090 | };
1091 | }
1092 | function parseInterpolation(context) {
1093 | // {{message}}
1094 | const openDelimiter = "{{";
1095 | const closeDelimiter = "}}";
1096 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length);
1097 | advanceBy(context, openDelimiter.length);
1098 | const rawContentLength = closeIndex - openDelimiter.length;
1099 | const rawContent = context.source.slice(0, rawContentLength);
1100 | const content = rawContent.trim();
1101 | advanceBy(context, rawContentLength + closeDelimiter.length);
1102 | return {
1103 | type: NodeTypes.INTERPOLATION,
1104 | content: {
1105 | type: NodeTypes.SIMPLE_EXPRESSION,
1106 | content,
1107 | },
1108 | };
1109 | }
1110 | function advanceBy(context, length) {
1111 | context.source = context.source.slice(length);
1112 | }
1113 | function createRoot(children) {
1114 | return {
1115 | children,
1116 | type: NodeTypes.ROOT,
1117 | };
1118 | }
1119 | function createParserContext(content) {
1120 | return {
1121 | source: content,
1122 | };
1123 | }
1124 |
1125 | function transform(root, options = {}) {
1126 | const context = createTransformContext(root, options);
1127 | // 1.深度优先搜索
1128 | traverseNode(root, context);
1129 | createRootCodegen(root);
1130 | root.helpers = [...context.helpers.keys()];
1131 | }
1132 | function createRootCodegen(root) {
1133 | const child = root.children[0];
1134 | if (child.type === NodeTypes.ELEMENT && child.codegenNode) {
1135 | root.codegenNode = child.codegenNode;
1136 | }
1137 | else {
1138 | root.codegenNode = child;
1139 | }
1140 | }
1141 | function createTransformContext(root, options) {
1142 | const context = {
1143 | root,
1144 | nodeTransforms: options.nodeTransforms || {},
1145 | helpers: new Map(),
1146 | helper(key) {
1147 | context.helpers.set(key, 1);
1148 | },
1149 | };
1150 | return context;
1151 | }
1152 | function traverseNode(node, context) {
1153 | const nodeTransforms = context.nodeTransforms;
1154 | const exitFns = [];
1155 | for (let i = 0; i < nodeTransforms.length; i++) {
1156 | const transform = nodeTransforms[i];
1157 | const onExit = transform(node, context);
1158 | if (onExit)
1159 | exitFns.push(onExit);
1160 | }
1161 | switch (node.type) {
1162 | case NodeTypes.INTERPOLATION:
1163 | context.helper(TO_DISPLAY_STRING);
1164 | break;
1165 | case NodeTypes.ROOT:
1166 | tranverseChildren(node, context);
1167 | break;
1168 | case NodeTypes.ELEMENT:
1169 | tranverseChildren(node, context);
1170 | break;
1171 | }
1172 | // 退出流程
1173 | let i = exitFns.length;
1174 | while (i--) {
1175 | exitFns[i]();
1176 | }
1177 | }
1178 | function tranverseChildren(node, context) {
1179 | const children = node.children;
1180 | for (let i = 0; i < children.length; i++) {
1181 | const node = children[i];
1182 | traverseNode(node, context);
1183 | }
1184 | }
1185 |
1186 | function generate(ast) {
1187 | const context = codegenContext();
1188 | const { push } = context;
1189 | getFunctionPreamble(context, ast);
1190 | push("return ");
1191 | const functionName = "render";
1192 | const args = ["_ctx,_cache"];
1193 | const signature = args.join(", ");
1194 | push(`function ${functionName}(${signature}){return `);
1195 | genNode(ast.codegenNode, context);
1196 | push("}");
1197 | return {
1198 | code: context.code,
1199 | };
1200 | }
1201 | function codegenContext() {
1202 | const context = {
1203 | code: "",
1204 | push(source) {
1205 | context.code += source;
1206 | },
1207 | helper(key) {
1208 | return `${helperMapName[key]}`;
1209 | },
1210 | };
1211 | return context;
1212 | }
1213 | function genNode(node, context) {
1214 | switch (node.type) {
1215 | case NodeTypes.TEXT:
1216 | genText(node, context);
1217 | break;
1218 | case NodeTypes.INTERPOLATION:
1219 | genInterpolation(node, context);
1220 | break;
1221 | case NodeTypes.SIMPLE_EXPRESSION:
1222 | getExpression(node, context);
1223 | break;
1224 | case NodeTypes.ELEMENT:
1225 | getElement(node, context);
1226 | break;
1227 | case NodeTypes.COMPOUND_EXPRESSION:
1228 | getCompoundExpression(node, context);
1229 | break;
1230 | }
1231 | }
1232 | function getFunctionPreamble(context, ast) {
1233 | const { push, helper } = context;
1234 | const vueBinging = "vue";
1235 | const aliasHelpers = (s) => `${helper(s)}:_${helper(s)}`;
1236 | if (ast.helpers.length > 0) {
1237 | push(`const { ${ast.helpers.map(aliasHelpers).join(", ")} } = ${vueBinging}`);
1238 | }
1239 | push("\n");
1240 | }
1241 | function genText(node, context) {
1242 | const { push } = context;
1243 | push(`"${node.content}"`);
1244 | }
1245 | function genInterpolation(node, context) {
1246 | const { push, helper } = context;
1247 | push(`_${helper(TO_DISPLAY_STRING)}(`);
1248 | genNode(node.content, context);
1249 | push(")");
1250 | }
1251 | function getExpression(node, context) {
1252 | const { push } = context;
1253 | push(`${node.content}`);
1254 | }
1255 | function getElement(node, context) {
1256 | const { push, helper } = context;
1257 | const { tag, children, props } = node;
1258 | push(`_${helper(CREATE_ELEMENT_VNODE)}(`);
1259 | genNodeList(genNullalbe([tag, props, children]), context);
1260 | // genNode(children, context);
1261 | push(")");
1262 | }
1263 | function getCompoundExpression(node, context) {
1264 | const { children } = node;
1265 | const { push } = context;
1266 | for (let i = 0; i < children.length; i++) {
1267 | const child = children[i];
1268 | if (isString(child)) {
1269 | push(child);
1270 | }
1271 | else {
1272 | genNode(child, context);
1273 | }
1274 | }
1275 | }
1276 | function genNullalbe(args) {
1277 | return args.map((arg) => arg || "null");
1278 | }
1279 | function genNodeList(nodes, context) {
1280 | const { push } = context;
1281 | for (let i = 0; i < nodes.length; i++) {
1282 | const node = nodes[i];
1283 | if (isString(node)) {
1284 | push(node);
1285 | }
1286 | else {
1287 | genNode(node, context);
1288 | }
1289 | if (i < nodes.length - 1) {
1290 | push(",");
1291 | }
1292 | }
1293 | }
1294 |
1295 | function transformElement(node, context) {
1296 | if (node.type === NodeTypes.ELEMENT) {
1297 | return () => {
1298 | // 中间处理层
1299 | // tag
1300 | const vnodeTag = `"${node.tag}"`;
1301 | // props
1302 | let vnodeProps;
1303 | //children
1304 | const { children } = node;
1305 | let vnodeChildren = children[0];
1306 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);
1307 | };
1308 | }
1309 | }
1310 |
1311 | function transformExpression(node) {
1312 | if (node.type === NodeTypes.INTERPOLATION) {
1313 | return () => {
1314 | node.content = processExpression(node.content);
1315 | };
1316 | }
1317 | }
1318 | function processExpression(node) {
1319 | node.content = `_ctx.${node.content}`;
1320 | return node;
1321 | }
1322 |
1323 | function isText(node) {
1324 | return node.type === NodeTypes.TEXT || NodeTypes.INTERPOLATION;
1325 | }
1326 |
1327 | function transformText(node) {
1328 | const { children } = node;
1329 | if (node.type === NodeTypes.ELEMENT) {
1330 | return () => {
1331 | let currentContainer;
1332 | for (let i = 0; i < children.length; i++) {
1333 | const child = children[i];
1334 | if (isText(child)) {
1335 | for (let j = i + 1; j < children.length; j++) {
1336 | const next = children[j];
1337 | if (isText(next)) {
1338 | if (!currentContainer) {
1339 | currentContainer = children[i] = {
1340 | type: NodeTypes.COMPOUND_EXPRESSION,
1341 | children: [child],
1342 | };
1343 | }
1344 | currentContainer.children.push(" + ");
1345 | currentContainer.children.push(next);
1346 | children.splice(j, 1);
1347 | j--;
1348 | }
1349 | else {
1350 | currentContainer = undefined;
1351 | break;
1352 | }
1353 | }
1354 | }
1355 | }
1356 | };
1357 | }
1358 | }
1359 |
1360 | function baseCompile(template) {
1361 | const ast = baseParse(template);
1362 | transform(ast, {
1363 | nodeTransforms: [transformExpression, transformElement, transformText],
1364 | });
1365 | return generate(ast);
1366 | }
1367 |
1368 | // mini-vue 出口
1369 | function complieToFunction(template) {
1370 | const { code } = baseCompile(template);
1371 | const render = new Function("vue", code)(runtimeDom);
1372 | return render;
1373 | }
1374 | registerRuntimeComplier(complieToFunction);
1375 |
1376 | export { createApp, createAppAPI, createVNode as createElementVNode, createRender, createTextVNode, getCurrentInstance, h, inject, nextTick, provide, proxyRefs, ref, registerRuntimeComplier, renderSlots, toDisplayString };
1377 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mymini-vue",
3 | "version": "1.0.0",
4 | "main": "lib/mini-vue.cjs.js",
5 | "module": "lib/mini-vue.esm.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "test": "jest",
9 | "build": "rollup -c rollup.config.js",
10 | "dev": "rollup -c -w"
11 | },
12 | "dependencies": {},
13 | "devDependencies": {
14 | "@babel/core": "^7.17.7",
15 | "@babel/preset-env": "^7.16.11",
16 | "@babel/preset-typescript": "^7.16.7",
17 | "@rollup/plugin-typescript": "^8.3.1",
18 | "@types/jest": "^27.4.1",
19 | "babel-jest": "^27.5.1",
20 | "jest": "^27.5.1",
21 | "rollup": "^2.70.0",
22 | "tslib": "^2.3.1",
23 | "typescript": "^4.6.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/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 => commonjs
7 | // 2. esm
8 | {
9 | format: "cjs",
10 | file: pkg.main,
11 | },
12 | {
13 | format: "es",
14 | file: pkg.module,
15 | },
16 | ],
17 | plugins: [typescript()],
18 | };
19 |
--------------------------------------------------------------------------------
/src/compiler-core/src/ast.ts:
--------------------------------------------------------------------------------
1 | import { CREATE_ELEMENT_VNODE } from "./runtimeHelpers";
2 |
3 | export enum NodeTypes {
4 | INTERPOLATION = "interpolation",
5 | SIMPLE_EXPRESSION = "simple_expression",
6 | ELEMENT = "element",
7 | TEXT = "text",
8 | ROOT = "root",
9 | COMPOUND_EXPRESSION = "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 { NodeTypes } from "./ast";
2 | import { isString } from "../../shared";
3 | import {
4 | CREATE_ELEMENT_VNODE,
5 | helperMapName,
6 | TO_DISPLAY_STRING,
7 | } from "./runtimeHelpers";
8 |
9 | export function generate(ast) {
10 | const context = codegenContext();
11 | const { push } = context;
12 |
13 | getFunctionPreamble(context, ast);
14 |
15 | push("return ");
16 | const functionName = "render";
17 | const args = ["_ctx,_cache"];
18 | const signature = args.join(", ");
19 | push(`function ${functionName}(${signature}){return `);
20 | genNode(ast.codegenNode, context);
21 | push("}");
22 | return {
23 | code: context.code,
24 | };
25 | }
26 |
27 | function codegenContext() {
28 | const context = {
29 | code: "",
30 | push(source) {
31 | context.code += source;
32 | },
33 | helper(key) {
34 | return `${helperMapName[key]}`;
35 | },
36 | };
37 | return context;
38 | }
39 |
40 | function genNode(node, context) {
41 | switch (node.type) {
42 | case NodeTypes.TEXT:
43 | genText(node, context);
44 | break;
45 | case NodeTypes.INTERPOLATION:
46 | genInterpolation(node, context);
47 | break;
48 | case NodeTypes.SIMPLE_EXPRESSION:
49 | getExpression(node, context);
50 | break;
51 | case NodeTypes.ELEMENT:
52 | getElement(node, context);
53 | break;
54 | case NodeTypes.COMPOUND_EXPRESSION:
55 | getCompoundExpression(node, context);
56 | break;
57 | default:
58 | break;
59 | }
60 | }
61 | function getFunctionPreamble(context, ast) {
62 | const { push, helper } = context;
63 | const vueBinging = "vue";
64 | const aliasHelpers = (s) => `${helper(s)}:_${helper(s)}`;
65 | if (ast.helpers.length > 0) {
66 | push(
67 | `const { ${ast.helpers.map(aliasHelpers).join(", ")} } = ${vueBinging}`
68 | );
69 | }
70 | push("\n");
71 | }
72 |
73 | function genText(node: any, context: any) {
74 | const { push } = context;
75 | push(`"${node.content}"`);
76 | }
77 | function genInterpolation(node: any, context: any) {
78 | const { push, helper } = context;
79 | push(`_${helper(TO_DISPLAY_STRING)}(`);
80 | genNode(node.content, context);
81 | push(")");
82 | }
83 | function getExpression(node: any, context: any) {
84 | const { push } = context;
85 | push(`${node.content}`);
86 | }
87 | function getElement(node: any, context: any) {
88 | const { push, helper } = context;
89 | const { tag, children, props } = node;
90 | push(`_${helper(CREATE_ELEMENT_VNODE)}(`);
91 |
92 | genNodeList(genNullalbe([tag, props, children]), context);
93 | // genNode(children, context);
94 | push(")");
95 | }
96 | function getCompoundExpression(node: any, context: any) {
97 | const { children } = node;
98 | const { push } = context;
99 | for (let i = 0; i < children.length; i++) {
100 | const child = children[i];
101 | if (isString(child)) {
102 | push(child);
103 | } else {
104 | genNode(child, context);
105 | }
106 | }
107 | }
108 | function genNullalbe(args: any[]) {
109 | return args.map((arg) => arg || "null");
110 | }
111 | function genNodeList(nodes, context) {
112 | const { push } = context;
113 | for (let i = 0; i < nodes.length; i++) {
114 | const node = nodes[i];
115 | if (isString(node)) {
116 | push(node);
117 | } else {
118 | genNode(node, context);
119 | }
120 | if (i < nodes.length - 1) {
121 | push(",");
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/compiler-core/src/complie.ts:
--------------------------------------------------------------------------------
1 | import { baseParse } from "./parse";
2 | import { transform } from "./transform";
3 | import { generate } from "./codegen";
4 | import { transformElement } from "./transforms/transformElement";
5 | import { transformExpression } from "./transforms/transfromExpression";
6 | import { transformText } from "./transforms/transformText";
7 |
8 | export function baseCompile(template) {
9 | const ast: any = baseParse(template);
10 | transform(ast, {
11 | nodeTransforms: [transformExpression, transformElement, transformText],
12 | });
13 | return generate(ast);
14 | }
15 |
--------------------------------------------------------------------------------
/src/compiler-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./complie";
2 |
--------------------------------------------------------------------------------
/src/compiler-core/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 | const enum TagsType {
3 | START = "start",
4 | END = "end",
5 | }
6 | export function baseParse(content: string) {
7 | const context = createParserContext(content);
8 | return createRoot(parseChildren(context, []));
9 | }
10 | function parseChildren(context, ancestors) {
11 | const nodes: any = [];
12 | while (!isLoopEnd(context, ancestors)) {
13 | let node;
14 | const s = context.source;
15 | if (s.startsWith("{{")) {
16 | node = parseInterpolation(context);
17 | } else if (s[0] === "<") {
18 | if (/[a-z]/.test(s[1])) {
19 | node = parseElement(context, ancestors);
20 | }
21 | } else {
22 | node = parseText(context);
23 | }
24 | nodes.push(node);
25 | }
26 | return nodes;
27 | }
28 |
29 | function isLoopEnd(context, ancestors) {
30 | //1.source 为空
31 | const s = context.source;
32 | if (s.startsWith("")) {
33 | for (let i = ancestors.length - 1; i >= 0; i--) {
34 | const tag = ancestors[i].tag;
35 | if (startsWithEndTagOpen(s, tag)) {
36 | return true;
37 | }
38 | }
39 | return true;
40 | }
41 | //2.碰到结束标签
42 | return !s;
43 | }
44 |
45 | function parseText(context: any) {
46 | let endTokens = ["{{", "<"];
47 | let endIndex = context.source.length;
48 | for (let i = 0; i < endTokens.length; i++) {
49 | const index = context.source.indexOf(endTokens[i]);
50 | if (index !== -1 && index < endIndex) {
51 | endIndex = index;
52 | }
53 | }
54 | const content = parseTextData(context, endIndex);
55 | return {
56 | type: NodeTypes.TEXT,
57 | content,
58 | };
59 | }
60 | function parseTextData(context, length: number) {
61 | const rawText = context.source.slice(0, length);
62 | advanceBy(context, length);
63 | return rawText;
64 | }
65 | function parseElement(context: any, ancestors: number[]) {
66 | //Implement
67 | //1.解析
68 | const element: any = parseTag(context, TagsType.START);
69 | ancestors.push(element);
70 | element.children = parseChildren(context, ancestors);
71 | ancestors.pop();
72 | if (startsWithEndTagOpen(context.source, element.tag)) {
73 | parseTag(context, TagsType.END);
74 | } else {
75 | throw new Error(`缺少结束标签:${element.tag}`);
76 | }
77 |
78 | return element;
79 | }
80 |
81 | function startsWithEndTagOpen(source, tag) {
82 | return (
83 | source.startsWith("") &&
84 | source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
85 | );
86 | }
87 |
88 | function parseTag(context, type: TagsType) {
89 | const match: any = /^<\/?([a-z]*)/i.exec(context.source);
90 | const tag = match[1];
91 | //2.删除处理完成的代码
92 | advanceBy(context, match[0].length);
93 | advanceBy(context, 1);
94 | if (type === TagsType.END) return;
95 | return {
96 | type: NodeTypes.ELEMENT,
97 | tag,
98 | };
99 | }
100 |
101 | function parseInterpolation(context) {
102 | // {{message}}
103 | const openDelimiter = "{{";
104 | const closeDelimiter = "}}";
105 | const closeIndex = context.source.indexOf(
106 | closeDelimiter,
107 | openDelimiter.length
108 | );
109 | advanceBy(context, openDelimiter.length);
110 | const rawContentLength = closeIndex - openDelimiter.length;
111 | const rawContent = context.source.slice(0, rawContentLength);
112 | const content = rawContent.trim();
113 | advanceBy(context, rawContentLength + closeDelimiter.length);
114 | return {
115 | type: NodeTypes.INTERPOLATION,
116 | content: {
117 | type: NodeTypes.SIMPLE_EXPRESSION,
118 | content,
119 | },
120 | };
121 | }
122 | function advanceBy(context: any, length: number) {
123 | context.source = context.source.slice(length);
124 | }
125 | function createRoot(children) {
126 | return {
127 | children,
128 | type: NodeTypes.ROOT,
129 | };
130 | }
131 |
132 | function createParserContext(content: string) {
133 | return {
134 | source: content,
135 | };
136 | }
137 |
--------------------------------------------------------------------------------
/src/compiler-core/src/runtimeHelpers.ts:
--------------------------------------------------------------------------------
1 | export const TO_DISPLAY_STRING = Symbol("toDisplayString");
2 | export const CREATE_ELEMENT_VNODE = Symbol("createElementVNode");
3 | export const helperMapName = {
4 | [TO_DISPLAY_STRING]: "toDisplayString",
5 | [CREATE_ELEMENT_VNODE]: "createElementVNode",
6 | };
7 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transform.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 | import { TO_DISPLAY_STRING } from "./runtimeHelpers";
3 |
4 | export function transform(root, options = {}) {
5 | const context = createTransformContext(root, options);
6 | // 1.深度优先搜索
7 | traverseNode(root, context);
8 |
9 | createRootCodegen(root);
10 |
11 | root.helpers = [...context.helpers.keys()];
12 | }
13 |
14 | function createRootCodegen(root: any) {
15 | const child = root.children[0];
16 | if (child.type === NodeTypes.ELEMENT && child.codegenNode) {
17 | root.codegenNode = child.codegenNode;
18 | } else {
19 | root.codegenNode = child;
20 | }
21 | }
22 |
23 | function createTransformContext(root, options) {
24 | const context = {
25 | root,
26 | nodeTransforms: options.nodeTransforms || {},
27 | helpers: new Map(),
28 | helper(key) {
29 | context.helpers.set(key, 1);
30 | },
31 | };
32 | return context;
33 | }
34 |
35 | function traverseNode(node, context) {
36 | const nodeTransforms = context.nodeTransforms;
37 | const exitFns: any = [];
38 | for (let i = 0; i < nodeTransforms.length; i++) {
39 | const transform = nodeTransforms[i];
40 | const onExit = transform(node, context);
41 | if (onExit) exitFns.push(onExit);
42 | }
43 |
44 | switch (node.type) {
45 | case NodeTypes.INTERPOLATION:
46 | context.helper(TO_DISPLAY_STRING);
47 | break;
48 | case NodeTypes.ROOT:
49 | tranverseChildren(node, context);
50 | break;
51 | case NodeTypes.ELEMENT:
52 | tranverseChildren(node, context);
53 | break;
54 | default:
55 | break;
56 | }
57 | // 退出流程
58 | let i = exitFns.length;
59 | while (i--) {
60 | exitFns[i]();
61 | }
62 | }
63 |
64 | function tranverseChildren(node, context) {
65 | const children = node.children;
66 |
67 | for (let i = 0; i < children.length; i++) {
68 | const node = children[i];
69 | traverseNode(node, context);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformElement.ts:
--------------------------------------------------------------------------------
1 | import { createVNodeCall, NodeTypes } from "../ast";
2 |
3 | export function transformElement(node, context) {
4 | if (node.type === NodeTypes.ELEMENT) {
5 | return () => {
6 | // 中间处理层
7 | // tag
8 | const vnodeTag = `"${node.tag}"`;
9 | // props
10 | let vnodeProps;
11 | //children
12 | const { children } = node;
13 | let vnodeChildren = children[0];
14 |
15 | node.codegenNode = createVNodeCall(
16 | context,
17 | vnodeTag,
18 | vnodeProps,
19 | vnodeChildren
20 | );
21 | };
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformText.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 | import { isText } from "../utils";
3 |
4 | export function transformText(node) {
5 | const { children } = node;
6 |
7 | if (node.type === NodeTypes.ELEMENT) {
8 | return () => {
9 | let currentContainer;
10 | for (let i = 0; i < children.length; i++) {
11 | const child = children[i];
12 | if (isText(child)) {
13 | for (let j = i + 1; j < children.length; j++) {
14 | const next = children[j];
15 | if (isText(next)) {
16 | if (!currentContainer) {
17 | currentContainer = children[i] = {
18 | type: NodeTypes.COMPOUND_EXPRESSION,
19 | children: [child],
20 | };
21 | }
22 | currentContainer.children.push(" + ");
23 | currentContainer.children.push(next);
24 | children.splice(j, 1);
25 | j--;
26 | } else {
27 | currentContainer = undefined;
28 | break;
29 | }
30 | }
31 | }
32 | }
33 | };
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transfromExpression.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 |
3 | export function transformExpression(node) {
4 | if (node.type === NodeTypes.INTERPOLATION) {
5 | return () => {
6 | node.content = processExpression(node.content);
7 | };
8 | }
9 | }
10 |
11 | function processExpression(node) {
12 | node.content = `_ctx.${node.content}`;
13 | return node;
14 | }
15 |
--------------------------------------------------------------------------------
/src/compiler-core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | export function isText(node) {
4 | return node.type === NodeTypes.TEXT || 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 { baseParse } from "../src/parse";
2 | import { generate } from "../src/codegen";
3 | import { transform } from "../src/transform";
4 | import { transformExpression } from "../src/transforms/transfromExpression";
5 | import { transformElement } from "../src/transforms/transformElement";
6 | import { transformText } from "../src/transforms/transformText";
7 | describe("codegen", () => {
8 | it("string", () => {
9 | const ast = baseParse("hi");
10 | transform(ast);
11 | const { code } = generate(ast);
12 | // 快照测试
13 | //1.抓bug
14 | //2.有意更新,主动更新快照 -u
15 | expect(code).toMatchSnapshot();
16 | });
17 |
18 | it("interpolation", () => {
19 | const ast = baseParse("{{message}}");
20 | transform(ast, {
21 | nodeTransforms: [transformExpression],
22 | });
23 | const { code } = generate(ast);
24 | // 快照测试
25 | //1.抓bug
26 | //2.有意更新,主动更新快照 -u
27 | expect(code).toMatchSnapshot();
28 | });
29 |
30 | it("element", () => {
31 | const ast: any = baseParse("hi,{{message}}
");
32 | transform(ast, {
33 | nodeTransforms: [transformExpression, transformElement, transformText],
34 | });
35 | const { code } = generate(ast);
36 | // 快照测试
37 | //1.抓bug
38 | //2.有意更新,主动更新快照 -u
39 | expect(code).toMatchSnapshot();
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/parse.spec.ts:
--------------------------------------------------------------------------------
1 | import { baseParse } from "../src/parse";
2 | import { NodeTypes } from "../src/ast";
3 | describe("Parse", () => {
4 | describe("interpolation", () => {
5 | test("simple interpolation", () => {
6 | const ast = baseParse("{{ message }}");
7 | expect(ast.children[0]).toStrictEqual({
8 | type: NodeTypes.INTERPOLATION,
9 | content: {
10 | type: NodeTypes.SIMPLE_EXPRESSION,
11 | content: "message",
12 | },
13 | });
14 | });
15 | });
16 | describe("element", () => {
17 | const ast = baseParse("");
18 | it("simple element div", () => {
19 | expect(ast.children[0]).toStrictEqual({
20 | type: NodeTypes.ELEMENT,
21 | tag: "div",
22 | children: [],
23 | });
24 | });
25 | });
26 | describe("text", () => {
27 | it("simple text", () => {
28 | const ast = baseParse("hello world");
29 | expect(ast.children[0]).toStrictEqual({
30 | type: NodeTypes.TEXT,
31 | content: "hello world",
32 | });
33 | });
34 | });
35 | test("hello world", () => {
36 | const ast = baseParse("hi,{{message}}
");
37 | expect(ast.children[0]).toStrictEqual({
38 | type: NodeTypes.ELEMENT,
39 | tag: "div",
40 | children: [
41 | {
42 | type: NodeTypes.TEXT,
43 | content: "hi,",
44 | },
45 | {
46 | type: NodeTypes.INTERPOLATION,
47 | content: {
48 | type: NodeTypes.SIMPLE_EXPRESSION,
49 | content: "message",
50 | },
51 | },
52 | ],
53 | });
54 | });
55 | test("nested element", () => {
56 | const ast = baseParse("");
57 | expect(ast.children[0]).toStrictEqual({
58 | type: NodeTypes.ELEMENT,
59 | tag: "div",
60 | children: [
61 | {
62 | type: NodeTypes.ELEMENT,
63 | tag: "p",
64 | children: [
65 | {
66 | type: NodeTypes.TEXT,
67 | content: "hi",
68 | },
69 | ],
70 | },
71 | {
72 | type: NodeTypes.INTERPOLATION,
73 | content: {
74 | type: NodeTypes.SIMPLE_EXPRESSION,
75 | content: "message",
76 | },
77 | },
78 | ],
79 | });
80 | });
81 | test("should thorw when lack end tag", () => {
82 | expect(() => {
83 | baseParse("
");
84 | }).toThrow("缺少结束标签:span");
85 | });
86 | });
87 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/transform.spec.ts:
--------------------------------------------------------------------------------
1 | import { transform } from "../src/transform";
2 | import { baseParse } from "../src/parse";
3 | import { NodeTypes } from "../src/ast";
4 |
5 | describe("transform", () => {
6 | it("happy path", () => {
7 | const ast = baseParse("hi,{{message}}
");
8 | const plugin = (node) => {
9 | if (node.type === NodeTypes.TEXT) {
10 | node.content = node.content + "mini-vue";
11 | }
12 | };
13 | transform(ast, {
14 | nodeTransforms: [plugin],
15 | });
16 | const nodeText = ast.children[0].children[0];
17 | expect(nodeText.content).toBe("hi,mini-vue");
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 出口
2 | export * from "./runtime-dom";
3 |
4 | import { baseCompile } from "./compiler-core/src";
5 | import * as runtimeDom from "./runtime-dom";
6 | import { registerRuntimeComplier } from "./runtime-dom";
7 | function complieToFunction(template) {
8 | const { code } = baseCompile(template);
9 | const render = new Function("vue", code)(runtimeDom);
10 | return render;
11 | }
12 | registerRuntimeComplier(complieToFunction);
13 |
--------------------------------------------------------------------------------
/src/reactivity/baseHandler.ts:
--------------------------------------------------------------------------------
1 | import { track, trigger } from "./effect";
2 | import {reactive,ReactiveFlags,readonly} from './reactive'
3 | import { isObject } from "../shared";
4 | import { unRef,isRef } from "./ref";
5 | const get = createGetter();
6 | const set = createSetter();
7 | const readonlyGet = createGetter(true);
8 | const shallowReadonlyGet = createGetter(true, true);
9 | function createGetter(isReadonly = false,isShallow = false){
10 | return function get(target:any,key:any){
11 | if(key === ReactiveFlags.IS_REACTIVE){
12 | return !isReadonly;
13 | } else if(key === ReactiveFlags.IS_READONLY){
14 | return isReadonly;
15 | }
16 | const res = Reflect.get(target,key);
17 | // 判断shallow 直接返回res
18 | if(isShallow) return res;
19 | // 判断 res是不是一个Object
20 | if(isObject(res)){
21 | return isReadonly? readonly(res) : reactive(res);
22 | }
23 | if(!isReadonly){
24 | track(target,key);
25 | }
26 | return res;
27 | }
28 | }
29 |
30 | function createSetter(isReadonly = false){
31 | return function set(target:any,key:any,value:any){
32 | const res = Reflect.set(target, key, value);
33 | if(!isReadonly){
34 | // trigger 触发依赖
35 | trigger(target, key);
36 | }
37 | return res;
38 | }
39 | }
40 | export const mutableHandlers = {
41 | get,
42 | set
43 | }
44 | export const readonlyHandlers = {
45 | get:readonlyGet,
46 | set(target:any,key:any){
47 | console.warn(`${target} is readonly,could't set ${key}!`,target)
48 | return true;
49 | }
50 | }
51 |
52 | export const shallowReadonlyHandlers = Object.assign({},readonlyHandlers,{
53 | get:shallowReadonlyGet
54 | })
55 |
56 | // get => age(ref包裹) 返回 .value
57 | // 如果没有被ref包裹 返回 本身的值
58 | export const withRefHandlers = {
59 | get(target:any,key:any){
60 | return unRef(Reflect.get(target,key));
61 | },
62 | set(target:any,key:any,value:any){
63 | if(isRef(target[key]) && !isRef(value)){
64 | return target[key].value = value;
65 | } else {
66 | return Reflect.set(target, key, value);
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged,convert } from '../shared'
2 | import {ReactiveEffect} from './effect';
3 | class ComputedRefImp{
4 | private _getter;
5 | private _dirty : Boolean = true;
6 | private _value : any;
7 | private _effect: any
8 | constructor(getter:any){
9 | this._getter = getter;
10 |
11 | this._effect = new ReactiveEffect(getter,()=>{
12 | if(!this._dirty) this._dirty = true;
13 | })
14 | }
15 | get value(){
16 | // get
17 | // 当依赖的响应式对象的值发生改变
18 | // effect
19 | if(this._dirty){
20 | this._dirty = false;
21 | this._value = this._effect.run();
22 | }
23 | return this._value
24 | }
25 | }
26 | export function computed(getter:any){
27 | return new ComputedRefImp(getter)
28 | }
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend }from '../shared';
2 | let activeEffect:any;
3 | let shouldTrack = false;
4 | const targetMap = new Map();
5 | export class ReactiveEffect{
6 | private _fn: any;
7 | deps = [];
8 | active = true;
9 | onStop?: ()=> void;
10 | public scheduler:Function|undefined
11 | constructor(fn:any,scheduler?:Function){
12 | this._fn = fn
13 | this.scheduler = scheduler;
14 | }
15 | run(){
16 | if(!this.active){
17 | return this._fn();
18 | }
19 | shouldTrack = true;
20 | activeEffect = this;
21 | const result = this._fn();
22 | shouldTrack = false
23 | activeEffect = undefined;
24 | return result
25 | }
26 | stop(){
27 | if(this.active){
28 | cleanupEffect(this);
29 | if(this.onStop){
30 | this.onStop();
31 | }
32 | this.active = false;
33 | }
34 | }
35 | }
36 | function cleanupEffect(effect:any){
37 | effect.deps.forEach((dep:any) => {
38 | dep.delete(effect);
39 | });
40 | effect.deps.length = 0;
41 | }
42 | export function track(target:any, key:any){
43 | if(!isTracking()){
44 | return
45 | }
46 | // target => key => dep
47 | let depsMap = targetMap.get(target);
48 | if (!depsMap){
49 | depsMap = new Map();
50 | targetMap.set(target, depsMap);
51 | }
52 | let dep = depsMap.get(key);
53 | if(!dep){
54 | dep = new Set();
55 | depsMap.set(key,dep);
56 | }
57 | trackEffects(dep)
58 | }
59 | export function trackEffects(dep:any){
60 | // 判断dep是不是已经添加了这个activeEffect
61 | if(dep.has(activeEffect)) return
62 | dep.add(activeEffect);
63 | activeEffect.deps.push(dep);
64 | }
65 | export function trigger(target:any,key:any){
66 | let depsMap = targetMap.get(target);
67 | let dep = depsMap.get(key);
68 | triggerEffects(dep);
69 | }
70 | export function triggerEffects(dep:any){
71 | for(const effect of dep){
72 | if(effect.scheduler){
73 | effect.scheduler()
74 | }else{
75 | effect.run();
76 | }
77 | }
78 | }
79 | export function effect(fn:any, options:any = {}){
80 | //fn
81 | const _effect = new ReactiveEffect(fn, options.scheduler);
82 | // options
83 | // Object.assign(_effect,options);
84 | //extend
85 | extend(_effect,options)
86 | _effect.run();
87 | const runner:any = _effect.run.bind(_effect);
88 | runner.effect = _effect
89 | return runner;
90 | }
91 | export function stop(runner:any){
92 | runner.effect.stop();
93 | }
94 | export function isTracking(){
95 | return shouldTrack && activeEffect !== undefined
96 | }
97 |
98 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export { ref, proxyRefs } from "./ref";
2 |
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "../shared";
2 | import { mutableHandlers,readonlyHandlers,shallowReadonlyHandlers } from "./baseHandler";
3 | export const enum ReactiveFlags {
4 | IS_REACTIVE = "__v_isReactive",
5 | IS_READONLY = "__v_isReadOnly"
6 | }
7 | export function reactive(raw:any) {
8 | return createActiveObject(raw, mutableHandlers)
9 | }
10 | export function readonly(raw:any){
11 | return createActiveObject(raw,readonlyHandlers)
12 | }
13 | export function shallowReadonly(raw:any){
14 | return createActiveObject(raw,shallowReadonlyHandlers);
15 | }
16 | export function isReactive(value:any){
17 | return !!value[ReactiveFlags.IS_REACTIVE]
18 | }
19 | export function isReadonly(value:any){
20 | return !!value[ReactiveFlags.IS_READONLY]
21 | }
22 | export function isProxy(value:any){
23 | return isReactive(value) || isReadonly(value)
24 | }
25 | function createActiveObject(target: any, baseHandlers:any){
26 | if(!isObject(target)){
27 | console.warn(`target:${target} must be a Object!`)
28 | }
29 | return new Proxy(target,baseHandlers)
30 | }
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged,convert } from '../shared';
2 | import {trackEffects, triggerEffects,isTracking} from './effect';
3 | import {withRefHandlers} from './baseHandler';
4 | class RefImp {
5 | private _value:any;
6 | public dep:any;
7 | private _rawValue;
8 | public __v_isRef = true;
9 | constructor(value:any){
10 | this._rawValue = value;
11 | // 判断value是不是 一个对象
12 | this._value = convert(value);
13 | this.dep = new Set()
14 | }
15 | get value(){
16 | trackRefValue(this);
17 | return this._value
18 | }
19 | set value(newValue){
20 | // 判断value的值是否修改 对比的时候应该是两个Object对象而不是Proxy对象
21 | if(!hasChanged(this._rawValue,newValue)) return;
22 | this._rawValue = newValue;
23 | this._value = convert(newValue);
24 | triggerEffects(this.dep)
25 | return
26 | }
27 | }
28 | function trackRefValue(ref:any){
29 | if(isTracking()){
30 | trackEffects(ref.dep);
31 | }
32 | }
33 | export function ref(value:any){
34 | return new RefImp(value)
35 | }
36 | export function isRef(ref:any){
37 | return !!ref.__v_isRef;
38 | }
39 | export function unRef(ref:any){
40 | return isRef(ref)? ref.value : ref;
41 | }
42 | export function proxyRefs(objectWithRef:any){
43 | return new Proxy(objectWithRef,withRefHandlers)
44 | }
--------------------------------------------------------------------------------
/src/reactivity/tests/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import {computed} from '../computed';
2 | import {reactive} from '../reactive';
3 | describe('computed',()=>{
4 | it('happy path',()=>{
5 | // ref
6 | // .value
7 | // 缓存
8 | const user = reactive({
9 | age:1
10 | })
11 | const age = computed(()=>{
12 | return user.age;
13 | })
14 | expect(age.value).toBe(user.age);
15 | })
16 | it('should compute lazily',()=>{
17 | const value = reactive({
18 | foo:1
19 | });
20 | const getter = jest.fn(() =>{
21 | return value.foo;
22 | })
23 | const cValue = computed(getter);
24 | //lazy
25 | expect(getter).not.toBeCalledTimes(1);
26 |
27 | expect(cValue.value).toBe(1);
28 | expect(getter).toBeCalledTimes(1);
29 | expect(cValue.value).toBe(1);
30 | expect(getter).toBeCalledTimes(1);
31 | value.foo = 2;// trigger => effect => get 重新执行
32 | expect(getter).toHaveBeenCalledTimes(1);
33 | expect(cValue.value).toBe(2);
34 | expect(getter).toHaveBeenCalledTimes(2);
35 | })
36 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import {reactive} from '../reactive';
2 | import {effect,stop} from '../effect';
3 | describe('effect',()=>{
4 | it('happy path',()=>{
5 | const user = reactive({
6 | age:10
7 | })
8 | let nextAge;
9 | effect(()=>{
10 | nextAge = user.age + 2;
11 | })
12 | expect(nextAge).toBe(12);
13 |
14 | // update
15 | user.age++;
16 | expect(nextAge).toBe(13);
17 | })
18 | it('should return a runner when user call the effect',()=>{
19 | //1. effect(fn) => function(runner) => fn => return
20 | let foo = 10;
21 | const runner = effect(()=>{
22 | foo++;
23 | return 'foo runner';
24 | });
25 | expect(foo).toBe(11);
26 | const r = runner();
27 | expect(foo).toBe(12);
28 | expect(r).toBe('foo runner');
29 | })
30 |
31 | it("scheduler", () => {
32 | // 1.通过effect的第二个参数scheduler的fn
33 | // 2.effect第一次执行的时候 还会执行fn
34 | // 3.当响应式对象set 即update的时候 不会执行 fn 而是执行scheduler
35 | // 4.如果当执行runner的时候会再次执行fn
36 | let dummy;
37 | let run: any;
38 | const scheduler = jest.fn(() => {
39 | run = runner;
40 | });
41 | const obj = reactive({ foo: 1 });
42 | const runner = effect(
43 | () => {
44 | dummy = obj.foo;
45 | },
46 | { scheduler }
47 | );
48 | expect(scheduler).not.toHaveBeenCalled();
49 | expect(dummy).toBe(1);
50 | // should be called on first trigger
51 | obj.foo++;
52 | expect(scheduler).toHaveBeenCalledTimes(1);
53 | // // should not run yet
54 | expect(dummy).toBe(1);
55 | // // manually run
56 | run();
57 | // // should have run
58 | expect(dummy).toBe(2);
59 | });
60 | it('stop',()=>{
61 | let dummy:any;
62 | const obj =reactive({prop:1});
63 | const runner = effect(()=>{
64 | dummy = obj.prop;
65 | })
66 | obj.prop = 2;
67 | expect(dummy).toBe(2);
68 | stop(runner);
69 | obj.prop ++;
70 |
71 | expect(dummy).toBe(2);
72 |
73 | // stopped effect should still be manully callalbe
74 | runner();
75 | expect(dummy).toBe(3);
76 | })
77 | it('onstop',()=>{
78 | const obj = reactive({
79 | foo:1
80 | })
81 | const onStop = jest.fn();
82 | let dummy;
83 | const runner = effect(()=>{
84 | dummy=obj.foo;
85 | }, {
86 | onStop
87 | })
88 |
89 | stop(runner);
90 | expect(onStop).toBeCalledTimes(1);
91 | })
92 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import {reactive,isReactive,isProxy} from '../reactive'
2 | describe('reactive',()=>{
3 | it('happy path',()=>{
4 | const original = {foo:1};
5 | const observed = reactive(original);
6 | expect(observed).not.toBe(original);
7 | expect(observed.foo).toBe(1);
8 | expect(isReactive(original)).toBe(false);
9 | expect(isReactive(observed)).toBe(true);
10 | expect(isProxy(observed)).toBe(true);
11 | })
12 | test('nested reactive',()=>{
13 | const original = {
14 | nested:{
15 | foo:1
16 | },
17 | array:[{bar:1}]
18 | };
19 | const observed = reactive(original);
20 | expect(isReactive(observed.nested)).toBe(true);
21 | expect(isReactive(observed.array)).toBe(true);
22 | expect(isReactive(observed.array[0])).toBe(true);
23 | })
24 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import {readonly,isReadonly,isProxy} from '../reactive'
2 | describe('readonly',()=>{
3 | it('happy path',()=>{
4 | // cant set
5 | const original = {foo:1,bar:2}
6 | const wraped = readonly(original);
7 | expect(original).not.toBe(wraped);
8 | expect(original.bar).toBe(2);
9 | })
10 | it('should make nested values readonly',()=>{
11 | const original = {foo:1,bar:{ baz:2}};
12 | const wraped = readonly(original);
13 | expect(isReadonly(wraped)).toBe(true);
14 | expect(isReadonly(original)).toBe(false);
15 | expect(isReadonly(wraped.bar)).toBe(true);
16 | expect(isProxy(wraped)).toBe(true);
17 | })
18 | it('should call console.warn warn when call set',()=>{
19 | // console.warn()
20 | // mock
21 | console.warn = jest.fn()
22 | const user = readonly({
23 | age:10
24 | })
25 | user.age++;
26 | expect(console.warn).toBeCalled();
27 | })
28 | it('isReadOnly',()=>{
29 | const user = {age:10}
30 | const readonlyUser = readonly({
31 | age:10
32 | })
33 | expect(isReadonly(user)).toBe(false);
34 | expect(isReadonly(readonlyUser)).toBe(true);
35 | })
36 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import {effect} from '../effect';
2 | import {ref, isRef, unRef,proxyRefs} from '../ref';
3 | import {reactive} from '../reactive';
4 | describe('ref',()=>{
5 | it("happy path",()=>{
6 | const a = ref(1);
7 | expect(a.value).toBe(1);
8 | })
9 | it('should be reactive',()=>{
10 | const a = ref(1);
11 | let dummy;
12 | let calls= 0;
13 | effect(()=>{
14 | calls++;
15 | dummy = a.value;
16 | })
17 | expect(dummy).toBe(1);
18 | expect(calls).toBe(1);
19 | a.value = 2;
20 | expect(calls).toBe(2);
21 | expect(dummy).toBe(2);
22 | a.value = 2;
23 | // same value should't trigger;
24 | expect(calls).toBe(2);
25 | expect(dummy).toBe(2);
26 | })
27 | it('should make nested properties reactive',()=>{
28 | const a = ref({
29 | count:1
30 | })
31 | expect(a.value.count).toBe(1);
32 | let dummy;
33 | effect(()=>{
34 | dummy = a.value.count;
35 | })
36 | expect(dummy).toBe(1);
37 | a.value.count++;
38 | expect(dummy).toBe(2);
39 | })
40 | // isRef unRef
41 | it('is ref',()=>{
42 | const a = ref(1);
43 | const user = reactive({
44 | age:1
45 | })
46 | expect(isRef(a)).toBe(true);
47 | expect(isRef(user)).toBe(false);
48 | expect(isRef(1)).toBe(false);
49 | })
50 | it('should be unRef',()=>{
51 | const a = ref(1);
52 | expect(isRef(a)).toBe(true);
53 | const b = unRef(a);
54 | expect(isRef(b)).toBe(false);
55 | })
56 |
57 | it('proxyRefs',()=>{
58 | const user = {
59 | age:ref(10),
60 | name:'Tom'
61 | }
62 | // get
63 | const proxyUser = proxyRefs(user);
64 | expect(user.age.value).toBe(10);
65 | expect(proxyUser.age).toBe(10);
66 | expect(proxyUser.name).toBe('Tom');
67 | //set
68 | proxyUser.age = 11;
69 | expect(proxyUser.age).toBe(11);
70 | proxyUser.age = ref(12);
71 | expect(user.age.value).toBe(12);
72 | expect(proxyUser.age).toBe(12);
73 | })
74 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { shallowReadonly,isReadonly,isProxy } from "../reactive";
2 |
3 | describe('shallowReadonly',()=>{
4 | it('should not make non-reactive properties reactive',()=>{
5 | const props = shallowReadonly({n:{foo:1}});
6 | console.warn = jest.fn();
7 | expect(isReadonly(props)).toBe(true);
8 | expect(isReadonly(props.n)).toBe(false);
9 | expect(isProxy(props)).toBe(true);
10 | })
11 | it('should be call console.warn when call set',()=>{
12 | const props = shallowReadonly({n:{foo:1}});
13 | console.warn = jest.fn();
14 | props.n = {foo:2};
15 | expect(console.warn).toBeCalled();
16 | })
17 | })
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 | export function provide(key,value){
3 | // set
4 | const currentInstance:any = getCurrentInstance();
5 | if(currentInstance){
6 | let {provides} = currentInstance;
7 | const parentProvides = currentInstance.parent.provides;
8 | // 把provide的原型指向parentProvides
9 | // init 的时候执行
10 | if (provides === parentProvides){
11 | provides = currentInstance.provides = Object.create(parentProvides);
12 | }
13 | provides[key] = value;
14 | }
15 | }
16 | export function inject(key,defaultValue){
17 | // get
18 | const currentInstance:any = getCurrentInstance();
19 | if(currentInstance){
20 | const parentProvides = currentInstance.parent.provides;
21 | if( key in parentProvides){
22 | return parentProvides[key];
23 | }else if (defaultValue){
24 | if(typeof defaultValue === 'function'){
25 | return defaultValue()
26 | }
27 | return defaultValue;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { initProps } from "./componentProps";
2 | import { initSlots } from "./componentSlots";
3 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance";
4 | import { shallowReadonly } from "../reactivity/reactive";
5 | import { emit } from "./componetEmit";
6 | import { proxyRefs } from "../reactivity";
7 |
8 | export function createComponentInstance(vnode: any, parent: any) {
9 | const instance = {
10 | vnode,
11 | type: vnode.type,
12 | setupState: {},
13 | props: {},
14 | slots: {},
15 | provides: parent ? parent.provides : {},
16 | parent,
17 | isMounted: false,
18 | emit: () => {},
19 | };
20 |
21 | // 初始化赋值emit
22 | instance.emit = emit.bind(null, instance) as any;
23 | return instance;
24 | }
25 | export function setupInstance(instance: any) {
26 | initProps(instance, instance.vnode.props);
27 | initSlots(instance, instance.vnode.children);
28 | setupStatefulComponent(instance);
29 | }
30 | export function setupStatefulComponent(instance: any) {
31 | const Component = instance.type;
32 |
33 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
34 |
35 | const { setup } = Component;
36 | if (setup) {
37 | setCurrentInstance(instance);
38 | const setupResult =
39 | setup && setup(shallowReadonly(instance.props), { emit: instance.emit });
40 | setCurrentInstance(null);
41 | handleSetupResult(instance, setupResult);
42 | }
43 | }
44 | export function handleSetupResult(instance: any, setupResult: any) {
45 | // Object
46 | if (typeof setupResult === "object") {
47 | instance.setupState = proxyRefs(setupResult);
48 | }
49 |
50 | finishCompoentSetup(instance);
51 | // function
52 | }
53 |
54 | function finishCompoentSetup(instance: any) {
55 | const Component = instance.type;
56 | // compile
57 | if (compiler && !Component.render) {
58 | if (Component.template) {
59 | Component.render = compiler(Component.template);
60 | }
61 | }
62 | instance.render = Component.render;
63 | }
64 |
65 | let currentInstance = null;
66 |
67 | // getCurrentInstance
68 | export function getCurrentInstance() {
69 | return currentInstance;
70 | }
71 |
72 | function setCurrentInstance(instance: null) {
73 | currentInstance = instance;
74 | }
75 |
76 | let compiler;
77 |
78 | export function registerRuntimeComplier(_compiler) {
79 | compiler = _compiler;
80 | }
81 |
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance: any,rawProps: any){
2 | // attrs
3 | instance.props = rawProps || {};
4 | }
--------------------------------------------------------------------------------
/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}: any, key: string){
11 | // setupState
12 | const {setupState,props} = instance;
13 | if(hasOwn(setupState,key)){
14 | return setupState[key];
15 | }else if (hasOwn(props,key)){
16 | return props[key];
17 | }
18 |
19 | const publicGetter = publicPropertiesMap[key];
20 | if(publicGetter){
21 | return publicGetter(instance);
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/shapeFlags";
2 |
3 | export function initSlots(instance, children){
4 | // instance.slots = Array.isArray(children) ? children : [children];
5 | const {vnode} = instance;
6 | if(vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN){
7 | normalizeObjectSlots(instance.slots,children);
8 | }
9 | }
10 | export function normalizeObjectSlots(slots,children){
11 | for (const key in children) {
12 | const value = children[key];
13 | slots[key] = (props) => normalizeSlotValue(value(props));
14 | }
15 | }
16 | export function normalizeSlotValue(value){
17 | return Array.isArray(value) ? value : [value];
18 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentUpdateUtils.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 | }
--------------------------------------------------------------------------------
/src/runtime-core/componetEmit.ts:
--------------------------------------------------------------------------------
1 | import {toHandleKey,cameLize} from '../shared'
2 | export function emit(instance,event,...args){;
3 | // instance.props => event
4 | const { props } = instance;
5 |
6 | const handlerName = toHandleKey(event);
7 | const handler = props[cameLize(handlerName)];
8 | handler && handler(...args);
9 | }
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function createAppAPI (render){
4 | return function createApp(rootComponent:any){
5 | return {
6 | mount(rootContainer:any){
7 | //先转化为虚拟节点vnode
8 | // component => vnode
9 | const vnode = createVNode(rootComponent);
10 | render(vnode, rootContainer)
11 | }
12 | }
13 | }
14 | }
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 | export function h(type:String, props?:Object, children?:any){
3 | return createVNode(type, props, children);
4 | }
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import {createVNode , Fragment} from '../vnode';
2 | export function renderSlots(slots,name, props){
3 | const slot = slots[name];
4 | if(slot){
5 | // function
6 | if(typeof slot === 'function'){
7 | //children 不可以有 array
8 | // 只需要把 children
9 | return createVNode(Fragment, {}, slot(props))
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | export { createAppAPI } from "./createApp";
2 | export { h } from "./h";
3 | export { renderSlots } from "./helpers/renderSlots";
4 | export { createTextVNode, createElementVNode } from "./vnode";
5 | export { getCurrentInstance, registerRuntimeComplier } from "./component";
6 | export { inject, provide } from "./apiInject";
7 | export { createRender } from "./render";
8 | export { nextTick } from "./scheduler";
9 | export { toDisplayString } from "../shared";
10 | // 模块适应vue设计层级 compile 与 runtime-dom => runtime-core => reactivity
11 | export * from "../reactivity";
12 |
--------------------------------------------------------------------------------
/src/runtime-core/render.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../reactivity/effect";
2 | import { ShapeFlags } from "../shared/shapeFlags";
3 | import { createComponentInstance, setupInstance } from "./component";
4 | import { createAppAPI } from "./createApp";
5 | import { Fragment, Text } from "./vnode";
6 | import { shouldUpdateComponent } from "./componentUpdateUtils";
7 | import { queueJob } from "./scheduler";
8 |
9 | export function createRender(options) {
10 | const {
11 | createElement: hostCreateElement,
12 | patchProp: hostPatchProp,
13 | insert: hostInsert,
14 | remove: hostRemove,
15 | setElementText: hostSetElementText,
16 | setElementArray: hostSetElementArray,
17 | } = options;
18 |
19 | function render(vnode: any, container: any) {
20 | // shapeFlags
21 | // patch递归
22 | // 判断是不是一个element类型
23 | patch(null, vnode, container);
24 | }
25 |
26 | function patch(
27 | n1,
28 | n2: any,
29 | container: any,
30 | parentComponent: any = null,
31 | anchor: any = null
32 | ) {
33 | // todo 判断是不是一个element
34 | // 处理组件
35 | // shapeflag 判断
36 | const { type, shapeFlag } = n2;
37 | // Fragment => 只渲染children
38 | switch (type) {
39 | case Fragment:
40 | processFragment(n1, n2, container, parentComponent);
41 | break;
42 | case Text:
43 | processText(n1, n2, container);
44 | break;
45 | default:
46 | if (shapeFlag & ShapeFlags.ELEMENT) {
47 | processElement(n1, n2, container, parentComponent, anchor);
48 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
49 | processComponent(n1, n2, container, parentComponent);
50 | }
51 | break;
52 | }
53 | }
54 |
55 | function processElement(
56 | n1,
57 | n2: any,
58 | container: any,
59 | parentComponent,
60 | anchor
61 | ) {
62 | if (!n1) {
63 | // init
64 | mountElement(n2, container, parentComponent, anchor);
65 | } else {
66 | // update
67 | patchElement(n1, n2, container, parentComponent, anchor);
68 | }
69 | }
70 | const EMPTY_OBJ = {};
71 | function patchElement(n1, n2, container, parentComponent, anchor) {
72 | const oldProps = n1.props || EMPTY_OBJ;
73 | const newProps = n2.props || EMPTY_OBJ;
74 | const el = (n2.el = n1.el);
75 | patchChildren(n1, n2, el, parentComponent, anchor);
76 | patchProps(el, oldProps, newProps);
77 | }
78 |
79 | function patchChildren(n1, n2, container, parentComponent, anchor) {
80 | const prevShapeFlag = n1.shapeFlag;
81 | const nextShapeFlag = n2.shapeFlag;
82 | const c1 = n1.children;
83 | const c2 = n2.children;
84 | if (nextShapeFlag & ShapeFlags.TEXT_CHILDREN) {
85 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
86 | // array => text
87 | // 1.把老的children清空
88 | unmountChildren(c1);
89 | // 2.set 新的textchildren
90 | // 如果 n1.children !== n2.children 则直接把n2的文本值设置为container的textContent
91 | // text => text 同时需执行以下
92 | hostSetElementText(container, c2);
93 | } else {
94 | hostSetElementText(container, c2);
95 | }
96 | } else {
97 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
98 | // array => text
99 | // 1.把容器的文本内容清空
100 | hostSetElementText(container, "");
101 | mountChildren(c2, container, parentComponent);
102 | } else {
103 | // array diff => array
104 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
105 | }
106 | }
107 | }
108 |
109 | function patchKeyedChildren(
110 | c1,
111 | c2,
112 | container,
113 | parentComponent,
114 | parentAnchor
115 | ) {
116 | console.log("patch child");
117 | let i = 0;
118 | let e1 = c1.length - 1;
119 | let e2 = c2.length - 1;
120 | const l2 = c2.length;
121 | // 左侧对比
122 | while (i <= e1 && i <= e2) {
123 | const n1 = c1[i];
124 | const n2 = c2[i];
125 | if (isSameVNodeType(n1, n2)) {
126 | patch(n1, n2, container, parentComponent);
127 | } else {
128 | break;
129 | }
130 | i++;
131 | }
132 | // 右侧对比
133 | while (i <= e1 && i <= e2) {
134 | const n1 = c1[e1];
135 | const n2 = c2[e2];
136 | if (isSameVNodeType(n1, n2)) {
137 | patch(n1, n2, container, parentComponent);
138 | } else {
139 | break;
140 | }
141 | e1--;
142 | e2--;
143 | }
144 | if (i > e1) {
145 | // 新的比老的多
146 | if (i <= e2) {
147 | // 如果是这种情况的话就说明 e2 也就是新节点的数量大于旧节点的数量
148 | // 也就是说新增了 vnode
149 | // 应该循环 c2
150 | // 锚点的计算:新的节点有可能需要添加到尾部,也可能添加到头部,所以需要指定添加的问题
151 | // 要添加的位置是当前的位置(e2 开始)+1
152 | // 因为对于往左侧添加的话,应该获取到 c2 的第一个元素
153 | // 所以我们需要从 e2 + 1 取到锚点的位置
154 | const nextPos = e2 + 1;
155 | const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;
156 | while (i <= e2) {
157 | console.log(`需要新创建一个 vnode: ${c2[i].key}`);
158 | patch(null, c2[i], container, parentComponent, anchor);
159 | i++;
160 | }
161 | }
162 | } else if (i > e2) {
163 | // 新的比老的少
164 | const anchorIndex = i + e1 - e2;
165 | while (i < anchorIndex) {
166 | hostRemove(c1[i].el);
167 | i++;
168 | }
169 | } else {
170 | // 中间对比
171 | let s1 = i;
172 | let s2 = i;
173 | let isPatchedCount = 0;
174 | const toBePatched = e2 - s2 + 1;
175 | const keyToNewIndexMap = new Map();
176 | const newIndexToOldIndexMap = new Array(toBePatched);
177 | let moved = false;
178 | let maxNewIndexSoFar = 0;
179 | for (let i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;
180 | for (let i = s2; i <= e2; i++) {
181 | const nextChild = c2[i];
182 | keyToNewIndexMap.set(nextChild.key, i);
183 | }
184 | for (let i = s1; i <= e1; i++) {
185 | const prevChild = c1[i];
186 | if (isPatchedCount >= toBePatched) {
187 | hostRemove(prevChild.el);
188 | continue;
189 | }
190 | let newIndex;
191 | if (prevChild.key != null) {
192 | newIndex = keyToNewIndexMap.get(prevChild.key);
193 | } else {
194 | for (let j = s2; j <= e2; j++) {
195 | if (isSameVNodeType(prevChild, c2[j])) {
196 | newIndex = j;
197 | break;
198 | }
199 | }
200 | }
201 | if (newIndex === undefined) {
202 | hostRemove(prevChild.el);
203 | } else {
204 | if (newIndex >= maxNewIndexSoFar) {
205 | maxNewIndexSoFar = newIndex;
206 | } else {
207 | moved = true;
208 | }
209 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
210 | patch(prevChild, c2[newIndex], container, parentComponent, null);
211 | isPatchedCount++;
212 | }
213 | }
214 | const increasingNewIndexSequence = moved
215 | ? getSequence(newIndexToOldIndexMap)
216 | : [];
217 | let j = increasingNewIndexSequence.length - 1;
218 | for (let i = toBePatched - 1; i >= 0; i--) {
219 | const nextIndex = i + s2;
220 | const nextChild = c2[nextIndex];
221 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
222 | if (newIndexToOldIndexMap[i] === 0) {
223 | patch(null, nextChild, container, parentComponent, anchor);
224 | } else if (moved) {
225 | // 如果判断需要moved
226 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
227 | console.log(anchor);
228 | hostInsert(nextChild.el, container, anchor);
229 | } else {
230 | j--;
231 | }
232 | }
233 | }
234 | }
235 | }
236 |
237 | // 获取最长递增子序列算法
238 | function getSequence(arr: number[]): number[] {
239 | const p = arr.slice();
240 | const result = [0];
241 | let i, j, u, v, c;
242 | const len = arr.length;
243 | for (i = 0; i < len; i++) {
244 | const arrI = arr[i];
245 | if (arrI !== 0) {
246 | j = result[result.length - 1];
247 | if (arr[j] < arrI) {
248 | p[i] = j;
249 | result.push(i);
250 | continue;
251 | }
252 | u = 0;
253 | v = result.length - 1;
254 | while (u < v) {
255 | c = (u + v) >> 1;
256 | if (arr[result[c]] < arrI) {
257 | u = c + 1;
258 | } else {
259 | v = c;
260 | }
261 | }
262 | if (arrI < arr[result[u]]) {
263 | if (u > 0) {
264 | p[i] = result[u - 1];
265 | }
266 | result[u] = i;
267 | }
268 | }
269 | }
270 | u = result.length;
271 | v = result[u - 1];
272 | while (u-- > 0) {
273 | result[u] = v;
274 | v = p[v];
275 | }
276 | return result;
277 | }
278 |
279 | function isSameVNodeType(n1, n2) {
280 | return n1.type === n2.type && n1.key === n2.key;
281 | }
282 |
283 | function unmountChildren(children) {
284 | for (let i = 0; i < children.length; i++) {
285 | const el = children[i].el;
286 | // remove
287 | hostRemove(el);
288 | }
289 | }
290 | function patchProps(el, oldProps, newProps) {
291 | // 两个props Object判断不相等?
292 | if (oldProps !== newProps) {
293 | for (const key in newProps) {
294 | const prevProp = oldProps[key] ? oldProps[key] : null;
295 | const nextProp = newProps[key];
296 | if (prevProp !== nextProp) {
297 | hostPatchProp(el, key, prevProp, nextProp);
298 | }
299 | }
300 | if (oldProps !== EMPTY_OBJ) {
301 | for (const key in oldProps) {
302 | if (!(key in newProps)) {
303 | hostPatchProp(el, key, oldProps[key], null);
304 | }
305 | }
306 | }
307 | }
308 | }
309 |
310 | function processComponent(n1, n2: any, container: any, parentComponent) {
311 | if (!n1) {
312 | mountComponent(n2, container, parentComponent);
313 | } else {
314 | updateComponent(n1, n2);
315 | }
316 | }
317 |
318 | function updateComponent(n1, n2) {
319 | const instance = (n2.component = n1.component);
320 | if (shouldUpdateComponent(n1, n2)) {
321 | console.log("组件更新", n1, n2);
322 | instance.next = n2;
323 | instance.update();
324 | } else {
325 | n2.el = n1.el;
326 | n2.vnode = n2;
327 | }
328 | }
329 |
330 | function updateComponentPreRender(instance, nextVNode) {
331 | instance.vnode = nextVNode;
332 | instance.next = null;
333 | instance.props = nextVNode.props;
334 | }
335 |
336 | function processFragment(n1, n2: any, container: any, parentComponent) {
337 | // Implement
338 | mountChildren(n2.children, container, parentComponent);
339 | }
340 |
341 | function processText(n1, n2: any, container) {
342 | const { children } = n2;
343 | const textNode = (n2.el = document.createTextNode(children));
344 | container.append(textNode);
345 | }
346 |
347 | function mountElement(vnode: any, container: any, parentComponent, anchor) {
348 | // canvas
349 | // new Element()
350 | // createElement()
351 | const el = (vnode.el = hostCreateElement(vnode.type));
352 | // const el = (vnode.el = document.createElement(vnode.type));
353 |
354 | //children: string array
355 | const { children, shapeFlag } = vnode;
356 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
357 | el.textContent = children;
358 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
359 | mountChildren(children, el, parentComponent);
360 | }
361 | // props
362 | const { props } = vnode;
363 | // patch Prop
364 |
365 | for (const key in props) {
366 | const val = props[key];
367 | hostPatchProp(el, key, null, val);
368 | }
369 |
370 | // container.append(el);
371 | hostInsert(el, container, anchor);
372 | }
373 |
374 | function mountChildren(children: any[], container: any, parentComponent) {
375 | children.forEach((child: any) => {
376 | patch(null, child, container, parentComponent);
377 | });
378 | }
379 |
380 | function mountComponent(
381 | initalVNode: any,
382 | container: any,
383 | parentComponent: any
384 | ) {
385 | const instance = (initalVNode.component = createComponentInstance(
386 | initalVNode,
387 | parentComponent
388 | ));
389 | setupInstance(instance);
390 | setupRenderEffect(instance, initalVNode, container);
391 | }
392 |
393 | function setupRenderEffect(instance: any, initalVNode, container: any) {
394 | // 拆分更新与初始化
395 | instance.update = effect(
396 | () => {
397 | if (!instance.isMounted) {
398 | console.log("init");
399 | const { proxy } = instance;
400 | const subTree = (instance.subTree = instance.render.call(
401 | proxy,
402 | proxy
403 | ));
404 | // vnode => patch
405 | // vnode => element => mountElement
406 | patch(null, subTree, container, instance);
407 | // element => mounted
408 | initalVNode.el = subTree.el;
409 | instance.isMounted = true;
410 | } else {
411 | console.log("update");
412 | const { proxy, next, vnode } = instance;
413 | if (next) {
414 | next.el = vnode.el;
415 | updateComponentPreRender(instance, next);
416 | }
417 | const prevSubTree = instance.subTree;
418 | const subTree = (instance.subTree = instance.render.call(
419 | proxy,
420 | proxy
421 | ));
422 | // vnode => patch
423 | // vnode => element => mountElement
424 | patch(prevSubTree, subTree, container, instance);
425 | // element => mounted
426 | initalVNode.el = subTree.el;
427 | }
428 | },
429 | {
430 | scheduler() {
431 | console.log("update-----scheduler");
432 | queueJob(instance.update);
433 | },
434 | }
435 | );
436 | }
437 |
438 | return {
439 | createApp: createAppAPI(render),
440 | };
441 | }
442 |
--------------------------------------------------------------------------------
/src/runtime-core/scheduler.ts:
--------------------------------------------------------------------------------
1 | const queue:any[]= [];
2 |
3 | let isFlushPending = false;
4 | const p = Promise.resolve();
5 | export function nextTick(fn){
6 | return fn ? p.then(fn) : p;
7 | }
8 | export function queueJob(job){
9 | if(!queue.includes(job)){
10 | queue.push(job);
11 | }
12 | queueFlash();
13 | }
14 | function queueFlash(){
15 | if(isFlushPending) return;
16 | isFlushPending = true;
17 | nextTick(
18 | flushJobs
19 | )
20 | }
21 | function flushJobs(){
22 | isFlushPending = false;
23 | let job;
24 | while(job = queue.shift()){
25 | job && job();
26 | }
27 | }
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags, getShapeFlag } from "../shared/shapeFlags";
2 | export { createVNode as createElementVNode };
3 | export const Fragment = Symbol("Fragment");
4 | export const Text = Symbol("Text");
5 | export function createVNode(type: any, props?: any, children?: any) {
6 | const VNode = {
7 | el: null,
8 | type,
9 | props: props || {},
10 | children,
11 | component: null,
12 | next: null,
13 | key: props?.key,
14 | shapeFlag: getShapeFlag(type),
15 | };
16 | // children?
17 | if (typeof children === "string") {
18 | VNode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
19 | } else if (Array.isArray(children)) {
20 | VNode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
21 | }
22 | if (VNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
23 | if (typeof children === "object") {
24 | VNode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
25 | }
26 | }
27 | return VNode;
28 | }
29 |
30 | export function createTextVNode(text: string) {
31 | return createVNode(Text, {}, text);
32 | }
33 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import {createRender} from '../runtime-core';
2 | import { isOn } from "../shared/index";
3 | function createElement(type) {
4 | return document.createElement(type);
5 | }
6 |
7 | function patchProp(el, key, prevVal, nextVal){
8 | if(isOn(key)){
9 | const event = key.slice(2).toLocaleLowerCase();
10 | el.addEventListener(event,()=>{
11 | nextVal();
12 | });
13 | }else{
14 | if(nextVal === undefined || nextVal === null){
15 | el.removeAttribute(key);
16 | }else{
17 | el.setAttribute(key,nextVal);
18 | }
19 | }
20 | }
21 |
22 | function insert(child, parent, anchor:any = null){
23 | console.log('insert',child);
24 | parent.insertBefore(child,anchor);
25 | }
26 |
27 | function remove(child){
28 | const parent = child.parentNode;
29 | if(parent){
30 | parent.removeChild(child);
31 | }
32 | }
33 |
34 | function setElementText(el, text){
35 | el.textContent = text;
36 | }
37 |
38 | function setElementArray(el, children){
39 | console.log(el);
40 | console.log(children);
41 | }
42 | const render:any= createRender({
43 | createElement,
44 | patchProp,
45 | insert,
46 | remove,
47 | setElementText,
48 | setElementArray
49 | })
50 |
51 | export function createApp(...args){
52 | return render.createApp(...args)
53 | }
54 |
55 | export * from '../runtime-core';
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "../reactivity/reactive";
2 |
3 | export * from "./toDisplayString";
4 |
5 | export const extend = Object.assign;
6 |
7 | export const isObject = (val: any) => {
8 | return val !== null && typeof val === "object";
9 | };
10 |
11 | export const isString = (val: any) => typeof val === "string";
12 |
13 | export const hasChanged = (val: any, newVal: any) => {
14 | return !Object.is(val, newVal);
15 | };
16 |
17 | export const convert = (newValue: any) => {
18 | return isObject(newValue) ? reactive(newValue) : newValue;
19 | };
20 | export const isOn = (event: string) => {
21 | return /^on[A-Z]/.test(event);
22 | };
23 |
24 | export const hasOwn = (properties, key) => {
25 | return Object.prototype.hasOwnProperty.call(properties, key);
26 | };
27 | // TPP 先写一个特定的行为 再重构成一个通用的行为
28 | // add => Add
29 | export const capitalize = (str: string) => {
30 | return str.charAt(0).toUpperCase() + str.slice(1);
31 | };
32 | export const toHandleKey = (str: string) => {
33 | return str ? "on" + capitalize(str) : "";
34 | };
35 | // add-foo => addFoo
36 | export const cameLize = (str: string) => {
37 | return str.replace(/-(\w)/g, (_, c: string) => {
38 | return c ? c.toUpperCase() : "";
39 | });
40 | };
41 |
--------------------------------------------------------------------------------
/src/shared/shapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags{
2 | ELEMENT = 1,
3 | STATEFUL_COMPONENT = 1 << 1,
4 | TEXT_CHILDREN = 1 << 2,
5 | ARRAY_CHILDREN = 1 << 3,
6 | SLOT_CHILDREN = 1 << 4
7 | }
8 | export function getShapeFlag(type: String){
9 | return typeof type === "string"? ShapeFlags.ELEMENT : ShapeFlags.STATEFUL_COMPONENT
10 | }
11 |
--------------------------------------------------------------------------------
/src/shared/toDisplayString.ts:
--------------------------------------------------------------------------------
1 | export function toDisplayString(value) {
2 | return String(value);
3 | }
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "moduleResolution": "node",
5 | "esModuleInterop": true,
6 | "target": "es2016",
7 | "module": "esnext",
8 | "noImplicitAny": false,
9 | "removeComments": false,
10 | "preserveConstEnums": true,
11 | "sourceMap": true,
12 | "downlevelIteration": true,
13 | "lib": ["es6", "DOM"]
14 | },
15 | "include": ["src/index.ts", "src/global.d.ts"]
16 | }
17 |
--------------------------------------------------------------------------------