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

hi

{{message}}
"); 57 | expect(ast.children[0]).toStrictEqual({ 58 | type: NodeTypes.ELEMENT, 59 | tag: "div", 60 | children: [ 61 | { 62 | type: NodeTypes.ELEMENT, 63 | tag: "p", 64 | children: [ 65 | { 66 | type: NodeTypes.TEXT, 67 | content: "hi", 68 | }, 69 | ], 70 | }, 71 | { 72 | type: NodeTypes.INTERPOLATION, 73 | content: { 74 | type: NodeTypes.SIMPLE_EXPRESSION, 75 | content: "message", 76 | }, 77 | }, 78 | ], 79 | }); 80 | }); 81 | test("should thorw when lack end tag", () => { 82 | expect(() => { 83 | baseParse("
"); 84 | }).toThrow("缺少结束标签:span"); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /src/compiler-core/tests/transform.spec.ts: -------------------------------------------------------------------------------- 1 | import { transform } from "../src/transform"; 2 | import { baseParse } from "../src/parse"; 3 | import { NodeTypes } from "../src/ast"; 4 | 5 | describe("transform", () => { 6 | it("happy path", () => { 7 | const ast = baseParse("
hi,{{message}}
"); 8 | const plugin = (node) => { 9 | if (node.type === NodeTypes.TEXT) { 10 | node.content = node.content + "mini-vue"; 11 | } 12 | }; 13 | transform(ast, { 14 | nodeTransforms: [plugin], 15 | }); 16 | const nodeText = ast.children[0].children[0]; 17 | expect(nodeText.content).toBe("hi,mini-vue"); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // mini-vue 出口 2 | export * from "./runtime-dom"; 3 | 4 | import { baseCompile } from "./compiler-core/src"; 5 | import * as runtimeDom from "./runtime-dom"; 6 | import { registerRuntimeComplier } from "./runtime-dom"; 7 | function complieToFunction(template) { 8 | const { code } = baseCompile(template); 9 | const render = new Function("vue", code)(runtimeDom); 10 | return render; 11 | } 12 | registerRuntimeComplier(complieToFunction); 13 | -------------------------------------------------------------------------------- /src/reactivity/baseHandler.ts: -------------------------------------------------------------------------------- 1 | import { track, trigger } from "./effect"; 2 | import {reactive,ReactiveFlags,readonly} from './reactive' 3 | import { isObject } from "../shared"; 4 | import { unRef,isRef } from "./ref"; 5 | const get = createGetter(); 6 | const set = createSetter(); 7 | const readonlyGet = createGetter(true); 8 | const shallowReadonlyGet = createGetter(true, true); 9 | function createGetter(isReadonly = false,isShallow = false){ 10 | return function get(target:any,key:any){ 11 | if(key === ReactiveFlags.IS_REACTIVE){ 12 | return !isReadonly; 13 | } else if(key === ReactiveFlags.IS_READONLY){ 14 | return isReadonly; 15 | } 16 | const res = Reflect.get(target,key); 17 | // 判断shallow 直接返回res 18 | if(isShallow) return res; 19 | // 判断 res是不是一个Object 20 | if(isObject(res)){ 21 | return isReadonly? readonly(res) : reactive(res); 22 | } 23 | if(!isReadonly){ 24 | track(target,key); 25 | } 26 | return res; 27 | } 28 | } 29 | 30 | function createSetter(isReadonly = false){ 31 | return function set(target:any,key:any,value:any){ 32 | const res = Reflect.set(target, key, value); 33 | if(!isReadonly){ 34 | // trigger 触发依赖 35 | trigger(target, key); 36 | } 37 | return res; 38 | } 39 | } 40 | export const mutableHandlers = { 41 | get, 42 | set 43 | } 44 | export const readonlyHandlers = { 45 | get:readonlyGet, 46 | set(target:any,key:any){ 47 | console.warn(`${target} is readonly,could't set ${key}!`,target) 48 | return true; 49 | } 50 | } 51 | 52 | export const shallowReadonlyHandlers = Object.assign({},readonlyHandlers,{ 53 | get:shallowReadonlyGet 54 | }) 55 | 56 | // get => age(ref包裹) 返回 .value 57 | // 如果没有被ref包裹 返回 本身的值 58 | export const withRefHandlers = { 59 | get(target:any,key:any){ 60 | return unRef(Reflect.get(target,key)); 61 | }, 62 | set(target:any,key:any,value:any){ 63 | if(isRef(target[key]) && !isRef(value)){ 64 | return target[key].value = value; 65 | } else { 66 | return Reflect.set(target, key, value); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged,convert } from '../shared' 2 | import {ReactiveEffect} from './effect'; 3 | class ComputedRefImp{ 4 | private _getter; 5 | private _dirty : Boolean = true; 6 | private _value : any; 7 | private _effect: any 8 | constructor(getter:any){ 9 | this._getter = getter; 10 | 11 | this._effect = new ReactiveEffect(getter,()=>{ 12 | if(!this._dirty) this._dirty = true; 13 | }) 14 | } 15 | get value(){ 16 | // get 17 | // 当依赖的响应式对象的值发生改变 18 | // effect 19 | if(this._dirty){ 20 | this._dirty = false; 21 | this._value = this._effect.run(); 22 | } 23 | return this._value 24 | } 25 | } 26 | export function computed(getter:any){ 27 | return new ComputedRefImp(getter) 28 | } -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend }from '../shared'; 2 | let activeEffect:any; 3 | let shouldTrack = false; 4 | const targetMap = new Map(); 5 | export class ReactiveEffect{ 6 | private _fn: any; 7 | deps = []; 8 | active = true; 9 | onStop?: ()=> void; 10 | public scheduler:Function|undefined 11 | constructor(fn:any,scheduler?:Function){ 12 | this._fn = fn 13 | this.scheduler = scheduler; 14 | } 15 | run(){ 16 | if(!this.active){ 17 | return this._fn(); 18 | } 19 | shouldTrack = true; 20 | activeEffect = this; 21 | const result = this._fn(); 22 | shouldTrack = false 23 | activeEffect = undefined; 24 | return result 25 | } 26 | stop(){ 27 | if(this.active){ 28 | cleanupEffect(this); 29 | if(this.onStop){ 30 | this.onStop(); 31 | } 32 | this.active = false; 33 | } 34 | } 35 | } 36 | function cleanupEffect(effect:any){ 37 | effect.deps.forEach((dep:any) => { 38 | dep.delete(effect); 39 | }); 40 | effect.deps.length = 0; 41 | } 42 | export function track(target:any, key:any){ 43 | if(!isTracking()){ 44 | return 45 | } 46 | // target => key => dep 47 | let depsMap = targetMap.get(target); 48 | if (!depsMap){ 49 | depsMap = new Map(); 50 | targetMap.set(target, depsMap); 51 | } 52 | let dep = depsMap.get(key); 53 | if(!dep){ 54 | dep = new Set(); 55 | depsMap.set(key,dep); 56 | } 57 | trackEffects(dep) 58 | } 59 | export function trackEffects(dep:any){ 60 | // 判断dep是不是已经添加了这个activeEffect 61 | if(dep.has(activeEffect)) return 62 | dep.add(activeEffect); 63 | activeEffect.deps.push(dep); 64 | } 65 | export function trigger(target:any,key:any){ 66 | let depsMap = targetMap.get(target); 67 | let dep = depsMap.get(key); 68 | triggerEffects(dep); 69 | } 70 | export function triggerEffects(dep:any){ 71 | for(const effect of dep){ 72 | if(effect.scheduler){ 73 | effect.scheduler() 74 | }else{ 75 | effect.run(); 76 | } 77 | } 78 | } 79 | export function effect(fn:any, options:any = {}){ 80 | //fn 81 | const _effect = new ReactiveEffect(fn, options.scheduler); 82 | // options 83 | // Object.assign(_effect,options); 84 | //extend 85 | extend(_effect,options) 86 | _effect.run(); 87 | const runner:any = _effect.run.bind(_effect); 88 | runner.effect = _effect 89 | return runner; 90 | } 91 | export function stop(runner:any){ 92 | runner.effect.stop(); 93 | } 94 | export function isTracking(){ 95 | return shouldTrack && activeEffect !== undefined 96 | } 97 | 98 | -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- 1 | export { ref, proxyRefs } from "./ref"; 2 | -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "../shared"; 2 | import { mutableHandlers,readonlyHandlers,shallowReadonlyHandlers } from "./baseHandler"; 3 | export const enum ReactiveFlags { 4 | IS_REACTIVE = "__v_isReactive", 5 | IS_READONLY = "__v_isReadOnly" 6 | } 7 | export function reactive(raw:any) { 8 | return createActiveObject(raw, mutableHandlers) 9 | } 10 | export function readonly(raw:any){ 11 | return createActiveObject(raw,readonlyHandlers) 12 | } 13 | export function shallowReadonly(raw:any){ 14 | return createActiveObject(raw,shallowReadonlyHandlers); 15 | } 16 | export function isReactive(value:any){ 17 | return !!value[ReactiveFlags.IS_REACTIVE] 18 | } 19 | export function isReadonly(value:any){ 20 | return !!value[ReactiveFlags.IS_READONLY] 21 | } 22 | export function isProxy(value:any){ 23 | return isReactive(value) || isReadonly(value) 24 | } 25 | function createActiveObject(target: any, baseHandlers:any){ 26 | if(!isObject(target)){ 27 | console.warn(`target:${target} must be a Object!`) 28 | } 29 | return new Proxy(target,baseHandlers) 30 | } -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged,convert } from '../shared'; 2 | import {trackEffects, triggerEffects,isTracking} from './effect'; 3 | import {withRefHandlers} from './baseHandler'; 4 | class RefImp { 5 | private _value:any; 6 | public dep:any; 7 | private _rawValue; 8 | public __v_isRef = true; 9 | constructor(value:any){ 10 | this._rawValue = value; 11 | // 判断value是不是 一个对象 12 | this._value = convert(value); 13 | this.dep = new Set() 14 | } 15 | get value(){ 16 | trackRefValue(this); 17 | return this._value 18 | } 19 | set value(newValue){ 20 | // 判断value的值是否修改 对比的时候应该是两个Object对象而不是Proxy对象 21 | if(!hasChanged(this._rawValue,newValue)) return; 22 | this._rawValue = newValue; 23 | this._value = convert(newValue); 24 | triggerEffects(this.dep) 25 | return 26 | } 27 | } 28 | function trackRefValue(ref:any){ 29 | if(isTracking()){ 30 | trackEffects(ref.dep); 31 | } 32 | } 33 | export function ref(value:any){ 34 | return new RefImp(value) 35 | } 36 | export function isRef(ref:any){ 37 | return !!ref.__v_isRef; 38 | } 39 | export function unRef(ref:any){ 40 | return isRef(ref)? ref.value : ref; 41 | } 42 | export function proxyRefs(objectWithRef:any){ 43 | return new Proxy(objectWithRef,withRefHandlers) 44 | } -------------------------------------------------------------------------------- /src/reactivity/tests/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import {computed} from '../computed'; 2 | import {reactive} from '../reactive'; 3 | describe('computed',()=>{ 4 | it('happy path',()=>{ 5 | // ref 6 | // .value 7 | // 缓存 8 | const user = reactive({ 9 | age:1 10 | }) 11 | const age = computed(()=>{ 12 | return user.age; 13 | }) 14 | expect(age.value).toBe(user.age); 15 | }) 16 | it('should compute lazily',()=>{ 17 | const value = reactive({ 18 | foo:1 19 | }); 20 | const getter = jest.fn(() =>{ 21 | return value.foo; 22 | }) 23 | const cValue = computed(getter); 24 | //lazy 25 | expect(getter).not.toBeCalledTimes(1); 26 | 27 | expect(cValue.value).toBe(1); 28 | expect(getter).toBeCalledTimes(1); 29 | expect(cValue.value).toBe(1); 30 | expect(getter).toBeCalledTimes(1); 31 | value.foo = 2;// trigger => effect => get 重新执行 32 | expect(getter).toHaveBeenCalledTimes(1); 33 | expect(cValue.value).toBe(2); 34 | expect(getter).toHaveBeenCalledTimes(2); 35 | }) 36 | }) -------------------------------------------------------------------------------- /src/reactivity/tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import {reactive} from '../reactive'; 2 | import {effect,stop} from '../effect'; 3 | describe('effect',()=>{ 4 | it('happy path',()=>{ 5 | const user = reactive({ 6 | age:10 7 | }) 8 | let nextAge; 9 | effect(()=>{ 10 | nextAge = user.age + 2; 11 | }) 12 | expect(nextAge).toBe(12); 13 | 14 | // update 15 | user.age++; 16 | expect(nextAge).toBe(13); 17 | }) 18 | it('should return a runner when user call the effect',()=>{ 19 | //1. effect(fn) => function(runner) => fn => return 20 | let foo = 10; 21 | const runner = effect(()=>{ 22 | foo++; 23 | return 'foo runner'; 24 | }); 25 | expect(foo).toBe(11); 26 | const r = runner(); 27 | expect(foo).toBe(12); 28 | expect(r).toBe('foo runner'); 29 | }) 30 | 31 | it("scheduler", () => { 32 | // 1.通过effect的第二个参数scheduler的fn 33 | // 2.effect第一次执行的时候 还会执行fn 34 | // 3.当响应式对象set 即update的时候 不会执行 fn 而是执行scheduler 35 | // 4.如果当执行runner的时候会再次执行fn 36 | let dummy; 37 | let run: any; 38 | const scheduler = jest.fn(() => { 39 | run = runner; 40 | }); 41 | const obj = reactive({ foo: 1 }); 42 | const runner = effect( 43 | () => { 44 | dummy = obj.foo; 45 | }, 46 | { scheduler } 47 | ); 48 | expect(scheduler).not.toHaveBeenCalled(); 49 | expect(dummy).toBe(1); 50 | // should be called on first trigger 51 | obj.foo++; 52 | expect(scheduler).toHaveBeenCalledTimes(1); 53 | // // should not run yet 54 | expect(dummy).toBe(1); 55 | // // manually run 56 | run(); 57 | // // should have run 58 | expect(dummy).toBe(2); 59 | }); 60 | it('stop',()=>{ 61 | let dummy:any; 62 | const obj =reactive({prop:1}); 63 | const runner = effect(()=>{ 64 | dummy = obj.prop; 65 | }) 66 | obj.prop = 2; 67 | expect(dummy).toBe(2); 68 | stop(runner); 69 | obj.prop ++; 70 | 71 | expect(dummy).toBe(2); 72 | 73 | // stopped effect should still be manully callalbe 74 | runner(); 75 | expect(dummy).toBe(3); 76 | }) 77 | it('onstop',()=>{ 78 | const obj = reactive({ 79 | foo:1 80 | }) 81 | const onStop = jest.fn(); 82 | let dummy; 83 | const runner = effect(()=>{ 84 | dummy=obj.foo; 85 | }, { 86 | onStop 87 | }) 88 | 89 | stop(runner); 90 | expect(onStop).toBeCalledTimes(1); 91 | }) 92 | }) -------------------------------------------------------------------------------- /src/reactivity/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import {reactive,isReactive,isProxy} from '../reactive' 2 | describe('reactive',()=>{ 3 | it('happy path',()=>{ 4 | const original = {foo:1}; 5 | const observed = reactive(original); 6 | expect(observed).not.toBe(original); 7 | expect(observed.foo).toBe(1); 8 | expect(isReactive(original)).toBe(false); 9 | expect(isReactive(observed)).toBe(true); 10 | expect(isProxy(observed)).toBe(true); 11 | }) 12 | test('nested reactive',()=>{ 13 | const original = { 14 | nested:{ 15 | foo:1 16 | }, 17 | array:[{bar:1}] 18 | }; 19 | const observed = reactive(original); 20 | expect(isReactive(observed.nested)).toBe(true); 21 | expect(isReactive(observed.array)).toBe(true); 22 | expect(isReactive(observed.array[0])).toBe(true); 23 | }) 24 | }) -------------------------------------------------------------------------------- /src/reactivity/tests/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import {readonly,isReadonly,isProxy} from '../reactive' 2 | describe('readonly',()=>{ 3 | it('happy path',()=>{ 4 | // cant set 5 | const original = {foo:1,bar:2} 6 | const wraped = readonly(original); 7 | expect(original).not.toBe(wraped); 8 | expect(original.bar).toBe(2); 9 | }) 10 | it('should make nested values readonly',()=>{ 11 | const original = {foo:1,bar:{ baz:2}}; 12 | const wraped = readonly(original); 13 | expect(isReadonly(wraped)).toBe(true); 14 | expect(isReadonly(original)).toBe(false); 15 | expect(isReadonly(wraped.bar)).toBe(true); 16 | expect(isProxy(wraped)).toBe(true); 17 | }) 18 | it('should call console.warn warn when call set',()=>{ 19 | // console.warn() 20 | // mock 21 | console.warn = jest.fn() 22 | const user = readonly({ 23 | age:10 24 | }) 25 | user.age++; 26 | expect(console.warn).toBeCalled(); 27 | }) 28 | it('isReadOnly',()=>{ 29 | const user = {age:10} 30 | const readonlyUser = readonly({ 31 | age:10 32 | }) 33 | expect(isReadonly(user)).toBe(false); 34 | expect(isReadonly(readonlyUser)).toBe(true); 35 | }) 36 | }) -------------------------------------------------------------------------------- /src/reactivity/tests/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import {effect} from '../effect'; 2 | import {ref, isRef, unRef,proxyRefs} from '../ref'; 3 | import {reactive} from '../reactive'; 4 | describe('ref',()=>{ 5 | it("happy path",()=>{ 6 | const a = ref(1); 7 | expect(a.value).toBe(1); 8 | }) 9 | it('should be reactive',()=>{ 10 | const a = ref(1); 11 | let dummy; 12 | let calls= 0; 13 | effect(()=>{ 14 | calls++; 15 | dummy = a.value; 16 | }) 17 | expect(dummy).toBe(1); 18 | expect(calls).toBe(1); 19 | a.value = 2; 20 | expect(calls).toBe(2); 21 | expect(dummy).toBe(2); 22 | a.value = 2; 23 | // same value should't trigger; 24 | expect(calls).toBe(2); 25 | expect(dummy).toBe(2); 26 | }) 27 | it('should make nested properties reactive',()=>{ 28 | const a = ref({ 29 | count:1 30 | }) 31 | expect(a.value.count).toBe(1); 32 | let dummy; 33 | effect(()=>{ 34 | dummy = a.value.count; 35 | }) 36 | expect(dummy).toBe(1); 37 | a.value.count++; 38 | expect(dummy).toBe(2); 39 | }) 40 | // isRef unRef 41 | it('is ref',()=>{ 42 | const a = ref(1); 43 | const user = reactive({ 44 | age:1 45 | }) 46 | expect(isRef(a)).toBe(true); 47 | expect(isRef(user)).toBe(false); 48 | expect(isRef(1)).toBe(false); 49 | }) 50 | it('should be unRef',()=>{ 51 | const a = ref(1); 52 | expect(isRef(a)).toBe(true); 53 | const b = unRef(a); 54 | expect(isRef(b)).toBe(false); 55 | }) 56 | 57 | it('proxyRefs',()=>{ 58 | const user = { 59 | age:ref(10), 60 | name:'Tom' 61 | } 62 | // get 63 | const proxyUser = proxyRefs(user); 64 | expect(user.age.value).toBe(10); 65 | expect(proxyUser.age).toBe(10); 66 | expect(proxyUser.name).toBe('Tom'); 67 | //set 68 | proxyUser.age = 11; 69 | expect(proxyUser.age).toBe(11); 70 | proxyUser.age = ref(12); 71 | expect(user.age.value).toBe(12); 72 | expect(proxyUser.age).toBe(12); 73 | }) 74 | }) -------------------------------------------------------------------------------- /src/reactivity/tests/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowReadonly,isReadonly,isProxy } from "../reactive"; 2 | 3 | describe('shallowReadonly',()=>{ 4 | it('should not make non-reactive properties reactive',()=>{ 5 | const props = shallowReadonly({n:{foo:1}}); 6 | console.warn = jest.fn(); 7 | expect(isReadonly(props)).toBe(true); 8 | expect(isReadonly(props.n)).toBe(false); 9 | expect(isProxy(props)).toBe(true); 10 | }) 11 | it('should be call console.warn when call set',()=>{ 12 | const props = shallowReadonly({n:{foo:1}}); 13 | console.warn = jest.fn(); 14 | props.n = {foo:2}; 15 | expect(console.warn).toBeCalled(); 16 | }) 17 | }) -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "./component"; 2 | export function provide(key,value){ 3 | // set 4 | const currentInstance:any = getCurrentInstance(); 5 | if(currentInstance){ 6 | let {provides} = currentInstance; 7 | const parentProvides = currentInstance.parent.provides; 8 | // 把provide的原型指向parentProvides 9 | // init 的时候执行 10 | if (provides === parentProvides){ 11 | provides = currentInstance.provides = Object.create(parentProvides); 12 | } 13 | provides[key] = value; 14 | } 15 | } 16 | export function inject(key,defaultValue){ 17 | // get 18 | const currentInstance:any = getCurrentInstance(); 19 | if(currentInstance){ 20 | const parentProvides = currentInstance.parent.provides; 21 | if( key in parentProvides){ 22 | return parentProvides[key]; 23 | }else if (defaultValue){ 24 | if(typeof defaultValue === 'function'){ 25 | return defaultValue() 26 | } 27 | return defaultValue; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { initProps } from "./componentProps"; 2 | import { initSlots } from "./componentSlots"; 3 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance"; 4 | import { shallowReadonly } from "../reactivity/reactive"; 5 | import { emit } from "./componetEmit"; 6 | import { proxyRefs } from "../reactivity"; 7 | 8 | export function createComponentInstance(vnode: any, parent: any) { 9 | const instance = { 10 | vnode, 11 | type: vnode.type, 12 | setupState: {}, 13 | props: {}, 14 | slots: {}, 15 | provides: parent ? parent.provides : {}, 16 | parent, 17 | isMounted: false, 18 | emit: () => {}, 19 | }; 20 | 21 | // 初始化赋值emit 22 | instance.emit = emit.bind(null, instance) as any; 23 | return instance; 24 | } 25 | export function setupInstance(instance: any) { 26 | initProps(instance, instance.vnode.props); 27 | initSlots(instance, instance.vnode.children); 28 | setupStatefulComponent(instance); 29 | } 30 | export function setupStatefulComponent(instance: any) { 31 | const Component = instance.type; 32 | 33 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers); 34 | 35 | const { setup } = Component; 36 | if (setup) { 37 | setCurrentInstance(instance); 38 | const setupResult = 39 | setup && setup(shallowReadonly(instance.props), { emit: instance.emit }); 40 | setCurrentInstance(null); 41 | handleSetupResult(instance, setupResult); 42 | } 43 | } 44 | export function handleSetupResult(instance: any, setupResult: any) { 45 | // Object 46 | if (typeof setupResult === "object") { 47 | instance.setupState = proxyRefs(setupResult); 48 | } 49 | 50 | finishCompoentSetup(instance); 51 | // function 52 | } 53 | 54 | function finishCompoentSetup(instance: any) { 55 | const Component = instance.type; 56 | // compile 57 | if (compiler && !Component.render) { 58 | if (Component.template) { 59 | Component.render = compiler(Component.template); 60 | } 61 | } 62 | instance.render = Component.render; 63 | } 64 | 65 | let currentInstance = null; 66 | 67 | // getCurrentInstance 68 | export function getCurrentInstance() { 69 | return currentInstance; 70 | } 71 | 72 | function setCurrentInstance(instance: null) { 73 | currentInstance = instance; 74 | } 75 | 76 | let compiler; 77 | 78 | export function registerRuntimeComplier(_compiler) { 79 | compiler = _compiler; 80 | } 81 | -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance: any,rawProps: any){ 2 | // attrs 3 | instance.props = rawProps || {}; 4 | } -------------------------------------------------------------------------------- /src/runtime-core/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from "../shared"; 2 | 3 | const publicPropertiesMap = { 4 | $el: (i) => i.vnode.el, 5 | $slots: i => i.slots, 6 | $props: i => i.props 7 | } 8 | 9 | export const PublicInstanceProxyHandlers = { 10 | get({_:instance}: any, key: string){ 11 | // setupState 12 | const {setupState,props} = instance; 13 | if(hasOwn(setupState,key)){ 14 | return setupState[key]; 15 | }else if (hasOwn(props,key)){ 16 | return props[key]; 17 | } 18 | 19 | const publicGetter = publicPropertiesMap[key]; 20 | if(publicGetter){ 21 | return publicGetter(instance); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/shapeFlags"; 2 | 3 | export function initSlots(instance, children){ 4 | // instance.slots = Array.isArray(children) ? children : [children]; 5 | const {vnode} = instance; 6 | if(vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN){ 7 | normalizeObjectSlots(instance.slots,children); 8 | } 9 | } 10 | export function normalizeObjectSlots(slots,children){ 11 | for (const key in children) { 12 | const value = children[key]; 13 | slots[key] = (props) => normalizeSlotValue(value(props)); 14 | } 15 | } 16 | export function normalizeSlotValue(value){ 17 | return Array.isArray(value) ? value : [value]; 18 | } -------------------------------------------------------------------------------- /src/runtime-core/componentUpdateUtils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUpdateComponent(prevVNode,nextVNode){ 2 | const {props:prevProps} = prevVNode; 3 | const {props:nextProps} = nextVNode; 4 | for (const key in nextProps) { 5 | if (nextProps[key] !== prevProps[key]) { 6 | return true; 7 | } 8 | } 9 | return false; 10 | } -------------------------------------------------------------------------------- /src/runtime-core/componetEmit.ts: -------------------------------------------------------------------------------- 1 | import {toHandleKey,cameLize} from '../shared' 2 | export function emit(instance,event,...args){; 3 | // instance.props => event 4 | const { props } = instance; 5 | 6 | const handlerName = toHandleKey(event); 7 | const handler = props[cameLize(handlerName)]; 8 | handler && handler(...args); 9 | } -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | 3 | export function createAppAPI (render){ 4 | return function createApp(rootComponent:any){ 5 | return { 6 | mount(rootContainer:any){ 7 | //先转化为虚拟节点vnode 8 | // component => vnode 9 | const vnode = createVNode(rootComponent); 10 | render(vnode, rootContainer) 11 | } 12 | } 13 | } 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | export function h(type:String, props?:Object, children?:any){ 3 | return createVNode(type, props, children); 4 | } -------------------------------------------------------------------------------- /src/runtime-core/helpers/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import {createVNode , Fragment} from '../vnode'; 2 | export function renderSlots(slots,name, props){ 3 | const slot = slots[name]; 4 | if(slot){ 5 | // function 6 | if(typeof slot === 'function'){ 7 | //children 不可以有 array 8 | // 只需要把 children 9 | return createVNode(Fragment, {}, slot(props)) 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | export { createAppAPI } from "./createApp"; 2 | export { h } from "./h"; 3 | export { renderSlots } from "./helpers/renderSlots"; 4 | export { createTextVNode, createElementVNode } from "./vnode"; 5 | export { getCurrentInstance, registerRuntimeComplier } from "./component"; 6 | export { inject, provide } from "./apiInject"; 7 | export { createRender } from "./render"; 8 | export { nextTick } from "./scheduler"; 9 | export { toDisplayString } from "../shared"; 10 | // 模块适应vue设计层级 compile 与 runtime-dom => runtime-core => reactivity 11 | export * from "../reactivity"; 12 | -------------------------------------------------------------------------------- /src/runtime-core/render.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../reactivity/effect"; 2 | import { ShapeFlags } from "../shared/shapeFlags"; 3 | import { createComponentInstance, setupInstance } from "./component"; 4 | import { createAppAPI } from "./createApp"; 5 | import { Fragment, Text } from "./vnode"; 6 | import { shouldUpdateComponent } from "./componentUpdateUtils"; 7 | import { queueJob } from "./scheduler"; 8 | 9 | export function createRender(options) { 10 | const { 11 | createElement: hostCreateElement, 12 | patchProp: hostPatchProp, 13 | insert: hostInsert, 14 | remove: hostRemove, 15 | setElementText: hostSetElementText, 16 | setElementArray: hostSetElementArray, 17 | } = options; 18 | 19 | function render(vnode: any, container: any) { 20 | // shapeFlags 21 | // patch递归 22 | // 判断是不是一个element类型 23 | patch(null, vnode, container); 24 | } 25 | 26 | function patch( 27 | n1, 28 | n2: any, 29 | container: any, 30 | parentComponent: any = null, 31 | anchor: any = null 32 | ) { 33 | // todo 判断是不是一个element 34 | // 处理组件 35 | // shapeflag 判断 36 | const { type, shapeFlag } = n2; 37 | // Fragment => 只渲染children 38 | switch (type) { 39 | case Fragment: 40 | processFragment(n1, n2, container, parentComponent); 41 | break; 42 | case Text: 43 | processText(n1, n2, container); 44 | break; 45 | default: 46 | if (shapeFlag & ShapeFlags.ELEMENT) { 47 | processElement(n1, n2, container, parentComponent, anchor); 48 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 49 | processComponent(n1, n2, container, parentComponent); 50 | } 51 | break; 52 | } 53 | } 54 | 55 | function processElement( 56 | n1, 57 | n2: any, 58 | container: any, 59 | parentComponent, 60 | anchor 61 | ) { 62 | if (!n1) { 63 | // init 64 | mountElement(n2, container, parentComponent, anchor); 65 | } else { 66 | // update 67 | patchElement(n1, n2, container, parentComponent, anchor); 68 | } 69 | } 70 | const EMPTY_OBJ = {}; 71 | function patchElement(n1, n2, container, parentComponent, anchor) { 72 | const oldProps = n1.props || EMPTY_OBJ; 73 | const newProps = n2.props || EMPTY_OBJ; 74 | const el = (n2.el = n1.el); 75 | patchChildren(n1, n2, el, parentComponent, anchor); 76 | patchProps(el, oldProps, newProps); 77 | } 78 | 79 | function patchChildren(n1, n2, container, parentComponent, anchor) { 80 | const prevShapeFlag = n1.shapeFlag; 81 | const nextShapeFlag = n2.shapeFlag; 82 | const c1 = n1.children; 83 | const c2 = n2.children; 84 | if (nextShapeFlag & ShapeFlags.TEXT_CHILDREN) { 85 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { 86 | // array => text 87 | // 1.把老的children清空 88 | unmountChildren(c1); 89 | // 2.set 新的textchildren 90 | // 如果 n1.children !== n2.children 则直接把n2的文本值设置为container的textContent 91 | // text => text 同时需执行以下 92 | hostSetElementText(container, c2); 93 | } else { 94 | hostSetElementText(container, c2); 95 | } 96 | } else { 97 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { 98 | // array => text 99 | // 1.把容器的文本内容清空 100 | hostSetElementText(container, ""); 101 | mountChildren(c2, container, parentComponent); 102 | } else { 103 | // array diff => array 104 | patchKeyedChildren(c1, c2, container, parentComponent, anchor); 105 | } 106 | } 107 | } 108 | 109 | function patchKeyedChildren( 110 | c1, 111 | c2, 112 | container, 113 | parentComponent, 114 | parentAnchor 115 | ) { 116 | console.log("patch child"); 117 | let i = 0; 118 | let e1 = c1.length - 1; 119 | let e2 = c2.length - 1; 120 | const l2 = c2.length; 121 | // 左侧对比 122 | while (i <= e1 && i <= e2) { 123 | const n1 = c1[i]; 124 | const n2 = c2[i]; 125 | if (isSameVNodeType(n1, n2)) { 126 | patch(n1, n2, container, parentComponent); 127 | } else { 128 | break; 129 | } 130 | i++; 131 | } 132 | // 右侧对比 133 | while (i <= e1 && i <= e2) { 134 | const n1 = c1[e1]; 135 | const n2 = c2[e2]; 136 | if (isSameVNodeType(n1, n2)) { 137 | patch(n1, n2, container, parentComponent); 138 | } else { 139 | break; 140 | } 141 | e1--; 142 | e2--; 143 | } 144 | if (i > e1) { 145 | // 新的比老的多 146 | if (i <= e2) { 147 | // 如果是这种情况的话就说明 e2 也就是新节点的数量大于旧节点的数量 148 | // 也就是说新增了 vnode 149 | // 应该循环 c2 150 | // 锚点的计算:新的节点有可能需要添加到尾部,也可能添加到头部,所以需要指定添加的问题 151 | // 要添加的位置是当前的位置(e2 开始)+1 152 | // 因为对于往左侧添加的话,应该获取到 c2 的第一个元素 153 | // 所以我们需要从 e2 + 1 取到锚点的位置 154 | const nextPos = e2 + 1; 155 | const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor; 156 | while (i <= e2) { 157 | console.log(`需要新创建一个 vnode: ${c2[i].key}`); 158 | patch(null, c2[i], container, parentComponent, anchor); 159 | i++; 160 | } 161 | } 162 | } else if (i > e2) { 163 | // 新的比老的少 164 | const anchorIndex = i + e1 - e2; 165 | while (i < anchorIndex) { 166 | hostRemove(c1[i].el); 167 | i++; 168 | } 169 | } else { 170 | // 中间对比 171 | let s1 = i; 172 | let s2 = i; 173 | let isPatchedCount = 0; 174 | const toBePatched = e2 - s2 + 1; 175 | const keyToNewIndexMap = new Map(); 176 | const newIndexToOldIndexMap = new Array(toBePatched); 177 | let moved = false; 178 | let maxNewIndexSoFar = 0; 179 | for (let i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0; 180 | for (let i = s2; i <= e2; i++) { 181 | const nextChild = c2[i]; 182 | keyToNewIndexMap.set(nextChild.key, i); 183 | } 184 | for (let i = s1; i <= e1; i++) { 185 | const prevChild = c1[i]; 186 | if (isPatchedCount >= toBePatched) { 187 | hostRemove(prevChild.el); 188 | continue; 189 | } 190 | let newIndex; 191 | if (prevChild.key != null) { 192 | newIndex = keyToNewIndexMap.get(prevChild.key); 193 | } else { 194 | for (let j = s2; j <= e2; j++) { 195 | if (isSameVNodeType(prevChild, c2[j])) { 196 | newIndex = j; 197 | break; 198 | } 199 | } 200 | } 201 | if (newIndex === undefined) { 202 | hostRemove(prevChild.el); 203 | } else { 204 | if (newIndex >= maxNewIndexSoFar) { 205 | maxNewIndexSoFar = newIndex; 206 | } else { 207 | moved = true; 208 | } 209 | newIndexToOldIndexMap[newIndex - s2] = i + 1; 210 | patch(prevChild, c2[newIndex], container, parentComponent, null); 211 | isPatchedCount++; 212 | } 213 | } 214 | const increasingNewIndexSequence = moved 215 | ? getSequence(newIndexToOldIndexMap) 216 | : []; 217 | let j = increasingNewIndexSequence.length - 1; 218 | for (let i = toBePatched - 1; i >= 0; i--) { 219 | const nextIndex = i + s2; 220 | const nextChild = c2[nextIndex]; 221 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null; 222 | if (newIndexToOldIndexMap[i] === 0) { 223 | patch(null, nextChild, container, parentComponent, anchor); 224 | } else if (moved) { 225 | // 如果判断需要moved 226 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 227 | console.log(anchor); 228 | hostInsert(nextChild.el, container, anchor); 229 | } else { 230 | j--; 231 | } 232 | } 233 | } 234 | } 235 | } 236 | 237 | // 获取最长递增子序列算法 238 | function getSequence(arr: number[]): number[] { 239 | const p = arr.slice(); 240 | const result = [0]; 241 | let i, j, u, v, c; 242 | const len = arr.length; 243 | for (i = 0; i < len; i++) { 244 | const arrI = arr[i]; 245 | if (arrI !== 0) { 246 | j = result[result.length - 1]; 247 | if (arr[j] < arrI) { 248 | p[i] = j; 249 | result.push(i); 250 | continue; 251 | } 252 | u = 0; 253 | v = result.length - 1; 254 | while (u < v) { 255 | c = (u + v) >> 1; 256 | if (arr[result[c]] < arrI) { 257 | u = c + 1; 258 | } else { 259 | v = c; 260 | } 261 | } 262 | if (arrI < arr[result[u]]) { 263 | if (u > 0) { 264 | p[i] = result[u - 1]; 265 | } 266 | result[u] = i; 267 | } 268 | } 269 | } 270 | u = result.length; 271 | v = result[u - 1]; 272 | while (u-- > 0) { 273 | result[u] = v; 274 | v = p[v]; 275 | } 276 | return result; 277 | } 278 | 279 | function isSameVNodeType(n1, n2) { 280 | return n1.type === n2.type && n1.key === n2.key; 281 | } 282 | 283 | function unmountChildren(children) { 284 | for (let i = 0; i < children.length; i++) { 285 | const el = children[i].el; 286 | // remove 287 | hostRemove(el); 288 | } 289 | } 290 | function patchProps(el, oldProps, newProps) { 291 | // 两个props Object判断不相等? 292 | if (oldProps !== newProps) { 293 | for (const key in newProps) { 294 | const prevProp = oldProps[key] ? oldProps[key] : null; 295 | const nextProp = newProps[key]; 296 | if (prevProp !== nextProp) { 297 | hostPatchProp(el, key, prevProp, nextProp); 298 | } 299 | } 300 | if (oldProps !== EMPTY_OBJ) { 301 | for (const key in oldProps) { 302 | if (!(key in newProps)) { 303 | hostPatchProp(el, key, oldProps[key], null); 304 | } 305 | } 306 | } 307 | } 308 | } 309 | 310 | function processComponent(n1, n2: any, container: any, parentComponent) { 311 | if (!n1) { 312 | mountComponent(n2, container, parentComponent); 313 | } else { 314 | updateComponent(n1, n2); 315 | } 316 | } 317 | 318 | function updateComponent(n1, n2) { 319 | const instance = (n2.component = n1.component); 320 | if (shouldUpdateComponent(n1, n2)) { 321 | console.log("组件更新", n1, n2); 322 | instance.next = n2; 323 | instance.update(); 324 | } else { 325 | n2.el = n1.el; 326 | n2.vnode = n2; 327 | } 328 | } 329 | 330 | function updateComponentPreRender(instance, nextVNode) { 331 | instance.vnode = nextVNode; 332 | instance.next = null; 333 | instance.props = nextVNode.props; 334 | } 335 | 336 | function processFragment(n1, n2: any, container: any, parentComponent) { 337 | // Implement 338 | mountChildren(n2.children, container, parentComponent); 339 | } 340 | 341 | function processText(n1, n2: any, container) { 342 | const { children } = n2; 343 | const textNode = (n2.el = document.createTextNode(children)); 344 | container.append(textNode); 345 | } 346 | 347 | function mountElement(vnode: any, container: any, parentComponent, anchor) { 348 | // canvas 349 | // new Element() 350 | // createElement() 351 | const el = (vnode.el = hostCreateElement(vnode.type)); 352 | // const el = (vnode.el = document.createElement(vnode.type)); 353 | 354 | //children: string array 355 | const { children, shapeFlag } = vnode; 356 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 357 | el.textContent = children; 358 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 359 | mountChildren(children, el, parentComponent); 360 | } 361 | // props 362 | const { props } = vnode; 363 | // patch Prop 364 | 365 | for (const key in props) { 366 | const val = props[key]; 367 | hostPatchProp(el, key, null, val); 368 | } 369 | 370 | // container.append(el); 371 | hostInsert(el, container, anchor); 372 | } 373 | 374 | function mountChildren(children: any[], container: any, parentComponent) { 375 | children.forEach((child: any) => { 376 | patch(null, child, container, parentComponent); 377 | }); 378 | } 379 | 380 | function mountComponent( 381 | initalVNode: any, 382 | container: any, 383 | parentComponent: any 384 | ) { 385 | const instance = (initalVNode.component = createComponentInstance( 386 | initalVNode, 387 | parentComponent 388 | )); 389 | setupInstance(instance); 390 | setupRenderEffect(instance, initalVNode, container); 391 | } 392 | 393 | function setupRenderEffect(instance: any, initalVNode, container: any) { 394 | // 拆分更新与初始化 395 | instance.update = effect( 396 | () => { 397 | if (!instance.isMounted) { 398 | console.log("init"); 399 | const { proxy } = instance; 400 | const subTree = (instance.subTree = instance.render.call( 401 | proxy, 402 | proxy 403 | )); 404 | // vnode => patch 405 | // vnode => element => mountElement 406 | patch(null, subTree, container, instance); 407 | // element => mounted 408 | initalVNode.el = subTree.el; 409 | instance.isMounted = true; 410 | } else { 411 | console.log("update"); 412 | const { proxy, next, vnode } = instance; 413 | if (next) { 414 | next.el = vnode.el; 415 | updateComponentPreRender(instance, next); 416 | } 417 | const prevSubTree = instance.subTree; 418 | const subTree = (instance.subTree = instance.render.call( 419 | proxy, 420 | proxy 421 | )); 422 | // vnode => patch 423 | // vnode => element => mountElement 424 | patch(prevSubTree, subTree, container, instance); 425 | // element => mounted 426 | initalVNode.el = subTree.el; 427 | } 428 | }, 429 | { 430 | scheduler() { 431 | console.log("update-----scheduler"); 432 | queueJob(instance.update); 433 | }, 434 | } 435 | ); 436 | } 437 | 438 | return { 439 | createApp: createAppAPI(render), 440 | }; 441 | } 442 | -------------------------------------------------------------------------------- /src/runtime-core/scheduler.ts: -------------------------------------------------------------------------------- 1 | const queue:any[]= []; 2 | 3 | let isFlushPending = false; 4 | const p = Promise.resolve(); 5 | export function nextTick(fn){ 6 | return fn ? p.then(fn) : p; 7 | } 8 | export function queueJob(job){ 9 | if(!queue.includes(job)){ 10 | queue.push(job); 11 | } 12 | queueFlash(); 13 | } 14 | function queueFlash(){ 15 | if(isFlushPending) return; 16 | isFlushPending = true; 17 | nextTick( 18 | flushJobs 19 | ) 20 | } 21 | function flushJobs(){ 22 | isFlushPending = false; 23 | let job; 24 | while(job = queue.shift()){ 25 | job && job(); 26 | } 27 | } -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags, getShapeFlag } from "../shared/shapeFlags"; 2 | export { createVNode as createElementVNode }; 3 | export const Fragment = Symbol("Fragment"); 4 | export const Text = Symbol("Text"); 5 | export function createVNode(type: any, props?: any, children?: any) { 6 | const VNode = { 7 | el: null, 8 | type, 9 | props: props || {}, 10 | children, 11 | component: null, 12 | next: null, 13 | key: props?.key, 14 | shapeFlag: getShapeFlag(type), 15 | }; 16 | // children? 17 | if (typeof children === "string") { 18 | VNode.shapeFlag |= ShapeFlags.TEXT_CHILDREN; 19 | } else if (Array.isArray(children)) { 20 | VNode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN; 21 | } 22 | if (VNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 23 | if (typeof children === "object") { 24 | VNode.shapeFlag |= ShapeFlags.SLOT_CHILDREN; 25 | } 26 | } 27 | return VNode; 28 | } 29 | 30 | export function createTextVNode(text: string) { 31 | return createVNode(Text, {}, text); 32 | } 33 | -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import {createRender} from '../runtime-core'; 2 | import { isOn } from "../shared/index"; 3 | function createElement(type) { 4 | return document.createElement(type); 5 | } 6 | 7 | function patchProp(el, key, prevVal, nextVal){ 8 | if(isOn(key)){ 9 | const event = key.slice(2).toLocaleLowerCase(); 10 | el.addEventListener(event,()=>{ 11 | nextVal(); 12 | }); 13 | }else{ 14 | if(nextVal === undefined || nextVal === null){ 15 | el.removeAttribute(key); 16 | }else{ 17 | el.setAttribute(key,nextVal); 18 | } 19 | } 20 | } 21 | 22 | function insert(child, parent, anchor:any = null){ 23 | console.log('insert',child); 24 | parent.insertBefore(child,anchor); 25 | } 26 | 27 | function remove(child){ 28 | const parent = child.parentNode; 29 | if(parent){ 30 | parent.removeChild(child); 31 | } 32 | } 33 | 34 | function setElementText(el, text){ 35 | el.textContent = text; 36 | } 37 | 38 | function setElementArray(el, children){ 39 | console.log(el); 40 | console.log(children); 41 | } 42 | const render:any= createRender({ 43 | createElement, 44 | patchProp, 45 | insert, 46 | remove, 47 | setElementText, 48 | setElementArray 49 | }) 50 | 51 | export function createApp(...args){ 52 | return render.createApp(...args) 53 | } 54 | 55 | export * from '../runtime-core'; -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from "../reactivity/reactive"; 2 | 3 | export * from "./toDisplayString"; 4 | 5 | export const extend = Object.assign; 6 | 7 | export const isObject = (val: any) => { 8 | return val !== null && typeof val === "object"; 9 | }; 10 | 11 | export const isString = (val: any) => typeof val === "string"; 12 | 13 | export const hasChanged = (val: any, newVal: any) => { 14 | return !Object.is(val, newVal); 15 | }; 16 | 17 | export const convert = (newValue: any) => { 18 | return isObject(newValue) ? reactive(newValue) : newValue; 19 | }; 20 | export const isOn = (event: string) => { 21 | return /^on[A-Z]/.test(event); 22 | }; 23 | 24 | export const hasOwn = (properties, key) => { 25 | return Object.prototype.hasOwnProperty.call(properties, key); 26 | }; 27 | // TPP 先写一个特定的行为 再重构成一个通用的行为 28 | // add => Add 29 | export const capitalize = (str: string) => { 30 | return str.charAt(0).toUpperCase() + str.slice(1); 31 | }; 32 | export const toHandleKey = (str: string) => { 33 | return str ? "on" + capitalize(str) : ""; 34 | }; 35 | // add-foo => addFoo 36 | export const cameLize = (str: string) => { 37 | return str.replace(/-(\w)/g, (_, c: string) => { 38 | return c ? c.toUpperCase() : ""; 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /src/shared/shapeFlags.ts: -------------------------------------------------------------------------------- 1 | export const enum ShapeFlags{ 2 | ELEMENT = 1, 3 | STATEFUL_COMPONENT = 1 << 1, 4 | TEXT_CHILDREN = 1 << 2, 5 | ARRAY_CHILDREN = 1 << 3, 6 | SLOT_CHILDREN = 1 << 4 7 | } 8 | export function getShapeFlag(type: String){ 9 | return typeof type === "string"? ShapeFlags.ELEMENT : ShapeFlags.STATEFUL_COMPONENT 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/toDisplayString.ts: -------------------------------------------------------------------------------- 1 | export function toDisplayString(value) { 2 | return String(value); 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "target": "es2016", 7 | "module": "esnext", 8 | "noImplicitAny": false, 9 | "removeComments": false, 10 | "preserveConstEnums": true, 11 | "sourceMap": true, 12 | "downlevelIteration": true, 13 | "lib": ["es6", "DOM"] 14 | }, 15 | "include": ["src/index.ts", "src/global.d.ts"] 16 | } 17 | --------------------------------------------------------------------------------