revojs 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/index.d.ts +32 -0
- package/dist/event/index.d.ts +19 -0
- package/dist/html/index.d.ts +75 -0
- package/dist/http/index.d.ts +5 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +502 -0
- package/dist/jsx/index.d.ts +8 -0
- package/dist/jsx/index.js +16 -0
- package/dist/markdown/index.d.ts +2 -0
- package/dist/presets/bun.d.ts +1 -0
- package/dist/presets/bun.js +7 -0
- package/dist/presets/cloudflare.d.ts +4 -0
- package/dist/presets/cloudflare.js +7 -0
- package/dist/presets/deno.d.ts +1 -0
- package/dist/presets/deno.js +5 -0
- package/dist/presets/node.d.ts +1 -0
- package/dist/presets/node.js +5 -0
- package/dist/radix/index.d.ts +12 -0
- package/dist/router/index.d.ts +4 -0
- package/dist/runtime/index.d.ts +17 -0
- package/dist/signals/index.d.ts +20 -0
- package/package.json +37 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Template } from "../html";
|
|
2
|
+
export type NestedPartial<T> = Partial<{
|
|
3
|
+
[K in keyof T]: T[K] extends object ? NestedPartial<T[K]> : T[K];
|
|
4
|
+
}>;
|
|
5
|
+
export type AssetConfig = {
|
|
6
|
+
path: string;
|
|
7
|
+
suffix?: string;
|
|
8
|
+
import?: string;
|
|
9
|
+
};
|
|
10
|
+
export type RouteConfig = {
|
|
11
|
+
path: string;
|
|
12
|
+
};
|
|
13
|
+
export type ServerEntry = "revojs/presets/node" | "revojs/presets/deno" | "revojs/presets/bun" | "revojs/presets/cloudflare" | (string & {});
|
|
14
|
+
export type ClientConfig = {
|
|
15
|
+
entry: string;
|
|
16
|
+
};
|
|
17
|
+
export type ServerConfig = {
|
|
18
|
+
entry: ServerEntry;
|
|
19
|
+
};
|
|
20
|
+
export type Config = {
|
|
21
|
+
assets: Array<AssetConfig>;
|
|
22
|
+
routes: Array<RouteConfig>;
|
|
23
|
+
client: ClientConfig;
|
|
24
|
+
server: ServerConfig;
|
|
25
|
+
markdown: Record<string, Template>;
|
|
26
|
+
};
|
|
27
|
+
export type App = {
|
|
28
|
+
config: Config;
|
|
29
|
+
virtuals: Record<string, () => string>;
|
|
30
|
+
};
|
|
31
|
+
export declare const getAssets: <T>(regex: RegExp) => Promise<Record<string, () => Promise<T>>>;
|
|
32
|
+
export declare const createApp: (config?: NestedPartial<Config>) => App;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StatusCode } from "../http";
|
|
2
|
+
export type Context = {
|
|
3
|
+
inputs: Record<string, string>;
|
|
4
|
+
};
|
|
5
|
+
export type Set = {
|
|
6
|
+
status?: StatusCode;
|
|
7
|
+
message?: string;
|
|
8
|
+
headers: Headers;
|
|
9
|
+
};
|
|
10
|
+
export type Event<T = Context> = {
|
|
11
|
+
request: Request;
|
|
12
|
+
response: Set;
|
|
13
|
+
context: T;
|
|
14
|
+
};
|
|
15
|
+
export declare const createEvent: <T = Context>(request: Request, context: T) => Event<T>;
|
|
16
|
+
export declare const sendText: <T = Context>(event: Event<T>, text?: string) => Response;
|
|
17
|
+
export declare const sendHtml: <T = Context>(event: Event<T>, text: string) => Response;
|
|
18
|
+
export declare const sendJson: <TValue, T = Context>(event: Event<T>, value: TValue) => Response;
|
|
19
|
+
export declare const setHeader: <T>(event: Event<T>, name: string, value: string) => void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type State } from "../signals";
|
|
2
|
+
export type TypeOf<T> = {
|
|
3
|
+
(): T;
|
|
4
|
+
};
|
|
5
|
+
export type Infer<T> = T extends TypeOf<infer U> ? U : unknown;
|
|
6
|
+
export type Slot = void | string | number | boolean | undefined | object | Function | Template;
|
|
7
|
+
export type Template = {
|
|
8
|
+
tag: string;
|
|
9
|
+
attributes: Record<string, Slot>;
|
|
10
|
+
children: Array<Slot>;
|
|
11
|
+
};
|
|
12
|
+
export type EventInput<T extends Events> = {
|
|
13
|
+
[K in keyof T]?: (event: Infer<T[K]["type"]>) => void;
|
|
14
|
+
};
|
|
15
|
+
export type AttributeInput<T extends Attributes> = {
|
|
16
|
+
[K in keyof T]?: Infer<T[K]["type"]>;
|
|
17
|
+
};
|
|
18
|
+
export type Input<TEvents extends Events, TAttributes extends Attributes> = EventInput<TEvents> & AttributeInput<TAttributes>;
|
|
19
|
+
export type EventOutput<T extends Events> = {
|
|
20
|
+
[K in keyof T]?: (event: Infer<T[K]["type"]>) => void;
|
|
21
|
+
};
|
|
22
|
+
export type AttributeOutput<T extends Attributes> = {
|
|
23
|
+
[K in keyof T]: T[K]["default"] extends undefined ? Infer<T[K]["type"]> | undefined : Infer<T[K]["type"]>;
|
|
24
|
+
};
|
|
25
|
+
export type EventOptions<T> = {
|
|
26
|
+
type: T;
|
|
27
|
+
bubbles?: boolean;
|
|
28
|
+
cancelable?: boolean;
|
|
29
|
+
composed?: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type AttributeOptions<T> = {
|
|
32
|
+
type: T;
|
|
33
|
+
default?: Infer<T>;
|
|
34
|
+
};
|
|
35
|
+
export type Events = Record<string, EventOptions<Slot>>;
|
|
36
|
+
export type Attributes = Record<string, AttributeOptions<Slot>>;
|
|
37
|
+
export interface ComponentOptions<TEvents extends Events, TAttributes extends Attributes> {
|
|
38
|
+
name: string;
|
|
39
|
+
events?: TEvents;
|
|
40
|
+
attributes?: TAttributes;
|
|
41
|
+
shadowRoot?: false | ShadowRootInit;
|
|
42
|
+
setup: (component: Component<TEvents, TAttributes>) => Slot | Promise<Slot>;
|
|
43
|
+
}
|
|
44
|
+
export interface Component<TEvents extends Events, TAttributes extends Attributes> {
|
|
45
|
+
readonly events: EventOutput<TEvents>;
|
|
46
|
+
readonly attributes: State<AttributeOutput<TAttributes>>;
|
|
47
|
+
readonly shadowRoot: false | ShadowRootInit;
|
|
48
|
+
setup: () => Slot | Promise<Slot>;
|
|
49
|
+
}
|
|
50
|
+
export interface ComponentConstructor<TEvents extends Events, TAttributes extends Attributes> {
|
|
51
|
+
$name: string;
|
|
52
|
+
$events: TEvents;
|
|
53
|
+
$attributes: TAttributes;
|
|
54
|
+
new (input?: Input<TEvents, TAttributes>): Component<TEvents, TAttributes>;
|
|
55
|
+
}
|
|
56
|
+
export interface CustomElement<TEvents extends Events, TAttributes extends Attributes> extends HTMLElement {
|
|
57
|
+
readonly component: Component<TEvents, TAttributes>;
|
|
58
|
+
}
|
|
59
|
+
export interface CustomElementConstructor<TEvents extends Events, TAttributes extends Attributes> {
|
|
60
|
+
new (): CustomElement<TEvents, TAttributes>;
|
|
61
|
+
}
|
|
62
|
+
export declare const createElement: <TEvents extends Events, TAttributes extends Attributes>(input: string | ComponentConstructor<TEvents, TAttributes>, attributes?: AttributeInput<TAttributes>, ...children: Array<Slot>) => Slot;
|
|
63
|
+
export declare const toString: (slot: Slot) => string;
|
|
64
|
+
export declare const slotToString: (slot: Slot) => Promise<string>;
|
|
65
|
+
export declare const slotToNode: (slot: Slot) => Promise<Node>;
|
|
66
|
+
export declare const defineComponent: <TEvents extends Events = {}, TAttributes extends Attributes = {}>(options: ComponentOptions<TEvents, TAttributes>) => ComponentConstructor<TEvents, TAttributes>;
|
|
67
|
+
export declare const toCustomElement: <TEvents extends Events = {}, TAttributes extends Attributes = {}>(component: ComponentConstructor<TEvents, TAttributes>) => CustomElementConstructor<TEvents, TAttributes>;
|
|
68
|
+
export declare const registerComponent: <TEvents extends Events = {}, TAttributes extends Attributes = {}>(component: ComponentConstructor<TEvents, TAttributes>) => ComponentConstructor<TEvents, TAttributes>;
|
|
69
|
+
export declare const isClient: () => boolean;
|
|
70
|
+
export declare const isServer: () => boolean;
|
|
71
|
+
export declare const addStyles: (...styles: Array<CSSStyleSheet>) => void;
|
|
72
|
+
export declare const getGlobalStyles: () => CSSStyleSheet[];
|
|
73
|
+
export declare let globalStyles: Array<CSSStyleSheet>;
|
|
74
|
+
export declare let activeElement: CustomElement<Events, Attributes> | undefined;
|
|
75
|
+
export declare const components: Map<string, ComponentConstructor<Events, Attributes>>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type HttpMethod = "GET" | "HEAD" | "PATCH" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE";
|
|
2
|
+
export type Encoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex";
|
|
3
|
+
export type StatusCode = 100 | 101 | 102 | 103 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 506 | 507 | 508 | 509 | 510 | 511 | 521 | 522 | 523 | 525 | 530 | 599;
|
|
4
|
+
export type MimeType = "text/css" | "text/javascript" | "text/plain";
|
|
5
|
+
export declare const getMimeType: (file: string) => MimeType;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import defu, { defu as defu$1 } from "defu";
|
|
2
|
+
import { h } from "revojs/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/app/index.ts
|
|
5
|
+
const getAssets = async (regex) => {
|
|
6
|
+
const assets = await import("#virtual/assets").then((module) => module.assets);
|
|
7
|
+
return Object.entries(assets).reduce((assets$1, [name, value]) => {
|
|
8
|
+
if (regex.test(name)) assets$1[name] = value;
|
|
9
|
+
return assets$1;
|
|
10
|
+
}, {});
|
|
11
|
+
};
|
|
12
|
+
const createApp = (config) => {
|
|
13
|
+
return {
|
|
14
|
+
config: defu$1(config, {
|
|
15
|
+
assets: [{ path: "./assets" }],
|
|
16
|
+
routes: [{ path: "./routes" }],
|
|
17
|
+
client: { entry: "./index.html" },
|
|
18
|
+
server: { entry: "revojs/presets/node" },
|
|
19
|
+
markdown: {}
|
|
20
|
+
}),
|
|
21
|
+
virtuals: {}
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/event/index.ts
|
|
27
|
+
const createEvent = (request, context) => {
|
|
28
|
+
return {
|
|
29
|
+
request,
|
|
30
|
+
response: { headers: new Headers() },
|
|
31
|
+
context
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
const sendText = (event, text) => {
|
|
35
|
+
return new Response(text, event.response);
|
|
36
|
+
};
|
|
37
|
+
const sendHtml = (event, text) => {
|
|
38
|
+
setHeader(event, "Content-Type", "text/html");
|
|
39
|
+
return new Response(text, event.response);
|
|
40
|
+
};
|
|
41
|
+
const sendJson = (event, value) => {
|
|
42
|
+
return new Response(JSON.stringify(value), event.response);
|
|
43
|
+
};
|
|
44
|
+
const setHeader = (event, name, value) => {
|
|
45
|
+
event.response.headers.set(name, value);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/signals/index.ts
|
|
50
|
+
var Handler = class Handler {
|
|
51
|
+
get = (target, path) => {
|
|
52
|
+
if (runningCompute) {
|
|
53
|
+
const effects = targets.get(target) ?? new Map();
|
|
54
|
+
const set = effects.get(path) ?? new Set();
|
|
55
|
+
effects.set(path, set.add(runningCompute));
|
|
56
|
+
targets.set(target, effects);
|
|
57
|
+
runningCompute.previous?.cleanUps.push((compute) => set.delete(compute));
|
|
58
|
+
}
|
|
59
|
+
const value = Reflect.get(target, path);
|
|
60
|
+
if (value instanceof Object) return new Proxy(value, new Handler());
|
|
61
|
+
return value;
|
|
62
|
+
};
|
|
63
|
+
set = (target, path, value) => {
|
|
64
|
+
const result = Reflect.set(target, path, value);
|
|
65
|
+
for (const effect of targets.get(target)?.get(path) ?? []) {
|
|
66
|
+
while (effect.cleanUps.length) effect.cleanUps.pop()?.(effect);
|
|
67
|
+
effect.invoke();
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const createState = (value) => {
|
|
73
|
+
return new Proxy({ value }, new Handler());
|
|
74
|
+
};
|
|
75
|
+
const createCompute = (invoke) => {
|
|
76
|
+
return runCompute({
|
|
77
|
+
invoke,
|
|
78
|
+
cleanUps: []
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
const runCompute = (compute) => {
|
|
82
|
+
compute.previous = runningCompute;
|
|
83
|
+
runningCompute = compute;
|
|
84
|
+
try {
|
|
85
|
+
return compute.invoke();
|
|
86
|
+
} finally {
|
|
87
|
+
runningCompute = compute.previous;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const fromValue = (value) => {
|
|
91
|
+
if (value) {
|
|
92
|
+
if (value instanceof Function) return fromValue(value());
|
|
93
|
+
if (value instanceof Object) return value.value;
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
};
|
|
97
|
+
const onCleanUp = (cleanUp) => {
|
|
98
|
+
runningCompute?.cleanUps.push(cleanUp);
|
|
99
|
+
};
|
|
100
|
+
const targets = new WeakMap();
|
|
101
|
+
let runningCompute;
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/html/index.ts
|
|
105
|
+
const createElement = (input, attributes, ...children) => {
|
|
106
|
+
return {
|
|
107
|
+
tag: typeof input === "function" ? input.$name : input,
|
|
108
|
+
attributes: attributes ?? {},
|
|
109
|
+
children
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
const toString = (slot) => {
|
|
113
|
+
switch (typeof slot) {
|
|
114
|
+
case "string":
|
|
115
|
+
case "number":
|
|
116
|
+
case "bigint":
|
|
117
|
+
case "boolean":
|
|
118
|
+
case "symbol": return slot.toString();
|
|
119
|
+
case "object": return JSON.stringify(slot);
|
|
120
|
+
case "function": return toString(slot());
|
|
121
|
+
default: return "";
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const slotToString = async (slot) => {
|
|
125
|
+
if (Array.isArray(slot)) return await Promise.all(slot.map(slotToString)).then((chunks) => chunks.join(" "));
|
|
126
|
+
if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return toString(slot);
|
|
127
|
+
if (typeof slot === "function") return await slotToString(await slot());
|
|
128
|
+
if (typeof slot === "object") {
|
|
129
|
+
if ("tag" in slot && "attributes" in slot && "children" in slot) {
|
|
130
|
+
const Constructor = components.get(slot.tag);
|
|
131
|
+
const prefix = [slot.tag, ...Object.entries(slot.attributes ?? {}).map(([name, value]) => {
|
|
132
|
+
if (!name.startsWith("on")) return `${name}="${toString(value)}"`;
|
|
133
|
+
})].filter(Boolean).join(" ");
|
|
134
|
+
const children = await slotToString(slot.children);
|
|
135
|
+
if (Constructor) {
|
|
136
|
+
const component = new Constructor(slot.attributes);
|
|
137
|
+
const template = await slotToString(await component.setup());
|
|
138
|
+
if (component.shadowRoot) return `<${prefix}> <template shadowRootMode="${component.shadowRoot.mode}"> ${template} </template> ${children} </${slot.tag}>`;
|
|
139
|
+
return `<${prefix}> ${template} ${children} </${slot.tag}>`;
|
|
140
|
+
}
|
|
141
|
+
return `<${prefix}> ${children} </${slot.tag}>`;
|
|
142
|
+
}
|
|
143
|
+
return toString(slot);
|
|
144
|
+
}
|
|
145
|
+
return "";
|
|
146
|
+
};
|
|
147
|
+
const slotToNode = async (slot) => {
|
|
148
|
+
if (Array.isArray(slot)) {
|
|
149
|
+
const fragment = document.createDocumentFragment();
|
|
150
|
+
fragment.replaceChildren(...await Promise.all(slot.map(slotToNode)));
|
|
151
|
+
return fragment;
|
|
152
|
+
}
|
|
153
|
+
if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return document.createTextNode(slot.toString());
|
|
154
|
+
if (typeof slot === "function") {
|
|
155
|
+
let oldNode;
|
|
156
|
+
return await createCompute(async () => {
|
|
157
|
+
const node = await slotToNode(await slot());
|
|
158
|
+
oldNode?.parentNode?.replaceChild(node, oldNode);
|
|
159
|
+
oldNode = node;
|
|
160
|
+
return node;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (typeof slot === "object") {
|
|
164
|
+
if ("tag" in slot && "attributes" in slot && "children" in slot) {
|
|
165
|
+
const node = document.createElement(slot.tag);
|
|
166
|
+
for (const key in slot.attributes) {
|
|
167
|
+
const value = slot.attributes[key];
|
|
168
|
+
if (value) if (key.startsWith("on")) {
|
|
169
|
+
const event = key.substring(2).toLowerCase();
|
|
170
|
+
node.addEventListener(event, value);
|
|
171
|
+
onCleanUp(() => node.removeEventListener(event, value));
|
|
172
|
+
} else node.setAttribute(key, toString(value));
|
|
173
|
+
}
|
|
174
|
+
node.replaceChildren(await slotToNode(slot.children));
|
|
175
|
+
return node;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return document.createComment("");
|
|
179
|
+
};
|
|
180
|
+
const defineComponent = (options) => {
|
|
181
|
+
return registerComponent(class {
|
|
182
|
+
static $name = options.name;
|
|
183
|
+
static $events = options.events ?? {};
|
|
184
|
+
static $attributes = options.attributes ?? {};
|
|
185
|
+
events;
|
|
186
|
+
attributes;
|
|
187
|
+
shadowRoot;
|
|
188
|
+
constructor(input) {
|
|
189
|
+
this.events = Object.keys(options.events ?? {}).reduce((output, name) => {
|
|
190
|
+
Reflect.set(output, name, input?.[name], output);
|
|
191
|
+
return output;
|
|
192
|
+
}, {});
|
|
193
|
+
this.attributes = Object.entries(options.attributes ?? {}).reduce((output, [name, attribute]) => {
|
|
194
|
+
Reflect.set(output.value, name, input?.[name] ?? attribute.default, output.value);
|
|
195
|
+
return output;
|
|
196
|
+
}, createState({}));
|
|
197
|
+
this.shadowRoot = options.shadowRoot ?? { mode: "open" };
|
|
198
|
+
}
|
|
199
|
+
setup = () => options.setup(this);
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
const toCustomElement = (component) => {
|
|
203
|
+
return class extends HTMLElement {
|
|
204
|
+
component;
|
|
205
|
+
constructor() {
|
|
206
|
+
super();
|
|
207
|
+
this.component = new component();
|
|
208
|
+
}
|
|
209
|
+
async connectedCallback() {
|
|
210
|
+
let previous = activeElement;
|
|
211
|
+
activeElement = this;
|
|
212
|
+
try {
|
|
213
|
+
const shadow = this.component.shadowRoot ? this.attachShadow(this.component.shadowRoot) : this;
|
|
214
|
+
for (const [name, event] of Object.entries(component.$events)) Reflect.set(this.component.events, name, (value) => {
|
|
215
|
+
this.dispatchEvent(new CustomEvent(name.substring(2).toLowerCase(), {
|
|
216
|
+
...event,
|
|
217
|
+
detail: value
|
|
218
|
+
}));
|
|
219
|
+
}, this.component.events);
|
|
220
|
+
shadow.replaceChildren(await slotToNode(await this.component.setup()));
|
|
221
|
+
} finally {
|
|
222
|
+
activeElement = previous;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
attributeChangedCallback(name, _, value) {
|
|
226
|
+
const attribute = component.$attributes?.[name];
|
|
227
|
+
if (attribute) {
|
|
228
|
+
let convertedValue;
|
|
229
|
+
if (value) switch (attribute.type) {
|
|
230
|
+
case String:
|
|
231
|
+
convertedValue = value;
|
|
232
|
+
break;
|
|
233
|
+
case Number:
|
|
234
|
+
convertedValue = Number(value);
|
|
235
|
+
break;
|
|
236
|
+
case Boolean:
|
|
237
|
+
convertedValue = Boolean(value);
|
|
238
|
+
break;
|
|
239
|
+
case Object:
|
|
240
|
+
convertedValue = JSON.parse(value);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
Reflect.set(this.component.attributes.value, name, convertedValue ?? attribute.default, this.component.attributes.value);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
static get observedAttributes() {
|
|
247
|
+
return Object.keys(component.$attributes ?? {});
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
const registerComponent = (component) => {
|
|
252
|
+
components.set(component.$name, component);
|
|
253
|
+
if (isClient()) customElements.define(component.$name, toCustomElement(component));
|
|
254
|
+
return component;
|
|
255
|
+
};
|
|
256
|
+
const isClient = () => {
|
|
257
|
+
return typeof window !== "undefined";
|
|
258
|
+
};
|
|
259
|
+
const isServer = () => {
|
|
260
|
+
return typeof window === "undefined";
|
|
261
|
+
};
|
|
262
|
+
const addStyles = (...styles) => {
|
|
263
|
+
activeElement?.shadowRoot?.adoptedStyleSheets.push(...styles);
|
|
264
|
+
};
|
|
265
|
+
const getGlobalStyles = () => {
|
|
266
|
+
return Array.from(isServer() ? [] : document.styleSheets).map((style) => {
|
|
267
|
+
const sheet = new CSSStyleSheet();
|
|
268
|
+
const css = Array.from(style.cssRules).map((rule) => rule.cssText).join(" ");
|
|
269
|
+
sheet.replaceSync(css);
|
|
270
|
+
return sheet;
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
let globalStyles;
|
|
274
|
+
let activeElement;
|
|
275
|
+
const components = new Map();
|
|
276
|
+
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/http/index.ts
|
|
279
|
+
const mimeTypes = {
|
|
280
|
+
css: "text/css",
|
|
281
|
+
js: "text/javascript",
|
|
282
|
+
txt: "text/plain"
|
|
283
|
+
};
|
|
284
|
+
const getMimeType = (file) => {
|
|
285
|
+
const extension = /\.([a-zA-Z0-9]+?)$/.exec(file)?.at(1);
|
|
286
|
+
return mimeTypes[extension ?? ""] ?? "text/plain";
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/markdown/index.ts
|
|
291
|
+
const charWhile = (buffer, start, ...chars) => {
|
|
292
|
+
let depth = 0;
|
|
293
|
+
let current = buffer.at(start + depth);
|
|
294
|
+
while (current && chars.includes(current)) {
|
|
295
|
+
depth += 1;
|
|
296
|
+
current = buffer.at(start + depth);
|
|
297
|
+
}
|
|
298
|
+
return depth;
|
|
299
|
+
};
|
|
300
|
+
const charUntil = (buffer, start, ...chars) => {
|
|
301
|
+
let depth = 0;
|
|
302
|
+
let current = buffer.at(start + depth);
|
|
303
|
+
while (current && !chars.includes(current)) {
|
|
304
|
+
depth += 1;
|
|
305
|
+
current = buffer.at(start + depth);
|
|
306
|
+
}
|
|
307
|
+
return depth;
|
|
308
|
+
};
|
|
309
|
+
const inlineText = (buffer, options) => {
|
|
310
|
+
const nodes = new Array();
|
|
311
|
+
let index = 0;
|
|
312
|
+
while (index < buffer.length) {
|
|
313
|
+
const char = buffer.charAt(index);
|
|
314
|
+
const text = charUntil(buffer, index, "*", "_", "\n");
|
|
315
|
+
if (text > 0) {
|
|
316
|
+
nodes.push(buffer.slice(index, index + text));
|
|
317
|
+
index += text;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (char === "*" || char === "_") {
|
|
321
|
+
const start = charWhile(buffer, index, char);
|
|
322
|
+
const between = charUntil(buffer, index + start, char);
|
|
323
|
+
const end = charWhile(buffer, index + start + between, char);
|
|
324
|
+
const min = Math.min(start, end, 2);
|
|
325
|
+
const leading = start - min;
|
|
326
|
+
const trailing = end - min;
|
|
327
|
+
const slice = buffer.slice(index + leading + min, index + start + between + end - trailing - min);
|
|
328
|
+
if (slice.length > 0) {
|
|
329
|
+
const inline = inlineText(char.repeat(leading) + slice + char.repeat(trailing), options);
|
|
330
|
+
const tag = min === 2 ? "strong" : "em";
|
|
331
|
+
nodes.push(defu(options?.[tag], {
|
|
332
|
+
tag,
|
|
333
|
+
attributes: {},
|
|
334
|
+
children: inline
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
index += start + between + end;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (char === "\n") {
|
|
341
|
+
nodes.push(defu(options?.["br"], {
|
|
342
|
+
tag: "br",
|
|
343
|
+
attributes: {},
|
|
344
|
+
children: []
|
|
345
|
+
}));
|
|
346
|
+
index += 1;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return nodes;
|
|
351
|
+
};
|
|
352
|
+
const markdownToSlot = (input, options) => {
|
|
353
|
+
const nodes = new Array();
|
|
354
|
+
const buffer = input.replace(/[\r]+/g, "").trim();
|
|
355
|
+
let index = 0;
|
|
356
|
+
while (index < buffer.length) {
|
|
357
|
+
const start = index;
|
|
358
|
+
let lines = charWhile(buffer, index, "\n");
|
|
359
|
+
while (lines < 2 && index < buffer.length) {
|
|
360
|
+
index += lines + charUntil(buffer, index + lines, "\n");
|
|
361
|
+
lines = charWhile(buffer, index, "\n");
|
|
362
|
+
}
|
|
363
|
+
const block = buffer.slice(start, index);
|
|
364
|
+
if (block.startsWith("#")) {
|
|
365
|
+
const depth = charWhile(block, 0, "#");
|
|
366
|
+
const tag = "h" + depth;
|
|
367
|
+
nodes.push(defu(options?.[tag], {
|
|
368
|
+
tag,
|
|
369
|
+
attributes: {},
|
|
370
|
+
children: inlineText(block.slice(depth))
|
|
371
|
+
}));
|
|
372
|
+
} else nodes.push(defu(options?.["p"], {
|
|
373
|
+
tag: "p",
|
|
374
|
+
attributes: {},
|
|
375
|
+
children: inlineText(block)
|
|
376
|
+
}));
|
|
377
|
+
index += lines;
|
|
378
|
+
}
|
|
379
|
+
return nodes;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region src/radix/index.ts
|
|
384
|
+
var Radix = class Radix {
|
|
385
|
+
value;
|
|
386
|
+
input;
|
|
387
|
+
children;
|
|
388
|
+
constructor(input) {
|
|
389
|
+
this.input = input;
|
|
390
|
+
this.children = new Map();
|
|
391
|
+
}
|
|
392
|
+
insert = (path, value) => {
|
|
393
|
+
let node = this;
|
|
394
|
+
for (let segment of path.split("/")) {
|
|
395
|
+
let input;
|
|
396
|
+
if (segment.startsWith(":")) {
|
|
397
|
+
input = segment.substring(1);
|
|
398
|
+
segment = ":";
|
|
399
|
+
}
|
|
400
|
+
let childNode = node.children.get(segment);
|
|
401
|
+
if (childNode === undefined) {
|
|
402
|
+
childNode = new Radix(input);
|
|
403
|
+
node.children.set(segment, childNode);
|
|
404
|
+
}
|
|
405
|
+
node = childNode;
|
|
406
|
+
}
|
|
407
|
+
node.value = value;
|
|
408
|
+
return this;
|
|
409
|
+
};
|
|
410
|
+
match = (path) => {
|
|
411
|
+
let node = this;
|
|
412
|
+
const match = { inputs: {} };
|
|
413
|
+
for (const segment of path.split("/")) {
|
|
414
|
+
node = node?.children.get(segment) ?? node?.children.get(":");
|
|
415
|
+
if (node?.input) match.inputs[node.input] = segment;
|
|
416
|
+
}
|
|
417
|
+
match.value = node?.value;
|
|
418
|
+
return match;
|
|
419
|
+
};
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/runtime/index.ts
|
|
424
|
+
const defineRoute = (route) => {
|
|
425
|
+
return route;
|
|
426
|
+
};
|
|
427
|
+
const defineSocket = (socket) => {
|
|
428
|
+
return socket;
|
|
429
|
+
};
|
|
430
|
+
const toPath = (value) => {
|
|
431
|
+
return value.replace(/\/index/, "").replace(/\.(js|ts|jsx|tsx)$/, "").replaceAll(/\[(.*)\]/g, (_, name) => ":" + name).padStart(1, "/");
|
|
432
|
+
};
|
|
433
|
+
const createRuntime = async () => {
|
|
434
|
+
const radix = new Radix();
|
|
435
|
+
const assets = await import("#virtual/assets").then((module) => module.assets);
|
|
436
|
+
for (const path in assets) radix.insert("GET" + path, defineRoute({ fetch: async (event) => {
|
|
437
|
+
setHeader(event, "Content-Type", getMimeType(path));
|
|
438
|
+
return sendText(event, await assets[path]());
|
|
439
|
+
} }));
|
|
440
|
+
const routes = await import("#virtual/routes").then((module) => module.routes);
|
|
441
|
+
for (const path in routes) radix.insert("GET" + toPath(path), defineRoute({ fetch: async (event) => {
|
|
442
|
+
const route = await routes[path]();
|
|
443
|
+
if (route) {
|
|
444
|
+
if ("message" in route) return sendText(event, "WebSocket");
|
|
445
|
+
if ("$name" in route) {
|
|
446
|
+
const slot = await import("#virtual/client").then((module) => module.index);
|
|
447
|
+
return sendHtml(event, await slotToString(slot));
|
|
448
|
+
}
|
|
449
|
+
return route.fetch(event);
|
|
450
|
+
}
|
|
451
|
+
} }));
|
|
452
|
+
return { fetch: async (request, context) => {
|
|
453
|
+
const url = new URL(request.url);
|
|
454
|
+
const { value: route, inputs } = radix.match(request.method + url.pathname);
|
|
455
|
+
activeEvent = createEvent(request, {
|
|
456
|
+
...context,
|
|
457
|
+
inputs
|
|
458
|
+
});
|
|
459
|
+
try {
|
|
460
|
+
return await route?.fetch(activeEvent) ?? sendText(activeEvent, "Not found");
|
|
461
|
+
} finally {
|
|
462
|
+
activeEvent = undefined;
|
|
463
|
+
}
|
|
464
|
+
} };
|
|
465
|
+
};
|
|
466
|
+
let activeEvent;
|
|
467
|
+
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region src/router/index.tsx
|
|
470
|
+
const Outlet = defineComponent({
|
|
471
|
+
name: "x-outlet",
|
|
472
|
+
setup: async () => {
|
|
473
|
+
const radix = new Radix();
|
|
474
|
+
const routes = await import("#virtual/routes").then((module) => module.routes);
|
|
475
|
+
for (const path in routes) radix.insert("GET" + toPath(path), routes[path]);
|
|
476
|
+
const url = createState(new URL(activeEvent ? activeEvent.request.url : window.location.href));
|
|
477
|
+
if (isClient()) {
|
|
478
|
+
const controller = new AbortController();
|
|
479
|
+
window.addEventListener("popstate", () => url.value = new URL(window.location.href), { signal: controller.signal });
|
|
480
|
+
onCleanUp(() => controller.abort());
|
|
481
|
+
}
|
|
482
|
+
return async () => {
|
|
483
|
+
const { value, inputs } = radix.match("GET" + url.value.pathname);
|
|
484
|
+
const Page = await value?.();
|
|
485
|
+
if (Page) return h(Page, inputs);
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
const navigate = (url) => {
|
|
490
|
+
if (isClient()) {
|
|
491
|
+
const state = window.history.state;
|
|
492
|
+
window.history.pushState(state, "", url);
|
|
493
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state }));
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
const anchorNavigate = (event) => {
|
|
497
|
+
event.preventDefault();
|
|
498
|
+
navigate(event.currentTarget?.getAttribute("href") ?? "/");
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
//#endregion
|
|
502
|
+
export { Handler, Outlet, Radix, activeElement, activeEvent, addStyles, anchorNavigate, components, createApp, createCompute, createElement, createEvent, createRuntime, createState, defineComponent, defineRoute, defineSocket, fromValue, getAssets, getGlobalStyles, getMimeType, globalStyles, isClient, isServer, markdownToSlot, navigate, onCleanUp, registerComponent, runCompute, runningCompute, sendHtml, sendJson, sendText, setHeader, slotToNode, slotToString, targets, toCustomElement, toPath, toString };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Slot } from "../html";
|
|
2
|
+
export declare namespace JSX {
|
|
3
|
+
type Element = Slot;
|
|
4
|
+
interface IntrinsicElements {
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare const h: <TEvents extends import("..").Events, TAttributes extends import("..").Attributes>(input: string | import("..").ComponentConstructor<TEvents, TAttributes>, attributes?: import("..").AttributeInput<TAttributes>, ...children: Array<Slot>) => Slot;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/html/index.ts
|
|
3
|
+
const createElement = (input, attributes, ...children) => {
|
|
4
|
+
return {
|
|
5
|
+
tag: typeof input === "function" ? input.$name : input,
|
|
6
|
+
attributes: attributes ?? {},
|
|
7
|
+
children
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/jsx/index.ts
|
|
13
|
+
const h = createElement;
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { h };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Match<T> = {
|
|
2
|
+
value?: T;
|
|
3
|
+
inputs: Record<string, string>;
|
|
4
|
+
};
|
|
5
|
+
export declare class Radix<T> {
|
|
6
|
+
value?: T;
|
|
7
|
+
input?: string;
|
|
8
|
+
children: Map<string, Radix<T>>;
|
|
9
|
+
constructor(input?: string);
|
|
10
|
+
insert: (path: string, value: T) => this;
|
|
11
|
+
match: (path: string) => Match<T>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Context, Event } from "../event";
|
|
2
|
+
export type Route<T = Context> = {
|
|
3
|
+
fetch: (event: Event<T>) => void | Response | Promise<void | Response>;
|
|
4
|
+
};
|
|
5
|
+
export type Socket<T = Context> = {
|
|
6
|
+
open: (event: Event<T>) => void | Promise<void>;
|
|
7
|
+
message: (event: Event<T>) => void | Promise<void>;
|
|
8
|
+
close: (event: Event<T>) => void | Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export type Runtime<T = Context> = {
|
|
11
|
+
fetch: (request: Request, context: T) => Promise<Response>;
|
|
12
|
+
};
|
|
13
|
+
export declare const defineRoute: <T = Context>(route: Route<T>) => Route<T>;
|
|
14
|
+
export declare const defineSocket: <T = Context>(socket: Socket<T>) => Socket<T>;
|
|
15
|
+
export declare const toPath: (value: string) => string;
|
|
16
|
+
export declare const createRuntime: <T = Context>() => Promise<Runtime<T>>;
|
|
17
|
+
export declare let activeEvent: Event | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type Value<T> = T | State<T> | (() => Value<T>);
|
|
2
|
+
export interface State<T = unknown> {
|
|
3
|
+
value: T;
|
|
4
|
+
}
|
|
5
|
+
export interface Compute<T = unknown> {
|
|
6
|
+
invoke: () => T;
|
|
7
|
+
cleanUps: Array<(compute: Compute) => void>;
|
|
8
|
+
previous?: Compute;
|
|
9
|
+
}
|
|
10
|
+
export declare class Handler<T extends object> implements ProxyHandler<T> {
|
|
11
|
+
get: <TValue>(target: T, path: string | symbol) => TValue;
|
|
12
|
+
set: <TValue>(target: T, path: string | symbol, value: TValue) => boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const createState: <T>(value: T) => State<T>;
|
|
15
|
+
export declare const createCompute: <T>(invoke: () => T) => T;
|
|
16
|
+
export declare const runCompute: <T>(compute: Compute<T>) => T;
|
|
17
|
+
export declare const fromValue: <T>(value: Value<T>) => T;
|
|
18
|
+
export declare const onCleanUp: <T>(cleanUp: (compute: Compute) => T) => void;
|
|
19
|
+
export declare const targets: WeakMap<object, Map<string | symbol, Set<Compute<unknown>>>>;
|
|
20
|
+
export declare let runningCompute: Compute | undefined;
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "revojs",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"repository": "coverbase/revojs",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./presets/*": {
|
|
13
|
+
"types": "./dist/presets/*.d.ts",
|
|
14
|
+
"import": "./dist/presets/*.js"
|
|
15
|
+
},
|
|
16
|
+
"./jsx-runtime": {
|
|
17
|
+
"types": "./dist/jsx/index.d.ts",
|
|
18
|
+
"import": "./dist/jsx/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"files": ["dist"],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "rolldown -c rolldown.config.ts && tsc",
|
|
27
|
+
"watch": "rolldown -w -c rolldown.config.ts && tsc --watch"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"defu": "^6.1.4"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@revojs/tsconfig": "0.0.0",
|
|
34
|
+
"@revojs/rolldown": "0.0.0",
|
|
35
|
+
"rolldown": "^1.0.0-beta.1"
|
|
36
|
+
}
|
|
37
|
+
}
|