├── .gitignore ├── README.md ├── babel.config.js ├── example ├── apiInject │ ├── App.js │ └── index.html ├── compiler-base │ ├── App.js │ └── index.html ├── componentEmit │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentSlot │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentUpdate │ ├── App.js │ ├── Child.js │ └── index.html ├── currentInstance │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── customRenderer │ ├── App.js │ ├── index.html │ └── main.js ├── helloword │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── nextTicker │ ├── App.js │ ├── NextTicker.js │ ├── index.html │ └── main.js ├── patchChildren │ ├── App.js │ ├── ArrayToArray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js ├── patchChildrenDiff │ ├── App.js │ ├── PatchChildren.js │ ├── index.html │ └── main.js └── update │ ├── App.js │ ├── index.html │ └── main.js ├── package.json ├── rollup.config.js ├── src ├── compiler-core │ ├── src │ │ ├── ast.ts │ │ ├── codegen.ts │ │ ├── compiler.ts │ │ ├── index.ts │ │ ├── parse.ts │ │ ├── runtimeHelpers.ts │ │ ├── transform.ts │ │ ├── transforms │ │ │ ├── transformElement.ts │ │ │ ├── transformExpression.ts │ │ │ └── transformText.ts │ │ └── utisl.ts │ └── tests │ │ ├── __snapshots__ │ │ └── codegen.spec.ts.snap │ │ ├── codegen.spec.ts │ │ ├── parse.spec.ts │ │ └── transform.spec.ts ├── index.ts ├── reactivity │ ├── computed │ │ └── computed.ts │ ├── effect │ │ └── effect.ts │ ├── index.ts │ ├── reactive │ │ ├── README.md │ │ ├── baseHandlers.ts │ │ └── reactive.ts │ ├── ref │ │ └── index.ts │ └── tests │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── compomentUpdateUtils.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentPublicInstance.ts │ ├── componentSlot.ts │ ├── componentsProps.ts │ ├── createApp.ts │ ├── h.ts │ ├── helper │ │ └── renderSlots.ts │ ├── index.ts │ ├── renderer.ts │ ├── scheduler.ts │ └── vnode.ts ├── runtime-dom │ └── index.ts └── shared │ ├── ShapeFlags.ts │ ├── index.ts │ └── toDisplayString.ts ├── tsconfig.json ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /lib -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-vue 2 | 3 | ## 实现功能 4 | 5 | #### reactivity 6 | 7 | > - reactive 8 | > - effect 9 | > - readonly 10 | > - isReactive 11 | > - isReadonly 12 | > - isProxy 13 | > - ref 14 | > - isRef 15 | > - unRef 16 | > - proxyRef 17 | > - computed 18 | 19 | #### runtime-core 20 | 21 | > - component 主流程 22 | > - rollup 打包 23 | > - 组件代理对象 24 | > - shapeFlags 25 | > - 注册事件 26 | > - props 逻辑 27 | > - emit 逻辑 28 | > - slots 逻辑 29 | > - fragment text 逻辑 30 | > - getCurrentInstance 逻辑 31 | > - provide inject 逻辑 32 | > - custom renderer 逻辑 33 | > - 更新 element 初始化流程搭建 34 | > - 更新 element props 逻辑 35 | > - 更新 element children (text -> array 、array -> text、text -> text) 逻辑 36 | > - 更新 element diff 双端对比算法 逻辑(1) 双端对比 37 | > - 更新 element diff 双端对比算法 逻辑(2) 中间对比 删除 逻辑 38 | > - 更新 element diff 双端对比算法 逻辑(3) 中间对比 移动、新增 逻辑 39 | > - 组件更新 逻辑 40 | > - nextTick 逻辑 41 | > - 解析插值功能 逻辑 42 | > - 解析 element 逻辑 43 | > - 解析 text 逻辑 44 | > - 解析三种联合类型 逻辑 45 | > - 有限状态机 (parse 图解) 46 | > - transform 逻辑 47 | > - 实现代码生成 string 类型 逻辑 48 | > - 实现代码生成插值类型 逻辑 49 | > - 实现代码生成三种联合类型 50 | > - 实现编译 template 成 render 函数 逻辑 51 | > - 完结撒花 52 | 53 | > 课程学习地址 54 | > ![dce7da28dbf31cf13f2e1d8a2585e29](https://user-images.githubusercontent.com/29727848/159952869-17f82eec-2a21-4dfd-95fa-bb589549c9a0.jpg) 55 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /example/apiInject/App.js: -------------------------------------------------------------------------------- 1 | import { h, provide, inject } from "../../lib/my-mini-vue.esm.js"; 2 | 3 | const ProviderOne = { 4 | name: "ProviderOne", 5 | setup() { 6 | provide("foo", "fooVal"); 7 | provide("bar", "barVal"); 8 | }, 9 | render() { 10 | return h("div", {}, [h("p", {}, "ProviderOne"), h(ProviderTwo)]); 11 | }, 12 | }; 13 | const ProviderTwo = { 14 | name: "ProviderTwo", 15 | setup() { 16 | const foo = inject("foo"); 17 | provide("foo", "fooTwo"); 18 | // provide("bar", "barVal"); 19 | return { 20 | foo, 21 | }; 22 | }, 23 | render() { 24 | return h("div", {}, [h("p", {}, `ProviderTwo - ${this.foo}`), h(Consumer)]); 25 | }, 26 | }; 27 | 28 | const Consumer = { 29 | name: "Consumer", 30 | setup() { 31 | const foo = inject("foo"); 32 | const bar = inject("bar"); 33 | // const baz = inject("baz", "defaultBaz"); 34 | const baz = inject("baz", () => "defaultBazFunction"); 35 | return { 36 | foo, 37 | bar, 38 | baz, 39 | }; 40 | }, 41 | render() { 42 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar} - ${this.baz}`); 43 | }, 44 | }; 45 | export default { 46 | name: "App", 47 | setup() {}, 48 | render() { 49 | return h("div", {}, [h("p", {}, "apiInject"), h(ProviderOne)]); 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini-vue 8 | 9 | 10 |
11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/compiler-base/App.js: -------------------------------------------------------------------------------- 1 | import { ref } from "../../lib/my-mini-vue.esm.js"; 2 | // 最简单的情况 3 | // template 只有一个 interpolation 4 | // export default { 5 | // template: `{{msg}}`, 6 | // setup() { 7 | // return { 8 | // msg: "vue3 - compiler", 9 | // }; 10 | // }, 11 | // }; 12 | 13 | // 复杂一点 14 | // template 包含 element 和 interpolation 15 | // export default { 16 | // template: `

