Skip to content

Commit

Permalink
[Feat] update template page & add config pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
mistricky committed Aug 19, 2024
1 parent 883e6e0 commit fbf6af0
Show file tree
Hide file tree
Showing 63 changed files with 1,404 additions and 41 deletions.
6 changes: 3 additions & 3 deletions examples/counter/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { Component, Default, State } from "@thewu/core";

@Component()
class Counter {
// @Default({ prop: "initCount" })
@State
@Default({ prop: "initCount" })
count!: number;
count: number = 1;

render() {
return (
<div>
<p>{this.count}</p>
<p>{`Hello ${this.count}`}</p>
<button onClick={() => this.count++}>+1</button>
</div>
);
Expand Down
Empty file added examples/fetch/main.tsx
Empty file.
8 changes: 8 additions & 0 deletions examples/fetch/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "@thewu/config",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
},
"includes": ["src/**/*"]
}
7 changes: 5 additions & 2 deletions examples/todo-list/src/add-task.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class AddTask {
}

this.onTaskAdd({ name: this.content, isDone: false });
this.content = "";
// this.content = "";
}

render() {
Expand All @@ -30,7 +30,10 @@ export class AddTask {
class="input input-bordered input-md w-full max-w-xs mr-2"
/>
<button
onClick={() => this.handleAddTaskButtonClick()}
onClick={() => {
console.info("aaaaa");
this.handleAddTaskButtonClick();
}}
class="btn btn-primary text-white"
>
Add
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"main": "index.js",
"scripts": {
"build": "pnpm --filter './packages/**' build",
"dev": "pnpm --filter './packages/**' -r --parallel dev",
"publish:packages": "pnpm --filter './packages/**' publish --access public"
},
"keywords": [],
Expand All @@ -15,4 +16,4 @@
"@types/jest": "^29.5.12",
"ts-jest": "^29.1.5"
}
}
}
1 change: 1 addition & 0 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./dom";
export * from "./renderer";
8 changes: 6 additions & 2 deletions packages/browser/src/renderer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from "./destroy";

export class BrowserRenderer implements Renderer {
removeNode({ componentType, el, value }: ParsedWuNode): void {
console.info("[Action] remove node", el, value);
if (componentType === ComponentType.CLASS_COMPONENT) {
const instance = value as Component;

Expand All @@ -29,19 +30,22 @@ export class BrowserRenderer implements Renderer {
container: HTMLElement,
position?: number | undefined,
): void {
console.info(renderToDOM(node, this), container, "nnnnnn");
console.info("[Action] insert node", node);

insert(renderToDOM(node, this) as any, container, position);
}

updateTextNodeContent(oldVDom: ParsedWuNode, newVDom: ParsedWuNode): void {
console.info("[Action] remove node", oldVDom, newVDom);

if (oldVDom.value !== newVDom.value) {
oldVDom.el!.textContent = (newVDom.value as string | undefined) ?? null;
}
}

replaceWith(oldVDom: ParsedWuNode, newVDom: ParsedWuNode): void {
console.info("wwwwwwww");
console.info("[Action] remove node", oldVDom, newVDom);

oldVDom.el!.replaceWith(newVDom.el!);
}

Expand Down
20 changes: 20 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@thewu/core",
"version": "0.0.7",
"description": "",
"main": "./dist/index.js",
"scripts": {
"dev": "tsc -w",
"build": "tsc"
},
"keywords": [],
"author": "",
"files": [
"dist"
],
"license": "ISC",
"dependencies": {
"reflect-metadata": "^0.2.2",
"@thewu/wux": "workspace:*"
}
}
111 changes: 111 additions & 0 deletions packages/core/src/component/decorators/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import "reflect-metadata";
import { METADATA_PROP_KEY, setProp, setProps } from "./prop";
import { METADATA_STATE_KEY } from "./state";
import { assignDefault } from "./default";
import { WuNode, WuNodeProps } from "../../jsx";
import { ParsedWuNode, initializeNode } from "../../initialize";
import { patch } from "../../reconciliation";
import { Renderer } from "../../renderer";
import { stream, subscribe } from "@thewu/wux";

export interface Component {
render(): WuNode;
initVdom(vdom: ParsedWuNode): void;
createInstance(...args: any[]): Component;
updateProps(props: WuNodeProps): void;
setRenderer(renderer: Renderer): void;
[key: string]: any;
}

type Props = Record<string, unknown>;

interface ComponentParams {
props: Props;
}

interface ComponentParams {
props: Props;
}

export const Component =
() =>
<T extends { new (...args: any[]): any }>(Constructor: T) =>
class Component extends Constructor {
props: Props = {};
vdom: ParsedWuNode | undefined;
renderer: Renderer | undefined;
proxyInstance: any;

static $instance: Component;

static createInstance(params: ComponentParams) {
const { props } = params;
const instance =
Component.$instance ?? (Component.$instance = new Component(params));

assignDefault(instance, props);
setProps(instance, props);
setProp(instance, props);

return instance;
}

constructor(...args: any[]) {
super(...args);

// When states was changed, trigger patch method
(Reflect.getMetadata(METADATA_STATE_KEY, this) ?? []).forEach(
(stateName: string) => {
stream(
() => {
return JSON.stringify(this[stateName]);
},
() => {
this.patch();
},
);
},
);
}

initVdom(vdom: ParsedWuNode) {
if (!this.vdom) {
this.vdom = vdom;
}
}

// After first mounted, the renderer will be set to the component
setRenderer(renderer: Renderer) {
this.renderer = renderer;
}

render() {
return super.render.bind(this)();
}

// When the state was changed, the patch action will invoke automatically
patch() {
if (!this.vdom) {
throw new Error("Cannot call patch function before render call");
}

if (!this.renderer) {
throw new Error("Component mount failed");
}

console.info("Patching...");

// Move to next tick of event loop to make sure the state always be the latest
Promise.resolve().then(() => {
patch(
this.vdom!,
(this.vdom = initializeNode(
this.render(),
this.vdom!.parentEl,
this.vdom?.parentNode,
)),
this.renderer!,
);
});
}
};
25 changes: 25 additions & 0 deletions packages/core/src/component/decorators/default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineMetadatas } from "../../utils";

interface DefaultParams {
prop?: string;
}

const DEFAULT_FROM_PROP_METADATA_KEY = Symbol("DEFAULT_FROM_PROP_METADATA_KEY");

export const Default =
({ prop }: DefaultParams) =>
(target: any, propertyKey: string) => {
defineMetadatas(DEFAULT_FROM_PROP_METADATA_KEY, target, {
prop,
propertyKey,
});
};

export const assignDefault = (target: any, props: Record<string, unknown>) => {
const propDefaultMetadata =
Reflect.getMetadata(DEFAULT_FROM_PROP_METADATA_KEY, target) ?? [];

for (const { prop: propName, propertyKey } of propDefaultMetadata) {
target[propertyKey] = props[propName];
}
};
6 changes: 6 additions & 0 deletions packages/core/src/component/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./component";
export * from "./state";
export * from "./prop";
export * from "./default";
export * as LifeCycle from "./life-cycle";
export * from "./life-cycle";
24 changes: 24 additions & 0 deletions packages/core/src/component/decorators/life-cycle/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);

