├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── README.md ├── babel.config.js ├── example ├── apiInject │ ├── App.js │ ├── index.html │ └── main.js ├── componentEmit │ ├── App.js │ ├── foo.js │ ├── index.html │ └── main.js ├── componentSlot │ ├── App.js │ ├── Foo.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 ├── patchChildren │ ├── App.js │ ├── ArrayToArray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js └── update │ ├── App.js │ ├── index.html │ └── main.js ├── package.json ├── rollup.config.js ├── src ├── index.ts ├── reactivity │ ├── baseHandlers.ts │ ├── computed.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ └── tests │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentPubilcInstance.ts │ ├── componentSlots.ts │ ├── createApp.ts │ ├── h.ts │ ├── helper │ │ └── renderSlots.ts │ ├── index.ts │ ├── renderer.ts │ └── vnode.ts ├── runtime-dom │ └── index.ts └── shared │ ├── index.ts │ └── shapeFlags.ts ├── tsconfig.json └── yarn.lock /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | jobs: 4 | publish: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | - uses: actions/setup-node@v1 9 | with: 10 | node-version: 10 11 | - run: npm install 12 | - run: npm build 13 | - uses: JS-DevTools/npm-publish@v1 14 | with: 15 | token: ${{ secrets.MINI_VUE3 }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS Desktop Services Store 2 | .DS_Store 3 | */.DS_Store 4 | 5 | node_modules 6 | /dist 7 | /lib 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qc-z/mini-vue3/1be580aa52128b40dfbf671fb7d4e3c0cd459638/.npmrc -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## 1.1.0 (2022-03-13) 6 | 7 | 8 | ### Features 9 | 10 | * workflow测试 ([66d7efb](https://github.com/qc-z/mini-vue3/commit/66d7efbbb58f15278873548685a69817b3e59e34)) 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## mini-vue 笔记 2 | 3 | 1. [笔记汇总](https://github.com/qc-z/study-note) 4 | 5 | 2. vue3 运行 流程 6 | 7 | ![vue3运行流程图](https://raw.githubusercontent.com/qc-z/study-note/master/vue3source/assets/yuque_diagram.jpg) 8 | 9 | 3. 依赖收集关系 10 | 11 | ![依赖收集](https://raw.githubusercontent.com/qc-z/study-note/master/vue3source/assets/targetMap.png) 12 | 13 | 依赖收集运行 14 | ![依赖收集运行](https://raw.githubusercontent.com/qc-z/study-note/master/vue3source/assets/targetMapRun.jpg) 15 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'] 3 | } 4 | -------------------------------------------------------------------------------- /example/apiInject/App.js: -------------------------------------------------------------------------------- 1 | import { h, provide, inject } from '../../lib/mini-vue.esm.js' 2 | 3 | const Provide = { 4 | name: 'Provide', 5 | render() { 6 | return h('div', {}, [h('p', {}, "Provide"), h(ProvideTwo)]) 7 | }, 8 | setup() { 9 | provide('foo', "fooVal") 10 | provide('bar', "barVal") 11 | } 12 | } 13 | const ProvideTwo = { 14 | name: 'ProvideTwo', 15 | render() { 16 | return h('div', {}, [h('p', {}, `ProvideTwo foo - ${this.foo}`), h(Consumer)]) 17 | }, 18 | setup() { 19 | provide('foo', "fooTwo") 20 | const foo = inject('foo') 21 | return { 22 | foo 23 | } 24 | } 25 | } 26 | 27 | const Consumer = { 28 | name: 'Consumer', 29 | render() { 30 | return h('div', {}, `Consumer: ${this.foo} - ${this.bar} - ${this.baz}`) 31 | }, 32 | setup() { 33 | const foo = inject('foo') 34 | const bar = inject('bar') 35 | const baz = inject('baz', () => 'bazDefault') 36 | return { 37 | foo, 38 | bar, 39 | baz 40 | } 41 | } 42 | } 43 | 44 | export const App = { 45 | name: 'provide', 46 | render() { 47 | return h('div', {}, [h('div', {}, [h("p", {}, "apiInject"), h(Provide)])]) 48 | }, 49 | setup() { 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/apiInject/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue.esm.js' 2 | import { App } from './App.js' 3 | // 先用container 后面在写兼容字符串 4 | const rootContainer = document.querySelector('#app') 5 | // vue3语法 6 | createApp(App).mount(rootContainer) 7 | -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue.esm.js' 2 | import { Foo } from './foo.js' 3 | 4 | export const App = { 5 | name: 'App', 6 | render() { 7 | return h( 8 | 'div', 9 | {}, 10 | [h('div', {}, 'App'), h(Foo, { 11 | onAdd(a, b) { 12 | console.log('onAdd', a) 13 | console.log('onAdd', b) 14 | }, 15 | onAddFoo(a) { 16 | console.log('onAddFoo', a) 17 | } 18 | })]) 19 | }, 20 | setup() { 21 | return { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/componentEmit/foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue.esm.js' 2 | 3 | export const Foo = { 4 | setup(props, { emit }) { 5 | const emitAdd = () => { 6 | emit('add', 1, 2) 7 | emit('add-foo', 1) 8 | } 9 | return { 10 | emitAdd 11 | } 12 | }, 13 | render() { 14 | const btn = h('button', { 15 | onClick: this.emitAdd 16 | }, 17 | 'emitAdd1' 18 | ) 19 | const foo = h('p', {}, 'foo:') 20 | return h('div', {}, [foo, btn]) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue.esm.js' 2 | import { App } from './App.js' 3 | // 先用container 后面在写兼容字符串 4 | const rootContainer = document.querySelector('#app') 5 | // vue3语法 6 | createApp(App).mount(rootContainer) 7 | -------------------------------------------------------------------------------- /example/componentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { createTextVnode, h } from '../../lib/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 | // 1 数组 9 | // const foo = h(Foo, {}, [h('p ', {}, '123'), h('p', {}, '1234')]) 10 | // 2 单个值 11 | // const foo = h(Foo, {}, h('p', {}, '123')) 12 | // 3 指定位置 13 | const foo = h(Foo, {}, 14 | { 15 | header: ({ age }) => [h('p', {}, 'header' + age), createTextVnode('hello')], 16 | footer: ({ name }) => h('p', {}, 'footer' + name) 17 | } 18 | ) 19 | return h('div', {}, [app, foo]) 20 | }, 21 | setup() { 22 | return {} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/componentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from '../../lib/mini-vue.esm.js' 2 | export const Foo = { 3 | name: 'foo', 4 | setup() { 5 | 6 | }, 7 | render() { 8 | const foo = h('h1', {}, 'foo') 9 | return h('div', {}, 10 | [ 11 | renderSlots(this.$slots, 'header', { 12 | age: 28 13 | }), 14 | foo, 15 | renderSlots(this.$slots, 'footer', { 16 | name: 'xiaoc' 17 | }) 18 | ] 19 | ) 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /example/componentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/componentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue.esm.js' 2 | import { App } from './App.js' 3 | // 先用container 后面在写兼容字符串 4 | const rootContainer = document.querySelector('#app') 5 | // vue3语法 6 | createApp(App).mount(rootContainer) 7 | -------------------------------------------------------------------------------- /example/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from '../../lib/mini-vue.esm.js' 2 | import { Foo } from './foo.js' 3 | 4 | window.self = null 5 | export const App = { 6 | name: 'App', 7 | render() { 8 | return h('div', {}, [h('p', {}, 'currentinstance demo'), h(Foo)]) 9 | }, 10 | setup() { 11 | const instance = getCurrentInstance() 12 | console.log('App instance', instance) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/currentInstance/foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from '../../lib/mini-vue.esm.js' 2 | 3 | export const Foo = { 4 | name: 'foo', 5 | setup(props) { 6 | const instance = getCurrentInstance() 7 | console.log('Foo instance', 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 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue.esm.js' 2 | import { App } from './App.js' 3 | // 先用container 后面在写兼容字符串 4 | const rootContainer = document.querySelector('#app') 5 | // vue3语法 6 | createApp(App).mount(rootContainer) 7 | -------------------------------------------------------------------------------- /example/customRender/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue.esm.js' 2 | 3 | export const App = { 4 | name: 'customRander', 5 | render() { 6 | return h('rect', { 7 | x: this.x, 8 | y: this.y, 9 | }) 10 | }, 11 | setup() { 12 | return { 13 | x: 200, 14 | y: 100 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/customRender/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/customRender/main.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createRenderer } from '../../lib/mini-vue.esm.js' 3 | 4 | import { App } from './App.js' 5 | const game = new PIXI.Application({ 6 | width: 500, 7 | height: 500, 8 | }) 9 | const rootContainer = document.querySelector('#app') 10 | rootContainer.append(game.view) 11 | const renderer = createRenderer({ 12 | createElement(type) { 13 | if (type === "rect") { 14 | const rect = new PIXI.Graphics() 15 | rect.beginFill(0xff0000) 16 | rect.drawRect(0, 0, 100, 100) 17 | rect.endFill() 18 | return rect 19 | } 20 | }, 21 | patchProp(el, key, val) { 22 | el[key] = val 23 | }, 24 | insert(el, parent) { 25 | parent.addChild(el) 26 | }, 27 | }) 28 | 29 | renderer.createApp(App).mount(game.stage) 30 | -------------------------------------------------------------------------------- /example/helloword/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue.esm.js' 2 | import { Foo } from './foo.js' 3 | 4 | window.self = null 5 | export const App = { 6 | name: 'App', 7 | // 先实现render函数,因为template有点复杂 8 | render() { 9 | return h( 10 | 'div', 11 | { 12 | class: ['red', 'green'], 13 | id: 'root', 14 | onClick: (val) => { 15 | console.log('click', val) 16 | }, 17 | onMousedown: (val) => { 18 | console.log('onMousedown', val) 19 | }, 20 | onMouseover: (val) => { 21 | console.log('onMouseover', val) 22 | } 23 | }, 24 | [h('div', {}, 'hello-' + this.msg), h(Foo, { count: 1 })]) 25 | }, 26 | setup() { 27 | // componsition api 28 | return { 29 | msg: 'mini-vue' 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/helloword/foo.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/mini-vue.esm.js' 2 | 3 | export const Foo = { 4 | setup(props) { 5 | console.log('Foo props', props) 6 | props.count++ 7 | // 1在setup接收props 8 | // 在render通过this获取值 9 | // props必须是shallreadonly 10 | }, 11 | render() { 12 | return h('div', {}, 'foo:' + this.count) 13 | } 14 | } -------------------------------------------------------------------------------- /example/helloword/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/helloword/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue.esm.js' 2 | import { App } from './App.js' 3 | // 先用container 后面在写兼容字符串 4 | const rootContainer = document.querySelector('#app') 5 | // vue3语法 6 | createApp(App).mount(rootContainer) 7 | -------------------------------------------------------------------------------- /example/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/mini-vue.esm.js" 2 | import TextToText from "./TextToText.js" 3 | import TextToArray from "./TextToArray.js" 4 | import ArrayToArray from "./ArrayToArray.js" 5 | import ArrayToText from "./ArrayToText.js" 6 | 7 | export const App = { 8 | name: "App", 9 | setup() { 10 | 11 | }, 12 | render() { 13 | return h("div", { tId: 1 }, 14 | [ 15 | h('span', {}, '主页'), 16 | // h(TextToText), 17 | // h(TextToArray), 18 | h(ArrayToArray), 19 | // h(ArrayToText) 20 | ] 21 | ) 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { h, ref } from "../../lib/mini-vue.esm.js" 3 | 4 | const prevChildren = [h("div", {}, "B"), h("div", {}, "A")] 5 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")] 6 | 7 | export default { 8 | name: "App", 9 | setup() { 10 | const isChange = ref(false) 11 | window.isChange = isChange 12 | return { 13 | isChange 14 | } 15 | }, 16 | render() { 17 | const self = this 18 | 19 | return self.isChange 20 | ? h("div", {}, prevChildren) 21 | : h("div", {}, nextChildren) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { h, ref } from "../../lib/mini-vue.esm.js" 3 | 4 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")] 5 | const nextChildren = "newChild" 6 | 7 | export default { 8 | name: "App", 9 | setup() { 10 | const isChange = ref(false) 11 | window.isChange = isChange 12 | return { 13 | isChange 14 | } 15 | }, 16 | render() { 17 | const self = this 18 | 19 | return self.isChange 20 | ? h("div", {}, nextChildren) 21 | : h("div", {}, prevChildren) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/patchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { h, ref } from "../../lib/mini-vue.esm.js" 3 | 4 | const prevChildren = "oldChild" 5 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")] 6 | 7 | export default { 8 | name: "App", 9 | setup() { 10 | const isChange = ref(false) 11 | window.isChange = isChange 12 | return { 13 | isChange 14 | } 15 | }, 16 | render() { 17 | const self = this 18 | 19 | return self.isChange 20 | ? h("div", {}, nextChildren) 21 | : h("div", {}, prevChildren) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/patchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { h, ref } from "../../lib/mini-vue.esm.js" 3 | 4 | const prevChildren = "oldChild" 5 | const nextChildren = "newChild" 6 | 7 | export default { 8 | name: "App", 9 | setup() { 10 | const isChange = ref(false) 11 | window.isChange = isChange 12 | return { 13 | isChange 14 | } 15 | }, 16 | render() { 17 | const self = this 18 | 19 | return self.isChange 20 | ? h("div", {}, nextChildren) 21 | : h("div", {}, prevChildren) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/patchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 对比children 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/patchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue.esm.js' 2 | import { App } from './App.js' 3 | // 先用container 后面在写兼容字符串 4 | const rootContainer = document.querySelector('#app') 5 | // vue3语法 6 | createApp(App).mount(rootContainer) 7 | -------------------------------------------------------------------------------- /example/update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/mini-vue.esm.js" 2 | 3 | export const App = { 4 | name: "App", 5 | 6 | setup() { 7 | const count = ref(0) 8 | const onClick = () => { 9 | count.value++ 10 | } 11 | 12 | const props = ref({ 13 | foo: 'foo', 14 | bar: 'bar' 15 | }) 16 | //props修改 17 | const onChangePropsDemo1 = () => { 18 | props.value.foo = 'new-foo' 19 | } 20 | //props值变成了 undefined - 删除 21 | const onChangePropsDemo2 = () => { 22 | props.value.foo = undefined 23 | 24 | } 25 | //props删除 26 | const onChangePropsDemo3 = () => { 27 | props.value = { 28 | foo: "foo", 29 | } 30 | } 31 | return { 32 | count, 33 | onClick, 34 | props, 35 | onChangePropsDemo1, 36 | onChangePropsDemo2, 37 | onChangePropsDemo3 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 | "props修改" 62 | ), 63 | h( 64 | "button", 65 | { 66 | onClick: this.onChangePropsDemo2, 67 | }, 68 | "changeProps - 值变成了 undefined - 删除" 69 | ), 70 | h( 71 | "button", 72 | { 73 | onClick: this.onChangePropsDemo3, 74 | }, 75 | "changeProps - key 在新的里面没有了 - 删除" 76 | ), 77 | ] 78 | ) 79 | }, 80 | } 81 | -------------------------------------------------------------------------------- /example/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 更新element 8 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue.esm.js' 2 | import { App } from './App.js' 3 | // 先用container 后面在写兼容字符串 4 | const rootContainer = document.querySelector('#app') 5 | // vue3语法 6 | createApp(App).mount(rootContainer) 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "small-vue3", 3 | "version": "1.1.1", 4 | "description": "实现最简单的vue3模型", 5 | "main": "lib/mini-vue.cjs.js", 6 | "module": "lib/mini-vue.esm.js", 7 | "author": "jujuc", 8 | "license": "MIT", 9 | "bugs": { 10 | "url": "https://github.com/qc-z/mini-vue3/issues" 11 | }, 12 | "homepage": "https://github.com/qc-z/mini-vue3", 13 | "scripts": { 14 | "test": "jest --watch", 15 | "build": "rollup -c rollup.config.js --watch" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.15.5", 19 | "@babel/preset-env": "^7.15.6", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@rollup/plugin-typescript": "^8.3.0", 22 | "@types/jest": "^27.0.1", 23 | "babel-jest": "^27.1.1", 24 | "jest": "^27.1.1", 25 | "rollup": "^2.61.1", 26 | "tslib": "^2.3.1", 27 | "typescript": "^4.5.4" 28 | }, 29 | "dependencies": {} 30 | } 31 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' 2 | import typescript from '@rollup/plugin-typescript' 3 | export default { 4 | input: 'src/index.ts', 5 | output: [ 6 | // 1 cjs ->commomjs 7 | { 8 | format: 'cjs', 9 | file: pkg.main 10 | }, 11 | // 2 esm -> es6 12 | { 13 | format: 'esm', 14 | file: pkg.module 15 | } 16 | ], 17 | plugins: [typescript()] 18 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './runtime-dom' 2 | export * from './reactivity' 3 | -------------------------------------------------------------------------------- /src/reactivity/baseHandlers.ts: -------------------------------------------------------------------------------- 1 | import { extend, isObject } from '../shared' 2 | import { track, trigger } from './effect' 3 | import { reactive, readonly, ReactiveFlags } from './reactive' 4 | // 第一次进来就创建,不用每次都创建,缓存 5 | const get = createGetter() 6 | const set = createSetter() 7 | const readonlyGet = createGetter(true) 8 | const shallowReadonlyGet = createGetter(true, true) 9 | 10 | /** 11 | * @description: 高阶函数,创建get 12 | * @param {*} isReadonly 13 | * @return {*} 14 | */ 15 | function createGetter(isReadonly = false, shallow = false) { 16 | return function get(target, key) { 17 | // 如果key是IS_REACTIVE直接返回 18 | if (key === ReactiveFlags.IS_REACTIVE) { 19 | return !isReadonly 20 | // 如果key是IS_READONLY直接返回 21 | } else if (key === ReactiveFlags.IS_READONLY) { 22 | return isReadonly 23 | } 24 | const res = Reflect.get(target, key) 25 | 26 | if (shallow) { 27 | return res 28 | } 29 | // 看看res是不是object,递归 30 | if (isObject(res)) { 31 | return isReadonly ? readonly(res) : reactive(res) 32 | } 33 | if (!isReadonly) { 34 | // 只有非readonly才收集依赖 35 | track(target, key) 36 | } 37 | return res 38 | } 39 | } 40 | 41 | /** 42 | * @description: 高阶函数,创建set 43 | * @param {*} 44 | * @return {*} 45 | */ 46 | function createSetter() { 47 | return function set(target, key, value) { 48 | const res = Reflect.set(target, key, value) 49 | // TODO触发依赖 50 | trigger(target, key) 51 | return res 52 | } 53 | } 54 | /** 55 | * @description: reactive的handlers 56 | * @param {*} 57 | * @return {*} 58 | */ 59 | export const mutableHanders = { 60 | get, 61 | set 62 | } 63 | /** 64 | * @description: readonly的Handers 65 | * @param {*} 66 | * @return {*} 67 | */ 68 | export const readonlyHandlers = { 69 | get: readonlyGet, 70 | set(target, key, value) { 71 | // 不可以set 72 | console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target) 73 | return true 74 | } 75 | } 76 | /** 77 | * @description: shallowReadonlyHandlers 78 | * @param {*} 79 | * @return {*} 80 | */ 81 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 82 | get: shallowReadonlyGet 83 | }) 84 | -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from './effect' 2 | 3 | class computedRefImpl { 4 | private _getter: any 5 | private _dirty: boolean = true 6 | private _value: any 7 | private _effect: ReactiveEffect 8 | constructor(getter) { 9 | this._getter = getter 10 | // 此处不运行run方法,只运行scheduler 11 | // 只有在get的时候判断this._dirty状态才去调用run方法 12 | this._effect = new ReactiveEffect(getter, () => { 13 | if (!this._dirty) { 14 | this._dirty = true 15 | } 16 | }) 17 | } 18 | get value() { 19 | // 什么时候打开_dirty,锁上 20 | if (this._dirty) { 21 | this._dirty = false 22 | this._value = this._effect.run() 23 | } 24 | return this._value 25 | } 26 | } 27 | export function computed(getter) { 28 | return new computedRefImpl(getter) 29 | } 30 | -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '../shared' 2 | let activeEffect 3 | let shouldTrack 4 | const targetMap = new Map() 5 | 6 | export class ReactiveEffect { 7 | private _fn: any 8 | deps = [] 9 | active = true 10 | onStop?: () => void 11 | public scheduler: Function | undefined 12 | // public外部可以引用 ?可选 13 | constructor(fn, scheduler?: Function) { 14 | this._fn = fn 15 | this.scheduler = scheduler 16 | } 17 | run() { 18 | // 调用stop后,active为false,不收集依赖,只需要调用一下fn即可 19 | if (!this.active) { 20 | return this._fn() 21 | } 22 | // 只有run之后才有activeEffect,dep才能收集进去 23 | activeEffect = this 24 | // 把开关打开,之后调用fn,触发get方法,进行依赖收集 25 | shouldTrack = true 26 | const result = this._fn() 27 | // 把shouldTrack变为false,重置一下状态,只有active为true才打开shouldTrack,进行依赖收集 28 | shouldTrack = false 29 | return result 30 | } 31 | stop() { 32 | // 防止多次stop,一个实例调用一次stop就应该移除掉这个dep 33 | if (this.active) { 34 | cleanupEffect(this) 35 | if (this.onStop) { 36 | this.onStop() 37 | } 38 | this.active = false 39 | } 40 | } 41 | } 42 | /** 43 | * @description: 调用stop之后,清除deps收集的effext 44 | * @param {*} effect 45 | * @return {*} 46 | */ 47 | function cleanupEffect(effect) { 48 | effect.deps.forEach((dep: any) => { 49 | // effect 是set 50 | dep.delete(effect) 51 | }) 52 | // MARK: 清空 53 | effect.deps.length = 0 54 | } 55 | 56 | export function effect(fn, options: any = {}) { 57 | const _effect = new ReactiveEffect(fn, options.scheduler) 58 | // 后面不确定的options自动合并在ReactiveEffect类上 59 | extend(_effect, options) 60 | // effect收集进来先跑一编 61 | _effect.run() 62 | // 这个runner就是stop的runner bind为了防止runner调用this指向不正确 63 | const runner: any = _effect.run.bind(_effect) 64 | // 挂载effect在runner上,stop里面要调用_effect的stop方法 65 | runner.effect = _effect 66 | return runner 67 | } 68 | 69 | export function track(target, key) { 70 | if (!isTracking()) return 71 | // 一个taget对应一个key new Map() 72 | // 一个key对应一个dep new Map() 73 | // dep里面依赖是不能重复的 new Set() 74 | // 拿到以key为基准的map集合 75 | let depsMap = targetMap.get(target) 76 | if (!depsMap) { 77 | //初始化 78 | depsMap = new Map() 79 | targetMap.set(target, depsMap) 80 | } 81 | let dep = depsMap.get(key) 82 | if (!dep) { 83 | //初始化 84 | dep = new Set() 85 | depsMap.set(key, dep) 86 | } 87 | trackEffects(dep) 88 | } 89 | export function trackEffects(dep) { 90 | // 优化 91 | if (dep.has(activeEffect)) return 92 | dep.add(activeEffect) 93 | // 反向收集dep 94 | activeEffect.deps.push(dep) 95 | } 96 | 97 | export function isTracking() { 98 | return shouldTrack && activeEffect !== undefined 99 | } 100 | 101 | export function trigger(target, key) { 102 | let depsMap = targetMap.get(target) 103 | let dep = depsMap.get(key) 104 | // MARK: 依赖没有effect,但是触发了set操作,这里会报错(边缘case) 105 | dep && triggerEffects(dep) 106 | } 107 | 108 | export function triggerEffects(dep) { 109 | for (const effect of dep) { 110 | if (effect.scheduler) { 111 | effect.scheduler() 112 | } else { 113 | effect.run() 114 | } 115 | } 116 | } 117 | 118 | export function stop(runner) { 119 | // 在触发依赖的时候就把effect删除掉 120 | // 指向stop方法 121 | runner.effect.stop() 122 | } 123 | -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- 1 | export { ref, proxyRefs } from './ref'; 2 | export { reactive } from './reactive'; 3 | -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "../shared/index"; 2 | import { 3 | mutableHanders, 4 | readonlyHandlers, 5 | shallowReadonlyHandlers, 6 | } from "./baseHandlers"; 7 | 8 | export const enum ReactiveFlags { 9 | IS_REACTIVE = "__v_isReactive", 10 | IS_READONLY = "__v_isReadonly", 11 | } 12 | 13 | export function reactive(raw) { 14 | return createReactiveObject(raw, mutableHanders); 15 | } 16 | 17 | export function readonly(raw) { 18 | return createReactiveObject(raw, readonlyHandlers); 19 | } 20 | 21 | export function shallowReadonly(raw) { 22 | return createReactiveObject(raw, shallowReadonlyHandlers); 23 | } 24 | 25 | export function isReactive(value) { 26 | return !!value[ReactiveFlags.IS_REACTIVE]; 27 | } 28 | 29 | export function isReadonly(value) { 30 | return !!value[ReactiveFlags.IS_READONLY]; 31 | } 32 | 33 | export function isProxy(value) { 34 | return isReactive(value) || isReadonly(value); 35 | } 36 | 37 | function createReactiveObject(target, baseHandles) { 38 | if (!isObject(target)) { 39 | console.warn(`target ${target} 必须是一个对象`); 40 | return target 41 | } 42 | 43 | return new Proxy(target, baseHandles); 44 | } 45 | -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged, isObject } from '../shared' 2 | import { isTracking, trackEffects, triggerEffects } from './effect' 3 | import { reactive } from './reactive' 4 | 5 | class refImpl { 6 | private _value: any 7 | public dep 8 | private _rawValue: any 9 | public __v_isRef = true 10 | constructor(value) { 11 | this._rawValue = value 12 | // 先判断value是不是一个对象 13 | this._value = convert(value) 14 | // 因为value是一个key对应一个dep 只需要dep就行 15 | this.dep = new Set() 16 | } 17 | get value() { 18 | trackRefValue(this) 19 | return this._value 20 | } 21 | set value(newValue) { 22 | // 值是一样的就不触发依赖 23 | if (hasChanged(newValue, this._rawValue)) { 24 | // 先去修改值再去触发依赖 25 | this._rawValue = newValue 26 | this._value = convert(newValue) 27 | triggerEffects(this.dep) 28 | } 29 | } 30 | } 31 | function trackRefValue(ref) { 32 | // 依赖收集 33 | if (isTracking()) { 34 | trackEffects(ref.dep) 35 | } 36 | } 37 | function convert(value) { 38 | return isObject(value) ? reactive(value) : value 39 | } 40 | export function ref(value) { 41 | return new refImpl(value) 42 | } 43 | 44 | export function isRef(ref) { 45 | // MARK:ref有可能为空 46 | return !!ref && !!ref.__v_isRef 47 | } 48 | export function unRef(ref) { 49 | return isRef(ref) ? ref.value : ref 50 | } 51 | export function proxyRefs(objectWidthRefs) { 52 | return new Proxy(objectWidthRefs, { 53 | get(target, key) { 54 | // 1用proxyRefs转换的值访问里面的ref自动解包不用带上.value 55 | // 2如果里面的值不是ref返回本身 56 | return unRef(Reflect.get(target, key)) 57 | }, 58 | set(target, key, value) { 59 | if (isRef(target[key]) && !isRef(value)) { 60 | return (target[key].value = value) 61 | } else { 62 | return Reflect.set(target, key, value) 63 | } 64 | // 当旧值为ref,新的值不是ref才替换 65 | } 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /src/reactivity/tests/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { computed } from '../computed' 2 | import { reactive } from '../reactive' 3 | 4 | describe('computed', () => { 5 | // 类似ref 使用的时候用.value 6 | 7 | it('happy path', () => { 8 | const user = reactive({ 9 | age: 1 10 | }) 11 | const age = computed(() => { 12 | return user.age 13 | }) 14 | expect(age.value).toBe(1) 15 | }) 16 | // 缓存机制 17 | it('should computed be 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 | //lazy 没有调用.value的时候getter函数不会调用 26 | expect(getter).not.toHaveBeenCalled() 27 | expect(cValue.value).toBe(1) 28 | expect(getter).toHaveBeenCalledTimes(1) 29 | 30 | // // 调用.value,只会触发get,结果应该会被缓存起来 31 | cValue.value 32 | // expect(getter).toHaveBeenCalledTimes(1) 33 | // 改变响应式对象值时,getter应该会调用一次 34 | value.foo = 2 35 | expect(getter).toHaveBeenCalledTimes(1) 36 | 37 | expect(cValue.value).toBe(2) 38 | 39 | // 调用cValue.value,getter再次调用 40 | expect(getter).toHaveBeenCalledTimes(2) 41 | 42 | //再缓存,getter不再调用 43 | cValue.value 44 | expect(getter).toHaveBeenCalledTimes(2) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /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 | name: '张三' 9 | }) 10 | let nextAge 11 | effect(() => { 12 | nextAge = user.age + 1 13 | }) 14 | expect(nextAge).toBe(11) 15 | // update 16 | user.age++ 17 | user.name = '李四' 18 | expect(nextAge).toBe(12) 19 | }) 20 | it('should bu return runner', () => { 21 | // 1 调用effect会返回一个函数(runner),调用该函数会运行一次effect内部的fn 22 | // 2 调用runner会返回fn的返回值 23 | let foo = 10 24 | const runner = effect(() => { 25 | foo++ 26 | return 'foo' 27 | }) 28 | expect(foo).toBe(11) 29 | const r = runner() 30 | expect(r).toBe('foo') 31 | }) 32 | // 实现一个scheduler 33 | it('scheduler', () => { 34 | // scheduler功能 35 | // 1 通过effect的第二个参数给定一个schedluer的函数fn 36 | // 2 effect第一次执行的时候还会执行fn(第一个参数不是scheduler的fn) 37 | // 3 当响应式对象set的时候就是更新的时候,不会执行fn而是执行scheduler 38 | // 4 如果执行runner(effect返回值)的时候,会再次执行fn(第一个参数,不是scheduler的fn) 39 | let dummy 40 | let run: any 41 | const scheduler = jest.fn(() => { 42 | run = runner 43 | }) 44 | const obj = reactive({ foo: 1 }) 45 | const runner = effect( 46 | () => { 47 | dummy = obj.foo 48 | }, 49 | { scheduler } 50 | ) 51 | // 第一次不会被执行 52 | expect(scheduler).not.toHaveBeenCalled() 53 | // fn正常执行 54 | expect(dummy).toBe(1) 55 | // 更新响应式时fn不会被执行 scheduler被执行 56 | obj.foo++ 57 | expect(scheduler).toHaveBeenCalledTimes(1) 58 | expect(dummy).toBe(1) 59 | run() 60 | expect(dummy).toBe(2) 61 | }) 62 | // 实现一个stop 63 | it('stop', () => { 64 | let dummy 65 | const obj = reactive({ 66 | prop: 1 67 | }) 68 | const runner = effect(() => { 69 | dummy = obj.prop 70 | }) 71 | obj.prop = 2 72 | expect(dummy).toBe(2) 73 | stop(runner) 74 | // 分析obj.prop++ -----> obj.prop = obj.prop + 1 75 | // 1 触发get(依赖收集) 76 | // 2触发set(依赖触发) 77 | obj.prop++ 78 | expect(dummy).toBe(2) 79 | // 再次运行effect 80 | runner() 81 | expect(dummy).toBe(3) 82 | }) 83 | // 实现一个onStop,stop的回调函数 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 | it('clean effect', () => { 104 | const user = reactive({ 105 | man: true, 106 | age: 10, 107 | name: '张三' 108 | }) 109 | let nextAge 110 | const fn = jest.fn(() => { 111 | nextAge = user.man ? user.age : 100 112 | }) 113 | effect(() => { 114 | fn() 115 | }) 116 | user.age = 10 117 | expect(fn).toHaveBeenCalledTimes(2) 118 | }) 119 | }) 120 | -------------------------------------------------------------------------------- /src/reactivity/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReactive, reactive, isProxy } from '../reactive' 2 | describe('reactive', () => { 3 | it('happy path', () => { 4 | const origin = { foo: 1 } 5 | const observed = reactive(origin) 6 | expect(observed).not.toBe(origin) 7 | expect(observed.foo).toBe(1) 8 | expect(isReactive(observed)).toBe(true) 9 | expect(isReactive(origin)).toBe(false) 10 | expect(isProxy(observed)).toBe(true) 11 | }) 12 | test('nested reactive', () => { 13 | const original = { 14 | nested: { foo: 1 }, 15 | array: [{ bar: 1 }] 16 | } 17 | const observed = reactive(original) 18 | expect(isReactive(observed.nested)).toBe(true) 19 | expect(isReactive(observed.array)).toBe(true) 20 | expect(isReactive(observed.array[0])).toBe(true) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /src/reactivity/tests/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReadonly, readonly, isProxy } from '../reactive' 2 | 3 | describe('readonly', () => { 4 | it('happy path', () => { 5 | const origin = { foo: 1, bar: { baz: 2 } } 6 | const wrapper = readonly(origin) 7 | expect(wrapper).not.toBe(origin) 8 | expect(isReadonly(wrapper)).toBe(true) 9 | expect(isReadonly(origin)).toBe(false) 10 | expect(isReadonly(wrapper.bar)).toBe(true) 11 | expect(isReadonly(origin.bar)).toBe(false) 12 | expect(isProxy(wrapper)).toBe(true) 13 | expect(wrapper.foo).toBe(1) 14 | }) 15 | it('warn then call set', () => { 16 | console.warn = jest.fn() 17 | const user = readonly({ age: 10 }) 18 | user.age = 11 19 | expect(console.warn).toBeCalled() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/reactivity/tests/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from '../effect' 2 | import { reactive } from '../reactive' 3 | import { isRef, proxyRefs, ref, unRef } from '../ref' 4 | 5 | describe('ref', () => { 6 | it('happy path', () => { 7 | const a = ref(1) 8 | expect(a.value).toBe(1) 9 | }) 10 | it('should be reactive', () => { 11 | const a = ref(1) 12 | let dummy 13 | let calls = 0 14 | effect(() => { 15 | calls++ 16 | dummy = a.value 17 | }) 18 | expect(calls).toBe(1) 19 | expect(dummy).toBe(1) 20 | a.value = 2 21 | expect(calls).toBe(2) 22 | expect(dummy).toBe(2) 23 | // some value should not trigger 24 | a.value = 2 25 | expect(calls).toBe(2) 26 | expect(dummy).toBe(2) 27 | }) 28 | it('should make nested properties reactive', () => { 29 | const a = ref({ 30 | count: 1 31 | }) 32 | let dummy 33 | effect(() => { 34 | dummy = a.value.count 35 | }) 36 | expect(dummy).toBe(1) 37 | a.value.count = 2 38 | expect(dummy).toBe(2) 39 | }) 40 | it('isRef', () => { 41 | const a = ref(1) 42 | const user = reactive({ 43 | age: 1 44 | }) 45 | expect(isRef(a)).toBe(true) 46 | expect(isRef(1)).toBe(false) 47 | expect(isRef(user)).toBe(false) 48 | }) 49 | it('unRef', () => { 50 | const a = ref(1) 51 | 52 | expect(unRef(a)).toBe(1) 53 | expect(unRef(1)).toBe(1) 54 | }) 55 | 56 | it('proxyRefs', () => { 57 | const user = { 58 | age: ref(10), 59 | name: 'zhangsan' 60 | } 61 | const proxyUser = proxyRefs(user) 62 | 63 | expect(proxyUser.age).toBe(10) 64 | expect(user.age.value).toBe(10) 65 | expect(proxyUser.name).toBe('zhangsan') 66 | proxyUser.age = 20 67 | expect(proxyUser.age).toBe(20) 68 | expect(user.age.value).toBe(20) 69 | proxyUser.age = ref(10) 70 | expect(proxyUser.age).toBe(10) 71 | expect(user.age.value).toBe(10) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /src/reactivity/tests/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReadonly, shallowReadonly } from '../reactive' 2 | 3 | describe('shallowReadonly', () => { 4 | it('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 | it('warn then call set', () => { 10 | console.warn = jest.fn() 11 | const user = shallowReadonly({ age: 10 }) 12 | user.age = 11 13 | expect(console.warn).toBeCalled() 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from './component'; 2 | 3 | export function provide(key, value) { 4 | const currentInstance: any = getCurrentInstance() 5 | if (currentInstance) { 6 | let { provides } = currentInstance 7 | const parentProvides = currentInstance.parent.provides 8 | if (provides === parentProvides) { 9 | provides = currentInstance.provides = Object.create(parentProvides) 10 | } 11 | provides[key] = value 12 | } 13 | 14 | } 15 | export function inject(key, defaultValue) { 16 | const currentInstance: any = getCurrentInstance() 17 | if (currentInstance) { 18 | const parentProvides = currentInstance.parent.provides 19 | if (key in parentProvides) { 20 | return parentProvides[key] 21 | } else if (defaultValue) { 22 | if (typeof defaultValue === 'function') { 23 | return defaultValue() 24 | } 25 | return defaultValue 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { shallowReadonly } from '../reactivity/reactive' 2 | import { emit } from './componentEmit' 3 | import { PublicInstanceProxyHandlers } from './componentPubilcInstance' 4 | import { initProps } from './componentProps' 5 | import { initSlots } from './componentSlots' 6 | import { proxyRefs } from '../reactivity' 7 | 8 | // 创建一个组件实例 9 | export function createComponentInstance(vnode, parent) { 10 | const component = { 11 | vnode, 12 | type: vnode.type, 13 | setupState: {}, 14 | props: {}, 15 | slots: {}, 16 | provides: parent ? parent.provides : {}, 17 | parent, 18 | isMounted: false, 19 | subTree: {}, 20 | emit: () => { } 21 | } 22 | component.emit = emit.bind(null, component) as any 23 | return component 24 | } 25 | export function setupComponent(instance) { 26 | // initProps初始化props 27 | initProps(instance, instance.vnode.props) 28 | // TODO: initSlots初始化slots 29 | initSlots(instance, instance.vnode.children) 30 | // 初始化有状态的组件 31 | setupStatefulComponent(instance) 32 | } 33 | // 解构setup,并执行 34 | export function setupStatefulComponent(instance: any) { 35 | const Component = instance.type 36 | 37 | // ctx 38 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers) 39 | const { setup } = Component 40 | // 用户可能不会写setup 41 | if (setup) { 42 | setCurrentInstance(instance) 43 | const setupResult = setup(shallowReadonly(instance.props), { 44 | emit: instance.emit 45 | }) 46 | setCurrentInstance(null) 47 | // setupResult可能是对象或者函数 48 | handlerSetupResult(instance, setupResult) 49 | } 50 | } 51 | 52 | function handlerSetupResult(instance, setupResult: any) { 53 | //TODO: 1 setupResult是function 54 | 55 | // 2 setupResult是object 56 | if (typeof setupResult === 'object') { 57 | // 挂载在实例上 58 | instance.setupState = proxyRefs(setupResult) 59 | } 60 | finishComponentSetup(instance) 61 | } 62 | 63 | function finishComponentSetup(instance: any) { 64 | const Component = instance.type 65 | // TODO:假设用户一定要写render 66 | // if (Component.render) { 67 | instance.render = Component.render 68 | // } 69 | } 70 | let currentInstance = null 71 | export function getCurrentInstance() { 72 | return currentInstance 73 | } 74 | 75 | export function setCurrentInstance(instance) { 76 | currentInstance = instance 77 | } 78 | -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { cameliz, toHandlerKey } from '../shared' 2 | 3 | export function emit(instance, event: string, ...args: any[]) { 4 | // instance.props --> event 5 | // 用户要传的是emit(event),instance是无感知的,所以要用到bind 6 | const { props } = instance 7 | // TPP,先写一个特例,再写一个通用的 8 | // add->Add 9 | const str = event.charAt(0).toUpperCase() + event.slice(1) 10 | 11 | const handler = props[toHandlerKey(cameliz(event))] 12 | handler && handler(...args) 13 | } 14 | -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance: any, rawProps: any) { 2 | instance.props = rawProps || {} 3 | // attrs 4 | } 5 | -------------------------------------------------------------------------------- /src/runtime-core/componentPubilcInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from '../shared' 2 | 3 | const publicPropertiesMap = { 4 | $el: (i) => i.vnode.$el, 5 | $slots: (i) => i.slots 6 | } 7 | export const PublicInstanceProxyHandlers = { 8 | get({ _: instance }, key) { 9 | const { setupState, props } = instance 10 | if (hasOwn(setupState, key)) { 11 | return setupState[key] 12 | } else if (hasOwn(props, key)) { 13 | return props[key] 14 | } 15 | const publicGetter = publicPropertiesMap[key] 16 | if (publicGetter) { 17 | return publicGetter(instance) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from '../shared/ShapeFlags' 2 | 3 | export function initSlots(instance: any, children: any) { 4 | const { vnode } = instance 5 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) { 6 | normalizeObjectSlots(children, instance.slots) 7 | } 8 | } 9 | 10 | function normalizeObjectSlots(children: any, slots) { 11 | for (const key in children) { 12 | const value = children[key] 13 | slots[key] = (props) => normalizeSlotValue(value(props)) 14 | } 15 | } 16 | 17 | function normalizeSlotValue(value: any) { 18 | return Array.isArray(value) ? value : [value] 19 | } 20 | -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from './vnode' 2 | 3 | export function createAppAPI(render) { 4 | return function createApp(rootComponent) { 5 | return { 6 | // 这里先写一个容器,因为vue3传递的是选择器,现在先用容器 7 | // 为了把App即系rootComponent的渲染到这个根容器里面 8 | mount(rootContainer) { 9 | // 先把rootComponent变成一个虚拟节点 vnode (rootComponent->vnode) 10 | // 后续所有逻辑都会基于这个虚拟节点进行处理 11 | const vnode = createVNode(rootComponent) 12 | //接受一个vnode和虚拟节点 13 | render(vnode, rootContainer) 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from './vnode' 2 | 3 | export function h(type, props?, children?) { 4 | return createVNode(type, props, children) 5 | } 6 | -------------------------------------------------------------------------------- /src/runtime-core/helper/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, Fragment } from '../vnode'; 2 | 3 | export function renderSlots(slots, name, props) { 4 | const slot = slots[name]; 5 | if (slot) { 6 | if (typeof slot === 'function') { 7 | return createVNode(Fragment, {}, slot(props)); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | export { h } from './h' 2 | export { renderSlots } from './helper/renderSlots' 3 | export { createTextVnode } from './vnode' 4 | export { getCurrentInstance } from './component' 5 | export { provide, inject } from './apiInject' 6 | export { createRenderer } from './renderer' 7 | -------------------------------------------------------------------------------- /src/runtime-core/renderer.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from '../shared/ShapeFlags' 2 | import { createComponentInstance, setupComponent } from './component' 3 | import { Fragment, Text } from './vnode' 4 | import { createAppAPI } from './createApp'; 5 | import { effect } from '../reactivity/effect'; 6 | import { EMPTY_OBJ } from '../shared'; 7 | 8 | export function createRenderer(options) { 9 | const { 10 | createElement: hostCreateElement, 11 | patchProp: hostPatchProp, 12 | insert: hostInsert, 13 | setElementText: hostSetElementText, 14 | remove: hostRemove 15 | } = options 16 | 17 | function render(vnode, container) { 18 | // 调用path,进行递归处理 19 | patch(null, vnode, container, null) 20 | } 21 | function patch(n1, n2, container, parentComponent) { 22 | const { shapeFlag, type } = n2 23 | switch (type) { 24 | case Fragment: 25 | processFragment(n1, n2, container, parentComponent) 26 | break 27 | case Text: 28 | processText(n1, n2, container) 29 | break 30 | default: 31 | // type 是Fragment只渲染children 32 | if (shapeFlag & ShapeFlags.ELEMENT) { 33 | // 1 处理element 34 | processElement(n1, n2, container, parentComponent) 35 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 36 | // 2 处理组件 37 | processComponent(n1, n2, container, parentComponent) 38 | 39 | } 40 | break 41 | } 42 | } 43 | 44 | function setupRenderEffect(instance: any, initialVNode, container: any) { 45 | effect(() => { 46 | if (!instance.isMounted) { 47 | const { proxy } = instance; 48 | const subTree = (instance.subTree = instance.render.call(proxy)); 49 | 50 | patch(null, subTree, container, instance); 51 | 52 | initialVNode.el = subTree.el; 53 | 54 | instance.isMounted = true; 55 | } else { 56 | const { proxy } = instance; 57 | const subTree = instance.render.call(proxy); 58 | const prevSubTree = instance.subTree; 59 | instance.subTree = subTree; 60 | 61 | patch(prevSubTree, subTree, container, instance); 62 | } 63 | }); 64 | } 65 | 66 | // MARK:处理element类型 67 | function processElement(n1, n2, container: any, parentComponent) { 68 | if (!n1) { 69 | mountElement(n2, container, parentComponent); 70 | } else { 71 | patchElement(n1, n2, container, parentComponent); 72 | 73 | } 74 | } 75 | function patchElement(n1, n2, container, parentComponent) { 76 | 77 | const oldProps = n1.props || EMPTY_OBJ 78 | const newProps = n2.props || EMPTY_OBJ 79 | const el = (n2.el = n1.el); 80 | 81 | patchPorps(el, oldProps, newProps); 82 | patchChildren(n1, n2, el, parentComponent); 83 | } 84 | function patchChildren(n1, n2, container, parentComponent) { 85 | const c1 = n1.children; 86 | const preShapeFlag = n1.shapeFlag; 87 | const { shapeFlag } = n2; 88 | const c2 = n2.children; 89 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 90 | if (preShapeFlag & ShapeFlags.ARRAY_CHILDREN) { 91 | console.log('旧节点是 array,新节点是 text'); 92 | unmountChildren(n1.children); 93 | } 94 | // 新老不相等,直接更新(新节点是 text,不管老节点是什么) 95 | if (c1 !== c2) { 96 | console.log('新节点是 text,不管老节点是什么'); 97 | hostSetElementText(container, c2); 98 | } 99 | } else { 100 | if (preShapeFlag & ShapeFlags.TEXT_CHILDREN) { 101 | console.log('旧节点是 text,新节点是 array'); 102 | hostSetElementText(container, '') 103 | mountChildren(c2, container, parentComponent); 104 | 105 | } else { 106 | console.log('旧节点是 array,新节点是 array') 107 | } 108 | } 109 | } 110 | 111 | function unmountChildren(children) { 112 | for (let i = 0; i < children.length; i++) { 113 | const el = children[i].el; 114 | hostRemove(el); 115 | } 116 | } 117 | 118 | function patchPorps(el, oldProps: any, newProps: any) { 119 | if (oldProps !== newProps) { 120 | // 有值 更新 121 | for (const key in newProps) { 122 | const prevProp = oldProps[key]; 123 | const nextProp = newProps[key]; 124 | if (prevProp !== nextProp) { 125 | hostPatchProp(el, key, prevProp, nextProp); 126 | } 127 | } 128 | if (oldProps !== EMPTY_OBJ) { 129 | // 无值 删除 130 | for (const key in oldProps) { 131 | if (!(key in newProps)) { 132 | hostPatchProp(el, key, oldProps[key], null); 133 | } 134 | } 135 | } 136 | } 137 | 138 | } 139 | 140 | function mountElement(vnode, container: any, parentComponent) { 141 | // 创建元素 142 | const el = (vnode.el = hostCreateElement(vnode.type)) 143 | // 判断children 144 | const { children, shapeFlag } = vnode 145 | // children是string 146 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 147 | el.textContent = children 148 | // children是array 149 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 150 | mountChildren(vnode.children, el, parentComponent) 151 | } 152 | 153 | // 生成props 154 | const { props } = vnode 155 | // 旧props有值,则更新 156 | for (const key in props) { 157 | const val = props[key] 158 | hostPatchProp(el, key, null, val) 159 | } 160 | hostInsert(el, container) 161 | } 162 | 163 | // MARK:处理组件类型 164 | function processComponent(n1, n2, container: any, parentComponent) { 165 | mountComponent(n2, container, parentComponent) 166 | } 167 | function mountComponent(initialVnode: any, container: any, parentComponent) { 168 | // 创建一个组件实例 169 | const instance = createComponentInstance(initialVnode, parentComponent) 170 | setupComponent(instance) 171 | setupRenderEffect(instance, initialVnode, container) 172 | } 173 | 174 | // MARK:处理Fragment件类型 175 | function processFragment(n1, n2, container: any, parentComponent) { 176 | mountChildren(n2.children, container, parentComponent) 177 | } 178 | 179 | // MARK:处理Text件类型 180 | function processText(n1, n2, container: any) { 181 | const { children } = n2 182 | const textNode = (n2.el = document.createTextNode(children)) 183 | container.append(textNode) 184 | } 185 | 186 | 187 | function mountChildren(children, container, parentComponent) { 188 | children.forEach((v: any) => { 189 | patch(null, v, container, parentComponent) 190 | }) 191 | } 192 | 193 | return { 194 | createApp: createAppAPI(render) 195 | } 196 | } 197 | 198 | 199 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/ShapeFlags"; 2 | export const Fragment = Symbol("Fragment"); 3 | export const Text = Symbol("Text"); 4 | 5 | export function createVNode(type, props?, children?) { 6 | const vnode = { 7 | type, 8 | props, 9 | children, 10 | shapeFlag: getShapeFlag(type), 11 | el: null, 12 | }; 13 | 14 | if (typeof children === "string") { 15 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN; 16 | } else if (Array.isArray(children)) { 17 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN; 18 | } 19 | 20 | 21 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 22 | if (typeof children === "object") { 23 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN; 24 | } 25 | } 26 | 27 | 28 | return vnode; 29 | } 30 | 31 | export function createTextVnode(text: string) { 32 | return createVNode(Text, {}, text); 33 | } 34 | function getShapeFlag(type) { 35 | return typeof type === "string" 36 | ? ShapeFlags.ELEMENT 37 | : ShapeFlags.STATEFUL_COMPONENT; 38 | } 39 | -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from '../runtime-core' 2 | 3 | function createElement(type) { 4 | return document.createElement(type) 5 | } 6 | function patchProp(el, key, prevVal, nextProp) { 7 | const isOn = (key: string) => /^on[A-Z]/.test(key) 8 | if (isOn(key)) { 9 | const event = key.slice(2).toLowerCase() 10 | el.addEventListener(event, nextProp) 11 | } else { 12 | // nextProp是 null 或者 undefined 则删除 13 | if (nextProp === null || nextProp === undefined) { 14 | el.removeAttribute(key) 15 | } else { 16 | el.setAttribute(key, nextProp) 17 | } 18 | } 19 | } 20 | function insert(el, container) { 21 | container.appendChild(el) 22 | } 23 | function setElementText(el, text) { 24 | el.textContent = text 25 | } 26 | function remove(child) { 27 | const parent = child.parentNode 28 | if (parent) { 29 | parent.removeChild(child) 30 | } 31 | } 32 | 33 | const render: any = createRenderer({ 34 | createElement, 35 | patchProp, 36 | insert, 37 | setElementText, 38 | remove 39 | }) 40 | 41 | export function createApp(...args) { 42 | return render.createApp(...args) 43 | } 44 | 45 | export * from "../runtime-core"; 46 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export const extend = Object.assign 2 | 3 | export const EMPTY_OBJ = {}; 4 | 5 | export const isObject = (val) => { 6 | return val !== null && typeof val === 'object' 7 | } 8 | export const hasChanged = (newValue, value) => { 9 | return !Object.is(newValue, value) 10 | } 11 | 12 | export const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key) 13 | 14 | const capitalize = (str: string) => { 15 | return str.charAt(0).toUpperCase() + str.slice(1) 16 | } 17 | export const toHandlerKey = (str: string) => { 18 | return str ? 'on' + capitalize(str) : '' 19 | } 20 | // add-foo --> addFoo 21 | export const cameliz = (str: string) => { 22 | return str.replace(/-(\w)/g, (_, c: string) => { 23 | return c ? c.toUpperCase() : '' 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /src/shared/shapeFlags.ts: -------------------------------------------------------------------------------- 1 | export const enum ShapeFlags { 2 | ELEMENT = 1, 3 | STATEFUL_COMPONENT = 1 << 1, 4 | TEXT_CHILDREN = 1 << 2, 5 | ARRAY_CHILDREN = 1 << 3, 6 | SLOT_CHILDREN = 1 << 4 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 | "ES6" 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": [ 38 | "jest" 39 | ] /* Specify type package names to be included without being referenced in a source file. */, 40 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 41 | // "resolveJsonModule": true, /* Enable importing .json files */ 42 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 43 | 44 | /* JavaScript Support */ 45 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 46 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 47 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 48 | 49 | /* Emit */ 50 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 51 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 52 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 53 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 54 | // "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. */ 55 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 56 | // "removeComments": true, /* Disable emitting comments. */ 57 | // "noEmit": true, /* Disable emitting files from a compilation. */ 58 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 59 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 60 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 61 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 63 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 64 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 65 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 66 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 67 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 68 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 69 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 70 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 71 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 72 | 73 | /* Interop Constraints */ 74 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 75 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 76 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, 77 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 78 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 79 | 80 | /* Type Checking */ 81 | "strict": true /* Enable all strict type-checking options. */, 82 | "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied `any` type.. */, 83 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 84 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 85 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 86 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 87 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 88 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 89 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 90 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 91 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 92 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 93 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 94 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 95 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 96 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 97 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 98 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 99 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 100 | 101 | /* Completeness */ 102 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 103 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 104 | } 105 | } 106 | --------------------------------------------------------------------------------