├── README.md ├── src ├── index.ts ├── reactivity │ ├── index.ts │ ├── tests │ │ ├── shallowReadonly.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── computed.spec.ts │ │ ├── ref.spec.ts │ │ └── effect.spec.ts │ ├── computed.ts │ ├── reactive.ts │ ├── baseHandler.ts │ ├── ref.ts │ └── effect.ts ├── runtime-core │ ├── componentProps.ts │ ├── h.ts │ ├── index.ts │ ├── componentEmit.ts │ ├── helpers │ │ └── renderSlots.ts │ ├── createApp.ts │ ├── componentPublicInstance.ts │ ├── componentSlots.ts │ ├── apiInject.ts │ ├── vnode.ts │ ├── component.ts │ └── renderer.ts ├── shared │ ├── ShapeFlags.ts │ └── index.ts └── runtime-dom │ └── index.ts ├── .gitignore ├── .vscode └── settings.json ├── .npmrc ├── cache ├── index.js ├── index.html └── App.js ├── babel.config.js ├── example ├── helloworld │ ├── main.js │ ├── Foo.js │ ├── index.html │ └── App.js ├── componentEmit │ ├── main.js │ ├── index.html │ ├── App.js │ └── Foo.js ├── componentSlot │ ├── main.js │ ├── index.html │ ├── Foo.js │ └── App.js ├── currentInstance │ ├── main.js │ ├── Foo.js │ ├── App.js │ └── index.html ├── customRenderer │ ├── App.js │ ├── index.html │ └── main.js └── apiInject │ ├── index.html │ └── App.js ├── rollup.config.js ├── core ├── index.js └── reactivity │ ├── index.js │ └── reactivity.js ├── package.json ├── tsconfig.json └── lib ├── guide-mini-vue.esm.js └── guide-mini-vue.cjs.js /README.md: -------------------------------------------------------------------------------- 1 | # mini-vue3 2 | 实现一个mini版的vue3来学习vue的原理 -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // mini-vue 出口 2 | export * from "./runtime-dom" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- 1 | export function add(a, b) { 2 | return a + b; 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": [], 3 | "liveServer.settings.port": 5501 4 | } 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npm.taobao.org 2 | @imf:registry=http://maven.jinuo.me/repository/jinuo-npm-inner 3 | -------------------------------------------------------------------------------- /cache/index.js: -------------------------------------------------------------------------------- 1 | import App from './App.js' 2 | import { createApp } from './core/index.js' 3 | createApp(App).mount('#app') -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance, rawProps) { 2 | instance.props = rawProps || {} 3 | } 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | 3 | export function h(type, props?, children?) { 4 | return createVNode(type, props, children); 5 | } 6 | -------------------------------------------------------------------------------- /example/helloworld/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/componentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /src/shared/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, // 1000 6 | SLOT_CHILDREN = 1 << 4, 7 | } 8 | -------------------------------------------------------------------------------- /example/customRenderer/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const App = { 4 | setup() { 5 | return { 6 | x: 100, 7 | y: 100, 8 | }; 9 | }, 10 | render() { 11 | return h("rect", { x: this.x, y: this.y }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | export { h } from "./h"; 2 | export { renderSlots } from "./helpers/renderSlots"; 3 | export { createTextVNode } from "./vnode"; 4 | export { getCurrentInstance } from "./component"; 5 | export { provide, inject } from "./apiInject"; 6 | export { createRenderer } from "./renderer"; 7 | -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { camelize, toHandlerKey } from "../shared/index"; 2 | 3 | export function emit(instance, event, ...args) { 4 | const { props } = instance; 5 | const handlerName = toHandlerKey(camelize(event)); 6 | const handler = props[handlerName]; 7 | handler && handler(...args); 8 | } 9 | -------------------------------------------------------------------------------- /src/runtime-core/helpers/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, Fragment } from "../vnode"; 2 | 3 | export function renderSlots(slots, name, props) { 4 | const slot = slots[name]; 5 | 6 | if (slot) { 7 | if (typeof slot === "function") { 8 | return createVNode(Fragment, {}, slot(props)); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/currentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | name:"Foo", 5 | setup() { 6 | const instance = getCurrentInstance(); 7 | console.log("Foo:", instance); 8 | return {}; 9 | }, 10 | render() { 11 | return h("div", {}, "foo"); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/runtime-core/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 vnode = createVNode(rootComponent); 8 | 9 | render(vnode, rootContainer); 10 | }, 11 | }; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /example/helloworld/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | setup(props) { 5 | // props.count 6 | console.log(props); 7 | 8 | // 3. 9 | // shallow readonly 10 | props.count++ 11 | console.log(props); 12 | 13 | }, 14 | render() { 15 | return h("div", {}, "foo: " + this.count); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /example/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const App = { 5 | name: "App", 6 | render() { 7 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]); 8 | }, 9 | 10 | setup() { 11 | const instance = getCurrentInstance(); 12 | console.log("App:", instance); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from "./package.json"; 2 | import typescript from "@rollup/plugin-typescript"; 3 | export default { 4 | input: "./src/index.ts", 5 | output: [ 6 | // 1. cjs -> commonjs 7 | // 2. esm 8 | { 9 | format: "cjs", 10 | file: pkg.main, 11 | }, 12 | { 13 | format: "es", 14 | file: pkg.module, 15 | }, 16 | ], 17 | 18 | plugins: [typescript()], 19 | }; 20 | -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/componentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const App = { 5 | name: "App", 6 | render() { 7 | // emit 8 | return h("div", {}, [ 9 | h("div", {}, "App"), 10 | h(Foo, { 11 | onAdd(a, b) { 12 | console.log("onAdd", a, b); 13 | }, 14 | onAddFoo() { 15 | console.log("onAddFoo"); 16 | }, 17 | }), 18 | ]); 19 | }, 20 | 21 | setup() { 22 | return {}; 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /core/index.js: -------------------------------------------------------------------------------- 1 | import {watchEffect} from './reactivity/reactivity.js' 2 | export function createApp(rootComponent){ 3 | return { 4 | mount(rootContainer){ 5 | const context = rootComponent.setup() 6 | watchEffect(()=>{ 7 | //当状体更改,重新生成视图 8 | document.querySelector(rootContainer).innerHTML=`` 9 | const element = rootComponent.render(context) 10 | document.querySelector(rootContainer).append(element) 11 | }) 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /example/helloworld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/currentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/componentEmit/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | setup(props, { emit }) { 5 | const emitAdd = () => { 6 | console.log("emit add"); 7 | emit("add",1,2); 8 | emit("add-foo"); 9 | }; 10 | 11 | return { 12 | emitAdd, 13 | }; 14 | }, 15 | render() { 16 | const btn = h( 17 | "button", 18 | { 19 | onClick: this.emitAdd, 20 | }, 21 | "emitAdd" 22 | ); 23 | 24 | const foo = h("p", {}, "foo"); 25 | return h("div", {}, [foo, btn]); 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /cache/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 15 | 👇 响应式数据 16 |
17 | 18 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect"; 2 | 3 | class ComputedRefImpl { 4 | private _dirty: boolean = true; 5 | private _value: any; 6 | private _effect: any; 7 | constructor(getter) { 8 | this._effect = new ReactiveEffect(getter, () => { 9 | if (!this._dirty) { 10 | this._dirty = true; 11 | } 12 | }); 13 | } 14 | 15 | get value() { 16 | if (this._dirty) { 17 | this._dirty = false; 18 | this._value = this._effect.run(); 19 | } 20 | 21 | return this._value; 22 | } 23 | } 24 | 25 | export function computed(getter) { 26 | return new ComputedRefImpl(getter); 27 | } 28 | -------------------------------------------------------------------------------- /example/componentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | setup() { 5 | return {}; 6 | }, 7 | render() { 8 | const foo = h("p", {}, "foo"); 9 | 10 | // Foo .vnode. children 11 | console.log(this.$slots); 12 | // children -> vnode 13 | // 14 | // renderSlots 15 | // 具名插槽 16 | // 1. 获取到要渲染的元素 1 17 | // 2. 要获取到渲染的位置 18 | // 作用域插槽 19 | const age = 18; 20 | return h("div", {}, [ 21 | renderSlots(this.$slots, "header", { 22 | age, 23 | }), 24 | foo, 25 | renderSlots(this.$slots, "footer"), 26 | ]); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guide-mini-vue", 3 | "version": "1.0.0", 4 | "main": "lib/guide-mini-vue.cjs.js", 5 | "module": "lib/guide-mini-vue.esm.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "jest", 9 | "build": "rollup -c rollup.config.js" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.15.5", 13 | "@babel/preset-env": "^7.15.4", 14 | "@babel/preset-typescript": "^7.15.0", 15 | "@rollup/plugin-typescript": "^8.2.5", 16 | "@types/jest": "^27.0.1", 17 | "babel-jest": "^27.1.0", 18 | "jest": "^27.1.0", 19 | "rollup": "^2.57.0", 20 | "tslib": "^2.3.1", 21 | "typescript": "^4.4.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/componentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { h, createTextVNode } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | // Fragment 以及 Text 5 | export const App = { 6 | name: "App", 7 | render() { 8 | const app = h("div", {}, "App"); 9 | // object key 10 | const foo = h( 11 | Foo, 12 | {}, 13 | { 14 | header: ({ age }) => [ 15 | h("p", {}, "header" + age), 16 | createTextVNode("你好呀"), 17 | ], 18 | footer: () => h("p", {}, "footer"), 19 | } 20 | ); 21 | // 数组 vnode 22 | // const foo = h(Foo, {}, h("p", {}, "123")); 23 | return h("div", {}, [app, foo]); 24 | }, 25 | 26 | setup() { 27 | return {}; 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /example/customRenderer/main.js: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const game = new PIXI.Application({ 5 | width: 500, 6 | height: 500, 7 | }); 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); -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export const extend = Object.assign; 2 | 3 | export const isObject = (value) => { 4 | return value !== null && typeof value === "object"; 5 | }; 6 | 7 | export const hasChanged = (val, newValue) => { 8 | return !Object.is(val, newValue); 9 | }; 10 | 11 | export const hasOwn = (val, key) => 12 | Object.prototype.hasOwnProperty.call(val, key); 13 | 14 | export const camelize = (str: string) => { 15 | return str.replace(/-(\w)/g, (_, c: string) => { 16 | return c ? c.toUpperCase() : ""; 17 | }); 18 | }; 19 | 20 | const capitalize = (str: string) => { 21 | return str.charAt(0).toUpperCase() + str.slice(1); 22 | }; 23 | 24 | export const toHandlerKey = (str: string) => { 25 | return str ? "on" + capitalize(str) : ""; 26 | }; 27 | -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../runtime-core"; 2 | 3 | function createElement(type) { 4 | return document.createElement(type); 5 | } 6 | 7 | function patchProp(el, key, val) { 8 | const isOn = (key: string) => /^on[A-Z]/.test(key); 9 | if (isOn(key)) { 10 | const event = key.slice(2).toLowerCase(); 11 | el.addEventListener(event, val); 12 | } else { 13 | el.setAttribute(key, val); 14 | } 15 | } 16 | 17 | function insert(el, parent) { 18 | parent.append(el); 19 | } 20 | 21 | const renderer: any = createRenderer({ 22 | createElement, 23 | patchProp, 24 | insert, 25 | }); 26 | 27 | export function createApp(...args) { 28 | return renderer.createApp(...args); 29 | } 30 | 31 | export * from "../runtime-core"; 32 | -------------------------------------------------------------------------------- /core/reactivity/index.js: -------------------------------------------------------------------------------- 1 | let currentEffect 2 | class Dep{ 3 | constructor(value){ 4 | this._value=value 5 | this.effects= new Set() 6 | } 7 | 8 | get value(){ 9 | this.dep() 10 | return this._value 11 | } 12 | 13 | set value(newValue){ 14 | this._value=newValue 15 | this.notify() 16 | } 17 | 18 | dep(){ 19 | if(currentEffect){ 20 | this.effects.add(currentEffect) 21 | } 22 | } 23 | 24 | notify(){ 25 | this.effects.forEach(effect=>effect()) 26 | } 27 | 28 | } 29 | 30 | const watchEffect=(effect)=>{ 31 | currentEffect=effect 32 | effect() 33 | currentEffect=null 34 | } 35 | 36 | const dep = new Dep(10) 37 | let b 38 | const effect=()=>{ 39 | b=dep.value+10 40 | console.log(b) 41 | } 42 | watchEffect(effect) 43 | dep.value=20 44 | 45 | -------------------------------------------------------------------------------- /src/reactivity/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReactive, reactive, isProxy } from "../reactive"; 2 | describe("reactive", () => { 3 | it("happy path", () => { 4 | const original = { foo: 1 }; 5 | const observed = reactive(original); 6 | expect(observed).not.toBe(original); 7 | expect(observed.foo).toBe(1); 8 | expect(isReactive(observed)).toBe(true); 9 | expect(isReactive(original)).toBe(false); 10 | expect(isProxy(observed)).toBe(true); 11 | }); 12 | 13 | test("nested reactives", () => { 14 | const original = { 15 | nested: { 16 | foo: 1, 17 | }, 18 | array: [{ bar: 2 }], 19 | }; 20 | const observed = reactive(original); 21 | expect(isReactive(observed.nested)).toBe(true); 22 | expect(isReactive(observed.array)).toBe(true); 23 | expect(isReactive(observed.array[0])).toBe(true); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/reactivity/tests/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReadonly, readonly } from "../reactive"; 2 | 3 | describe("readonly", () => { 4 | it("should make nested values readonly", () => { 5 | const original = { foo: 1, bar: { baz: 2 } }; 6 | const wrapped = readonly(original); 7 | expect(wrapped).not.toBe(original); 8 | expect(isReadonly(wrapped)).toBe(true); 9 | expect(isReadonly(original)).toBe(false); 10 | expect(isReadonly(wrapped.bar)).toBe(true); 11 | expect(isReadonly(original.bar)).toBe(false); 12 | expect(isProxy(wrapped)).toBe(true); 13 | 14 | expect(wrapped.foo).toBe(1); 15 | }); 16 | 17 | it("should call console.warn when set", () => { 18 | console.warn = jest.fn(); 19 | const user = readonly({ 20 | age: 10, 21 | }); 22 | 23 | user.age = 11; 24 | expect(console.warn).toHaveBeenCalled(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /cache/App.js: -------------------------------------------------------------------------------- 1 | import {watchEffect,reactive}from './core/reactivity/reactivity.js' 2 | 3 | // const a = reactive({ 4 | // uname:"sudongyu" 5 | // }) 6 | // window.a=a 7 | // watchEffect(()=>{ 8 | // document.body.innerText='' 9 | // document.body.innerText=a.uname 10 | // }) 11 | 12 | 13 | export default { 14 | render(context){ 15 | //构建视图 16 | // watchEffect(()=>{ 17 | // document.body.innerHTML=`` 18 | const div = document.createElement('div') 19 | div.innerText=context.state.uname 20 | return div 21 | // document.body.appendChild(div) 22 | // }) 23 | }, 24 | setup() { 25 | //响应式数据 26 | const state = reactive({ 27 | uname:'sudongyu🐳' 28 | }) 29 | window.state=state 30 | return {state} 31 | } 32 | } 33 | 34 | // App.render(App.setup()) -------------------------------------------------------------------------------- /example/helloworld/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | window.self = null; 5 | export const App = { 6 | // 必须要写 render 7 | name:"App", 8 | render() { 9 | window.self = this; 10 | // ui 11 | return h( 12 | "div", 13 | { 14 | id: "root", 15 | class: ["red", "hard"], 16 | onClick() { 17 | console.log("click"); 18 | }, 19 | onMousedown() { 20 | console.log("mousedown"); 21 | }, 22 | }, 23 | [ 24 | h("div", {}, "hi," + this.msg), 25 | h(Foo, { 26 | count: 1, 27 | }), 28 | ] 29 | // "hi, " + this.msg 30 | // string 31 | // "hi, mini-vue" 32 | // Array 33 | // [h("p", { class:"red"}, "hi"), h("p", {class:"blue"}, "mini-vue")] 34 | ); 35 | }, 36 | 37 | setup() { 38 | return { 39 | msg: "mini-vue-haha", 40 | }; 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "./component"; 2 | 3 | export function provide(key, value) { 4 | const currentInstance: any = getCurrentInstance(); 5 | 6 | if (currentInstance) { 7 | let { provides } = currentInstance; 8 | const parentProvides = currentInstance.parent.provides; 9 | 10 | if (provides === parentProvides) { 11 | provides = currentInstance.provides = Object.create(parentProvides); 12 | } 13 | 14 | provides[key] = value; 15 | } 16 | } 17 | 18 | export function inject(key, defaultValue) { 19 | const currentInstance: any = getCurrentInstance(); 20 | 21 | if (currentInstance) { 22 | const parentProvides = currentInstance.parent.provides; 23 | 24 | if (key in parentProvides) { 25 | return parentProvides[key]; 26 | }else if(defaultValue){ 27 | if(typeof defaultValue === "function"){ 28 | return defaultValue() 29 | } 30 | return defaultValue 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/ShapeFlags"; 2 | 3 | export const Fragment = Symbol("Fragment"); 4 | export const Text = Symbol("Text"); 5 | 6 | export function createVNode(type, props?, children?) { 7 | const vnode = { 8 | type, 9 | props, 10 | children, 11 | shapeFlag: getShapeFlag(type), 12 | el: null, 13 | }; 14 | 15 | if (typeof children === "string") { 16 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN; 17 | } else if (Array.isArray(children)) { 18 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN; 19 | } 20 | 21 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 22 | if (typeof children === "object") { 23 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN; 24 | } 25 | } 26 | 27 | return vnode; 28 | } 29 | 30 | export function createTextVNode(text: string) { 31 | return createVNode(Text, {}, text); 32 | } 33 | 34 | function getShapeFlag(type) { 35 | return typeof type === "string" 36 | ? ShapeFlags.ELEMENT 37 | : ShapeFlags.STATEFUL_COMPONENT; 38 | } 39 | -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "../shared/index"; 2 | import { 3 | mutableHandlers, 4 | readonlyHandlers, 5 | shallowReadonlyHandlers, 6 | } from "./baseHandler"; 7 | 8 | export const enum ReactiveFlags { 9 | IS_REACTIVE = "__v_isReactive", 10 | IS_READONLY = "__v_isReadonly", 11 | } 12 | 13 | export function reactive(raw) { 14 | return createReactiveObject(raw, mutableHandlers); 15 | } 16 | 17 | export function readonly(raw) { 18 | return createReactiveObject(raw, readonlyHandlers); 19 | } 20 | 21 | export function shallowReadonly(raw) { 22 | return createReactiveObject(raw, shallowReadonlyHandlers); 23 | } 24 | 25 | export function isReactive(value) { 26 | return !!value[ReactiveFlags.IS_REACTIVE]; 27 | } 28 | 29 | export function isReadonly(value) { 30 | return !!value[ReactiveFlags.IS_READONLY]; 31 | } 32 | 33 | export function isProxy(value) { 34 | return isReactive(value) || isReadonly(value); 35 | } 36 | 37 | function createReactiveObject(target, baseHandles) { 38 | if (!isObject(target)) { 39 | console.warn(`target ${target} 必须是一个对象`); 40 | return target 41 | } 42 | 43 | return new Proxy(target, baseHandles); 44 | } 45 | -------------------------------------------------------------------------------- /src/reactivity/tests/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "../computed"; 2 | import { reactive } from "../reactive"; 3 | 4 | describe("computed", () => { 5 | it("happy path", () => { 6 | const user = reactive({ 7 | age: 1, 8 | }); 9 | 10 | const age = computed(() => { 11 | return user.age; 12 | }); 13 | 14 | expect(age.value).toBe(1); 15 | }); 16 | 17 | it("should compute lazily", () => { 18 | const value = reactive({ 19 | foo: 1, 20 | }); 21 | const getter = jest.fn(() => { 22 | return value.foo; 23 | }); 24 | const cValue = computed(getter); 25 | 26 | // lazy 27 | expect(getter).not.toHaveBeenCalled(); 28 | 29 | expect(cValue.value).toBe(1); 30 | expect(getter).toHaveBeenCalledTimes(1); 31 | 32 | // should not compute again 33 | cValue.value; // get 34 | expect(getter).toHaveBeenCalledTimes(1); 35 | 36 | // should not compute until needed 37 | value.foo = 2; 38 | expect(getter).toHaveBeenCalledTimes(1); 39 | 40 | // now it should compute 41 | expect(cValue.value).toBe(2); 42 | expect(getter).toHaveBeenCalledTimes(2); 43 | 44 | // should not compute again 45 | cValue.value; 46 | expect(getter).toHaveBeenCalledTimes(2); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /example/apiInject/App.js: -------------------------------------------------------------------------------- 1 | // 组件 provide 和 inject 功能 2 | import { h, provide, inject } from "../../lib/guide-mini-vue.esm.js"; 3 | 4 | const Provider = { 5 | name: "Provider", 6 | setup() { 7 | provide("foo", "fooVal"); 8 | provide("bar", "barVal"); 9 | }, 10 | render() { 11 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]); 12 | }, 13 | }; 14 | 15 | const ProviderTwo = { 16 | name: "ProviderTwo", 17 | setup() { 18 | provide("foo", "fooTwo"); 19 | const foo = inject("foo"); 20 | 21 | return { 22 | foo, 23 | }; 24 | }, 25 | render() { 26 | return h("div", {}, [ 27 | h("p", {}, `ProviderTwo foo:${this.foo}`), 28 | h(Consumer), 29 | ]); 30 | }, 31 | }; 32 | 33 | const Consumer = { 34 | name: "Consumer", 35 | setup() { 36 | const foo = inject("foo"); 37 | const bar = inject("bar"); 38 | // const baz = inject("baz", "bazDefault"); 39 | const baz = inject("baz", () => "bazDefault"); 40 | 41 | return { 42 | foo, 43 | bar, 44 | baz, 45 | }; 46 | }, 47 | 48 | render() { 49 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar}-${this.baz}`); 50 | }, 51 | }; 52 | 53 | export default { 54 | name: "App", 55 | setup() {}, 56 | render() { 57 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]); 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/reactivity/baseHandler.ts: -------------------------------------------------------------------------------- 1 | import { extend, isObject } from "../shared"; 2 | import { track, trigger } from "./effect"; 3 | import { reactive, ReactiveFlags, readonly, shallowReadonly } from "./reactive"; 4 | 5 | const get = createGetter(); 6 | const set = createSetter(); 7 | const readonlyGet = createGetter(true); 8 | const shallowReadonlyGet = createGetter(true, true); 9 | 10 | function createGetter(isReadonly = false, shallow = false) { 11 | return function get(target, key) { 12 | if (key === ReactiveFlags.IS_REACTIVE) { 13 | return !isReadonly; 14 | } else if (key === ReactiveFlags.IS_READONLY) { 15 | return isReadonly; 16 | } 17 | 18 | const res = Reflect.get(target, key); 19 | 20 | if (shallow) { 21 | return res; 22 | } 23 | 24 | if (isObject(res)) { 25 | return isReadonly ? readonly(res) : reactive(res); 26 | } 27 | 28 | if (!isReadonly) { 29 | track(target, key); 30 | } 31 | return res; 32 | }; 33 | } 34 | 35 | function createSetter() { 36 | return function set(target, key, value) { 37 | const res = Reflect.set(target, key, value); 38 | 39 | trigger(target, key); 40 | return res; 41 | }; 42 | } 43 | 44 | export const mutableHandlers = { 45 | get, 46 | set, 47 | }; 48 | 49 | export const readonlyHandlers = { 50 | get: readonlyGet, 51 | set(target, key) { 52 | console.warn( 53 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, 54 | target 55 | ); 56 | 57 | return true; 58 | }, 59 | }; 60 | 61 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 62 | get: shallowReadonlyGet, 63 | }); 64 | -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged, isObject } from "../shared"; 2 | import { isTracking, trackEffects, triggerEffects } from "./effect"; 3 | import { reactive } from "./reactive"; 4 | 5 | class RefImpl { 6 | private _value: any; 7 | public dep; 8 | private _rawValue: any; 9 | public __v_isRef = true; 10 | constructor(value) { 11 | this._rawValue = value; 12 | this._value = convert(value); 13 | this.dep = new Set(); 14 | } 15 | 16 | get value() { 17 | trackRefValue(this); 18 | return this._value; 19 | } 20 | 21 | set value(newValue) { 22 | if (hasChanged(newValue, this._rawValue)) { 23 | this._rawValue = newValue; 24 | this._value = convert(newValue); 25 | triggerEffects(this.dep); 26 | } 27 | } 28 | } 29 | 30 | function convert(value) { 31 | return isObject(value) ? reactive(value) : value; 32 | } 33 | 34 | function trackRefValue(ref) { 35 | if (isTracking()) { 36 | trackEffects(ref.dep); 37 | } 38 | } 39 | 40 | export function ref(value) { 41 | return new RefImpl(value); 42 | } 43 | 44 | export function isRef(ref) { 45 | return !!ref.__v_isRef; 46 | } 47 | 48 | export function unRef(ref) { 49 | return isRef(ref) ? ref.value : ref; 50 | } 51 | 52 | export function proxyRefs(objectWithRefs) { 53 | return new Proxy(objectWithRefs, { 54 | get(target, key) { 55 | return unRef(Reflect.get(target, key)); 56 | }, 57 | 58 | set(target, key, value) { 59 | if (isRef(target[key]) && !isRef(value)) { 60 | return (target[key].value = value); 61 | } else { 62 | return Reflect.set(target, key, value); 63 | } 64 | }, 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { shallowReadonly } from "../reactivity/reactive"; 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 | setupState: {}, 12 | props: {}, 13 | slots: {}, 14 | provides: parent ? parent.provides : {}, 15 | parent, 16 | emit: () => {}, 17 | }; 18 | 19 | component.emit = emit.bind(null, component) as any; 20 | 21 | return component; 22 | } 23 | 24 | export function setupComponent(instance) { 25 | initProps(instance, instance.vnode.props); 26 | initSlots(instance, instance.vnode.children); 27 | setupStatefulComponent(instance); 28 | } 29 | 30 | function setupStatefulComponent(instance: any) { 31 | const Component = instance.type; 32 | 33 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers); 34 | 35 | const { setup } = Component; 36 | 37 | if (setup) { 38 | setCurrentInstance(instance); 39 | const setupResult = setup(shallowReadonly(instance.props), { 40 | emit: instance.emit, 41 | }); 42 | setCurrentInstance(null); 43 | 44 | handleSetupResult(instance, setupResult); 45 | } 46 | } 47 | 48 | function handleSetupResult(instance, setupResult: any) { 49 | if (typeof setupResult === "object") { 50 | instance.setupState = setupResult; 51 | } 52 | 53 | finishComponentSetup(instance); 54 | } 55 | 56 | function finishComponentSetup(instance: any) { 57 | const Component = instance.type; 58 | 59 | instance.render = Component.render; 60 | } 61 | 62 | let currentInstance = null; 63 | 64 | export function getCurrentInstance() { 65 | return currentInstance; 66 | } 67 | 68 | export function setCurrentInstance(instance) { 69 | currentInstance = instance; 70 | } 71 | -------------------------------------------------------------------------------- /src/reactivity/tests/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | import { isRef, ref, unRef, proxyRefs } from "../ref"; 4 | describe("ref", () => { 5 | it("happy path", () => { 6 | const a = ref(1); 7 | expect(a.value).toBe(1); 8 | }); 9 | 10 | it("should be reactive", () => { 11 | const a = ref(1); 12 | let dummy; 13 | let calls = 0; 14 | effect(() => { 15 | calls++; 16 | dummy = a.value; 17 | }); 18 | expect(calls).toBe(1); 19 | expect(dummy).toBe(1); 20 | a.value = 2; 21 | expect(calls).toBe(2); 22 | expect(dummy).toBe(2); 23 | // same value should not trigger 24 | a.value = 2; 25 | expect(calls).toBe(2); 26 | expect(dummy).toBe(2); 27 | }); 28 | 29 | it("should make nested properties reactive", () => { 30 | const a = ref({ 31 | count: 1, 32 | }); 33 | let dummy; 34 | effect(() => { 35 | dummy = a.value.count; 36 | }); 37 | expect(dummy).toBe(1); 38 | a.value.count = 2; 39 | expect(dummy).toBe(2); 40 | }); 41 | 42 | it("isRef", () => { 43 | const a = ref(1); 44 | const user = reactive({ 45 | age: 1, 46 | }); 47 | expect(isRef(a)).toBe(true); 48 | expect(isRef(1)).toBe(false); 49 | expect(isRef(user)).toBe(false); 50 | }); 51 | 52 | it("unRef", () => { 53 | const a = ref(1); 54 | expect(unRef(a)).toBe(1); 55 | expect(unRef(1)).toBe(1); 56 | }); 57 | 58 | it("proxyRefs", () => { 59 | const user = { 60 | age: ref(10), 61 | name: "xiaohong", 62 | }; 63 | 64 | const proxyUser = proxyRefs(user); 65 | expect(user.age.value).toBe(10); 66 | expect(proxyUser.age).toBe(10); 67 | expect(proxyUser.name).toBe("xiaohong"); 68 | 69 | proxyUser.age = 20; 70 | 71 | expect(proxyUser.age).toBe(20); 72 | expect(user.age.value).toBe(20); 73 | 74 | proxyUser.age = ref(10); 75 | expect(proxyUser.age).toBe(10); 76 | expect(user.age.value).toBe(10); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /core/reactivity/reactivity.js: -------------------------------------------------------------------------------- 1 | let currentEffect 2 | class Dep{ 3 | constructor(value){ 4 | this._value=value 5 | this.effects= new Set() 6 | } 7 | 8 | get value(){ 9 | this.dep() 10 | return this._value 11 | } 12 | 13 | set value(newValue){ 14 | this._value=newValue 15 | this.notify() 16 | } 17 | 18 | dep(){ 19 | if(currentEffect){ 20 | this.effects.add(currentEffect) 21 | } 22 | } 23 | 24 | notify(){ 25 | this.effects.forEach(effect=>effect()) 26 | } 27 | 28 | } 29 | 30 | 31 | export const watchEffect=(effect)=>{ 32 | currentEffect=effect 33 | effect() 34 | // dep.dep(effect) 35 | currentEffect=null 36 | } 37 | 38 | const targetMap = new Map() 39 | 40 | function getDep(target,key){ 41 | //获取当前实例的depsMap 42 | let depsMap = targetMap.get(target) 43 | if(!depsMap){ 44 | depsMap = new Map() 45 | targetMap.set(target,depsMap) 46 | } 47 | //获取对应实例的属性的Dep 48 | let dep=depsMap.get(key) 49 | if(!dep){ 50 | dep = new Dep() 51 | depsMap.set(key,dep) 52 | } 53 | return dep 54 | } 55 | 56 | export function reactive(raw){ 57 | return new Proxy(raw,{ 58 | get(target,key){ 59 | const dep = getDep(target,key) 60 | //收集依赖 61 | dep.dep() 62 | return Reflect.get(...arguments) 63 | }, 64 | set(target,key,value){ 65 | //触发依赖 66 | const dep = getDep(target,key) 67 | const result=Reflect.set(...arguments) 68 | dep.notify() 69 | return result 70 | } 71 | }) 72 | } 73 | 74 | // const user = reactive({ 75 | // uname:'sudongyu' 76 | // }) 77 | 78 | // watchEffect(()=>{ 79 | // //必须读取响应式对象的值,触发收集依赖 80 | // console.log("reactive======",user.uname)//这里思考一下到依赖被通知的时候,会不会再次收集依赖呢?答案不会,因为通知的时候只执行当前函数,并没有在watchEffect中,currentEffect为null,所以不会重复收集依赖 81 | // }) 82 | 83 | // //触发依赖 84 | // user.uname='lisi' 85 | 86 | // user.uname='wangwu' 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/reactivity/tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect, stop } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | 4 | describe("effect", () => { 5 | it("happy path", () => { 6 | const user = reactive({ 7 | age: 10, 8 | }); 9 | 10 | let nextAge; 11 | effect(() => { 12 | nextAge = user.age + 1; 13 | }); 14 | 15 | expect(nextAge).toBe(11); 16 | 17 | // update 18 | user.age++; 19 | expect(nextAge).toBe(12); 20 | }); 21 | 22 | it("should return runner when call effect", () => { 23 | // 当调用 runner 的时候可以重新执行 effect.run 24 | // runner 的返回值就是用户给的 fn 的返回值 25 | let foo = 0; 26 | const runner = effect(() => { 27 | foo++; 28 | return foo; 29 | }); 30 | 31 | expect(foo).toBe(1); 32 | runner(); 33 | expect(foo).toBe(2); 34 | expect(runner()).toBe(3); 35 | }); 36 | 37 | it("scheduler", () => { 38 | let dummy; 39 | let run: any; 40 | const scheduler = jest.fn(() => { 41 | run = runner; 42 | }); 43 | const obj = reactive({ foo: 1 }); 44 | const runner = effect( 45 | () => { 46 | dummy = obj.foo; 47 | }, 48 | { scheduler } 49 | ); 50 | expect(scheduler).not.toHaveBeenCalled(); 51 | expect(dummy).toBe(1); 52 | // should be called on first trigger 53 | obj.foo++; 54 | expect(scheduler).toHaveBeenCalledTimes(1); 55 | // // should not run yet 56 | expect(dummy).toBe(1); 57 | // // manually run 58 | run(); 59 | // // should have run 60 | expect(dummy).toBe(2); 61 | }); 62 | 63 | it("stop", () => { 64 | let dummy; 65 | const obj = reactive({ prop: 1 }); 66 | const runner = effect(() => { 67 | dummy = obj.prop; 68 | }); 69 | obj.prop = 2; 70 | expect(dummy).toBe(2); 71 | stop(runner); 72 | // obj.prop = 3; 73 | obj.prop++; 74 | expect(dummy).toBe(2); 75 | 76 | // stopped effect should still be manually callable 77 | runner(); 78 | expect(dummy).toBe(3); 79 | }); 80 | 81 | it("onStop", () => { 82 | const obj = reactive({ 83 | foo: 1, 84 | }); 85 | const onStop = jest.fn(); 86 | let dummy; 87 | const runner = effect( 88 | () => { 89 | dummy = obj.foo; 90 | }, 91 | { 92 | onStop, 93 | } 94 | ); 95 | 96 | stop(runner); 97 | expect(onStop).toBeCalledTimes(1); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "../shared"; 2 | 3 | let activeEffect; 4 | let shouldTrack = false; 5 | export class ReactiveEffect { 6 | private _fn: any; 7 | deps = []; 8 | active = true; 9 | onStop?: () => void; 10 | public scheduler: Function | undefined; 11 | constructor(fn, scheduler?: Function) { 12 | this._fn = fn; 13 | this.scheduler = scheduler; 14 | } 15 | run() { 16 | if (!this.active) { 17 | return this._fn(); 18 | } 19 | 20 | // 应该收集 21 | shouldTrack = true; 22 | activeEffect = this; 23 | const r = this._fn(); 24 | 25 | // 重置 26 | shouldTrack = false; 27 | 28 | return r; 29 | } 30 | stop() { 31 | if (this.active) { 32 | cleanupEffect(this); 33 | if (this.onStop) { 34 | this.onStop(); 35 | } 36 | this.active = false; 37 | } 38 | } 39 | } 40 | 41 | function cleanupEffect(effect) { 42 | effect.deps.forEach((dep: any) => { 43 | dep.delete(effect); 44 | }); 45 | 46 | // 把 effect.deps 清空 47 | effect.deps.length = 0; 48 | } 49 | 50 | const targetMap = new Map(); 51 | export function track(target, key) { 52 | if (!isTracking()) return; 53 | // target -> key -> dep 54 | let depsMap = targetMap.get(target); 55 | if (!depsMap) { 56 | depsMap = new Map(); 57 | targetMap.set(target, depsMap); 58 | } 59 | 60 | let dep = depsMap.get(key); 61 | if (!dep) { 62 | dep = new Set(); 63 | depsMap.set(key, dep); 64 | } 65 | 66 | trackEffects(dep); 67 | } 68 | 69 | export function trackEffects(dep) { 70 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了 71 | if (dep.has(activeEffect)) return; 72 | 73 | dep.add(activeEffect); 74 | activeEffect.deps.push(dep); 75 | } 76 | 77 | export function isTracking() { 78 | return shouldTrack && activeEffect !== undefined; 79 | } 80 | 81 | export function trigger(target, key) { 82 | let depsMap = targetMap.get(target); 83 | let dep = depsMap.get(key); 84 | triggerEffects(dep); 85 | } 86 | 87 | export function triggerEffects(dep) { 88 | for (const effect of dep) { 89 | if (effect.scheduler) { 90 | effect.scheduler(); 91 | } else { 92 | effect.run(); 93 | } 94 | } 95 | } 96 | 97 | export function effect(fn, options: any = {}) { 98 | // fn 99 | const _effect = new ReactiveEffect(fn, options.scheduler); 100 | extend(_effect, options); 101 | 102 | _effect.run(); 103 | 104 | const runner: any = _effect.run.bind(_effect); 105 | runner.effect = _effect; 106 | 107 | return runner; 108 | } 109 | 110 | export function stop(runner) { 111 | runner.effect.stop(); 112 | } 113 | -------------------------------------------------------------------------------- /src/runtime-core/renderer.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/ShapeFlags"; 2 | import { createComponentInstance, setupComponent } from "./component"; 3 | import { createAppAPI } from "./createApp"; 4 | import { Fragment, Text } from "./vnode"; 5 | 6 | export function createRenderer(options) { 7 | const { 8 | createElement: hostCreateElement, 9 | patchProp: hostPatchProp, 10 | insert: hostInsert, 11 | } = options; 12 | 13 | function render(vnode, container) { 14 | patch(vnode, container, null); 15 | } 16 | 17 | function patch(vnode, container, parentComponent) { 18 | const { type, shapeFlag } = vnode; 19 | 20 | switch (type) { 21 | case Fragment: 22 | processFragment(vnode, container, parentComponent); 23 | break; 24 | case Text: 25 | processText(vnode, container); 26 | break; 27 | 28 | default: 29 | if (shapeFlag & ShapeFlags.ELEMENT) { 30 | processElement(vnode, container, parentComponent); 31 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 32 | processComponent(vnode, container, parentComponent); 33 | } 34 | break; 35 | } 36 | } 37 | 38 | function processText(vnode: any, container: any) { 39 | const { children } = vnode; 40 | const textNode = (vnode.el = document.createTextNode(children)); 41 | container.append(textNode); 42 | } 43 | 44 | function processFragment(vnode: any, container: any, parentComponent) { 45 | mountChildren(vnode, container, parentComponent); 46 | } 47 | 48 | function processElement(vnode: any, container: any, parentComponent) { 49 | mountElement(vnode, container, parentComponent); 50 | } 51 | 52 | function mountElement(vnode: any, container: any, parentComponent) { 53 | //canvas 54 | // new Element() 55 | const el = (vnode.el = hostCreateElement(vnode.type)); 56 | 57 | const { children, shapeFlag } = vnode; 58 | 59 | // children 60 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 61 | el.textContent = children; 62 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 63 | mountChildren(vnode, el, parentComponent); 64 | } 65 | 66 | // props 67 | const { props } = vnode; 68 | for (const key in props) { 69 | const val = props[key]; 70 | hostPatchProp(el, key, val); 71 | } 72 | // canvs 73 | // el.x = 10 74 | 75 | // container.append(el); 76 | // addChild() 77 | hostInsert(el, container); 78 | } 79 | 80 | function mountChildren(vnode, container, parentComponent) { 81 | vnode.children.forEach((v) => { 82 | patch(v, container, parentComponent); 83 | }); 84 | } 85 | 86 | function processComponent(vnode: any, container: any, parentComponent) { 87 | mountComponent(vnode, container, parentComponent); 88 | } 89 | 90 | function mountComponent(initialVNode: any, container, parentComponent) { 91 | const instance = createComponentInstance(initialVNode, parentComponent); 92 | 93 | setupComponent(instance); 94 | setupRenderEffect(instance, initialVNode, container); 95 | } 96 | 97 | function setupRenderEffect(instance: any, initialVNode, container) { 98 | const { proxy } = instance; 99 | const subTree = instance.render.call(proxy); 100 | 101 | patch(subTree, container, instance); 102 | 103 | initialVNode.el = subTree.el; 104 | } 105 | 106 | return { 107 | createApp: createAppAPI(render), 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /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": "es5" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | "lib": [ 16 | "DOM", 17 | "es6" 18 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, 19 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 25 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 28 | 29 | /* Modules */ 30 | "module": "esnext" /* Specify what module code is generated. */, 31 | // "rootDir": "./", /* Specify the root folder within your source files. */ 32 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 33 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 34 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 35 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 36 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 37 | "types": [ 38 | "jest" 39 | ] /* Specify type package names to be included without being referenced in a source file. */, 40 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 41 | // "resolveJsonModule": true, /* Enable importing .json files */ 42 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 43 | 44 | /* JavaScript Support */ 45 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 46 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 47 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 48 | 49 | /* Emit */ 50 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 51 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 52 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 53 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 54 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 55 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 56 | // "removeComments": true, /* Disable emitting comments. */ 57 | // "noEmit": true, /* Disable emitting files from a compilation. */ 58 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 59 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 60 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 61 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 63 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 64 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 65 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 66 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 67 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 68 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 69 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 70 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 71 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 72 | 73 | /* Interop Constraints */ 74 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 75 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 76 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, 77 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 78 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 79 | 80 | /* Type Checking */ 81 | "strict": true /* Enable all strict type-checking options. */, 82 | "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied `any` type.. */, 83 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 84 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 85 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 86 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 87 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 88 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 89 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 90 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 91 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 92 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 93 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 94 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 95 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 96 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 97 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 98 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 99 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 100 | 101 | /* Completeness */ 102 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 103 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/guide-mini-vue.esm.js: -------------------------------------------------------------------------------- 1 | var Fragment = Symbol("Fragment"); 2 | var Text = Symbol("Text"); 3 | function createVNode(type, props, children) { 4 | var vnode = { 5 | type: type, 6 | props: props, 7 | children: children, 8 | shapeFlag: getShapeFlag(type), 9 | el: null, 10 | }; 11 | if (typeof children === "string") { 12 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */; 13 | } 14 | else if (Array.isArray(children)) { 15 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */; 16 | } 17 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 18 | if (typeof children === "object") { 19 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */; 20 | } 21 | } 22 | return vnode; 23 | } 24 | function createTextVNode(text) { 25 | return createVNode(Text, {}, text); 26 | } 27 | function getShapeFlag(type) { 28 | return typeof type === "string" 29 | ? 1 /* ELEMENT */ 30 | : 2 /* STATEFUL_COMPONENT */; 31 | } 32 | 33 | function h(type, props, children) { 34 | return createVNode(type, props, children); 35 | } 36 | 37 | function renderSlots(slots, name, props) { 38 | var slot = slots[name]; 39 | if (slot) { 40 | if (typeof slot === "function") { 41 | return createVNode(Fragment, {}, slot(props)); 42 | } 43 | } 44 | } 45 | 46 | var extend = Object.assign; 47 | var isObject = function (value) { 48 | return value !== null && typeof value === "object"; 49 | }; 50 | var hasOwn = function (val, key) { 51 | return Object.prototype.hasOwnProperty.call(val, key); 52 | }; 53 | var camelize = function (str) { 54 | return str.replace(/-(\w)/g, function (_, c) { 55 | return c ? c.toUpperCase() : ""; 56 | }); 57 | }; 58 | var capitalize = function (str) { 59 | return str.charAt(0).toUpperCase() + str.slice(1); 60 | }; 61 | var toHandlerKey = function (str) { 62 | return str ? "on" + capitalize(str) : ""; 63 | }; 64 | 65 | var targetMap = new Map(); 66 | function trigger(target, key) { 67 | var depsMap = targetMap.get(target); 68 | var dep = depsMap.get(key); 69 | triggerEffects(dep); 70 | } 71 | function triggerEffects(dep) { 72 | for (var _i = 0, dep_1 = dep; _i < dep_1.length; _i++) { 73 | var effect_1 = dep_1[_i]; 74 | if (effect_1.scheduler) { 75 | effect_1.scheduler(); 76 | } 77 | else { 78 | effect_1.run(); 79 | } 80 | } 81 | } 82 | 83 | var get = createGetter(); 84 | var set = createSetter(); 85 | var readonlyGet = createGetter(true); 86 | var shallowReadonlyGet = createGetter(true, true); 87 | function createGetter(isReadonly, shallow) { 88 | if (isReadonly === void 0) { isReadonly = false; } 89 | if (shallow === void 0) { shallow = false; } 90 | return function get(target, key) { 91 | if (key === "__v_isReactive" /* IS_REACTIVE */) { 92 | return !isReadonly; 93 | } 94 | else if (key === "__v_isReadonly" /* IS_READONLY */) { 95 | return isReadonly; 96 | } 97 | var res = Reflect.get(target, key); 98 | if (shallow) { 99 | return res; 100 | } 101 | if (isObject(res)) { 102 | return isReadonly ? readonly(res) : reactive(res); 103 | } 104 | return res; 105 | }; 106 | } 107 | function createSetter() { 108 | return function set(target, key, value) { 109 | var res = Reflect.set(target, key, value); 110 | trigger(target, key); 111 | return res; 112 | }; 113 | } 114 | var mutableHandlers = { 115 | get: get, 116 | set: set, 117 | }; 118 | var readonlyHandlers = { 119 | get: readonlyGet, 120 | set: function (target, key) { 121 | console.warn("key :\"".concat(String(key), "\" set \u5931\u8D25\uFF0C\u56E0\u4E3A target \u662F readonly \u7C7B\u578B"), target); 122 | return true; 123 | }, 124 | }; 125 | var shallowReadonlyHandlers = extend({}, readonlyHandlers, { 126 | get: shallowReadonlyGet, 127 | }); 128 | 129 | function reactive(raw) { 130 | return createReactiveObject(raw, mutableHandlers); 131 | } 132 | function readonly(raw) { 133 | return createReactiveObject(raw, readonlyHandlers); 134 | } 135 | function shallowReadonly(raw) { 136 | return createReactiveObject(raw, shallowReadonlyHandlers); 137 | } 138 | function createReactiveObject(target, baseHandles) { 139 | if (!isObject(target)) { 140 | console.warn("target ".concat(target, " \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61")); 141 | return target; 142 | } 143 | return new Proxy(target, baseHandles); 144 | } 145 | 146 | function emit(instance, event) { 147 | var args = []; 148 | for (var _i = 2; _i < arguments.length; _i++) { 149 | args[_i - 2] = arguments[_i]; 150 | } 151 | var props = instance.props; 152 | var handlerName = toHandlerKey(camelize(event)); 153 | var handler = props[handlerName]; 154 | handler && handler.apply(void 0, args); 155 | } 156 | 157 | function initProps(instance, rawProps) { 158 | instance.props = rawProps || {}; 159 | } 160 | 161 | var publicPropertiesMap = { 162 | $el: function (i) { return i.vnode.el; }, 163 | $slots: function (i) { return i.slots; }, 164 | }; 165 | var PublicInstanceProxyHandlers = { 166 | get: function (_a, key) { 167 | var instance = _a._; 168 | var setupState = instance.setupState, props = instance.props; 169 | if (hasOwn(setupState, key)) { 170 | return setupState[key]; 171 | } 172 | else if (hasOwn(props, key)) { 173 | return props[key]; 174 | } 175 | var publicGetter = publicPropertiesMap[key]; 176 | if (publicGetter) { 177 | return publicGetter(instance); 178 | } 179 | }, 180 | }; 181 | 182 | function initSlots(instance, children) { 183 | // slots 184 | var vnode = instance.vnode; 185 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) { 186 | normalizeObjectSlots(children, instance.slots); 187 | } 188 | } 189 | function normalizeObjectSlots(children, slots) { 190 | var _loop_1 = function (key) { 191 | var value = children[key]; 192 | slots[key] = function (props) { return normalizeSlotValue(value(props)); }; 193 | }; 194 | for (var key in children) { 195 | _loop_1(key); 196 | } 197 | } 198 | function normalizeSlotValue(value) { 199 | return Array.isArray(value) ? value : [value]; 200 | } 201 | 202 | function createComponentInstance(vnode, parent) { 203 | var component = { 204 | vnode: vnode, 205 | type: vnode.type, 206 | setupState: {}, 207 | props: {}, 208 | slots: {}, 209 | provides: parent ? parent.provides : {}, 210 | parent: parent, 211 | emit: function () { }, 212 | }; 213 | component.emit = emit.bind(null, component); 214 | return component; 215 | } 216 | function setupComponent(instance) { 217 | initProps(instance, instance.vnode.props); 218 | initSlots(instance, instance.vnode.children); 219 | setupStatefulComponent(instance); 220 | } 221 | function setupStatefulComponent(instance) { 222 | var Component = instance.type; 223 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers); 224 | var setup = Component.setup; 225 | if (setup) { 226 | setCurrentInstance(instance); 227 | var setupResult = setup(shallowReadonly(instance.props), { 228 | emit: instance.emit, 229 | }); 230 | setCurrentInstance(null); 231 | handleSetupResult(instance, setupResult); 232 | } 233 | } 234 | function handleSetupResult(instance, setupResult) { 235 | if (typeof setupResult === "object") { 236 | instance.setupState = setupResult; 237 | } 238 | finishComponentSetup(instance); 239 | } 240 | function finishComponentSetup(instance) { 241 | var Component = instance.type; 242 | instance.render = Component.render; 243 | } 244 | var currentInstance = null; 245 | function getCurrentInstance() { 246 | return currentInstance; 247 | } 248 | function setCurrentInstance(instance) { 249 | currentInstance = instance; 250 | } 251 | 252 | function provide(key, value) { 253 | var currentInstance = getCurrentInstance(); 254 | if (currentInstance) { 255 | var provides = currentInstance.provides; 256 | var parentProvides = currentInstance.parent.provides; 257 | if (provides === parentProvides) { 258 | provides = currentInstance.provides = Object.create(parentProvides); 259 | } 260 | provides[key] = value; 261 | } 262 | } 263 | function inject(key, defaultValue) { 264 | var currentInstance = getCurrentInstance(); 265 | if (currentInstance) { 266 | var parentProvides = currentInstance.parent.provides; 267 | if (key in parentProvides) { 268 | return parentProvides[key]; 269 | } 270 | else if (defaultValue) { 271 | if (typeof defaultValue === "function") { 272 | return defaultValue(); 273 | } 274 | return defaultValue; 275 | } 276 | } 277 | } 278 | 279 | function createAppAPI(render) { 280 | return function createApp(rootComponent) { 281 | return { 282 | mount: function (rootContainer) { 283 | var vnode = createVNode(rootComponent); 284 | render(vnode, rootContainer); 285 | }, 286 | }; 287 | }; 288 | } 289 | 290 | function createRenderer(options) { 291 | var hostCreateElement = options.createElement, hostPatchProp = options.patchProp, hostInsert = options.insert; 292 | function render(vnode, container) { 293 | patch(vnode, container, null); 294 | } 295 | function patch(vnode, container, parentComponent) { 296 | var type = vnode.type, shapeFlag = vnode.shapeFlag; 297 | switch (type) { 298 | case Fragment: 299 | processFragment(vnode, container, parentComponent); 300 | break; 301 | case Text: 302 | processText(vnode, container); 303 | break; 304 | default: 305 | if (shapeFlag & 1 /* ELEMENT */) { 306 | processElement(vnode, container, parentComponent); 307 | } 308 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) { 309 | processComponent(vnode, container, parentComponent); 310 | } 311 | break; 312 | } 313 | } 314 | function processText(vnode, container) { 315 | var children = vnode.children; 316 | var textNode = (vnode.el = document.createTextNode(children)); 317 | container.append(textNode); 318 | } 319 | function processFragment(vnode, container, parentComponent) { 320 | mountChildren(vnode, container, parentComponent); 321 | } 322 | function processElement(vnode, container, parentComponent) { 323 | mountElement(vnode, container, parentComponent); 324 | } 325 | function mountElement(vnode, container, parentComponent) { 326 | //canvas 327 | // new Element() 328 | var el = (vnode.el = hostCreateElement(vnode.type)); 329 | var children = vnode.children, shapeFlag = vnode.shapeFlag; 330 | // children 331 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 332 | el.textContent = children; 333 | } 334 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 335 | mountChildren(vnode, el, parentComponent); 336 | } 337 | // props 338 | var props = vnode.props; 339 | for (var key in props) { 340 | var val = props[key]; 341 | hostPatchProp(el, key, val); 342 | } 343 | // canvs 344 | // el.x = 10 345 | // container.append(el); 346 | // addChild() 347 | hostInsert(el, container); 348 | } 349 | function mountChildren(vnode, container, parentComponent) { 350 | vnode.children.forEach(function (v) { 351 | patch(v, container, parentComponent); 352 | }); 353 | } 354 | function processComponent(vnode, container, parentComponent) { 355 | mountComponent(vnode, container, parentComponent); 356 | } 357 | function mountComponent(initialVNode, container, parentComponent) { 358 | var instance = createComponentInstance(initialVNode, parentComponent); 359 | setupComponent(instance); 360 | setupRenderEffect(instance, initialVNode, container); 361 | } 362 | function setupRenderEffect(instance, initialVNode, container) { 363 | var proxy = instance.proxy; 364 | var subTree = instance.render.call(proxy); 365 | patch(subTree, container, instance); 366 | initialVNode.el = subTree.el; 367 | } 368 | return { 369 | createApp: createAppAPI(render), 370 | }; 371 | } 372 | 373 | function createElement(type) { 374 | return document.createElement(type); 375 | } 376 | function patchProp(el, key, val) { 377 | var isOn = function (key) { return /^on[A-Z]/.test(key); }; 378 | if (isOn(key)) { 379 | var event_1 = key.slice(2).toLowerCase(); 380 | el.addEventListener(event_1, val); 381 | } 382 | else { 383 | el.setAttribute(key, val); 384 | } 385 | } 386 | function insert(el, parent) { 387 | parent.append(el); 388 | } 389 | var renderer = createRenderer({ 390 | createElement: createElement, 391 | patchProp: patchProp, 392 | insert: insert, 393 | }); 394 | function createApp() { 395 | var args = []; 396 | for (var _i = 0; _i < arguments.length; _i++) { 397 | args[_i] = arguments[_i]; 398 | } 399 | return renderer.createApp.apply(renderer, args); 400 | } 401 | 402 | export { createApp, createRenderer, createTextVNode, getCurrentInstance, h, inject, provide, renderSlots }; 403 | -------------------------------------------------------------------------------- /lib/guide-mini-vue.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | var Fragment = Symbol("Fragment"); 6 | var Text = Symbol("Text"); 7 | function createVNode(type, props, children) { 8 | var vnode = { 9 | type: type, 10 | props: props, 11 | children: children, 12 | shapeFlag: getShapeFlag(type), 13 | el: null, 14 | }; 15 | if (typeof children === "string") { 16 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */; 17 | } 18 | else if (Array.isArray(children)) { 19 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */; 20 | } 21 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 22 | if (typeof children === "object") { 23 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */; 24 | } 25 | } 26 | return vnode; 27 | } 28 | function createTextVNode(text) { 29 | return createVNode(Text, {}, text); 30 | } 31 | function getShapeFlag(type) { 32 | return typeof type === "string" 33 | ? 1 /* ELEMENT */ 34 | : 2 /* STATEFUL_COMPONENT */; 35 | } 36 | 37 | function h(type, props, children) { 38 | return createVNode(type, props, children); 39 | } 40 | 41 | function renderSlots(slots, name, props) { 42 | var slot = slots[name]; 43 | if (slot) { 44 | if (typeof slot === "function") { 45 | return createVNode(Fragment, {}, slot(props)); 46 | } 47 | } 48 | } 49 | 50 | var extend = Object.assign; 51 | var isObject = function (value) { 52 | return value !== null && typeof value === "object"; 53 | }; 54 | var hasOwn = function (val, key) { 55 | return Object.prototype.hasOwnProperty.call(val, key); 56 | }; 57 | var camelize = function (str) { 58 | return str.replace(/-(\w)/g, function (_, c) { 59 | return c ? c.toUpperCase() : ""; 60 | }); 61 | }; 62 | var capitalize = function (str) { 63 | return str.charAt(0).toUpperCase() + str.slice(1); 64 | }; 65 | var toHandlerKey = function (str) { 66 | return str ? "on" + capitalize(str) : ""; 67 | }; 68 | 69 | var targetMap = new Map(); 70 | function trigger(target, key) { 71 | var depsMap = targetMap.get(target); 72 | var dep = depsMap.get(key); 73 | triggerEffects(dep); 74 | } 75 | function triggerEffects(dep) { 76 | for (var _i = 0, dep_1 = dep; _i < dep_1.length; _i++) { 77 | var effect_1 = dep_1[_i]; 78 | if (effect_1.scheduler) { 79 | effect_1.scheduler(); 80 | } 81 | else { 82 | effect_1.run(); 83 | } 84 | } 85 | } 86 | 87 | var get = createGetter(); 88 | var set = createSetter(); 89 | var readonlyGet = createGetter(true); 90 | var shallowReadonlyGet = createGetter(true, true); 91 | function createGetter(isReadonly, shallow) { 92 | if (isReadonly === void 0) { isReadonly = false; } 93 | if (shallow === void 0) { shallow = false; } 94 | return function get(target, key) { 95 | if (key === "__v_isReactive" /* IS_REACTIVE */) { 96 | return !isReadonly; 97 | } 98 | else if (key === "__v_isReadonly" /* IS_READONLY */) { 99 | return isReadonly; 100 | } 101 | var res = Reflect.get(target, key); 102 | if (shallow) { 103 | return res; 104 | } 105 | if (isObject(res)) { 106 | return isReadonly ? readonly(res) : reactive(res); 107 | } 108 | return res; 109 | }; 110 | } 111 | function createSetter() { 112 | return function set(target, key, value) { 113 | var res = Reflect.set(target, key, value); 114 | trigger(target, key); 115 | return res; 116 | }; 117 | } 118 | var mutableHandlers = { 119 | get: get, 120 | set: set, 121 | }; 122 | var readonlyHandlers = { 123 | get: readonlyGet, 124 | set: function (target, key) { 125 | console.warn("key :\"".concat(String(key), "\" set \u5931\u8D25\uFF0C\u56E0\u4E3A target \u662F readonly \u7C7B\u578B"), target); 126 | return true; 127 | }, 128 | }; 129 | var shallowReadonlyHandlers = extend({}, readonlyHandlers, { 130 | get: shallowReadonlyGet, 131 | }); 132 | 133 | function reactive(raw) { 134 | return createReactiveObject(raw, mutableHandlers); 135 | } 136 | function readonly(raw) { 137 | return createReactiveObject(raw, readonlyHandlers); 138 | } 139 | function shallowReadonly(raw) { 140 | return createReactiveObject(raw, shallowReadonlyHandlers); 141 | } 142 | function createReactiveObject(target, baseHandles) { 143 | if (!isObject(target)) { 144 | console.warn("target ".concat(target, " \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61")); 145 | return target; 146 | } 147 | return new Proxy(target, baseHandles); 148 | } 149 | 150 | function emit(instance, event) { 151 | var args = []; 152 | for (var _i = 2; _i < arguments.length; _i++) { 153 | args[_i - 2] = arguments[_i]; 154 | } 155 | var props = instance.props; 156 | var handlerName = toHandlerKey(camelize(event)); 157 | var handler = props[handlerName]; 158 | handler && handler.apply(void 0, args); 159 | } 160 | 161 | function initProps(instance, rawProps) { 162 | instance.props = rawProps || {}; 163 | } 164 | 165 | var publicPropertiesMap = { 166 | $el: function (i) { return i.vnode.el; }, 167 | $slots: function (i) { return i.slots; }, 168 | }; 169 | var PublicInstanceProxyHandlers = { 170 | get: function (_a, key) { 171 | var instance = _a._; 172 | var setupState = instance.setupState, props = instance.props; 173 | if (hasOwn(setupState, key)) { 174 | return setupState[key]; 175 | } 176 | else if (hasOwn(props, key)) { 177 | return props[key]; 178 | } 179 | var publicGetter = publicPropertiesMap[key]; 180 | if (publicGetter) { 181 | return publicGetter(instance); 182 | } 183 | }, 184 | }; 185 | 186 | function initSlots(instance, children) { 187 | // slots 188 | var vnode = instance.vnode; 189 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) { 190 | normalizeObjectSlots(children, instance.slots); 191 | } 192 | } 193 | function normalizeObjectSlots(children, slots) { 194 | var _loop_1 = function (key) { 195 | var value = children[key]; 196 | slots[key] = function (props) { return normalizeSlotValue(value(props)); }; 197 | }; 198 | for (var key in children) { 199 | _loop_1(key); 200 | } 201 | } 202 | function normalizeSlotValue(value) { 203 | return Array.isArray(value) ? value : [value]; 204 | } 205 | 206 | function createComponentInstance(vnode, parent) { 207 | var component = { 208 | vnode: vnode, 209 | type: vnode.type, 210 | setupState: {}, 211 | props: {}, 212 | slots: {}, 213 | provides: parent ? parent.provides : {}, 214 | parent: parent, 215 | emit: function () { }, 216 | }; 217 | component.emit = emit.bind(null, component); 218 | return component; 219 | } 220 | function setupComponent(instance) { 221 | initProps(instance, instance.vnode.props); 222 | initSlots(instance, instance.vnode.children); 223 | setupStatefulComponent(instance); 224 | } 225 | function setupStatefulComponent(instance) { 226 | var Component = instance.type; 227 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers); 228 | var setup = Component.setup; 229 | if (setup) { 230 | setCurrentInstance(instance); 231 | var setupResult = setup(shallowReadonly(instance.props), { 232 | emit: instance.emit, 233 | }); 234 | setCurrentInstance(null); 235 | handleSetupResult(instance, setupResult); 236 | } 237 | } 238 | function handleSetupResult(instance, setupResult) { 239 | if (typeof setupResult === "object") { 240 | instance.setupState = setupResult; 241 | } 242 | finishComponentSetup(instance); 243 | } 244 | function finishComponentSetup(instance) { 245 | var Component = instance.type; 246 | instance.render = Component.render; 247 | } 248 | var currentInstance = null; 249 | function getCurrentInstance() { 250 | return currentInstance; 251 | } 252 | function setCurrentInstance(instance) { 253 | currentInstance = instance; 254 | } 255 | 256 | function provide(key, value) { 257 | var currentInstance = getCurrentInstance(); 258 | if (currentInstance) { 259 | var provides = currentInstance.provides; 260 | var parentProvides = currentInstance.parent.provides; 261 | if (provides === parentProvides) { 262 | provides = currentInstance.provides = Object.create(parentProvides); 263 | } 264 | provides[key] = value; 265 | } 266 | } 267 | function inject(key, defaultValue) { 268 | var currentInstance = getCurrentInstance(); 269 | if (currentInstance) { 270 | var parentProvides = currentInstance.parent.provides; 271 | if (key in parentProvides) { 272 | return parentProvides[key]; 273 | } 274 | else if (defaultValue) { 275 | if (typeof defaultValue === "function") { 276 | return defaultValue(); 277 | } 278 | return defaultValue; 279 | } 280 | } 281 | } 282 | 283 | function createAppAPI(render) { 284 | return function createApp(rootComponent) { 285 | return { 286 | mount: function (rootContainer) { 287 | var vnode = createVNode(rootComponent); 288 | render(vnode, rootContainer); 289 | }, 290 | }; 291 | }; 292 | } 293 | 294 | function createRenderer(options) { 295 | var hostCreateElement = options.createElement, hostPatchProp = options.patchProp, hostInsert = options.insert; 296 | function render(vnode, container) { 297 | patch(vnode, container, null); 298 | } 299 | function patch(vnode, container, parentComponent) { 300 | var type = vnode.type, shapeFlag = vnode.shapeFlag; 301 | switch (type) { 302 | case Fragment: 303 | processFragment(vnode, container, parentComponent); 304 | break; 305 | case Text: 306 | processText(vnode, container); 307 | break; 308 | default: 309 | if (shapeFlag & 1 /* ELEMENT */) { 310 | processElement(vnode, container, parentComponent); 311 | } 312 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) { 313 | processComponent(vnode, container, parentComponent); 314 | } 315 | break; 316 | } 317 | } 318 | function processText(vnode, container) { 319 | var children = vnode.children; 320 | var textNode = (vnode.el = document.createTextNode(children)); 321 | container.append(textNode); 322 | } 323 | function processFragment(vnode, container, parentComponent) { 324 | mountChildren(vnode, container, parentComponent); 325 | } 326 | function processElement(vnode, container, parentComponent) { 327 | mountElement(vnode, container, parentComponent); 328 | } 329 | function mountElement(vnode, container, parentComponent) { 330 | //canvas 331 | // new Element() 332 | var el = (vnode.el = hostCreateElement(vnode.type)); 333 | var children = vnode.children, shapeFlag = vnode.shapeFlag; 334 | // children 335 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 336 | el.textContent = children; 337 | } 338 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 339 | mountChildren(vnode, el, parentComponent); 340 | } 341 | // props 342 | var props = vnode.props; 343 | for (var key in props) { 344 | var val = props[key]; 345 | hostPatchProp(el, key, val); 346 | } 347 | // canvs 348 | // el.x = 10 349 | // container.append(el); 350 | // addChild() 351 | hostInsert(el, container); 352 | } 353 | function mountChildren(vnode, container, parentComponent) { 354 | vnode.children.forEach(function (v) { 355 | patch(v, container, parentComponent); 356 | }); 357 | } 358 | function processComponent(vnode, container, parentComponent) { 359 | mountComponent(vnode, container, parentComponent); 360 | } 361 | function mountComponent(initialVNode, container, parentComponent) { 362 | var instance = createComponentInstance(initialVNode, parentComponent); 363 | setupComponent(instance); 364 | setupRenderEffect(instance, initialVNode, container); 365 | } 366 | function setupRenderEffect(instance, initialVNode, container) { 367 | var proxy = instance.proxy; 368 | var subTree = instance.render.call(proxy); 369 | patch(subTree, container, instance); 370 | initialVNode.el = subTree.el; 371 | } 372 | return { 373 | createApp: createAppAPI(render), 374 | }; 375 | } 376 | 377 | function createElement(type) { 378 | return document.createElement(type); 379 | } 380 | function patchProp(el, key, val) { 381 | var isOn = function (key) { return /^on[A-Z]/.test(key); }; 382 | if (isOn(key)) { 383 | var event_1 = key.slice(2).toLowerCase(); 384 | el.addEventListener(event_1, val); 385 | } 386 | else { 387 | el.setAttribute(key, val); 388 | } 389 | } 390 | function insert(el, parent) { 391 | parent.append(el); 392 | } 393 | var renderer = createRenderer({ 394 | createElement: createElement, 395 | patchProp: patchProp, 396 | insert: insert, 397 | }); 398 | function createApp() { 399 | var args = []; 400 | for (var _i = 0; _i < arguments.length; _i++) { 401 | args[_i] = arguments[_i]; 402 | } 403 | return renderer.createApp.apply(renderer, args); 404 | } 405 | 406 | exports.createApp = createApp; 407 | exports.createRenderer = createRenderer; 408 | exports.createTextVNode = createTextVNode; 409 | exports.getCurrentInstance = getCurrentInstance; 410 | exports.h = h; 411 | exports.inject = inject; 412 | exports.provide = provide; 413 | exports.renderSlots = renderSlots; 414 | --------------------------------------------------------------------------------