├── .gitignore
├── src
├── reactivity
│ ├── index.ts
│ ├── tests
│ │ ├── shallowReadonly.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── computed.spec.ts
│ │ ├── ref.spec.ts
│ │ └── effect.spec.ts
│ ├── computed.ts
│ ├── reactive.ts
│ ├── baseHandler.ts
│ ├── ref.ts
│ └── effect.ts
├── index.ts
├── runtime-core
│ ├── componentProps.ts
│ ├── h.ts
│ ├── index.ts
│ ├── componentEmit.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── createApp.ts
│ ├── componentSlots.ts
│ ├── componentPublicInstance.ts
│ ├── apiInject.ts
│ ├── vnode.ts
│ ├── component.ts
│ └── renderer.ts
├── shared
│ ├── ShapeFlags.ts
│ └── index.ts
└── runtime-dom
│ └── index.ts
├── babel.config.js
├── example
├── update
│ ├── main.js
│ ├── index.html
│ └── App.js
├── componentEmit
│ ├── main.js
│ ├── index.html
│ ├── App.js
│ └── Foo.js
├── componentSlot
│ ├── main.js
│ ├── index.html
│ ├── Foo.js
│ └── App.js
├── helloworld
│ ├── main.js
│ ├── Foo.js
│ ├── index.html
│ └── 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
├── package.json
├── .vscode
└── launch.json
├── tsconfig.json
└── lib
├── guide-mini-vue.esm.js
└── guide-mini-vue.cjs.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export { ref, proxyRefs } from "./ref";
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 出口
2 | export * from "./runtime-dom"
3 | export * from "./reactivity"
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance, rawProps) {
2 | instance.props = rawProps || {}
3 | }
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {targets: {node: 'current'}}], '@babel/preset-typescript',
4 | ],
5 | };
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/example/update/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);
--------------------------------------------------------------------------------
/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);
--------------------------------------------------------------------------------
/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);
--------------------------------------------------------------------------------
/example/helloworld/main.js:
--------------------------------------------------------------------------------
1 |
2 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
3 | import { App } from "./App.js";
4 |
5 | const rootContainer = document.querySelector("#app");
6 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/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";
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/example/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
17 |
18 |
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/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 | });
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/example/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/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 | // setupState
11 | const { setupState, props } = instance;
12 | if (hasOwn(setupState, key)) {
13 | return setupState[key];
14 | } else if (hasOwn(props, key)) {
15 | return props[key];
16 | }
17 | const publicGetter = publicPropertiesMap[key];
18 | if (publicGetter) {
19 | return publicGetter(instance);
20 | }
21 | },
22 | };
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "guide-mini-vue-2",
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.16.0",
13 | "@babel/preset-env": "^7.16.4",
14 | "@babel/preset-typescript": "^7.16.0",
15 | "@rollup/plugin-typescript": "^8.3.0",
16 | "@types/jest": "^27.0.3",
17 | "babel-jest": "^27.4.2",
18 | "jest": "^27.4.3",
19 | "rollup": "^2.60.2",
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 | export const App = {
5 | name: "App",
6 | render() {
7 | const app = h("div", {}, "App");
8 | // object key
9 | const foo = h(
10 | Foo,
11 | {},
12 | {
13 | header: ({ age }) => [
14 | h("p", {}, "header" + age),
15 | createTextVNode("你好呀"),
16 | ],
17 | footer: () => h("p", {}, "footer"),
18 | }
19 | );
20 | // 数组 vnode
21 | // const foo = h(Foo, {}, h("p", {}, "123"));
22 | return h("div", {}, [app, foo]);
23 | },
24 |
25 | setup() {
26 | return {};
27 | },
28 | };
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 | class ComputedRefImpl {
4 | private _getter: any;
5 | private _dirty: boolean = true;
6 | private _value: any;
7 | private _effect: ReactiveEffect;
8 | constructor(getter) {
9 | this._getter = getter
10 | this._effect = new ReactiveEffect(this._getter, () => {
11 | if (!this._dirty) {
12 | this._dirty = true
13 | }
14 | })
15 | }
16 | get value() {
17 | if (this._dirty) {
18 | this._dirty = false
19 | this._value = this._effect.run()
20 | }
21 | return this._value
22 | }
23 | }
24 |
25 |
26 | export function computed(getter) {
27 | return new ComputedRefImpl(getter);
28 | }
--------------------------------------------------------------------------------
/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 |
15 | export const camelize = (str: string) => {
16 | return str.replace(/-(\w)/g, (_, c: string) => {
17 | return c ? c.toUpperCase() : "";
18 | });
19 | };
20 |
21 | const capitalize = (str: string) => {
22 | return str.charAt(0).toUpperCase() + str.slice(1);
23 | };
24 |
25 | export const toHandlerKey = (str: string) => {
26 | return str ? "on" + capitalize(str) : "";
27 | };
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { readonly, isReadonly, isProxy} 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 | expect(wrapped.foo).toBe(1);
14 | });
15 |
16 | it("should call console.warn when set", () => {
17 | console.warn = jest.fn();
18 | const user = readonly({
19 | age: 10,
20 | });
21 |
22 | user.age = 11;
23 | expect(console.warn).toHaveBeenCalled();
24 | });
25 | });
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive, isReactive, 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 | });
--------------------------------------------------------------------------------
/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, prevVal, nextVal) {
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, nextVal);
12 | } else {
13 | if (nextVal === undefined || nextVal === null) {
14 | el.removeAttribute(key);
15 | } else {
16 | el.setAttribute(key, nextVal);
17 | }
18 | }
19 | }
20 |
21 | function insert(el, parent) {
22 | parent.append(el);
23 | }
24 |
25 | const renderer: any = createRenderer({
26 | createElement,
27 | patchProp,
28 | insert,
29 | });
30 |
31 | export function createApp(...args) {
32 | return renderer.createApp(...args);
33 | }
34 |
35 | export * from "../runtime-core";
--------------------------------------------------------------------------------
/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 | name:"App",
7 | render() {
8 | window.self = this
9 | // ui
10 | return h(
11 | "div",
12 | {
13 | id: "root",
14 | class: ["red", "hard"],
15 | onClick() {
16 | console.log("click");
17 | },
18 | onMousedown(){
19 | console.log("mousedown")
20 | }
21 | },
22 | [
23 | h("div", {}, "hi," + this.msg),
24 | h(Foo, {
25 | count: 1,
26 | }),
27 | ]
28 | // "hi, " + this.msg
29 | // string
30 | // "hi, mini-vue"
31 | // Array
32 | // [h("p", { class:"red"}, "hi"), h("p", {class:"blue"}, "mini-vue")]
33 | );
34 | },
35 |
36 | setup() {
37 | return {
38 | msg: "mini-vue-mini2",
39 | };
40 | },
41 | };
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 |
8 | {
9 | "version": "0.2.0",
10 | "configurations": [
11 | {
12 | "name": "Debug Jest Tests",
13 | "type": "node",
14 | "request": "launch",
15 | "runtimeArgs": [
16 | "--inspect-brk",
17 | "${workspaceRoot}/node_modules/.bin/jest",
18 | "--runInBand"
19 | ],
20 | "console": "integratedTerminal",
21 | "internalConsoleOptions": "neverOpen",
22 | "port": "9229"
23 | }
24 | ]
25 | },
26 | {
27 | "type": "pwa-chrome",
28 | "request": "launch",
29 | "name": "Launch Chrome against localhost",
30 | "url": "http://localhost:8080",
31 | "webRoot": "${workspaceFolder}"
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 | }
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "../shared/index"
2 | import { mutableHandlers, readonlyHandlers, shallowReadonlyHandlers } from "./baseHandler"
3 |
4 | export const enum ReactiveFlags {
5 | IS_REACTIVE = "__v_isReactive",
6 | IS_READONLY = "__v_isReadonly",
7 | }
8 |
9 | export function reactive (raw) {
10 | return createReactiveObject(raw, mutableHandlers)
11 | }
12 |
13 |
14 | export function readonly(raw) {
15 | return createReactiveObject(raw, readonlyHandlers)
16 | }
17 |
18 | export function shallowReadonly (raw) {
19 | return createReactiveObject(raw, shallowReadonlyHandlers)
20 | }
21 |
22 | export function isReactive(value) {
23 | return !!value[ReactiveFlags.IS_REACTIVE]
24 | }
25 | export function isReadonly(value) {
26 | return !!value[ReactiveFlags.IS_READONLY]
27 | }
28 |
29 | export function isProxy (value) {
30 | return isReactive(value) || isReadonly(value)
31 | }
32 |
33 | function createReactiveObject(target, baseHandles) {
34 | if (!isObject(target)) {
35 | console.warn(`target ${target} 必须是一个对象`);
36 | return target
37 | }
38 |
39 | return new Proxy(target, baseHandles);
40 | }
--------------------------------------------------------------------------------
/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 | });
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/src/reactivity/baseHandler.ts:
--------------------------------------------------------------------------------
1 | import { extend, isObject } from "../shared";
2 | import { track, trigger } from "./effect";
3 | import { reactive, ReactiveFlags, readonly } 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 |
13 | if (key === ReactiveFlags.IS_REACTIVE) {
14 | return !isReadonly;
15 | } else if (key === ReactiveFlags.IS_READONLY) {
16 | return isReadonly
17 | }
18 | const res = Reflect.get(target, key)
19 | if (shallow) {
20 | return res
21 | }
22 | if (isObject(res)) {
23 | return isReadonly ? readonly(res) : reactive(res);
24 | }
25 | if (!isReadonly) {
26 | // 依赖收集
27 | track(target, key)
28 | }
29 | return res
30 | }
31 | }
32 |
33 | function createSetter() {
34 | return function set(target, key, value) {
35 | const res = Reflect.set(target, key, value);
36 | trigger(target, key);
37 | return res;
38 | };
39 | }
40 |
41 |
42 |
43 | export const mutableHandlers = {
44 | get,
45 | set
46 | }
47 |
48 | export const readonlyHandlers = {
49 | get: readonlyGet,
50 | set(target, key) {
51 | console.warn(
52 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`,
53 | target
54 | );
55 |
56 | return true;
57 | },
58 | };
59 |
60 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
61 | get: shallowReadonlyGet,
62 | });
--------------------------------------------------------------------------------
/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 RefTmpl {
6 | private _value: any
7 | public __v_isRef = true;
8 | public dep
9 | private _rawValue: any;
10 | constructor (value) {
11 | this._rawValue = value;
12 | this._value = convert(value)
13 | this.dep = new Set()
14 | }
15 | get value () {
16 | trackRefValue(this);
17 | return this._value
18 | }
19 | set value (newValue) {
20 | if (hasChanged(newValue, this._rawValue)) {
21 | this._rawValue = newValue;
22 | this._value = convert(newValue)
23 | triggerEffects(this.dep)
24 | }
25 |
26 | }
27 | }
28 |
29 | function trackRefValue(ref) {
30 | if (isTracking()) {
31 | trackEffects(ref.dep)
32 | }
33 | }
34 |
35 |
36 | function convert(value) {
37 | return isObject(value) ? reactive(value) : value;
38 | }
39 |
40 |
41 | export function ref(value) {
42 | return new RefTmpl(value)
43 | }
44 |
45 | export function isRef(ref) {
46 | return !!ref.__v_isRef
47 | }
48 |
49 | export function unRef (ref) {
50 | return isRef(ref) ? ref.value : ref;
51 | }
52 |
53 | export function proxyRefs(objectWithRefs) {
54 | return new Proxy(objectWithRefs, {
55 | get(target, key) {
56 | return unRef(Reflect.get(target, key))
57 | },
58 | set(target, key, value) {
59 | if (isRef(target[key]) && !isRef(value)) {
60 | return target[key].value = value
61 | } else {
62 | return Reflect.set(target, key, value)
63 | }
64 | }
65 | })
66 | }
--------------------------------------------------------------------------------
/example/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 |
6 | setup() {
7 | const count = ref(0);
8 |
9 | const onClick = () => {
10 | count.value++;
11 | };
12 |
13 | const props = ref({
14 | foo: "foo",
15 | bar: "bar",
16 | });
17 | const onChangePropsDemo1 = () => {
18 | props.value.foo = "new-foo";
19 | };
20 |
21 | const onChangePropsDemo2 = () => {
22 | props.value.foo = undefined;
23 | };
24 |
25 | const onChangePropsDemo3 = () => {
26 | props.value = {
27 | foo: "foo",
28 | };
29 | };
30 |
31 | return {
32 | count,
33 | onClick,
34 | onChangePropsDemo1,
35 | onChangePropsDemo2,
36 | onChangePropsDemo3,
37 | props,
38 | };
39 | },
40 | render() {
41 | return h(
42 | "div",
43 | {
44 | id: "root",
45 | ...this.props,
46 | },
47 | [
48 | h("div", {}, "count:" + this.count),
49 | h(
50 | "button",
51 | {
52 | onClick: this.onClick,
53 | },
54 | "click"
55 | ),
56 | h(
57 | "button",
58 | {
59 | onClick: this.onChangePropsDemo1,
60 | },
61 | "changeProps - 值改变了 - 修改"
62 | ),
63 |
64 | h(
65 | "button",
66 | {
67 | onClick: this.onChangePropsDemo2,
68 | },
69 | "changeProps - 值变成了 undefined - 删除"
70 | ),
71 |
72 | h(
73 | "button",
74 | {
75 | onClick: this.onChangePropsDemo3,
76 | },
77 | "changeProps - key 在新的里面没有了 - 删除"
78 | ),
79 | ]
80 | );
81 | },
82 | };
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect";
2 | import { reactive } from "../reactive";
3 | import { isRef, proxyRefs, ref, unRef } from "../ref";
4 | 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 | });
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRefs } from "..";
2 | import { shallowReadonly } from "../reactivity/reactive";
3 | import { emit } from "./componentEmit";
4 | import { initProps } from "./componentProps";
5 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance";
6 | import { initSlots } from "./componentSlots";
7 |
8 | export function createComponentInstance(vnode, parent) {
9 | const component = {
10 | vnode,
11 | type: vnode.type,
12 | setupState: {},
13 | props: {},
14 | slots: {},
15 | provides: parent ? parent.provides : {},
16 | parent,
17 | isMounted: false,
18 | subTree: {},
19 | emit: () => {}
20 | };
21 | component.emit = emit.bind(null, component) as any;
22 | return component;
23 | }
24 |
25 | export function setupComponent(instance) {
26 | // TODO
27 | initProps(instance, instance.vnode.props);
28 | initSlots(instance, instance.vnode.children)
29 | setupStatefulComponent(instance);
30 | }
31 |
32 | function setupStatefulComponent(instance: any) {
33 | const Component = instance.type;
34 |
35 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
36 | const { setup } = Component;
37 |
38 | if (setup) {
39 | setCurrentInstance(instance);
40 | const setupResult = setup(shallowReadonly(instance.props), {
41 | emit: instance.emit,
42 | });
43 | setCurrentInstance(null);
44 | handleSetupResult(instance, setupResult);
45 | }
46 | }
47 |
48 | function handleSetupResult(instance, setupResult: any) {
49 | // function Object
50 | // TODO function
51 | if (typeof setupResult === "object") {
52 | instance.setupState = proxyRefs(setupResult);
53 | }
54 |
55 | finishComponentSetup(instance);
56 | }
57 |
58 | function finishComponentSetup(instance: any) {
59 | const Component = instance.type;
60 |
61 | instance.render = Component.render;
62 | }
63 |
64 |
65 | let currentInstance = null;
66 |
67 | export function getCurrentInstance() {
68 | return currentInstance;
69 | }
70 |
71 | export function setCurrentInstance(instance) {
72 | currentInstance = instance;
73 | }
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "../shared";
2 | let activeEffect;
3 | let shouldTrack;
4 | export class ReactiveEffect {
5 | private _fn: any;
6 | deps =[]
7 | active = true;
8 | onStop?: () => void;
9 | constructor (fn, public scheduler?) {
10 | this._fn = fn
11 | }
12 | run () {
13 | if (!this.active) {
14 | this._fn()
15 | }
16 | shouldTrack = true
17 | activeEffect = this
18 | const result = this._fn()
19 | shouldTrack = false
20 | return result
21 | }
22 |
23 | stop() {
24 | if (this.active) {
25 | cleanupEffect( this)
26 | if (this.onStop) {
27 | this.onStop()
28 | }
29 | }
30 | this.active = false
31 | }
32 |
33 | }
34 |
35 | function cleanupEffect(effect) {
36 | effect.deps.forEach((dep: any) => {
37 | dep.delete(effect);
38 | });
39 | effect.deps.length = 0
40 | }
41 |
42 |
43 | const targetMap = new Map()
44 | export function track(target, key) {
45 | if (!isTracking()) return;
46 | // target -> key -> dep
47 | let depsMap = targetMap.get(target)
48 | if (!depsMap) {
49 | depsMap = new Map()
50 | targetMap.set(target, depsMap)
51 | }
52 |
53 | let dep = depsMap.get(key)
54 | if (!dep) {
55 | dep = new Set()
56 | depsMap.set(key, dep)
57 | }
58 | trackEffects(dep)
59 | }
60 |
61 | export function trackEffects (dep) {
62 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
63 | if (dep.has(activeEffect)) return;
64 | dep.add(activeEffect)
65 | activeEffect.deps.push(dep)
66 | }
67 |
68 | export function isTracking() {
69 | return shouldTrack && activeEffect !== undefined;
70 | }
71 |
72 | export function trigger (target, key) {
73 | let depsMap = targetMap.get(target)
74 | let dep = depsMap.get(key)
75 | triggerEffects(dep)
76 | }
77 |
78 | export function triggerEffects(dep) {
79 | for (const effect of dep) {
80 | if (effect.scheduler) {
81 | effect.scheduler()
82 | } else {
83 | effect.run()
84 | }
85 | }
86 | }
87 |
88 | type effectOptions = {
89 | scheduler?: Function;
90 | };
91 |
92 |
93 |
94 |
95 |
96 | export function effect (fn, options: effectOptions = {}) {
97 | // fn
98 | const _effect = new ReactiveEffect(fn, options.scheduler);
99 | extend(_effect, options)
100 | _effect.run()
101 | const runner: any = _effect.run.bind(_effect)
102 | runner.effect = _effect
103 | return runner
104 | }
105 |
106 | export function stop (runner) {
107 | runner.effect.stop()
108 | }
--------------------------------------------------------------------------------
/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 | // 1.通过 effect 的第二个参数给定的一个 scheduler 的 fn
39 | // 2. effect 第一次执行的时候还会执行 fn
40 | // 3. 当响应式对象 set update 不会执行 fn 而是执行 scheduler
41 | // 4. 如果说当执行 runner 的时候,会再次的执行 fn
42 | let dummy;
43 | let run: any;
44 | let runner:any
45 | const scheduler = jest.fn(() => {
46 | run = runner;
47 | });
48 | const obj = reactive({ foo: 1 });
49 | runner = effect(
50 | () => {
51 | dummy = obj.foo;
52 | },
53 | { scheduler }
54 | );
55 | expect(scheduler).not.toHaveBeenCalled();
56 | expect(dummy).toBe(1);
57 | // should be called on first trigger
58 | obj.foo++;
59 | expect(scheduler).toHaveBeenCalledTimes(1);
60 | // // should not run yet
61 | expect(dummy).toBe(1);
62 | // // manually run
63 | run();
64 | // // should have run
65 | expect(dummy).toBe(2);
66 | });
67 |
68 |
69 | it("stop", () => {
70 | let dummy;
71 | const obj = reactive({ prop: 1 });
72 | const runner = effect(() => {
73 | dummy = obj.prop;
74 | });
75 | obj.prop = 2;
76 | expect(dummy).toBe(2);
77 | stop(runner);
78 | // obj.prop = 3;
79 | obj.prop++
80 | expect(dummy).toBe(2);
81 |
82 | // stopped effect should still be manually callable
83 | runner();
84 | expect(dummy).toBe(3);
85 | });
86 |
87 | it("onStop", () => {
88 | const obj = reactive({
89 | foo: 1,
90 | });
91 | const onStop = jest.fn();
92 | let dummy;
93 | const runner = effect(
94 | () => {
95 | dummy = obj.foo;
96 | },
97 | {
98 | onStop,
99 | }
100 | );
101 |
102 | stop(runner);
103 | expect(onStop).toBeCalledTimes(1);
104 | });
105 | });
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../reactivity/effect";
2 | import { ShapeFlags } from "../shared/ShapeFlags";
3 | import { createComponentInstance, setupComponent } from "./component";
4 | import { createAppAPI } from "./createApp";
5 | import { Fragment, Text } from "./vnode";
6 |
7 | export function createRenderer(options) {
8 | const {
9 | createElement: hostCreateElement,
10 | patchProp: hostPatchProp,
11 | insert: hostInsert,
12 | } = options;
13 |
14 | function render(vnode, container) {
15 | patch(null, vnode, container, null);
16 | }
17 |
18 | function patch(n1, n2, container, parentComponent) {
19 | const { type, shapeFlag } = n2;
20 |
21 | switch (type) {
22 | case Fragment:
23 | processFragment(n1, n2, container, parentComponent);
24 | break;
25 | case Text:
26 | processText(n1, n2, container);
27 | break;
28 |
29 | default:
30 | if (shapeFlag & ShapeFlags.ELEMENT) {
31 | processElement(n1, n2, container, parentComponent);
32 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
33 | processComponent(n1, n2, container, parentComponent);
34 | }
35 | break;
36 | }
37 | }
38 |
39 | function processText(n1, n2: any, container: any) {
40 | const { children } = n2;
41 | const textNode = (n2.el = document.createTextNode(children));
42 | container.append(textNode);
43 | }
44 |
45 | function processFragment(n1, n2: any, container: any, parentComponent) {
46 | mountChildren(n2, container, parentComponent);
47 | }
48 |
49 | function processElement(n1, n2: any, container: any, parentComponent) {
50 | if (!n1) {
51 | mountElement(n2, container, parentComponent);
52 | } else {
53 | patchElement(n1, n2, container);
54 | }
55 | }
56 |
57 | function patchElement(n1, n2, container) {
58 | console.log("patchElement");
59 | console.log("n1", n1);
60 | console.log("n2", n2);
61 |
62 | const oldProps = n1.props || {};
63 | const newProps = n2.props || {};
64 |
65 | const el = (n2.el = n1.el);
66 |
67 | patchProps(el, oldProps, newProps);
68 | }
69 |
70 | function patchProps(el, oldProps, newProps) {
71 | if (oldProps !== newProps) {
72 | for (const key in newProps) {
73 | const prevProp = oldProps[key];
74 | const nextProp = newProps[key];
75 |
76 | if (prevProp !== nextProp) {
77 | hostPatchProp(el, key, prevProp, nextProp);
78 | }
79 | }
80 |
81 | if (oldProps !== {}) {
82 | for (const key in oldProps) {
83 | if (!(key in newProps)) {
84 | hostPatchProp(el, key, oldProps[key], null);
85 | }
86 | }
87 | }
88 | }
89 | }
90 |
91 | function mountElement(vnode: any, container: any, parentComponent) {
92 | const el = (vnode.el = hostCreateElement(vnode.type));
93 |
94 | const { children, shapeFlag } = vnode;
95 |
96 | // children
97 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
98 | el.textContent = children;
99 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
100 | mountChildren(vnode, el, parentComponent);
101 | }
102 |
103 | // props
104 | const { props } = vnode;
105 | for (const key in props) {
106 | const val = props[key];
107 | hostPatchProp(el, key, null, val);
108 | }
109 | hostInsert(el, container);
110 | }
111 |
112 | function mountChildren(vnode, container, parentComponent) {
113 | vnode.children.forEach((v) => {
114 | patch(null, v, container, parentComponent);
115 | });
116 | }
117 |
118 | function processComponent(n1, n2: any, container: any, parentComponent) {
119 | mountComponent(n2, container, parentComponent);
120 | }
121 |
122 | function mountComponent(initialVNode: any, container, parentComponent) {
123 | const instance = createComponentInstance(initialVNode, parentComponent);
124 |
125 | setupComponent(instance);
126 | setupRenderEffect(instance, initialVNode, container);
127 | }
128 |
129 | function setupRenderEffect(instance: any, initialVNode, container) {
130 | effect(() => {
131 | if (!instance.isMounted) {
132 | console.log("init");
133 | const { proxy } = instance;
134 | const subTree = (instance.subTree = instance.render.call(proxy));
135 |
136 | patch(null, subTree, container, instance);
137 |
138 | initialVNode.el = subTree.el;
139 |
140 | instance.isMounted = true;
141 | } else {
142 | console.log("update");
143 | const { proxy } = instance;
144 | const subTree = instance.render.call(proxy);
145 | const prevSubTree = instance.subTree;
146 | instance.subTree = subTree;
147 |
148 | patch(prevSubTree, subTree, container, instance);
149 | }
150 | });
151 | }
152 |
153 | return {
154 | createApp: createAppAPI(render),
155 | };
156 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 | "lib": ["DOM", "ES6"], /* Specify library files to be included in the compilation. */
10 | // "allowJs": true, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | // "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | // "outDir": "./", /* Redirect output structure to the directory. */
18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | // "removeComments": true, /* Do not emit comments to output. */
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true, /* Enable all strict type-checking options. */
29 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
43 |
44 | /* Module Resolution Options */
45 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
49 | // "typeRoots": [], /* List of folders to include type definitions from. */
50 | "types": ["jest"], /* Type declaration files to be included in compilation. */
51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
55 |
56 | /* Source Map Options */
57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
61 |
62 | /* Experimental Options */
63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
65 |
66 | /* Advanced Options */
67 | "skipLibCheck": true, /* Skip type checking of declaration files. */
68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | const Fragment = Symbol("Fragment");
2 | const Text = Symbol("Text");
3 | function createVNode(type, props, children) {
4 | const vnode = {
5 | type,
6 | props,
7 | children,
8 | 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 | const slot = slots[name];
39 | if (slot) {
40 | if (typeof slot === "function") {
41 | return createVNode(Fragment, {}, slot(props));
42 | }
43 | }
44 | }
45 |
46 | const extend = Object.assign;
47 | const isObject = (value) => {
48 | return value !== null && typeof value === "object";
49 | };
50 | const hasChanged = (val, newValue) => {
51 | return !Object.is(val, newValue);
52 | };
53 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
54 | const camelize = (str) => {
55 | return str.replace(/-(\w)/g, (_, c) => {
56 | return c ? c.toUpperCase() : "";
57 | });
58 | };
59 | const capitalize = (str) => {
60 | return str.charAt(0).toUpperCase() + str.slice(1);
61 | };
62 | const toHandlerKey = (str) => {
63 | return str ? "on" + capitalize(str) : "";
64 | };
65 |
66 | let activeEffect;
67 | let shouldTrack;
68 | class ReactiveEffect {
69 | constructor(fn, scheduler) {
70 | this.scheduler = scheduler;
71 | this.deps = [];
72 | this.active = true;
73 | this._fn = fn;
74 | }
75 | run() {
76 | if (!this.active) {
77 | this._fn();
78 | }
79 | shouldTrack = true;
80 | activeEffect = this;
81 | const result = this._fn();
82 | shouldTrack = false;
83 | return result;
84 | }
85 | stop() {
86 | if (this.active) {
87 | cleanupEffect(this);
88 | if (this.onStop) {
89 | this.onStop();
90 | }
91 | }
92 | this.active = false;
93 | }
94 | }
95 | function cleanupEffect(effect) {
96 | effect.deps.forEach((dep) => {
97 | dep.delete(effect);
98 | });
99 | effect.deps.length = 0;
100 | }
101 | const targetMap = new Map();
102 | function track(target, key) {
103 | if (!isTracking())
104 | return;
105 | // target -> key -> dep
106 | let depsMap = targetMap.get(target);
107 | if (!depsMap) {
108 | depsMap = new Map();
109 | targetMap.set(target, depsMap);
110 | }
111 | let dep = depsMap.get(key);
112 | if (!dep) {
113 | dep = new Set();
114 | depsMap.set(key, dep);
115 | }
116 | trackEffects(dep);
117 | }
118 | function trackEffects(dep) {
119 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
120 | if (dep.has(activeEffect))
121 | return;
122 | dep.add(activeEffect);
123 | activeEffect.deps.push(dep);
124 | }
125 | function isTracking() {
126 | return shouldTrack && activeEffect !== undefined;
127 | }
128 | function trigger(target, key) {
129 | let depsMap = targetMap.get(target);
130 | let dep = depsMap.get(key);
131 | triggerEffects(dep);
132 | }
133 | function triggerEffects(dep) {
134 | for (const effect of dep) {
135 | if (effect.scheduler) {
136 | effect.scheduler();
137 | }
138 | else {
139 | effect.run();
140 | }
141 | }
142 | }
143 | function effect(fn, options = {}) {
144 | // fn
145 | const _effect = new ReactiveEffect(fn, options.scheduler);
146 | extend(_effect, options);
147 | _effect.run();
148 | const runner = _effect.run.bind(_effect);
149 | runner.effect = _effect;
150 | return runner;
151 | }
152 |
153 | const get = createGetter();
154 | const set = createSetter();
155 | const readonlyGet = createGetter(true);
156 | const shallowReadonlyGet = createGetter(true, true);
157 | function createGetter(isReadonly = false, shallow = false) {
158 | return function get(target, key) {
159 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
160 | return !isReadonly;
161 | }
162 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
163 | return isReadonly;
164 | }
165 | const res = Reflect.get(target, key);
166 | if (shallow) {
167 | return res;
168 | }
169 | if (isObject(res)) {
170 | return isReadonly ? readonly(res) : reactive(res);
171 | }
172 | if (!isReadonly) {
173 | // 依赖收集
174 | track(target, key);
175 | }
176 | return res;
177 | };
178 | }
179 | function createSetter() {
180 | return function set(target, key, value) {
181 | const res = Reflect.set(target, key, value);
182 | trigger(target, key);
183 | return res;
184 | };
185 | }
186 | const mutableHandlers = {
187 | get,
188 | set
189 | };
190 | const readonlyHandlers = {
191 | get: readonlyGet,
192 | set(target, key) {
193 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target);
194 | return true;
195 | },
196 | };
197 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
198 | get: shallowReadonlyGet,
199 | });
200 |
201 | function reactive(raw) {
202 | return createReactiveObject(raw, mutableHandlers);
203 | }
204 | function readonly(raw) {
205 | return createReactiveObject(raw, readonlyHandlers);
206 | }
207 | function shallowReadonly(raw) {
208 | return createReactiveObject(raw, shallowReadonlyHandlers);
209 | }
210 | function createReactiveObject(target, baseHandles) {
211 | if (!isObject(target)) {
212 | console.warn(`target ${target} 必须是一个对象`);
213 | return target;
214 | }
215 | return new Proxy(target, baseHandles);
216 | }
217 |
218 | function emit(instance, event, ...args) {
219 | const { props } = instance;
220 | const handlerName = toHandlerKey(camelize(event));
221 | const handler = props[handlerName];
222 | handler && handler(...args);
223 | }
224 |
225 | function initProps(instance, rawProps) {
226 | instance.props = rawProps || {};
227 | }
228 |
229 | const publicPropertiesMap = {
230 | $el: (i) => i.vnode.el,
231 | $slots: (i) => i.slots,
232 | };
233 | const PublicInstanceProxyHandlers = {
234 | get({ _: instance }, key) {
235 | // setupState
236 | const { setupState, props } = instance;
237 | if (hasOwn(setupState, key)) {
238 | return setupState[key];
239 | }
240 | else if (hasOwn(props, key)) {
241 | return props[key];
242 | }
243 | const publicGetter = publicPropertiesMap[key];
244 | if (publicGetter) {
245 | return publicGetter(instance);
246 | }
247 | },
248 | };
249 |
250 | function initSlots(instance, children) {
251 | // slots
252 | const { vnode } = instance;
253 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
254 | normalizeObjectSlots(children, instance.slots);
255 | }
256 | }
257 | function normalizeObjectSlots(children, slots) {
258 | for (const key in children) {
259 | const value = children[key];
260 | slots[key] = (props) => normalizeSlotValue(value(props));
261 | }
262 | }
263 | function normalizeSlotValue(value) {
264 | return Array.isArray(value) ? value : [value];
265 | }
266 |
267 | function createComponentInstance(vnode, parent) {
268 | const component = {
269 | vnode,
270 | type: vnode.type,
271 | setupState: {},
272 | props: {},
273 | slots: {},
274 | provides: parent ? parent.provides : {},
275 | parent,
276 | isMounted: false,
277 | subTree: {},
278 | emit: () => { }
279 | };
280 | component.emit = emit.bind(null, component);
281 | return component;
282 | }
283 | function setupComponent(instance) {
284 | // TODO
285 | initProps(instance, instance.vnode.props);
286 | initSlots(instance, instance.vnode.children);
287 | setupStatefulComponent(instance);
288 | }
289 | function setupStatefulComponent(instance) {
290 | const Component = instance.type;
291 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
292 | const { setup } = Component;
293 | if (setup) {
294 | setCurrentInstance(instance);
295 | const setupResult = setup(shallowReadonly(instance.props), {
296 | emit: instance.emit,
297 | });
298 | setCurrentInstance(null);
299 | handleSetupResult(instance, setupResult);
300 | }
301 | }
302 | function handleSetupResult(instance, setupResult) {
303 | // function Object
304 | // TODO function
305 | if (typeof setupResult === "object") {
306 | instance.setupState = proxyRefs(setupResult);
307 | }
308 | finishComponentSetup(instance);
309 | }
310 | function finishComponentSetup(instance) {
311 | const Component = instance.type;
312 | instance.render = Component.render;
313 | }
314 | let currentInstance = null;
315 | function getCurrentInstance() {
316 | return currentInstance;
317 | }
318 | function setCurrentInstance(instance) {
319 | currentInstance = instance;
320 | }
321 |
322 | function provide(key, value) {
323 | const currentInstance = getCurrentInstance();
324 | if (currentInstance) {
325 | let { provides } = currentInstance;
326 | const parentProvides = currentInstance.parent.provides;
327 | if (provides === parentProvides) {
328 | provides = currentInstance.provides = Object.create(parentProvides);
329 | }
330 | provides[key] = value;
331 | }
332 | }
333 | function inject(key, defaultValue) {
334 | const currentInstance = getCurrentInstance();
335 | if (currentInstance) {
336 | const parentProvides = currentInstance.parent.provides;
337 | if (key in parentProvides) {
338 | return parentProvides[key];
339 | }
340 | else if (defaultValue) {
341 | if (typeof defaultValue === "function") {
342 | return defaultValue();
343 | }
344 | return defaultValue;
345 | }
346 | }
347 | }
348 |
349 | function createAppAPI(render) {
350 | return function createApp(rootComponent) {
351 | return {
352 | mount(rootContainer) {
353 | const vnode = createVNode(rootComponent);
354 | render(vnode, rootContainer);
355 | },
356 | };
357 | };
358 | }
359 |
360 | function createRenderer(options) {
361 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, } = options;
362 | function render(vnode, container) {
363 | patch(null, vnode, container, null);
364 | }
365 | function patch(n1, n2, container, parentComponent) {
366 | const { type, shapeFlag } = n2;
367 | switch (type) {
368 | case Fragment:
369 | processFragment(n1, n2, container, parentComponent);
370 | break;
371 | case Text:
372 | processText(n1, n2, container);
373 | break;
374 | default:
375 | if (shapeFlag & 1 /* ELEMENT */) {
376 | processElement(n1, n2, container, parentComponent);
377 | }
378 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
379 | processComponent(n1, n2, container, parentComponent);
380 | }
381 | break;
382 | }
383 | }
384 | function processText(n1, n2, container) {
385 | const { children } = n2;
386 | const textNode = (n2.el = document.createTextNode(children));
387 | container.append(textNode);
388 | }
389 | function processFragment(n1, n2, container, parentComponent) {
390 | mountChildren(n2, container, parentComponent);
391 | }
392 | function processElement(n1, n2, container, parentComponent) {
393 | if (!n1) {
394 | mountElement(n2, container, parentComponent);
395 | }
396 | else {
397 | patchElement(n1, n2);
398 | }
399 | }
400 | function patchElement(n1, n2, container) {
401 | console.log("patchElement");
402 | console.log("n1", n1);
403 | console.log("n2", n2);
404 | const oldProps = n1.props || {};
405 | const newProps = n2.props || {};
406 | const el = (n2.el = n1.el);
407 | patchProps(el, oldProps, newProps);
408 | }
409 | function patchProps(el, oldProps, newProps) {
410 | if (oldProps !== newProps) {
411 | for (const key in newProps) {
412 | const prevProp = oldProps[key];
413 | const nextProp = newProps[key];
414 | if (prevProp !== nextProp) {
415 | hostPatchProp(el, key, prevProp, nextProp);
416 | }
417 | }
418 | if (oldProps !== {}) {
419 | for (const key in oldProps) {
420 | if (!(key in newProps)) {
421 | hostPatchProp(el, key, oldProps[key], null);
422 | }
423 | }
424 | }
425 | }
426 | }
427 | function mountElement(vnode, container, parentComponent) {
428 | const el = (vnode.el = hostCreateElement(vnode.type));
429 | const { children, shapeFlag } = vnode;
430 | // children
431 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
432 | el.textContent = children;
433 | }
434 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
435 | mountChildren(vnode, el, parentComponent);
436 | }
437 | // props
438 | const { props } = vnode;
439 | for (const key in props) {
440 | const val = props[key];
441 | hostPatchProp(el, key, null, val);
442 | }
443 | hostInsert(el, container);
444 | }
445 | function mountChildren(vnode, container, parentComponent) {
446 | vnode.children.forEach((v) => {
447 | patch(null, v, container, parentComponent);
448 | });
449 | }
450 | function processComponent(n1, n2, container, parentComponent) {
451 | mountComponent(n2, container, parentComponent);
452 | }
453 | function mountComponent(initialVNode, container, parentComponent) {
454 | const instance = createComponentInstance(initialVNode, parentComponent);
455 | setupComponent(instance);
456 | setupRenderEffect(instance, initialVNode, container);
457 | }
458 | function setupRenderEffect(instance, initialVNode, container) {
459 | effect(() => {
460 | if (!instance.isMounted) {
461 | console.log("init");
462 | const { proxy } = instance;
463 | const subTree = (instance.subTree = instance.render.call(proxy));
464 | patch(null, subTree, container, instance);
465 | initialVNode.el = subTree.el;
466 | instance.isMounted = true;
467 | }
468 | else {
469 | console.log("update");
470 | const { proxy } = instance;
471 | const subTree = instance.render.call(proxy);
472 | const prevSubTree = instance.subTree;
473 | instance.subTree = subTree;
474 | patch(prevSubTree, subTree, container, instance);
475 | }
476 | });
477 | }
478 | return {
479 | createApp: createAppAPI(render),
480 | };
481 | }
482 |
483 | function createElement(type) {
484 | return document.createElement(type);
485 | }
486 | function patchProp(el, key, prevVal, nextVal) {
487 | const isOn = (key) => /^on[A-Z]/.test(key);
488 | if (isOn(key)) {
489 | const event = key.slice(2).toLowerCase();
490 | el.addEventListener(event, nextVal);
491 | }
492 | else {
493 | if (nextVal === undefined || nextVal === null) {
494 | el.removeAttribute(key);
495 | }
496 | else {
497 | el.setAttribute(key, nextVal);
498 | }
499 | }
500 | }
501 | function insert(el, parent) {
502 | parent.append(el);
503 | }
504 | const renderer = createRenderer({
505 | createElement,
506 | patchProp,
507 | insert,
508 | });
509 | function createApp(...args) {
510 | return renderer.createApp(...args);
511 | }
512 |
513 | class RefTmpl {
514 | constructor(value) {
515 | this.__v_isRef = true;
516 | this._rawValue = value;
517 | this._value = convert(value);
518 | this.dep = new Set();
519 | }
520 | get value() {
521 | trackRefValue(this);
522 | return this._value;
523 | }
524 | set value(newValue) {
525 | if (hasChanged(newValue, this._rawValue)) {
526 | this._rawValue = newValue;
527 | this._value = convert(newValue);
528 | triggerEffects(this.dep);
529 | }
530 | }
531 | }
532 | function trackRefValue(ref) {
533 | if (isTracking()) {
534 | trackEffects(ref.dep);
535 | }
536 | }
537 | function convert(value) {
538 | return isObject(value) ? reactive(value) : value;
539 | }
540 | function ref(value) {
541 | return new RefTmpl(value);
542 | }
543 | function isRef(ref) {
544 | return !!ref.__v_isRef;
545 | }
546 | function unRef(ref) {
547 | return isRef(ref) ? ref.value : ref;
548 | }
549 | function proxyRefs(objectWithRefs) {
550 | return new Proxy(objectWithRefs, {
551 | get(target, key) {
552 | return unRef(Reflect.get(target, key));
553 | },
554 | set(target, key, value) {
555 | if (isRef(target[key]) && !isRef(value)) {
556 | return target[key].value = value;
557 | }
558 | else {
559 | return Reflect.set(target, key, value);
560 | }
561 | }
562 | });
563 | }
564 |
565 | export { createApp, createRenderer, createTextVNode, getCurrentInstance, h, inject, provide, proxyRefs, ref, renderSlots };
566 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | const Fragment = Symbol("Fragment");
6 | const Text = Symbol("Text");
7 | function createVNode(type, props, children) {
8 | const vnode = {
9 | type,
10 | props,
11 | children,
12 | 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 | const slot = slots[name];
43 | if (slot) {
44 | if (typeof slot === "function") {
45 | return createVNode(Fragment, {}, slot(props));
46 | }
47 | }
48 | }
49 |
50 | const extend = Object.assign;
51 | const isObject = (value) => {
52 | return value !== null && typeof value === "object";
53 | };
54 | const hasChanged = (val, newValue) => {
55 | return !Object.is(val, newValue);
56 | };
57 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
58 | const camelize = (str) => {
59 | return str.replace(/-(\w)/g, (_, c) => {
60 | return c ? c.toUpperCase() : "";
61 | });
62 | };
63 | const capitalize = (str) => {
64 | return str.charAt(0).toUpperCase() + str.slice(1);
65 | };
66 | const toHandlerKey = (str) => {
67 | return str ? "on" + capitalize(str) : "";
68 | };
69 |
70 | let activeEffect;
71 | let shouldTrack;
72 | class ReactiveEffect {
73 | constructor(fn, scheduler) {
74 | this.scheduler = scheduler;
75 | this.deps = [];
76 | this.active = true;
77 | this._fn = fn;
78 | }
79 | run() {
80 | if (!this.active) {
81 | this._fn();
82 | }
83 | shouldTrack = true;
84 | activeEffect = this;
85 | const result = this._fn();
86 | shouldTrack = false;
87 | return result;
88 | }
89 | stop() {
90 | if (this.active) {
91 | cleanupEffect(this);
92 | if (this.onStop) {
93 | this.onStop();
94 | }
95 | }
96 | this.active = false;
97 | }
98 | }
99 | function cleanupEffect(effect) {
100 | effect.deps.forEach((dep) => {
101 | dep.delete(effect);
102 | });
103 | effect.deps.length = 0;
104 | }
105 | const targetMap = new Map();
106 | function track(target, key) {
107 | if (!isTracking())
108 | return;
109 | // target -> key -> dep
110 | let depsMap = targetMap.get(target);
111 | if (!depsMap) {
112 | depsMap = new Map();
113 | targetMap.set(target, depsMap);
114 | }
115 | let dep = depsMap.get(key);
116 | if (!dep) {
117 | dep = new Set();
118 | depsMap.set(key, dep);
119 | }
120 | trackEffects(dep);
121 | }
122 | function trackEffects(dep) {
123 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
124 | if (dep.has(activeEffect))
125 | return;
126 | dep.add(activeEffect);
127 | activeEffect.deps.push(dep);
128 | }
129 | function isTracking() {
130 | return shouldTrack && activeEffect !== undefined;
131 | }
132 | function trigger(target, key) {
133 | let depsMap = targetMap.get(target);
134 | let dep = depsMap.get(key);
135 | triggerEffects(dep);
136 | }
137 | function triggerEffects(dep) {
138 | for (const effect of dep) {
139 | if (effect.scheduler) {
140 | effect.scheduler();
141 | }
142 | else {
143 | effect.run();
144 | }
145 | }
146 | }
147 | function effect(fn, options = {}) {
148 | // fn
149 | const _effect = new ReactiveEffect(fn, options.scheduler);
150 | extend(_effect, options);
151 | _effect.run();
152 | const runner = _effect.run.bind(_effect);
153 | runner.effect = _effect;
154 | return runner;
155 | }
156 |
157 | const get = createGetter();
158 | const set = createSetter();
159 | const readonlyGet = createGetter(true);
160 | const shallowReadonlyGet = createGetter(true, true);
161 | function createGetter(isReadonly = false, shallow = false) {
162 | return function get(target, key) {
163 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
164 | return !isReadonly;
165 | }
166 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
167 | return isReadonly;
168 | }
169 | const res = Reflect.get(target, key);
170 | if (shallow) {
171 | return res;
172 | }
173 | if (isObject(res)) {
174 | return isReadonly ? readonly(res) : reactive(res);
175 | }
176 | if (!isReadonly) {
177 | // 依赖收集
178 | track(target, key);
179 | }
180 | return res;
181 | };
182 | }
183 | function createSetter() {
184 | return function set(target, key, value) {
185 | const res = Reflect.set(target, key, value);
186 | trigger(target, key);
187 | return res;
188 | };
189 | }
190 | const mutableHandlers = {
191 | get,
192 | set
193 | };
194 | const readonlyHandlers = {
195 | get: readonlyGet,
196 | set(target, key) {
197 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target);
198 | return true;
199 | },
200 | };
201 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
202 | get: shallowReadonlyGet,
203 | });
204 |
205 | function reactive(raw) {
206 | return createReactiveObject(raw, mutableHandlers);
207 | }
208 | function readonly(raw) {
209 | return createReactiveObject(raw, readonlyHandlers);
210 | }
211 | function shallowReadonly(raw) {
212 | return createReactiveObject(raw, shallowReadonlyHandlers);
213 | }
214 | function createReactiveObject(target, baseHandles) {
215 | if (!isObject(target)) {
216 | console.warn(`target ${target} 必须是一个对象`);
217 | return target;
218 | }
219 | return new Proxy(target, baseHandles);
220 | }
221 |
222 | function emit(instance, event, ...args) {
223 | const { props } = instance;
224 | const handlerName = toHandlerKey(camelize(event));
225 | const handler = props[handlerName];
226 | handler && handler(...args);
227 | }
228 |
229 | function initProps(instance, rawProps) {
230 | instance.props = rawProps || {};
231 | }
232 |
233 | const publicPropertiesMap = {
234 | $el: (i) => i.vnode.el,
235 | $slots: (i) => i.slots,
236 | };
237 | const PublicInstanceProxyHandlers = {
238 | get({ _: instance }, key) {
239 | // setupState
240 | const { setupState, props } = instance;
241 | if (hasOwn(setupState, key)) {
242 | return setupState[key];
243 | }
244 | else if (hasOwn(props, key)) {
245 | return props[key];
246 | }
247 | const publicGetter = publicPropertiesMap[key];
248 | if (publicGetter) {
249 | return publicGetter(instance);
250 | }
251 | },
252 | };
253 |
254 | function initSlots(instance, children) {
255 | // slots
256 | const { vnode } = instance;
257 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
258 | normalizeObjectSlots(children, instance.slots);
259 | }
260 | }
261 | function normalizeObjectSlots(children, slots) {
262 | for (const key in children) {
263 | const value = children[key];
264 | slots[key] = (props) => normalizeSlotValue(value(props));
265 | }
266 | }
267 | function normalizeSlotValue(value) {
268 | return Array.isArray(value) ? value : [value];
269 | }
270 |
271 | function createComponentInstance(vnode, parent) {
272 | const component = {
273 | vnode,
274 | type: vnode.type,
275 | setupState: {},
276 | props: {},
277 | slots: {},
278 | provides: parent ? parent.provides : {},
279 | parent,
280 | isMounted: false,
281 | subTree: {},
282 | emit: () => { }
283 | };
284 | component.emit = emit.bind(null, component);
285 | return component;
286 | }
287 | function setupComponent(instance) {
288 | // TODO
289 | initProps(instance, instance.vnode.props);
290 | initSlots(instance, instance.vnode.children);
291 | setupStatefulComponent(instance);
292 | }
293 | function setupStatefulComponent(instance) {
294 | const Component = instance.type;
295 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
296 | const { setup } = Component;
297 | if (setup) {
298 | setCurrentInstance(instance);
299 | const setupResult = setup(shallowReadonly(instance.props), {
300 | emit: instance.emit,
301 | });
302 | setCurrentInstance(null);
303 | handleSetupResult(instance, setupResult);
304 | }
305 | }
306 | function handleSetupResult(instance, setupResult) {
307 | // function Object
308 | // TODO function
309 | if (typeof setupResult === "object") {
310 | instance.setupState = proxyRefs(setupResult);
311 | }
312 | finishComponentSetup(instance);
313 | }
314 | function finishComponentSetup(instance) {
315 | const Component = instance.type;
316 | instance.render = Component.render;
317 | }
318 | let currentInstance = null;
319 | function getCurrentInstance() {
320 | return currentInstance;
321 | }
322 | function setCurrentInstance(instance) {
323 | currentInstance = instance;
324 | }
325 |
326 | function provide(key, value) {
327 | const currentInstance = getCurrentInstance();
328 | if (currentInstance) {
329 | let { provides } = currentInstance;
330 | const parentProvides = currentInstance.parent.provides;
331 | if (provides === parentProvides) {
332 | provides = currentInstance.provides = Object.create(parentProvides);
333 | }
334 | provides[key] = value;
335 | }
336 | }
337 | function inject(key, defaultValue) {
338 | const currentInstance = getCurrentInstance();
339 | if (currentInstance) {
340 | const parentProvides = currentInstance.parent.provides;
341 | if (key in parentProvides) {
342 | return parentProvides[key];
343 | }
344 | else if (defaultValue) {
345 | if (typeof defaultValue === "function") {
346 | return defaultValue();
347 | }
348 | return defaultValue;
349 | }
350 | }
351 | }
352 |
353 | function createAppAPI(render) {
354 | return function createApp(rootComponent) {
355 | return {
356 | mount(rootContainer) {
357 | const vnode = createVNode(rootComponent);
358 | render(vnode, rootContainer);
359 | },
360 | };
361 | };
362 | }
363 |
364 | function createRenderer(options) {
365 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, } = options;
366 | function render(vnode, container) {
367 | patch(null, vnode, container, null);
368 | }
369 | function patch(n1, n2, container, parentComponent) {
370 | const { type, shapeFlag } = n2;
371 | switch (type) {
372 | case Fragment:
373 | processFragment(n1, n2, container, parentComponent);
374 | break;
375 | case Text:
376 | processText(n1, n2, container);
377 | break;
378 | default:
379 | if (shapeFlag & 1 /* ELEMENT */) {
380 | processElement(n1, n2, container, parentComponent);
381 | }
382 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
383 | processComponent(n1, n2, container, parentComponent);
384 | }
385 | break;
386 | }
387 | }
388 | function processText(n1, n2, container) {
389 | const { children } = n2;
390 | const textNode = (n2.el = document.createTextNode(children));
391 | container.append(textNode);
392 | }
393 | function processFragment(n1, n2, container, parentComponent) {
394 | mountChildren(n2, container, parentComponent);
395 | }
396 | function processElement(n1, n2, container, parentComponent) {
397 | if (!n1) {
398 | mountElement(n2, container, parentComponent);
399 | }
400 | else {
401 | patchElement(n1, n2);
402 | }
403 | }
404 | function patchElement(n1, n2, container) {
405 | console.log("patchElement");
406 | console.log("n1", n1);
407 | console.log("n2", n2);
408 | const oldProps = n1.props || {};
409 | const newProps = n2.props || {};
410 | const el = (n2.el = n1.el);
411 | patchProps(el, oldProps, newProps);
412 | }
413 | function patchProps(el, oldProps, newProps) {
414 | if (oldProps !== newProps) {
415 | for (const key in newProps) {
416 | const prevProp = oldProps[key];
417 | const nextProp = newProps[key];
418 | if (prevProp !== nextProp) {
419 | hostPatchProp(el, key, prevProp, nextProp);
420 | }
421 | }
422 | if (oldProps !== {}) {
423 | for (const key in oldProps) {
424 | if (!(key in newProps)) {
425 | hostPatchProp(el, key, oldProps[key], null);
426 | }
427 | }
428 | }
429 | }
430 | }
431 | function mountElement(vnode, container, parentComponent) {
432 | const el = (vnode.el = hostCreateElement(vnode.type));
433 | const { children, shapeFlag } = vnode;
434 | // children
435 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
436 | el.textContent = children;
437 | }
438 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
439 | mountChildren(vnode, el, parentComponent);
440 | }
441 | // props
442 | const { props } = vnode;
443 | for (const key in props) {
444 | const val = props[key];
445 | hostPatchProp(el, key, null, val);
446 | }
447 | hostInsert(el, container);
448 | }
449 | function mountChildren(vnode, container, parentComponent) {
450 | vnode.children.forEach((v) => {
451 | patch(null, v, container, parentComponent);
452 | });
453 | }
454 | function processComponent(n1, n2, container, parentComponent) {
455 | mountComponent(n2, container, parentComponent);
456 | }
457 | function mountComponent(initialVNode, container, parentComponent) {
458 | const instance = createComponentInstance(initialVNode, parentComponent);
459 | setupComponent(instance);
460 | setupRenderEffect(instance, initialVNode, container);
461 | }
462 | function setupRenderEffect(instance, initialVNode, container) {
463 | effect(() => {
464 | if (!instance.isMounted) {
465 | console.log("init");
466 | const { proxy } = instance;
467 | const subTree = (instance.subTree = instance.render.call(proxy));
468 | patch(null, subTree, container, instance);
469 | initialVNode.el = subTree.el;
470 | instance.isMounted = true;
471 | }
472 | else {
473 | console.log("update");
474 | const { proxy } = instance;
475 | const subTree = instance.render.call(proxy);
476 | const prevSubTree = instance.subTree;
477 | instance.subTree = subTree;
478 | patch(prevSubTree, subTree, container, instance);
479 | }
480 | });
481 | }
482 | return {
483 | createApp: createAppAPI(render),
484 | };
485 | }
486 |
487 | function createElement(type) {
488 | return document.createElement(type);
489 | }
490 | function patchProp(el, key, prevVal, nextVal) {
491 | const isOn = (key) => /^on[A-Z]/.test(key);
492 | if (isOn(key)) {
493 | const event = key.slice(2).toLowerCase();
494 | el.addEventListener(event, nextVal);
495 | }
496 | else {
497 | if (nextVal === undefined || nextVal === null) {
498 | el.removeAttribute(key);
499 | }
500 | else {
501 | el.setAttribute(key, nextVal);
502 | }
503 | }
504 | }
505 | function insert(el, parent) {
506 | parent.append(el);
507 | }
508 | const renderer = createRenderer({
509 | createElement,
510 | patchProp,
511 | insert,
512 | });
513 | function createApp(...args) {
514 | return renderer.createApp(...args);
515 | }
516 |
517 | class RefTmpl {
518 | constructor(value) {
519 | this.__v_isRef = true;
520 | this._rawValue = value;
521 | this._value = convert(value);
522 | this.dep = new Set();
523 | }
524 | get value() {
525 | trackRefValue(this);
526 | return this._value;
527 | }
528 | set value(newValue) {
529 | if (hasChanged(newValue, this._rawValue)) {
530 | this._rawValue = newValue;
531 | this._value = convert(newValue);
532 | triggerEffects(this.dep);
533 | }
534 | }
535 | }
536 | function trackRefValue(ref) {
537 | if (isTracking()) {
538 | trackEffects(ref.dep);
539 | }
540 | }
541 | function convert(value) {
542 | return isObject(value) ? reactive(value) : value;
543 | }
544 | function ref(value) {
545 | return new RefTmpl(value);
546 | }
547 | function isRef(ref) {
548 | return !!ref.__v_isRef;
549 | }
550 | function unRef(ref) {
551 | return isRef(ref) ? ref.value : ref;
552 | }
553 | function proxyRefs(objectWithRefs) {
554 | return new Proxy(objectWithRefs, {
555 | get(target, key) {
556 | return unRef(Reflect.get(target, key));
557 | },
558 | set(target, key, value) {
559 | if (isRef(target[key]) && !isRef(value)) {
560 | return target[key].value = value;
561 | }
562 | else {
563 | return Reflect.set(target, key, value);
564 | }
565 | }
566 | });
567 | }
568 |
569 | exports.createApp = createApp;
570 | exports.createRenderer = createRenderer;
571 | exports.createTextVNode = createTextVNode;
572 | exports.getCurrentInstance = getCurrentInstance;
573 | exports.h = h;
574 | exports.inject = inject;
575 | exports.provide = provide;
576 | exports.proxyRefs = proxyRefs;
577 | exports.ref = ref;
578 | exports.renderSlots = renderSlots;
579 |
--------------------------------------------------------------------------------