├── .gitignore ├── README.md ├── babel.config.js ├── example ├── apiInject │ ├── App.js │ └── index.html ├── compiler-base │ ├── App.js │ ├── index.html │ └── main.js ├── componentEmit │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentSlot │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentUpdate │ ├── App.js │ ├── Child.js │ ├── index.html │ └── main.js ├── currentInstance │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── customRenderer │ ├── App.js │ ├── index.html │ └── main.js ├── helloWorld │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── nextTicker │ ├── App.js │ ├── index.html │ └── main.js ├── patchChildren │ ├── App.js │ ├── ArrayToArray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js └── update │ ├── App.js │ ├── index.html │ └── main.js ├── lib ├── guide-mini-vue.cjs.js └── guide-mini-vue.esm.js ├── md ├── customRender.md └── tag.md ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── 112.js ├── compiler-core │ ├── src │ │ ├── ast.ts │ │ ├── codegen.ts │ │ ├── compile.ts │ │ ├── index.ts │ │ ├── parse.ts │ │ ├── runtimeHelpers.ts │ │ ├── transform.ts │ │ ├── transforms │ │ │ ├── transformElement.ts │ │ │ ├── transformExpression.ts │ │ │ └── transformText.ts │ │ └── utils.ts │ └── tests │ │ ├── __snapshots__ │ │ └── codegen.spec.ts.snap │ │ ├── codegen.spec.ts │ │ ├── parse.spec.ts │ │ └── transform.spec.ts ├── index.ts ├── reactivity │ ├── baseHandlers.ts │ ├── computed.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ └── tests │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reaadonly.spec.ts │ │ ├── reactive.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentPublicInstance.ts │ ├── componentSlots.ts │ ├── componentUpdateUtils.ts │ ├── createApp.ts │ ├── h.ts │ ├── helpers │ │ └── renderSolts.ts │ ├── index.ts │ ├── renderer.ts │ ├── scheduler.ts │ └── vnode.ts ├── runtime-dom │ └── index.ts └── shared │ ├── ShapeFlags.ts │ ├── index.ts │ └── toDisplayString.ts ├── tsconfig.json ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 给特定的某个 commit 版本打标签,比如现在某次提交的 id 为 039bf8b 2 | 3 | - git tag v1.0.0 039bf8b 4 | - git tag v1.0.0 -m "add tags information" 039bf8b 5 | - git tag v1.0.0 039bf8b -m "add tags information" 6 | 7 | ### 删除本地某个标签 8 | 9 | - git tag --delete v1.0.0 10 | - git tag -d v1.0.0 11 | - git tag --d v1.0.0 12 | 13 | ### 删除远程的某个标签 14 | 15 | - git push -d origin v1.0.0 16 | - git push --delete origin v1.0.0 17 | - git push origin -d v1.0.0 18 | - git push origin --delete v1.0.0 19 | - git push origin :v1.0.0 20 | 21 | ### 将本地标签一次性推送到远程 注意 这并不代表 push 代码,代码需要单独的 git push 22 | 23 | - git push --tag 24 | 25 | ``` 26 | it("happy path", () => { 27 | const user = reactive({ 28 | age:10 29 | }) 30 | let nextAge 31 | effect(() => { 32 | nextAge = user.age + 1 33 | }) 34 | expect(nextAge).toBe(11) 35 | ``` 36 | -------------------------------------------------------------------------------- /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 | // 组件 provide 和 inject 功能 2 | import { h, provide, inject } from "../../lib/guide-mini-vue.esm.js"; 3 | 4 | const Provider = { 5 | name: "Provider", 6 | setup() { 7 | provide("foo", "fooOne11111"); 8 | provide("bar", "barVal1111"); 9 | }, 10 | render() { 11 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]); 12 | }, 13 | }; 14 | 15 | const ProviderTwo = { 16 | name: "ProviderTwo", 17 | setup() { 18 | provide("foo", "fooTwo22222"); 19 | const foo = inject("foo"); 20 | 21 | return { 22 | foo, 23 | }; 24 | }, 25 | render() { 26 | return h("div", {}, [ 27 | h("p", {}, `ProviderTwo foo:${this.foo}`), 28 | h(Consumer), 29 | ]); 30 | }, 31 | }; 32 | 33 | const Consumer = { 34 | name: "Consumer", 35 | setup() { 36 | const foo = inject("foo"); 37 | const bar = inject("bar"); 38 | // const baz = inject("baz", "bazDefault"); 39 | const baz = inject("baz", () => "bazDefault"); 40 | 41 | return { 42 | foo, 43 | bar, 44 | baz, 45 | }; 46 | }, 47 | 48 | render() { 49 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar}-${this.baz}`); 50 | }, 51 | }; 52 | 53 | export default { 54 | name: "App", 55 | setup() {}, 56 | render() { 57 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]); 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/compiler-base/App.js: -------------------------------------------------------------------------------- 1 | import { ref } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const App = { 4 | name: "App", 5 | template: `
hi,{{count}}, {{message}}
`, 6 | // template: `
hi,{{message}}
`, 7 | setup() { 8 | const count = (window.count = ref(1)); 9 | return { 10 | count, 11 | message: "mini-vue", 12 | }; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /example/compiler-base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/compiler-base/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { 2 | h 3 | } from '../../lib/guide-mini-vue.esm.js' 4 | import { 5 | Foo 6 | } from './Foo.js' 7 | 8 | export const APP = { 9 | 10 | setup() { 11 | return { 12 | msg: 'this setState' 13 | } 14 | }, 15 | render() { 16 | return h('div', {}, [ 17 | h('div', {}, 'app'), 18 | h(Foo, { 19 | onAdd(a,b) { 20 | console.log('onAdd',a,b); 21 | }, 22 | onAddFoo (a,b) { 23 | console.log('onAddFoo',a,b); 24 | } 25 | }) 26 | ]) 27 | }, 28 | } -------------------------------------------------------------------------------- /example/componentEmit/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | export const Foo = { 3 | setup(props, { emit }) { 4 | const emitAdd = () => { 5 | emit("add",1,6) 6 | emit("add-foo",2,5) 7 | }; 8 | return { 9 | emitAdd, 10 | }; 11 | }, 12 | render() { 13 | const btn = h( 14 | "button", 15 | { 16 | onClick: this.emitAdd, 17 | }, 18 | "emitAdd" 19 | ); 20 | const foo = h("p", {}, "foo"); 21 | 22 | return h("div", {}, [foo, btn]); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/guide-mini-vue.esm.js' 2 | const rootContainer = document.querySelector('#app') 3 | import { APP } from './App.js' 4 | createApp(APP).mount(rootContainer) -------------------------------------------------------------------------------- /example/componentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { h,createTextVNode } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const APP = { 5 | render() { 6 | const app = h("div", {}, "App"); 7 | // 虚拟节点的children 赋值给slots 8 | // p标签 9 | // const foo = h(Foo,{},h("p", {}, "123")) 10 | 11 | // 数组 12 | // const foo = h(Foo, {}, [h("p", {}, "header"), h("p", {}, "footer")]); 13 | 14 | // 对象,具名插槽 15 | const foo = h( 16 | Foo, 17 | {}, 18 | { 19 | header: ({ age }) => [ 20 | h("p", {}, "header" + age), 21 | createTextVNode("text vnode"), 22 | ], 23 | footer: () => h("p", {}, "footer"), 24 | } 25 | ); 26 | return h("div", {}, [app, foo]); 27 | }, 28 | setup() { 29 | return {}; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /example/componentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from "../../lib/guide-mini-vue.esm.js"; 2 | export const Foo = { 3 | setup() { 4 | return {}; 5 | }, 6 | render() { 7 | const foo = h("p", {}, "foo"); 8 | console.log(this.$slots); 9 | 10 | // children 可以为string, array 11 | // children在这里应该是vnode,vnode只能是string或组件,如果是数组应该处理 12 | // return h("div", {}, [foo, this.$slots]);不能渲染 13 | // 所以这里要用一个vnode包裹一下 14 | // return h("div", {}, [foo, h("div", {}, this.$slots)]); 15 | // 优化一下 16 | 17 | // 带参数 作用域插槽 18 | return h("div", {}, [ 19 | renderSlots(this.$slots, "header", {age: 18}), 20 | foo, 21 | renderSlots(this.$slots, "footer"), 22 | ]); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /example/componentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/componentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/guide-mini-vue.esm.js' 2 | const rootContainer = document.querySelector('#app') 3 | import { APP } from './App.js' 4 | createApp(APP).mount(rootContainer) -------------------------------------------------------------------------------- /example/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/guide-mini-vue.esm.js"; 2 | import Child from "./Child.js"; 3 | 4 | export const App = { 5 | name: "App", 6 | setup() { 7 | const msg = ref("123"); 8 | const count = ref(1); 9 | 10 | window.msg = msg; 11 | 12 | const changeChildProps = () => { 13 | msg.value = "456"; 14 | }; 15 | 16 | const changeCount = () => { 17 | count.value++; 18 | }; 19 | 20 | return { msg, changeChildProps, changeCount, count }; 21 | }, 22 | 23 | render() { 24 | return h("div", {}, [ 25 | h("div", {}, "你好"), 26 | h( 27 | "button", 28 | { 29 | onClick: this.changeChildProps, 30 | }, 31 | "change child props" 32 | ), 33 | h(Child), 34 | h( 35 | "button", 36 | { 37 | onClick: this.changeCount, 38 | }, 39 | "change self count" 40 | ), 41 | h("p", {}, "count: " + this.count), 42 | ]); 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /example/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | export default { 3 | name: "Child", 4 | setup(props, { emit }) {}, 5 | render(proxy) { 6 | return h("div", {}, [h("div", {}, "child - props - msg: " + this.$props.msg)]); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /example/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/componentUpdate/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const APP = { 5 | render() { 6 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]); 7 | }, 8 | setup() { 9 | const instance = getCurrentInstance(); 10 | console.log("App:", instance); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /example/currentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | name: "Foo", 5 | setup(props, {}) { 6 | const instance = getCurrentInstance(); 7 | console.log("Foo: ", instance); 8 | return {}; 9 | }, 10 | render() { 11 | return h("div", {}, "foo"); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /example/currentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/guide-mini-vue.esm.js' 2 | const rootContainer = document.querySelector('#app') 3 | import { APP } from './App.js' 4 | createApp(APP).mount(rootContainer) -------------------------------------------------------------------------------- /example/customRenderer/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const App = { 4 | setup() { 5 | return { 6 | x: 100, 7 | y: 100, 8 | }; 9 | }, 10 | render() { 11 | return h("rect", { x: this.x, y: this.y }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /example/customRenderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /example/customRenderer/main.js: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const game = new PIXI.Application({ 5 | width: 500, 6 | height: 500, 7 | }); 8 | 9 | document.body.append(game.view); 10 | 11 | const renderer = createRenderer({ 12 | createElement(type) { 13 | if (type === "rect") { 14 | const rect = new PIXI.Graphics(); 15 | rect.beginFill(0xff0000); 16 | rect.drawRect(0, 0, 100, 100); 17 | rect.endFill(); 18 | 19 | return rect; 20 | } 21 | }, 22 | patchProp(el, key, val) { 23 | el[key] = val; 24 | }, 25 | insert(el, parent) { 26 | parent.addChild(el); 27 | }, 28 | }); 29 | 30 | renderer.createApp(App).mount(game.stage); 31 | // 相当于 createApp(App).mount("container") -------------------------------------------------------------------------------- /example/helloWorld/App.js: -------------------------------------------------------------------------------- 1 | import { h,ref } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | window.self = null; 4 | export const App = { 5 | render() { 6 | window.self = this; 7 | return h( 8 | "div", 9 | { 10 | id: "root", 11 | class: ["red hard"], 12 | onClick: this.click1, 13 | }, 14 | // children 是string 15 | "hi " + this.msg 16 | // childred 是 array 17 | // [h("p",{class: "red"}, "hi p"), h("h3",{class: "green"}, "hi h3")] 18 | // props 19 | // [h("div", {}, "hi " + this.msg), h(Foo, { count: 1 })] 20 | ); 21 | }, 22 | setup() { 23 | const msg = ref('1') 24 | const click1 = () => { 25 | console.log("6666"); 26 | msg.value = msg.value+1 27 | } 28 | 29 | 30 | return { 31 | click1, 32 | msg, 33 | }; 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /example/helloWorld/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | export const Foo = { 3 | setup(props) { 4 | // 1 props.count 5 | console.log("props", props) 6 | 7 | // 2 this.count 访问 8 | 9 | // shallow readdonly 10 | // props.count++ 11 | }, 12 | render() { 13 | return h('p',{}, "foo "+ this.count) 14 | } 15 | } -------------------------------------------------------------------------------- /example/helloWorld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/helloWorld/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from "../../lib/guide-mini-vue.esm.js" 2 | import {App} from "./App.js" 3 | 4 | 5 | const rootContainer = document.querySelector("#app") 6 | createApp(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/nextTicker/App.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | h, 4 | ref, 5 | getCurrentInstance, 6 | nextTick, 7 | } from "../../lib/guide-mini-vue.esm.js"; 8 | 9 | export default { 10 | 11 | name: "App", 12 | setup() { 13 | const count = ref(1); 14 | const instance = getCurrentInstance(); 15 | 16 | function onClick() { 17 | for (let i = 0; i < 100; i++) { 18 | console.log("update"); 19 | count.value = i; 20 | } 21 | 22 | // debugger; 23 | console.log(instance); 24 | nextTick(() => { 25 | console.log(instance); 26 | }); 27 | 28 | // await nextTick() 29 | // console.log(instance) 30 | } 31 | 32 | return { 33 | onClick, 34 | count, 35 | }; 36 | }, 37 | render() { 38 | const button = h("button", { onClick: this.onClick }, "update"); 39 | const p = h("p", {}, "count:" + this.count); 40 | 41 | return h("div", {}, [button, p]); 42 | }, 43 | }; -------------------------------------------------------------------------------- /example/nextTicker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/nextTicker/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | import ArrayToText from "./ArrayToText.js"; 4 | import TextToText from "./TextToText.js"; 5 | import TextToArray from "./TextToArray.js"; 6 | import ArrayToArray from "./ArrayToArray.js"; 7 | 8 | export default { 9 | name: "App", 10 | setup() {}, 11 | 12 | render() { 13 | return h("div", { tId: 1 }, [ 14 | h("p", {}, "主页"), 15 | // 老的是 array 新的是 text 16 | // h(ArrayToText), 17 | // 老的是 text 新的是 text 18 | // h(TextToText), 19 | // 老的是 text 新的是 array 20 | // h(TextToArray) 21 | // 老的是 array 新的是 array 22 | h(ArrayToArray), 23 | ]); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | // 老的是 array 2 | // 新的是 array 3 | 4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js"; 5 | 6 | // 1. 左侧的对比 7 | // (a b) c 8 | // (a b) d e 9 | // const prevChildren = [ 10 | // h("p", { key: "A" }, "A"), 11 | // h("p", { key: "C" }, "C"), 12 | // ]; 13 | // const nextChildren = [ 14 | // h("p", { key: "A" }, "A"), 15 | // h("p", { key: "D" }, "D"), 16 | // h("p", { key: "E" }, "E"), 17 | // ]; 18 | 19 | // 2. 右侧的对比 20 | // a (b c) 21 | // d e (b c) 22 | // const prevChildren = [ 23 | // h("p", { key: "A" }, "A"), 24 | // h("p", { key: "B" }, "B"), 25 | // h("p", { key: "C" }, "C"), 26 | // ]; 27 | // const nextChildren = [ 28 | // h("p", { key: "D" }, "D"), 29 | // h("p", { key: "E" }, "E"), 30 | // h("p", { key: "B" }, "B"), 31 | // h("p", { key: "C" }, "C"), 32 | // ]; 33 | 34 | // 3. 新的比老的长 35 | // 创建新的 36 | // 左侧 37 | // (a b) 38 | // (a b) c 39 | // i = 2, e1 = 1, e2 = 2 40 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 41 | // const nextChildren = [ 42 | // h("p", { key: "A" }, "A"), 43 | // h("p", { key: "B" }, "B"), 44 | // h("p", { key: "C" }, "C"), 45 | // h("p", { key: "D" }, "D"), 46 | // ]; 47 | 48 | // 右侧 49 | // (a b) 50 | // c (a b) 51 | // i = 0, e1 = -1, e2 = 0 52 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 53 | // const nextChildren = [ 54 | // h("p", { key: "C" }, "Ca"), 55 | // h("p", { key: "C" }, "Ca"), 56 | // h("p", { key: "A" }, "A"), 57 | // h("p", { key: "B" }, "B"), 58 | // ]; 59 | 60 | // 4. 老的比新的长 61 | // 删除老的 62 | // 左侧 63 | // (a b) c 64 | // (a b) 65 | // i = 2, e1 = 2, e2 = 1 66 | // const prevChildren = [ 67 | // h("p", { key: "A" }, "A"), 68 | // h("p", { key: "B" }, "B"), 69 | // h("p", { key: "C" }, "C"), 70 | // ]; 71 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 72 | 73 | // 右侧 74 | // a (b c) 75 | // (b c) 76 | // i = 0, e1 = 0, e2 = -1 77 | 78 | // const prevChildren = [ 79 | // h("p", { key: "A" }, "A"), 80 | // h("p", { key: "B" }, "B"), 81 | // h("p", { key: "C" }, "C"), 82 | // ]; 83 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 84 | 85 | // 5. 对比中间的部分 86 | // 删除老的 (在老的里面存在,新的里面不存在) 87 | // 5.1 88 | // a,b,(c,d),f,g 89 | // a,b,(e,c),f,g 90 | // D 节点在新的里面是没有的 - 需要删除掉 91 | // C 节点 props 也发生了变化 92 | 93 | // const prevChildren = [ 94 | // h("p", { key: "A" }, "A"), 95 | // h("p", { key: "B" }, "B"), 96 | // h("p", { key: "C", id: "c-prev" }, "C"), 97 | // h("p", { key: "D" }, "D"), 98 | // h("p", { key: "F" }, "F"), 99 | // h("p", { key: "G" }, "G"), 100 | // ]; 101 | 102 | // const nextChildren = [ 103 | // h("p", { key: "A" }, "A"), 104 | // h("p", { key: "B" }, "B"), 105 | // h("p", { key: "E" }, "E"), 106 | // h("p", { key: "C", id:"c-next" }, "C"), 107 | // h("p", { key: "F" }, "F"), 108 | // h("p", { key: "G" }, "G"), 109 | // ]; 110 | 111 | // 5.1.1 112 | // a,b,(c,e,d),f,g 113 | // a,b,(e,c),f,g 114 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑) 115 | // const prevChildren = [ 116 | // h("p", { key: "A" }, "A"), 117 | // h("p", { key: "B" }, "B"), 118 | // h("p", { key: "C", id: "c-prev" }, "C"), 119 | // h("p", { key: "E" }, "E"), 120 | // h("p", { key: "D" }, "D"), 121 | // h("p", { key: "F" }, "F"), 122 | // h("p", { key: "G" }, "G"), 123 | // ]; 124 | 125 | // const nextChildren = [ 126 | // h("p", { key: "A" }, "A"), 127 | // h("p", { key: "B" }, "B"), 128 | // h("p", { key: "E" }, "E"), 129 | // h("p", { key: "C", id:"c-next" }, "C"), 130 | // h("p", { key: "F" }, "F"), 131 | // h("p", { key: "G" }, "G"), 132 | // ]; 133 | 134 | 135 | 136 | 137 | // 2 移动 (节点存在于新的和老的里面,但是位置变了) 138 | // const prevChildren = [ 139 | // h("p", { key: "A" }, "A"), 140 | // h("p", { key: "B" }, "B"), 141 | 142 | // h("p", { key: "C" }, "C"), 143 | // h("p", { key: "D" }, "D"), 144 | // h("p", { key: "E" }, "E"), 145 | 146 | // h("p", { key: "F" }, "F"), 147 | // h("p", { key: "G" }, "G"), 148 | // ]; 149 | 150 | // const nextChildren = [ 151 | // h("p", { key: "A" }, "A"), 152 | // h("p", { key: "B" }, "B"), 153 | 154 | // h("p", { key: "E" }, "E"), 155 | // h("p", { key: "C" }, "C"), 156 | // h("p", { key: "D" }, "D"), 157 | 158 | // h("p", { key: "F" }, "F"), 159 | // h("p", { key: "G" }, "G"), 160 | // ]; 161 | 162 | // 综合例子 163 | // a,b,(c,d,e,z),f,g 164 | // a,b,(d,c,y,e),f,g 165 | 166 | const prevChildren = [ 167 | h("p", { key: "A" }, "A"), 168 | h("p", { key: "B" }, "B"), 169 | h("p", { key: "C" }, "C"), 170 | h("p", { key: "D" }, "D"), 171 | h("p", { key: "E" }, "E"), 172 | h("p", { key: "Z" }, "Z"), 173 | h("p", { key: "F" }, "F"), 174 | h("p", { key: "G" }, "G"), 175 | ]; 176 | 177 | const nextChildren = [ 178 | h("p", { key: "A" }, "A"), 179 | h("p", { key: "B" }, "B"), 180 | h("p", { key: "D" }, "D"), 181 | h("p", { key: "C" }, "C"), 182 | h("p", { key: "Y" }, "Y"), 183 | h("p", { key: "E" }, "E"), 184 | h("p", { key: "F" }, "F"), 185 | h("p", { key: "G" }, "G"), 186 | ]; 187 | 188 | export default { 189 | name: "ArrayToArray", 190 | setup() { 191 | const isChange = ref(false); 192 | window.isChange = isChange; 193 | 194 | return { 195 | isChange, 196 | }; 197 | }, 198 | render() { 199 | const self = this; 200 | return self.isChange === true 201 | ? h("div", {}, nextChildren) 202 | : h("div", {}, prevChildren); 203 | }, 204 | }; 205 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | // 老的是 array 2 | // 新的是 text 3 | 4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js"; 5 | const nextChildren = "newChildren"; 6 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")]; 7 | 8 | export default { 9 | name: "ArrayToText", 10 | setup() { 11 | const isChange = ref(false); 12 | window.isChange = isChange; 13 | 14 | return { 15 | isChange, 16 | }; 17 | }, 18 | render() { 19 | const self = this; 20 | 21 | return self.isChange === true 22 | ? h("div", {}, nextChildren) 23 | : h("div", {}, prevChildren); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /example/patchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | // 新的是 array 2 | // 老的是 text 3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js"; 4 | 5 | const prevChildren = "oldChild"; 6 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")]; 7 | 8 | export default { 9 | name: "TextToArray", 10 | setup() { 11 | const isChange = ref(false); 12 | window.isChange = isChange; 13 | 14 | return { 15 | isChange, 16 | }; 17 | }, 18 | render() { 19 | const self = this; 20 | 21 | return self.isChange === true 22 | ? h("div", {}, nextChildren) 23 | : h("div", {}, prevChildren); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /example/patchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | // 新的是 text 2 | // 老的是 text 3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js"; 4 | 5 | const prevChildren = "oldChild"; 6 | const nextChildren = "newChild"; 7 | 8 | export default { 9 | name: "TextToText", 10 | setup() { 11 | const isChange = ref(false); 12 | window.isChange = isChange; 13 | 14 | return { 15 | isChange, 16 | }; 17 | }, 18 | render() { 19 | const self = this; 20 | 21 | return self.isChange === true 22 | ? h("div", {}, nextChildren) 23 | : h("div", {}, prevChildren); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /example/patchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /example/patchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#root"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const App = { 4 | name: "App", 5 | 6 | setup() { 7 | const count = ref(0); 8 | 9 | const onClick = () => { 10 | count.value++; 11 | }; 12 | 13 | const props = ref({ 14 | foo: "foo", 15 | bar: "bar", 16 | }); 17 | const onChangePropsDemo1 = () => { 18 | props.value.foo = "new-foo"; 19 | }; 20 | 21 | const onChangePropsDemo2 = () => { 22 | props.value.foo = undefined; 23 | }; 24 | 25 | const onChangePropsDemo3 = () => { 26 | props.value = { 27 | foo: "foo", 28 | }; 29 | }; 30 | 31 | return { 32 | count, 33 | onClick, 34 | onChangePropsDemo1, 35 | onChangePropsDemo2, 36 | onChangePropsDemo3, 37 | props, 38 | }; 39 | }, 40 | render() { 41 | return h( 42 | "div", 43 | { 44 | id: "root", 45 | ...this.props, 46 | }, 47 | [ 48 | h("div", {}, "count:" + this.count), 49 | h( 50 | "button", 51 | { 52 | onClick: this.onClick, 53 | }, 54 | "click" 55 | ), 56 | h( 57 | "button", 58 | { 59 | onClick: this.onChangePropsDemo1, 60 | }, 61 | "changeProps - 值改变了 - 修改" 62 | ), 63 | 64 | h( 65 | "button", 66 | { 67 | onClick: this.onChangePropsDemo2, 68 | }, 69 | "changeProps - 值变成了 undefined - 删除" 70 | ), 71 | 72 | h( 73 | "button", 74 | { 75 | onClick: this.onChangePropsDemo3, 76 | }, 77 | "changeProps - key 在新的里面没有了 - 删除" 78 | ), 79 | ] 80 | ); 81 | }, 82 | }; 83 | -------------------------------------------------------------------------------- /example/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /lib/guide-mini-vue.esm.js: -------------------------------------------------------------------------------- 1 | const Fragment = Symbol("Fragment"); 2 | const Text = Symbol("Text"); 3 | function createVNode(type, props, children) { 4 | // console.log(props) 5 | // 相同的节点 type key 相同 6 | const vnode = { 7 | type, 8 | key: props && props.key, 9 | props, 10 | // 组件实例 instance 11 | component: null, 12 | children, 13 | shapeFlag: getShapeFlag(type), 14 | el: null, 15 | }; 16 | // 为处理children准备,给vnode再次添加一个flag 17 | // 这里的逻辑是这样的 18 | /** 19 | * a,b,c,d 为二进制数 20 | * 如果 c = a | b,那么 c&b 和 c&a 后转为十进制为非0, c&d 后转为10进制为0 21 | * 22 | */ 23 | if (typeof children === "string") { 24 | // 0001 | 0100 -> 0101 25 | // 0010 | 0100 -> 0110 26 | vnode.shapeFlag = vnode.shapeFlag | 4 /* TEXT_CHILDREN */; 27 | } 28 | else if (Array.isArray(children)) { 29 | // 0001 | 1000 -> 1001 30 | // 0010 | 1000 -> 1010 31 | vnode.shapeFlag = vnode.shapeFlag | 8 /* ARRAY_CHILDREN */; 32 | } 33 | // slots children 34 | // 组件 + children object 35 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 36 | if (typeof children === "object") { 37 | vnode.shapeFlag |= 16 /* SLOTS_CHILDREN */; 38 | } 39 | } 40 | return vnode; 41 | } 42 | function createTextVNode(text) { 43 | return createVNode(Text, {}, text); 44 | } 45 | function getShapeFlag(type) { 46 | // vnode 是element元素 还是 组件 0001 0010 47 | return typeof type === "string" 48 | ? 1 /* ELEMENT */ 49 | : 2 /* STATEFUL_COMPONENT */; 50 | } 51 | 52 | function h(type, props, children) { 53 | return createVNode(type, props, children); 54 | } 55 | 56 | function renderSlots(slots, name, props) { 57 | const slot = slots[name]; 58 | if (slot) { 59 | if (typeof slot === "function") { 60 | return createVNode(Fragment, {}, slot(props)); 61 | } 62 | } 63 | } 64 | 65 | function toDisplayString(value) { 66 | return String(value); 67 | } 68 | 69 | const extend = Object.assign; 70 | const isObject = (value) => { 71 | return value !== null && typeof value === "object"; 72 | }; 73 | const hasChanged = (v1, v2) => { 74 | return !Object.is(v1, v2); 75 | }; 76 | const camelize = (str) => { 77 | return str.replace(/-(\w)/g, (_, c) => { 78 | return c ? c.toUpperCase() : ""; 79 | }); 80 | }; 81 | const isString = (value) => typeof value === "string"; 82 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); 83 | const capitalize = (str) => { 84 | return str.charAt(0).toUpperCase() + str.slice(1); 85 | }; 86 | const toHandlerKey = (str) => { 87 | return str ? "on" + capitalize(str) : ""; 88 | }; 89 | 90 | class ReactiveEffect { 91 | // pbulic 是为了给外部获取到 92 | constructor(fn, scheduler) { 93 | this.scheduler = scheduler; 94 | this.deps = []; 95 | // active 是处理重复调用stop的 96 | this.active = true; 97 | this._fn = fn; 98 | } 99 | run() { 100 | if (!this.active) { 101 | // 不应该收集依赖 102 | // 如果调用了stop,active 为 false 103 | // 只调用第一次的 _fn, 不进行下面的依赖赋值,也就是不进行依赖收集的 track 操作 104 | return this._fn(); 105 | } 106 | // this 就是依赖的,依赖的run 方法就是执行fn 107 | // 应该收集依赖逻辑 108 | activeEffect = this; 109 | shouldTract = true; 110 | const r = this._fn(); 111 | shouldTract = false; 112 | return r; 113 | } 114 | stop() { 115 | // 1个 dep 对应多个 effect,同一个effect可能存在多个dep里面 116 | // 现在要清除所有 dep 里面的 目标effect,也就是先遍历depsMap得到dep,在delete每一个dep里面的effect 117 | // 但是depsMap 与 effect不存在关联关系,也就是说当前的effect 不能关系到 所有的depsMap 118 | // 这样处理, 119 | /** 120 | * 1. dep 与 effect 的关系的 dep.add(effect) 121 | * 2. 我们给每一个effect 添加一个deps 的数组空间,用来存储谁 add 当前端的effect 了 122 | * 3. 那么,我们就能从effect 本身关联到与他有关的所有dep了,也就是 deps 数组 123 | * 4. 返回来,只要遍历当前的的efect的deps属性(deps这里面的每一个dep都存在effect),dep是Set,deps是数组 124 | * 5. effect.deps.forEach(dep => dep.delete(effect)) 125 | */ 126 | if (this.active) { 127 | if (this.onStop) { 128 | this.onStop(); 129 | } 130 | cleanUpEffect(this); 131 | this.active = false; 132 | } 133 | } 134 | } 135 | function cleanUpEffect(effect) { 136 | effect.deps.forEach((dep) => { 137 | dep.delete(effect); 138 | }); 139 | effect.deps.length = 0; 140 | } 141 | let targetMap = new Map(); 142 | let activeEffect; 143 | let shouldTract; 144 | function track(target, key) { 145 | if (!isTracking()) 146 | return; 147 | // target key dep 148 | // 对象-- key -- 依赖 149 | let depsMap = targetMap.get(target); 150 | if (!depsMap) { 151 | depsMap = new Map(); 152 | targetMap.set(target, depsMap); 153 | } 154 | let dep = depsMap.get(key); 155 | if (!dep) { 156 | dep = new Set(); 157 | depsMap.set(key, dep); 158 | } 159 | // 这不光光是抽离一个函数那么简单,为ref做准备 160 | trackEffects(dep); 161 | // if(dep.has(activeEffect)) return 162 | // dep.add(activeEffect); 163 | // activeEffect.deps.push(dep); 164 | } 165 | function trackEffects(dep) { 166 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了 167 | if (dep.has(activeEffect)) 168 | return; 169 | dep.add(activeEffect); 170 | activeEffect.deps.push(dep); 171 | } 172 | function isTracking() { 173 | return shouldTract && activeEffect !== undefined; 174 | } 175 | function effect(fn, options = {}) { 176 | const _effect = new ReactiveEffect(fn, options.scheduler); 177 | _effect.onStop = options.onStop; 178 | extend(_effect, options); 179 | _effect.run(); 180 | const runner = _effect.run.bind(_effect); 181 | runner.effect = _effect; 182 | return runner; 183 | } 184 | function trigger(target, key) { 185 | let depsMap = targetMap.get(target); 186 | let dep = depsMap.get(key); 187 | triggerEffect(dep); 188 | } 189 | function triggerEffect(dep) { 190 | for (const effect of dep) { 191 | if (effect.scheduler) { 192 | effect.scheduler(); 193 | } 194 | else { 195 | effect.run(); 196 | } 197 | } 198 | } 199 | 200 | const get = createGetter(); 201 | const set = createSetter(); 202 | const readonlyGet = createGetter(true); 203 | const shallowReadonlyGet = createGetter(true, true); 204 | // shallow 浅层次 205 | function createGetter(isReadOnly = false, shallow = false) { 206 | return function get(target, key) { 207 | if (key === "__v_isReactive" /* IS_REACTIVE */) { 208 | return !isReadOnly; 209 | } 210 | else if (key === "__v_isREADONLY" /* IS_READONLY */) { 211 | return isReadOnly; 212 | } 213 | let res = Reflect.get(target, key); 214 | if (shallow) { 215 | return res; 216 | } 217 | if (isObject(res)) { 218 | return isReadOnly ? readonly(res) : reactive(res); 219 | } 220 | // TODO 收集依赖 221 | if (!isReadOnly) { 222 | track(target, key); 223 | } 224 | return res; 225 | }; 226 | } 227 | function createSetter() { 228 | return function set(target, key, value) { 229 | let res = Reflect.set(target, key, value); 230 | trigger(target, key); 231 | return res; 232 | }; 233 | } 234 | const mutableHandles = { 235 | get, 236 | set, 237 | }; 238 | const readonlyHandles = { 239 | get: readonlyGet, 240 | set(target, key, value) { 241 | console.warn(`${key} 不能set,readonly!`); 242 | return true; 243 | }, 244 | }; 245 | const shallowReadonlyHandles = extend({}, readonlyHandles, { 246 | get: shallowReadonlyGet, 247 | }); 248 | 249 | // raw 生的 250 | function reactive(raw) { 251 | return createReactiveObject(raw, mutableHandles); 252 | } 253 | function readonly(raw) { 254 | return createReactiveObject(raw, readonlyHandles); 255 | } 256 | function createReactiveObject(raw, baseHandlers) { 257 | return new Proxy(raw, baseHandlers); 258 | } 259 | function shallowReadonly(raw) { 260 | return createReactiveObject(raw, shallowReadonlyHandles); 261 | } 262 | 263 | class RefImpl { 264 | constructor(value) { 265 | this.__v_isRef = true; 266 | // 存一下原始值,当value 为reactive时候使用 267 | this._rawValue = value; 268 | this._value = convert(value); 269 | this.dep = new Set(); 270 | } 271 | get value() { 272 | trackRefValue(this); 273 | return this._value; 274 | } 275 | set value(newValue) { 276 | // 如果value 是个reactive类型,那么需要用他的原始值作比较 277 | if (hasChanged(newValue, this._rawValue)) { 278 | this._rawValue = newValue; 279 | this._value = convert(newValue); 280 | this._rawValue = newValue; 281 | triggerEffect(this.dep); 282 | } 283 | } 284 | } 285 | function convert(value) { 286 | return isObject(value) ? reactive(value) : value; 287 | } 288 | function ref(value) { 289 | return new RefImpl(value); 290 | } 291 | function trackRefValue(ref) { 292 | if (isTracking()) { 293 | trackEffects(ref.dep); 294 | } 295 | } 296 | function isRef(value) { 297 | return !!value.__v_isRef; 298 | } 299 | function unRef(value) { 300 | return !!value.__v_isRef ? value.value : value; 301 | } 302 | function proxyRefs(objectWithRefs) { 303 | return new Proxy(objectWithRefs, { 304 | get(target, key) { 305 | return unRef(Reflect.get(target, key)); 306 | }, 307 | set(target, key, value) { 308 | if (isRef(target[key]) && !isRef(value)) { 309 | return target[key].value = value; 310 | } 311 | else { 312 | return Reflect.set(target, key, value); 313 | } 314 | } 315 | }); 316 | } 317 | 318 | function emit(instance, event, ...arg) { 319 | const { props } = instance; 320 | // add -> Add 321 | // add-add -> addAdd 322 | const handlerName = toHandlerKey(camelize(event)); 323 | // console.log(handlerName) 324 | const handler = props[handlerName]; 325 | handler && handler(...arg); 326 | } 327 | 328 | function initProps(instance, rawProps) { 329 | instance.props = rawProps || {}; 330 | } 331 | 332 | const publicPropertiesMap = { 333 | $el: (i) => i.vnode.el, 334 | $slots: (i) => i.slots, 335 | $props: (i) => i.props, 336 | }; 337 | const PublicInstanceProxyHandlers = { 338 | get({ _: instance }, key) { 339 | // console.log(instance) 340 | const { setupState, props } = instance; 341 | if (hasOwn(setupState, key)) { 342 | return setupState[key]; 343 | } 344 | else if (hasOwn(props, key)) { 345 | return props[key]; 346 | } 347 | // $el 348 | const publicGetter = publicPropertiesMap[key]; 349 | if (publicGetter) { 350 | return publicGetter(instance); 351 | } 352 | }, 353 | }; 354 | 355 | function initSlots(instance, children) { 356 | const { vnode } = instance; 357 | if (vnode.shapeFlag & 16 /* SLOTS_CHILDREN */) { 358 | normalizeObjectSlots(children, instance.slots); 359 | } 360 | } 361 | function normalizeObjectSlots(children, slots) { 362 | for (const key in children) { 363 | const value = children[key]; 364 | slots[key] = props => normalizeSlotValue(value(props)); 365 | } 366 | } 367 | function normalizeSlotValue(value) { 368 | return Array.isArray(value) ? value : [value]; 369 | } 370 | 371 | function createComponentInstance(vnode, parent) { 372 | // instance component 373 | const instance = { 374 | vnode, 375 | // 下次要更新的虚拟节点 376 | next: null, 377 | type: vnode.type, 378 | setupState: {}, 379 | isMounted: false, 380 | // subTree:'', 381 | emit: () => { }, 382 | slots: {}, 383 | provides: parent ? parent.provides : {}, 384 | parent, 385 | props: {}, 386 | }; 387 | instance.emit = emit.bind(null, instance); 388 | return instance; 389 | } 390 | function setupComponent(instance) { 391 | // 初始化 392 | // props 393 | initProps(instance, instance.vnode.props); 394 | initSlots(instance, instance.vnode.children); 395 | // 创建有状态的组件 396 | setupStatefulComponent(instance); 397 | } 398 | function setupStatefulComponent(instance) { 399 | // 调用setup 函数,拿到setup函数的返回值 400 | const Component = instance.vnode.type; 401 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers); 402 | const { setup } = Component; 403 | if (setup) { 404 | setCurrentInstance(instance); 405 | const setupResult = setup(shallowReadonly(instance.props), { 406 | emit: instance.emit, 407 | }); 408 | setCurrentInstance(null); 409 | handleSetupResult(instance, setupResult); 410 | } 411 | } 412 | function handleSetupResult(instance, setupResult) { 413 | // 返回值是function,那就是render函数 414 | // 返回值是Object,那需要把这个对象挂到组件上下文 415 | if (typeof setupResult === "object") { 416 | instance.setupState = proxyRefs(setupResult); 417 | } 418 | // 保证组件render有值 419 | // 组件 -> const App = { 420 | // render() { 421 | // return h("div", this.msg) 422 | // }, 423 | // setup() { 424 | // return { 425 | // msg: "hello vue" 426 | // } 427 | // } 428 | // } 429 | finishComponentSetup(instance); 430 | } 431 | function finishComponentSetup(instance) { 432 | const Component = instance.type; 433 | if (compiler && !Component.render) { 434 | if (Component.template) { 435 | Component.render = compiler(Component.template); 436 | } 437 | } 438 | instance.render = Component.render; 439 | // instance -> { 440 | // render: 441 | // setupState 442 | // vnode: { 443 | // type: App 444 | // } 445 | // } 446 | } 447 | let currentInstance = null; 448 | function getCurrentInstance() { 449 | return currentInstance; 450 | } 451 | function setCurrentInstance(instance) { 452 | currentInstance = instance; 453 | } 454 | let compiler; 455 | function registerRuntimeCompiler(_compiler) { 456 | compiler = _compiler; 457 | } 458 | 459 | function provide(key, value) { 460 | const currentInstance = getCurrentInstance(); 461 | if (currentInstance) { 462 | let { provides } = currentInstance; 463 | const parentProvides = currentInstance.parent.provides; 464 | // 初始化 465 | if (provides === parentProvides) { 466 | provides = currentInstance.provides = Object.create(parentProvides); 467 | } 468 | provides[key] = value; 469 | } 470 | } 471 | function inject(key, defaultValue) { 472 | const currentInstance = getCurrentInstance(); 473 | if (currentInstance) { 474 | const parentProvides = currentInstance.parent.provides; 475 | if (key in parentProvides) { 476 | return parentProvides[key]; 477 | } 478 | else if (defaultValue) { 479 | if (typeof defaultValue === "function") { 480 | return defaultValue(); 481 | } 482 | return defaultValue; 483 | } 484 | } 485 | } 486 | 487 | function shouldUpdateComponent(prevVNode, nextVNode) { 488 | const { props: prevProps } = prevVNode; 489 | const { props: nextProps } = nextVNode; 490 | for (const key in nextProps) { 491 | if (nextProps[key] !== prevProps[key]) { 492 | return true; 493 | } 494 | } 495 | return false; 496 | } 497 | 498 | // import { render } from "./renderer"; 499 | function createAppAPI(render) { 500 | return function createApp(rootComponent) { 501 | return { 502 | mount(rootContainer) { 503 | // 先创建 vnode 504 | // component -> vnode 505 | // 所有逻辑操作 都会基于 vnode 做处理 506 | const vnode = createVNode(rootComponent); 507 | // 渲染虚拟节点 508 | render(vnode, rootContainer); 509 | }, 510 | }; 511 | }; 512 | } 513 | 514 | const queue = []; 515 | let isFlushPending = false; 516 | function nextTick(fn) { 517 | return fn ? Promise.resolve().then(fn) : Promise.resolve(); 518 | } 519 | function queueJobs(job) { 520 | if (!queue.includes(job)) { 521 | queue.push(job); 522 | } 523 | queueFlush(); 524 | } 525 | function queueFlush() { 526 | if (isFlushPending) { 527 | return; 528 | } 529 | isFlushPending = true; 530 | nextTick(flushJobs); 531 | } 532 | function flushJobs() { 533 | Promise.resolve().then(() => { 534 | isFlushPending = false; 535 | let job; 536 | while ((job = queue.shift())) { 537 | job && job(); 538 | } 539 | }); 540 | } 541 | 542 | function createRenderer(options) { 543 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, setElementText: hostSetElementText, remove: hostRemove, } = options; 544 | // 查查初始化时候调用render了么? 545 | function render(vnode, container) { 546 | // patch 547 | patch(null, vnode, container, null, null); 548 | } 549 | /** 550 | * n1 老的 551 | * n2 新的 552 | */ 553 | function patch(n1, n2, container, parentComponent, anchor) { 554 | // 当vnode.type的值时,组件是object,element是string,这样区分组件和元素 555 | const { type, shapeFlag } = n2; 556 | switch (type) { 557 | case Fragment: 558 | processFragment(n1, n2, container, parentComponent, anchor); 559 | break; 560 | case Text: 561 | processText(n1, n2, container); 562 | break; 563 | default: 564 | // if (typeof vnode.type === "string") { 565 | if (shapeFlag & 1 /* ELEMENT */) { 566 | // patch element 567 | processElement(n1, n2, container, parentComponent, anchor); 568 | // } else if (isObject(vnode.type)) { 569 | } 570 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) { 571 | // patch 组件 572 | console.log("组件逻辑"); 573 | processComponent(n1, n2, container, parentComponent, anchor); 574 | } 575 | } 576 | } 577 | function processText(n1, n2, container) { 578 | const { children } = n2; 579 | const text = document.createTextNode(children); 580 | container.append(text); 581 | } 582 | function processFragment(n1, n2, container, parentComponent, anchor) { 583 | mountChildren(n2.children, container, parentComponent, anchor); 584 | } 585 | function processElement(n1, n2, container, parentComponent, anchor) { 586 | // 包含初始化和更新流程 587 | // init 588 | if (!n1) { 589 | mountElement(n2, container, parentComponent, anchor); 590 | } 591 | else { 592 | patchElement(n1, n2, container, parentComponent, anchor); 593 | } 594 | } 595 | function patchElement(n1, n2, container, parentComponent, anchor) { 596 | // 获取新,老 prosp 597 | const oldProps = n1.props || {}; 598 | const newProps = n2.props || {}; 599 | // 对比新老props 600 | const el = (n2.el = n1.el); 601 | patchProps(el, oldProps, newProps); 602 | // 对比children 603 | patchChildren(n1, n2, el, parentComponent, anchor); 604 | } 605 | function patchChildren(n1, n2, container, parentComponent, anchor) { 606 | // 子节点只有两种类型 文本节点 数组 607 | /* 608 | 1 新的是text,老的是array 609 | 2 删除老的array 添加 文本节点 610 | */ 611 | /* 612 | 1 新的 老的都是 文本节点 613 | 2 对比是否相同,不相同的话 替换老的节点 614 | */ 615 | /* 616 | 1 新的是数组,老的是文本 617 | 2 删除老的,挂载新的 618 | */ 619 | const { shapeFlag } = n2; 620 | const c2 = n2.children; 621 | const c1 = n1.children; 622 | const prevshapeFlag = n1.shapeFlag; 623 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 624 | // if (prevshapeFlag & ShapeFlags.ARRAY_CHILDREN) { 625 | // // 1 把老的 children 删除 626 | // unmountChildren(n1.children); 627 | // // 2 添加 text 628 | // hostSetElementText(container, c2); 629 | // } else { 630 | // // 新老都是文本节点 631 | // if(c1 !== c2) { 632 | // hostSetElementText(container, c2); 633 | // } 634 | // } 635 | // 重构一下 636 | if (prevshapeFlag & 8 /* ARRAY_CHILDREN */) { 637 | unmountChildren(n1.children); 638 | } 639 | if (c1 !== c2) { 640 | hostSetElementText(container, c2); 641 | } 642 | } 643 | else { 644 | // 新的是array 老的是text 645 | if (prevshapeFlag & 4 /* TEXT_CHILDREN */) { 646 | hostSetElementText(container, ""); 647 | mountChildren(c2, container, parentComponent, anchor); 648 | } 649 | else { 650 | // array diff array 651 | patchKeyedChildren(c1, c2, container, parentComponent, anchor); 652 | } 653 | } 654 | } 655 | /** 656 | * @description array diff array 657 | * @author Werewolf 658 | * @date 2021-12-20 659 | * @param {*} c1 老 660 | * @param {*} c2 新 661 | * @param {*} container 容器 662 | * @param {*} parentComponent 父组件 663 | * @param {*} parentAnthor 在这个元素之前插入。原由:插入有位置的要求 664 | */ 665 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnthor) { 666 | // 初始指针 i 667 | let i = 0; 668 | let l2 = c2.length; 669 | let e1 = c1.length - 1; 670 | let e2 = l2 - 1; 671 | function isSameNodeType(n1, n2) { 672 | // 相同节点 type key 相同 673 | return n1.type === n2.type && n1.key === n2.key; 674 | } 675 | // 初始指针不能超过两个数组 676 | /** 677 | * 第一种情况 678 | * 左侧对吧 679 | * ab c 680 | * ab de 681 | */ 682 | while (i <= e1 && i <= e2) { 683 | const n1 = c1[i]; 684 | const n2 = c2[i]; 685 | if (isSameNodeType(n1, n2)) { 686 | patch(n1, n2, container, parentComponent, parentAnthor); 687 | } 688 | else { 689 | break; 690 | } 691 | i++; 692 | } 693 | /** 694 | * 第二种情况 695 | * 右侧对比 696 | * a bc 697 | * de bc 698 | */ 699 | while (i <= e1 && i <= e2) { 700 | const n1 = c1[e1]; 701 | const n2 = c2[e2]; 702 | if (isSameNodeType(n1, n2)) { 703 | patch(n1, n2, container, parentComponent, parentAnthor); 704 | } 705 | else { 706 | break; 707 | } 708 | e1--; 709 | e2--; 710 | } 711 | /** 712 | * 第三种情况 713 | * 新的比老的多,两种情况 714 | * ab ab 715 | * ab c c ab 716 | */ 717 | if (i > e1) { 718 | if (i <= e2) { 719 | const nextPos = i + 1; 720 | const anchor = i + 1 > l2 ? null : c2[nextPos].el; 721 | while (i <= e2) { 722 | patch(null, c2[i], container, parentComponent, anchor); 723 | i++; 724 | } 725 | } 726 | } 727 | else if (i > e2) { 728 | /** 729 | * 第四种情况 730 | * 新的比老的少, 两种情况 731 | * ab c a bc 732 | * ab bc 733 | */ 734 | while (i <= e1) { 735 | hostRemove(c1[i].el); 736 | i++; 737 | } 738 | } 739 | else { 740 | // 中间对比,经过以上逻辑已经找到了两个临界点 741 | /** 742 | * 第五种情况-1。删除老的d,修改c 743 | * 旧 ab cd fg 744 | * 新 ab ec fg 745 | * 1 旧的里面存在,新的不存在(d),那么需要删除 d。 746 | * 如果在ec里面遍历看是否存在d,那么时间复杂度是O(n),如果用 key 映射,那么时间复杂度是O(1) 747 | * 748 | */ 749 | /** 750 | * 根据新的节点建立关于key的映射关系 keyToNewIndexMap 751 | * 在老的节点里根据key查找是否存在值,也就是是否存在 keyToNewIndexMap[oldChild.key] 752 | * 存在说明是相同节点,拿到索引,进行深度 patch,不存在直接在老的节点里删除 753 | * 注意:老的节点可能是用户没有写key属性,那只能 for 遍历了 754 | * 755 | */ 756 | // s1 s2 新老节点中间不同的起始位置 757 | let s1 = i; 758 | let s2 = i; 759 | /** 760 | * 优化点:当新节点的个数小于老节点点个数,也就是新的已经patch完毕,但是老节点还存在,那么老节点剩下的无需在对比,直接删除 761 | * 老 ab cedm fg,新 ab ec fg,当新节点的ec对比完毕,老节点还剩dm,那么直接删除,无需对比 762 | * 763 | * toBePatched 新节点需要patch的个数 764 | * patched 已经处理的个数 765 | * 766 | */ 767 | const toBePatched = e2 - s2 + 1; 768 | let patched = 0; 769 | // 映射关系 770 | const keyToNewIndexMap = new Map(); 771 | // 节点位置移动的逻辑 772 | /** 773 | * 旧 ab cde fg 774 | * 新 ab ecd fg 775 | * newIndexToOldIndexMap的长度是3, 指的是新的 ecd 的映射 776 | * 我们要把 e 在老数组的的位置(4)映射到 newIndexToOldIndexMap 里面。newIndexToOldIndexMap[0] = 4 777 | * 778 | */ 779 | // 建立 初始化映射表 定长数组性能相对要好 780 | const newIndexToOldIndexMap = new Array(toBePatched); 781 | /** 782 | * 优化逻辑 783 | * moved 784 | * maxNewIndexSoFar 785 | */ 786 | let moved = false; 787 | let maxNewIndexSoFar = 0; 788 | for (let i = 0; i < toBePatched; i++) { 789 | newIndexToOldIndexMap[i] = 0; 790 | } 791 | // 建立 新的映射关系 792 | for (let i = s2; i <= e2; i++) { 793 | const nextChild = c2[i]; 794 | keyToNewIndexMap.set(nextChild.key, i); 795 | } 796 | // 老的映射关系 797 | for (let i = s1; i <= e1; i++) { 798 | // 老节点 prevChild 799 | const prevChild = c1[i]; 800 | if (patched >= toBePatched) { 801 | // 新的已经对比完,但是老的还没完事。直接删除 802 | hostRemove(prevChild.el); 803 | // 进入下一次循环 804 | continue; 805 | } 806 | let newIndex; 807 | /** 808 | * 如果 newIndex 存在,说明 prevChild 在新的里面存在。 809 | * 如果用户写了key,用key映射查找。如果没写key,用循环查找 810 | */ 811 | if (prevChild.key !== null) { 812 | newIndex = keyToNewIndexMap.get(prevChild.key); 813 | } 814 | else { 815 | for (let j = s2; j <= e2; j++) { 816 | if (isSameNodeType(c2[j], prevChild)) { 817 | newIndex = j; 818 | break; 819 | } 820 | } 821 | } 822 | if (newIndex === undefined) { 823 | // 说明不存在prevChild,删掉老的 prevChild 824 | hostRemove(prevChild.el); 825 | } 826 | else { 827 | /** 828 | * 优化点 829 | * 思路: 830 | * 1 首先最长递归子序列是递增,那么我们想要 newIndex 也应该是递增,也就不用遍历递增序列了,优化了性能 831 | * 2 如果不是递增,那么肯定需要 移动并插入 832 | * 833 | */ 834 | if (newIndex >= maxNewIndexSoFar) { 835 | maxNewIndexSoFar = newIndex; 836 | } 837 | else { 838 | moved = true; 839 | } 840 | /** 841 | * ab ecd fg 842 | * 从e开始映射 e 为 0,newIndex - s2 减去前面相同的 s2 部分 843 | * 由于 newIndexToOldIndexMap[i] 的初始化都为 0,0的意义代表 新的存在,老的不存在,需要创建新的 844 | * 这里的 e 为 0,有歧义,所以用 i+1 处理,最小 为 1,不会有歧义 845 | * 846 | * */ 847 | /** 848 | * newIndexToOldIndexMap 逻辑是这样的 849 | * 老的 ab cde fg 850 | * 新的 ad ecd fg 851 | * 初始 newIndexToOldIndexMap -> [0, 0, 0] 852 | * 遍历老节点,老c存在新节点创建的 Map 中,即 老c 的索引是0,所以newIndexToOldIndexMap[1] = 1(0+1) 853 | * 同理,老d存在新节点创建的Map中,即 老d 的索引是 1,所以 newIndexToOldIndexMap[2] = 2(1+1) 854 | * 老e的索引是2,所以 newIndexToOldIndexMap[0] = 3(2+1) 855 | * newIndexToOldIndexMap -> [3, 1, 2] 856 | */ 857 | // 图12 858 | newIndexToOldIndexMap[newIndex - s2] = i + 1; 859 | // 存在,继续进行深度对比 860 | patch(prevChild, c2[newIndex], container, parentComponent, null); 861 | patched++; 862 | } 863 | } 864 | /** 865 | * 移动节点 866 | * 新老都存在,只需要移动节点 867 | * 找到一个固定的序列cd,减少对比插入次数 868 | * 算法:最长递增子序列 869 | * [4,2,3] => [1,2], [4,2,3,5]=>[1,2,4] 870 | * a[i]= 0; i--) { 906 | // 拿到一个倒序的索引 907 | const nextIndex = i + s2; 908 | // 新节点树c2对应的 节点 909 | const nextChild = c2[nextIndex]; 910 | // 这个节点的下一个节点的el,如果需要移动,那么就插入到这个节点之前,这就是他为锚点 911 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null; 912 | if (newIndexToOldIndexMap[i] === 0) { 913 | // 创建逻辑 914 | patch(null, nextChild, container, parentComponent, anchor); 915 | } 916 | else if (moved) { 917 | /** 918 | * i 是 c2 的中间部分的索引 919 | * 如果倒序的索引 i 跟当前的 最长递归子序列的倒序索引 j 相同,那么说明是这个节点的位置不用移动 920 | * 如果不相同,那么需要插入这个节点 921 | * 需要找到这个节点,和锚点 922 | * 923 | * */ 924 | if (j < 0 || i !== increasingNewSequence[j]) { 925 | hostInsert(nextChild.el, container, anchor); 926 | // 不在最长递归子序列 927 | console.log("移动位置"); 928 | } 929 | else { 930 | j--; 931 | } 932 | } 933 | } 934 | } 935 | } 936 | /** 937 | * @description 删除children 节点 938 | * @author Werewolf 939 | * @date 2021-12-17 940 | * @param {*} children 941 | */ 942 | function unmountChildren(children) { 943 | for (var i = 0; i < children.length; i++) { 944 | const el = children[i].el; 945 | hostRemove(el); 946 | } 947 | } 948 | /** 949 | * @description patch 属性 950 | * @author Werewolf 951 | * @date 2021-12-20 952 | * @param {*} el 953 | * @param {*} oldProps 954 | * @param {*} newProps 955 | */ 956 | function patchProps(el, oldProps, newProps) { 957 | if (oldProps !== newProps) { 958 | // newProps 里面的 prop 不在 oldProps 里面,遍历新的 959 | for (const key in newProps) { 960 | // 对比props对象的属性 961 | const prveProp = oldProps[key]; 962 | const nextprop = newProps[key]; 963 | if (prveProp !== nextprop) { 964 | // 调用之前的 添加属性方法,需要一个 el 965 | // 多传一个参数,同时需要修改 hostPatchProp 方法 966 | // hostPatchProp(el, key, prveProp, nextprop) 967 | hostPatchProp(el, key, prveProp, nextprop); 968 | } 969 | } 970 | // oldProps 里的 prop 不在 newProps 里面,遍历旧的 971 | if (oldProps !== {}) { 972 | for (const key in oldProps) { 973 | if (!(key in newProps)) { 974 | hostPatchProp(el, key, oldProps[key], null); 975 | } 976 | } 977 | } 978 | } 979 | } 980 | function mountElement(vnode, container, parentComponent, anchor) { 981 | // canvas new Element 982 | // const el = (vnode.el = document.createElement(vnode.type)); 983 | const el = (vnode.el = hostCreateElement(vnode.type)); 984 | const { props, children, shapeFlag } = vnode; 985 | // string array 986 | // if (typeof children === "string") { 987 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 988 | el.textContent = children; 989 | } 990 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 991 | mountChildren(vnode.children, el, parentComponent, anchor); 992 | } 993 | for (const key in props) { 994 | const val = props[key]; 995 | hostPatchProp(el, key, null, val); 996 | } 997 | // canvas el.x = 10 998 | // container.append(el); 999 | hostInsert(el, container, anchor); 1000 | // canvas addChild() 1001 | } 1002 | /** 1003 | * @description 挂载数组节点 1004 | * @author Werewolf 1005 | * @date 2021-12-17 1006 | * @param {*} children [vnode1,vnode2] 1007 | * @param {*} container 1008 | * @param {*} parentComponent 1009 | */ 1010 | function mountChildren(children, container, parentComponent, anchor) { 1011 | children.forEach((v) => { 1012 | patch(null, v, container, parentComponent, anchor); 1013 | }); 1014 | } 1015 | function processComponent(n1, n2, container, parentComponent, anchor) { 1016 | if (!n1) { 1017 | // 初始化 1018 | mountComponent(n2, container, parentComponent, anchor); 1019 | } 1020 | else { 1021 | // 更新组件 调用当前组件的render 函数,重新 vnode 重新 patch, 也就是走 setupRenderEffect 逻辑 1022 | updateComponent(n1, n2); 1023 | } 1024 | } 1025 | /** 1026 | * @description 组件更新 1027 | * @author Werewolf 1028 | * @date 2021-12-24 1029 | * @param {*} n1 1030 | * @param {*} n2 1031 | */ 1032 | function updateComponent(n1, n2) { 1033 | // 利用effect runner 逻辑 1034 | /** 1035 | * 怎么找instance,现在只有n 虚拟节点 1036 | * 那么把实例挂载到虚拟节点 1037 | * 1038 | */ 1039 | const instance = (n2.component = n1.component); 1040 | if (shouldUpdateComponent(n1, n2)) { 1041 | instance.next = n2; 1042 | instance.update(); 1043 | } 1044 | else { 1045 | // 不需要更新也要重置虚拟节点 和 el 1046 | n2.el = n1.el; 1047 | n2.vnode = n2; 1048 | } 1049 | } 1050 | function mountComponent(initialVNode, container, parentComponent, anchor) { 1051 | // 根据虚拟节点创建组件实例 1052 | // 将组件实例 挂载到虚拟接节点 1053 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent)); 1054 | // 初始化,收集信息,instance挂载相关属性,方法, 装箱 1055 | setupComponent(instance); 1056 | // 渲染组件,调用组件的render方法 1057 | // 组件 -> const App = { 1058 | // render() { 1059 | // return h("div", this.msg) 1060 | // }, 1061 | // setup() { 1062 | // return { 1063 | // msg: "hello vue" 1064 | // } 1065 | // } 1066 | // } 1067 | // 一个组件不会真实渲染出来,渲染的是组件的render函数内部的element值,拆箱过程 1068 | // render 返回的subTree 给patch,如果是组件继续递归,如果是element 则渲染 1069 | setupRenderEffect(instance, initialVNode, container, anchor); 1070 | } 1071 | /** 1072 | * @description 调用render,也就是生成虚拟节点,进行patch。包括 初始化和更新流程 1073 | * @author Werewolf 1074 | * @date 2021-12-24 1075 | * @param {*} instance 1076 | * @param {*} initialVNode 1077 | * @param {*} container 1078 | * @param {*} anchor 1079 | */ 1080 | function setupRenderEffect(instance, initialVNode, container, anchor) { 1081 | instance.update = effect(() => { 1082 | if (!instance.isMounted) { 1083 | console.log("init 初始化"); 1084 | const { proxy } = instance; 1085 | const subTree = (instance.subTree = instance.render.call(proxy, proxy)); 1086 | patch(null, subTree, container, instance, anchor); 1087 | initialVNode.el = subTree.el; 1088 | instance.isMounted = true; 1089 | } 1090 | else { 1091 | console.log("update 更新"); 1092 | // next 新的虚拟节点 1093 | // vnode 老的虚拟节点 1094 | const { next, vnode } = instance; 1095 | // 更新el 1096 | if (next) { 1097 | next.el = vnode.el; 1098 | // 更新属性 1099 | updateComponentPreRender(instance, next); 1100 | } 1101 | const { proxy } = instance; 1102 | const subTree = instance.render.call(proxy, proxy); 1103 | const prevSubTree = instance.subTree; 1104 | instance.subTree = subTree; 1105 | patch(prevSubTree, subTree, container, instance, anchor); 1106 | } 1107 | }, { 1108 | scheduler() { 1109 | console.log("effect 的 scheduler 逻辑,数据更新,视图不更新"); 1110 | queueJobs(instance.update); 1111 | }, 1112 | }); 1113 | } 1114 | return { 1115 | createApp: createAppAPI(render), 1116 | }; 1117 | } 1118 | /** 1119 | * @description 更新属性 1120 | * @author Werewolf 1121 | * @date 2021-12-24 1122 | * @param {*} instance 1123 | * @param {*} nextVNode 1124 | */ 1125 | function updateComponentPreRender(instance, nextVNode) { 1126 | // 更新实例的虚拟节点 1127 | instance.vnode = nextVNode; 1128 | instance.next = null; 1129 | // 更新props 1130 | instance.props = nextVNode.props; 1131 | } 1132 | /** 1133 | * @description 最长递增子序列 1134 | * @author Werewolf 1135 | * @date 2021-12-24 1136 | * @param {*} arr 1137 | * @return {*} 1138 | */ 1139 | function getSequence(arr) { 1140 | const p = arr.slice(); 1141 | const result = [0]; 1142 | let i, j, u, v, c; 1143 | const len = arr.length; 1144 | for (i = 0; i < len; i++) { 1145 | const arrI = arr[i]; 1146 | if (arrI !== 0) { 1147 | j = result[result.length - 1]; 1148 | if (arr[j] < arrI) { 1149 | p[i] = j; 1150 | result.push(i); 1151 | continue; 1152 | } 1153 | u = 0; 1154 | v = result.length - 1; 1155 | while (u < v) { 1156 | c = (u + v) >> 1; 1157 | if (arr[result[c]] < arrI) { 1158 | u = c + 1; 1159 | } 1160 | else { 1161 | v = c; 1162 | } 1163 | } 1164 | if (arrI < arr[result[u]]) { 1165 | if (u > 0) { 1166 | p[i] = result[u - 1]; 1167 | } 1168 | result[u] = i; 1169 | } 1170 | } 1171 | } 1172 | u = result.length; 1173 | v = result[u - 1]; 1174 | while (u-- > 0) { 1175 | result[u] = v; 1176 | v = p[v]; 1177 | } 1178 | return result; 1179 | } 1180 | 1181 | function createElement(type) { 1182 | // console.log("dom ------api") 1183 | return document.createElement(type); 1184 | } 1185 | function patchProp(el, key, prevVal, nextVal) { 1186 | const isOn = (key) => /^on[A-Z]/.test(key); 1187 | if (isOn(key)) { 1188 | const event = key.slice(2).toLowerCase(); 1189 | el.addEventListener(event, nextVal); 1190 | } 1191 | else { 1192 | if (nextVal === undefined || nextVal === null) { 1193 | el.removeAttribute(key); 1194 | } 1195 | else { 1196 | el.setAttribute(key, nextVal); 1197 | } 1198 | } 1199 | } 1200 | /** 1201 | * @description 将子节点插入到指定位置anchor,没有指定位置默认插入到最后 1202 | * @author Werewolf 1203 | * @date 2021-12-20 1204 | * @param {*} child 1205 | * @param {*} parent 1206 | * @param {*} anchor 将要插在这个节点之前 1207 | */ 1208 | function insert(child, parent, anchor) { 1209 | // console.log("dom ------api") 1210 | // 插入到最后 1211 | // parent.append(child) 等价于 parent.insertBefore(child, parent, null) 1212 | // console.log() 1213 | parent.insertBefore(child, anchor || null); 1214 | } 1215 | /** 1216 | * @description 删除子节点 1217 | * @author Werewolf 1218 | * @date 2021-12-17 1219 | * @param {*} child 子节点 1220 | */ 1221 | function remove(child) { 1222 | const parent = child.parentNode; 1223 | if (parent) { 1224 | parent.removeChild(child); 1225 | } 1226 | } 1227 | /** 1228 | * @description 设置text 节点 1229 | * @author Werewolf 1230 | * @date 2021-12-17 1231 | * @param {*} el 父容器 1232 | * @param {*} text 子节点 1233 | */ 1234 | function setElementText(el, text) { 1235 | el.textContent = text; 1236 | } 1237 | const renderer = createRenderer({ 1238 | createElement, 1239 | patchProp, 1240 | setElementText, 1241 | remove, 1242 | insert, 1243 | }); 1244 | // return { 1245 | // createApp: createAppAPI(render) 1246 | // } 1247 | function createApp(...args) { 1248 | return renderer.createApp(...args); 1249 | // 调用流程 1250 | // return createAppAPI(render)(...args); 1251 | // export function createAppAPI(render) { 1252 | // return function createApp(rootComponent) { 1253 | // return { 1254 | // mount(rootContainer) { 1255 | // // 先创建 vnode 1256 | // // component -> vnode 1257 | // // 所有逻辑操作 都会基于 vnode 做处理 1258 | // const vnode = createVNode(rootComponent); 1259 | // // 渲染虚拟节点 1260 | // render(vnode, rootContainer); 1261 | // }, 1262 | // }; 1263 | // } 1264 | // } 1265 | } 1266 | 1267 | var runtimeDom = /*#__PURE__*/Object.freeze({ 1268 | __proto__: null, 1269 | createApp: createApp, 1270 | h: h, 1271 | renderSlots: renderSlots, 1272 | createTextVNode: createTextVNode, 1273 | createElementVNode: createVNode, 1274 | getCurrentInstance: getCurrentInstance, 1275 | registerRuntimeCompiler: registerRuntimeCompiler, 1276 | provide: provide, 1277 | inject: inject, 1278 | createRenderer: createRenderer, 1279 | nextTick: nextTick, 1280 | toDisplayString: toDisplayString, 1281 | ref: ref, 1282 | isRef: isRef, 1283 | unRef: unRef, 1284 | proxyRefs: proxyRefs 1285 | }); 1286 | 1287 | const TO_DISPLAY_STRING = Symbol("toDisplayString"); 1288 | const CREATE_ELEMENT_VNODE = Symbol("createElementVNode"); 1289 | const helperMapName = { 1290 | [TO_DISPLAY_STRING]: "toDisplayString", 1291 | [CREATE_ELEMENT_VNODE]: "createElementVNode", 1292 | }; 1293 | 1294 | function generate(ast) { 1295 | const context = createCodegenContext(); 1296 | const { push } = context; 1297 | // 导入逻辑 const { toDisplayString: _toDisplayString } = Vue 1298 | genFunctionPreamble(ast, context); 1299 | const functionName = "render"; 1300 | const args = ["_ctx", "_cache"]; 1301 | const signature = args.join(", "); 1302 | push(`function ${functionName}(${signature}) {`); 1303 | push("return "); 1304 | genNode(ast.codegenNode, context); 1305 | push("}"); 1306 | return { 1307 | code: context.code, 1308 | }; 1309 | } 1310 | function genFunctionPreamble(ast, context) { 1311 | const { push } = context; 1312 | const VueBinging = "Vue"; 1313 | if (ast.helpers.length) { 1314 | const aliasHelper = (s) => `${helperMapName[s]}: _${helperMapName[s]}`; 1315 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`); 1316 | push("\n"); 1317 | } 1318 | push("return "); 1319 | } 1320 | function genNode(node, context) { 1321 | switch (node.type) { 1322 | case 3 /* TEXT */: 1323 | // 处理文本 把内容返回 1324 | genText(node, context); 1325 | break; 1326 | case 0 /* INTERPOLATION */: 1327 | // 处理插值 _toDisplayString 1328 | // node - { type: 0, content: { type: 1, content: 'message' } } 1329 | genInterpolation(node, context); 1330 | break; 1331 | case 1 /* SIMPLE_EXPRESSION */: 1332 | genExpression(node, context); 1333 | break; 1334 | case 2 /* ELEMENT */: 1335 | genElement(node, context); 1336 | break; 1337 | case 5 /* COMPOUND_EXPRESSION */: 1338 | genCompoundExpression(node, context); 1339 | break; 1340 | } 1341 | } 1342 | function genCompoundExpression(node, context) { 1343 | const { push } = context; 1344 | const children = node.children; 1345 | for (let i = 0; i < children.length; i++) { 1346 | const child = children[i]; 1347 | if (isString(child)) { 1348 | push(child); 1349 | } 1350 | else { 1351 | genNode(child, context); 1352 | } 1353 | } 1354 | } 1355 | function genElement(node, context) { 1356 | const { push, helper } = context; 1357 | const { tag, children, props } = node; 1358 | push(`${helper(CREATE_ELEMENT_VNODE)}(`); 1359 | genNodeList(genNullable([tag, props, children]), context); 1360 | // console.log(children); 1361 | // [ 1362 | // { type: 3, content: 'hi, ' }, 1363 | // { type: 0, content: { type: 1, content: 'message' } } 1364 | // ] 1365 | // for (let i = 0; i < children.length; i++) { 1366 | // const child = children[i]; 1367 | // genNode(child, context); 1368 | // } 1369 | // genNode(children, context); 1370 | push(")"); 1371 | } 1372 | function genNodeList(nodes, context) { 1373 | const { push } = context; 1374 | for (let i = 0; i < nodes.length; i++) { 1375 | const node = nodes[i]; 1376 | if (isString(node)) { 1377 | push(node); 1378 | } 1379 | else { 1380 | genNode(node, context); 1381 | } 1382 | if (i < nodes.length - 1) { 1383 | push(", "); 1384 | } 1385 | } 1386 | } 1387 | function genNullable(args) { 1388 | return args.map((arg) => arg || "null"); 1389 | } 1390 | function genText(node, context) { 1391 | const { push } = context; 1392 | push(`'${node.content}'`); 1393 | } 1394 | function createCodegenContext() { 1395 | const context = { 1396 | code: "", 1397 | push(source) { 1398 | context.code += source; 1399 | }, 1400 | helper(key) { 1401 | return `_${helperMapName[key]}`; 1402 | }, 1403 | }; 1404 | return context; 1405 | } 1406 | function genInterpolation(node, context) { 1407 | const { push, helper } = context; 1408 | // push(`_toDisplayString(_ctx.message)`) 1409 | push(`${helper(TO_DISPLAY_STRING)}(`); 1410 | genNode(node.content, context); 1411 | push(`)`); 1412 | } 1413 | function genExpression(node, context) { 1414 | const { push } = context; 1415 | push(`${node.content}`); 1416 | } 1417 | 1418 | function baseParse(content) { 1419 | const context = createParserContext(content); 1420 | return createRoot(parseChildren(context, [])); 1421 | } 1422 | function parseChildren(context, ancestors) { 1423 | const nodes = []; 1424 | while (!isEnd(context, ancestors)) { 1425 | let node; 1426 | let s = context.source; 1427 | if (s.startsWith("{{")) { 1428 | node = parseInterpolation(context); 1429 | } 1430 | else if (s[0] === "<") { 1431 | // element 1432 | if (/[a-z]/i.test(s[1])) { 1433 | node = parseElement(context, ancestors); 1434 | } 1435 | } 1436 | if (!node) { 1437 | node = parseText(context); 1438 | } 1439 | nodes.push(node); 1440 | } 1441 | return nodes; 1442 | } 1443 | function isEnd(context, ancestors) { 1444 | const s = context.source; 1445 | if (s.startsWith("= 0; i--) { 1447 | const tag = ancestors[i].tag; 1448 | // if (s.slice(2, 2 + tag.length) === tag) { 1449 | if (startsWithEndTagOpen(s, tag)) { 1450 | return true; 1451 | } 1452 | } 1453 | } 1454 | return !s; 1455 | } 1456 | function parseText(context) { 1457 | let endIndex = context.source.length; 1458 | let endToken = ["<", "{{"]; 1459 | for (let i = 0; i < endToken.length; i++) { 1460 | const index = context.source.indexOf(endToken[i]); 1461 | if (index !== -1 && endIndex > index) { 1462 | endIndex = index; 1463 | } 1464 | } 1465 | const content = parseTextData(context, endIndex); 1466 | return { 1467 | type: 3 /* TEXT */, 1468 | content: content, 1469 | }; 1470 | } 1471 | function parseTextData(context, length) { 1472 | const content = context.source.slice(0, length); 1473 | advanceBy(context, length); 1474 | return content; 1475 | } 1476 | function parseInterpolation(context) { 1477 | // {{xxx}} 1478 | const openDelimiter = "{{"; 1479 | const closeDelimiter = "}}"; 1480 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length); 1481 | advanceBy(context, openDelimiter.length); 1482 | const rawContentLength = closeIndex - openDelimiter.length; 1483 | const rawContent = parseTextData(context, rawContentLength); 1484 | // context.source.slice(0, rawContentLength); 1485 | const content = rawContent.trim(); 1486 | // delete }} 1487 | advanceBy(context, closeDelimiter.length); 1488 | return { 1489 | type: 0 /* INTERPOLATION */, 1490 | content: { 1491 | type: 1 /* SIMPLE_EXPRESSION */, 1492 | content: content, 1493 | }, 1494 | }; 1495 | } 1496 | function advanceBy(context, length) { 1497 | context.source = context.source.slice(length); 1498 | } 1499 | function createRoot(children) { 1500 | return { 1501 | children, 1502 | type: 4 /* ROOT */, 1503 | }; 1504 | } 1505 | function createParserContext(context) { 1506 | return { 1507 | source: context, 1508 | }; 1509 | } 1510 | function parseElement(context, ancestors) { 1511 | // 解析tag 1512 | const element = parseTag(context, 0 /* Start */); 1513 | ancestors.push(element); 1514 | element.children = parseChildren(context, ancestors); 1515 | ancestors.pop(); 1516 | if (startsWithEndTagOpen(context.source, element.tag)) { 1517 | parseTag(context, 1 /* End */); 1518 | } 1519 | else { 1520 | throw new Error(`缺少结束标签:${element.tag}`); 1521 | } 1522 | // element 已经推进,所以直接地柜即可 1523 | return element; 1524 | } 1525 | function startsWithEndTagOpen(source, tag) { 1526 | return source.startsWith(" { 1617 | // tag 1618 | const vnodeTag = `"${node.tag}"`; 1619 | // props 1620 | let vnodeProps; 1621 | // children 1622 | const children = node.children; 1623 | let vnodeChildren = children[0]; 1624 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren); 1625 | }; 1626 | } 1627 | } 1628 | 1629 | function transformExpression(node) { 1630 | if (node.type === 0 /* INTERPOLATION */) { 1631 | node.content = processExpression(node.content); 1632 | } 1633 | } 1634 | function processExpression(node) { 1635 | node.content = `_ctx.${node.content}`; 1636 | return node; 1637 | } 1638 | 1639 | function isText(node) { 1640 | return (node.nodeType = 1641 | 3 /* TEXT */ /* INTERPOLATION */); 1642 | } 1643 | 1644 | function transformText(node, context) { 1645 | if (node.type === 2 /* ELEMENT */) { 1646 | return () => { 1647 | const { children } = node; 1648 | let currentContainer; 1649 | for (let i = 0; i < children.length; i++) { 1650 | const child = children[i]; 1651 | if (isText(child)) { 1652 | for (let j = i + 1; j < children.length; j++) { 1653 | const next = children[j]; 1654 | if (isText(next)) { 1655 | if (!currentContainer) { 1656 | currentContainer = children[i] = { 1657 | type: 5 /* COMPOUND_EXPRESSION */, 1658 | children: [child], 1659 | }; 1660 | } 1661 | currentContainer.children.push(" + "); 1662 | currentContainer.children.push(next); 1663 | children.splice(j, 1); 1664 | j--; 1665 | } 1666 | else { 1667 | currentContainer = undefined; 1668 | break; 1669 | } 1670 | } 1671 | } 1672 | } 1673 | }; 1674 | } 1675 | } 1676 | 1677 | function baseCompile(template) { 1678 | const ast = baseParse(template); 1679 | transform(ast, { 1680 | nodeTransforms: [transformExpression, transformElement, transformText], 1681 | }); 1682 | return generate(ast); 1683 | } 1684 | 1685 | // mini-vue 的出口 1686 | function compileToFunction(template) { 1687 | const { code } = baseCompile(template); 1688 | const render = new Function("Vue", code)(runtimeDom); 1689 | return render; 1690 | } 1691 | registerRuntimeCompiler(compileToFunction); 1692 | 1693 | export { createApp, createVNode as createElementVNode, createRenderer, createTextVNode, getCurrentInstance, h, inject, isRef, nextTick, provide, proxyRefs, ref, registerRuntimeCompiler, renderSlots, toDisplayString, unRef }; 1694 | -------------------------------------------------------------------------------- /md/customRender.md: -------------------------------------------------------------------------------- 1 | ## 第一种情况,只基于 dom api 的 render 2 | 3 | 1. 用户调用 createApp 4 | 5 | ```ts 6 | createApp(App).mount(rootContainer); 7 | ``` 8 | 9 | 2. createApp 10 | 11 | ```ts 12 | // 直接导出的 createApp 13 | export function createApp(rootComponent) { 14 | return { 15 | mount(rootContainer) { 16 | const vnode = createVNode(rootComponent); 17 | render(vnode, rootContainer); 18 | }, 19 | }; 20 | } 21 | ``` 22 | 23 | 3. render 24 | 25 | ```ts 26 | export function render(vnode, container) { 27 | // dom api 创建元素 28 | const el = (vnode.el = document.createElement(vnode.type)); 29 | // dom api 设置属性 30 | // ... 31 | // dom api 插入 32 | container.append(el); 33 | } 34 | ``` 35 | 36 | ## 第二种情况,基于用户传入 api 的 dom 的 customRenderer 37 | 38 | 1. 用户调用 createApp 39 | 40 | ```ts 41 | createApp(App).mount(rootContainer); 42 | ``` 43 | 44 | 2. createApp 45 | 46 | ```ts 47 | // runtime-dom 里面的导出的 createApp 48 | export function createApp(...args) { 49 | return renderer.createApp(...args); 50 | } 51 | 52 | const renderer: any = createRenderer({ 53 | //下面的api 都是 dom的 54 | createElement, 55 | patchProp, 56 | insert, 57 | }); 58 | ``` 59 | 60 | 3. createRenderer 61 | 62 | ```ts 63 | export function createRenderer(options) { 64 | const { createElement, patchProp, insert } = options; 65 | 66 | function render(vnode, container) { 67 | // 同第一种情况的render相同了,相当只是提取了 68 | 69 | // dom api 创建元素 70 | createElement(); 71 | // dom api 设置属性 72 | patchProp(); 73 | // ... 74 | // dom api 插入 75 | insert(); 76 | } 77 | 78 | // ... 79 | return { 80 | createApp: createAppAPI(render), 81 | }; 82 | } 83 | 84 | function createAppAPI(render) { 85 | return function createApp(rootComponent) { 86 | return { 87 | mount(rootContain) { 88 | const vnode = createVNode(rootComponent); 89 | render(vnode, rootContain); 90 | }, 91 | }; 92 | }; 93 | } 94 | ``` 95 | 96 | ## 第二种情况拓展-基于用户传入的 api 的 customRenderer 97 | 98 | #### 例子基于 canvas 99 | 100 | 1. 用户调用 101 | 102 | ```ts 103 | renderer.createApp(App).mount(game.stage); 104 | ``` 105 | 106 | 2. renderer 107 | 108 | ```ts 109 | const renderer = createRenderer({ 110 | // 用户传入的基于 canvas 的 api 111 | createElement(type) { 112 | if (type === "rect") { 113 | const rect = new PIXI.Graphics(); 114 | rect.beginFill(0xff0000); 115 | rect.drawRect(0, 0, 100, 100); 116 | rect.endFill(); 117 | return rect; 118 | } 119 | }, 120 | patchProp(el, key, val) { 121 | el[key] = val; 122 | }, 123 | insert(el, parent) { 124 | parent.addChild(el); 125 | }, 126 | }); 127 | 128 | export function createRenderer(options) { 129 | const { createElement, patchProp, insert } = options; 130 | 131 | function render(vnode, container) { 132 | // canvas api 创建元素 133 | createElement(); 134 | // canvas api 设置属性 135 | patchProp(); 136 | // ... 137 | // canvas api 插入 138 | insert(); 139 | } 140 | 141 | // ... 142 | return { 143 | createApp: createAppAPI(render), 144 | }; 145 | } 146 | // 下面同第二种情况的 createApp 的逻辑了 147 | function createAppAPI(render) { 148 | return function createApp(rootComponent) { 149 | return { 150 | mount(rootContain) { 151 | const vnode = createVNode(rootComponent); 152 | render(vnode, rootContain); 153 | }, 154 | }; 155 | }; 156 | } 157 | ``` 158 | 159 | _总结_ 160 | 161 | - 基于 dom 的 createApp 是在 runtime-dom 里面导出的, render 函数使用的 api 是默认传入的 162 | - 基于 canvas 的 createApp 是用户自定义的,用户需要定义一些 api,给 render 函数使用 163 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-vue-again", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "build": "rollup -c rollup.config.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/fengjinlong/mini-vue-again.git" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/fengjinlong/mini-vue-again/issues" 19 | }, 20 | "homepage": "https://github.com/fengjinlong/mini-vue-again#readme", 21 | "devDependencies": { 22 | "@babel/core": "^7.16.0", 23 | "@babel/preset-env": "^7.16.4", 24 | "@babel/preset-typescript": "^7.16.0", 25 | "@rollup/plugin-typescript": "^8.3.0", 26 | "@types/jest": "^27.0.3", 27 | "jest": "^27.4.3", 28 | "rollup": "^2.61.1", 29 | "tslib": "^2.3.1", 30 | "typescript": "^4.5.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript" 2 | export default { 3 | input: "./src/index.ts", 4 | output: [ 5 | { 6 | format: "cjs", 7 | file: "lib/guide-mini-vue.cjs.js" 8 | }, 9 | { 10 | format: "es", 11 | file: "lib/guide-mini-vue.esm.js" 12 | } 13 | ], 14 | plugins: [typescript()] 15 | } -------------------------------------------------------------------------------- /src/112.js: -------------------------------------------------------------------------------- 1 | function fun(arr) { 2 | let maxArrIndex = []; 3 | 4 | // 起始点 5 | for (let i = 0; i < arr.length; i++) { 6 | // 从i出发,第一个递增子数组 7 | const list = []; 8 | // i< arr.length 可以优化 9 | 10 | let j = i; 11 | while (j < arr.length && arr[j] <= arr[j + 1]) { 12 | list.push(j); 13 | j++; 14 | } 15 | list.push(j); 16 | 17 | if (list.length > maxArrIndex.length) { 18 | maxArrIndex = []; 19 | for (t = 0; t < list.length; t++) { 20 | maxArrIndex.push(list[t]); 21 | } 22 | } 23 | } 24 | console.log(maxArrIndex); 25 | } 26 | const arr = [3, 2, 5, 1, 2, 3]; 27 | fun(arr); 28 | // 最长递增子序列 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/compiler-core/src/ast.ts: -------------------------------------------------------------------------------- 1 | import { CREATE_ELEMENT_VNODE } from "./runtimeHelpers"; 2 | 3 | export const enum NodeTypes { 4 | INTERPOLATION, 5 | SIMPLE_EXPRESSION, 6 | ELEMENT, 7 | TEXT, 8 | ROOT, 9 | COMPOUND_EXPRESSION 10 | } 11 | export function createVNodeCall(context, tag, props, children) { 12 | context.helper(CREATE_ELEMENT_VNODE); 13 | 14 | return { 15 | type: NodeTypes.ELEMENT, 16 | tag, 17 | props, 18 | children, 19 | }; 20 | } -------------------------------------------------------------------------------- /src/compiler-core/src/codegen.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "../../shared"; 2 | import { NodeTypes } from "./ast"; 3 | import { 4 | CREATE_ELEMENT_VNODE, 5 | helperMapName, 6 | TO_DISPLAY_STRING, 7 | } from "./runtimeHelpers"; 8 | 9 | export function generate(ast) { 10 | const context = createCodegenContext(); 11 | const { push } = context; 12 | 13 | // 导入逻辑 const { toDisplayString: _toDisplayString } = Vue 14 | genFunctionPreamble(ast, context); 15 | 16 | const functionName = "render"; 17 | const args = ["_ctx", "_cache"]; 18 | const signature = args.join(", "); 19 | push(`function ${functionName}(${signature}) {`); 20 | push("return "); 21 | genNode(ast.codegenNode, context); 22 | push("}"); 23 | return { 24 | code: context.code, 25 | }; 26 | } 27 | function genFunctionPreamble(ast, context) { 28 | const { push } = context; 29 | const VueBinging = "Vue"; 30 | if (ast.helpers.length) { 31 | const aliasHelper = (s) => `${helperMapName[s]}: _${helperMapName[s]}`; 32 | push( 33 | `const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}` 34 | ); 35 | push("\n"); 36 | } 37 | push("return "); 38 | } 39 | 40 | function genNode(node: any, context) { 41 | switch (node.type) { 42 | case NodeTypes.TEXT: 43 | // 处理文本 把内容返回 44 | genText(node, context); 45 | break; 46 | case NodeTypes.INTERPOLATION: 47 | // 处理插值 _toDisplayString 48 | // node - { type: 0, content: { type: 1, content: 'message' } } 49 | genInterpolation(node, context); 50 | break; 51 | case NodeTypes.SIMPLE_EXPRESSION: 52 | genExpression(node, context); 53 | break; 54 | case NodeTypes.ELEMENT: 55 | genElement(node, context); 56 | break; 57 | case NodeTypes.COMPOUND_EXPRESSION: 58 | genCompoundExpression(node, context); 59 | break; 60 | } 61 | } 62 | 63 | function genCompoundExpression(node: any, context: any) { 64 | const { push } = context; 65 | const children = node.children; 66 | for (let i = 0; i < children.length; i++) { 67 | const child = children[i]; 68 | if (isString(child)) { 69 | push(child); 70 | } else { 71 | genNode(child, context); 72 | } 73 | } 74 | } 75 | function genElement(node: any, context) { 76 | const { push, helper } = context; 77 | const { tag, children, props } = node; 78 | push(`${helper(CREATE_ELEMENT_VNODE)}(`); 79 | genNodeList(genNullable([tag, props, children]), context); 80 | // console.log(children); 81 | // [ 82 | // { type: 3, content: 'hi, ' }, 83 | // { type: 0, content: { type: 1, content: 'message' } } 84 | // ] 85 | 86 | // for (let i = 0; i < children.length; i++) { 87 | // const child = children[i]; 88 | // genNode(child, context); 89 | // } 90 | // genNode(children, context); 91 | push(")"); 92 | } 93 | function genNodeList(nodes, context) { 94 | const { push } = context; 95 | 96 | for (let i = 0; i < nodes.length; i++) { 97 | const node = nodes[i]; 98 | if (isString(node)) { 99 | push(node); 100 | } else { 101 | genNode(node, context); 102 | } 103 | 104 | if (i < nodes.length - 1) { 105 | push(", "); 106 | } 107 | } 108 | } 109 | function genNullable(args: any) { 110 | return args.map((arg) => arg || "null"); 111 | } 112 | function genText(node, context: any) { 113 | const { push } = context; 114 | push(`'${node.content}'`); 115 | } 116 | 117 | function createCodegenContext() { 118 | const context = { 119 | code: "", 120 | push(source) { 121 | context.code += source; 122 | }, 123 | helper(key) { 124 | return `_${helperMapName[key]}`; 125 | }, 126 | }; 127 | return context; 128 | } 129 | function genInterpolation(node: any, context: any) { 130 | const { push, helper } = context; 131 | // push(`_toDisplayString(_ctx.message)`) 132 | push(`${helper(TO_DISPLAY_STRING)}(`); 133 | genNode(node.content, context); 134 | push(`)`); 135 | } 136 | 137 | function genExpression(node: any, context: any) { 138 | const { push } = context; 139 | push(`${node.content}`); 140 | } 141 | -------------------------------------------------------------------------------- /src/compiler-core/src/compile.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "./codegen"; 2 | import { baseParse } from "./parse"; 3 | import { transform } from "./transform"; 4 | import { transformElement } from "./transforms/transformElement"; 5 | import { transformExpression } from "./transforms/transformExpression"; 6 | import { transformText } from "./transforms/transformText"; 7 | 8 | export function baseCompile(template) { 9 | const ast: any = baseParse(template); 10 | transform(ast, { 11 | nodeTransforms: [transformExpression, transformElement, transformText], 12 | }); 13 | return generate(ast); 14 | } 15 | -------------------------------------------------------------------------------- /src/compiler-core/src/index.ts: -------------------------------------------------------------------------------- 1 | // 每个模块基于 此 对外导出方法 2 | export * from "./compile"; 3 | -------------------------------------------------------------------------------- /src/compiler-core/src/parse.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | 3 | const enum TagType { 4 | Start, 5 | End, 6 | } 7 | 8 | export function baseParse(content: string) { 9 | const context = createParserContext(content); 10 | 11 | return createRoot(parseChildren(context, [])); 12 | } 13 | 14 | function parseChildren(context, ancestors) { 15 | const nodes: any = []; 16 | while (!isEnd(context, ancestors)) { 17 | let node; 18 | let s = context.source; 19 | if (s.startsWith("{{")) { 20 | node = parseInterpolation(context); 21 | } else if (s[0] === "<") { 22 | // element 23 | if (/[a-z]/i.test(s[1])) { 24 | node = parseElement(context, ancestors); 25 | } 26 | } 27 | if (!node) { 28 | node = parseText(context); 29 | } 30 | nodes.push(node); 31 | } 32 | return nodes; 33 | } 34 | function isEnd(context, ancestors) { 35 | const s = context.source; 36 | if (s.startsWith("= 0; i--) { 38 | const tag = ancestors[i].tag; 39 | // if (s.slice(2, 2 + tag.length) === tag) { 40 | if (startsWithEndTagOpen(s, tag)) { 41 | return true; 42 | } 43 | } 44 | } 45 | return !s; 46 | } 47 | 48 | function parseText(context: any): any { 49 | let endIndex = context.source.length; 50 | let endToken = ["<", "{{"]; 51 | for (let i = 0; i < endToken.length; i++) { 52 | const index = context.source.indexOf(endToken[i]); 53 | 54 | if (index !== -1 && endIndex > index) { 55 | endIndex = index; 56 | } 57 | } 58 | const content = parseTextData(context, endIndex); 59 | return { 60 | type: NodeTypes.TEXT, 61 | content: content, 62 | }; 63 | } 64 | 65 | function parseTextData(context: any, length) { 66 | const content = context.source.slice(0, length); 67 | advanceBy(context, length); 68 | return content; 69 | } 70 | 71 | function parseInterpolation(context: any) { 72 | // {{xxx}} 73 | 74 | const openDelimiter = "{{"; 75 | const closeDelimiter = "}}"; 76 | 77 | const closeIndex = context.source.indexOf( 78 | closeDelimiter, 79 | openDelimiter.length 80 | ); 81 | advanceBy(context, openDelimiter.length); 82 | const rawContentLength = closeIndex - openDelimiter.length; 83 | const rawContent = parseTextData(context, rawContentLength); 84 | // context.source.slice(0, rawContentLength); 85 | const content = rawContent.trim(); 86 | 87 | // delete }} 88 | advanceBy(context, closeDelimiter.length); 89 | 90 | return { 91 | type: NodeTypes.INTERPOLATION, 92 | content: { 93 | type: NodeTypes.SIMPLE_EXPRESSION, 94 | content: content, 95 | }, 96 | }; 97 | } 98 | 99 | function advanceBy(context: any, length: number) { 100 | context.source = context.source.slice(length); 101 | } 102 | 103 | function createRoot(children) { 104 | return { 105 | children, 106 | type: NodeTypes.ROOT, 107 | }; 108 | } 109 | function createParserContext(context: any) { 110 | return { 111 | source: context, 112 | }; 113 | } 114 | function parseElement(context: any, ancestors) { 115 | // 解析tag 116 | const element: any = parseTag(context, TagType.Start); 117 | ancestors.push(element); 118 | element.children = parseChildren(context, ancestors); 119 | ancestors.pop(); 120 | if (startsWithEndTagOpen(context.source, element.tag)) { 121 | parseTag(context, TagType.End); 122 | } else { 123 | throw new Error(`缺少结束标签:${element.tag}`); 124 | } 125 | // element 已经推进,所以直接地柜即可 126 | return element; 127 | } 128 | 129 | function startsWithEndTagOpen(source: any, tag: any) { 130 | return source.startsWith(" { 8 | // tag 9 | const vnodeTag = `"${node.tag}"`; 10 | 11 | // props 12 | let vnodeProps; 13 | 14 | // children 15 | const children = node.children; 16 | let vnodeChildren = children[0]; 17 | 18 | node.codegenNode = createVNodeCall( 19 | context, 20 | vnodeTag, 21 | vnodeProps, 22 | vnodeChildren 23 | ); 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformExpression.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | 3 | export function transformExpression(node) { 4 | if (node.type === NodeTypes.INTERPOLATION) { 5 | node.content = processExpression(node.content); 6 | } 7 | } 8 | 9 | function processExpression(node: any) { 10 | node.content = `_ctx.${node.content}`; 11 | return node; 12 | } 13 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformText.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | import { isText } from "../utils"; 3 | 4 | export function transformText(node, context) { 5 | if (node.type === NodeTypes.ELEMENT) { 6 | return () => { 7 | const { children } = node; 8 | let currentContainer; 9 | for (let i = 0; i < children.length; i++) { 10 | const child = children[i]; 11 | if (isText(child)) { 12 | for (let j = i + 1; j < children.length; j++) { 13 | const next = children[j]; 14 | if (isText(next)) { 15 | if (!currentContainer) { 16 | currentContainer = children[i] = { 17 | type: NodeTypes.COMPOUND_EXPRESSION, 18 | children: [child], 19 | }; 20 | } 21 | currentContainer.children.push(" + "); 22 | currentContainer.children.push(next); 23 | children.splice(j, 1); 24 | j--; 25 | } else { 26 | currentContainer = undefined; 27 | break; 28 | } 29 | } 30 | } 31 | } 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/compiler-core/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | 3 | export function isText(node) { 4 | return (node.nodeType = 5 | NodeTypes.TEXT || node.nodeType === NodeTypes.INTERPOLATION); 6 | } 7 | -------------------------------------------------------------------------------- /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`] = `"return function render(_ctx, _cache) {return 'hi'}"`; 14 | -------------------------------------------------------------------------------- /src/compiler-core/tests/codegen.spec.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "../src/codegen"; 2 | import { baseParse } from "../src/parse"; 3 | import { transform } from "../src/transform"; 4 | import { transformElement } from "../src/transforms/transformElement"; 5 | import { transformExpression } from "../src/transforms/transformExpression"; 6 | import { transformText } from "../src/transforms/transformText"; 7 | 8 | describe("codegen", () => { 9 | it("string", () => { 10 | const ast = baseParse("hi"); 11 | transform(ast); 12 | const { code } = generate(ast); 13 | expect(code).toMatchSnapshot(); 14 | }); 15 | 16 | it("interpolation", () => { 17 | const ast = baseParse("{{message}}"); 18 | transform(ast, { 19 | nodeTransforms: [transformExpression], 20 | }); 21 | const { code } = generate(ast); 22 | // 快照 23 | expect(code).toMatchSnapshot(); 24 | }); 25 | it("element", () => { 26 | const ast: any = baseParse("
hi, {{message}}
"); 27 | transform(ast, { 28 | nodeTransforms: [transformExpression, transformElement, transformText], 29 | }); 30 | const { code } = generate(ast); 31 | expect(code).toMatchSnapshot(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/compiler-core/tests/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../src/ast"; 2 | import { baseParse } from "../src/parse"; 3 | 4 | describe("Parse", () => { 5 | describe("interpolation", () => { 6 | test("interpolation", () => { 7 | const ast = baseParse("{{message}}"); 8 | expect(ast.children[0]).toStrictEqual({ 9 | type: NodeTypes.INTERPOLATION, 10 | content: { 11 | type: NodeTypes.SIMPLE_EXPRESSION, 12 | content: "message", 13 | }, 14 | }); 15 | }); 16 | }); 17 | 18 | describe("element", () => { 19 | it("simple element div", () => { 20 | // const ast = baseParse("

"); 21 | const ast = baseParse("
"); 22 | 23 | expect(ast.children[0]).toStrictEqual({ 24 | type: NodeTypes.ELEMENT, 25 | tag: "div", 26 | children: [ 27 | // { 28 | // type: NodeTypes.ELEMENT, 29 | // tag: "div", 30 | // children: [], 31 | // }, 32 | ], 33 | }); 34 | }); 35 | }); 36 | 37 | describe("text", () => { 38 | it("simple text", () => { 39 | const ast = baseParse("some text"); 40 | 41 | expect(ast.children[0]).toStrictEqual({ 42 | type: NodeTypes.TEXT, 43 | content: "some text", 44 | }); 45 | }); 46 | }); 47 | 48 | test("hello world", () => { 49 | const ast = baseParse("

hi,{{message}}

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

hi

{{message}}
"); 72 | 73 | expect(ast.children[0]).toStrictEqual({ 74 | type: NodeTypes.ELEMENT, 75 | tag: "div", 76 | children: [ 77 | { 78 | type: NodeTypes.ELEMENT, 79 | tag: "p", 80 | children: [ 81 | { 82 | type: NodeTypes.TEXT, 83 | content: "hi", 84 | }, 85 | ], 86 | }, 87 | { 88 | type: NodeTypes.INTERPOLATION, 89 | content: { 90 | type: NodeTypes.SIMPLE_EXPRESSION, 91 | content: "message", 92 | }, 93 | }, 94 | ], 95 | }); 96 | }); 97 | test("should throw error when lack end tag", () => { 98 | expect(() => { 99 | baseParse("
"); 100 | }).toThrow(`缺少结束标签:span`); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /src/compiler-core/tests/transform.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../src/ast"; 2 | import { baseParse } from "../src/parse"; 3 | import { transform } from "../src/transform"; 4 | 5 | describe("transform", () => { 6 | it("happpy path", () => { 7 | const ast = baseParse("
hi,{{message}}
"); 8 | const plugin = (node) => { 9 | if (node.type === NodeTypes.TEXT) { 10 | node.content = "hi, mini-vue"; 11 | } 12 | }; 13 | 14 | transform(ast, { 15 | nodeTransforms: [plugin], 16 | }); 17 | const nodeText = ast.children[0].children[0]; 18 | expect(nodeText.content).toBe("hi, mini-vue"); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // mini-vue 的出口 2 | export * from "./runtime-dom"; 3 | import { baseCompile } from "./compiler-core/src"; 4 | import * as runtimeDom from "./runtime-dom"; 5 | import {registerRuntimeCompiler} from './runtime-core' 6 | 7 | function compileToFunction(template) { 8 | const { code } = baseCompile(template); 9 | const render = new Function("Vue", code)(runtimeDom); 10 | return render; 11 | } 12 | registerRuntimeCompiler(compileToFunction) 13 | -------------------------------------------------------------------------------- /src/reactivity/baseHandlers.ts: -------------------------------------------------------------------------------- 1 | import { extend, isObject } from "../shared/index"; 2 | import { track, trigger } from "./effect"; 3 | import { reactive, ReactiveFlegs, readonly } from "./reactive"; 4 | 5 | const get = createGetter(); 6 | const set = createSetter(); 7 | const readonlyGet = createGetter(true); 8 | const shallowReadonlyGet = createGetter(true, true); 9 | 10 | // shallow 浅层次 11 | function createGetter(isReadOnly = false, shallow = false) { 12 | return function get(target, key) { 13 | if (key === ReactiveFlegs.IS_REACTIVE) { 14 | return !isReadOnly; 15 | } else if (key === ReactiveFlegs.IS_READONLY) { 16 | return isReadOnly; 17 | } 18 | let res = Reflect.get(target, key); 19 | if (shallow) { 20 | return res 21 | } 22 | if (isObject(res)) { 23 | return isReadOnly ? readonly(res) : reactive(res); 24 | } 25 | // TODO 收集依赖 26 | if (!isReadOnly) { 27 | track(target, key); 28 | } 29 | return res; 30 | }; 31 | } 32 | function createSetter() { 33 | return function set(target, key, value) { 34 | let res = Reflect.set(target, key, value); 35 | trigger(target, key); 36 | return res; 37 | }; 38 | } 39 | 40 | export const mutableHandles = { 41 | get, 42 | set, 43 | }; 44 | export const readonlyHandles = { 45 | get: readonlyGet, 46 | set(target, key, value) { 47 | console.warn(`${key} 不能set,readonly!`); 48 | return true; 49 | }, 50 | }; 51 | export const shallowReadonlyHandles = extend({}, readonlyHandles, { 52 | get: shallowReadonlyGet, 53 | }); 54 | -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect"; 2 | 3 | /** 4 | * computed 接受一个函数 5 | * 返回一个类似ref的类型,也就是需要调用 .value 取值 6 | * 可以缓存 7 | * 缓存的原理就是通过设置一个标记_dirty,调用一次把值缓存_value, 8 | * 第二次调用不用触发getter(),直接把缓存的值_value返回 9 | */ 10 | class ComputedRefImpl { 11 | private _getter: any; 12 | private _dirty: boolean = true; 13 | private _value: any; 14 | private _effect: ReactiveEffect; 15 | 16 | /** 17 | * 结合测试1 看 18 | * 下面get value 实现了 .value, 缓存 19 | */ 20 | // constructor(getter) { 21 | // this._getter = getter; 22 | // } 23 | // get value() { 24 | // if (this._dirty) { 25 | // this._dirty = false; 26 | // this._value = this._getter() 27 | // } 28 | // return this._value 29 | // } 30 | /** 31 | * 结合测试2 看 32 | * 当执行value.foo = 2 时候,由于 value 是响应式对象,所以触发set操作,也就是要进行trigger,但是 33 | * getter是一个函数,相当于effect(fn)中的fn,这里只是执行了,const cValue = computed(getter) 34 | * 并没有执行effect(getter),也就是没有进行依赖收集,trigger操作肯定报错 35 | * 所以我们要收集这个 fn ,也就是收集getter,改写ComputedRefImpl 的 get value() {} 36 | * 目标:收集 getter 37 | */ 38 | // constructor(getter) { 39 | // this._getter = getter; 40 | // this._effect = new ReactiveEffect(getter); 41 | // } 42 | // get value() { 43 | // if (this._dirty) { 44 | // this._dirty = false; 45 | // this._value = this._effect.run(); 46 | // } 47 | // return this._value; 48 | // } 49 | /** 50 | * 结合测试2,测试3 看 51 | * 先执行 value.foo = 2;触发trigger,也就是遍历执行effect.run(),也就是又执行了一次getter() 52 | * 实际上getter() 有执行了一次 53 | * 所以 expect(getter).toHaveBeenCalledTimes(1) 失败,实际为 2 54 | * 怎么才能在trigger时候不执行run方法呢?想想effect(fn, scheduler), scheduler 的的作用 55 | * 当执行 triggerEffect 时候,effect.scheduler ?effect.scheduler() : effect.run(), 56 | * ok 随便给scheduler 一个值就行了 57 | * 58 | */ 59 | // export function triggerEffect(dep: any) { 60 | // for (const effect of dep) { 61 | // if (effect.scheduler) { 62 | // effect.scheduler(); 63 | // } else { 64 | // effect.run(); 65 | // } 66 | // } 67 | // } 68 | 69 | // constructor(getter) { 70 | // this._getter = getter; 71 | // this._effect = new ReactiveEffect(getter, () => {}); 72 | // } 73 | // get value() { 74 | // if (this._dirty) { 75 | // this._dirty = false; 76 | // this._value = this._effect.run(); 77 | // } 78 | // return this._value; 79 | // } 80 | 81 | /** 82 | * 结合测试2,测试3,测试4 看 83 | * 当原始reactive类型value改变后,value.foo = 2 84 | * 希望cValue.foo也改变,毕竟是响应式的 85 | * 测试4 取 cValue.foo 时候,触发 get value(){} 的操作, 86 | * 发现_dirty还锁着呢,_value 没有更新,问题在这里 87 | * 那什么时候打开_dirty 呢? 88 | * 没错就是他, schedulers !!! 89 | * 90 | */ 91 | constructor(getter) { 92 | this._getter = getter; 93 | this._effect = new ReactiveEffect(getter, () => { 94 | if (!this._dirty) { 95 | this._dirty = true; 96 | } 97 | }); 98 | } 99 | get value() { 100 | if (this._dirty) { 101 | this._dirty = false; 102 | this._value = this._effect.run(); 103 | } 104 | return this._value; 105 | } 106 | } 107 | export function computed(getter) { 108 | return new ComputedRefImpl(getter); 109 | } 110 | -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "../shared"; 2 | 3 | export class ReactiveEffect { 4 | private _fn: any; 5 | deps = []; 6 | // active 是处理重复调用stop的 7 | active = true; 8 | onStop?: () => void; 9 | // pbulic 是为了给外部获取到 10 | constructor(fn, public scheduler?) { 11 | this._fn = fn; 12 | } 13 | run() { 14 | if (!this.active) { 15 | // 不应该收集依赖 16 | // 如果调用了stop,active 为 false 17 | // 只调用第一次的 _fn, 不进行下面的依赖赋值,也就是不进行依赖收集的 track 操作 18 | return this._fn(); 19 | } 20 | // this 就是依赖的,依赖的run 方法就是执行fn 21 | 22 | // 应该收集依赖逻辑 23 | activeEffect = this; 24 | shouldTract = true; 25 | const r = this._fn(); 26 | shouldTract = false; 27 | return r; 28 | } 29 | stop() { 30 | // 1个 dep 对应多个 effect,同一个effect可能存在多个dep里面 31 | // 现在要清除所有 dep 里面的 目标effect,也就是先遍历depsMap得到dep,在delete每一个dep里面的effect 32 | // 但是depsMap 与 effect不存在关联关系,也就是说当前的effect 不能关系到 所有的depsMap 33 | // 这样处理, 34 | /** 35 | * 1. dep 与 effect 的关系的 dep.add(effect) 36 | * 2. 我们给每一个effect 添加一个deps 的数组空间,用来存储谁 add 当前端的effect 了 37 | * 3. 那么,我们就能从effect 本身关联到与他有关的所有dep了,也就是 deps 数组 38 | * 4. 返回来,只要遍历当前的的efect的deps属性(deps这里面的每一个dep都存在effect),dep是Set,deps是数组 39 | * 5. effect.deps.forEach(dep => dep.delete(effect)) 40 | */ 41 | if (this.active) { 42 | if (this.onStop) { 43 | this.onStop(); 44 | } 45 | cleanUpEffect(this); 46 | this.active = false; 47 | } 48 | } 49 | } 50 | function cleanUpEffect(effect) { 51 | effect.deps.forEach((dep: any) => { 52 | dep.delete(effect); 53 | }); 54 | effect.deps.length = 0; 55 | } 56 | let targetMap = new Map(); 57 | let activeEffect; 58 | let shouldTract; 59 | 60 | export function track(target, key) { 61 | if (!isTracking()) return; 62 | // target key dep 63 | // 对象-- key -- 依赖 64 | let depsMap = targetMap.get(target); 65 | if (!depsMap) { 66 | depsMap = new Map(); 67 | targetMap.set(target, depsMap); 68 | } 69 | 70 | let dep = depsMap.get(key); 71 | if (!dep) { 72 | dep = new Set(); 73 | depsMap.set(key, dep); 74 | } 75 | // 这不光光是抽离一个函数那么简单,为ref做准备 76 | trackEffects(dep); 77 | // if(dep.has(activeEffect)) return 78 | // dep.add(activeEffect); 79 | // activeEffect.deps.push(dep); 80 | } 81 | export function trackEffects(dep) { 82 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了 83 | if (dep.has(activeEffect)) return; 84 | 85 | dep.add(activeEffect); 86 | activeEffect.deps.push(dep); 87 | } 88 | 89 | export function isTracking() { 90 | return shouldTract && activeEffect !== undefined; 91 | } 92 | 93 | export function effect(fn, options: any = {}) { 94 | const _effect = new ReactiveEffect(fn, options.scheduler); 95 | _effect.onStop = options.onStop; 96 | extend(_effect, options); 97 | _effect.run(); 98 | 99 | const runner: any = _effect.run.bind(_effect); 100 | runner.effect = _effect; 101 | return runner; 102 | } 103 | export function trigger(target, key) { 104 | let depsMap = targetMap.get(target); 105 | let dep = depsMap.get(key); 106 | 107 | triggerEffect(dep); 108 | } 109 | export function triggerEffect(dep: any) { 110 | for (const effect of dep) { 111 | if (effect.scheduler) { 112 | effect.scheduler(); 113 | } else { 114 | effect.run(); 115 | } 116 | } 117 | } 118 | 119 | export function stop(runner) { 120 | runner.effect.stop(); 121 | // 指向类 的stop方法 122 | } 123 | -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ref" -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | import { mutableHandles, readonlyHandles,shallowReadonlyHandles } from "./baseHandlers"; 2 | export const enum ReactiveFlegs { 3 | IS_REACTIVE = '__v_isReactive', 4 | IS_READONLY = '__v_isREADONLY' 5 | } 6 | // raw 生的 7 | export function reactive(raw) { 8 | return createReactiveObject(raw, mutableHandles) 9 | } 10 | export function readonly(raw) { 11 | return createReactiveObject(raw, readonlyHandles) 12 | } 13 | function createReactiveObject(raw:any, baseHandlers: ProxyHandler) { 14 | return new Proxy(raw, baseHandlers) 15 | } 16 | export function isReadOnly(raw) { 17 | return !!raw[ReactiveFlegs.IS_READONLY] 18 | } 19 | export function isReactive(raw) { 20 | return !!raw[ReactiveFlegs.IS_REACTIVE] 21 | } 22 | export function shallowReadonly(raw) { 23 | return createReactiveObject(raw, shallowReadonlyHandles) 24 | } 25 | export function isProxy (raw) { 26 | return isReadOnly(raw) || isReactive(raw) 27 | } -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged, isObject } from "../shared"; 2 | import { isTracking, trackEffects, triggerEffect } from "./effect"; 3 | import { reactive } from "./reactive"; 4 | 5 | class RefImpl { 6 | private _value: any; 7 | public dep; 8 | private _rawValue: any; 9 | public __v_isRef = true; 10 | constructor(value) { 11 | // 存一下原始值,当value 为reactive时候使用 12 | this._rawValue = value; 13 | this._value = convert(value); 14 | this.dep = new Set(); 15 | } 16 | get value() { 17 | trackRefValue(this); 18 | return this._value; 19 | } 20 | set value(newValue: any) { 21 | // 如果value 是个reactive类型,那么需要用他的原始值作比较 22 | 23 | if (hasChanged(newValue, this._rawValue)) { 24 | this._rawValue = newValue; 25 | this._value = convert(newValue); 26 | this._rawValue = newValue; 27 | triggerEffect(this.dep); 28 | } 29 | } 30 | } 31 | function convert(value) { 32 | return isObject(value) ? reactive(value) : value; 33 | 34 | } 35 | export function ref(value) { 36 | return new RefImpl(value); 37 | } 38 | function trackRefValue(ref) { 39 | if (isTracking()) { 40 | trackEffects(ref.dep); 41 | } 42 | } 43 | export function isRef(value) { 44 | return !!value.__v_isRef 45 | } 46 | export function unRef(value) { 47 | return !!value.__v_isRef ? value.value : value 48 | } 49 | export function proxyRefs(objectWithRefs) { 50 | return new Proxy(objectWithRefs, { 51 | get (target, key) { 52 | return unRef(Reflect.get(target, key)) 53 | }, 54 | set (target, key, value) { 55 | if (isRef(target[key]) && !isRef(value)) { 56 | return target[key].value = value 57 | } else { 58 | return Reflect.set(target, key, value) 59 | } 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /src/reactivity/tests/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { computed } from '../computed' 2 | import { reactive } from '../reactive' 3 | 4 | describe('computed', () => { 5 | it('happy path', () => { 6 | // ref 7 | // .value 8 | // 缓存 9 | 10 | const user = reactive({ 11 | age: 1, 12 | }) 13 | 14 | const age = computed(() => { 15 | return user.age 16 | }) 17 | 18 | expect(age.value).toBe(1) 19 | }) 20 | it('should computed lazily', () => { 21 | const value = reactive({ 22 | foo: 1 23 | }) 24 | const getter = jest.fn(() => { 25 | return value.foo 26 | }) 27 | const cValue = computed(getter) 28 | 29 | // 测试 1 30 | // lazy 31 | /** 32 | * 如果没有调用 cValue 的话,getter 不会执行 33 | */ 34 | expect(getter).not.toHaveBeenCalled() 35 | // 调用一次 cValue 36 | expect((cValue.value)).toBe(1) 37 | // 触发一次函数 38 | expect(getter).toHaveBeenCalledTimes(1) 39 | 40 | // 再次调用 41 | cValue.value; 42 | expect(getter).toHaveBeenCalledTimes(1) 43 | 44 | // 测试 2 45 | value.foo = 2 46 | 47 | // 测试 3 触发set 操作,同样不想再次调用一次getter(),不然缓存有什么用 48 | expect(getter).toHaveBeenCalledTimes(1) 49 | 50 | // 测试 4 51 | // // now it should computed 52 | expect(cValue.value).toBe(2) 53 | expect(getter).toHaveBeenCalledTimes(2) 54 | 55 | // // // sgould not computed again 56 | cValue.value; 57 | expect(getter).toHaveBeenCalledTimes(2) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /src/reactivity/tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect, stop } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | describe("effect", () => { 4 | it("happy path", () => { 5 | const user = reactive({ 6 | age:10 7 | }) 8 | let nextAge 9 | effect(() => { 10 | nextAge = user.age + 1 11 | }) 12 | expect(nextAge).toBe(11) 13 | 14 | // update 15 | user.age++ 16 | expect(nextAge).toBe(12) 17 | }); 18 | it('should return runner when call effect', () => { 19 | let foo = 10 20 | const runner = effect(() => { 21 | foo++ 22 | return foo 23 | }) 24 | expect(foo).toBe(11) 25 | const r = runner() 26 | expect(foo).toBe(12) 27 | expect(r).toBe(foo) 28 | }) 29 | 30 | 31 | it('scheduler', () => { 32 | let dummy 33 | let run: any 34 | const scheduler = jest.fn(() => { 35 | run = runner 36 | }) 37 | const obj = reactive({ foo: 1 }) 38 | const runner = effect( 39 | () => { 40 | dummy = obj.foo 41 | }, 42 | { 43 | scheduler, 44 | } 45 | ) 46 | expect(scheduler).not.toHaveBeenCalled() 47 | expect(dummy).toBe(1) 48 | 49 | obj.foo++ 50 | expect(scheduler).toHaveBeenCalledTimes(1) 51 | expect(dummy).toBe(1) 52 | run() 53 | expect(dummy).toBe(2) 54 | }) 55 | 56 | it("stop", () => { 57 | let dummy; 58 | const obj = reactive({prop: 1}); 59 | const runner = effect(() => { 60 | dummy = obj.prop; 61 | }) 62 | obj.prop = 2 63 | expect(dummy).toBe(2) 64 | 65 | stop(runner) 66 | // 如果stop 包裹这个reunner, 数据不再是响应式的, 67 | // 也就是说需要把 对应的effect 从 deps 里删掉 68 | // 根据单测,stop参数就是runner 69 | 70 | // 只执行一次set操作 71 | // obj.prop = 5; 72 | 73 | // 先执行get 在执行set 74 | obj.prop++; 75 | expect(dummy).toBe(2) 76 | 77 | runner() 78 | expect(dummy).toBe(3) 79 | }) 80 | it("onStop", () => { 81 | const obj = reactive({ 82 | foo:1 83 | }) 84 | const onStop = jest.fn(); 85 | let dummy; 86 | const runner = effect(() => { 87 | dummy = obj.foo; 88 | }, { 89 | onStop 90 | }) 91 | stop(runner) 92 | expect(onStop).toBeCalledTimes(1) 93 | }) 94 | 95 | }); 96 | -------------------------------------------------------------------------------- /src/reactivity/tests/reaadonly.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import {isProxy, isReadOnly, readonly} from '../reactive' 3 | describe("reactive", () => { 4 | it("happy path", () => { 5 | // no set 6 | const orginal = {foo: 1, bar: {baz:2}} 7 | const wrapped = readonly(orginal) 8 | 9 | expect(wrapped).not.toBe(orginal) 10 | expect(isReadOnly(wrapped)).toBe(true) 11 | expect(isProxy(wrapped)).toBe(true) 12 | expect(isReadOnly(wrapped.bar)).toBe(true) 13 | expect(isReadOnly(orginal.bar)).toBe(false) 14 | expect(isReadOnly(orginal)).toBe(false) 15 | expect(orginal.foo).toBe(1) 16 | }) // set 警告 17 | it('warn then call set', () => { 18 | console.warn = jest.fn() 19 | const user = readonly({ 20 | age: 10, 21 | }) 22 | user.age++; 23 | expect(console.warn).toBeCalled() 24 | }) 25 | }) -------------------------------------------------------------------------------- /src/reactivity/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReactive, reactive } 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(isProxy(observed)).toBe(true) 9 | expect(isReactive(observed)).toBe(true) 10 | expect(isReactive(original)).toBe(false) 11 | original.foo = 2 12 | expect(observed.foo).toBe(2) 13 | }); 14 | it("nested reactive", () => { 15 | const orginal = { 16 | nested: { 17 | foo: 1 18 | }, 19 | array: [ 20 | { 21 | bar: 2 22 | } 23 | ] 24 | } 25 | 26 | const observed = reactive(orginal) 27 | expect(isReactive(observed)).toBe(true) 28 | expect(isReactive(observed.nested)).toBe(true) 29 | expect(isReactive(observed.array)).toBe(true) 30 | expect(isReactive(observed.array[0])).toBe(true) 31 | }) 32 | }); 33 | -------------------------------------------------------------------------------- /src/reactivity/tests/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from '../effect' 2 | import { reactive } from '../reactive' 3 | import { isRef, proxyRefs, ref, unRef } from '../ref' 4 | 5 | describe('ref', () => { 6 | it('happy path', () => { 7 | const a = ref(1) 8 | expect(a.value).toBe(1) 9 | }) 10 | it('should be reactive', () => { 11 | const a = ref(1) 12 | let dummy; 13 | let calls = 0; 14 | effect(() => { 15 | calls++; 16 | dummy = a.value; 17 | }) 18 | expect(calls).toBe(1) 19 | expect(dummy).toBe(1) 20 | // a.value = 2; 21 | a.value++ 22 | expect(calls).toBe(2) 23 | expect(dummy).toBe(2) 24 | 25 | a.value = 2; 26 | expect(calls).toBe(2) 27 | expect(dummy).toBe(2) 28 | }) 29 | it('should be nestes properties reactive', () => { 30 | const a = ref({ 31 | count: 1, 32 | }) 33 | let dummy; 34 | effect(() => { 35 | dummy = a.value.count 36 | }) 37 | expect(dummy).toBe(1) 38 | a.value.count = 2 39 | expect(dummy).toBe(2) 40 | }) 41 | it('isRef', () => { 42 | const a = ref(1) 43 | const aa= ref({ 44 | value: 11 45 | }) 46 | const user = reactive({ 47 | age: 10 48 | }) 49 | expect(isRef(a)).toBe(true) 50 | expect(isRef(aa)).toBe(true) 51 | expect(aa.value).toStrictEqual({"value": 11}) 52 | expect(isRef(1)).toBe(false) 53 | expect(isRef(user)).toBe(false) 54 | }) 55 | 56 | it('unRef', () => { 57 | const a = ref(1) 58 | expect(unRef(a)).toBe(1) 59 | expect(unRef(1)).toBe(1) 60 | }) 61 | 62 | it('proxyRefs', () => { 63 | const user= { 64 | age: ref(10), 65 | name: 'xxx' 66 | } 67 | const proxyUser = proxyRefs(user) 68 | expect(user.age.value).toBe(10) 69 | expect(proxyUser.age).toBe(10) 70 | expect(proxyUser.name).toBe('xxx') 71 | 72 | proxyUser.age = 20 73 | expect(proxyUser.age).toBe(20) 74 | expect(user.age.value).toBe(20) 75 | 76 | proxyUser.age = ref(10) 77 | expect(proxyUser.age).toBe(10) 78 | expect(user.age.value).toBe(10) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /src/reactivity/tests/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReadOnly, shallowReadonly } from "../reactive" 2 | describe("shallowReadonly", () => { 3 | it("should not make non-reactive propertive reactive", () => { 4 | const props = shallowReadonly({ 5 | n: { 6 | foo: 1 7 | } 8 | }) 9 | expect(isReadOnly(props)).toBe(true) 10 | expect(isReadOnly(props.n)).toBe(false) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "./component"; 2 | 3 | export function provide(key, value) { 4 | const currentInstance: any = getCurrentInstance(); 5 | 6 | if (currentInstance) { 7 | let { provides } = currentInstance; 8 | const parentProvides = currentInstance.parent.provides; 9 | 10 | // 初始化 11 | if (provides === parentProvides) { 12 | provides = currentInstance.provides = Object.create(parentProvides); 13 | } 14 | provides[key] = value; 15 | } 16 | } 17 | export function inject(key, defaultValue) { 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 | } 31 | -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { shallowReadonly } from "../reactivity/reactive"; 2 | import { proxyRefs } from "../reactivity/ref"; 3 | import { emit } from "./componentEmit"; 4 | import { initProps } from "./componentProps"; 5 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance"; 6 | import { initSlots } from "./componentSlots"; 7 | 8 | export function createComponentInstance(vnode, parent) { 9 | // instance component 10 | const instance = { 11 | vnode, 12 | // 下次要更新的虚拟节点 13 | next: null, 14 | type: vnode.type, 15 | setupState: {}, 16 | isMounted: false, 17 | // subTree:'', 18 | emit: () => {}, 19 | slots: {}, 20 | provides: parent ? parent.provides : {}, 21 | parent, 22 | props: {}, 23 | }; 24 | instance.emit = emit.bind(null, instance) as any; 25 | return instance; 26 | } 27 | 28 | export function setupComponent(instance) { 29 | // 初始化 30 | // props 31 | initProps(instance, instance.vnode.props); 32 | initSlots(instance, instance.vnode.children); 33 | 34 | // 创建有状态的组件 35 | setupStatefulComponent(instance); 36 | } 37 | 38 | function setupStatefulComponent(instance: any) { 39 | // 调用setup 函数,拿到setup函数的返回值 40 | 41 | const Component = instance.vnode.type; 42 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers); 43 | 44 | const { setup } = Component; 45 | if (setup) { 46 | setCurrentInstance(instance); 47 | const setupResult = setup(shallowReadonly(instance.props), { 48 | emit: instance.emit, 49 | }); 50 | setCurrentInstance(null); 51 | 52 | handleSetupResult(instance, setupResult); 53 | } 54 | } 55 | function handleSetupResult(instance: any, setupResult: any) { 56 | // 返回值是function,那就是render函数 57 | // 返回值是Object,那需要把这个对象挂到组件上下文 58 | if (typeof setupResult === "object") { 59 | instance.setupState = proxyRefs(setupResult); 60 | } 61 | 62 | // 保证组件render有值 63 | // 组件 -> const App = { 64 | // render() { 65 | // return h("div", this.msg) 66 | // }, 67 | // setup() { 68 | // return { 69 | // msg: "hello vue" 70 | // } 71 | // } 72 | // } 73 | finishComponentSetup(instance); 74 | } 75 | function finishComponentSetup(instance: any) { 76 | const Component = instance.type; 77 | if (compiler && !Component.render) { 78 | if (Component.template) { 79 | Component.render = compiler(Component.template); 80 | } 81 | } 82 | instance.render = Component.render; 83 | // instance -> { 84 | // render: 85 | // setupState 86 | // vnode: { 87 | // type: App 88 | // } 89 | // } 90 | } 91 | 92 | let currentInstance = null; 93 | export function getCurrentInstance() { 94 | return currentInstance; 95 | } 96 | export function setCurrentInstance(instance) { 97 | currentInstance = instance; 98 | } 99 | 100 | let compiler; 101 | export function registerRuntimeCompiler(_compiler) { 102 | compiler = _compiler; 103 | } 104 | -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { camelize, toHandlerKey } from "../shared/index" 2 | 3 | export function emit(instance, event, ...arg) { 4 | const { props } = instance 5 | // add -> Add 6 | // add-add -> addAdd 7 | 8 | const handlerName = toHandlerKey(camelize(event)) 9 | // console.log(handlerName) 10 | const handler = props[handlerName] 11 | handler && handler(...arg) 12 | } -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | 2 | export function initProps(instance, rawProps) { 3 | instance.props = rawProps || {} 4 | } -------------------------------------------------------------------------------- /src/runtime-core/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from "../shared/index"; 2 | 3 | const publicPropertiesMap = { 4 | $el: (i) => i.vnode.el, 5 | $slots: (i) => i.slots, 6 | $props: (i) => i.props, 7 | }; 8 | export const PublicInstanceProxyHandlers = { 9 | get({ _: instance }, key) { 10 | // console.log(instance) 11 | const { setupState, props } = instance; 12 | 13 | if (hasOwn(setupState, key)) { 14 | return setupState[key]; 15 | } else if (hasOwn(props, key)) { 16 | return props[key]; 17 | } 18 | // $el 19 | const publicGetter = publicPropertiesMap[key]; 20 | if (publicGetter) { 21 | return publicGetter(instance); 22 | } 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from '../shared/ShapeFlags' 2 | 3 | export function initSlots(instance, children) { 4 | const { vnode } = instance 5 | 6 | if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { 7 | normalizeObjectSlots(children, instance.slots) 8 | } 9 | } 10 | 11 | function normalizeObjectSlots(children, slots) { 12 | for (const key in children) { 13 | const value = children[key] 14 | slots[key] = props => normalizeSlotValue(value(props)) 15 | } 16 | } 17 | function normalizeSlotValue(value) { 18 | return Array.isArray(value) ? value : [value] 19 | } -------------------------------------------------------------------------------- /src/runtime-core/componentUpdateUtils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUpdateComponent(prevVNode, nextVNode) { 2 | const { props: prevProps } = prevVNode; 3 | const { props: nextProps } = nextVNode; 4 | 5 | for (const key in nextProps) { 6 | if (nextProps[key] !== prevProps[key]) { 7 | return true; 8 | } 9 | } 10 | 11 | return false; 12 | } 13 | -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | // import { render } from "./renderer"; 3 | 4 | export function createAppAPI(render) { 5 | return function createApp(rootComponent) { 6 | return { 7 | mount(rootContainer) { 8 | // 先创建 vnode 9 | // component -> vnode 10 | // 所有逻辑操作 都会基于 vnode 做处理 11 | const vnode = createVNode(rootComponent); 12 | // 渲染虚拟节点 13 | render(vnode, rootContainer); 14 | }, 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import {createVNode} from "./vnode" 2 | export function h(type, props?, children?) { 3 | return createVNode(type, props, children) 4 | } -------------------------------------------------------------------------------- /src/runtime-core/helpers/renderSolts.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, Fragment } from "../vnode"; 2 | 3 | export function renderSlots(slots, name, props) { 4 | const slot = slots[name]; 5 | if (slot) { 6 | if (typeof slot === "function") { 7 | return createVNode(Fragment, {}, slot(props)); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | // export {createApp} from './createApp' 2 | export { h } from "./h"; 3 | export { renderSlots } from "./helpers/renderSolts"; 4 | export { createTextVNode, createElementVNode } from "./vnode"; 5 | export { getCurrentInstance, registerRuntimeCompiler } from "./component"; 6 | export { provide, inject } from "./apiInject"; 7 | export { createRenderer } from "./renderer"; 8 | export { nextTick } from "./scheduler"; 9 | export { toDisplayString } from "../shared"; 10 | export * from "../reactivity"; 11 | -------------------------------------------------------------------------------- /src/runtime-core/renderer.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../reactivity/effect"; 2 | import { ShapeFlags } from "../shared/ShapeFlags"; 3 | import { createComponentInstance, setupComponent } from "./component"; 4 | import { shouldUpdateComponent } from "./componentUpdateUtils"; 5 | import { createAppAPI } from "./createApp"; 6 | import { queueJobs } from "./scheduler"; 7 | import { Fragment, Text } from "./vnode"; 8 | 9 | export function createRenderer(options) { 10 | const { 11 | createElement: hostCreateElement, 12 | patchProp: hostPatchProp, 13 | insert: hostInsert, 14 | setElementText: hostSetElementText, 15 | remove: hostRemove, 16 | } = options; 17 | 18 | // 查查初始化时候调用render了么? 19 | function render(vnode, container) { 20 | // patch 21 | patch(null, vnode, container, null, null); 22 | } 23 | 24 | /** 25 | * n1 老的 26 | * n2 新的 27 | */ 28 | function patch(n1, n2: any, container: any, parentComponent, anchor) { 29 | // 当vnode.type的值时,组件是object,element是string,这样区分组件和元素 30 | const { type, shapeFlag } = n2; 31 | switch (type) { 32 | case Fragment: 33 | processFragment(n1, n2, container, parentComponent, anchor); 34 | break; 35 | case Text: 36 | processText(n1, n2, container); 37 | break; 38 | default: 39 | // if (typeof vnode.type === "string") { 40 | if (shapeFlag & ShapeFlags.ELEMENT) { 41 | // patch element 42 | processElement(n1, n2, container, parentComponent, anchor); 43 | // } else if (isObject(vnode.type)) { 44 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 45 | // patch 组件 46 | console.log("组件逻辑"); 47 | processComponent(n1, n2, container, parentComponent, anchor); 48 | } 49 | } 50 | } 51 | 52 | function processText(n1, n2: any, container: any) { 53 | const { children } = n2; 54 | const text = document.createTextNode(children); 55 | container.append(text); 56 | } 57 | 58 | function processFragment( 59 | n1, 60 | n2: any, 61 | container: any, 62 | parentComponent, 63 | anchor 64 | ) { 65 | mountChildren(n2.children, container, parentComponent, anchor); 66 | } 67 | 68 | function processElement( 69 | n1, 70 | n2: any, 71 | container: any, 72 | parentComponent, 73 | anchor 74 | ) { 75 | // 包含初始化和更新流程 76 | // init 77 | if (!n1) { 78 | mountElement(n2, container, parentComponent, anchor); 79 | } else { 80 | patchElement(n1, n2, container, parentComponent, anchor); 81 | } 82 | } 83 | function patchElement(n1, n2, container, parentComponent, anchor) { 84 | // 获取新,老 prosp 85 | const oldProps = n1.props || {}; 86 | const newProps = n2.props || {}; 87 | // 对比新老props 88 | const el = (n2.el = n1.el); 89 | patchProps(el, oldProps, newProps); 90 | 91 | // 对比children 92 | patchChildren(n1, n2, el, parentComponent, anchor); 93 | } 94 | function patchChildren(n1, n2, container, parentComponent, anchor) { 95 | // 子节点只有两种类型 文本节点 数组 96 | 97 | /* 98 | 1 新的是text,老的是array 99 | 2 删除老的array 添加 文本节点 100 | */ 101 | 102 | /* 103 | 1 新的 老的都是 文本节点 104 | 2 对比是否相同,不相同的话 替换老的节点 105 | */ 106 | 107 | /* 108 | 1 新的是数组,老的是文本 109 | 2 删除老的,挂载新的 110 | */ 111 | const { shapeFlag } = n2; 112 | const c2 = n2.children; 113 | const c1 = n1.children; 114 | const prevshapeFlag = n1.shapeFlag; 115 | 116 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 117 | // if (prevshapeFlag & ShapeFlags.ARRAY_CHILDREN) { 118 | // // 1 把老的 children 删除 119 | // unmountChildren(n1.children); 120 | // // 2 添加 text 121 | // hostSetElementText(container, c2); 122 | // } else { 123 | // // 新老都是文本节点 124 | // if(c1 !== c2) { 125 | // hostSetElementText(container, c2); 126 | // } 127 | // } 128 | // 重构一下 129 | if (prevshapeFlag & ShapeFlags.ARRAY_CHILDREN) { 130 | unmountChildren(n1.children); 131 | } 132 | if (c1 !== c2) { 133 | hostSetElementText(container, c2); 134 | } 135 | } else { 136 | // 新的是array 老的是text 137 | 138 | if (prevshapeFlag & ShapeFlags.TEXT_CHILDREN) { 139 | hostSetElementText(container, ""); 140 | mountChildren(c2, container, parentComponent, anchor); 141 | } else { 142 | // array diff array 143 | patchKeyedChildren(c1, c2, container, parentComponent, anchor); 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * @description array diff array 150 | * @author Werewolf 151 | * @date 2021-12-20 152 | * @param {*} c1 老 153 | * @param {*} c2 新 154 | * @param {*} container 容器 155 | * @param {*} parentComponent 父组件 156 | * @param {*} parentAnthor 在这个元素之前插入。原由:插入有位置的要求 157 | */ 158 | 159 | function patchKeyedChildren( 160 | c1, 161 | c2, 162 | container, 163 | parentComponent, 164 | parentAnthor 165 | ) { 166 | // 初始指针 i 167 | let i = 0; 168 | let l2 = c2.length; 169 | let e1 = c1.length - 1; 170 | let e2 = l2 - 1; 171 | 172 | function isSameNodeType(n1, n2) { 173 | // 相同节点 type key 相同 174 | return n1.type === n2.type && n1.key === n2.key; 175 | } 176 | // 初始指针不能超过两个数组 177 | 178 | /** 179 | * 第一种情况 180 | * 左侧对吧 181 | * ab c 182 | * ab de 183 | */ 184 | while (i <= e1 && i <= e2) { 185 | const n1 = c1[i]; 186 | const n2 = c2[i]; 187 | 188 | if (isSameNodeType(n1, n2)) { 189 | patch(n1, n2, container, parentComponent, parentAnthor); 190 | } else { 191 | break; 192 | } 193 | i++; 194 | } 195 | /** 196 | * 第二种情况 197 | * 右侧对比 198 | * a bc 199 | * de bc 200 | */ 201 | while (i <= e1 && i <= e2) { 202 | const n1 = c1[e1]; 203 | const n2 = c2[e2]; 204 | 205 | if (isSameNodeType(n1, n2)) { 206 | patch(n1, n2, container, parentComponent, parentAnthor); 207 | } else { 208 | break; 209 | } 210 | e1--; 211 | e2--; 212 | } 213 | 214 | /** 215 | * 第三种情况 216 | * 新的比老的多,两种情况 217 | * ab ab 218 | * ab c c ab 219 | */ 220 | if (i > e1) { 221 | if (i <= e2) { 222 | const nextPos = i + 1; 223 | const anchor = i + 1 > l2 ? null : c2[nextPos].el; 224 | while (i <= e2) { 225 | patch(null, c2[i], container, parentComponent, anchor); 226 | i++; 227 | } 228 | } 229 | } else if (i > e2) { 230 | /** 231 | * 第四种情况 232 | * 新的比老的少, 两种情况 233 | * ab c a bc 234 | * ab bc 235 | */ 236 | while (i <= e1) { 237 | hostRemove(c1[i].el); 238 | i++; 239 | } 240 | } else { 241 | // 中间对比,经过以上逻辑已经找到了两个临界点 242 | /** 243 | * 第五种情况-1。删除老的d,修改c 244 | * 旧 ab cd fg 245 | * 新 ab ec fg 246 | * 1 旧的里面存在,新的不存在(d),那么需要删除 d。 247 | * 如果在ec里面遍历看是否存在d,那么时间复杂度是O(n),如果用 key 映射,那么时间复杂度是O(1) 248 | * 249 | */ 250 | 251 | /** 252 | * 根据新的节点建立关于key的映射关系 keyToNewIndexMap 253 | * 在老的节点里根据key查找是否存在值,也就是是否存在 keyToNewIndexMap[oldChild.key] 254 | * 存在说明是相同节点,拿到索引,进行深度 patch,不存在直接在老的节点里删除 255 | * 注意:老的节点可能是用户没有写key属性,那只能 for 遍历了 256 | * 257 | */ 258 | 259 | // s1 s2 新老节点中间不同的起始位置 260 | let s1 = i; 261 | let s2 = i; 262 | 263 | /** 264 | * 优化点:当新节点的个数小于老节点点个数,也就是新的已经patch完毕,但是老节点还存在,那么老节点剩下的无需在对比,直接删除 265 | * 老 ab cedm fg,新 ab ec fg,当新节点的ec对比完毕,老节点还剩dm,那么直接删除,无需对比 266 | * 267 | * toBePatched 新节点需要patch的个数 268 | * patched 已经处理的个数 269 | * 270 | */ 271 | const toBePatched = e2 - s2 + 1; 272 | let patched = 0; 273 | 274 | // 映射关系 275 | const keyToNewIndexMap = new Map(); 276 | 277 | // 节点位置移动的逻辑 278 | /** 279 | * 旧 ab cde fg 280 | * 新 ab ecd fg 281 | * newIndexToOldIndexMap的长度是3, 指的是新的 ecd 的映射 282 | * 我们要把 e 在老数组的的位置(4)映射到 newIndexToOldIndexMap 里面。newIndexToOldIndexMap[0] = 4 283 | * 284 | */ 285 | 286 | // 建立 初始化映射表 定长数组性能相对要好 287 | const newIndexToOldIndexMap = new Array(toBePatched); 288 | 289 | /** 290 | * 优化逻辑 291 | * moved 292 | * maxNewIndexSoFar 293 | */ 294 | let moved = false; 295 | let maxNewIndexSoFar = 0; 296 | 297 | for (let i = 0; i < toBePatched; i++) { 298 | newIndexToOldIndexMap[i] = 0; 299 | } 300 | 301 | // 建立 新的映射关系 302 | for (let i = s2; i <= e2; i++) { 303 | const nextChild = c2[i]; 304 | keyToNewIndexMap.set(nextChild.key, i); 305 | } 306 | 307 | // 老的映射关系 308 | for (let i = s1; i <= e1; i++) { 309 | // 老节点 prevChild 310 | const prevChild = c1[i]; 311 | if (patched >= toBePatched) { 312 | // 新的已经对比完,但是老的还没完事。直接删除 313 | hostRemove(prevChild.el); 314 | // 进入下一次循环 315 | continue; 316 | } 317 | let newIndex; 318 | /** 319 | * 如果 newIndex 存在,说明 prevChild 在新的里面存在。 320 | * 如果用户写了key,用key映射查找。如果没写key,用循环查找 321 | */ 322 | if (prevChild.key !== null) { 323 | newIndex = keyToNewIndexMap.get(prevChild.key); 324 | } else { 325 | for (let j = s2; j <= e2; j++) { 326 | if (isSameNodeType(c2[j], prevChild)) { 327 | newIndex = j; 328 | break; 329 | } 330 | } 331 | } 332 | 333 | if (newIndex === undefined) { 334 | // 说明不存在prevChild,删掉老的 prevChild 335 | hostRemove(prevChild.el); 336 | } else { 337 | /** 338 | * 优化点 339 | * 思路: 340 | * 1 首先最长递归子序列是递增,那么我们想要 newIndex 也应该是递增,也就不用遍历递增序列了,优化了性能 341 | * 2 如果不是递增,那么肯定需要 移动并插入 342 | * 343 | */ 344 | if (newIndex >= maxNewIndexSoFar) { 345 | maxNewIndexSoFar = newIndex; 346 | } else { 347 | moved = true; 348 | } 349 | 350 | /** 351 | * ab ecd fg 352 | * 从e开始映射 e 为 0,newIndex - s2 减去前面相同的 s2 部分 353 | * 由于 newIndexToOldIndexMap[i] 的初始化都为 0,0的意义代表 新的存在,老的不存在,需要创建新的 354 | * 这里的 e 为 0,有歧义,所以用 i+1 处理,最小 为 1,不会有歧义 355 | * 356 | * */ 357 | 358 | /** 359 | * newIndexToOldIndexMap 逻辑是这样的 360 | * 老的 ab cde fg 361 | * 新的 ad ecd fg 362 | * 初始 newIndexToOldIndexMap -> [0, 0, 0] 363 | * 遍历老节点,老c存在新节点创建的 Map 中,即 老c 的索引是0,所以newIndexToOldIndexMap[1] = 1(0+1) 364 | * 同理,老d存在新节点创建的Map中,即 老d 的索引是 1,所以 newIndexToOldIndexMap[2] = 2(1+1) 365 | * 老e的索引是2,所以 newIndexToOldIndexMap[0] = 3(2+1) 366 | * newIndexToOldIndexMap -> [3, 1, 2] 367 | */ 368 | // 图12 369 | newIndexToOldIndexMap[newIndex - s2] = i + 1; 370 | // 存在,继续进行深度对比 371 | 372 | patch(prevChild, c2[newIndex], container, parentComponent, null); 373 | patched++; 374 | } 375 | } 376 | /** 377 | * 移动节点 378 | * 新老都存在,只需要移动节点 379 | * 找到一个固定的序列cd,减少对比插入次数 380 | * 算法:最长递增子序列 381 | * [4,2,3] => [1,2], [4,2,3,5]=>[1,2,4] 382 | * a[i]= 0; i--) { 423 | // 拿到一个倒序的索引 424 | const nextIndex = i + s2; 425 | // 新节点树c2对应的 节点 426 | const nextChild = c2[nextIndex]; 427 | // 这个节点的下一个节点的el,如果需要移动,那么就插入到这个节点之前,这就是他为锚点 428 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null; 429 | if (newIndexToOldIndexMap[i] === 0) { 430 | // 创建逻辑 431 | 432 | patch(null, nextChild, container, parentComponent, anchor); 433 | } else if (moved) { 434 | /** 435 | * i 是 c2 的中间部分的索引 436 | * 如果倒序的索引 i 跟当前的 最长递归子序列的倒序索引 j 相同,那么说明是这个节点的位置不用移动 437 | * 如果不相同,那么需要插入这个节点 438 | * 需要找到这个节点,和锚点 439 | * 440 | * */ 441 | if (j < 0 || i !== increasingNewSequence[j]) { 442 | hostInsert(nextChild.el, container, anchor); 443 | // 不在最长递归子序列 444 | console.log("移动位置"); 445 | } else { 446 | j--; 447 | } 448 | } 449 | } 450 | } 451 | } 452 | 453 | /** 454 | * @description 删除children 节点 455 | * @author Werewolf 456 | * @date 2021-12-17 457 | * @param {*} children 458 | */ 459 | function unmountChildren(children) { 460 | for (var i = 0; i < children.length; i++) { 461 | const el = children[i].el; 462 | hostRemove(el); 463 | } 464 | } 465 | /** 466 | * @description patch 属性 467 | * @author Werewolf 468 | * @date 2021-12-20 469 | * @param {*} el 470 | * @param {*} oldProps 471 | * @param {*} newProps 472 | */ 473 | function patchProps(el, oldProps, newProps) { 474 | if (oldProps !== newProps) { 475 | // newProps 里面的 prop 不在 oldProps 里面,遍历新的 476 | for (const key in newProps) { 477 | // 对比props对象的属性 478 | const prveProp = oldProps[key]; 479 | const nextprop = newProps[key]; 480 | if (prveProp !== nextprop) { 481 | // 调用之前的 添加属性方法,需要一个 el 482 | // 多传一个参数,同时需要修改 hostPatchProp 方法 483 | // hostPatchProp(el, key, prveProp, nextprop) 484 | hostPatchProp(el, key, prveProp, nextprop); 485 | } 486 | } 487 | 488 | // oldProps 里的 prop 不在 newProps 里面,遍历旧的 489 | if (oldProps !== {}) { 490 | for (const key in oldProps) { 491 | if (!(key in newProps)) { 492 | hostPatchProp(el, key, oldProps[key], null); 493 | } 494 | } 495 | } 496 | } 497 | } 498 | 499 | function mountElement(vnode: any, container: any, parentComponent, anchor) { 500 | // canvas new Element 501 | // const el = (vnode.el = document.createElement(vnode.type)); 502 | const el = (vnode.el = hostCreateElement(vnode.type)); 503 | 504 | const { props, children, shapeFlag } = vnode; 505 | // string array 506 | // if (typeof children === "string") { 507 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 508 | el.textContent = children; 509 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 510 | mountChildren(vnode.children, el, parentComponent, anchor); 511 | } 512 | for (const key in props) { 513 | const val = props[key]; 514 | 515 | hostPatchProp(el, key, null, val); 516 | } 517 | // canvas el.x = 10 518 | // container.append(el); 519 | hostInsert(el, container, anchor); 520 | // canvas addChild() 521 | } 522 | /** 523 | * @description 挂载数组节点 524 | * @author Werewolf 525 | * @date 2021-12-17 526 | * @param {*} children [vnode1,vnode2] 527 | * @param {*} container 528 | * @param {*} parentComponent 529 | */ 530 | function mountChildren(children, container, parentComponent, anchor) { 531 | children.forEach((v) => { 532 | patch(null, v, container, parentComponent, anchor); 533 | }); 534 | } 535 | function processComponent( 536 | n1, 537 | n2: any, 538 | container: any, 539 | parentComponent, 540 | anchor 541 | ) { 542 | if (!n1) { 543 | // 初始化 544 | mountComponent(n2, container, parentComponent, anchor); 545 | } else { 546 | // 更新组件 调用当前组件的render 函数,重新 vnode 重新 patch, 也就是走 setupRenderEffect 逻辑 547 | updateComponent(n1, n2); 548 | } 549 | } 550 | /** 551 | * @description 组件更新 552 | * @author Werewolf 553 | * @date 2021-12-24 554 | * @param {*} n1 555 | * @param {*} n2 556 | */ 557 | function updateComponent(n1, n2) { 558 | // 利用effect runner 逻辑 559 | /** 560 | * 怎么找instance,现在只有n 虚拟节点 561 | * 那么把实例挂载到虚拟节点 562 | * 563 | */ 564 | 565 | const instance = (n2.component = n1.component); 566 | if (shouldUpdateComponent(n1, n2)) { 567 | instance.next = n2; 568 | instance.update(); 569 | } else { 570 | // 不需要更新也要重置虚拟节点 和 el 571 | n2.el = n1.el; 572 | n2.vnode = n2; 573 | } 574 | } 575 | 576 | function mountComponent( 577 | initialVNode: any, 578 | container: any, 579 | parentComponent, 580 | anchor 581 | ) { 582 | // 根据虚拟节点创建组件实例 583 | 584 | // 将组件实例 挂载到虚拟接节点 585 | 586 | const instance = (initialVNode.component = createComponentInstance( 587 | initialVNode, 588 | parentComponent 589 | )); 590 | 591 | // 初始化,收集信息,instance挂载相关属性,方法, 装箱 592 | setupComponent(instance); 593 | 594 | // 渲染组件,调用组件的render方法 595 | // 组件 -> const App = { 596 | // render() { 597 | // return h("div", this.msg) 598 | // }, 599 | // setup() { 600 | // return { 601 | // msg: "hello vue" 602 | // } 603 | // } 604 | // } 605 | 606 | // 一个组件不会真实渲染出来,渲染的是组件的render函数内部的element值,拆箱过程 607 | // render 返回的subTree 给patch,如果是组件继续递归,如果是element 则渲染 608 | setupRenderEffect(instance, initialVNode, container, anchor); 609 | } 610 | 611 | /** 612 | * @description 调用render,也就是生成虚拟节点,进行patch。包括 初始化和更新流程 613 | * @author Werewolf 614 | * @date 2021-12-24 615 | * @param {*} instance 616 | * @param {*} initialVNode 617 | * @param {*} container 618 | * @param {*} anchor 619 | */ 620 | function setupRenderEffect(instance: any, initialVNode, container, anchor) { 621 | instance.update = effect( 622 | () => { 623 | if (!instance.isMounted) { 624 | console.log("init 初始化"); 625 | const { proxy } = instance; 626 | const subTree = (instance.subTree = instance.render.call( 627 | proxy, 628 | proxy 629 | )); 630 | 631 | patch(null, subTree, container, instance, anchor); 632 | 633 | initialVNode.el = subTree.el; 634 | 635 | instance.isMounted = true; 636 | } else { 637 | console.log("update 更新"); 638 | 639 | // next 新的虚拟节点 640 | // vnode 老的虚拟节点 641 | const { next, vnode } = instance; 642 | // 更新el 643 | if (next) { 644 | next.el = vnode.el; 645 | // 更新属性 646 | updateComponentPreRender(instance, next); 647 | } 648 | 649 | const { proxy } = instance; 650 | const subTree = instance.render.call(proxy, proxy); 651 | const prevSubTree = instance.subTree; 652 | instance.subTree = subTree; 653 | 654 | patch(prevSubTree, subTree, container, instance, anchor); 655 | } 656 | }, 657 | { 658 | scheduler() { 659 | console.log("effect 的 scheduler 逻辑,数据更新,视图不更新"); 660 | queueJobs(instance.update); 661 | }, 662 | } 663 | ); 664 | } 665 | 666 | return { 667 | createApp: createAppAPI(render), 668 | }; 669 | } 670 | 671 | /** 672 | * @description 更新属性 673 | * @author Werewolf 674 | * @date 2021-12-24 675 | * @param {*} instance 676 | * @param {*} nextVNode 677 | */ 678 | function updateComponentPreRender(instance, nextVNode) { 679 | // 更新实例的虚拟节点 680 | instance.vnode = nextVNode; 681 | instance.next = null; 682 | 683 | // 更新props 684 | instance.props = nextVNode.props; 685 | } 686 | 687 | /** 688 | * @description 最长递增子序列 689 | * @author Werewolf 690 | * @date 2021-12-24 691 | * @param {*} arr 692 | * @return {*} 693 | */ 694 | function getSequence(arr) { 695 | const p = arr.slice(); 696 | const result = [0]; 697 | let i, j, u, v, c; 698 | const len = arr.length; 699 | for (i = 0; i < len; i++) { 700 | const arrI = arr[i]; 701 | if (arrI !== 0) { 702 | j = result[result.length - 1]; 703 | if (arr[j] < arrI) { 704 | p[i] = j; 705 | result.push(i); 706 | continue; 707 | } 708 | u = 0; 709 | v = result.length - 1; 710 | while (u < v) { 711 | c = (u + v) >> 1; 712 | if (arr[result[c]] < arrI) { 713 | u = c + 1; 714 | } else { 715 | v = c; 716 | } 717 | } 718 | if (arrI < arr[result[u]]) { 719 | if (u > 0) { 720 | p[i] = result[u - 1]; 721 | } 722 | result[u] = i; 723 | } 724 | } 725 | } 726 | u = result.length; 727 | v = result[u - 1]; 728 | while (u-- > 0) { 729 | result[u] = v; 730 | v = p[v]; 731 | } 732 | return result; 733 | } 734 | -------------------------------------------------------------------------------- /src/runtime-core/scheduler.ts: -------------------------------------------------------------------------------- 1 | const queue: any[] = []; 2 | 3 | let isFlushPending = false; 4 | 5 | export function nextTick(fn) { 6 | return fn ? Promise.resolve().then(fn) : Promise.resolve(); 7 | } 8 | export function queueJobs(job) { 9 | if (!queue.includes(job)) { 10 | queue.push(job); 11 | } 12 | queueFlush(); 13 | } 14 | function queueFlush() { 15 | if (isFlushPending) { 16 | return; 17 | } 18 | isFlushPending = true; 19 | nextTick(flushJobs); 20 | } 21 | function flushJobs() { 22 | Promise.resolve().then(() => { 23 | isFlushPending = false; 24 | let job; 25 | 26 | while ((job = queue.shift())) { 27 | job && job(); 28 | } 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/ShapeFlags"; 2 | export const Fragment = Symbol("Fragment"); 3 | export const Text = Symbol("Text"); 4 | export { createVNode as createElementVNode }; 5 | export function createVNode(type, props?, children?) { 6 | // console.log(props) 7 | // 相同的节点 type key 相同 8 | const vnode = { 9 | type, 10 | key: props && props.key, 11 | props, 12 | 13 | // 组件实例 instance 14 | component: null, 15 | children, 16 | shapeFlag: getShapeFlag(type), 17 | el: null, 18 | }; 19 | 20 | // 为处理children准备,给vnode再次添加一个flag 21 | // 这里的逻辑是这样的 22 | /** 23 | * a,b,c,d 为二进制数 24 | * 如果 c = a | b,那么 c&b 和 c&a 后转为十进制为非0, c&d 后转为10进制为0 25 | * 26 | */ 27 | if (typeof children === "string") { 28 | // 0001 | 0100 -> 0101 29 | // 0010 | 0100 -> 0110 30 | vnode.shapeFlag = vnode.shapeFlag | ShapeFlags.TEXT_CHILDREN; 31 | } else if (Array.isArray(children)) { 32 | // 0001 | 1000 -> 1001 33 | // 0010 | 1000 -> 1010 34 | vnode.shapeFlag = vnode.shapeFlag | ShapeFlags.ARRAY_CHILDREN; 35 | } 36 | 37 | // slots children 38 | // 组件 + children object 39 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 40 | if (typeof children === "object") { 41 | vnode.shapeFlag |= ShapeFlags.SLOTS_CHILDREN; 42 | } 43 | } 44 | return vnode; 45 | } 46 | export function createTextVNode(text: string) { 47 | return createVNode(Text, {}, text); 48 | } 49 | 50 | function getShapeFlag(type: any) { 51 | // vnode 是element元素 还是 组件 0001 0010 52 | return typeof type === "string" 53 | ? ShapeFlags.ELEMENT 54 | : ShapeFlags.STATEFUL_COMPONENT; 55 | } 56 | -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../runtime-core"; 2 | function createElement(type: string) { 3 | // console.log("dom ------api") 4 | return document.createElement(type); 5 | } 6 | function patchProp(el, key, prevVal, nextVal) { 7 | const isOn = (key: string) => /^on[A-Z]/.test(key); 8 | if (isOn(key)) { 9 | const event = key.slice(2).toLowerCase(); 10 | el.addEventListener(event, nextVal); 11 | } else { 12 | if (nextVal === undefined || nextVal === null) { 13 | el.removeAttribute(key); 14 | } else { 15 | el.setAttribute(key, nextVal); 16 | } 17 | } 18 | } 19 | /** 20 | * @description 将子节点插入到指定位置anchor,没有指定位置默认插入到最后 21 | * @author Werewolf 22 | * @date 2021-12-20 23 | * @param {*} child 24 | * @param {*} parent 25 | * @param {*} anchor 将要插在这个节点之前 26 | */ 27 | function insert(child, parent, anchor) { 28 | // console.log("dom ------api") 29 | 30 | // 插入到最后 31 | // parent.append(child) 等价于 parent.insertBefore(child, parent, null) 32 | // console.log() 33 | parent.insertBefore(child, anchor || null); 34 | } 35 | /** 36 | * @description 删除子节点 37 | * @author Werewolf 38 | * @date 2021-12-17 39 | * @param {*} child 子节点 40 | */ 41 | function remove(child) { 42 | const parent = child.parentNode; 43 | if (parent) { 44 | parent.removeChild(child); 45 | } 46 | } 47 | 48 | /** 49 | * @description 设置text 节点 50 | * @author Werewolf 51 | * @date 2021-12-17 52 | * @param {*} el 父容器 53 | * @param {*} text 子节点 54 | */ 55 | function setElementText(el, text) { 56 | el.textContent = text; 57 | } 58 | 59 | const renderer: any = createRenderer({ 60 | createElement, 61 | patchProp, 62 | setElementText, 63 | remove, 64 | insert, 65 | }); 66 | 67 | // return { 68 | // createApp: createAppAPI(render) 69 | // } 70 | export function createApp(...args) { 71 | return renderer.createApp(...args); 72 | 73 | // 调用流程 74 | // return createAppAPI(render)(...args); 75 | // export function createAppAPI(render) { 76 | // return function createApp(rootComponent) { 77 | // return { 78 | // mount(rootContainer) { 79 | // // 先创建 vnode 80 | // // component -> vnode 81 | // // 所有逻辑操作 都会基于 vnode 做处理 82 | // const vnode = createVNode(rootComponent); 83 | // // 渲染虚拟节点 84 | // render(vnode, rootContainer); 85 | // }, 86 | // }; 87 | // } 88 | // } 89 | } 90 | 91 | export * from "../runtime-core"; 92 | -------------------------------------------------------------------------------- /src/shared/ShapeFlags.ts: -------------------------------------------------------------------------------- 1 | export const enum ShapeFlags { 2 | ELEMENT = 1, // 0001 1 3 | STATEFUL_COMPONENT = 1 << 1, // 0010 2 4 | TEXT_CHILDREN = 1 << 2, // 0100 4 5 | ARRAY_CHILDREN = 1 << 3, // 1000 8 6 | SLOTS_CHILDREN = 1 << 4, // 10000 16 7 | } -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export const extend = Object.assign; 2 | export const isObject = (value) => { 3 | return value !== null && typeof value === "object"; 4 | }; 5 | export const hasChanged = (v1, v2) => { 6 | return !Object.is(v1, v2); 7 | }; 8 | export const camelize = (str: string) => { 9 | return str.replace(/-(\w)/g, (_, c: string) => { 10 | return c ? c.toUpperCase() : ""; 11 | }); 12 | }; 13 | export const isString = (value) => typeof value === "string"; 14 | export const hasOwn = (val, key) => 15 | Object.prototype.hasOwnProperty.call(val, key); 16 | const capitalize = (str: string) => { 17 | return str.charAt(0).toUpperCase() + str.slice(1); 18 | }; 19 | 20 | export const toHandlerKey = (str: string) => { 21 | return str ? "on" + capitalize(str) : ""; 22 | }; 23 | export * from "./toDisplayString"; 24 | -------------------------------------------------------------------------------- /src/shared/toDisplayString.ts: -------------------------------------------------------------------------------- 1 | export function toDisplayString(value) { 2 | return String(value); 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | "lib": ["DOM","ES2016"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "esnext", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 75 | 76 | /* Type Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | C:\Program Files\nodejs\node.exe C:\Program Files (x86)\Yarn\bin\yarn.js add rollup --dev 3 | 4 | PATH: 5 | C:\Users\fe\Desktop\cmder_mini\vendor\conemu-maximus5\ConEmu\Scripts;C:\Users\fe\Desktop\cmder_mini\vendor\conemu-maximus5;C:\Users\fe\Desktop\cmder_mini\vendor\conemu-maximus5\ConEmu;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\Git\cmd;C:\ProgramData\chocolatey\bin;C:\Program Files (x86)\Yarn\bin\;D:\mingw\bin;C:\Program Files\nodejs\;C:\Users\fe\AppData\Local\Microsoft\WindowsApps;C:\Users\fe\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\fe\AppData\Local\Yarn\bin;C:\Users\fe\AppData\Roaming\npm;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Users\fe\Desktop\cmder_mini\vendor\bin;C:\Users\fe\Desktop\cmder_mini 6 | 7 | Yarn version: 8 | 1.22.5 9 | 10 | Node version: 11 | 16.13.1 12 | 13 | Platform: 14 | win32 x64 15 | 16 | Trace: 17 | Error: ENOENT: no such file or directory, copyfile 'C:\Users\fe\AppData\Local\Yarn\Cache\v6\npm-ansi-styles-3.2.1-41fbb20243e50b12be0f04b8dedbf07520ce841d-integrity\node_modules\ansi-styles\index.js' -> 'C:\Users\fe\Desktop\mini-vue-again\node_modules\@babel\highlight\node_modules\ansi-styles\index.js' 18 | 19 | npm manifest: 20 | { 21 | "name": "mini-vue-again", 22 | "version": "1.0.0", 23 | "description": "", 24 | "main": "index.js", 25 | "scripts": { 26 | "test": "jest" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/fengjinlong/mini-vue-again.git" 31 | }, 32 | "keywords": [], 33 | "author": "", 34 | "license": "ISC", 35 | "bugs": { 36 | "url": "https://github.com/fengjinlong/mini-vue-again/issues" 37 | }, 38 | "homepage": "https://github.com/fengjinlong/mini-vue-again#readme", 39 | "devDependencies": { 40 | "@babel/core": "^7.16.0", 41 | "@babel/preset-env": "^7.16.4", 42 | "@babel/preset-typescript": "^7.16.0", 43 | "@types/jest": "^27.0.3", 44 | "jest": "^27.4.3", 45 | "typescript": "^4.5.2" 46 | } 47 | } 48 | 49 | yarn manifest: 50 | No manifest 51 | 52 | Lockfile: 53 | No lockfile 54 | --------------------------------------------------------------------------------