├── .DS_Store ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── babel.config.js ├── example ├── apiInject │ ├── App.js │ └── index.html ├── compiler-base │ ├── App.js │ ├── index.html │ └── main.js ├── componentEmit │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentSlot │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentUpdate │ ├── App.js │ ├── Child.js │ ├── index.html │ └── main.js ├── currentInstance │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── customRenderer │ ├── App.js │ ├── index.html │ └── main.js ├── helloworld │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── nextTicker │ ├── App.js │ ├── index.html │ └── main.js ├── patchChildren │ ├── App.js │ ├── ArrayToArray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js └── update │ ├── App.js │ ├── index.html │ └── main.js ├── image ├── .DS_Store ├── children1.png ├── componentUpdate.png ├── course │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ └── 6.png ├── generate.png ├── nextTick.png ├── parse.png ├── props.png ├── transform.png ├── 双端对比-中间对比.png ├── 双端对比-左侧与右侧对比.png └── 响应式原理.png ├── lib ├── mini-vue3.cjs.js └── mini-vue3.esm.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── compiler-core │ ├── src │ │ ├── ast.ts │ │ ├── codegen.ts │ │ ├── compile.ts │ │ ├── index.ts │ │ ├── parse.ts │ │ ├── runtimeHelpers.ts │ │ ├── transform.ts │ │ ├── transforms │ │ │ ├── transformElement.ts │ │ │ ├── transformExpression.ts │ │ │ └── transformText.ts │ │ └── utils.ts │ └── tests │ │ ├── __snapshots__ │ │ └── codegen.spec.ts.snap │ │ ├── codegen.spec.ts │ │ ├── parse.spec.ts │ │ └── transform.spec.ts ├── index.ts ├── reactivity │ ├── baseHandlers.ts │ ├── computed.ts │ ├── dep.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ └── tests │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── apiWatch.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentPublicInstance.ts │ ├── componentSlots.ts │ ├── componentUpdateUtils.ts │ ├── createApp.ts │ ├── h.ts │ ├── helpers │ │ └── renderSlots.ts │ ├── index.ts │ ├── renderer.ts │ ├── scheduler.ts │ ├── tests │ │ └── apiWatch.spec.ts │ └── vnode.ts ├── runtime-dom │ └── index.ts ├── shared │ ├── index.ts │ ├── shapeFlags.ts │ └── toDisplayString.ts └── vue │ ├── compiler-sfc │ └── index.js │ ├── package-lock.json │ ├── package.json │ └── src │ ├── dev.ts │ └── index.ts ├── tsconfig.json └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin-debug/ 3 | bin-release/ 4 | [Oo]bj/ 5 | [Bb]in/ 6 | 7 | # Other files and folders 8 | .settings/ 9 | 10 | # Executables 11 | *.swf 12 | *.air 13 | *.ipa 14 | *.apk 15 | 16 | # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` 17 | # should NOT be excluded as they contain compiler settings and other important 18 | # information for Eclipse / Flash Builder. 19 | node_modules 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 4202, 3 | "cSpell.words": ["vnode"] 4 | } 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # mini-vue3 3 | 4 | > 实现 Vue3 核心逻辑的最简模型,本项目参考 [Vue3](https://github.com/vuejs/core) 、[mini-vue地址](https://github.com/cuixiaorui/mini-vue) 实现。 5 | 6 | ## Tasking 7 | 8 | - 响应式核心模块 9 | - 运行时 10 | 11 | **reactivity** 12 | 13 | - [x] reactive 的实现 14 | - [x] ref 的实现 15 | - [x] readonly 的实现 16 | - [x] computed 的实现 17 | - [x] track 依赖收集 18 | - [x] trigger 触发依赖 19 | - [x] 支持 isReactive 20 | - [x] 支持嵌套 reactive 21 | - [x] 支持 toRaw 22 | - [x] 支持 effect.scheduler 23 | - [x] 支持 effect.stop 24 | - [x] 支持 isReadonly 25 | - [x] 支持 isProxy 26 | - [x] 支持 shallowReadonly 27 | - [x] 支持 proxyRefs 28 | 29 | **runtime-core** 30 | 31 | - [x] 支持组件类型 32 | - [x] 支持 element 类型 33 | - [x] 初始化 props 34 | - [x] setup 可获取 props 和 context 35 | - [x] 支持 component emit 36 | - [x] 支持 proxy 37 | - [x] 可以在 render 函数中获取 setup 返回的对象 38 | - [x] nextTick 的实现 39 | - [x] 支持 getCurrentInstance 40 | - [x] 支持 provide/inject 41 | - [x] 支持最基础的 slots 42 | - [x] 支持 Text 类型节点 43 | - [x] 支持 $el api 44 | - [x] watch、watchEffect 的实现 45 | 46 | **runtime-dom** 47 | 48 | - [x] 支持 custom renderer 49 | 50 | **vue** 51 | 52 | - [x] compileToFunction 函数 53 | 54 | ## Ending 55 | 56 | 感谢 mini-vue 作者让我的源码阅读入门,学习 mini-vue 的过程中学到了很多得底层基础知识,学习过程记录在 dev 分支的 readme 有兴趣的同学可以看一下哦! 57 | -------------------------------------------------------------------------------- /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/mini-vue3.esm.js'; 3 | 4 | const Provider = { 5 | name: 'Provider', 6 | setup() { 7 | provide('foo', 'fooVal'); 8 | provide('bar', 'barVal'); 9 | }, 10 | render() { 11 | return h('div', {}, [h('p', {}, 'Provider'), h(Consumer)]); 12 | } 13 | }; 14 | 15 | const ProviderTwo = { 16 | name: 'ProviderTwo', 17 | setup() { 18 | provide('foo', 'fooTwo'); 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', () => 'bazDefaultFun'); 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 | return h('div', {}, `Consumer: - ${this.foo} - ${this.bar}`); 51 | } 52 | }; 53 | 54 | export default { 55 | name: 'App', 56 | setup() {}, 57 | render() { 58 | return h('div', {}, [h('p', {}, 'apiInject'), h(Provider)]); 59 | } 60 | }; -------------------------------------------------------------------------------- /example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/compiler-base/App.js: -------------------------------------------------------------------------------- 1 | import { ref } from "../../lib/mini-vue3.esm.js"; 2 | 3 | export const App = { 4 | name: "App", 5 | template: `
hi,{{count}}
`, 6 | setup() { 7 | const count = (window.count = ref(1)); 8 | return { 9 | count, 10 | }; 11 | }, 12 | }; -------------------------------------------------------------------------------- /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 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-28 10:16:36 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 10:16:50 6 | */ 7 | import { createApp } from "../../lib/mini-vue3.esm.js"; 8 | import { App } from "./App.js"; 9 | 10 | const rootContainer = document.querySelector("#app"); 11 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue3.esm.js'; 2 | import { Foo } from './Foo.js'; 3 | 4 | window.self = null; 5 | export const App = { 6 | // 在 .vue 文件中是 7 | // 在 template 中写 8 | // 然后编译成 render 函数执行 9 | render() { 10 | window.self = this; 11 | return h( 12 | 'div', {}, 13 | // string 14 | // this.$el -> get root element 15 | // 这个 msg 是我们调用 setup 返回的 msg 16 | // 实现思路:将 setup 返回的值 绑定到 render 函数的 this 上 17 | // proxy 是方便用户能够便捷的 获取组件的实例 不用说 this.setup.xxx 18 | 19 | // 在组件中创建一个 代理对象 - 初始化 20 | // 调用 render 绑定 代理对象 到 this 上 21 | // "hi," + this.msg 22 | // Array 23 | // [h("p", { class: "red" }, "hi red")] 24 | [ 25 | h(Foo, { 26 | onAdd(a, b, c) { 27 | console.log('我执行了onAdd', a, b, c); 28 | }, 29 | onAddFoo() { 30 | console.log('我执行了addFoo'); 31 | } 32 | }) 33 | ] 34 | ); 35 | }, 36 | setup() { 37 | return { 38 | msg: 'mini-vue' 39 | }; 40 | } 41 | }; -------------------------------------------------------------------------------- /example/componentEmit/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue3.esm.js'; 2 | 3 | export const Foo = { 4 | setup(props, { emit }) { 5 | // props 6 | // 父组件向子组件传入了 props 7 | // 在 render 中可以通过 this.count 使用 8 | // 只可以使用 不可以修改 9 | // emit 10 | // 子组件通过 emit 调用父组件中的方法 11 | // emit 是 setup 函数的 第二个 参数 12 | const emitAdd = () => { 13 | emit('add', 1, 2, 3); 14 | emit('add-foo'); 15 | }; 16 | return { emitAdd }; 17 | }, 18 | render() { 19 | // h(标签名,属性,内容) 20 | return h('button', { onClick: this.emitAdd }, 'emitAdd'); 21 | } 22 | }; -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | // vue3 用法 2 | import { createApp } from '../../lib/mini-vue3.esm.js'; 3 | import { App } from './App.js'; 4 | 5 | // mount 是接受一个 string 6 | // 目前的代码是接受一个容器 7 | // TODO 如果将 string -> 一个容器 8 | 9 | const rootContainer = document.querySelector('#app'); 10 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/componentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots, createTextVNode } from '../../lib/mini-vue3.esm.js'; 2 | import { Foo } from './Foo.js'; 3 | 4 | export const App = { 5 | render() { 6 | const app = h('div', {}, 'App'); 7 | // 如果是数组 8 | // h 函数必须渲染的是 虚拟节点 此时是一个数组 9 | // 我们需要一个帮助函数帮我们转换 10 | // const foo = h(Foo, {}, [h('p', {}, 'header'), h('p', {}, 'footer')]) 11 | // 第三个参数 变成 对象 -> key 转换为一个 具名插槽 12 | // const foo = h(Foo, {}, { 13 | // header: h('p', {}, 'header'), 14 | // footer: h('p', {}, 'footer') 15 | // }); 16 | // return h( 17 | // 'div', {}, [app, foo] 18 | // ); 19 | const foo = h( 20 | Foo, {}, { 21 | header: ({ age }) => [ 22 | h('p', {}, 'header' + age), 23 | createTextVNode('你好啊') 24 | ], 25 | footer: () => h('p', {}, 'footer') 26 | } 27 | ); 28 | return h('div', {}, [app, foo]); 29 | }, 30 | setup() { 31 | return { 32 | msg: 'mini-vue' 33 | }; 34 | } 35 | }; -------------------------------------------------------------------------------- /example/componentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from '../../lib/mini-vue3.esm.js'; 2 | 3 | export const Foo = { 4 | setup() {}, 5 | render() { 6 | // 实现 3 中插槽方式 7 | // 1、普通插槽 8 | // 单个标签 9 | // 数组形式 10 | // const foo = h('div', {}, 'Foo') 11 | // return h('div', {}, [foo, renderSlots(this.$slots)]); 12 | // 2、具名插槽 13 | // const foo = h('div', {}, 'Foo') 14 | // return h('div', {}, [renderSlots(this.$slots, 'header'), foo, renderSlots(this.$slots, 'footer')]); 15 | // 3、作用域插槽 16 | // 父组件能够使用子组件中的数据 17 | const foo = h('div', {}, 'Foo'); 18 | const age = 18; 19 | return h('div', {}, [ 20 | renderSlots(this.$slots, 'header', { age }), 21 | foo, 22 | renderSlots(this.$slots, 'footer') 23 | ]); 24 | // h(标签名,属性,内容) 25 | } 26 | }; -------------------------------------------------------------------------------- /example/componentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/componentSlot/main.js: -------------------------------------------------------------------------------- 1 | // vue3 用法 2 | import { createApp } from '../../lib/mini-vue3.esm.js'; 3 | import { App } from './App.js'; 4 | 5 | // mount 是接受一个 string 6 | // 目前的代码是接受一个容器 7 | // TODO 如果将 string -> 一个容器 8 | 9 | const rootContainer = document.querySelector('#app'); 10 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/mini-vue3.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 | onClick: this.changeChildProps 29 | }, 30 | 'change child props' 31 | ), 32 | h(Child, { 33 | msg: this.msg 34 | }), 35 | h( 36 | 'button', { 37 | onClick: this.changeCount 38 | }, 39 | 'change self count' 40 | ), 41 | h('p', {}, 'count: ' + this.count) 42 | ]); 43 | } 44 | }; -------------------------------------------------------------------------------- /example/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue3.esm.js'; 2 | export default { 3 | name: 'Child', 4 | setup(props, { emit }) {}, 5 | render(proxy) { 6 | return h('div', {}, [ 7 | // 为能直接使用 $props 就要修改 componentPublicInstance 文件 8 | h('div', {}, 'child - props - msg: ' + this.$props.msg) 9 | ]); 10 | } 11 | }; -------------------------------------------------------------------------------- /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/mini-vue3.esm.js'; 2 | import { App } from './App.js'; 3 | 4 | const rootContainer = document.querySelector('#app'); 5 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from '../../lib/mini-vue3.esm.js'; 2 | import { Foo } from './Foo.js'; 3 | 4 | export const App = { 5 | name: 'App', 6 | render() { 7 | return h('div', {}, [h('p', {}, 'currentInstance demo'), h(Foo)]); 8 | }, 9 | 10 | setup() { 11 | // getCurrentInstance 是获取当前组件对象实例 12 | // 在 setup 中使用 13 | // 实现步骤 14 | // 先找到 setup 调用的地方 保存实例 15 | // 最后 清空 即可 因为每个组件的 实例都是不同的 16 | const instance = getCurrentInstance(); 17 | console.log('App:', instance); 18 | } 19 | }; -------------------------------------------------------------------------------- /example/currentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from '../../lib/mini-vue3.esm.js'; 2 | 3 | export const Foo = { 4 | name: 'Foo', 5 | setup() { 6 | const instance = getCurrentInstance(); 7 | console.log('Foo:', instance); 8 | return {}; 9 | }, 10 | render() { 11 | return h('div', {}, 'foo'); 12 | } 13 | }; -------------------------------------------------------------------------------- /example/currentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue3.esm.js'; 2 | import { App } from './App.js'; 3 | 4 | const rootContainer = document.querySelector('#app'); 5 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/customRenderer/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue3.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 | }; -------------------------------------------------------------------------------- /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/mini-vue3.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); -------------------------------------------------------------------------------- /example/helloworld/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue3.esm.js'; 2 | import { Foo } from './Foo.js'; 3 | 4 | window.self = null; 5 | export const App = { 6 | // 在 .vue 文件中是 7 | // 在 template 中写 8 | // 然后编译成 render 函数执行 9 | render() { 10 | window.self = this; 11 | return h( 12 | 'div', { 13 | id: 'root', 14 | class: ['red', 'hard'], 15 | onClick() { 16 | console.log('onClick'); 17 | }, 18 | onMouseenter() { 19 | console.log('onMouseenter'); 20 | } 21 | }, 22 | // string 23 | // this.$el -> get root element 24 | // 这个 msg 是我们调用 setup 返回的 msg 25 | // 实现思路:将 setup 返回的值 绑定到 render 函数的 this 上 26 | // proxy 是方便用户能够便捷的 获取组件的实例 不用说 this.setup.xxx 27 | 28 | // 在组件中创建一个 代理对象 - 初始化 29 | // 调用 render 绑定 代理对象 到 this 上 30 | // "hi," + this.msg 31 | // Array 32 | // [h("p", { class: "red" }, "hi red")] 33 | [h(Foo, { count: 1 })] 34 | ); 35 | }, 36 | setup() { 37 | return { 38 | msg: 'mini-vue' 39 | }; 40 | } 41 | }; -------------------------------------------------------------------------------- /example/helloworld/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue3.esm.js'; 2 | 3 | export const Foo = { 4 | setup(props) { 5 | // 父组件向子组件传入了 props 6 | // 在 render 中可以通过 this.count 使用 7 | // 只可以使用 不可以修改 8 | }, 9 | render() { 10 | // h(标签名,属性,内容) 11 | return h('div', {}, 'foo:' + this.count); 12 | } 13 | }; -------------------------------------------------------------------------------- /example/helloworld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/helloworld/main.js: -------------------------------------------------------------------------------- 1 | // vue3 用法 2 | import { createApp } from '../../lib/mini-vue3.esm.js'; 3 | import { App } from './App.js'; 4 | 5 | // mount 是接受一个 string 6 | // 目前的代码是接受一个容器 7 | // TODO 如果将 string -> 一个容器 8 | 9 | const rootContainer = document.querySelector('#app'); 10 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/nextTicker/App.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | ref, 4 | getCurrentInstance, 5 | nextTick 6 | } from '../../lib/mini-vue3.esm.js'; 7 | 8 | export default { 9 | name: 'App', 10 | setup() { 11 | const count = ref(1); 12 | const instance = getCurrentInstance(); 13 | 14 | function onClick() { 15 | // 做视图更新的时候 只需要渲染一次 就能达到这个效果 16 | // 当同步任务都执行完之后,再执行微任务 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 | 25 | // nextTick 解决了什么问题 26 | // 比如 有一个 for 循环 咱们只需要更新最后一次 27 | // 如何实现? 28 | // 将更新函数变成 微任务 就可以等待同步任务完成后 再执行 29 | // 而 nextTick 的作用把这个回调函数推入 微任务调用栈 30 | // 往前想一下 effect 中 有一个 scheduler 函数 31 | // 我们可以使用 scheduler 函数实现 32 | nextTick(() => { 33 | console.log(instance); 34 | }); 35 | 36 | // await nextTick() 37 | // console.log(instance) 38 | } 39 | 40 | return { 41 | onClick, 42 | count 43 | }; 44 | }, 45 | render() { 46 | const button = h('button', { onClick: this.onClick }, 'update'); 47 | const p = h('p', {}, 'count:' + this.count); 48 | 49 | return h('div', {}, [button, p]); 50 | } 51 | }; -------------------------------------------------------------------------------- /example/nextTicker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/nextTicker/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue3.esm.js'; 2 | import App from './App.js'; 3 | 4 | const rootContainer = document.querySelector('#root'); 5 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue3.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 | }; -------------------------------------------------------------------------------- /example/patchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | // 老的是 array 2 | // 新的是 array 3 | 4 | import { ref, h } from '../../lib/mini-vue3.esm.js'; 5 | 6 | // 1. 左侧的对比 7 | // (a b) c 8 | // (a b) d e 9 | // const prevChildren = [ 10 | // h("p", { key: "A" }, "A"), 11 | // h("p", { key: "B" }, "B"), 12 | // h("p", { key: "C" }, "C"), 13 | // ]; 14 | // const nextChildren = [ 15 | // h("p", { key: "A" }, "A"), 16 | // h("p", { key: "B" }, "B"), 17 | // h("p", { key: "D" }, "D"), 18 | // h("p", { key: "E" }, "E"), 19 | // ]; 20 | 21 | // 2. 右侧的对比 22 | // a (b c) 23 | // d e (b c) 24 | // const prevChildren = [ 25 | // h("p", { key: "A" }, "A"), 26 | // h("p", { key: "B" }, "B"), 27 | // h("p", { key: "C" }, "C"), 28 | // ]; 29 | // const nextChildren = [ 30 | // h("p", { key: "D" }, "D"), 31 | // h("p", { key: "E" }, "E"), 32 | // h("p", { key: "B" }, "B"), 33 | // h("p", { key: "C" }, "C"), 34 | // ]; 35 | 36 | // 3. 新的比老的长 37 | // 创建新的 38 | // 左侧 39 | // (a b) 40 | // (a b) c 41 | // i = 2, e1 = 1, e2 = 2 42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 43 | // const nextChildren = [ 44 | // h("p", { key: "A" }, "A"), 45 | // h("p", { key: "B" }, "B"), 46 | // h("p", { key: "C" }, "C"), 47 | // h("p", { key: "D" }, "D"), 48 | // ]; 49 | 50 | // 右侧 51 | // (a b) 52 | // c (a b) 53 | // i = 0, e1 = -1, e2 = 0 54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 55 | // const nextChildren = [ 56 | // h("p", { key: "C" }, "C"), 57 | // h("p", { key: "A" }, "A"), 58 | // h("p", { key: "B" }, "B"), 59 | // ]; 60 | 61 | // 4. 老的比新的长 62 | // 删除老的 63 | // 左侧 64 | // (a b) c 65 | // (a b) 66 | // i = 2, e1 = 2, e2 = 1 67 | // const prevChildren = [ 68 | // h("p", { key: "A" }, "A"), 69 | // h("p", { key: "B" }, "B"), 70 | // h("p", { key: "C" }, "C"), 71 | // ]; 72 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 73 | 74 | // 右侧 75 | // a (b c) 76 | // (b c) 77 | // i = 0, e1 = 0, e2 = -1 78 | 79 | // const prevChildren = [ 80 | // h("p", { key: "A" }, "A"), 81 | // h("p", { key: "B" }, "B"), 82 | // h("p", { key: "C" }, "C"), 83 | // ]; 84 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 85 | 86 | // 5. 对比中间的部分 87 | // 删除老的 (在老的里面存在,新的里面不存在) 88 | // 5.1 89 | // a,b,(c,d),f,g 90 | // a,b,(e,c),f,g 91 | // D 节点在新的里面是没有的 - 需要删除掉 92 | // C 节点 props 也发生了变化 93 | 94 | // const prevChildren = [ 95 | // h("p", { key: "A" }, "A"), 96 | // h("p", { key: "B" }, "B"), 97 | // h("p", { key: "C", id: "c-prev" }, "C"), 98 | // h("p", { key: "D" }, "D"), 99 | // h("p", { key: "F" }, "F"), 100 | // h("p", { key: "G" }, "G"), 101 | // ]; 102 | 103 | // const nextChildren = [ 104 | // h("p", { key: "A" }, "A"), 105 | // h("p", { key: "B" }, "B"), 106 | // h("p", { key: "E" }, "E"), 107 | // h("p", { key: "C", id:"c-next" }, "C"), 108 | // h("p", { key: "F" }, "F"), 109 | // h("p", { key: "G" }, "G"), 110 | // ]; 111 | 112 | // 5.1.1 113 | // a,b,(c,e,d),f,g 114 | // a,b,(e,c),f,g 115 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑) 116 | // const prevChildren = [ 117 | // h("p", { key: "A" }, "A"), 118 | // h("p", { key: "B" }, "B"), 119 | // h("p", { key: "C", id: "c-prev" }, "C"), 120 | // h("p", { key: "E" }, "E"), 121 | // h("p", { key: "D" }, "D"), 122 | // h("p", { key: "F" }, "F"), 123 | // h("p", { key: "G" }, "G"), 124 | // ]; 125 | 126 | // const nextChildren = [ 127 | // h("p", { key: "A" }, "A"), 128 | // h("p", { key: "B" }, "B"), 129 | // h("p", { key: "E" }, "E"), 130 | // h("p", { key: "C", id:"c-next" }, "C"), 131 | // h("p", { key: "F" }, "F"), 132 | // h("p", { key: "G" }, "G"), 133 | // ]; 134 | 135 | // 2 移动 (节点存在于新的和老的里面,但是位置变了) 136 | 137 | // 2.1 138 | // a,b,(c,d,e),f,g 139 | // a,b,(e,c,d),f,g 140 | // 最长子序列: [1,2] 141 | 142 | // const prevChildren = [ 143 | // h("p", { key: "A" }, "A"), 144 | // h("p", { key: "B" }, "B"), 145 | // h("p", { key: "C" }, "C"), 146 | // h("p", { key: "D" }, "D"), 147 | // h("p", { key: "E" }, "E"), 148 | // h("p", { key: "F" }, "F"), 149 | // h("p", { key: "G" }, "G"), 150 | // ]; 151 | 152 | // const nextChildren = [ 153 | // h("p", { key: "A" }, "A"), 154 | // h("p", { key: "B" }, "B"), 155 | // h("p", { key: "E" }, "E"), 156 | // h("p", { key: "C" }, "C"), 157 | // h("p", { key: "D" }, "D"), 158 | // h("p", { key: "F" }, "F"), 159 | // h("p", { key: "G" }, "G"), 160 | // ]; 161 | 162 | // 3. 创建新的节点 163 | // a,b,(c,e),f,g 164 | // a,b,(e,c,d),f,g 165 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建 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: "E" }, "E"), 171 | // h("p", { key: "F" }, "F"), 172 | // h("p", { key: "G" }, "G"), 173 | // ]; 174 | 175 | // const nextChildren = [ 176 | // h("p", { key: "A" }, "A"), 177 | // h("p", { key: "B" }, "B"), 178 | // h("p", { key: "E" }, "E"), 179 | // h("p", { key: "C" }, "C"), 180 | // h("p", { key: "D" }, "D"), 181 | // h("p", { key: "F" }, "F"), 182 | // h("p", { key: "G" }, "G"), 183 | // ]; 184 | 185 | // 综合例子 186 | // a,b,(c,d,e,z),f,g 187 | // a,b,(d,c,y,e),f,g 188 | 189 | const prevChildren = [ 190 | h('p', { key: 'A' }, 'A'), 191 | h('p', { key: 'B' }, 'B'), 192 | h('p', { key: 'C' }, 'C'), 193 | h('p', { key: 'D' }, 'D'), 194 | h('p', { key: 'E' }, 'E'), 195 | h('p', { key: 'F' }, 'F'), 196 | h('p', { key: 'G' }, 'G') 197 | ]; 198 | 199 | const nextChildren = [ 200 | h('p', { key: 'A' }, 'A'), 201 | h('p', { key: 'B' }, 'B'), 202 | h('p', { key: 'E' }, 'E'), 203 | h('p', { key: 'C' }, 'C'), 204 | h('p', { key: 'D' }, 'D'), 205 | h('p', { key: 'F' }, 'F'), 206 | h('p', { key: 'G' }, 'G') 207 | ]; 208 | 209 | export default { 210 | name: 'ArrayToArray', 211 | setup() { 212 | const isChange = ref(false); 213 | window.isChange = isChange; 214 | 215 | return { 216 | isChange 217 | }; 218 | }, 219 | render() { 220 | const self = this; 221 | 222 | return self.isChange === true ? 223 | h('div', {}, nextChildren) : 224 | h('div', {}, prevChildren); 225 | } 226 | }; -------------------------------------------------------------------------------- /example/patchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | // 老的是 array 2 | // 新的是 text 3 | 4 | import { ref, h } from '../../lib/mini-vue3.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 | }; -------------------------------------------------------------------------------- /example/patchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | // 新的是 array 2 | // 老的是 text 3 | import { ref, h } from '../../lib/mini-vue3.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 | }; -------------------------------------------------------------------------------- /example/patchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | // 新的是 text 2 | // 老的是 text 3 | import { ref, h } from '../../lib/mini-vue3.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 | }; -------------------------------------------------------------------------------- /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/mini-vue3.esm.js'; 2 | import App from './App.js'; 3 | 4 | const rootContainer = document.querySelector('#root'); 5 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/mini-vue3.esm.js'; 2 | 3 | export const App = { 4 | name: 'App', 5 | 6 | setup() { 7 | const count = ref(0); 8 | 9 | const onClick = () => { 10 | count.value++; 11 | }; 12 | 13 | const props = ref({ 14 | foo: 'foo', 15 | bar: 'bar' 16 | }); 17 | 18 | const onChangePropsDemo1 = () => { 19 | props.value.foo = 'new-foo'; 20 | }; 21 | 22 | const onChangePropsDemo2 = () => { 23 | props.value.foo = undefined; 24 | }; 25 | 26 | const onChangePropsDemo3 = () => { 27 | props.value = { 28 | foo: 'foo' 29 | }; 30 | }; 31 | 32 | return { 33 | count, 34 | onClick, 35 | onChangePropsDemo1, 36 | onChangePropsDemo2, 37 | onChangePropsDemo3, 38 | props 39 | }; 40 | }, 41 | render() { 42 | return h( 43 | 'div', { 44 | id: 'root', 45 | ...this.props 46 | }, [ 47 | h('div', {}, 'count:' + this.count), 48 | h( 49 | 'button', { 50 | onClick: this.onClick 51 | }, 52 | 'click' 53 | ), 54 | h( 55 | 'button', { 56 | onClick: this.onChangePropsDemo1 57 | }, 58 | 'changeProps - 值改变了 - 修改' 59 | ), 60 | 61 | h( 62 | 'button', { 63 | onClick: this.onChangePropsDemo2 64 | }, 65 | 'changeProps - 值变成了 undefined - 删除' 66 | ), 67 | 68 | h( 69 | 'button', { 70 | onClick: this.onChangePropsDemo3 71 | }, 72 | 'changeProps - key 在新的里面没有了 - 删除' 73 | ) 74 | ] 75 | ); 76 | } 77 | }; -------------------------------------------------------------------------------- /example/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue3.esm.js'; 2 | import { App } from './App.js'; 3 | 4 | const rootContainer = document.querySelector('#app'); 5 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /image/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/.DS_Store -------------------------------------------------------------------------------- /image/children1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/children1.png -------------------------------------------------------------------------------- /image/componentUpdate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/componentUpdate.png -------------------------------------------------------------------------------- /image/course/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/course/1.png -------------------------------------------------------------------------------- /image/course/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/course/2.png -------------------------------------------------------------------------------- /image/course/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/course/3.png -------------------------------------------------------------------------------- /image/course/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/course/4.png -------------------------------------------------------------------------------- /image/course/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/course/5.png -------------------------------------------------------------------------------- /image/course/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/course/6.png -------------------------------------------------------------------------------- /image/generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/generate.png -------------------------------------------------------------------------------- /image/nextTick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/nextTick.png -------------------------------------------------------------------------------- /image/parse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/parse.png -------------------------------------------------------------------------------- /image/props.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/props.png -------------------------------------------------------------------------------- /image/transform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/transform.png -------------------------------------------------------------------------------- /image/双端对比-中间对比.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/双端对比-中间对比.png -------------------------------------------------------------------------------- /image/双端对比-左侧与右侧对比.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/双端对比-左侧与右侧对比.png -------------------------------------------------------------------------------- /image/响应式原理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leiloloaa/mini-vue3/097bd4384adedc36d372e6f311b04f9e7710420e/image/响应式原理.png -------------------------------------------------------------------------------- /lib/mini-vue3.esm.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-28 11:12:06 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 11:12:06 6 | */ 7 | function toDisplayString(value) { 8 | return String(value); 9 | } 10 | 11 | /* 12 | * @Author: Stone 13 | * @Date: 2022-04-24 19:41:18 14 | * @LastEditors: Stone 15 | * @LastEditTime: 2022-04-28 11:12:59 16 | */ 17 | const extend = Object.assign; 18 | const isObject = (value) => { 19 | return value !== null && typeof value === "object"; 20 | }; 21 | const isFunction = (value) => { 22 | return value !== null && typeof value === "function"; 23 | }; 24 | const isString = (value) => { 25 | return value !== null && typeof value === "string"; 26 | }; 27 | const isArray = (value) => { 28 | return value !== null && Array.isArray(value); 29 | }; 30 | const hasChanged = (value, newValue) => { return !Object.is(value, newValue); }; 31 | const isOn = (key) => { 32 | return /^on[A-Z]/.test(key); 33 | }; 34 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); 35 | const camelize = (str) => { 36 | // 需要将 str 中的 - 全部替换,斌且下一个要 设置成大写 37 | // \w 匹配字母或数字或下划线或汉字 等价于 '[^A-Za-z0-9_]'。 38 | // \s 匹配任意的空白符 39 | // \d 匹配数字 40 | // \b 匹配单词的开始或结束 41 | // ^ 匹配字符串的开始 42 | // $ 匹配字符串的结束 43 | // replace 第二参数是值得话就是直接替换 44 | // 如果是一个回调函数 那么 就可以依次的修改值 45 | return str.replace(/-(\w)/g, (_, c) => { 46 | return c ? c.toUpperCase() : ''; 47 | }); 48 | }; 49 | const capitalize = (str) => { 50 | return str.charAt(0).toUpperCase() + str.slice(1); 51 | }; 52 | const toHandlerKey = (str) => { 53 | return str ? "on" + capitalize(str) : ''; 54 | }; 55 | 56 | let activeEffect; 57 | let shouldTrack = false; 58 | class ReactiveEffect { 59 | constructor(fn, scheduler) { 60 | this.deps = []; 61 | this.active = true; 62 | this._fn = fn; 63 | this.scheduler = scheduler; 64 | } 65 | run() { 66 | // 会收集依赖 67 | // shouldTrack 来区分 68 | // 如果是 stop 的状态 69 | // 就不收集 70 | if (!this.active) { 71 | return this._fn(); 72 | } 73 | // 否则收集 74 | shouldTrack = true; 75 | activeEffect = this; 76 | const result = this._fn(); 77 | // reset 因为是全局变量 78 | // 处理完要还原 79 | shouldTrack = false; 80 | activeEffect = null; 81 | return result; 82 | } 83 | stop() { 84 | // 性能问题 85 | // 第一次调用 就已经清空了 86 | if (this.active) { 87 | cleanupEffect(this); 88 | if (this.onStop) { 89 | this.onStop(); 90 | } 91 | this.active = false; 92 | } 93 | } 94 | } 95 | function cleanupEffect(effect) { 96 | effect.deps.forEach((dep) => { 97 | dep.delete(effect); 98 | }); 99 | effect.deps.length = 0; 100 | } 101 | const targetsMap = new Map(); 102 | function track(target, key) { 103 | // 是否收集 shouldTrack 为 true 和 activeEffect 有值的时候要收集 否则就 return 出去 104 | if (!isTracking()) 105 | return; 106 | // 收集依赖 107 | // reactive 传入的是一个对象 {} 108 | // 收集关系: targetsMap 收集所有依赖 然后 每一个 {} 作为一个 depsMap 109 | // 再把 {} 里面的每一个变量作为 dep(set 结构) 的 key 存放所有的 fn 110 | let depsMap = targetsMap.get(target); 111 | // 不存在的时候 要先初始化 112 | if (!depsMap) { 113 | depsMap = new Map(); 114 | targetsMap.set(target, depsMap); 115 | } 116 | let dep = depsMap.get(key); 117 | if (!dep) { 118 | dep = new Set(); 119 | depsMap.set(key, dep); 120 | } 121 | // 如果是单纯的获取 就不会有 activeEffect 122 | // 因为 activeEffect 是在 effect.run 执行的时候 才会存在 123 | // if (!activeEffect) return 124 | // 应该收集依赖 125 | // !! 思考 什么时候被赋值呢? 126 | // 触发 set 执行 fn 然后再触发 get 127 | // 所以在 run 方法中 128 | // if (!shouldTrack) return 129 | // if (dep.has(activeEffect)) return 130 | // // 要存入的是一个 fn 131 | // // 所以要利用一个全局变量 132 | // dep.add(activeEffect) 133 | // // 如何通过当前的 effect 去找到 deps? 134 | // // 反向收集 deps 135 | // activeEffect.deps.push(dep) 136 | trackEffects(dep); 137 | } 138 | // 抽离 track 与 ref 公用 139 | function trackEffects(dep) { 140 | if (dep.has(activeEffect)) 141 | return; 142 | // 要存入的是一个 fn 143 | // 所以要利用一个全局变量 144 | dep.add(activeEffect); 145 | // 如何通过当前的 effect 去找到 deps? 146 | // 反向收集 deps 147 | activeEffect.deps.push(dep); 148 | } 149 | function isTracking() { 150 | return shouldTrack && activeEffect !== undefined; 151 | } 152 | function trigger(target, type, key) { 153 | // 触发依赖 154 | let depsMap = targetsMap.get(target); 155 | let dep = depsMap.get(key); 156 | triggerEffects(dep); 157 | } 158 | function triggerEffects(dep) { 159 | for (const effect of dep) { 160 | if (effect.scheduler) { 161 | effect.scheduler(); 162 | } 163 | else { 164 | effect.run(); 165 | } 166 | } 167 | } 168 | function effect(fn, options = {}) { 169 | // ReactiveEffect 构造函数(一定要用 new 关键字实现) 170 | const _effect = new ReactiveEffect(fn, options.scheduler); 171 | // 考虑到后面还会有很多 options 172 | // 使用 Object.assign() 方法自动合并 173 | // _effect.onStop = options.onStop 174 | // Object.assign(_effect, options); 175 | // extend 扩展 更有可读性 176 | extend(_effect, options); 177 | _effect.run(); 178 | const runner = _effect.run.bind(_effect); 179 | // 保存 180 | runner.effect = _effect; 181 | return runner; 182 | } 183 | function stop(runner) { 184 | // stop 的意义 是找要到这个实例 然后删除 185 | runner.effect.stop(); 186 | } 187 | 188 | // 缓存 首次创建即可 189 | const get = createGetter(); 190 | const set = createSetter(); 191 | const readonlyGet = createGetter(true); 192 | const shallowReadonlyGet = createGetter(true, true); 193 | // 1、reactive 和 readonly 逻辑相似 抽离代码 194 | // 2、使用高阶函数 来区分是否要 track 195 | function createGetter(isReadonly = false, shallow = false) { 196 | return function get(target, key, receiver) { 197 | const isExistInReactiveMap = () => key === "__v_raw" /* RAW */ && receiver === reactiveMap.get(target); 198 | const isExistInReadonlyMap = () => key === "__v_raw" /* RAW */ && receiver === readonlyMap.get(target); 199 | const isExistInShallowReadonlyMap = () => key === "__v_raw" /* RAW */ && receiver === shallowReadonlyMap.get(target); 200 | if (key === "__v_isReactive" /* IS_REACTIVE */) { 201 | return !isReadonly; 202 | } 203 | else if (key === "__v_isReadonly" /* IS_READONLY */) { 204 | return isReadonly; 205 | } 206 | else if (isExistInReactiveMap() || 207 | isExistInReadonlyMap() || 208 | isExistInShallowReadonlyMap()) { 209 | return target; 210 | } 211 | const res = Reflect.get(target, key); 212 | // Proxy 要和 Reflect 配合使用 213 | // Reflect.get 中 receiver 参数,保留了对正确引用 this(即 admin)的引用,该引用将 Reflect.get 中正确的对象使用传递给 get 214 | // 不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为 215 | // 如果为 true 就直接返回 216 | if (shallow) { 217 | return res; 218 | } 219 | // 如果 res 是 Object 220 | if (isObject(res)) { 221 | return isReadonly ? readonly(res) : reactive(res); 222 | } 223 | if (!isReadonly) { 224 | track(target, key); 225 | } 226 | return res; 227 | }; 228 | } 229 | function createSetter() { 230 | return function set(target, key, value, receiver) { 231 | // set 操作是会放回 true or false 232 | // set() 方法应当返回一个布尔值。 233 | // 返回 true 代表属性设置成功。 234 | // 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。 235 | const res = Reflect.set(target, key, value, receiver); 236 | trigger(target, "get", key); 237 | return res; 238 | }; 239 | } 240 | const mutableHandlers = { 241 | get, 242 | set 243 | }; 244 | const readonlyHandlers = { 245 | get: readonlyGet, 246 | set(target, key) { 247 | console.warn(`key:${key}`); 248 | return true; 249 | } 250 | }; 251 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, { get: shallowReadonlyGet }); 252 | 253 | const reactiveMap = new WeakMap(); 254 | const readonlyMap = new WeakMap(); 255 | const shallowReadonlyMap = new WeakMap(); 256 | function reactive(target) { 257 | return createReactiveObject(target, reactiveMap, mutableHandlers); 258 | } 259 | function readonly(target) { 260 | return createReactiveObject(target, readonlyMap, readonlyHandlers); 261 | } 262 | function shallowReadonly(target) { 263 | return createReactiveObject(target, shallowReadonlyMap, shallowReadonlyHandlers); 264 | } 265 | function isReactive(value) { 266 | // 触发 get 操作 就可以判断 value.xxx 就会触发 267 | // value["is_reactive"] get 就可以获取到 is_reactive 268 | // 如果传过来的不是 proxy 值,所以就不会去调用 get 方法 269 | // 也没挂载 ReactiveFlags.IS_REACTIVE 属性 所以是 undefined 270 | // 使用 !! 转换成 boolean 值就可以了 271 | return !!value["__v_isReactive" /* IS_REACTIVE */]; 272 | } 273 | function isReadonly(value) { 274 | return !!value["__v_isReadonly" /* IS_READONLY */]; 275 | } 276 | function isProxy(value) { 277 | return isReactive(value) || isReadonly(value); 278 | } 279 | function createReactiveObject(target, proxyMap, baseHandlers) { 280 | if (!isObject(target)) { 281 | console.log('不是一个对象'); 282 | } 283 | // 核心就是 proxy 284 | // 目的是可以侦听到用户 get 或者 set 的动作 285 | // 如果命中的话就直接返回就好了 不需要每次都重新创建 286 | // 使用缓存做的优化点 287 | const existingProxy = proxyMap.get(target); 288 | if (existingProxy) { 289 | return existingProxy; 290 | } 291 | const proxy = new Proxy(target, baseHandlers); 292 | // 把创建好的 proxy 给存起来 293 | proxyMap.set(target, proxy); 294 | return proxy; 295 | } 296 | 297 | // 用于存储所有的 effect 对象 298 | function createDep(effects) { 299 | const dep = new Set(effects); 300 | return dep; 301 | } 302 | 303 | // 1 true '1' 304 | // get set 305 | // 而 proxy -》只能监听对象 306 | // 我们包裹一个 对象 307 | // Impl 表示一个接口的缩写 308 | class RefImpl { 309 | constructor(value) { 310 | this.__v_isRef = true; 311 | // 存储一个新值 用于后面的对比 312 | this._rawValue = value; 313 | // value -> reactive 314 | // 看看 value 是不是 对象 315 | this._value = convert(value); 316 | this.dep = createDep(); 317 | } 318 | // 属性访问器模式 319 | get value() { 320 | // 确保调用过 run 方法 不然 dep 就是 undefined 321 | // if (isTracking()) { 322 | // trackEffects(this.dep) 323 | // } 324 | trackRefValue(this); 325 | return this._value; 326 | } 327 | set value(newValue) { 328 | // 一定是先修改了 value 329 | // newValue -> this._value 相同不修改 330 | // if (Object.is(newValue, this._value)) return 331 | // hasChanged 332 | // 改变才运行 333 | // 对比的时候 object 334 | // 有可能 this.value 是 porxy 那么他们就不会相等 335 | if (hasChanged(newValue, this._rawValue)) { 336 | this._rawValue = newValue; 337 | this._value = convert(newValue); 338 | triggerEffects(this.dep); 339 | } 340 | } 341 | } 342 | function convert(value) { 343 | return isObject(value) ? reactive(value) : value; 344 | } 345 | function trackRefValue(ref) { 346 | if (isTracking()) { 347 | trackEffects(ref.dep); 348 | } 349 | } 350 | function ref(value) { 351 | return new RefImpl(value); 352 | } 353 | function isRef(ref) { 354 | return !!ref.__v_isRef; 355 | } 356 | // 语法糖 如果是 ref 就放回 .value 否则返回本身 357 | function unRef(ref) { 358 | return isRef(ref) ? ref.value : ref; 359 | } 360 | function proxyRefs(objectWithRefs) { 361 | return new Proxy(objectWithRefs, { 362 | get(target, key) { 363 | // get 如果获取到的是 age 是个 ref 那么就返回 .value 364 | // 如果不是 ref 就直接返回本身 365 | return unRef(Reflect.get(target, key)); 366 | }, 367 | set(target, key, value) { 368 | // value 是新值 369 | // 如果目标是 ref 且替换的值不是 ref 370 | if (isRef(target[key]) && !isRef(value)) { 371 | return target[key].value = value; 372 | } 373 | else { 374 | return Reflect.set(target, key, value); 375 | } 376 | } 377 | }); 378 | } 379 | 380 | class ComputedRefImpl { 381 | constructor(getter) { 382 | this._dirty = true; 383 | this._effect = new ReactiveEffect(getter, () => { 384 | if (!this._dirty) { 385 | this._dirty = true; 386 | } 387 | }); 388 | } 389 | get value() { 390 | // get 调用完一次就锁上 391 | // 当依赖的响应式对象的值发生改变的时候 392 | // effect 393 | if (this._dirty) { 394 | this._dirty = false; 395 | this._value = this._effect.run(); 396 | } 397 | return this._value; 398 | } 399 | } 400 | // getter 是一个函数 401 | function computed(getter) { 402 | return new ComputedRefImpl(getter); 403 | } 404 | 405 | function emit(instance, event, ...args) { 406 | const { props } = instance; 407 | // TPP 408 | // 先去实现 特定行为,然后再重构成 通用行为 409 | // add -> Add -> onAdd 410 | // add-foo -> addFoo -> onAddFoo 411 | // const camelize = (str) => { 412 | // 需要将 str 中的 - 全部替换,斌且下一个要 设置成大写 413 | // \w 匹配字母或数字或下划线或汉字 等价于 '[^A-Za-z0-9_]'。 414 | // \s 匹配任意的空白符 415 | // \d 匹配数字 416 | // \b 匹配单词的开始或结束 417 | // ^ 匹配字符串的开始 418 | // $ 匹配字符串的结束 419 | // replace 第二参数是值得话就是直接替换 420 | // 如果是一个回调函数 那么 就可以依次的修改值 421 | // return str.replace(/-(\w)/g, (_, c: string) => { 422 | // return c ? c.toUpperCase() : '' 423 | // }) 424 | // } 425 | // const capitalize = (str) => { 426 | // return str.charAt(0).toUpperCase() + str.slice(1) 427 | // } 428 | // const toHandlerKey = (str) => { 429 | // return str ? "on" + capitalize(str) : '' 430 | // } 431 | const handler = props[toHandlerKey(camelize(event))]; 432 | handler && handler(...args); 433 | } 434 | 435 | function initProps(instance, rawProps) { 436 | instance.props = rawProps || {}; 437 | } 438 | 439 | // 通过 map 的方式扩展 440 | // $el 是个 key 441 | const publicPropertiesMap = { 442 | $el: (i) => i.vnode.el, 443 | $slots: (i) => i.slots, 444 | $props: (i) => i.props 445 | }; 446 | const PublicInstanceProxyHandlers = { 447 | get({ _: instance }, key) { 448 | // setupState 449 | const { setupState, props } = instance; 450 | // if (Reflect.has(setupState, key)) { 451 | // return setupState[key] 452 | // } 453 | // 检测 key 是否在目标 上 454 | if (hasOwn(setupState, key)) { 455 | return setupState[key]; 456 | } 457 | else if (hasOwn(props, key)) { 458 | return props[key]; 459 | } 460 | // key -> $el 461 | // if (key === "$el") { 462 | // return instance.vnode.el 463 | // } 464 | const publicGetter = publicPropertiesMap[key]; 465 | if (publicGetter) { 466 | return publicGetter(instance); 467 | } 468 | // setup -> options data 469 | // $data 470 | } 471 | }; 472 | 473 | function initSlots(instance, children) { 474 | // array 475 | // instance.slots = Array.isArray(children) ? children : [children] 476 | // object 477 | // const slots = {} 478 | // for (const key in children) { 479 | // const value = children[key]; 480 | // slots[key] = Array.isArray(value) ? value : [value] 481 | // } 482 | // instance.slots = slots 483 | // const slots = {} 484 | // for (const key in children) { 485 | // const value = children[key]; 486 | // slots[key] = (props) => normalizeSlotValue(value(props)) 487 | // } 488 | // instance.slots = slots 489 | // 优化 并不是所有的 children 都有 slots 490 | // 通过 位运算 来处理 491 | const { vnode } = instance; 492 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) { 493 | normalizeObjectSlots(children, instance.slots); 494 | } 495 | } 496 | function normalizeObjectSlots(children, slots) { 497 | for (const key in children) { 498 | const value = children[key]; 499 | // slots[key] = Array.isArray(value) ? value : [value] 500 | // slots[key] = normalizeSlotValue(value) 501 | // 修改 当 是一个 函数的时候 直接调用 502 | slots[key] = (props) => normalizeSlotValue(value(props)); 503 | } 504 | } 505 | function normalizeSlotValue(value) { 506 | return isArray(value) ? value : [value]; 507 | } 508 | 509 | /* 510 | * @Author: Stone 511 | * @Date: 2022-04-24 19:41:18 512 | * @LastEditors: Stone 513 | * @LastEditTime: 2022-04-28 11:00:06 514 | */ 515 | function createComponentInstance(vnode, parent) { 516 | const component = { 517 | vnode, 518 | type: vnode.type, 519 | next: null, 520 | props: {}, 521 | slots: {}, 522 | setupState: {}, 523 | provides: parent ? parent.provides : {}, 524 | parent, 525 | isMount: false, 526 | subTree: {}, 527 | emit: () => { } 528 | }; 529 | // bind 的第一个参数 如果是 undefined 或者 null 那么 this 就是指向 windows 530 | // 这样做的目的是 实现了 emit 的第一个参数 为 component 实例 这是预置入 531 | component.emit = emit.bind(null, component); 532 | return component; 533 | } 534 | function setupComponent(instance) { 535 | initSlots(instance, instance.vnode.children); 536 | initProps(instance, instance.vnode.props); 537 | // console.log(instance); 538 | // 初始化一个有状态的 component 539 | // 有状态的组件 和 函数组件 540 | setupStatefulComponent(instance); 541 | } 542 | function setupStatefulComponent(instance) { 543 | // 调用 setup 然后 拿到返回值 544 | // type 就是 app 对象 545 | const Component = instance.type; 546 | // ctx 547 | instance.proxy = new Proxy({ 548 | _: instance 549 | }, PublicInstanceProxyHandlers); 550 | // 解构 setup 551 | const { setup } = Component; 552 | if (setup) { 553 | setCurrentInstance(instance); 554 | // 返回一个 function 或者是 Object 555 | // 如果是 function 则认为是 render 函数 556 | // 如果是 Object 则注入到当前组件的上下文中 557 | const setupResult = setup(shallowReadonly(instance.proxy), { emit: instance.emit }); 558 | setCurrentInstance(null); 559 | handleSetupResult(instance, setupResult); 560 | } 561 | } 562 | function handleSetupResult(instance, setupResult) { 563 | // TODO function 564 | if (isObject(setupResult)) { 565 | instance.setupState = proxyRefs(setupResult); 566 | } 567 | finishComponentSetup(instance); 568 | } 569 | function finishComponentSetup(instance) { 570 | const Component = instance.type; 571 | // template => render 函数 572 | // 我们之前是直接调用 render 函数,但是用户不会传入 render 函数,只会传入 template 573 | // 所以我们需要调用 compile,但是又不能直接再 runtime-core 里面调用 574 | // 因为这样会形成强依赖关系 Vue3 支持单个包拆分使用 包之间不能直接引入模块的东西 575 | // Vue 可以只存在运行时,就不需要 compiler-core 576 | // 使用 webpack 或者 rollup 打包工具的时候,在运行前先把 template 编译成 render 函数 577 | // 线上运行的时候就可以直接跑这个 runtime-core 就行了,这样包就更小 578 | // Vue 给出的解决方案就是,先导入到 Vue 里面,然后再使用。这样就没有了强依赖关系 579 | if (compiler && !Component.render) { 580 | // 如果 compiler 存在并且 用户 没有传入 render 函数,如果用户传入的 render 函数,那么它的优先级会更高 581 | if (Component.template) { 582 | Component.render = compiler(Component.template); 583 | } 584 | } 585 | instance.render = Component.render; 586 | } 587 | let currentInstance = null; 588 | function getCurrentInstance() { 589 | // 需要返回实例 590 | return currentInstance; 591 | } 592 | // 赋值时 封装函数的好处 593 | // 我们可以清晰的知道 谁调用了 方便调试 594 | function setCurrentInstance(instance) { 595 | currentInstance = instance; 596 | } 597 | let compiler; 598 | function registerRuntimerCompiler(_compiler) { 599 | compiler = _compiler; 600 | } 601 | 602 | // provide-inject 提供了组件之间跨层级传递数据 父子、祖孙 等 603 | function provide(key, value) { 604 | // 存储 605 | // 想一下,数据应该存在哪里? 606 | // 如果是存在 最外层的 component 中,里面组件都可以访问到了 607 | // 接着就要获取组件实例 使用 getCurrentInstance,所以 provide 只能在 setup 中使用 608 | const currentInstance = getCurrentInstance(); 609 | if (currentInstance) { 610 | let { provides } = currentInstance; 611 | const parentProvides = currentInstance.parent.provides; 612 | // 如果当前组件的 provides 等于 父级组件的 provides 613 | // 是要 通过 原型链 的方式 去查找 614 | // Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__ 615 | // 这里要解决一个问题 616 | // 当父级 key 和 爷爷级别的 key 重复的时候,对于子组件来讲,需要取最近的父级别组件的值 617 | // 那这里的解决方案就是利用原型链来解决 618 | // provides 初始化的时候是在 createComponent 时处理的,当时是直接把 parent.provides 赋值给组件的 provides 的 619 | // 所以,如果说这里发现 provides 和 parentProvides 相等的话,那么就说明是第一次做 provide(对于当前组件来讲) 620 | // 我们就可以把 parent.provides 作为 currentInstance.provides 的原型重新赋值 621 | // 至于为什么不在 createComponent 的时候做这个处理,可能的好处是在这里初始化的话,是有个懒执行的效果(优化点,只有需要的时候在初始化) 622 | // 首先咱们要知道 初始化 的时候 子组件 的 provides 就是父组件的 provides 623 | // currentInstance.parent.provides 是 爷爷组件 624 | // 当两个 key 值相同的时候要取 最近的 父组件的 625 | if (provides === parentProvides) { 626 | provides = currentInstance.provides = Object.create(parentProvides); 627 | } 628 | provides[key] = value; 629 | } 630 | } 631 | function inject(key, defaultValue) { 632 | // 取出 633 | // 从哪里取?若是 祖 -> 孙,要获取哪里的?? 634 | const currentInstance = getCurrentInstance(); 635 | if (currentInstance) { 636 | const parentProvides = currentInstance.parent.provides; 637 | if (key in parentProvides) { 638 | return parentProvides[key]; 639 | } 640 | else if (defaultValue) { 641 | if (typeof defaultValue === 'function') { 642 | return defaultValue(); 643 | } 644 | return defaultValue; 645 | } 646 | } 647 | return currentInstance.provides[key]; 648 | } 649 | 650 | /* 651 | * @Author: Stone 652 | * @Date: 2022-04-24 19:41:18 653 | * @LastEditors: Stone 654 | * @LastEditTime: 2022-04-28 11:14:33 655 | */ 656 | const Fragment = Symbol('Fragment'); 657 | const Text = Symbol('Text'); 658 | function createVNode(type, props, children) { 659 | const vnode = { 660 | type, 661 | props, 662 | children, 663 | component: null, 664 | key: props && props.key, 665 | shapeFlag: getShapeFlag(type), 666 | el: null 667 | }; 668 | // children 669 | if (isString(children)) { 670 | // vnode.shapeFlag = vnode.shapeFlag | ShapeFlags.TEXT_CHILDREN 671 | // | 两位都为 0 才为 0 672 | // 0100 | 0100 = 0100 673 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */; 674 | } 675 | else if (isArray(children)) { 676 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */; 677 | } 678 | // 组件类型 + children 是 object 就有 slot 679 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 680 | if (isObject(children)) { 681 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */; 682 | } 683 | } 684 | return vnode; 685 | } 686 | function getShapeFlag(type) { 687 | // string -> div -> element 688 | return isString(type) ? 1 /* ELEMENT */ : 2 /* STATEFUL_COMPONENT */; 689 | } 690 | function createTextVNode(text) { 691 | return createVNode(Text, {}, text); 692 | } 693 | 694 | function h(type, props, children) { 695 | return createVNode(type, props, children); 696 | } 697 | 698 | function renderSlots(slots, name, props) { 699 | const slot = slots[name]; 700 | if (slot) { 701 | if (isFunction(slot)) { 702 | // 我们为了渲染 插槽中的 元素 主动在外层添加了一个 div -> component 703 | // 修改 直接变成 element -> mountChildren 704 | // Symbol 常量 Fragment 705 | return createVNode(Fragment, {}, slot(props)); 706 | } 707 | } 708 | } 709 | 710 | // 因为 render 函数被包裹了 所以 调用 createApp 的时候传入 render 711 | // 为了让用户又能直接使用 createApp 所以 前往 renderer 导出一个 createApp 712 | const createAppAPI = (render) => { 713 | return function createApp(rootComponent) { 714 | return { 715 | mount(rootContainer) { 716 | // 转换成 vdom 717 | // component -> vnode 718 | // 所有的逻辑操作 都会基于 vnode 做处理 719 | const vnode = createVNode(rootComponent); 720 | // !! bug render 是将虚拟 dom 渲染到 rootComponent 中 721 | render(vnode, rootContainer); 722 | } 723 | }; 724 | }; 725 | }; 726 | 727 | function shouldUpdateComponent(prevVNode, nextVNode) { 728 | // 只有 props 发生了改变才需要更新 729 | const { props: prevProps } = prevVNode; 730 | const { props: nextProps } = nextVNode; 731 | for (const key in nextProps) { 732 | if (nextProps[key] != prevProps[key]) { 733 | return true; 734 | } 735 | } 736 | return false; 737 | } 738 | 739 | const queue = []; 740 | // 通过一个策略 只生成一个 promise 741 | let isFlushPending = false; 742 | const p = Promise.resolve(); 743 | // nextTick 执行的时间 就是把 fn 推到微任务 744 | function nextTick(fn) { 745 | // 传了就执行 没传就 等待到微任务执行的时候 746 | return fn ? p.then(fn) : p; 747 | } 748 | function queueJobs(job) { 749 | if (!queue.includes(job)) { 750 | queue.push(job); 751 | } 752 | queueFlush(); 753 | } 754 | function queueFlush() { 755 | if (isFlushPending) 756 | return; 757 | isFlushPending = true; 758 | // 然后就是就是生成一个 微任务 759 | // 如何生成微任务? 760 | // p.then(() => { 761 | // isFlushPending = false 762 | // let job 763 | // while (job = queue.shift()) { 764 | // job & job() 765 | // } 766 | // }) 767 | nextTick(flushJob); 768 | } 769 | function flushJob() { 770 | isFlushPending = false; 771 | let job; 772 | while (job = queue.shift()) { 773 | job & job(); 774 | } 775 | } 776 | 777 | // 使用闭包 createRenderer 函数 包裹所有的函数 778 | function createRenderer(options) { 779 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText } = options; 780 | function render(vnode, container) { 781 | // 只需要调用 patch 方法 782 | // 方便后续的递归处理 783 | patch(null, vnode, container, null, null); 784 | } 785 | function patch(n1, n2, container, parentComponent, anchor) { 786 | // TODO 去处理组件 787 | // 判断什么类型 788 | // 是 element 那么就应该去处理 element 789 | // 如何区分是 element 还是 component 类型??? 790 | // console.log(vnode.type); 791 | // object 是 component 792 | // div 是 element 793 | // debugger 794 | const { type, shapeFlag } = n2; 795 | // 根据 type 来渲染 796 | // console.log(type); 797 | // Object 798 | // div/p -> String 799 | // Fragment 800 | // Text 801 | switch (type) { 802 | case Fragment: 803 | processFragment(n1, n2, container, parentComponent, anchor); 804 | break; 805 | case Text: 806 | processText(n1, n2, container); 807 | break; 808 | default: 809 | // 0001 & 0001 -> 0001 810 | if (shapeFlag & 1 /* ELEMENT */) { 811 | processElement(n1, n2, container, parentComponent, anchor); 812 | } 813 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) { 814 | processComponent(n1, n2, container, parentComponent, anchor); 815 | } 816 | break; 817 | } 818 | } 819 | // 首先因为每次修改 响应式都会处理 element 820 | // 在 processElement 的时候就会判断 821 | // 如果是传入的 n1 存在 那就是新建 否则是更新 822 | // 更新 patchElement 又得进行两个节点的对比 823 | function processElement(n1, n2, container, parentComponent, anchor) { 824 | if (!n1) { 825 | // 初始化 826 | mountElement(n2, container, parentComponent, anchor); 827 | } 828 | else { 829 | patchElement(n1, n2, container, parentComponent, anchor); 830 | } 831 | } 832 | function patchElement(n1, n2, container, parentComponent, anchor) { 833 | console.log("n1", n1); 834 | console.log("n2", n2); 835 | // 新老节点 836 | const oldProps = n1.props || {}; 837 | const newProps = n2.props || {}; 838 | // n1 是老的虚拟节点 上有 el 在 mountElement 有赋值 839 | // 同时 要赋值 到 n2 上面 因为 mountElement 只有初始 840 | const el = (n2.el = n1.el); 841 | // 处理 842 | patchChildren(n1, n2, el, parentComponent, anchor); 843 | patchProps(el, oldProps, newProps); 844 | } 845 | function patchChildren(n1, n2, container, parentComponent, anchor) { 846 | // 常见有四种情况 847 | // array => text 848 | // text => array 849 | // text => text 850 | // array => array 851 | // 如何知道类型呢? 通过 shapeFlag 852 | const prevShapeFlag = n1.shapeFlag; 853 | const c1 = n1.children; 854 | const { shapeFlag } = n2; 855 | const c2 = n2.children; 856 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 857 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) { 858 | // 1、要卸载原来的组件 859 | unmountChildren(n1.children); 860 | // 2、将 text 挂载上去 861 | } 862 | if (c1 !== c2) { 863 | hostSetElementText(container, c2); 864 | } 865 | } 866 | else { 867 | // 现在是 array 的情况 之前是 text 868 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) { 869 | // 1、原先的 text 清空 870 | hostSetElementText(container, ''); 871 | // 2、挂载现在的 array 872 | mountChildren(c2, container, parentComponent, anchor); 873 | } 874 | else { 875 | // 都是数组的情况就需要 876 | patchKeyedChildren(c1, c2, container, parentComponent, anchor); 877 | } 878 | } 879 | } 880 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) { 881 | const len2 = c2.length; 882 | // 需要定义三个指针 883 | let i = 0; // 从新的节点开始 884 | let e1 = c1.length - 1; // 老的最后一个 索引值 885 | let e2 = len2 - 1; // 新的最后一个 索引值 886 | function isSomeVNodeType(n1, n2) { 887 | // 对比节点是否相等 可以通过 type 和 key 888 | return n1.type === n2.type && n1.key === n2.key; 889 | } 890 | debugger; 891 | // 左侧对比 移动 i 指针 892 | while (i <= e1 && i <= e2) { 893 | const n1 = c1[i]; 894 | const n2 = c2[i]; 895 | if (isSomeVNodeType(n1, n2)) { 896 | patch(n1, n2, container, parentComponent, parentAnchor); 897 | } 898 | else { 899 | break; 900 | } 901 | i++; 902 | } 903 | // 右侧对比 移动 e1 和 e2 指针 904 | while (i <= e1 && i <= e2) { 905 | const n1 = c1[e1]; 906 | const n2 = c2[e2]; 907 | if (isSomeVNodeType(n1, n2)) { 908 | patch(n1, n2, container, parentComponent, parentAnchor); 909 | } 910 | else { 911 | break; 912 | } 913 | e1--; 914 | e2--; 915 | } 916 | // 对比完两侧后 就要处理以下几种情况 917 | // 新的比老的多 创建 918 | if (i > e1) { 919 | if (i <= e2) { 920 | // 左侧 可以直接加在末尾 921 | // 右侧的话 我们就需要引入一个 概念 锚点 的概念 922 | // 通过 anchor 锚点 我们将新建的元素插入的指定的位置 923 | const nextPos = e2 + 1; 924 | // 如果 e2 + 1 大于 c2 的 length 那就是最后一个 否则就是最先的元素 925 | // 锚点是一个 元素 926 | const anchor = nextPos < len2 ? c2[nextPos].el : null; 927 | while (i <= e2) { 928 | patch(null, c2[i], container, parentComponent, anchor); 929 | i++; 930 | } 931 | } 932 | } 933 | else if (i > e2) { 934 | // 老的比新的多 删除 935 | // e1 就是 老的 最后一个 936 | while (i <= e1) { 937 | hostRemove(c1[i].el); 938 | i++; 939 | } 940 | } 941 | else { 942 | // 乱序部分 943 | // 遍历老节点 然后检查在新的里面是否存在 944 | // 方案一 同时遍历新的 时间复杂度 O(n*n) 945 | // 方案二 新的节点建立一个映射表 时间复杂度 O(1) 只要根据 key 去查是否存在 946 | // 为了性能最优 选则方案二 947 | let s1 = i; // i 是停止的位置 差异开始的地方 948 | let s2 = i; 949 | // 如果新的节点少于老的节点,当遍历完新的之后,就不需要再遍历了 950 | // 通过一个总数和一个遍历次数 来优化 951 | // 要遍历的数量 952 | const toBePatched = e2 - s2 + 1; 953 | // 已经遍历的数量 954 | let patched = 0; 955 | // 拆分问题 => 获取最长递增子序列 956 | // abcdefg -> 老 957 | // adecdfg -> 新 958 | // 1.确定新老节点之间的关系 新的元素在老的节点中的索引 e:4,c:2,d:3 959 | // newIndexToOldIndexMap 的初始值是一个定值数组,初始项都是 0,newIndexToOldIndexMap = [0,0,0] => [5,3,4] 加了1 因为 0 是有意义的。 960 | // 递增的索引值就是 [1,2] 961 | // 2.最长的递增子序列 [1,2] 对比 ecd 这个变动的序列 962 | // 利用两个指针 i 和 j 963 | // i 去遍历新的索引值 ecd [0,1,2] j 去遍历 [1,2] 964 | // 如果 i!=j 那么就是需要移动 965 | // 新建一个定长数组(需要变动的长度) 性能是最好的 来确定新老之间索引关系 我们要查到最长递增的子序列 也就是索引值 966 | const newIndexToOldIndexMap = new Array(toBePatched); 967 | // 确定是否需要移动 只要后一个索引值小于前一个 就需要移动 968 | let moved = false; 969 | let maxNewIndexSoFar = 0; 970 | // 赋值 971 | for (let i = 0; i < toBePatched; i++) { 972 | newIndexToOldIndexMap[i] = 0; 973 | } 974 | // 建立新节点的映射表 975 | const keyToNewIndexMap = new Map(); 976 | // 循环 e2 977 | for (let i = s2; i <= e2; i++) { 978 | const nextChild = c2[i]; 979 | keyToNewIndexMap.set(nextChild.key, i); 980 | } 981 | // 循环 e1 982 | for (let i = s1; i <= e1; i++) { 983 | const prevChild = c1[i]; 984 | if (patched >= toBePatched) { 985 | hostRemove(prevChild.el); 986 | continue; 987 | } 988 | let newIndex; 989 | if (prevChild.key !== null) { 990 | // 用户输入 key 991 | newIndex = keyToNewIndexMap.get(prevChild.key); 992 | } 993 | else { 994 | // 用户没有输入 key 995 | for (let j = s2; j < e2; j++) { 996 | if (isSomeVNodeType(prevChild, c2[j])) { 997 | newIndex = j; 998 | break; 999 | } 1000 | } 1001 | } 1002 | if (newIndex === undefined) { 1003 | hostRemove(prevChild.el); 1004 | } 1005 | else { 1006 | if (newIndex >= maxNewIndexSoFar) { 1007 | maxNewIndexSoFar = newIndex; 1008 | } 1009 | else { 1010 | moved = true; 1011 | } 1012 | // 实际上是等于 i 就可以 因为 0 表示不存在 所以 定义成 i + 1 1013 | newIndexToOldIndexMap[newIndex - s2] = i + 1; 1014 | // 存在就再次深度对比 1015 | patch(prevChild, c2[newIndex], container, parentComponent, null); 1016 | // patch 完就证明已经遍历完一个新的节点 1017 | patched++; 1018 | } 1019 | } 1020 | // 获取最长递增子序列 1021 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; 1022 | let j = increasingNewIndexSequence.length - 1; 1023 | // 倒序的好处就是 能够确定稳定的位置 1024 | // ecdf 1025 | // cdef 1026 | // 如果是从 f 开始就能确定 e 的位置 1027 | // 从最后开始就能依次确定位置 1028 | for (let i = toBePatched; i >= 0; i--) { 1029 | const nextIndex = i + s2; 1030 | const nextChild = c2[nextIndex]; 1031 | const anchor = nextIndex + 1 < len2 ? c2[nextIndex + 1].el : null; 1032 | if (newIndexToOldIndexMap[i] === 0) { 1033 | patch(null, nextChild, container, parentComponent, anchor); 1034 | } 1035 | else if (moved) { 1036 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 1037 | // 移动位置 调用 insert 1038 | hostInsert(nextChild.el, container, anchor); 1039 | } 1040 | else { 1041 | j++; 1042 | } 1043 | } 1044 | } 1045 | } 1046 | } 1047 | function unmountChildren(children) { 1048 | for (let i = 0; i < children.length; i++) { 1049 | hostRemove(children[i].el); 1050 | } 1051 | } 1052 | function patchProps(el, oldProps, newProps) { 1053 | // 常见的有三种情况 1054 | // 值改变了 => 删除 1055 | // 值变成了 null 或 undefined => 删除 1056 | // 增加了 => 增加 1057 | if (oldProps !== newProps) { 1058 | for (const key in newProps) { 1059 | const prevProp = oldProps[key]; 1060 | const nextProp = newProps[key]; 1061 | if (prevProp !== nextProp) { 1062 | hostPatchProp(el, key, prevProp, nextProp); 1063 | } 1064 | } 1065 | } 1066 | // 处理值 变成 null 或 undefined 的情况 1067 | // 新的就不会有 所以遍历老的 oldProps 看是否存在于新的里面 1068 | if (oldProps !== {}) { 1069 | for (const key in oldProps) { 1070 | if (!(key in newProps)) { 1071 | hostPatchProp(el, key, oldProps[key], null); 1072 | } 1073 | } 1074 | } 1075 | } 1076 | function processComponent(n1, n2, container, parentComponent, anchor) { 1077 | if (!n1) { 1078 | // 挂载组件 1079 | mountComponent(n2, container, parentComponent, anchor); 1080 | } 1081 | else { 1082 | // 更新组件 1083 | updateComponent(n1, n2); 1084 | } 1085 | } 1086 | function updateComponent(n1, n2) { 1087 | // 更新实际上只需要想办法 调用 render 函数 然后再 patch 去更新 1088 | // instance 从哪里来呢? 在挂载阶段 我们会生成 instance 然后挂载到 虚拟dom 上 1089 | // n2 没有 所以要赋值 1090 | const instance = n2.component = n1.component; 1091 | // 只有但子组件的 props 发生了改变才需要更新 1092 | if (shouldUpdateComponent(n1, n2)) { 1093 | // 然后再把 n2 设置为下次需要更新的 虚拟 dom 1094 | instance.next = n2; 1095 | instance.update(); 1096 | } 1097 | else { 1098 | n2.el = n1.el; 1099 | n2.vnode = n2; 1100 | } 1101 | } 1102 | function mountComponent(initialVNode, container, parentComponent, anchor) { 1103 | // 创建组件实例 1104 | // 这个实例上面有很多属性 1105 | const instance = initialVNode.component = createComponentInstance(initialVNode, parentComponent); 1106 | // 初始化 1107 | setupComponent(instance); 1108 | // 调用 render 函数 1109 | setupRenderEffect(instance, initialVNode, container, anchor); 1110 | } 1111 | function mountElement(vnode, container, parentComponent, anchor) { 1112 | // const el = document.createElement("div") 1113 | // string 或 array 1114 | // el.textContent = "hi , mini-vue" 1115 | // el.setAttribute("id", "root") 1116 | // document.body.append(el) 1117 | // 这里的 vnode -> element -> div 1118 | // 自定义渲染器 1119 | // 修改一 hostCreateElement 1120 | // canvas 是 new Element() 1121 | // const el = vnode.el = document.createElement(vnode.type) 1122 | const el = vnode.el = hostCreateElement(vnode.type); 1123 | const { children, shapeFlag } = vnode; 1124 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 1125 | el.textContent = children; 1126 | } 1127 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 1128 | mountChildren(children, el, parentComponent, anchor); 1129 | } 1130 | // 修改二 hostPatchProp 1131 | // props 1132 | const { props } = vnode; 1133 | for (const key in props) { 1134 | const val = props[key]; 1135 | // onClick 、 onMouseenter 等等这些的共同特征 1136 | // 以 on 开头 + 一个大写字母 1137 | // if (isOn(key)) { 1138 | // const event = key.slice(2).toLowerCase() 1139 | // el.addEventListener(event, val); 1140 | // } else { 1141 | // el.setAttribute(key, val) 1142 | // } 1143 | hostPatchProp(el, key, null, val); 1144 | } 1145 | // 修改三 canvas 添加元素 1146 | // el.x = 10 1147 | // container.append(el) 1148 | // canvas 中添加元素是 addChild() 1149 | // container.append(el) 1150 | hostInsert(el, container, anchor); 1151 | } 1152 | function mountChildren(children, container, parentComponent, anchor) { 1153 | children.forEach((v) => { 1154 | patch(null, v, container, parentComponent, anchor); 1155 | }); 1156 | } 1157 | function setupRenderEffect(instance, initialVNode, container, anchor) { 1158 | // 将 effect 放在 instance 实例身上 1159 | instance.update = effect(() => { 1160 | if (!instance.isMount) { 1161 | console.log('init'); 1162 | const { proxy } = instance; 1163 | // 虚拟节点树 1164 | // 一开始是创建在 instance 上 1165 | // 在这里就绑定 this 1166 | const subTree = instance.subTree = instance.render.call(proxy, proxy); 1167 | // vnode -> patch 1168 | // vnode -> element -> mountElement 1169 | patch(null, subTree, container, instance, null); 1170 | // 所有的 element -> mount 1171 | initialVNode.el = subTree.el; 1172 | instance.isMount = true; 1173 | } 1174 | else { 1175 | console.log('update'); 1176 | // next 是下一个 要更新的 vnode 是老的 1177 | const { next, vnode } = instance; 1178 | if (next) { 1179 | next.el = vnode.el; 1180 | updateComponentPreRender(instance, next); 1181 | } 1182 | const { proxy } = instance; 1183 | // 当前的虚拟节点树 1184 | const subTree = instance.render.call(proxy, proxy); 1185 | // 老的虚拟节点树 1186 | const prevSubTree = instance.subTree; 1187 | instance.subTree = subTree; 1188 | patch(prevSubTree, subTree, container, instance, anchor); 1189 | } 1190 | }, { 1191 | scheduler() { 1192 | queueJobs(instance.update); 1193 | } 1194 | }); 1195 | } 1196 | function processFragment(n1, n2, container, parentComponent, anchor) { 1197 | // 此时,拿出 vnode 中的 children 1198 | mountChildren(n2.children, container, parentComponent, anchor); 1199 | } 1200 | function processText(n1, n2, container) { 1201 | // console.log(vnode); 1202 | // 文本内容 在 children 中 1203 | const { children } = n2; 1204 | // 创建文本节点 1205 | const textNode = n2.el = document.createTextNode(children); 1206 | // 挂载到容器中 1207 | container.append(textNode); 1208 | } 1209 | // 为了让用户又能直接使用 createApp 所以导出一个 createApp 1210 | return { 1211 | createApp: createAppAPI(render) 1212 | }; 1213 | } 1214 | function updateComponentPreRender(instance, nextVNode) { 1215 | instance.vnode = nextVNode; 1216 | instance.next = null; 1217 | // 然后就是更新 props 1218 | // 这里只是简单的赋值 1219 | instance.props = nextVNode.props; 1220 | } 1221 | function getSequence(arr) { 1222 | const p = arr.slice(); 1223 | const result = [0]; 1224 | let i, j, u, v, c; 1225 | const len = arr.length; 1226 | for (i = 0; i < len; i++) { 1227 | const arrI = arr[i]; 1228 | if (arrI !== 0) { 1229 | j = result[result.length - 1]; 1230 | if (arr[j] < arrI) { 1231 | p[i] = j; 1232 | result.push(i); 1233 | continue; 1234 | } 1235 | u = 0; 1236 | v = result.length - 1; 1237 | while (u < v) { 1238 | c = (u + v) >> 1; 1239 | if (arr[result[c]] < arrI) { 1240 | u = c + 1; 1241 | } 1242 | else { 1243 | v = c; 1244 | } 1245 | } 1246 | if (arrI < arr[result[u]]) { 1247 | if (u > 0) { 1248 | p[i] = result[u - 1]; 1249 | } 1250 | result[u] = i; 1251 | } 1252 | } 1253 | } 1254 | u = result.length; 1255 | v = result[u - 1]; 1256 | while (u-- > 0) { 1257 | result[u] = v; 1258 | v = p[v]; 1259 | } 1260 | return result; 1261 | } 1262 | 1263 | /* 1264 | * @Author: Stone 1265 | * @Date: 2022-04-24 19:41:18 1266 | * @LastEditors: Stone 1267 | * @LastEditTime: 2022-04-28 11:15:15 1268 | */ 1269 | 1270 | var runtimerDOM = /*#__PURE__*/Object.freeze({ 1271 | __proto__: null, 1272 | getCurrentInstance: getCurrentInstance, 1273 | registerRuntimerCompiler: registerRuntimerCompiler, 1274 | toDisplayString: toDisplayString, 1275 | inject: inject, 1276 | provide: provide, 1277 | h: h, 1278 | renderSlots: renderSlots, 1279 | createRenderer: createRenderer, 1280 | nextTick: nextTick, 1281 | createElementVNode: createVNode, 1282 | createTextVNode: createTextVNode 1283 | }); 1284 | 1285 | function createElement(type) { 1286 | return document.createElement(type); 1287 | } 1288 | function patchProp(el, key, prevVal, nextVal) { 1289 | if (isOn(key)) { 1290 | const event = key.slice(2).toLowerCase(); 1291 | el.addEventListener(event, nextVal); 1292 | } 1293 | else { 1294 | if (nextVal === undefined || nextVal === null) { 1295 | el.removeAttribute(key); 1296 | } 1297 | else { 1298 | el.setAttribute(key, nextVal); 1299 | } 1300 | } 1301 | } 1302 | function insert(child, parent, anchor) { 1303 | // insertBefore 是把指定的元素添加到指定的位置 1304 | // 如果没有传入 anchor 那就相当于 append(child) 1305 | parent.insertBefore(child, anchor || null); 1306 | } 1307 | function remove(child) { 1308 | // 拿到父级节点 然后删除子节点 1309 | // 调用原生 dom 删除节点 1310 | const parent = child.parentNode; 1311 | if (parent) { 1312 | parent.removeChild(child); 1313 | } 1314 | } 1315 | function setElementText(el, text) { 1316 | el.textContent = text; 1317 | } 1318 | // 调用 renderer.ts 中的 createRenderer 1319 | const renderer = createRenderer({ 1320 | createElement, 1321 | patchProp, 1322 | insert, 1323 | remove, 1324 | setElementText 1325 | }); 1326 | // 这样用户就可以正常的使用 createApp 了 1327 | function createApp(...args) { 1328 | return renderer.createApp(...args); 1329 | } 1330 | 1331 | /* 1332 | * @Author: Stone 1333 | * @Date: 2022-04-27 14:24:20 1334 | * @LastEditors: Stone 1335 | * @LastEditTime: 2022-04-27 18:36:18 1336 | */ 1337 | const TO_DISPLAY_STRING = Symbol('toDisplayString'); 1338 | const CREATE_ELEMENT_VNODE = Symbol('createElementVNode'); 1339 | // Symbol 定义的变量不可以遍历 所以转一下 1340 | const helperMapName = { 1341 | [TO_DISPLAY_STRING]: 'toDisplayString', 1342 | [CREATE_ELEMENT_VNODE]: 'createElementVNode' 1343 | }; 1344 | 1345 | /* 1346 | * @Author: Stone 1347 | * @Date: 2022-04-26 14:57:37 1348 | * @LastEditors: Stone 1349 | * @LastEditTime: 2022-04-28 10:14:25 1350 | */ 1351 | function generate(ast) { 1352 | // 实现功能的步骤 1353 | // 1、先知道要达到的效果 1354 | // 2、任务拆分实现 1355 | // 3、优化提取代码 1356 | const context = createCodegenContext(); 1357 | const { push } = context; 1358 | genFunctionPreamble(ast, context); 1359 | const functionName = "render"; 1360 | const args = ["_ctx", "_cache"]; 1361 | const signature = args.join(", "); 1362 | push(`function ${functionName}(${signature}){`); 1363 | push("return "); 1364 | genNode(ast.codegenNode, context); 1365 | push("}"); 1366 | return { 1367 | code: context.code 1368 | }; 1369 | } 1370 | function createCodegenContext() { 1371 | const context = { 1372 | code: "", 1373 | push(source) { 1374 | context.code += source; 1375 | }, 1376 | helper(key) { 1377 | return `_${helperMapName[key]}`; 1378 | } 1379 | }; 1380 | return context; 1381 | } 1382 | function genNode(node, context) { 1383 | // 这里之前只处理 text 之后还需要处理别的类型 使用一个 switch 1384 | switch (node.type) { 1385 | case 3 /* TEXT */: 1386 | genText(node, context); 1387 | break; 1388 | case 0 /* INTERPOLATION */: 1389 | genInterpolation(node, context); 1390 | break; 1391 | case 1 /* SIMPLE_EXPRESSION */: 1392 | genExpression(node, context); 1393 | break; 1394 | case 2 /* ELEMENT */: 1395 | genElement(node, context); 1396 | break; 1397 | case 5 /* COMPOUND_EXPRESSION */: 1398 | genCompoundExpression(node, context); 1399 | break; 1400 | } 1401 | // const { push } = context 1402 | // push(`'${node.content}'`) 1403 | } 1404 | function genFunctionPreamble(ast, context) { 1405 | const { push } = context; 1406 | const VueBinging = "Vue"; 1407 | // const helpers = ["toDisplayString"] // 帮助函数 后期需要实现 修改写在一个 helper 里面 1408 | const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}`; // 别名 带下划线 1409 | if (ast.helpers.length > 0) { 1410 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")}} = ${VueBinging}`); 1411 | } 1412 | push("\n"); 1413 | push("return "); 1414 | } 1415 | function genText(node, context) { 1416 | const { push } = context; 1417 | push(`'${node.content}'`); 1418 | } 1419 | function genInterpolation(node, context) { 1420 | const { push, helper } = context; 1421 | push(`${helper(TO_DISPLAY_STRING)}(`); 1422 | genNode(node.content, context); 1423 | push(")"); 1424 | } 1425 | function genExpression(node, context) { 1426 | const { push } = context; 1427 | push(`${node.content}`); 1428 | } 1429 | function genElement(node, context) { 1430 | const { push, helper } = context; 1431 | const { tag, children, props } = node; 1432 | // console.log('children', children) 1433 | // [ { type: 3, content: 'h1,' }, 1434 | // { type: 0, content: { type: 1, content: 'message' } } 1435 | // ] 1436 | // push(`${helper(CREATE_ELEMENT_VNODE)}("${tag}"), null, "hi," + _toDisplayString(_ctx.message)`) 1437 | // element 里面的 children 一个一个拼接 循环遍历 1438 | // const child = children[0] 1439 | push(`${helper(CREATE_ELEMENT_VNODE)}(`); 1440 | // for (let i = 0; i < children.length; i++) { 1441 | // const child = children[i]; 1442 | // genNode(child, context) 1443 | // } 1444 | genNodeList(genNullable([tag, props, children]), context); 1445 | // genNode(children, context) 1446 | push(")"); 1447 | } 1448 | function genNodeList(nodes, context) { 1449 | const { push } = context; 1450 | for (let i = 0; i < nodes.length; i++) { 1451 | const node = nodes[i]; 1452 | if (isString(node)) { 1453 | push(node); 1454 | } 1455 | else { 1456 | genNode(node, context); 1457 | } 1458 | if (i < nodes.length - 1) { 1459 | push(", "); 1460 | } 1461 | } 1462 | } 1463 | function genNullable(args) { 1464 | return args.map((arg) => arg || "null"); 1465 | } 1466 | function genCompoundExpression(node, context) { 1467 | const { push } = context; 1468 | const { children } = node; 1469 | for (let i = 0; i < children.length; i++) { 1470 | const child = children[i]; 1471 | if (isString(child)) { 1472 | push(child); 1473 | } 1474 | else { 1475 | genNode(child, context); 1476 | } 1477 | } 1478 | } 1479 | 1480 | function baseParse(content) { 1481 | const context = createParserContent(content); 1482 | return createRoot(parseChildren(context, [])); 1483 | } 1484 | function parseChildren(context, ancestors) { 1485 | const nodes = []; 1486 | while (!isEnd(context, ancestors)) { 1487 | let node; 1488 | const s = context.source; 1489 | if (s.startsWith('{{')) { 1490 | node = parseInterpolation(context); 1491 | } 1492 | else if (s[0] === "<") { 1493 | // 需要用正则表达判断 1494 | //
1495 | // /^<[a-z]/i/ 1496 | if (/[a-z]/i.test(s[1])) { 1497 | node = parseElement(context, ancestors); 1498 | } 1499 | } 1500 | if (!node) { 1501 | node = parseText(context); 1502 | } 1503 | nodes.push(node); 1504 | } 1505 | return nodes; 1506 | } 1507 | function isEnd(context, ancestors) { 1508 | // 1、source 有值的时候 1509 | // 2、当遇到结束标签的时候 1510 | const s = context.source; 1511 | if (s.startsWith('= 0; i--) { 1513 | const tag = ancestors[i].tag; 1514 | if (startsWithEndTagOpen(s, tag)) { 1515 | return true; 1516 | } 1517 | } 1518 | } 1519 | return !s; 1520 | } 1521 | function startsWithEndTagOpen(source, tag) { 1522 | // 以左括号开头才有意义 并且 还需要转换为小写比较 1523 | return (source.startsWith(" index) { 1532 | endIndex = index; 1533 | } 1534 | } 1535 | // 解析文本 之前是 从头截取到尾部 但真是的环境是文本后面会有其它类型的 element 所以要指明停止的位置 1536 | const content = parseTextData(context, endIndex); 1537 | console.log('content -------', content); 1538 | return { 1539 | type: 3 /* TEXT */, 1540 | content 1541 | }; 1542 | } 1543 | function parseTextData(context, length) { 1544 | const content = context.source.slice(0, length); 1545 | advanceBy(context, length); 1546 | return content; 1547 | } 1548 | function parseElement(context, ancestors) { 1549 | // 解析标签 1550 | const element = parseTag(context, 0 /* Start */); 1551 | ancestors.push(element); 1552 | // 获取完标签后 需要把内部的 元素保存起来 需要用递归的方式去遍历内部的 element 1553 | element.children = parseChildren(context, ancestors); 1554 | ancestors.pop(); 1555 | // 这里要判断一下 开始标签和结束标签是否是一致的 不能直接消费完就 return 1556 | if (startsWithEndTagOpen(context.source, element.tag)) { 1557 | parseTag(context, 1 /* End */); 1558 | } 1559 | else { 1560 | throw new Error(`缺少结束标签:${element.tag}`); 1561 | } 1562 | return element; 1563 | } 1564 | function parseTag(context, type) { 1565 | //
1566 | // 匹配解析 1567 | // 推进 1568 | const match = /^<\/?([a-z]*)/i.exec(context.source); 1569 | const tag = match[1]; 1570 | // 获取完后要推进 1571 | advanceBy(context, match[0].length); 1572 | advanceBy(context, 1); 1573 | if (type === 1 /* End */) 1574 | return; 1575 | return { 1576 | type: 2 /* ELEMENT */, 1577 | tag 1578 | }; 1579 | } 1580 | function parseInterpolation(context) { 1581 | // {{message}} 1582 | // 拿出来定义的好处就是 如果需要更改 改动会很小 1583 | const openDelimiter = '{{'; 1584 | const closeDelimiter = '}}'; 1585 | // 我们要知道关闭的位置 1586 | // indexOf 表示 检索 }} 从 2 开始 1587 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length); 1588 | // 删除 前两个字符串 1589 | // context.source = context.source.slice(openDelimiter.length) 1590 | advanceBy(context, openDelimiter.length); 1591 | // 内容的长度就等于 closeIndex - openDelimiter 的长度 1592 | const rawContentLength = closeIndex - openDelimiter.length; 1593 | const rawContent = parseTextData(context, rawContentLength); 1594 | const content = rawContent.trim(); 1595 | // 然后还需要把这个字符串给删了 模板是一个字符串 要接着遍历后面的内容 1596 | // context.source = context.source.slice(rawContentLength + closeDelimiter.length); 1597 | advanceBy(context, closeDelimiter.length); 1598 | return { 1599 | type: 0 /* INTERPOLATION */, 1600 | content: { 1601 | type: 1 /* SIMPLE_EXPRESSION */, 1602 | content 1603 | } 1604 | }; 1605 | } 1606 | function advanceBy(context, length) { 1607 | context.source = context.source.slice(length); 1608 | } 1609 | function createRoot(children) { 1610 | return { children, type: 4 /* ROOT */ }; 1611 | } 1612 | function createParserContent(content) { 1613 | return { 1614 | source: content 1615 | }; 1616 | } 1617 | 1618 | /* 1619 | * @Author: Stone 1620 | * @Date: 2022-04-25 17:19:47 1621 | * @LastEditors: Stone 1622 | * @LastEditTime: 2022-04-28 10:02:39 1623 | */ 1624 | // options 提供了更动态的传参方式 1625 | function transform(root, options = {}) { 1626 | // 任务拆分 1627 | // 1 遍历 - 深度优先遍历 和 广度优先遍历 1628 | // 2 修改 test content 1629 | // 创建上下文本 1630 | const context = createTransformContext(root, options); 1631 | traverseNode(root, context); 1632 | createRootCodegen(root); 1633 | root.helpers = [...context.helpers.keys()]; 1634 | console.log('root.helpers', root.helpers); 1635 | } 1636 | function createTransformContext(root, options) { 1637 | const context = { 1638 | root, 1639 | nodeTransforms: options.nodeTransforms || [], 1640 | helpers: new Map(), 1641 | helper(key) { 1642 | context.helpers.set(key, 1); 1643 | } 1644 | }; 1645 | return context; 1646 | } 1647 | function traverseNode(node, context) { 1648 | const nodeTransforms = context.nodeTransforms; 1649 | const exitFns = []; 1650 | for (let i = 0; i < nodeTransforms.length; i++) { 1651 | // 调用插件 1652 | const transform = nodeTransforms[i]; 1653 | const onExit = transform(node, context); 1654 | if (onExit) 1655 | exitFns.push(onExit); 1656 | } 1657 | switch (node.type) { 1658 | case 0 /* INTERPOLATION */: 1659 | context.helper(TO_DISPLAY_STRING); 1660 | break; 1661 | case 4 /* ROOT */: 1662 | case 2 /* ELEMENT */: 1663 | traverseChildren(node, context); 1664 | break; 1665 | } 1666 | let i = exitFns.length; 1667 | while (i--) { 1668 | exitFns[i](); 1669 | } 1670 | } 1671 | function traverseChildren(node, context) { 1672 | const children = node.children; 1673 | for (let i = 0; i < children.length; i++) { 1674 | const node = children[i]; 1675 | traverseNode(node, context); 1676 | } 1677 | } 1678 | function createRootCodegen(root) { 1679 | const child = root.children[0]; 1680 | if (child.type === 2 /* ELEMENT */) { 1681 | root.codegenNode = child.codegenNode; 1682 | } 1683 | else { 1684 | root.codegenNode = root.children[0]; 1685 | } 1686 | } 1687 | 1688 | function createVNodeCall(context, tag, props, children) { 1689 | context.helper(CREATE_ELEMENT_VNODE); 1690 | return { 1691 | type: 2 /* ELEMENT */, 1692 | tag, 1693 | props, 1694 | children, 1695 | }; 1696 | } 1697 | 1698 | /* 1699 | * @Author: Stone 1700 | * @Date: 2022-04-27 15:23:00 1701 | * @LastEditors: Stone 1702 | * @LastEditTime: 2022-04-27 18:43:06 1703 | */ 1704 | function transformElement(node, context) { 1705 | if (node.type === 2 /* ELEMENT */) { 1706 | return () => { 1707 | // 中间处理层 1708 | // tag 1709 | const vnodeTag = `'${node.tag}'`; 1710 | // props 1711 | let vnodeProps; 1712 | // children 1713 | const { children } = node; 1714 | let vnodeChildren = children[0]; 1715 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren); 1716 | }; 1717 | } 1718 | } 1719 | 1720 | /* 1721 | * @Author: Stone 1722 | * @Date: 2022-04-27 14:52:31 1723 | * @LastEditors: Stone 1724 | * @LastEditTime: 2022-04-27 16:28:23 1725 | */ 1726 | function transformExpression(node) { 1727 | if (node.type === 0 /* INTERPOLATION */) { 1728 | node.content = processExpression(node.content); 1729 | } 1730 | } 1731 | function processExpression(node) { 1732 | node.content = `_ctx.${node.content}`; 1733 | return node; 1734 | } 1735 | 1736 | function isText(node) { 1737 | return (node.type === 3 /* TEXT */ || node.type === 0 /* INTERPOLATION */); 1738 | } 1739 | 1740 | /* 1741 | * @Author: Stone 1742 | * @Date: 2022-04-27 15:58:33 1743 | * @LastEditors: Stone 1744 | * @LastEditTime: 2022-04-27 18:41:50 1745 | */ 1746 | function transformText(node) { 1747 | // 实现一个 compose 类型的节点 1748 | // 目的是将 element 类型下的 chilren + 起来(注意 是一个接一个的) 1749 | if (node.type === 2 /* ELEMENT */) { 1750 | return () => { 1751 | const { children } = node; 1752 | let currentContainer; 1753 | for (let i = 0; i < children.length; i++) { 1754 | const child = children[i]; 1755 | if (isText(child)) { 1756 | for (let j = i + 1; j < children.length; j++) { 1757 | const next = children[j]; 1758 | if (isText(next)) { 1759 | if (!currentContainer) { 1760 | currentContainer = children[i] = { 1761 | type: 5 /* COMPOUND_EXPRESSION */, 1762 | children: [child] 1763 | }; 1764 | } 1765 | currentContainer.children.push(" + "); 1766 | currentContainer.children.push(next); 1767 | children.splice(j, 1); 1768 | j--; 1769 | } 1770 | else { 1771 | currentContainer = undefined; 1772 | break; 1773 | } 1774 | } 1775 | } 1776 | } 1777 | }; 1778 | } 1779 | } 1780 | 1781 | /* 1782 | * @Author: Stone 1783 | * @Date: 2022-04-28 10:23:39 1784 | * @LastEditors: Stone 1785 | * @LastEditTime: 2022-04-28 10:42:46 1786 | */ 1787 | // compile 统一的出口 后面通过调用 baseCompile 生成 render 1788 | function baseCompile(template) { 1789 | const ast = baseParse(template); 1790 | transform(ast, { 1791 | nodeTransforms: [transformExpression, transformElement, transformText], 1792 | }); 1793 | return generate(ast); 1794 | } 1795 | 1796 | /* 1797 | * @Author: Stone 1798 | * @Date: 2022-04-24 19:41:18 1799 | * @LastEditors: Stone 1800 | * @LastEditTime: 2022-04-28 10:56:58 1801 | */ 1802 | function compileToFunction(template) { 1803 | const { code } = baseCompile(template); 1804 | // 想要的 render 函数其实也依赖了一些 Vue 内部的函数 所以要想一个策略 直接把这个 render 函数返回出去就可以放在组件中使用了 1805 | // import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" 1806 | // export function render(_ctx, _cache, $props, $setup, $data, $options) { 1807 | // return (_openBlock(), _createElementBlock("div", null, "Hello World," + _toDisplayString(_ctx.message), 1 /* TEXT */)) 1808 | // } 1809 | const render = new Function("Vue", code)(runtimerDOM); 1810 | return render; 1811 | } 1812 | // 这个函数一开始就会执行 1813 | registerRuntimerCompiler(compileToFunction); 1814 | 1815 | export { computed, createApp, createVNode as createElementVNode, createRenderer, createTextVNode, effect, getCurrentInstance, h, inject, isProxy, isReactive, isReadonly, isRef, nextTick, provide, proxyRefs, reactive, readonly, ref, registerRuntimerCompiler, renderSlots, shallowReadonly, stop, toDisplayString, unRef }; 1816 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-vue3", 3 | "version": "1.0.0", 4 | "main": "lib/mini-vue3.cjs.js", 5 | "module": "lib/mini-vue3.esm.js", 6 | "repository": "git@github.com:Leiloloaa/mini-vue3.git", 7 | "author": "cl1997 ", 8 | "license": "MIT", 9 | "scripts": { 10 | "test": "jest", 11 | "build": "rollup -c rollup.config.js" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.15.8", 15 | "@babel/preset-env": "^7.15.8", 16 | "@babel/preset-typescript": "^7.15.0", 17 | "@rollup/plugin-typescript": "^8.3.0", 18 | "@types/jest": "^27.5.1", 19 | "babel-jest": "^27.2.5", 20 | "jest": "^27.2.5", 21 | "rollup": "^2.58.1", 22 | "tslib": "^2.3.1", 23 | "typescript": "^4.4.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript' 2 | import pkg from './package.json' 3 | // 天然支持 esm 语法 4 | export default { 5 | input: './src/index.ts', 6 | output: [ 7 | //1. cjs -> commonjs 8 | //2. esm 9 | { 10 | format: 'cjs', 11 | file: pkg.main 12 | }, 13 | { 14 | format: 'es', 15 | file: pkg.module 16 | } 17 | ], 18 | // 代码使用 ts 写的 需要编译 19 | plugins: [typescript()] 20 | } -------------------------------------------------------------------------------- /src/compiler-core/src/ast.ts: -------------------------------------------------------------------------------- 1 | import { CREATE_ELEMENT_VNODE } from "./runtimeHelpers"; 2 | 3 | /* 4 | * @Author: Stone 5 | * @Date: 2022-04-24 19:41:18 6 | * @LastEditors: Stone 7 | * @LastEditTime: 2022-04-27 18:42:07 8 | */ 9 | export const enum NodeTypes { 10 | INTERPOLATION, 11 | SIMPLE_EXPRESSION, 12 | ELEMENT, 13 | TEXT, 14 | ROOT, 15 | COMPOUND_EXPRESSION 16 | } 17 | 18 | export function createVNodeCall(context, tag, props, children) { 19 | context.helper(CREATE_ELEMENT_VNODE); 20 | 21 | return { 22 | type: NodeTypes.ELEMENT, 23 | tag, 24 | props, 25 | children, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/compiler-core/src/codegen.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "../../shared" 2 | import { NodeTypes } from "./ast" 3 | import { CREATE_ELEMENT_VNODE, helperMapName, TO_DISPLAY_STRING } from "./runtimeHelpers" 4 | 5 | /* 6 | * @Author: Stone 7 | * @Date: 2022-04-26 14:57:37 8 | * @LastEditors: Stone 9 | * @LastEditTime: 2022-04-28 10:14:25 10 | */ 11 | export function generate(ast) { 12 | // 实现功能的步骤 13 | // 1、先知道要达到的效果 14 | // 2、任务拆分实现 15 | // 3、优化提取代码 16 | const context = createCodegenContext() 17 | const { push } = context 18 | 19 | genFunctionPreamble(ast, context) 20 | 21 | const functionName = "render" 22 | const args = ["_ctx", "_cache"] 23 | const signature = args.join(", ") 24 | 25 | push(`function ${functionName}(${signature}){`) 26 | push("return ") 27 | genNode(ast.codegenNode, context) 28 | push("}") 29 | 30 | return { 31 | code: context.code 32 | } 33 | } 34 | 35 | function createCodegenContext() { 36 | const context = { 37 | code: "", 38 | push(source) { 39 | context.code += source 40 | }, 41 | helper(key) { 42 | return `_${helperMapName[key]}` 43 | } 44 | } 45 | return context 46 | } 47 | 48 | function genNode(node: any, context) { 49 | // 这里之前只处理 text 之后还需要处理别的类型 使用一个 switch 50 | switch (node.type) { 51 | case NodeTypes.TEXT: 52 | genText(node, context) 53 | break; 54 | case NodeTypes.INTERPOLATION: 55 | genInterpolation(node, context) 56 | break 57 | case NodeTypes.SIMPLE_EXPRESSION: 58 | genExpression(node, context) 59 | break 60 | case NodeTypes.ELEMENT: 61 | genElement(node, context) 62 | break 63 | case NodeTypes.COMPOUND_EXPRESSION: 64 | genCompoundExpression(node, context) 65 | break 66 | default: 67 | break; 68 | } 69 | // const { push } = context 70 | // push(`'${node.content}'`) 71 | } 72 | 73 | function genFunctionPreamble(ast: any, context) { 74 | const { push } = context 75 | const VueBinging = "Vue" 76 | // const helpers = ["toDisplayString"] // 帮助函数 后期需要实现 修改写在一个 helper 里面 77 | const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}` // 别名 带下划线 78 | if (ast.helpers.length > 0) { 79 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")}} = ${VueBinging}`) 80 | } 81 | push("\n") 82 | push("return ") 83 | } 84 | 85 | function genText(node: any, context: any) { 86 | const { push } = context 87 | push(`'${node.content}'`) 88 | } 89 | 90 | function genInterpolation(node: any, context: any) { 91 | const { push, helper } = context 92 | push(`${helper(TO_DISPLAY_STRING)}(`) 93 | genNode(node.content, context) 94 | push(")") 95 | } 96 | 97 | function genExpression(node: any, context: any) { 98 | const { push } = context 99 | push(`${node.content}`) 100 | } 101 | 102 | function genElement(node, context) { 103 | const { push, helper } = context 104 | const { tag, children, props } = node 105 | // console.log('children', children) 106 | // [ { type: 3, content: 'h1,' }, 107 | // { type: 0, content: { type: 1, content: 'message' } } 108 | // ] 109 | // push(`${helper(CREATE_ELEMENT_VNODE)}("${tag}"), null, "hi," + _toDisplayString(_ctx.message)`) 110 | // element 里面的 children 一个一个拼接 循环遍历 111 | // const child = children[0] 112 | push(`${helper(CREATE_ELEMENT_VNODE)}(`) 113 | // for (let i = 0; i < children.length; i++) { 114 | // const child = children[i]; 115 | // genNode(child, context) 116 | // } 117 | genNodeList(genNullable([tag, props, children]), context) 118 | // genNode(children, context) 119 | push(")") 120 | } 121 | 122 | function genNodeList(nodes, context) { 123 | const { push } = context 124 | for (let i = 0; i < nodes.length; i++) { 125 | const node = nodes[i]; 126 | if (isString(node)) { 127 | push(node) 128 | } else { 129 | genNode(node, context) 130 | } 131 | if (i < nodes.length - 1) { 132 | push(", ") 133 | } 134 | } 135 | } 136 | 137 | function genNullable(args) { 138 | return args.map((arg) => arg || "null") 139 | } 140 | 141 | function genCompoundExpression(node: any, context: any) { 142 | const { push } = context 143 | const { children } = node 144 | for (let i = 0; i < children.length; i++) { 145 | const child = children[i]; 146 | if (isString(child)) { 147 | push(child) 148 | } else { 149 | genNode(child, context) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/compiler-core/src/compile.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-28 10:23:39 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 10:42:46 6 | */ 7 | 8 | import { generate } from "./codegen"; 9 | import { baseParse } from "./parse"; 10 | import { transform } from "./transform"; 11 | import { transformElement } from "./transforms/transformElement"; 12 | import { transformExpression } from "./transforms/transformExpression"; 13 | import { transformText } from "./transforms/transformText"; 14 | 15 | // compile 统一的出口 后面通过调用 baseCompile 生成 render 16 | export function baseCompile(template) { 17 | const ast: any = baseParse(template); 18 | transform(ast, { 19 | nodeTransforms: [transformExpression, transformElement, transformText], 20 | }); 21 | 22 | return generate(ast); 23 | } -------------------------------------------------------------------------------- /src/compiler-core/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-28 10:29:52 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 10:29:52 6 | */ 7 | // 对外导出函数都是要基于 index 函数 8 | 9 | export * from './compile'; 10 | -------------------------------------------------------------------------------- /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) { 9 | const context = createParserContent(content) 10 | return createRoot(parseChildren(context, [])) 11 | } 12 | 13 | function parseChildren(context, ancestors) { 14 | const nodes: any = [] 15 | while (!isEnd(context, ancestors)) { 16 | let node 17 | const s = context.source; 18 | if (s.startsWith('{{')) { 19 | node = parseInterpolation(context) 20 | } else if (s[0] === "<") { 21 | // 需要用正则表达判断 22 | //
23 | // /^<[a-z]/i/ 24 | if (/[a-z]/i.test(s[1])) { 25 | node = parseElement(context, ancestors); 26 | } 27 | } 28 | if (!node) { 29 | node = parseText(context); 30 | } 31 | 32 | nodes.push(node) 33 | } 34 | return nodes 35 | } 36 | 37 | function isEnd(context, ancestors) { 38 | // 1、source 有值的时候 39 | // 2、当遇到结束标签的时候 40 | const s = context.source; 41 | if (s.startsWith('= 0; i--) { 43 | const tag = ancestors[i].tag; 44 | if (startsWithEndTagOpen(s, tag)) { 45 | return true; 46 | } 47 | } 48 | } 49 | return !s; 50 | } 51 | 52 | function startsWithEndTagOpen(source, tag) { 53 | // 以左括号开头才有意义 并且 还需要转换为小写比较 54 | return ( 55 | source.startsWith(" index) { 66 | endIndex = index 67 | } 68 | } 69 | 70 | // 解析文本 之前是 从头截取到尾部 但真是的环境是文本后面会有其它类型的 element 所以要指明停止的位置 71 | const content = parseTextData(context, endIndex) 72 | 73 | console.log('content -------', content); 74 | 75 | return { 76 | type: NodeTypes.TEXT, 77 | content 78 | } 79 | } 80 | 81 | function parseTextData(context, length) { 82 | const content = context.source.slice(0, length) 83 | 84 | advanceBy(context, length) 85 | return content 86 | } 87 | 88 | function parseElement(context, ancestors) { 89 | // 解析标签 90 | const element: any = parseTag(context, TagType.Start) 91 | ancestors.push(element) 92 | // 获取完标签后 需要把内部的 元素保存起来 需要用递归的方式去遍历内部的 element 93 | element.children = parseChildren(context, ancestors) 94 | ancestors.pop() 95 | 96 | // 这里要判断一下 开始标签和结束标签是否是一致的 不能直接消费完就 return 97 | if (startsWithEndTagOpen(context.source, element.tag)) { 98 | parseTag(context, TagType.End) 99 | } else { 100 | throw new Error(`缺少结束标签:${element.tag}`) 101 | } 102 | 103 | return element 104 | } 105 | 106 | function parseTag(context, type) { 107 | //
108 | // 匹配解析 109 | // 推进 110 | const match: any = /^<\/?([a-z]*)/i.exec(context.source) 111 | const tag = match[1] 112 | // 获取完后要推进 113 | advanceBy(context, match[0].length) 114 | advanceBy(context, 1); 115 | 116 | if (type === TagType.End) return 117 | return { 118 | type: NodeTypes.ELEMENT, 119 | tag 120 | } 121 | } 122 | 123 | function parseInterpolation(context) { 124 | // {{message}} 125 | // 拿出来定义的好处就是 如果需要更改 改动会很小 126 | const openDelimiter = '{{' 127 | const closeDelimiter = '}}' 128 | 129 | // 我们要知道关闭的位置 130 | // indexOf 表示 检索 }} 从 2 开始 131 | const closeIndex = context.source.indexOf( 132 | closeDelimiter, 133 | openDelimiter.length 134 | ) 135 | 136 | // 删除 前两个字符串 137 | // context.source = context.source.slice(openDelimiter.length) 138 | advanceBy(context, openDelimiter.length) 139 | 140 | // 内容的长度就等于 closeIndex - openDelimiter 的长度 141 | const rawContentLength = closeIndex - openDelimiter.length 142 | const rawContent = parseTextData(context, rawContentLength) 143 | const content = rawContent.trim() 144 | 145 | // 然后还需要把这个字符串给删了 模板是一个字符串 要接着遍历后面的内容 146 | // context.source = context.source.slice(rawContentLength + closeDelimiter.length); 147 | advanceBy(context, closeDelimiter.length) 148 | 149 | return { 150 | type: NodeTypes.INTERPOLATION, 151 | content: { 152 | type: NodeTypes.SIMPLE_EXPRESSION, 153 | content 154 | } 155 | } 156 | } 157 | 158 | function advanceBy(context, length) { 159 | context.source = context.source.slice(length); 160 | } 161 | 162 | function createRoot(children) { 163 | return { children, type: NodeTypes.ROOT } 164 | } 165 | 166 | function createParserContent(content) { 167 | return { 168 | source: content 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/compiler-core/src/runtimeHelpers.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-27 14:24:20 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-27 18:36:18 6 | */ 7 | export const TO_DISPLAY_STRING = Symbol('toDisplayString') 8 | export const CREATE_ELEMENT_VNODE = Symbol('createElementVNode'); 9 | 10 | // Symbol 定义的变量不可以遍历 所以转一下 11 | export const helperMapName = { 12 | [TO_DISPLAY_STRING]: 'toDisplayString', 13 | [CREATE_ELEMENT_VNODE]: 'createElementVNode' 14 | } -------------------------------------------------------------------------------- /src/compiler-core/src/transform.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-25 17:19:47 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 10:02:39 6 | */ 7 | 8 | import { NodeTypes } from "./ast" 9 | import { TO_DISPLAY_STRING } from "./runtimeHelpers" 10 | 11 | // options 提供了更动态的传参方式 12 | export function transform(root, options = {}) { 13 | // 任务拆分 14 | // 1 遍历 - 深度优先遍历 和 广度优先遍历 15 | // 2 修改 test content 16 | 17 | // 创建上下文本 18 | const context = createTransformContext(root, options) 19 | traverseNode(root, context) 20 | createRootCodegen(root) 21 | 22 | root.helpers = [...context.helpers.keys()] 23 | console.log('root.helpers', root.helpers) 24 | } 25 | 26 | function createTransformContext(root: any, options: any): any { 27 | const context = { 28 | root, 29 | nodeTransforms: options.nodeTransforms || [], // 插件列表 30 | helpers: new Map(), 31 | helper(key) { 32 | context.helpers.set(key, 1) 33 | } 34 | } 35 | return context 36 | } 37 | 38 | function traverseNode(node: any, context) { 39 | const nodeTransforms = context.nodeTransforms 40 | const exitFns: any = [] 41 | for (let i = 0; i < nodeTransforms.length; i++) { 42 | // 调用插件 43 | const transform = nodeTransforms[i]; 44 | const onExit = transform(node, context) 45 | if (onExit) exitFns.push(onExit) 46 | } 47 | 48 | switch (node.type) { 49 | case NodeTypes.INTERPOLATION: 50 | context.helper(TO_DISPLAY_STRING) 51 | break; 52 | case NodeTypes.ROOT: 53 | case NodeTypes.ELEMENT: 54 | traverseChildren(node, context) 55 | break; 56 | default: 57 | break; 58 | } 59 | let i = exitFns.length 60 | while (i--) { 61 | exitFns[i]() 62 | } 63 | } 64 | 65 | function traverseChildren(node: any, context: any) { 66 | const children = node.children 67 | for (let i = 0; i < children.length; i++) { 68 | const node = children[i]; 69 | traverseNode(node, context) 70 | } 71 | } 72 | 73 | function createRootCodegen(root: any) { 74 | const child = root.children[0] 75 | if (child.type === NodeTypes.ELEMENT) { 76 | root.codegenNode = child.codegenNode 77 | } else { 78 | root.codegenNode = root.children[0] 79 | } 80 | } -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformElement.ts: -------------------------------------------------------------------------------- 1 | import { createVNodeCall, NodeTypes } from "../ast"; 2 | 3 | /* 4 | * @Author: Stone 5 | * @Date: 2022-04-27 15:23:00 6 | * @LastEditors: Stone 7 | * @LastEditTime: 2022-04-27 18:43:06 8 | */ 9 | export function transformElement(node, context) { 10 | if (node.type === NodeTypes.ELEMENT) { 11 | 12 | return () => { 13 | // 中间处理层 14 | 15 | // tag 16 | const vnodeTag = `'${node.tag}'` 17 | 18 | // props 19 | let vnodeProps 20 | 21 | // children 22 | const { children } = node 23 | let vnodeChildren = children[0] 24 | node.codegenNode = createVNodeCall( 25 | context, 26 | vnodeTag, 27 | vnodeProps, 28 | vnodeChildren 29 | ); 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformExpression.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast" 2 | 3 | /* 4 | * @Author: Stone 5 | * @Date: 2022-04-27 14:52:31 6 | * @LastEditors: Stone 7 | * @LastEditTime: 2022-04-27 16:28:23 8 | */ 9 | export function transformExpression(node) { 10 | if (node.type === NodeTypes.INTERPOLATION) { 11 | node.content = processExpression(node.content) 12 | } 13 | } 14 | 15 | function processExpression(node) { 16 | node.content = `_ctx.${node.content}` 17 | return node 18 | } -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformText.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | import { isText } from "../utils"; 3 | 4 | /* 5 | * @Author: Stone 6 | * @Date: 2022-04-27 15:58:33 7 | * @LastEditors: Stone 8 | * @LastEditTime: 2022-04-27 18:41:50 9 | */ 10 | export function transformText(node) { 11 | // 实现一个 compose 类型的节点 12 | // 目的是将 element 类型下的 chilren + 起来(注意 是一个接一个的) 13 | if (node.type === NodeTypes.ELEMENT) { 14 | 15 | return () => { 16 | const { children } = node 17 | 18 | let currentContainer 19 | for (let i = 0; i < children.length; i++) { 20 | const child = children[i]; 21 | if (isText(child)) { 22 | for (let j = i + 1; j < children.length; j++) { 23 | const next = children[j]; 24 | if (isText(next)) { 25 | if (!currentContainer) { 26 | currentContainer = children[i] = { 27 | type: NodeTypes.COMPOUND_EXPRESSION, 28 | children: [child] 29 | } 30 | } 31 | currentContainer.children.push(" + ") 32 | currentContainer.children.push(next) 33 | children.splice(j, 1) 34 | j-- 35 | } else { 36 | currentContainer = undefined 37 | break 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/compiler-core/src/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-27 18:41:36 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-27 18:41:36 6 | */ 7 | import { NodeTypes } from "./ast"; 8 | 9 | export function isText(node) { 10 | return ( 11 | node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION 12 | ); 13 | } -------------------------------------------------------------------------------- /src/compiler-core/tests/__snapshots__/codegen.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`codegen element 1`] = ` 4 | "const { toDisplayString:_toDisplayString, createElementVNode:_createElementVNode} = Vue 5 | return function render(_ctx, _cache){return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))}" 6 | `; 7 | 8 | exports[`codegen interpolation 1`] = ` 9 | "const { toDisplayString:_toDisplayString} = Vue 10 | return function render(_ctx, _cache){return _toDisplayString(_ctx.message)}" 11 | `; 12 | 13 | exports[`codegen string 1`] = ` 14 | " 15 | return function render(_ctx, _cache){return 'hi'}" 16 | `; 17 | -------------------------------------------------------------------------------- /src/compiler-core/tests/codegen.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-26 14:57:19 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 09:59:49 6 | */ 7 | import { generate } from "../src/codegen"; 8 | import { baseParse } from "../src/parse"; 9 | import { transform } from "../src/transform"; 10 | import { transformExpression } from "../src/transforms/transformExpression"; 11 | import { transformElement } from './../src/transforms/transformElement'; 12 | import { transformText } from './../src/transforms/transformText'; 13 | 14 | describe("codegen", () => { 15 | it("string", () => { 16 | const ast = baseParse("hi"); 17 | transform(ast); 18 | const { code } = generate(ast); 19 | // 这是生成快照 如果需要更新快照 运行命令后面加上 -u 20 | expect(code).toMatchSnapshot(); 21 | }); 22 | 23 | it("interpolation", () => { 24 | const ast = baseParse("{{message}}"); 25 | transform(ast, { 26 | nodeTransforms: [transformExpression], 27 | }); 28 | const { code } = generate(ast); 29 | expect(code).toMatchSnapshot(); 30 | }); 31 | 32 | it("element", () => { 33 | const ast: any = baseParse("
hi,{{message}}
"); 34 | transform(ast, { 35 | nodeTransforms: [transformExpression,transformElement, transformText], 36 | }); 37 | 38 | const { code } = generate(ast); 39 | expect(code).toMatchSnapshot(); 40 | }); 41 | }); -------------------------------------------------------------------------------- /src/compiler-core/tests/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../src/ast"; 2 | import { baseParse } from "../src/parse"; 3 | describe("Parse", () => { 4 | describe("interpolation", () => { 5 | test("simple interpolation", () => { 6 | const ast = baseParse("{{ message }}"); 7 | 8 | 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 | 22 | expect(ast.children[0]).toStrictEqual({ 23 | type: NodeTypes.ELEMENT, 24 | tag: "div", 25 | children: [], 26 | }); 27 | }); 28 | }); 29 | 30 | describe("text", () => { 31 | it("simple text", () => { 32 | const ast = baseParse("some text"); 33 | 34 | expect(ast.children[0]).toStrictEqual({ 35 | type: NodeTypes.TEXT, 36 | content: "some text", 37 | }); 38 | }); 39 | }); 40 | 41 | test("hello world", () => { 42 | const ast = baseParse("
hi,{{message}}
"); 43 | 44 | expect(ast.children[0]).toStrictEqual({ 45 | type: NodeTypes.ELEMENT, 46 | tag: "div", 47 | children: [ 48 | { 49 | type: NodeTypes.TEXT, 50 | content: "hi,", 51 | }, 52 | { 53 | type: NodeTypes.INTERPOLATION, 54 | content: { 55 | type: NodeTypes.SIMPLE_EXPRESSION, 56 | content: "message", 57 | }, 58 | }, 59 | ], 60 | }); 61 | }); 62 | 63 | test("Nested element ", () => { 64 | const ast = baseParse("

hi

{{message}}
"); 65 | 66 | expect(ast.children[0]).toStrictEqual({ 67 | type: NodeTypes.ELEMENT, 68 | tag: "div", 69 | children: [ 70 | { 71 | type: NodeTypes.ELEMENT, 72 | tag: "p", 73 | children: [ 74 | { 75 | type: NodeTypes.TEXT, 76 | content: "hi", 77 | }, 78 | ], 79 | }, 80 | { 81 | type: NodeTypes.INTERPOLATION, 82 | content: { 83 | type: NodeTypes.SIMPLE_EXPRESSION, 84 | content: "message", 85 | }, 86 | }, 87 | ], 88 | }); 89 | }); 90 | 91 | test("should throw error when lack end tag", () => { 92 | expect(() => { 93 | baseParse("
"); 94 | }).toThrow(`缺少结束标签:span`); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /src/compiler-core/tests/transform.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-25 17:19:33 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-25 17:38:11 6 | */ 7 | import { NodeTypes } from "../src/ast"; 8 | import { baseParse } from "../src/parse"; 9 | import { transform } from "../src/transform"; 10 | 11 | describe("transform", () => { 12 | it("happy path", () => { 13 | const ast = baseParse("
hi,{{message}}
"); 14 | 15 | // 使用插件的方式 修改指定的内容 16 | const plugin = (node) => { 17 | if (node.type === NodeTypes.TEXT) { 18 | node.content = node.content + " mini-vue"; 19 | } 20 | }; 21 | 22 | transform(ast, { 23 | nodeTransforms: [plugin], 24 | }); 25 | 26 | const nodeText = ast.children[0].children[0]; 27 | expect(nodeText.content).toBe("hi, mini-vue"); 28 | }); 29 | }); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-24 19:41:18 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 11:17:33 6 | */ 7 | // mini-vue 的出口 8 | export * from './runtime-dom'; 9 | 10 | // baseCompile 是返回一个 {code} 达不到效果,因为我们想要的是 render 函数,所以需要一个函数处理一下 11 | import { baseCompile } from './compiler-core/src'; 12 | import * as runtimerDOM from './runtime-core'; 13 | import { registerRuntimerCompiler } from './runtime-dom'; 14 | 15 | function compileToFunction(template) { 16 | const { code } = baseCompile(template) 17 | // 想要的 render 函数其实也依赖了一些 Vue 内部的函数 所以要想一个策略 直接把这个 render 函数返回出去就可以放在组件中使用了 18 | // import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" 19 | // export function render(_ctx, _cache, $props, $setup, $data, $options) { 20 | // return (_openBlock(), _createElementBlock("div", null, "Hello World," + _toDisplayString(_ctx.message), 1 /* TEXT */)) 21 | // } 22 | const render = new Function("Vue", code)(runtimerDOM) 23 | return render 24 | } 25 | 26 | // 这个函数一开始就会执行 27 | registerRuntimerCompiler(compileToFunction) -------------------------------------------------------------------------------- /src/reactivity/baseHandlers.ts: -------------------------------------------------------------------------------- 1 | import { extend, isObject } from "../shared" 2 | import { track, trigger } from "./effect" 3 | import { reactive, ReactiveFlags, reactiveMap, readonly, readonlyMap, shallowReadonlyMap } from "./reactive" 4 | 5 | // 缓存 首次创建即可 6 | const get = createGetter() 7 | const set = createSetter() 8 | const readonlyGet = createGetter(true) 9 | const shallowReadonlyGet = createGetter(true, true) 10 | 11 | // 1、reactive 和 readonly 逻辑相似 抽离代码 12 | // 2、使用高阶函数 来区分是否要 track 13 | function createGetter(isReadonly = false, shallow = false) { 14 | return function get(target, key, receiver) { 15 | const isExistInReactiveMap = () => 16 | key === ReactiveFlags.RAW && receiver === reactiveMap.get(target); 17 | 18 | const isExistInReadonlyMap = () => 19 | key === ReactiveFlags.RAW && receiver === readonlyMap.get(target); 20 | 21 | const isExistInShallowReadonlyMap = () => 22 | key === ReactiveFlags.RAW && receiver === shallowReadonlyMap.get(target); 23 | 24 | if (key === ReactiveFlags.IS_REACTIVE) { 25 | return !isReadonly 26 | } else if (key === ReactiveFlags.IS_READONLY) { 27 | return isReadonly 28 | } else if ( 29 | isExistInReactiveMap() || 30 | isExistInReadonlyMap() || 31 | isExistInShallowReadonlyMap() 32 | ) { 33 | return target; 34 | } 35 | 36 | const res = Reflect.get(target, key) 37 | // Proxy 要和 Reflect 配合使用 38 | // Reflect.get 中 receiver 参数,保留了对正确引用 this(即 admin)的引用,该引用将 Reflect.get 中正确的对象使用传递给 get 39 | // 不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为 40 | 41 | // 如果为 true 就直接返回 42 | if (shallow) { 43 | return res 44 | } 45 | 46 | // 如果 res 是 Object 47 | if (isObject(res)) { 48 | return isReadonly ? readonly(res) : reactive(res) 49 | } 50 | 51 | if (!isReadonly) { 52 | track(target, key) 53 | } 54 | return res 55 | } 56 | } 57 | 58 | function createSetter() { 59 | return function set(target, key, value, receiver) { 60 | // set 操作是会放回 true or false 61 | // set() 方法应当返回一个布尔值。 62 | // 返回 true 代表属性设置成功。 63 | // 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。 64 | const res = Reflect.set(target, key, value, receiver) 65 | trigger(target, "get", key) 66 | return res 67 | } 68 | } 69 | 70 | export const mutableHandlers = { 71 | get, 72 | set 73 | }; 74 | 75 | export const readonlyHandlers = { 76 | get: readonlyGet, 77 | set(target, key) { 78 | console.warn(`key:${key}`) 79 | return true 80 | } 81 | }; 82 | 83 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, { get: shallowReadonlyGet }); -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect" 2 | 3 | class ComputedRefImpl { 4 | private _dirty: boolean = true 5 | private _value: any 6 | private _effect: ReactiveEffect 7 | constructor(getter) { 8 | this._effect = new ReactiveEffect(getter, () => { 9 | if (!this._dirty) { 10 | this._dirty = true 11 | } 12 | }) 13 | } 14 | get value() { 15 | // get 调用完一次就锁上 16 | // 当依赖的响应式对象的值发生改变的时候 17 | // effect 18 | if (this._dirty) { 19 | this._dirty = false 20 | this._value = this._effect.run() 21 | } 22 | 23 | return this._value 24 | } 25 | } 26 | 27 | // getter 是一个函数 28 | export function computed(getter) { 29 | return new ComputedRefImpl(getter) 30 | } -------------------------------------------------------------------------------- /src/reactivity/dep.ts: -------------------------------------------------------------------------------- 1 | // 用于存储所有的 effect 对象 2 | export function createDep(effects?) { 3 | const dep = new Set(effects); 4 | return dep; 5 | } 6 | -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "../shared" 2 | 3 | let activeEffect 4 | let shouldTrack = false 5 | export class ReactiveEffect { 6 | private _fn: any 7 | deps = [] 8 | scheduler: Function | undefined 9 | active = true 10 | onStop?: () => void 11 | constructor(fn, scheduler?: Function) { 12 | this._fn = fn 13 | this.scheduler = scheduler 14 | } 15 | run() { 16 | // 会收集依赖 17 | // shouldTrack 来区分 18 | 19 | // 如果是 stop 的状态 20 | // 就不收集 21 | if (!this.active) { 22 | return this._fn() 23 | } 24 | 25 | // 否则收集 26 | shouldTrack = true 27 | activeEffect = this 28 | 29 | const result = this._fn() 30 | 31 | // reset 因为是全局变量 32 | // 处理完要还原 33 | shouldTrack = false 34 | activeEffect = null 35 | return result 36 | } 37 | stop() { 38 | // 性能问题 39 | // 第一次调用 就已经清空了 40 | if (this.active) { 41 | cleanupEffect(this) 42 | if (this.onStop) { 43 | this.onStop() 44 | } 45 | this.active = false 46 | } 47 | } 48 | } 49 | 50 | function cleanupEffect(effect) { 51 | effect.deps.forEach((dep: any) => { 52 | dep.delete(effect) 53 | }); 54 | effect.deps.length = 0 55 | } 56 | 57 | const targetsMap = new Map() 58 | export function track(target, key) { 59 | // 是否收集 shouldTrack 为 true 和 activeEffect 有值的时候要收集 否则就 return 出去 60 | if (!isTracking()) return 61 | 62 | // 收集依赖 63 | // reactive 传入的是一个对象 {} 64 | // 收集关系: targetsMap 收集所有依赖 然后 每一个 {} 作为一个 depsMap 65 | // 再把 {} 里面的每一个变量作为 dep(set 结构) 的 key 存放所有的 fn 66 | let depsMap = targetsMap.get(target) 67 | // 不存在的时候 要先初始化 68 | if (!depsMap) { 69 | depsMap = new Map() 70 | targetsMap.set(target, depsMap) 71 | } 72 | let dep = depsMap.get(key) 73 | if (!dep) { 74 | dep = new Set() 75 | depsMap.set(key, dep) 76 | } 77 | 78 | // 如果是单纯的获取 就不会有 activeEffect 79 | // 因为 activeEffect 是在 effect.run 执行的时候 才会存在 80 | // if (!activeEffect) return 81 | 82 | // 应该收集依赖 83 | // !! 思考 什么时候被赋值呢? 84 | // 触发 set 执行 fn 然后再触发 get 85 | // 所以在 run 方法中 86 | // if (!shouldTrack) return 87 | 88 | // if (dep.has(activeEffect)) return 89 | 90 | // // 要存入的是一个 fn 91 | // // 所以要利用一个全局变量 92 | // dep.add(activeEffect) 93 | 94 | // // 如何通过当前的 effect 去找到 deps? 95 | // // 反向收集 deps 96 | // activeEffect.deps.push(dep) 97 | 98 | trackEffects(dep) 99 | } 100 | 101 | // 抽离 track 与 ref 公用 102 | export function trackEffects(dep) { 103 | if (dep.has(activeEffect)) return 104 | 105 | // 要存入的是一个 fn 106 | // 所以要利用一个全局变量 107 | dep.add(activeEffect) 108 | 109 | // 如何通过当前的 effect 去找到 deps? 110 | // 反向收集 deps 111 | activeEffect.deps.push(dep) 112 | } 113 | 114 | export function isTracking() { 115 | return shouldTrack && activeEffect !== undefined 116 | } 117 | 118 | export function trigger(target, type, key) { 119 | // 触发依赖 120 | let depsMap = targetsMap.get(target) 121 | let dep = depsMap.get(key) 122 | triggerEffects(dep) 123 | } 124 | 125 | export function triggerEffects(dep) { 126 | for (const effect of dep) { 127 | if (effect.scheduler) { 128 | effect.scheduler() 129 | } else { 130 | effect.run() 131 | } 132 | } 133 | } 134 | 135 | type effectOptions = { 136 | scheduler?: Function; 137 | onStop?: Function; 138 | }; 139 | 140 | export function effect(fn, options: effectOptions = {}) { 141 | // ReactiveEffect 构造函数(一定要用 new 关键字实现) 142 | const _effect = new ReactiveEffect(fn, options.scheduler) 143 | // 考虑到后面还会有很多 options 144 | // 使用 Object.assign() 方法自动合并 145 | // _effect.onStop = options.onStop 146 | // Object.assign(_effect, options); 147 | // extend 扩展 更有可读性 148 | extend(_effect, options) 149 | _effect.run() 150 | 151 | const runner: any = _effect.run.bind(_effect) 152 | // 保存 153 | runner.effect = _effect 154 | 155 | return runner 156 | } 157 | 158 | export function stop(runner) { 159 | // stop 的意义 是找要到这个实例 然后删除 160 | runner.effect.stop() 161 | } 162 | 163 | -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | reactive, 3 | readonly, 4 | shallowReadonly, 5 | isReadonly, 6 | isReactive, 7 | isProxy, 8 | } from "./reactive"; 9 | 10 | export { ref, proxyRefs, unRef, isRef } from "./ref"; 11 | 12 | export { effect, stop } from "./effect"; 13 | 14 | export { computed } from "./computed"; 15 | -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "./../shared/index"; 2 | import { 3 | mutableHandlers, 4 | readonlyHandlers, 5 | shallowReadonlyHandlers, 6 | } from "./baseHandlers"; 7 | 8 | export const reactiveMap = new WeakMap(); 9 | export const readonlyMap = new WeakMap(); 10 | export const shallowReadonlyMap = new WeakMap(); 11 | 12 | export const enum ReactiveFlags { 13 | IS_REACTIVE = "__v_isReactive", 14 | IS_READONLY = "__v_isReadonly", 15 | RAW = "__v_raw", 16 | } 17 | 18 | export function reactive(target) { 19 | // 如果试图去观察一个只读的代理对象,会直接返回只读版本 20 | // 这种情况对于的是 readonly 变量 再用 reactive 代理 21 | if (isReadonly(target)){ 22 | return target; 23 | } 24 | return createReactiveObject(target, reactiveMap, mutableHandlers); 25 | } 26 | 27 | export function readonly(target) { 28 | return createReactiveObject(target, readonlyMap, readonlyHandlers); 29 | } 30 | 31 | export function shallowReadonly(target) { 32 | return createReactiveObject( 33 | target, 34 | shallowReadonlyMap, 35 | shallowReadonlyHandlers 36 | ); 37 | } 38 | 39 | export function isReactive(value) { 40 | // 触发 get 操作 就可以判断 value.xxx 就会触发 41 | // value["is_reactive"] get 就可以获取到 is_reactive 42 | // 如果传过来的不是 proxy 值,所以就不会去调用 get 方法 43 | // 也没挂载 ReactiveFlags.IS_REACTIVE 属性 所以是 undefined 44 | // 使用 !! 转换成 boolean 值就可以了 45 | return !!value[ReactiveFlags.IS_REACTIVE]; 46 | } 47 | 48 | export function isReadonly(value) { 49 | return !!value[ReactiveFlags.IS_READONLY]; 50 | } 51 | 52 | export function isProxy(value) { 53 | return isReactive(value) || isReadonly(value); 54 | } 55 | 56 | export function toRaw(value) { 57 | // 如果 value 是proxy 的话 ,那么直接返回就可以了 58 | // 因为会触发 createGetter 内的逻辑 59 | // 如果 value 是普通对象的话, 60 | // 我们就应该返回普通对象 61 | // 只要不是 proxy ,只要是得到的 undefined 的话,那么就一定是普通对象 62 | // TODO 这里和源码里面实现的不一样,不确定后面会不会有问题 63 | if (!value[ReactiveFlags.RAW]) { 64 | return value; 65 | } 66 | 67 | return value[ReactiveFlags.RAW]; 68 | } 69 | 70 | function createReactiveObject(target, proxyMap, baseHandlers) { 71 | if (!isObject(target)) { 72 | console.log("不是一个对象"); 73 | } 74 | // 核心就是 proxy 75 | // 目的是可以侦听到用户 get 或者 set 的动作 76 | 77 | // 如果命中的话就直接返回就好了 不需要每次都重新创建 78 | // 使用缓存做的优化点 79 | const existingProxy = proxyMap.get(target); 80 | if (existingProxy) { 81 | return existingProxy; 82 | } 83 | const proxy = new Proxy(target, baseHandlers); 84 | // 把创建好的 proxy 给存起来 85 | proxyMap.set(target, proxy); 86 | return proxy; 87 | } 88 | -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged, isObject } from "../shared"; 2 | import { createDep } from "./dep"; 3 | import { isTracking, trackEffects, triggerEffects } from "./effect"; 4 | import { reactive } from "./reactive"; 5 | 6 | // 1 true '1' 7 | // get set 8 | // 而 proxy -》只能监听对象 9 | // 我们包裹一个 对象 10 | 11 | // Impl 表示一个接口的缩写 12 | class RefImpl { 13 | private _value: any 14 | public dep 15 | private _rawValue: any 16 | __v_isRef = true 17 | constructor(value) { 18 | // 存储一个新值 用于后面的对比 19 | this._rawValue = value 20 | // value -> reactive 21 | // 看看 value 是不是 对象 22 | this._value = convert(value) 23 | 24 | this.dep = createDep() 25 | } 26 | 27 | // 属性访问器模式 28 | get value() { 29 | // 确保调用过 run 方法 不然 dep 就是 undefined 30 | // if (isTracking()) { 31 | // trackEffects(this.dep) 32 | // } 33 | trackRefValue(this) 34 | return this._value 35 | } 36 | 37 | set value(newValue) { 38 | // 一定是先修改了 value 39 | // newValue -> this._value 相同不修改 40 | // if (Object.is(newValue, this._value)) return 41 | // hasChanged 42 | // 改变才运行 43 | // 对比的时候 object 44 | // 有可能 this.value 是 porxy 那么他们就不会相等 45 | if (hasChanged(newValue, this._rawValue)) { 46 | this._rawValue = newValue 47 | this._value = convert(newValue) 48 | triggerEffects(this.dep) 49 | } 50 | } 51 | } 52 | 53 | function convert(value) { 54 | return isObject(value) ? reactive(value) : value 55 | } 56 | 57 | function trackRefValue(ref) { 58 | if (isTracking()) { 59 | trackEffects(ref.dep) 60 | } 61 | } 62 | 63 | export function ref(value) { 64 | return new RefImpl(value) 65 | } 66 | 67 | export function isRef(ref) { 68 | return !!ref.__v_isRef 69 | } 70 | 71 | // 语法糖 如果是 ref 就放回 .value 否则返回本身 72 | export function unRef(ref) { 73 | return isRef(ref) ? ref.value : ref 74 | } 75 | 76 | export function proxyRefs(objectWithRefs) { 77 | return new Proxy(objectWithRefs, { 78 | get(target, key) { 79 | // get 如果获取到的是 age 是个 ref 那么就返回 .value 80 | // 如果不是 ref 就直接返回本身 81 | return unRef(Reflect.get(target, key)) 82 | }, 83 | set(target, key, value) { 84 | // value 是新值 85 | // 如果目标是 ref 且替换的值不是 ref 86 | if (isRef(target[key]) && !isRef(value)) { 87 | return target[key].value = value 88 | } else { 89 | return Reflect.set(target, key, value) 90 | } 91 | } 92 | }) 93 | } -------------------------------------------------------------------------------- /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 | const user = reactive({ age: 1 }) 10 | const age = computed(() => { 11 | return user.age 12 | }) 13 | 14 | expect(age.value).toBe(1) 15 | }); 16 | 17 | it("should compute lazily", () => { 18 | const value = reactive({ 19 | foo: 1, 20 | }); 21 | const getter = jest.fn(() => { 22 | return value.foo; 23 | }); 24 | const cValue = computed(getter); 25 | 26 | // lazy 27 | // cValue 没有调用 就不会调用 getter 28 | expect(getter).not.toHaveBeenCalled(); 29 | 30 | expect(cValue.value).toBe(1); 31 | expect(getter).toHaveBeenCalledTimes(1); 32 | 33 | // should not compute again 34 | cValue.value; 35 | expect(getter).toHaveBeenCalledTimes(1); 36 | 37 | // should not compute until needed 38 | value.foo = 2; // 对象调用了 set 之后 就会触发 trigger 39 | // trigger -> effect -> get 重新执行 40 | expect(getter).toHaveBeenCalledTimes(1); 41 | 42 | // now it should compute 43 | expect(cValue.value).toBe(2); 44 | expect(getter).toHaveBeenCalledTimes(2); 45 | 46 | // should not compute again 47 | cValue.value; 48 | expect(getter).toHaveBeenCalledTimes(2); 49 | }); 50 | }); -------------------------------------------------------------------------------- /src/reactivity/tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect, stop } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | 4 | describe('effect', () => { 5 | it('happy path', () => { 6 | // reactive 核心 7 | // get 收集依赖 8 | // set 触发依赖 9 | const user = reactive({ 10 | age: 10 11 | }) 12 | 13 | let nextAge 14 | effect(() => { 15 | nextAge = user.age + 1 16 | }) 17 | expect(nextAge).toBe(11) 18 | 19 | // update 20 | user.age++ 21 | expect(nextAge).toBe(12) 22 | }); 23 | 24 | it('runner', () => { 25 | // effect(fn) -> function(runner) -> fn - return 26 | let foo = 10; 27 | const runner = effect(() => { 28 | foo++; 29 | return 'foo' 30 | }) 31 | 32 | expect(foo).toBe(11); 33 | const r = runner(); 34 | expect(foo).toBe(12); 35 | expect(r).toBe('foo') 36 | }); 37 | 38 | it('scheduler', () => { 39 | // scheduler 的实现逻辑 40 | // 1、当响应式对象第一次发生改变的时候,会执行 fn,scheduler 不会执行 41 | // 2、第二次发生改变的时候,会执行性 scheduler,赋值 run 方法 42 | // 3、调用 run 方法的时候,才会执行 fn 43 | let dummy 44 | let run: any 45 | const scheduler = jest.fn(() => { 46 | run = runner 47 | }) 48 | const obj = reactive({ foo: 1 }) 49 | const runner = effect( 50 | () => { 51 | dummy = obj.foo 52 | }, 53 | { scheduler } 54 | ) 55 | // 一开始不会被调用 scheduler 函数 56 | // 因为 effect 中一开始是执行的是 run 方法 57 | // 只有当 trigger 触发更新依赖的时候,有 scheduler 才执行 58 | expect(scheduler).not.toHaveBeenCalled() 59 | expect(dummy).toBe(1) 60 | // should be called on first trigger 61 | obj.foo++ 62 | expect(scheduler).toHaveBeenCalledTimes(1) 63 | // should not run yet 64 | expect(dummy).toBe(1) 65 | // manually run 66 | run() 67 | // should have run 68 | expect(dummy).toBe(2) 69 | }) 70 | 71 | it('stop', () => { 72 | let dummy 73 | const obj = reactive({ prop: 1 }) 74 | const runner = effect(() => { 75 | dummy = obj.prop 76 | }) 77 | obj.prop = 2 78 | expect(dummy).toBe(2) 79 | stop(runner) 80 | // obj.prop = 3 81 | // obj.prop = obj.prop + 1 82 | // 先触发 get 操作 再触发 set 操作 83 | // get 操作 会重新收集依赖 84 | obj.prop++ 85 | expect(dummy).toBe(2) 86 | 87 | // stopped effect should still be manually callable 88 | runner() 89 | expect(dummy).toBe(3) 90 | }) 91 | 92 | it('events: onStop', () => { 93 | // stop 之后 onStop 会被执行 94 | // 允许用户做一些额外的处理 95 | const obj = reactive({ foo: 1 }); 96 | const onStop = jest.fn(); 97 | let dummy; 98 | const runner = effect(() => { 99 | dummy = obj.foo; 100 | }, { 101 | onStop 102 | }); 103 | 104 | stop(runner); 105 | expect(onStop).toHaveBeenCalled(); 106 | }) 107 | }); -------------------------------------------------------------------------------- /src/reactivity/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReactive, reactive } from "../reactive"; 2 | 3 | describe('reactive', () => { 4 | it('happy path', () => { 5 | const original = { foo: 1 }; 6 | const observed = reactive(original); 7 | expect(observed).not.toBe(original); 8 | expect(observed.foo).toBe(1); 9 | expect(isProxy(observed)).toBe(true); 10 | }); 11 | 12 | test("nested reactives", () => { 13 | const original = { 14 | nested: { 15 | foo: 1, 16 | }, 17 | array: [{ bar: 2 }], 18 | }; 19 | const observed = reactive(original); 20 | expect(isReactive(observed.nested)).toBe(true); 21 | expect(isReactive(observed.array)).toBe(true); 22 | expect(isReactive(observed.array[0])).toBe(true); 23 | }); 24 | }) 25 | -------------------------------------------------------------------------------- /src/reactivity/tests/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReactive, isReadonly, reactive, readonly } from "../reactive"; 2 | 3 | describe('readonly', () => { 4 | it('happy path', () => { 5 | const original = { foo: 1, bar: { baz: 2 } }; 6 | const wrapped = readonly(original); 7 | const obj = reactive({ foo: 1 }) 8 | expect(wrapped).not.toBe(original); 9 | expect(isReactive(obj)).toBe(true); 10 | expect(isReadonly(wrapped.bar)).toBe(true); 11 | expect(isReadonly(original)).toBe(false); 12 | expect(wrapped.foo).toBe(1); 13 | }); 14 | 15 | it('should call console.warn when set', () => { 16 | console.warn = jest.fn(); 17 | const user = readonly({ 18 | age: 10 19 | }); 20 | 21 | user.age = 11; 22 | expect(console.warn).toHaveBeenCalled(); 23 | }); 24 | 25 | it('test', () => { 26 | const original: any = { foo: 1, bar: { baz: 2 } }; 27 | const temp = reactive(original); 28 | original.add = 3; 29 | expect(temp.add).toBe(3) 30 | }); 31 | }) -------------------------------------------------------------------------------- /src/reactivity/tests/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | import { isRef, ref, unRef, proxyRefs } from "../ref"; 4 | describe("ref", () => { 5 | it("happy path", () => { 6 | const a = ref(1); 7 | expect(a.value).toBe(1); 8 | }); 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 | expect(calls).toBe(2); 22 | expect(dummy).toBe(2); 23 | // same value should not trigger 24 | // a.value = 2; 25 | // expect(calls).toBe(2); 26 | // expect(dummy).toBe(2); 27 | }); 28 | 29 | it("should make nested properties reactive", () => { 30 | const a = ref({ 31 | count: 1, 32 | }); 33 | let dummy; 34 | effect(() => { 35 | dummy = a.value.count; 36 | }); 37 | expect(dummy).toBe(1); 38 | a.value.count = 2; 39 | expect(dummy).toBe(2); 40 | }); 41 | 42 | it("isRef", () => { 43 | const a = ref(1); 44 | const user = reactive({ 45 | age: 1, 46 | }); 47 | expect(isRef(a)).toBe(true); 48 | expect(isRef(1)).toBe(false); 49 | expect(isRef(user)).toBe(false); 50 | }); 51 | 52 | it("unRef", () => { 53 | const a = ref(1); 54 | expect(unRef(a)).toBe(1); 55 | expect(unRef(1)).toBe(1); 56 | }); 57 | 58 | it("proxyRefs", () => { 59 | // 主要应用 就是 拆箱 ref 60 | // 在 template 中可以不用使用 .value 61 | const user = { 62 | age: ref(10), 63 | name: "xiaohong", 64 | }; 65 | 66 | const proxyUser = proxyRefs(user); 67 | expect(user.age.value).toBe(10); 68 | expect(proxyUser.age).toBe(10); 69 | expect(proxyUser.name).toBe("xiaohong"); 70 | 71 | proxyUser.age = 20; 72 | 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 | 3 | describe("shallowReadonly", () => { 4 | test("should not make non-reactive properties reactive", () => { 5 | // shallowReadonly 只有表层是 readonly 响应式对象、 6 | // 适用于 程序优化 7 | // 避免所有的对象都是响应式对象 8 | const props = shallowReadonly({ n: { foo: 1 } }); 9 | expect(isReadonly(props)).toBe(true); 10 | expect(isReadonly(props.n)).toBe(false); 11 | }); 12 | 13 | it("should call console.warn when set", () => { 14 | console.warn = jest.fn(); 15 | const user = shallowReadonly({ 16 | age: 10, 17 | }); 18 | 19 | user.age = 11; 20 | expect(console.warn).toHaveBeenCalled(); 21 | }); 22 | }); -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "./component"; 2 | 3 | // provide-inject 提供了组件之间跨层级传递数据 父子、祖孙 等 4 | export function provide(key, value) { 5 | // 存储 6 | // 想一下,数据应该存在哪里? 7 | // 如果是存在 最外层的 component 中,里面组件都可以访问到了 8 | // 接着就要获取组件实例 使用 getCurrentInstance,所以 provide 只能在 setup 中使用 9 | const currentInstance: any = getCurrentInstance() 10 | if (currentInstance) { 11 | let { provides } = currentInstance 12 | const parentProvides = currentInstance.parent.provides 13 | 14 | // 如果当前组件的 provides 等于 父级组件的 provides 15 | // 是要 通过 原型链 的方式 去查找 16 | // Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__ 17 | 18 | // 这里要解决一个问题 19 | // 当父级 key 和 爷爷级别的 key 重复的时候,对于子组件来讲,需要取最近的父级别组件的值 20 | // 那这里的解决方案就是利用原型链来解决 21 | // provides 初始化的时候是在 createComponent 时处理的,当时是直接把 parent.provides 赋值给组件的 provides 的 22 | // 所以,如果说这里发现 provides 和 parentProvides 相等的话,那么就说明是第一次做 provide(对于当前组件来讲) 23 | // 我们就可以把 parent.provides 作为 currentInstance.provides 的原型重新赋值 24 | // 至于为什么不在 createComponent 的时候做这个处理,可能的好处是在这里初始化的话,是有个懒执行的效果(优化点,只有需要的时候在初始化) 25 | 26 | // 首先咱们要知道 初始化 的时候 子组件 的 provides 就是父组件的 provides 27 | // currentInstance.parent.provides 是 爷爷组件 28 | // 当两个 key 值相同的时候要取 最近的 父组件的 29 | if (provides === parentProvides) { 30 | provides = currentInstance.provides = Object.create(parentProvides); 31 | } 32 | provides[key] = value 33 | } 34 | } 35 | 36 | export function inject(key, defaultValue: any) { 37 | // 取出 38 | // 从哪里取?若是 祖 -> 孙,要获取哪里的?? 39 | const currentInstance: any = getCurrentInstance() 40 | if (currentInstance) { 41 | const parentProvides = currentInstance.parent.provides 42 | if (key in parentProvides) { 43 | return parentProvides[key] 44 | } else if (defaultValue) { 45 | if (typeof defaultValue === 'function') { 46 | return defaultValue() 47 | } 48 | return defaultValue 49 | } 50 | } 51 | return currentInstance.provides[key] 52 | } -------------------------------------------------------------------------------- /src/runtime-core/apiWatch.ts: -------------------------------------------------------------------------------- 1 | import { isFunction, isObject, NOOP } from "../shared"; 2 | import { effect, ReactiveEffect } from "../reactivity/effect"; 3 | import { isReactive, isRef } from "../reactivity"; 4 | import { isArray } from "../shared/index"; 5 | 6 | export type WatchEffect = () => void; 7 | 8 | export interface WatchOptions { 9 | immediate?: boolean; 10 | deep?: boolean; 11 | flush?: "pre" | "post" | "sync"; 12 | } 13 | 14 | export function watchEffect(effect: WatchEffect, options?: WatchOptions) { 15 | return doWatch(effect, null, (options = {})); 16 | } 17 | 18 | export function watch(source, cb, options: WatchOptions = {}) { 19 | return doWatch(source as any, cb, options); 20 | } 21 | 22 | function doWatch(source, cb, { immediate, deep, flush }: WatchOptions) { 23 | // source 可以是对象的值 或者 是函数 等 24 | // 定义 getter 函数 25 | let getter: () => any; 26 | if (isRef(source)) { 27 | getter = () => source.value; 28 | } else if (isReactive(source)) { 29 | // 如果是 reactive 类型 30 | getter = () => source; 31 | // 深度监听为 true 32 | // 所以是对象的时候 都可以不用指定 deep 33 | deep = true; 34 | } else if (isArray(source)) { 35 | getter = () => 36 | source.map((s) => { 37 | if (isRef(s)) { 38 | return s.value; 39 | } else if (isReactive(s)) { 40 | return traverse(s); 41 | } else if (isFunction(s)) { 42 | return s; 43 | } 44 | }); 45 | } else if (isFunction(source)) { 46 | getter = source; 47 | } else { 48 | getter = NOOP; 49 | } 50 | 51 | if (cb && deep) { 52 | // 如果有回调函数并且深度监听为 true,那么就通过 traverse 函数进行深度递归监听 53 | const baseGetter = getter; 54 | getter = () => traverse(baseGetter()); 55 | } 56 | 57 | // 定义旧值和新值 58 | let oldValue; 59 | 60 | // 提取 scheduler 调度函数为一个独立的 job 函数 61 | const job = () => { 62 | if (cb) { 63 | // 如果有回调函数 64 | // 执行effect.run获取新值 65 | const newValue = effect.run(); 66 | if (deep) { 67 | // 执行回调函数 68 | // 第一次执行的时候,旧值是undefined,这是符合预期的 69 | cb(newValue, oldValue); 70 | // 把新值赋值给旧值 71 | oldValue = newValue; 72 | } 73 | } else { 74 | // 没有回调函数则是 watchEffect 75 | effect.run(); 76 | } 77 | // newValue = effect.run(); 78 | // // 当数据变化时,调用回调函数 cb 79 | // cb(newValue, oldValue); 80 | // oldValue = newValue; 81 | }; 82 | 83 | let scheduler; 84 | if (flush === "post") { 85 | scheduler = () => { 86 | const p = Promise.resolve(); 87 | p.then(job); 88 | }; 89 | } else { 90 | // 如果是 'pre' 或者是 'sync' 91 | scheduler = () => { 92 | job(); 93 | }; 94 | } 95 | const effect = new ReactiveEffect(getter, scheduler); 96 | 97 | // traverse 递归地读取 source 98 | // const effectFn = effect(() => getter(), { 99 | // // 除了可以使用 immediate 之外,还可以使用 flush 指定调度函数的执行时间 100 | // scheduler: () => { 101 | // // 在调度函数中判断 flush 是否为 post 102 | // // 如果是 将其放到微任务队列中执行(执行栈底) 103 | // if (flush === "post") { 104 | // const p = Promise.resolve(); 105 | // p.then(job); 106 | // } else { 107 | // // 如果是 'pre' 或者是 'sync' 108 | // job(); 109 | // } 110 | // }, 111 | // }); 112 | 113 | // options.immediate 为 true 回调函数会在 watch 创建的时候执行一次 114 | // if (immediate) { 115 | // job(); 116 | // } else { 117 | // oldValue = effectFn(); 118 | // } 119 | 120 | // initial run 121 | if (cb) { 122 | if (immediate) { 123 | job(); 124 | } else { 125 | oldValue = effect.run(); 126 | } 127 | } else { 128 | oldValue = effect.run(); 129 | } 130 | } 131 | 132 | function traverse(value, seen = new Set()) { 133 | // 如果要读取的数据是原始值,或者已经被读取过了,那么就什么都别做 134 | if (!isObject || value === null || seen.has(value)) return; 135 | // 将数据添加到 seen 中,代表遍历读取过了,避免循环引用引起的死循环 136 | seen.add(value); 137 | // 暂时不考虑数组等其他结构 138 | // 假设 value 就是一个对象,使用 forin 读取对象的每一个值,并递归的调用 traverse 进行处理 139 | for (const k in value) { 140 | traverse(value[k], seen); 141 | } 142 | return value; 143 | } 144 | -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-24 19:41:18 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 11:00:06 6 | */ 7 | import { proxyRefs } from '../reactivity'; 8 | import { shallowReadonly } from "../reactivity/reactive"; 9 | import { isObject } from './../shared/index'; 10 | import { emit } from "./componentEmit"; 11 | import { initProps } from "./componentProps"; 12 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance"; 13 | import { initSlots } from "./componentSlots"; 14 | 15 | export function createComponentInstance(vnode, parent) { 16 | const component = { 17 | vnode, 18 | type: vnode.type, 19 | next: null, // 表示下一个要更新的 vnode 20 | props: {}, 21 | slots: {}, 22 | setupState: {}, 23 | provides: parent ? parent.provides : {}, // 获取 parent 的 provides 作为当前组件的初始化值 这样就可以继承 parent.provides 的属性了 24 | parent, 25 | isMount: false, 26 | subTree: {}, // 更新了 要挂载老的树 27 | emit: () => { } 28 | } 29 | // bind 的第一个参数 如果是 undefined 或者 null 那么 this 就是指向 windows 30 | // 这样做的目的是 实现了 emit 的第一个参数 为 component 实例 这是预置入 31 | component.emit = emit.bind(null, component) as any 32 | return component 33 | } 34 | 35 | export function setupComponent(instance) { 36 | initSlots(instance, instance.vnode.children) 37 | initProps(instance, instance.vnode.props) 38 | // console.log(instance); 39 | 40 | // 初始化一个有状态的 component 41 | // 有状态的组件 和 函数组件 42 | setupStatefulComponent(instance) 43 | } 44 | 45 | function setupStatefulComponent(instance) { 46 | // 调用 setup 然后 拿到返回值 47 | // type 就是 app 对象 48 | const Component = instance.type 49 | 50 | // ctx 51 | instance.proxy = new Proxy({ 52 | _: instance 53 | }, PublicInstanceProxyHandlers) 54 | 55 | // 解构 setup 56 | const { setup } = Component 57 | 58 | if (setup) { 59 | setCurrentInstance(instance) 60 | // 返回一个 function 或者是 Object 61 | // 如果是 function 则认为是 render 函数 62 | // 如果是 Object 则注入到当前组件的上下文中 63 | const setupResult = setup(shallowReadonly(instance.proxy), { emit: instance.emit }) 64 | setCurrentInstance(null) 65 | 66 | handleSetupResult(instance, setupResult) 67 | } 68 | } 69 | 70 | function handleSetupResult(instance, setupResult: any) { 71 | // TODO function 72 | 73 | if (isObject(setupResult)) { 74 | instance.setupState = proxyRefs(setupResult) 75 | } 76 | 77 | finishComponentSetup(instance) 78 | } 79 | 80 | function finishComponentSetup(instance: any) { 81 | const Component = instance.type 82 | 83 | // template => render 函数 84 | // 我们之前是直接调用 render 函数,但是用户不会传入 render 函数,只会传入 template 85 | // 所以我们需要调用 compile,但是又不能直接再 runtime-core 里面调用 86 | // 因为这样会形成强依赖关系 Vue3 支持单个包拆分使用 包之间不能直接引入模块的东西 87 | // Vue 可以只存在运行时,就不需要 compiler-core 88 | // 使用 webpack 或者 rollup 打包工具的时候,在运行前先把 template 编译成 render 函数 89 | // 线上运行的时候就可以直接跑这个 runtime-core 就行了,这样包就更小 90 | // Vue 给出的解决方案就是,先导入到 Vue 里面,然后再使用。这样就没有了强依赖关系 91 | if (compiler && !Component.render) { 92 | // 如果 compiler 存在并且 用户 没有传入 render 函数,如果用户传入的 render 函数,那么它的优先级会更高 93 | if(Component.template){ 94 | Component.render = compiler(Component.template) 95 | } 96 | } 97 | instance.render = Component.render 98 | } 99 | 100 | let currentInstance = null 101 | export function getCurrentInstance() { 102 | // 需要返回实例 103 | return currentInstance 104 | } 105 | 106 | // 赋值时 封装函数的好处 107 | // 我们可以清晰的知道 谁调用了 方便调试 108 | export function setCurrentInstance(instance) { 109 | currentInstance = instance; 110 | } 111 | 112 | let compiler; 113 | export function registerRuntimerCompiler(_compiler) { 114 | compiler = _compiler 115 | } -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { camelize, toHandlerKey } from "../shared/index" 2 | 3 | export function emit(instance, event, ...args) { 4 | const { props } = instance 5 | 6 | // TPP 7 | // 先去实现 特定行为,然后再重构成 通用行为 8 | // add -> Add -> onAdd 9 | // add-foo -> addFoo -> onAddFoo 10 | // const camelize = (str) => { 11 | // 需要将 str 中的 - 全部替换,斌且下一个要 设置成大写 12 | // \w 匹配字母或数字或下划线或汉字 等价于 '[^A-Za-z0-9_]'。 13 | // \s 匹配任意的空白符 14 | // \d 匹配数字 15 | // \b 匹配单词的开始或结束 16 | // ^ 匹配字符串的开始 17 | // $ 匹配字符串的结束 18 | // replace 第二参数是值得话就是直接替换 19 | // 如果是一个回调函数 那么 就可以依次的修改值 20 | // return str.replace(/-(\w)/g, (_, c: string) => { 21 | // return c ? c.toUpperCase() : '' 22 | // }) 23 | // } 24 | 25 | // const capitalize = (str) => { 26 | // return str.charAt(0).toUpperCase() + str.slice(1) 27 | // } 28 | 29 | // const toHandlerKey = (str) => { 30 | // return str ? "on" + capitalize(str) : '' 31 | // } 32 | 33 | const handler = props[toHandlerKey(camelize(event))] 34 | handler && handler(...args) 35 | } -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance, rawProps) { 2 | instance.props = rawProps || {} 3 | } -------------------------------------------------------------------------------- /src/runtime-core/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | // 通过 map 的方式扩展 2 | 3 | import { hasOwn } from "../shared/index"; 4 | 5 | // $el 是个 key 6 | const publicPropertiesMap = { 7 | $el: (i) => i.vnode.el, 8 | $slots: (i) => i.slots, 9 | $props: (i) => i.props 10 | } 11 | 12 | export const PublicInstanceProxyHandlers = { 13 | get({ _: instance }, key) { 14 | // setupState 15 | const { setupState, props } = instance 16 | // if (Reflect.has(setupState, key)) { 17 | // return setupState[key] 18 | // } 19 | 20 | // 检测 key 是否在目标 上 21 | if (hasOwn(setupState, key)) { 22 | return setupState[key] 23 | } else if (hasOwn(props, key)) { 24 | return props[key] 25 | } 26 | 27 | // key -> $el 28 | // if (key === "$el") { 29 | // return instance.vnode.el 30 | // } 31 | 32 | const publicGetter = publicPropertiesMap[key] 33 | if (publicGetter) { 34 | return publicGetter(instance) 35 | } 36 | 37 | // setup -> options data 38 | // $data 39 | } 40 | }; -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from './../shared/index'; 2 | import { ShapeFlags } from "../shared/shapeFlags" 3 | 4 | export function initSlots(instance, children) { 5 | // array 6 | // instance.slots = Array.isArray(children) ? children : [children] 7 | 8 | // object 9 | // const slots = {} 10 | // for (const key in children) { 11 | // const value = children[key]; 12 | // slots[key] = Array.isArray(value) ? value : [value] 13 | // } 14 | // instance.slots = slots 15 | 16 | 17 | // const slots = {} 18 | // for (const key in children) { 19 | // const value = children[key]; 20 | // slots[key] = (props) => normalizeSlotValue(value(props)) 21 | // } 22 | // instance.slots = slots 23 | 24 | // 优化 并不是所有的 children 都有 slots 25 | // 通过 位运算 来处理 26 | const { vnode } = instance 27 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) { 28 | normalizeObjectSlots(children, instance.slots) 29 | } 30 | } 31 | 32 | function normalizeObjectSlots(children, slots) { 33 | for (const key in children) { 34 | const value = children[key]; 35 | // slots[key] = Array.isArray(value) ? value : [value] 36 | // slots[key] = normalizeSlotValue(value) 37 | // 修改 当 是一个 函数的时候 直接调用 38 | slots[key] = (props) => normalizeSlotValue(value(props)) 39 | } 40 | } 41 | 42 | function normalizeSlotValue(value) { 43 | return isArray(value) ? value : [value] 44 | } -------------------------------------------------------------------------------- /src/runtime-core/componentUpdateUtils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUpdateComponent(prevVNode, nextVNode) { 2 | // 只有 props 发生了改变才需要更新 3 | const { props: prevProps } = prevVNode 4 | const { props: nextProps } = nextVNode 5 | 6 | for (const key in nextProps) { 7 | if (nextProps[key] != prevProps[key]) { 8 | return true 9 | } 10 | } 11 | return false 12 | } -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | // 因为 render 函数被包裹了 所以 调用 createApp 的时候传入 render 2 | // import { render } from "./renderer" 3 | import { createVNode } from "./vnode" 4 | 5 | // 为了让用户又能直接使用 createApp 所以 前往 renderer 导出一个 createApp 6 | export const createAppAPI = (render) => { 7 | return function createApp(rootComponent) { 8 | return { 9 | mount(rootContainer) { 10 | // 转换成 vdom 11 | // component -> vnode 12 | // 所有的逻辑操作 都会基于 vnode 做处理 13 | const vnode = createVNode(rootComponent) 14 | // !! bug render 是将虚拟 dom 渲染到 rootComponent 中 15 | render(vnode, rootContainer) 16 | } 17 | } 18 | } 19 | }; 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | 2 | import { createVNode } from "./vnode" 3 | 4 | export function h(type, props?, children?) { 5 | return createVNode(type, props, children) 6 | } -------------------------------------------------------------------------------- /src/runtime-core/helpers/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '../../shared/index'; 2 | import { createVNode, Fragment } from './../vnode'; 3 | 4 | export function renderSlots(slots, name, props) { 5 | const slot = slots[name] 6 | if (slot) { 7 | if (isFunction(slot)) { 8 | // 我们为了渲染 插槽中的 元素 主动在外层添加了一个 div -> component 9 | // 修改 直接变成 element -> mountChildren 10 | // Symbol 常量 Fragment 11 | return createVNode(Fragment, {}, slot(props)) 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-24 19:41:18 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 11:18:52 6 | */ 7 | // h 就是去调用我们的创建虚拟节点 8 | // 要按照 runtime-dom -> runtime-core -> reactivity 的顺序引入包 9 | // 这个也一个 源码拔高点 10 | export * from "../reactivity"; 11 | export { getCurrentInstance, registerRuntimerCompiler } from '../runtime-core/component'; 12 | export { toDisplayString } from '../shared'; 13 | export { inject, provide } from './apiInject'; 14 | export { h } from "./h"; 15 | export { renderSlots } from './helpers/renderSlots'; 16 | export { createRenderer } from './renderer'; 17 | export { nextTick } from './scheduler'; 18 | export { createElementVNode, createTextVNode } from './vnode'; 19 | -------------------------------------------------------------------------------- /src/runtime-core/renderer.ts: -------------------------------------------------------------------------------- 1 | import { Fragment, Text } from './vnode'; 2 | import { createComponentInstance, setupComponent } from "./component" 3 | import { ShapeFlags } from "../shared/shapeFlags" 4 | import { createAppAPI } from './createApp'; 5 | import { effect } from '../reactivity/effect'; 6 | import { shouldUpdateComponent } from './componentUpdateUtils'; 7 | import { queueJobs } from './scheduler'; 8 | 9 | // 使用闭包 createRenderer 函数 包裹所有的函数 10 | export function createRenderer(options) { 11 | const { 12 | createElement: hostCreateElement, 13 | patchProp: hostPatchProp, 14 | insert: hostInsert, 15 | remove: hostRemove, 16 | setElementText: hostSetElementText 17 | } = options 18 | 19 | function render(vnode, container) { 20 | // 只需要调用 patch 方法 21 | // 方便后续的递归处理 22 | patch(null, vnode, container, null, null) 23 | } 24 | 25 | function patch(n1, n2: any, container: any, parentComponent, anchor) { 26 | // TODO 去处理组件 27 | // 判断什么类型 28 | // 是 element 那么就应该去处理 element 29 | // 如何区分是 element 还是 component 类型??? 30 | // console.log(vnode.type); 31 | // object 是 component 32 | // div 是 element 33 | 34 | // debugger 35 | 36 | const { type, shapeFlag } = n2 37 | // 根据 type 来渲染 38 | // console.log(type); 39 | // Object 40 | // div/p -> String 41 | // Fragment 42 | // Text 43 | switch (type) { 44 | case Fragment: 45 | processFragment(n1, n2, container, parentComponent, anchor) 46 | break; 47 | case Text: 48 | processText(n1, n2, container) 49 | break; 50 | default: 51 | // 0001 & 0001 -> 0001 52 | if (shapeFlag & ShapeFlags.ELEMENT) { 53 | processElement(n1, n2, container, parentComponent, anchor) 54 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 55 | processComponent(n1, n2, container, parentComponent, anchor) 56 | } 57 | break; 58 | } 59 | } 60 | 61 | // 首先因为每次修改 响应式都会处理 element 62 | // 在 processElement 的时候就会判断 63 | // 如果是传入的 n1 存在 那就是新建 否则是更新 64 | // 更新 patchElement 又得进行两个节点的对比 65 | function processElement(n1, n2, container, parentComponent, anchor) { 66 | if (!n1) { 67 | // 初始化 68 | mountElement(n2, container, parentComponent, anchor) 69 | } else { 70 | patchElement(n1, n2, container, parentComponent, anchor) 71 | } 72 | } 73 | 74 | function patchElement(n1, n2, container, parentComponent, anchor) { 75 | console.log("n1", n1); 76 | console.log("n2", n2); 77 | 78 | // 新老节点 79 | const oldProps = n1.props || {} 80 | const newProps = n2.props || {} 81 | 82 | // n1 是老的虚拟节点 上有 el 在 mountElement 有赋值 83 | // 同时 要赋值 到 n2 上面 因为 mountElement 只有初始 84 | const el = (n2.el = n1.el) 85 | 86 | // 处理 87 | patchChildren(n1, n2, el, parentComponent, anchor) 88 | patchProps(el, oldProps, newProps) 89 | } 90 | 91 | function patchChildren(n1, n2, container, parentComponent, anchor) { 92 | // 常见有四种情况 93 | // array => text 94 | // text => array 95 | // text => text 96 | // array => array 97 | // 如何知道类型呢? 通过 shapeFlag 98 | const prevShapeFlag = n1.shapeFlag 99 | const c1 = n1.children 100 | const { shapeFlag } = n2 101 | const c2 = n2.children 102 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 103 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { 104 | // 1、要卸载原来的组件 105 | unmountChildren(n1.children) 106 | // 2、将 text 挂载上去 107 | } 108 | if (c1 !== c2) { 109 | hostSetElementText(container, c2) 110 | } 111 | } else { 112 | // 现在是 array 的情况 之前是 text 113 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { 114 | // 1、原先的 text 清空 115 | hostSetElementText(container, '') 116 | // 2、挂载现在的 array 117 | mountChildren(c2, container, parentComponent, anchor) 118 | } else { 119 | // 都是数组的情况就需要 120 | patchKeyedChildren(c1, c2, container, parentComponent, anchor) 121 | } 122 | } 123 | } 124 | 125 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) { 126 | const len2 = c2.length 127 | // 需要定义三个指针 128 | let i = 0 // 从新的节点开始 129 | let e1 = c1.length - 1 // 老的最后一个 索引值 130 | let e2 = len2 - 1 // 新的最后一个 索引值 131 | 132 | function isSomeVNodeType(n1, n2) { 133 | // 对比节点是否相等 可以通过 type 和 key 134 | return n1.type === n2.type && n1.key === n2.key 135 | } 136 | 137 | debugger 138 | // 左侧对比 移动 i 指针 139 | while (i <= e1 && i <= e2) { 140 | const n1 = c1[i]; 141 | const n2 = c2[i]; 142 | if (isSomeVNodeType(n1, n2)) { 143 | patch(n1, n2, container, parentComponent, parentAnchor); 144 | } else { 145 | break; 146 | } 147 | i++; 148 | } 149 | // 右侧对比 移动 e1 和 e2 指针 150 | while (i <= e1 && i <= e2) { 151 | const n1 = c1[e1]; 152 | const n2 = c2[e2]; 153 | if (isSomeVNodeType(n1, n2)) { 154 | patch(n1, n2, container, parentComponent, parentAnchor); 155 | } else { 156 | break; 157 | } 158 | e1--; 159 | e2--; 160 | } 161 | // 对比完两侧后 就要处理以下几种情况 162 | // 新的比老的多 创建 163 | if (i > e1) { 164 | if (i <= e2) { 165 | // 左侧 可以直接加在末尾 166 | // 右侧的话 我们就需要引入一个 概念 锚点 的概念 167 | // 通过 anchor 锚点 我们将新建的元素插入的指定的位置 168 | const nextPos = e2 + 1 169 | // 如果 e2 + 1 大于 c2 的 length 那就是最后一个 否则就是最先的元素 170 | // 锚点是一个 元素 171 | const anchor = nextPos < len2 ? c2[nextPos].el : null 172 | while (i <= e2) { 173 | patch(null, c2[i], container, parentComponent, anchor) 174 | i++ 175 | } 176 | } 177 | } else if (i > e2) { 178 | // 老的比新的多 删除 179 | // e1 就是 老的 最后一个 180 | while (i <= e1) { 181 | hostRemove(c1[i].el); 182 | i++; 183 | } 184 | } else { 185 | // 乱序部分 186 | // 遍历老节点 然后检查在新的里面是否存在 187 | // 方案一 同时遍历新的 时间复杂度 O(n*n) 188 | // 方案二 新的节点建立一个映射表 时间复杂度 O(1) 只要根据 key 去查是否存在 189 | // 为了性能最优 选则方案二 190 | let s1 = i // i 是停止的位置 差异开始的地方 191 | let s2 = i 192 | 193 | // 如果新的节点少于老的节点,当遍历完新的之后,就不需要再遍历了 194 | // 通过一个总数和一个遍历次数 来优化 195 | // 要遍历的数量 196 | const toBePatched = e2 - s2 + 1 197 | // 已经遍历的数量 198 | let patched = 0 199 | 200 | // 拆分问题 => 获取最长递增子序列 201 | // abcdefg -> 老 202 | // adecdfg -> 新 203 | // 1.确定新老节点之间的关系 新的元素在老的节点中的索引 e:4,c:2,d:3 204 | // newIndexToOldIndexMap 的初始值是一个定值数组,初始项都是 0,newIndexToOldIndexMap = [0,0,0] => [5,3,4] 加了1 因为 0 是有意义的。 205 | // 递增的索引值就是 [1,2] 206 | // 2.最长的递增子序列 [1,2] 对比 ecd 这个变动的序列 207 | // 利用两个指针 i 和 j 208 | // i 去遍历新的索引值 ecd [0,1,2] j 去遍历 [1,2] 209 | // 如果 i!=j 那么就是需要移动 210 | 211 | // 新建一个定长数组(需要变动的长度) 性能是最好的 来确定新老之间索引关系 我们要查到最长递增的子序列 也就是索引值 212 | const newIndexToOldIndexMap = new Array(toBePatched) 213 | // 确定是否需要移动 只要后一个索引值小于前一个 就需要移动 214 | let moved = false 215 | let maxNewIndexSoFar = 0 216 | // 赋值 217 | for (let i = 0; i < toBePatched; i++) { 218 | newIndexToOldIndexMap[i] = 0 219 | } 220 | 221 | // 建立新节点的映射表 222 | const keyToNewIndexMap = new Map() 223 | // 循环 e2 224 | for (let i = s2; i <= e2; i++) { 225 | const nextChild = c2[i]; 226 | keyToNewIndexMap.set(nextChild.key, i) 227 | } 228 | 229 | // 循环 e1 230 | for (let i = s1; i <= e1; i++) { 231 | const prevChild = c1[i]; 232 | 233 | if (patched >= toBePatched) { 234 | hostRemove(prevChild.el) 235 | continue 236 | } 237 | 238 | let newIndex 239 | if (prevChild.key !== null) { 240 | // 用户输入 key 241 | newIndex = keyToNewIndexMap.get(prevChild.key) 242 | } else { 243 | // 用户没有输入 key 244 | for (let j = s2; j < e2; j++) { 245 | if (isSomeVNodeType(prevChild, c2[j])) { 246 | newIndex = j; 247 | break; 248 | } 249 | } 250 | } 251 | 252 | if (newIndex === undefined) { 253 | hostRemove(prevChild.el) 254 | } else { 255 | if (newIndex >= maxNewIndexSoFar) { 256 | maxNewIndexSoFar = newIndex 257 | } else { 258 | moved = true 259 | } 260 | 261 | // 实际上是等于 i 就可以 因为 0 表示不存在 所以 定义成 i + 1 262 | newIndexToOldIndexMap[newIndex - s2] = i + 1 263 | 264 | // 存在就再次深度对比 265 | patch(prevChild, c2[newIndex], container, parentComponent, null) 266 | // patch 完就证明已经遍历完一个新的节点 267 | patched++ 268 | } 269 | } 270 | 271 | // 获取最长递增子序列 272 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [] 273 | 274 | let j = increasingNewIndexSequence.length - 1 275 | // 倒序的好处就是 能够确定稳定的位置 276 | // ecdf 277 | // cdef 278 | // 如果是从 f 开始就能确定 e 的位置 279 | // 从最后开始就能依次确定位置 280 | for (let i = toBePatched; i >= 0; i--) { 281 | const nextIndex = i + s2 282 | const nextChild = c2[nextIndex] 283 | const anchor = nextIndex + 1 < len2 ? c2[nextIndex + 1].el : null 284 | if (newIndexToOldIndexMap[i] === 0) { 285 | patch(null, nextChild, container, parentComponent, anchor) 286 | } else if (moved) { 287 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 288 | // 移动位置 调用 insert 289 | hostInsert(nextChild.el, container, anchor) 290 | } else { 291 | j++ 292 | } 293 | } 294 | } 295 | } 296 | } 297 | 298 | function unmountChildren(children) { 299 | for (let i = 0; i < children.length; i++) { 300 | hostRemove(children[i].el) 301 | } 302 | } 303 | 304 | function patchProps(el, oldProps, newProps) { 305 | // 常见的有三种情况 306 | // 值改变了 => 删除 307 | // 值变成了 null 或 undefined => 删除 308 | // 增加了 => 增加 309 | if (oldProps !== newProps) { 310 | for (const key in newProps) { 311 | const prevProp = oldProps[key] 312 | const nextProp = newProps[key] 313 | if (prevProp !== nextProp) { 314 | hostPatchProp(el, key, prevProp, nextProp) 315 | } 316 | } 317 | } 318 | 319 | // 处理值 变成 null 或 undefined 的情况 320 | // 新的就不会有 所以遍历老的 oldProps 看是否存在于新的里面 321 | if (oldProps !== {}) { 322 | for (const key in oldProps) { 323 | if (!(key in newProps)) { 324 | hostPatchProp(el, key, oldProps[key], null) 325 | } 326 | } 327 | } 328 | } 329 | 330 | function processComponent(n1, n2: any, container: any, parentComponent, anchor) { 331 | if (!n1) { 332 | // 挂载组件 333 | mountComponent(n2, container, parentComponent, anchor) 334 | } else { 335 | // 更新组件 336 | updateComponent(n1, n2) 337 | } 338 | } 339 | 340 | function updateComponent(n1, n2) { 341 | // 更新实际上只需要想办法 调用 render 函数 然后再 patch 去更新 342 | // instance 从哪里来呢? 在挂载阶段 我们会生成 instance 然后挂载到 虚拟dom 上 343 | // n2 没有 所以要赋值 344 | const instance = n2.component = n1.component 345 | 346 | // 只有但子组件的 props 发生了改变才需要更新 347 | if (shouldUpdateComponent(n1, n2)) { 348 | // 然后再把 n2 设置为下次需要更新的 虚拟 dom 349 | instance.next = n2 350 | instance.update() 351 | } else { 352 | n2.el = n1.el 353 | n2.vnode = n2 354 | } 355 | } 356 | 357 | 358 | function mountComponent(initialVNode, container, parentComponent, anchor) { 359 | // 创建组件实例 360 | // 这个实例上面有很多属性 361 | const instance = initialVNode.component = createComponentInstance(initialVNode, parentComponent) 362 | 363 | // 初始化 364 | setupComponent(instance) 365 | 366 | // 调用 render 函数 367 | setupRenderEffect(instance, initialVNode, container, anchor) 368 | } 369 | 370 | function mountElement(vnode: any, container: any, parentComponent, anchor) { 371 | // const el = document.createElement("div") 372 | // string 或 array 373 | // el.textContent = "hi , mini-vue" 374 | // el.setAttribute("id", "root") 375 | // document.body.append(el) 376 | // 这里的 vnode -> element -> div 377 | 378 | // 自定义渲染器 379 | // 修改一 hostCreateElement 380 | // canvas 是 new Element() 381 | // const el = vnode.el = document.createElement(vnode.type) 382 | const el = vnode.el = hostCreateElement(vnode.type) 383 | const { children, shapeFlag } = vnode 384 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 385 | el.textContent = children 386 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 387 | mountChildren(children, el, parentComponent, anchor) 388 | } 389 | 390 | // 修改二 hostPatchProp 391 | // props 392 | const { props } = vnode 393 | for (const key in props) { 394 | const val = props[key] 395 | // onClick 、 onMouseenter 等等这些的共同特征 396 | // 以 on 开头 + 一个大写字母 397 | // if (isOn(key)) { 398 | // const event = key.slice(2).toLowerCase() 399 | // el.addEventListener(event, val); 400 | // } else { 401 | // el.setAttribute(key, val) 402 | // } 403 | hostPatchProp(el, key, null, val) 404 | } 405 | 406 | // 修改三 canvas 添加元素 407 | // el.x = 10 408 | // container.append(el) 409 | // canvas 中添加元素是 addChild() 410 | // container.append(el) 411 | hostInsert(el, container, anchor) 412 | } 413 | 414 | function mountChildren(children, container, parentComponent, anchor) { 415 | children.forEach((v) => { 416 | patch(null, v, container, parentComponent, anchor) 417 | }) 418 | } 419 | 420 | function setupRenderEffect(instance, initialVNode, container, anchor) { 421 | // 将 effect 放在 instance 实例身上 422 | instance.update = effect(() => { 423 | if (!instance.isMount) { 424 | console.log('init'); 425 | const { proxy } = instance 426 | // 虚拟节点树 427 | // 一开始是创建在 instance 上 428 | // 在这里就绑定 this 429 | const subTree = instance.subTree = instance.render.call(proxy, proxy) 430 | // vnode -> patch 431 | // vnode -> element -> mountElement 432 | patch(null, subTree, container, instance, null) 433 | // 所有的 element -> mount 434 | initialVNode.el = subTree.el 435 | instance.isMount = true 436 | } else { 437 | console.log('update'); 438 | // next 是下一个 要更新的 vnode 是老的 439 | const { next, vnode } = instance 440 | if (next) { 441 | next.el = vnode.el 442 | updateComponentPreRender(instance, next); 443 | } 444 | 445 | const { proxy } = instance 446 | // 当前的虚拟节点树 447 | const subTree = instance.render.call(proxy, proxy) 448 | // 老的虚拟节点树 449 | const prevSubTree = instance.subTree 450 | instance.subTree = subTree 451 | patch(prevSubTree, subTree, container, instance, anchor) 452 | } 453 | }, { 454 | scheduler() { 455 | queueJobs(instance.update) 456 | } 457 | }) 458 | } 459 | 460 | function processFragment(n1, n2: any, container: any, parentComponent, anchor) { 461 | // 此时,拿出 vnode 中的 children 462 | mountChildren(n2.children, container, parentComponent, anchor) 463 | } 464 | 465 | function processText(n1, n2: any, container: any) { 466 | // console.log(vnode); 467 | // 文本内容 在 children 中 468 | const { children } = n2 469 | // 创建文本节点 470 | const textNode = n2.el = document.createTextNode(children) 471 | // 挂载到容器中 472 | container.append(textNode); 473 | } 474 | 475 | // 为了让用户又能直接使用 createApp 所以导出一个 createApp 476 | return { 477 | createApp: createAppAPI(render) 478 | } 479 | } 480 | 481 | function updateComponentPreRender(instance, nextVNode) { 482 | instance.vnode = nextVNode 483 | instance.next = null 484 | 485 | // 然后就是更新 props 486 | // 这里只是简单的赋值 487 | instance.props = nextVNode.props 488 | } 489 | 490 | function getSequence(arr) { 491 | const p = arr.slice(); 492 | const result = [0]; 493 | let i, j, u, v, c; 494 | const len = arr.length; 495 | for (i = 0; i < len; i++) { 496 | const arrI = arr[i]; 497 | if (arrI !== 0) { 498 | j = result[result.length - 1]; 499 | if (arr[j] < arrI) { 500 | p[i] = j; 501 | result.push(i); 502 | continue; 503 | } 504 | u = 0; 505 | v = result.length - 1; 506 | while (u < v) { 507 | c = (u + v) >> 1; 508 | if (arr[result[c]] < arrI) { 509 | u = c + 1; 510 | } else { 511 | v = c; 512 | } 513 | } 514 | if (arrI < arr[result[u]]) { 515 | if (u > 0) { 516 | p[i] = result[u - 1]; 517 | } 518 | result[u] = i; 519 | } 520 | } 521 | } 522 | u = result.length; 523 | v = result[u - 1]; 524 | while (u-- > 0) { 525 | result[u] = v; 526 | v = p[v]; 527 | } 528 | return result; 529 | } -------------------------------------------------------------------------------- /src/runtime-core/scheduler.ts: -------------------------------------------------------------------------------- 1 | const queue: any[] = [] 2 | 3 | // 通过一个策略 只生成一个 promise 4 | let isFlushPending = false 5 | 6 | const p = Promise.resolve() 7 | 8 | // nextTick 执行的时间 就是把 fn 推到微任务 9 | export function nextTick(fn) { 10 | // 传了就执行 没传就 等待到微任务执行的时候 11 | return fn ? p.then(fn) : p 12 | } 13 | 14 | export function queueJobs(job) { 15 | if (!queue.includes(job)) { 16 | queue.push(job) 17 | } 18 | 19 | queueFlush() 20 | } 21 | 22 | function queueFlush() { 23 | if (isFlushPending) return 24 | isFlushPending = true 25 | // 然后就是就是生成一个 微任务 26 | // 如何生成微任务? 27 | // p.then(() => { 28 | // isFlushPending = false 29 | // let job 30 | // while (job = queue.shift()) { 31 | // job & job() 32 | // } 33 | // }) 34 | 35 | nextTick(flushJob) 36 | } 37 | 38 | function flushJob() { 39 | isFlushPending = false 40 | let job 41 | while (job = queue.shift()) { 42 | job & job() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/runtime-core/tests/apiWatch.spec.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from "../../reactivity/reactive"; 2 | import { ref } from "../../reactivity/ref"; 3 | import { watch, watchEffect } from "../apiWatch"; 4 | 5 | // watch 是用来监控数据 接受三个参数 属性、cb、{} 6 | // 实现 watch 步骤 7 | // 1、收集相关的依赖 8 | // 2、相关变量发生改变的时候,触发更新 9 | 10 | describe("api: watch", () => { 11 | it('effect', async () => { 12 | const state = reactive({ count: 0 }) 13 | let dummy 14 | watchEffect(() => { 15 | dummy = state.count 16 | }) 17 | 18 | expect(dummy).toBe(0) 19 | 20 | state.count++ 21 | 22 | expect(dummy).toBe(1) 23 | }) 24 | 25 | it("reactive", async () => { 26 | const state = reactive({ count: 0 }); 27 | const dummy = ref(0); 28 | watch( 29 | () => state.count, 30 | (newValue, oldValue) => { 31 | console.log('oldValue',oldValue); 32 | dummy.value = state.count; 33 | }, 34 | { 35 | // 回调函数会在 watch 创建的时候执行一次 36 | immediate: true, 37 | // 除了可以使用 immediate 之外,还可以使用 flush 指定调度函数的执行时间 38 | // flush: "pre", 39 | } 40 | ); 41 | 42 | expect(dummy.value).toBe(0); 43 | 44 | state.count++; 45 | 46 | // expect(dummy.value).toBe(1); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-24 19:41:18 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 11:14:33 6 | */ 7 | import { isArray, isObject, isString } from "../shared/index"; 8 | import { ShapeFlags } from "../shared/shapeFlags"; 9 | 10 | export const Fragment = Symbol('Fragment'); 11 | export const Text = Symbol('Text'); 12 | 13 | export { 14 | createVNode as createElementVNode 15 | } 16 | 17 | export function createVNode(type, props?, children?) { 18 | const vnode = { 19 | type, 20 | props, 21 | children, 22 | component: null, // 保存当前的实例 23 | key: props && props.key, 24 | shapeFlag: getShapeFlag(type), 25 | el: null 26 | } 27 | 28 | // children 29 | if (isString(children)) { 30 | // vnode.shapeFlag = vnode.shapeFlag | ShapeFlags.TEXT_CHILDREN 31 | // | 两位都为 0 才为 0 32 | // 0100 | 0100 = 0100 33 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN 34 | } else if (isArray(children)) { 35 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN 36 | } 37 | 38 | // 组件类型 + children 是 object 就有 slot 39 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 40 | if (isObject(children)) { 41 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN 42 | } 43 | } 44 | 45 | return vnode 46 | } 47 | 48 | function getShapeFlag(type: any) { 49 | // string -> div -> element 50 | return isString(type) ? ShapeFlags.ELEMENT : ShapeFlags.STATEFUL_COMPONENT 51 | } 52 | 53 | export function createTextVNode(text: string) { 54 | return createVNode(Text, {}, text) 55 | } -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from ".."; 2 | import { isOn } from "../shared"; 3 | 4 | function createElement(type) { 5 | return document.createElement(type) 6 | } 7 | 8 | function patchProp(el, key, prevVal, nextVal) { 9 | if (isOn(key)) { 10 | const event = key.slice(2).toLowerCase() 11 | el.addEventListener(event, nextVal); 12 | } else { 13 | if (nextVal === undefined || nextVal === null) { 14 | el.removeAttribute(key); 15 | } else { 16 | el.setAttribute(key, nextVal) 17 | } 18 | } 19 | } 20 | 21 | function insert(child, parent, anchor) { 22 | // insertBefore 是把指定的元素添加到指定的位置 23 | // 如果没有传入 anchor 那就相当于 append(child) 24 | parent.insertBefore(child, anchor || null) 25 | } 26 | 27 | function remove(child) { 28 | // 拿到父级节点 然后删除子节点 29 | // 调用原生 dom 删除节点 30 | const parent = child.parentNode 31 | if (parent) { 32 | parent.removeChild(child); 33 | } 34 | } 35 | 36 | function setElementText(el, text) { 37 | el.textContent = text 38 | } 39 | 40 | 41 | // 调用 renderer.ts 中的 createRenderer 42 | const renderer: any = createRenderer({ 43 | createElement, 44 | patchProp, 45 | insert, 46 | remove, 47 | setElementText 48 | }) 49 | 50 | // 这样用户就可以正常的使用 createApp 了 51 | export function createApp(...args) { 52 | return renderer.createApp(...args) 53 | } 54 | 55 | // 并且让 runtime-core 作为 runtime-dom 的子级 56 | export * from '../runtime-core'; -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-24 19:41:18 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 11:12:59 6 | */ 7 | export * from './toDisplayString'; 8 | 9 | export const NOOP = () => { } 10 | 11 | export const extend = Object.assign; 12 | 13 | export const isObject = (value) => { 14 | return value !== null && typeof value === "object" 15 | }; 16 | 17 | export const isFunction = (value) => { 18 | return value !== null && typeof value === "function" 19 | }; 20 | 21 | export const isString = (value) => { 22 | return value !== null && typeof value === "string" 23 | }; 24 | 25 | export const isArray = (value) => { 26 | return value !== null && Array.isArray(value) 27 | }; 28 | 29 | export const hasChanged = (value, newValue) => { return !Object.is(value, newValue) }; 30 | 31 | export const isOn = (key) => { 32 | return /^on[A-Z]/.test(key) 33 | }; 34 | 35 | export const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key) 36 | 37 | export const camelize = (str) => { 38 | // 需要将 str 中的 - 全部替换,斌且下一个要 设置成大写 39 | // \w 匹配字母或数字或下划线或汉字 等价于 '[^A-Za-z0-9_]'。 40 | // \s 匹配任意的空白符 41 | // \d 匹配数字 42 | // \b 匹配单词的开始或结束 43 | // ^ 匹配字符串的开始 44 | // $ 匹配字符串的结束 45 | // replace 第二参数是值得话就是直接替换 46 | // 如果是一个回调函数 那么 就可以依次的修改值 47 | return str.replace(/-(\w)/g, (_, c: string) => { 48 | return c ? c.toUpperCase() : '' 49 | }) 50 | } 51 | 52 | export const capitalize = (str) => { 53 | return str.charAt(0).toUpperCase() + str.slice(1) 54 | } 55 | 56 | export const toHandlerKey = (str) => { 57 | return str ? "on" + capitalize(str) : '' 58 | } 59 | -------------------------------------------------------------------------------- /src/shared/shapeFlags.ts: -------------------------------------------------------------------------------- 1 | // 使用 对象 -> key 的方式固然能实现,当是不够高效 2 | // 计算机最高效的是 位运算 都不用浏览器转换代码 3 | // const ShapeFlags = { 4 | // element: 0, 5 | // stateful_component: 0, 6 | // text_children: 0, 7 | // array_children: 0 8 | // } 9 | 10 | // => 修改 左移 乘以2 右移 除以2k 11 | export const enum ShapeFlags { 12 | ELEMENT = 1,// 0001 13 | STATEFUL_COMPONENT = 1 << 1,// 0010 14 | TEXT_CHILDREN = 1 << 2, // 0100 15 | ARRAY_CHILDREN = 1 << 3, // 1000 16 | SLOT_CHILDREN = 1 << 4, // 10000 17 | }; 18 | 19 | // 修改 20 | // 如果 vnode-> stateful_component set == 1 21 | // ShapeFlags.stateful_component = 1 22 | 23 | // 判断 24 | // if(ShapeFlags.element) 25 | 26 | // 对象和 key 的方式 不够高效 27 | // 通过 位运算的方式解决 高效问题 28 | // 0000 29 | // 0001 element 30 | // 0010 stateful 31 | // 0100 text 32 | // 1000 array 33 | 34 | // 1010 表示 stateful 和 array 35 | 36 | // | 或 两位都为0 才为0 37 | // & 与 两位都为1 才为1 38 | 39 | // 修改 40 | // 0000 41 | // | 0001 42 | // =>0001 43 | 44 | // 查找 45 | // 0001 46 | // & 0001 47 | // =>0001 -------------------------------------------------------------------------------- /src/shared/toDisplayString.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Stone 3 | * @Date: 2022-04-28 11:12:06 4 | * @LastEditors: Stone 5 | * @LastEditTime: 2022-04-28 11:12:06 6 | */ 7 | export function toDisplayString(value) { 8 | return String(value) 9 | } -------------------------------------------------------------------------------- /src/vue/compiler-sfc/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@vue/compiler-sfc') -------------------------------------------------------------------------------- /src/vue/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue", 3 | "version": "3.2.36", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/parser": { 8 | "version": "7.18.4", 9 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", 10 | "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==" 11 | }, 12 | "@vue/compiler-core": { 13 | "version": "3.2.36", 14 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.36.tgz", 15 | "integrity": "sha512-bbyZM5hvBicv0PW3KUfVi+x3ylHnfKG7DOn5wM+f2OztTzTjLEyBb/5yrarIYpmnGitVGbjZqDbODyW4iK8hqw==", 16 | "requires": { 17 | "@babel/parser": "^7.16.4", 18 | "@vue/shared": "3.2.36", 19 | "estree-walker": "^2.0.2", 20 | "source-map": "^0.6.1" 21 | } 22 | }, 23 | "@vue/compiler-dom": { 24 | "version": "3.2.36", 25 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.36.tgz", 26 | "integrity": "sha512-tcOTAOiW4s24QLnq+ON6J+GRONXJ+A/mqKCORi0LSlIh8XQlNnlm24y8xIL8la+ZDgkdbjarQ9ZqYSvEja6gVA==", 27 | "requires": { 28 | "@vue/compiler-core": "3.2.36", 29 | "@vue/shared": "3.2.36" 30 | } 31 | }, 32 | "@vue/compiler-sfc": { 33 | "version": "3.2.36", 34 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.36.tgz", 35 | "integrity": "sha512-AvGb4bTj4W8uQ4BqaSxo7UwTEqX5utdRSMyHy58OragWlt8nEACQ9mIeQh3K4di4/SX+41+pJrLIY01lHAOFOA==", 36 | "requires": { 37 | "@babel/parser": "^7.16.4", 38 | "@vue/compiler-core": "3.2.36", 39 | "@vue/compiler-dom": "3.2.36", 40 | "@vue/compiler-ssr": "3.2.36", 41 | "@vue/reactivity-transform": "3.2.36", 42 | "@vue/shared": "3.2.36", 43 | "estree-walker": "^2.0.2", 44 | "magic-string": "^0.25.7", 45 | "postcss": "^8.1.10", 46 | "source-map": "^0.6.1" 47 | } 48 | }, 49 | "@vue/compiler-ssr": { 50 | "version": "3.2.36", 51 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.36.tgz", 52 | "integrity": "sha512-+KugInUFRvOxEdLkZwE+W43BqHyhBh0jpYXhmqw1xGq2dmE6J9eZ8UUSOKNhdHtQ/iNLWWeK/wPZkVLUf3YGaw==", 53 | "requires": { 54 | "@vue/compiler-dom": "3.2.36", 55 | "@vue/shared": "3.2.36" 56 | } 57 | }, 58 | "@vue/reactivity": { 59 | "version": "3.2.36", 60 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.36.tgz", 61 | "integrity": "sha512-c2qvopo0crh9A4GXi2/2kfGYMxsJW4tVILrqRPydVGZHhq0fnzy6qmclWOhBFckEhmyxmpHpdJtIRYGeKcuhnA==", 62 | "requires": { 63 | "@vue/shared": "3.2.36" 64 | } 65 | }, 66 | "@vue/reactivity-transform": { 67 | "version": "3.2.36", 68 | "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.36.tgz", 69 | "integrity": "sha512-Jk5o2BhpODC9XTA7o4EL8hSJ4JyrFWErLtClG3NH8wDS7ri9jBDWxI7/549T7JY9uilKsaNM+4pJASLj5dtRwA==", 70 | "requires": { 71 | "@babel/parser": "^7.16.4", 72 | "@vue/compiler-core": "3.2.36", 73 | "@vue/shared": "3.2.36", 74 | "estree-walker": "^2.0.2", 75 | "magic-string": "^0.25.7" 76 | } 77 | }, 78 | "@vue/runtime-core": { 79 | "version": "3.2.36", 80 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.36.tgz", 81 | "integrity": "sha512-PTWBD+Lub+1U3/KhbCExrfxyS14hstLX+cBboxVHaz+kXoiDLNDEYAovPtxeTutbqtClIXtft+wcGdC+FUQ9qQ==", 82 | "requires": { 83 | "@vue/reactivity": "3.2.36", 84 | "@vue/shared": "3.2.36" 85 | } 86 | }, 87 | "@vue/runtime-dom": { 88 | "version": "3.2.36", 89 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.36.tgz", 90 | "integrity": "sha512-gYPYblm7QXHVuBohqNRRT7Wez0f2Mx2D40rb4fleehrJU9CnkjG0phhcGEZFfGwCmHZRqBCRgbFWE98bPULqkg==", 91 | "requires": { 92 | "@vue/runtime-core": "3.2.36", 93 | "@vue/shared": "3.2.36", 94 | "csstype": "^2.6.8" 95 | } 96 | }, 97 | "@vue/server-renderer": { 98 | "version": "3.2.36", 99 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.36.tgz", 100 | "integrity": "sha512-uZE0+jfye6yYXWvAQYeHZv+f50sRryvy16uiqzk3jn8hEY8zTjI+rzlmZSGoE915k+W/Ol9XSw6vxOUD8dGkUg==", 101 | "requires": { 102 | "@vue/compiler-ssr": "3.2.36", 103 | "@vue/shared": "3.2.36" 104 | } 105 | }, 106 | "@vue/shared": { 107 | "version": "3.2.36", 108 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.36.tgz", 109 | "integrity": "sha512-JtB41wXl7Au3+Nl3gD16Cfpj7k/6aCroZ6BbOiCMFCMvrOpkg/qQUXTso2XowaNqBbnkuGHurLAqkLBxNGc1hQ==" 110 | }, 111 | "csstype": { 112 | "version": "2.6.20", 113 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", 114 | "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" 115 | }, 116 | "estree-walker": { 117 | "version": "2.0.2", 118 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 119 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 120 | }, 121 | "magic-string": { 122 | "version": "0.25.9", 123 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", 124 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", 125 | "requires": { 126 | "sourcemap-codec": "^1.4.8" 127 | } 128 | }, 129 | "nanoid": { 130 | "version": "3.3.4", 131 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", 132 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" 133 | }, 134 | "picocolors": { 135 | "version": "1.0.0", 136 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 137 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 138 | }, 139 | "postcss": { 140 | "version": "8.4.14", 141 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", 142 | "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", 143 | "requires": { 144 | "nanoid": "^3.3.4", 145 | "picocolors": "^1.0.0", 146 | "source-map-js": "^1.0.2" 147 | } 148 | }, 149 | "source-map": { 150 | "version": "0.6.1", 151 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 152 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 153 | }, 154 | "source-map-js": { 155 | "version": "1.0.2", 156 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 157 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" 158 | }, 159 | "sourcemap-codec": { 160 | "version": "1.4.8", 161 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 162 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue", 3 | "version": "3.2.36", 4 | "description": "The progressive JavaScript framework for building modern web UI.", 5 | "main": "index.js", 6 | "module": "dist/vue.runtime.esm-bundler.js", 7 | "types": "dist/vue.d.ts", 8 | "unpkg": "dist/vue.global.js", 9 | "jsdelivr": "dist/vue.global.js", 10 | "files": [ 11 | "index.js", 12 | "index.mjs", 13 | "dist", 14 | "compiler-sfc", 15 | "server-renderer", 16 | "macros.d.ts", 17 | "macros-global.d.ts", 18 | "ref-macros.d.ts" 19 | ], 20 | "exports": { 21 | ".": { 22 | "import": { 23 | "node": "./index.mjs", 24 | "default": "./dist/vue.runtime.esm-bundler.js" 25 | }, 26 | "require": "./index.js", 27 | "types": "./dist/vue.d.ts" 28 | }, 29 | "./server-renderer": { 30 | "import": "./server-renderer/index.mjs", 31 | "require": "./server-renderer/index.js" 32 | }, 33 | "./compiler-sfc": { 34 | "import": "./compiler-sfc/index.mjs", 35 | "require": "./compiler-sfc/index.js" 36 | }, 37 | "./dist/*": "./dist/*", 38 | "./package.json": "./package.json", 39 | "./macros": "./macros.d.ts", 40 | "./macros-global": "./macros-global.d.ts", 41 | "./ref-macros": "./ref-macros.d.ts" 42 | }, 43 | "buildOptions": { 44 | "name": "Vue", 45 | "formats": [ 46 | "esm-bundler", 47 | "esm-bundler-runtime", 48 | "cjs", 49 | "global", 50 | "global-runtime", 51 | "esm-browser", 52 | "esm-browser-runtime" 53 | ] 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/vuejs/core.git" 58 | }, 59 | "keywords": [ 60 | "vue" 61 | ], 62 | "author": "Evan You", 63 | "license": "MIT", 64 | "bugs": { 65 | "url": "https://github.com/vuejs/core/issues" 66 | }, 67 | "homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme", 68 | "dependencies": { 69 | "@vue/shared": "3.2.36", 70 | "@vue/compiler-dom": "3.2.36", 71 | "@vue/runtime-dom": "3.2.36", 72 | "@vue/compiler-sfc": "3.2.36", 73 | "@vue/server-renderer": "3.2.36" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/vue/src/dev.ts: -------------------------------------------------------------------------------- 1 | import { initCustomFormatter } from '@vue/runtime-dom' 2 | 3 | export function initDev() { 4 | // 如果是 __BROWSER__ 浏览器环境 5 | // if (__BROWSER__) { 6 | // // 如果不是 esm bundler 打包器 7 | // if (!__ESM_BUNDLER__) { 8 | // console.info( 9 | // `You are running a development build of Vue.\n` + 10 | // `Make sure to use the production build (*.prod.js) when deploying for production.` 11 | // ) 12 | // } 13 | 14 | // initCustomFormatter() 15 | // } 16 | initCustomFormatter() 17 | } 18 | -------------------------------------------------------------------------------- /src/vue/src/index.ts: -------------------------------------------------------------------------------- 1 | // This entry is the "full-build" that includes both the runtime 2 | // and the compiler, and supports on-the-fly compilation of the template option. 3 | import { initDev } from "./dev"; 4 | import { compile, CompilerOptions, CompilerError } from "@vue/compiler-dom"; 5 | import { 6 | registerRuntimeCompiler, 7 | RenderFunction, 8 | warn, 9 | } from "@vue/runtime-dom"; 10 | import * as runtimeDom from "@vue/runtime-dom"; 11 | import { isString, NOOP, generateCodeFrame, extend } from "@vue/shared"; 12 | // import { InternalRenderFunction } from 'packages/runtime-core/src/component' 13 | type InternalRenderFunction = { 14 | // ( 15 | // ctx: ComponentPublicInstance, 16 | // cache: ComponentInternalInstance['renderCache'], 17 | // // for compiler-optimized bindings 18 | // $props: ComponentInternalInstance['props'], 19 | // $setup: ComponentInternalInstance['setupState'], 20 | // $data: ComponentInternalInstance['data'], 21 | // $options: ComponentInternalInstance['ctx'] 22 | // ): VNodeChild 23 | _rc?: boolean; // isRuntimeCompiled 24 | 25 | // __COMPAT__ only 26 | _compatChecked?: boolean; // v3 and already checked for v2 compat 27 | _compatWrapped?: boolean; // is wrapped for v2 compat 28 | }; 29 | 30 | // 入口文件流程 31 | // 1、依赖注入编译函数至 runtime registerRuntimeCompile(compileToFunction) 32 | // 2、runtime 调用编译函数 compileToFunction 33 | // 3、调用 compile 函数对模板进行解析,并返回 code 34 | // a. 先 parse 解析成 AST 语树 35 | // b. 再通过 transform 解析成 JavaScript(转换 v-on、v-if、v-for 等指令 ) 36 | // c. 再通过 generate 解析成 code 37 | // 4、将 code 作为参数传入 Function 的构造函数中,并且将生成的函数赋值给 render 变量 38 | // 5、将 render 函数作为编译结果返回 39 | 40 | const __DEV__ = true; // dev 环境,自己加的 41 | 42 | if (__DEV__) { 43 | initDev(); 44 | } 45 | 46 | // 编译缓存 Object.create(null) 创建了一个空对象 用途:缓存已经编译过的模板 47 | const compileCache: Record = Object.create(null); 48 | 49 | // vue 的入口文件 50 | function compileToFunction( 51 | template: string | HTMLElement, 52 | options?: CompilerOptions 53 | ): RenderFunction { 54 | // 如果不是 String 先处理一下 可忽略 55 | if (!isString(template)) { 56 | if (template.nodeType) { 57 | template = template.innerHTML; 58 | } else { 59 | __DEV__ && warn(`invalid template option: `, template); 60 | return NOOP; 61 | } 62 | } 63 | 64 | const key = template; 65 | // 添加缓存 存在就直接返回 不存在就是还需要进行编译 66 | const cached = compileCache[key]; 67 | if (cached) return cached; 68 | 69 | if (template[0] === "#") { 70 | const el = document.querySelector(template); 71 | if (__DEV__ && !el) { 72 | warn(`Template element not found or is empty: ${template}`); 73 | } 74 | // __UNSAFE__ 75 | // Reason: potential execution of JS expressions in in-DOM template. 76 | // The user must make sure the in-DOM template is trusted. If it's rendered 77 | // by the server, the template should not contain any user data. 78 | template = el ? el.innerHTML : ``; 79 | } 80 | 81 | // code 就是从 compile 返回的对象解构出来的 82 | const { code } = compile( 83 | template, 84 | extend( 85 | { 86 | hoistStatic: true, 87 | onError: __DEV__ ? onError : undefined, 88 | onWarn: __DEV__ ? (e) => onError(e, true) : NOOP, 89 | } as CompilerOptions, 90 | options 91 | ) 92 | ); 93 | 94 | function onError(err: CompilerError, asWarning = false) { 95 | const message = asWarning 96 | ? err.message 97 | : `Template compilation error: ${err.message}`; 98 | const codeFrame = 99 | err.loc && 100 | generateCodeFrame( 101 | template as string, 102 | err.loc.start.offset, 103 | err.loc.end.offset 104 | ); 105 | warn(codeFrame ? `${message}\n${codeFrame}` : message); 106 | } 107 | 108 | // The wildcard import results in a huge object with every export 109 | // with keys that cannot be mangled, and can be quite heavy size-wise. 110 | // In the global build we know `Vue` is available globally so we can avoid 111 | // the wildcard object. 112 | const render = 113 | // __GLOBAL__ ? new Function(code)() : new Function("Vue", code)(runtimeDom) 114 | // Vue3 可以支持自定义渲染器,即自定义传入不同的平台的 api,runtimeDom 是适用于 web 115 | new Function("Vue", code)(runtimeDom) as RenderFunction; 116 | 117 | // mark the function as runtime compiled 118 | (render as InternalRenderFunction)._rc = true; 119 | 120 | return (compileCache[key] = render); 121 | } 122 | 123 | registerRuntimeCompiler(compileToFunction); 124 | 125 | export { compileToFunction as compile }; 126 | export * from "@vue/runtime-dom"; 127 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es2016" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 8 | "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | "lib": [ 10 | "DOM", 11 | "ES6", 12 | "ES2016" 13 | ] /* Specify library files to be included in the compilation. */, 14 | // "allowJs": true, /* Allow javascript files to be compiled. */ 15 | // "checkJs": true, /* Report errors in .js files. */ 16 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 17 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 18 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 19 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 20 | // "outFile": "./", /* Concatenate and emit output to single file. */ 21 | // "outDir": "./", /* Redirect output structure to the directory. */ 22 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 23 | // "composite": true, /* Enable project compilation */ 24 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 25 | // "removeComments": true, /* Do not emit comments to output. */ 26 | // "noEmit": true, /* Do not emit outputs. */ 27 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 28 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 29 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 30 | 31 | /* Strict Type-Checking Options */ 32 | "strict": true /* Enable all strict type-checking options. */, 33 | "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, 34 | // "strictNullChecks": true, /* Enable strict null checks. */ 35 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 36 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 37 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 38 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 39 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 40 | 41 | /* Additional Checks */ 42 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 43 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 44 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 45 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 46 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 47 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 48 | 49 | /* Module Resolution Options */ 50 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 51 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 52 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 53 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 54 | // "typeRoots": [], /* List of folders to include type definitions from. */ 55 | // "types": [], /* Type declaration files to be included in compilation. */ 56 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 57 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 58 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 59 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 60 | 61 | /* Source Map Options */ 62 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 63 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 64 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 65 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 66 | 67 | /* Experimental Options */ 68 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 69 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 70 | 71 | /* Advanced Options */ 72 | "skipLibCheck": true /* Skip type checking of declaration files. */, 73 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 74 | } 75 | } 76 | --------------------------------------------------------------------------------