├── .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('')) {
1512 | for (let i = ancestors.length - 1; i >= 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("") &&
1524 | source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase());
1525 | }
1526 | function parseText(context) {
1527 | const endToken = ['{{', '']; // 停止的条件 如果同时存在 那么这个 index 要尽量的靠左 去最小的
1528 | let endIndex = context.source.length; // 停止的索引
1529 | for (let i = 0; i < endToken.length; i++) {
1530 | const index = context.source.indexOf(endToken[i]);
1531 | if (index !== -1 && endIndex > 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('')) {
42 | for (let i = ancestors.length - 1; i >= 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("") &&
56 | source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
57 | );
58 | }
59 |
60 | function parseText(context) {
61 | const endToken = ['{{', ''] // 停止的条件 如果同时存在 那么这个 index 要尽量的靠左 去最小的
62 | let endIndex = context.source.length // 停止的索引
63 | for (let i = 0; i < endToken.length; i++) {
64 | const index = context.source.indexOf(endToken[i])
65 | if (index !== -1 && endIndex > 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("");
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 |
--------------------------------------------------------------------------------