@opentf/web 0.3.0 → 0.5.0
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/components/Link.jsx +29 -0
- package/core/errors.js +48 -0
- package/core/signals.js +188 -0
- package/index.js +12 -0
- package/package.json +27 -18
- package/runtime/context.js +89 -0
- package/runtime/dom.js +280 -0
- package/runtime/error-boundary.js +109 -0
- package/runtime/events.js +17 -0
- package/runtime/index.js +16 -0
- package/runtime/lifecycle.js +9 -0
- package/runtime/mount.js +49 -0
- package/runtime/portal.js +59 -0
- package/runtime/router.js +246 -0
- package/server/builtins.js +11 -0
- package/server/index.js +7 -0
- package/server/render.js +76 -0
- package/server/ssg-runtime.js +109 -0
- package/dist/CHANGELOG.md +0 -24
- package/dist/babel.config.cjs +0 -6
- package/dist/compiler/babel-plugin.cjs +0 -731
- package/dist/core/mount.js +0 -32
- package/dist/index.js +0 -7
- package/dist/node_modules/@babel/core/LICENSE +0 -22
- package/dist/node_modules/@babel/core/README.md +0 -19
- package/dist/node_modules/@babel/core/lib/config/cache-contexts.js +0 -5
- package/dist/node_modules/@babel/core/lib/config/cache-contexts.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/caching.js +0 -261
- package/dist/node_modules/@babel/core/lib/config/caching.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/config-chain.js +0 -469
- package/dist/node_modules/@babel/core/lib/config/config-chain.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/config-descriptors.js +0 -190
- package/dist/node_modules/@babel/core/lib/config/config-descriptors.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/configuration.js +0 -290
- package/dist/node_modules/@babel/core/lib/config/files/configuration.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/import.cjs +0 -6
- package/dist/node_modules/@babel/core/lib/config/files/import.cjs.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/index-browser.js +0 -58
- package/dist/node_modules/@babel/core/lib/config/files/index-browser.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/index.js +0 -78
- package/dist/node_modules/@babel/core/lib/config/files/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/module-types.js +0 -203
- package/dist/node_modules/@babel/core/lib/config/files/module-types.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/package.js +0 -61
- package/dist/node_modules/@babel/core/lib/config/files/package.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/plugins.js +0 -220
- package/dist/node_modules/@babel/core/lib/config/files/plugins.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/types.js +0 -5
- package/dist/node_modules/@babel/core/lib/config/files/types.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/utils.js +0 -36
- package/dist/node_modules/@babel/core/lib/config/files/utils.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/full.js +0 -312
- package/dist/node_modules/@babel/core/lib/config/full.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/helpers/config-api.js +0 -85
- package/dist/node_modules/@babel/core/lib/config/helpers/config-api.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/helpers/deep-array.js +0 -23
- package/dist/node_modules/@babel/core/lib/config/helpers/deep-array.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/helpers/environment.js +0 -12
- package/dist/node_modules/@babel/core/lib/config/helpers/environment.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/index.js +0 -87
- package/dist/node_modules/@babel/core/lib/config/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/item.js +0 -67
- package/dist/node_modules/@babel/core/lib/config/item.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/partial.js +0 -158
- package/dist/node_modules/@babel/core/lib/config/partial.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/pattern-to-regex.js +0 -38
- package/dist/node_modules/@babel/core/lib/config/pattern-to-regex.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/plugin.js +0 -33
- package/dist/node_modules/@babel/core/lib/config/plugin.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/printer.js +0 -113
- package/dist/node_modules/@babel/core/lib/config/printer.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/resolve-targets-browser.js +0 -41
- package/dist/node_modules/@babel/core/lib/config/resolve-targets-browser.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/resolve-targets.js +0 -61
- package/dist/node_modules/@babel/core/lib/config/resolve-targets.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/util.js +0 -31
- package/dist/node_modules/@babel/core/lib/config/util.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/validation/option-assertions.js +0 -277
- package/dist/node_modules/@babel/core/lib/config/validation/option-assertions.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/validation/options.js +0 -187
- package/dist/node_modules/@babel/core/lib/config/validation/options.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/validation/plugins.js +0 -67
- package/dist/node_modules/@babel/core/lib/config/validation/plugins.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/validation/removed.js +0 -68
- package/dist/node_modules/@babel/core/lib/config/validation/removed.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/errors/config-error.js +0 -18
- package/dist/node_modules/@babel/core/lib/errors/config-error.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js +0 -98
- package/dist/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/gensync-utils/async.js +0 -90
- package/dist/node_modules/@babel/core/lib/gensync-utils/async.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/gensync-utils/fs.js +0 -31
- package/dist/node_modules/@babel/core/lib/gensync-utils/fs.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/gensync-utils/functional.js +0 -58
- package/dist/node_modules/@babel/core/lib/gensync-utils/functional.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/index.js +0 -230
- package/dist/node_modules/@babel/core/lib/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/parse.js +0 -45
- package/dist/node_modules/@babel/core/lib/parse.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/parser/index.js +0 -85
- package/dist/node_modules/@babel/core/lib/parser/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js +0 -337
- package/dist/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/tools/build-external-helpers.js +0 -144
- package/dist/node_modules/@babel/core/lib/tools/build-external-helpers.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transform-ast.js +0 -48
- package/dist/node_modules/@babel/core/lib/transform-ast.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transform-file-browser.js +0 -23
- package/dist/node_modules/@babel/core/lib/transform-file-browser.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transform-file.js +0 -40
- package/dist/node_modules/@babel/core/lib/transform-file.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transform.js +0 -47
- package/dist/node_modules/@babel/core/lib/transform.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js +0 -84
- package/dist/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs +0 -4
- package/dist/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/file/file.js +0 -204
- package/dist/node_modules/@babel/core/lib/transformation/file/file.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/file/generate.js +0 -84
- package/dist/node_modules/@babel/core/lib/transformation/file/generate.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/file/merge-map.js +0 -37
- package/dist/node_modules/@babel/core/lib/transformation/file/merge-map.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/index.js +0 -90
- package/dist/node_modules/@babel/core/lib/transformation/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/normalize-file.js +0 -127
- package/dist/node_modules/@babel/core/lib/transformation/normalize-file.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/normalize-opts.js +0 -59
- package/dist/node_modules/@babel/core/lib/transformation/normalize-opts.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/plugin-pass.js +0 -48
- package/dist/node_modules/@babel/core/lib/transformation/plugin-pass.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/util/clone-deep.js +0 -54
- package/dist/node_modules/@babel/core/lib/transformation/util/clone-deep.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/vendor/import-meta-resolve.js +0 -1042
- package/dist/node_modules/@babel/core/lib/vendor/import-meta-resolve.js.map +0 -1
- package/dist/node_modules/@babel/core/package.json +0 -82
- package/dist/node_modules/@babel/core/src/config/files/index-browser.ts +0 -115
- package/dist/node_modules/@babel/core/src/config/files/index.ts +0 -30
- package/dist/node_modules/@babel/core/src/config/resolve-targets-browser.ts +0 -42
- package/dist/node_modules/@babel/core/src/config/resolve-targets.ts +0 -54
- package/dist/node_modules/@babel/core/src/transform-file-browser.ts +0 -33
- package/dist/node_modules/@babel/core/src/transform-file.ts +0 -56
- package/dist/node_modules/@babel/helper-module-imports/LICENSE +0 -22
- package/dist/node_modules/@babel/helper-module-imports/README.md +0 -19
- package/dist/node_modules/@babel/helper-module-imports/lib/import-builder.js +0 -122
- package/dist/node_modules/@babel/helper-module-imports/lib/import-builder.js.map +0 -1
- package/dist/node_modules/@babel/helper-module-imports/lib/import-injector.js +0 -304
- package/dist/node_modules/@babel/helper-module-imports/lib/import-injector.js.map +0 -1
- package/dist/node_modules/@babel/helper-module-imports/lib/index.js +0 -37
- package/dist/node_modules/@babel/helper-module-imports/lib/index.js.map +0 -1
- package/dist/node_modules/@babel/helper-module-imports/lib/is-module.js +0 -11
- package/dist/node_modules/@babel/helper-module-imports/lib/is-module.js.map +0 -1
- package/dist/node_modules/@babel/helper-module-imports/package.json +0 -28
- package/dist/node_modules/@babel/plugin-syntax-jsx/LICENSE +0 -22
- package/dist/node_modules/@babel/plugin-syntax-jsx/README.md +0 -19
- package/dist/node_modules/@babel/plugin-syntax-jsx/lib/index.js +0 -21
- package/dist/node_modules/@babel/plugin-syntax-jsx/lib/index.js.map +0 -1
- package/dist/node_modules/@babel/plugin-syntax-jsx/package.json +0 -33
- package/dist/node_modules/@preact/signals-core/CHANGELOG.md +0 -331
- package/dist/node_modules/@preact/signals-core/LICENSE +0 -21
- package/dist/node_modules/@preact/signals-core/README.md +0 -258
- package/dist/node_modules/@preact/signals-core/dist/signals-core.d.ts +0 -149
- package/dist/node_modules/@preact/signals-core/dist/signals-core.js +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.js.map +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.min.js +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.min.js.map +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.mjs +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.mjs.map +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.module.js +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.module.js.map +0 -1
- package/dist/node_modules/@preact/signals-core/package.json +0 -48
- package/dist/node_modules/@preact/signals-core/src/index.ts +0 -1121
- package/dist/package.json +0 -30
- package/dist/router/Link.js +0 -73
- package/dist/router/index.js +0 -216
- package/dist/runtime/For.js +0 -16
- package/dist/runtime/dom.js +0 -73
- package/dist/runtime/lifecycle.js +0 -25
- package/dist/runtime/props.js +0 -103
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// <Link href> — client-side (SPA) navigation.
|
|
2
|
+
//
|
|
3
|
+
// A *pure JSX component*, shipped as source and compiled by the consuming app's
|
|
4
|
+
// pipeline (like a UI library ships components for the user's bundler). The
|
|
5
|
+
// framework dogfoods its own JSX → Custom Element model here instead of a
|
|
6
|
+
// hand-written HTMLElement class — so it gets reactive props, the children slot,
|
|
7
|
+
// and a matching SSG renderer for free, with no separate runtime build step.
|
|
8
|
+
//
|
|
9
|
+
// `class` is a declared prop, so it lands on the rendered <a> (the link), not on
|
|
10
|
+
// the <web-link> host — no duplicated styling.
|
|
11
|
+
|
|
12
|
+
import { navigate } from "../runtime/router.js";
|
|
13
|
+
|
|
14
|
+
export default function Link({ href, class: className, children }) {
|
|
15
|
+
const onclick = (e) => {
|
|
16
|
+
// Let the browser handle modified clicks (new tab, etc.) and non-primary buttons.
|
|
17
|
+
if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
navigate(href);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<a href={href} class={className} onclick={onclick}>
|
|
26
|
+
{children}
|
|
27
|
+
</a>
|
|
28
|
+
);
|
|
29
|
+
}
|
package/core/errors.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//! Central error reporting for the OTF Web runtime.
|
|
2
|
+
//
|
|
3
|
+
// Render, effect, lifecycle, and event errors are funneled here instead of being
|
|
4
|
+
// thrown into the void. Userland (and the dev overlay) subscribe via `onError`;
|
|
5
|
+
// in the browser every report is also dispatched as an `otfw:error` window event
|
|
6
|
+
// so a fully decoupled listener (e.g. the dev server's injected overlay) can show
|
|
7
|
+
// it without importing the bundle.
|
|
8
|
+
|
|
9
|
+
const handlers = new Set();
|
|
10
|
+
const isBrowser = typeof window !== "undefined";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Subscribe to runtime errors. `handler(error, context)` is called for every
|
|
14
|
+
* `reportError`. Returns a disposer that unsubscribes.
|
|
15
|
+
*/
|
|
16
|
+
export function onError(handler) {
|
|
17
|
+
handlers.add(handler);
|
|
18
|
+
return () => handlers.delete(handler);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Report a caught error. `context` carries where it happened
|
|
23
|
+
* (`{ phase: "render" | "effect" | "mount" | "event" | "route", … }`).
|
|
24
|
+
* Notifies subscribers, emits the `otfw:error` window event, and — if nobody is
|
|
25
|
+
* listening — logs to the console so errors are never silently swallowed.
|
|
26
|
+
*/
|
|
27
|
+
export function reportError(error, context = {}) {
|
|
28
|
+
for (const handler of handlers) {
|
|
29
|
+
try {
|
|
30
|
+
handler(error, context);
|
|
31
|
+
} catch {
|
|
32
|
+
// A failing handler must not mask the original error.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (isBrowser) {
|
|
36
|
+
window.dispatchEvent(new CustomEvent("otfw:error", { detail: { error, context } }));
|
|
37
|
+
}
|
|
38
|
+
if (handlers.size === 0) {
|
|
39
|
+
console.error(`[otfw${context.phase ? `:${context.phase}` : ""}]`, error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Signal that the error condition has cleared (e.g. a successful navigation). */
|
|
44
|
+
export function clearError(context = {}) {
|
|
45
|
+
if (isBrowser) {
|
|
46
|
+
window.dispatchEvent(new CustomEvent("otfw:error-clear", { detail: context }));
|
|
47
|
+
}
|
|
48
|
+
}
|
package/core/signals.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
//! Fine-grained reactivity — our own signals core.
|
|
2
|
+
//
|
|
3
|
+
// This replaces the previous `@preact/signals-core` dependency to remove a
|
|
4
|
+
// third-party supply-chain risk (SPEC §1.1). The public surface is the
|
|
5
|
+
// industry-standard `signal` / `computed` / `effect` with a `.value` accessor;
|
|
6
|
+
// more APIs (peek/untracked/subscribe) are added only when a consumer needs them.
|
|
7
|
+
//
|
|
8
|
+
// Model: producers (signals, computeds) hold a set of subscribers; consumers
|
|
9
|
+
// (effects, computeds) track the producers they read. Reads register a
|
|
10
|
+
// dependency on the active consumer; writes mark dependent computeds stale and
|
|
11
|
+
// queue dependent effects, which run once per change/batch (glitch-free: an
|
|
12
|
+
// effect that depends on a signal both directly and via a computed runs once).
|
|
13
|
+
|
|
14
|
+
import { reportError } from "./errors.js";
|
|
15
|
+
|
|
16
|
+
/** The consumer (effect or computed) currently executing, if any. */
|
|
17
|
+
let activeConsumer = null;
|
|
18
|
+
/** Depth of nested `batch()` calls; effects flush when this returns to 0. */
|
|
19
|
+
let batchDepth = 0;
|
|
20
|
+
/** Re-entrancy guard for the flush loop. */
|
|
21
|
+
let flushing = false;
|
|
22
|
+
/** Effects queued to run on the next flush. A Set so each runs at most once. */
|
|
23
|
+
const pendingEffects = new Set();
|
|
24
|
+
|
|
25
|
+
/** Register `producer` as a dependency of the consumer currently running. */
|
|
26
|
+
function track(producer) {
|
|
27
|
+
if (activeConsumer) {
|
|
28
|
+
activeConsumer.sources.add(producer);
|
|
29
|
+
producer.subscribers.add(activeConsumer);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Detach a consumer from all producers it previously read. */
|
|
34
|
+
function clearSources(consumer) {
|
|
35
|
+
for (const source of consumer.sources) {
|
|
36
|
+
source.subscribers.delete(consumer);
|
|
37
|
+
}
|
|
38
|
+
consumer.sources.clear();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Mark dependents stale: computeds recursively, effects into the run queue. */
|
|
42
|
+
function propagate(producer) {
|
|
43
|
+
for (const sub of producer.subscribers) {
|
|
44
|
+
if (sub.kind === "computed") {
|
|
45
|
+
if (!sub.stale) {
|
|
46
|
+
sub.stale = true;
|
|
47
|
+
propagate(sub);
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
pendingEffects.add(sub);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Run all queued effects, including any they queue in turn. */
|
|
56
|
+
function flush() {
|
|
57
|
+
if (flushing) return;
|
|
58
|
+
flushing = true;
|
|
59
|
+
try {
|
|
60
|
+
while (pendingEffects.size > 0) {
|
|
61
|
+
const batch = [...pendingEffects];
|
|
62
|
+
pendingEffects.clear();
|
|
63
|
+
for (const effectNode of batch) {
|
|
64
|
+
if (effectNode.disposed) continue;
|
|
65
|
+
// A throwing reactive update is reported but must not break the scheduler
|
|
66
|
+
// (other queued effects still run). Build-time (initial) runs propagate so
|
|
67
|
+
// the surrounding component/page catch can handle them.
|
|
68
|
+
try {
|
|
69
|
+
effectNode.run();
|
|
70
|
+
} catch (e) {
|
|
71
|
+
reportError(e, { phase: "effect" });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
flushing = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A writable reactive value. Reading `.value` inside an effect/computed
|
|
82
|
+
* subscribes to it; writing a changed `.value` notifies dependents.
|
|
83
|
+
*/
|
|
84
|
+
export function signal(initial) {
|
|
85
|
+
const node = { kind: "signal", value: initial, subscribers: new Set() };
|
|
86
|
+
return {
|
|
87
|
+
get value() {
|
|
88
|
+
track(node);
|
|
89
|
+
return node.value;
|
|
90
|
+
},
|
|
91
|
+
set value(next) {
|
|
92
|
+
if (Object.is(next, node.value)) return;
|
|
93
|
+
node.value = next;
|
|
94
|
+
propagate(node);
|
|
95
|
+
if (batchDepth === 0) flush();
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A derived, read-only reactive value. Lazy and cached: the compute function
|
|
102
|
+
* runs only when `.value` is read while stale, and recomputes only after a
|
|
103
|
+
* dependency changes.
|
|
104
|
+
*/
|
|
105
|
+
export function computed(fn) {
|
|
106
|
+
const node = {
|
|
107
|
+
kind: "computed",
|
|
108
|
+
fn,
|
|
109
|
+
value: undefined,
|
|
110
|
+
stale: true,
|
|
111
|
+
sources: new Set(),
|
|
112
|
+
subscribers: new Set(),
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
get value() {
|
|
116
|
+
if (node.stale) {
|
|
117
|
+
const prev = activeConsumer;
|
|
118
|
+
clearSources(node);
|
|
119
|
+
activeConsumer = node;
|
|
120
|
+
try {
|
|
121
|
+
node.value = node.fn();
|
|
122
|
+
node.stale = false;
|
|
123
|
+
} finally {
|
|
124
|
+
activeConsumer = prev;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
track(node);
|
|
128
|
+
return node.value;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Run `fn` immediately, tracking the signals it reads, and re-run it whenever
|
|
135
|
+
* any of them change. `fn` may return a cleanup function, which runs before the
|
|
136
|
+
* next re-run and on dispose. Returns a disposer that stops the effect.
|
|
137
|
+
*/
|
|
138
|
+
export function effect(fn) {
|
|
139
|
+
const node = {
|
|
140
|
+
kind: "effect",
|
|
141
|
+
fn,
|
|
142
|
+
cleanup: undefined,
|
|
143
|
+
sources: new Set(),
|
|
144
|
+
disposed: false,
|
|
145
|
+
run() {
|
|
146
|
+
if (node.disposed) return;
|
|
147
|
+
runCleanup(node);
|
|
148
|
+
clearSources(node);
|
|
149
|
+
const prev = activeConsumer;
|
|
150
|
+
activeConsumer = node;
|
|
151
|
+
try {
|
|
152
|
+
const result = node.fn();
|
|
153
|
+
if (typeof result === "function") node.cleanup = result;
|
|
154
|
+
} finally {
|
|
155
|
+
activeConsumer = prev;
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
node.run();
|
|
160
|
+
return function dispose() {
|
|
161
|
+
if (node.disposed) return;
|
|
162
|
+
node.disposed = true;
|
|
163
|
+
runCleanup(node);
|
|
164
|
+
clearSources(node);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function runCleanup(node) {
|
|
169
|
+
if (node.cleanup) {
|
|
170
|
+
const fn = node.cleanup;
|
|
171
|
+
node.cleanup = undefined;
|
|
172
|
+
fn();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Group multiple writes so dependent effects run once after the outermost batch
|
|
178
|
+
* completes, instead of after each write.
|
|
179
|
+
*/
|
|
180
|
+
export function batch(fn) {
|
|
181
|
+
batchDepth++;
|
|
182
|
+
try {
|
|
183
|
+
return fn();
|
|
184
|
+
} finally {
|
|
185
|
+
batchDepth--;
|
|
186
|
+
if (batchDepth === 0) flush();
|
|
187
|
+
}
|
|
188
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Public entry for the OTF Web runtime: reactivity + DOM operations.
|
|
2
|
+
// CSR-generated modules import their helpers (signal/computed/bindText/…) from here.
|
|
3
|
+
export * from "./core/signals.js";
|
|
4
|
+
export * from "./core/errors.js";
|
|
5
|
+
export * from "./runtime/index.js";
|
|
6
|
+
// `Link` is shipped as JSX source and compiled by the consuming app's pipeline
|
|
7
|
+
// (the `.jsx` lands in the module graph and otfwc transforms it). Its compiled
|
|
8
|
+
// module self-registers `web-link` (CSR `customElements.define` / SSG `defineSSG`).
|
|
9
|
+
// The bare import forces that registration to be retained — `<Link>` is used as a
|
|
10
|
+
// tag, so the `Link` binding alone would be tree-shaken.
|
|
11
|
+
import "./components/Link.jsx";
|
|
12
|
+
export { default as Link } from "./components/Link.jsx";
|
package/package.json
CHANGED
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentf/web",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.js",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "The native-first OTF Web runtime — signal-based reactivity and zero-VDOM DOM operations, paired with the IR-based compiler.",
|
|
7
5
|
"type": "module",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "babel . --out-dir dist --extensions .js,.jsx --copy-files --config-file ./babel.config.cjs --ignore \"node_modules,dist,package.json,bun.lock,*.md\"",
|
|
10
|
-
"prepublishOnly": "npm run build"
|
|
11
|
-
},
|
|
12
6
|
"exports": {
|
|
13
|
-
".": "./
|
|
14
|
-
"./
|
|
15
|
-
"./runtime": "./
|
|
16
|
-
"./
|
|
7
|
+
".": "./index.js",
|
|
8
|
+
"./signals": "./core/signals.js",
|
|
9
|
+
"./runtime": "./runtime/index.js",
|
|
10
|
+
"./server": "./server/index.js"
|
|
17
11
|
},
|
|
18
12
|
"files": [
|
|
19
|
-
"
|
|
13
|
+
"index.js",
|
|
14
|
+
"core",
|
|
15
|
+
"runtime",
|
|
16
|
+
"components",
|
|
17
|
+
"server",
|
|
18
|
+
"!**/*.test.js"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"opentf",
|
|
22
|
+
"web-components",
|
|
23
|
+
"signals",
|
|
24
|
+
"reactivity",
|
|
25
|
+
"runtime",
|
|
26
|
+
"ssg"
|
|
20
27
|
],
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"@babel/plugin-syntax-jsx": "^7.28.6"
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/Open-Tech-Foundation/Web-App-Framework.git",
|
|
31
|
+
"directory": "packages/web"
|
|
26
32
|
},
|
|
33
|
+
"homepage": "https://github.com/Open-Tech-Foundation/Web-App-Framework#readme",
|
|
34
|
+
"bugs": "https://github.com/Open-Tech-Foundation/Web-App-Framework/issues",
|
|
35
|
+
"license": "MIT",
|
|
27
36
|
"publishConfig": {
|
|
28
37
|
"access": "public"
|
|
29
38
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//! Context API — scoped dependency injection over the component tree.
|
|
2
|
+
//
|
|
3
|
+
// Unlike a module-level signal (which is global) or a prop (which must be drilled),
|
|
4
|
+
// context provides a value to a *subtree* and lets a nested provider override it.
|
|
5
|
+
// Because components compile to Custom Elements, the component tree *is* the DOM
|
|
6
|
+
// tree, so we resolve a consumer's provider with `closest()` — no special graph.
|
|
7
|
+
//
|
|
8
|
+
// const ThemeContext = createContext("dark");
|
|
9
|
+
// <ContextProvider context={ThemeContext} value={theme}> … </ContextProvider>
|
|
10
|
+
// const theme = $context(ThemeContext); // compiler macro; use the bare name
|
|
11
|
+
//
|
|
12
|
+
// `value` flows through the provider's element as a signal, so a reactive `value`
|
|
13
|
+
// (e.g. backed by `$state`) updates every consumer fine-grained.
|
|
14
|
+
|
|
15
|
+
import { signal } from "../core/signals.js";
|
|
16
|
+
|
|
17
|
+
let nextId = 0;
|
|
18
|
+
|
|
19
|
+
// Stack of the elements whose component bodies are currently executing. A stack
|
|
20
|
+
// (not a single slot) because filling a component's children slot connects nested
|
|
21
|
+
// components synchronously inside the parent's connect — each push is balanced by
|
|
22
|
+
// a pop in the generated `finally`, so the parent's host is restored after.
|
|
23
|
+
const hostStack = [];
|
|
24
|
+
|
|
25
|
+
/** Push the current component host (compiler-emitted, paired with `exitHost`). */
|
|
26
|
+
export function enterHost(el) {
|
|
27
|
+
hostStack.push(el);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Pop the current component host (compiler-emitted, in a `finally`). */
|
|
31
|
+
export function exitHost() {
|
|
32
|
+
hostStack.pop();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a context with a default value. The returned token is passed to
|
|
37
|
+
* `<ContextProvider context={…}>` and `$context(…)`.
|
|
38
|
+
*/
|
|
39
|
+
export function createContext(defaultValue) {
|
|
40
|
+
return { id: `otfw-ctx-${nextId++}`, fallback: signal(defaultValue) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the value provided by the nearest ancestor `ContextProvider` for
|
|
45
|
+
* `context`, or the context's default if there is none. Returns a **signal**. This
|
|
46
|
+
* is what the `$context(Ctx)` compiler macro lowers to; the macro registers the
|
|
47
|
+
* binding as a signal so the bare name reads `.value` reactively.
|
|
48
|
+
*/
|
|
49
|
+
export function readContext(context) {
|
|
50
|
+
let el = hostStack[hostStack.length - 1];
|
|
51
|
+
const sel = `[data-otfw-ctx~="${context.id}"]`;
|
|
52
|
+
// Walk up the DOM; when the search reaches a portal boundary without finding a
|
|
53
|
+
// provider, hop to the portal's logical location (`__portalHost`) and continue,
|
|
54
|
+
// so context resolves across a <Portal> as if the content were still in place.
|
|
55
|
+
while (el) {
|
|
56
|
+
const provider = el.closest(sel);
|
|
57
|
+
if (provider) return provider._signal;
|
|
58
|
+
const boundary = el.closest("[data-otfw-portal]");
|
|
59
|
+
el = boundary ? boundary.__portalHost : null;
|
|
60
|
+
}
|
|
61
|
+
return context.fallback;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// `<ContextProvider context={Ctx} value={v}>`: a host element that publishes `v`
|
|
65
|
+
// (as a signal) for `Ctx` to its subtree. `context`/`value` arrive as element
|
|
66
|
+
// properties (the compiler sets component props reactively), so a changing `value`
|
|
67
|
+
// propagates to consumers automatically.
|
|
68
|
+
export class ContextProviderElement extends HTMLElement {
|
|
69
|
+
constructor() {
|
|
70
|
+
super();
|
|
71
|
+
this._signal = signal(undefined);
|
|
72
|
+
}
|
|
73
|
+
set context(ctx) {
|
|
74
|
+
if (ctx && ctx.id) this.setAttribute("data-otfw-ctx", ctx.id);
|
|
75
|
+
}
|
|
76
|
+
set value(v) {
|
|
77
|
+
this._signal.value = v;
|
|
78
|
+
}
|
|
79
|
+
get value() {
|
|
80
|
+
return this._signal.value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Exported so `import { ContextProvider } from "@opentf/web"` resolves; the import
|
|
85
|
+
// side effect registers the element (JSX uses the `web-context-provider` tag).
|
|
86
|
+
export const ContextProvider = ContextProviderElement;
|
|
87
|
+
if (typeof customElements !== "undefined" && !customElements.get("web-context-provider")) {
|
|
88
|
+
customElements.define("web-context-provider", ContextProviderElement);
|
|
89
|
+
}
|