├── 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 |
19 |
20 |
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, shallowReadonly } from "../reactive";
2 |
3 | describe("shallowReadonly", () => {
4 | test("should not make non-reactive properties reactive", () => {
5 | const props = shallowReadonly({ n: { foo: 1 } });
6 | expect(isReadonly(props)).toBe(true);
7 | expect(isReadonly(props.n)).toBe(false);
8 | });
9 |
10 | it("should call console.warn when set", () => {
11 | console.warn = jest.fn();
12 | const user = shallowReadonly({
13 | age: 10,
14 | });
15 |
16 | user.age = 11;
17 | expect(console.warn).toHaveBeenCalled();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from "../shared/index";
2 |
3 | const publicPropertiesMap = {
4 | $el: (i) => i.vnode.el,
5 | $slots: (i) => i.slots,
6 | };
7 |
8 | export const PublicInstanceProxyHandlers = {
9 | get({ _: instance }, key) {
10 | const { setupState, props } = instance;
11 |
12 | if (hasOwn(setupState, key)) {
13 | return setupState[key];
14 | } else if (hasOwn(props, key)) {
15 | return props[key];
16 | }
17 |
18 | const publicGetter = publicPropertiesMap[key];
19 | if (publicGetter) {
20 | return publicGetter(instance);
21 | }
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | export function initSlots(instance, children) {
4 | // slots
5 | const { vnode } = instance;
6 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) {
7 | normalizeObjectSlots(children, instance.slots);
8 | }
9 | }
10 |
11 | function normalizeObjectSlots(children: any, slots: any) {
12 | for (const key in children) {
13 | const value = children[key];
14 | slots[key] = (props) => normalizeSlotValue(value(props));
15 | }
16 | }
17 |
18 | function normalizeSlotValue(value) {
19 | return Array.isArray(value) ? value : [value];
20 | }
21 |
--------------------------------------------------------------------------------
/example/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
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 |
--------------------------------------------------------------------------------