{{msg}}

`, 17 | // setup() { 18 | // return { 19 | // msg: "vue3 - compiler", 20 | // }; 21 | // }, 22 | // }; 23 | 24 | export const App = { 25 | name: "App", 26 | template: `
hi,{{message}}---{{count}}
`, 27 | setup() { 28 | const count = (window.count = ref(1)); 29 | return { 30 | count, 31 | message: "mini-vue", 32 | }; 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /example/compiler-base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/my-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | window.self = null; 4 | export default { 5 | // ui 6 | render() { 7 | window.self = this; 8 | return h("div", {}, [ 9 | h("p", {}, "hi, " + this.msg), 10 | h(Foo, { 11 | onAdd(a, b) { 12 | console.log("Is onAdd", a, b); 13 | }, 14 | onAddFoo() { 15 | console.log("Is onAddFoo"); 16 | }, 17 | }), 18 | ]); 19 | }, 20 | setup() { 21 | return { 22 | msg: "App", 23 | }; 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /example/componentEmit/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/my-mini-vue.esm.js"; 2 | export const Foo = { 3 | name: "Foo", 4 | setup(props, { emit }) { 5 | const emitAdd = () => { 6 | console.log("Is emitAdd"); 7 | emit("add", 1, 2); 8 | emit("add-foo"); 9 | }; 10 | return { 11 | emitAdd, 12 | }; 13 | }, 14 | render() { 15 | const btn = h("button", { onClick: this.emitAdd }, "emitAdd"); 16 | const foo = h("p", {}, "foo"); 17 | return h("div", {}, [foo, btn]); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini-vue 8 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/my-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | // 流程 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/componentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { h, createTextVNode } from "../../lib/my-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | export default { 4 | // ui 5 | render() { 6 | const app = h("p", {}, this.msg); 7 | // const foo = h(Foo, {}, h("p", {}, "123")); 8 | const foo = h( 9 | Foo, 10 | {}, 11 | { 12 | header: ({ age }) => [ 13 | h("p", {}, "header" + age), 14 | createTextVNode("你好啊!"), 15 | ], 16 | footer: () => h("p", {}, "footer"), 17 | } 18 | ); 19 | 20 | return h("div", {}, [app, foo]); 21 | }, 22 | setup() { 23 | return { 24 | msg: "App", 25 | }; 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /example/componentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from "../../lib/my-mini-vue.esm.js"; 2 | export const Foo = { 3 | name: "Foo", 4 | setup() {}, 5 | render() { 6 | const foo = h("p", {}, "foo"); 7 | const age = 18; 8 | return h("div", {}, [ 9 | renderSlots(this.$slots, "header", { age }), 10 | foo, 11 | renderSlots(this.$slots, "footer"), 12 | ]); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /example/componentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini-vue 8 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/componentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/my-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | // 流程 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | // 在 render 中使用 proxy 调用 emit 函数 2 | // 也可以直接使用 this 3 | // 验证 proxy 的实现逻辑 4 | import { h, ref } from "../../lib/my-mini-vue.esm.js"; 5 | import Child from "./Child.js"; 6 | 7 | export default { 8 | name: "App", 9 | setup() { 10 | const msg = ref("123"); 11 | const count = ref(0); 12 | window.msg = msg; 13 | 14 | const changeChildProps = () => { 15 | msg.value = "456"; 16 | }; 17 | const changeCount = () => { 18 | count.value++; 19 | }; 20 | 21 | return { msg, count, changeChildProps, changeCount }; 22 | }, 23 | 24 | render() { 25 | return h("div", {}, [ 26 | h("div", {}, "你好"), 27 | h( 28 | "button", 29 | { 30 | onClick: this.changeChildProps, 31 | }, 32 | "change child props" 33 | ), 34 | h(Child, { 35 | msg: this.msg, 36 | }), 37 | h("button", { onClick: this.changeCount }, "change self count"), 38 | h("p", {}, "count: " + this.count), 39 | ]); 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /example/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/my-mini-vue.esm.js"; 2 | export default { 3 | name: "Child", 4 | setup(props, { emit }) {}, 5 | render(proxy) { 6 | return h("div", {}, [h("div", {}, "child" + this.$props.msg)]); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /example/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/my-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | export const App = { 4 | name: "App", 5 | render() { 6 | const app = h("p", {}, "app"); 7 | const foo = h(Foo); 8 | 9 | return h("div", {}, [app, foo]); 10 | }, 11 | setup() { 12 | const instance = getCurrentInstance(); 13 | console.log("App", instance); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /example/currentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/my-mini-vue.esm.js"; 2 | export const Foo = { 3 | name: "Foo", 4 | setup() { 5 | const instance = getCurrentInstance(); 6 | console.log("Foo", instance); 7 | }, 8 | render() { 9 | const foo = h("p", { name: "Foo" }, "foo"); 10 | return h("div", {}, [foo]); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /example/currentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini-vue 8 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/my-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | // 流程 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/customRenderer/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/my-mini-vue.esm.js"; 2 | export const App = { 3 | setup() { 4 | return { 5 | x: 100, 6 | y: 100, 7 | }; 8 | }, 9 | // ui 10 | render() { 11 | return h("rect", { 12 | x: this.x, 13 | y: this.y, 14 | }); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /example/customRenderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini-vue 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/customRenderer/main.js: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../../lib/my-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | console.log(PIXI); 4 | const game = new PIXI.Application({ 5 | width: 666, 6 | height: 666, 7 | }); 8 | document.body.append(game.view); 9 | const renderer = createRenderer({ 10 | createElement(type) { 11 | if (type === "rect") { 12 | const rect = new PIXI.Graphics(); 13 | rect.beginFill(0xff0000); 14 | rect.drawRect(0, 0, 100, 100); 15 | rect.endFill(); 16 | return rect; 17 | } 18 | }, 19 | patchProp(el, key, val) { 20 | el[key] = val; 21 | }, 22 | insert(el, parent) { 23 | parent.addChild(el); 24 | }, 25 | }); 26 | 27 | renderer.createApp(App).mount(game.stage); 28 | 29 | // 流程 30 | // const rootContainer = document.querySelector("#app"); 31 | // createApp(App).mount(rootContainer); 32 | -------------------------------------------------------------------------------- /example/helloword/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/my-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | window.self = null; 4 | export default { 5 | // ui 6 | render() { 7 | window.self = this; 8 | return h( 9 | "div", 10 | { 11 | id: "h", 12 | class: "blue", 13 | onClick() { 14 | console.log("click"); 15 | }, 16 | }, 17 | [h("p", {}, "hi, " + this.msg), h(Foo, { count: 1 })] 18 | ); 19 | // return h("div", { id: "h", class: "red" }, [ 20 | // h("p", { class: "red" }, "hi"), 21 | // h("p", { class: "blue" }, this.msg), 22 | // ]); 23 | }, 24 | setup() { 25 | return { 26 | msg: "my-mini-vue", 27 | }; 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /example/helloword/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/my-mini-vue.esm.js"; 2 | export const Foo = { 3 | name: "Foo", 4 | setup(props) { 5 | console.log(props); 6 | props.count++; 7 | console.log(props); 8 | }, 9 | render() { 10 | return h("div", {}, "Foo: " + this.count); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /example/helloword/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini-vue 8 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/helloword/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/my-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | // 流程 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/nextTicker/App.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | ref, 4 | getCurrentInstance, 5 | nextTick, 6 | } from "../../lib/my-mini-vue.esm.js"; 7 | 8 | export default { 9 | name: "App", 10 | setup() { 11 | const count = ref(0); 12 | const instance = getCurrentInstance(); 13 | function onClick() { 14 | for (let i = 0; i < 100; i++) { 15 | count.value = i; 16 | } 17 | console.log(instance, "instance"); 18 | nextTick(() => { 19 | console.log(instance, "instance"); 20 | }); 21 | } 22 | 23 | return { count, onClick }; 24 | }, 25 | 26 | render() { 27 | return h("div", { tId: 1 }, [ 28 | // h("p", {}, "主页"), 29 | h("p", {}, "count: " + this.count), 30 | h("button", { onClick: this.onClick }, "update"), 31 | // h(NextTicker), 32 | ]); 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /example/nextTicker/NextTicker.js: -------------------------------------------------------------------------------- 1 | // 测试 nextTick 逻辑 2 | import { h, ref } from "../../lib/my-mini-vue.esm.js"; 3 | 4 | // 如果 for 循环改变 count 的值 100 次的话 5 | // 会同时触发 100 次的 update 页面逻辑 6 | // 这里可以把 update 页面的逻辑放到微任务中执行 7 | // 避免更改了响应式对象就会执行 update 的逻辑 8 | // 因为只有最后一次调用 update 才是有价值的 9 | window.count = ref(1); 10 | 11 | // 如果一个响应式变量同时触发了两个组件的 update 12 | // 会发生什么有趣的事呢? 13 | const Child1 = { 14 | name: "NextTickerChild1", 15 | setup() {}, 16 | render() { 17 | return h("div", {}, `child1 count: ${window.count.value}`); 18 | }, 19 | }; 20 | 21 | const Child2 = { 22 | name: "NextTickerChild2", 23 | setup() {}, 24 | render() { 25 | return h("div", {}, `child2 count: ${window.count.value}`); 26 | }, 27 | }; 28 | 29 | export default { 30 | name: "NextTicker", 31 | setup() {}, 32 | render() { 33 | return h( 34 | "div", 35 | { tId: "nextTicker" }, 36 | [h(Child1), h(Child2)] 37 | // `for nextTick: count: ${window.count.value}` 38 | ); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /example/nextTicker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/nextTicker/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from "../../lib/my-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#root"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/my-mini-vue.esm.js"; 2 | import ArrayToText from "./ArrayToText.js"; 3 | import TextToText from "./TextToText.js"; 4 | import TextToArray from "./TextToArray.js"; 5 | 6 | export default { 7 | name: "App", 8 | setup() {}, 9 | 10 | render() { 11 | return h("div", { tId: 1 }, [ 12 | h("p", {}, "主页"), 13 | // 老的是 Array 新的是 Text 14 | // h(ArrayToText), 15 | // 老的是 Text 新的是 Text 16 | // h(TextToText), 17 | // 老的是 Text 新的是 Array 18 | h(TextToArray), 19 | ]); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/my-mini-vue.esm.js"; 2 | const nextChildren = [h("div", {}, "C"), h("div", {}, "D")]; 3 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")]; 4 | 5 | export default { 6 | name: "ArrayToArray", 7 | setup() { 8 | const isChange = ref(false); 9 | window.isChange = isChange; 10 | return { 11 | isChange, 12 | }; 13 | }, 14 | render() { 15 | const self = this; 16 | return self.isChange === true 17 | ? h("div", {}, nextChildren) 18 | : h("div", {}, prevChildren); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/my-mini-vue.esm.js"; 2 | const nextChildren = "newChildren"; 3 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")]; 4 | 5 | export default { 6 | name: "ArrayToText", 7 | setup() { 8 | const isChange = ref(false); 9 | window.isChange = isChange; 10 | return { 11 | isChange, 12 | }; 13 | }, 14 | render() { 15 | const self = this; 16 | return self.isChange === true 17 | ? h("div", {}, nextChildren) 18 | : h("div", {}, prevChildren); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /example/patchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/my-mini-vue.esm.js"; 2 | const prevChildren = "newChildren"; 3 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")]; 4 | 5 | export default { 6 | name: "TextToArray", 7 | setup() { 8 | const isChange = ref(false); 9 | window.isChange = isChange; 10 | return { 11 | isChange, 12 | }; 13 | }, 14 | render() { 15 | const self = this; 16 | return self.isChange === true 17 | ? h("div", {}, nextChildren) 18 | : h("div", {}, prevChildren); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /example/patchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/my-mini-vue.esm.js"; 2 | const nextChildren = "newChildren"; 3 | const prevChildren = "prevChildren"; 4 | 5 | export default { 6 | name: "TextToText", 7 | setup() { 8 | const isChange = ref(false); 9 | window.isChange = isChange; 10 | return { 11 | isChange, 12 | }; 13 | }, 14 | render() { 15 | const self = this; 16 | return self.isChange === true 17 | ? h("div", {}, nextChildren) 18 | : h("div", {}, prevChildren); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /example/patchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/patchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/my-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#root"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/patchChildrenDiff/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/my-mini-vue.esm.js"; 2 | import PatchChildren from "./PatchChildren.js"; 3 | 4 | export default { 5 | name: "App", 6 | setup() {}, 7 | 8 | render() { 9 | return h("div", { tId: 1 }, [h("p", {}, "主页"), h(PatchChildren)]); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /example/patchChildrenDiff/PatchChildren.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/my-mini-vue.esm.js"; 2 | import { ref } from "../../lib/my-mini-vue.esm.js"; 3 | 4 | const isChange = ref(false); 5 | 6 | // 1. 左侧的对比 7 | // (a b) c 8 | // (a b) d e 9 | // const prevChildren = [ 10 | // h("p", { key: "A" }, "A"), 11 | // h("p", { key: "B" }, "B"), 12 | // h("p", { key: "C" }, "C"), 13 | // ]; 14 | // const nextChildren = [ 15 | // h("p", { key: "A" }, "A"), 16 | // h("p", { key: "B" }, "B"), 17 | // h("p", { key: "D" }, "D"), 18 | // h("p", { key: "E" }, "E"), 19 | // ]; 20 | 21 | // 2. 右侧的对比 22 | // a (b c) 23 | // d e (b c) 24 | // const prevChildren = [ 25 | // h("p", { key: "A" }, "A"), 26 | // h("p", { key: "B" }, "B"), 27 | // h("p", { key: "C" }, "C"), 28 | // ]; 29 | // const nextChildren = [ 30 | // h("p", { key: "D" }, "D"), 31 | // h("p", { key: "E" }, "E"), 32 | // h("p", { key: "B" }, "B"), 33 | // h("p", { key: "C" }, "C"), 34 | // ]; 35 | 36 | // 3. 新的比老的长 37 | // 创建新的 38 | // 左侧 39 | // (a b) 40 | // (a b) c 41 | // i = 2, e1 = 1, e2 = 2 42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 43 | // const nextChildren = [ 44 | // h("p", { key: "A" }, "A"), 45 | // h("p", { key: "B" }, "B"), 46 | // h("p", { key: "C" }, "C"), 47 | // h("p", { key: "D" }, "D"), 48 | // ]; 49 | 50 | // 右侧 51 | // (a b) 52 | // c (a b) 53 | // i = 0, e1 = -1, e2 = 0 54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 55 | // const nextChildren = [ 56 | // h("p", { key: "D" }, "D"), 57 | // h("p", { key: "C" }, "C"), 58 | // h("p", { key: "A" }, "A"), 59 | // h("p", { key: "B" }, "B"), 60 | // ]; 61 | 62 | // 4. 老的比新的长 63 | // 删除老的 64 | // 左侧 65 | // (a b) c 66 | // (a b) 67 | // i = 2, e1 = 2, e2 = 1 68 | // const prevChildren = [ 69 | // h("p", { key: "A" }, "A"), 70 | // h("p", { key: "B" }, "B"), 71 | // h("p", { key: "C" }, "C"), 72 | // ]; 73 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 74 | 75 | // 右侧 76 | // a (b c) 77 | // (b c) 78 | // i = 0, e1 = 0, e2 = -1 79 | 80 | // const prevChildren = [ 81 | // h("p", { key: "A" }, "A"), 82 | // h("p", { key: "B" }, "B"), 83 | // h("p", { key: "C" }, "C"), 84 | // ]; 85 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 86 | 87 | // 5. 对比中间的部分 88 | // 删除老的 (在老的里面存在,新的里面不存在) 89 | // 5.1 90 | // a,b,(c,d),f,g 91 | // a,b,(e,c),f,g 92 | // D 节点在新的里面是没有的 - 需要删除掉 93 | // C 节点 props 也发生了变化 94 | 95 | // const prevChildren = [ 96 | // h("p", { key: "A" }, "A"), 97 | // h("p", { key: "B" }, "B"), 98 | // h("p", { key: "C", id: "c-prev" }, "C"), 99 | // h("p", { key: "D" }, "D"), 100 | // h("p", { key: "F" }, "F"), 101 | // h("p", { key: "G" }, "G"), 102 | // ]; 103 | 104 | // const nextChildren = [ 105 | // h("p", { key: "A" }, "A"), 106 | // h("p", { key: "B" }, "B"), 107 | // h("p", { key: "E" }, "E"), 108 | // h("p", { key: "C", id:"c-next" }, "C"), 109 | // h("p", { key: "F" }, "F"), 110 | // h("p", { key: "G" }, "G"), 111 | // ]; 112 | 113 | // 5.1.1 114 | // a,b,(c,e,d),f,g 115 | // a,b,(e,c),f,g 116 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑) 117 | // const prevChildren = [ 118 | // h("p", { key: "A" }, "A"), 119 | // h("p", { key: "B" }, "B"), 120 | // h("p", { key: "C", id: "c-prev" }, "C"), 121 | // h("p", { key: "E" }, "E"), 122 | // h("p", { key: "D" }, "D"), 123 | // h("p", { key: "F" }, "F"), 124 | // h("p", { key: "G" }, "G"), 125 | // ]; 126 | 127 | // const nextChildren = [ 128 | // h("p", { key: "A" }, "A"), 129 | // h("p", { key: "B" }, "B"), 130 | // h("p", { key: "E" }, "E"), 131 | // h("p", { key: "C", id:"c-next" }, "C"), 132 | // h("p", { key: "F" }, "F"), 133 | // h("p", { key: "G" }, "G"), 134 | // ]; 135 | 136 | // 2 移动 (节点存在于新的和老的里面,但是位置变了) 137 | 138 | // 2.1 139 | // a,b,(c,d,e),f,g 140 | // a,b,(e,c,d),f,g 141 | // 最长子序列: [1,2] 142 | 143 | // const prevChildren = [ 144 | // h("p", { key: "A" }, "A"), 145 | // h("p", { key: "B" }, "B"), 146 | // h("p", { key: "C" }, "C"), 147 | // h("p", { key: "D" }, "D"), 148 | // h("p", { key: "E" }, "E"), 149 | // h("p", { key: "F" }, "F"), 150 | // h("p", { key: "G" }, "G"), 151 | // ]; 152 | 153 | // const nextChildren = [ 154 | // h("p", { key: "A" }, "A"), 155 | // h("p", { key: "B" }, "B"), 156 | // h("p", { key: "E" }, "E"), 157 | // h("p", { key: "C" }, "C"), 158 | // h("p", { key: "D" }, "D"), 159 | // h("p", { key: "F" }, "F"), 160 | // h("p", { key: "G" }, "G"), 161 | // ]; 162 | 163 | // 2.2 164 | // a,b,(c,d,e,z),f,g 165 | // a,b,(d,c,y,e),f,g 166 | // 最长子序列: [1,3] 167 | 168 | // const prevChildren = [ 169 | // h("p", { key: "A" }, "A"), 170 | // h("p", { key: "B" }, "B"), 171 | // h("p", { key: "C" }, "C"), 172 | // h("p", { key: "D" }, "D"), 173 | // h("p", { key: "E" }, "E"), 174 | // h("p", { key: "Z" }, "Z"), 175 | // h("p", { key: "F" }, "F"), 176 | // h("p", { key: "G" }, "G"), 177 | // ]; 178 | 179 | // const nextChildren = [ 180 | // h("p", { key: "A" }, "A"), 181 | // h("p", { key: "B" }, "B"), 182 | // h("p", { key: "D" }, "D"), 183 | // h("p", { key: "C" }, "C"), 184 | // h("p", { key: "Y" }, "Y"), 185 | // h("p", { key: "E" }, "E"), 186 | // h("p", { key: "F" }, "F"), 187 | // h("p", { key: "G" }, "G"), 188 | // ]; 189 | 190 | // 3. 创建新的节点 191 | // a,b,(c,e),f,g 192 | // a,b,(e,c,d),f,g 193 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建 194 | // const prevChildren = [ 195 | // h("p", { key: "A" }, "A"), 196 | // h("p", { key: "B" }, "B"), 197 | // h("p", { key: "C" }, "C"), 198 | // h("p", { key: "E" }, "E"), 199 | // h("p", { key: "F" }, "F"), 200 | // h("p", { key: "G" }, "G"), 201 | // ]; 202 | 203 | // const nextChildren = [ 204 | // h("p", { key: "A" }, "A"), 205 | // h("p", { key: "B" }, "B"), 206 | // h("p", { key: "E" }, "E"), 207 | // h("p", { key: "C" }, "C"), 208 | // h("p", { key: "D" }, "D"), 209 | // h("p", { key: "F" }, "F"), 210 | // h("p", { key: "G" }, "G"), 211 | // ]; 212 | 213 | // fix: 验证 没有设置 key 时 逻辑错误问题 214 | const prevChildren = [ 215 | h("p", { key: "A" }, "A"), 216 | h("p", {}, "C"), 217 | h("p", { key: "B" }, "B"), 218 | h("p", { key: "D" }, "D"), 219 | ]; 220 | 221 | const nextChildren = [ 222 | h("p", { key: "A" }, "A"), 223 | h("p", { key: "B" }, "B"), 224 | h("p", {}, "C"), 225 | h("p", { key: "D" }, "D"), 226 | ]; 227 | 228 | export default { 229 | name: "PatchChildren", 230 | setup() {}, 231 | render() { 232 | return h("div", {}, [ 233 | h( 234 | "button", 235 | { 236 | onClick: () => { 237 | isChange.value = !isChange.value; 238 | }, 239 | }, 240 | "测试子组件之间的 patch 逻辑" 241 | ), 242 | h("children", {}, isChange.value === true ? nextChildren : prevChildren), 243 | ]); 244 | }, 245 | }; 246 | -------------------------------------------------------------------------------- /example/patchChildrenDiff/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/patchChildrenDiff/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/my-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#root"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/my-mini-vue.esm.js"; 2 | export default { 3 | name: "App", 4 | setup() { 5 | const count = ref(0); 6 | const props = ref({ 7 | foo: "foo", 8 | bar: "bar", 9 | }); 10 | const onClick = () => { 11 | count.value++; 12 | }; 13 | const onChangeProps = () => { 14 | props.value.foo = "new-foo"; 15 | }; 16 | const onChangeProps1 = () => { 17 | props.value.foo = undefined; 18 | }; 19 | const onChangeProps2 = () => { 20 | props.value = { 21 | foo: "foo", 22 | }; 23 | }; 24 | return { 25 | count, 26 | props, 27 | onClick, 28 | onChangeProps, 29 | onChangeProps1, 30 | onChangeProps2, 31 | }; 32 | }, 33 | // ui 34 | render() { 35 | return h( 36 | "div", 37 | { 38 | id: "root", 39 | foo: this.props.foo, 40 | bar: this.props.bar, 41 | }, 42 | [ 43 | h("div", {}, `count: ${this.count}`), // 依赖收集 44 | h( 45 | "button", 46 | { 47 | onClick: this.onClick, 48 | }, 49 | "click" 50 | ), 51 | h( 52 | "button", 53 | { 54 | onClick: this.onChangeProps, 55 | }, 56 | "修改 foo 值为 new-foo" 57 | ), 58 | h( 59 | "button", 60 | { 61 | onClick: this.onChangeProps1, 62 | }, 63 | "修改 foo 值为 undefined" 64 | ), 65 | h( 66 | "button", 67 | { 68 | onClick: this.onChangeProps2, 69 | }, 70 | "删除 bar" 71 | ), 72 | ] 73 | ); 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /example/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini-vue 8 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/my-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | // 流程 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-mini-vue", 3 | "version": "1.0.0", 4 | "main": "lib/my-mini-vue.cjs.js", 5 | "module": "lib/my-mini-vue.esm.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "jest", 9 | "build": "rollup -c rollup.config.js" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.17.4", 13 | "@babel/preset-env": "^7.16.11", 14 | "@babel/preset-typescript": "^7.16.7", 15 | "@rollup/plugin-typescript": "^8.3.2", 16 | "@types/jest": "^27.4.0", 17 | "babel-jest": "^27.5.1", 18 | "jest": "^27.5.1", 19 | "rollup": "^2.70.2", 20 | "tslib": "^2.3.1", 21 | "typescript": "^4.5.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from "./package.json"; 2 | import typescript from "@rollup/plugin-typescript"; 3 | export default { 4 | input: "./src/index.ts", 5 | output: [ 6 | // 1. cjs -> common.js 7 | // 2. esm 8 | { 9 | format: "cjs", 10 | file: pkg.main, 11 | sourcemap: true, 12 | }, 13 | { 14 | format: "es", 15 | file: pkg.module, 16 | sourcemap: true, 17 | }, 18 | ], 19 | plugins: [typescript()], 20 | }; 21 | -------------------------------------------------------------------------------- /src/compiler-core/src/ast.ts: -------------------------------------------------------------------------------- 1 | import { CREATE_ELEMENT_VNODE } from "./runtimeHelpers"; 2 | 3 | export const enum NodeTypes { 4 | INTERPOLATION, 5 | SIMPLE_EXPRESSION, 6 | ELEMENT, 7 | TEXT, 8 | ROOT, 9 | COMPOUND_EXPRESSION, 10 | } 11 | 12 | export function createVnodeCall(context, tag, props, children) { 13 | context.helper(CREATE_ELEMENT_VNODE); 14 | return { 15 | type: NodeTypes.ELEMENT, 16 | tag, 17 | props, 18 | children, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/compiler-core/src/codegen.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "../../shared"; 2 | import { NodeTypes } from "./ast"; 3 | import { 4 | CREATE_ELEMENT_VNODE, 5 | helperMapName, 6 | TO_DISPLAY_STRING, 7 | } from "./runtimeHelpers"; 8 | 9 | export function generate(ast) { 10 | const context = createCodegenContext(); 11 | const { push } = context; 12 | 13 | genFunctionPreamble(ast, context); 14 | 15 | const functionName = "render"; 16 | const args = ["_ctx", "_cache"]; 17 | const signature = args.join(", "); 18 | 19 | push(`function ${functionName} (${signature}){`); 20 | push("return "); 21 | genNode(ast.codegenNode, context); 22 | push("}"); 23 | 24 | return { 25 | code: context.code, 26 | }; 27 | } 28 | // 导入部分代码生成 29 | function genFunctionPreamble(ast, context) { 30 | const { push, helper } = context; 31 | const VueBinging = "Vue"; 32 | const aliasHelper = (s) => `${helperMapName[s]}:${helper(s)}`; 33 | if (ast.helpers.length) { 34 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")}} = ${VueBinging}`); 35 | } 36 | push(`\n`); 37 | push("return "); 38 | } 39 | function createCodegenContext() { 40 | const context = { 41 | code: "", 42 | push(source) { 43 | context.code += source; 44 | }, 45 | helper(key) { 46 | return `_${helperMapName[key]}`; 47 | }, 48 | }; 49 | return context; 50 | } 51 | // 处理 code 52 | function genNode(node: any, context: any) { 53 | switch (node.type) { 54 | case NodeTypes.TEXT: 55 | genText(node, context); 56 | break; 57 | case NodeTypes.INTERPOLATION: 58 | genInterpolation(node, context); 59 | break; 60 | case NodeTypes.SIMPLE_EXPRESSION: 61 | genExpression(node, context); 62 | break; 63 | case NodeTypes.ELEMENT: 64 | genElement(node, context); 65 | break; 66 | case NodeTypes.COMPOUND_EXPRESSION: 67 | genCompoundExpression(node, context); 68 | break; 69 | default: 70 | break; 71 | } 72 | } 73 | // 处理 element 74 | function genElement(node, context) { 75 | const { push, helper } = context; 76 | const { tag, children, props } = node; 77 | push(`${helper(CREATE_ELEMENT_VNODE)}(`); 78 | genNodeList(genNullable([tag, props, children]), context); 79 | push(")"); 80 | } 81 | // 处理转换 null 后的数组 82 | function genNodeList(nodes, context) { 83 | const { push } = context; 84 | for (let i = 0; i < nodes.length; i++) { 85 | const node = nodes[i]; 86 | if (isString(node)) { 87 | push(node); 88 | } else { 89 | genNode(node, context); 90 | } 91 | if (i < nodes.length - 1) { 92 | push(", "); 93 | } 94 | } 95 | } 96 | // 将所有的 undefined 等转为 null 97 | function genNullable(args) { 98 | return args.map((arg) => arg || "null"); 99 | } 100 | // 处理 string 101 | function genText(node, context) { 102 | const { push } = context; 103 | push(`'${node.content}'`); 104 | } 105 | // 处理 插值 106 | function genInterpolation(node: any, context: any) { 107 | const { push, helper } = context; 108 | push(`${helper(TO_DISPLAY_STRING)}(`); 109 | genNode(node.content, context); 110 | push(`)`); 111 | } 112 | // 处理 表达式 113 | function genExpression(node: any, context: any) { 114 | const { push } = context; 115 | push(`${node.content}`); 116 | } 117 | // 处理复合类型 118 | function genCompoundExpression(node: any, context: any) { 119 | const { push } = context; 120 | const { tag, children } = node; 121 | for (let i = 0; i < children.length; i++) { 122 | const child = children[i]; 123 | if (isString(child)) { 124 | push(child); 125 | } else { 126 | genNode(child, context); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/compiler-core/src/compiler.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "./codegen"; 2 | import { baseParse } from "./parse"; 3 | import { transform } from "./transform"; 4 | import { transformElement } from "./transforms/transformElement"; 5 | import { transformExpression } from "./transforms/transformExpression"; 6 | import { transformText } from "./transforms/transformText"; 7 | 8 | export function baseCompiler(template) { 9 | const ast: any = baseParse(template); 10 | transform(ast, { 11 | // 插件先调用 会后执行 12 | nodeTransforms: [transformExpression, transformElement, transformText], 13 | }); 14 | return generate(ast); 15 | } 16 | -------------------------------------------------------------------------------- /src/compiler-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./compiler"; 2 | -------------------------------------------------------------------------------- /src/compiler-core/src/parse.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | 3 | const enum TagTypes { 4 | Start, 5 | End, 6 | } 7 | 8 | export function baseParse(content: string) { 9 | const context = createParserContext(content); 10 | return createRoot(parseChildren(context, [])); 11 | } 12 | function parseChildren(context, ancestors) { 13 | let nodes: any = []; 14 | // 循环处理 在未达到结束他条件时 一直循环处理 内部所有的 数据 15 | while (!isEnd(context, ancestors)) { 16 | let node; 17 | const s = context.source; 18 | // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith 19 | if (s.startsWith("{{")) { 20 | node = parseInterpolation(context); 21 | } else if (s[0] === "<") { 22 | if (/[a-z]/i.test(s[1])) { 23 | node = parseElement(context, ancestors); 24 | } 25 | } else { 26 | // if (/[a-z]|[0-9]|[\u4e00-\u9fa5]/i.test(s[0])) 27 | node = parseText(context); 28 | } 29 | nodes.push(node); 30 | } 31 | return nodes; 32 | } 33 | 34 | // text 处理 35 | function parseText(context) { 36 | const endTokens = ["<", "{{"]; 37 | let endIndex = context.source.length; 38 | for (let i = 0; i <= endTokens.length; i++) { 39 | const index = context.source.indexOf(endTokens[i]); 40 | if (index !== -1 && endIndex > index) { 41 | endIndex = index; 42 | } 43 | } 44 | const content = parseTextData(context, endIndex); 45 | return { 46 | type: NodeTypes.TEXT, 47 | content, 48 | }; 49 | } 50 | 51 | function parseTextData(context, length) { 52 | const content = context.source.slice(0, length); 53 | advanceBy(context, length); 54 | return content; 55 | } 56 | // element 处理 57 | function parseElement(context: any, ancestors) { 58 | // 获取tag 59 | const element: any = parseTag(context, TagTypes.Start); 60 | ancestors.push(element); 61 | element.children = parseChildren(context, ancestors); 62 | ancestors.pop(); 63 | // 这一步是为了去除 闭合标签 64 | if (startsWithEndTagOpen(context.source, element.tag)) { 65 | parseTag(context, TagTypes.Start); 66 | } else { 67 | throw new Error(`缺少结束标签:${element.tag}`); 68 | } 69 | 70 | return element; 71 | } 72 | 73 | function startsWithEndTagOpen(source, tag) { 74 | return ( 75 | source.startsWith("= 0; i--) { 132 | const tag = ancestors[i].tag; 133 | if (startsWithEndTagOpen(s, tag)) { 134 | return true; 135 | } 136 | } 137 | } 138 | if (ancestors && s.startsWith(``)) { 139 | return true; 140 | } 141 | // 1. context.source 有值时 继续循环 142 | return !s; 143 | } 144 | -------------------------------------------------------------------------------- /src/compiler-core/src/runtimeHelpers.ts: -------------------------------------------------------------------------------- 1 | export const TO_DISPLAY_STRING = Symbol("toDisplayString"); 2 | export const CREATE_ELEMENT_VNODE = Symbol("createElementVNode"); 3 | 4 | export const helperMapName = { 5 | [TO_DISPLAY_STRING]: "toDisplayString", 6 | [CREATE_ELEMENT_VNODE]: "createElementVNode", 7 | }; 8 | -------------------------------------------------------------------------------- /src/compiler-core/src/transform.ts: -------------------------------------------------------------------------------- 1 | // 1. 深度优先搜索 2 | 3 | import { NodeTypes } from "./ast"; 4 | import { TO_DISPLAY_STRING } from "./runtimeHelpers"; 5 | 6 | // 2. 修改 text content 7 | export function transform(root, options = {}) { 8 | const context = createTransformsContext(root, options); 9 | // 1. 深度优先搜索 10 | traverseNode(root, context); 11 | createRootCodegen(root); 12 | 13 | root.helpers = [...context.helpers.keys()]; 14 | } 15 | 16 | function createRootCodegen(root: any) { 17 | const child = root.children[0]; 18 | if (child.type === NodeTypes.ELEMENT) { 19 | root.codegenNode = child.codegenNode; 20 | } else { 21 | root.codegenNode = root.children[0]; 22 | } 23 | } 24 | // 处理传入的 ast 树 深度遍历寻找需要修改的 值 25 | function traverseNode(node: any, context) { 26 | // console.log(node); 27 | // 为了达到测试效果 判断 node.type 是不是 text 如果是 就进行修改 28 | // 2. 修改 text content 29 | // if (node.type === NodeTypes.TEXT) { 30 | // node.content = node.content + "my-mini-vue"; 31 | // } 32 | const nodeTransforms = context.transforms; 33 | const exitFns: any = []; // 插件的退出收集 34 | for (let i = 0; i < nodeTransforms.length; i++) { 35 | const transform = nodeTransforms[i]; 36 | const onExit = transform(node, context); 37 | if (onExit) { 38 | exitFns.push(onExit); 39 | } 40 | } 41 | 42 | switch (node.type) { 43 | case NodeTypes.INTERPOLATION: 44 | context.helper(TO_DISPLAY_STRING); 45 | break; 46 | case NodeTypes.ROOT: 47 | case NodeTypes.ELEMENT: 48 | traverseChildren(node, context); 49 | 50 | default: 51 | break; 52 | } 53 | // 处理插件的退出逻辑 54 | let i = exitFns.length; 55 | while (i--) { 56 | exitFns[i](); 57 | } 58 | } 59 | // 处理 node children 60 | function traverseChildren(node, context) { 61 | const children = node.children; 62 | for (let i = 0; i < children.length; i++) { 63 | const node = children[i]; 64 | traverseNode(node, context); 65 | } 66 | } 67 | 68 | function createTransformsContext(root, options) { 69 | const context = { 70 | root, 71 | transforms: options.nodeTransforms || [], 72 | helpers: new Map(), 73 | helper(key) { 74 | context.helpers.set(key, 1); 75 | }, 76 | }; 77 | return context; 78 | } 79 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformElement.ts: -------------------------------------------------------------------------------- 1 | import { createVnodeCall, NodeTypes } from "../ast"; 2 | import { CREATE_ELEMENT_VNODE } from "../runtimeHelpers"; 3 | 4 | export function transformElement(node, context) { 5 | if (node.type === NodeTypes.ELEMENT) { 6 | return () => { 7 | // 这里是中间处理层 8 | // tag 标签 9 | const vnodeTag = `'${node.tag}'`; 10 | // props 参数 11 | let vnoceProps; 12 | // children 节点 13 | const children = node.children; 14 | let vnodeChildren = children[0]; 15 | 16 | const vnodeElement = createVnodeCall( 17 | context, 18 | vnodeTag, 19 | vnoceProps, 20 | vnodeChildren 21 | ); 22 | node.codegenNode = vnodeElement; 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformExpression.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | 3 | // 处理表达式 4 | export function transformExpression(node) { 5 | if (node.type === NodeTypes.INTERPOLATION) { 6 | node.content = processExpression(node.content); 7 | } 8 | } 9 | // 因为 node.content.content 过长 进行封装优化处理 10 | function processExpression(node: any) { 11 | node.content = `_ctx.${node.content}`; 12 | return node; 13 | } 14 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformText.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | import { isText } from "../utisl"; 3 | 4 | export function transformText(node) { 5 | if (node.type === NodeTypes.ELEMENT) { 6 | return () => { 7 | const children = node.children; 8 | let currentContainer; 9 | for (let i = 0; i < children.length; i++) { 10 | const child = children[i]; 11 | // 判断是不是一个text 12 | if (isText(child)) { 13 | for (let j = i + 1; j < children.length; j++) { 14 | const next = children[j]; 15 | 16 | if (isText(next)) { 17 | if (!currentContainer) { 18 | currentContainer = children[i] = { 19 | type: NodeTypes.COMPOUND_EXPRESSION, 20 | children: [child], 21 | }; 22 | } 23 | currentContainer.children.push(" + "); 24 | currentContainer.children.push(next); 25 | // 添加完成后将处理过的删除掉 26 | children.splice(j, 1); 27 | // 因为删除了元素 所以 会影响到 children 少了一个 所以这里需要进行 j-- 28 | j--; 29 | } else { 30 | // 如果是 element 就返回 31 | currentContainer = undefined; 32 | } 33 | } 34 | } 35 | } 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/compiler-core/src/utisl.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | 3 | export function isText(node) { 4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION; 5 | } 6 | -------------------------------------------------------------------------------- /src/compiler-core/tests/__snapshots__/codegen.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`codegen element 1`] = ` 4 | "const { toDisplayString:_toDisplayString, createElementVNode:_createElementVNode} = Vue 5 | return function render (_ctx, _cache){return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))}" 6 | `; 7 | 8 | exports[`codegen interpolation 1`] = ` 9 | "const { toDisplayString:_toDisplayString} = Vue 10 | return function render (_ctx, _cache){return _toDisplayString(_ctx.message)}" 11 | `; 12 | 13 | exports[`codegen string 1`] = ` 14 | " 15 | return function render (_ctx, _cache){return 'hi'}" 16 | `; 17 | -------------------------------------------------------------------------------- /src/compiler-core/tests/codegen.spec.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "../src/codegen"; 2 | import { baseParse } from "../src/parse"; 3 | import { transform } from "../src/transform"; 4 | import { transformElement } from "../src/transforms/transformElement"; 5 | import { transformExpression } from "../src/transforms/transformExpression"; 6 | import { transformText } from "../src/transforms/transformText"; 7 | 8 | describe("codegen", () => { 9 | it("string", () => { 10 | const ast = baseParse("hi"); 11 | transform(ast); 12 | const { code } = generate(ast); 13 | expect(code).toMatchSnapshot(); 14 | }); 15 | 16 | it("interpolation", () => { 17 | const ast = baseParse("{{message}}"); 18 | transform(ast, { 19 | nodeTransforms: [transformExpression], 20 | }); 21 | const { code } = generate(ast); 22 | expect(code).toMatchSnapshot(); 23 | }); 24 | 25 | it("element", () => { 26 | const ast: any = baseParse("
hi,{{message}}
"); 27 | transform(ast, { 28 | // 插件先调用 会后执行 29 | nodeTransforms: [transformExpression, transformElement, transformText], 30 | }); 31 | const { code } = generate(ast); 32 | expect(code).toMatchSnapshot(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/compiler-core/tests/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../src/ast"; 2 | import { baseParse } from "../src/parse"; 3 | 4 | describe("Parse", () => { 5 | // 插值测试 6 | describe("interpolation", () => { 7 | test("simple interpolation", () => { 8 | const ast = baseParse("{{ message }}"); 9 | // root 10 | expect(ast.children[0]).toStrictEqual({ 11 | type: NodeTypes.INTERPOLATION, 12 | content: { 13 | type: NodeTypes.SIMPLE_EXPRESSION, 14 | content: "message", 15 | }, 16 | }); 17 | }); 18 | }); 19 | 20 | // element 测试 21 | describe("element", () => { 22 | test("simple element div", () => { 23 | const ast = baseParse("
"); 24 | // root 25 | expect(ast.children[0]).toStrictEqual({ 26 | type: NodeTypes.ELEMENT, 27 | tag: "div", 28 | children: [], 29 | }); 30 | }); 31 | }); 32 | 33 | // text 测试 34 | describe("text", () => { 35 | test("simple text", () => { 36 | const ast = baseParse("some text"); 37 | // root 38 | expect(ast.children[0]).toStrictEqual({ 39 | type: NodeTypes.TEXT, 40 | content: "some text", 41 | }); 42 | }); 43 | }); 44 | 45 | // 三种表达式混合测试 46 | test("hello world", () => { 47 | const ast = baseParse("

hi,{{message}}

"); 48 | expect(ast.children[0]).toStrictEqual({ 49 | type: NodeTypes.ELEMENT, 50 | tag: "p", 51 | children: [ 52 | { 53 | type: NodeTypes.TEXT, 54 | content: "hi,", 55 | }, 56 | { 57 | type: NodeTypes.INTERPOLATION, 58 | content: { 59 | type: NodeTypes.SIMPLE_EXPRESSION, 60 | content: "message", 61 | }, 62 | }, 63 | ], 64 | }); 65 | }); 66 | // edge case 67 | test("hello world", () => { 68 | const ast = baseParse("

hi

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