├── .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("

h1

{{ message }}
"); 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(" 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(" 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("

h1

{{ message }}
"); 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 | --------------------------------------------------------------------------------