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