Skip to content

Commit ff9b685

Browse files
committed
refactor: JIT registering events
1 parent 5655bef commit ff9b685

File tree

6 files changed

+77
-14
lines changed

6 files changed

+77
-14
lines changed

packages/async-framework/async-loader.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
// deno-lint-ignore-file no-explicit-any
22
import { escapeSelector, isPromise } from "./utils.ts";
3+
4+
export interface AsyncLoaderContext {
5+
value: any;
6+
attrValue: string;
7+
dispatch: (eventName: string, detail?: any) => void;
8+
element: Element;
9+
event: Event;
10+
eventName: string;
11+
handlers: { handler: (context: AsyncLoaderContext) => Promise<any> | any };
12+
container: Element;
13+
stop: () => void;
14+
}
15+
316
export interface AsyncLoaderConfig {
4-
handlerRegistry: { handler: (context: any) => Promise<any> | any };
17+
handlerRegistry: { handler: (context: AsyncLoaderContext) => Promise<any> | any };
518
containerAttribute?: string;
619
eventPrefix?: string;
720
containers?: Map<Element, Map<string, Map<Element, string>>>;
@@ -226,11 +239,16 @@ export class AsyncLoader {
226239
const listeners = new Map();
227240
this.containers.set(containerElement, listeners);
228241

242+
// TODO: We still need to parse the container for the event type
243+
// even when doing lazy event registration
229244
this.events.forEach((eventName) => {
230245
// console.log('setupContainerListeners: adding event listener for', eventName);
231246
containerElement.addEventListener(
232247
eventName,
233248
(event) => {
249+
// TODO: we may not need to parse the container anymore for the event type
250+
// when doing lazy event registration
251+
234252
// Lazy parse the element for the event type before handling the event
235253
this.parseContainerElement(containerElement, eventName);
236254
// Handle the event when it occurs
@@ -344,6 +362,7 @@ export class AsyncLoader {
344362
dispatch(eventName: string | CustomEvent, detail?: any) {
345363
// create the custom event
346364
let customEvent;
365+
let result = false;
347366
if (eventName instanceof CustomEvent) {
348367
customEvent = eventName;
349368
detail = eventName.detail;
@@ -353,10 +372,30 @@ export class AsyncLoader {
353372
}
354373
// grab all listeners for the event and emit the event to all elements that have registered handlers for the event
355374
this.containers.forEach((listeners, containerElement) => {
375+
// TODO: refactor code to avoid adding the same event listener multiple times
376+
// this is now lazy registering
377+
if (!this.events.includes(eventName)) {
378+
this.events.push(eventName);
379+
// add the event listener to the container
380+
containerElement.addEventListener(
381+
eventName,
382+
(event) => {
383+
// TODO: we don't need to parse the container for the event type
384+
// when doing lazy event registration
385+
386+
// Lazy parse the element for the event type before handling the event
387+
// this.parseContainerElement(containerElement, eventName);
388+
// Handle the event when it occurs
389+
this.handleContainerEvent(containerElement, event);
390+
// console.log('setupContainerListeners: event handled', res);
391+
},
392+
true, // Use capturing phase to ensure the handler runs before other listeners
393+
);
394+
// add the event to the events array
395+
}
356396
// console.log('dispatch: parsing container elements for event', eventName);
357397
// lazy parse the container for the event type
358398
this.parseContainerElement(containerElement, eventName);
359-
360399
// if there are listeners for the event and rely on side effects
361400
if (listeners.has(eventName)) {
362401
// Parse the container for the event type before handling the event
@@ -366,6 +405,7 @@ export class AsyncLoader {
366405
eventListeners.forEach((_attrValue, element) => {
367406
if (element.isConnected) {
368407
element.dispatchEvent(customEvent);
408+
result = true;
369409
} else {
370410
cleanup.push(element);
371411
}
@@ -377,6 +417,7 @@ export class AsyncLoader {
377417
}
378418
}
379419
});
420+
return result;
380421
}
381422

382423
// Handles an event occurring within a container

packages/async-framework/render.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,21 @@ export function render(
1919
config = document.querySelector("[data-container='root']") as HTMLElement;
2020
}
2121
// Handle case where config is just the root element
22-
const domRoot = config instanceof HTMLElement ? config : config.root;
22+
let domRoot = config instanceof HTMLElement ? config : config.root;
2323
const renderConfig = config instanceof HTMLElement ? {} : config;
2424
if (!domRoot) {
25-
throw new Error("Root element is required for rendering");
25+
domRoot = document.querySelector("[data-container='root']") as HTMLElement;
26+
if (!domRoot) {
27+
throw new Error("Root element is required for rendering");
28+
}
2629
}
30+
const currentPath = location.pathname;
2731
const containerAttribute = renderConfig.containerAttribute ??
2832
"data-container";
2933
const context = renderConfig.context ?? {};
3034
const events = renderConfig.events ?? [];
31-
const basePath = renderConfig.basePath ?? "./handlers/";
35+
// should be /handlers?
36+
const basePath = renderConfig.basePath ?? currentPath;
3237
const origin = renderConfig.origin ?? "";
3338
const eventPrefix = renderConfig.eventPrefix ?? "on:";
3439

@@ -61,13 +66,14 @@ export function render(
6166
loader.init();
6267

6368
// Return utilities for cleanup and access to loader/registry
64-
return {
69+
const asyncFramework = {
6570
loader,
6671
handlers: registry,
6772
unmount: () => {
6873
domRoot.removeChild(element);
6974
},
7075
};
76+
return asyncFramework;
7177
}
7278

7379
// Example usage:

packages/dev/server-utils/bundler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ export function createBundler(rootDir: string) {
4242
...options,
4343
define: {
4444
...options.define,
45-
"import.meta.url": "import_meta_url",
45+
// "import.meta.url": "import_meta_url",
4646
},
4747
write: false,
4848
jsxFactory: "jsx",
4949
jsxFragment: "Fragment",
5050
jsxImportSource: "async-framework",
5151
entryPoints: [absolutePath],
52+
// format: "iife",
53+
// format: "cjs",
5254
format: "esm",
5355
plugins: [
5456
{

packages/examples/jsx-client/App.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
// deno-lint-ignore no-unused-vars
2-
import { createSignal, jsx } from "async-framework";
2+
import { jsx, createSignal, AsyncLoaderContext } from "async-framework";
33
import { Counter } from "./Counter.tsx";
44

5+
export function onUpdate(context: AsyncLoaderContext) {
6+
console.log("onUpdate", context);
7+
}
8+
59
export function App() {
610
const [name, setName, nameSig] = createSignal("World");
711

812
return (
9-
<div class="min-h-screen bg-gray-100 py-8 px-4">
13+
<div class="min-h-screen bg-gray-100 py-8 px-4" on:update="App.tsx">
1014
<div class="max-w-3xl mx-auto space-y-8">
1115
<div class="bg-white rounded-lg shadow-md p-6">
1216
<h1 class="text-3xl font-bold text-gray-800 mb-4">Hello {nameSig}</h1>
@@ -18,6 +22,13 @@ export function App() {
1822
onInput={(e) => {
1923
if (e.target instanceof HTMLInputElement) {
2024
setName(e.target.value);
25+
const success = globalThis.framework.loader.dispatch(
26+
"update",
27+
e.target.value
28+
);
29+
if (!success) {
30+
console.error("Failed to dispatch update event");
31+
}
2132
}
2233
}}
2334
/>

packages/examples/jsx-client/Counter.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// deno-lint-ignore no-unused-vars
2-
import { cls, computed, jsx, signal } from "async-framework";
2+
import { jsx, cls, computed, signal } from "async-framework";
33

44
export function Counter() {
55
const count = signal(0);
@@ -33,9 +33,9 @@ export function Counter() {
3333
Count: {count}
3434
</h2>
3535
<button
36-
onClick={() => (theme.value = theme.value === "light"
37-
? "dark"
38-
: "light")}
36+
onClick={() =>
37+
(theme.value = theme.value === "light" ? "dark" : "light")
38+
}
3939
class="px-4 py-2 rounded-md bg-gray-200 hover:bg-gray-300 transition-colors"
4040
>
4141
Toggle Theme

packages/examples/jsx-client/index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
import { render } from "async-framework";
2020
import { App } from "./App.tsx";
2121

22-
render(App());
22+
const framework = render(App(), {
23+
// events: ["update"],
24+
});
25+
window.framework = framework;
2326
</script>
2427
</head>
2528
<body class="bg-gray-100 min-h-screen">

0 commit comments

Comments
 (0)