├── .editorconfig
├── .gitignore
├── .prettierrc
├── README.md
├── babel.config.js
├── example
├── apiInject
│ ├── App.js
│ ├── index.html
│ └── main.js
├── compiler-base
│ ├── App.js
│ ├── index.html
│ └── main.js
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlots
│ ├── 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
├── customRender
│ ├── App.js
│ ├── index.html
│ └── main.js
├── helloword
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── nextTick
│ ├── App.js
│ ├── index.html
│ └── main.js
├── patchChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
└── update
│ ├── App.js
│ ├── index.html
│ └── main.js
├── index.js
├── lib
├── guide-mini-vue.cjs.js
└── guide-mini-vue.esm.js
├── 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
│ ├── baseHandler.ts
│ ├── computed.ts
│ ├── effect.ts
│ ├── enum.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ └── shallowReadonly.spec.ts
├── runtime-core
│ ├── apiInject.ts
│ ├── component.ts
│ ├── componentEmit.ts
│ ├── componentProps.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── componentUpdateUtils.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── index.ts
│ ├── render.ts
│ ├── scheduler.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── ShapeFlags.ts
│ ├── index.ts
│ └── toDisplayString.ts
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 |
7 | # Matches multiple files with brace expansion notation
8 | [*.{js,jsx,ts,tsx,html,sass,vue}]
9 | charset = utf-8
10 | indent_style = tab
11 | indent_size = 2
12 | trim_trailing_whitespace = true
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "singleQuote": true,
5 | "semi": false,
6 | "trailingComma": "none"
7 | }
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## mini-vue
2 |
3 | 实现最简 vue3 模型,用于深入学习 vue3
4 |
5 | [这个是配套的笔记](https://github.com/TTiip/mini-vue-docs) 搭配起来食用更佳哟
6 |
7 | ## Why
8 |
9 | 当我们需要深入学习 vue3 时,我们就需要看源码来学习,但是像这种工业级别的库,源码中有很多逻辑是用于处理边缘情况或者是兼容处理逻辑,是不利于我们学习的。
10 |
11 | 我们应该关注于核心逻辑,而这个库的目的就是把 vue3 源码中最核心的逻辑剥离出来,只留下核心逻辑,以供大家学习。
12 |
13 | ## How
14 |
15 | 基于 vue3 的功能点,一点一点的拆分出来。
16 |
17 | 代码命名会保持和源码中的一致,方便大家通过命名去源码中查找逻辑。
18 |
19 | ### Tasking
20 |
21 | #### runtime-core
22 |
23 | - [x] 支持组件类型
24 | - [x] 支持 element 类型
25 | - [x] 初始化 props
26 | - [x] setup 可获取 props 和 context
27 | - [x] 支持 component emit
28 | - [x] 支持 proxy
29 | - [x] 可以在 render 函数中获取 setup 返回的对象
30 | - [x] nextTick 的实现
31 | - [x] 支持 getCurrentInstance
32 | - [x] 支持 provide/inject
33 | - [x] 支持最基础的 slots
34 | - [x] 支持 Text 类型节点
35 | - [x] 支持 $el api
36 |
37 |
38 | #### reactivity
39 |
40 | 目标是用自己的 reactivity 支持现有的 demo 运行
41 |
42 | - [x] reactive 的实现
43 | - [x] ref 的实现
44 | - [x] readonly 的实现
45 | - [x] computed 的实现
46 | - [x] track 依赖收集
47 | - [x] trigger 触发依赖
48 | - [x] 支持 isReactive
49 | - [x] 支持嵌套 reactive
50 | - [x] 支持 toRaw
51 | - [x] 支持 effect.scheduler
52 | - [x] 支持 effect.stop
53 | - [x] 支持 isReadonly
54 | - [x] 支持 isProxy
55 | - [x] 支持 shallowReadonly
56 | - [x] 支持 proxyRefs
57 |
58 | ### runtime-dom
59 | - [x] 支持 custom renderer
60 |
61 | ### build
62 |
63 | ```shell
64 | yarn build
65 | ```
66 |
67 | ### example
68 |
69 | 通过 server 的方式打开 example/\* 下的 index.html 即可
70 |
71 | > 推荐使用 [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
72 |
73 | ### 初始化
74 |
75 | #### 流程图
76 | 
77 |
78 |
79 | #### 关键函数调用图
80 |
81 |
82 | 
83 |
84 | > 可以基于函数名快速搜索到源码内容
85 |
86 | ### update
87 |
88 | #### 流程图
89 |
90 | 
91 |
92 | #### 关键函数调用图
93 |
94 | 
95 |
96 |
97 | > 可以基于函数名快速搜索到源码内容
98 |
99 |
100 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | // 使用当前node版本为基础进行转换
4 | ["@babel/preset-env", { targets: { node: "current" } }],
5 | // 支持typescript
6 | '@babel/preset-typescript',
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/example/apiInject/App.js:
--------------------------------------------------------------------------------
1 | // 组件 provide 和 inject 功能
2 | import { h, provide, inject } from '../../lib/guide-mini-vue.esm.js'
3 |
4 | const Provider = {
5 | name: 'Provider',
6 | setup() {
7 | provide('foo', 'fooVal1')
8 | provide('bar', 'barVal1')
9 | },
10 | render() {
11 | return h('div', {}, [h('p', {}, 'Provider'), h(ProviderTwo)])
12 | }
13 | }
14 |
15 | const ProviderTwo = {
16 | name: 'ProviderTwo',
17 | setup() {
18 | provide('foo', 'fooVal2')
19 | // provide('bar', 'barVal2')
20 |
21 | const foo = inject('foo')
22 | const bar = inject('bar')
23 |
24 | return {
25 | foo,
26 | bar
27 | }
28 | },
29 | render() {
30 | return h('div', {}, [
31 | h('p', {}, `ProviderTwo: ${this.foo} & ${this.bar}`),
32 | h(ProviderThree)
33 | ])
34 | }
35 | }
36 |
37 | const ProviderThree = {
38 | name: 'ProviderThree',
39 | setup() {
40 | const foo = inject('foo')
41 | const bar = inject('bar')
42 | // const baz = inject("baz", 'bazDefault')
43 | const baz = inject('baz', () => 'bazDefault')
44 |
45 | return {
46 | foo,
47 | bar,
48 | baz
49 | }
50 | },
51 |
52 | render() {
53 | return h('div', {}, `ProviderThree: ${this.foo} & ${this.bar} & ${this.baz}`)
54 | }
55 | }
56 |
57 | const App = {
58 | name: 'App',
59 | setup() {},
60 | render() {
61 | return h('div', {}, [h('p', {}, ''), h(Provider)])
62 | }
63 | }
64 |
65 | export {
66 | App
67 | }
68 |
--------------------------------------------------------------------------------
/example/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/apiInject/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/compiler-base/App.js:
--------------------------------------------------------------------------------
1 | import { ref } from '../../lib/guide-mini-vue.esm.js'
2 |
3 | export const App = {
4 | name: 'App',
5 | template: `hi, {{count}}
`,
6 | setup() {
7 | const count = (window.count = ref(1));
8 | return {
9 | count
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/example/compiler-base/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/compiler-base/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../lib/guide-mini-vue.esm.js'
2 | import { Foo } from './Foo.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | render() {
7 | // emit
8 | return h('div', {}, [
9 | h('div', {}, 'App'),
10 | h(Foo, {
11 | onAdd(a, b) {
12 | console.log('onAdd', a, b)
13 | },
14 | onAddFoo() {
15 | console.log('onAddFoo')
16 | }
17 | })
18 | ])
19 | },
20 |
21 | setup() {
22 | return {}
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../lib/guide-mini-vue.esm.js'
2 |
3 | export const Foo = {
4 | name: 'Foo',
5 | setup(props, { emit }) {
6 | const emitAdd = () => {
7 | emit('add', 1, 2)
8 | emit('add-foo')
9 | }
10 |
11 | return {
12 | emitAdd,
13 | }
14 | },
15 | render() {
16 | const btn = h(
17 | 'button',
18 | {
19 | onClick: this.emitAdd,
20 | },
21 | 'emitAdd'
22 | )
23 |
24 | const foo = h('p', {}, 'foo')
25 | return h('div', {}, [foo, btn])
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/componentSlots/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVNode } from '../../lib/guide-mini-vue.esm.js'
2 | import { Foo } from './Foo.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | render() {
7 | const app = h('div', {}, 'App')
8 | // object key
9 | const foo = h(
10 | Foo,
11 | {},
12 | {
13 | header: ({ age }) => [h('p', {}, 'header: ' + age), createTextVNode('你好呀')],
14 | footer: () => h('p', {}, 'footer')
15 | }
16 | )
17 | // 数组 vnode
18 | // const foo = h(Foo, {}, h("p", {}, "123"));
19 | return h('div', {}, [app, foo])
20 | },
21 |
22 | setup() {
23 | return {}
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/componentSlots/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from '../../lib/guide-mini-vue.esm.js'
2 |
3 | export const Foo = {
4 | name: 'Foo',
5 | setup() {
6 | return {}
7 | },
8 | render() {
9 | const foo = h('p', {}, 'foo')
10 |
11 | // Foo .vnode. children
12 | console.log(this.$slots)
13 | // children -> vnode
14 | //
15 | // renderSlots
16 | // 具名插槽
17 | // 1. 获取到要渲染的元素 1
18 | // 2. 要获取到渲染的位置
19 | // 作用域插槽
20 | const age = 18
21 | return h('div', {}, [
22 | renderSlots(this.$slots, 'header', {
23 | age
24 | }),
25 | foo,
26 | renderSlots(this.$slots, 'footer')
27 | ])
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/example/componentSlots/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/componentSlots/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from '../../lib/guide-mini-vue.esm.js'
2 | import Child from './Child.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | setup() {
7 | const msg = ref('123')
8 | const count = ref(1)
9 |
10 | window.msg = msg
11 |
12 | const changeChildProps = () => {
13 | msg.value = '456'
14 | }
15 |
16 | const changeCount = () => {
17 | count.value++
18 | }
19 |
20 | return { msg, changeChildProps, changeCount, count }
21 | },
22 |
23 | render() {
24 | return h('div', {}, [
25 | h('div', {}, '你好'),
26 | h(
27 | 'button',
28 | {
29 | onClick: this.changeChildProps,
30 | },
31 | 'change child props'
32 | ),
33 | h(Child, {
34 | msg: this.msg,
35 | }),
36 | h(
37 | 'button',
38 | {
39 | onClick: this.changeCount,
40 | },
41 | 'change self count'
42 | ),
43 | h('p', {}, 'count: ' + this.count),
44 | ])
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/example/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../lib/guide-mini-vue.esm.js'
2 | export default {
3 | name: 'Child',
4 | setup(props, { emit }) {},
5 | render(proxy) {
6 | return h('div', {}, [h('div', {}, 'child - props - msg: ' + this.$props.msg)])
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/example/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/componentUpdate/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from '../../lib/guide-mini-vue.esm.js'
2 | import { Foo } from './Foo.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | render() {
7 | return h('div', {}, [h('p', {}, 'currentInstance demo'), h(Foo)])
8 | },
9 |
10 | setup() {
11 | const instance = getCurrentInstance()
12 | console.log('App:', instance)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/example/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from '../../lib/guide-mini-vue.esm.js'
2 |
3 | export const Foo = {
4 | name: 'Foo',
5 | setup() {
6 | const instance = getCurrentInstance()
7 | console.log('Foo:', instance)
8 | return {}
9 | },
10 | render() {
11 | return h('div', {}, 'foo')
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/customRender/App.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../lib/guide-mini-vue.esm.js'
2 |
3 | const App = {
4 | setup () {
5 | return {
6 | x: 100,
7 | y: 100
8 | }
9 | },
10 | render() {
11 | return h('rect', { x: this.x, y: this.y })
12 | }
13 | }
14 |
15 | export {
16 | App
17 | }
18 |
--------------------------------------------------------------------------------
/example/customRender/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/customRender/main.js:
--------------------------------------------------------------------------------
1 | import { createRender } from '../../lib/guide-mini-vue.esm.js'
2 | import { App } from './App.js'
3 |
4 | console.log(PIXI)
5 |
6 | const game = new PIXI.Application({
7 | width: 500,
8 | height: 500
9 | })
10 |
11 | document.body.append(game.view)
12 |
13 | const render = createRender({
14 | createElement (type) {
15 | if (type === 'rect') {
16 | const rect = new PIXI.Graphics()
17 | rect.beginFill(0xff0000)
18 | rect.drawRect(0, 0, 100, 100)
19 | rect.endFill()
20 |
21 | return rect
22 | }
23 | },
24 | patchProp (el, key, val) {
25 | el[key] = val
26 | },
27 | insert (el, container) {
28 | container.addChild(el)
29 | }
30 | })
31 |
32 | render.createApp(App).mount(game.stage)
33 |
34 |
35 | // const container = document.querySelector('#app')
36 | // createApp(App).mount(container)
--------------------------------------------------------------------------------
/example/helloword/App.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../lib/guide-mini-vue.esm.js'
2 | import Foo from './Foo.js'
3 |
4 | window.self = null
5 |
6 | const App = {
7 | // .vue
8 | //
9 |
10 | name: 'App',
11 | // render
12 | render () {
13 | window.self = this
14 | return h(
15 | 'dev',
16 | {
17 | id: 'root',
18 | class: ['red', 'hard'],
19 | onClick () {
20 | console.log('click')
21 | },
22 | onMousedown () {
23 | console.log('mousedown')
24 | }
25 | },
26 | // string 类型
27 | // setupState 能够获取到setup种返回的 变量
28 | // this.$el --> 获取到 组件的根节点 dom实例
29 |
30 | // `hi, ${this.msg}`,
31 |
32 | // array 类型
33 | // [
34 | // h('p', { class: 'red' }, 'hi'),
35 | // h('div', { class: 'blue' }, 'mini-vue'),
36 | // ]
37 | [h('div', {}, `hi, ${this.msg}`), h(Foo, { count: 12 })]
38 | )
39 | },
40 | setup () {
41 | // composition api
42 |
43 | return {
44 | msg: 'mini-vue'
45 | }
46 | }
47 | }
48 |
49 | export default App
--------------------------------------------------------------------------------
/example/helloword/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../lib/guide-mini-vue.esm.js'
2 |
3 | const Foo = {
4 | name: 'Foo',
5 | render () {
6 | return h(
7 | 'dev',
8 | {
9 | class: 'Foo',
10 | onClick () {
11 | console.log('click')
12 | },
13 | onMousedown () {
14 | console.log('mousedown')
15 | }
16 | },
17 | // string 类型
18 | // setupState 能够获取到setup种返回的 变量
19 | // this.$el --> 获取到 组件的根节点 dom实例
20 |
21 | `Foo, ${this.count}`,
22 | )
23 | },
24 | setup (props) {
25 | props.count++
26 | console.log(props)
27 | // props 是一个 readonly 类型
28 | return {
29 | msg: 'Foo'
30 | }
31 | }
32 | }
33 |
34 | export default Foo
--------------------------------------------------------------------------------
/example/helloword/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/helloword/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import App from './App.js'
3 |
4 | const container = document.querySelector('#app')
5 | createApp(App).mount(container)
--------------------------------------------------------------------------------
/example/nextTick/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref, getCurrentInstance, nextTick } from '../../lib/guide-mini-vue.esm.js'
2 |
3 | export default {
4 | name: 'App',
5 | setup() {
6 | const count = ref(1)
7 | const instance = getCurrentInstance()
8 |
9 | const onClick = async () => {
10 | for (let i = 0; i < 100; i++) {
11 | console.log('update')
12 | count.value = i
13 | }
14 | console.log(instance)
15 | // nextTick(() => {
16 | // console.log(instance)
17 | // })
18 |
19 | await nextTick()
20 | console.log(instance)
21 | }
22 |
23 | return {
24 | onClick,
25 | count
26 | }
27 | },
28 | render() {
29 | const button = h('button', { onClick: this.onClick }, 'update')
30 | const p = h('p', {}, 'count:' + this.count)
31 |
32 | return h('div', {}, [button, p])
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/example/nextTick/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/nextTick/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import App from './App.js'
3 |
4 | const rootContainer = document.querySelector('#root')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../lib/guide-mini-vue.esm.js'
2 |
3 | import ArrayToText from './ArrayToText.js'
4 | import TextToText from './TextToText.js'
5 | import TextToArray from './TextToArray.js'
6 | import ArrayToArray from './ArrayToArray.js'
7 |
8 | export default {
9 | name: 'App',
10 | setup() {},
11 |
12 | render() {
13 | return h('div', { tId: 1 }, [
14 | h('p', {}, '主页'),
15 | // 老的是 array 新的是 text
16 | // h(ArrayToText),
17 | // 老的是 text 新的是 text
18 | // h(TextToText),
19 | // 老的是 text 新的是 array
20 | // h(TextToArray)
21 | // 老的是 array 新的是 array
22 | h(ArrayToArray)
23 | ])
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 array
3 |
4 | import { ref, h } from '../../lib/guide-mini-vue.esm.js'
5 |
6 | // 1. 左侧的对比
7 | // (a b) c
8 | // (a b) d e
9 | // const prevChildren = [
10 | // h('p', { key: 'A' }, 'A'),
11 | // h('p', { key: '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 | // ##########
37 | // 以上两个例子没有实际效果仅仅是用来 确定 双端对比中 i 的下标!!!
38 |
39 | // 3. 新的比老的长
40 | // 创建新的
41 | // 左侧
42 | // (a b)
43 | // (a b) c
44 | // i = 2, e1 = 1, e2 = 2
45 | // const prevChildren = [h('p', { key: 'A' }, 'A'), h('p', { key: 'B' }, 'B')]
46 | // const nextChildren = [
47 | // h('p', { key: 'A' }, 'A'),
48 | // h('p', { key: 'B' }, 'B'),
49 | // h('p', { key: 'C' }, 'C'),
50 | // h('p', { key: 'D' }, 'D')
51 | // ]
52 |
53 | // 右侧
54 | // (a b)
55 | // d c (a b)
56 | // i = 0, e1 = -1, e2 = 0
57 | // const prevChildren = [h('p', { key: 'A' }, 'A'), h('p', { key: 'B' }, 'B')]
58 | // const nextChildren = [
59 | // h('p', { key: 'D' }, 'D'),
60 | // h('p', { key: 'C' }, 'C'),
61 | // h('p', { key: 'A' }, 'A'),
62 | // h('p', { key: 'B' }, 'B')
63 | // ]
64 |
65 | // 4. 老的比新的长
66 | // 删除老的
67 | // 左侧
68 | // (a b) c d
69 | // (a b)
70 | // i = 2, e1 = 2, e2 = 1
71 | // const prevChildren = [
72 | // h('p', { key: 'A' }, 'A'),
73 | // h('p', { key: 'B' }, 'B'),
74 | // h('p', { key: 'C' }, 'C'),
75 | // h('p', { key: 'D' }, 'D')
76 | // ]
77 | // const nextChildren = [h('p', { key: 'A' }, 'A'), h('p', { key: 'B' }, 'B')]
78 |
79 | // 右侧
80 | // a b (c d)
81 | // (c d)
82 | // i = 0, e1 = 0, e2 = -1
83 |
84 | // const prevChildren = [
85 | // h('p', { key: 'A' }, 'A'),
86 | // h('p', { key: 'B' }, 'B'),
87 | // h('p', { key: 'C' }, 'C'),
88 | // h('p', { key: 'D' }, 'D')
89 | // ]
90 | // const nextChildren = [h('p', { key: 'C' }, 'C'), h('p', { key: 'D' }, 'D')]
91 |
92 | // 5. 对比中间的部分
93 | // 删除老的 (在老的里面存在,新的里面不存在)
94 | // 5.1
95 | // a,b,(c,d),f,g
96 | // a,b,(e,c),f,g
97 | // D 节点在新的里面是没有的 - 需要删除掉
98 | // C 节点 props 也发生了变化
99 |
100 | // const prevChildren = [
101 | // h('p', { key: 'A' }, 'A'),
102 | // h('p', { key: 'B' }, 'B'),
103 | // h('p', { key: 'C', id: 'c-prev' }, 'C'),
104 | // h('p', { key: 'D' }, 'D'),
105 | // h('p', { key: 'F' }, 'F'),
106 | // h('p', { key: 'G' }, 'G')
107 | // ]
108 |
109 | // const nextChildren = [
110 | // h('p', { key: 'A' }, 'A'),
111 | // h('p', { key: 'B' }, 'B'),
112 | // h('p', { key: 'E' }, 'E'),
113 | // h('p', { key: 'C', id: 'c-next' }, 'C'),
114 | // h('p', { key: 'F' }, 'F'),
115 | // h('p', { key: 'G' }, 'G')
116 | // ]
117 |
118 | // 5.1.1
119 | // a,b,(c,e,d),f,g
120 | // a,b,(e,c),f,g
121 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
122 | // const prevChildren = [
123 | // h('p', { key: 'A' }, 'A'),
124 | // h('p', { key: 'B' }, 'B'),
125 | // h('p', { key: 'C', id: 'c-prev' }, 'C'),
126 | // h('p', { key: 'E' }, 'E'),
127 | // h('p', { key: 'D' }, 'D'),
128 | // h('p', { key: 'F' }, 'F'),
129 | // h('p', { key: 'G' }, 'G')
130 | // ]
131 |
132 | // const nextChildren = [
133 | // h('p', { key: 'A' }, 'A'),
134 | // h('p', { key: 'B' }, 'B'),
135 | // h('p', { key: 'E' }, 'E'),
136 | // h('p', { key: 'C', id: 'c-next' }, 'C'),
137 | // h('p', { key: 'F' }, 'F'),
138 | // h('p', { key: 'G' }, 'G')
139 | // ]
140 |
141 | // 2 移动 (节点存在于新的和老的里面,但是位置变了)
142 |
143 | // 2.1
144 | // a,b,(c,d,e),f,g
145 | // a,b,(e,c,d),f,g
146 | // 最长子序列: [1,2]
147 |
148 | // const prevChildren = [
149 | // h('p', { key: 'A' }, 'A'),
150 | // h('p', { key: 'B' }, 'B'),
151 | // h('p', { key: 'C' }, 'C'),
152 | // h('p', { key: 'D' }, 'D'),
153 | // h('p', { key: 'E' }, 'E'),
154 | // h('p', { key: 'F' }, 'F'),
155 | // h('p', { key: 'G' }, 'G')
156 | // ]
157 |
158 | // const nextChildren = [
159 | // h('p', { key: 'A' }, 'A'),
160 | // h('p', { key: 'B' }, 'B'),
161 | // h('p', { key: 'E' }, 'E'),
162 | // h('p', { key: 'C' }, 'C'),
163 | // h('p', { key: 'D' }, 'D'),
164 | // h('p', { key: 'F' }, 'F'),
165 | // h('p', { key: 'G' }, 'G')
166 | // ]
167 |
168 | // 3. 创建新的节点
169 | // a,b,(c,e),f,g
170 | // a,b,(e,c,d),f,g
171 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
172 | // const prevChildren = [
173 | // h('p', { key: 'A' }, 'A'),
174 | // h('p', { key: 'B' }, 'B'),
175 | // h('p', { key: 'C' }, 'C'),
176 | // h('p', { key: 'E' }, 'E'),
177 | // h('p', { key: 'F' }, 'F'),
178 | // h('p', { key: 'G' }, 'G')
179 | // ]
180 |
181 | // const nextChildren = [
182 | // h('p', { key: 'A' }, 'A'),
183 | // h('p', { key: 'B' }, 'B'),
184 | // h('p', { key: 'E' }, 'E'),
185 | // h('p', { key: 'C' }, 'C'),
186 | // h('p', { key: 'D' }, 'D'),
187 | // h('p', { key: 'F' }, 'F'),
188 | // h('p', { key: 'G' }, 'G')
189 | // ]
190 |
191 | // 综合例子
192 | // a,b,(c,d,e,z),f,g
193 | // a,b,(d,c,y,e),f,g
194 |
195 | // const prevChildren = [
196 | // h('p', { key: 'A' }, 'A'),
197 | // h('p', { key: 'B' }, 'B'),
198 | // h('p', { key: 'C', id: 'prev-c' }, 'C'),
199 | // h('p', { key: 'D' }, 'D'),
200 | // h('p', { key: 'E' }, 'E'),
201 | // h('p', { key: 'Z' }, 'Z'),
202 | // h('p', { key: 'F' }, 'F'),
203 | // h('p', { key: 'G' }, 'G')
204 | // ]
205 |
206 | // const nextChildren = [
207 | // h('p', { key: 'A' }, 'A'),
208 | // h('p', { key: 'B' }, 'B'),
209 | // h('p', { key: 'D' }, 'D'),
210 | // h('p', { key: 'C', id: 'next-c' }, 'C'),
211 | // h('p', { key: 'Y' }, 'Y'),
212 | // h('p', { key: 'E' }, 'E'),
213 | // h('p', { key: 'F' }, 'F'),
214 | // h('p', { key: 'G' }, 'G')
215 | // ]
216 |
217 | // fix c 节点应该是 move 而不是删除之后重新创建的.
218 | const prevChildren = [
219 | h('p', { key: 'A' }, 'A'),
220 | h('p', { id: 'prev-c' }, 'C'),
221 | h('p', { key: 'B' }, 'B'),
222 | h('p', { key: 'D' }, 'D')
223 | ]
224 |
225 | const nextChildren = [
226 | h('p', { key: 'A' }, 'A'),
227 | h('p', { key: 'B' }, 'B'),
228 | h('p', { id: 'next-c' }, 'C'),
229 | h('p', { key: 'D' }, 'D')
230 | ]
231 |
232 | export default {
233 | name: 'ArrayToArray',
234 | setup() {
235 | const isChange = ref(false)
236 | window.isChange = isChange
237 |
238 | return {
239 | isChange
240 | }
241 | },
242 | render() {
243 | const self = this
244 |
245 | return self.isChange === true
246 | ? h('div', {}, nextChildren)
247 | : h('div', {}, prevChildren)
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 text
3 |
4 | import { ref, h } from '../../lib/guide-mini-vue.esm.js'
5 | const nextChildren = 'newChildren'
6 | const prevChildren = [h('div', {}, 'A'), h('div', {}, 'B')]
7 |
8 | export default {
9 | name: 'ArrayToText',
10 | setup() {
11 | const isChange = ref(false)
12 | window.isChange = isChange
13 |
14 | return {
15 | isChange
16 | }
17 | },
18 | render() {
19 | const self = this
20 |
21 | return self.isChange === true
22 | ? h('div', {}, nextChildren)
23 | : h('div', {}, prevChildren)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | // 新的是 array
2 | // 老的是 text
3 | import { ref, h } from '../../lib/guide-mini-vue.esm.js'
4 |
5 | const prevChildren = 'oldChild'
6 | const nextChildren = [h('div', {}, 'A'), h('div', {}, 'B')]
7 |
8 | export default {
9 | name: 'TextToArray',
10 | setup() {
11 | const isChange = ref(false)
12 | window.isChange = isChange
13 |
14 | return {
15 | isChange
16 | }
17 | },
18 | render() {
19 | const self = this
20 |
21 | return self.isChange === true
22 | ? h('div', {}, nextChildren)
23 | : h('div', {}, prevChildren)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | // 新的是 text
2 | // 老的是 text
3 | import { ref, h } from '../../lib/guide-mini-vue.esm.js'
4 |
5 | const prevChildren = 'oldChild'
6 | const nextChildren = 'newChild'
7 |
8 | export default {
9 | name: 'TextToText',
10 | setup() {
11 | const isChange = ref(false)
12 | window.isChange = isChange
13 |
14 | return {
15 | isChange
16 | }
17 | },
18 | render() {
19 | const self = this
20 |
21 | return self.isChange === true
22 | ? h('div', {}, nextChildren)
23 | : h('div', {}, prevChildren)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import App from './App.js'
3 |
4 | const rootContainer = document.querySelector('#root')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from '../../lib/guide-mini-vue.esm.js'
2 |
3 | export const App = {
4 | name: 'App',
5 |
6 | setup() {
7 | const count = ref(0)
8 |
9 | const onClick = () => {
10 | count.value++
11 | }
12 |
13 | const props = ref({
14 | foo: 'foo',
15 | bar: 'bar'
16 | })
17 | const onChangePropsDemo1 = () => {
18 | props.value.foo = 'new-foo'
19 | }
20 |
21 | const onChangePropsDemo2 = () => {
22 | props.value.foo = undefined
23 | }
24 |
25 | const onChangePropsDemo3 = () => {
26 | props.value = {
27 | foo: 'foo'
28 | }
29 | }
30 |
31 | return {
32 | count,
33 | onClick,
34 | onChangePropsDemo1,
35 | onChangePropsDemo2,
36 | onChangePropsDemo3,
37 | props
38 | }
39 | },
40 | render() {
41 | return h(
42 | 'div',
43 | {
44 | id: 'root',
45 | ...this.props
46 | },
47 | [
48 | h('div', {}, 'count:' + this.count),
49 | h(
50 | 'button',
51 | {
52 | onClick: this.onClick
53 | },
54 | 'click'
55 | ),
56 | h(
57 | 'button',
58 | {
59 | onClick: this.onChangePropsDemo1
60 | },
61 | 'changeProps - 值改变了 - 修改'
62 | ),
63 |
64 | h(
65 | 'button',
66 | {
67 | onClick: this.onChangePropsDemo2
68 | },
69 | 'changeProps - 值变成了 undefined - 删除'
70 | ),
71 |
72 | h(
73 | 'button',
74 | {
75 | onClick: this.onChangePropsDemo3
76 | },
77 | 'changeProps - key 在新的里面没有了 - 删除'
78 | )
79 | ]
80 | )
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | var isObject = function (value) {
6 | return value !== null && typeof value === 'object';
7 | };
8 |
9 | var createComponentInstance = function (vnode) {
10 | var component = {
11 | vnode: vnode,
12 | type: vnode.type
13 | };
14 | return component;
15 | };
16 | var setupComponent = function (instance) {
17 | // TODO initProps
18 | // TODO initSlot
19 | //
20 | setupStatefulComponent(instance);
21 | };
22 | var setupStatefulComponent = function (instance) {
23 | var Component = instance.type;
24 | var setup = Component.setup;
25 | // 用户可能不会写 setup 函数
26 | if (setup) {
27 | // 可以返回一个 fn 也可能是一个 object
28 | var setupResult = setup();
29 | handleSetupResult(instance, setupResult);
30 | }
31 | };
32 | var handleSetupResult = function (instance, setupResult) {
33 | // function or object
34 | // TODO function
35 | if (typeof setupResult === 'object') {
36 | instance.setupState = setupResult;
37 | }
38 | // 初始化 render 函数
39 | finishCompentSetup(instance);
40 | };
41 | var finishCompentSetup = function (instance) {
42 | var Component = instance.type;
43 | var render = Component.render;
44 | // render 存在时赋值
45 | if (render) {
46 | instance.render = render;
47 | }
48 | };
49 |
50 | // Component
51 | var processComponent = function (vnode, container) {
52 | mountComponent(vnode, container);
53 | };
54 | var mountComponent = function (vnode, container) {
55 | var instance = createComponentInstance(vnode);
56 | setupComponent(instance);
57 | setupRenderEffect(instance, container);
58 | };
59 | // Element
60 | var processElement = function (vnode, container) {
61 | mountElement(vnode, container);
62 | };
63 | var mountElement = function (vnode, container) {
64 | var el = document.createElement(vnode.type);
65 | // children 可能时 string array
66 | // TODO array
67 | var children = vnode.children, props = vnode.props;
68 | // children
69 | el.textContent = children;
70 | // props
71 | for (var key in props) {
72 | var val = props[key];
73 | el.setAttribute(key, val);
74 | }
75 | // 挂在在页面上
76 | container.append(el);
77 | };
78 | var render = function (vnode, container) {
79 | // patch
80 | patch(vnode, container);
81 | };
82 | var patch = function (vnode, container) {
83 | // 去处理我们的组件
84 | // TODO 判断一下 vnode 是不是 element 类型
85 | // 调用对应的方法去处理对应的 方法
86 | // processElement()
87 | if (typeof vnode.type === 'string') {
88 | console.log(111);
89 | processElement(vnode, container);
90 | }
91 | else if (isObject(vnode.type)) {
92 | processComponent(vnode, container);
93 | }
94 | };
95 | var setupRenderEffect = function (instance, container) {
96 | var subTree = instance.render();
97 | // vnode --> patch
98 | // vnode --> element --> mount
99 | patch(subTree, container);
100 | };
101 |
102 | var createVNode = function (type, props, children) {
103 | return {
104 | type: type,
105 | props: props,
106 | children: children
107 | };
108 | };
109 |
110 | var createApp = function (rootComponent) {
111 | return {
112 | mount: function (rootContainer) {
113 | // 先转换成虚拟节点
114 | // component --> vnode
115 | // 后续所有的逻辑操作 都会基于 vnode 去操作
116 | var vnode = createVNode(rootComponent);
117 | render(vnode, rootContainer);
118 | }
119 | };
120 | };
121 |
122 | var h = function (type, props, children) {
123 | return createVNode(type, props, children);
124 | };
125 |
126 | exports.createApp = createApp;
127 | exports.h = h;
128 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | const toDisplayString = (value) => {
6 | return String(value);
7 | };
8 |
9 | const extend = Object.assign;
10 | // 设置一个全局空对象方便后续曲比较
11 | const EMPTY_OBJ = {};
12 | const isObject = (value) => {
13 | return value !== null && typeof value === 'object';
14 | };
15 | const isString = (value) => {
16 | return value !== null && typeof value === 'string';
17 | };
18 | const hasChanged = (val, newValue) => {
19 | return !Object.is(val, newValue);
20 | };
21 | const getShapeFlag = (type) => {
22 | return typeof type === 'string'
23 | ? 1 /* ELEMENT */
24 | : 2 /* STATEFUL_COMPONENT */;
25 | };
26 | const hasOwn = (val = {}, key) => Object.prototype.hasOwnProperty.call(val, key);
27 | const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
28 | const camelize = (str) => {
29 | // ex: add-foo
30 | // _ 代表匹配到的值规则(-f)
31 | // targetValue 代表匹配到的值(f)
32 | return str.replace(/-(\w)/g, (_, targetValue) => {
33 | return targetValue ? targetValue.toUpperCase() : '';
34 | });
35 | };
36 | // add --> onAdd
37 | const toHandlerKey = (str) => str ? camelize('on' + capitalize(str)) : '';
38 | const getSequence = (arr) => {
39 | const p = arr.slice();
40 | const result = [0];
41 | let i, j, u, v, c;
42 | const len = arr.length;
43 | for (i = 0; i < len; i++) {
44 | const arrI = arr[i];
45 | if (arrI !== 0) {
46 | j = result[result.length - 1];
47 | if (arr[j] < arrI) {
48 | p[i] = j;
49 | result.push(i);
50 | continue;
51 | }
52 | u = 0;
53 | v = result.length - 1;
54 | while (u < v) {
55 | c = (u + v) >> 1;
56 | if (arr[result[c]] < arrI) {
57 | u = c + 1;
58 | }
59 | else {
60 | v = c;
61 | }
62 | }
63 | if (arrI < arr[result[u]]) {
64 | if (u > 0) {
65 | p[i] = result[u - 1];
66 | }
67 | result[u] = i;
68 | }
69 | }
70 | }
71 | u = result.length;
72 | v = result[u - 1];
73 | while (u-- > 0) {
74 | result[u] = v;
75 | v = p[v];
76 | }
77 | return result;
78 | };
79 |
80 | const Fragment = Symbol('Fragment');
81 | const Text = Symbol('Text');
82 | const createVNode = (type, props, children) => {
83 | const vnode = {
84 | type,
85 | props,
86 | children,
87 | component: null,
88 | key: props && props.key,
89 | shapeFlag: getShapeFlag(type),
90 | el: null
91 | };
92 | if (typeof children === 'string') {
93 | // 元素节点
94 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
95 | }
96 | else if (Array.isArray(children)) {
97 | // 组件节点
98 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
99 | }
100 | // 判断是否需要 slots 处理
101 | // 首先是必须是一个组件类型,其次 children 必须是一个对象
102 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
103 | if (typeof children === 'object') {
104 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
105 | }
106 | }
107 | return vnode;
108 | };
109 | const createTextVNode = (text) => {
110 | return createVNode(Text, {}, text);
111 | };
112 |
113 | const createAppAPI = (render) => {
114 | const createApp = (rootComponent) => {
115 | return {
116 | mount(rootContainer) {
117 | // 先转换成虚拟节点
118 | // component --> vnode
119 | // 后续所有的逻辑操作 都会基于 vnode 去操作
120 | const vnode = createVNode(rootComponent);
121 | render(vnode, rootContainer);
122 | }
123 | };
124 | };
125 | return createApp;
126 | };
127 |
128 | const renderSlots = (slots, slotName, props) => {
129 | const slot = slots[slotName];
130 | if (slot) {
131 | if (typeof slot === 'function') {
132 | return createVNode(Fragment, {}, slot(props));
133 | }
134 | }
135 | };
136 |
137 | const h = (type, props, children) => {
138 | return createVNode(type, props, children);
139 | };
140 |
141 | let activeEffect;
142 | let shouldTrack;
143 | class ReactiveEffect {
144 | constructor(fn, scheduler) {
145 | this.active = true; // stop 状态
146 | this.deps = [];
147 | this._fn = fn;
148 | this._scheduler = scheduler;
149 | }
150 | run() {
151 | // 会收集依赖
152 | // shouldTrack 来做区分
153 | if (!this.active) {
154 | return this._fn();
155 | }
156 | shouldTrack = true;
157 | activeEffect = this;
158 | const res = this._fn();
159 | // 全局变量 reset
160 | shouldTrack = false;
161 | return res;
162 | }
163 | stop() {
164 | if (this.active) {
165 | cleanUpEffect(this);
166 | this.active = false;
167 | if (this.onStop) {
168 | this.onStop();
169 | }
170 | }
171 | }
172 | }
173 | const isTracking = () => {
174 | // 判断是不是 应该 收集依赖 & 有没有全局的 effect
175 | return shouldTrack && activeEffect !== undefined;
176 | };
177 | const cleanUpEffect = (effect) => {
178 | effect.deps.map((dep) => {
179 | dep.delete(effect);
180 | });
181 | effect.deps.length = 0;
182 | };
183 | const effect = (fn, options = {}) => {
184 | const { scheduler } = options;
185 | // 初始化的时候就需要调用一次fn
186 | const _effect = new ReactiveEffect(fn, scheduler);
187 | // 将调用 options 中的参数 和 类上同名参数赋值
188 | // onStop --> onStop
189 | extend(_effect, options);
190 | _effect.run();
191 | // 将传进来的 fn 返回出去
192 | // bind 以当前的 effect 实例作为函数的 this 指针
193 | const runner = _effect.run.bind(_effect);
194 | runner.effect = _effect;
195 | return runner;
196 | };
197 | const targetMap = new Map();
198 | const trackEffects = (dep) => {
199 | // 如果没有 effect 实例直接不做后面的操作
200 | // if (!activeEffect) return
201 | // if (!shouldTrack) return
202 | dep.add(activeEffect);
203 | activeEffect.deps.push(dep);
204 | };
205 | const track = (target, key) => {
206 | if (!isTracking())
207 | return;
208 | // target --> key --> dep
209 | let depsMap = targetMap.get(target);
210 | // 不存在despMap 先初始化一下
211 | if (!depsMap) {
212 | depsMap = new Map();
213 | targetMap.set(target, depsMap);
214 | }
215 | // 不存在dep 先初始化一下
216 | let dep = depsMap.get(key);
217 | if (!dep) {
218 | dep = new Set();
219 | depsMap.set(key, dep);
220 | }
221 | trackEffects(dep);
222 | };
223 | const triggerEffects = (dep) => {
224 | // 循环调用 dep 的 run 方法 触发每一个 dep 的 _fn
225 | for (const effect of dep) {
226 | if (effect._scheduler) {
227 | effect._scheduler();
228 | }
229 | else {
230 | effect.run();
231 | }
232 | }
233 | };
234 | const trigger = (target, key) => {
235 | // 取出 target 对应的 depsMap
236 | let depsMap = targetMap.get(target);
237 | // 取出 key 对应的 dep
238 | let dep = depsMap.get(key);
239 | triggerEffects(dep);
240 | };
241 |
242 | let get;
243 | let readOnlyGet;
244 | let shallowReadonlyGet;
245 | let set;
246 | const createGetter = (isReadOnly = false, shallow = false) => {
247 | const get = (target, key) => {
248 | // target: { foo: 1 }
249 | // key: foo
250 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
251 | // 通过 isReadOnly 来判断是不是 reactive 对象
252 | // return !isReadOnly
253 | // ???
254 | // 这里是不是可以直接指定为true,因为调用 getter 函数 肯定是 proxy 对象,所以一定是 reactive
255 | return true;
256 | }
257 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
258 | return isReadOnly;
259 | }
260 | const res = Reflect.get(target, key);
261 | // 如果是 shallow 类型(即外层是响应是对象,里面的不是 且设计成只读模式)
262 | if (shallow) {
263 | return res;
264 | }
265 | if (!isReadOnly) {
266 | // 依赖收集
267 | track(target, key);
268 | }
269 | // 看看 res 是不是 object
270 | if (isObject(res)) {
271 | return isReadOnly ? readonly(res) : reactive(res);
272 | }
273 | return res;
274 | };
275 | return get;
276 | };
277 | const createSetter = () => {
278 | const set = (target, key, value) => {
279 | const res = Reflect.set(target, key, value);
280 | // 触发依赖
281 | trigger(target, key);
282 | return res;
283 | };
284 | return set;
285 | };
286 | get = createGetter();
287 | readOnlyGet = createGetter(true);
288 | shallowReadonlyGet = createGetter(true, true);
289 | set = createSetter();
290 | const mutableHandlers = {
291 | get,
292 | set
293 | };
294 | const readonlyHandlers = {
295 | get: readOnlyGet,
296 | set(target, key, value) {
297 | console.warn(`key: ${key} set 失败, 因为 target:`, target, `是 readonly状态!`);
298 | return true;
299 | }
300 | };
301 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
302 | get: shallowReadonlyGet
303 | });
304 |
305 | const createActiveObject = (raw, baseHandlers) => {
306 | if (!isObject(raw)) {
307 | console.warn(`target: ${raw} 必须是一个对象!`);
308 | }
309 | else {
310 | return new Proxy(raw, baseHandlers);
311 | }
312 | };
313 | const reactive = (raw) => {
314 | return createActiveObject(raw, mutableHandlers);
315 | };
316 | const readonly = (raw) => {
317 | return createActiveObject(raw, readonlyHandlers);
318 | };
319 | const shallowReadonly = (raw) => {
320 | return createActiveObject(raw, shallowReadonlyHandlers);
321 | };
322 |
323 | const emit = (instance, eventName, ...args) => {
324 | const { props } = instance;
325 | const handlerName = toHandlerKey(eventName);
326 | const handler = props[handlerName];
327 | handler && handler(...args);
328 | };
329 |
330 | const initProps = (instance, rawProps = {}) => {
331 | instance.props = rawProps;
332 | };
333 |
334 | const initSlots = (instance, children) => {
335 | const { vnode } = instance;
336 | // 如果是 slot 类型 再进行处理
337 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
338 | normalizeObjectSlots(instance.slots, children);
339 | }
340 | };
341 | const normalizeObjectSlots = (slots, children) => {
342 | for (const key in children) {
343 | const slotVal = children[key];
344 | // 将设计的 props 传入对应的 slot
345 | slots[key] = (props) => normalizeSlotValue(slotVal(props));
346 | }
347 | slots = slots;
348 | };
349 | const normalizeSlotValue = (value) => {
350 | // 传入的 children(slots) 是不是数组,不是数组转换一下
351 | return Array.isArray(value) ? value : [value];
352 | };
353 |
354 | const publicPropertiesMap = {
355 | $el: (i) => i.vnode.el,
356 | $slots: (i) => i.slots,
357 | $props: (i) => i.props,
358 | };
359 | const publickInstanceProxyhandlers = {
360 | get({ _: instance }, key) {
361 | // setupState
362 | // 这里必须要在这里 获取 setupState
363 | // 因为 只有在初始化组件 的时候 获取 setupState
364 | const { setupState, props } = instance;
365 | if (hasOwn(setupState, key)) {
366 | // setupState 里面获取值
367 | return setupState[key];
368 | }
369 | else if (hasOwn(props, key)) {
370 | return props[key];
371 | }
372 | // $el
373 | // if (key === '$el') {
374 | // // key --> $el
375 | // // 如果是 this.$el 则 key 值就是 $el
376 | // return vnode.el
377 | // }
378 | const publicGetter = publicPropertiesMap[key];
379 | if (publicGetter) {
380 | return publicGetter(instance);
381 | }
382 | }
383 | };
384 |
385 | class RefImpl {
386 | constructor(value) {
387 | this.__v_isRef = true;
388 | // value 如果是对象 要用 reactive 转换成响应式对象
389 | this._rawValue = value;
390 | this._value = covert(value);
391 | this.dep = new Set();
392 | }
393 | get value() {
394 | trackRefValue(this);
395 | return this._value;
396 | }
397 | set value(newValue) {
398 | if (hasChanged(this._rawValue, newValue)) {
399 | // 一定先修改值,再触发\
400 | this._rawValue = newValue;
401 | this._value = covert(newValue);
402 | triggerEffects(this.dep);
403 | }
404 | }
405 | }
406 | const covert = (value) => {
407 | return isObject(value) ? reactive(value) : value;
408 | };
409 | const trackRefValue = (ref) => {
410 | if (isTracking()) {
411 | trackEffects(ref.dep);
412 | }
413 | };
414 | const ref = (value) => {
415 | return new RefImpl(value);
416 | };
417 | const isRef = (ref) => {
418 | return !!ref.__v_isRef;
419 | };
420 | const unRef = (ref) => {
421 | return isRef(ref) ? ref.value : ref;
422 | };
423 | const proxyRefs = (objectWithRefs) => {
424 | // 如果获取的值 是 ref 类型 那么就返回 .value
425 | // 如果获取的值 不是 ref 那么就直接返回 它本身的值
426 | return new Proxy(objectWithRefs, {
427 | get(target, key) {
428 | return unRef(Reflect.get(target, key));
429 | },
430 | set(target, key, value) {
431 | // 看看是 是 ref 类型 是的话修改 .value
432 | // 看看是 不是 ref 类型 是的话修改 本身的值
433 | if (isRef(target[key]) && !isRef(value)) {
434 | return Reflect.set(target[key], 'value', value);
435 | // return target[key].value = value
436 | }
437 | else {
438 | return Reflect.set(target, key, value);
439 | }
440 | }
441 | });
442 | };
443 |
444 | let currentInstance;
445 | const createComponentInstance = (vnode, parent) => {
446 | const component = {
447 | vnode,
448 | type: vnode.type,
449 | props: {},
450 | emit: {},
451 | slots: {},
452 | next: null,
453 | setupState: {},
454 | parent,
455 | // parent,
456 | provides: parent ? parent.provides : {},
457 | isMounted: false,
458 | subTree: {}
459 | };
460 | component.emit = emit.bind(null, component);
461 | return component;
462 | };
463 | const setupComponent = (instance) => {
464 | // initProps
465 | initProps(instance, instance.vnode.props);
466 | // initSlot
467 | initSlots(instance, instance.vnode.children);
468 | // initComponent
469 | setupStatefulComponent(instance);
470 | };
471 | const setupStatefulComponent = (instance) => {
472 | const Component = instance.type;
473 | const { setup } = Component;
474 | // ctx
475 | instance.proxy = new Proxy({
476 | _: instance
477 | }, publickInstanceProxyhandlers);
478 | // 用户可能不会写 setup 函数
479 | if (setup) {
480 | // 初始化获取 instance
481 | setCurrentInstance(instance);
482 | // 可以返回一个 fn 也可能是一个 object
483 | const setupResult = setup(shallowReadonly(instance.props), {
484 | emit: instance.emit
485 | });
486 | handleSetupResult(instance, setupResult);
487 | // 重制获取 instance
488 | setCurrentInstance(null);
489 | }
490 | };
491 | const handleSetupResult = (instance, setupResult) => {
492 | // function or object
493 | // TODO function
494 | if (typeof setupResult === 'object') {
495 | instance.setupState = proxyRefs(setupResult);
496 | }
497 | // 初始化 render 函数
498 | finishCompentSetup(instance);
499 | };
500 | const finishCompentSetup = (instance) => {
501 | const Component = instance.type;
502 | if (compiler && !Component.render) {
503 | if (Component.template) {
504 | Component.render = compiler(Component.template);
505 | }
506 | }
507 | instance.render = Component.render;
508 | };
509 | const getCurrentInstance = () => {
510 | return currentInstance;
511 | };
512 | const setCurrentInstance = (instance) => {
513 | currentInstance = instance;
514 | };
515 | let compiler;
516 | const registerRuntimeCompiler = (_compiler) => {
517 | compiler = _compiler;
518 | };
519 |
520 | const provide = (key, value) => {
521 | // 存
522 | const currentInstance = getCurrentInstance();
523 | if (currentInstance) {
524 | let { provides } = currentInstance;
525 | const parentProvides = currentInstance.parent.provides;
526 | // init
527 | // 只需要初始化的时候 执行一次
528 | // 当初始化完成以后实例的 provides 肯定是和父级的 provides相等
529 | // 调用过过 provide 以后 当前实例的 provides 肯定和父级不一样(因为有赋值操作)
530 | if (provides === parentProvides) {
531 | // 将当前 provide 实例的原型指向父级
532 | provides = currentInstance.provides = Object.create(parentProvides);
533 | }
534 | provides[key] = value;
535 | }
536 | };
537 | const inject = (key, defaultVal) => {
538 | // 取
539 | const currentInstance = getCurrentInstance();
540 | if (currentInstance) {
541 | const { parent } = currentInstance;
542 | const parentProvides = parent.provides;
543 | if (key in parentProvides) {
544 | return parentProvides[key];
545 | }
546 | else if (defaultVal) {
547 | if (typeof defaultVal === 'function') {
548 | return defaultVal();
549 | }
550 | return defaultVal;
551 | }
552 | }
553 | };
554 |
555 | const shouldUpdateComponent = (prevVNode, nextVNode) => {
556 | const { props: prevProps } = prevVNode;
557 | const { props: nextProps } = nextVNode;
558 | for (const key in nextProps) {
559 | if (nextProps[key] !== prevProps[key]) {
560 | return true;
561 | }
562 | }
563 | return false;
564 | };
565 |
566 | const queue = [];
567 | // 用来标识是否需要 执行 创建 promise
568 | let isFlushPending = false;
569 | const queueJobs = (job) => {
570 | // 如果队列中没有 该 任务
571 | if (!queue.includes(job)) {
572 | queue.push(job);
573 | }
574 | // 创建一个 promise 去接收
575 | queueFlush();
576 | };
577 | const queueFlush = () => {
578 | if (isFlushPending) {
579 | return;
580 | }
581 | isFlushPending = true;
582 | nextTick(() => {
583 | flushJobs();
584 | });
585 | };
586 | const flushJobs = () => {
587 | // 执行完 微任务以后在将开关打开。
588 | isFlushPending = false;
589 | let job;
590 | while (job = queue.shift()) {
591 | job && job();
592 | }
593 | };
594 | const p = Promise.resolve();
595 | const nextTick = (fn) => {
596 | // 如果用户传入 nextTick 的回调函数,则执行
597 | // 否则,执行 Promise.resolve 的回调函数
598 | return fn ? p.then(fn) : p;
599 | };
600 |
601 | // custom render
602 | const createRender = (options) => {
603 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
604 | // Component
605 | const processComponent = (n1, n2, container, parentComponent, anchor) => {
606 | if (!n1) {
607 | mountComponent(n2, container, parentComponent, anchor);
608 | }
609 | else {
610 | updateComponent(n1, n2);
611 | }
612 | };
613 | const updateComponent = (n1, n2) => {
614 | const instance = n2.component = n1.component;
615 | // 如果 props 完全相同 则不需要更新
616 | if (shouldUpdateComponent(n1, n2)) {
617 | instance.next = n2;
618 | instance.update();
619 | }
620 | else {
621 | n2.el = n1.el;
622 | n2.vnode = n2;
623 | }
624 | };
625 | const mountComponent = (initinalVNode, container, parentComponent, anchor) => {
626 | const instance = initinalVNode.component = createComponentInstance(initinalVNode, parentComponent);
627 | setupComponent(instance);
628 | setupRenderEffect(instance, initinalVNode, container, anchor);
629 | };
630 | const mountChildren = (children, container, parentComponent, anchor) => {
631 | // 遍历 children 拿到节点 再次调用patch
632 | children.map(childrenItem => {
633 | patch(null, childrenItem, container, parentComponent, anchor);
634 | });
635 | };
636 | // Element
637 | const processElement = (n1, n2, container, parentComponent, anchor) => {
638 | if (!n1) {
639 | mountElement(n2, container, parentComponent, anchor);
640 | }
641 | else {
642 | patchElement(n1, n2, parentComponent, anchor);
643 | }
644 | };
645 | const patchElement = (n1, n2, parentComponent, anchor) => {
646 | // console.log('patchElement')
647 | // console.log('n1', n1)
648 | // console.log('n2', n2)
649 | // props 修改 有以下几种情况:
650 | // 1.之前属性的值和现在的值不一样了 --> 修改
651 | // 2.之前属性的值变成 undefined 或者 null --> 删除
652 | // 3.之前属性的值 现在没有了 --> 删除
653 | const oldProps = n1.props || EMPTY_OBJ;
654 | const newProps = n2.props || EMPTY_OBJ;
655 | // 当 第二次 patchElement 时 第一次的 n2 应该当作 第二次的 n1 去使用
656 | // 但是 n2 上并不存在 el 所以此处应当赋值以便于第二次调用。
657 | const el = n2.el = n1.el;
658 | patchProps(el, oldProps, newProps);
659 | // children 修改有以下几种情况
660 | // text --> text
661 | // text --> array
662 | // array --> array
663 | // array --> text
664 | patchChildren(n1, n2, el, parentComponent, anchor);
665 | };
666 | const patchProps = (el, oldProps, newProps) => {
667 | if (oldProps !== newProps) {
668 | for (const key in newProps) {
669 | const prevProp = oldProps[key];
670 | const nextProp = newProps[key];
671 | if (prevProp !== nextProp) {
672 | // 不相等的时候更新
673 | hostPatchProp(el, key, prevProp, nextProp);
674 | }
675 | }
676 | // 不等与空对象 才会去对比
677 | // 不能直接 rops !== {} 这个相当于创建了一个新的内存地址 所以这个判断一定是 true
678 | if (oldProps !== EMPTY_OBJ) {
679 | // 如果新设置的props 在原来的 props中不存在 则直接删除掉。
680 | for (const key in oldProps) {
681 | const prevProp = oldProps[key];
682 | if (!(key in newProps)) {
683 | hostPatchProp(el, key, prevProp, null);
684 | }
685 | }
686 | }
687 | }
688 | };
689 | const patchChildren = (n1, n2, container, parentComponent, anchor) => {
690 | const prevShapeFlag = n1.shapeFlag;
691 | const nextShapeFlag = n2.shapeFlag;
692 | const prevChildren = n1.children;
693 | const nextChildren = n2.children;
694 | if (nextShapeFlag & 4 /* TEXT_CHILDREN */) {
695 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
696 | // 老的是 array & 新的是 text
697 | // 1.把老的 children 清空
698 | unmountChildren(n1.children);
699 | // 2.设置 text
700 | // hostSetElementText(container, nextChildren)
701 | }
702 | // 老的是 text & 新的是 text
703 | if (prevChildren !== nextChildren) {
704 | hostSetElementText(container, nextChildren);
705 | }
706 | }
707 | else {
708 | // 老的是 array & 新的是 text
709 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
710 | // 1.把老的 text 清空
711 | hostSetElementText(container, '');
712 | mountChildren(nextChildren, container, parentComponent, anchor);
713 | }
714 | else {
715 | // 老的是 array 新的也是 array
716 | patchKeyedChildren(prevChildren, nextChildren, container, parentComponent, anchor);
717 | }
718 | }
719 | };
720 | const patchKeyedChildren = (c1, c2, container, parentComponent, parentAnchor) => {
721 | let i = 0;
722 | let l2 = c2.length;
723 | let e1 = c1.length - 1;
724 | let e2 = l2 - 1;
725 | // i 标识双端对比的相同部分的下标
726 | // e1 e2 分别表示 原数据 和现数据 末尾端 的下标
727 | // ------> 左侧对比
728 | while (i <= e1 && i <= e2) {
729 | const n1 = c1[i];
730 | const n2 = c2[i];
731 | if (isSameVNodeType(n1, n2)) {
732 | patch(n1, n2, container, parentComponent, parentAnchor);
733 | }
734 | else {
735 | break;
736 | }
737 | // 相等的时候 每次移动指针 i
738 | i++;
739 | }
740 | // ------> 右侧对比
741 | while (i <= e1 && i <= e2) {
742 | const n1 = c1[e1];
743 | const n2 = c2[e2];
744 | if (isSameVNodeType(n1, n2)) {
745 | patch(n1, n2, container, parentComponent, parentAnchor);
746 | }
747 | else {
748 | break;
749 | }
750 | e1--;
751 | e2--;
752 | }
753 | if (i > e1) {
754 | // 新的比老的多 需要创建
755 | // 左侧右侧 都有效果
756 | if (i <= e2) {
757 | const nextPos = e2 + 1;
758 | // const anchor = e2 + 1 >= l2 ? null : c2[nextPos].el
759 | const anchor = e2 + 1 < l2 ? c2[nextPos].el : null;
760 | while (i <= e2) {
761 | patch(null, c2[i], container, parentComponent, anchor);
762 | // 一定记得移动 指针 否则会死循环。
763 | i++;
764 | }
765 | }
766 | }
767 | else if (i > e2) {
768 | // 新的比老的少 需要删除
769 | // 左侧右侧 都有效果
770 | while (i <= e1) {
771 | // remove
772 | hostRemove(c1[i].el);
773 | i++;
774 | }
775 | }
776 | else {
777 | // 乱序部分 中间对比
778 | let s1 = i;
779 | let s2 = i;
780 | // a,b,(c,e,d),f,g
781 | // a,b,(e,c),f,g
782 | // 设定一个值来判断是不是所有的 新数据 中的 数据都比较完了,用来剔除就数据比新数据多,即(去掉这里的 d)。
783 | // 此处是索引需要 +1
784 | const toBePatched = e2 - s2 + 1;
785 | let patched = 0;
786 | // 是否需要移动
787 | let moved = false;
788 | let maxNewIndexSoFar = 0;
789 | // 建立一个 key 映射表
790 | const keyToNewIndexMap = new Map();
791 | // 最长递增子序列
792 | const newIndexToOldIndexMap = new Array(toBePatched);
793 | for (let i = 0; i < toBePatched; i++) {
794 | newIndexToOldIndexMap[i] = 0;
795 | }
796 | for (let i = s2; i <= e2; i++) {
797 | const nextChild = c2[i];
798 | keyToNewIndexMap.set(nextChild.key, i);
799 | }
800 | let newIndex;
801 | for (let i = s1; i <= e1; i++) {
802 | const prevChild = c1[i];
803 | // 如果 所有新的不同节点都已经 patch 完毕
804 | if (patched >= toBePatched) {
805 | hostRemove(prevChild.el);
806 | continue;
807 | }
808 | // 如果用户设置了 key 值
809 | if (prevChild.key != null) {
810 | newIndex = keyToNewIndexMap.get(prevChild.key);
811 | }
812 | else {
813 | for (let j = s2; j <= e2; j++) {
814 | if (isSameVNodeType(prevChild, c2[j])) {
815 | // 如果存在表示找到的原来的节点对应的映射,直接跳出
816 | newIndex = j;
817 | break;
818 | }
819 | }
820 | }
821 | //
822 | if (newIndex === undefined) {
823 | hostRemove(prevChild.el);
824 | }
825 | else {
826 | if (newIndex >= maxNewIndexSoFar) {
827 | maxNewIndexSoFar = newIndex;
828 | }
829 | else {
830 | moved = true;
831 | }
832 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
833 | patch(prevChild, c2[newIndex], container, parentComponent, null);
834 | patched++;
835 | }
836 | }
837 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];
838 | let j = increasingNewIndexSequence.length - 1;
839 | for (let i = toBePatched - 1; i >= 0; i--) {
840 | const nextIndex = i + s2;
841 | const nextChild = c2[nextIndex];
842 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
843 | if (newIndexToOldIndexMap[i] === 0) {
844 | patch(null, nextChild, container, parentComponent, anchor);
845 | }
846 | else if (moved) {
847 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
848 | console.log('移动位置');
849 | hostInsert(nextChild.el, container, anchor);
850 | }
851 | else {
852 | j--;
853 | }
854 | }
855 | }
856 | }
857 | };
858 | const isSameVNodeType = (n1, n2) => {
859 | // type key 两个东西去判断是不是想等
860 | return n1.type === n2.type && n1.key === n2.key;
861 | };
862 | const unmountChildren = (children) => {
863 | children.map(item => {
864 | const el = item.el;
865 | // remove
866 | hostRemove(el);
867 | });
868 | };
869 | const mountElement = (vnode, container, parentComponent, anchor) => {
870 | // vnode --> element 类型的 --> div
871 | const el = vnode.el = hostCreateElement(vnode.type);
872 | // children 可能是 string array
873 | const { children, props, shapeFlag } = vnode;
874 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
875 | // children
876 | el.textContent = children;
877 | }
878 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
879 | mountChildren(vnode.children, el, parentComponent, anchor);
880 | }
881 | // props
882 | for (const key in props) {
883 | let val;
884 | if (Array.isArray(props[key])) {
885 | val = props[key].join(' ');
886 | }
887 | else {
888 | val = props[key];
889 | }
890 | hostPatchProp(el, key, null, val);
891 | }
892 | // 挂载在页面上
893 | hostInsert(el, container, anchor);
894 | };
895 | // Fragment
896 | const processFragment = (n1, n2, container, parentComponent, anchor) => {
897 | mountChildren(n2.children, container, parentComponent, anchor);
898 | };
899 | // Text
900 | const processText = (n1, n2, container) => {
901 | const { children } = n2;
902 | const textNode = n2.el = document.createTextNode(children);
903 | container.append(textNode);
904 | };
905 | const render = (vnode, container) => {
906 | patch(null, vnode, container, null, null);
907 | };
908 | const patch = (n1, n2, container, parentComponent, anchor) => {
909 | // 判断一下 vnode 类型
910 | // 调用对应的方法去处理
911 | const { type, shapeFlag } = n2;
912 | switch (type) {
913 | case Fragment:
914 | processFragment(n1, n2, container, parentComponent, anchor);
915 | break;
916 | case Text:
917 | processText(n1, n2, container);
918 | break;
919 | default:
920 | if (shapeFlag & 1 /* ELEMENT */) {
921 | processElement(n1, n2, container, parentComponent, anchor);
922 | }
923 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
924 | processComponent(n1, n2, container, parentComponent, anchor);
925 | }
926 | break;
927 | }
928 | };
929 | const setupRenderEffect = (instance, initinalVNode, container, anchor) => {
930 | // 利用 effect 做依赖收集
931 | instance.update = effect(() => {
932 | if (!instance.isMounted) {
933 | const { proxy } = instance;
934 | // 这里传递的 第一个 proxy 是组件实例this,第二个 proxy 是当做组件的参数,方便 render 函数中调用 ctx.xxx
935 | // 这里是方便组件内部调用 获取 ctx 的操作
936 | // 后续可能还有很多参数 (_cache, $props, $setup, $data, $options)
937 | const subTree = instance.subTree = instance.render.call(proxy, proxy);
938 | // vnode --> patch
939 | // vnode --> element --> mount
940 | patch(null, subTree, container, instance, anchor);
941 | initinalVNode.el = subTree.el;
942 | instance.isMounted = true;
943 | }
944 | else {
945 | // 这里 在更新的时候还需要更新组件的 props
946 | // 需要 更新完成以后的 vnode
947 | // vnode: 更新之前的 虚拟节点
948 | // next: 下次要更新的 虚拟节点
949 | const { next, vnode } = instance;
950 | if (next) {
951 | // 更新 el
952 | next.el = vnode.el;
953 | updateComponentPreRender(instance, next);
954 | }
955 | const { proxy } = instance;
956 | const prevSubTree = instance.subTree;
957 | // 这里传递的 第一个 proxy 是组件实例this,第二个 proxy 是当做组件的参数,方便 render 函数中调用 ctx.xxx
958 | // 这里是方便组件内部调用 获取 ctx 的操作
959 | // 后续可能还有很多参数 (_cache, $props, $setup, $data, $options)
960 | const subTree = instance.subTree = instance.render.call(proxy, proxy);
961 | // vnode --> patch
962 | // vnode --> element --> mount
963 | patch(prevSubTree, subTree, container, instance, anchor);
964 | initinalVNode.el = subTree.el;
965 | instance.isMounted = true;
966 | }
967 | }, {
968 | // 优化 不需要每次更新数据都去执行 effect 收集的依赖。
969 | scheduler: () => {
970 | // 建立一个 微任务去 等待数据完成在执行回调。
971 | queueJobs(instance.update);
972 | }
973 | });
974 | };
975 | return {
976 | createApp: createAppAPI(render)
977 | };
978 | };
979 | const updateComponentPreRender = (instance, nextVNode) => {
980 | instance.vnode = nextVNode;
981 | instance.next = null;
982 | instance.props = nextVNode.props;
983 | };
984 |
985 | const createElement = (type) => {
986 | return document.createElement(type);
987 | };
988 | const patchProp = (el, key, prevVal, nextVal) => {
989 | // 实现注册 事件
990 | const isOn = key => /^on[A-Z]/.test(key);
991 | if (isOn(key)) {
992 | const eventName = key.slice(2).toLowerCase();
993 | el.addEventListener(eventName, nextVal);
994 | }
995 | else {
996 | // 如果设置值为 undefined 或者 null 的时候删除掉该属性
997 | if (nextVal == null) {
998 | el.removeAttribute(key);
999 | }
1000 | else {
1001 | el.setAttribute(key, nextVal);
1002 | }
1003 | }
1004 | };
1005 | const insert = (child, parent, anchor = null) => {
1006 | // parent.append(child)
1007 | parent.insertBefore(child, anchor);
1008 | };
1009 | const remove = (children) => {
1010 | const parent = children.parentNode;
1011 | if (parent) {
1012 | parent.removeChild(children);
1013 | }
1014 | };
1015 | const setElementText = (el, text) => {
1016 | el.textContent = text;
1017 | };
1018 | const render = createRender({
1019 | createElement,
1020 | patchProp,
1021 | insert,
1022 | remove,
1023 | setElementText
1024 | });
1025 | const createApp = (...args) => {
1026 | return render.createApp(...args);
1027 | };
1028 |
1029 | var runtimeDom = /*#__PURE__*/Object.freeze({
1030 | __proto__: null,
1031 | createElement: createElement,
1032 | patchProp: patchProp,
1033 | insert: insert,
1034 | render: render,
1035 | createApp: createApp,
1036 | createAppAPI: createAppAPI,
1037 | renderSlots: renderSlots,
1038 | h: h,
1039 | createElementVNode: createVNode,
1040 | createVNode: createVNode,
1041 | createTextVNode: createTextVNode,
1042 | Fragment: Fragment,
1043 | Text: Text,
1044 | createComponentInstance: createComponentInstance,
1045 | setupComponent: setupComponent,
1046 | setupStatefulComponent: setupStatefulComponent,
1047 | getCurrentInstance: getCurrentInstance,
1048 | registerRuntimeCompiler: registerRuntimeCompiler,
1049 | provide: provide,
1050 | inject: inject,
1051 | createRender: createRender,
1052 | queueJobs: queueJobs,
1053 | nextTick: nextTick,
1054 | toDisplayString: toDisplayString,
1055 | extend: extend,
1056 | EMPTY_OBJ: EMPTY_OBJ,
1057 | isObject: isObject,
1058 | isString: isString,
1059 | hasChanged: hasChanged,
1060 | getShapeFlag: getShapeFlag,
1061 | hasOwn: hasOwn,
1062 | camelize: camelize,
1063 | toHandlerKey: toHandlerKey,
1064 | getSequence: getSequence
1065 | });
1066 |
1067 | const TO_DISPLAY_STRING = Symbol('toDisplayString');
1068 | const CREATE_ELEMENT_VNODE = Symbol('ctreateElementVNode');
1069 | const helperMapName = {
1070 | [TO_DISPLAY_STRING]: 'toDisplayString',
1071 | [CREATE_ELEMENT_VNODE]: 'createElementVNode'
1072 | };
1073 |
1074 | const generate = (ast) => {
1075 | const context = createCodegenContext();
1076 | const { push } = context;
1077 | genFunctionPreamble(ast, context);
1078 | push('return ');
1079 | const functionName = 'render';
1080 | const args = ['_ctx', '_cache', '$props', '$setup', '$data', '$options'];
1081 | const signature = args.join(', ');
1082 | push(`function ${functionName} (${signature}) { `);
1083 | push('return ');
1084 | genNode(ast.codegenNode, context);
1085 | push(' }');
1086 | return {
1087 | code: context.code
1088 | };
1089 | };
1090 | const genNodeList = (nodes, context) => {
1091 | const { push } = context;
1092 | for (let i = 0; i < nodes.length; i++) {
1093 | const node = nodes[i];
1094 | if (isString(node)) {
1095 | push(node);
1096 | }
1097 | else {
1098 | genNode(node, context);
1099 | }
1100 | if (i < nodes.length - 1) {
1101 | push(', ');
1102 | }
1103 | }
1104 | };
1105 | const genNode = (node, context) => {
1106 | // 获取 ast的入口,在外部处理内容。
1107 | // 区分一下类型
1108 | switch (node.type) {
1109 | case 3 /* TEXT */:
1110 | genText(node, context);
1111 | break;
1112 | case 0 /* INTERPOLATION */:
1113 | genInterpolation(node, context);
1114 | break;
1115 | case 1 /* SIMPLE_EXPRESSION */:
1116 | genExpression(node, context);
1117 | break;
1118 | case 2 /* ELEMENT */:
1119 | genElement(node, context);
1120 | break;
1121 | case 5 /* COMPOUND_EXPRESSION */:
1122 | genCompoundExpression(node, context);
1123 | break;
1124 | }
1125 | };
1126 | const createCodegenContext = () => {
1127 | const context = {
1128 | code: '',
1129 | push(source) {
1130 | context.code += source;
1131 | },
1132 | getHelperName(key) {
1133 | return `_${helperMapName[key]}`;
1134 | }
1135 | };
1136 | return context;
1137 | };
1138 | const genFunctionPreamble = (ast, context) => {
1139 | const { push } = context;
1140 | const VueBinging = 'vue';
1141 | const aliasHelpers = (s) => `${helperMapName[s]}: _${helperMapName[s]}`;
1142 | if (ast.helpers.length > 0) {
1143 | push(`const { ${ast.helpers.map(aliasHelpers).join(', ')} } = ${VueBinging}`);
1144 | }
1145 | push('\n');
1146 | };
1147 | const genText = (node, context) => {
1148 | const { push } = context;
1149 | push(`'${node.content}'`);
1150 | };
1151 | const genInterpolation = (node, context) => {
1152 | const { push, getHelperName } = context;
1153 | push(`${getHelperName(TO_DISPLAY_STRING)}(`);
1154 | genNode(node.content, context);
1155 | push(`)`);
1156 | };
1157 | const genExpression = (node, context) => {
1158 | const { push } = context;
1159 | push(node.content);
1160 | };
1161 | const genElement = (node, context) => {
1162 | const { push, getHelperName } = context;
1163 | const { tag, children, props } = node;
1164 | push(`${getHelperName(CREATE_ELEMENT_VNODE)}(`);
1165 | genNodeList(getNullAble([tag, props, children]), context);
1166 | // genNode(children, context)
1167 | // push(')')
1168 | };
1169 | const genCompoundExpression = (node, context) => {
1170 | const { push } = context;
1171 | const { children } = node;
1172 | for (let i = 0; i < children.length; i++) {
1173 | const child = children[i];
1174 | if (isString(child)) {
1175 | push(child);
1176 | }
1177 | else {
1178 | genNode(child, context);
1179 | }
1180 | }
1181 | push(')');
1182 | };
1183 | const getNullAble = (arg) => {
1184 | return arg.map(item => item || 'null');
1185 | };
1186 |
1187 | const interpolationOpenDelimiter = '{{';
1188 | const interpolationCloseDelimiter = '}}';
1189 | const ElementCloseDelimiter = '<';
1190 | const baseParse = (content) => {
1191 | const context = createparserContent(content);
1192 | // 初始化的时候 标签数组 传递一个 []
1193 | return createRoot(parserChildren(context, []));
1194 | };
1195 | const parserChildren = (context, ancestors) => {
1196 | const nodes = [];
1197 | // 循环解析 字符串。
1198 | while (!isEnd(context, ancestors)) {
1199 | let node;
1200 | const source = context.source;
1201 | // 字符串是以 {{ 开头的才需要处理
1202 | if (source.startsWith(interpolationOpenDelimiter)) {
1203 | // 插值
1204 | node = parseInterpolation(context);
1205 | }
1206 | else if (source.startsWith(ElementCloseDelimiter)) { // source[0] === '<'
1207 | // element
1208 | if (/[a-z]/i.test(source[1])) {
1209 | node = parserElement(context, ancestors);
1210 | }
1211 | }
1212 | // 如果前面的的两个判断都没有命中,表示是文本。
1213 | if (!node) {
1214 | node = parseText(context);
1215 | }
1216 | nodes.push(node);
1217 | }
1218 | return nodes;
1219 | };
1220 | const isEnd = (context, ancestors) => {
1221 | // 1.当遇到结束标签的时候
1222 | const source = context.source;
1223 | if (source.startsWith('')) {
1224 | for (let i = ancestors.length - 1; i >= 0; i--) {
1225 | const tag = ancestors[i].tag;
1226 | if (startWithEndTagOpen(source, tag)) {
1227 | return true;
1228 | }
1229 | }
1230 | }
1231 | // 2.context.source 有值的时候
1232 | return !context.source;
1233 | };
1234 | const createRoot = (children) => {
1235 | return {
1236 | children,
1237 | type: 4 /* ROOT */
1238 | };
1239 | };
1240 | const createparserContent = (content) => {
1241 | return {
1242 | source: content
1243 | };
1244 | };
1245 | const advanceBy = (context, length) => {
1246 | context.source = context.source.slice(length);
1247 | };
1248 | // 插值
1249 | const parseInterpolation = (context) => {
1250 | // {{ message }} ---> 拿到这个 message
1251 | // 从第二个字符位置开始查找, 到 '}}' 结束
1252 | const closeIndex = context.source.indexOf(interpolationCloseDelimiter, interpolationOpenDelimiter.length);
1253 | // 去掉 前面的 '{{'
1254 | advanceBy(context, interpolationCloseDelimiter.length);
1255 | const rawContentLength = closeIndex - interpolationOpenDelimiter.length;
1256 | // 可能存在空格 trim去掉~
1257 | // const rawContent = context.source.slice(0, rawContentLength)
1258 | const rawContent = parseTextData(context, rawContentLength);
1259 | const content = rawContent.trim();
1260 | advanceBy(context, interpolationCloseDelimiter.length);
1261 | //
1262 | // TODO 思考 上面的逻辑 可以使用 slice(2, -2) 来直接获取吗?
1263 | // context.source = context.source.slice(2, -2)
1264 | // const content = context.source.slice(interpolationOpenDelimiter.length, -interpolationCloseDelimiter.length).trim()
1265 | return {
1266 | type: 0 /* INTERPOLATION */,
1267 | content: {
1268 | type: 1 /* SIMPLE_EXPRESSION */,
1269 | content
1270 | }
1271 | };
1272 | };
1273 | // element
1274 | // 在调用 parserElement 的时候,使用栈的 先进后出特性,把 element push进去
1275 | // 之后在完成解析以后取出,比较标签有没有闭合。
1276 | const parserElement = (context, ancestors) => {
1277 | // 这里需要调用两次!!!切记 开始标签匹配一次
1278 | const element = parserTag(context, 0 /* Start */);
1279 | ancestors.push(element);
1280 | element.children = parserChildren(context, ancestors);
1281 | ancestors.pop();
1282 | // 这里需要判断标签是不是匹配,如果匹配才能销毁,或者删掉。
1283 | if (startWithEndTagOpen(context.source, element.tag)) {
1284 | // 结束标签匹配一次!!!
1285 | parserTag(context, 1 /* End */);
1286 | }
1287 | else {
1288 | throw new Error(`缺少结束标签: ${element.tag}`);
1289 | }
1290 | return element;
1291 | };
1292 | const startWithEndTagOpen = (source, tag) => {
1293 | return source.startsWith('') && source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase();
1294 | };
1295 | const parserTag = (context, type) => {
1296 | // 1.解析 tag
1297 | //
1298 | //
1299 | // 匹配以 < 开头或者以 开头的 字符,/ 可以没有。
1300 | const match = /^<\/?([a-z]*)/i.exec(context.source);
1301 | const tag = match[1];
1302 | // 2.删除处理完成的代码
1303 | advanceBy(context, match[0].length);
1304 | advanceBy(context, 1);
1305 | if (type === 1 /* End */) {
1306 | // 如果是结束标签 () 直接不用返回 后面的东西了。
1307 | return;
1308 | }
1309 | return {
1310 | type: 2 /* ELEMENT */,
1311 | tag
1312 | };
1313 | };
1314 | // text 文本类型
1315 | const parseText = (context) => {
1316 | let endTokens = ['{{', '<'];
1317 | let endIndex = context.source.length;
1318 | // 遇到 {{ 或者 < 都应该直接停下,返回了
1319 | for (let i = 0; i < endTokens.length; i++) {
1320 | const index = context.source.indexOf(endTokens[i]);
1321 | // 当 字符串中 存在 {{ 表示是文本和 插值混合的。
1322 | if (index !== -1 && endIndex > index) {
1323 | endIndex = index;
1324 | }
1325 | }
1326 | // 1. 获取content
1327 | const content = parseTextData(context, endIndex);
1328 | return {
1329 | type: 3 /* TEXT */,
1330 | content
1331 | };
1332 | };
1333 | const parseTextData = (context, length) => {
1334 | const content = context.source.slice(0, length);
1335 | // 2. 推进
1336 | advanceBy(context, length);
1337 | return content;
1338 | };
1339 |
1340 | const transform = (root, options = {}) => {
1341 | const context = createTransformContext(root, options);
1342 | traverseNode(root, context);
1343 | createRootCodegen(root);
1344 | root.helpers = [...context.helpers.keys()];
1345 | };
1346 | const createRootCodegen = (root) => {
1347 | const child = root.children[0];
1348 | if (child.type === 2 /* ELEMENT */) {
1349 | root.codegenNode = child.codegenNode;
1350 | }
1351 | else {
1352 | root.codegenNode = root.children[0];
1353 | }
1354 | };
1355 | const traverseNode = (node, context) => {
1356 | const nodeTransforms = context.nodeTransforms;
1357 | const exitFns = [];
1358 | for (let i = 0; i < nodeTransforms.length; i++) {
1359 | const transform = nodeTransforms[i];
1360 | const onExit = transform(node, context);
1361 | if (onExit) {
1362 | exitFns.push(onExit);
1363 | }
1364 | }
1365 | // 这里需要 分情况处理不同类型的逻辑
1366 | switch (node.type) {
1367 | // 插值类型
1368 | case 0 /* INTERPOLATION */:
1369 | context.helper(TO_DISPLAY_STRING);
1370 | break;
1371 | // root 根结点
1372 | case 4 /* ROOT */:
1373 | case 2 /* ELEMENT */:
1374 | // 处理 children
1375 | traverseChildren(node, context);
1376 | break;
1377 | }
1378 | let i = exitFns.length;
1379 | while (i--) {
1380 | exitFns[i]();
1381 | }
1382 | };
1383 | const createTransformContext = (root, options) => {
1384 | const context = {
1385 | root,
1386 | helpers: new Map(),
1387 | helper(key) {
1388 | context.helpers.set(key, 1);
1389 | },
1390 | nodeTransforms: options.nodeTransforms || []
1391 | };
1392 | return context;
1393 | };
1394 | const traverseChildren = (node, context) => {
1395 | const children = node.children;
1396 | for (let i = 0; i < children.length; i++) {
1397 | const node = children[i];
1398 | traverseNode(node, context);
1399 | }
1400 | };
1401 |
1402 | const createVNodeCall = (context, tag, props, children) => {
1403 | context.helper(CREATE_ELEMENT_VNODE);
1404 | return {
1405 | type: 2 /* ELEMENT */,
1406 | tag,
1407 | props,
1408 | children
1409 | };
1410 | };
1411 |
1412 | const transformElement = (node, context) => {
1413 | return () => {
1414 | if (node.type === 2 /* ELEMENT */) {
1415 | // 以下中间处理层,处理一下数据~
1416 | // tag
1417 | const vnodeTag = `'${node.tag}'`;
1418 | // props
1419 | let vnodeProps;
1420 | let vnodeChild = node.children[0];
1421 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChild);
1422 | }
1423 | };
1424 | };
1425 |
1426 | const transformExpression = (node) => {
1427 | if (node.type === 0 /* INTERPOLATION */) {
1428 | processExpression(node.content);
1429 | }
1430 | };
1431 | const processExpression = (node) => {
1432 | node.content = `_ctx.${node.content}`;
1433 | };
1434 |
1435 | const isText = (node) => {
1436 | return (node.type === 3 /* TEXT */ || node.type === 0 /* INTERPOLATION */);
1437 | };
1438 |
1439 | const transformText = (node) => {
1440 | return () => {
1441 | const { children } = node;
1442 | let currentContainer;
1443 | if (node.type === 2 /* ELEMENT */) {
1444 | for (let i = 0; i < children.length; i++) {
1445 | const child = children[i];
1446 | if (isText(child)) {
1447 | for (let j = i + 1; j < children.length; j++) {
1448 | const nextChild = children[j];
1449 | if (isText(nextChild)) {
1450 | if (!currentContainer) {
1451 | currentContainer = children[i] = {
1452 | type: 5 /* COMPOUND_EXPRESSION */,
1453 | children: [child]
1454 | };
1455 | }
1456 | currentContainer.children.push(' + ');
1457 | currentContainer.children.push(nextChild);
1458 | // 添加完成的 元素需要去掉
1459 | children.splice(j, 1);
1460 | // 删除以后后面的元素前移,导致取错,--即可
1461 | j--;
1462 | }
1463 | else {
1464 | currentContainer = undefined;
1465 | break;
1466 | }
1467 | }
1468 | }
1469 | }
1470 | }
1471 | };
1472 | };
1473 |
1474 | function baseCompile(template) {
1475 | const ast = baseParse(template);
1476 | transform(ast, {
1477 | nodeTransforms: [transformExpression, transformElement, transformText]
1478 | });
1479 | return generate(ast);
1480 | }
1481 |
1482 | // mini-vue 出口
1483 | function compileToFunction(template) {
1484 | const { code } = baseCompile(template);
1485 | const render = new Function('vue', code)(runtimeDom);
1486 | return render;
1487 | }
1488 | registerRuntimeCompiler(compileToFunction);
1489 |
1490 | exports.EMPTY_OBJ = EMPTY_OBJ;
1491 | exports.Fragment = Fragment;
1492 | exports.Text = Text;
1493 | exports.camelize = camelize;
1494 | exports.createApp = createApp;
1495 | exports.createAppAPI = createAppAPI;
1496 | exports.createComponentInstance = createComponentInstance;
1497 | exports.createElement = createElement;
1498 | exports.createElementVNode = createVNode;
1499 | exports.createRender = createRender;
1500 | exports.createTextVNode = createTextVNode;
1501 | exports.createVNode = createVNode;
1502 | exports.extend = extend;
1503 | exports.getCurrentInstance = getCurrentInstance;
1504 | exports.getSequence = getSequence;
1505 | exports.getShapeFlag = getShapeFlag;
1506 | exports.h = h;
1507 | exports.hasChanged = hasChanged;
1508 | exports.hasOwn = hasOwn;
1509 | exports.inject = inject;
1510 | exports.insert = insert;
1511 | exports.isObject = isObject;
1512 | exports.isRef = isRef;
1513 | exports.isString = isString;
1514 | exports.nextTick = nextTick;
1515 | exports.patchProp = patchProp;
1516 | exports.provide = provide;
1517 | exports.proxyRefs = proxyRefs;
1518 | exports.queueJobs = queueJobs;
1519 | exports.ref = ref;
1520 | exports.registerRuntimeCompiler = registerRuntimeCompiler;
1521 | exports.render = render;
1522 | exports.renderSlots = renderSlots;
1523 | exports.setupComponent = setupComponent;
1524 | exports.setupStatefulComponent = setupStatefulComponent;
1525 | exports.toDisplayString = toDisplayString;
1526 | exports.toHandlerKey = toHandlerKey;
1527 | exports.unRef = unRef;
1528 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | const toDisplayString = (value) => {
2 | return String(value);
3 | };
4 |
5 | const extend = Object.assign;
6 | // 设置一个全局空对象方便后续曲比较
7 | const EMPTY_OBJ = {};
8 | const isObject = (value) => {
9 | return value !== null && typeof value === 'object';
10 | };
11 | const isString = (value) => {
12 | return value !== null && typeof value === 'string';
13 | };
14 | const hasChanged = (val, newValue) => {
15 | return !Object.is(val, newValue);
16 | };
17 | const getShapeFlag = (type) => {
18 | return typeof type === 'string'
19 | ? 1 /* ELEMENT */
20 | : 2 /* STATEFUL_COMPONENT */;
21 | };
22 | const hasOwn = (val = {}, key) => Object.prototype.hasOwnProperty.call(val, key);
23 | const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
24 | const camelize = (str) => {
25 | // ex: add-foo
26 | // _ 代表匹配到的值规则(-f)
27 | // targetValue 代表匹配到的值(f)
28 | return str.replace(/-(\w)/g, (_, targetValue) => {
29 | return targetValue ? targetValue.toUpperCase() : '';
30 | });
31 | };
32 | // add --> onAdd
33 | const toHandlerKey = (str) => str ? camelize('on' + capitalize(str)) : '';
34 | const getSequence = (arr) => {
35 | const p = arr.slice();
36 | const result = [0];
37 | let i, j, u, v, c;
38 | const len = arr.length;
39 | for (i = 0; i < len; i++) {
40 | const arrI = arr[i];
41 | if (arrI !== 0) {
42 | j = result[result.length - 1];
43 | if (arr[j] < arrI) {
44 | p[i] = j;
45 | result.push(i);
46 | continue;
47 | }
48 | u = 0;
49 | v = result.length - 1;
50 | while (u < v) {
51 | c = (u + v) >> 1;
52 | if (arr[result[c]] < arrI) {
53 | u = c + 1;
54 | }
55 | else {
56 | v = c;
57 | }
58 | }
59 | if (arrI < arr[result[u]]) {
60 | if (u > 0) {
61 | p[i] = result[u - 1];
62 | }
63 | result[u] = i;
64 | }
65 | }
66 | }
67 | u = result.length;
68 | v = result[u - 1];
69 | while (u-- > 0) {
70 | result[u] = v;
71 | v = p[v];
72 | }
73 | return result;
74 | };
75 |
76 | const Fragment = Symbol('Fragment');
77 | const Text = Symbol('Text');
78 | const createVNode = (type, props, children) => {
79 | const vnode = {
80 | type,
81 | props,
82 | children,
83 | component: null,
84 | key: props && props.key,
85 | shapeFlag: getShapeFlag(type),
86 | el: null
87 | };
88 | if (typeof children === 'string') {
89 | // 元素节点
90 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
91 | }
92 | else if (Array.isArray(children)) {
93 | // 组件节点
94 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
95 | }
96 | // 判断是否需要 slots 处理
97 | // 首先是必须是一个组件类型,其次 children 必须是一个对象
98 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
99 | if (typeof children === 'object') {
100 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
101 | }
102 | }
103 | return vnode;
104 | };
105 | const createTextVNode = (text) => {
106 | return createVNode(Text, {}, text);
107 | };
108 |
109 | const createAppAPI = (render) => {
110 | const createApp = (rootComponent) => {
111 | return {
112 | mount(rootContainer) {
113 | // 先转换成虚拟节点
114 | // component --> vnode
115 | // 后续所有的逻辑操作 都会基于 vnode 去操作
116 | const vnode = createVNode(rootComponent);
117 | render(vnode, rootContainer);
118 | }
119 | };
120 | };
121 | return createApp;
122 | };
123 |
124 | const renderSlots = (slots, slotName, props) => {
125 | const slot = slots[slotName];
126 | if (slot) {
127 | if (typeof slot === 'function') {
128 | return createVNode(Fragment, {}, slot(props));
129 | }
130 | }
131 | };
132 |
133 | const h = (type, props, children) => {
134 | return createVNode(type, props, children);
135 | };
136 |
137 | let activeEffect;
138 | let shouldTrack;
139 | class ReactiveEffect {
140 | constructor(fn, scheduler) {
141 | this.active = true; // stop 状态
142 | this.deps = [];
143 | this._fn = fn;
144 | this._scheduler = scheduler;
145 | }
146 | run() {
147 | // 会收集依赖
148 | // shouldTrack 来做区分
149 | if (!this.active) {
150 | return this._fn();
151 | }
152 | shouldTrack = true;
153 | activeEffect = this;
154 | const res = this._fn();
155 | // 全局变量 reset
156 | shouldTrack = false;
157 | return res;
158 | }
159 | stop() {
160 | if (this.active) {
161 | cleanUpEffect(this);
162 | this.active = false;
163 | if (this.onStop) {
164 | this.onStop();
165 | }
166 | }
167 | }
168 | }
169 | const isTracking = () => {
170 | // 判断是不是 应该 收集依赖 & 有没有全局的 effect
171 | return shouldTrack && activeEffect !== undefined;
172 | };
173 | const cleanUpEffect = (effect) => {
174 | effect.deps.map((dep) => {
175 | dep.delete(effect);
176 | });
177 | effect.deps.length = 0;
178 | };
179 | const effect = (fn, options = {}) => {
180 | const { scheduler } = options;
181 | // 初始化的时候就需要调用一次fn
182 | const _effect = new ReactiveEffect(fn, scheduler);
183 | // 将调用 options 中的参数 和 类上同名参数赋值
184 | // onStop --> onStop
185 | extend(_effect, options);
186 | _effect.run();
187 | // 将传进来的 fn 返回出去
188 | // bind 以当前的 effect 实例作为函数的 this 指针
189 | const runner = _effect.run.bind(_effect);
190 | runner.effect = _effect;
191 | return runner;
192 | };
193 | const targetMap = new Map();
194 | const trackEffects = (dep) => {
195 | // 如果没有 effect 实例直接不做后面的操作
196 | // if (!activeEffect) return
197 | // if (!shouldTrack) return
198 | dep.add(activeEffect);
199 | activeEffect.deps.push(dep);
200 | };
201 | const track = (target, key) => {
202 | if (!isTracking())
203 | return;
204 | // target --> key --> dep
205 | let depsMap = targetMap.get(target);
206 | // 不存在despMap 先初始化一下
207 | if (!depsMap) {
208 | depsMap = new Map();
209 | targetMap.set(target, depsMap);
210 | }
211 | // 不存在dep 先初始化一下
212 | let dep = depsMap.get(key);
213 | if (!dep) {
214 | dep = new Set();
215 | depsMap.set(key, dep);
216 | }
217 | trackEffects(dep);
218 | };
219 | const triggerEffects = (dep) => {
220 | // 循环调用 dep 的 run 方法 触发每一个 dep 的 _fn
221 | for (const effect of dep) {
222 | if (effect._scheduler) {
223 | effect._scheduler();
224 | }
225 | else {
226 | effect.run();
227 | }
228 | }
229 | };
230 | const trigger = (target, key) => {
231 | // 取出 target 对应的 depsMap
232 | let depsMap = targetMap.get(target);
233 | // 取出 key 对应的 dep
234 | let dep = depsMap.get(key);
235 | triggerEffects(dep);
236 | };
237 |
238 | let get;
239 | let readOnlyGet;
240 | let shallowReadonlyGet;
241 | let set;
242 | const createGetter = (isReadOnly = false, shallow = false) => {
243 | const get = (target, key) => {
244 | // target: { foo: 1 }
245 | // key: foo
246 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
247 | // 通过 isReadOnly 来判断是不是 reactive 对象
248 | // return !isReadOnly
249 | // ???
250 | // 这里是不是可以直接指定为true,因为调用 getter 函数 肯定是 proxy 对象,所以一定是 reactive
251 | return true;
252 | }
253 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
254 | return isReadOnly;
255 | }
256 | const res = Reflect.get(target, key);
257 | // 如果是 shallow 类型(即外层是响应是对象,里面的不是 且设计成只读模式)
258 | if (shallow) {
259 | return res;
260 | }
261 | if (!isReadOnly) {
262 | // 依赖收集
263 | track(target, key);
264 | }
265 | // 看看 res 是不是 object
266 | if (isObject(res)) {
267 | return isReadOnly ? readonly(res) : reactive(res);
268 | }
269 | return res;
270 | };
271 | return get;
272 | };
273 | const createSetter = () => {
274 | const set = (target, key, value) => {
275 | const res = Reflect.set(target, key, value);
276 | // 触发依赖
277 | trigger(target, key);
278 | return res;
279 | };
280 | return set;
281 | };
282 | get = createGetter();
283 | readOnlyGet = createGetter(true);
284 | shallowReadonlyGet = createGetter(true, true);
285 | set = createSetter();
286 | const mutableHandlers = {
287 | get,
288 | set
289 | };
290 | const readonlyHandlers = {
291 | get: readOnlyGet,
292 | set(target, key, value) {
293 | console.warn(`key: ${key} set 失败, 因为 target:`, target, `是 readonly状态!`);
294 | return true;
295 | }
296 | };
297 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
298 | get: shallowReadonlyGet
299 | });
300 |
301 | const createActiveObject = (raw, baseHandlers) => {
302 | if (!isObject(raw)) {
303 | console.warn(`target: ${raw} 必须是一个对象!`);
304 | }
305 | else {
306 | return new Proxy(raw, baseHandlers);
307 | }
308 | };
309 | const reactive = (raw) => {
310 | return createActiveObject(raw, mutableHandlers);
311 | };
312 | const readonly = (raw) => {
313 | return createActiveObject(raw, readonlyHandlers);
314 | };
315 | const shallowReadonly = (raw) => {
316 | return createActiveObject(raw, shallowReadonlyHandlers);
317 | };
318 |
319 | const emit = (instance, eventName, ...args) => {
320 | const { props } = instance;
321 | const handlerName = toHandlerKey(eventName);
322 | const handler = props[handlerName];
323 | handler && handler(...args);
324 | };
325 |
326 | const initProps = (instance, rawProps = {}) => {
327 | instance.props = rawProps;
328 | };
329 |
330 | const initSlots = (instance, children) => {
331 | const { vnode } = instance;
332 | // 如果是 slot 类型 再进行处理
333 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
334 | normalizeObjectSlots(instance.slots, children);
335 | }
336 | };
337 | const normalizeObjectSlots = (slots, children) => {
338 | for (const key in children) {
339 | const slotVal = children[key];
340 | // 将设计的 props 传入对应的 slot
341 | slots[key] = (props) => normalizeSlotValue(slotVal(props));
342 | }
343 | slots = slots;
344 | };
345 | const normalizeSlotValue = (value) => {
346 | // 传入的 children(slots) 是不是数组,不是数组转换一下
347 | return Array.isArray(value) ? value : [value];
348 | };
349 |
350 | const publicPropertiesMap = {
351 | $el: (i) => i.vnode.el,
352 | $slots: (i) => i.slots,
353 | $props: (i) => i.props,
354 | };
355 | const publickInstanceProxyhandlers = {
356 | get({ _: instance }, key) {
357 | // setupState
358 | // 这里必须要在这里 获取 setupState
359 | // 因为 只有在初始化组件 的时候 获取 setupState
360 | const { setupState, props } = instance;
361 | if (hasOwn(setupState, key)) {
362 | // setupState 里面获取值
363 | return setupState[key];
364 | }
365 | else if (hasOwn(props, key)) {
366 | return props[key];
367 | }
368 | // $el
369 | // if (key === '$el') {
370 | // // key --> $el
371 | // // 如果是 this.$el 则 key 值就是 $el
372 | // return vnode.el
373 | // }
374 | const publicGetter = publicPropertiesMap[key];
375 | if (publicGetter) {
376 | return publicGetter(instance);
377 | }
378 | }
379 | };
380 |
381 | class RefImpl {
382 | constructor(value) {
383 | this.__v_isRef = true;
384 | // value 如果是对象 要用 reactive 转换成响应式对象
385 | this._rawValue = value;
386 | this._value = covert(value);
387 | this.dep = new Set();
388 | }
389 | get value() {
390 | trackRefValue(this);
391 | return this._value;
392 | }
393 | set value(newValue) {
394 | if (hasChanged(this._rawValue, newValue)) {
395 | // 一定先修改值,再触发\
396 | this._rawValue = newValue;
397 | this._value = covert(newValue);
398 | triggerEffects(this.dep);
399 | }
400 | }
401 | }
402 | const covert = (value) => {
403 | return isObject(value) ? reactive(value) : value;
404 | };
405 | const trackRefValue = (ref) => {
406 | if (isTracking()) {
407 | trackEffects(ref.dep);
408 | }
409 | };
410 | const ref = (value) => {
411 | return new RefImpl(value);
412 | };
413 | const isRef = (ref) => {
414 | return !!ref.__v_isRef;
415 | };
416 | const unRef = (ref) => {
417 | return isRef(ref) ? ref.value : ref;
418 | };
419 | const proxyRefs = (objectWithRefs) => {
420 | // 如果获取的值 是 ref 类型 那么就返回 .value
421 | // 如果获取的值 不是 ref 那么就直接返回 它本身的值
422 | return new Proxy(objectWithRefs, {
423 | get(target, key) {
424 | return unRef(Reflect.get(target, key));
425 | },
426 | set(target, key, value) {
427 | // 看看是 是 ref 类型 是的话修改 .value
428 | // 看看是 不是 ref 类型 是的话修改 本身的值
429 | if (isRef(target[key]) && !isRef(value)) {
430 | return Reflect.set(target[key], 'value', value);
431 | // return target[key].value = value
432 | }
433 | else {
434 | return Reflect.set(target, key, value);
435 | }
436 | }
437 | });
438 | };
439 |
440 | let currentInstance;
441 | const createComponentInstance = (vnode, parent) => {
442 | const component = {
443 | vnode,
444 | type: vnode.type,
445 | props: {},
446 | emit: {},
447 | slots: {},
448 | next: null,
449 | setupState: {},
450 | parent,
451 | // parent,
452 | provides: parent ? parent.provides : {},
453 | isMounted: false,
454 | subTree: {}
455 | };
456 | component.emit = emit.bind(null, component);
457 | return component;
458 | };
459 | const setupComponent = (instance) => {
460 | // initProps
461 | initProps(instance, instance.vnode.props);
462 | // initSlot
463 | initSlots(instance, instance.vnode.children);
464 | // initComponent
465 | setupStatefulComponent(instance);
466 | };
467 | const setupStatefulComponent = (instance) => {
468 | const Component = instance.type;
469 | const { setup } = Component;
470 | // ctx
471 | instance.proxy = new Proxy({
472 | _: instance
473 | }, publickInstanceProxyhandlers);
474 | // 用户可能不会写 setup 函数
475 | if (setup) {
476 | // 初始化获取 instance
477 | setCurrentInstance(instance);
478 | // 可以返回一个 fn 也可能是一个 object
479 | const setupResult = setup(shallowReadonly(instance.props), {
480 | emit: instance.emit
481 | });
482 | handleSetupResult(instance, setupResult);
483 | // 重制获取 instance
484 | setCurrentInstance(null);
485 | }
486 | };
487 | const handleSetupResult = (instance, setupResult) => {
488 | // function or object
489 | // TODO function
490 | if (typeof setupResult === 'object') {
491 | instance.setupState = proxyRefs(setupResult);
492 | }
493 | // 初始化 render 函数
494 | finishCompentSetup(instance);
495 | };
496 | const finishCompentSetup = (instance) => {
497 | const Component = instance.type;
498 | if (compiler && !Component.render) {
499 | if (Component.template) {
500 | Component.render = compiler(Component.template);
501 | }
502 | }
503 | instance.render = Component.render;
504 | };
505 | const getCurrentInstance = () => {
506 | return currentInstance;
507 | };
508 | const setCurrentInstance = (instance) => {
509 | currentInstance = instance;
510 | };
511 | let compiler;
512 | const registerRuntimeCompiler = (_compiler) => {
513 | compiler = _compiler;
514 | };
515 |
516 | const provide = (key, value) => {
517 | // 存
518 | const currentInstance = getCurrentInstance();
519 | if (currentInstance) {
520 | let { provides } = currentInstance;
521 | const parentProvides = currentInstance.parent.provides;
522 | // init
523 | // 只需要初始化的时候 执行一次
524 | // 当初始化完成以后实例的 provides 肯定是和父级的 provides相等
525 | // 调用过过 provide 以后 当前实例的 provides 肯定和父级不一样(因为有赋值操作)
526 | if (provides === parentProvides) {
527 | // 将当前 provide 实例的原型指向父级
528 | provides = currentInstance.provides = Object.create(parentProvides);
529 | }
530 | provides[key] = value;
531 | }
532 | };
533 | const inject = (key, defaultVal) => {
534 | // 取
535 | const currentInstance = getCurrentInstance();
536 | if (currentInstance) {
537 | const { parent } = currentInstance;
538 | const parentProvides = parent.provides;
539 | if (key in parentProvides) {
540 | return parentProvides[key];
541 | }
542 | else if (defaultVal) {
543 | if (typeof defaultVal === 'function') {
544 | return defaultVal();
545 | }
546 | return defaultVal;
547 | }
548 | }
549 | };
550 |
551 | const shouldUpdateComponent = (prevVNode, nextVNode) => {
552 | const { props: prevProps } = prevVNode;
553 | const { props: nextProps } = nextVNode;
554 | for (const key in nextProps) {
555 | if (nextProps[key] !== prevProps[key]) {
556 | return true;
557 | }
558 | }
559 | return false;
560 | };
561 |
562 | const queue = [];
563 | // 用来标识是否需要 执行 创建 promise
564 | let isFlushPending = false;
565 | const queueJobs = (job) => {
566 | // 如果队列中没有 该 任务
567 | if (!queue.includes(job)) {
568 | queue.push(job);
569 | }
570 | // 创建一个 promise 去接收
571 | queueFlush();
572 | };
573 | const queueFlush = () => {
574 | if (isFlushPending) {
575 | return;
576 | }
577 | isFlushPending = true;
578 | nextTick(() => {
579 | flushJobs();
580 | });
581 | };
582 | const flushJobs = () => {
583 | // 执行完 微任务以后在将开关打开。
584 | isFlushPending = false;
585 | let job;
586 | while (job = queue.shift()) {
587 | job && job();
588 | }
589 | };
590 | const p = Promise.resolve();
591 | const nextTick = (fn) => {
592 | // 如果用户传入 nextTick 的回调函数,则执行
593 | // 否则,执行 Promise.resolve 的回调函数
594 | return fn ? p.then(fn) : p;
595 | };
596 |
597 | // custom render
598 | const createRender = (options) => {
599 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
600 | // Component
601 | const processComponent = (n1, n2, container, parentComponent, anchor) => {
602 | if (!n1) {
603 | mountComponent(n2, container, parentComponent, anchor);
604 | }
605 | else {
606 | updateComponent(n1, n2);
607 | }
608 | };
609 | const updateComponent = (n1, n2) => {
610 | const instance = n2.component = n1.component;
611 | // 如果 props 完全相同 则不需要更新
612 | if (shouldUpdateComponent(n1, n2)) {
613 | instance.next = n2;
614 | instance.update();
615 | }
616 | else {
617 | n2.el = n1.el;
618 | n2.vnode = n2;
619 | }
620 | };
621 | const mountComponent = (initinalVNode, container, parentComponent, anchor) => {
622 | const instance = initinalVNode.component = createComponentInstance(initinalVNode, parentComponent);
623 | setupComponent(instance);
624 | setupRenderEffect(instance, initinalVNode, container, anchor);
625 | };
626 | const mountChildren = (children, container, parentComponent, anchor) => {
627 | // 遍历 children 拿到节点 再次调用patch
628 | children.map(childrenItem => {
629 | patch(null, childrenItem, container, parentComponent, anchor);
630 | });
631 | };
632 | // Element
633 | const processElement = (n1, n2, container, parentComponent, anchor) => {
634 | if (!n1) {
635 | mountElement(n2, container, parentComponent, anchor);
636 | }
637 | else {
638 | patchElement(n1, n2, parentComponent, anchor);
639 | }
640 | };
641 | const patchElement = (n1, n2, parentComponent, anchor) => {
642 | // console.log('patchElement')
643 | // console.log('n1', n1)
644 | // console.log('n2', n2)
645 | // props 修改 有以下几种情况:
646 | // 1.之前属性的值和现在的值不一样了 --> 修改
647 | // 2.之前属性的值变成 undefined 或者 null --> 删除
648 | // 3.之前属性的值 现在没有了 --> 删除
649 | const oldProps = n1.props || EMPTY_OBJ;
650 | const newProps = n2.props || EMPTY_OBJ;
651 | // 当 第二次 patchElement 时 第一次的 n2 应该当作 第二次的 n1 去使用
652 | // 但是 n2 上并不存在 el 所以此处应当赋值以便于第二次调用。
653 | const el = n2.el = n1.el;
654 | patchProps(el, oldProps, newProps);
655 | // children 修改有以下几种情况
656 | // text --> text
657 | // text --> array
658 | // array --> array
659 | // array --> text
660 | patchChildren(n1, n2, el, parentComponent, anchor);
661 | };
662 | const patchProps = (el, oldProps, newProps) => {
663 | if (oldProps !== newProps) {
664 | for (const key in newProps) {
665 | const prevProp = oldProps[key];
666 | const nextProp = newProps[key];
667 | if (prevProp !== nextProp) {
668 | // 不相等的时候更新
669 | hostPatchProp(el, key, prevProp, nextProp);
670 | }
671 | }
672 | // 不等与空对象 才会去对比
673 | // 不能直接 rops !== {} 这个相当于创建了一个新的内存地址 所以这个判断一定是 true
674 | if (oldProps !== EMPTY_OBJ) {
675 | // 如果新设置的props 在原来的 props中不存在 则直接删除掉。
676 | for (const key in oldProps) {
677 | const prevProp = oldProps[key];
678 | if (!(key in newProps)) {
679 | hostPatchProp(el, key, prevProp, null);
680 | }
681 | }
682 | }
683 | }
684 | };
685 | const patchChildren = (n1, n2, container, parentComponent, anchor) => {
686 | const prevShapeFlag = n1.shapeFlag;
687 | const nextShapeFlag = n2.shapeFlag;
688 | const prevChildren = n1.children;
689 | const nextChildren = n2.children;
690 | if (nextShapeFlag & 4 /* TEXT_CHILDREN */) {
691 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
692 | // 老的是 array & 新的是 text
693 | // 1.把老的 children 清空
694 | unmountChildren(n1.children);
695 | // 2.设置 text
696 | // hostSetElementText(container, nextChildren)
697 | }
698 | // 老的是 text & 新的是 text
699 | if (prevChildren !== nextChildren) {
700 | hostSetElementText(container, nextChildren);
701 | }
702 | }
703 | else {
704 | // 老的是 array & 新的是 text
705 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
706 | // 1.把老的 text 清空
707 | hostSetElementText(container, '');
708 | mountChildren(nextChildren, container, parentComponent, anchor);
709 | }
710 | else {
711 | // 老的是 array 新的也是 array
712 | patchKeyedChildren(prevChildren, nextChildren, container, parentComponent, anchor);
713 | }
714 | }
715 | };
716 | const patchKeyedChildren = (c1, c2, container, parentComponent, parentAnchor) => {
717 | let i = 0;
718 | let l2 = c2.length;
719 | let e1 = c1.length - 1;
720 | let e2 = l2 - 1;
721 | // i 标识双端对比的相同部分的下标
722 | // e1 e2 分别表示 原数据 和现数据 末尾端 的下标
723 | // ------> 左侧对比
724 | while (i <= e1 && i <= e2) {
725 | const n1 = c1[i];
726 | const n2 = c2[i];
727 | if (isSameVNodeType(n1, n2)) {
728 | patch(n1, n2, container, parentComponent, parentAnchor);
729 | }
730 | else {
731 | break;
732 | }
733 | // 相等的时候 每次移动指针 i
734 | i++;
735 | }
736 | // ------> 右侧对比
737 | while (i <= e1 && i <= e2) {
738 | const n1 = c1[e1];
739 | const n2 = c2[e2];
740 | if (isSameVNodeType(n1, n2)) {
741 | patch(n1, n2, container, parentComponent, parentAnchor);
742 | }
743 | else {
744 | break;
745 | }
746 | e1--;
747 | e2--;
748 | }
749 | if (i > e1) {
750 | // 新的比老的多 需要创建
751 | // 左侧右侧 都有效果
752 | if (i <= e2) {
753 | const nextPos = e2 + 1;
754 | // const anchor = e2 + 1 >= l2 ? null : c2[nextPos].el
755 | const anchor = e2 + 1 < l2 ? c2[nextPos].el : null;
756 | while (i <= e2) {
757 | patch(null, c2[i], container, parentComponent, anchor);
758 | // 一定记得移动 指针 否则会死循环。
759 | i++;
760 | }
761 | }
762 | }
763 | else if (i > e2) {
764 | // 新的比老的少 需要删除
765 | // 左侧右侧 都有效果
766 | while (i <= e1) {
767 | // remove
768 | hostRemove(c1[i].el);
769 | i++;
770 | }
771 | }
772 | else {
773 | // 乱序部分 中间对比
774 | let s1 = i;
775 | let s2 = i;
776 | // a,b,(c,e,d),f,g
777 | // a,b,(e,c),f,g
778 | // 设定一个值来判断是不是所有的 新数据 中的 数据都比较完了,用来剔除就数据比新数据多,即(去掉这里的 d)。
779 | // 此处是索引需要 +1
780 | const toBePatched = e2 - s2 + 1;
781 | let patched = 0;
782 | // 是否需要移动
783 | let moved = false;
784 | let maxNewIndexSoFar = 0;
785 | // 建立一个 key 映射表
786 | const keyToNewIndexMap = new Map();
787 | // 最长递增子序列
788 | const newIndexToOldIndexMap = new Array(toBePatched);
789 | for (let i = 0; i < toBePatched; i++) {
790 | newIndexToOldIndexMap[i] = 0;
791 | }
792 | for (let i = s2; i <= e2; i++) {
793 | const nextChild = c2[i];
794 | keyToNewIndexMap.set(nextChild.key, i);
795 | }
796 | let newIndex;
797 | for (let i = s1; i <= e1; i++) {
798 | const prevChild = c1[i];
799 | // 如果 所有新的不同节点都已经 patch 完毕
800 | if (patched >= toBePatched) {
801 | hostRemove(prevChild.el);
802 | continue;
803 | }
804 | // 如果用户设置了 key 值
805 | if (prevChild.key != null) {
806 | newIndex = keyToNewIndexMap.get(prevChild.key);
807 | }
808 | else {
809 | for (let j = s2; j <= e2; j++) {
810 | if (isSameVNodeType(prevChild, c2[j])) {
811 | // 如果存在表示找到的原来的节点对应的映射,直接跳出
812 | newIndex = j;
813 | break;
814 | }
815 | }
816 | }
817 | //
818 | if (newIndex === undefined) {
819 | hostRemove(prevChild.el);
820 | }
821 | else {
822 | if (newIndex >= maxNewIndexSoFar) {
823 | maxNewIndexSoFar = newIndex;
824 | }
825 | else {
826 | moved = true;
827 | }
828 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
829 | patch(prevChild, c2[newIndex], container, parentComponent, null);
830 | patched++;
831 | }
832 | }
833 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];
834 | let j = increasingNewIndexSequence.length - 1;
835 | for (let i = toBePatched - 1; i >= 0; i--) {
836 | const nextIndex = i + s2;
837 | const nextChild = c2[nextIndex];
838 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
839 | if (newIndexToOldIndexMap[i] === 0) {
840 | patch(null, nextChild, container, parentComponent, anchor);
841 | }
842 | else if (moved) {
843 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
844 | console.log('移动位置');
845 | hostInsert(nextChild.el, container, anchor);
846 | }
847 | else {
848 | j--;
849 | }
850 | }
851 | }
852 | }
853 | };
854 | const isSameVNodeType = (n1, n2) => {
855 | // type key 两个东西去判断是不是想等
856 | return n1.type === n2.type && n1.key === n2.key;
857 | };
858 | const unmountChildren = (children) => {
859 | children.map(item => {
860 | const el = item.el;
861 | // remove
862 | hostRemove(el);
863 | });
864 | };
865 | const mountElement = (vnode, container, parentComponent, anchor) => {
866 | // vnode --> element 类型的 --> div
867 | const el = vnode.el = hostCreateElement(vnode.type);
868 | // children 可能是 string array
869 | const { children, props, shapeFlag } = vnode;
870 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
871 | // children
872 | el.textContent = children;
873 | }
874 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
875 | mountChildren(vnode.children, el, parentComponent, anchor);
876 | }
877 | // props
878 | for (const key in props) {
879 | let val;
880 | if (Array.isArray(props[key])) {
881 | val = props[key].join(' ');
882 | }
883 | else {
884 | val = props[key];
885 | }
886 | hostPatchProp(el, key, null, val);
887 | }
888 | // 挂载在页面上
889 | hostInsert(el, container, anchor);
890 | };
891 | // Fragment
892 | const processFragment = (n1, n2, container, parentComponent, anchor) => {
893 | mountChildren(n2.children, container, parentComponent, anchor);
894 | };
895 | // Text
896 | const processText = (n1, n2, container) => {
897 | const { children } = n2;
898 | const textNode = n2.el = document.createTextNode(children);
899 | container.append(textNode);
900 | };
901 | const render = (vnode, container) => {
902 | patch(null, vnode, container, null, null);
903 | };
904 | const patch = (n1, n2, container, parentComponent, anchor) => {
905 | // 判断一下 vnode 类型
906 | // 调用对应的方法去处理
907 | const { type, shapeFlag } = n2;
908 | switch (type) {
909 | case Fragment:
910 | processFragment(n1, n2, container, parentComponent, anchor);
911 | break;
912 | case Text:
913 | processText(n1, n2, container);
914 | break;
915 | default:
916 | if (shapeFlag & 1 /* ELEMENT */) {
917 | processElement(n1, n2, container, parentComponent, anchor);
918 | }
919 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
920 | processComponent(n1, n2, container, parentComponent, anchor);
921 | }
922 | break;
923 | }
924 | };
925 | const setupRenderEffect = (instance, initinalVNode, container, anchor) => {
926 | // 利用 effect 做依赖收集
927 | instance.update = effect(() => {
928 | if (!instance.isMounted) {
929 | const { proxy } = instance;
930 | // 这里传递的 第一个 proxy 是组件实例this,第二个 proxy 是当做组件的参数,方便 render 函数中调用 ctx.xxx
931 | // 这里是方便组件内部调用 获取 ctx 的操作
932 | // 后续可能还有很多参数 (_cache, $props, $setup, $data, $options)
933 | const subTree = instance.subTree = instance.render.call(proxy, proxy);
934 | // vnode --> patch
935 | // vnode --> element --> mount
936 | patch(null, subTree, container, instance, anchor);
937 | initinalVNode.el = subTree.el;
938 | instance.isMounted = true;
939 | }
940 | else {
941 | // 这里 在更新的时候还需要更新组件的 props
942 | // 需要 更新完成以后的 vnode
943 | // vnode: 更新之前的 虚拟节点
944 | // next: 下次要更新的 虚拟节点
945 | const { next, vnode } = instance;
946 | if (next) {
947 | // 更新 el
948 | next.el = vnode.el;
949 | updateComponentPreRender(instance, next);
950 | }
951 | const { proxy } = instance;
952 | const prevSubTree = instance.subTree;
953 | // 这里传递的 第一个 proxy 是组件实例this,第二个 proxy 是当做组件的参数,方便 render 函数中调用 ctx.xxx
954 | // 这里是方便组件内部调用 获取 ctx 的操作
955 | // 后续可能还有很多参数 (_cache, $props, $setup, $data, $options)
956 | const subTree = instance.subTree = instance.render.call(proxy, proxy);
957 | // vnode --> patch
958 | // vnode --> element --> mount
959 | patch(prevSubTree, subTree, container, instance, anchor);
960 | initinalVNode.el = subTree.el;
961 | instance.isMounted = true;
962 | }
963 | }, {
964 | // 优化 不需要每次更新数据都去执行 effect 收集的依赖。
965 | scheduler: () => {
966 | // 建立一个 微任务去 等待数据完成在执行回调。
967 | queueJobs(instance.update);
968 | }
969 | });
970 | };
971 | return {
972 | createApp: createAppAPI(render)
973 | };
974 | };
975 | const updateComponentPreRender = (instance, nextVNode) => {
976 | instance.vnode = nextVNode;
977 | instance.next = null;
978 | instance.props = nextVNode.props;
979 | };
980 |
981 | const createElement = (type) => {
982 | return document.createElement(type);
983 | };
984 | const patchProp = (el, key, prevVal, nextVal) => {
985 | // 实现注册 事件
986 | const isOn = key => /^on[A-Z]/.test(key);
987 | if (isOn(key)) {
988 | const eventName = key.slice(2).toLowerCase();
989 | el.addEventListener(eventName, nextVal);
990 | }
991 | else {
992 | // 如果设置值为 undefined 或者 null 的时候删除掉该属性
993 | if (nextVal == null) {
994 | el.removeAttribute(key);
995 | }
996 | else {
997 | el.setAttribute(key, nextVal);
998 | }
999 | }
1000 | };
1001 | const insert = (child, parent, anchor = null) => {
1002 | // parent.append(child)
1003 | parent.insertBefore(child, anchor);
1004 | };
1005 | const remove = (children) => {
1006 | const parent = children.parentNode;
1007 | if (parent) {
1008 | parent.removeChild(children);
1009 | }
1010 | };
1011 | const setElementText = (el, text) => {
1012 | el.textContent = text;
1013 | };
1014 | const render = createRender({
1015 | createElement,
1016 | patchProp,
1017 | insert,
1018 | remove,
1019 | setElementText
1020 | });
1021 | const createApp = (...args) => {
1022 | return render.createApp(...args);
1023 | };
1024 |
1025 | var runtimeDom = /*#__PURE__*/Object.freeze({
1026 | __proto__: null,
1027 | createElement: createElement,
1028 | patchProp: patchProp,
1029 | insert: insert,
1030 | render: render,
1031 | createApp: createApp,
1032 | createAppAPI: createAppAPI,
1033 | renderSlots: renderSlots,
1034 | h: h,
1035 | createElementVNode: createVNode,
1036 | createVNode: createVNode,
1037 | createTextVNode: createTextVNode,
1038 | Fragment: Fragment,
1039 | Text: Text,
1040 | createComponentInstance: createComponentInstance,
1041 | setupComponent: setupComponent,
1042 | setupStatefulComponent: setupStatefulComponent,
1043 | getCurrentInstance: getCurrentInstance,
1044 | registerRuntimeCompiler: registerRuntimeCompiler,
1045 | provide: provide,
1046 | inject: inject,
1047 | createRender: createRender,
1048 | queueJobs: queueJobs,
1049 | nextTick: nextTick,
1050 | toDisplayString: toDisplayString,
1051 | extend: extend,
1052 | EMPTY_OBJ: EMPTY_OBJ,
1053 | isObject: isObject,
1054 | isString: isString,
1055 | hasChanged: hasChanged,
1056 | getShapeFlag: getShapeFlag,
1057 | hasOwn: hasOwn,
1058 | camelize: camelize,
1059 | toHandlerKey: toHandlerKey,
1060 | getSequence: getSequence
1061 | });
1062 |
1063 | const TO_DISPLAY_STRING = Symbol('toDisplayString');
1064 | const CREATE_ELEMENT_VNODE = Symbol('ctreateElementVNode');
1065 | const helperMapName = {
1066 | [TO_DISPLAY_STRING]: 'toDisplayString',
1067 | [CREATE_ELEMENT_VNODE]: 'createElementVNode'
1068 | };
1069 |
1070 | const generate = (ast) => {
1071 | const context = createCodegenContext();
1072 | const { push } = context;
1073 | genFunctionPreamble(ast, context);
1074 | push('return ');
1075 | const functionName = 'render';
1076 | const args = ['_ctx', '_cache', '$props', '$setup', '$data', '$options'];
1077 | const signature = args.join(', ');
1078 | push(`function ${functionName} (${signature}) { `);
1079 | push('return ');
1080 | genNode(ast.codegenNode, context);
1081 | push(' }');
1082 | return {
1083 | code: context.code
1084 | };
1085 | };
1086 | const genNodeList = (nodes, context) => {
1087 | const { push } = context;
1088 | for (let i = 0; i < nodes.length; i++) {
1089 | const node = nodes[i];
1090 | if (isString(node)) {
1091 | push(node);
1092 | }
1093 | else {
1094 | genNode(node, context);
1095 | }
1096 | if (i < nodes.length - 1) {
1097 | push(', ');
1098 | }
1099 | }
1100 | };
1101 | const genNode = (node, context) => {
1102 | // 获取 ast的入口,在外部处理内容。
1103 | // 区分一下类型
1104 | switch (node.type) {
1105 | case 3 /* TEXT */:
1106 | genText(node, context);
1107 | break;
1108 | case 0 /* INTERPOLATION */:
1109 | genInterpolation(node, context);
1110 | break;
1111 | case 1 /* SIMPLE_EXPRESSION */:
1112 | genExpression(node, context);
1113 | break;
1114 | case 2 /* ELEMENT */:
1115 | genElement(node, context);
1116 | break;
1117 | case 5 /* COMPOUND_EXPRESSION */:
1118 | genCompoundExpression(node, context);
1119 | break;
1120 | }
1121 | };
1122 | const createCodegenContext = () => {
1123 | const context = {
1124 | code: '',
1125 | push(source) {
1126 | context.code += source;
1127 | },
1128 | getHelperName(key) {
1129 | return `_${helperMapName[key]}`;
1130 | }
1131 | };
1132 | return context;
1133 | };
1134 | const genFunctionPreamble = (ast, context) => {
1135 | const { push } = context;
1136 | const VueBinging = 'vue';
1137 | const aliasHelpers = (s) => `${helperMapName[s]}: _${helperMapName[s]}`;
1138 | if (ast.helpers.length > 0) {
1139 | push(`const { ${ast.helpers.map(aliasHelpers).join(', ')} } = ${VueBinging}`);
1140 | }
1141 | push('\n');
1142 | };
1143 | const genText = (node, context) => {
1144 | const { push } = context;
1145 | push(`'${node.content}'`);
1146 | };
1147 | const genInterpolation = (node, context) => {
1148 | const { push, getHelperName } = context;
1149 | push(`${getHelperName(TO_DISPLAY_STRING)}(`);
1150 | genNode(node.content, context);
1151 | push(`)`);
1152 | };
1153 | const genExpression = (node, context) => {
1154 | const { push } = context;
1155 | push(node.content);
1156 | };
1157 | const genElement = (node, context) => {
1158 | const { push, getHelperName } = context;
1159 | const { tag, children, props } = node;
1160 | push(`${getHelperName(CREATE_ELEMENT_VNODE)}(`);
1161 | genNodeList(getNullAble([tag, props, children]), context);
1162 | // genNode(children, context)
1163 | // push(')')
1164 | };
1165 | const genCompoundExpression = (node, context) => {
1166 | const { push } = context;
1167 | const { children } = node;
1168 | for (let i = 0; i < children.length; i++) {
1169 | const child = children[i];
1170 | if (isString(child)) {
1171 | push(child);
1172 | }
1173 | else {
1174 | genNode(child, context);
1175 | }
1176 | }
1177 | push(')');
1178 | };
1179 | const getNullAble = (arg) => {
1180 | return arg.map(item => item || 'null');
1181 | };
1182 |
1183 | const interpolationOpenDelimiter = '{{';
1184 | const interpolationCloseDelimiter = '}}';
1185 | const ElementCloseDelimiter = '<';
1186 | const baseParse = (content) => {
1187 | const context = createparserContent(content);
1188 | // 初始化的时候 标签数组 传递一个 []
1189 | return createRoot(parserChildren(context, []));
1190 | };
1191 | const parserChildren = (context, ancestors) => {
1192 | const nodes = [];
1193 | // 循环解析 字符串。
1194 | while (!isEnd(context, ancestors)) {
1195 | let node;
1196 | const source = context.source;
1197 | // 字符串是以 {{ 开头的才需要处理
1198 | if (source.startsWith(interpolationOpenDelimiter)) {
1199 | // 插值
1200 | node = parseInterpolation(context);
1201 | }
1202 | else if (source.startsWith(ElementCloseDelimiter)) { // source[0] === '<'
1203 | // element
1204 | if (/[a-z]/i.test(source[1])) {
1205 | node = parserElement(context, ancestors);
1206 | }
1207 | }
1208 | // 如果前面的的两个判断都没有命中,表示是文本。
1209 | if (!node) {
1210 | node = parseText(context);
1211 | }
1212 | nodes.push(node);
1213 | }
1214 | return nodes;
1215 | };
1216 | const isEnd = (context, ancestors) => {
1217 | // 1.当遇到结束标签的时候
1218 | const source = context.source;
1219 | if (source.startsWith('')) {
1220 | for (let i = ancestors.length - 1; i >= 0; i--) {
1221 | const tag = ancestors[i].tag;
1222 | if (startWithEndTagOpen(source, tag)) {
1223 | return true;
1224 | }
1225 | }
1226 | }
1227 | // 2.context.source 有值的时候
1228 | return !context.source;
1229 | };
1230 | const createRoot = (children) => {
1231 | return {
1232 | children,
1233 | type: 4 /* ROOT */
1234 | };
1235 | };
1236 | const createparserContent = (content) => {
1237 | return {
1238 | source: content
1239 | };
1240 | };
1241 | const advanceBy = (context, length) => {
1242 | context.source = context.source.slice(length);
1243 | };
1244 | // 插值
1245 | const parseInterpolation = (context) => {
1246 | // {{ message }} ---> 拿到这个 message
1247 | // 从第二个字符位置开始查找, 到 '}}' 结束
1248 | const closeIndex = context.source.indexOf(interpolationCloseDelimiter, interpolationOpenDelimiter.length);
1249 | // 去掉 前面的 '{{'
1250 | advanceBy(context, interpolationCloseDelimiter.length);
1251 | const rawContentLength = closeIndex - interpolationOpenDelimiter.length;
1252 | // 可能存在空格 trim去掉~
1253 | // const rawContent = context.source.slice(0, rawContentLength)
1254 | const rawContent = parseTextData(context, rawContentLength);
1255 | const content = rawContent.trim();
1256 | advanceBy(context, interpolationCloseDelimiter.length);
1257 | //
1258 | // TODO 思考 上面的逻辑 可以使用 slice(2, -2) 来直接获取吗?
1259 | // context.source = context.source.slice(2, -2)
1260 | // const content = context.source.slice(interpolationOpenDelimiter.length, -interpolationCloseDelimiter.length).trim()
1261 | return {
1262 | type: 0 /* INTERPOLATION */,
1263 | content: {
1264 | type: 1 /* SIMPLE_EXPRESSION */,
1265 | content
1266 | }
1267 | };
1268 | };
1269 | // element
1270 | // 在调用 parserElement 的时候,使用栈的 先进后出特性,把 element push进去
1271 | // 之后在完成解析以后取出,比较标签有没有闭合。
1272 | const parserElement = (context, ancestors) => {
1273 | // 这里需要调用两次!!!切记 开始标签匹配一次
1274 | const element = parserTag(context, 0 /* Start */);
1275 | ancestors.push(element);
1276 | element.children = parserChildren(context, ancestors);
1277 | ancestors.pop();
1278 | // 这里需要判断标签是不是匹配,如果匹配才能销毁,或者删掉。
1279 | if (startWithEndTagOpen(context.source, element.tag)) {
1280 | // 结束标签匹配一次!!!
1281 | parserTag(context, 1 /* End */);
1282 | }
1283 | else {
1284 | throw new Error(`缺少结束标签: ${element.tag}`);
1285 | }
1286 | return element;
1287 | };
1288 | const startWithEndTagOpen = (source, tag) => {
1289 | return source.startsWith('') && source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase();
1290 | };
1291 | const parserTag = (context, type) => {
1292 | // 1.解析 tag
1293 | //
1294 | //
1295 | // 匹配以 < 开头或者以 开头的 字符,/ 可以没有。
1296 | const match = /^<\/?([a-z]*)/i.exec(context.source);
1297 | const tag = match[1];
1298 | // 2.删除处理完成的代码
1299 | advanceBy(context, match[0].length);
1300 | advanceBy(context, 1);
1301 | if (type === 1 /* End */) {
1302 | // 如果是结束标签 () 直接不用返回 后面的东西了。
1303 | return;
1304 | }
1305 | return {
1306 | type: 2 /* ELEMENT */,
1307 | tag
1308 | };
1309 | };
1310 | // text 文本类型
1311 | const parseText = (context) => {
1312 | let endTokens = ['{{', '<'];
1313 | let endIndex = context.source.length;
1314 | // 遇到 {{ 或者 < 都应该直接停下,返回了
1315 | for (let i = 0; i < endTokens.length; i++) {
1316 | const index = context.source.indexOf(endTokens[i]);
1317 | // 当 字符串中 存在 {{ 表示是文本和 插值混合的。
1318 | if (index !== -1 && endIndex > index) {
1319 | endIndex = index;
1320 | }
1321 | }
1322 | // 1. 获取content
1323 | const content = parseTextData(context, endIndex);
1324 | return {
1325 | type: 3 /* TEXT */,
1326 | content
1327 | };
1328 | };
1329 | const parseTextData = (context, length) => {
1330 | const content = context.source.slice(0, length);
1331 | // 2. 推进
1332 | advanceBy(context, length);
1333 | return content;
1334 | };
1335 |
1336 | const transform = (root, options = {}) => {
1337 | const context = createTransformContext(root, options);
1338 | traverseNode(root, context);
1339 | createRootCodegen(root);
1340 | root.helpers = [...context.helpers.keys()];
1341 | };
1342 | const createRootCodegen = (root) => {
1343 | const child = root.children[0];
1344 | if (child.type === 2 /* ELEMENT */) {
1345 | root.codegenNode = child.codegenNode;
1346 | }
1347 | else {
1348 | root.codegenNode = root.children[0];
1349 | }
1350 | };
1351 | const traverseNode = (node, context) => {
1352 | const nodeTransforms = context.nodeTransforms;
1353 | const exitFns = [];
1354 | for (let i = 0; i < nodeTransforms.length; i++) {
1355 | const transform = nodeTransforms[i];
1356 | const onExit = transform(node, context);
1357 | if (onExit) {
1358 | exitFns.push(onExit);
1359 | }
1360 | }
1361 | // 这里需要 分情况处理不同类型的逻辑
1362 | switch (node.type) {
1363 | // 插值类型
1364 | case 0 /* INTERPOLATION */:
1365 | context.helper(TO_DISPLAY_STRING);
1366 | break;
1367 | // root 根结点
1368 | case 4 /* ROOT */:
1369 | case 2 /* ELEMENT */:
1370 | // 处理 children
1371 | traverseChildren(node, context);
1372 | break;
1373 | }
1374 | let i = exitFns.length;
1375 | while (i--) {
1376 | exitFns[i]();
1377 | }
1378 | };
1379 | const createTransformContext = (root, options) => {
1380 | const context = {
1381 | root,
1382 | helpers: new Map(),
1383 | helper(key) {
1384 | context.helpers.set(key, 1);
1385 | },
1386 | nodeTransforms: options.nodeTransforms || []
1387 | };
1388 | return context;
1389 | };
1390 | const traverseChildren = (node, context) => {
1391 | const children = node.children;
1392 | for (let i = 0; i < children.length; i++) {
1393 | const node = children[i];
1394 | traverseNode(node, context);
1395 | }
1396 | };
1397 |
1398 | const createVNodeCall = (context, tag, props, children) => {
1399 | context.helper(CREATE_ELEMENT_VNODE);
1400 | return {
1401 | type: 2 /* ELEMENT */,
1402 | tag,
1403 | props,
1404 | children
1405 | };
1406 | };
1407 |
1408 | const transformElement = (node, context) => {
1409 | return () => {
1410 | if (node.type === 2 /* ELEMENT */) {
1411 | // 以下中间处理层,处理一下数据~
1412 | // tag
1413 | const vnodeTag = `'${node.tag}'`;
1414 | // props
1415 | let vnodeProps;
1416 | let vnodeChild = node.children[0];
1417 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChild);
1418 | }
1419 | };
1420 | };
1421 |
1422 | const transformExpression = (node) => {
1423 | if (node.type === 0 /* INTERPOLATION */) {
1424 | processExpression(node.content);
1425 | }
1426 | };
1427 | const processExpression = (node) => {
1428 | node.content = `_ctx.${node.content}`;
1429 | };
1430 |
1431 | const isText = (node) => {
1432 | return (node.type === 3 /* TEXT */ || node.type === 0 /* INTERPOLATION */);
1433 | };
1434 |
1435 | const transformText = (node) => {
1436 | return () => {
1437 | const { children } = node;
1438 | let currentContainer;
1439 | if (node.type === 2 /* ELEMENT */) {
1440 | for (let i = 0; i < children.length; i++) {
1441 | const child = children[i];
1442 | if (isText(child)) {
1443 | for (let j = i + 1; j < children.length; j++) {
1444 | const nextChild = children[j];
1445 | if (isText(nextChild)) {
1446 | if (!currentContainer) {
1447 | currentContainer = children[i] = {
1448 | type: 5 /* COMPOUND_EXPRESSION */,
1449 | children: [child]
1450 | };
1451 | }
1452 | currentContainer.children.push(' + ');
1453 | currentContainer.children.push(nextChild);
1454 | // 添加完成的 元素需要去掉
1455 | children.splice(j, 1);
1456 | // 删除以后后面的元素前移,导致取错,--即可
1457 | j--;
1458 | }
1459 | else {
1460 | currentContainer = undefined;
1461 | break;
1462 | }
1463 | }
1464 | }
1465 | }
1466 | }
1467 | };
1468 | };
1469 |
1470 | function baseCompile(template) {
1471 | const ast = baseParse(template);
1472 | transform(ast, {
1473 | nodeTransforms: [transformExpression, transformElement, transformText]
1474 | });
1475 | return generate(ast);
1476 | }
1477 |
1478 | // mini-vue 出口
1479 | function compileToFunction(template) {
1480 | const { code } = baseCompile(template);
1481 | const render = new Function('vue', code)(runtimeDom);
1482 | return render;
1483 | }
1484 | registerRuntimeCompiler(compileToFunction);
1485 |
1486 | export { EMPTY_OBJ, Fragment, Text, camelize, createApp, createAppAPI, createComponentInstance, createElement, createVNode as createElementVNode, createRender, createTextVNode, createVNode, extend, getCurrentInstance, getSequence, getShapeFlag, h, hasChanged, hasOwn, inject, insert, isObject, isRef, isString, nextTick, patchProp, provide, proxyRefs, queueJobs, ref, registerRuntimeCompiler, render, renderSlots, setupComponent, setupStatefulComponent, toDisplayString, toHandlerKey, unRef };
1487 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-mini-vue",
3 | "version": "1.0.0",
4 | "main": "lib/guide-mini-vue.cjs.js",
5 | "module": "lib/guide-mini-vue.esm.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "test": "jest",
9 | "build": "rollup -c rollup.config.js"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.17.5",
13 | "@babel/preset-env": "^7.16.11",
14 | "@babel/preset-typescript": "^7.16.7",
15 | "@rollup/plugin-typescript": "^8.3.1",
16 | "@types/jest": "^27.4.1",
17 | "babel-jest": "^27.5.1",
18 | "jest": "^27.5.1",
19 | "rollup": "^2.69.1",
20 | "tslib": "^2.3.1",
21 | "typescript": "^4.6.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json'
2 | import rollupTypescript from '@rollup/plugin-typescript'
3 |
4 | export default {
5 | input: './src/index.ts',
6 | output: [
7 | // 1.cjs --> common.js
8 | // 2.esm
9 | {
10 | format: 'cjs',
11 | file: pkg.main
12 | },
13 | {
14 | format: 'es',
15 | file: pkg.module
16 | }
17 | ],
18 | plugins: [
19 | rollupTypescript()
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/src/compiler-core/src/ast.ts:
--------------------------------------------------------------------------------
1 | import { CREATE_ELEMENT_VNODE } from './runtimeHelpers'
2 |
3 | const enum NodeTypes {
4 | INTERPOLATION,
5 | SIMPLE_EXPRESSION,
6 | ELEMENT,
7 | TEXT,
8 | ROOT,
9 | COMPOUND_EXPRESSION
10 | }
11 |
12 | const createVNodeCall = (context, tag, props, children) => {
13 | context.helper(CREATE_ELEMENT_VNODE)
14 | return {
15 | type: NodeTypes.ELEMENT,
16 | tag,
17 | props,
18 | children
19 | }
20 | }
21 |
22 | export {
23 | NodeTypes,
24 | createVNodeCall
25 | }
26 |
--------------------------------------------------------------------------------
/src/compiler-core/src/codegen.ts:
--------------------------------------------------------------------------------
1 | import { isString } from '../../shared'
2 | import { NodeTypes } from './ast'
3 | import { helperMapName, TO_DISPLAY_STRING, CREATE_ELEMENT_VNODE } from './runtimeHelpers'
4 |
5 | const generate = (ast) => {
6 | const context: any = createCodegenContext()
7 | const { push } = context
8 |
9 | genFunctionPreamble(ast, context)
10 |
11 | push('return ')
12 |
13 | const functionName = 'render'
14 | const args = ['_ctx', '_cache', '$props', '$setup', '$data', '$options']
15 | const signature = args.join(', ')
16 |
17 | push(`function ${functionName} (${signature}) { `)
18 | push('return ')
19 | genNode(ast.codegenNode, context)
20 | push(' }')
21 |
22 | return {
23 | code: context.code
24 | }
25 | }
26 |
27 | const genNodeList = (nodes, context) => {
28 | const { push } = context
29 |
30 | for (let i = 0; i < nodes.length; i++) {
31 | const node = nodes[i]
32 | if (isString(node)) {
33 | push(node)
34 | } else {
35 | genNode(node, context)
36 | }
37 | if (i < nodes.length - 1) {
38 | push(', ')
39 | }
40 | }
41 | }
42 |
43 | const genNode = (node, context) => {
44 | // 获取 ast的入口,在外部处理内容。
45 | // 区分一下类型
46 | switch (node.type) {
47 | case NodeTypes.TEXT:
48 | genText(node, context)
49 | break
50 | case NodeTypes.INTERPOLATION:
51 | genInterpolation(node, context)
52 | break
53 | case NodeTypes.SIMPLE_EXPRESSION:
54 | genExpression(node, context)
55 | break
56 | case NodeTypes.ELEMENT:
57 | genElement(node, context)
58 | break
59 | case NodeTypes.COMPOUND_EXPRESSION:
60 | genCompoundExpression(node, context)
61 | break
62 |
63 | default:
64 | break
65 | }
66 | }
67 |
68 | const createCodegenContext = () => {
69 | const context = {
70 | code: '',
71 | push(source) {
72 | context.code += source
73 | },
74 | getHelperName (key) {
75 | return `_${helperMapName[key]}`
76 | }
77 | }
78 | return context
79 | }
80 |
81 | const genFunctionPreamble = (ast: any, context: any) => {
82 | const { push } = context
83 | const VueBinging = 'vue'
84 |
85 | const aliasHelpers = (s: string) => `${helperMapName[s]}: _${helperMapName[s]}`
86 |
87 | if (ast.helpers.length > 0) {
88 | push(`const { ${ast.helpers.map(aliasHelpers).join(', ')} } = ${VueBinging}`)
89 | }
90 | push('\n')
91 | }
92 |
93 | const genText = (node: any, context: any) => {
94 | const { push } = context
95 | push(`'${node.content}'`)
96 | }
97 |
98 | const genInterpolation = (node: any, context: any) => {
99 | const { push, getHelperName } = context
100 | push(`${getHelperName(TO_DISPLAY_STRING)}(`)
101 | genNode(node.content, context)
102 | push(`)`)
103 | }
104 |
105 | const genExpression = (node: any, context: any) => {
106 | const { push } = context
107 | push(node.content)
108 | }
109 |
110 | const genElement = (node: any, context: any) => {
111 | const { push, getHelperName } = context
112 | const { tag, children, props } = node
113 |
114 | push(`${getHelperName(CREATE_ELEMENT_VNODE)}(`)
115 | genNodeList(getNullAble([tag, props, children]), context)
116 | // genNode(children, context)
117 | // push(')')
118 | }
119 |
120 | const genCompoundExpression = (node: any, context: any) => {
121 | const { push } = context
122 | const { children } = node
123 |
124 | for (let i = 0; i < children.length; i++) {
125 | const child = children[i]
126 | if (isString(child)) {
127 | push(child)
128 | } else {
129 | genNode(child, context)
130 | }
131 | }
132 |
133 | push(')')
134 | }
135 |
136 | const getNullAble = (arg) => {
137 | return arg.map(item => item || 'null')
138 | }
139 |
140 | export {
141 | generate
142 | }
143 |
144 |
--------------------------------------------------------------------------------
/src/compiler-core/src/compile.ts:
--------------------------------------------------------------------------------
1 | import { generate } from './codegen'
2 | import { baseParse } from './parse'
3 | import { transform } from './transform'
4 | import { transformElement } from './transforms/transformElement'
5 | import { transformExpression } from './transforms/transformExpression'
6 | import { transformText } from './transforms/transformText'
7 |
8 | export function baseCompile(template) {
9 | const ast: any = baseParse(template)
10 | transform(ast, {
11 | nodeTransforms: [transformExpression, transformElement, transformText]
12 | })
13 |
14 | return generate(ast)
15 | }
16 |
--------------------------------------------------------------------------------
/src/compiler-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './compile'
2 |
--------------------------------------------------------------------------------
/src/compiler-core/src/parse.ts:
--------------------------------------------------------------------------------
1 |
2 | import { NodeTypes } from './ast'
3 |
4 | const interpolationOpenDelimiter = '{{'
5 | const interpolationCloseDelimiter = '}}'
6 |
7 | const ElementCloseDelimiter = '<'
8 |
9 | const enum TagType {
10 | Start,
11 | End
12 | }
13 |
14 | const baseParse = (content: string) => {
15 | const context = createparserContent(content)
16 | // 初始化的时候 标签数组 传递一个 []
17 | return createRoot(parserChildren(context, []))
18 | }
19 |
20 | const parserChildren = (context: { source: string }, ancestors) => {
21 | const nodes: any = []
22 | // 循环解析 字符串。
23 | while (!isEnd(context, ancestors)) {
24 | let node
25 | const source = context.source
26 |
27 | // 字符串是以 {{ 开头的才需要处理
28 | if (source.startsWith(interpolationOpenDelimiter)) {
29 | // 插值
30 | node = parseInterpolation(context)
31 | } else if (source.startsWith(ElementCloseDelimiter)) { // source[0] === '<'
32 | // element
33 | if (/[a-z]/i.test(source[1])) {
34 | node = parserElement(context, ancestors)
35 | }
36 | }
37 |
38 | // 如果前面的的两个判断都没有命中,表示是文本。
39 | if (!node) {
40 | node = parseText(context)
41 | }
42 | nodes.push(node)
43 | }
44 |
45 | return nodes
46 | }
47 |
48 | const isEnd = (context, ancestors) => {
49 | // 1.当遇到结束标签的时候
50 | const source = context.source
51 | if (source.startsWith('')) {
52 | for (let i = ancestors.length - 1; i >= 0; i--) {
53 | const tag = ancestors[i].tag
54 | if (startWithEndTagOpen(source, tag)) {
55 | return true
56 | }
57 | }
58 | }
59 |
60 | // 2.context.source 有值的时候
61 | return !context.source
62 | }
63 |
64 | const createRoot = (children) => {
65 | return {
66 | children,
67 | type: NodeTypes.ROOT
68 | }
69 | }
70 |
71 | const createparserContent = (content: string) => {
72 | return {
73 | source: content
74 | }
75 | }
76 |
77 | const advanceBy = (context, length) => {
78 | context.source = context.source.slice(length)
79 | }
80 |
81 | // 插值
82 | const parseInterpolation = (context) => {
83 | // {{ message }} ---> 拿到这个 message
84 |
85 | // 从第二个字符位置开始查找, 到 '}}' 结束
86 | const closeIndex = context.source.indexOf(interpolationCloseDelimiter, interpolationOpenDelimiter.length)
87 | // 去掉 前面的 '{{'
88 | advanceBy(context, interpolationCloseDelimiter.length)
89 |
90 | const rawContentLength = closeIndex - interpolationOpenDelimiter.length
91 | // 可能存在空格 trim去掉~
92 | // const rawContent = context.source.slice(0, rawContentLength)
93 | const rawContent = parseTextData(context, rawContentLength)
94 | const content = rawContent.trim()
95 |
96 | advanceBy(context, interpolationCloseDelimiter.length)
97 |
98 | //
99 | // TODO 思考 上面的逻辑 可以使用 slice(2, -2) 来直接获取吗?
100 | // context.source = context.source.slice(2, -2)
101 | // const content = context.source.slice(interpolationOpenDelimiter.length, -interpolationCloseDelimiter.length).trim()
102 |
103 | return {
104 | type: NodeTypes.INTERPOLATION,
105 | content: {
106 | type: NodeTypes.SIMPLE_EXPRESSION,
107 | content
108 | }
109 | }
110 | }
111 |
112 | // element
113 | // 在调用 parserElement 的时候,使用栈的 先进后出特性,把 element push进去
114 | // 之后在完成解析以后取出,比较标签有没有闭合。
115 | const parserElement = (context, ancestors) => {
116 | // 这里需要调用两次!!!切记 开始标签匹配一次
117 | const element: any = parserTag(context, TagType.Start)
118 |
119 | ancestors.push(element)
120 |
121 | element.children = parserChildren(context, ancestors)
122 |
123 | ancestors.pop()
124 | // 这里需要判断标签是不是匹配,如果匹配才能销毁,或者删掉。
125 | if (startWithEndTagOpen(context.source, element.tag)) {
126 | // 结束标签匹配一次!!!
127 | parserTag(context, TagType.End)
128 | } else {
129 | throw new Error(`缺少结束标签: ${element.tag}`)
130 | }
131 |
132 | return element
133 | }
134 |
135 | const startWithEndTagOpen = (source, tag) => {
136 | return source.startsWith('') && source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
137 | }
138 |
139 | const parserTag = (context, type: TagType) => {
140 | // 1.解析 tag
141 | //
142 | //
143 | // 匹配以 < 开头或者以 开头的 字符,/ 可以没有。
144 | const match: any = /^<\/?([a-z]*)/i.exec(context.source)
145 | const tag = match[1]
146 |
147 | // 2.删除处理完成的代码
148 | advanceBy(context, match[0].length)
149 | advanceBy(context, 1)
150 | if (type === TagType.End) {
151 | // 如果是结束标签 () 直接不用返回 后面的东西了。
152 | return
153 | }
154 |
155 | return {
156 | type: NodeTypes.ELEMENT,
157 | tag
158 | }
159 | }
160 |
161 | // text 文本类型
162 | const parseText = (context) => {
163 | let endTokens = ['{{', '<']
164 | let endIndex = context.source.length
165 | // 遇到 {{ 或者 < 都应该直接停下,返回了
166 | for (let i = 0; i < endTokens.length; i++) {
167 | const index = context.source.indexOf(endTokens[i])
168 | // 当 字符串中 存在 {{ 表示是文本和 插值混合的。
169 | if (index !== -1 && endIndex > index) {
170 | endIndex = index
171 | }
172 | }
173 |
174 | // 1. 获取content
175 | const content = parseTextData(context, endIndex)
176 |
177 | return {
178 | type: NodeTypes.TEXT,
179 | content
180 | }
181 | }
182 |
183 | const parseTextData = (context: any, length) => {
184 | const content = context.source.slice(0, length)
185 |
186 | // 2. 推进
187 | advanceBy(context, length)
188 |
189 | return content
190 | }
191 |
192 | export {
193 | baseParse
194 | }
195 |
196 |
--------------------------------------------------------------------------------
/src/compiler-core/src/runtimeHelpers.ts:
--------------------------------------------------------------------------------
1 | const TO_DISPLAY_STRING = Symbol('toDisplayString')
2 | const CREATE_ELEMENT_VNODE = Symbol('ctreateElementVNode')
3 |
4 | const helperMapName = {
5 | [TO_DISPLAY_STRING]: 'toDisplayString',
6 | [CREATE_ELEMENT_VNODE]: 'createElementVNode'
7 | }
8 |
9 | export {
10 | helperMapName,
11 | TO_DISPLAY_STRING,
12 | CREATE_ELEMENT_VNODE
13 | }
14 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transform.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from './ast'
2 | import { TO_DISPLAY_STRING } from './runtimeHelpers'
3 |
4 | const transform = (root, options = {}) => {
5 |
6 | const context = createTransformContext(root, options)
7 |
8 | traverseNode(root, context)
9 | createRootCodegen(root)
10 |
11 | root.helpers = [...context.helpers.keys()]
12 | }
13 |
14 | const createRootCodegen = (root: any) => {
15 | const child = root.children[0]
16 |
17 | if (child.type === NodeTypes.ELEMENT) {
18 | root.codegenNode = child.codegenNode
19 | } else {
20 | root.codegenNode = root.children[0]
21 | }
22 | }
23 |
24 | const traverseNode = (node, context) => {
25 | const nodeTransforms = context.nodeTransforms
26 | const exitFns: any = []
27 |
28 | for (let i = 0; i < nodeTransforms.length; i++) {
29 | const transform = nodeTransforms[i]
30 | const onExit = transform(node, context)
31 | if (onExit) {
32 | exitFns.push(onExit)
33 | }
34 | }
35 | // 这里需要 分情况处理不同类型的逻辑
36 | switch (node.type) {
37 | // 插值类型
38 | case NodeTypes.INTERPOLATION:
39 | context.helper(TO_DISPLAY_STRING)
40 | break
41 | // root 根结点
42 | case NodeTypes.ROOT:
43 | case NodeTypes.ELEMENT:
44 | // 处理 children
45 | traverseChildren(node, context)
46 | break
47 | default:
48 | break
49 | }
50 |
51 | let i = exitFns.length
52 | while (i--) {
53 | exitFns[i]()
54 | }
55 | }
56 |
57 | const createTransformContext = (root, options) => {
58 | const context = {
59 | root,
60 | helpers: new Map(),
61 | helper(key) {
62 | context.helpers.set(key, 1)
63 | },
64 | nodeTransforms: options.nodeTransforms || []
65 | }
66 | return context
67 | }
68 |
69 | const traverseChildren = (node: any, context: any) => {
70 | const children = node.children
71 | for (let i = 0; i < children.length; i++) {
72 | const node = children[i]
73 | traverseNode(node, context)
74 | }
75 | }
76 |
77 | export {
78 | transform
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformElement.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes, createVNodeCall } from '../ast'
2 |
3 | const transformElement = (node, context) => {
4 | return () => {
5 | if (node.type === NodeTypes.ELEMENT) {
6 | // 以下中间处理层,处理一下数据~
7 |
8 | // tag
9 | const vnodeTag = `'${node.tag}'`
10 |
11 | // props
12 | let vnodeProps
13 |
14 | let vnodeChild = node.children[0]
15 |
16 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChild)
17 | }
18 | }
19 | }
20 |
21 | export {
22 | transformElement
23 | }
24 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformExpression.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from '../ast'
2 |
3 | const transformExpression = (node) => {
4 | if (node.type === NodeTypes.INTERPOLATION) {
5 | processExpression(node.content)
6 | }
7 | }
8 |
9 | const processExpression = (node) => {
10 | node.content = `_ctx.${node.content}`
11 | }
12 |
13 | export {
14 | transformExpression
15 | }
16 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformText.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from '../ast'
2 | import { isText } from '../utils'
3 |
4 | const transformText = (node) => {
5 | return () => {
6 | const { children } = node
7 | let currentContainer
8 |
9 | if (node.type === NodeTypes.ELEMENT) {
10 | for (let i = 0; i < children.length; i++) {
11 | const child = children[i]
12 | if (isText(child)) {
13 | for (let j = i + 1; j < children.length; j++) {
14 | const nextChild = children[j]
15 | if (isText(nextChild)) {
16 | if (!currentContainer) {
17 | currentContainer = children[i] = {
18 | type: NodeTypes.COMPOUND_EXPRESSION,
19 | children: [child]
20 | }
21 | }
22 | currentContainer.children.push(' + ')
23 | currentContainer.children.push(nextChild)
24 | // 添加完成的 元素需要去掉
25 | children.splice(j, 1)
26 | // 删除以后后面的元素前移,导致取错,--即可
27 | j--
28 | } else {
29 | currentContainer = undefined
30 | break
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
39 | export {
40 | transformText
41 | }
42 |
--------------------------------------------------------------------------------
/src/compiler-core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from './ast'
2 |
3 | const isText = (node) => {
4 | return (
5 | node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION
6 | );
7 | }
8 |
9 | export {
10 | isText
11 | }
12 |
--------------------------------------------------------------------------------
/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, $props, $setup, $data, $options) { 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, $props, $setup, $data, $options) { return _toDisplayString(_ctx.message) }"
11 | `;
12 |
13 | exports[`codegen string 1`] = `
14 | "
15 | return function render (_ctx, _cache, $props, $setup, $data, $options) { return 'hi' }"
16 | `;
17 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/codegen.spec.ts:
--------------------------------------------------------------------------------
1 | import { generate } from '../src/codegen'
2 | import { baseParse } from '../src/parse'
3 | import { transform } from '../src/transform'
4 | import { transformExpression } from '../src/transforms/transformExpression'
5 | import { transformElement } from '../src/transforms/transformElement'
6 | import { transformText } from '../src/transforms/transformText'
7 |
8 | describe('codegen', () => {
9 | it('string', () => {
10 | const ast = baseParse('hi')
11 | transform(ast)
12 | const { code } = generate(ast)
13 | expect(code).toMatchSnapshot()
14 | })
15 |
16 | it('interpolation', () => {
17 | const ast = baseParse('{{message}}')
18 | transform(ast, {
19 | nodeTransforms: [transformExpression]
20 | })
21 | const { code } = generate(ast)
22 | expect(code).toMatchSnapshot()
23 | })
24 |
25 | it('element', () => {
26 | const ast: any = baseParse('hi, {{ message }}
')
27 | transform(ast, {
28 | nodeTransforms: [
29 | transformExpression,
30 | transformElement,
31 | transformText
32 | ]
33 | })
34 |
35 | const { code } = generate(ast)
36 | expect(code).toMatchSnapshot()
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/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 | test('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 | test('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 | describe('三种类型联合', () => {
42 | test('hello world', () => {
43 | const ast = baseParse('hi, {{message}}
')
44 |
45 | expect(ast.children[0]).toStrictEqual({
46 | type: NodeTypes.ELEMENT,
47 | tag: 'div',
48 | children: [
49 | {
50 | type: NodeTypes.TEXT,
51 | content: 'hi, '
52 | },
53 | {
54 | type: NodeTypes.INTERPOLATION,
55 | content: {
56 | type: NodeTypes.SIMPLE_EXPRESSION,
57 | content: 'message'
58 | }
59 | }
60 | ]
61 | })
62 | })
63 |
64 | test('Nested element', () => {
65 | const ast = baseParse('');
66 |
67 | expect(ast.children[0]).toStrictEqual({
68 | type: NodeTypes.ELEMENT,
69 | tag: 'div',
70 | children: [
71 | {
72 | type: NodeTypes.ELEMENT,
73 | tag: 'p',
74 | children: [
75 | {
76 | type: NodeTypes.TEXT,
77 | content: 'hi'
78 | }
79 | ]
80 | },
81 | {
82 | type: NodeTypes.INTERPOLATION,
83 | content: {
84 | type: NodeTypes.SIMPLE_EXPRESSION,
85 | content: 'message'
86 | }
87 | }
88 | ]
89 | })
90 | })
91 |
92 | test('Nested element ~~~ ', () => {
93 | const ast = baseParse('hi
{{message}} -- {{message11}}
');
94 |
95 | expect(ast.children[0]).toStrictEqual({
96 | type: NodeTypes.ELEMENT,
97 | tag: 'div',
98 | children: [
99 | {
100 | type: NodeTypes.ELEMENT,
101 | tag: 'p',
102 | children: [
103 | {
104 | type: NodeTypes.TEXT,
105 | content: 'hi'
106 | }
107 | ]
108 | },
109 | {
110 | type: NodeTypes.INTERPOLATION,
111 | content: {
112 | type: NodeTypes.SIMPLE_EXPRESSION,
113 | content: 'message'
114 | }
115 | },
116 | {
117 | type: NodeTypes.TEXT,
118 | content: ' -- '
119 | },
120 | {
121 | type: NodeTypes.INTERPOLATION,
122 | content: {
123 | type: NodeTypes.SIMPLE_EXPRESSION,
124 | content: 'message11'
125 | }
126 | }
127 | ]
128 | })
129 | })
130 |
131 | test('should throw error when lack end tag', () => {
132 | expect(() => {
133 | baseParse('
')
134 | }).toThrow(`缺少结束标签: span`)
135 | })
136 | })
137 | })
138 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/transform.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from '../src/ast'
2 | import { baseParse } from '../src/parse'
3 | import { transform } from '../src/transform'
4 |
5 | describe('transform', () => {
6 | it('happy path', () => {
7 | const ast = baseParse('hi, {{message}}
')
8 |
9 | const plugin = (node) => {
10 | if (node.type === NodeTypes.TEXT) {
11 | node.content = node.content + 'mini-vue'
12 | }
13 | }
14 |
15 | transform(ast, {
16 | nodeTransforms: [plugin]
17 | })
18 |
19 | const nodeText = ast.children[0].children[0]
20 | expect(nodeText.content).toBe('hi, mini-vue')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 出口
2 | export * from './runtime-dom'
3 | export * from './reactivity'
4 | export * from './runtime-dom'
5 | import { baseCompile } from './compiler-core/src'
6 | import * as runtimeDom from './runtime-dom'
7 | import { registerRuntimeCompiler } from './runtime-dom'
8 |
9 | function compileToFunction(template) {
10 | const { code } = baseCompile(template)
11 | const render = new Function('vue', code)(runtimeDom)
12 | return render
13 | }
14 |
15 | registerRuntimeCompiler(compileToFunction)
16 |
--------------------------------------------------------------------------------
/src/reactivity/baseHandler.ts:
--------------------------------------------------------------------------------
1 | import { track, trigger } from './effect'
2 | import { ReactiveFlags } from '../reactivity/enum'
3 | import { reactive, readonly } from './reactive'
4 | import { extend, isObject } from '../shared/index'
5 |
6 | let get
7 | let readOnlyGet
8 | let shallowReadonlyGet
9 | let set
10 |
11 | const createGetter = (isReadOnly = false, shallow = false) => {
12 | const get = (target, key) => {
13 | // target: { foo: 1 }
14 | // key: foo
15 | if (key === ReactiveFlags.IS_REACTIVE) {
16 | // 通过 isReadOnly 来判断是不是 reactive 对象
17 | // return !isReadOnly
18 |
19 | // ???
20 | // 这里是不是可以直接指定为true,因为调用 getter 函数 肯定是 proxy 对象,所以一定是 reactive
21 | return true
22 | } else if (key === ReactiveFlags.IS_READONLY) {
23 | return isReadOnly
24 | }
25 | const res = Reflect.get(target, key)
26 | // 如果是 shallow 类型(即外层是响应是对象,里面的不是 且设计成只读模式)
27 | if (shallow) {
28 | return res
29 | }
30 | if (!isReadOnly) {
31 | // 依赖收集
32 | track(target, key)
33 | }
34 |
35 | // 看看 res 是不是 object
36 | if (isObject(res)) {
37 | return isReadOnly ? readonly(res) : reactive(res)
38 | }
39 |
40 | return res
41 | }
42 | return get
43 | }
44 |
45 | const createSetter = () => {
46 | const set = (target, key, value) => {
47 | const res = Reflect.set(target, key, value)
48 | // 触发依赖
49 | trigger(target, key)
50 | return res
51 | }
52 | return set
53 | }
54 |
55 | get = createGetter()
56 | readOnlyGet = createGetter(true)
57 | shallowReadonlyGet = createGetter(true, true)
58 | set = createSetter()
59 |
60 | const mutableHandlers = {
61 | get,
62 | set
63 | }
64 |
65 | const readonlyHandlers = {
66 | get: readOnlyGet,
67 | set (target, key, value) {
68 | console.warn(`key: ${key} set 失败, 因为 target:`, target, `是 readonly状态!`)
69 | return true
70 | }
71 | }
72 |
73 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
74 | get: shallowReadonlyGet
75 | })
76 |
77 | export {
78 | mutableHandlers,
79 | readonlyHandlers,
80 | shallowReadonlyHandlers
81 | }
82 |
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from './effect'
2 |
3 | class ComputedRefImpl {
4 | // private _getter: any
5 | private _value: any
6 | private _effect: any
7 | private _dirty: boolean = true
8 | constructor (getter) {
9 | // this._getter = getter
10 | this._effect = new ReactiveEffect(getter, () => {
11 | if (!this._dirty) {
12 | this._dirty = true
13 | }
14 | })
15 | }
16 | get value () {
17 | // get value --> _dirty is true
18 | // 当依赖的响应式对象发生改变的时候 修改 _dirty
19 | // effect
20 | if (this._dirty) {
21 | this._dirty = false
22 | this._value = this._effect.run()
23 | }
24 | return this._value
25 | }
26 | }
27 |
28 |
29 | const computed = (getter) => {
30 | return new ComputedRefImpl(getter)
31 | }
32 |
33 | export {
34 | computed
35 | }
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from '../shared'
2 |
3 | let activeEffect
4 | let shouldTrack
5 | class ReactiveEffect {
6 | private _fn: any
7 | public _scheduler: any
8 | onStop?: () => void
9 | active = true // stop 状态
10 | deps = []
11 |
12 | constructor(fn, scheduler?){
13 | this._fn = fn
14 | this._scheduler = scheduler
15 | }
16 |
17 | run() {
18 | // 会收集依赖
19 | // shouldTrack 来做区分
20 | if (!this.active) {
21 | return this._fn()
22 | }
23 |
24 | shouldTrack = true
25 | activeEffect = this
26 | const res = this._fn()
27 | // 全局变量 reset
28 | shouldTrack = false
29 |
30 | return res
31 | }
32 |
33 | stop () {
34 | if (this.active) {
35 | cleanUpEffect(this)
36 | this.active = false
37 | if (this.onStop) {
38 | this.onStop()
39 | }
40 | }
41 | }
42 | }
43 | const isTracking = () => {
44 | // 判断是不是 应该 收集依赖 & 有没有全局的 effect
45 | return shouldTrack && activeEffect !== undefined
46 | }
47 |
48 | const cleanUpEffect = (effect) => {
49 | effect.deps.map((dep: any) => {
50 | dep.delete(effect)
51 | })
52 | effect.deps.length = 0
53 | }
54 |
55 | const effect = (fn, options: any = {}) => {
56 | const { scheduler } = options
57 | // 初始化的时候就需要调用一次fn
58 | const _effect = new ReactiveEffect(fn, scheduler)
59 |
60 | // 将调用 options 中的参数 和 类上同名参数赋值
61 | // onStop --> onStop
62 | extend(_effect, options)
63 |
64 | _effect.run()
65 |
66 | // 将传进来的 fn 返回出去
67 | // bind 以当前的 effect 实例作为函数的 this 指针
68 | const runner: any = _effect.run.bind(_effect)
69 | runner.effect = _effect
70 |
71 | return runner
72 | }
73 |
74 | const targetMap = new Map()
75 |
76 | const trackEffects = (dep) => {
77 | // 如果没有 effect 实例直接不做后面的操作
78 | // if (!activeEffect) return
79 | // if (!shouldTrack) return
80 |
81 | dep.add(activeEffect)
82 | activeEffect.deps.push(dep)
83 | }
84 |
85 | const track = (target, key) => {
86 | if (!isTracking()) return
87 | // target --> key --> dep
88 | let depsMap = targetMap.get(target)
89 | // 不存在despMap 先初始化一下
90 | if (!depsMap) {
91 | depsMap = new Map()
92 | targetMap.set(target, depsMap)
93 | }
94 |
95 | // 不存在dep 先初始化一下
96 | let dep = depsMap.get(key)
97 | if (!dep) {
98 | dep = new Set()
99 | depsMap.set(key, dep)
100 | }
101 | trackEffects(dep)
102 | }
103 |
104 | const triggerEffects = (dep) => {
105 | // 循环调用 dep 的 run 方法 触发每一个 dep 的 _fn
106 | for (const effect of dep) {
107 | if (effect._scheduler) {
108 | effect._scheduler()
109 | } else {
110 | effect.run()
111 | }
112 | }
113 | }
114 |
115 | const trigger = (target, key) => {
116 | // 取出 target 对应的 depsMap
117 | let depsMap = targetMap.get(target)
118 |
119 | // 取出 key 对应的 dep
120 | let dep = depsMap.get(key)
121 | triggerEffects(dep)
122 | }
123 | const stop = (runner) => {
124 | runner.effect.stop()
125 | }
126 |
127 |
128 |
129 | export {
130 | ReactiveEffect,
131 | effect,
132 | isTracking,
133 | track,
134 | trackEffects,
135 | trigger,
136 | triggerEffects,
137 | stop
138 | }
--------------------------------------------------------------------------------
/src/reactivity/enum.ts:
--------------------------------------------------------------------------------
1 | const enum ReactiveFlags {
2 | IS_REACTIVE = '__v_isReactive',
3 | IS_READONLY = '__v_isReadonly'
4 | }
5 |
6 | export {
7 | ReactiveFlags
8 | }
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ref'
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from '../shared/index'
2 | import { mutableHandlers, readonlyHandlers, shallowReadonlyHandlers } from './baseHandler'
3 | import { ReactiveFlags } from './enum'
4 |
5 | const createActiveObject = (raw, baseHandlers) => {
6 | if (!isObject(raw)) {
7 | console.warn(`target: ${raw} 必须是一个对象!`)
8 | } else {
9 | return new Proxy(raw, baseHandlers)
10 | }
11 | }
12 |
13 | const reactive = (raw) => {
14 | return createActiveObject(raw, mutableHandlers)
15 | }
16 |
17 | const readonly = (raw) => {
18 | return createActiveObject(raw, readonlyHandlers)
19 | }
20 |
21 | const isReactive = (raw) => {
22 | return !!raw[ReactiveFlags.IS_REACTIVE]
23 | }
24 |
25 | const isReadonly = (raw) => {
26 | return !!raw[ReactiveFlags.IS_READONLY]
27 | }
28 |
29 | const shallowReadonly = (raw) => {
30 | return createActiveObject(raw, shallowReadonlyHandlers)
31 | }
32 |
33 | const isProxy = (raw) => {
34 | return isReactive(raw) || isReadonly(raw)
35 | }
36 |
37 | const proxyRefs = (raw) => {
38 | return isReactive(raw) || isReadonly(raw)
39 | }
40 |
41 | export {
42 | reactive,
43 | readonly,
44 | isReactive,
45 | isReadonly,
46 | shallowReadonly,
47 | isProxy,
48 | proxyRefs
49 | }
50 |
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from '../shared'
2 | import { trackEffects, triggerEffects, isTracking } from './effect'
3 | import { reactive } from './reactive'
4 |
5 | class RefImpl {
6 | private _value
7 | private _rawValue
8 | public dep
9 | public __v_isRef = true
10 | constructor(value) {
11 | // value 如果是对象 要用 reactive 转换成响应式对象
12 | this._rawValue = value
13 | this._value = covert(value)
14 | this.dep = new Set()
15 | }
16 | get value () {
17 | trackRefValue(this)
18 | return this._value
19 | }
20 | set value (newValue) {
21 | if (hasChanged(this._rawValue, newValue)) {
22 | // 一定先修改值,再触发\
23 | this._rawValue = newValue
24 | this._value = covert(newValue)
25 | triggerEffects(this.dep)
26 | }
27 | }
28 | }
29 |
30 | const covert = (value) => {
31 | return isObject(value) ? reactive(value) : value
32 | }
33 |
34 | const trackRefValue = (ref) => {
35 | if (isTracking()) {
36 | trackEffects(ref.dep)
37 | }
38 | }
39 |
40 | const ref = (value) => {
41 | return new RefImpl(value)
42 | }
43 |
44 | const isRef = (ref) => {
45 | return !!ref.__v_isRef
46 | }
47 |
48 | const unRef = (ref) => {
49 | return isRef(ref) ? ref.value: ref
50 | }
51 |
52 | const proxyRefs = (objectWithRefs) => {
53 | // 如果获取的值 是 ref 类型 那么就返回 .value
54 | // 如果获取的值 不是 ref 那么就直接返回 它本身的值
55 | return new Proxy(objectWithRefs, {
56 | get (target, key) {
57 | return unRef(Reflect.get(target, key))
58 | },
59 | set (target, key, value) {
60 | // 看看是 是 ref 类型 是的话修改 .value
61 | // 看看是 不是 ref 类型 是的话修改 本身的值
62 | if (isRef(target[key]) && !isRef(value)) {
63 | return Reflect.set(target[key], 'value', value)
64 | // return target[key].value = value
65 | } else {
66 | return Reflect.set(target, key, value)
67 | }
68 | }
69 | })
70 | }
71 |
72 | export {
73 | ref,
74 | isRef,
75 | unRef,
76 | proxyRefs
77 | }
--------------------------------------------------------------------------------
/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 | const user = reactive({
7 | age: 1
8 | })
9 |
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 | expect(getter).not.toHaveBeenCalled()
28 |
29 | expect(cValue.value).toBe(1)
30 | expect(getter).toHaveBeenCalledTimes(1)
31 |
32 | // should not compute again
33 | cValue.value // get
34 | expect(getter).toHaveBeenCalledTimes(1)
35 |
36 | // should not compute until needed
37 | // trigger --> effect --> get 重新执行
38 | value.foo = 2
39 | expect(getter).toHaveBeenCalledTimes(1)
40 |
41 | // now it should compute
42 | expect(cValue.value).toBe(2)
43 | expect(getter).toHaveBeenCalledTimes(2)
44 |
45 | // should not compute again
46 | cValue.value
47 | expect(getter).toHaveBeenCalledTimes(2)
48 | })
49 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from '../reactive'
2 | import { effect, stop } from '../effect'
3 |
4 | describe('effect', () => {
5 | it('happy path', () => {
6 | const user = reactive({
7 | age: 10
8 | })
9 |
10 | let nextAge
11 | effect(() => {
12 | nextAge = user.age + 1
13 | })
14 |
15 | expect(nextAge).toBe(11)
16 |
17 | user.age++
18 | expect(nextAge).toBe(12)
19 | })
20 |
21 | it('should return runner when call effect', () => {
22 | // 调用 effect 之后 返回一个函数 runner,当调用 runner 会再次执行 传入effect 内部的 fn,并且获的fn的返回值
23 | let foo = 10
24 | const runner = effect(() => {
25 | foo++
26 | return 'foo'
27 | })
28 | expect(foo).toBe(11)
29 |
30 | const r = runner()
31 | expect(foo).toBe(12)
32 | expect(r).toBe('foo')
33 | })
34 |
35 | it('scheduler', () => {
36 | // 1.通过 effect 的第二个参数 给定一个 shcheduler 的 fn,并且获的fn的返回值
37 | // 2.effect 第一次执行的时候还会执行 fn,并且获的fn的返回值
38 | // 3.当响应式 对象set update 不会执行 fn 而是执行scheduler
39 | // 4.如果说当执行 runner 的时候 会再次的执行 fn
40 | let dummy
41 | let run: any
42 | const scheduler = jest.fn(() => {
43 | run = runner
44 | })
45 | const obj = reactive({ foo: 1 })
46 | const runner = effect(
47 | () => {
48 | dummy = obj.foo
49 | },
50 | { scheduler }
51 | )
52 | expect(scheduler).not.toHaveBeenCalled()
53 | expect(dummy).toBe(1)
54 | // should be called on first trigger
55 | obj.foo++
56 | expect(scheduler).toHaveBeenCalledTimes(1)
57 | // should not run yet
58 | expect(dummy).toBe(1)
59 | // // manually run
60 | run()
61 | // should have run
62 | expect(dummy).toBe(2)
63 | })
64 |
65 | it('stop', () => {
66 | let dummy
67 | const obj = reactive({ prop: 1 })
68 | const runner = effect(() => {
69 | dummy = obj.prop
70 | })
71 | obj.prop = 2
72 | expect(dummy).toBe(2)
73 | stop(runner)
74 | // obj.prop = 3
75 | // 先 get 再 set
76 | obj.prop++
77 | expect(dummy).toBe(2)
78 |
79 | // stopped effect should still be manually callable
80 | runner()
81 | expect(dummy).toBe(3)
82 | })
83 |
84 | it('onStop', () => {
85 | const obj = reactive({
86 | foo: 1
87 | })
88 | const onStop = jest.fn()
89 | let dummy
90 | const runner = effect(
91 | () => {
92 | dummy = obj.foo
93 | },
94 | {
95 | onStop,
96 | }
97 | )
98 |
99 | stop(runner)
100 | expect(onStop).toBeCalledTimes(1)
101 | })
102 | })
103 |
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive, isReactive, readonly, isProxy } from '../reactive'
2 |
3 | describe('reactive', () => {
4 | it('happy path', () => {
5 | const original = { foo: 1 }
6 | const observed = reactive(original)
7 | const readonlyObj = readonly(original)
8 |
9 | expect(original).not.toBe(observed)
10 | expect(original.foo).toBe(1)
11 | expect(observed.foo).toBe(1)
12 |
13 | expect(isReactive(observed)).toBe(true)
14 | expect(isReactive(original)).toBe(false)
15 | expect(isReactive(readonlyObj)).toBe(true)
16 |
17 | expect(isProxy(original)).toBe(false)
18 | expect(isProxy(observed)).toBe(true)
19 | })
20 |
21 | test('nested reactives', () => {
22 | const original = {
23 | nested: {
24 | foo: 1
25 | },
26 | array: [{ bar: 2 }]
27 | }
28 | const observed = reactive(original)
29 | expect(isReactive(observed.nested)).toBe(true)
30 | expect(isReactive(observed.array)).toBe(true)
31 | expect(isReactive(observed.array[0])).toBe(true)
32 | });
33 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, readonly, isProxy } from '../reactive'
2 |
3 | describe('readonly', () => {
4 | it('should make nested values readonly', () => {
5 | const original = { foo: 1, bar: { baz: 2 } }
6 | const wrapped = readonly(original)
7 | expect(wrapped).not.toBe(original)
8 | expect(wrapped.foo).toBe(1)
9 | expect(isReadonly(wrapped.bar)).toBe(true)
10 | expect(isReadonly(original.bar)).toBe(false)
11 |
12 | expect(isReadonly(wrapped)).toBe(true)
13 | expect(isReadonly(original)).toBe(false)
14 |
15 | expect(isProxy(original)).toBe(false)
16 | expect(isProxy(wrapped)).toBe(true)
17 |
18 | })
19 |
20 | it('should call console.warn when set', () => {
21 | console.warn = jest.fn()
22 | const user = readonly({
23 | age: 10
24 | })
25 |
26 | user.age = 11
27 | expect(console.warn).toHaveBeenCalled()
28 | })
29 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from '../effect'
2 | import { reactive } from '../reactive'
3 | import { ref, isRef, 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 | const user = {
60 | age: ref(10),
61 | name: 'xiaohong'
62 | }
63 |
64 | const proxyUser = proxyRefs(user)
65 | expect(user.age.value).toBe(10)
66 | expect(proxyUser.age).toBe(10)
67 | expect(proxyUser.name).toBe('xiaohong')
68 |
69 | proxyUser.age = 20
70 |
71 | expect(proxyUser.age).toBe(20)
72 | expect(user.age.value).toBe(20)
73 |
74 | proxyUser.age = ref(10)
75 | expect(proxyUser.age).toBe(10)
76 | expect(user.age.value).toBe(10)
77 | })
78 | })
--------------------------------------------------------------------------------
/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 | const props = shallowReadonly({ n: { foo: 1 } })
6 | expect(isReadonly(props)).toBe(true)
7 | expect(isReadonly(props.n)).toBe(false)
8 | })
9 |
10 | it('should call console.warn when set', () => {
11 | console.warn = jest.fn()
12 | const user = shallowReadonly({
13 | age: 10,
14 | })
15 |
16 | user.age = 11
17 | expect(console.warn).toHaveBeenCalled()
18 | })
19 | })
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from './component'
2 |
3 | const provide = (key, value) => {
4 | // 存
5 | const currentInstance = getCurrentInstance()
6 | if (currentInstance) {
7 | let { provides } = currentInstance
8 | const parentProvides = currentInstance.parent.provides
9 | // init
10 | // 只需要初始化的时候 执行一次
11 | // 当初始化完成以后实例的 provides 肯定是和父级的 provides相等
12 | // 调用过过 provide 以后 当前实例的 provides 肯定和父级不一样(因为有赋值操作)
13 | if (provides === parentProvides) {
14 | // 将当前 provide 实例的原型指向父级
15 | provides = currentInstance.provides = Object.create(parentProvides)
16 | }
17 |
18 | provides[key] = value
19 | }
20 | }
21 |
22 | const inject = (key, defaultVal) => {
23 | // 取
24 | const currentInstance = getCurrentInstance()
25 | if (currentInstance) {
26 | const { parent } = currentInstance
27 | const parentProvides = parent.provides
28 | if (key in parentProvides) {
29 | return parentProvides[key]
30 | } else if (defaultVal) {
31 | if (typeof defaultVal === 'function') {
32 | return defaultVal()
33 | }
34 | return defaultVal
35 | }
36 | }
37 | }
38 |
39 | export {
40 | provide,
41 | inject
42 | }
43 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { shallowReadonly } from '../reactivity/reactive'
2 | import { emit } from './componentEmit'
3 | import { initProps } from './componentProps'
4 | import { initSlots } from './componentSlots'
5 | import { publickInstanceProxyhandlers } from './componentPublicInstance'
6 | import { proxyRefs } from '../reactivity'
7 |
8 | let currentInstance
9 |
10 | const createComponentInstance = (vnode, parent) => {
11 | const component = {
12 | vnode,
13 | type: vnode.type,
14 | props: {},
15 | emit: {},
16 | slots: {},
17 | next: null,
18 | setupState: {},
19 | parent,
20 | // parent,
21 | provides: parent ? parent.provides : {},
22 | isMounted: false,
23 | subTree: {}
24 | }
25 | component.emit = emit.bind(null, component)
26 |
27 | return component
28 | }
29 |
30 | const setupComponent = (instance) => {
31 | // initProps
32 | initProps(instance, instance.vnode.props)
33 | // initSlot
34 | initSlots(instance, instance.vnode.children)
35 | // initComponent
36 | setupStatefulComponent(instance)
37 | }
38 |
39 | const setupStatefulComponent = (instance) => {
40 | const Component = instance.type
41 | const { setup } = Component
42 |
43 | // ctx
44 | instance.proxy = new Proxy({
45 | _: instance
46 | }, publickInstanceProxyhandlers)
47 |
48 | // 用户可能不会写 setup 函数
49 | if (setup) {
50 | // 初始化获取 instance
51 | setCurrentInstance(instance)
52 |
53 | // 可以返回一个 fn 也可能是一个 object
54 | const setupResult = setup(shallowReadonly(instance.props), {
55 | emit: instance.emit
56 | })
57 |
58 | handleSetupResult(instance, setupResult)
59 |
60 | // 重制获取 instance
61 | setCurrentInstance(null)
62 | }
63 | }
64 |
65 | const handleSetupResult = (instance, setupResult) => {
66 | // function or object
67 | // TODO function
68 |
69 | if (typeof setupResult === 'object') {
70 | instance.setupState = proxyRefs(setupResult)
71 | }
72 | // 初始化 render 函数
73 | finishCompentSetup(instance)
74 | }
75 |
76 | const finishCompentSetup =(instance) => {
77 | const Component = instance.type
78 | if (compiler && !Component.render) {
79 | if (Component.template) {
80 | Component.render = compiler(Component.template);
81 | }
82 | }
83 |
84 | instance.render = Component.render
85 | }
86 |
87 | const getCurrentInstance = () => {
88 | return currentInstance
89 | }
90 |
91 | const setCurrentInstance = (instance) => {
92 | currentInstance = instance;
93 | }
94 |
95 | let compiler
96 |
97 | const registerRuntimeCompiler = (_compiler) => {
98 | compiler = _compiler
99 | }
100 |
101 | export {
102 | createComponentInstance,
103 | setupComponent,
104 | setupStatefulComponent,
105 | getCurrentInstance,
106 | registerRuntimeCompiler
107 | }
108 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { toHandlerKey } from '../shared/index'
2 |
3 | const emit = (instance, eventName, ...args) => {
4 | const { props } = instance
5 | const handlerName = toHandlerKey(eventName)
6 | const handler = props[handlerName]
7 |
8 | handler && handler(...args)
9 | }
10 |
11 | export { emit }
12 |
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | const initProps = (instance, rawProps = {}) => {
2 | instance.props = rawProps
3 | }
4 |
5 | export {
6 | initProps
7 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from '../shared/index'
2 |
3 | const publicPropertiesMap = {
4 | $el: (i) => i.vnode.el,
5 | $slots: (i) => i.slots,
6 | $props: (i) => i.props,
7 | }
8 |
9 | const publickInstanceProxyhandlers = {
10 | get({ _: instance }, key) {
11 | // setupState
12 | // 这里必须要在这里 获取 setupState
13 | // 因为 只有在初始化组件 的时候 获取 setupState
14 | const { setupState, props } = instance
15 | if (hasOwn(setupState, key)) {
16 | // setupState 里面获取值
17 | return setupState[key]
18 | } else if (hasOwn(props, key)) {
19 | return props[key]
20 | }
21 |
22 | // $el
23 | // if (key === '$el') {
24 | // // key --> $el
25 | // // 如果是 this.$el 则 key 值就是 $el
26 | // return vnode.el
27 | // }
28 |
29 | const publicGetter = publicPropertiesMap[key]
30 | if (publicGetter) {
31 | return publicGetter(instance)
32 | }
33 | }
34 | }
35 |
36 | export {
37 | publickInstanceProxyhandlers
38 | }
39 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from '../shared/ShapeFlags'
2 |
3 | const initSlots = (instance, children) => {
4 | const { vnode } = instance
5 | // 如果是 slot 类型 再进行处理
6 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) {
7 | normalizeObjectSlots(instance.slots, children)
8 | }
9 | }
10 |
11 | const normalizeObjectSlots = (slots, children) => {
12 | for (const key in children) {
13 | const slotVal = children[key]
14 | // 将设计的 props 传入对应的 slot
15 | slots[key] = (props) => normalizeSlotValue(slotVal(props))
16 | }
17 |
18 | slots = slots
19 | }
20 |
21 | const normalizeSlotValue = (value) => {
22 | // 传入的 children(slots) 是不是数组,不是数组转换一下
23 | return Array.isArray(value) ? value: [value]
24 | }
25 |
26 | export {
27 | initSlots
28 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | const shouldUpdateComponent = (prevVNode, nextVNode) => {
2 | const { props: prevProps } = prevVNode
3 | const { props: nextProps } = nextVNode
4 |
5 | for (const key in nextProps) {
6 | if (nextProps[key] !== prevProps[key]) {
7 | return true
8 | }
9 | }
10 |
11 | return false
12 | }
13 |
14 | export {
15 | shouldUpdateComponent
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from './vnode'
2 |
3 | const createAppAPI = (render) => {
4 | const createApp = (rootComponent) => {
5 | return {
6 | mount(rootContainer) {
7 | // 先转换成虚拟节点
8 | // component --> vnode
9 | // 后续所有的逻辑操作 都会基于 vnode 去操作
10 | const vnode = createVNode(rootComponent)
11 |
12 | render(vnode, rootContainer)
13 | }
14 | }
15 | }
16 | return createApp
17 | }
18 |
19 | export {
20 | createAppAPI
21 | }
22 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from './vnode'
2 |
3 | const h = (type, props?, children?) => {
4 | return createVNode(type, props, children)
5 | }
6 |
7 | export {
8 | h
9 | }
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from '../vnode'
2 |
3 | const renderSlots = (slots, slotName, props) => {
4 | const slot = slots[slotName]
5 |
6 | if (slot) {
7 | if (typeof slot === 'function') {
8 | return createVNode(Fragment, {}, slot(props))
9 | }
10 | }
11 | }
12 |
13 | export {
14 | renderSlots
15 | }
16 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createApp'
2 | export * from './helpers/renderSlots'
3 | export * from './h'
4 | export * from './vnode'
5 | export * from './component'
6 | export * from './apiInject'
7 | export * from './render'
8 | export * from './scheduler'
9 | export * from '../shared'
10 |
--------------------------------------------------------------------------------
/src/runtime-core/render.ts:
--------------------------------------------------------------------------------
1 | import { effect } from '../reactivity/effect'
2 | import { ShapeFlags } from '../shared/ShapeFlags'
3 | import { EMPTY_OBJ, getSequence } from '../shared'
4 | import { createComponentInstance, setupComponent } from './component'
5 | import { createAppAPI } from './createApp'
6 | import { Fragment, Text } from './vnode'
7 | import { shouldUpdateComponent } from './componentUpdateUtils'
8 | import { queueJobs } from './scheduler'
9 |
10 | // custom render
11 | const createRender = (options) => {
12 | const {
13 | createElement: hostCreateElement,
14 | patchProp: hostPatchProp,
15 | insert: hostInsert,
16 | remove: hostRemove,
17 | setElementText: hostSetElementText,
18 | } = options
19 |
20 | // Component
21 | const processComponent = (n1, n2, container, parentComponent, anchor) => {
22 | if (!n1) {
23 | mountComponent(n2, container, parentComponent, anchor)
24 | } else {
25 | updateComponent(n1, n2)
26 | }
27 | }
28 |
29 | const updateComponent = (n1, n2) => {
30 | const instance = n2.component = n1.component
31 | // 如果 props 完全相同 则不需要更新
32 | if (shouldUpdateComponent(n1, n2)) {
33 | instance.next = n2
34 | instance.update()
35 | } else {
36 | n2.el = n1.el
37 | n2.vnode = n2
38 | }
39 | }
40 |
41 | const mountComponent = (initinalVNode, container, parentComponent, anchor) => {
42 | const instance = initinalVNode.component = createComponentInstance(initinalVNode, parentComponent)
43 |
44 | setupComponent(instance)
45 | setupRenderEffect(instance, initinalVNode, container, anchor)
46 | }
47 |
48 | const mountChildren = (children, container, parentComponent, anchor) => {
49 | // 遍历 children 拿到节点 再次调用patch
50 | children.map(childrenItem => {
51 | patch(null, childrenItem, container, parentComponent, anchor)
52 | })
53 | }
54 |
55 | // Element
56 | const processElement = (n1, n2, container, parentComponent, anchor) => {
57 | if (!n1) {
58 | mountElement(n2, container, parentComponent, anchor)
59 | } else {
60 | patchElement(n1, n2, parentComponent, anchor)
61 | }
62 | }
63 |
64 | const patchElement = (n1, n2, parentComponent, anchor) => {
65 | // console.log('patchElement')
66 | // console.log('n1', n1)
67 | // console.log('n2', n2)
68 | // props 修改 有以下几种情况:
69 | // 1.之前属性的值和现在的值不一样了 --> 修改
70 | // 2.之前属性的值变成 undefined 或者 null --> 删除
71 | // 3.之前属性的值 现在没有了 --> 删除
72 |
73 | const oldProps = n1.props || EMPTY_OBJ
74 | const newProps = n2.props || EMPTY_OBJ
75 | // 当 第二次 patchElement 时 第一次的 n2 应该当作 第二次的 n1 去使用
76 | // 但是 n2 上并不存在 el 所以此处应当赋值以便于第二次调用。
77 | const el = n2.el = n1.el
78 | patchProps(el, oldProps, newProps)
79 |
80 | // children 修改有以下几种情况
81 | // text --> text
82 | // text --> array
83 | // array --> array
84 | // array --> text
85 | patchChildren(n1, n2, el, parentComponent, anchor)
86 | }
87 |
88 | const patchProps = (el, oldProps, newProps) => {
89 | if (oldProps !== newProps) {
90 | for (const key in newProps) {
91 | const prevProp = oldProps[key]
92 | const nextProp = newProps[key]
93 | if (prevProp !== nextProp) {
94 | // 不相等的时候更新
95 | hostPatchProp(el, key, prevProp, nextProp)
96 | }
97 | }
98 |
99 | // 不等与空对象 才会去对比
100 | // 不能直接 rops !== {} 这个相当于创建了一个新的内存地址 所以这个判断一定是 true
101 | if (oldProps !== EMPTY_OBJ) {
102 | // 如果新设置的props 在原来的 props中不存在 则直接删除掉。
103 | for (const key in oldProps) {
104 | const prevProp = oldProps[key]
105 | if (!(key in newProps )) {
106 | hostPatchProp(el, key, prevProp, null)
107 | }
108 | }
109 | }
110 | }
111 | }
112 |
113 | const patchChildren = (n1, n2, container, parentComponent, anchor) => {
114 | const prevShapeFlag = n1.shapeFlag
115 | const nextShapeFlag = n2.shapeFlag
116 | const prevChildren = n1.children
117 | const nextChildren = n2.children
118 |
119 | if (nextShapeFlag & ShapeFlags.TEXT_CHILDREN) {
120 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
121 | // 老的是 array & 新的是 text
122 | // 1.把老的 children 清空
123 | unmountChildren(n1.children)
124 | // 2.设置 text
125 | // hostSetElementText(container, nextChildren)
126 | }
127 | // 老的是 text & 新的是 text
128 | if (prevChildren !== nextChildren) {
129 | hostSetElementText(container, nextChildren)
130 | }
131 | } else {
132 | // 老的是 array & 新的是 text
133 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
134 | // 1.把老的 text 清空
135 | hostSetElementText(container, '')
136 | mountChildren(nextChildren, container, parentComponent, anchor)
137 | } else {
138 | // 老的是 array 新的也是 array
139 | patchKeyedChildren(prevChildren, nextChildren, container, parentComponent, anchor)
140 | }
141 | }
142 | }
143 |
144 | const patchKeyedChildren = (c1, c2, container, parentComponent, parentAnchor) => {
145 | let i = 0
146 | let l2 = c2.length
147 | let e1 = c1.length - 1
148 | let e2 = l2 - 1
149 | // i 标识双端对比的相同部分的下标
150 | // e1 e2 分别表示 原数据 和现数据 末尾端 的下标
151 |
152 | // ------> 左侧对比
153 | while (i <= e1 && i <= e2) {
154 | const n1 = c1[i]
155 | const n2 = c2[i]
156 | if (isSameVNodeType(n1, n2)) {
157 | patch(n1, n2, container, parentComponent, parentAnchor)
158 | } else {
159 | break
160 | }
161 | // 相等的时候 每次移动指针 i
162 | i++
163 | }
164 |
165 | // ------> 右侧对比
166 | while (i <= e1 && i <= e2) {
167 | const n1 = c1[e1]
168 | const n2 = c2[e2]
169 | if (isSameVNodeType(n1, n2)) {
170 | patch(n1, n2, container, parentComponent, parentAnchor)
171 | } else {
172 | break
173 | }
174 | e1--
175 | e2--
176 | }
177 |
178 | if (i > e1) {
179 | // 新的比老的多 需要创建
180 | // 左侧右侧 都有效果
181 | if (i <= e2) {
182 | const nextPos = e2 + 1
183 | // const anchor = e2 + 1 >= l2 ? null : c2[nextPos].el
184 | const anchor = e2 + 1 < l2 ? c2[nextPos].el : null
185 | while (i <= e2) {
186 | patch(null, c2[i], container, parentComponent, anchor)
187 | // 一定记得移动 指针 否则会死循环。
188 | i++
189 | }
190 | }
191 | } else if (i > e2) {
192 | // 新的比老的少 需要删除
193 | // 左侧右侧 都有效果
194 | while (i <= e1) {
195 | // remove
196 | hostRemove(c1[i].el)
197 | i++
198 | }
199 | } else {
200 | // 乱序部分 中间对比
201 | let s1 = i
202 | let s2 = i
203 | // a,b,(c,e,d),f,g
204 | // a,b,(e,c),f,g
205 | // 设定一个值来判断是不是所有的 新数据 中的 数据都比较完了,用来剔除就数据比新数据多,即(去掉这里的 d)。
206 |
207 | // 此处是索引需要 +1
208 | const toBePatched = e2 - s2 + 1
209 | let patched = 0
210 | // 是否需要移动
211 | let moved = false
212 | let maxNewIndexSoFar = 0
213 | // 建立一个 key 映射表
214 | const keyToNewIndexMap = new Map()
215 | // 最长递增子序列
216 | const newIndexToOldIndexMap = new Array(toBePatched)
217 | for (let i = 0; i < toBePatched; i++) {
218 | newIndexToOldIndexMap[i] = 0
219 | }
220 |
221 | for (let i = s2; i <= e2; i++) {
222 | const nextChild = c2[i]
223 | keyToNewIndexMap.set(nextChild.key, i)
224 | }
225 |
226 | let newIndex
227 | for (let i = s1; i <= e1; i++) {
228 | const prevChild = c1[i]
229 | // 如果 所有新的不同节点都已经 patch 完毕
230 | if (patched >= toBePatched) {
231 | hostRemove(prevChild.el)
232 | continue
233 | }
234 |
235 | // 如果用户设置了 key 值
236 | if (prevChild.key != null) {
237 | newIndex = keyToNewIndexMap.get(prevChild.key)
238 | } else {
239 | for (let j = s2; j <= e2; j++) {
240 | if (isSameVNodeType(prevChild, c2[j])) {
241 | // 如果存在表示找到的原来的节点对应的映射,直接跳出
242 | newIndex = j
243 | break
244 | }
245 | }
246 | }
247 | //
248 | if (newIndex === undefined) {
249 | hostRemove(prevChild.el)
250 | } else {
251 | if (newIndex >= maxNewIndexSoFar) {
252 | maxNewIndexSoFar = newIndex
253 | } else {
254 | moved = true
255 | }
256 | newIndexToOldIndexMap[newIndex - s2] = i + 1
257 | patch(prevChild, c2[newIndex], container, parentComponent, null)
258 | patched++
259 | }
260 | }
261 |
262 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []
263 | let j = increasingNewIndexSequence.length - 1
264 |
265 | for (let i = toBePatched - 1; i >= 0; i--) {
266 | const nextIndex = i + s2
267 | const nextChild = c2[nextIndex]
268 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null
269 |
270 | if (newIndexToOldIndexMap[i] === 0) {
271 | patch(null, nextChild, container, parentComponent, anchor)
272 | } else if (moved) {
273 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
274 | console.log('移动位置')
275 | hostInsert(nextChild.el, container, anchor)
276 | } else {
277 | j--
278 | }
279 | }
280 | }
281 | }
282 | }
283 |
284 | const isSameVNodeType = (n1, n2) => {
285 | // type key 两个东西去判断是不是想等
286 | return n1.type === n2.type && n1.key === n2.key
287 | }
288 |
289 | const unmountChildren = (children) => {
290 | children.map(item => {
291 | const el = item.el
292 | // remove
293 | hostRemove(el)
294 | })
295 | }
296 |
297 | const mountElement = (vnode, container, parentComponent, anchor) => {
298 | // vnode --> element 类型的 --> div
299 | const el = vnode.el = hostCreateElement(vnode.type)
300 |
301 | // children 可能是 string array
302 | const { children, props, shapeFlag } = vnode
303 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
304 | // children
305 | el.textContent = children
306 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
307 | mountChildren(vnode.children, el, parentComponent, anchor)
308 | }
309 |
310 | // props
311 | for (const key in props) {
312 | let val
313 | if (Array.isArray(props[key])) {
314 | val = props[key].join(' ')
315 | } else {
316 | val = props[key]
317 | }
318 | hostPatchProp(el, key, null, val)
319 | }
320 |
321 | // 挂载在页面上
322 | hostInsert(el, container, anchor)
323 | }
324 |
325 | // Fragment
326 | const processFragment = (n1, n2, container, parentComponent, anchor) => {
327 | mountChildren(n2.children, container, parentComponent, anchor)
328 | }
329 |
330 | // Text
331 | const processText = (n1, n2, container) => {
332 | const { children } = n2
333 | const textNode = n2.el = document.createTextNode(children)
334 | container.append(textNode)
335 | }
336 |
337 | const render = (vnode, container) => {
338 | patch(null, vnode, container, null, null)
339 | }
340 |
341 | const patch = (n1, n2, container, parentComponent, anchor) => {
342 | // 判断一下 vnode 类型
343 | // 调用对应的方法去处理
344 | const { type, shapeFlag } = n2
345 | switch (type) {
346 | case Fragment:
347 | processFragment(n1, n2, container, parentComponent, anchor)
348 | break
349 | case Text:
350 | processText(n1, n2, container)
351 | break
352 | default:
353 | if (shapeFlag & ShapeFlags.ELEMENT) {
354 | processElement(n1, n2, container, parentComponent, anchor)
355 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
356 | processComponent(n1, n2, container, parentComponent, anchor)
357 | }
358 | break
359 | }
360 | }
361 |
362 | const setupRenderEffect = (instance, initinalVNode, container, anchor) => {
363 | // 利用 effect 做依赖收集
364 | instance.update = effect(() => {
365 | if (!instance.isMounted) {
366 | const { proxy } = instance
367 | // 这里传递的 第一个 proxy 是组件实例this,第二个 proxy 是当做组件的参数,方便 render 函数中调用 ctx.xxx
368 | // 这里是方便组件内部调用 获取 ctx 的操作
369 | // 后续可能还有很多参数 (_cache, $props, $setup, $data, $options)
370 | const subTree = instance.subTree = instance.render.call(proxy, proxy)
371 |
372 | // vnode --> patch
373 | // vnode --> element --> mount
374 | patch(null, subTree, container, instance, anchor)
375 |
376 | initinalVNode.el = subTree.el
377 | instance.isMounted = true
378 | } else {
379 | // 这里 在更新的时候还需要更新组件的 props
380 | // 需要 更新完成以后的 vnode
381 |
382 | // vnode: 更新之前的 虚拟节点
383 | // next: 下次要更新的 虚拟节点
384 | const { next, vnode } = instance
385 |
386 | if (next) {
387 | // 更新 el
388 | next.el = vnode.el
389 |
390 | updateComponentPreRender(instance, next)
391 | }
392 |
393 | const { proxy } = instance
394 | const prevSubTree = instance.subTree
395 | // 这里传递的 第一个 proxy 是组件实例this,第二个 proxy 是当做组件的参数,方便 render 函数中调用 ctx.xxx
396 | // 这里是方便组件内部调用 获取 ctx 的操作
397 | // 后续可能还有很多参数 (_cache, $props, $setup, $data, $options)
398 | const subTree = instance.subTree = instance.render.call(proxy, proxy)
399 |
400 | // vnode --> patch
401 | // vnode --> element --> mount
402 | patch(prevSubTree, subTree, container, instance, anchor)
403 |
404 | initinalVNode.el = subTree.el
405 | instance.isMounted = true
406 | }
407 | }, {
408 | // 优化 不需要每次更新数据都去执行 effect 收集的依赖。
409 | scheduler: () => {
410 | // 建立一个 微任务去 等待数据完成在执行回调。
411 | queueJobs(instance.update)
412 | }
413 | })
414 | }
415 |
416 | return {
417 | createApp: createAppAPI(render)
418 | }
419 | }
420 |
421 | const updateComponentPreRender = (instance, nextVNode) => {
422 | instance.vnode = nextVNode
423 | instance.next = null
424 | instance.props = nextVNode.props
425 | }
426 |
427 | export {
428 | createRender
429 | }
430 |
--------------------------------------------------------------------------------
/src/runtime-core/scheduler.ts:
--------------------------------------------------------------------------------
1 | const queue: any = []
2 |
3 | // 用来标识是否需要 执行 创建 promise
4 | let isFlushPending = false
5 |
6 | const queueJobs = (job: any) => {
7 | // 如果队列中没有 该 任务
8 | if (!queue.includes(job)) {
9 | queue.push(job)
10 | }
11 | // 创建一个 promise 去接收
12 | queueFlush()
13 | }
14 |
15 | const queueFlush = () => {
16 | if (isFlushPending) {
17 | return
18 | }
19 | isFlushPending = true
20 | nextTick(() => {
21 | flushJobs()
22 | })
23 | }
24 |
25 | const flushJobs =() => {
26 | // 执行完 微任务以后在将开关打开。
27 | isFlushPending = false
28 | let job
29 | while (job = queue.shift()) {
30 | job && job()
31 | }
32 | }
33 |
34 | const p = Promise.resolve()
35 |
36 | const nextTick = (fn) => {
37 | // 如果用户传入 nextTick 的回调函数,则执行
38 | // 否则,执行 Promise.resolve 的回调函数
39 | return fn ? p.then(fn) : p
40 | }
41 |
42 | export {
43 | queueJobs,
44 | nextTick
45 | }
46 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { getShapeFlag } from '../shared/index'
2 | import { ShapeFlags } from '../shared/ShapeFlags'
3 |
4 | const Fragment = Symbol('Fragment')
5 | const Text = Symbol('Text')
6 |
7 | const createVNode = (type, props?, children?) => {
8 | const vnode = {
9 | type,
10 | props,
11 | children,
12 | component: null,
13 | key: props && props.key,
14 | shapeFlag: getShapeFlag(type),
15 | el: null
16 | }
17 |
18 | if (typeof children === 'string') {
19 | // 元素节点
20 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN
21 | } else if (Array.isArray(children)) {
22 | // 组件节点
23 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN
24 | }
25 |
26 | // 判断是否需要 slots 处理
27 | // 首先是必须是一个组件类型,其次 children 必须是一个对象
28 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
29 | if (typeof children === 'object') {
30 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN
31 | }
32 | }
33 |
34 | return vnode
35 | }
36 |
37 | const createTextVNode = (text: string) => {
38 | return createVNode(Text, {}, text)
39 | }
40 |
41 | export {
42 | createVNode as createElementVNode
43 | }
44 |
45 | export {
46 | createVNode,
47 | createTextVNode,
48 | Fragment,
49 | Text
50 | }
51 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import { createRender } from '../runtime-core'
2 |
3 | const createElement = (type) => {
4 | return document.createElement(type)
5 | }
6 |
7 | const patchProp = (el, key, prevVal, nextVal) => {
8 | // 实现注册 事件
9 | const isOn = key => /^on[A-Z]/.test(key)
10 | if (isOn(key)) {
11 | const eventName = key.slice(2).toLowerCase()
12 | el.addEventListener(eventName, nextVal)
13 | } else {
14 | // 如果设置值为 undefined 或者 null 的时候删除掉该属性
15 | if (nextVal == null) {
16 | el.removeAttribute(key)
17 | } else {
18 | el.setAttribute(key, nextVal)
19 | }
20 | }
21 | }
22 |
23 | const insert = (child, parent, anchor = null) => {
24 | // parent.append(child)
25 | parent.insertBefore(child, anchor)
26 | }
27 |
28 | const remove = (children) => {
29 | const parent = children.parentNode
30 | if (parent) {
31 | parent.removeChild(children)
32 | }
33 | }
34 |
35 | const setElementText = (el, text) => {
36 | el.textContent = text
37 | }
38 |
39 | const render = createRender({
40 | createElement,
41 | patchProp,
42 | insert,
43 | remove,
44 | setElementText
45 | })
46 |
47 | const createApp = (...args: [any]) => {
48 | return render.createApp(...args)
49 | }
50 |
51 | export * from '../runtime-core'
52 |
53 | export {
54 | createElement,
55 | patchProp,
56 | insert,
57 | render,
58 | createApp
59 | }
60 |
--------------------------------------------------------------------------------
/src/shared/ShapeFlags.ts:
--------------------------------------------------------------------------------
1 | const enum ShapeFlags {
2 | ELEMENT = 1, // 0001
3 | STATEFUL_COMPONENT = 1 << 1, // 0010
4 | TEXT_CHILDREN = 1 << 2, // 0100
5 | ARRAY_CHILDREN = 1 << 3, // 1000
6 | SLOT_CHILDREN = 1 << 4, // 10000
7 | }
8 |
9 | export {
10 | ShapeFlags
11 | }
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from './ShapeFlags'
2 |
3 | export * from "./toDisplayString"
4 |
5 | const extend = Object.assign
6 |
7 | // 设置一个全局空对象方便后续曲比较
8 | const EMPTY_OBJ = {}
9 |
10 | const isObject = (value) => {
11 | return value !== null && typeof value === 'object'
12 | }
13 |
14 | const isString = (value) => {
15 | return value !== null && typeof value === 'string'
16 | }
17 |
18 | const hasChanged = (val, newValue) => {
19 | return !Object.is(val, newValue)
20 | }
21 |
22 | const getShapeFlag = (type) => {
23 | return typeof type === 'string'
24 | ? ShapeFlags.ELEMENT
25 | : ShapeFlags.STATEFUL_COMPONENT
26 | }
27 |
28 | const hasOwn = (val = {}, key) => Object.prototype.hasOwnProperty.call(val, key)
29 |
30 | const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
31 |
32 | const camelize = (str: string) => {
33 | // ex: add-foo
34 | // _ 代表匹配到的值规则(-f)
35 | // targetValue 代表匹配到的值(f)
36 | return str.replace(/-(\w)/g, (_, targetValue: string) => {
37 | return targetValue ? targetValue.toUpperCase() : ''
38 | })
39 | }
40 | // add --> onAdd
41 | const toHandlerKey = (str: string) => str ? camelize('on' + capitalize(str)) : ''
42 |
43 | const getSequence = (arr) => {
44 | const p = arr.slice()
45 | const result = [0]
46 | let i, j, u, v, c
47 | const len = arr.length
48 | for (i = 0; i < len; i++) {
49 | const arrI = arr[i]
50 | if (arrI !== 0) {
51 | j = result[result.length - 1]
52 | if (arr[j] < arrI) {
53 | p[i] = j
54 | result.push(i)
55 | continue
56 | }
57 | u = 0
58 | v = result.length - 1
59 | while (u < v) {
60 | c = (u + v) >> 1
61 | if (arr[result[c]] < arrI) {
62 | u = c + 1
63 | } else {
64 | v = c
65 | }
66 | }
67 | if (arrI < arr[result[u]]) {
68 | if (u > 0) {
69 | p[i] = result[u - 1]
70 | }
71 | result[u] = i
72 | }
73 | }
74 | }
75 | u = result.length
76 | v = result[u - 1]
77 | while (u-- > 0) {
78 | result[u] = v
79 | v = p[v]
80 | }
81 | return result;
82 | }
83 |
84 | export {
85 | extend,
86 | EMPTY_OBJ,
87 | isObject,
88 | isString,
89 | hasChanged,
90 | getShapeFlag,
91 | hasOwn,
92 | camelize,
93 | toHandlerKey,
94 | getSequence
95 | }
96 |
--------------------------------------------------------------------------------
/src/shared/toDisplayString.ts:
--------------------------------------------------------------------------------
1 | const toDisplayString = (value) => {
2 | return String(value)
3 | }
4 |
5 | export {
6 | toDisplayString
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | "lib": [
16 | "dom",
17 | "es2016"
18 | ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
19 | // "jsx": "preserve", /* Specify what JSX code is generated. */
20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
25 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
28 |
29 | /* Modules */
30 | "module": "esnext", /* Specify what module code is generated. */
31 | // "rootDir": "./", /* Specify the root folder within your source files. */
32 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
33 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
34 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
35 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
36 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
37 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */
38 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
39 | // "resolveJsonModule": true, /* Enable importing .json files */
40 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
41 |
42 | /* JavaScript Support */
43 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
44 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
45 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
46 |
47 | /* Emit */
48 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
49 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
50 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
51 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
52 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
53 | // "outDir": "./", /* Specify an output folder for all emitted files. */
54 | // "removeComments": true, /* Disable emitting comments. */
55 | // "noEmit": true, /* Disable emitting files from a compilation. */
56 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
57 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
58 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
59 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
61 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
62 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
63 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
64 | // "newLine": "crlf", /* Set the newline character for emitting files. */
65 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
66 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
67 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
68 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
69 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
70 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
71 |
72 | /* Interop Constraints */
73 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
74 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
75 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
76 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
77 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
78 |
79 | /* Type Checking */
80 | "strict": true, /* Enable all strict type-checking options. */
81 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
82 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
83 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
84 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
85 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
86 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
87 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
88 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
89 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
90 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
91 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
92 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
93 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
94 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
95 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
96 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
97 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
98 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
99 |
100 | /* Completeness */
101 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
102 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
103 | }
104 | }
105 |
--------------------------------------------------------------------------------