├── .gitignore
├── pnpm-workspace.yaml
├── packages
├── compiler-core
│ ├── src
│ │ ├── index.ts
│ │ ├── utils.ts
│ │ ├── runtimeHelpers.ts
│ │ ├── transforms
│ │ │ ├── transformExpression.ts
│ │ │ ├── transformElement.ts
│ │ │ └── transformText.ts
│ │ ├── ast.ts
│ │ ├── compile.ts
│ │ ├── transform.ts
│ │ ├── codegen.ts
│ │ └── parse.ts
│ ├── package.json
│ └── __test__
│ │ ├── __snapshots__
│ │ └── codegen.test.ts.snap
│ │ ├── transform.test.ts
│ │ ├── codegen.test.ts
│ │ └── parse.test.ts
├── shared
│ ├── src
│ │ ├── toDisplayString.ts
│ │ ├── shapeFlags.ts
│ │ └── index.ts
│ └── package.json
├── runtime-core
│ ├── src
│ │ ├── h.ts
│ │ ├── componentProps.ts
│ │ ├── index.ts
│ │ ├── createApp.ts
│ │ ├── componentEmit.ts
│ │ ├── apiWatch.ts
│ │ ├── componentsSlots.ts
│ │ ├── componentPublicInstance.ts
│ │ ├── scheduler.ts
│ │ ├── componentUpdateUtils.ts
│ │ ├── vnode.ts
│ │ ├── apiInject.ts
│ │ ├── helps
│ │ │ └── renderSlots.ts
│ │ ├── component.ts
│ │ └── renderer.ts
│ ├── package.json
│ └── __tests__
│ │ └── apiWatch.test.ts
├── vue
│ ├── examples
│ │ ├── update
│ │ │ ├── main.js
│ │ │ ├── index.html
│ │ │ └── App.js
│ │ ├── apiInject
│ │ │ ├── main.js
│ │ │ ├── index.html
│ │ │ └── App.js
│ │ ├── helloWorld
│ │ │ ├── main.js
│ │ │ ├── Foo.js
│ │ │ ├── index.html
│ │ │ └── App.js
│ │ ├── nextTicker
│ │ │ ├── main.js
│ │ │ ├── index.html
│ │ │ └── App.js
│ │ ├── compiler-base
│ │ │ ├── main.js
│ │ │ ├── App.js
│ │ │ └── index.html
│ │ ├── componentEmit
│ │ │ ├── main.js
│ │ │ ├── App.js
│ │ │ ├── index.html
│ │ │ └── Foo.js
│ │ ├── componentSlot
│ │ │ ├── main.js
│ │ │ ├── index.html
│ │ │ ├── Foo.js
│ │ │ └── App.js
│ │ ├── componentUpdate
│ │ │ ├── main.js
│ │ │ ├── Child.js
│ │ │ ├── index.html
│ │ │ └── App.js
│ │ ├── currentInstance
│ │ │ ├── main.js
│ │ │ ├── Foo.js
│ │ │ ├── App.js
│ │ │ └── index.html
│ │ ├── patchChildren
│ │ │ ├── main.js
│ │ │ ├── index.html
│ │ │ ├── TextToText.js
│ │ │ ├── App.js
│ │ │ ├── ArrayToText.js
│ │ │ ├── TextToArray.js
│ │ │ └── ArrayToArray.js
│ │ └── customRenderer
│ │ │ ├── App.js
│ │ │ ├── index.html
│ │ │ └── main.js
│ ├── package.json
│ └── src
│ │ └── index.ts
├── reactivity
│ ├── src
│ │ ├── index.ts
│ │ ├── computed.ts
│ │ ├── reactive.ts
│ │ ├── ref.ts
│ │ ├── baseHandlers.ts
│ │ └── effect.ts
│ ├── package.json
│ └── __test__
│ │ ├── shallowReadonly.test.ts
│ │ ├── reactive.test.ts
│ │ ├── readonly.test.ts
│ │ ├── computed.test.ts
│ │ ├── ref.test.ts
│ │ └── effect.test.ts
└── runtime-dom
│ ├── package.json
│ └── src
│ └── index.ts
├── .eslintrc
├── vitest.config.ts
├── rollup.config.js
├── package.json
├── README.md
├── tsconfig.json
├── yarn.lock
└── yarn-error.log
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
3 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './compile'
2 |
--------------------------------------------------------------------------------
/packages/shared/src/toDisplayString.ts:
--------------------------------------------------------------------------------
1 | export function toDisplayString(value) {
2 | return String(value)
3 | }
4 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from './vnode'
2 |
3 | export function h(type, props?, children?) {
4 | return createVNode(type, props, children)
5 | }
6 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from './ast'
2 |
3 | export function isText(node) {
4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION
5 | }
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/apiInject/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloWorld/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/nextTicker/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/compiler-base/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../dist/vue3-mini.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@antfu",
3 | "rules": {
4 | "@typescript-eslint/no-use-before-define": "off",
5 | "no-console": "off",
6 | "no-cond-assign": "off",
7 | "@typescript-eslint/no-this-alias": "off",
8 | "no-new-func": "off"
9 | }
10 | }
--------------------------------------------------------------------------------
/packages/vue/examples/customRenderer/App.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../dist/vue3-mini.esm.js'
2 |
3 | export const App = {
4 | setup() {
5 | return {
6 | x: 100,
7 | y: 100,
8 | }
9 | },
10 | render() {
11 | return h('rect', { x: this.x, y: this.y })
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/packages/shared/src/shapeFlags.ts:
--------------------------------------------------------------------------------
1 | // | 两位都为0 才为0 用作赋值
2 | // & 两位都为1 才为1 用作判断
3 |
4 | export enum ShapeFlags {
5 | ELEMENT = 1, // 0001
6 | STATEFUL_COMPONENT = 1 << 1, // 0010
7 | TEXT_CHILDREN = 1 << 2, // 0100
8 | ARRAY_CHILDREN = 1 << 3, // 1000
9 | SLOT_CHILDREN = 1 << 4, // 1000
10 | }
11 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/runtimeHelpers.ts:
--------------------------------------------------------------------------------
1 | export const TO_DISPLAY_STRING = Symbol('toDisplayString')
2 | export const CREATE_ELEMENT_VNODE = Symbol('createElementVNode')
3 |
4 | export const helperMapName = {
5 | [TO_DISPLAY_STRING]: 'toDisplayString',
6 | [CREATE_ELEMENT_VNODE]: 'createElementVNode',
7 | }
8 |
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue3-mini/shared",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "",
6 | "license": "ISC",
7 | "keywords": [],
8 | "main": "index.js",
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vue/examples/compiler-base/App.js:
--------------------------------------------------------------------------------
1 | import { ref } from '../../dist/vue3-mini.esm.js'
2 |
3 | export const App = {
4 | name: 'App',
5 | template: '
hi,{{count}}
',
6 | setup() {
7 | const count = (window.count = ref(1))
8 | return {
9 | count,
10 | }
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../dist/vue3-mini.esm.js'
2 | export default {
3 | name: 'Child',
4 | setup(props, { emit }) {},
5 | render(proxy) {
6 | return h('div', {}, [
7 | h('div', {}, `child - props - msg: ${this.$props.msg}`),
8 | ])
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloWorld/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../dist/vue3-mini.esm.js'
2 |
3 | export const Foo = {
4 | setup(props) {
5 | console.log(props)
6 | // shallowReadonly -> warn
7 | props.count++
8 | },
9 |
10 | render() {
11 | return h('div', {}, `foo:${this.count}`)
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance, rawProps) {
2 | // 将props赋值给instance.props
3 | console.log('初始化 props')
4 | // TODO
5 | // 应该还有 attrs 的概念
6 | // attrs
7 | // 如果组件声明了 props 的话,那么才可以进入 props 属性内
8 | // 不然的话是需要存储在 attrs 内
9 | instance.props = rawProps || {}
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance, h } from '../../dist/vue3-mini.esm.js'
2 |
3 | export const Foo = {
4 | name: 'Foo',
5 | setup() {
6 | const instance = getCurrentInstance()
7 | console.log('Foo:', instance)
8 | return {}
9 | },
10 | render() {
11 | return h('div', {}, 'foo')
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/packages/reactivity/src/index.ts:
--------------------------------------------------------------------------------
1 | export { effect, track, trigger, stop, trackEffects, triggerEffects, isTracking } from './effect'
2 | export { computed } from './computed'
3 | export { ref, isRef, unRef, proxyRefs } from './ref'
4 | export {
5 | reactive,
6 | readonly,
7 | isReactive,
8 | isReadonly,
9 | shallowReadonly,
10 | isProxy,
11 | } from './reactive'
12 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/transforms/transformExpression.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from '../ast'
2 |
3 | export function transformExpression(node) {
4 | if (node.type === NodeTypes.INTERPOLATION)
5 | node.content = processExpression(node.content)
6 | }
7 | function processExpression(node: any) {
8 | node.content = `_ctx.${node.content}`
9 |
10 | return node
11 | }
12 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { defineConfig } from 'vitest/config'
3 |
4 | export default defineConfig({
5 | test: {
6 | globals: true,
7 | },
8 | resolve: {
9 | alias: [
10 | {
11 | find: /@vue3-mini\/(\w*)/,
12 | replacement: `${path.resolve(__dirname, 'packages')}/$1/src`,
13 | },
14 | ],
15 | },
16 | })
17 |
--------------------------------------------------------------------------------
/packages/reactivity/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue3-mini/reactivity",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "",
6 | "license": "ISC",
7 | "keywords": [],
8 | "main": "index.js",
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "dependencies": {
13 | "@vue3-mini/shared": "workspace:^1.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/compiler-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue3-mini/compiler-core",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "",
6 | "license": "ISC",
7 | "keywords": [],
8 | "main": "index.js",
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "dependencies": {
13 | "@vue3-mini/shared": "workspace:^1.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance, h } from '../../dist/vue3-mini.esm.js'
2 | import { Foo } from './Foo.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | render() {
7 | return h('div', {}, [h('p', {}, 'currentInstanceDemo'), h(Foo)])
8 | },
9 |
10 | setup() {
11 | const instance = getCurrentInstance()
12 | console.log('App:', instance)
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3-mini",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "",
6 | "license": "ISC",
7 | "keywords": [],
8 | "main": "index.js",
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "dependencies": {
13 | "@vue3-mini/compiler-core": "workspace:^1.0.0",
14 | "@vue3-mini/runtime-dom": "workspace:^1.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/runtime-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue3-mini/runtime-core",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "",
6 | "license": "ISC",
7 | "keywords": [],
8 | "main": "index.js",
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "dependencies": {
13 | "@vue3-mini/reactivity": "workspace:^1.0.0",
14 | "@vue3-mini/shared": "workspace:^1.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/runtime-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue3-mini/runtime-dom",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "",
6 | "license": "ISC",
7 | "keywords": [],
8 | "main": "index.js",
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "dependencies": {
13 | "@vue3-mini/runtime-core": "workspace:^1.0.0",
14 | "@vue3-mini/shared": "workspace:^1.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from '@rollup/plugin-typescript'
2 |
3 | export default {
4 | input: './packages/vue/src/index.ts',
5 | output: [
6 | // 1. cjs -> commonjs规范
7 | // 2. esm -> 标准化模块规范
8 | {
9 | format: 'cjs',
10 | file: 'packages/vue/dist/vue3-mini.cjs.js',
11 | },
12 | {
13 | format: 'es',
14 | file: 'packages/vue/dist/vue3-mini.esm.js',
15 | },
16 | ],
17 | plugins: [
18 | typescript(),
19 | ],
20 | }
21 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/ast.ts:
--------------------------------------------------------------------------------
1 | import { CREATE_ELEMENT_VNODE } from './runtimeHelpers'
2 |
3 | export const enum NodeTypes {
4 | INTERPOLATION,
5 | SIMPLE_EXPRESSION,
6 | ELEMENT,
7 | TEXT,
8 | ROOT,
9 | COMPOUND_EXPRESSION,
10 | }
11 |
12 | export function createVNodeCall(context, tag, props, children) {
13 | context.helper(CREATE_ELEMENT_VNODE)
14 | const vnodeElement = {
15 | type: NodeTypes.ELEMENT,
16 | tag,
17 | props,
18 | children,
19 | }
20 | return vnodeElement
21 | }
22 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../dist/vue3-mini.esm.js'
2 | import { Foo } from './Foo.js'
3 |
4 | export const App = {
5 | render() {
6 | return h('div', {}, [
7 | h('div', {}, 'App'),
8 | h(Foo, {
9 | onAdd(a, b) {
10 | console.log('onAdd', a, b)
11 | },
12 | onAddFoo(a, b) {
13 | console.log('onAddFoo', a, b)
14 | },
15 | }),
16 | ])
17 | },
18 |
19 | setup() {
20 | return {}
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/packages/vue/examples/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloWorld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/nextTicker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export { h } from './h'
2 | export { renderSlots } from './helps/renderSlots'
3 | export { createTextVnode, createElementVNode } from './vnode'
4 | export { getCurrentInstance, registerRuntimeCompiler } from './component'
5 | export { provide, inject } from './apiInject'
6 | export { createRenderer } from './renderer'
7 | export { nextTick } from './scheduler'
8 | export { Text, Fragment } from './vnode'
9 | export { toDisplayString } from '@vue3-mini/shared'
10 | export * from '@vue3-mini/reactivity'
11 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from '../../dist/vue3-mini.esm.js'
2 |
3 | export const Foo = {
4 | setup() {
5 | return {}
6 | },
7 |
8 | render() {
9 | const foo = h('p', {}, 'foo')
10 | // 具名插槽
11 | // 1. 获取到渲染的元素
12 | // 2. 获取到渲染的位置
13 | // 作用域插槽
14 | const age = 18
15 | return h('div', {}, [
16 | renderSlots(this.$slots, 'header', {
17 | age,
18 | }),
19 | foo,
20 | renderSlots(this.$slots, 'footer'),
21 | ])
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from '../../dist/vue3-mini.esm.js'
2 |
3 | const prevChildren = 'oldChild'
4 | const nextChildren = 'newChild'
5 |
6 | export default {
7 | name: 'ArrayToText',
8 |
9 | setup() {
10 | const isChange = ref(false)
11 | window.isChange = isChange
12 |
13 | return {
14 | isChange,
15 | }
16 | },
17 |
18 | render() {
19 | const self = this
20 | return self.isChange === true
21 | ? h('div', {}, nextChildren)
22 | : h('div', {}, prevChildren)
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/packages/vue/src/index.ts:
--------------------------------------------------------------------------------
1 | import { baseCompile } from '@vue3-mini/compiler-core'
2 | import * as runtimeDom from '@vue3-mini/runtime-dom'
3 | import { registerRuntimeCompiler } from '@vue3-mini/runtime-dom'
4 | // 出口文件
5 | export * from '@vue3-mini/runtime-dom'
6 |
7 | function compileToFunction(template) {
8 | const { code } = baseCompile(template)
9 | const render = new Function('Vue', code)(runtimeDom)
10 | console.log('生成render函数---------------------------------------', render)
11 | return render
12 | }
13 |
14 | registerRuntimeCompiler(compileToFunction)
15 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../dist/vue3-mini.esm.js'
2 |
3 | export const Foo = {
4 | setup(props, { emit }) {
5 | const emitAdd = () => {
6 | console.log('emitAdd')
7 | emit('add', 1, 2)
8 | emit('add-foo', 1, 2)
9 | }
10 | return {
11 | emitAdd,
12 | }
13 | },
14 |
15 | render() {
16 | const btn = h('button',
17 | {
18 | onClick: this.emitAdd,
19 | },
20 | 'emitAdd')
21 | const foo = h('p', {}, 'foo')
22 | return h('div', {}, [foo, btn])
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from './vnode'
2 |
3 | export function createAppAPI(render) {
4 | return function createApp(rootComponent) {
5 | // rootComponent根组件
6 | return {
7 | mount(rootContainer) {
8 | // component -> vnode
9 | // 所有的逻辑操作 都会基于vnode
10 | // 创建根组件vnode
11 | console.log('基于根组件创建 vnode')
12 | const vnode = createVNode(rootComponent)
13 | // 渲染
14 | console.log('调用 render,基于 vnode 进行开箱')
15 | render(vnode, rootContainer)
16 | },
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/compile.ts:
--------------------------------------------------------------------------------
1 | import { generate } from './codegen'
2 | import { baseParse } from './parse'
3 | import { transform } from './transform'
4 | import { transformElement } from './transforms/transformElement'
5 | import { transformExpression } from './transforms/transformExpression'
6 | import { transformText } from './transforms/transformText'
7 |
8 | export function baseCompile(template) {
9 | const ast = baseParse(template)
10 | transform(ast, {
11 | nodeTransforms: [transformExpression, transformElement, transformText],
12 | })
13 | return generate(ast)
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vue/examples/compiler-base/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/packages/vue/examples/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
22 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../dist/vue3-mini.esm.js'
2 | import ArrayToArray from './ArrayToArray.js'
3 |
4 | export const App = {
5 | name: 'App',
6 |
7 | setup() {
8 | return {}
9 | },
10 |
11 | render() {
12 | return h('div', { tId: 1 }, [
13 | h('p', {}, '主页'),
14 | // 老的是array, 新的是text
15 | // h(ArrayToText),
16 | // 老的是text, 新的是text
17 | // h(TextToText),
18 | // 老的是text, 新的是array
19 | // h(TextToArray),
20 | // 老的是array, 新的是array
21 | h(ArrayToArray),
22 | ])
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from '../../dist/vue3-mini.esm.js'
2 |
3 | const nextChildren = 'newChildren'
4 | const prevChildren = [h('div', {}, 'A'), h('div', {}, 'B')]
5 |
6 | export default {
7 | name: 'ArrayToText',
8 |
9 | setup() {
10 | const isChange = ref(false)
11 | window.isChange = isChange
12 |
13 | return {
14 | isChange,
15 | }
16 | },
17 |
18 | render() {
19 | const self = this
20 | return self.isChange === true
21 | ? h('div', {}, nextChildren)
22 | : h('div', {}, prevChildren)
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from '../../dist/vue3-mini.esm.js'
2 |
3 | const prevChildren = 'newChildren'
4 | const nextChildren = [h('div', {}, 'A'), h('div', {}, 'B')]
5 |
6 | export default {
7 | name: 'ArrayToText',
8 |
9 | setup() {
10 | const isChange = ref(false)
11 | window.isChange = isChange
12 |
13 | return {
14 | isChange,
15 | }
16 | },
17 |
18 | render() {
19 | const self = this
20 | return self.isChange === true
21 | ? h('div', {}, nextChildren)
22 | : h('div', {}, prevChildren)
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/transforms/transformElement.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes, createVNodeCall } from '../ast'
2 |
3 | export function transformElement(node, context) {
4 | if (node.type === NodeTypes.ELEMENT) {
5 | return () => {
6 | // 中间处理层
7 | // tag
8 | const vnodeTag = `'${node.tag}'`
9 | // props
10 | let vnodeProps
11 | const children = node.children
12 | const vnodeChildren = children[0]
13 |
14 | node.codegenNode = createVNodeCall(
15 | context,
16 | vnodeTag,
17 | vnodeProps,
18 | vnodeChildren,
19 | )
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { createTextVnode, h } from '../../dist/vue3-mini.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 | const foo = h(
9 | Foo,
10 | {},
11 | {
12 | header: ({ age }) => [
13 | h('p', {}, `header${age}`),
14 | createTextVnode('你好'),
15 | ],
16 | footer: () => h('p', {}, 'footer'),
17 | },
18 | )
19 |
20 | return h('div', {}, [app, foo])
21 | },
22 |
23 | setup() {
24 | return {}
25 | },
26 | }
27 |
--------------------------------------------------------------------------------
/packages/compiler-core/__test__/__snapshots__/codegen.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1
2 |
3 | exports[`codegen > element 1`] = `
4 | "const { toDisplayString:_toDisplayString, createElementVNode:_createElementVNode } = Vue
5 | return function render(_ctx, _cache){return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.message))}"
6 | `;
7 |
8 | exports[`codegen > interpolation 1`] = `
9 | "const { toDisplayString:_toDisplayString } = Vue
10 | return function render(_ctx, _cache){return _toDisplayString(_ctx.message)}"
11 | `;
12 |
13 | exports[`codegen > string 1`] = `
14 | "
15 | return function render(_ctx, _cache){return 'hi'}"
16 | `;
17 |
--------------------------------------------------------------------------------
/packages/reactivity/__test__/shallowReadonly.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest'
2 | import { isReadonly, shallowReadonly } from '../src/index'
3 |
4 | describe('shallowReadonly', () => {
5 | it('should not make non-reactive properties reactive', () => {
6 | const props = shallowReadonly({ n: { foo: 1 } })
7 | expect(isReadonly(props)).toBe(true)
8 | expect(isReadonly(props.n)).toBe(false)
9 | })
10 | it('warn when call set', () => {
11 | console.warn = vi.fn()
12 | const user = shallowReadonly({
13 | age: 10,
14 | })
15 | user.age = 11
16 | expect(console.warn).toBeCalledTimes(1)
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerKey } from '@vue3-mini/shared'
2 |
3 | /**
4 | * description
5 | * emit方法
6 | *
7 | * @param instance 当前实例
8 | * @param event 事件名
9 | * @param args 传递的参数
10 | * @return void
11 | *
12 | */
13 | export function emit(instance, event, ...args) {
14 | // 如果要调用emit 则props里需要有onXXX这个Prop
15 | // 找到instance.props -> 寻找event
16 | const { props } = instance
17 |
18 | // 格式化event名称 将add-two / addTwo转化成 onAddTwo
19 | const handlerName = toHandlerKey(camelize(event))
20 | // 在Props里寻找转化后的propName
21 | const handler = props[handlerName]
22 | // 如果寻找到了就去执行
23 | handler && handler(...args)
24 | }
25 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/apiWatch.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from './../../reactivity/src/effect'
2 | import { queuePreFlushCb } from './scheduler'
3 |
4 | export function watchEffect(source) {
5 | function job() {
6 | effect.run()
7 | }
8 |
9 | let cleanup
10 |
11 | const onCleanup = (fn) => {
12 | cleanup = effect.onStop = () => {
13 | fn()
14 | }
15 | }
16 | function getter() {
17 | // 初始化的时候不调用
18 | if (cleanup)
19 | cleanup()
20 |
21 | source(onCleanup)
22 | }
23 |
24 | const effect = new ReactiveEffect(getter, () => {
25 | queuePreFlushCb(job)
26 | })
27 |
28 | effect.run()
29 |
30 | return () => {
31 | effect.stop()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/compiler-core/__test__/transform.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest'
2 | import { NodeTypes } from '../src/ast'
3 | import { baseParse } from '../src/parse'
4 | import { transform } from '../src/transform'
5 |
6 | describe('transform', () => {
7 | it('happy path', () => {
8 | const ast = baseParse('hi,{{message}}
')
9 |
10 | const plugin = (node) => {
11 | if (node.type === NodeTypes.TEXT)
12 | node.content = `${node.content} mini-vue`
13 | }
14 |
15 | transform(ast, {
16 | nodeTransforms: [plugin],
17 | })
18 |
19 | const nodeText = ast.children[0].children[0]
20 | expect(nodeText.content).toBe('hi, mini-vue')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/packages/vue/examples/helloWorld/App.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../dist/vue3-mini.esm.js'
2 | import { Foo } from './Foo.js'
3 |
4 | window.self = null
5 | export const App = {
6 | render() {
7 | window.self = this
8 | return h(
9 | 'div',
10 | {
11 | id: 'root',
12 | class: ['red', 'hard'],
13 |
14 | onClick() {
15 | console.log('click')
16 | },
17 | onMouseDown() {
18 | console.log('down')
19 | },
20 | },
21 | [h('div', {}, `hi${this.msg}`), h(Foo, { count: 1 })],
22 | // 'hello' + this.msg
23 | // [
24 | // h('p', { class: 'red' }, 'HELLO'),
25 | // h('p', { class: 'blue' }, 'WORLD')
26 | // ]
27 | )
28 | },
29 |
30 | setup() {
31 | // composition api
32 | return {
33 | msg: 'hello world',
34 | }
35 | },
36 | }
37 |
--------------------------------------------------------------------------------
/packages/vue/examples/nextTicker/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | getCurrentInstance,
3 | h,
4 | nextTick,
5 | ref,
6 | } from '../../dist/vue3-mini.esm.js'
7 |
8 | export const App = {
9 | name: 'App',
10 | setup() {
11 | const count = ref(1)
12 | const instance = getCurrentInstance()
13 |
14 | function onClick() {
15 | for (let i = 0; i < 100; i++) {
16 | console.log('update')
17 | count.value = i
18 | }
19 |
20 | // 拿不到视图的变更
21 | console.log(instance)
22 | nextTick(() => {
23 | console.log(instance)
24 | })
25 | }
26 |
27 | return {
28 | onClick,
29 | count,
30 | }
31 | },
32 | render() {
33 | const button = h('button', { onClick: this.onClick }, 'update')
34 | const p = h('p', {}, `count:${this.count}`)
35 |
36 | return h('div', {}, [button, p])
37 | },
38 | }
39 |
--------------------------------------------------------------------------------
/packages/reactivity/src/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from './effect'
2 |
3 | class ComputedRefImpl {
4 | private _getter: any
5 | private _dirty = true // 初始化dirty时为true
6 | private _value: any
7 | private _effect: any
8 | constructor(getter) {
9 | this._getter = getter
10 | // 当依赖的响应式对象的值发生改变的时候 会通过scheduler 将dirty设置成true
11 | this._effect = new ReactiveEffect(getter, () => {
12 | if (!this._dirty)
13 | this._dirty = true
14 | })
15 | }
16 |
17 | get value() {
18 | // 如果dirty为true 则说明依赖发生过改变 或初始化状态
19 | // 这个时候需要重新执行effect.run() 获取最新的值
20 | if (this._dirty) {
21 | this._dirty = false
22 | // 第一次run的时候会触发响应式对象 get -> 收集依赖
23 | this._value = this._effect.run()
24 | }
25 | return this._value
26 | }
27 | }
28 |
29 | export function computed(getter) {
30 | return new ComputedRefImpl(getter)
31 | }
32 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentsSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from '@vue3-mini/shared'
2 | export function initSlots(instance, children) {
3 | // 初始化slots
4 | const { vnode } = instance
5 | console.log('初始化 slots')
6 | // 判断当前节点类型是否为slotChildren
7 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN)
8 | normalizeObjectSlots(children, instance.slots)
9 | }
10 |
11 | function normalizeSlotValue(value) {
12 | // 把 function 返回的值转换成 array ,这样 slot 就可以支持多个元素了
13 | return Array.isArray(value) ? value : [value]
14 | }
15 |
16 | function normalizeObjectSlots(children, slots) {
17 | for (const key in children) {
18 | const value = children[key]
19 | // 父组件是 header: (age) => h('div', {}, age) 传参
20 | // value需要执行下value(props)才能得到返回结果
21 | // 在renderSlots时需要用函数调用的方式获取prop 所以slots[key]也是一个函数的形式
22 | slots[key] = props => normalizeSlotValue(value(props))
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from '@vue3-mini/shared'
2 |
3 | const publicPropertiesMap = {
4 | $el: i => i.vnode.el, // 当前组件vnode实例
5 | $slots: i => i.slots, // 当前组件slots
6 | $props: i => i.props,
7 | }
8 |
9 | export const PublicInstanceProxyHandlers = {
10 | get({ _: instance }, key) {
11 | const { setupState, props } = instance
12 | console.log(`触发 proxy hook , key -> : ${key}`)
13 |
14 | // 通过this可以获取setup返回值或prop
15 | if (key[0] !== '$') {
16 | // 说明不是访问 public api
17 | // 先检测访问的 key 是否存在于 setupState 中, 是的话直接返回
18 | if (hasOwn(setupState, key))
19 | return setupState[key]
20 | else if (hasOwn(props, key))
21 | return props[key]
22 | }
23 | const publicGetter = publicPropertiesMap[key]
24 | // 其他代理对象 $el等
25 | if (publicGetter)
26 | return publicGetter(instance)
27 | },
28 | }
29 |
--------------------------------------------------------------------------------
/packages/vue/examples/customRenderer/main.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import { createRenderer } from '../../dist/vue3-mini.esm.js'
3 | import { App } from './App.js'
4 |
5 | console.log(PIXI)
6 | const game = new PIXI.Application({
7 | width: 500,
8 | height: 500,
9 | })
10 |
11 | document.body.append(game.view)
12 |
13 | const renderer = createRenderer({
14 | createElement(type) {
15 | if (type === 'rect') {
16 | const rect = new PIXI.Graphics()
17 | rect.beginFill(0xFF0000)
18 | rect.drawRect(0, 0, 100, 100)
19 | rect.endFill()
20 | return rect
21 | }
22 | },
23 | patchProp(el, key, val) {
24 | el[key] = val
25 | },
26 |
27 | insert(el, parent) {
28 | parent.addChild(el)
29 | },
30 | })
31 |
32 | renderer.createApp(App).mount(game.stage)
33 |
34 | // const rootContainer = document.querySelector('#app')
35 | // createApp(App).mount(rootContainer)
36 |
--------------------------------------------------------------------------------
/packages/reactivity/__test__/reactive.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest'
2 | import { isProxy, isReactive, reactive } from '../src/index'
3 |
4 | describe('reactive', () => {
5 | it('happy path', () => {
6 | const original = {
7 | foo: 1,
8 | }
9 | const observed = reactive(original)
10 | expect(observed).not.toBe(original)
11 | expect(observed.foo).toBe(1)
12 | expect(isReactive(observed)).toBe(true)
13 | expect(isReactive(original)).toBe(false)
14 | expect(isProxy(observed)).toBe(true)
15 | })
16 |
17 | it('nested reactive', () => {
18 | const original = {
19 | nested: {
20 | foo: 1,
21 | },
22 | array: [{ bar: 2 }],
23 | }
24 |
25 | const observed = reactive(original)
26 | expect(isReactive(observed.nested)).toBe(true)
27 | expect(isReactive(observed.array)).toBe(true)
28 | expect(isReactive(observed.array[0])).toBe(true)
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/packages/reactivity/__test__/readonly.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest'
2 | import { isProxy, isReadonly, readonly } from '../src/index'
3 |
4 | describe('readonly', () => {
5 | it('should make nested values readonly', () => {
6 | const original = {
7 | foo: 1,
8 | bar: {
9 | baz: 2,
10 | },
11 | }
12 | const wrapped = readonly(original)
13 | expect(wrapped).not.toBe(original)
14 | expect(isReadonly(wrapped)).toBe(true)
15 | expect(isReadonly(original)).toBe(false)
16 | expect(isReadonly(wrapped.bar)).toBe(true)
17 | expect(isReadonly(original.bar)).toBe(false)
18 | expect(isProxy(wrapped)).toBe(true)
19 | expect(wrapped.foo).toBe(1)
20 | })
21 |
22 | it('warn when call set', () => {
23 | console.warn = vi.fn()
24 | const user = readonly({
25 | age: 10,
26 | })
27 | user.age = 11
28 | expect(console.warn).toBeCalledTimes(1)
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3-proxy",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "cuiyiming1998",
6 | "license": "ISC",
7 | "homepage": "https://github.com/cuiyiming1998/Vue3-mini/#readme",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/cuiyiming1998/Vue3-mini.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/cuiyiming1998/Vue3-mini/issues"
14 | },
15 | "keywords": [],
16 | "main": "lib/vue3-mini.cjs.js",
17 | "module": "lib/vue3-mini.esm.js",
18 | "scripts": {
19 | "test": "vitest",
20 | "build": "rollup -c rollup.config.js",
21 | "lint": "eslint .",
22 | "lint:fix": "eslint . --fix"
23 | },
24 | "devDependencies": {
25 | "@antfu/eslint-config": "^0.36.0",
26 | "@rollup/plugin-typescript": "^8.4.0",
27 | "eslint": "^8.36.0",
28 | "rollup": "^2.79.0",
29 | "tslib": "^2.4.0",
30 | "typescript": "^4.7.3",
31 | "vitest": "^0.22.1"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './toDisplayString'
2 | export * from './shapeFlags'
3 |
4 | export const extend = Object.assign
5 | export const EMPTY_OBJ = {}
6 |
7 | export const isObject = (val) => {
8 | return val !== null && typeof val === 'object'
9 | }
10 |
11 | export const hasChanged = (value, newValue) => {
12 | return !Object.is(value, newValue)
13 | }
14 |
15 | export const hasOwn = (val, key) => {
16 | return Object.prototype.hasOwnProperty.call(val, key)
17 | }
18 |
19 | // 横杠转驼峰add-foo -> addFoo
20 | export const camelize = (str: string) => {
21 | return str.replace(/-(\w)/g, (_, c: string) => {
22 | return c ? c.toUpperCase() : ''
23 | })
24 | }
25 |
26 | // 首字母大写
27 | export const capitalize = (str: string) => {
28 | return str.charAt(0).toUpperCase() + str.slice(1)
29 | }
30 |
31 | // 驼峰加on
32 | export const toHandlerKey = (str: string) => {
33 | return str ? `on${capitalize(str)}` : ''
34 | }
35 |
36 | export const isString = (val) => {
37 | return typeof val === 'string'
38 | }
39 |
--------------------------------------------------------------------------------
/packages/vue/examples/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from '../../dist/vue3-mini.esm.js'
2 | import Child from './Child.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | setup() {
7 | const msg = ref('123')
8 | const count = ref(1)
9 |
10 | window.msg = msg
11 |
12 | const changeChildProps = () => {
13 | msg.value = '456'
14 | }
15 |
16 | const changeCount = () => {
17 | count.value++
18 | }
19 |
20 | return { msg, changeChildProps, changeCount, count }
21 | },
22 |
23 | render() {
24 | return h('div', {}, [
25 | h('div', {}, '你好'),
26 | h(
27 | 'button',
28 | {
29 | onClick: this.changeChildProps,
30 | },
31 | 'change child props',
32 | ),
33 | h(Child, {
34 | msg: this.msg,
35 | }),
36 | h(
37 | 'button',
38 | {
39 | onClick: this.changeCount,
40 | },
41 | 'change self count',
42 | ),
43 | h('p', {}, `count: ${this.count}`),
44 | ])
45 | },
46 | }
47 |
--------------------------------------------------------------------------------
/packages/reactivity/src/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from '@vue3-mini/shared'
2 | import { mutableHandlers, readonlyHandlers, shallowReadonlyHandlers } from './baseHandlers'
3 |
4 | export const enum ReactiveFlags {
5 | IS_REACTIVE = '__v_isReactive',
6 | IS_READONLY = '__v_isReadonly',
7 | }
8 |
9 | export function reactive(raw) {
10 | return createActiveObject(raw, mutableHandlers)
11 | }
12 |
13 | export function isReactive(value) {
14 | return !!value[ReactiveFlags.IS_REACTIVE]
15 | }
16 |
17 | export function readonly(raw) {
18 | return createActiveObject(raw, readonlyHandlers)
19 | }
20 |
21 | export function shallowReadonly(raw) {
22 | return createActiveObject(raw, shallowReadonlyHandlers)
23 | }
24 |
25 | export function isReadonly(value) {
26 | return !!value[ReactiveFlags.IS_READONLY]
27 | }
28 |
29 | export function isProxy(value) {
30 | return isReactive(value) || isReadonly(value)
31 | }
32 |
33 | export function createActiveObject(target: any, baseHandlers) {
34 | if (!isObject(target)) {
35 | console.warn(`target ${target} 必须是一个对象`)
36 | return
37 | }
38 | return new Proxy(target, baseHandlers)
39 | }
40 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/transforms/transformText.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from '../ast'
2 | import { isText } from '../utils'
3 |
4 | export function transformText(node) {
5 | let currentContainer
6 | if (node.type === NodeTypes.ELEMENT) {
7 | return () => {
8 | const { children } = node
9 | for (let i = 0; i < children.length; i++) {
10 | const child = children[i]
11 | if (isText(child)) {
12 | for (let j = i + 1; j < children.length; j++) {
13 | const next = children[j]
14 | if (isText(next)) {
15 | if (!currentContainer) {
16 | currentContainer = children[i] = {
17 | type: NodeTypes.COMPOUND_EXPRESSION,
18 | children: [child],
19 | }
20 | }
21 | currentContainer.children.push(' + ')
22 | currentContainer.children.push(next)
23 | children.splice(j, 1)
24 | j--
25 | }
26 | else {
27 | currentContainer = undefined
28 | break
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/scheduler.ts:
--------------------------------------------------------------------------------
1 | const queue: any[] = []
2 | const activePrefFlushCbs: any[] = []
3 |
4 | const p = Promise.resolve()
5 | let isFlushPending = false
6 |
7 | export function nextTick(fn?) {
8 | return fn ? p.then(fn) : p
9 | }
10 |
11 | export function queueJobs(job) {
12 | if (!queue.includes(job))
13 | queue.push(job)
14 |
15 | // 执行所有的job
16 | queueFlush()
17 | }
18 |
19 | function queueFlush() {
20 | // 如果同时触发了两个组件的更新的话
21 | // 这里就会触发两次 then (微任务逻辑)
22 | // 但是着是没有必要的
23 | // 我们只需要触发一次即可处理完所有的 job 调用
24 | // 所以需要判断一下 如果已经触发过 nextTick 了
25 | // 那么后面就不需要再次触发一次 nextTick 逻辑了
26 | if (isFlushPending)
27 | return
28 |
29 | isFlushPending = true
30 | // 创建微任务
31 | nextTick(flushJobs)
32 | }
33 |
34 | export function queuePreFlushCb(job) {
35 | activePrefFlushCbs.push(job)
36 | queueFlush()
37 | }
38 |
39 | function flushJobs() {
40 | // isFlushPending 置为false 重新记录job
41 | isFlushPending = false
42 |
43 | flushPreFlushCbs()
44 |
45 | let job
46 |
47 | while ((job = queue.shift()))
48 | job && job()
49 | }
50 | function flushPreFlushCbs() {
51 | for (let i = 0; i < activePrefFlushCbs.length; i++)
52 | activePrefFlushCbs[i]()
53 | }
54 |
--------------------------------------------------------------------------------
/packages/compiler-core/__test__/codegen.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest'
2 | import { generate } from '../src/codegen'
3 | import { baseParse } from '../src/parse'
4 | import { transform } from '../src/transform'
5 | import { transformElement } from '../src/transforms/transformElement'
6 | import { transformExpression } from '../src/transforms/transformExpression'
7 | import { transformText } from '../src/transforms/transformText'
8 |
9 | describe('codegen', () => {
10 | it('string', () => {
11 | const ast = baseParse('hi')
12 | transform(ast)
13 | const { code } = generate(ast)
14 | expect(code).toMatchSnapshot()
15 | })
16 |
17 | it('interpolation', () => {
18 | const ast = baseParse('{{message}}')
19 | transform(ast, {
20 | nodeTransforms: [transformExpression],
21 | })
22 | const { code } = generate(ast)
23 | expect(code).toMatchSnapshot()
24 | })
25 |
26 | it('element', () => {
27 | const ast = baseParse('hi,{{message}}
')
28 | transform(ast, {
29 | nodeTransforms: [transformExpression, transformElement, transformText],
30 | })
31 | const { code } = generate(ast)
32 | expect(code).toMatchSnapshot()
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/componentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | export function shouldUpdateComponent(prevVNode, nextVNode) {
2 | const { props: prevProps } = prevVNode
3 | const { props: nextProps } = nextVNode
4 | // const emits = component!.emitsOptions;
5 |
6 | // 这里主要是检测组件的 props
7 | // 核心:只要是 props 发生改变了,那么这个 component 就需要更新
8 |
9 | // 1. props 没有变化,那么不需要更新
10 | if (prevProps === nextProps)
11 | return false
12 |
13 | // 如果之前没有 props,那么就需要看看现在有没有 props 了
14 | // 所以这里基于 nextProps 的值来决定是否更新
15 | if (!prevProps)
16 | return !!nextProps
17 |
18 | // 之前有值,现在没值,那么肯定需要更新
19 | if (!nextProps)
20 | return true
21 |
22 | // 以上都是比较明显的可以知道 props 是否是变化的
23 | // 在 hasPropsChanged 会做更细致的对比检测
24 | return hasPropsChanged(prevProps, nextProps)
25 | }
26 |
27 | function hasPropsChanged(prevProps, nextProps): boolean {
28 | // 依次对比每一个 props.key
29 |
30 | // 提前对比一下 length ,length 不一致肯定是需要更新的
31 | const nextKeys = Object.keys(nextProps)
32 | if (nextKeys.length !== Object.keys(prevProps).length)
33 | return true
34 |
35 | // 只要现在的 prop 和之前的 prop 不一样那么就需要更新
36 | for (let i = 0; i < nextKeys.length; i++) {
37 | const key = nextKeys[i]
38 | if (nextProps[key] !== prevProps[key])
39 | return true
40 | }
41 | return false
42 | }
43 |
--------------------------------------------------------------------------------
/packages/reactivity/__test__/computed.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest'
2 | import { computed, reactive } from '../src/index'
3 |
4 | describe('computed', () => {
5 | it('happy path', () => {
6 | const user = reactive({
7 | age: 1,
8 | })
9 |
10 | const age = computed(() => {
11 | return user.age
12 | })
13 |
14 | expect(age.value).toBe(1)
15 | })
16 |
17 | it('should compute lazily', () => {
18 | const value = reactive({
19 | foo: 1,
20 | })
21 | const getter = vi.fn(() => {
22 | return value.foo
23 | })
24 | const cValue = computed(getter)
25 |
26 | // lazy
27 | expect(getter).not.toHaveBeenCalled()
28 | expect(cValue.value).toBe(1)
29 | expect(getter).toHaveBeenCalledTimes(1)
30 |
31 | // should not compute again
32 | cValue.value
33 | expect(getter).toHaveBeenCalledTimes(1)
34 |
35 | // should not compute until needed
36 | value.foo = 2 // 这里会调用trigger
37 | expect(getter).toHaveBeenCalledTimes(1)
38 |
39 | // now it should compute
40 | expect(cValue.value).toBe(2)
41 | expect(getter).toHaveBeenCalledTimes(2)
42 |
43 | // should not compute again
44 | cValue.value
45 | expect(getter).toHaveBeenCalledTimes(2)
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/packages/vue/examples/apiInject/App.js:
--------------------------------------------------------------------------------
1 | import { h, inject, provide } from '../../dist/vue3-mini.esm.js'
2 |
3 | const Provider = {
4 | name: 'Provider',
5 | setup() {
6 | provide('foo', 'fooVal')
7 | provide('bar', 'barVal')
8 | },
9 | render() {
10 | return h('div', {}, [h('p', {}, 'Provider'), h(ProviderTwo)])
11 | },
12 | }
13 |
14 | const ProviderTwo = {
15 | name: 'Provider',
16 | setup() {
17 | provide('foo', 'fooTwo')
18 | const foo = inject('foo')
19 | return { foo }
20 | },
21 | render() {
22 | return h('div', {}, [h('p', {}, `ProviderTwo foo: ${this.foo}`), h(Consumer)])
23 | },
24 | }
25 |
26 | const Consumer = {
27 | name: 'Consumer',
28 | setup() {
29 | const foo = inject('foo')
30 | const bar = inject('bar')
31 | const baz = inject('baz', () => 'bazDefault')
32 | return {
33 | foo,
34 | bar,
35 | baz,
36 | }
37 | },
38 |
39 | template: 'Consumer: xxx
',
40 |
41 | render() {
42 | return h('div', {}, `Consumer: - ${this.foo} - ${this.bar} - ${this.baz}`)
43 | },
44 | }
45 |
46 | export const App = {
47 | name: 'App',
48 | render() {
49 | return h('div', {}, [h('p', {}, 'apiInject'), h(Provider)])
50 | },
51 |
52 | setup() {
53 | return {}
54 | },
55 | }
56 |
--------------------------------------------------------------------------------
/packages/vue/examples/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from '../../dist/vue3-mini.esm.js'
2 |
3 | export const App = {
4 | name: 'App',
5 |
6 | setup() {
7 | const count = ref(0)
8 |
9 | const onClick = () => {
10 | count.value++
11 | }
12 |
13 | const props = ref({
14 | foo: 'foo',
15 | bar: 'bar',
16 | })
17 |
18 | const onChangePropsDemo1 = () => {
19 | props.value.foo = 'new-foo'
20 | }
21 |
22 | const onChangePropsDemo2 = () => {
23 | props.value.foo = undefined
24 | }
25 |
26 | const onChangePropsDemo3 = () => {
27 | props.value = {
28 | foo: 'foo',
29 | }
30 | }
31 |
32 | return {
33 | count,
34 | onClick,
35 | onChangePropsDemo1,
36 | onChangePropsDemo2,
37 | onChangePropsDemo3,
38 | props,
39 | }
40 | },
41 |
42 | render() {
43 | return h('div', { id: 'root', ...this.props }, [
44 | h('button', { onClick: this.onClick }, 'click'),
45 | h('button', { onClick: this.onChangePropsDemo1 }, 'changeProps - 值改变了 - 修改'),
46 | h('button', { onClick: this.onChangePropsDemo2 }, 'changeProps - 值变成了undefined - 删除'),
47 | h('button', { onClick: this.onChangePropsDemo3 }, 'changeProps - key在新的里面没有了 - 删除'),
48 | ])
49 | },
50 | }
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Vue3-mini
3 |
4 | 手写一遍Vue3!
5 |
6 | #### runtime-core
7 |
8 | - [x] 支持组件类型
9 | - [x] 支持 element 类型
10 | - [x] 初始化 props
11 | - [x] setup 可获取 props 和 context
12 | - [x] 支持 component emit
13 | - [x] 支持 proxy
14 | - [x] 可以在 render 函数中获取 setup 返回的对象
15 | - [x] nextTick 的实现
16 | - [x] 支持 getCurrentInstance
17 | - [x] 支持 provide/inject
18 | - [x] 支持最基础的 slots
19 | - [x] 支持 Text 类型节点
20 | - [x] 支持 $el api
21 | - [x] 支持 watchEffect
22 |
23 |
24 | #### reactivity
25 |
26 | 目标是用自己的 reactivity 支持现有的 demo 运行
27 |
28 | - [x] reactive 的实现
29 | - [x] ref 的实现
30 | - [x] readonly 的实现
31 | - [x] computed 的实现
32 | - [x] track 依赖收集
33 | - [x] trigger 触发依赖
34 | - [x] 支持 isReactive
35 | - [x] 支持嵌套 reactive
36 | - [x] 支持 toRaw
37 | - [x] 支持 effect.scheduler
38 | - [x] 支持 effect.stop
39 | - [x] 支持 isReadonly
40 | - [x] 支持 isProxy
41 | - [x] 支持 shallowReadonly
42 | - [x] 支持 proxyRefs
43 |
44 | ### compiler-core
45 | - [x] 解析插值
46 | - [x] 解析 element
47 | - [x] 解析 text
48 |
49 | ### runtime-dom
50 | - [x] 支持 custom renderer
51 |
52 | ### runtime-test
53 | - [x] 支持测试 runtime-core 的逻辑
54 |
55 | ### infrastructure
56 | - [x] support monorepo with pnpm
57 | ### build
58 |
59 | ```shell
60 | pnpm build
61 | ```
62 |
63 | ### example
64 |
65 | 通过 server 的方式打开 packages/vue/example/\* 下的 index.html 即可
66 |
67 | > 推荐使用 [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
68 |
--------------------------------------------------------------------------------
/packages/runtime-core/__tests__/apiWatch.test.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from '@vue3-mini/reactivity'
2 | import { describe, expect, it, vi } from 'vitest'
3 | import { nextTick } from '../src/scheduler'
4 | import { watchEffect } from '../src/apiWatch'
5 |
6 | describe('api: watch', () => {
7 | it('effect', async () => {
8 | const state = reactive({ count: 0 })
9 | let dummy
10 | watchEffect(() => {
11 | dummy = state.count
12 | })
13 | expect(dummy).toBe(0)
14 |
15 | state.count++
16 | await nextTick()
17 | expect(dummy).toBe(1)
18 | })
19 |
20 | it('stopping the watcher (effect)', async () => {
21 | const state = reactive({ count: 0 })
22 | let dummy
23 | const stop: any = watchEffect(() => {
24 | dummy = state.count
25 | })
26 | expect(dummy).toBe(0)
27 |
28 | stop()
29 | state.count++
30 | await nextTick()
31 | // should not update
32 | expect(dummy).toBe(0)
33 | })
34 |
35 | it('cleanup registration (effect)', async () => {
36 | const state = reactive({ count: 0 })
37 | const cleanup = vi.fn()
38 | let dummy
39 | const stop: any = watchEffect((onCleanup) => {
40 | onCleanup(cleanup)
41 | dummy = state.count
42 | })
43 | expect(dummy).toBe(0)
44 |
45 | state.count++
46 | await nextTick()
47 | expect(cleanup).toHaveBeenCalledTimes(1)
48 | expect(dummy).toBe(1)
49 |
50 | stop()
51 | expect(cleanup).toHaveBeenCalledTimes(2)
52 | })
53 | })
54 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from '@vue3-mini/shared'
2 |
3 | export const Fragment = Symbol('Fragment')
4 | export const Text = Symbol('Text')
5 |
6 | export {
7 | createVNode as createElementVNode,
8 | }
9 | /*
10 | type -> 'div' / 'span'
11 | props -> attribute
12 | children
13 | */
14 | export function createVNode(type, props?, children?) {
15 | const vnode = {
16 | type, // 组件类型
17 | props, // props
18 | component: null,
19 | children, // 孩子节点, 可以是text(文本节点) 或者 array(嵌套子节点)
20 | shapeFlag: getShapeFlag(type), // 类型标识
21 | key: props && props.key,
22 | el: null, // 当前实例
23 | }
24 | if (typeof children === 'string')
25 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN
26 | else if (Array.isArray(children))
27 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN
28 |
29 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
30 | if (typeof children === 'object') {
31 | // 如果是组件类型 且children为object
32 | // 则当前节点属于slotChildren
33 | // 暂时只有 element 类型和 component 类型的组件
34 | // 所以这里除了 element ,那么只要是 component 的话,那么children 肯定就是 slots 了
35 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN
36 | }
37 | }
38 | return vnode
39 | }
40 |
41 | export function createTextVnode(text: string) {
42 | return createVNode(Text, {}, text)
43 | }
44 |
45 | function getShapeFlag(type) {
46 | // 如果类型是string则为element类型
47 | // 否则为component类型
48 | return typeof type === 'string'
49 | ? ShapeFlags.ELEMENT
50 | : ShapeFlags.STATEFUL_COMPONENT
51 | }
52 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from './component'
2 |
3 | export function provide(key, value) {
4 | // 存
5 | // 获取当前组件实例
6 | const currentInstance: any = getCurrentInstance()
7 |
8 | if (currentInstance) {
9 | let { provides } = currentInstance
10 |
11 | // 当父级 key 和 爷爷级别的 key 重复的时候,对于子组件来讲,需要取最近的父级别组件的值
12 | // 那这里的解决方案就是利用原型链来解决
13 | // provides 初始化的时候是在 createComponent 时处理的,当时是直接把 parent.provides 赋值给组件的 provides 的
14 | // 所以,如果说这里发现 provides 和 parentProvides 相等的话,那么就说明是第一次做 provide(对于当前组件来讲)
15 | // 我们就可以把 parent.provides 作为 currentInstance.provides 的原型重新赋值
16 | // 至于为什么不在 createComponent 的时候做这个处理,可能的好处是在这里初始化的话,是有个懒执行的效果(优化点,只有需要的时候在初始化)
17 |
18 | // 更改prototype 使provide能够嵌套使用
19 | // 如果parent没有 则向上寻找
20 | const parentProvides = currentInstance.parent.provides
21 | if (provides === parentProvides) {
22 | // 如果第一次 则通过继承父provides进行初始化
23 | provides = currentInstance.provides = Object.create(parentProvides)
24 | }
25 | provides[key] = value
26 | }
27 | }
28 |
29 | export function inject(key, defaultValue) {
30 | // 取
31 | const currentInstance: any = getCurrentInstance()
32 | if (currentInstance) {
33 | // 因为provides是层层继承的 所以只需找到parent的provide 即为所有的provides
34 | const parentProvides = currentInstance.parent.provides
35 | if (key in parentProvides) {
36 | return parentProvides[key]
37 | }
38 | else if (defaultValue) {
39 | // 如果没有找到 且有default 则赋值
40 | if (typeof defaultValue === 'function')
41 | return defaultValue()
42 |
43 | return defaultValue
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/helps/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { Fragment, createVNode } from '../vnode'
2 |
3 | /**
4 | * Compiler runtime helper for rendering ``
5 | * 用来 render slot 的
6 | * 之前是把 slot 的数据都存在 instance.slots 内(可以看 componentSlot.ts),
7 | * 这里就是取数据然后渲染出来的点
8 | * 这个是由 compiler 模块直接渲染出来的 -可以参看这个 demo https://vue-next-template-explorer.netlify.app/#%7B%22src%22%3A%22%3Cdiv%3E%5Cn%20%20%3Cslot%3E%3C%2Fslot%3E%5Cn%3C%2Fdiv%3E%22%2C%22ssr%22%3Afalse%2C%22options%22%3A%7B%22mode%22%3A%22module%22%2C%22prefixIdentifiers%22%3Afalse%2C%22optimizeImports%22%3Afalse%2C%22hoistStatic%22%3Afalse%2C%22cacheHandlers%22%3Afalse%2C%22scopeId%22%3Anull%2C%22inline%22%3Afalse%2C%22ssrCssVars%22%3A%22%7B%20color%20%7D%22%2C%22bindingMetadata%22%3A%7B%22TestComponent%22%3A%22setup-const%22%2C%22setupRef%22%3A%22setup-ref%22%2C%22setupConst%22%3A%22setup-const%22%2C%22setupLet%22%3A%22setup-let%22%2C%22setupMaybeRef%22%3A%22setup-maybe-ref%22%2C%22setupProp%22%3A%22props%22%2C%22vMySetupDir%22%3A%22setup-const%22%7D%2C%22optimizeBindings%22%3Afalse%7D%7D
9 | * 其最终目的就是在 render 函数中调用 renderSlot 取 instance.slots 内的数据
10 | * TODO 这里应该是一个返回一个 block ,但是暂时还没有支持 block ,所以这个暂时只需要返回一个 vnode 即可
11 | * 因为 block 的本质就是返回一个 vnode
12 | *
13 | * @private
14 | */
15 | export function renderSlots(slots, name, props) {
16 | // 寻找slots[name]
17 | // 如果存在 则使用Fragment创建虚拟节点 传入Props
18 | const slot = slots[name]
19 | if (slot) {
20 | // 因为 slot 是一个返回 vnode 的函数,我们只需要把这个结果返回出去即可
21 | // slot 就是一个函数,所以就可以把当前组件的一些数据给传出去,这个就是作用域插槽
22 | // 参数就是 props
23 | if (typeof slot === 'function')
24 | return createVNode(Fragment, {}, slot(props))
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/runtime-dom/src/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from '@vue3-mini/runtime-core'
2 |
3 | function createElement(type) {
4 | // 创建element
5 | return document.createElement(type)
6 | }
7 |
8 | function patchProp(el, key, prevVal, nextVal) {
9 | // 处理prop
10 | // 如果是on开头的 则添加eventListener
11 |
12 | console.log(`PatchProp 设置属性:${key} 值:${nextVal}`)
13 | console.log(`key: ${key} 之前的值是:${prevVal}`)
14 |
15 | const isOn = (key: string) => /^on[A-Z]/.test(key)
16 | if (isOn(key)) {
17 | const event = key.slice(2).toLocaleLowerCase()
18 | el.addEventListener(event, nextVal)
19 | }
20 | else {
21 | // 其余的是attribute
22 | if (undefined === nextVal || nextVal === null) {
23 | // 如果prop删除了(赋值undefined或null) 则removeAttr
24 | el.removeAttribute(key, nextVal)
25 | }
26 | else {
27 | el.setAttribute(key, nextVal)
28 | }
29 | }
30 | }
31 |
32 | function insert(child, parent, anchor) {
33 | // 添加到DOM的方法
34 | parent.insertBefore(child, anchor || null)
35 | }
36 |
37 | function remove(child) {
38 | // 删除的方法
39 | // 寻找parentNode
40 | // 通过parentNode.removeChild删除
41 | const parent = child.parentNode
42 | if (parent)
43 | parent.removeChild(child)
44 | }
45 |
46 | function setElementText(el, text) {
47 | // 设置text节点
48 | el.textContent = text
49 | }
50 |
51 | function createText(text) {
52 | return document.createTextNode(text)
53 | }
54 |
55 | function setText(node, text) {
56 | node.nodeValue = text
57 | }
58 |
59 | // 默认HTML DOM中的render
60 | const renderer: any = createRenderer({
61 | createElement,
62 | patchProp,
63 | insert,
64 | remove,
65 | setElementText,
66 | createText,
67 | setText,
68 | })
69 |
70 | export function createApp(...args) {
71 | // main.js使用的createApp在这里
72 | return renderer.createApp(...args)
73 | }
74 |
75 | export * from '@vue3-mini/runtime-core'
76 |
--------------------------------------------------------------------------------
/packages/reactivity/src/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from '@vue3-mini/shared'
2 | import { isTracking, reactive, trackEffects, triggerEffects } from './index'
3 |
4 | class RefImpl {
5 | private _value: any
6 | public dep
7 | public _rawValue
8 | public __v_ifRef = true
9 | constructor(value) {
10 | this._rawValue = value
11 | // 如果value是对象 --> 转成reactive
12 | this._value = convert(value)
13 | this.dep = new Set()
14 | }
15 |
16 | get value() {
17 | // 这时被effect包裹时 访问.value 会触发track收集依赖
18 | trackRefValue(this)
19 | return this._value
20 | }
21 |
22 | set value(newValue) {
23 | // 如果ref是对象 则_value为proxy 需要用一个raw值来代替判断
24 | if (hasChanged(newValue, this._rawValue)) {
25 | // 先修改value的值
26 | this._rawValue = newValue
27 | this._value = convert(newValue)
28 | // 执行trigger更新
29 | triggerEffects(this.dep)
30 | }
31 | }
32 | }
33 |
34 | function convert(value) {
35 | return isObject(value) ? reactive(value) : value
36 | }
37 |
38 | function trackRefValue(ref) {
39 | if (isTracking())
40 | trackEffects(ref.dep)
41 | }
42 |
43 | export function ref(value) {
44 | return new RefImpl(value)
45 | }
46 |
47 | export function isRef(ref) {
48 | return !!ref?.__v_ifRef
49 | }
50 |
51 | export function unRef(ref) {
52 | // 判断是否是ref 如果是返回ref.value 否则直接返回值
53 | return isRef(ref) ? ref.value : ref
54 | }
55 |
56 | export function proxyRefs(objectWithRefs) {
57 | return new Proxy(objectWithRefs, {
58 | get(target, key) {
59 | // get -> 如果是ref 返回.value
60 | return unRef(Reflect.get(target, key))
61 | },
62 |
63 | set(target, key, value) {
64 | // 如果原来的值是ref 且新值不是ref 则替换原来的值的.value
65 | if (isRef(target[key]) && !isRef(value))
66 | return target[key].value = value
67 | else
68 | return Reflect.set(target, key, value)
69 | },
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/transform.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from './ast'
2 | import { TO_DISPLAY_STRING } from './runtimeHelpers'
3 |
4 | export function transform(root, options = {}) {
5 | // 1. 遍历
6 | // 深度优先搜索ast树
7 | const context = createTransformContext(root, options)
8 | traverseNode(root, context)
9 | // 2. 修改
10 | // root.codegenNode
11 | createRootCodegen(root)
12 |
13 | root.helpers = [...context.helpers.keys()]
14 | }
15 |
16 | function traverseNode(node: any, context) {
17 | // 由外部传入的transforms来驱动
18 | const nodeTransforms = context.nodeTransforms
19 | const exitFns: any = []
20 | for (let i = 0; i < nodeTransforms.length; i++) {
21 | const transform = nodeTransforms[i]
22 | const onExit = transform(node, context)
23 | if (onExit)
24 | exitFns.push(onExit)
25 | }
26 |
27 | switch (node.type) {
28 | case NodeTypes.INTERPOLATION:
29 | context.helper(TO_DISPLAY_STRING)
30 | break
31 | case NodeTypes.ROOT:
32 | case NodeTypes.ELEMENT:
33 | traverseChildren(node, context)
34 | break
35 | default:
36 | break
37 | }
38 |
39 | let i = exitFns.length
40 | while (i--)
41 | exitFns[i]()
42 | }
43 | function traverseChildren(node: any, context: any) {
44 | const children = node.children
45 |
46 | for (let i = 0; i < children.length; i++) {
47 | const node = children[i]
48 | traverseNode(node, context)
49 | }
50 | }
51 |
52 | function createTransformContext(root: any, options: any) {
53 | const context = {
54 | root,
55 | nodeTransforms: options.nodeTransforms || [],
56 | helpers: new Map(),
57 | helper(key) {
58 | context.helpers.set(key, 1)
59 | },
60 | }
61 |
62 | return context
63 | }
64 | function createRootCodegen(root: any) {
65 | const child = root.children[0]
66 | if (child.type === NodeTypes.ELEMENT)
67 | root.codegenNode = child.codegenNode
68 | else
69 | root.codegenNode = root.children[0]
70 | }
71 |
--------------------------------------------------------------------------------
/packages/reactivity/__test__/ref.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest'
2 | import { effect, isRef, proxyRefs, reactive, ref, unRef } from '../src/index'
3 |
4 | describe('ref', () => {
5 | it('happy path', () => {
6 | const a = ref(1)
7 | expect(a.value).toBe(1)
8 | })
9 |
10 | it('should be reactive', () => {
11 | const a = ref(1)
12 | let dummy
13 | let calls = 0
14 | effect(() => {
15 | calls++
16 | dummy = a.value
17 | })
18 | expect(calls).toBe(1)
19 | expect(dummy).toBe(1)
20 | a.value = 2
21 | expect(calls).toBe(2)
22 | expect(dummy).toBe(2)
23 | a.value = 2
24 | expect(calls).toBe(2)
25 | expect(dummy).toBe(2)
26 | })
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 |
41 | it('isRef', () => {
42 | const a = ref(1)
43 | const user = reactive({
44 | age: 1,
45 | })
46 | expect(isRef(a)).toBe(true)
47 | expect(isRef(1)).toBe(false)
48 | expect(isRef(user)).toBe(false)
49 | })
50 |
51 | it('unRef', () => {
52 | const a = ref(1)
53 | const user = reactive({
54 | age: 1,
55 | })
56 | expect(unRef(a)).toBe(1)
57 | expect(unRef(1)).toBe(1)
58 | })
59 |
60 | it('proxyRefs', () => {
61 | // 通常使用在template里 调用ref而可以不用.value
62 | const user = {
63 | age: ref(10),
64 | name: 'zhangsan',
65 | }
66 | const proxyUser = proxyRefs(user)
67 | expect(user.age.value).toBe(10)
68 | expect(proxyUser.age).toBe(10)
69 | expect(proxyUser.name).toBe('zhangsan')
70 |
71 | proxyUser.age = 20
72 | expect(proxyUser.age).toBe(20)
73 | expect(user.age.value).toBe(20)
74 |
75 | proxyUser.age = ref(10)
76 | expect(proxyUser.age).toBe(10)
77 | expect(user.age.value).toBe(10)
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/packages/reactivity/src/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { extend, isObject } from '@vue3-mini/shared'
2 | import { ReactiveFlags } from './reactive'
3 | import { reactive, readonly, track, trigger } from './index'
4 |
5 | const get = createGetter()
6 | const readonlyGet = createGetter(true) // readonly的get
7 | const set = createSetter()
8 | const shallowReadonlyGet = createGetter(true, true) // shallowReadonly的get
9 |
10 | /**
11 | * description
12 | * 创建getter
13 | *
14 | * @param isReadonly 是否readonly
15 | * @param isShallow 是否shallow
16 | * @return
17 | *
18 | */
19 | function createGetter(isReadonly = false, isShallow = false) {
20 | return function get(target, key) {
21 | if (ReactiveFlags.IS_REACTIVE === key) {
22 | // 如果调用isReactive 返回!isReadonly
23 | return !isReadonly
24 | }
25 | else if (ReactiveFlags.IS_READONLY === key) {
26 | // 如果调用isReadonly 返回isReadonly
27 | return isReadonly
28 | }
29 | const res = Reflect.get(target, key)
30 |
31 | // 如果是shallow 则直接返回res
32 | if (isShallow)
33 | return res
34 |
35 | // 如果res是对象
36 | // 则继续对res进行readonly或reactive操作
37 | if (isObject(res))
38 | return isReadonly ? readonly(res) : reactive(res)
39 |
40 | // 如果不是readonly 进行track
41 | if (!isReadonly)
42 | track(target, key)
43 |
44 | return res
45 | }
46 | }
47 |
48 | /**
49 | * @description: 创建setter
50 | * @param { Void }
51 | * @return { set }
52 | */
53 | function createSetter() {
54 | return function set(target, key, value) {
55 | const res = Reflect.set(target, key, value)
56 | // 触发trigger
57 | trigger(target, key)
58 | return res
59 | }
60 | }
61 |
62 | export const mutableHandlers = {
63 | get,
64 | set,
65 | }
66 |
67 | export const readonlyHandlers = {
68 | get: readonlyGet,
69 | set(target, key, value) {
70 | console.warn(`key: ${key} set 失败, 因为target为 readonly, ${target}`)
71 | return true
72 | },
73 | }
74 |
75 | // shallowReadonly继承readonlyHandlers
76 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
77 | get: shallowReadonlyGet,
78 | })
79 |
--------------------------------------------------------------------------------
/packages/reactivity/__test__/effect.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest'
2 | import { effect, reactive, stop } from '../src/index'
3 |
4 | describe('effect', () => {
5 | it('happy path', () => {
6 | const user = reactive({
7 | age: 10,
8 | })
9 | let nextAge
10 | effect(() => {
11 | nextAge = user.age + 1
12 | })
13 | expect(nextAge).toBe(11)
14 |
15 | // update
16 | user.age++
17 | expect(nextAge).toBe(12)
18 | })
19 |
20 | it('should return runner when call effect', () => {
21 | // runner
22 | let foo = 10
23 | const runner = effect(() => {
24 | foo++
25 | return 'foo'
26 | })
27 | expect(foo).toBe(11)
28 | const r = runner()
29 | expect(foo).toBe(12)
30 | expect(r).toBe('foo')
31 | })
32 |
33 | it('scheduler', () => {
34 | // 通过effect的第二个参数 给定一个scheduler
35 | // 当effect第一次执行的时候, 执行fn
36 | // 当响应式对象更新的时候 不会执行fn, 而是执行scheduler
37 | // 当执行runner的时候 会再次执行fn
38 | let dummy
39 | let run: any
40 | const scheduler = vi.fn(() => {
41 | run = runner
42 | })
43 | const obj = reactive({ foo: 1 })
44 | const runner = effect(
45 | () => {
46 | dummy = obj.foo
47 | },
48 | { scheduler },
49 | )
50 | expect(scheduler).not.toHaveBeenCalled()
51 | obj.foo++
52 | expect(scheduler).toHaveBeenCalledTimes(1)
53 | expect(dummy).toBe(1)
54 | run()
55 | expect(dummy).toBe(2)
56 | })
57 |
58 | it('stop', () => {
59 | let dummy
60 | const obj = reactive({ prop: 1 })
61 | const runner = effect(() => {
62 | dummy = obj.prop
63 | })
64 | obj.prop = 2
65 | expect(dummy).toBe(2)
66 | stop(runner)
67 | // obj.prop = 3
68 | obj.prop++
69 | expect(dummy).toBe(2)
70 |
71 | // stopped effect should still be manually callable
72 | runner()
73 | expect(dummy).toBe(3)
74 | })
75 |
76 | it('onStop', () => {
77 | const obj = reactive({
78 | foo: 1,
79 | })
80 | const onStop = vi.fn()
81 | let dummy
82 | const runner = effect(
83 | () => {
84 | dummy = obj.foo
85 | },
86 | {
87 | onStop,
88 | },
89 | )
90 |
91 | stop(runner)
92 | expect(onStop).toBeCalledTimes(1)
93 | })
94 | })
95 |
--------------------------------------------------------------------------------
/packages/compiler-core/__test__/parse.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest'
2 | import { NodeTypes } from '../src/ast'
3 | import { baseParse } from '../src/parse'
4 |
5 | describe('Parse', () => {
6 | describe('interpolation', () => {
7 | it('simple interpolation', () => {
8 | const ast = baseParse('{{ message }}')
9 |
10 | expect(ast.children[0]).toStrictEqual({
11 | type: NodeTypes.INTERPOLATION,
12 | content: {
13 | type: NodeTypes.SIMPLE_EXPRESSION,
14 | content: 'message',
15 | },
16 | })
17 | })
18 | })
19 |
20 | describe('element', () => {
21 | it('simple element div', () => {
22 | const ast = baseParse('')
23 |
24 | expect(ast.children[0]).toStrictEqual({
25 | type: NodeTypes.ELEMENT,
26 | tag: 'div',
27 | children: [],
28 | })
29 | })
30 | })
31 |
32 | describe('text', () => {
33 | it('simple test', () => {
34 | const ast = baseParse('some text')
35 |
36 | expect(ast.children[0]).toStrictEqual({
37 | type: NodeTypes.TEXT,
38 | content: 'some text',
39 | })
40 | })
41 | })
42 |
43 | it('hello world', () => {
44 | const ast = baseParse('hi,{{message}}
')
45 |
46 | expect(ast.children[0]).toStrictEqual({
47 | type: NodeTypes.ELEMENT,
48 | tag: 'div',
49 | children: [
50 | {
51 | type: NodeTypes.TEXT,
52 | content: 'hi,',
53 | },
54 | {
55 | type: NodeTypes.INTERPOLATION,
56 | content: {
57 | type: NodeTypes.SIMPLE_EXPRESSION,
58 | content: 'message',
59 | },
60 | },
61 | ],
62 | })
63 | })
64 |
65 | it('Nested ', () => {
66 | const ast = baseParse('')
67 |
68 | expect(ast.children[0]).toStrictEqual({
69 | type: NodeTypes.ELEMENT,
70 | tag: 'div',
71 | children: [
72 | {
73 | type: NodeTypes.ELEMENT,
74 | tag: 'p',
75 | children: [
76 | {
77 | type: NodeTypes.TEXT,
78 | content: 'hi',
79 | },
80 | ],
81 | },
82 | {
83 | type: NodeTypes.INTERPOLATION,
84 | content: {
85 | type: NodeTypes.SIMPLE_EXPRESSION,
86 | content: 'message',
87 | },
88 | },
89 | ],
90 | })
91 | })
92 |
93 | it('should throw error when lack end tag', () => {
94 | expect(() => {
95 | baseParse('
')
96 | console.log(222222222222222)
97 | }).toThrow()
98 | })
99 | })
100 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/codegen.ts:
--------------------------------------------------------------------------------
1 | import { isString } from '@vue3-mini/shared'
2 | import { NodeTypes } from './ast'
3 | import {
4 | CREATE_ELEMENT_VNODE,
5 | TO_DISPLAY_STRING,
6 | helperMapName,
7 | } from './runtimeHelpers'
8 |
9 | export function generate(ast) {
10 | const context = createCodegenContext()
11 | const { push } = context
12 |
13 | genFunctionPreamble(ast, context)
14 |
15 | const functionName = 'render'
16 | const args = ['_ctx', '_cache']
17 | const signature = args.join(', ')
18 |
19 | push(`function ${functionName}(${signature}){`)
20 |
21 | push('return ')
22 | genNode(ast.codegenNode, context)
23 | push('}')
24 |
25 | return { code: context.code }
26 | }
27 |
28 | function genFunctionPreamble(ast, context: any) {
29 | const { push } = context
30 |
31 | const VueBinging = 'Vue'
32 |
33 | const aliasHelper = s => `${helperMapName[s]}:_${helperMapName[s]}`
34 | if (ast.helpers.length > 0)
35 | push(`const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinging}`)
36 |
37 | push('\n')
38 |
39 | push('return ')
40 | }
41 |
42 | function genNode(node: any, context: any) {
43 | switch (node.type) {
44 | case NodeTypes.TEXT:
45 | genText(node, context)
46 | break
47 | case NodeTypes.INTERPOLATION:
48 | genInterpolation(node, context)
49 | break
50 | case NodeTypes.SIMPLE_EXPRESSION:
51 | genExpression(node, context)
52 | break
53 | case NodeTypes.ELEMENT:
54 | genElement(node, context)
55 | break
56 | case NodeTypes.COMPOUND_EXPRESSION:
57 | genCompoundExpression(node, context)
58 | break
59 | default:
60 | break
61 | }
62 | }
63 |
64 | function genElement(node, context) {
65 | const { push, helper } = context
66 | const { tag, children, props } = node
67 |
68 | push(`${helper(CREATE_ELEMENT_VNODE)}(`)
69 |
70 | // genNode(children, context)
71 |
72 | genNodeList(genNullable([tag, props, children]), context)
73 |
74 | push(')')
75 | }
76 |
77 | function genNodeList(nodes: any, context) {
78 | const { push } = context
79 | for (let i = 0; i < nodes.length; i++) {
80 | const node = nodes[i]
81 | if (isString(node))
82 | push(node)
83 | else
84 | genNode(node, context)
85 |
86 | if (i < nodes.length - 1)
87 | push(', ')
88 | }
89 | }
90 |
91 | function genNullable(args: any) {
92 | return args.map(arg => arg || 'null')
93 | }
94 |
95 | function genText(node, context) {
96 | const { push } = context
97 | push(`'${node.content}'`)
98 | }
99 |
100 | function createCodegenContext() {
101 | const context = {
102 | code: '',
103 | push(source) {
104 | context.code += source
105 | },
106 | helper(key) {
107 | return `_${helperMapName[key]}`
108 | },
109 | }
110 |
111 | return context
112 | }
113 | function genInterpolation(node: any, context: any) {
114 | const { push, helper } = context
115 |
116 | push(`${helper(TO_DISPLAY_STRING)}(`)
117 | genNode(node.content, context)
118 | push(')')
119 | }
120 |
121 | function genExpression(node: any, context: any) {
122 | const { push } = context
123 |
124 | push(`${node.content}`)
125 | }
126 | function genCompoundExpression(node: any, context: any) {
127 | const { push } = context
128 | const children = node.children
129 | for (let i = 0; i < children.length; i++) {
130 | const child = children[i]
131 | if (isString(child))
132 | push(child)
133 | else
134 | genNode(child, context)
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRefs, shallowReadonly } from '@vue3-mini/reactivity'
2 | import { emit } from './componentEmit'
3 | import { initProps } from './componentProps'
4 | import { PublicInstanceProxyHandlers } from './componentPublicInstance'
5 | import { initSlots } from './componentsSlots'
6 |
7 | export function createComponentInstance(vnode, parent) {
8 | // 创建组件实例
9 | const component = {
10 | vnode, // 当前vnode
11 | type: vnode.type, // vnode的type
12 | next: null, // 需要更新的 vnode,用于更新 component 类型的组件
13 | setupState: {}, // setup的返回值
14 | props: {}, // 当前组件props
15 | slots: {}, // 当前组件的插槽
16 | provides: parent ? parent.provides : {}, // 获取 parent 的 provides 作为当前组件的初始化值 这样就可以继承 parent.provides 的属性了
17 | parent, // 父组件
18 | isMounted: false, // 是否被mount过, 在初始化后会被修改成true
19 | subTree: {}, // 组件的虚拟节点树
20 | emit: () => {}, // emit方法
21 | }
22 |
23 | // 赋值 emit
24 | // 这里使用 bind 把 instance 和 component 进行绑定
25 | // 后面用户使用的时候只需要给 event 和参数即可
26 | component.emit = emit.bind(null, component) as any
27 | return component
28 | }
29 |
30 | export function setupComponent(instance) {
31 | // 初始化props
32 | // 将instance.vnode.props赋值到instance上
33 | initProps(instance, instance.vnode.props)
34 | // 初始化slots
35 | initSlots(instance, instance.vnode.children)
36 |
37 | // 初始化有状态的component
38 | setupStatefulComponent(instance)
39 | }
40 |
41 | function setupStatefulComponent(instance: any) {
42 | const Component = instance.type
43 |
44 | // 创建代理对象 -> 通过this访问props/$el/$slots等
45 | // 1. 创建代理对象
46 | console.log('创建 proxy')
47 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers)
48 |
49 | // 2. 调用setup
50 | // 调用 setup 的时候传入 props
51 | const { setup } = Component
52 | if (setup) {
53 | // 赋值currentInstance
54 | // 必须要在调用 setup 之前
55 | setCurrentInstance(instance)
56 | // 调用setup 获取setup的返回值
57 | // 第一个参数为shallowReadonly属性的props
58 | // 第二个参数为 { emit }
59 | const setupResult = setup(shallowReadonly(instance.props), {
60 | emit: instance.emit,
61 | })
62 | setCurrentInstance(null)
63 |
64 | handleSetupResult(instance, setupResult)
65 | }
66 | }
67 |
68 | function handleSetupResult(instance: any, setupResult: any) {
69 | // 处理setup的返回值
70 | // TODO 后续实现function
71 | if (typeof setupResult === 'object') {
72 | // setup的返回值用proxyRef包裹
73 | // proxyRefs 的作用就是把 setupResult 对象做一层代理
74 | // 方便用户直接访问 ref 类型的值
75 | // 比如 setupResult 里面有个 count 是个 ref 类型的对象,用户使用的时候就可以直接使用 count 了,而不需要在 count.value
76 | // 这里也就是官网里面说到的自动结构 Ref 类型
77 | instance.setupState = proxyRefs(setupResult)
78 | }
79 |
80 | // 完成组件setup
81 | finishComponentSetup(instance)
82 | }
83 |
84 | function finishComponentSetup(instance: any) {
85 | const Component = instance.type
86 |
87 | if (compiler && !Component.render) {
88 | if (Component.template)
89 | Component.render = compiler(Component.template)
90 | }
91 | // template
92 | if (!instance.render) {
93 | // render赋值
94 | instance.render = Component.render
95 | }
96 | }
97 |
98 | let currentInstance = null
99 | export function getCurrentInstance() {
100 | // 获取当前组件instance实例
101 | // 因为是在setup阶段赋值, 所以只能在setup阶段获取到currentInstance
102 | return currentInstance
103 | }
104 |
105 | export function setCurrentInstance(instance) {
106 | // 赋值currentInstance
107 | currentInstance = instance
108 | }
109 |
110 | let compiler
111 |
112 | export function registerRuntimeCompiler(_compiler) {
113 | compiler = _compiler
114 | }
115 |
--------------------------------------------------------------------------------
/packages/reactivity/src/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from '@vue3-mini/shared'
2 |
3 | let activeEffect // 代表当前的副作用对象 ReactiveEffect
4 | let shouldTrack = false // 代表当前是否需要 track 收集依赖
5 | const targetMap = new WeakMap()
6 |
7 | export class ReactiveEffect {
8 | private _fn: any
9 | public scheduler: Function | undefined
10 | public active = true
11 | public deps: any[] = []
12 | public onStop?: () => void
13 | constructor(_fn, scheduler?) {
14 | console.log('创建 ReactiveEffect 对象')
15 | this._fn = _fn
16 | this.scheduler = scheduler
17 | }
18 |
19 | run() {
20 | // 将this赋值给activeEffect 当前正在执行的effect
21 | activeEffect = this
22 | if (!this.active)
23 | return this._fn()
24 |
25 | shouldTrack = true
26 | // shouldTrack为true
27 | // 执行fn收集依赖
28 | // 执行的时候给全局的 activeEffect 赋值
29 | // 利用全局属性来获取当前的 effect
30 | activeEffect = this
31 | const result = this._fn()
32 | // 收集完依赖将关闭shouldTrack
33 | shouldTrack = false
34 |
35 | return result
36 | }
37 |
38 | stop() {
39 | // 停止响应式
40 | if (this.active) {
41 | // 清空dep
42 | cleanupEffect(this)
43 | if (this.onStop) {
44 | // 执行生命周期函数
45 | this.onStop()
46 | }
47 | // 修改active状态为false
48 | this.active = false
49 | }
50 | }
51 | }
52 |
53 | function cleanupEffect(effect) {
54 | // 清除effect.deps
55 | // 找到所有依赖这个 effect 的响应式对象
56 | // 从这些响应式对象里面把 effect 给删除掉
57 | effect.deps.forEach((dep: any) => {
58 | dep.delete(effect)
59 | })
60 | effect.deps.length = 0
61 | }
62 |
63 | export function track(target, key) {
64 | if (!isTracking())
65 | return
66 |
67 | console.log(`触发 track -> target: ${target} key:${key}`)
68 | // 从targetMap中获取depsMap
69 | let depsMap = targetMap.get(target)
70 | if (!depsMap)
71 | targetMap.set(target, (depsMap = new Map()))
72 |
73 | let dep = depsMap.get(key)
74 | if (!dep)
75 | depsMap.set(key, (dep = new Set()))
76 |
77 | // 寻找到当前target, key的dep后添加effect
78 | trackEffects(dep)
79 | }
80 |
81 | export function trackEffects(dep) {
82 | if (dep.has(activeEffect))
83 | return
84 |
85 | // dep添加effect
86 | // activeEffect的上层依赖中也添加dep
87 | dep.add(activeEffect)
88 | activeEffect.deps.push(dep)
89 | }
90 | /*
91 | * 没有被 effect 包裹时,由于没有副作用函数(即没有依赖,activeEffect === undefined),不应该收集依赖
92 | * 只有在 effect内部的情况下才会收集依赖
93 | * 某些特殊情况,即使包裹在 effect,也不应该收集依赖(即 shouldTrack === false)。如:组件生命周期执行、组件 setup 执行
94 | */
95 | export function isTracking() {
96 | return shouldTrack && undefined !== activeEffect
97 | }
98 |
99 | export function trigger(target, key) {
100 | const depsMap = targetMap.get(target)
101 | const dep = depsMap.get(key)
102 | // 获取target key对应的dep
103 | triggerEffects(dep)
104 | }
105 | export function triggerEffects(dep) {
106 | // 循环dep 如果有scheduler 则执行
107 | // 否则run
108 | for (const effect of dep) {
109 | if (effect.scheduler) {
110 | // scheduler 可以让用户自己选择调用的时机
111 | // 这样就可以灵活的控制调用了
112 | // 在 runtime-core 中,就是使用了 scheduler 实现了在 next ticker 中调用的逻辑
113 | effect.scheduler()
114 | }
115 | else {
116 | effect.run()
117 | }
118 | }
119 | }
120 | export function effect(fn, options: any = {}) {
121 | const _effect = new ReactiveEffect(fn, options.scheduler)
122 | // 创建activeEffect
123 | extend(_effect, options)
124 | // 创建时执行一次
125 | _effect.run()
126 |
127 | // 创建返回值 effect会返回内部函数
128 | const runner: any = _effect.run.bind(_effect)
129 | runner.effect = _effect
130 |
131 | // 把 _effect.run 这个方法返回
132 | // 让用户可以自行选择调用的时机(调用 fn)
133 | return runner
134 | }
135 |
136 | export function stop(runner) {
137 | runner.effect.stop()
138 | }
139 |
--------------------------------------------------------------------------------
/packages/compiler-core/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from './ast'
2 |
3 | const enum TagType {
4 | Start,
5 | End,
6 | }
7 |
8 | export function baseParse(content: string) {
9 | const context = createParserContext(content)
10 |
11 | return createRoot(parseChildren(context, []))
12 | }
13 |
14 | function parseChildren(context, ancestors) {
15 | const nodes: any = []
16 |
17 | while (!isEnd(context, ancestors)) {
18 | let node
19 | const s = context.source
20 | if (s.startsWith('{{')) {
21 | // 插值类型
22 | node = parseInterpolation(context)
23 | }
24 | else if (s[0] === '<') {
25 | if (/[a-z]/i.test(s[1]))
26 | node = parseElement(context, ancestors)
27 | }
28 | // 如果node没有值 需要把它当成text来解析
29 | if (!node)
30 | node = parseText(context)
31 |
32 | nodes.push(node)
33 | }
34 | return nodes
35 | }
36 |
37 | function isEnd(context, ancestors) {
38 | const s = context.source
39 | if (s.startsWith('')) {
40 | for (let i = ancestors.length - 1; i >= 0; i--) {
41 | const tag = ancestors[i].tag
42 | if (startsWithEndTagOpen(s, tag))
43 | return true
44 | }
45 | }
46 | return !s
47 | // 2. 当遇到结束标签的时候
48 | // 1. source有值的时候
49 | }
50 |
51 | function startsWithEndTagOpen(source, tag) {
52 | return (
53 | source.startsWith('')
54 | && source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
55 | )
56 | }
57 |
58 | function parseText(context: any): any {
59 | let endIndex = context.source.length
60 | const endTokens = ['<', '{{']
61 |
62 | for (let i = 0; i < endTokens.length; i++) {
63 | const index = context.source.indexOf(endTokens[i])
64 | if (index !== -1 && endIndex > index)
65 | endIndex = index
66 | }
67 |
68 | const content = parseTextData(context, endIndex)
69 | return {
70 | type: NodeTypes.TEXT,
71 | content,
72 | }
73 | }
74 |
75 | function parseTextData(context: any, length: number) {
76 | // 1. 获取content
77 | const content = context.source.slice(0, length)
78 | // 2. 推进
79 | advanceBy(context, length)
80 | return content
81 | }
82 |
83 | function parseElement(context: any, ancestors) {
84 | // 解析element
85 | // 这里删除了左侧的
86 | const element: any = parseTag(context, TagType.Start)
87 | // 收集tag名称
88 | ancestors.push(element)
89 | element.children = parseChildren(context, ancestors)
90 | // 再次调用删除
91 | // 弹出tag名称
92 | ancestors.pop()
93 |
94 | if (startsWithEndTagOpen(context.source, element.tag))
95 | parseTag(context, TagType.End)
96 | else
97 | throw new Error(`缺少结束标签: ${element.tag}`)
98 |
99 | return element
100 | }
101 |
102 | function parseTag(context: any, type: TagType) {
103 | // 1. 解析tag
104 | // < 开头 a-z忽略大小写
105 | const match: any = /^<\/?([a-z]*)/i.exec(context.source)
106 | const tag = match[1]
107 | // 2. 删除处理完成的代码
108 | advanceBy(context, match[0].length)
109 | // 再删除剩下的 >
110 | advanceBy(context, 1)
111 | if (TagType.End === type)
112 | return
113 |
114 | return {
115 | type: NodeTypes.ELEMENT,
116 | tag,
117 | }
118 | }
119 |
120 | function parseInterpolation(context) {
121 | // 解析插值
122 | // {{ message }}
123 | const openDelimiter = '{{'
124 | const closeDelimiter = '}}'
125 |
126 | // 寻找后面的 }}
127 | const closeIndex = context.source.indexOf(
128 | closeDelimiter,
129 | closeDelimiter.length,
130 | )
131 | // 推进指针
132 | // 也就是执行删除
133 | // 去除前面的 {{
134 | advanceBy(context, openDelimiter.length)
135 |
136 | // 截取
137 | const rawContentLength = closeIndex - openDelimiter.length
138 | const rawContent = parseTextData(context, rawContentLength)
139 | const content = rawContent.trim()
140 |
141 | // 处理后需要删除掉
142 | advanceBy(context, closeDelimiter.length)
143 | return {
144 | type: NodeTypes.INTERPOLATION,
145 | content: {
146 | type: NodeTypes.SIMPLE_EXPRESSION,
147 | content,
148 | },
149 | }
150 | }
151 |
152 | function advanceBy(context: any, length: number) {
153 | context.source = context.source.slice(length)
154 | }
155 |
156 | function createRoot(children) {
157 | return {
158 | children,
159 | type: NodeTypes.ROOT,
160 | }
161 | }
162 |
163 | function createParserContext(content: string): any {
164 | return {
165 | source: content,
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/packages/vue/examples/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 array
3 |
4 | import { Text, h, ref } from '../../dist/vue3-mini.esm.js'
5 |
6 | // 1. 左侧的对比
7 | // (a b) c
8 | // (a b) d e
9 | // const prevChildren = [
10 | // h("p", { key: "A" }, "A"),
11 | // h("p", { key: "B" }, "B"),
12 | // h("p", { key: "C" }, "C"),
13 | // ];
14 | // const nextChildren = [
15 | // h("p", { key: "A" }, "A"),
16 | // h("p", { key: "B" }, "B"),
17 | // h("p", { key: "D" }, "D"),
18 | // h("p", { key: "E" }, "E"),
19 | // ];
20 |
21 | // 2. 右侧的对比
22 | // a (b c)
23 | // d e (b c)
24 | // const prevChildren = [
25 | // h("p", { key: "A" }, "A"),
26 | // h("p", { key: "B" }, "B"),
27 | // h("p", { key: "C" }, "C"),
28 | // ];
29 | // const nextChildren = [
30 | // h("p", { key: "D" }, "D"),
31 | // h("p", { key: "E" }, "E"),
32 | // h("p", { key: "B" }, "B"),
33 | // h("p", { key: "C" }, "C"),
34 | // ];
35 |
36 | // 3. 新的比老的长
37 | // 创建新的
38 | // 左侧
39 | // (a b)
40 | // (a b) c
41 | // i = 2, e1 = 1, e2 = 2
42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
43 | // const nextChildren = [
44 | // h("p", { key: "A" }, "A"),
45 | // h("p", { key: "B" }, "B"),
46 | // h("p", { key: "C" }, "C"),
47 | // h("p", { key: "D" }, "D"),
48 | // ];
49 |
50 | // 右侧
51 | // (a b)
52 | // c (a b)
53 | // i = 0, e1 = -1, e2 = 0
54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
55 | // const nextChildren = [
56 | // h("p", { key: "D" }, "D"),
57 | // h("p", { key: "C" }, "C"),
58 | // h("p", { key: "A" }, "A"),
59 | // h("p", { key: "B" }, "B"),
60 | // ];
61 |
62 | // 4. 老的比新的长
63 | // 删除老的
64 | // 左侧
65 | // (a b) c
66 | // (a b)
67 | // i = 2, e1 = 2, e2 = 1
68 | // const prevChildren = [
69 | // h("p", { key: "A" }, "A"),
70 | // h("p", { key: "B" }, "B"),
71 | // h("p", { key: "C" }, "C"),
72 | // ];
73 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
74 |
75 | // 右侧
76 | // a (b c)
77 | // (b c)
78 | // i = 0, e1 = 0, e2 = -1
79 | // const prevChildren = [
80 | // h("p", { key: "A" }, "A"),
81 | // h("p", { key: "B" }, "B"),
82 | // h("p", { key: "C" }, "C"),
83 | // ];
84 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
85 |
86 | // 5. 对比中间的部分
87 | // 删除老的 (在老的里面存在,新的里面不存在)
88 | // 5.1
89 | // a,b,(c,d),f,g
90 | // a,b,(e,c),f,g
91 | // D 节点在新的里面是没有的 - 需要删除掉
92 | // C 节点 props 也发生了变化
93 |
94 | // const prevChildren = [
95 | // h("p", { key: "A" }, "A"),
96 | // h("p", { key: "B" }, "B"),
97 | // h("p", { key: "C", id: "c-prev" }, "C"),
98 | // h("p", { key: "D" }, "D"),
99 | // h("p", { key: "F" }, "F"),
100 | // h("p", { key: "G" }, "G"),
101 | // ];
102 |
103 | // const nextChildren = [
104 | // h("p", { key: "A" }, "A"),
105 | // h("p", { key: "B" }, "B"),
106 | // h("p", { key: "E" }, "E"),
107 | // h("p", { key: "C", id:"c-next" }, "C"),
108 | // h("p", { key: "F" }, "F"),
109 | // h("p", { key: "G" }, "G"),
110 | // ];
111 |
112 | // 5.1.1
113 | // a,b,(c,e,d),f,g
114 | // a,b,(e,c),f,g
115 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
116 | // const prevChildren = [
117 | // h("p", { key: "A" }, "A"),
118 | // h("p", { key: "B" }, "B"),
119 | // h("p", { key: "C", id: "c-prev" }, "C"),
120 | // h("p", { key: "E" }, "E"),
121 | // h("p", { key: "D" }, "D"),
122 | // h("p", { key: "F" }, "F"),
123 | // h("p", { key: "G" }, "G"),
124 | // ];
125 |
126 | // const nextChildren = [
127 | // h("p", { key: "A" }, "A"),
128 | // h("p", { key: "B" }, "B"),
129 | // h("p", { key: "E" }, "E"),
130 | // h("p", { key: "C", id:"c-next" }, "C"),
131 | // h("p", { key: "F" }, "F"),
132 | // h("p", { key: "G" }, "G"),
133 | // ];
134 |
135 | // 2 移动 (节点存在于新的和老的里面,但是位置变了)
136 |
137 | // 2.1
138 | // a,b,(c,d,e),f,g
139 | // a,b,(e,c,d),f,g
140 | // 最长子序列: [1,2]
141 |
142 | // const prevChildren = [
143 | // h("p", { key: "A" }, "A"),
144 | // h("p", { key: "B" }, "B"),
145 | // h("p", { key: "C" }, "C"),
146 | // h("p", { key: "D" }, "D"),
147 | // h("p", { key: "E" }, "E"),
148 | // h("p", { key: "F" }, "F"),
149 | // h("p", { key: "G" }, "G"),
150 | // ];
151 |
152 | // const nextChildren = [
153 | // h("p", { key: "A" }, "A"),
154 | // h("p", { key: "B" }, "B"),
155 | // h("p", { key: "E" }, "E"),
156 | // h("p", { key: "C" }, "C"),
157 | // h("p", { key: "D" }, "D"),
158 | // h("p", { key: "F" }, "F"),
159 | // h("p", { key: "G" }, "G"),
160 | // ];
161 |
162 | // 3. 创建新的节点
163 | // a,b,(c,e),f,g
164 | // a,b,(e,c,d),f,g
165 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
166 | // const prevChildren = [
167 | // h("p", { key: "A" }, "A"),
168 | // h("p", { key: "B" }, "B"),
169 | // h("p", { key: "C" }, "C"),
170 | // h("p", { key: "E" }, "E"),
171 | // h("p", { key: "F" }, "F"),
172 | // h("p", { key: "G" }, "G"),
173 | // ];
174 |
175 | // const nextChildren = [
176 | // h("p", { key: "A" }, "A"),
177 | // h("p", { key: "B" }, "B"),
178 | // h("p", { key: "E" }, "E"),
179 | // h("p", { key: "C" }, "C"),
180 | // h("p", { key: "D" }, "D"),
181 | // h("p", { key: "F" }, "F"),
182 | // h("p", { key: "G" }, "G"),
183 | // ];
184 |
185 | // 综合例子
186 | // a,b,(c,d,e,z),f,g
187 | // a,b,(d,c,y,e),f,g
188 |
189 | // const prevChildren = [
190 | // h("p", { key: "A" }, "A"),
191 | // h("p", { key: "B" }, "B"),
192 | // h("p", { key: "C" }, "C"),
193 | // h("p", { key: "D" }, "D"),
194 | // h("p", { key: "E" }, "E"),
195 | // h("p", { key: "Z" }, "Z"),
196 | // h("p", { key: "F" }, "F"),
197 | // h("p", { key: "G" }, "G"),
198 | // ];
199 |
200 | // const nextChildren = [
201 | // h("p", { key: "A" }, "A"),
202 | // h("p", { key: "B" }, "B"),
203 | // h("p", { key: "D" }, "D"),
204 | // h("p", { key: "C" }, "C"),
205 | // h("p", { key: "Y" }, "Y"),
206 | // h("p", { key: "E" }, "E"),
207 | // h("p", { key: "F" }, "F"),
208 | // h("p", { key: "G" }, "G"),
209 | // ];
210 |
211 | // fix c 节点应该是 move 而不是删除之后重新创建的
212 | const prevChildren = [
213 | h(Text, { key: 'A' }, 'A'),
214 | h('p', {}, 'C'),
215 | h('p', { key: 'B' }, 'B'),
216 | h('p', { key: 'D' }, 'D'),
217 | ]
218 |
219 | const nextChildren = [
220 | h(Text, { key: 'A' }, 'TEXT'),
221 | h('p', { key: 'B' }, 'B'),
222 | h('p', {}, 'C'),
223 | h('p', { key: 'D' }, 'D'),
224 | ]
225 |
226 | export default {
227 | name: 'ArrayToArray',
228 | setup() {
229 | const isChange = ref(false)
230 | window.isChange = isChange
231 |
232 | return {
233 | isChange,
234 | }
235 | },
236 | render() {
237 | const self = this
238 |
239 | return self.isChange === true
240 | ? h('div', {}, nextChildren)
241 | : h('div', {}, prevChildren)
242 | },
243 | }
244 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
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": ["DOM", "es6", "ES2016"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26 |
27 | /* Modules */
28 | "module": "esnext", /* Specify what module code is generated. */
29 | // "rootDir": "./", /* Specify the root folder within your source files. */
30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35 | "types": ["vitest/globals"], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38 | // "resolveJsonModule": true, /* Enable importing .json files. */
39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
40 |
41 | /* JavaScript Support */
42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
45 |
46 | /* Emit */
47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
51 | // "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. */
52 | // "outDir": "./", /* Specify an output folder for all emitted files. */
53 | // "removeComments": true, /* Disable emitting comments. */
54 | // "noEmit": true, /* Disable emitting files from a compilation. */
55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
63 | // "newLine": "crlf", /* Set the newline character for emitting files. */
64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
70 |
71 | /* Interop Constraints */
72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
77 |
78 | /* Type Checking */
79 | "strict": true, /* Enable all strict type-checking options. */
80 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
98 |
99 | /* Completeness */
100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
101 | "skipLibCheck": true, /* Skip type checking all .d.ts files. */
102 | "paths": {
103 | "@vue3-mini/*": [
104 | "./packages/*/src"
105 | ]
106 | }
107 | },
108 | "include": [
109 | "packages/*/src",
110 | "packages/*/__test__"
111 | ]
112 | }
113 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@esbuild/linux-loong64@0.14.54":
6 | version "0.14.54"
7 | resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
8 | integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
9 |
10 | "@rollup/plugin-typescript@^8.4.0":
11 | version "8.4.0"
12 | resolved "https://registry.npmmirror.com/@rollup/plugin-typescript/-/plugin-typescript-8.4.0.tgz#a8a384b6dbaab42b4cafb075278b15743c0f5ef8"
13 | integrity sha512-QssfoOP6V4/6skX12EfOW5UzJAv/c334F4OJWmQpe2kg3agEa0JwVCckwmfuvEgDixyX+XyxjFenH7M2rDKUyQ==
14 | dependencies:
15 | "@rollup/pluginutils" "^3.1.0"
16 | resolve "^1.17.0"
17 |
18 | "@rollup/pluginutils@^3.1.0":
19 | version "3.1.0"
20 | resolved "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
21 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
22 | dependencies:
23 | "@types/estree" "0.0.39"
24 | estree-walker "^1.0.1"
25 | picomatch "^2.2.2"
26 |
27 | "@types/chai-subset@^1.3.3":
28 | version "1.3.3"
29 | resolved "https://registry.npmmirror.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
30 | integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==
31 | dependencies:
32 | "@types/chai" "*"
33 |
34 | "@types/chai@*":
35 | version "4.3.1"
36 | resolved "https://registry.npmmirror.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04"
37 | integrity sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==
38 |
39 | "@types/chai@^4.3.3":
40 | version "4.3.3"
41 | resolved "https://registry.npmmirror.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07"
42 | integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==
43 |
44 | "@types/estree@0.0.39":
45 | version "0.0.39"
46 | resolved "https://registry.npmmirror.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
47 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
48 |
49 | "@types/node@*":
50 | version "17.0.42"
51 | resolved "https://registry.npmmirror.com/@types/node/-/node-17.0.42.tgz#d7e8f22700efc94d125103075c074396b5f41f9b"
52 | integrity sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==
53 |
54 | assertion-error@^1.1.0:
55 | version "1.1.0"
56 | resolved "https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
57 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
58 |
59 | chai@^4.3.6:
60 | version "4.3.6"
61 | resolved "https://registry.npmmirror.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c"
62 | integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==
63 | dependencies:
64 | assertion-error "^1.1.0"
65 | check-error "^1.0.2"
66 | deep-eql "^3.0.1"
67 | get-func-name "^2.0.0"
68 | loupe "^2.3.1"
69 | pathval "^1.1.1"
70 | type-detect "^4.0.5"
71 |
72 | check-error@^1.0.2:
73 | version "1.0.2"
74 | resolved "https://registry.npmmirror.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
75 | integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==
76 |
77 | debug@^4.3.4:
78 | version "4.3.4"
79 | resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
80 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
81 | dependencies:
82 | ms "2.1.2"
83 |
84 | deep-eql@^3.0.1:
85 | version "3.0.1"
86 | resolved "https://registry.npmmirror.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
87 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==
88 | dependencies:
89 | type-detect "^4.0.0"
90 |
91 | esbuild-android-64@0.14.54:
92 | version "0.14.54"
93 | resolved "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
94 | integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
95 |
96 | esbuild-android-arm64@0.14.54:
97 | version "0.14.54"
98 | resolved "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
99 | integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
100 |
101 | esbuild-darwin-64@0.14.54:
102 | version "0.14.54"
103 | resolved "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
104 | integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
105 |
106 | esbuild-darwin-arm64@0.14.54:
107 | version "0.14.54"
108 | resolved "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
109 | integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
110 |
111 | esbuild-freebsd-64@0.14.54:
112 | version "0.14.54"
113 | resolved "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
114 | integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
115 |
116 | esbuild-freebsd-arm64@0.14.54:
117 | version "0.14.54"
118 | resolved "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
119 | integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
120 |
121 | esbuild-linux-32@0.14.54:
122 | version "0.14.54"
123 | resolved "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
124 | integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
125 |
126 | esbuild-linux-64@0.14.54:
127 | version "0.14.54"
128 | resolved "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
129 | integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
130 |
131 | esbuild-linux-arm64@0.14.54:
132 | version "0.14.54"
133 | resolved "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
134 | integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
135 |
136 | esbuild-linux-arm@0.14.54:
137 | version "0.14.54"
138 | resolved "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
139 | integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
140 |
141 | esbuild-linux-mips64le@0.14.54:
142 | version "0.14.54"
143 | resolved "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
144 | integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
145 |
146 | esbuild-linux-ppc64le@0.14.54:
147 | version "0.14.54"
148 | resolved "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
149 | integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
150 |
151 | esbuild-linux-riscv64@0.14.54:
152 | version "0.14.54"
153 | resolved "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
154 | integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
155 |
156 | esbuild-linux-s390x@0.14.54:
157 | version "0.14.54"
158 | resolved "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
159 | integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
160 |
161 | esbuild-netbsd-64@0.14.54:
162 | version "0.14.54"
163 | resolved "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
164 | integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
165 |
166 | esbuild-openbsd-64@0.14.54:
167 | version "0.14.54"
168 | resolved "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
169 | integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
170 |
171 | esbuild-sunos-64@0.14.54:
172 | version "0.14.54"
173 | resolved "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
174 | integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
175 |
176 | esbuild-windows-32@0.14.54:
177 | version "0.14.54"
178 | resolved "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
179 | integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
180 |
181 | esbuild-windows-64@0.14.54:
182 | version "0.14.54"
183 | resolved "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
184 | integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
185 |
186 | esbuild-windows-arm64@0.14.54:
187 | version "0.14.54"
188 | resolved "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
189 | integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
190 |
191 | esbuild@^0.14.47:
192 | version "0.14.54"
193 | resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
194 | integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
195 | optionalDependencies:
196 | "@esbuild/linux-loong64" "0.14.54"
197 | esbuild-android-64 "0.14.54"
198 | esbuild-android-arm64 "0.14.54"
199 | esbuild-darwin-64 "0.14.54"
200 | esbuild-darwin-arm64 "0.14.54"
201 | esbuild-freebsd-64 "0.14.54"
202 | esbuild-freebsd-arm64 "0.14.54"
203 | esbuild-linux-32 "0.14.54"
204 | esbuild-linux-64 "0.14.54"
205 | esbuild-linux-arm "0.14.54"
206 | esbuild-linux-arm64 "0.14.54"
207 | esbuild-linux-mips64le "0.14.54"
208 | esbuild-linux-ppc64le "0.14.54"
209 | esbuild-linux-riscv64 "0.14.54"
210 | esbuild-linux-s390x "0.14.54"
211 | esbuild-netbsd-64 "0.14.54"
212 | esbuild-openbsd-64 "0.14.54"
213 | esbuild-sunos-64 "0.14.54"
214 | esbuild-windows-32 "0.14.54"
215 | esbuild-windows-64 "0.14.54"
216 | esbuild-windows-arm64 "0.14.54"
217 |
218 | estree-walker@^1.0.1:
219 | version "1.0.1"
220 | resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
221 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
222 |
223 | fsevents@~2.3.2:
224 | version "2.3.2"
225 | resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
226 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
227 |
228 | function-bind@^1.1.1:
229 | version "1.1.1"
230 | resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
231 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
232 |
233 | get-func-name@^2.0.0:
234 | version "2.0.0"
235 | resolved "https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
236 | integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==
237 |
238 | has@^1.0.3:
239 | version "1.0.3"
240 | resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
241 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
242 | dependencies:
243 | function-bind "^1.1.1"
244 |
245 | is-core-module@^2.9.0:
246 | version "2.10.0"
247 | resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
248 | integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
249 | dependencies:
250 | has "^1.0.3"
251 |
252 | local-pkg@^0.4.2:
253 | version "0.4.2"
254 | resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.2.tgz#13107310b77e74a0e513147a131a2ba288176c2f"
255 | integrity sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==
256 |
257 | loupe@^2.3.1:
258 | version "2.3.4"
259 | resolved "https://registry.npmmirror.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3"
260 | integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==
261 | dependencies:
262 | get-func-name "^2.0.0"
263 |
264 | ms@2.1.2:
265 | version "2.1.2"
266 | resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
267 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
268 |
269 | nanoid@^3.3.4:
270 | version "3.3.4"
271 | resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
272 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
273 |
274 | path-parse@^1.0.7:
275 | version "1.0.7"
276 | resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
277 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
278 |
279 | pathval@^1.1.1:
280 | version "1.1.1"
281 | resolved "https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
282 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
283 |
284 | picocolors@^1.0.0:
285 | version "1.0.0"
286 | resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
287 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
288 |
289 | picomatch@^2.2.2:
290 | version "2.3.1"
291 | resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
292 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
293 |
294 | postcss@^8.4.16:
295 | version "8.4.16"
296 | resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
297 | integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==
298 | dependencies:
299 | nanoid "^3.3.4"
300 | picocolors "^1.0.0"
301 | source-map-js "^1.0.2"
302 |
303 | resolve@^1.17.0, resolve@^1.22.1:
304 | version "1.22.1"
305 | resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
306 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
307 | dependencies:
308 | is-core-module "^2.9.0"
309 | path-parse "^1.0.7"
310 | supports-preserve-symlinks-flag "^1.0.0"
311 |
312 | "rollup@>=2.75.6 <2.77.0 || ~2.77.0":
313 | version "2.77.3"
314 | resolved "https://registry.npmmirror.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12"
315 | integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==
316 | optionalDependencies:
317 | fsevents "~2.3.2"
318 |
319 | rollup@^2.79.0:
320 | version "2.79.0"
321 | resolved "https://registry.npmmirror.com/rollup/-/rollup-2.79.0.tgz#9177992c9f09eb58c5e56cbfa641607a12b57ce2"
322 | integrity sha512-x4KsrCgwQ7ZJPcFA/SUu6QVcYlO7uRLfLAy0DSA4NS2eG8japdbpM50ToH7z4iObodRYOJ0soneF0iaQRJ6zhA==
323 | optionalDependencies:
324 | fsevents "~2.3.2"
325 |
326 | source-map-js@^1.0.2:
327 | version "1.0.2"
328 | resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
329 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
330 |
331 | supports-preserve-symlinks-flag@^1.0.0:
332 | version "1.0.0"
333 | resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
334 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
335 |
336 | tinypool@^0.2.4:
337 | version "0.2.4"
338 | resolved "https://registry.npmmirror.com/tinypool/-/tinypool-0.2.4.tgz#4d2598c4689d1a2ce267ddf3360a9c6b3925a20c"
339 | integrity sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==
340 |
341 | tinyspy@^1.0.2:
342 | version "1.0.2"
343 | resolved "https://registry.npmmirror.com/tinyspy/-/tinyspy-1.0.2.tgz#6da0b3918bfd56170fb3cd3a2b5ef832ee1dff0d"
344 | integrity sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==
345 |
346 | tslib@^2.4.0:
347 | version "2.4.0"
348 | resolved "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
349 | integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
350 |
351 | type-detect@^4.0.0, type-detect@^4.0.5:
352 | version "4.0.8"
353 | resolved "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
354 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
355 |
356 | typescript@^4.7.3:
357 | version "4.7.3"
358 | resolved "https://registry.npmmirror.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
359 | integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
360 |
361 | "vite@^2.9.12 || ^3.0.0-0":
362 | version "3.0.9"
363 | resolved "https://registry.npmmirror.com/vite/-/vite-3.0.9.tgz#45fac22c2a5290a970f23d66c1aef56a04be8a30"
364 | integrity sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==
365 | dependencies:
366 | esbuild "^0.14.47"
367 | postcss "^8.4.16"
368 | resolve "^1.22.1"
369 | rollup ">=2.75.6 <2.77.0 || ~2.77.0"
370 | optionalDependencies:
371 | fsevents "~2.3.2"
372 |
373 | vitest@^0.22.1:
374 | version "0.22.1"
375 | resolved "https://registry.npmmirror.com/vitest/-/vitest-0.22.1.tgz#3122e6024bf782ee9aca53034017af7adb009c32"
376 | integrity sha512-+x28YTnSLth4KbXg7MCzoDAzPJlJex7YgiZbUh6YLp0/4PqVZ7q7/zyfdL0OaPtKTpNiQFPpMC8Y2MSzk8F7dw==
377 | dependencies:
378 | "@types/chai" "^4.3.3"
379 | "@types/chai-subset" "^1.3.3"
380 | "@types/node" "*"
381 | chai "^4.3.6"
382 | debug "^4.3.4"
383 | local-pkg "^0.4.2"
384 | tinypool "^0.2.4"
385 | tinyspy "^1.0.2"
386 | vite "^2.9.12 || ^3.0.0-0"
387 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/renderer.ts:
--------------------------------------------------------------------------------
1 | import { EMPTY_OBJ, ShapeFlags } from '@vue3-mini/shared'
2 | import { effect } from '@vue3-mini/reactivity'
3 | import { Fragment, Text } from './vnode'
4 | import { createComponentInstance, setupComponent } from './component'
5 | import { createAppAPI } from './createApp'
6 | import { shouldUpdateComponent } from './componentUpdateUtils'
7 | import { queueJobs } from './scheduler'
8 |
9 | export function createRenderer(options) {
10 | // 传入自定义渲染方法
11 | const {
12 | createElement: hostCreateElement,
13 | patchProp: hostPatchProp,
14 | insert: hostInsert,
15 | remove: hostRemove,
16 | setElementText: hostSetElementText,
17 | createText: hostCreateText,
18 | setText: hostSetText,
19 | } = options
20 | function render(vnode, container) {
21 | // 调用patch方法
22 | // 一开始没有n1新节点 传null
23 | patch(null, vnode, container, null, null)
24 | }
25 |
26 | // n1 老节点
27 | // n2 新节点
28 | function patch(n1, n2, container, parentComponent, anchor) {
29 | // 因为n2是新节点
30 | // 需要基于新节点的类型来判断
31 | const { type, shapeFlag } = n2
32 | // 其中还有几个类型比如: static fragment comment
33 | switch (type) {
34 | case Fragment:
35 | // Fragment -> 只渲染children
36 | processFragment(n1, n2, container, parentComponent, anchor)
37 | break
38 | case Text:
39 | // Text -> 渲染为textNode节点
40 | processText(n1, n2, container)
41 | break
42 | default:
43 | if (shapeFlag & ShapeFlags.ELEMENT) {
44 | // 处理element类型
45 | console.log('处理 Element 类型')
46 | processElement(n1, n2, container, parentComponent, anchor)
47 | }
48 | else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
49 | // 处理component类型
50 | console.log('处理 Component 类型')
51 | processComponent(n1, n2, container, parentComponent, anchor)
52 | }
53 | break
54 | }
55 | }
56 |
57 | function processText(n1, n2: any, container: any) {
58 | // 如果是text类型 则children为text: string
59 | console.log('处理 Text 节点')
60 | if (n1 === null) {
61 | // 如果n1没有 说明是初始化阶段
62 | // 使用hostCreateText创建text节点 然后insert
63 | console.log('初始化 Text 节点')
64 | hostInsert((n2.el = hostCreateText(n2.children as string)), container)
65 | }
66 | else {
67 | // update
68 | // 先对比一下 updated 之后的内容是否和之前的不一样
69 | // 在不一样的时候才需要 update text
70 | // 这里抽离出来的接口是 setText
71 | // 注意,这里一定要记得把 n1.el 赋值给 n2.el, 不然后续是找不到值的
72 | const el = (n2.el = n1.el!)
73 | if (n2.children !== n1.children) {
74 | console.log('更新 Text 节点')
75 | hostSetText(el, n2.children as string)
76 | }
77 | }
78 | }
79 |
80 | function processFragment(
81 | n1,
82 | n2: any,
83 | container: any,
84 | parentComponent,
85 | anchor,
86 | ) {
87 | // 如果是Fragment类型 就跳过当前节点 直接mount他的子节点children
88 | console.log('初始化 Fragment 类型的节点')
89 | mountChildren(n2.children, container, parentComponent, anchor)
90 | }
91 |
92 | function processElement(
93 | n1,
94 | n2: any,
95 | container: any,
96 | parentComponent,
97 | anchor,
98 | ) {
99 | if (!n1) {
100 | // 如果没有老节点 则直接mount新节点
101 | mountElement(n2, container, parentComponent, anchor)
102 | }
103 | else {
104 | // 有老节点 调用patchElement进行对比
105 | patchElement(n1, n2, container, parentComponent, anchor)
106 | }
107 | }
108 |
109 | function patchElement(n1, n2, container, parentComponent, anchor) {
110 | console.log('应该更新 element')
111 | console.log('旧的 vnode', n1)
112 | console.log('新的 vnode', n2)
113 |
114 | const oldProps = n1.props || EMPTY_OBJ
115 | const newProps = n2.props || EMPTY_OBJ
116 |
117 | // 获取el 供hostPatchProps使用
118 | const el = (n2.el = n1.el)
119 |
120 | // 更新props
121 | patchProps(el, oldProps, newProps)
122 |
123 | // 更新子节点children
124 | patchChildren(n1, n2, el, parentComponent, anchor)
125 | }
126 |
127 | function patchChildren(n1, n2, container, parentComponent, anchor) {
128 | const { shapeFlag: prevShapeFlag, children: c1 } = n1
129 | const { shapeFlag, children: c2 } = n2
130 |
131 | // 进行对比新老children
132 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
133 | // 如果 n2 的 children 是 text 类型的话
134 | // 就看看和之前的 n1 的 children 是不是一样的
135 | // 如果不一样的话直接重新设置一下 text 即可
136 | if (c1 !== c2)
137 | hostSetElementText(container, c2)
138 |
139 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
140 | // 如果n1的children是array 那么需要清空children
141 | unmountChildren(n1.children)
142 | }
143 | }
144 | else {
145 | // 新节点为array类型
146 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
147 | // 老节点为text
148 | // 1. 清空text
149 | hostSetElementText(container, '')
150 | // 2. mountChildren
151 | mountChildren(c2, container, parentComponent, anchor)
152 | }
153 | else {
154 | // 如果之前是 array_children
155 | // 现在还是 array_children 的话
156 | // 那么我们就需要对比两个 children 走diff算法了
157 | patchKeyedChildren(c1, c2, container, parentComponent, anchor)
158 | }
159 | }
160 | }
161 |
162 | function patchKeyedChildren(
163 | c1,
164 | c2,
165 | container,
166 | parentComponent,
167 | parentAnchor,
168 | ) {
169 | // i 头指针
170 | // e1 e2分别为两个数组的尾指针
171 | // 头部如果相同 i ++ 一直找到不同的位置为止
172 | let i = 0
173 | const l2 = c2.length
174 | const l1 = c1.length
175 | let e1 = l1 - 1
176 | let e2 = l2 - 1
177 |
178 | function isSameVNodeType(n1, n2) {
179 | // 基于type和key判断两个节点是否一样
180 | return n1.type === n2.type && n1.key === n2.key
181 | }
182 |
183 | // 左侧对比
184 | while (i <= e1 && i <= e2) {
185 | const n1 = c1[i]
186 | const n2 = c2[i]
187 |
188 | if (isSameVNodeType(n1, n2)) {
189 | // 如果type key相同 则调用patch去更新props和children
190 | console.log(
191 | '两个 child 相等,接下来对比这两个 child 节点(从左往右比对)',
192 | )
193 | patch(n1, n2, container, parentComponent, parentAnchor)
194 | }
195 | else {
196 | console.log('两个 child 不相等(从左往右比对)')
197 | console.log(`prevChild:${n1}`)
198 | console.log(`nextChild:${n2}`)
199 | break
200 | }
201 | // 移动左侧指针
202 | i++
203 | }
204 |
205 | // 右侧对比
206 | while (i <= e1 && i <= e2) {
207 | const n1 = c1[e1]
208 | const n2 = c2[e2]
209 |
210 | if (isSameVNodeType(n1, n2)) {
211 | // 如果type key相同 则调用patch去对比props和children
212 | console.log(
213 | '两个 child 相等,接下来对比这两个 child 节点(从右往左比对)',
214 | )
215 | patch(n1, n2, container, parentComponent, parentAnchor)
216 | }
217 | else {
218 | console.log('两个 child 不相等(从右往左比对)')
219 | console.log(`prevChild:${n1}`)
220 | console.log(`nextChild:${n2}`)
221 | break
222 | }
223 | // 移动右侧指针
224 | e1--
225 | e2--
226 | }
227 |
228 | // 新的比老的长 创建
229 | // i > e1
230 | if (i > e1) {
231 | if (i <= e2) {
232 | // 如果是这种情况的话就说明 e2 也就是新节点的数量大于旧节点的数量
233 | // 也就是说新增了 vnode
234 | // 应该循环 c2
235 | // 锚点的计算:新的节点有可能需要添加到尾部,也可能添加到头部,所以需要指定添加的问题
236 | // 要添加的位置是当前的位置(e2 开始)+1
237 | // 因为对于往左侧添加的话,应该获取到 c2 的第一个元素
238 | // 所以我们需要从 e2 + 1 取到锚点的位置
239 | // 如果大于c2.length 则说明是右侧加 锚点为null 否则是左侧加 锚点为i + 1
240 | const nextPos = e2 + 1
241 | const anchor = nextPos < l2 ? c2[nextPos].el : null
242 | // 循环patch创建
243 | while (i <= e2) {
244 | // 创建新的节点 此时n1为null
245 | patch(null, c2[i], container, parentComponent, anchor)
246 | i++
247 | }
248 | }
249 | }
250 | else if (i > e2) {
251 | // 新的比老的短
252 | // i > e2
253 | while (i <= e1) {
254 | hostRemove(c1[i].el)
255 | i++
256 | }
257 | }
258 | else {
259 | // 乱序的部分 中间对比
260 | // 左右两边都比对完了,然后剩下的就是中间部位顺序变动的
261 | // 例如下面的情况
262 | // a,b,[c,d,e],f,g
263 | // a,b,[e,c,d],f,g
264 | // i -> 左侧 e1 -> 更新前的e e2 -> 更新后的e
265 | const s1 = i
266 | const s2 = i
267 | const toBePatched = e2 - s2 + 1 // 需要patch的新节点的数量
268 | let patched = 0 // 已经patch过的数量
269 | // 通过key建立映射表
270 | // key -> i
271 | const keyToNewIndex = new Map()
272 | // 初始化 从新的index映射为老的index
273 | // 创建数组的时候给定数组的长度,这个是性能最快的写法
274 | const newIndexToOldIndexMap = new Array(toBePatched)
275 | // 初始化为 0 , 后面处理的时候 如果发现是 0 的话,那么就说明新值在老的里面不存在
276 | for (let i = 0; i < toBePatched; i++)
277 | newIndexToOldIndexMap[i] = 0
278 |
279 | // 是否移动了位置
280 | let moved = false
281 | let maxNewIndexSoFar = 0
282 |
283 | for (let i = s2; i <= e2; i++) {
284 | const nextChild = c2[i]
285 | keyToNewIndex.set(nextChild.key, i)
286 | }
287 |
288 | // 去map里查找更新后是否存在
289 | // 遍历老节点
290 | // 1. 需要找出老节点有,而新节点没有的 -> 需要把这个节点删除掉
291 | // 2. 新老节点都有的,—> 需要 patch
292 | for (let i = s1; i <= e1; i++) {
293 | const prevChild = c1[i]
294 | // 如果patch过的数量 >= 需要patch的数量
295 | // 说明所有新节点都已经patch过了, 老节点直接删除掉 不需要再走下面的逻辑了
296 | if (patched >= toBePatched) {
297 | hostRemove(prevChild.el)
298 | continue
299 | }
300 | let newIndex
301 | if (prevChild.key != null) {
302 | // 这里就可以通过key快速的查找了, 看看在新的里面这个节点存在不存在
303 | newIndex = keyToNewIndex.get(prevChild.key)
304 | }
305 | else {
306 | // 如果没key 的话,那么只能是遍历所有的新节点来确定当前节点存在不存在了
307 | for (let j = s2; j <= e2; j++) {
308 | if (isSameVNodeType(prevChild, c2[j])) {
309 | // 如果是same 则说明此节点更新后也存在
310 | // 给newIndex的值更新成j 然后跳出循环
311 | newIndex = j
312 | break
313 | }
314 | }
315 | }
316 |
317 | // 因为有可能 nextIndex 的值为0(0也是正常值)
318 | // 所以需要通过值是不是 undefined 或者 null 来判断
319 | if (newIndex === undefined) {
320 | // 如果newIndex没有被赋值, 则说明节点被删除了
321 | hostRemove(prevChild.el)
322 | }
323 | else {
324 | // 老节点还存在
325 | console.log('新老节点都存在')
326 |
327 | // 这里是确定中间的节点是不是需要移动
328 | // 新的 newIndex 如果一直是升序的话,那么就说明没有移动
329 | // 所以我们可以记录最后一个节点在新的里面的索引,然后看看是不是升序
330 | // 不是升序的话,我们就可以确定节点移动过了
331 | if (newIndex >= maxNewIndexSoFar) {
332 | // 大于等于记录的点, 说明相对位置没有改变
333 | maxNewIndexSoFar = newIndex
334 | }
335 | else {
336 | // 新得到的index比记录的点小 说明需要移动位置
337 | moved = true
338 | }
339 | // 赋值新老节点位置的映射关系 从0开始
340 | // index -> 位置
341 | // 0 代表没有 所以等号右侧需要 + 1
342 | newIndexToOldIndexMap[newIndex - s2] = i + 1
343 | // 调用patch深度对比
344 | patch(prevChild, c2[newIndex], container, parentComponent, null)
345 | patched++
346 | }
347 | }
348 |
349 | // 求最长递增子序列
350 | // 最长递增子序列为稳定的序列 不需要进行移动
351 | // [4, 2, 3] -> [1, 2]
352 | // 利用最长递增子序列来优化移动逻辑
353 | // 因为元素是升序的话,那么这些元素就是不需要移动的
354 | // 而我们就可以通过最长递增子序列来获取到升序的列表
355 | // 在移动的时候我们去对比这个列表,如果对比上的话,就说明当前元素不需要移动
356 | // 通过 moved 来进行优化,如果没有移动过的话 那么就不需要执行算法
357 | // getSequence 返回的是 newIndexToOldIndexMap 的索引值
358 | // 所以后面我们可以直接遍历索引值来处理,也就是直接使用 toBePatched 即可
359 | const increasingNewIndexSequence = moved
360 | ? getSequence(newIndexToOldIndexMap)
361 | : []
362 | // 创建指针
363 | let j = increasingNewIndexSequence.length - 1
364 |
365 | // 遍历新节点
366 | // 1. 需要找出老节点没有,而新节点有的 -> 需要把这个节点创建
367 | // 2. 最后需要移动一下位置,比如 [c,d,e] -> [e,c,d]
368 | // 这里倒循环是因为在 insert 的时候,需要保证锚点是处理完的节点(也就是已经确定位置了)
369 | // 因为 insert 逻辑是使用的 insertBefore()
370 | for (let i = toBePatched - 1; i >= 0; i--) {
371 | const nextIndex = i + s2
372 | const nextChild = c2[nextIndex]
373 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null
374 | if (newIndexToOldIndexMap[i] === 0) {
375 | // 新节点新增的
376 | patch(null, nextChild, container, parentComponent, anchor)
377 | }
378 | if (moved) {
379 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
380 | // 需要移动
381 | // 1. j 已经没有了 说明剩下的都需要移动了
382 | // 2. 最长子序列里面的值和当前的值匹配不上, 说明当前元素需要移动
383 | hostInsert(nextChild.el, container, anchor)
384 | }
385 | else {
386 | // 在最长递增子序列内, 直接移动指针
387 | j--
388 | }
389 | }
390 | }
391 | }
392 | }
393 |
394 | function unmountChildren(children) {
395 | // 循环children 删除子节点
396 | for (let i = 0; i < children.length; i++) {
397 | const el = children[i].el
398 | hostRemove(el)
399 | }
400 | }
401 |
402 | function patchProps(el, oldProps, newProps) {
403 | if (oldProps !== newProps) {
404 | // 对比 props 有以下几种情况
405 | // 1. oldProps 有,newProps 也有,但是 val 值变更了
406 | // 举个栗子
407 | // 之前: oldProps.id = 1 ,更新后:newProps.id = 2
408 |
409 | // key 存在 oldProps 里 也存在 newProps 内
410 | // 以 newProps 作为基准
411 | for (const key in newProps) {
412 | const prevProp = oldProps[key]
413 | const nextProp = newProps[key]
414 | // 获取改变前后的prop 进行更新
415 | if (prevProp !== nextProp)
416 | hostPatchProp(el, key, prevProp, nextProp)
417 | }
418 | // 2. oldProps 有,而 newProps 没有了
419 | // 之前: {id:1,tId:2} 更新后: {id:1}
420 | // 这种情况下我们就应该以 oldProps 作为基准,因为在 newProps 里面是没有的 tId 的
421 | if (oldProps !== EMPTY_OBJ) {
422 | for (const key in oldProps) {
423 | // 如果更新后key不在newProps里 删除
424 | if (!(key in newProps))
425 | hostPatchProp(el, key, oldProps[key], null)
426 | }
427 | }
428 | }
429 | }
430 |
431 | function mountElement(n2: any, container: any, parentComponent, anchor) {
432 | // 创建element节点
433 | const el = (n2.el = hostCreateElement(n2.type))
434 | const { children, props, shapeFlag } = n2
435 |
436 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
437 | // 举个栗子
438 | // render(){
439 | // return h("div",{},"test")
440 | // }
441 | // 这里 children 就是 test ,只需要渲染一下就完事了
442 | console.log(`处理文本 ${children}`)
443 | hostSetElementText(el, children)
444 | }
445 | else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
446 | // 如果是array类型 调用mountChildren
447 | // 举个栗子
448 | // render(){
449 | // Hello 是个 component
450 | // return h("div",{},[h("p"),h(Hello)])
451 | // }
452 | // 这里 children 就是个数组了,就需要依次调用 patch 递归来处理
453 | mountChildren(n2.children, el, parentComponent, anchor)
454 | }
455 | if (props) {
456 | // 循环节点的props 进行props的赋值
457 | // TODO
458 | // 需要过滤掉vue自身用的key
459 | // 比如生命周期相关的 key: beforeMount、mounted
460 | for (const key in props) {
461 | const val = props[key]
462 | hostPatchProp(el, key, null, val)
463 | }
464 | }
465 |
466 | // 触发beforeMount()
467 | console.log('vnodeHook -> onVnodeBeforeMount')
468 | console.log('DirectiveHook -> beforeMount')
469 | console.log('transition -> beforeEnter')
470 |
471 | // 生成真实DOM 添加到页面中
472 | hostInsert(el, container, anchor)
473 |
474 | // 触发mounted()
475 | console.log('vnodeHook -> onVnodeMounted')
476 | console.log('DirectiveHook -> mounted')
477 | console.log('transition -> enter')
478 | }
479 |
480 | function mountChildren(children, container, parentComponent, anchor) {
481 | // 循环children, 再次调用patch
482 | children.forEach((v) => {
483 | patch(null, v, container, parentComponent, anchor)
484 | })
485 | }
486 |
487 | function processComponent(
488 | n1,
489 | n2: any,
490 | container: any,
491 | parentComponent,
492 | anchor,
493 | ) {
494 | if (!n1) {
495 | // 如果没有n1 那么就是初始化mount
496 | mountComponent(n2, container, parentComponent, anchor)
497 | }
498 | else {
499 | updateComponent(n1, n2)
500 | }
501 | }
502 |
503 | function updateComponent(n1, n2) {
504 | const instance = (n2.component = n1.component)
505 | // 如果props相等 就不需要更新
506 | if (shouldUpdateComponent(n1, n2)) {
507 | console.log(`组件需要更新: ${instance}`)
508 | // 下次要更新的节点
509 | instance.next = n2
510 | // 这里的 update 是在 setupRenderEffect 里面初始化的,update 函数除了当内部的响应式对象发生改变的时候会调用
511 | // 还可以直接主动的调用(这是属于 effect 的特性)
512 | // 调用 update 再次更新调用 patch 逻辑
513 | // 在update 中调用的 next 就变成了 n2了
514 | // TODO 需要在 update 中处理支持 next 的逻辑
515 | instance.update()
516 | }
517 | else {
518 | // 不执行update的话 还是需要给el vnode重新赋值的
519 | console.log(`组件不需要更新: ${instance}`)
520 | n2.el = n1.el
521 | instance.vnode = n2
522 | }
523 | }
524 |
525 | function mountComponent(
526 | initialVNode: any,
527 | container: any,
528 | parentComponent,
529 | anchor,
530 | ) {
531 | // 创建组件实例
532 | const instance = (initialVNode.component = createComponentInstance(
533 | initialVNode,
534 | parentComponent,
535 | ))
536 | console.log(`创建组件实例:${instance.type.name}`)
537 | // 初始化组件
538 | setupComponent(instance)
539 | // 设置effect 收集依赖 获取subTree虚拟节点树
540 | setupRenderEffect(instance, initialVNode, container, anchor)
541 | }
542 |
543 | function setupRenderEffect(
544 | instance: any,
545 | initialVNode: any,
546 | container: any,
547 | anchor,
548 | ) {
549 | // 调用render时 会触发响应式对象ref/reactive的get收集依赖
550 | // 响应式对象改变了 会触发内部的函数 自动调用render生成新的subTree
551 | // 保存更新函数
552 | instance.update = effect(
553 | () => {
554 | if (!instance.isMounted) {
555 | // 组件初始化的时候会执行这里
556 | // 为什么要在这里调用 render 函数呢
557 | // 是因为在 effect 内调用 render 才能触发依赖收集
558 | // 等到后面响应式的值变更后会再次触发这个函数
559 | console.log(`setupRenderEffect -> ${instance.type.name} -> init`)
560 | console.log(`${instance.type.name}:调用 render,获取 subTree`)
561 | const { proxy } = instance
562 | // 可在 render 函数中通过 this 来使用 proxy
563 | const subTree = (instance.subTree = instance.render.call(
564 | proxy,
565 | proxy,
566 | ))
567 |
568 | // 这里触发beforeMount
569 | console.log(`${instance.type.name}:触发 beforeMount hook`)
570 | console.log(`${instance.type.name}:触发 onVnodeBeforeMount hook`)
571 |
572 | // 这里基于 subTree 再次调用 patch
573 | // 基于 render 返回的 vnode ,再次进行渲染
574 | // 这里我把这个行为隐喻成开箱
575 | // 一个组件就是一个箱子
576 | // 里面有可能是 element (也就是可以直接渲染的)
577 | // 也有可能还是 component
578 | // 这里就是递归的开箱
579 | // 而 subTree 就是当前的这个箱子(组件)装的东西
580 | // 箱子(组件)只是个概念,它实际是不需要渲染的
581 | // 要渲染的是箱子里面的 subTree
582 | patch(null, subTree, container, instance, anchor)
583 |
584 | // 更新el
585 | // 把 root element 赋值给 组件的vnode.el ,为后续调用 $el 的时候获取值
586 | initialVNode.el = subTree.el
587 |
588 | // 这里触发mounted
589 | console.log(`${instance.type.name}:触发 mounted hook`)
590 | // 更新instance的状态为isMounted 依赖变更时进入else分支
591 | instance.isMounted = true
592 | }
593 | else {
594 | // 响应式对象发生改变时会进到这里
595 | // 拿到新的 vnode ,然后和之前的 vnode 进行对比
596 | console.log(`setupRenderEffect -> ${instance.type.name} -> update`)
597 | console.log(`${instance.type.name}:调用更新逻辑`)
598 | // vnode是更新之前的 next是更新之后的
599 | const { proxy, next, vnode } = instance
600 | // 如果有 next 的话, 说明需要更新组件的数据(props,slots 等)
601 | // 先更新组件的数据,然后更新完成后,在继续对比当前组件的子元素
602 | // 更新el
603 | if (next) {
604 | next.el = vnode.el
605 | updateComponentPreRender(instance, next)
606 | }
607 | // 获取虚拟节点树
608 | const subTree = instance.render.call(proxy, proxy)
609 | const prevSubTree = instance.subTree
610 | // 更新subTree
611 | instance.subTree = subTree
612 |
613 | // 触发 beforeUpdated hook
614 | console.log(`${instance.type.name}:触发 beforeUpdated hook`)
615 | console.log(`${instance.type.name}:触发 onVnodeBeforeUpdate hook`)
616 |
617 | // 新旧虚拟节点数进行对比, 重新patch
618 | patch(prevSubTree, subTree, container, instance, anchor)
619 |
620 | // 触发 updated hook
621 | console.log(`${instance.type.name}:触发 updated hook`)
622 | console.log(`${instance.type.name}:触发 onVnodeUpdated hook`)
623 | }
624 | },
625 | {
626 | // 如果组件中有循环的话, 那么就会多次update组件造成性能浪费
627 | // 将所有update作为job存储在一个队列中
628 | // 当所有的同步任务结束之后 再统一拿出来执行
629 | scheduler() {
630 | console.log('组件更新 ---- 执行scheduler储存jobs')
631 | queueJobs(instance.update)
632 | },
633 | },
634 | )
635 | }
636 |
637 | function updateComponentPreRender(instance, nextVNode) {
638 | // 更新 nextVNode 的组件实例
639 | // 现在 instance.vnode 是组件实例更新前的
640 | // 所以之前的 props 就是基于 instance.vnode.props 来获取
641 | // 接着需要更新 vnode ,方便下一次更新的时候获取到正确的值
642 | instance.vnode = nextVNode
643 | instance.next = null
644 | // 更新props
645 | instance.props = nextVNode.props
646 | }
647 |
648 | return {
649 | createApp: createAppAPI(render),
650 | }
651 | }
652 |
653 | function getSequence(arr) {
654 | const p = arr.slice()
655 | const result = [0]
656 | let i, j, u, v, c
657 | const len = arr.length
658 | for (i = 0; i < len; i++) {
659 | const arrI = arr[i]
660 | if (arrI !== 0) {
661 | j = result[result.length - 1]
662 | if (arr[j] < arrI) {
663 | p[i] = j
664 | result.push(i)
665 | continue
666 | }
667 | u = 0
668 | v = result.length - 1
669 | while (u < v) {
670 | c = (u + v) >> 1
671 | if (arr[result[c]] < arrI)
672 | u = c + 1
673 | else
674 | v = c
675 | }
676 | if (arrI < arr[result[u]]) {
677 | if (u > 0)
678 | p[i] = result[u - 1]
679 |
680 | result[u] = i
681 | }
682 | }
683 | }
684 | u = result.length
685 | v = result[u - 1]
686 | while (u-- > 0) {
687 | result[u] = v
688 | v = p[v]
689 | }
690 | return result
691 | }
692 |
--------------------------------------------------------------------------------
/yarn-error.log:
--------------------------------------------------------------------------------
1 | Arguments:
2 | /usr/local/bin/node /usr/local/bin/yarn add vitest
3 |
4 | PATH:
5 | /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Users/mac/.fig/bin
6 |
7 | Yarn version:
8 | 1.22.11
9 |
10 | Node version:
11 | 14.17.1
12 |
13 | Platform:
14 | darwin x64
15 |
16 | Trace:
17 | SyntaxError: /Users/mac/OpenSource/vue3-proxy/package.json: Unexpected token } in JSON at position 214
18 | at JSON.parse ()
19 | at /usr/local/lib/node_modules/yarn/lib/cli.js:1625:59
20 | at Generator.next ()
21 | at step (/usr/local/lib/node_modules/yarn/lib/cli.js:310:30)
22 | at /usr/local/lib/node_modules/yarn/lib/cli.js:321:13
23 |
24 | npm manifest:
25 | {
26 | "name": "vue3-proxy",
27 | "version": "1.0.0",
28 | "main": "index.js",
29 | "devDependencies": {
30 | "@rollup/plugin-typescript": "^8.4.0",
31 | "rollup": "^2.79.0",
32 | "tslib": "^2.4.0",
33 | "typescript": "^4.7.3",
34 | },
35 | "scripts": {
36 | "test": "vitest src/",
37 | "build": "rollup -c rollup.config.js"
38 | },
39 | "repository": {
40 | "type": "git",
41 | "url": "git+https://github.com/cuiyiming1998/vue3-proxy.git"
42 | },
43 | "keywords": [],
44 | "author": "yiming.cui ",
45 | "license": "ISC",
46 | "bugs": {
47 | "url": "https://github.com/cuiyiming1998/vue3-proxy/issues"
48 | },
49 | "homepage": "https://github.com/cuiyiming1998/vue3-proxy#readme",
50 | "description": ""
51 | }
52 |
53 | yarn manifest:
54 | No manifest
55 |
56 | Lockfile:
57 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
58 | # yarn lockfile v1
59 |
60 |
61 | "@rollup/plugin-typescript@^8.4.0":
62 | version "8.4.0"
63 | resolved "https://registry.npmmirror.com/@rollup/plugin-typescript/-/plugin-typescript-8.4.0.tgz#a8a384b6dbaab42b4cafb075278b15743c0f5ef8"
64 | integrity sha512-QssfoOP6V4/6skX12EfOW5UzJAv/c334F4OJWmQpe2kg3agEa0JwVCckwmfuvEgDixyX+XyxjFenH7M2rDKUyQ==
65 | dependencies:
66 | "@rollup/pluginutils" "^3.1.0"
67 | resolve "^1.17.0"
68 |
69 | "@rollup/pluginutils@^3.1.0":
70 | version "3.1.0"
71 | resolved "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
72 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
73 | dependencies:
74 | "@types/estree" "0.0.39"
75 | estree-walker "^1.0.1"
76 | picomatch "^2.2.2"
77 |
78 | "@types/chai-subset@^1.3.3":
79 | version "1.3.3"
80 | resolved "https://registry.npmmirror.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
81 | integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==
82 | dependencies:
83 | "@types/chai" "*"
84 |
85 | "@types/chai@*", "@types/chai@^4.3.1":
86 | version "4.3.1"
87 | resolved "https://registry.npmmirror.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04"
88 | integrity sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==
89 |
90 | "@types/estree@0.0.39":
91 | version "0.0.39"
92 | resolved "https://registry.npmmirror.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
93 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
94 |
95 | "@types/node@*":
96 | version "17.0.42"
97 | resolved "https://registry.npmmirror.com/@types/node/-/node-17.0.42.tgz#d7e8f22700efc94d125103075c074396b5f41f9b"
98 | integrity sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==
99 |
100 | assertion-error@^1.1.0:
101 | version "1.1.0"
102 | resolved "https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
103 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
104 |
105 | chai@^4.3.6:
106 | version "4.3.6"
107 | resolved "https://registry.npmmirror.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c"
108 | integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==
109 | dependencies:
110 | assertion-error "^1.1.0"
111 | check-error "^1.0.2"
112 | deep-eql "^3.0.1"
113 | get-func-name "^2.0.0"
114 | loupe "^2.3.1"
115 | pathval "^1.1.1"
116 | type-detect "^4.0.5"
117 |
118 | check-error@^1.0.2:
119 | version "1.0.2"
120 | resolved "https://registry.npmmirror.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
121 | integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==
122 |
123 | debug@^4.3.4:
124 | version "4.3.4"
125 | resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
126 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
127 | dependencies:
128 | ms "2.1.2"
129 |
130 | deep-eql@^3.0.1:
131 | version "3.0.1"
132 | resolved "https://registry.npmmirror.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
133 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==
134 | dependencies:
135 | type-detect "^4.0.0"
136 |
137 | esbuild-android-64@0.14.44:
138 | version "0.14.44"
139 | resolved "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.44.tgz#62f5cb563d0ba318d898b6eb230c61ad3dc93619"
140 | integrity sha512-dFPHBXmx385zuJULAD/Cmq/LyPRXiAWbf9ylZtY0wJ8iVyWfKYaCYxeJx8OAZUuj46ZwNa7MzW2GBAQLOeiemg==
141 |
142 | esbuild-android-arm64@0.14.44:
143 | version "0.14.44"
144 | resolved "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.44.tgz#ee7fcc9f47855b3395dfd1abcc2c43d8c53e57db"
145 | integrity sha512-qqaqqyxHXjZ/0ddKU3I3Nb7lAvVM69ELMhb8+91FyomAUmQPlHtxe+TTiWxXGHE72XEzcgTEGq4VauqLNkN22g==
146 |
147 | esbuild-darwin-64@0.14.44:
148 | version "0.14.44"
149 | resolved "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.44.tgz#75ea7f594687a7189a8ba62070d42f13178e68d0"
150 | integrity sha512-RBmtGKGY06+AW6IOJ1LE/dEeF7HH34C1/Ces9FSitU4bIbIpL4KEuQpTFoxwb4ry5s2hyw7vbPhhtyOd18FH9g==
151 |
152 | esbuild-darwin-arm64@0.14.44:
153 | version "0.14.44"
154 | resolved "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.44.tgz#550631fd135688abda042f4039b962a27f3a5f78"
155 | integrity sha512-Bmhx5Cfo4Hdb7WyyyDupTB8HPmnFZ8baLfPlzLdYvF6OzsIbV+CY+m/AWf0OQvY40BlkzCLJ/7Lfwbb71Tngmg==
156 |
157 | esbuild-freebsd-64@0.14.44:
158 | version "0.14.44"
159 | resolved "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.44.tgz#f98069b964266ca79bb361cfb9d7454a05b7e8ca"
160 | integrity sha512-O4HpWa5ZgxbNPQTF7URicLzYa+TidGlmGT/RAC3GjbGEQQYkd0R1Slyh69Yrmb2qmcOcPAgWHbNo1UhK4WmZ4w==
161 |
162 | esbuild-freebsd-arm64@0.14.44:
163 | version "0.14.44"
164 | resolved "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.44.tgz#247a8553d6033a58b6c3ba4d539216300497d7f6"
165 | integrity sha512-f0/jkAKccnDY7mg1F9l/AMzEm+VXWXK6c3IrOEmd13jyKfpTZKTIlt+yI04THPDCDZTzXHTRUBLozqp+m8Mg5Q==
166 |
167 | esbuild-linux-32@0.14.44:
168 | version "0.14.44"
169 | resolved "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.44.tgz#4b72747f9f367d3ee3c1d80bf75c617e6b109821"
170 | integrity sha512-WSIhzLldMR7YUoEL7Ix319tC+NFmW9Pu7NgFWxUfOXeWsT0Wg484hm6bNgs7+oY2pGzg715y/Wrqi1uNOMmZJw==
171 |
172 | esbuild-linux-64@0.14.44:
173 | version "0.14.44"
174 | resolved "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.44.tgz#6cd158fdd11f8d037c45139ccddb4fa5966f7ab6"
175 | integrity sha512-zgscTrCMcRZRIsVugqBTP/B5lPLNchBlWjQ8sQq2Epnv+UDtYKgXEq1ctWAmibZNy2E9QRCItKMeIEqeTUT5kA==
176 |
177 | esbuild-linux-arm64@0.14.44:
178 | version "0.14.44"
179 | resolved "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.44.tgz#f14f3c8c3501067f5b2cef7a79c9a0058092cb22"
180 | integrity sha512-H0H/2/wgiScTwBve/JR8/o+Zhabx5KPk8T2mkYZFKQGl1hpUgC+AOmRyqy/Js3p66Wim4F4Akv3I3sJA1sKg0w==
181 |
182 | esbuild-linux-arm@0.14.44:
183 | version "0.14.44"
184 | resolved "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.44.tgz#625478cc6ea4f64f5335e7fb07f80d6f659aeb5c"
185 | integrity sha512-laPBPwGfsbBxGw6F6jnqic2CPXLyC1bPrmnSOeJ9oEnx1rcKkizd4HWCRUc0xv+l4z/USRfx/sEfYlWSLeqoJQ==
186 |
187 | esbuild-linux-mips64le@0.14.44:
188 | version "0.14.44"
189 | resolved "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.44.tgz#593c909bb612998300af7f7db2809a63a0d62eec"
190 | integrity sha512-ri3Okw0aleYy7o5n9zlIq+FCtq3tcMlctN6X1H1ucILjBJuH8pan2trJPKWeb8ppntFvE28I9eEXhwkWh6wYKg==
191 |
192 | esbuild-linux-ppc64le@0.14.44:
193 | version "0.14.44"
194 | resolved "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.44.tgz#8fc2fcee671e322a5d44fcf8da6889095a187df9"
195 | integrity sha512-96TqL/MvFRuIVXz+GtCIXzRQ43ZwEk4XTn0RWUNJduXXMDQ/V1iOV28U6x6Oe3NesK4xkoKSaK2+F3VHcU8ZrA==
196 |
197 | esbuild-linux-riscv64@0.14.44:
198 | version "0.14.44"
199 | resolved "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.44.tgz#cc2b7158c8345b67e74458a0ec020c80ca777046"
200 | integrity sha512-rrK9qEp2M8dhilsPn4T9gxUsAumkITc1kqYbpyNMr9EWo+J5ZBj04n3GYldULrcCw4ZCHAJ+qPjqr8b6kG2inA==
201 |
202 | esbuild-linux-s390x@0.14.44:
203 | version "0.14.44"
204 | resolved "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.44.tgz#5a1e87d5d6236a8791026820936478f3b9cf8e35"
205 | integrity sha512-2YmTm9BrW5aUwBSe8wIEARd9EcnOQmkHp4+IVaO09Ez/C5T866x+ABzhG0bwx0b+QRo9q97CRMaQx2Ngb6/hfw==
206 |
207 | esbuild-netbsd-64@0.14.44:
208 | version "0.14.44"
209 | resolved "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.44.tgz#8afb16880b530264ce2ed9fb3df26071eb841312"
210 | integrity sha512-zypdzPmZTCqYS30WHxbcvtC0E6e/ECvl4WueUdbdWhs2dfWJt5RtCBME664EpTznixR3lSN1MQ2NhwQF8MQryw==
211 |
212 | esbuild-openbsd-64@0.14.44:
213 | version "0.14.44"
214 | resolved "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.44.tgz#a87455be446d6f5b07a60f060e1eafad454c9d4b"
215 | integrity sha512-8J43ab9ByYl7KteC03HGQjr2HY1ge7sN04lFnwMFWYk2NCn8IuaeeThvLeNjzOYhyT3I6K8puJP0uVXUu+D1xw==
216 |
217 | esbuild-sunos-64@0.14.44:
218 | version "0.14.44"
219 | resolved "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.44.tgz#74ce7d5fd815fa60fe0a85b9db7549b1dcb32804"
220 | integrity sha512-OH1/09CGUJwffA+HNM6mqPkSIyHVC3ZnURU/4CCIx7IqWUBn1Sh1HRLQC8/TWNgcs0/1u7ygnc2pgf/AHZJ/Ow==
221 |
222 | esbuild-windows-32@0.14.44:
223 | version "0.14.44"
224 | resolved "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.44.tgz#499e1c6cb46c216bdcb62f5b006fec3999bb06a7"
225 | integrity sha512-mCAOL9/rRqwfOfxTu2sjq/eAIs7eAXGiU6sPBnowggI7QS953Iq6o3/uDu010LwfN7zr18c/lEj6/PTwwTB3AA==
226 |
227 | esbuild-windows-64@0.14.44:
228 | version "0.14.44"
229 | resolved "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.44.tgz#a8dae69a4615b17f62375859ce9536dd96940a06"
230 | integrity sha512-AG6BH3+YG0s2Q/IfB1cm68FdyFnoE1P+GFbmgFO3tA4UIP8+BKsmKGGZ5I3+ZjcnzOwvT74bQRVrfnQow2KO5Q==
231 |
232 | esbuild-windows-arm64@0.14.44:
233 | version "0.14.44"
234 | resolved "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.44.tgz#db73bb68aa75a880bdaa938ccacc0f17e7a4bfea"
235 | integrity sha512-ygYPfYE5By4Sd6szsNr10B0RtWVNOSGmZABSaj4YQBLqh9b9i45VAjVWa8tyIy+UAbKF7WGwybi2wTbSVliO8A==
236 |
237 | esbuild@^0.14.27:
238 | version "0.14.44"
239 | resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.44.tgz#27fa3cd4f55d36780650c7f32cab581abc7a4473"
240 | integrity sha512-Rn+lRRfj60r/3svI6NgAVnetzp3vMOj17BThuhshSj/gS1LR03xrjkDYyfPmrYG/0c3D68rC6FNYMQ3yRbiXeQ==
241 | optionalDependencies:
242 | esbuild-android-64 "0.14.44"
243 | esbuild-android-arm64 "0.14.44"
244 | esbuild-darwin-64 "0.14.44"
245 | esbuild-darwin-arm64 "0.14.44"
246 | esbuild-freebsd-64 "0.14.44"
247 | esbuild-freebsd-arm64 "0.14.44"
248 | esbuild-linux-32 "0.14.44"
249 | esbuild-linux-64 "0.14.44"
250 | esbuild-linux-arm "0.14.44"
251 | esbuild-linux-arm64 "0.14.44"
252 | esbuild-linux-mips64le "0.14.44"
253 | esbuild-linux-ppc64le "0.14.44"
254 | esbuild-linux-riscv64 "0.14.44"
255 | esbuild-linux-s390x "0.14.44"
256 | esbuild-netbsd-64 "0.14.44"
257 | esbuild-openbsd-64 "0.14.44"
258 | esbuild-sunos-64 "0.14.44"
259 | esbuild-windows-32 "0.14.44"
260 | esbuild-windows-64 "0.14.44"
261 | esbuild-windows-arm64 "0.14.44"
262 |
263 | estree-walker@^1.0.1:
264 | version "1.0.1"
265 | resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
266 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
267 |
268 | fsevents@~2.3.2:
269 | version "2.3.2"
270 | resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
271 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
272 |
273 | function-bind@^1.1.1:
274 | version "1.1.1"
275 | resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
276 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
277 |
278 | get-func-name@^2.0.0:
279 | version "2.0.0"
280 | resolved "https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
281 | integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==
282 |
283 | has@^1.0.3:
284 | version "1.0.3"
285 | resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
286 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
287 | dependencies:
288 | function-bind "^1.1.1"
289 |
290 | is-core-module@^2.8.1:
291 | version "2.9.0"
292 | resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
293 | integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
294 | dependencies:
295 | has "^1.0.3"
296 |
297 | is-core-module@^2.9.0:
298 | version "2.10.0"
299 | resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
300 | integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
301 | dependencies:
302 | has "^1.0.3"
303 |
304 | local-pkg@^0.4.1:
305 | version "0.4.1"
306 | resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.1.tgz#e7b0d7aa0b9c498a1110a5ac5b00ba66ef38cfff"
307 | integrity sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==
308 |
309 | loupe@^2.3.1:
310 | version "2.3.4"
311 | resolved "https://registry.npmmirror.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3"
312 | integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==
313 | dependencies:
314 | get-func-name "^2.0.0"
315 |
316 | ms@2.1.2:
317 | version "2.1.2"
318 | resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
319 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
320 |
321 | nanoid@^3.3.4:
322 | version "3.3.4"
323 | resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
324 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
325 |
326 | path-parse@^1.0.7:
327 | version "1.0.7"
328 | resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
329 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
330 |
331 | pathval@^1.1.1:
332 | version "1.1.1"
333 | resolved "https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
334 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
335 |
336 | picocolors@^1.0.0:
337 | version "1.0.0"
338 | resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
339 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
340 |
341 | picomatch@^2.2.2:
342 | version "2.3.1"
343 | resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
344 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
345 |
346 | postcss@^8.4.13:
347 | version "8.4.14"
348 | resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
349 | integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
350 | dependencies:
351 | nanoid "^3.3.4"
352 | picocolors "^1.0.0"
353 | source-map-js "^1.0.2"
354 |
355 | resolve@^1.17.0:
356 | version "1.22.1"
357 | resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
358 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
359 | dependencies:
360 | is-core-module "^2.9.0"
361 | path-parse "^1.0.7"
362 | supports-preserve-symlinks-flag "^1.0.0"
363 |
364 | resolve@^1.22.0:
365 | version "1.22.0"
366 | resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
367 | integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
368 | dependencies:
369 | is-core-module "^2.8.1"
370 | path-parse "^1.0.7"
371 | supports-preserve-symlinks-flag "^1.0.0"
372 |
373 | rollup@^2.59.0:
374 | version "2.75.6"
375 | resolved "https://registry.npmmirror.com/rollup/-/rollup-2.75.6.tgz#ac4dc8600f95942a0180f61c7c9d6200e374b439"
376 | integrity sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==
377 | optionalDependencies:
378 | fsevents "~2.3.2"
379 |
380 | rollup@^2.79.0:
381 | version "2.79.0"
382 | resolved "https://registry.npmmirror.com/rollup/-/rollup-2.79.0.tgz#9177992c9f09eb58c5e56cbfa641607a12b57ce2"
383 | integrity sha512-x4KsrCgwQ7ZJPcFA/SUu6QVcYlO7uRLfLAy0DSA4NS2eG8japdbpM50ToH7z4iObodRYOJ0soneF0iaQRJ6zhA==
384 | optionalDependencies:
385 | fsevents "~2.3.2"
386 |
387 | source-map-js@^1.0.2:
388 | version "1.0.2"
389 | resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
390 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
391 |
392 | supports-preserve-symlinks-flag@^1.0.0:
393 | version "1.0.0"
394 | resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
395 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
396 |
397 | tinypool@^0.1.3:
398 | version "0.1.3"
399 | resolved "https://registry.npmmirror.com/tinypool/-/tinypool-0.1.3.tgz#b5570b364a1775fd403de5e7660b325308fee26b"
400 | integrity sha512-2IfcQh7CP46XGWGGbdyO4pjcKqsmVqFAPcXfPxcPXmOWt9cYkTP9HcDmGgsfijYoAEc4z9qcpM/BaBz46Y9/CQ==
401 |
402 | tinyspy@^0.3.2:
403 | version "0.3.3"
404 | resolved "https://registry.npmmirror.com/tinyspy/-/tinyspy-0.3.3.tgz#8b57f8aec7fe1bf583a3a49cb9ab30c742f69237"
405 | integrity sha512-gRiUR8fuhUf0W9lzojPf1N1euJYA30ISebSfgca8z76FOvXtVXqd5ojEIaKLWbDQhAaC3ibxZIjqbyi4ybjcTw==
406 |
407 | tslib@^2.4.0:
408 | version "2.4.0"
409 | resolved "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
410 | integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
411 |
412 | type-detect@^4.0.0, type-detect@^4.0.5:
413 | version "4.0.8"
414 | resolved "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
415 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
416 |
417 | typescript@^4.7.3:
418 | version "4.7.3"
419 | resolved "https://registry.npmmirror.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
420 | integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
421 |
422 | vite@^2.9.12:
423 | version "2.9.12"
424 | resolved "https://registry.npmmirror.com/vite/-/vite-2.9.12.tgz#b1d636b0a8ac636afe9d83e3792d4895509a941b"
425 | integrity sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew==
426 | dependencies:
427 | esbuild "^0.14.27"
428 | postcss "^8.4.13"
429 | resolve "^1.22.0"
430 | rollup "^2.59.0"
431 | optionalDependencies:
432 | fsevents "~2.3.2"
433 |
434 | vitest@^0.15.1:
435 | version "0.15.1"
436 | resolved "https://registry.npmmirror.com/vitest/-/vitest-0.15.1.tgz#8bdb42544261fa95afe8ea10bae3f315ca7e4c74"
437 | integrity sha512-NaNFi93JKSuvV4YGnfQ0l0GKYxH0EsLcTrrXaCzd6qfVEZM/RJpjwSevg6waNFqu2DyN6e0aHHdrCZW5/vh5NA==
438 | dependencies:
439 | "@types/chai" "^4.3.1"
440 | "@types/chai-subset" "^1.3.3"
441 | "@types/node" "*"
442 | chai "^4.3.6"
443 | debug "^4.3.4"
444 | local-pkg "^0.4.1"
445 | tinypool "^0.1.3"
446 | tinyspy "^0.3.2"
447 | vite "^2.9.12"
448 |
--------------------------------------------------------------------------------