type LifeCycleHook<N extends string> = {
[K in `on${Capitalize<N>}`]: (target: any, propertyKey: string) => void;
} & {
[K in `get${Capitalize<N>}LifeCycleHookName`]: (target: any) => string;
};

export const createLifeCycleHook = <N extends string>(
name: N,
metadataKey: symbol,
): LifeCycleHook<N> => {
const capitalizedHookName = capitalize(name);
const decoratorName = `on${capitalizedHookName}`;
const getLifeCycleHookNameName = `get${capitalizedHookName}LifeCycleHookName`;

return {
[decoratorName]: (target: any, propertyKey: string) => {
Reflect.defineMetadata(metadataKey, propertyKey, target);
},
[getLifeCycleHookNameName]: (target: any) =>
Reflect.getMetadata(metadataKey, target),
} as LifeCycleHook<N>;
};
2 changes: 2 additions & 0 deletions packages/core/src/component/decorators/life-cycle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./on-mounted";
export * from "./on-destroy";
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createLifeCycleHook } from "./common";

export const { onDestroy, getDestroyLifeCycleHookName } = createLifeCycleHook(
"destroy",
Symbol("DESTROY_METADATA_KEY"),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createLifeCycleHook } from "./common";

export const { onMounted, getMountedLifeCycleHookName } = createLifeCycleHook(
"mounted",
Symbol("MOUNTED_METADATA_KEY"),
);
35 changes: 35 additions & 0 deletions packages/core/src/component/decorators/prop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import "reflect-metadata";
import { defineMetadatas } from "../../utils";

export const METADATA_PROP_KEY = Symbol("METADATA_PROP_KEY");

// The Prop decorator map the prop value to the property of instance
export const Prop =
(propName?: string) => (target: any, propertyKey: string) => {
defineMetadatas(METADATA_PROP_KEY, target, {
propertyKey,
propName: propName ?? propertyKey,
});
};

export const setProp = (target: any, props: Record<string, unknown>) => {
const propMetadatas = Reflect.getMetadata(METADATA_PROP_KEY, target) ?? [];

for (const { propertyKey, propName } of propMetadatas) {
target[propertyKey] = props[propName];
}
};

export const METADATA_PROPS_KEY = Symbol("METADATA_PROP_KEY");

export const Props = (target: any, propertyKey: string) => {
defineMetadatas(METADATA_PROPS_KEY, target, propertyKey);
};

export const setProps = (target: any, props: Record<string, unknown>) => {
const propsKeys = Reflect.getMetadata(METADATA_PROPS_KEY, target) ?? [];

for (const propertyKey of propsKeys) {
target[propertyKey] = { ...props };
}
};
30 changes: 30 additions & 0 deletions packages/core/src/component/decorators/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "reflect-metadata";
import { createReactiveFactory } from "@thewu/wux";
import { defineMetadatas, isObject } from "../../utils";

export const METADATA_STATE_KEY = Symbol("METADATA_STATE_KEY");
export const METADATA_STATE_VALUE = Symbol("METADATA_STATE_VALUE");

export const State = (target: any, propertyKey: string): any => {
const reactive = createReactiveFactory();
const reactiveProperty = reactive(
{ value: target[propertyKey] },
{ recursion: true },
);

defineMetadatas(METADATA_STATE_KEY, target, propertyKey);

return {
get: () => reactiveProperty.value,
set: (value: unknown) => {
if (isObject(value) || Array.isArray(value)) {
reactiveProperty.value = reactive(value, {
recursion: true,
});
return true;
}

reactiveProperty.value = value;
},
};
};
Empty file.
1 change: 1 addition & 0 deletions packages/core/src/component/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./decorators";
6 changes: 6 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./renderer";
export * from "./jsx";
export * from "./utils";
export * from "./initialize";
export * from "./reconciliation";
export * from "./component";
Loading

0 comments on commit fbf6af0

Please sign in to comment.