├── .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 | ![初始化流程图](https://user-images.githubusercontent.com/12064746/138114565-3e0eecbb-7fd0-4203-bf36-5e5fd8003ce0.png) 77 | 78 | 79 | #### 关键函数调用图 80 | 81 | 82 | ![关键函数调用图2](https://camo.githubusercontent.com/adb0a362da748a9c0ec1e010764c387271a0fa0018625773dde747cbe6a7a576/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323032302f362f32322f313732646330383834306532356234323f773d3138313626683d39333426663d706e6726733d353530373232) 83 | 84 | > 可以基于函数名快速搜索到源码内容 85 | 86 | ### update 87 | 88 | #### 流程图 89 | 90 | ![image](https://user-images.githubusercontent.com/12064746/138115157-1f4fb8a2-7e60-412d-96de-12e68eb0288c.png) 91 | 92 | #### 关键函数调用图 93 | 94 | ![image](https://user-images.githubusercontent.com/12064746/138114969-9139e4af-b2df-41b2-a5d9-069d8b41903c.png) 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('= 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(' { 1296 | // 1.解析 tag 1297 | //
1298 | //
1299 | // 匹配以 < 开头或者以 ) 直接不用返回 后面的东西了。 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('= 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(' { 1292 | // 1.解析 tag 1293 | //
1294 | //
1295 | // 匹配以 < 开头或者以 ) 直接不用返回 后面的东西了。 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('= 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(' { 140 | // 1.解析 tag 141 | //
142 | //
143 | // 匹配以 < 开头或者以 ) 直接不用返回 后面的东西了。 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('

hi

{{message}}
'); 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 | --------------------------------------------------------------------------------