├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── babel.config.js
├── example
├── apiInject
│ ├── App.js
│ └── index.html
├── compiler-base
│ ├── App.js
│ ├── index.html
│ └── main.js
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlot
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentUpdate
│ ├── App.js
│ ├── Child.js
│ ├── index.html
│ └── main.js
├── currentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── customRenderer
│ ├── App.js
│ ├── index.html
│ └── main.js
├── helloworld
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── nextTicker
│ ├── App.js
│ ├── index.html
│ └── main.js
├── patchChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
└── update
│ ├── App.js
│ ├── index.html
│ └── main.js
├── lib
├── 5c24-mini-vue.cjs.js
└── 5c24-mini-vue.esm.js
├── package.json
├── packages
├── compiler-core
│ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── codegen.spec.ts.snap
│ │ ├── codegen.spec.ts
│ │ ├── parse.spec.ts
│ │ └── transform.spec.ts
│ ├── package.json
│ └── src
│ │ ├── ast.ts
│ │ ├── codegen.ts
│ │ ├── compile.ts
│ │ ├── index.ts
│ │ ├── parse.ts
│ │ ├── runtimeHelpers.ts
│ │ ├── transform.ts
│ │ ├── transforms
│ │ ├── transformElement.ts
│ │ ├── transformExpression.ts
│ │ └── transformText.ts
│ │ └── utils.ts
├── reactivity
│ ├── __tests__
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ └── shallowReadonly.spec.ts
│ ├── package.json
│ └── src
│ │ ├── baseHandlers.ts
│ │ ├── computed.ts
│ │ ├── effect.ts
│ │ ├── index.ts
│ │ ├── reactive.ts
│ │ └── ref.ts
├── runtime-core
│ ├── package.json
│ └── src
│ │ ├── apiInject.ts
│ │ ├── component.ts
│ │ ├── componentEmit.ts
│ │ ├── componentProps.ts
│ │ ├── componentPublicInstance.ts
│ │ ├── componentSlots.ts
│ │ ├── componentUpdateUtils.ts
│ │ ├── createApp.ts
│ │ ├── h.ts
│ │ ├── helpers
│ │ └── renderSlots.ts
│ │ ├── index.ts
│ │ ├── renderer.ts
│ │ ├── scheduler.ts
│ │ └── vnode.ts
├── runtime-dom
│ ├── package.json
│ └── src
│ │ └── index.ts
├── shared
│ ├── package.json
│ └── src
│ │ ├── ShapeFlags.ts
│ │ ├── index.ts
│ │ └── toDisplayString.ts
└── vue
│ ├── dist
│ ├── 5c24-mini-vue.cjs.js
│ └── 5c24-mini-vue.esm.js
│ ├── examples
│ ├── apiInject
│ │ ├── App.js
│ │ └── index.html
│ ├── compiler-base
│ │ ├── App.js
│ │ ├── index.html
│ │ └── main.js
│ ├── componentEmit
│ │ ├── App.js
│ │ ├── Foo.js
│ │ ├── index.html
│ │ └── main.js
│ ├── componentSlot
│ │ ├── App.js
│ │ ├── Foo.js
│ │ ├── index.html
│ │ └── main.js
│ ├── componentUpdate
│ │ ├── App.js
│ │ ├── Child.js
│ │ ├── index.html
│ │ └── main.js
│ ├── currentInstance
│ │ ├── App.js
│ │ ├── Foo.js
│ │ ├── index.html
│ │ └── main.js
│ ├── customRenderer
│ │ ├── App.js
│ │ ├── index.html
│ │ └── main.js
│ ├── helloworld
│ │ ├── App.js
│ │ ├── Foo.js
│ │ ├── index.html
│ │ └── main.js
│ ├── nextTicker
│ │ ├── App.js
│ │ ├── index.html
│ │ └── main.js
│ ├── patchChildren
│ │ ├── App.js
│ │ ├── ArrayToArray.js
│ │ ├── ArrayToText.js
│ │ ├── TextToArray.js
│ │ ├── TextToText.js
│ │ ├── index.html
│ │ └── main.js
│ └── update
│ │ ├── App.js
│ │ ├── index.html
│ │ └── main.js
│ ├── package.json
│ └── src
│ └── index.ts
├── pkg.main
├── pkg.module
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── rollup.config.js
├── src
├── compiler-core
│ ├── src
│ │ ├── ast.ts
│ │ ├── codegen.ts
│ │ ├── compile.ts
│ │ ├── index.ts
│ │ ├── parse.ts
│ │ ├── runtimeHelpers.ts
│ │ ├── transform.ts
│ │ ├── transforms
│ │ │ ├── transformElement.ts
│ │ │ ├── transformExpression.ts
│ │ │ └── transformText.ts
│ │ └── utils.ts
│ └── tests
│ │ ├── __snapshots__
│ │ └── codegen.spec.ts.snap
│ │ ├── codegen.spec.ts
│ │ ├── parse.spec.ts
│ │ └── transform.spec.ts
├── index.ts
├── reactivity
│ ├── baseHandlers.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
│ ├── componentEmit.ts
│ ├── componentProps.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── componentUpdateUtils.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── index.ts
│ ├── renderer.ts
│ ├── scheduler.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── ShapeFlags.ts
│ ├── index.ts
│ └── toDisplayString.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "liveServer.settings.port": 5502
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 5c24-mini-vue
2 | ### 学习心得总结
3 | [吃透mini-vue](https://juejin.cn/column/7071633886781898782)
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ["@babel/preset-env", { targets: { node: "current" } }],
4 | "@babel/preset-typescript",
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/example/apiInject/App.js:
--------------------------------------------------------------------------------
1 | import { h, provide, inject } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | const Provider = {
4 | name: "Provider",
5 | setup() {
6 | provide("foo", "fooVal");
7 | provide("bar", "barVal");
8 | },
9 | render() {
10 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]);
11 | },
12 | };
13 |
14 | const ProviderTwo = {
15 | name: "ProviderTwo",
16 | setup() {
17 | provide("foo", "fooTwo");
18 | const foo = inject("foo");
19 |
20 | return {
21 | foo,
22 | };
23 | },
24 | render() {
25 | return h("div", {}, [h("p", {}, `ProviderTwo -${this.foo}`), h(Consumer)]);
26 | },
27 | };
28 |
29 | const Consumer = {
30 | name: "Consumer",
31 | setup() {
32 | const foo = inject("foo");
33 | const bar = inject("bar");
34 | const baz = inject("baz", "bazDefault");
35 |
36 | return {
37 | foo,
38 | bar,
39 | baz,
40 | };
41 | },
42 |
43 | render() {
44 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar} - ${this.baz}`);
45 | },
46 | };
47 |
48 | export default {
49 | name: "App",
50 | setup() {},
51 | render() {
52 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]);
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/example/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/compiler-base/App.js:
--------------------------------------------------------------------------------
1 | import { ref } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 | template: `hi,{{count}}
`,
6 | setup() {
7 | const count = (window.count = ref(1));
8 | return {
9 | count,
10 | };
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/example/compiler-base/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/compiler-base/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const App = {
5 | name: "App",
6 | render() {
7 | return h("div", {}, [
8 | h("div", {}, "App"),
9 | h(Foo, {
10 | onAdd(a, b) {
11 | console.log("onAdd", a, b);
12 | },
13 | onAddFoo() {
14 | console.log("onAddFoo");
15 | },
16 | }),
17 | ]);
18 | },
19 |
20 | setup() {
21 | return {
22 | msg: "mini-vue",
23 | };
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup(props, { emit }) {
6 | const emitAdd = () => {
7 | console.log("emmit add");
8 | emit("add", 1, 2);
9 | emit("add-foo");
10 | };
11 |
12 | return {
13 | emitAdd,
14 | };
15 | },
16 |
17 | render() {
18 | const btn = h(
19 | "button",
20 | {
21 | onClick: this.emitAdd,
22 | },
23 | "emitAdd"
24 | );
25 |
26 | const foo = h("p", {}, "foo");
27 | return h("div", {}, [foo, btn]);
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVNode } from "../../lib/5c24-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export 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("hahaha"),
15 | ],
16 | footer: () =>
17 | h("p", {}, [h("span", {}, "footer "), h("span", {}, "123")]),
18 | }
19 | );
20 | // const foo = h(Foo, {}, h("p", {}, "123"));
21 |
22 | return h("div", {}, [app, foo]);
23 | },
24 |
25 | setup() {
26 | return {};
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/example/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup() {
6 | return {};
7 | },
8 | render() {
9 | const foo = h("p", {}, "foo");
10 | const age = 18;
11 | return h("div", {}, [
12 | renderSlots(this.$slots, "header", {
13 | age,
14 | }),
15 | foo,
16 | renderSlots(this.$slots, "footer"),
17 | ]);
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/example/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/5c24-mini-vue.esm.js";
2 | import Child from "./Child.js";
3 |
4 | export const App = {
5 | name: "App",
6 | setup() {
7 | const msg = ref("123");
8 | const count = ref(1);
9 |
10 | window.msg = msg;
11 |
12 | const changeChildProps = () => {
13 | msg.value = "456";
14 | };
15 |
16 | const changeCount = () => {
17 | count.value++;
18 | };
19 |
20 | return { msg, changeChildProps, changeCount, count };
21 | },
22 |
23 | render() {
24 | return h("div", {}, [
25 | h("div", {}, "你好"),
26 | h(
27 | "button",
28 | {
29 | onClick: this.changeChildProps,
30 | },
31 | "change child props"
32 | ),
33 | h(Child, {
34 | msg: this.msg,
35 | }),
36 | h(
37 | "button",
38 | {
39 | onClick: this.changeCount,
40 | },
41 | "change self count"
42 | ),
43 | h("p", {}, "count: " + this.count),
44 | ]);
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/example/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 | export default {
3 | name: "Child",
4 | setup(props, { emit }) {},
5 | render(proxy) {
6 | return h("div", {}, [
7 | h("div", {}, "child - props - msg: " + this.$props.msg),
8 | ]);
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/example/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/componentUpdate/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/5c24-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const App = {
5 | name: "App",
6 | render() {
7 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]);
8 | },
9 |
10 | setup() {
11 | const instance = getCurrentInstance();
12 | console.log("App:", instance);
13 | return {};
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/example/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup() {
6 | const instance = getCurrentInstance();
7 | console.log("Foo:", instance);
8 | return {};
9 | },
10 | render() {
11 | return h("div", {}, "foo");
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/customRenderer/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const App = {
4 | setup() {
5 | return {
6 | x: 100,
7 | y: 100,
8 | };
9 | },
10 | render() {
11 | return h("rect", { x: this.x, y: this.y });
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/example/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/example/customRenderer/main.js:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const game = new PIXI.Application({
5 | width: 500,
6 | height: 500,
7 | });
8 |
9 | document.body.append(game.view);
10 |
11 | const renderer = createRenderer({
12 | createElement(type) {
13 | if (type === "rect") {
14 | const rect = new PIXI.Graphics();
15 | rect.beginFill(0xff0000);
16 | rect.drawRect(0, 0, 100, 100);
17 | rect.endFill();
18 |
19 | return rect;
20 | }
21 | },
22 | patchProp(el, key, val) {
23 | el[key] = val;
24 | },
25 | insert(el, parent) {
26 | parent.addChild(el);
27 | },
28 | });
29 |
30 | renderer.createApp(App).mount(game.stage);
31 |
--------------------------------------------------------------------------------
/example/helloworld/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | window.self = null;
5 | export const App = {
6 | name: "App",
7 | render() {
8 | window.self = this;
9 | return h(
10 | "div",
11 | {
12 | id: "root",
13 | class: ["red", "blue"],
14 | onClick: () => {
15 | console.log("click");
16 | },
17 | onMousedown: () => {
18 | console.log("mousedown");
19 | },
20 | },
21 | // [h("div", {}, "hi, " + this.msg), h(Foo, { count: 1 })]
22 | // "hi, " + this.msg
23 | // "hi, mini-vue"
24 | [h("p", { class: "red" }, "hi,"), h("p", { class: "blue" }, "mini-vue")]
25 | );
26 | },
27 |
28 | setup() {
29 | return {
30 | msg: "mini-vue",
31 | };
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/example/helloworld/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup(props) {
6 | console.log(props);
7 |
8 | // readonly
9 | props.count++;
10 | console.log(props);
11 | },
12 |
13 | render() {
14 | return h("div", {}, "foo: " + this.count);
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/example/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/helloworld/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/nextTicker/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | ref,
4 | getCurrentInstance,
5 | nextTick,
6 | } from "../../lib/5c24-mini-vue.esm.js";
7 |
8 | export default {
9 | name: "App",
10 | setup() {
11 | const count = ref(1);
12 | const instance = getCurrentInstance();
13 |
14 | function onClick() {
15 | for (let i = 0; i < 100; i++) {
16 | console.log("update");
17 | count.value = i;
18 | }
19 |
20 | // debugger;
21 | console.log(instance);
22 | nextTick(() => {
23 | console.log(instance);
24 | });
25 |
26 | // await nextTick()
27 | // console.log(instance)
28 | }
29 |
30 | return {
31 | onClick,
32 | count,
33 | };
34 | },
35 | render() {
36 | const button = h("button", { onClick: this.onClick }, "update");
37 | const p = h("p", {}, "count:" + this.count);
38 |
39 | return h("div", {}, [button, p]);
40 | },
41 | };
42 |
--------------------------------------------------------------------------------
/example/nextTicker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/nextTicker/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | import ArrayToText from "./ArrayToText.js";
4 | import TextToText from "./TextToText.js";
5 | import TextToArray from "./TextToArray.js";
6 | import ArrayToArray from "./ArrayToArray.js";
7 |
8 | export const App = {
9 | name: "App",
10 | setup() {},
11 | render() {
12 | return h("div", { tId: 1 }, [
13 | h("p", {}, "主页"),
14 | // 老的是 array 新的是 text
15 | // h(ArrayToText),
16 | // 老的是 text 新的是 text
17 | // h(TextToText),
18 | // 老的是 text 新的是 array
19 | // h(TextToArray),
20 | // 老的是 array 新的是 array
21 | h(ArrayToArray),
22 | ]);
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 array
3 |
4 | import { ref, h } from "../../lib/5c24-mini-vue.esm.js";
5 |
6 | // 1. 左侧的对比
7 | // (a b) c
8 | // (a b) d e
9 | // const prevChildren = [
10 | // h("p", { key: "A" }, "A"),
11 | // h("p", { key: "B" }, "B"),
12 | // h("p", { key: "C" }, "C"),
13 | // ];
14 | // const nextChildren = [
15 | // h("p", { key: "A" }, "A"),
16 | // h("p", { key: "B" }, "B"),
17 | // h("p", { key: "D" }, "D"),
18 | // h("p", { key: "E" }, "E"),
19 | // ];
20 |
21 | // 2. 右侧的对比
22 | // a (b c)
23 | // d e (b c)
24 | // const prevChildren = [
25 | // h("p", { key: "A" }, "A"),
26 | // h("p", { key: "B" }, "B"),
27 | // h("p", { key: "C" }, "C"),
28 | // ];
29 | // const nextChildren = [
30 | // h("p", { key: "D" }, "D"),
31 | // h("p", { key: "E" }, "E"),
32 | // h("p", { key: "B" }, "B"),
33 | // h("p", { key: "C" }, "C"),
34 | // ];
35 |
36 | // 3. 新的比老的长
37 | // 创建新的
38 | // 左侧
39 | // (a b)
40 | // (a b) c
41 | // i = 2, e1 = 1, e2 = 2
42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
43 | // const nextChildren = [
44 | // h("p", { key: "A" }, "A"),
45 | // h("p", { key: "B" }, "B"),
46 | // h("p", { key: "C" }, "C"),
47 | // h("p", { key: "D" }, "D"),
48 | // ];
49 |
50 | // 右侧
51 | // (a b)
52 | // c (a b)
53 | // i = 0, e1 = -1, e2 = 0
54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
55 | // const nextChildren = [
56 | // h("p", { key: "D" }, "D"),
57 | // h("p", { key: "C" }, "C"),
58 | // h("p", { key: "A" }, "A"),
59 | // h("p", { key: "B" }, "B"),
60 | // ];
61 |
62 | // 4. 老的比新的长
63 | // 删除老的
64 | // 左侧
65 | // (a b) c
66 | // (a b)
67 | // i = 2, e1 = 2, e2 = 1
68 | // const prevChildren = [
69 | // h("p", { key: "A" }, "A"),
70 | // h("p", { key: "B" }, "B"),
71 | // h("p", { key: "C" }, "C"),
72 | // ];
73 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
74 |
75 | // 右侧
76 | // a (b c)
77 | // (b c)
78 | // i = 0, e1 = 0, e2 = -1
79 |
80 | // const prevChildren = [
81 | // h("p", { key: "A" }, "A"),
82 | // h("p", { key: "B" }, "B"),
83 | // h("p", { key: "C" }, "C"),
84 | // ];
85 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
86 |
87 | // 5. 对比中间的部分
88 | // 删除老的 (在老的里面存在,新的里面不存在)
89 | // 5.1
90 | // a,b,(c,d),f,g
91 | // a,b,(e,c),f,g
92 | // D 节点在新的里面是没有的 - 需要删除掉
93 | // C 节点 props 也发生了变化
94 |
95 | // const prevChildren = [
96 | // h("p", { key: "A" }, "A"),
97 | // h("p", { key: "B" }, "B"),
98 | // h("p", { key: "C", id: "c-prev" }, "C"),
99 | // h("p", { key: "D" }, "D"),
100 | // h("p", { key: "F" }, "F"),
101 | // h("p", { key: "G" }, "G"),
102 | // ];
103 |
104 | // const nextChildren = [
105 | // h("p", { key: "A" }, "A"),
106 | // h("p", { key: "B" }, "B"),
107 | // h("p", { key: "E" }, "E"),
108 | // h("p", { key: "C", id: "c-next" }, "C"),
109 | // h("p", { key: "F" }, "F"),
110 | // h("p", { key: "G" }, "G"),
111 | // ];
112 |
113 | // 5.1.1
114 | // a,b,(c,e,d),f,g
115 | // a,b,(e,c),f,g
116 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
117 | // const prevChildren = [
118 | // h("p", { key: "A" }, "A"),
119 | // h("p", { key: "B" }, "B"),
120 | // h("p", { key: "C", id: "c-prev" }, "C"),
121 | // h("p", { key: "E" }, "E"),
122 | // h("p", { key: "D" }, "D"),
123 | // h("p", { key: "F" }, "F"),
124 | // h("p", { key: "G" }, "G"),
125 | // ];
126 |
127 | // const nextChildren = [
128 | // h("p", { key: "A" }, "A"),
129 | // h("p", { key: "B" }, "B"),
130 | // h("p", { key: "E" }, "E"),
131 | // h("p", { key: "C", id: "c-next" }, "C"),
132 | // h("p", { key: "F" }, "F"),
133 | // h("p", { key: "G" }, "G"),
134 | // ];
135 |
136 | // 2 移动 (节点存在于新的和老的里面,但是位置变了)
137 |
138 | // 2.1
139 | // a,b,(c,d,e),f,g
140 | // a,b,(e,c,d),f,g
141 | // 最长子序列: [1,2]
142 |
143 | const prevChildren = [
144 | h("p", { key: "A" }, "A"),
145 | h("p", { key: "B" }, "B"),
146 | h("p", { key: "C" }, "C"),
147 | h("p", { key: "D" }, "D"),
148 | h("p", { key: "E" }, "E"),
149 | h("p", { key: "F" }, "F"),
150 | h("p", { key: "G" }, "G"),
151 | ];
152 |
153 | const nextChildren = [
154 | h("p", { key: "A" }, "A"),
155 | h("p", { key: "B" }, "B"),
156 | h("p", { key: "E" }, "E"),
157 | h("p", { key: "C" }, "C"),
158 | h("p", { key: "D" }, "D"),
159 | h("p", { key: "F" }, "F"),
160 | h("p", { key: "G" }, "G"),
161 | ];
162 |
163 | // 3. 创建新的节点
164 | // a,b,(c,e),f,g
165 | // a,b,(e,c,d),f,g
166 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
167 | // const prevChildren = [
168 | // h("p", { key: "A" }, "A"),
169 | // h("p", { key: "B" }, "B"),
170 | // h("p", { key: "C" }, "C"),
171 | // h("p", { key: "E" }, "E"),
172 | // h("p", { key: "F" }, "F"),
173 | // h("p", { key: "G" }, "G"),
174 | // ];
175 |
176 | // const nextChildren = [
177 | // h("p", { key: "A" }, "A"),
178 | // h("p", { key: "B" }, "B"),
179 | // h("p", { key: "E" }, "E"),
180 | // h("p", { key: "C" }, "C"),
181 | // h("p", { key: "D" }, "D"),
182 | // h("p", { key: "F" }, "F"),
183 | // h("p", { key: "G" }, "G"),
184 | // ];
185 |
186 | // 综合例子
187 | // a,b,(c,d,e,z),f,g
188 | // a,b,(d,c,y,e),f,g
189 |
190 | // const prevChildren = [
191 | // h("p", { key: "A" }, "A"),
192 | // h("p", { key: "B" }, "B"),
193 | // h("p", { key: "C" }, "C"),
194 | // h("p", { key: "D" }, "D"),
195 | // h("p", { key: "E" }, "E"),
196 | // h("p", { key: "Z" }, "Z"),
197 | // h("p", { key: "F" }, "F"),
198 | // h("p", { key: "G" }, "G"),
199 | // ];
200 |
201 | // const nextChildren = [
202 | // h("p", { key: "A" }, "A"),
203 | // h("p", { key: "B" }, "B"),
204 | // h("p", { key: "D" }, "D"),
205 | // h("p", { key: "C" }, "C"),
206 | // h("p", { key: "Y" }, "Y"),
207 | // h("p", { key: "E" }, "E"),
208 | // h("p", { key: "F" }, "F"),
209 | // h("p", { key: "G" }, "G"),
210 | // ];
211 |
212 | // fix c 节点应该是 move 而不是删除之后重新创建的
213 | // const prevChildren = [
214 | // h("p", { key: "A" }, "A"),
215 | // h("p", {}, "C"),
216 | // h("p", { key: "B" }, "B"),
217 | // h("p", { key: "D" }, "D"),
218 | // ];
219 |
220 | // const nextChildren = [
221 | // h("p", { key: "A" }, "A"),
222 | // h("p", { key: "B" }, "B"),
223 | // h("p", {}, "C"),
224 | // h("p", { key: "D" }, "D"),
225 | // ];
226 |
227 | export default {
228 | name: "ArrayToArray",
229 | setup() {
230 | const isChange = ref(false);
231 | window.isChange = isChange;
232 |
233 | return {
234 | isChange,
235 | };
236 | },
237 | render() {
238 | const self = this;
239 |
240 | return self.isChange === true
241 | ? h("div", {}, nextChildren)
242 | : h("div", {}, prevChildren);
243 | },
244 | };
245 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 text
3 |
4 | import { ref, h } from "../../lib/5c24-mini-vue.esm.js";
5 | const nextChildren = "newChildren";
6 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "ArrayToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | // 新的是 array
2 | // 老的是 text
3 | import { ref, h } from "../../lib/5c24-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "TextToArray",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | // 新的是 text
2 | // 老的是 text
3 | import { ref, h } from "../../lib/5c24-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = "newChild";
7 |
8 | export default {
9 | name: "TextToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/example/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref, proxyRefs } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 |
6 | setup() {
7 | const count = ref(0);
8 |
9 | const onClick = () => {
10 | count.value++;
11 | };
12 |
13 | const props = ref({
14 | foo: "foo",
15 | bar: "bar",
16 | });
17 |
18 | const onChangePropsDemo1 = () => {
19 | props.value.foo = "new-foo";
20 | };
21 |
22 | const onChangePropsDemo2 = () => {
23 | props.value.foo = undefined;
24 | };
25 |
26 | const onChangePropsDemo3 = () => {
27 | props.value = {
28 | foo: "foo",
29 | };
30 | };
31 |
32 | return {
33 | count,
34 | onClick,
35 | props,
36 | onChangePropsDemo1,
37 | onChangePropsDemo2,
38 | onChangePropsDemo3,
39 | };
40 | },
41 |
42 | render() {
43 | return h(
44 | "div",
45 | {
46 | id: "root",
47 | ...this.props,
48 | },
49 | [
50 | h("div", {}, "count:" + this.count),
51 | h("button", { onClick: this.onClick }, "click"),
52 | h(
53 | "button",
54 | { onClick: this.onChangePropsDemo1, style: "margin-left: 24px;" },
55 | "changeProps - 值改变了 - 修改"
56 | ),
57 | h(
58 | "button",
59 | { onClick: this.onChangePropsDemo2, style: "margin-left: 24px;" },
60 | "changeProps - 值变成了 undefined - 删除"
61 | ),
62 | h(
63 | "button",
64 | { onClick: this.onChangePropsDemo3, style: "margin-left: 24px;" },
65 | "changeProps - key 在新的里面没有了 - 删除"
66 | ),
67 | ]
68 | );
69 | },
70 | };
71 |
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "5c24-mini-vue",
3 | "version": "1.0.0",
4 | "main": "lib/5c24-mini-vue.cjs.js",
5 | "module": "lib/5c24-mini-vue.esm.js",
6 | "repository": "https://github.com/You-5C24/5c24-mini-vue.git",
7 | "author": "5C24 ",
8 | "license": "MIT",
9 | "scripts": {
10 | "test": "jest",
11 | "build": "rollup -c rollup.config.js"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "^7.17.5",
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.0",
19 | "babel-jest": "^27.5.1",
20 | "jest": "^27.5.1",
21 | "rollup": "^2.70.0",
22 | "tslib": "^2.3.1",
23 | "typescript": "^4.5.5"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/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, 'h1, ' + _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 |
--------------------------------------------------------------------------------
/packages/compiler-core/__tests__/codegen.spec.ts:
--------------------------------------------------------------------------------
1 | import { generate } from "../src/codegen";
2 | import { baseParse } from "../src/parse";
3 | import { transform } from "../src/transform";
4 | import { transformElement } from "../src/transforms/transformElement";
5 | import { transformExpression } from "../src/transforms/transformExpression";
6 | import { transformText } from "../src/transforms/transformText";
7 |
8 | describe("codegen", () => {
9 | it("string", () => {
10 | const ast = baseParse("hi");
11 | transform(ast);
12 | const { code } = generate(ast);
13 |
14 | expect(code).toMatchSnapshot();
15 | });
16 |
17 | it("interpolation", () => {
18 | const ast = baseParse("{{ message }}");
19 | transform(ast, {
20 | nodeTransforms: [transformExpression],
21 | });
22 | const { code } = generate(ast);
23 |
24 | expect(code).toMatchSnapshot();
25 | });
26 |
27 | it("element", () => {
28 | const ast: any = baseParse("h1, {{ message }}
");
29 | transform(ast, {
30 | nodeTransforms: [transformExpression, transformElement, transformText],
31 | });
32 |
33 | const { code } = generate(ast);
34 |
35 | expect(code).toMatchSnapshot();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/packages/compiler-core/__tests__/parse.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../src/ast";
2 | import { baseParse } from "../src/parse";
3 | describe("Parse", () => {
4 | describe("interpolation", () => {
5 | test("simple interpolation", () => {
6 | const ast = baseParse("{{ message }}");
7 |
8 | // root
9 | expect(ast.children[0]).toStrictEqual({
10 | type: NodeTypes.INTERPOLATION,
11 | content: {
12 | type: NodeTypes.SIMPLE_EXPRESSION,
13 | content: "message",
14 | },
15 | });
16 | });
17 | });
18 |
19 | describe("element", () => {
20 | it("simple element div", () => {
21 | const ast = baseParse("");
22 |
23 | expect(ast.children[0]).toStrictEqual({
24 | type: NodeTypes.ELEMENT,
25 | tag: "div",
26 | children: [],
27 | });
28 | });
29 | });
30 |
31 | describe("text", () => {
32 | it("simple text", () => {
33 | const ast = baseParse("some text");
34 |
35 | expect(ast.children[0]).toStrictEqual({
36 | type: NodeTypes.TEXT,
37 | content: "some text",
38 | });
39 | });
40 | });
41 |
42 | test("hello world", () => {
43 | const ast = baseParse("h1,{{ message }}
");
44 |
45 | expect(ast.children[0]).toStrictEqual({
46 | type: NodeTypes.ELEMENT,
47 | tag: "div",
48 | children: [
49 | {
50 | type: NodeTypes.TEXT,
51 | content: "h1,",
52 | },
53 | {
54 | type: NodeTypes.INTERPOLATION,
55 | content: {
56 | type: NodeTypes.SIMPLE_EXPRESSION,
57 | content: "message",
58 | },
59 | },
60 | ],
61 | });
62 | });
63 |
64 | test("Nested element", () => {
65 | const ast = baseParse("");
66 |
67 | expect(ast.children[0]).toStrictEqual({
68 | type: NodeTypes.ELEMENT,
69 | tag: "div",
70 | children: [
71 | {
72 | type: NodeTypes.ELEMENT,
73 | tag: "p",
74 | children: [
75 | {
76 | type: NodeTypes.TEXT,
77 | content: "h1",
78 | },
79 | ],
80 | },
81 | {
82 | type: NodeTypes.INTERPOLATION,
83 | content: {
84 | type: NodeTypes.SIMPLE_EXPRESSION,
85 | content: "message",
86 | },
87 | },
88 | ],
89 | });
90 | });
91 |
92 | test("should throw error when lack end tag", () => {
93 | expect(() => {
94 | baseParse("
");
95 | }).toThrow(`缺少结束标签: span`);
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/packages/compiler-core/__tests__/transform.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../src/ast";
2 | import { baseParse } from "../src/parse";
3 | import { transform } from "../src/transform";
4 |
5 | describe("transform", () => {
6 | it("happy path", () => {
7 | const ast = baseParse("h1, {{ message }}
");
8 | const plugin = (node) => {
9 | if (node.type === NodeTypes.TEXT) {
10 | node.content = node.content + "mini-vue";
11 | }
12 | };
13 |
14 | transform(ast, {
15 | nodeTransforms: [plugin],
16 | });
17 |
18 | const nodeText = ast.children[0].children[0];
19 |
20 | expect(nodeText.content).toBe("h1, mini-vue");
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/packages/compiler-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@5c24-mini-vue/compiler-core",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@5c24-mini-vue/shared": "workspace:^1.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/ast.ts:
--------------------------------------------------------------------------------
1 | import { CREATE_ELEMENT_VNODE } from "./runtimeHelpers";
2 |
3 | export const enum NodeTypes {
4 | INTERPOLATION,
5 | SIMPLE_EXPRESSION,
6 | ELEMENT,
7 | TEXT,
8 | ROOT,
9 | COMPOUND_EXPRESS,
10 | }
11 |
12 | export function createVNodeCall(context, tag, props, children) {
13 | context.helper(CREATE_ELEMENT_VNODE);
14 |
15 | return {
16 | type: NodeTypes.ELEMENT,
17 | tag,
18 | props,
19 | children,
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/codegen.ts:
--------------------------------------------------------------------------------
1 | import { isString } from "@5c24-mini-vue/shared";
2 | import { NodeTypes } from "./ast";
3 | import {
4 | CREATE_ELEMENT_VNODE,
5 | helperMapName,
6 | TO_DISPLAY_STRING,
7 | } from "./runtimeHelpers";
8 |
9 | export function generate(ast) {
10 | const context: any = createCodegenContext();
11 | const { push } = context;
12 |
13 | genFunctionPreamble(ast, context);
14 |
15 | const functionName = "render";
16 | const args = ["_ctx", "_cache"];
17 | const signature = args.join(", ");
18 |
19 | push(`function ${functionName}(${signature}){`);
20 | push(`return `);
21 |
22 | genNode(ast.codegenNode, context);
23 | push("}");
24 |
25 | return {
26 | code: context.code,
27 | };
28 | }
29 |
30 | function genFunctionPreamble(ast, context) {
31 | const { push } = context;
32 | const VueBinging = "Vue";
33 | const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}`;
34 |
35 | if (ast.helpers.length > 0) {
36 | push(
37 | `const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`
38 | );
39 | }
40 | push("\n");
41 | push("return ");
42 | }
43 |
44 | function createCodegenContext() {
45 | const context = {
46 | code: "",
47 | push(source) {
48 | context.code += source;
49 | },
50 | helper(key) {
51 | return `_${helperMapName[key]}`;
52 | },
53 | };
54 |
55 | return context;
56 | }
57 |
58 | function genNode(node, context) {
59 | switch (node.type) {
60 | case NodeTypes.TEXT:
61 | genText(node, context);
62 | break;
63 | case NodeTypes.INTERPOLATION:
64 | genInterpolation(node, context);
65 | break;
66 | case NodeTypes.SIMPLE_EXPRESSION:
67 | genExpression(node, context);
68 | break;
69 | case NodeTypes.ELEMENT:
70 | genElement(node, context);
71 | break;
72 | case NodeTypes.COMPOUND_EXPRESS:
73 | genCompoundExpression(node, context);
74 | break;
75 | default:
76 | break;
77 | }
78 | }
79 |
80 | function genCompoundExpression(node, context) {
81 | const { push } = context;
82 | const { children } = node;
83 |
84 | for (let i = 0; i < children.length; i++) {
85 | const child = children[i];
86 | if (isString(child)) {
87 | push(child);
88 | } else {
89 | genNode(child, context);
90 | }
91 | }
92 | }
93 |
94 | function genElement(node, context) {
95 | const { push, helper } = context;
96 | const { tag, children, props } = node;
97 |
98 | push(`${helper(CREATE_ELEMENT_VNODE)}(`);
99 | genNodeList(genNullable([tag, props, children]), context);
100 | push(")");
101 | }
102 |
103 | function genNodeList(nodes, context) {
104 | const { push } = context;
105 | for (let i = 0; i < nodes.length; i++) {
106 | const node = nodes[i];
107 | if (isString(node)) {
108 | push(node);
109 | } else {
110 | genNode(node, context);
111 | }
112 |
113 | if (i < nodes.length - 1) {
114 | push(", ");
115 | }
116 | }
117 | }
118 |
119 | function genNullable(args) {
120 | return args.map((arg) => arg || "null");
121 | }
122 |
123 | function genText(node, context) {
124 | const { push } = context;
125 | push(`'${node.content}'`);
126 | }
127 |
128 | function genInterpolation(node, context) {
129 | const { push, helper } = context;
130 |
131 | push(`${helper(TO_DISPLAY_STRING)}(`);
132 | genNode(node.content, context);
133 | push(")");
134 | }
135 |
136 | function genExpression(node, context) {
137 | const { push } = context;
138 |
139 | push(`${node.content}`);
140 | }
141 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/compile.ts:
--------------------------------------------------------------------------------
1 | import { generate } from "./codegen";
2 | import { baseParse } from "./parse";
3 | import { transform } from "./transform";
4 | import { transformElement } from "./transforms/transformElement";
5 | import { transformExpression } from "./transforms/transformExpression";
6 | import { transformText } from "./transforms/transformText";
7 |
8 | export function baseCompile(template) {
9 | const ast: any = baseParse(template);
10 | transform(ast, {
11 | nodeTransforms: [transformExpression, transformElement, transformText],
12 | });
13 |
14 | return generate(ast);
15 | }
16 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./compile";
2 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | const enum TagType {
4 | Start,
5 | End,
6 | }
7 |
8 | export function baseParse(content: string) {
9 | const context = createParserContext(content);
10 |
11 | return createRoot(parseChildren(context, []));
12 | }
13 |
14 | function parseChildren(context, ancestors) {
15 | const nodes: any = [];
16 |
17 | while (!isEnd(context, ancestors)) {
18 | let node;
19 | const s = context.source;
20 |
21 | if (s.startsWith("{{")) {
22 | node = parseInterpolation(context);
23 | } else if (s[0] === "<") {
24 | if (/[a-z]/i.test(s[1])) {
25 | node = parseElement(context, ancestors);
26 | }
27 | }
28 |
29 | if (!node) {
30 | node = parseText(context);
31 | }
32 |
33 | nodes.push(node);
34 | }
35 |
36 | return nodes;
37 | }
38 |
39 | function isEnd(context, ancestors) {
40 | // 遇到结束标签
41 | const s = context.source;
42 |
43 | if (s.startsWith("")) {
44 | for (let i = 0; i < ancestors.length; i++) {
45 | const tag = ancestors[i].tag;
46 | if (startsWithEndTagOpen(s, tag)) {
47 | return true;
48 | }
49 | }
50 | }
51 |
52 | // source 有值
53 | return !s;
54 | }
55 |
56 | function parseText(context) {
57 | let endIndex = context.source.length;
58 | let endTokens = ["<", "{{"];
59 |
60 | for (let i = 0; i < endTokens.length; i++) {
61 | const index = context.source.indexOf(endTokens[i]);
62 | if (index !== -1 && endIndex > index) {
63 | endIndex = index;
64 | }
65 | }
66 |
67 | const content = parseTextData(context, endIndex);
68 |
69 | return {
70 | type: NodeTypes.TEXT,
71 | content,
72 | };
73 | }
74 |
75 | function parseTextData(context, length) {
76 | const content = context.source.slice(0, length);
77 |
78 | advanceBy(context, content.length);
79 |
80 | return content;
81 | }
82 |
83 | function parseElement(context, ancestors) {
84 | const element: any = parseTag(context, TagType.Start);
85 | ancestors.push(element);
86 | element.children = parseChildren(context, ancestors);
87 | ancestors.pop();
88 |
89 | if (startsWithEndTagOpen(context.source, element.tag)) {
90 | parseTag(context, TagType.End);
91 | } else {
92 | throw new Error(`缺少结束标签: ${element.tag}`);
93 | }
94 |
95 | return element;
96 | }
97 |
98 | function startsWithEndTagOpen(source, tag) {
99 | return (
100 | source.startsWith("<") &&
101 | source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
102 | );
103 | }
104 |
105 | function parseTag(context, type: TagType) {
106 | const match: any = /^<\/?([a-z]*)/i.exec(context.source);
107 |
108 | const tag = match[1];
109 |
110 | advanceBy(context, match[0].length);
111 | advanceBy(context, 1);
112 |
113 | if (type === TagType.End) return;
114 |
115 | return {
116 | type: NodeTypes.ELEMENT,
117 | tag,
118 | };
119 | }
120 |
121 | function parseInterpolation(context) {
122 | const openDelimiter = "{{";
123 | const closeDelimiter = "}}";
124 |
125 | const closeIndex = context.source.indexOf(
126 | closeDelimiter,
127 | openDelimiter.length
128 | );
129 |
130 | advanceBy(context, openDelimiter.length);
131 |
132 | const rawContentLength = closeIndex - openDelimiter.length;
133 | const rawContent = parseTextData(context, rawContentLength);
134 | const content = rawContent.trim();
135 |
136 | advanceBy(context, closeDelimiter.length);
137 |
138 | return {
139 | type: NodeTypes.INTERPOLATION,
140 | content: {
141 | type: NodeTypes.SIMPLE_EXPRESSION,
142 | content: content,
143 | },
144 | };
145 | }
146 |
147 | function advanceBy(context, length) {
148 | context.source = context.source.slice(length);
149 | }
150 |
151 | function createRoot(children) {
152 | return {
153 | children,
154 | type: NodeTypes.ROOT
155 | };
156 | }
157 |
158 | function createParserContext(content: string) {
159 | return {
160 | source: content,
161 | };
162 | }
163 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/runtimeHelpers.ts:
--------------------------------------------------------------------------------
1 | export const TO_DISPLAY_STRING = Symbol("toDisplayString");
2 | export const CREATE_ELEMENT_VNODE = Symbol("createElementVNode");
3 |
4 | export const helperMapName = {
5 | [TO_DISPLAY_STRING]: "toDisplayString",
6 | [CREATE_ELEMENT_VNODE]: "createElementVNode"
7 | }
--------------------------------------------------------------------------------
/packages/compiler-core/src/transform.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 | import { TO_DISPLAY_STRING } from "./runtimeHelpers";
3 |
4 | export function transform(root, options = {}) {
5 | const context = createTransformerContext(root, options);
6 |
7 | traverseNode(root, context);
8 | createRootCodegen(root);
9 |
10 | root.helpers = [...context.helpers.keys()];
11 | }
12 |
13 | function createRootCodegen(root) {
14 | const child = root.children[0];
15 |
16 | if (child.type === NodeTypes.ELEMENT) {
17 | root.codegenNode = child.codegenNode;
18 | } else {
19 | root.codegenNode = root.children[0];
20 | }
21 | }
22 |
23 | function createTransformerContext(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 |
33 | return context;
34 | }
35 |
36 | function traverseNode(node, context) {
37 | const nodeTransforms = context.nodeTransforms;
38 | const exitFns: any = [];
39 |
40 | for (let i = 0; i < nodeTransforms.length; i++) {
41 | const transform = nodeTransforms[i];
42 | const onExit = transform(node, context);
43 | if (onExit) {
44 | exitFns.push(onExit);
45 | }
46 | }
47 |
48 | switch (node.type) {
49 | case NodeTypes.INTERPOLATION:
50 | context.helper(TO_DISPLAY_STRING);
51 | break;
52 | case NodeTypes.ROOT:
53 | case NodeTypes.ELEMENT:
54 | traverseChildren(node, context);
55 | break;
56 | default:
57 | break;
58 | }
59 |
60 | let i = exitFns.length;
61 | while (i--) {
62 | exitFns[i]();
63 | }
64 | }
65 |
66 | function traverseChildren(node: any, context) {
67 | const children = node.children;
68 |
69 | for (let i = 0; i < children.length; i++) {
70 | const node = children[i];
71 | traverseNode(node, context);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/transforms/transformElement.ts:
--------------------------------------------------------------------------------
1 | import { createVNodeCall, NodeTypes } from "../ast";
2 |
3 | export function transformElement(node, context) {
4 | if (node.type === NodeTypes.ELEMENT) {
5 | return () => {
6 | const vnodeTag = `'${node.tag}'`;
7 |
8 | let vnodeProps;
9 |
10 | const children = node.children;
11 | let vnodeChildren = children[0];
12 |
13 | const vnodeElement = createVNodeCall(
14 | context,
15 | vnodeTag,
16 | vnodeProps,
17 | vnodeChildren
18 | );
19 |
20 | node.codegenNode = vnodeElement;
21 | };
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/transforms/transformExpression.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 |
3 | export function transformExpression(node) {
4 | if (node.type === NodeTypes.INTERPOLATION) {
5 | node.content = processExpression(node.content)
6 | }
7 | }
8 |
9 | function processExpression(node) {
10 | node.content = `_ctx.${node.content}`
11 | return node
12 | }
13 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/transforms/transformText.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 | import { isText } from "../utils";
3 |
4 | export function transformText(node) {
5 | if (node.type === NodeTypes.ELEMENT) {
6 | return () => {
7 | const { children } = node;
8 | let currentContainer;
9 |
10 | for (let i = 0; i < children.length; i++) {
11 | const child = children[i];
12 |
13 | if (isText(child)) {
14 | for (let j = i + 1; j < children.length; j++) {
15 | const next = children[j];
16 | if (isText(next)) {
17 | if (!currentContainer) {
18 | currentContainer = children[i] = {
19 | type: NodeTypes.COMPOUND_EXPRESS,
20 | children: [child],
21 | };
22 | }
23 |
24 | currentContainer.children.push(" + ");
25 | currentContainer.children.push(next);
26 | children.splice(j, 1);
27 | j--;
28 | } else {
29 | currentContainer = undefined;
30 | break;
31 | }
32 | }
33 | }
34 | }
35 | };
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | export function isText(node) {
4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { computed } from "../src/computed";
2 | import { reactive } from "../src/reactive";
3 |
4 | describe("computed", () => {
5 | it("happy path", () => {
6 | const user = reactive({
7 | age: 10,
8 | });
9 |
10 | const age = computed(() => {
11 | return user.age;
12 | });
13 |
14 | expect(age.value).toBe(10);
15 | });
16 |
17 | it("should compute lazily", () => {
18 | const value = reactive({ foo: 1 });
19 | const getter = jest.fn(() => {
20 | return value.foo;
21 | });
22 | const cValue = computed(getter);
23 |
24 | // 懒执行 不调用 cValue 不会执行getter
25 | expect(getter).not.toHaveBeenCalled();
26 |
27 | expect(cValue.value).toBe(1);
28 | expect(getter).toHaveBeenCalledTimes(1);
29 |
30 | // 缓存机制 再次调用cValue.value getter不再执行
31 | cValue.value;
32 | expect(getter).toHaveBeenCalledTimes(1);
33 |
34 | value.foo = 2;
35 | expect(getter).toHaveBeenCalledTimes(1);
36 |
37 | // 当依赖的响应式对象的值发生改变 再次调用cValue.value getter执行
38 | expect(cValue.value).toBe(2);
39 | expect(getter).toHaveBeenCalledTimes(2);
40 |
41 | cValue.value;
42 | expect(getter).toHaveBeenCalledTimes(2);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "../src/reactive";
2 | import { effect, stop } from "../src/effect";
3 |
4 | describe("effect", () => {
5 | it("happy path", () => {
6 | let user = reactive({ age: 10, name: "5c24" });
7 | let nextAge;
8 | let nextInfo;
9 |
10 | effect(() => {
11 | nextAge = user.age + 1;
12 | });
13 |
14 | effect(() => {
15 | nextInfo = `name: ${user.name}, age: ${user.age}`;
16 | });
17 |
18 | expect(nextAge).toBe(11);
19 | expect(nextInfo).toBe(`name: 5c24, age: 10`);
20 |
21 | user.age++;
22 | expect(nextAge).toBe(12);
23 | expect(nextInfo).toBe(`name: 5c24, age: 11`);
24 |
25 | user.name = "You";
26 | expect(nextInfo).toBe(`name: You, age: 11`);
27 | });
28 |
29 | it("should runner when call effect", () => {
30 | let foo = 10;
31 | let runner = effect(() => {
32 | foo++;
33 | return "foo";
34 | });
35 |
36 | expect(foo).toBe(11);
37 | let r = runner();
38 | expect(foo).toBe(12);
39 | expect(r).toBe("foo");
40 | });
41 |
42 | it("scheduler", () => {
43 | let dummy;
44 | let run: any;
45 | const scheduler = jest.fn(() => {
46 | run = runner;
47 | });
48 | const obj = reactive({ foo: 1 });
49 | const runner = effect(
50 | () => {
51 | dummy = obj.foo;
52 | },
53 | { scheduler }
54 | );
55 | // 初始化不执行 scheduler
56 | expect(scheduler).not.toHaveBeenCalled();
57 | // 初始化执行 fn
58 | expect(dummy).toBe(1);
59 | obj.foo++;
60 | // 触发 set 执行 scheduler
61 | expect(scheduler).toHaveBeenCalledTimes(1);
62 | // 触发 set 不执行 fn
63 | expect(dummy).toBe(1);
64 | // 执行 runner ,再次执行 fn
65 | run();
66 | expect(dummy).toBe(2);
67 | });
68 |
69 | it("stop", () => {
70 | let dummy;
71 | const obj = reactive({ prop: 1 });
72 | const runner = effect(() => {
73 | dummy = obj.prop;
74 | });
75 |
76 | obj.prop = 2;
77 | expect(dummy).toBe(2);
78 | stop(runner);
79 | // obj.prop = 3;
80 | obj.prop++;
81 | expect(dummy).toBe(2);
82 |
83 | runner();
84 | expect(dummy).toBe(3);
85 | });
86 |
87 | it("onStop", () => {
88 | const obj = reactive(() => {
89 | foo: 1;
90 | });
91 | const onStop = jest.fn();
92 | let dummy;
93 | const runner = effect(
94 | () => {
95 | dummy = obj.foo;
96 | },
97 | { onStop }
98 | );
99 |
100 | stop(runner);
101 | expect(onStop).toBeCalledTimes(1);
102 | });
103 | });
104 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive, isReactive, isProxy } from "../src/reactive";
2 |
3 | describe("reactive", () => {
4 | it("happy path", () => {
5 | const original = { foo: 1 };
6 | const observed = reactive(original);
7 |
8 | expect(observed).not.toBe(original);
9 | expect(observed.foo).toBe(1);
10 | expect(isReactive(observed)).toBe(true);
11 | expect(isReactive(original)).toBe(false);
12 | expect(isProxy(observed)).toBe(true);
13 | });
14 |
15 | it("nested reactive", () => {
16 | const original = {
17 | nested: {
18 | foo: 1,
19 | },
20 | array: [{ bar: 2 }],
21 | };
22 | const observed = reactive(original);
23 | expect(isReactive(observed.nested)).toBe(true);
24 | expect(isReactive(observed.array)).toBe(true);
25 | expect(isReactive(observed.array[0])).toBe(true);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { readonly, isReadonly, isProxy } from "../src/reactive";
2 |
3 | describe("readonly", () => {
4 | it("happy path", () => {
5 | const original = { foo: 1, bar: { baz: 2 } };
6 | const wrapped = readonly(original);
7 |
8 | expect(wrapped).not.toBe(original);
9 | expect(wrapped.foo).toBe(1);
10 | expect(isReadonly(wrapped)).toBe(true);
11 | expect(isReadonly(original)).toBe(false);
12 | expect(isReadonly(wrapped.bar)).toBe(true);
13 | expect(isReadonly(original.bar)).toBe(false);
14 | expect(isProxy(wrapped)).toBe(true);
15 | });
16 |
17 | it("warn then call set", () => {
18 | console.warn = jest.fn();
19 |
20 | const user = readonly({ age: 10 });
21 |
22 | user.age = 11;
23 | expect(console.warn).toBeCalled();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../src/effect";
2 | import { reactive } from "../src/reactive";
3 | import { isRef, proxyRefs, ref, unRef } from "../src/ref";
4 |
5 | describe("ref", () => {
6 | it("happy path", () => {
7 | const a = ref(1);
8 | expect(a.value).toBe(1);
9 | });
10 |
11 | it("should be reactive", () => {
12 | const a = ref(1);
13 | let dummy;
14 | let calls = 0;
15 | effect(() => {
16 | calls++;
17 | dummy = a.value;
18 | });
19 | expect(calls).toBe(1);
20 | expect(dummy).toBe(1);
21 | a.value = 2;
22 | expect(calls).toBe(2);
23 | expect(dummy).toBe(2);
24 | // same value should not trigger
25 | a.value = 2;
26 | expect(calls).toBe(2);
27 | expect(dummy).toBe(2);
28 | });
29 |
30 | it("should make nested properties reactive", () => {
31 | const a = ref({
32 | count: 1,
33 | });
34 | let dummy;
35 | effect(() => {
36 | dummy = a.value.count;
37 | });
38 | expect(dummy).toBe(1);
39 | a.value.count++;
40 | expect(dummy).toBe(2);
41 | });
42 |
43 | it("isRef", () => {
44 | const a = ref(1);
45 | const b = reactive({ foo: 1 });
46 | expect(isRef(a)).toBe(true);
47 | expect(isRef(1)).toBe(false);
48 | expect(isRef(b)).toBe(false);
49 | });
50 |
51 | it("unRef", () => {
52 | const a = ref(1);
53 | expect(unRef(a)).toBe(1);
54 | expect(unRef(1)).toBe(1);
55 | });
56 |
57 | it("proxyRefs", () => {
58 | const user = {
59 | age: ref(10),
60 | name: "5c24",
61 | };
62 |
63 | const proxyUser = proxyRefs(user);
64 | expect(user.age.value).toBe(10);
65 | expect(proxyUser.age).toBe(10);
66 | expect(proxyUser.name).toBe("5c24");
67 |
68 | proxyUser.age = 20;
69 | expect(proxyUser.age).toBe(20);
70 | expect(user.age.value).toBe(20);
71 |
72 | proxyUser.age = ref(10);
73 | expect(proxyUser.age).toBe(10);
74 | expect(user.age.value).toBe(10);
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/packages/reactivity/__tests__/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, shallowReadonly } from "../src/reactive";
2 |
3 | describe("shallowReadonly", () => {
4 | it("should not make non-reactive properties reactive", () => {
5 | const props = shallowReadonly({ n: { foo: 1 } });
6 | expect(isReadonly(props)).toBe(true);
7 | expect(isReadonly(props.n)).toBe(false);
8 | });
9 |
10 | it("warn then call set", () => {
11 | console.warn = jest.fn();
12 |
13 | const user = shallowReadonly({ age: 10 });
14 |
15 | user.age = 11;
16 | expect(console.warn).toBeCalled();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/packages/reactivity/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@5c24-mini-vue/reactivity",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@5c24-mini-vue/shared": "workspace:^1.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/reactivity/src/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { extend, isObject } from "@5c24-mini-vue/shared";
2 | import { track, trigger } from "./effect";
3 | import { reactive, ReactiveFlags, readonly } from "./reactive";
4 |
5 | const get = createGetter();
6 | const set = createSetter();
7 | const readonlyGet = createGetter(true);
8 | const shallowReadonlyGet = createGetter(true, true);
9 |
10 | function createGetter(isReadonly = false, isShallow = false) {
11 | return function get(target, key) {
12 | if (key === ReactiveFlags.IS_REACTIVE) {
13 | return !isReadonly;
14 | } else if (key === ReactiveFlags.IS_READONLY) {
15 | return isReadonly;
16 | }
17 |
18 | const res = Reflect.get(target, key);
19 |
20 | if (isShallow) {
21 | return res;
22 | }
23 |
24 | // 看看 res 是不是 object
25 | if (isObject(res)) {
26 | return isReadonly ? readonly(res) : reactive(res);
27 | }
28 |
29 | // 依赖收集
30 | if (!isReadonly) {
31 | track(target, key);
32 | }
33 | return res;
34 | };
35 | }
36 |
37 | function createSetter() {
38 | return function set(target, key, value) {
39 | const res = Reflect.set(target, key, value);
40 |
41 | // 触发依赖
42 | trigger(target, key);
43 | return res;
44 | };
45 | }
46 |
47 | export const mutableHandlers = {
48 | get,
49 | set,
50 | };
51 |
52 | export const readonlyHandlers = {
53 | get: readonlyGet,
54 | set(target, key, value) {
55 | console.warn(`key:${key} set 失败 因为 target 是 readonly`);
56 |
57 | return true;
58 | },
59 | };
60 |
61 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
62 | get: shallowReadonlyGet,
63 | });
64 |
--------------------------------------------------------------------------------
/packages/reactivity/src/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 | class ComputedRefImpl {
4 | private _dirty: boolean = true;
5 | private _value: any;
6 | private _effect: any;
7 |
8 | constructor(getter) {
9 | this._effect = new ReactiveEffect(getter, () => {
10 | if (!this._dirty) {
11 | this._dirty = true;
12 | }
13 | });
14 | }
15 |
16 | get value() {
17 | if (this._dirty) {
18 | this._dirty = false;
19 | this._value = this._effect.run();
20 | }
21 | return this._value;
22 | }
23 | }
24 |
25 | export function computed(getter) {
26 | return new ComputedRefImpl(getter);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/reactivity/src/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "@5c24-mini-vue/shared";
2 |
3 | let activeEffect;
4 | let shouldTrack;
5 | export class ReactiveEffect {
6 | private _fn: any;
7 | public scheduler: Function | undefined;
8 | deps = [];
9 | active = true;
10 | onStop?: () => void;
11 | constructor(fn, scheduler?: Function) {
12 | this._fn = fn;
13 | this.scheduler = scheduler;
14 | }
15 |
16 | run() {
17 | if (!this.active) {
18 | return this._fn();
19 | }
20 |
21 | shouldTrack = true;
22 | activeEffect = this;
23 |
24 | const result = this._fn();
25 | shouldTrack = false;
26 |
27 | return result;
28 | }
29 |
30 | stop() {
31 | if (this.active) {
32 | cleanupEffect(this);
33 | if (this.onStop) {
34 | this.onStop();
35 | }
36 | this.active = false;
37 | }
38 | }
39 | }
40 |
41 | function cleanupEffect(effect) {
42 | effect.deps.forEach((dep: any) => {
43 | dep.delete(effect);
44 | });
45 | effect.deps.length = 0;
46 | }
47 |
48 | let targetMap = new WeakMap();
49 | export function track(target, key) {
50 | if (!isTracking()) return;
51 |
52 | let depsMap = targetMap.get(target);
53 | if (!depsMap) {
54 | depsMap = new Map();
55 | targetMap.set(target, depsMap);
56 | }
57 |
58 | let dep = depsMap.get(key);
59 | if (!dep) {
60 | dep = new Set();
61 | depsMap.set(key, dep);
62 | }
63 |
64 | trackEffects(dep);
65 | }
66 |
67 | export function trackEffects(dep) {
68 | // 依赖已经存在 dep 中就不再添加
69 | if (dep.has(activeEffect)) return;
70 |
71 | dep.add(activeEffect);
72 | activeEffect.deps.push(dep);
73 | }
74 |
75 | export function isTracking() {
76 | return shouldTrack && activeEffect !== undefined;
77 | }
78 |
79 | export function trigger(target, key) {
80 | let depsMap = targetMap.get(target);
81 | let dep = depsMap.get(key);
82 |
83 | triggerEffects(dep);
84 | }
85 |
86 | export function triggerEffects(dep) {
87 | for (let effect of dep) {
88 | if (effect.scheduler) {
89 | effect.scheduler();
90 | } else {
91 | effect.run();
92 | }
93 | }
94 | }
95 |
96 | export function effect(fn, options: any = {}) {
97 | const _effect = new ReactiveEffect(fn, options.scheduler);
98 | extend(_effect, options);
99 | _effect.run();
100 |
101 | const runner: any = _effect.run.bind(_effect);
102 | runner.effect = _effect;
103 |
104 | return runner;
105 | }
106 |
107 | export function stop(runner) {
108 | runner.effect.stop();
109 | }
110 |
--------------------------------------------------------------------------------
/packages/reactivity/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ref, proxyRefs, isRef, unRef } from "./ref";
2 | export {reactive, readonly, shallowReadonly, isReactive, isReadonly} from './reactive'
3 | export {effect} from './effect'
--------------------------------------------------------------------------------
/packages/reactivity/src/reactive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | mutableHandlers,
3 | readonlyHandlers,
4 | shallowReadonlyHandlers,
5 | } from "./baseHandlers";
6 |
7 | export const enum ReactiveFlags {
8 | IS_REACTIVE = "__v_isReactive",
9 | IS_READONLY = "__v_isReadonly",
10 | }
11 |
12 | export function reactive(raw: any) {
13 | return createActiveObject(raw, mutableHandlers);
14 | }
15 |
16 | export function readonly(raw: any) {
17 | return createActiveObject(raw, readonlyHandlers);
18 | }
19 |
20 | export function shallowReadonly(raw: any) {
21 | return createActiveObject(raw, shallowReadonlyHandlers);
22 | }
23 |
24 | export function isReactive(value) {
25 | return !!value[ReactiveFlags.IS_REACTIVE];
26 | }
27 |
28 | export function isReadonly(value) {
29 | return !!value[ReactiveFlags.IS_READONLY];
30 | }
31 |
32 | export function isProxy(value) {
33 | return isReactive(value) || isReadonly(value);
34 | }
35 |
36 | function createActiveObject(raw: any, baseHandle) {
37 | return new Proxy(raw, baseHandle);
38 | }
39 |
--------------------------------------------------------------------------------
/packages/reactivity/src/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from "@5c24-mini-vue/shared";
2 | import { isTracking, trackEffects, triggerEffects } from "./effect";
3 | import { reactive } from "./reactive";
4 |
5 | class RefImpl {
6 | private _value: any;
7 | private _rawValue: any; // 用于 set 时候和新值做对比
8 | public dep; // 存放依赖
9 | public __v_isRef = true;
10 |
11 | constructor(value) {
12 | this._rawValue = value;
13 | this._value = convert(value);
14 | this.dep = new Set();
15 | }
16 |
17 | get value() {
18 | // activeEffect 存在,才执行依赖收集
19 | trackRefValue(this);
20 | return this._value;
21 | }
22 |
23 | set value(newValue) {
24 | if (hasChanged(this._rawValue, newValue)) {
25 | this._rawValue = newValue;
26 | this._value = convert(newValue);
27 | triggerEffects(this.dep);
28 | }
29 | }
30 | }
31 |
32 | function convert(value) {
33 | return isObject(value) ? reactive(value) : value;
34 | }
35 |
36 | function trackRefValue(ref) {
37 | if (isTracking()) {
38 | trackEffects(ref.dep);
39 | }
40 | }
41 |
42 | export function ref(value) {
43 | return new RefImpl(value);
44 | }
45 |
46 | export function isRef(ref) {
47 | return !!ref.__v_isRef;
48 | }
49 |
50 | export function unRef(ref) {
51 | return isRef(ref) ? ref.value : ref;
52 | }
53 |
54 | // get 如果访问的是 ref 类型 返回 .value,如果不是 ref ,返回本身的值
55 | // set 如果新给值不是一个 ref 类型,把当前对象需要赋值的属性为 ref 的 .value 改掉
56 | // 如果新值是一个 ref,直接替换
57 | export function proxyRefs(objectWithRefs) {
58 | return new Proxy(objectWithRefs, {
59 | get(target, key) {
60 | return unRef(Reflect.get(target, key));
61 | },
62 |
63 | set(target, key, value) {
64 | if (isRef(target[key]) && !isRef(value)) {
65 | return (target[key].value = value);
66 | } else {
67 | return Reflect.set(target, key, value);
68 | }
69 | },
70 | });
71 | }
72 |
--------------------------------------------------------------------------------
/packages/runtime-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@5c24-mini-vue/runtime-core",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@5c24-mini-vue/reactivity": "workspace:^1.0.0",
14 | "@5c24-mini-vue/shared": "workspace:^1.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 |
3 | export function provide(key, value) {
4 | const currentInstance: any = getCurrentInstance();
5 | if (currentInstance) {
6 | let { provides } = currentInstance;
7 | const parentProvides = currentInstance.parent.provides;
8 |
9 | if (provides === parentProvides) {
10 | // 创建一个新的provides provides.__proto__ === parentProvides
11 | provides = currentInstance.provides = Object.create(parentProvides);
12 | }
13 |
14 | provides[key] = value;
15 | }
16 | }
17 |
18 | export function inject(key, defaultVal) {
19 | const currentInstance: any = getCurrentInstance();
20 | if (currentInstance) {
21 | const parentProvides = currentInstance.parent.provides;
22 | if (key in parentProvides) {
23 | return parentProvides[key];
24 | } else if (defaultVal) {
25 | if (typeof defaultVal === "function") {
26 | return defaultVal();
27 | }
28 | return defaultVal;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRefs } from "@5c24-mini-vue/reactivity";
2 | import { shallowReadonly } from "@5c24-mini-vue/reactivity/";
3 | import { emit } from "./componentEmit";
4 | import { initProps } from "./componentProps";
5 | import { PublicInstanceProxyhandlers } from "./componentPublicInstance";
6 | import { initSlots } from "./componentSlots";
7 |
8 | export function createComponentInstance(vnode, parent) {
9 | const component = {
10 | vnode,
11 | type: vnode.type,
12 | setupState: {},
13 | props: {},
14 | slots: {},
15 | next: null,
16 | provides: parent ? parent.provides : {},
17 | parent,
18 | isMounted: false,
19 | subTree: {},
20 | emit: () => {},
21 | };
22 | console.log(parent);
23 | component.emit = emit.bind(null, component) as any;
24 | return component;
25 | }
26 |
27 | export function setupComponent(instance) {
28 | initProps(instance, instance.vnode.props);
29 | initSlots(instance, instance.vnode.children);
30 | setupStatefulComponent(instance);
31 | }
32 |
33 | function setupStatefulComponent(instance) {
34 | const component = instance.type;
35 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyhandlers);
36 | const { setup } = component;
37 |
38 | if (setup) {
39 | setCurrentInstance(instance);
40 | const setupResult = setup(shallowReadonly(instance.props), {
41 | emit: instance.emit,
42 | });
43 | setCurrentInstance(null);
44 | handleSetupResult(instance, setupResult);
45 | }
46 | }
47 |
48 | function handleSetupResult(instance, setupResult) {
49 | if (typeof setupResult === "object") {
50 | instance.setupState = proxyRefs(setupResult);
51 | }
52 |
53 | finishComponentSetup(instance);
54 | }
55 |
56 | function finishComponentSetup(instance) {
57 | const Component = instance.type;
58 |
59 | if (compiler && !Component.render) {
60 | if (Component.template) {
61 | Component.render = compiler(Component.template);
62 | }
63 | }
64 | instance.render = Component.render;
65 | }
66 |
67 | let currentInstance = null;
68 |
69 | export function getCurrentInstance() {
70 | return currentInstance;
71 | }
72 |
73 | function setCurrentInstance(instance) {
74 | currentInstance = instance;
75 | }
76 |
77 | let compiler;
78 |
79 | export function registerRuntimeCompiler(_compiler) {
80 | compiler = _compiler;
81 | }
82 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { toHandlerKey } from "@5c24-mini-vue/shared";
2 |
3 | export function emit(instance, event, ...args) {
4 | const { props } = instance;
5 |
6 | const handlerName = toHandlerKey(event);
7 | const handler = props[handlerName];
8 | handler && handler(...args);
9 | }
10 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance, props) {
2 | // App 根组件的 props 是 undefined
3 | instance.props = props || {};
4 | }
5 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from "@5c24-mini-vue/shared";
2 |
3 | const publicPropertiesMap = {
4 | $el: (i) => i.vnode.el,
5 | $slots: (i) => i.slots,
6 | $props: (i) => i.props,
7 | };
8 |
9 | export const PublicInstanceProxyhandlers = {
10 | get({ _: instance }, key) {
11 | // setupState
12 | const { setupState, props } = instance;
13 |
14 | if (hasOwn(setupState, key)) {
15 | return setupState[key];
16 | } else if (hasOwn(props, key)) {
17 | return props[key];
18 | }
19 |
20 | const publicGetter = publicPropertiesMap[key];
21 | if (publicGetter) {
22 | return publicGetter(instance);
23 | }
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "@5c24-mini-vue/shared";
2 |
3 | export function initSlots(instance, children) {
4 | const { vnode } = instance;
5 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) {
6 | normalizeObjectSlots(instance.slots, children);
7 | }
8 | }
9 |
10 | function normalizeObjectSlots(slots, children) {
11 | for (let key in children) {
12 | let value = children[key];
13 | slots[key] = (props) => normalizeSlotValue(value(props));
14 | }
15 | }
16 |
17 | function normalizeSlotValue(value) {
18 | return Array.isArray(value) ? value : [value];
19 | }
20 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | export function shouldUpdateComponent(prevVNode, nextVNode) {
2 | const { props: prevProps } = prevVNode;
3 | const { props: nextProps } = nextVNode;
4 |
5 | for (const key in nextProps) {
6 | if (nextProps[key] !== prevProps[key]) {
7 | return true;
8 | }
9 | }
10 |
11 | return false;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function createAppAPI(render) {
4 | return function createApp(rootComponent) {
5 | return {
6 | mount(rootContainer) {
7 | const vnode = createVNode(rootComponent);
8 |
9 | render(vnode, rootContainer);
10 | },
11 | };
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function h(type, props?, children?) {
4 | return createVNode(type, props, children);
5 | }
6 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from "../vnode";
2 |
3 | export function renderSlots(slots, name, props) {
4 | const slot = slots[name];
5 | if (slot) {
6 | if (typeof slot === "function") {
7 | return createVNode(Fragment, {}, slot(props));
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export { h } from "./h";
2 | export { renderSlots } from "./helpers/renderSlots";
3 | export { createTextVNode, createElementVNode } from "./vnode";
4 | export { getCurrentInstance, registerRuntimeCompiler } from "./component";
5 | export { provide, inject } from "./apiInject";
6 | export { createRenderer } from "./renderer";
7 | export { nextTick } from "./scheduler";
8 | export { toDisplayString } from "@5c24-mini-vue/shared";
9 | export * from "@5c24-mini-vue/reactivity";
10 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "@5c24-mini-vue/reactivity";
2 | import { EMPTY_PROPS } from "@5c24-mini-vue/shared";
3 | import { ShapeFlags } from "@5c24-mini-vue/shared";
4 | import { createComponentInstance, setupComponent } from "./component";
5 | import { shouldUpdateComponent } from "./componentUpdateUtils";
6 | import { createAppAPI } from "./createApp";
7 | import { queueJobs } from "./scheduler";
8 | import { Fragment, Text } from "./vnode";
9 |
10 | export function createRenderer(options) {
11 | const {
12 | createElement: hostCreateElement,
13 | patchProp: hostPatchProp,
14 | insert: hostInsert,
15 | remove: hostRemove,
16 | setElementText: hostSetElementText,
17 | setElementArray: hostSetElementArray,
18 | } = options;
19 |
20 | function render(vnode, container) {
21 | patch(null, vnode, container, null, null);
22 | }
23 |
24 | function patch(n1, n2, container: any, parentComponent, anchor) {
25 | const { shapeFlag, type } = n2;
26 | switch (type) {
27 | case Fragment:
28 | // 只渲染 children
29 | processFlagment(n1, n2, container, parentComponent, anchor);
30 | break;
31 | case Text:
32 | processText(n1, n2, container);
33 | break;
34 | default:
35 | if (shapeFlag & ShapeFlags.ELEMENT) {
36 | processElement(n1, n2, container, parentComponent, anchor);
37 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
38 | processComponent(n1, n2, container, parentComponent, anchor);
39 | }
40 | break;
41 | }
42 | }
43 |
44 | function processText(n1, n2, container: any) {
45 | const { children } = n2;
46 | const textNode = (n2.el = document.createTextNode(children));
47 | container.append(textNode);
48 | }
49 |
50 | function processFlagment(n1, n2, container: any, parentComponent, anchor) {
51 | mountChildren(n2.children, container, parentComponent, anchor);
52 | }
53 |
54 | function processElement(n1, n2, container: any, parentComponent, anchor) {
55 | if (!n1) {
56 | mountElement(n2, container, parentComponent, anchor);
57 | } else {
58 | patchElement(n1, n2, container, parentComponent, anchor);
59 | }
60 | }
61 |
62 | function patchElement(n1, n2, container, parentComponent, anchor) {
63 | console.log("patchElement");
64 | console.log("n1", n1);
65 | console.log("n2", n2);
66 |
67 | const oldProps = n1.props || EMPTY_PROPS;
68 | const newProps = n2.props || EMPTY_PROPS;
69 | const el = (n2.el = n1.el);
70 | patchChildren(n1, n2, el, parentComponent, anchor);
71 | patchProps(el, oldProps, newProps);
72 | }
73 |
74 | function patchChildren(n1, n2, container, parentComponent, parentAnchor) {
75 | const preShapeFlag = n1.shapeFlag;
76 | const { shapeFlag } = n2;
77 | const c1 = n1.children;
78 | const c2 = n2.children;
79 |
80 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
81 | if (preShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
82 | unmountChildren(n1.children);
83 | }
84 | if (c1 !== c2) {
85 | hostSetElementText(container, c2);
86 | }
87 | } else {
88 | if (preShapeFlag & ShapeFlags.TEXT_CHILDREN) {
89 | hostSetElementText(container, "");
90 |
91 | mountChildren(n2.children, container, parentComponent, parentAnchor);
92 | } else {
93 | // array diff array
94 | patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor);
95 | }
96 | }
97 | }
98 |
99 | function patchKeyedChildren(
100 | c1,
101 | c2,
102 | container,
103 | parentComponent,
104 | parentAnchor
105 | ) {
106 | let i = 0;
107 | const l2 = c2.length;
108 | let e1 = c1.length - 1;
109 | let e2 = l2 - 1;
110 |
111 | function isSomeVNodeType(n1, n2) {
112 | // 基于 type 和 key 确定两个节点是否相同
113 | return n1.type === n2.type && n1.key === n2.key;
114 | }
115 |
116 | // 左侧对比
117 | while (i <= e1 && i <= e2) {
118 | const n1 = c1[i];
119 | const n2 = c2[i];
120 | if (isSomeVNodeType(n1, n2)) {
121 | patch(n1, n2, container, parentComponent, parentAnchor);
122 | } else {
123 | break;
124 | }
125 | i++;
126 | }
127 |
128 | // 右侧对比
129 | while (i <= e1 && i <= e2) {
130 | const n1 = c1[e1];
131 | const n2 = c2[e2];
132 | if (isSomeVNodeType(n1, n2)) {
133 | patch(n1, n2, container, parentComponent, parentAnchor);
134 | } else {
135 | break;
136 | }
137 | e1--;
138 | e2--;
139 | }
140 |
141 | if (i > e1) {
142 | // 新的比老的多 新增
143 | if (i <= e2) {
144 | const nextPos = e2 + 1;
145 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
146 | while (i <= e2) {
147 | patch(null, c2[i], container, parentComponent, anchor);
148 | i++;
149 | }
150 | }
151 | } else if (i > e2) {
152 | // 老的比新的多 删除老的
153 | while (i <= e1) {
154 | hostRemove(c1[i].el);
155 | i++;
156 | }
157 | } else {
158 | // 中间对比
159 | let s1 = i; // 老节点的开始
160 | let s2 = i; // 新节点的开始
161 | const toBePatched = e2 - s2 + 1; // 需要 patch 的新节点个数
162 | let patched = 0; // 已经 patch 的新节点数
163 |
164 | const keyToNewIndexMap = new Map();
165 | // 存储旧节点混乱元素的索引,创建指定长度的数组 性能更好
166 | const newIndexToOldIndexMap = new Array(toBePatched);
167 | let moved = false;
168 | let maxNewIndexSoFar = 0;
169 | // 初始化每一项索引,0表示未建立映射关系
170 | for (let i = 0; i < toBePatched; i++) {
171 | newIndexToOldIndexMap[i] = 0;
172 | }
173 | // 遍历新节点,设置 key index 映射关系
174 | for (let i = s2; i <= e2; i++) {
175 | const nextChild = c2[i];
176 | keyToNewIndexMap.set(nextChild.key, i);
177 | }
178 |
179 | for (let i = s1; i <= e1; i++) {
180 | const prevChild = c1[i];
181 |
182 | if (patched >= toBePatched) {
183 | hostRemove(prevChild.el);
184 | continue;
185 | }
186 |
187 | let newIndex;
188 | if (prevChild.key != null) {
189 | // 老节点给了 key 就用 map 映射
190 | newIndex = keyToNewIndexMap.get(prevChild.key);
191 | } else {
192 | // 没有 key 进行遍历
193 | for (let j = s2; j <= e2; j++) {
194 | if (isSomeVNodeType(prevChild, c2[j])) {
195 | newIndex = j;
196 |
197 | break;
198 | }
199 | }
200 | }
201 |
202 | if (newIndex === undefined) {
203 | // 新的里面没有当前老的节点
204 | hostRemove(prevChild.el);
205 | } else {
206 | // 旧节点在新节点中存在
207 |
208 | if (newIndex >= maxNewIndexSoFar) {
209 | maxNewIndexSoFar = newIndex;
210 | } else {
211 | moved = true;
212 | }
213 |
214 | // newIndex表示当前老节点在新节点中的下标, 减去 s2 是为了将索引归于0
215 | // 这里 i + 1 是初始化的时候0表示未建立映射关系,考虑到 i 为0的情况下,所以 +1
216 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
217 |
218 | patch(prevChild, c2[newIndex], container, parentComponent, null);
219 | patched++;
220 | }
221 | }
222 | // 获取最长递增子序列
223 | const increasingNewIndexSequence = moved
224 | ? getSequence(newIndexToOldIndexMap)
225 | : [];
226 | // j 指向获取出来的最长递增子序列的索引
227 | // i 指向新节点
228 | let j = increasingNewIndexSequence.length - 1;
229 |
230 | for (let i = toBePatched - 1; i >= 0; i--) {
231 | const nextIndex = i + s2;
232 | const nextChild = c2[nextIndex];
233 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
234 | if (newIndexToOldIndexMap[i] === 0) {
235 | patch(null, nextChild, container, parentComponent, anchor);
236 | } else if (moved) {
237 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
238 | hostInsert(nextChild.el, container, anchor);
239 | } else {
240 | j--;
241 | }
242 | }
243 | }
244 | }
245 | }
246 |
247 | function unmountChildren(children) {
248 | for (let i = 0; i < children.length; i++) {
249 | const el = children[i].el;
250 | hostRemove(el);
251 | }
252 | }
253 |
254 | function patchProps(el, oldProps, newProps) {
255 | for (let key in newProps) {
256 | const prevProps = oldProps[key];
257 | const nextProps = newProps[key];
258 |
259 | if (prevProps !== nextProps) {
260 | hostPatchProp(el, key, prevProps, nextProps);
261 | }
262 | }
263 |
264 | if (oldProps !== EMPTY_PROPS) {
265 | for (let key in oldProps) {
266 | if (!(key in newProps)) {
267 | hostPatchProp(el, key, oldProps[key], null);
268 | }
269 | }
270 | }
271 | }
272 |
273 | function mountElement(vnode: any, container: any, parentComponent, anchor) {
274 | const el = (vnode.el = hostCreateElement(vnode.type));
275 | const { children, shapeFlag } = vnode;
276 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
277 | el.textContent = children;
278 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
279 | mountChildren(vnode.children, el, parentComponent, anchor);
280 | }
281 |
282 | const { props } = vnode;
283 | for (const key in props) {
284 | const val = props[key];
285 |
286 | hostPatchProp(el, key, null, val);
287 | }
288 |
289 | hostInsert(el, container, anchor);
290 | }
291 |
292 | function mountChildren(
293 | children: any,
294 | container: any,
295 | parentComponent,
296 | anchor
297 | ) {
298 | children.forEach((v) => {
299 | patch(null, v, container, parentComponent, anchor);
300 | });
301 | }
302 |
303 | function processComponent(n1, n2, container: any, parentComponent, anchor) {
304 | if (!n1) {
305 | mountComponent(n2, container, parentComponent, anchor);
306 | } else {
307 | updateComponent(n1, n2);
308 | }
309 | }
310 |
311 | function updateComponent(n1, n2) {
312 | const instance = (n2.component = n1.component);
313 | if (shouldUpdateComponent(n1, n2)) {
314 | instance.next = n2;
315 | instance.update();
316 | } else {
317 | n2.el = n1.el;
318 | n2.vnode = n2;
319 | }
320 | }
321 |
322 | function mountComponent(
323 | initialVNode: any,
324 | container: any,
325 | parentComponent,
326 | anchor
327 | ) {
328 | const instance = (initialVNode.component = createComponentInstance(
329 | initialVNode,
330 | parentComponent
331 | ));
332 |
333 | setupComponent(instance);
334 | setupRenderEffect(instance, initialVNode, container, anchor);
335 | }
336 |
337 | function setupRenderEffect(instance, initialVNode, container, anchor) {
338 | instance.update = effect(
339 | () => {
340 | if (!instance.isMounted) {
341 | const { proxy } = instance;
342 | const subTree = (instance.subTree = instance.render.call(
343 | proxy,
344 | proxy
345 | ));
346 | patch(null, subTree, container, instance, anchor);
347 | initialVNode.el = subTree.el;
348 |
349 | instance.isMounted = true;
350 | } else {
351 | const { next, vnode } = instance;
352 | if (next) {
353 | next.el = vnode.el;
354 | updateComponentPreRender(instance, next);
355 | }
356 |
357 | const { proxy } = instance;
358 | const subTree = instance.render.call(proxy, proxy);
359 | const prevSubTree = instance.subTree;
360 | instance.subTree = subTree;
361 | patch(prevSubTree, subTree, container, instance, anchor);
362 | }
363 | },
364 | {
365 | scheduler() {
366 | queueJobs(instance.update);
367 | },
368 | }
369 | );
370 | }
371 |
372 | return {
373 | createApp: createAppAPI(render),
374 | };
375 | }
376 |
377 | function updateComponentPreRender(instance, nextVNode) {
378 | instance.vnode = nextVNode;
379 | instance.next = null;
380 | instance.props = nextVNode.props;
381 | }
382 |
383 | function getSequence(arr) {
384 | const p = arr.slice();
385 | const result = [0];
386 | let i, j, u, v, c;
387 | const len = arr.length;
388 | for (i = 0; i < len; i++) {
389 | const arrI = arr[i];
390 | if (arrI !== 0) {
391 | j = result[result.length - 1];
392 | if (arr[j] < arrI) {
393 | p[i] = j;
394 | result.push(i);
395 | continue;
396 | }
397 | u = 0;
398 | v = result.length - 1;
399 | while (u < v) {
400 | c = (u + v) >> 1;
401 | if (arr[result[c]] < arrI) {
402 | u = c + 1;
403 | } else {
404 | v = c;
405 | }
406 | }
407 | if (arrI < arr[result[u]]) {
408 | if (u > 0) {
409 | p[i] = result[u - 1];
410 | }
411 | result[u] = i;
412 | }
413 | }
414 | }
415 | u = result.length;
416 | v = result[u - 1];
417 | while (u-- > 0) {
418 | result[u] = v;
419 | v = p[v];
420 | }
421 | return result;
422 | }
423 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/scheduler.ts:
--------------------------------------------------------------------------------
1 | const queue: any[] = [];
2 |
3 | let isFlushPending = false;
4 | const p = Promise.resolve();
5 |
6 | export function nextTick(fn) {
7 | return fn ? p.then(fn) : p;
8 | }
9 |
10 | export function queueJobs(job) {
11 | if (!queue.includes(job)) {
12 | queue.push(job);
13 | }
14 |
15 | queueFlush();
16 | }
17 |
18 | function queueFlush() {
19 | if (isFlushPending) return;
20 | isFlushPending = true;
21 |
22 | nextTick(flushJobs);
23 | }
24 |
25 | function flushJobs() {
26 | isFlushPending = false;
27 |
28 | let job;
29 | while ((job = queue.shift())) {
30 | job && job();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "@5c24-mini-vue/shared";
2 |
3 | export const Fragment = Symbol("Fragment");
4 |
5 | export const Text = Symbol("Text");
6 |
7 | export { createVNode as createElementVNode };
8 |
9 | export function createVNode(type, props?, children?) {
10 | const vnode = {
11 | type,
12 | props,
13 | children,
14 | component: null,
15 | key: props && props.key,
16 | shapeFlag: getShapeFlag(type),
17 | el: null,
18 | };
19 |
20 | if (typeof children === "string") {
21 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
22 | } else if (Array.isArray(children)) {
23 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
24 | }
25 |
26 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
27 | if (typeof children === "object") {
28 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
29 | }
30 | }
31 |
32 | return vnode;
33 | }
34 |
35 | export function createTextVNode(text: string) {
36 | return createVNode(Text, {}, text);
37 | }
38 |
39 | function getShapeFlag(type: any) {
40 | return typeof type === "string"
41 | ? ShapeFlags.ELEMENT
42 | : ShapeFlags.STATEFUL_COMPONENT;
43 | }
44 |
--------------------------------------------------------------------------------
/packages/runtime-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@5c24-mini-vue/runtime-dom",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@5c24-mini-vue/runtime-core": "workspace:^1.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/runtime-dom/src/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "@5c24-mini-vue/runtime-core";
2 |
3 | function createElement(type) {
4 | console.log("createElement-------------");
5 | return document.createElement(type);
6 | }
7 |
8 | function patchProp(el, key, prevVal, nextVal) {
9 | console.log("patchProp-------------");
10 | const isOn = (key: string) => /^on[A-Z]/.test(key);
11 | if (isOn(key)) {
12 | const event = key.slice(2).toLocaleLowerCase();
13 | el.addEventListener(event, nextVal);
14 | } else {
15 | if (nextVal === undefined || nextVal === null) {
16 | el.removeAttribute(key);
17 | } else {
18 | el.setAttribute(key, nextVal);
19 | }
20 | }
21 | }
22 |
23 | function insert(child, parent, anchor = null) {
24 | console.log("insert-------------");
25 | // parent.append(el);
26 | parent.insertBefore(child, anchor);
27 | }
28 |
29 | function remove(child) {
30 | const parent = child.parentNode;
31 | if (parent) {
32 | parent.removeChild(child);
33 | }
34 | }
35 |
36 | function setElementText(el, text) {
37 | el.textContent = text;
38 | }
39 |
40 | const renderder: any = createRenderer({
41 | createElement,
42 | patchProp,
43 | insert,
44 | remove,
45 | setElementText,
46 | });
47 |
48 | export function createApp(...args) {
49 | return renderder.createApp(...args);
50 | }
51 |
52 | export * from "@5c24-mini-vue/runtime-core";
53 |
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@5c24-mini-vue/shared",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------
/packages/shared/src/ShapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags {
2 | ELEMENT = 1, // 0001
3 | STATEFUL_COMPONENT = 1 << 1, // 0010
4 | TEXT_CHILDREN = 1 << 2, // 0100
5 | ARRAY_CHILDREN = 1 << 3, // 1000
6 | SLOT_CHILDREN = 1 << 4,
7 | }
8 |
--------------------------------------------------------------------------------
/packages/shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./toDisplayString";
2 |
3 | export const extend = Object.assign;
4 |
5 | export const EMPTY_PROPS = {};
6 |
7 | export const isObject = (val) => {
8 | return val !== null && typeof val === "object";
9 | };
10 |
11 | export const isString = (val) => typeof val === "string";
12 |
13 | export const hasChanged = (val, newVal) => {
14 | return !Object.is(val, newVal);
15 | };
16 |
17 | export const hasOwn = (val, key) => {
18 | return Object.prototype.hasOwnProperty.call(val, key);
19 | };
20 |
21 | export const camelize = (str: string) => {
22 | return str.replace(/-(\w)/g, (_, c: string) => {
23 | return c ? c.toUpperCase() : "";
24 | });
25 | };
26 |
27 | const capitalize = (str: string) => {
28 | return str.charAt(0).toUpperCase() + str.slice(1);
29 | };
30 |
31 | export const toHandlerKey = (str: string) => {
32 | return str ? "on" + capitalize(camelize(str)) : "";
33 | };
34 |
35 | export {ShapeFlags} from './ShapeFlags'
--------------------------------------------------------------------------------
/packages/shared/src/toDisplayString.ts:
--------------------------------------------------------------------------------
1 | export function toDisplayString(value) {
2 | return String(value);
3 | }
4 |
--------------------------------------------------------------------------------
/packages/vue/examples/apiInject/App.js:
--------------------------------------------------------------------------------
1 | import { h, provide, inject } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | const Provider = {
4 | name: "Provider",
5 | setup() {
6 | provide("foo", "fooVal");
7 | provide("bar", "barVal");
8 | },
9 | render() {
10 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]);
11 | },
12 | };
13 |
14 | const ProviderTwo = {
15 | name: "ProviderTwo",
16 | setup() {
17 | provide("foo", "fooTwo");
18 | const foo = inject("foo");
19 |
20 | return {
21 | foo,
22 | };
23 | },
24 | render() {
25 | return h("div", {}, [h("p", {}, `ProviderTwo -${this.foo}`), h(Consumer)]);
26 | },
27 | };
28 |
29 | const Consumer = {
30 | name: "Consumer",
31 | setup() {
32 | const foo = inject("foo");
33 | const bar = inject("bar");
34 | const baz = inject("baz", "bazDefault");
35 |
36 | return {
37 | foo,
38 | bar,
39 | baz,
40 | };
41 | },
42 |
43 | render() {
44 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar} - ${this.baz}`);
45 | },
46 | };
47 |
48 | export default {
49 | name: "App",
50 | setup() {},
51 | render() {
52 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]);
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/packages/vue/examples/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/compiler-base/App.js:
--------------------------------------------------------------------------------
1 | import { ref } from "../../dist/5c24-mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 | template: `hi,{{count}}
`,
6 | setup() {
7 | const count = (window.count = ref(1));
8 | return {
9 | count,
10 | };
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/packages/vue/examples/compiler-base/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/packages/vue/examples/compiler-base/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const App = {
5 | name: "App",
6 | render() {
7 | return h("div", {}, [
8 | h("div", {}, "App"),
9 | h(Foo, {
10 | onAdd(a, b) {
11 | console.log("onAdd", a, b);
12 | },
13 | onAddFoo() {
14 | console.log("onAddFoo");
15 | },
16 | }),
17 | ]);
18 | },
19 |
20 | setup() {
21 | return {
22 | msg: "mini-vue",
23 | };
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup(props, { emit }) {
6 | const emitAdd = () => {
7 | console.log("emmit add");
8 | emit("add", 1, 2);
9 | emit("add-foo");
10 | };
11 |
12 | return {
13 | emitAdd,
14 | };
15 | },
16 |
17 | render() {
18 | const btn = h(
19 | "button",
20 | {
21 | onClick: this.emitAdd,
22 | },
23 | "emitAdd"
24 | );
25 |
26 | const foo = h("p", {}, "foo");
27 | return h("div", {}, [foo, btn]);
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVNode } from "../../lib/5c24-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export 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("hahaha"),
15 | ],
16 | footer: () =>
17 | h("p", {}, [h("span", {}, "footer "), h("span", {}, "123")]),
18 | }
19 | );
20 | // const foo = h(Foo, {}, h("p", {}, "123"));
21 |
22 | return h("div", {}, [app, foo]);
23 | },
24 |
25 | setup() {
26 | return {};
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup() {
6 | return {};
7 | },
8 | render() {
9 | const foo = h("p", {}, "foo");
10 | const age = 18;
11 | return h("div", {}, [
12 | renderSlots(this.$slots, "header", {
13 | age,
14 | }),
15 | foo,
16 | renderSlots(this.$slots, "footer"),
17 | ]);
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/5c24-mini-vue.esm.js";
2 | import Child from "./Child.js";
3 |
4 | export const App = {
5 | name: "App",
6 | setup() {
7 | const msg = ref("123");
8 | const count = ref(1);
9 |
10 | window.msg = msg;
11 |
12 | const changeChildProps = () => {
13 | msg.value = "456";
14 | };
15 |
16 | const changeCount = () => {
17 | count.value++;
18 | };
19 |
20 | return { msg, changeChildProps, changeCount, count };
21 | },
22 |
23 | render() {
24 | return h("div", {}, [
25 | h("div", {}, "你好"),
26 | h(
27 | "button",
28 | {
29 | onClick: this.changeChildProps,
30 | },
31 | "change child props"
32 | ),
33 | h(Child, {
34 | msg: this.msg,
35 | }),
36 | h(
37 | "button",
38 | {
39 | onClick: this.changeCount,
40 | },
41 | "change self count"
42 | ),
43 | h("p", {}, "count: " + this.count),
44 | ]);
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 | export default {
3 | name: "Child",
4 | setup(props, { emit }) {},
5 | render(proxy) {
6 | return h("div", {}, [
7 | h("div", {}, "child - props - msg: " + this.$props.msg),
8 | ]);
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/5c24-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const App = {
5 | name: "App",
6 | render() {
7 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]);
8 | },
9 |
10 | setup() {
11 | const instance = getCurrentInstance();
12 | console.log("App:", instance);
13 | return {};
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup() {
6 | const instance = getCurrentInstance();
7 | console.log("Foo:", instance);
8 | return {};
9 | },
10 | render() {
11 | return h("div", {}, "foo");
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/customRenderer/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const App = {
4 | setup() {
5 | return {
6 | x: 100,
7 | y: 100,
8 | };
9 | },
10 | render() {
11 | return h("rect", { x: this.x, y: this.y });
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/packages/vue/examples/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/packages/vue/examples/customRenderer/main.js:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const game = new PIXI.Application({
5 | width: 500,
6 | height: 500,
7 | });
8 |
9 | document.body.append(game.view);
10 |
11 | const renderer = createRenderer({
12 | createElement(type) {
13 | if (type === "rect") {
14 | const rect = new PIXI.Graphics();
15 | rect.beginFill(0xff0000);
16 | rect.drawRect(0, 0, 100, 100);
17 | rect.endFill();
18 |
19 | return rect;
20 | }
21 | },
22 | patchProp(el, key, val) {
23 | el[key] = val;
24 | },
25 | insert(el, parent) {
26 | parent.addChild(el);
27 | },
28 | });
29 |
30 | renderer.createApp(App).mount(game.stage);
31 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloworld/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../dist/5c24-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | window.self = null;
5 | export const App = {
6 | name: "App",
7 | render() {
8 | window.self = this;
9 | return h(
10 | "div",
11 | {
12 | id: "root",
13 | class: ["red", "blue"],
14 | onClick: () => {
15 | console.log("click");
16 | },
17 | onMousedown: () => {
18 | console.log("mousedown");
19 | },
20 | },
21 | // [h("div", {}, "hi, " + this.msg), h(Foo, { count: 1 })]
22 | // "hi, " + this.msg
23 | // "hi, mini-vue"
24 | [h("p", { class: "red" }, "hello,"), h("p", { class: "blue" }, "mini-vue")]
25 | );
26 | },
27 |
28 | setup() {
29 | return {
30 | msg: "mini-vue",
31 | };
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloworld/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../dist/5c24-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup(props) {
6 | console.log(props);
7 |
8 | // readonly
9 | props.count++;
10 | console.log(props);
11 | },
12 |
13 | render() {
14 | return h("div", {}, "foo: " + this.count);
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloworld/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../dist/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/nextTicker/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | ref,
4 | getCurrentInstance,
5 | nextTick,
6 | } from "../../lib/5c24-mini-vue.esm.js";
7 |
8 | export default {
9 | name: "App",
10 | setup() {
11 | const count = ref(1);
12 | const instance = getCurrentInstance();
13 |
14 | function onClick() {
15 | for (let i = 0; i < 100; i++) {
16 | console.log("update");
17 | count.value = i;
18 | }
19 |
20 | // debugger;
21 | console.log(instance);
22 | nextTick(() => {
23 | console.log(instance);
24 | });
25 |
26 | // await nextTick()
27 | // console.log(instance)
28 | }
29 |
30 | return {
31 | onClick,
32 | count,
33 | };
34 | },
35 | render() {
36 | const button = h("button", { onClick: this.onClick }, "update");
37 | const p = h("p", {}, "count:" + this.count);
38 |
39 | return h("div", {}, [button, p]);
40 | },
41 | };
42 |
--------------------------------------------------------------------------------
/packages/vue/examples/nextTicker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/packages/vue/examples/nextTicker/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | import ArrayToText from "./ArrayToText.js";
4 | import TextToText from "./TextToText.js";
5 | import TextToArray from "./TextToArray.js";
6 | import ArrayToArray from "./ArrayToArray.js";
7 |
8 | export const App = {
9 | name: "App",
10 | setup() {},
11 | render() {
12 | return h("div", { tId: 1 }, [
13 | h("p", {}, "主页"),
14 | // 老的是 array 新的是 text
15 | // h(ArrayToText),
16 | // 老的是 text 新的是 text
17 | // h(TextToText),
18 | // 老的是 text 新的是 array
19 | // h(TextToArray),
20 | // 老的是 array 新的是 array
21 | h(ArrayToArray),
22 | ]);
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 array
3 |
4 | import { ref, h } from "../../lib/5c24-mini-vue.esm.js";
5 |
6 | // 1. 左侧的对比
7 | // (a b) c
8 | // (a b) d e
9 | // const prevChildren = [
10 | // h("p", { key: "A" }, "A"),
11 | // h("p", { key: "B" }, "B"),
12 | // h("p", { key: "C" }, "C"),
13 | // ];
14 | // const nextChildren = [
15 | // h("p", { key: "A" }, "A"),
16 | // h("p", { key: "B" }, "B"),
17 | // h("p", { key: "D" }, "D"),
18 | // h("p", { key: "E" }, "E"),
19 | // ];
20 |
21 | // 2. 右侧的对比
22 | // a (b c)
23 | // d e (b c)
24 | // const prevChildren = [
25 | // h("p", { key: "A" }, "A"),
26 | // h("p", { key: "B" }, "B"),
27 | // h("p", { key: "C" }, "C"),
28 | // ];
29 | // const nextChildren = [
30 | // h("p", { key: "D" }, "D"),
31 | // h("p", { key: "E" }, "E"),
32 | // h("p", { key: "B" }, "B"),
33 | // h("p", { key: "C" }, "C"),
34 | // ];
35 |
36 | // 3. 新的比老的长
37 | // 创建新的
38 | // 左侧
39 | // (a b)
40 | // (a b) c
41 | // i = 2, e1 = 1, e2 = 2
42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
43 | // const nextChildren = [
44 | // h("p", { key: "A" }, "A"),
45 | // h("p", { key: "B" }, "B"),
46 | // h("p", { key: "C" }, "C"),
47 | // h("p", { key: "D" }, "D"),
48 | // ];
49 |
50 | // 右侧
51 | // (a b)
52 | // c (a b)
53 | // i = 0, e1 = -1, e2 = 0
54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
55 | // const nextChildren = [
56 | // h("p", { key: "D" }, "D"),
57 | // h("p", { key: "C" }, "C"),
58 | // h("p", { key: "A" }, "A"),
59 | // h("p", { key: "B" }, "B"),
60 | // ];
61 |
62 | // 4. 老的比新的长
63 | // 删除老的
64 | // 左侧
65 | // (a b) c
66 | // (a b)
67 | // i = 2, e1 = 2, e2 = 1
68 | // const prevChildren = [
69 | // h("p", { key: "A" }, "A"),
70 | // h("p", { key: "B" }, "B"),
71 | // h("p", { key: "C" }, "C"),
72 | // ];
73 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
74 |
75 | // 右侧
76 | // a (b c)
77 | // (b c)
78 | // i = 0, e1 = 0, e2 = -1
79 |
80 | // const prevChildren = [
81 | // h("p", { key: "A" }, "A"),
82 | // h("p", { key: "B" }, "B"),
83 | // h("p", { key: "C" }, "C"),
84 | // ];
85 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
86 |
87 | // 5. 对比中间的部分
88 | // 删除老的 (在老的里面存在,新的里面不存在)
89 | // 5.1
90 | // a,b,(c,d),f,g
91 | // a,b,(e,c),f,g
92 | // D 节点在新的里面是没有的 - 需要删除掉
93 | // C 节点 props 也发生了变化
94 |
95 | // const prevChildren = [
96 | // h("p", { key: "A" }, "A"),
97 | // h("p", { key: "B" }, "B"),
98 | // h("p", { key: "C", id: "c-prev" }, "C"),
99 | // h("p", { key: "D" }, "D"),
100 | // h("p", { key: "F" }, "F"),
101 | // h("p", { key: "G" }, "G"),
102 | // ];
103 |
104 | // const nextChildren = [
105 | // h("p", { key: "A" }, "A"),
106 | // h("p", { key: "B" }, "B"),
107 | // h("p", { key: "E" }, "E"),
108 | // h("p", { key: "C", id: "c-next" }, "C"),
109 | // h("p", { key: "F" }, "F"),
110 | // h("p", { key: "G" }, "G"),
111 | // ];
112 |
113 | // 5.1.1
114 | // a,b,(c,e,d),f,g
115 | // a,b,(e,c),f,g
116 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
117 | // const prevChildren = [
118 | // h("p", { key: "A" }, "A"),
119 | // h("p", { key: "B" }, "B"),
120 | // h("p", { key: "C", id: "c-prev" }, "C"),
121 | // h("p", { key: "E" }, "E"),
122 | // h("p", { key: "D" }, "D"),
123 | // h("p", { key: "F" }, "F"),
124 | // h("p", { key: "G" }, "G"),
125 | // ];
126 |
127 | // const nextChildren = [
128 | // h("p", { key: "A" }, "A"),
129 | // h("p", { key: "B" }, "B"),
130 | // h("p", { key: "E" }, "E"),
131 | // h("p", { key: "C", id: "c-next" }, "C"),
132 | // h("p", { key: "F" }, "F"),
133 | // h("p", { key: "G" }, "G"),
134 | // ];
135 |
136 | // 2 移动 (节点存在于新的和老的里面,但是位置变了)
137 |
138 | // 2.1
139 | // a,b,(c,d,e),f,g
140 | // a,b,(e,c,d),f,g
141 | // 最长子序列: [1,2]
142 |
143 | const prevChildren = [
144 | h("p", { key: "A" }, "A"),
145 | h("p", { key: "B" }, "B"),
146 | h("p", { key: "C" }, "C"),
147 | h("p", { key: "D" }, "D"),
148 | h("p", { key: "E" }, "E"),
149 | h("p", { key: "F" }, "F"),
150 | h("p", { key: "G" }, "G"),
151 | ];
152 |
153 | const nextChildren = [
154 | h("p", { key: "A" }, "A"),
155 | h("p", { key: "B" }, "B"),
156 | h("p", { key: "E" }, "E"),
157 | h("p", { key: "C" }, "C"),
158 | h("p", { key: "D" }, "D"),
159 | h("p", { key: "F" }, "F"),
160 | h("p", { key: "G" }, "G"),
161 | ];
162 |
163 | // 3. 创建新的节点
164 | // a,b,(c,e),f,g
165 | // a,b,(e,c,d),f,g
166 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
167 | // const prevChildren = [
168 | // h("p", { key: "A" }, "A"),
169 | // h("p", { key: "B" }, "B"),
170 | // h("p", { key: "C" }, "C"),
171 | // h("p", { key: "E" }, "E"),
172 | // h("p", { key: "F" }, "F"),
173 | // h("p", { key: "G" }, "G"),
174 | // ];
175 |
176 | // const nextChildren = [
177 | // h("p", { key: "A" }, "A"),
178 | // h("p", { key: "B" }, "B"),
179 | // h("p", { key: "E" }, "E"),
180 | // h("p", { key: "C" }, "C"),
181 | // h("p", { key: "D" }, "D"),
182 | // h("p", { key: "F" }, "F"),
183 | // h("p", { key: "G" }, "G"),
184 | // ];
185 |
186 | // 综合例子
187 | // a,b,(c,d,e,z),f,g
188 | // a,b,(d,c,y,e),f,g
189 |
190 | // const prevChildren = [
191 | // h("p", { key: "A" }, "A"),
192 | // h("p", { key: "B" }, "B"),
193 | // h("p", { key: "C" }, "C"),
194 | // h("p", { key: "D" }, "D"),
195 | // h("p", { key: "E" }, "E"),
196 | // h("p", { key: "Z" }, "Z"),
197 | // h("p", { key: "F" }, "F"),
198 | // h("p", { key: "G" }, "G"),
199 | // ];
200 |
201 | // const nextChildren = [
202 | // h("p", { key: "A" }, "A"),
203 | // h("p", { key: "B" }, "B"),
204 | // h("p", { key: "D" }, "D"),
205 | // h("p", { key: "C" }, "C"),
206 | // h("p", { key: "Y" }, "Y"),
207 | // h("p", { key: "E" }, "E"),
208 | // h("p", { key: "F" }, "F"),
209 | // h("p", { key: "G" }, "G"),
210 | // ];
211 |
212 | // fix c 节点应该是 move 而不是删除之后重新创建的
213 | // const prevChildren = [
214 | // h("p", { key: "A" }, "A"),
215 | // h("p", {}, "C"),
216 | // h("p", { key: "B" }, "B"),
217 | // h("p", { key: "D" }, "D"),
218 | // ];
219 |
220 | // const nextChildren = [
221 | // h("p", { key: "A" }, "A"),
222 | // h("p", { key: "B" }, "B"),
223 | // h("p", {}, "C"),
224 | // h("p", { key: "D" }, "D"),
225 | // ];
226 |
227 | export default {
228 | name: "ArrayToArray",
229 | setup() {
230 | const isChange = ref(false);
231 | window.isChange = isChange;
232 |
233 | return {
234 | isChange,
235 | };
236 | },
237 | render() {
238 | const self = this;
239 |
240 | return self.isChange === true
241 | ? h("div", {}, nextChildren)
242 | : h("div", {}, prevChildren);
243 | },
244 | };
245 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 text
3 |
4 | import { ref, h } from "../../lib/5c24-mini-vue.esm.js";
5 | const nextChildren = "newChildren";
6 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "ArrayToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | // 新的是 array
2 | // 老的是 text
3 | import { ref, h } from "../../lib/5c24-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "TextToArray",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | // 新的是 text
2 | // 老的是 text
3 | import { ref, h } from "../../lib/5c24-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = "newChild";
7 |
8 | export default {
9 | name: "TextToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref, proxyRefs } from "../../lib/5c24-mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 |
6 | setup() {
7 | const count = ref(0);
8 |
9 | const onClick = () => {
10 | count.value++;
11 | };
12 |
13 | const props = ref({
14 | foo: "foo",
15 | bar: "bar",
16 | });
17 |
18 | const onChangePropsDemo1 = () => {
19 | props.value.foo = "new-foo";
20 | };
21 |
22 | const onChangePropsDemo2 = () => {
23 | props.value.foo = undefined;
24 | };
25 |
26 | const onChangePropsDemo3 = () => {
27 | props.value = {
28 | foo: "foo",
29 | };
30 | };
31 |
32 | return {
33 | count,
34 | onClick,
35 | props,
36 | onChangePropsDemo1,
37 | onChangePropsDemo2,
38 | onChangePropsDemo3,
39 | };
40 | },
41 |
42 | render() {
43 | return h(
44 | "div",
45 | {
46 | id: "root",
47 | ...this.props,
48 | },
49 | [
50 | h("div", {}, "count:" + this.count),
51 | h("button", { onClick: this.onClick }, "click"),
52 | h(
53 | "button",
54 | { onClick: this.onChangePropsDemo1, style: "margin-left: 24px;" },
55 | "changeProps - 值改变了 - 修改"
56 | ),
57 | h(
58 | "button",
59 | { onClick: this.onChangePropsDemo2, style: "margin-left: 24px;" },
60 | "changeProps - 值变成了 undefined - 删除"
61 | ),
62 | h(
63 | "button",
64 | { onClick: this.onChangePropsDemo3, style: "margin-left: 24px;" },
65 | "changeProps - key 在新的里面没有了 - 删除"
66 | ),
67 | ]
68 | );
69 | },
70 | };
71 |
--------------------------------------------------------------------------------
/packages/vue/examples/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/packages/vue/examples/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/5c24-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/packages/vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "5c24-mini-vue",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@5c24-mini-vue/compiler-core": "workspace:^1.0.0",
14 | "@5c24-mini-vue/runtime-dom": "workspace:^1.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vue/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 出口
2 | export * from "@5c24-mini-vue/runtime-dom";
3 | import { baseCompile } from "@5c24-mini-vue/compiler-core";
4 | import * as runtimeDom from "@5c24-mini-vue/runtime-dom";
5 | import { registerRuntimeCompiler } from "@5c24-mini-vue/runtime-dom";
6 |
7 | function compileToFunction(template) {
8 | const { code } = baseCompile(template);
9 | const render = new Function("Vue", code)(runtimeDom);
10 | return render;
11 | }
12 |
13 | registerRuntimeCompiler(compileToFunction);
14 |
--------------------------------------------------------------------------------
/pkg.main:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | function createComponentInstance(vnode) {
6 | const component = {
7 | vnode,
8 | type: vnode.type,
9 | };
10 | return component;
11 | }
12 | function setupComponent(instance) {
13 | setupStatefulComponent(instance);
14 | }
15 | function setupStatefulComponent(instance) {
16 | const component = instance.type;
17 | const { setup } = component;
18 | if (setup) {
19 | const setupResult = setup();
20 | handleSetupResult(instance, setupResult);
21 | }
22 | }
23 | function handleSetupResult(instance, setupResult) {
24 | if (typeof setupResult === "object") {
25 | instance.setupState = setupResult;
26 | }
27 | finishComponentSetup(instance);
28 | }
29 | function finishComponentSetup(instance) {
30 | const Component = instance.type;
31 | instance.render = Component.render;
32 | }
33 |
34 | function render(vnode, container) {
35 | patch(vnode);
36 | }
37 | function patch(vnode, container) {
38 | // TODO 判断 vnode 是不是一个element
39 | // 是 element 就处理 element
40 | processComponent(vnode);
41 | }
42 | function processComponent(vnode, container) {
43 | mountComponent(vnode);
44 | }
45 | function mountComponent(vnode, container) {
46 | const instance = createComponentInstance(vnode);
47 | setupComponent(instance);
48 | setupRenderEffect(instance);
49 | }
50 | function setupRenderEffect(instance, container) {
51 | const subTree = instance.render();
52 | patch(subTree);
53 | }
54 |
55 | function createVNode(type, props, children) {
56 | const vnode = {
57 | type,
58 | props,
59 | children,
60 | };
61 | return vnode;
62 | }
63 |
64 | function createApp(rootComponent) {
65 | return {
66 | mount(rootContainer) {
67 | const vnode = createVNode(rootComponent);
68 | render(vnode);
69 | },
70 | };
71 | }
72 |
73 | function h(type, props, children) {
74 | return createVNode(type, props, children);
75 | }
76 |
77 | exports.createApp = createApp;
78 | exports.h = h;
79 |
--------------------------------------------------------------------------------
/pkg.module:
--------------------------------------------------------------------------------
1 | function createComponentInstance(vnode) {
2 | const component = {
3 | vnode,
4 | type: vnode.type,
5 | };
6 | return component;
7 | }
8 | function setupComponent(instance) {
9 | setupStatefulComponent(instance);
10 | }
11 | function setupStatefulComponent(instance) {
12 | const component = instance.type;
13 | const { setup } = component;
14 | if (setup) {
15 | const setupResult = setup();
16 | handleSetupResult(instance, setupResult);
17 | }
18 | }
19 | function handleSetupResult(instance, setupResult) {
20 | if (typeof setupResult === "object") {
21 | instance.setupState = setupResult;
22 | }
23 | finishComponentSetup(instance);
24 | }
25 | function finishComponentSetup(instance) {
26 | const Component = instance.type;
27 | instance.render = Component.render;
28 | }
29 |
30 | function render(vnode, container) {
31 | patch(vnode);
32 | }
33 | function patch(vnode, container) {
34 | // TODO 判断 vnode 是不是一个element
35 | // 是 element 就处理 element
36 | processComponent(vnode);
37 | }
38 | function processComponent(vnode, container) {
39 | mountComponent(vnode);
40 | }
41 | function mountComponent(vnode, container) {
42 | const instance = createComponentInstance(vnode);
43 | setupComponent(instance);
44 | setupRenderEffect(instance);
45 | }
46 | function setupRenderEffect(instance, container) {
47 | const subTree = instance.render();
48 | patch(subTree);
49 | }
50 |
51 | function createVNode(type, props, children) {
52 | const vnode = {
53 | type,
54 | props,
55 | children,
56 | };
57 | return vnode;
58 | }
59 |
60 | function createApp(rootComponent) {
61 | return {
62 | mount(rootContainer) {
63 | const vnode = createVNode(rootComponent);
64 | render(vnode);
65 | },
66 | };
67 | }
68 |
69 | function h(type, props, children) {
70 | return createVNode(type, props, children);
71 | }
72 |
73 | export { createApp, h };
74 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "packages/*"
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from "@rollup/plugin-typescript";
2 | export default {
3 | input: "./packages/vue/src/index.ts",
4 | output: [
5 | {
6 | format: "cjs",
7 | file: "./packages/vue/dist/5c24-mini-vue.cjs.js",
8 | },
9 | {
10 | format: "es",
11 | file: "./packages/vue/dist/5c24-mini-vue.esm.js",
12 | },
13 | ],
14 |
15 | plugins: [typescript()],
16 | };
17 |
--------------------------------------------------------------------------------
/src/compiler-core/src/ast.ts:
--------------------------------------------------------------------------------
1 | import { CREATE_ELEMENT_VNODE } from "./runtimeHelpers";
2 |
3 | export const enum NodeTypes {
4 | INTERPOLATION,
5 | SIMPLE_EXPRESSION,
6 | ELEMENT,
7 | TEXT,
8 | ROOT,
9 | COMPOUND_EXPRESS,
10 | }
11 |
12 | export function createVNodeCall(context, tag, props, children) {
13 | context.helper(CREATE_ELEMENT_VNODE);
14 |
15 | return {
16 | type: NodeTypes.ELEMENT,
17 | tag,
18 | props,
19 | children,
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/compiler-core/src/codegen.ts:
--------------------------------------------------------------------------------
1 | import { isString } from "../../shared";
2 | import { NodeTypes } from "./ast";
3 | import {
4 | CREATE_ELEMENT_VNODE,
5 | helperMapName,
6 | TO_DISPLAY_STRING,
7 | } from "./runtimeHelpers";
8 |
9 | export function generate(ast) {
10 | const context: any = createCodegenContext();
11 | const { push } = context;
12 |
13 | genFunctionPreamble(ast, context);
14 |
15 | const functionName = "render";
16 | const args = ["_ctx", "_cache"];
17 | const signature = args.join(", ");
18 |
19 | push(`function ${functionName}(${signature}){`);
20 | push(`return `);
21 |
22 | genNode(ast.codegenNode, context);
23 | push("}");
24 |
25 | return {
26 | code: context.code,
27 | };
28 | }
29 |
30 | function genFunctionPreamble(ast, context) {
31 | const { push } = context;
32 | const VueBinging = "Vue";
33 | const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}`;
34 |
35 | if (ast.helpers.length > 0) {
36 | push(
37 | `const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`
38 | );
39 | }
40 | push("\n");
41 | push("return ");
42 | }
43 |
44 | function createCodegenContext() {
45 | const context = {
46 | code: "",
47 | push(source) {
48 | context.code += source;
49 | },
50 | helper(key) {
51 | return `_${helperMapName[key]}`;
52 | },
53 | };
54 |
55 | return context;
56 | }
57 |
58 | function genNode(node, context) {
59 | switch (node.type) {
60 | case NodeTypes.TEXT:
61 | genText(node, context);
62 | break;
63 | case NodeTypes.INTERPOLATION:
64 | genInterpolation(node, context);
65 | break;
66 | case NodeTypes.SIMPLE_EXPRESSION:
67 | genExpression(node, context);
68 | break;
69 | case NodeTypes.ELEMENT:
70 | genElement(node, context);
71 | break;
72 | case NodeTypes.COMPOUND_EXPRESS:
73 | genCompoundExpression(node, context);
74 | break;
75 | default:
76 | break;
77 | }
78 | }
79 |
80 | function genCompoundExpression(node, context) {
81 | const { push } = context;
82 | const { children } = node;
83 |
84 | for (let i = 0; i < children.length; i++) {
85 | const child = children[i];
86 | if (isString(child)) {
87 | push(child);
88 | } else {
89 | genNode(child, context);
90 | }
91 | }
92 | }
93 |
94 | function genElement(node, context) {
95 | const { push, helper } = context;
96 | const { tag, children, props } = node;
97 |
98 | push(`${helper(CREATE_ELEMENT_VNODE)}(`);
99 | genNodeList(genNullable([tag, props, children]), context);
100 | push(")");
101 | }
102 |
103 | function genNodeList(nodes, context) {
104 | const { push } = context;
105 | for (let i = 0; i < nodes.length; i++) {
106 | const node = nodes[i];
107 | if (isString(node)) {
108 | push(node);
109 | } else {
110 | genNode(node, context);
111 | }
112 |
113 | if (i < nodes.length - 1) {
114 | push(", ");
115 | }
116 | }
117 | }
118 |
119 | function genNullable(args) {
120 | return args.map((arg) => arg || "null");
121 | }
122 |
123 | function genText(node, context) {
124 | const { push } = context;
125 | push(`'${node.content}'`);
126 | }
127 |
128 | function genInterpolation(node, context) {
129 | const { push, helper } = context;
130 |
131 | push(`${helper(TO_DISPLAY_STRING)}(`);
132 | genNode(node.content, context);
133 | push(")");
134 | }
135 |
136 | function genExpression(node, context) {
137 | const { push } = context;
138 |
139 | push(`${node.content}`);
140 | }
141 |
--------------------------------------------------------------------------------
/src/compiler-core/src/compile.ts:
--------------------------------------------------------------------------------
1 | import { generate } from "./codegen";
2 | import { baseParse } from "./parse";
3 | import { transform } from "./transform";
4 | import { transformElement } from "./transforms/transformElement";
5 | import { transformExpression } from "./transforms/transformExpression";
6 | import { transformText } from "./transforms/transformText";
7 |
8 | export function baseCompile(template) {
9 | const ast: any = baseParse(template);
10 | transform(ast, {
11 | nodeTransforms: [transformExpression, transformElement, transformText],
12 | });
13 |
14 | return generate(ast);
15 | }
16 |
--------------------------------------------------------------------------------
/src/compiler-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./compile";
2 |
--------------------------------------------------------------------------------
/src/compiler-core/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | const enum TagType {
4 | Start,
5 | End,
6 | }
7 |
8 | export function baseParse(content: string) {
9 | const context = createParserContext(content);
10 |
11 | return createRoot(parseChildren(context, []));
12 | }
13 |
14 | function parseChildren(context, ancestors) {
15 | const nodes: any = [];
16 |
17 | while (!isEnd(context, ancestors)) {
18 | let node;
19 | const s = context.source;
20 |
21 | if (s.startsWith("{{")) {
22 | node = parseInterpolation(context);
23 | } else if (s[0] === "<") {
24 | if (/[a-z]/i.test(s[1])) {
25 | node = parseElement(context, ancestors);
26 | }
27 | }
28 |
29 | if (!node) {
30 | node = parseText(context);
31 | }
32 |
33 | nodes.push(node);
34 | }
35 |
36 | return nodes;
37 | }
38 |
39 | function isEnd(context, ancestors) {
40 | // 遇到结束标签
41 | const s = context.source;
42 |
43 | if (s.startsWith("")) {
44 | for (let i = 0; i < ancestors.length; i++) {
45 | const tag = ancestors[i].tag;
46 | if (startsWithEndTagOpen(s, tag)) {
47 | return true;
48 | }
49 | }
50 | }
51 |
52 | // source 有值
53 | return !s;
54 | }
55 |
56 | function parseText(context) {
57 | let endIndex = context.source.length;
58 | let endTokens = ["<", "{{"];
59 |
60 | for (let i = 0; i < endTokens.length; i++) {
61 | const index = context.source.indexOf(endTokens[i]);
62 | if (index !== -1 && endIndex > index) {
63 | endIndex = index;
64 | }
65 | }
66 |
67 | const content = parseTextData(context, endIndex);
68 |
69 | return {
70 | type: NodeTypes.TEXT,
71 | content,
72 | };
73 | }
74 |
75 | function parseTextData(context, length) {
76 | const content = context.source.slice(0, length);
77 |
78 | advanceBy(context, content.length);
79 |
80 | return content;
81 | }
82 |
83 | function parseElement(context, ancestors) {
84 | const element: any = parseTag(context, TagType.Start);
85 | ancestors.push(element);
86 | element.children = parseChildren(context, ancestors);
87 | ancestors.pop();
88 |
89 | if (startsWithEndTagOpen(context.source, element.tag)) {
90 | parseTag(context, TagType.End);
91 | } else {
92 | throw new Error(`缺少结束标签: ${element.tag}`);
93 | }
94 |
95 | return element;
96 | }
97 |
98 | function startsWithEndTagOpen(source, tag) {
99 | return (
100 | source.startsWith("<") &&
101 | source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
102 | );
103 | }
104 |
105 | function parseTag(context, type: TagType) {
106 | const match: any = /^<\/?([a-z]*)/i.exec(context.source);
107 |
108 | const tag = match[1];
109 |
110 | advanceBy(context, match[0].length);
111 | advanceBy(context, 1);
112 |
113 | if (type === TagType.End) return;
114 |
115 | return {
116 | type: NodeTypes.ELEMENT,
117 | tag,
118 | };
119 | }
120 |
121 | function parseInterpolation(context) {
122 | const openDelimiter = "{{";
123 | const closeDelimiter = "}}";
124 |
125 | const closeIndex = context.source.indexOf(
126 | closeDelimiter,
127 | openDelimiter.length
128 | );
129 |
130 | advanceBy(context, openDelimiter.length);
131 |
132 | const rawContentLength = closeIndex - openDelimiter.length;
133 | const rawContent = parseTextData(context, rawContentLength);
134 | const content = rawContent.trim();
135 |
136 | advanceBy(context, closeDelimiter.length);
137 |
138 | return {
139 | type: NodeTypes.INTERPOLATION,
140 | content: {
141 | type: NodeTypes.SIMPLE_EXPRESSION,
142 | content: content,
143 | },
144 | };
145 | }
146 |
147 | function advanceBy(context, length) {
148 | context.source = context.source.slice(length);
149 | }
150 |
151 | function createRoot(children) {
152 | return {
153 | children,
154 | type: NodeTypes.ROOT
155 | };
156 | }
157 |
158 | function createParserContext(content: string) {
159 | return {
160 | source: content,
161 | };
162 | }
163 |
--------------------------------------------------------------------------------
/src/compiler-core/src/runtimeHelpers.ts:
--------------------------------------------------------------------------------
1 | export const TO_DISPLAY_STRING = Symbol("toDisplayString");
2 | export const CREATE_ELEMENT_VNODE = Symbol("createElementVNode");
3 |
4 | export const helperMapName = {
5 | [TO_DISPLAY_STRING]: "toDisplayString",
6 | [CREATE_ELEMENT_VNODE]: "createElementVNode"
7 | }
--------------------------------------------------------------------------------
/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 = createTransformerContext(root, options);
6 |
7 | traverseNode(root, context);
8 | createRootCodegen(root);
9 |
10 | root.helpers = [...context.helpers.keys()];
11 | }
12 |
13 | function createRootCodegen(root) {
14 | const child = root.children[0];
15 |
16 | if (child.type === NodeTypes.ELEMENT) {
17 | root.codegenNode = child.codegenNode;
18 | } else {
19 | root.codegenNode = root.children[0];
20 | }
21 | }
22 |
23 | function createTransformerContext(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 |
33 | return context;
34 | }
35 |
36 | function traverseNode(node, context) {
37 | const nodeTransforms = context.nodeTransforms;
38 | const exitFns: any = [];
39 |
40 | for (let i = 0; i < nodeTransforms.length; i++) {
41 | const transform = nodeTransforms[i];
42 | const onExit = transform(node, context);
43 | if (onExit) {
44 | exitFns.push(onExit);
45 | }
46 | }
47 |
48 | switch (node.type) {
49 | case NodeTypes.INTERPOLATION:
50 | context.helper(TO_DISPLAY_STRING);
51 | break;
52 | case NodeTypes.ROOT:
53 | case NodeTypes.ELEMENT:
54 | traverseChildren(node, context);
55 | break;
56 | default:
57 | break;
58 | }
59 |
60 | let i = exitFns.length;
61 | while (i--) {
62 | exitFns[i]();
63 | }
64 | }
65 |
66 | function traverseChildren(node: any, context) {
67 | const children = node.children;
68 |
69 | for (let i = 0; i < children.length; i++) {
70 | const node = children[i];
71 | traverseNode(node, context);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/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 | const vnodeTag = `'${node.tag}'`;
7 |
8 | let vnodeProps;
9 |
10 | const children = node.children;
11 | let vnodeChildren = children[0];
12 |
13 | const vnodeElement = createVNodeCall(
14 | context,
15 | vnodeTag,
16 | vnodeProps,
17 | vnodeChildren
18 | );
19 |
20 | node.codegenNode = vnodeElement;
21 | };
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformExpression.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 |
3 | export function transformExpression(node) {
4 | if (node.type === NodeTypes.INTERPOLATION) {
5 | node.content = processExpression(node.content)
6 | }
7 | }
8 |
9 | function processExpression(node) {
10 | node.content = `_ctx.${node.content}`
11 | return node
12 | }
13 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformText.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 | import { isText } from "../utils";
3 |
4 | export function transformText(node) {
5 | if (node.type === NodeTypes.ELEMENT) {
6 | return () => {
7 | const { children } = node;
8 | let currentContainer;
9 |
10 | for (let i = 0; i < children.length; i++) {
11 | const child = children[i];
12 |
13 | if (isText(child)) {
14 | for (let j = i + 1; j < children.length; j++) {
15 | const next = children[j];
16 | if (isText(next)) {
17 | if (!currentContainer) {
18 | currentContainer = children[i] = {
19 | type: NodeTypes.COMPOUND_EXPRESS,
20 | children: [child],
21 | };
22 | }
23 |
24 | currentContainer.children.push(" + ");
25 | currentContainer.children.push(next);
26 | children.splice(j, 1);
27 | j--;
28 | } else {
29 | currentContainer = undefined;
30 | break;
31 | }
32 | }
33 | }
34 | }
35 | };
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/compiler-core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | export function isText(node) {
4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION;
5 | }
6 |
--------------------------------------------------------------------------------
/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, 'h1, ' + _toDisplayString(_ctx.message))}"
6 | `;
7 |
8 | exports[`codegen interpolation 1`] = `
9 | "const { toDisplayString:_toDisplayString } = Vue
10 | return function render(_ctx, _cache){return _toDisplayString(_ctx.message)}"
11 | `;
12 |
13 | exports[`codegen string 1`] = `
14 | "
15 | return function render(_ctx, _cache){return 'hi'}"
16 | `;
17 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/codegen.spec.ts:
--------------------------------------------------------------------------------
1 | import { generate } from "../src/codegen";
2 | import { baseParse } from "../src/parse";
3 | import { transform } from "../src/transform";
4 | import { transformElement } from "../src/transforms/transformElement";
5 | import { transformExpression } from "../src/transforms/transformExpression";
6 | import { transformText } from "../src/transforms/transformText";
7 |
8 | describe("codegen", () => {
9 | it("string", () => {
10 | const ast = baseParse("hi");
11 | transform(ast);
12 | const { code } = generate(ast);
13 |
14 | expect(code).toMatchSnapshot();
15 | });
16 |
17 | it("interpolation", () => {
18 | const ast = baseParse("{{ message }}");
19 | transform(ast, {
20 | nodeTransforms: [transformExpression],
21 | });
22 | const { code } = generate(ast);
23 |
24 | expect(code).toMatchSnapshot();
25 | });
26 |
27 | it("element", () => {
28 | const ast: any = baseParse("h1, {{ message }}
");
29 | transform(ast, {
30 | nodeTransforms: [transformExpression, transformElement, transformText],
31 | });
32 |
33 | const { code } = generate(ast);
34 |
35 | expect(code).toMatchSnapshot();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/parse.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../src/ast";
2 | import { baseParse } from "../src/parse";
3 | describe("Parse", () => {
4 | describe("interpolation", () => {
5 | test("simple interpolation", () => {
6 | const ast = baseParse("{{ message }}");
7 |
8 | // root
9 | expect(ast.children[0]).toStrictEqual({
10 | type: NodeTypes.INTERPOLATION,
11 | content: {
12 | type: NodeTypes.SIMPLE_EXPRESSION,
13 | content: "message",
14 | },
15 | });
16 | });
17 | });
18 |
19 | describe("element", () => {
20 | it("simple element div", () => {
21 | const ast = baseParse("");
22 |
23 | expect(ast.children[0]).toStrictEqual({
24 | type: NodeTypes.ELEMENT,
25 | tag: "div",
26 | children: [],
27 | });
28 | });
29 | });
30 |
31 | describe("text", () => {
32 | it("simple text", () => {
33 | const ast = baseParse("some text");
34 |
35 | expect(ast.children[0]).toStrictEqual({
36 | type: NodeTypes.TEXT,
37 | content: "some text",
38 | });
39 | });
40 | });
41 |
42 | test("hello world", () => {
43 | const ast = baseParse("h1,{{ message }}
");
44 |
45 | expect(ast.children[0]).toStrictEqual({
46 | type: NodeTypes.ELEMENT,
47 | tag: "div",
48 | children: [
49 | {
50 | type: NodeTypes.TEXT,
51 | content: "h1,",
52 | },
53 | {
54 | type: NodeTypes.INTERPOLATION,
55 | content: {
56 | type: NodeTypes.SIMPLE_EXPRESSION,
57 | content: "message",
58 | },
59 | },
60 | ],
61 | });
62 | });
63 |
64 | test("Nested element", () => {
65 | const ast = baseParse("");
66 |
67 | expect(ast.children[0]).toStrictEqual({
68 | type: NodeTypes.ELEMENT,
69 | tag: "div",
70 | children: [
71 | {
72 | type: NodeTypes.ELEMENT,
73 | tag: "p",
74 | children: [
75 | {
76 | type: NodeTypes.TEXT,
77 | content: "h1",
78 | },
79 | ],
80 | },
81 | {
82 | type: NodeTypes.INTERPOLATION,
83 | content: {
84 | type: NodeTypes.SIMPLE_EXPRESSION,
85 | content: "message",
86 | },
87 | },
88 | ],
89 | });
90 | });
91 |
92 | test("should throw error when lack end tag", () => {
93 | expect(() => {
94 | baseParse("
");
95 | }).toThrow(`缺少结束标签: span`);
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/transform.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../src/ast";
2 | import { baseParse } from "../src/parse";
3 | import { transform } from "../src/transform";
4 |
5 | describe("transform", () => {
6 | it("happy path", () => {
7 | const ast = baseParse("h1, {{ message }}
");
8 | const plugin = (node) => {
9 | if (node.type === NodeTypes.TEXT) {
10 | node.content = node.content + "mini-vue";
11 | }
12 | };
13 |
14 | transform(ast, {
15 | nodeTransforms: [plugin],
16 | });
17 |
18 | const nodeText = ast.children[0].children[0];
19 |
20 | expect(nodeText.content).toBe("h1, mini-vue");
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 出口
2 | export * from "./runtime-dom";
3 | import { baseCompile } from "./compiler-core/src";
4 | import * as runtimeDom from "./runtime-dom";
5 | import { registerRuntimeCompiler } from "./runtime-dom";
6 |
7 | function compileToFunction(template) {
8 | const { code } = baseCompile(template);
9 | const render = new Function("Vue", code)(runtimeDom);
10 | return render;
11 | }
12 |
13 | registerRuntimeCompiler(compileToFunction);
14 |
--------------------------------------------------------------------------------
/src/reactivity/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { extend, isObject } from "../shared/index";
2 | import { track, trigger } from "./effect";
3 | import { reactive, ReactiveFlags, readonly } from "./reactive";
4 |
5 | const get = createGetter();
6 | const set = createSetter();
7 | const readonlyGet = createGetter(true);
8 | const shallowReadonlyGet = createGetter(true, true);
9 |
10 | function createGetter(isReadonly = false, isShallow = false) {
11 | return function get(target, key) {
12 | if (key === ReactiveFlags.IS_REACTIVE) {
13 | return !isReadonly;
14 | } else if (key === ReactiveFlags.IS_READONLY) {
15 | return isReadonly;
16 | }
17 |
18 | const res = Reflect.get(target, key);
19 |
20 | if (isShallow) {
21 | return res;
22 | }
23 |
24 | // 看看 res 是不是 object
25 | if (isObject(res)) {
26 | return isReadonly ? readonly(res) : reactive(res);
27 | }
28 |
29 | // 依赖收集
30 | if (!isReadonly) {
31 | track(target, key);
32 | }
33 | return res;
34 | };
35 | }
36 |
37 | function createSetter() {
38 | return function set(target, key, value) {
39 | const res = Reflect.set(target, key, value);
40 |
41 | // 触发依赖
42 | trigger(target, key);
43 | return res;
44 | };
45 | }
46 |
47 | export const mutableHandlers = {
48 | get,
49 | set,
50 | };
51 |
52 | export const readonlyHandlers = {
53 | get: readonlyGet,
54 | set(target, key, value) {
55 | console.warn(`key:${key} set 失败 因为 target 是 readonly`);
56 |
57 | return true;
58 | },
59 | };
60 |
61 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
62 | get: shallowReadonlyGet,
63 | });
64 |
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 | class ComputedRefImpl {
4 | private _dirty: boolean = true;
5 | private _value: any;
6 | private _effect: any;
7 |
8 | constructor(getter) {
9 | this._effect = new ReactiveEffect(getter, () => {
10 | if (!this._dirty) {
11 | this._dirty = true;
12 | }
13 | });
14 | }
15 |
16 | get value() {
17 | if (this._dirty) {
18 | this._dirty = false;
19 | this._value = this._effect.run();
20 | }
21 | return this._value;
22 | }
23 | }
24 |
25 | export function computed(getter) {
26 | return new ComputedRefImpl(getter);
27 | }
28 |
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "../shared/index";
2 |
3 | let activeEffect;
4 | let shouldTrack;
5 | export class ReactiveEffect {
6 | private _fn: any;
7 | public scheduler: Function | undefined;
8 | deps = [];
9 | active = true;
10 | onStop?: () => void;
11 | constructor(fn, scheduler?: Function) {
12 | this._fn = fn;
13 | this.scheduler = scheduler;
14 | }
15 |
16 | run() {
17 | if (!this.active) {
18 | return this._fn();
19 | }
20 |
21 | shouldTrack = true;
22 | activeEffect = this;
23 |
24 | const result = this._fn();
25 | shouldTrack = false;
26 |
27 | return result;
28 | }
29 |
30 | stop() {
31 | if (this.active) {
32 | cleanupEffect(this);
33 | if (this.onStop) {
34 | this.onStop();
35 | }
36 | this.active = false;
37 | }
38 | }
39 | }
40 |
41 | function cleanupEffect(effect) {
42 | effect.deps.forEach((dep: any) => {
43 | dep.delete(effect);
44 | });
45 | effect.deps.length = 0;
46 | }
47 |
48 | let targetMap = new WeakMap();
49 | export function track(target, key) {
50 | if (!isTracking()) return;
51 |
52 | let depsMap = targetMap.get(target);
53 | if (!depsMap) {
54 | depsMap = new Map();
55 | targetMap.set(target, depsMap);
56 | }
57 |
58 | let dep = depsMap.get(key);
59 | if (!dep) {
60 | dep = new Set();
61 | depsMap.set(key, dep);
62 | }
63 |
64 | trackEffects(dep);
65 | }
66 |
67 | export function trackEffects(dep) {
68 | // 依赖已经存在 dep 中就不再添加
69 | if (dep.has(activeEffect)) return;
70 |
71 | dep.add(activeEffect);
72 | activeEffect.deps.push(dep);
73 | }
74 |
75 | export function isTracking() {
76 | return shouldTrack && activeEffect !== undefined;
77 | }
78 |
79 | export function trigger(target, key) {
80 | let depsMap = targetMap.get(target);
81 | let dep = depsMap.get(key);
82 |
83 | triggerEffects(dep);
84 | }
85 |
86 | export function triggerEffects(dep) {
87 | for (let effect of dep) {
88 | if (effect.scheduler) {
89 | effect.scheduler();
90 | } else {
91 | effect.run();
92 | }
93 | }
94 | }
95 |
96 | export function effect(fn, options: any = {}) {
97 | const _effect = new ReactiveEffect(fn, options.scheduler);
98 | extend(_effect, options);
99 | _effect.run();
100 |
101 | const runner: any = _effect.run.bind(_effect);
102 | runner.effect = _effect;
103 |
104 | return runner;
105 | }
106 |
107 | export function stop(runner) {
108 | runner.effect.stop();
109 | }
110 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export { ref, proxyRefs } from "./ref";
2 |
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | mutableHandlers,
3 | readonlyHandlers,
4 | shallowReadonlyHandlers,
5 | } from "./baseHandlers";
6 |
7 | export const enum ReactiveFlags {
8 | IS_REACTIVE = "__v_isReactive",
9 | IS_READONLY = "__v_isReadonly",
10 | }
11 |
12 | export function reactive(raw: any) {
13 | return createActiveObject(raw, mutableHandlers);
14 | }
15 |
16 | export function readonly(raw: any) {
17 | return createActiveObject(raw, readonlyHandlers);
18 | }
19 |
20 | export function shallowReadonly(raw: any) {
21 | return createActiveObject(raw, shallowReadonlyHandlers);
22 | }
23 |
24 | export function isReactive(value) {
25 | return !!value[ReactiveFlags.IS_REACTIVE];
26 | }
27 |
28 | export function isReadonly(value) {
29 | return !!value[ReactiveFlags.IS_READONLY];
30 | }
31 |
32 | export function isProxy(value) {
33 | return isReactive(value) || isReadonly(value);
34 | }
35 |
36 | function createActiveObject(raw: any, baseHandle) {
37 | return new Proxy(raw, baseHandle);
38 | }
39 |
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from "../shared/index";
2 | import { isTracking, trackEffects, triggerEffects } from "./effect";
3 | import { reactive } from "./reactive";
4 |
5 | class RefImpl {
6 | private _value: any;
7 | private _rawValue: any; // 用于 set 时候和新值做对比
8 | public dep; // 存放依赖
9 | public __v_isRef = true;
10 |
11 | constructor(value) {
12 | this._rawValue = value;
13 | this._value = convert(value);
14 | this.dep = new Set();
15 | }
16 |
17 | get value() {
18 | // activeEffect 存在,才执行依赖收集
19 | trackRefValue(this);
20 | return this._value;
21 | }
22 |
23 | set value(newValue) {
24 | if (hasChanged(this._rawValue, newValue)) {
25 | this._rawValue = newValue;
26 | this._value = convert(newValue);
27 | triggerEffects(this.dep);
28 | }
29 | }
30 | }
31 |
32 | function convert(value) {
33 | return isObject(value) ? reactive(value) : value;
34 | }
35 |
36 | function trackRefValue(ref) {
37 | if (isTracking()) {
38 | trackEffects(ref.dep);
39 | }
40 | }
41 |
42 | export function ref(value) {
43 | return new RefImpl(value);
44 | }
45 |
46 | export function isRef(ref) {
47 | return !!ref.__v_isRef;
48 | }
49 |
50 | export function unRef(ref) {
51 | return isRef(ref) ? ref.value : ref;
52 | }
53 |
54 | // get 如果访问的是 ref 类型 返回 .value,如果不是 ref ,返回本身的值
55 | // set 如果新给值不是一个 ref 类型,把当前对象需要赋值的属性为 ref 的 .value 改掉
56 | // 如果新值是一个 ref,直接替换
57 | export function proxyRefs(objectWithRefs) {
58 | return new Proxy(objectWithRefs, {
59 | get(target, key) {
60 | return unRef(Reflect.get(target, key));
61 | },
62 |
63 | set(target, key, value) {
64 | if (isRef(target[key]) && !isRef(value)) {
65 | return (target[key].value = value);
66 | } else {
67 | return Reflect.set(target, key, value);
68 | }
69 | },
70 | });
71 | }
72 |
--------------------------------------------------------------------------------
/src/reactivity/tests/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { computed } from "../computed";
2 | import { reactive } from "../reactive";
3 |
4 | describe("computed", () => {
5 | it("happy path", () => {
6 | const user = reactive({
7 | age: 10,
8 | });
9 |
10 | const age = computed(() => {
11 | return user.age;
12 | });
13 |
14 | expect(age.value).toBe(10);
15 | });
16 |
17 | it("should compute lazily", () => {
18 | const value = reactive({ foo: 1 });
19 | const getter = jest.fn(() => {
20 | return value.foo;
21 | });
22 | const cValue = computed(getter);
23 |
24 | // 懒执行 不调用 cValue 不会执行getter
25 | expect(getter).not.toHaveBeenCalled();
26 |
27 | expect(cValue.value).toBe(1);
28 | expect(getter).toHaveBeenCalledTimes(1);
29 |
30 | // 缓存机制 再次调用cValue.value getter不再执行
31 | cValue.value;
32 | expect(getter).toHaveBeenCalledTimes(1);
33 |
34 | value.foo = 2;
35 | expect(getter).toHaveBeenCalledTimes(1);
36 |
37 | // 当依赖的响应式对象的值发生改变 再次调用cValue.value getter执行
38 | expect(cValue.value).toBe(2);
39 | expect(getter).toHaveBeenCalledTimes(2);
40 |
41 | cValue.value;
42 | expect(getter).toHaveBeenCalledTimes(2);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "../reactive";
2 | import { effect, stop } from "../effect";
3 |
4 | describe("effect", () => {
5 | it("happy path", () => {
6 | let user = reactive({ age: 10, name: "5c24" });
7 | let nextAge;
8 | let nextInfo;
9 |
10 | effect(() => {
11 | nextAge = user.age + 1;
12 | });
13 |
14 | effect(() => {
15 | nextInfo = `name: ${user.name}, age: ${user.age}`;
16 | });
17 |
18 | expect(nextAge).toBe(11);
19 | expect(nextInfo).toBe(`name: 5c24, age: 10`);
20 |
21 | user.age++;
22 | expect(nextAge).toBe(12);
23 | expect(nextInfo).toBe(`name: 5c24, age: 11`);
24 |
25 | user.name = "You";
26 | expect(nextInfo).toBe(`name: You, age: 11`);
27 | });
28 |
29 | it("should runner when call effect", () => {
30 | let foo = 10;
31 | let runner = effect(() => {
32 | foo++;
33 | return "foo";
34 | });
35 |
36 | expect(foo).toBe(11);
37 | let r = runner();
38 | expect(foo).toBe(12);
39 | expect(r).toBe("foo");
40 | });
41 |
42 | it("scheduler", () => {
43 | let dummy;
44 | let run: any;
45 | const scheduler = jest.fn(() => {
46 | run = runner;
47 | });
48 | const obj = reactive({ foo: 1 });
49 | const runner = effect(
50 | () => {
51 | dummy = obj.foo;
52 | },
53 | { scheduler }
54 | );
55 | // 初始化不执行 scheduler
56 | expect(scheduler).not.toHaveBeenCalled();
57 | // 初始化执行 fn
58 | expect(dummy).toBe(1);
59 | obj.foo++;
60 | // 触发 set 执行 scheduler
61 | expect(scheduler).toHaveBeenCalledTimes(1);
62 | // 触发 set 不执行 fn
63 | expect(dummy).toBe(1);
64 | // 执行 runner ,再次执行 fn
65 | run();
66 | expect(dummy).toBe(2);
67 | });
68 |
69 | it("stop", () => {
70 | let dummy;
71 | const obj = reactive({ prop: 1 });
72 | const runner = effect(() => {
73 | dummy = obj.prop;
74 | });
75 |
76 | obj.prop = 2;
77 | expect(dummy).toBe(2);
78 | stop(runner);
79 | // obj.prop = 3;
80 | obj.prop++;
81 | expect(dummy).toBe(2);
82 |
83 | runner();
84 | expect(dummy).toBe(3);
85 | });
86 |
87 | it("onStop", () => {
88 | const obj = reactive(() => {
89 | foo: 1;
90 | });
91 | const onStop = jest.fn();
92 | let dummy;
93 | const runner = effect(
94 | () => {
95 | dummy = obj.foo;
96 | },
97 | { onStop }
98 | );
99 |
100 | stop(runner);
101 | expect(onStop).toBeCalledTimes(1);
102 | });
103 | });
104 |
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive, isReactive, isProxy } from "../reactive";
2 |
3 | describe("reactive", () => {
4 | it("happy path", () => {
5 | const original = { foo: 1 };
6 | const observed = reactive(original);
7 |
8 | expect(observed).not.toBe(original);
9 | expect(observed.foo).toBe(1);
10 | expect(isReactive(observed)).toBe(true);
11 | expect(isReactive(original)).toBe(false);
12 | expect(isProxy(observed)).toBe(true);
13 | });
14 |
15 | it("nested reactive", () => {
16 | const original = {
17 | nested: {
18 | foo: 1,
19 | },
20 | array: [{ bar: 2 }],
21 | };
22 | const observed = reactive(original);
23 | expect(isReactive(observed.nested)).toBe(true);
24 | expect(isReactive(observed.array)).toBe(true);
25 | expect(isReactive(observed.array[0])).toBe(true);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { readonly, isReadonly, isProxy } from "../reactive";
2 |
3 | describe("readonly", () => {
4 | it("happy path", () => {
5 | const original = { foo: 1, bar: { baz: 2 } };
6 | const wrapped = readonly(original);
7 |
8 | expect(wrapped).not.toBe(original);
9 | expect(wrapped.foo).toBe(1);
10 | expect(isReadonly(wrapped)).toBe(true);
11 | expect(isReadonly(original)).toBe(false);
12 | expect(isReadonly(wrapped.bar)).toBe(true);
13 | expect(isReadonly(original.bar)).toBe(false);
14 | expect(isProxy(wrapped)).toBe(true);
15 | });
16 |
17 | it("warn then call set", () => {
18 | console.warn = jest.fn();
19 |
20 | const user = readonly({ age: 10 });
21 |
22 | user.age = 11;
23 | expect(console.warn).toBeCalled();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect";
2 | import { reactive } from "../reactive";
3 | import { isRef, proxyRefs, ref, unRef } from "../ref";
4 |
5 | describe("ref", () => {
6 | it("happy path", () => {
7 | const a = ref(1);
8 | expect(a.value).toBe(1);
9 | });
10 |
11 | it("should be reactive", () => {
12 | const a = ref(1);
13 | let dummy;
14 | let calls = 0;
15 | effect(() => {
16 | calls++;
17 | dummy = a.value;
18 | });
19 | expect(calls).toBe(1);
20 | expect(dummy).toBe(1);
21 | a.value = 2;
22 | expect(calls).toBe(2);
23 | expect(dummy).toBe(2);
24 | // same value should not trigger
25 | a.value = 2;
26 | expect(calls).toBe(2);
27 | expect(dummy).toBe(2);
28 | });
29 |
30 | it("should make nested properties reactive", () => {
31 | const a = ref({
32 | count: 1,
33 | });
34 | let dummy;
35 | effect(() => {
36 | dummy = a.value.count;
37 | });
38 | expect(dummy).toBe(1);
39 | a.value.count++;
40 | expect(dummy).toBe(2);
41 | });
42 |
43 | it("isRef", () => {
44 | const a = ref(1);
45 | const b = reactive({ foo: 1 });
46 | expect(isRef(a)).toBe(true);
47 | expect(isRef(1)).toBe(false);
48 | expect(isRef(b)).toBe(false);
49 | });
50 |
51 | it("unRef", () => {
52 | const a = ref(1);
53 | expect(unRef(a)).toBe(1);
54 | expect(unRef(1)).toBe(1);
55 | });
56 |
57 | it("proxyRefs", () => {
58 | const user = {
59 | age: ref(10),
60 | name: "5c24",
61 | };
62 |
63 | const proxyUser = proxyRefs(user);
64 | expect(user.age.value).toBe(10);
65 | expect(proxyUser.age).toBe(10);
66 | expect(proxyUser.name).toBe("5c24");
67 |
68 | proxyUser.age = 20;
69 | expect(proxyUser.age).toBe(20);
70 | expect(user.age.value).toBe(20);
71 |
72 | proxyUser.age = ref(10);
73 | expect(proxyUser.age).toBe(10);
74 | expect(user.age.value).toBe(10);
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, shallowReadonly } from "../reactive";
2 |
3 | describe("shallowReadonly", () => {
4 | it("should not make non-reactive properties reactive", () => {
5 | const props = shallowReadonly({ n: { foo: 1 } });
6 | expect(isReadonly(props)).toBe(true);
7 | expect(isReadonly(props.n)).toBe(false);
8 | });
9 |
10 | it("warn then call set", () => {
11 | console.warn = jest.fn();
12 |
13 | const user = shallowReadonly({ age: 10 });
14 |
15 | user.age = 11;
16 | expect(console.warn).toBeCalled();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 |
3 | export function provide(key, value) {
4 | const currentInstance: any = getCurrentInstance();
5 | if (currentInstance) {
6 | let { provides } = currentInstance;
7 | const parentProvides = currentInstance.parent.provides;
8 |
9 | if (provides === parentProvides) {
10 | // 创建一个新的provides provides.__proto__ === parentProvides
11 | provides = currentInstance.provides = Object.create(parentProvides);
12 | }
13 |
14 | provides[key] = value;
15 | }
16 | }
17 |
18 | export function inject(key, defaultVal) {
19 | const currentInstance: any = getCurrentInstance();
20 | if (currentInstance) {
21 | const parentProvides = currentInstance.parent.provides;
22 | if (key in parentProvides) {
23 | return parentProvides[key];
24 | } else if (defaultVal) {
25 | if (typeof defaultVal === "function") {
26 | return defaultVal();
27 | }
28 | return defaultVal;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRefs } from "../reactivity";
2 | import { shallowReadonly } from "../reactivity/reactive";
3 | import { emit } from "./componentEmit";
4 | import { initProps } from "./componentProps";
5 | import { PublicInstanceProxyhandlers } from "./componentPublicInstance";
6 | import { initSlots } from "./componentSlots";
7 |
8 | export function createComponentInstance(vnode, parent) {
9 | const component = {
10 | vnode,
11 | type: vnode.type,
12 | setupState: {},
13 | props: {},
14 | slots: {},
15 | next: null,
16 | provides: parent ? parent.provides : {},
17 | parent,
18 | isMounted: false,
19 | subTree: {},
20 | emit: () => {},
21 | };
22 | console.log(parent);
23 | component.emit = emit.bind(null, component) as any;
24 | return component;
25 | }
26 |
27 | export function setupComponent(instance) {
28 | initProps(instance, instance.vnode.props);
29 | initSlots(instance, instance.vnode.children);
30 | setupStatefulComponent(instance);
31 | }
32 |
33 | function setupStatefulComponent(instance) {
34 | const component = instance.type;
35 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyhandlers);
36 | const { setup } = component;
37 |
38 | if (setup) {
39 | setCurrentInstance(instance);
40 | const setupResult = setup(shallowReadonly(instance.props), {
41 | emit: instance.emit,
42 | });
43 | setCurrentInstance(null);
44 | handleSetupResult(instance, setupResult);
45 | }
46 | }
47 |
48 | function handleSetupResult(instance, setupResult) {
49 | if (typeof setupResult === "object") {
50 | instance.setupState = proxyRefs(setupResult);
51 | }
52 |
53 | finishComponentSetup(instance);
54 | }
55 |
56 | function finishComponentSetup(instance) {
57 | const Component = instance.type;
58 |
59 | if (compiler && !Component.render) {
60 | if (Component.template) {
61 | Component.render = compiler(Component.template);
62 | }
63 | }
64 | instance.render = Component.render;
65 | }
66 |
67 | let currentInstance = null;
68 |
69 | export function getCurrentInstance() {
70 | return currentInstance;
71 | }
72 |
73 | function setCurrentInstance(instance) {
74 | currentInstance = instance;
75 | }
76 |
77 | let compiler;
78 |
79 | export function registerRuntimeCompiler(_compiler) {
80 | compiler = _compiler;
81 | }
82 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { toHandlerKey } from "../shared/index";
2 |
3 | export function emit(instance, event, ...args) {
4 | const { props } = instance;
5 |
6 | const handlerName = toHandlerKey(event);
7 | const handler = props[handlerName];
8 | handler && handler(...args);
9 | }
10 |
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance, props) {
2 | // App 根组件的 props 是 undefined
3 | instance.props = props || {};
4 | }
5 |
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from "../shared/index";
2 |
3 | const publicPropertiesMap = {
4 | $el: (i) => i.vnode.el,
5 | $slots: (i) => i.slots,
6 | $props: (i) => i.props,
7 | };
8 |
9 | export const PublicInstanceProxyhandlers = {
10 | get({ _: instance }, key) {
11 | // setupState
12 | const { setupState, props } = instance;
13 |
14 | if (hasOwn(setupState, key)) {
15 | return setupState[key];
16 | } else if (hasOwn(props, key)) {
17 | return props[key];
18 | }
19 |
20 | const publicGetter = publicPropertiesMap[key];
21 | if (publicGetter) {
22 | return publicGetter(instance);
23 | }
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | export function initSlots(instance, children) {
4 | const { vnode } = instance;
5 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) {
6 | normalizeObjectSlots(instance.slots, children);
7 | }
8 | }
9 |
10 | function normalizeObjectSlots(slots, children) {
11 | for (let key in children) {
12 | let value = children[key];
13 | slots[key] = (props) => normalizeSlotValue(value(props));
14 | }
15 | }
16 |
17 | function normalizeSlotValue(value) {
18 | return Array.isArray(value) ? value : [value];
19 | }
20 |
--------------------------------------------------------------------------------
/src/runtime-core/componentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | export function shouldUpdateComponent(prevVNode, nextVNode) {
2 | const { props: prevProps } = prevVNode;
3 | const { props: nextProps } = nextVNode;
4 |
5 | for (const key in nextProps) {
6 | if (nextProps[key] !== prevProps[key]) {
7 | return true;
8 | }
9 | }
10 |
11 | return false;
12 | }
13 |
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function createAppAPI(render) {
4 | return function createApp(rootComponent) {
5 | return {
6 | mount(rootContainer) {
7 | const vnode = createVNode(rootComponent);
8 |
9 | render(vnode, rootContainer);
10 | },
11 | };
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function h(type, props?, children?) {
4 | return createVNode(type, props, children);
5 | }
6 |
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from "../vnode";
2 |
3 | export function renderSlots(slots, name, props) {
4 | const slot = slots[name];
5 | if (slot) {
6 | if (typeof slot === "function") {
7 | return createVNode(Fragment, {}, slot(props));
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | export { h } from "./h";
2 | export { renderSlots } from "./helpers/renderSlots";
3 | export { createTextVNode, createElementVNode } from "./vnode";
4 | export { getCurrentInstance, registerRuntimeCompiler } from "./component";
5 | export { provide, inject } from "./apiInject";
6 | export { createRenderer } from "./renderer";
7 | export { nextTick } from "./scheduler";
8 | export { toDisplayString } from "../shared";
9 | export * from "../reactivity";
10 |
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../reactivity/effect";
2 | import { EMPTY_PROPS } from "../shared";
3 | import { ShapeFlags } from "../shared/ShapeFlags";
4 | import { createComponentInstance, setupComponent } from "./component";
5 | import { shouldUpdateComponent } from "./componentUpdateUtils";
6 | import { createAppAPI } from "./createApp";
7 | import { queueJobs } from "./scheduler";
8 | import { Fragment, Text } from "./vnode";
9 |
10 | export function createRenderer(options) {
11 | const {
12 | createElement: hostCreateElement,
13 | patchProp: hostPatchProp,
14 | insert: hostInsert,
15 | remove: hostRemove,
16 | setElementText: hostSetElementText,
17 | setElementArray: hostSetElementArray,
18 | } = options;
19 |
20 | function render(vnode, container) {
21 | patch(null, vnode, container, null, null);
22 | }
23 |
24 | function patch(n1, n2, container: any, parentComponent, anchor) {
25 | const { shapeFlag, type } = n2;
26 | switch (type) {
27 | case Fragment:
28 | // 只渲染 children
29 | processFlagment(n1, n2, container, parentComponent, anchor);
30 | break;
31 | case Text:
32 | processText(n1, n2, container);
33 | break;
34 | default:
35 | if (shapeFlag & ShapeFlags.ELEMENT) {
36 | processElement(n1, n2, container, parentComponent, anchor);
37 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
38 | processComponent(n1, n2, container, parentComponent, anchor);
39 | }
40 | break;
41 | }
42 | }
43 |
44 | function processText(n1, n2, container: any) {
45 | const { children } = n2;
46 | const textNode = (n2.el = document.createTextNode(children));
47 | container.append(textNode);
48 | }
49 |
50 | function processFlagment(n1, n2, container: any, parentComponent, anchor) {
51 | mountChildren(n2.children, container, parentComponent, anchor);
52 | }
53 |
54 | function processElement(n1, n2, container: any, parentComponent, anchor) {
55 | if (!n1) {
56 | mountElement(n2, container, parentComponent, anchor);
57 | } else {
58 | patchElement(n1, n2, container, parentComponent, anchor);
59 | }
60 | }
61 |
62 | function patchElement(n1, n2, container, parentComponent, anchor) {
63 | console.log("patchElement");
64 | console.log("n1", n1);
65 | console.log("n2", n2);
66 |
67 | const oldProps = n1.props || EMPTY_PROPS;
68 | const newProps = n2.props || EMPTY_PROPS;
69 | const el = (n2.el = n1.el);
70 | patchChildren(n1, n2, el, parentComponent, anchor);
71 | patchProps(el, oldProps, newProps);
72 | }
73 |
74 | function patchChildren(n1, n2, container, parentComponent, parentAnchor) {
75 | const preShapeFlag = n1.shapeFlag;
76 | const { shapeFlag } = n2;
77 | const c1 = n1.children;
78 | const c2 = n2.children;
79 |
80 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
81 | if (preShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
82 | unmountChildren(n1.children);
83 | }
84 | if (c1 !== c2) {
85 | hostSetElementText(container, c2);
86 | }
87 | } else {
88 | if (preShapeFlag & ShapeFlags.TEXT_CHILDREN) {
89 | hostSetElementText(container, "");
90 |
91 | mountChildren(n2.children, container, parentComponent, parentAnchor);
92 | } else {
93 | // array diff array
94 | patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor);
95 | }
96 | }
97 | }
98 |
99 | function patchKeyedChildren(
100 | c1,
101 | c2,
102 | container,
103 | parentComponent,
104 | parentAnchor
105 | ) {
106 | let i = 0;
107 | const l2 = c2.length;
108 | let e1 = c1.length - 1;
109 | let e2 = l2 - 1;
110 |
111 | function isSomeVNodeType(n1, n2) {
112 | // 基于 type 和 key 确定两个节点是否相同
113 | return n1.type === n2.type && n1.key === n2.key;
114 | }
115 |
116 | // 左侧对比
117 | while (i <= e1 && i <= e2) {
118 | const n1 = c1[i];
119 | const n2 = c2[i];
120 | if (isSomeVNodeType(n1, n2)) {
121 | patch(n1, n2, container, parentComponent, parentAnchor);
122 | } else {
123 | break;
124 | }
125 | i++;
126 | }
127 |
128 | // 右侧对比
129 | while (i <= e1 && i <= e2) {
130 | const n1 = c1[e1];
131 | const n2 = c2[e2];
132 | if (isSomeVNodeType(n1, n2)) {
133 | patch(n1, n2, container, parentComponent, parentAnchor);
134 | } else {
135 | break;
136 | }
137 | e1--;
138 | e2--;
139 | }
140 |
141 | if (i > e1) {
142 | // 新的比老的多 新增
143 | if (i <= e2) {
144 | const nextPos = e2 + 1;
145 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
146 | while (i <= e2) {
147 | patch(null, c2[i], container, parentComponent, anchor);
148 | i++;
149 | }
150 | }
151 | } else if (i > e2) {
152 | // 老的比新的多 删除老的
153 | while (i <= e1) {
154 | hostRemove(c1[i].el);
155 | i++;
156 | }
157 | } else {
158 | // 中间对比
159 | let s1 = i; // 老节点的开始
160 | let s2 = i; // 新节点的开始
161 | const toBePatched = e2 - s2 + 1; // 需要 patch 的新节点个数
162 | let patched = 0; // 已经 patch 的新节点数
163 |
164 | const keyToNewIndexMap = new Map();
165 | // 存储旧节点混乱元素的索引,创建指定长度的数组 性能更好
166 | const newIndexToOldIndexMap = new Array(toBePatched);
167 | let moved = false;
168 | let maxNewIndexSoFar = 0;
169 | // 初始化每一项索引,0表示未建立映射关系
170 | for (let i = 0; i < toBePatched; i++) {
171 | newIndexToOldIndexMap[i] = 0;
172 | }
173 | // 遍历新节点,设置 key index 映射关系
174 | for (let i = s2; i <= e2; i++) {
175 | const nextChild = c2[i];
176 | keyToNewIndexMap.set(nextChild.key, i);
177 | }
178 |
179 | for (let i = s1; i <= e1; i++) {
180 | const prevChild = c1[i];
181 |
182 | if (patched >= toBePatched) {
183 | hostRemove(prevChild.el);
184 | continue;
185 | }
186 |
187 | let newIndex;
188 | if (prevChild.key != null) {
189 | // 老节点给了 key 就用 map 映射
190 | newIndex = keyToNewIndexMap.get(prevChild.key);
191 | } else {
192 | // 没有 key 进行遍历
193 | for (let j = s2; j <= e2; j++) {
194 | if (isSomeVNodeType(prevChild, c2[j])) {
195 | newIndex = j;
196 |
197 | break;
198 | }
199 | }
200 | }
201 |
202 | if (newIndex === undefined) {
203 | // 新的里面没有当前老的节点
204 | hostRemove(prevChild.el);
205 | } else {
206 | // 旧节点在新节点中存在
207 |
208 | if (newIndex >= maxNewIndexSoFar) {
209 | maxNewIndexSoFar = newIndex;
210 | } else {
211 | moved = true;
212 | }
213 |
214 | // newIndex表示当前老节点在新节点中的下标, 减去 s2 是为了将索引归于0
215 | // 这里 i + 1 是初始化的时候0表示未建立映射关系,考虑到 i 为0的情况下,所以 +1
216 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
217 |
218 | patch(prevChild, c2[newIndex], container, parentComponent, null);
219 | patched++;
220 | }
221 | }
222 | // 获取最长递增子序列
223 | const increasingNewIndexSequence = moved
224 | ? getSequence(newIndexToOldIndexMap)
225 | : [];
226 | // j 指向获取出来的最长递增子序列的索引
227 | // i 指向新节点
228 | let j = increasingNewIndexSequence.length - 1;
229 |
230 | for (let i = toBePatched - 1; i >= 0; i--) {
231 | const nextIndex = i + s2;
232 | const nextChild = c2[nextIndex];
233 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
234 | if (newIndexToOldIndexMap[i] === 0) {
235 | patch(null, nextChild, container, parentComponent, anchor);
236 | } else if (moved) {
237 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
238 | hostInsert(nextChild.el, container, anchor);
239 | } else {
240 | j--;
241 | }
242 | }
243 | }
244 | }
245 | }
246 |
247 | function unmountChildren(children) {
248 | for (let i = 0; i < children.length; i++) {
249 | const el = children[i].el;
250 | hostRemove(el);
251 | }
252 | }
253 |
254 | function patchProps(el, oldProps, newProps) {
255 | for (let key in newProps) {
256 | const prevProps = oldProps[key];
257 | const nextProps = newProps[key];
258 |
259 | if (prevProps !== nextProps) {
260 | hostPatchProp(el, key, prevProps, nextProps);
261 | }
262 | }
263 |
264 | if (oldProps !== EMPTY_PROPS) {
265 | for (let key in oldProps) {
266 | if (!(key in newProps)) {
267 | hostPatchProp(el, key, oldProps[key], null);
268 | }
269 | }
270 | }
271 | }
272 |
273 | function mountElement(vnode: any, container: any, parentComponent, anchor) {
274 | const el = (vnode.el = hostCreateElement(vnode.type));
275 | const { children, shapeFlag } = vnode;
276 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
277 | el.textContent = children;
278 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
279 | mountChildren(vnode.children, el, parentComponent, anchor);
280 | }
281 |
282 | const { props } = vnode;
283 | for (const key in props) {
284 | const val = props[key];
285 |
286 | hostPatchProp(el, key, null, val);
287 | }
288 |
289 | hostInsert(el, container, anchor);
290 | }
291 |
292 | function mountChildren(
293 | children: any,
294 | container: any,
295 | parentComponent,
296 | anchor
297 | ) {
298 | children.forEach((v) => {
299 | patch(null, v, container, parentComponent, anchor);
300 | });
301 | }
302 |
303 | function processComponent(n1, n2, container: any, parentComponent, anchor) {
304 | if (!n1) {
305 | mountComponent(n2, container, parentComponent, anchor);
306 | } else {
307 | updateComponent(n1, n2);
308 | }
309 | }
310 |
311 | function updateComponent(n1, n2) {
312 | const instance = (n2.component = n1.component);
313 | if (shouldUpdateComponent(n1, n2)) {
314 | instance.next = n2;
315 | instance.update();
316 | } else {
317 | n2.el = n1.el;
318 | n2.vnode = n2;
319 | }
320 | }
321 |
322 | function mountComponent(
323 | initialVNode: any,
324 | container: any,
325 | parentComponent,
326 | anchor
327 | ) {
328 | const instance = (initialVNode.component = createComponentInstance(
329 | initialVNode,
330 | parentComponent
331 | ));
332 |
333 | setupComponent(instance);
334 | setupRenderEffect(instance, initialVNode, container, anchor);
335 | }
336 |
337 | function setupRenderEffect(instance, initialVNode, container, anchor) {
338 | instance.update = effect(
339 | () => {
340 | if (!instance.isMounted) {
341 | const { proxy } = instance;
342 | const subTree = (instance.subTree = instance.render.call(
343 | proxy,
344 | proxy
345 | ));
346 | patch(null, subTree, container, instance, anchor);
347 | initialVNode.el = subTree.el;
348 |
349 | instance.isMounted = true;
350 | } else {
351 | const { next, vnode } = instance;
352 | if (next) {
353 | next.el = vnode.el;
354 | updateComponentPreRender(instance, next);
355 | }
356 |
357 | const { proxy } = instance;
358 | const subTree = instance.render.call(proxy, proxy);
359 | const prevSubTree = instance.subTree;
360 | instance.subTree = subTree;
361 | patch(prevSubTree, subTree, container, instance, anchor);
362 | }
363 | },
364 | {
365 | scheduler() {
366 | queueJobs(instance.update);
367 | },
368 | }
369 | );
370 | }
371 |
372 | return {
373 | createApp: createAppAPI(render),
374 | };
375 | }
376 |
377 | function updateComponentPreRender(instance, nextVNode) {
378 | instance.vnode = nextVNode;
379 | instance.next = null;
380 | instance.props = nextVNode.props;
381 | }
382 |
383 | function getSequence(arr) {
384 | const p = arr.slice();
385 | const result = [0];
386 | let i, j, u, v, c;
387 | const len = arr.length;
388 | for (i = 0; i < len; i++) {
389 | const arrI = arr[i];
390 | if (arrI !== 0) {
391 | j = result[result.length - 1];
392 | if (arr[j] < arrI) {
393 | p[i] = j;
394 | result.push(i);
395 | continue;
396 | }
397 | u = 0;
398 | v = result.length - 1;
399 | while (u < v) {
400 | c = (u + v) >> 1;
401 | if (arr[result[c]] < arrI) {
402 | u = c + 1;
403 | } else {
404 | v = c;
405 | }
406 | }
407 | if (arrI < arr[result[u]]) {
408 | if (u > 0) {
409 | p[i] = result[u - 1];
410 | }
411 | result[u] = i;
412 | }
413 | }
414 | }
415 | u = result.length;
416 | v = result[u - 1];
417 | while (u-- > 0) {
418 | result[u] = v;
419 | v = p[v];
420 | }
421 | return result;
422 | }
423 |
--------------------------------------------------------------------------------
/src/runtime-core/scheduler.ts:
--------------------------------------------------------------------------------
1 | const queue: any[] = [];
2 |
3 | let isFlushPending = false;
4 | const p = Promise.resolve();
5 |
6 | export function nextTick(fn) {
7 | return fn ? p.then(fn) : p;
8 | }
9 |
10 | export function queueJobs(job) {
11 | if (!queue.includes(job)) {
12 | queue.push(job);
13 | }
14 |
15 | queueFlush();
16 | }
17 |
18 | function queueFlush() {
19 | if (isFlushPending) return;
20 | isFlushPending = true;
21 |
22 | nextTick(flushJobs);
23 | }
24 |
25 | function flushJobs() {
26 | isFlushPending = false;
27 |
28 | let job;
29 | while ((job = queue.shift())) {
30 | job && job();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | export const Fragment = Symbol("Fragment");
4 |
5 | export const Text = Symbol("Text");
6 |
7 | export { createVNode as createElementVNode };
8 |
9 | export function createVNode(type, props?, children?) {
10 | const vnode = {
11 | type,
12 | props,
13 | children,
14 | component: null,
15 | key: props && props.key,
16 | shapeFlag: getShapeFlag(type),
17 | el: null,
18 | };
19 |
20 | if (typeof children === "string") {
21 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
22 | } else if (Array.isArray(children)) {
23 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
24 | }
25 |
26 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
27 | if (typeof children === "object") {
28 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
29 | }
30 | }
31 |
32 | return vnode;
33 | }
34 |
35 | export function createTextVNode(text: string) {
36 | return createVNode(Text, {}, text);
37 | }
38 |
39 | function getShapeFlag(type: any) {
40 | return typeof type === "string"
41 | ? ShapeFlags.ELEMENT
42 | : ShapeFlags.STATEFUL_COMPONENT;
43 | }
44 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../runtime-core";
2 |
3 | function createElement(type) {
4 | console.log("createElement-------------");
5 | return document.createElement(type);
6 | }
7 |
8 | function patchProp(el, key, prevVal, nextVal) {
9 | console.log("patchProp-------------");
10 | const isOn = (key: string) => /^on[A-Z]/.test(key);
11 | if (isOn(key)) {
12 | const event = key.slice(2).toLocaleLowerCase();
13 | el.addEventListener(event, nextVal);
14 | } else {
15 | if (nextVal === undefined || nextVal === null) {
16 | el.removeAttribute(key);
17 | } else {
18 | el.setAttribute(key, nextVal);
19 | }
20 | }
21 | }
22 |
23 | function insert(child, parent, anchor = null) {
24 | console.log("insert-------------");
25 | // parent.append(el);
26 | parent.insertBefore(child, anchor);
27 | }
28 |
29 | function remove(child) {
30 | const parent = child.parentNode;
31 | if (parent) {
32 | parent.removeChild(child);
33 | }
34 | }
35 |
36 | function setElementText(el, text) {
37 | el.textContent = text;
38 | }
39 |
40 | const renderder: any = createRenderer({
41 | createElement,
42 | patchProp,
43 | insert,
44 | remove,
45 | setElementText,
46 | });
47 |
48 | export function createApp(...args) {
49 | return renderder.createApp(...args);
50 | }
51 |
52 | export * from "../runtime-core";
53 |
--------------------------------------------------------------------------------
/src/shared/ShapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags {
2 | ELEMENT = 1, // 0001
3 | STATEFUL_COMPONENT = 1 << 1, // 0010
4 | TEXT_CHILDREN = 1 << 2, // 0100
5 | ARRAY_CHILDREN = 1 << 3, // 1000
6 | SLOT_CHILDREN = 1 << 4,
7 | }
8 |
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./toDisplayString";
2 |
3 | export const extend = Object.assign;
4 |
5 | export const EMPTY_PROPS = {};
6 |
7 | export const isObject = (val) => {
8 | return val !== null && typeof val === "object";
9 | };
10 |
11 | export const isString = (val) => typeof val === "string";
12 |
13 | export const hasChanged = (val, newVal) => {
14 | return !Object.is(val, newVal);
15 | };
16 |
17 | export const hasOwn = (val, key) => {
18 | return Object.prototype.hasOwnProperty.call(val, key);
19 | };
20 |
21 | export const camelize = (str: string) => {
22 | return str.replace(/-(\w)/g, (_, c: string) => {
23 | return c ? c.toUpperCase() : "";
24 | });
25 | };
26 |
27 | const capitalize = (str: string) => {
28 | return str.charAt(0).toUpperCase() + str.slice(1);
29 | };
30 |
31 | export const toHandlerKey = (str: string) => {
32 | return str ? "on" + capitalize(camelize(str)) : "";
33 | };
34 |
--------------------------------------------------------------------------------
/src/shared/toDisplayString.ts:
--------------------------------------------------------------------------------
1 | export function toDisplayString(value) {
2 | return String(value);
3 | }
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15 | "lib": [
16 | "DOM",
17 | "ES6",
18 | "ES2016"
19 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
20 | // "jsx": "preserve", /* Specify what JSX code is generated. */
21 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
22 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
23 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
24 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
25 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
26 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
27 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
28 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
29 |
30 | /* Modules */
31 | "module": "esnext" /* Specify what module code is generated. */,
32 | // "rootDir": "./", /* Specify the root folder within your source files. */
33 | "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
34 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
35 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
36 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
37 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
38 | "types": [
39 | "jest"
40 | ] /* Specify type package names to be included without being referenced in a source file. */,
41 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
42 | // "resolveJsonModule": true, /* Enable importing .json files */
43 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
44 |
45 | /* JavaScript Support */
46 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
47 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
48 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
49 |
50 | /* Emit */
51 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
52 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
53 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
54 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
55 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
56 | // "outDir": "./", /* Specify an output folder for all emitted files. */
57 | // "removeComments": true, /* Disable emitting comments. */
58 | // "noEmit": true, /* Disable emitting files from a compilation. */
59 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
60 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
61 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
62 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
63 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
64 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
65 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
66 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
67 | // "newLine": "crlf", /* Set the newline character for emitting files. */
68 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
69 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
70 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
71 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
72 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
73 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
74 |
75 | /* Interop Constraints */
76 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
77 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
78 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
79 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
80 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
81 |
82 | /* Type Checking */
83 | "strict": true /* Enable all strict type-checking options. */,
84 | "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied `any` type.. */,
85 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
86 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
87 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
88 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
89 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
90 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
91 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
92 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
93 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
94 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
95 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
96 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
97 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
98 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
99 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
100 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
101 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
102 |
103 | /* Completeness */
104 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
105 | "skipLibCheck": true, /* Skip type checking all .d.ts files. */
106 | "paths": {
107 | "@5c24-mini-vue/*": [
108 | "./packages/*/src"
109 | ]
110 | },
111 | },
112 | "include": [
113 | "packages/*/src",
114 | "packages/*/__test__"
115 | ]
116 | }
117 |
--------------------------------------------------------------------------------