├── .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 | 
8 |
9 | 3. 依赖收集关系
10 |
11 | 
12 |
13 | 依赖收集运行
14 | 
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 |
--------------------------------------------------------------------------------