@seanhogg/builderforce-webcontainers 2026.10.2 → 2026.10.3
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/README.md +6 -1
- package/dist/assets.d.ts +2 -0
- package/dist/assets.js +4 -2
- package/dist/check/cachedFetch.d.ts +3 -0
- package/dist/check/cachedFetch.d.ts.map +1 -0
- package/dist/check/cachedFetch.js +39 -0
- package/dist/check/cachedFetch.js.map +1 -0
- package/dist/check/index.d.ts +41 -0
- package/dist/check/index.d.ts.map +1 -0
- package/dist/check/index.js +74 -0
- package/dist/check/index.js.map +1 -0
- package/dist/check/protocol.d.ts +29 -0
- package/dist/check/protocol.d.ts.map +1 -0
- package/dist/check/protocol.js +13 -0
- package/dist/check/protocol.js.map +1 -0
- package/dist/check/worker.js +20 -0
- package/dist/componentCompilers.d.ts +21 -0
- package/dist/componentCompilers.d.ts.map +1 -0
- package/dist/componentCompilers.js +35 -0
- package/dist/componentCompilers.js.map +1 -0
- package/dist/esbuildWasm.d.ts +10 -4
- package/dist/esbuildWasm.d.ts.map +1 -1
- package/dist/esbuildWasm.js +14 -6
- package/dist/esbuildWasm.js.map +1 -1
- package/dist/host.d.ts +39 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +22 -7
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/node/index.d.ts +42 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +45 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/processHost.d.ts +65 -0
- package/dist/node/processHost.d.ts.map +1 -0
- package/dist/node/processHost.js +269 -0
- package/dist/node/processHost.js.map +1 -0
- package/dist/node/protocol.d.ts +139 -0
- package/dist/node/protocol.d.ts.map +1 -0
- package/dist/node/protocol.js +5 -0
- package/dist/node/protocol.js.map +1 -0
- package/dist/node/worker.js +197 -0
- package/dist/node/workerProcess.d.ts +22 -0
- package/dist/node/workerProcess.d.ts.map +1 -0
- package/dist/node/workerProcess.js +210 -0
- package/dist/node/workerProcess.js.map +1 -0
- package/dist/packageCache.d.ts +12 -0
- package/dist/packageCache.d.ts.map +1 -0
- package/dist/packageCache.js +28 -0
- package/dist/packageCache.js.map +1 -0
- package/dist/process-worker.js +197 -0
- package/dist/protocol.d.ts +9 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +7 -0
- package/dist/protocol.js.map +1 -1
- package/dist/relay.html +1 -1
- package/dist/relayWorker.d.ts +19 -0
- package/dist/relayWorker.d.ts.map +1 -0
- package/dist/relayWorker.js +39 -0
- package/dist/relayWorker.js.map +1 -0
- package/dist/sw.js +1 -1
- package/dist/swRouter.d.ts +9 -1
- package/dist/swRouter.d.ts.map +1 -1
- package/dist/swRouter.js +26 -4
- package/dist/swRouter.js.map +1 -1
- package/dist/transport.d.ts +7 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +7 -1
- package/dist/transport.js.map +1 -1
- package/package.json +12 -3
- package/src/check/cachedFetch.ts +47 -0
- package/src/check/index.ts +98 -0
- package/src/check/protocol.ts +34 -0
- package/src/check/worker.ts +49 -0
- package/src/componentCompilers.ts +66 -0
- package/src/esbuildWasm.ts +19 -7
- package/src/host.ts +64 -9
- package/src/index.ts +17 -4
- package/src/node/index.ts +69 -0
- package/src/node/processHost.ts +307 -0
- package/src/node/protocol.ts +63 -0
- package/src/node/worker.ts +46 -0
- package/src/node/workerProcess.ts +219 -0
- package/src/packageCache.ts +28 -0
- package/src/protocol.ts +13 -0
- package/src/relay.ts +5 -1
- package/src/relayWorker.ts +53 -0
- package/src/sw.ts +25 -6
- package/src/swRouter.ts +33 -4
- package/src/transport.ts +14 -0
package/dist/protocol.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export declare const RELAY_ATTACH = "bfwc:relay-attach";
|
|
|
26
26
|
export declare const RELAY_REATTACH = "bfwc:relay-reattach";
|
|
27
27
|
/** host → relay: reload every frame showing the preview at `base`. */
|
|
28
28
|
export declare const RELAY_RELOAD = "bfwc:relay-reload";
|
|
29
|
+
/** host → relay, with a MessagePort: start a process worker on the preview origin, bridged over the port. */
|
|
30
|
+
export declare const RELAY_SPAWN = "bfwc:relay-spawn";
|
|
29
31
|
export interface AttachMessage {
|
|
30
32
|
type: typeof ATTACH;
|
|
31
33
|
id: string;
|
|
@@ -39,7 +41,14 @@ export interface WireRequest {
|
|
|
39
41
|
reqId: number;
|
|
40
42
|
path: string;
|
|
41
43
|
search: string;
|
|
44
|
+
/** Present for requests to a virtual Node server (`/__port/<n>/…`), which see the full request. */
|
|
45
|
+
method?: string;
|
|
46
|
+
headers?: Record<string, string>;
|
|
47
|
+
body?: ArrayBuffer | null;
|
|
42
48
|
}
|
|
49
|
+
/** The path segment that routes a preview request to a virtual server: `/__port/3000/…`. */
|
|
50
|
+
export declare const PORT_SEGMENT = "__port";
|
|
51
|
+
export declare function isPortPath(path: string): boolean;
|
|
43
52
|
export interface WireResponse {
|
|
44
53
|
type: 'response';
|
|
45
54
|
reqId: number;
|
package/dist/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,eAAe,WAAW,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,UAAU,QAAqB,CAAC;AAE7C,4FAA4F;AAC5F,eAAO,MAAM,MAAM,gBAAgB,CAAC;AACpC,mFAAmF;AACnF,eAAO,MAAM,QAAQ,kBAAkB,CAAC;AAExC;;;;GAIG;AACH,sDAAsD;AACtD,eAAO,MAAM,WAAW,qBAAqB,CAAC;AAC9C,oEAAoE;AACpE,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAChD,qDAAqD;AACrD,eAAO,MAAM,cAAc,wBAAwB,CAAC;AACpD,sEAAsE;AACtE,eAAO,MAAM,YAAY,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,eAAe,WAAW,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,UAAU,QAAqB,CAAC;AAE7C,4FAA4F;AAC5F,eAAO,MAAM,MAAM,gBAAgB,CAAC;AACpC,mFAAmF;AACnF,eAAO,MAAM,QAAQ,kBAAkB,CAAC;AAExC;;;;GAIG;AACH,sDAAsD;AACtD,eAAO,MAAM,WAAW,qBAAqB,CAAC;AAC9C,oEAAoE;AACpE,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAChD,qDAAqD;AACrD,eAAO,MAAM,cAAc,wBAAwB,CAAC;AACpD,sEAAsE;AACtE,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAChD,6GAA6G;AAC7G,eAAO,MAAM,WAAW,qBAAqB,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,QAAQ,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3B;AAED,4FAA4F;AAC5F,eAAO,MAAM,YAAY,WAAW,CAAC;AAErC,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAGlE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAGpE"}
|
package/dist/protocol.js
CHANGED
|
@@ -26,6 +26,13 @@ export const RELAY_ATTACH = 'bfwc:relay-attach';
|
|
|
26
26
|
export const RELAY_REATTACH = 'bfwc:relay-reattach';
|
|
27
27
|
/** host → relay: reload every frame showing the preview at `base`. */
|
|
28
28
|
export const RELAY_RELOAD = 'bfwc:relay-reload';
|
|
29
|
+
/** host → relay, with a MessagePort: start a process worker on the preview origin, bridged over the port. */
|
|
30
|
+
export const RELAY_SPAWN = 'bfwc:relay-spawn';
|
|
31
|
+
/** The path segment that routes a preview request to a virtual server: `/__port/3000/…`. */
|
|
32
|
+
export const PORT_SEGMENT = '__port';
|
|
33
|
+
export function isPortPath(path) {
|
|
34
|
+
return path.startsWith(`/${PORT_SEGMENT}/`);
|
|
35
|
+
}
|
|
29
36
|
/**
|
|
30
37
|
* The URL prefix previews live under, for a worker scope. Serving the worker from
|
|
31
38
|
* `/__bfwc/sw.js` (scope `/__bfwc/`) keeps it off every other page of the host app;
|
package/dist/protocol.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC;AAExC;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAE7C,4FAA4F;AAC5F,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;AACpC,mFAAmF;AACnF,MAAM,CAAC,MAAM,QAAQ,GAAG,eAAe,CAAC;AAExC;;;;GAIG;AACH,sDAAsD;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAC9C,oEAAoE;AACpE,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAChD,qDAAqD;AACrD,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AACpD,sEAAsE;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC;AAExC;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAE7C,4FAA4F;AAC5F,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;AACpC,mFAAmF;AACnF,MAAM,CAAC,MAAM,QAAQ,GAAG,eAAe,CAAC;AAExC;;;;GAIG;AACH,sDAAsD;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAC9C,oEAAoE;AACpE,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAChD,qDAAqD;AACrD,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AACpD,sEAAsE;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAChD,6GAA6G;AAC7G,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAuB9C,4FAA4F;AAC5F,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAErC,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC;AAC9C,CAAC;AAUD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB;IAC7C,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC;IACpE,OAAO,KAAK,CAAC,QAAQ,CAAC,IAAI,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,eAAe,GAAG,CAAC;AACxF,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,EAAU;IACvD,OAAO,GAAG,aAAa,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,MAAM,CAAC,GAAG,KAA2B,CAAC;IACtC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;AAClG,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,MAAM,CAAC,GAAG,KAA4B,CAAC;IACvC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC;AACrE,CAAC"}
|
package/dist/relay.html
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<!doctype html><html><head><meta charset="utf-8"><title>Preview relay</title></head><body><script>"use strict";(()=>{var a="bfwc:attach",c="bfwc:reattach",
|
|
1
|
+
<!doctype html><html><head><meta charset="utf-8"><title>Preview relay</title></head><body><script>"use strict";(()=>{var a="bfwc:attach",c="bfwc:reattach",n="bfwc:relay-ready",p="bfwc:relay-attach",d="bfwc:relay-reattach",f="bfwc:relay-reload",m="bfwc:relay-spawn";function l(e,t){t.onmessage=r=>e.postMessage({kind:"message",data:r.data}),t.onerror=r=>e.postMessage({kind:"error",message:r.message||"The process worker failed."}),e.onmessage=r=>{let o=r.data;o?.kind==="message"?t.postMessage(o.data):o?.kind==="terminate"&&(t.terminate(),e.close())}}var g="process-worker.js";function i(e){return`bfwc:reload:${e}`}function u(e){if(e.active)return Promise.resolve(e.active);let t=e.installing??e.waiting;return t?new Promise((r,o)=>{t.addEventListener("statechange",()=>{t.state==="activated"&&e.active&&r(e.active),t.state==="redundant"&&o(new Error("The preview service worker failed to install."))})}):Promise.reject(new Error("The preview service worker did not install."))}function E(e){if(typeof BroadcastChannel>"u")return;let t=new BroadcastChannel(i(e));t.postMessage("reload"),t.close()}var s=window.parent;async function R(){let e=navigator.serviceWorker;if(!e)throw new Error("Service workers are unavailable on the preview origin.");let t=await e.register(new URL("./sw.js",location.href).href,{scope:"./"});await u(t),e.addEventListener("message",r=>{r.data?.type===c&&typeof r.data.id=="string"&&s.postMessage({type:d,id:r.data.id},"*")}),addEventListener("message",r=>{if(r.source!==s)return;let o=r.data;o?.type===p&&typeof o.id=="string"&&r.ports[0]?t.active?.postMessage({type:a,id:o.id},[r.ports[0]]):o?.type===f&&typeof o.base=="string"?E(o.base):o?.type===m&&r.ports[0]&&l(r.ports[0],new Worker(new URL(`./${g}`,location.href),{name:"bfwc-process"}))}),s.postMessage({type:n},"*")}R().catch(e=>{s.postMessage({type:n,error:e instanceof Error?e.message:String(e)},"*")});})();
|
|
2
2
|
</script></body></html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process workers on the PREVIEW origin, driven from the host page.
|
|
3
|
+
*
|
|
4
|
+
* A process worker runs whatever the project runs — npm packages, the user's
|
|
5
|
+
* server, an AI-written script. A worker started by the host page shares the
|
|
6
|
+
* host's origin: its IndexedDB, Cache Storage and same-origin requests. In relay
|
|
7
|
+
* mode that is exactly what the preview origin exists to keep apart, so the
|
|
8
|
+
* relay frame starts each worker on ITS origin and this bridges the two over a
|
|
9
|
+
* MessageChannel. The host side is a `WorkerLike`, so the process host cannot
|
|
10
|
+
* tell the difference.
|
|
11
|
+
*/
|
|
12
|
+
import type { WorkerLike } from './node/processHost.js';
|
|
13
|
+
/** Host side: a `WorkerLike` whose worker lives at the far end of `port`. */
|
|
14
|
+
export declare function workerOverPort(port: MessagePort): WorkerLike;
|
|
15
|
+
/** Relay side: run `worker`, relaying both ways over `port` until terminated. */
|
|
16
|
+
export declare function bridgeWorker(port: MessagePort, worker: Worker): void;
|
|
17
|
+
/** The process worker's file name beside `relay.html` on the preview origin. */
|
|
18
|
+
export declare const RELAY_PROCESS_WORKER = "process-worker.js";
|
|
19
|
+
//# sourceMappingURL=relayWorker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relayWorker.d.ts","sourceRoot":"","sources":["../src/relayWorker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAKxD,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAkB5D;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAWpE;AAED,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Host side: a `WorkerLike` whose worker lives at the far end of `port`. */
|
|
2
|
+
export function workerOverPort(port) {
|
|
3
|
+
const worker = {
|
|
4
|
+
onmessage: null,
|
|
5
|
+
onerror: null,
|
|
6
|
+
postMessage(data) {
|
|
7
|
+
port.postMessage({ kind: 'message', data });
|
|
8
|
+
},
|
|
9
|
+
terminate() {
|
|
10
|
+
port.postMessage({ kind: 'terminate' });
|
|
11
|
+
port.close();
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
port.onmessage = (event) => {
|
|
15
|
+
const message = event.data;
|
|
16
|
+
if (message?.kind === 'message')
|
|
17
|
+
worker.onmessage?.({ data: message.data });
|
|
18
|
+
else if (message?.kind === 'error')
|
|
19
|
+
worker.onerror?.({ message: message.message });
|
|
20
|
+
};
|
|
21
|
+
return worker;
|
|
22
|
+
}
|
|
23
|
+
/** Relay side: run `worker`, relaying both ways over `port` until terminated. */
|
|
24
|
+
export function bridgeWorker(port, worker) {
|
|
25
|
+
worker.onmessage = (event) => port.postMessage({ kind: 'message', data: event.data });
|
|
26
|
+
worker.onerror = (event) => port.postMessage({ kind: 'error', message: event.message || 'The process worker failed.' });
|
|
27
|
+
port.onmessage = (event) => {
|
|
28
|
+
const message = event.data;
|
|
29
|
+
if (message?.kind === 'message')
|
|
30
|
+
worker.postMessage(message.data);
|
|
31
|
+
else if (message?.kind === 'terminate') {
|
|
32
|
+
worker.terminate();
|
|
33
|
+
port.close();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** The process worker's file name beside `relay.html` on the preview origin. */
|
|
38
|
+
export const RELAY_PROCESS_WORKER = 'process-worker.js';
|
|
39
|
+
//# sourceMappingURL=relayWorker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relayWorker.js","sourceRoot":"","sources":["../src/relayWorker.ts"],"names":[],"mappings":"AAgBA,6EAA6E;AAC7E,MAAM,UAAU,cAAc,CAAC,IAAiB;IAC9C,MAAM,MAAM,GAAe;QACzB,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,IAAI;QACb,WAAW,CAAC,IAAI;YACd,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAqB,CAAC,CAAC;QACjE,CAAC;QACD,SAAS;YACP,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAqB,CAAC,CAAC;YAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;KACF,CAAC;IACF,IAAI,CAAC,SAAS,GAAG,CAAC,KAA+B,EAAE,EAAE;QACnD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;QAC3B,IAAI,OAAO,EAAE,IAAI,KAAK,SAAS;YAAE,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;aACvE,IAAI,OAAO,EAAE,IAAI,KAAK,OAAO;YAAE,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,IAAiB,EAAE,MAAc;IAC5D,MAAM,CAAC,SAAS,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAuB,CAAC,CAAC;IAC3G,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,4BAA4B,EAAuB,CAAC,CAAC;IAC7I,IAAI,CAAC,SAAS,GAAG,CAAC,KAA6B,EAAE,EAAE;QACjD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;QAC3B,IAAI,OAAO,EAAE,IAAI,KAAK,SAAS;YAAE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;aAC7D,IAAI,OAAO,EAAE,IAAI,KAAK,WAAW,EAAE,CAAC;YACvC,MAAM,CAAC,SAAS,EAAE,CAAC;YACnB,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC"}
|
package/dist/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";(()=>{var
|
|
1
|
+
"use strict";(()=>{var v="__bfwc",x=/^[A-Za-z0-9_-]+$/,P="bfwc:attach",A="bfwc:reattach";var C="__port";function g(t){return t.startsWith(`/${C}/`)}function b(t){let r=t.endsWith("/")?t:t+"/";return r.endsWith(`/${v}/`)?r:`${r}${v}/`}function T(t){let r=t;return!!r&&r.type==="response"&&typeof r.reqId=="number"}var E={"content-type":"text/plain; charset=utf-8","cache-control":"no-store"};function L(t){let r=b(t.scopePath),o=t.timeoutMs??3e4,l=t.reattachWaitMs??3e3,p=new Map,a=new Map,u=new Map,w=0;function y(e,s){p.set(e,s),s.onmessage=n=>{T(n.data)&&(u.get(n.data.reqId)?.(n.data),u.delete(n.data.reqId))};for(let n of a.get(e)??[])n(s);a.delete(e)}function h(e){if(!e.pathname.startsWith(r))return null;let s=e.pathname.slice(r.length),n=s.indexOf("/"),i=n<0?s:s.slice(0,n);return!i||!x.test(i)?null:{id:i,path:n<0?"/":s.slice(n),search:e.search}}async function W(e){let s=p.get(e);if(s)return s;let n=new Promise(i=>{let c=a.get(e)??[];c.push(i),a.set(e,c),setTimeout(()=>i(void 0),l)});return await t.requestReattach(e),n}let f=new Map;function k(e,s){if(!e||!g(s.path))return;let n=/^\/__port\/\d+/.exec(s.path)[0];f.set(e,{id:s.id,prefix:n}),f.size>200&&f.delete(f.keys().next().value)}function q(e,s,n){let i=h(e);if(i||!s||e.origin!==n)return i;let c=f.get(s);return c?{id:c.id,path:c.prefix+e.pathname,search:e.search}:null}async function M(e,s){let n=await W(e.id);if(!n)return{status:503,headers:E,body:"This preview is not running in any open tab."};let i=++w;return new Promise(c=>{let _=setTimeout(()=>{u.delete(i),p.get(e.id)===n&&p.delete(e.id),c({status:504,headers:E,body:"The preview did not answer in time."})},o);u.set(i,R=>{clearTimeout(_),c({status:R.status,headers:R.headers,body:R.body})}),n.postMessage({type:"request",reqId:i,path:e.path,search:e.search,...s})})}return{attach:y,match:h,matchForClient:q,rememberClient:k,respond:M,detach:e=>p.delete(e)}}var d=globalThis,m=L({scopePath:new URL(d.registration.scope).pathname,async requestReattach(t){let r=await d.clients.matchAll({type:"window",includeUncontrolled:!0});for(let o of r)o.postMessage({type:A,id:t})}});d.addEventListener("install",()=>{d.skipWaiting()});d.addEventListener("activate",t=>{t.waitUntil(d.clients.claim())});d.addEventListener("message",t=>{let r=t.data,o=t.ports[0];r?.type===P&&typeof r.id=="string"&&o&&m.attach(r.id,o)});var I=new Set([101,204,205,304]);d.addEventListener("fetch",t=>{let{request:r}=t,o=m.matchForClient(new URL(r.url),t.clientId,new URL(d.registration.scope).origin);if(!o)return;let l=g(o.path);r.method!=="GET"&&!l||(l&&m.rememberClient(t.resultingClientId,o),t.respondWith((async()=>{let p;if(l){let u={};r.headers.forEach((y,h)=>u[h]=y);let w=r.method==="GET"||r.method==="HEAD"?null:await r.arrayBuffer();p={method:r.method,headers:u,body:w}}let a=await m.respond(o,p);return new Response(I.has(a.status)?null:a.body,{status:a.status,headers:a.headers})})()))});})();
|
package/dist/swRouter.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export interface PreviewTarget {
|
|
|
10
10
|
path: string;
|
|
11
11
|
search: string;
|
|
12
12
|
}
|
|
13
|
+
/** The parts of a request a virtual server needs (method, headers, body). */
|
|
14
|
+
export interface RequestDetails {
|
|
15
|
+
method: string;
|
|
16
|
+
headers: Record<string, string>;
|
|
17
|
+
body: ArrayBuffer | null;
|
|
18
|
+
}
|
|
13
19
|
export interface RoutedResponse {
|
|
14
20
|
status: number;
|
|
15
21
|
headers: Record<string, string>;
|
|
@@ -37,7 +43,9 @@ export interface SwRouterOptions {
|
|
|
37
43
|
export declare function createSwRouter(options: SwRouterOptions): {
|
|
38
44
|
attach: (id: string, port: PortLike) => void;
|
|
39
45
|
match: (url: URL) => PreviewTarget | null;
|
|
40
|
-
|
|
46
|
+
matchForClient: (url: URL, clientId: string | undefined, origin: string) => PreviewTarget | null;
|
|
47
|
+
rememberClient: (clientId: string | undefined, target: PreviewTarget) => void;
|
|
48
|
+
respond: (target: PreviewTarget, details?: RequestDetails) => Promise<RoutedResponse>;
|
|
41
49
|
detach: (id: string) => boolean;
|
|
42
50
|
};
|
|
43
51
|
export type SwRouter = ReturnType<typeof createSwRouter>;
|
package/dist/swRouter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swRouter.d.ts","sourceRoot":"","sources":["../src/swRouter.ts"],"names":[],"mappings":"AAEA,4FAA4F;AAC5F,MAAM,WAAW,QAAQ;IACvB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClD,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe;iBASjC,MAAM,QAAQ,QAAQ,KAAG,IAAI;iBAW7B,GAAG,KAAG,aAAa,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"swRouter.d.ts","sourceRoot":"","sources":["../src/swRouter.ts"],"names":[],"mappings":"AAEA,4FAA4F;AAC5F,MAAM,WAAW,QAAQ;IACvB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClD,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe;iBASjC,MAAM,QAAQ,QAAQ,KAAG,IAAI;iBAW7B,GAAG,KAAG,aAAa,GAAG,IAAI;0BAuCjB,GAAG,YAAY,MAAM,GAAG,SAAS,UAAU,MAAM,KAAG,aAAa,GAAG,IAAI;+BAPnE,MAAM,GAAG,SAAS,UAAU,aAAa,KAAG,IAAI;sBAcnD,aAAa,YAAY,cAAc,KAAG,OAAO,CAAC,cAAc,CAAC;iBAkBlB,MAAM;EACrF;AAED,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC"}
|
package/dist/swRouter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PREVIEW_ID, isWireResponse, previewPrefix } from './protocol.js';
|
|
1
|
+
import { PREVIEW_ID, isPortPath, isWireResponse, previewPrefix } from './protocol.js';
|
|
2
2
|
const TEXT = { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' };
|
|
3
3
|
/**
|
|
4
4
|
* Request routing for the preview service worker, with no worker globals in it.
|
|
@@ -54,7 +54,29 @@ export function createSwRouter(options) {
|
|
|
54
54
|
await options.requestReattach(id);
|
|
55
55
|
return arrived;
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Pages served by a virtual server (`/__bfwc/<id>/__port/3000/`) write absolute
|
|
59
|
+
* URLs (`/style.css`, `fetch('/api')`). A service worker sees every request its
|
|
60
|
+
* clients make, so a same-origin request from such a page that falls outside
|
|
61
|
+
* the preview prefix is mapped back under that server's path.
|
|
62
|
+
*/
|
|
63
|
+
const clientServers = new Map();
|
|
64
|
+
function rememberClient(clientId, target) {
|
|
65
|
+
if (!clientId || !isPortPath(target.path))
|
|
66
|
+
return;
|
|
67
|
+
const prefix = /^\/__port\/\d+/.exec(target.path)[0];
|
|
68
|
+
clientServers.set(clientId, { id: target.id, prefix });
|
|
69
|
+
if (clientServers.size > 200)
|
|
70
|
+
clientServers.delete(clientServers.keys().next().value);
|
|
71
|
+
}
|
|
72
|
+
function matchForClient(url, clientId, origin) {
|
|
73
|
+
const direct = match(url);
|
|
74
|
+
if (direct || !clientId || url.origin !== origin)
|
|
75
|
+
return direct;
|
|
76
|
+
const server = clientServers.get(clientId);
|
|
77
|
+
return server ? { id: server.id, path: server.prefix + url.pathname, search: url.search } : null;
|
|
78
|
+
}
|
|
79
|
+
async function respond(target, details) {
|
|
58
80
|
const port = await portFor(target.id);
|
|
59
81
|
if (!port)
|
|
60
82
|
return { status: 503, headers: TEXT, body: 'This preview is not running in any open tab.' };
|
|
@@ -70,9 +92,9 @@ export function createSwRouter(options) {
|
|
|
70
92
|
clearTimeout(timer);
|
|
71
93
|
resolve({ status: response.status, headers: response.headers, body: response.body });
|
|
72
94
|
});
|
|
73
|
-
port.postMessage({ type: 'request', reqId, path: target.path, search: target.search });
|
|
95
|
+
port.postMessage({ type: 'request', reqId, path: target.path, search: target.search, ...details });
|
|
74
96
|
});
|
|
75
97
|
}
|
|
76
|
-
return { attach, match, respond, detach: (id) => ports.delete(id) };
|
|
98
|
+
return { attach, match, matchForClient, rememberClient, respond, detach: (id) => ports.delete(id) };
|
|
77
99
|
}
|
|
78
100
|
//# sourceMappingURL=swRouter.js.map
|
package/dist/swRouter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swRouter.js","sourceRoot":"","sources":["../src/swRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAqB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"swRouter.js","sourceRoot":"","sources":["../src/swRouter.ts"],"names":[],"mappings":"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"}
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WorkerLike } from './node/processHost.js';
|
|
1
2
|
export interface PreviewTransport {
|
|
2
3
|
/** The origin previews are served from. */
|
|
3
4
|
readonly origin: string;
|
|
@@ -9,6 +10,12 @@ export interface PreviewTransport {
|
|
|
9
10
|
onReattach(listener: (id: string) => void): () => void;
|
|
10
11
|
/** Reload every frame showing the preview at `base`. */
|
|
11
12
|
reload(base: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Start a process worker on the preview origin. Only the relay has one to
|
|
15
|
+
* offer; same-origin mode runs processes on the host's origin, which is the
|
|
16
|
+
* trust it already chose.
|
|
17
|
+
*/
|
|
18
|
+
createWorker?(): WorkerLike;
|
|
12
19
|
dispose(): void;
|
|
13
20
|
}
|
|
14
21
|
export declare function sameOriginTransport(serviceWorkerUrl: string, scopeOption?: string): Promise<PreviewTransport>;
|
package/dist/transport.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAIxD,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C,iDAAiD;IACjD,UAAU,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACvD,wDAAwD;IACxD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;OAIG;IACH,YAAY,CAAC,IAAI,UAAU,CAAC;IAC5B,OAAO,IAAI,IAAI,CAAC;CACjB;AAKD,wBAAsB,mBAAmB,CAAC,gBAAgB,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA6BnH;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA6DhF"}
|
package/dist/transport.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* then never read the host's cookies, storage or DOM, which is what an editor
|
|
11
11
|
* running AI-written code or arbitrary npm packages needs.
|
|
12
12
|
*/
|
|
13
|
-
import { ATTACH, REATTACH, RELAY_ATTACH, RELAY_READY, RELAY_REATTACH, RELAY_RELOAD, } from './protocol.js';
|
|
13
|
+
import { ATTACH, REATTACH, RELAY_ATTACH, RELAY_READY, RELAY_REATTACH, RELAY_RELOAD, RELAY_SPAWN, } from './protocol.js';
|
|
14
|
+
import { workerOverPort } from './relayWorker.js';
|
|
14
15
|
import { broadcastReload, waitForActive } from './worker.js';
|
|
15
16
|
/** How long the relay has to register its worker before boot gives up. */
|
|
16
17
|
const RELAY_TIMEOUT_MS = 15_000;
|
|
@@ -96,6 +97,11 @@ export async function relayTransport(relayUrl) {
|
|
|
96
97
|
return () => listeners.delete(listener);
|
|
97
98
|
},
|
|
98
99
|
reload: (base) => post({ type: RELAY_RELOAD, base }),
|
|
100
|
+
createWorker() {
|
|
101
|
+
const channel = new MessageChannel();
|
|
102
|
+
post({ type: RELAY_SPAWN }, [channel.port2]);
|
|
103
|
+
return workerOverPort(channel.port1);
|
|
104
|
+
},
|
|
99
105
|
dispose() {
|
|
100
106
|
window.removeEventListener('message', onRelayMessage);
|
|
101
107
|
listeners.clear();
|
package/dist/transport.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,cAAc,EACd,YAAY,
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanhogg/builderforce-webcontainers",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.3",
|
|
4
4
|
"description": "BuilderForce WebContainers — browser runtime. Boots an in-browser dev server for a project held in memory and serves it to an iframe through a service worker. Open source, no COOP/COEP, no session caps.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -10,12 +10,19 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
12
|
},
|
|
13
|
+
"./check": {
|
|
14
|
+
"types": "./dist/check/index.d.ts",
|
|
15
|
+
"import": "./dist/check/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./check/worker.js": "./dist/check/worker.js",
|
|
18
|
+
"./node/worker.js": "./dist/node/worker.js",
|
|
13
19
|
"./assets": {
|
|
14
20
|
"types": "./dist/assets.d.ts",
|
|
15
21
|
"import": "./dist/assets.js"
|
|
16
22
|
},
|
|
17
23
|
"./sw.js": "./dist/sw.js",
|
|
18
|
-
"./relay.html": "./dist/relay.html"
|
|
24
|
+
"./relay.html": "./dist/relay.html",
|
|
25
|
+
"./process-worker.js": "./dist/process-worker.js"
|
|
19
26
|
},
|
|
20
27
|
"type": "module",
|
|
21
28
|
"sideEffects": false,
|
|
@@ -58,10 +65,12 @@
|
|
|
58
65
|
},
|
|
59
66
|
"dependencies": {
|
|
60
67
|
"esbuild-wasm": "^0.24.0",
|
|
61
|
-
"@seanhogg/builderforce-webcontainers-core": "^2026.10.
|
|
68
|
+
"@seanhogg/builderforce-webcontainers-core": "^2026.10.3"
|
|
62
69
|
},
|
|
63
70
|
"devDependencies": {
|
|
71
|
+
"@vue/compiler-sfc": "^3.5.0",
|
|
64
72
|
"esbuild": "^0.24.0",
|
|
73
|
+
"svelte": "^5.0.0",
|
|
65
74
|
"typescript": "^5.5.0",
|
|
66
75
|
"vitest": "^2.1.0"
|
|
67
76
|
},
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { FetchLike, FetchResponseLike } from '@seanhogg/builderforce-webcontainers-core/check';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `fetch` for type definitions, backed by Cache Storage so a new tab (or a new
|
|
5
|
+
* worker) does not download lib.dom.d.ts and React's types again.
|
|
6
|
+
*
|
|
7
|
+
* Only immutable URLs are cached: lib files under a pinned TypeScript version and
|
|
8
|
+
* package files at an exact `name@x.y.z`. Range lookups (`react@^18`) always go to
|
|
9
|
+
* the network, so a new release is picked up. A cached entry keeps the final URL
|
|
10
|
+
* and the `X-TypeScript-Types` header, the two things acquisition reads besides
|
|
11
|
+
* the body.
|
|
12
|
+
*/
|
|
13
|
+
const CACHE_NAME = 'bfwc-types-v1';
|
|
14
|
+
const EXACT_VERSION = /@\d+\.\d+\.\d+(?:[-+][\w.]+)?\//;
|
|
15
|
+
const FINAL_URL = 'x-bfwc-final-url';
|
|
16
|
+
const TYPES = 'x-typescript-types';
|
|
17
|
+
|
|
18
|
+
interface CacheLike {
|
|
19
|
+
match(url: string): Promise<Response | undefined>;
|
|
20
|
+
put(url: string, response: Response): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function wrap(url: string, body: string, types: string | null): FetchResponseLike {
|
|
24
|
+
return { ok: true, url, headers: { get: (name) => (name.toLowerCase() === TYPES ? types : null) }, text: async () => body };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createCachedFetch(base: typeof fetch = fetch): FetchLike {
|
|
28
|
+
let cache: Promise<CacheLike | undefined> | undefined;
|
|
29
|
+
const open = () =>
|
|
30
|
+
(cache ??= typeof caches === 'undefined' ? Promise.resolve(undefined) : caches.open(CACHE_NAME).catch(() => undefined));
|
|
31
|
+
|
|
32
|
+
return async (url) => {
|
|
33
|
+
const cacheable = EXACT_VERSION.test(url);
|
|
34
|
+
const store = cacheable ? await open() : undefined;
|
|
35
|
+
const hit = await store?.match(url).catch(() => undefined);
|
|
36
|
+
if (hit) return wrap(hit.headers.get(FINAL_URL) ?? url, await hit.text(), hit.headers.get(TYPES));
|
|
37
|
+
|
|
38
|
+
const res = await base(url);
|
|
39
|
+
if (!res.ok || !store) return res;
|
|
40
|
+
const body = await res.text();
|
|
41
|
+
const types = res.headers.get(TYPES);
|
|
42
|
+
const headers: Record<string, string> = { [FINAL_URL]: res.url || url };
|
|
43
|
+
if (types) headers[TYPES] = types;
|
|
44
|
+
await store.put(url, new Response(body, { headers })).catch(() => undefined);
|
|
45
|
+
return wrap(res.url || url, body, types);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@seanhogg/builderforce-webcontainers/check` — type-check a project in a Web
|
|
3
|
+
* Worker, with no install and no server.
|
|
4
|
+
*
|
|
5
|
+
* The worker loads the TypeScript compiler from a CDN (pinned below) and fetches
|
|
6
|
+
* dependency types from esm.sh, caching both, so the page's main thread never
|
|
7
|
+
* parses lib.dom.d.ts. Diagnostics come back as plain data (see CheckDiagnostic).
|
|
8
|
+
*/
|
|
9
|
+
import { VirtualFileSystem, type FlatFiles } from '@seanhogg/builderforce-webcontainers-core';
|
|
10
|
+
import type { TypeSources, TypecheckResult } from '@seanhogg/builderforce-webcontainers-core/check';
|
|
11
|
+
import { CHECK, CHECK_RESULT, isCheckInput, isCheckReply, type CheckReply } from './protocol.js';
|
|
12
|
+
|
|
13
|
+
export type { CheckDiagnostic, DiagnosticCategory, TypecheckResult } from '@seanhogg/builderforce-webcontainers-core/check';
|
|
14
|
+
export { formatDiagnostic } from '@seanhogg/builderforce-webcontainers-core/check';
|
|
15
|
+
|
|
16
|
+
/** The TypeScript the checker runs. Pinned, so results do not change under a project. */
|
|
17
|
+
export const TYPESCRIPT_VERSION = '5.9.3';
|
|
18
|
+
export const DEFAULT_TYPESCRIPT_URL = `https://cdn.jsdelivr.net/npm/typescript@${TYPESCRIPT_VERSION}/lib/typescript.js`;
|
|
19
|
+
|
|
20
|
+
export interface CheckerOptions {
|
|
21
|
+
/**
|
|
22
|
+
* The worker script (this package's `dist/check/worker.js`). By default it is
|
|
23
|
+
* resolved beside this module, a pattern Vite, webpack 5 and Next.js all bundle.
|
|
24
|
+
*/
|
|
25
|
+
workerUrl?: string | URL;
|
|
26
|
+
/** Build the Worker yourself (a CSP that needs a specific origin, a test double). */
|
|
27
|
+
createWorker?: () => Worker;
|
|
28
|
+
/** A classic-script build of TypeScript that sets `self.ts`. Default: jsDelivr, {@link TYPESCRIPT_VERSION}. */
|
|
29
|
+
typescriptUrl?: string;
|
|
30
|
+
/** Where types, libs and tsconfig packages come from. Libs default to the loaded TypeScript's version. */
|
|
31
|
+
sources?: Partial<TypeSources>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CheckResult extends TypecheckResult {
|
|
35
|
+
durationMs: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Checker {
|
|
39
|
+
/** Type-check the project. Checks queue; each resolves with its own result. */
|
|
40
|
+
check(files: VirtualFileSystem | FlatFiles): Promise<CheckResult>;
|
|
41
|
+
/** Stop the worker; pending checks reject. */
|
|
42
|
+
dispose(): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Only the files that can affect checking, as text: assets never cross to the worker. */
|
|
46
|
+
export function checkInputs(files: VirtualFileSystem | FlatFiles): Record<string, string> {
|
|
47
|
+
let fs: VirtualFileSystem;
|
|
48
|
+
if (files instanceof VirtualFileSystem) fs = files;
|
|
49
|
+
else {
|
|
50
|
+
fs = new VirtualFileSystem();
|
|
51
|
+
fs.mount(files);
|
|
52
|
+
}
|
|
53
|
+
const out: Record<string, string> = {};
|
|
54
|
+
for (const path of fs.list()) if (isCheckInput(path)) out[path.slice(1)] = fs.readText(path) ?? '';
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createChecker(options: CheckerOptions = {}): Checker {
|
|
59
|
+
const worker = options.createWorker?.() ?? new Worker(options.workerUrl ?? new URL('./worker.js', import.meta.url));
|
|
60
|
+
const pending = new Map<number, { resolve(result: CheckResult): void; reject(error: Error): void }>();
|
|
61
|
+
let nextId = 0;
|
|
62
|
+
|
|
63
|
+
worker.addEventListener('message', (event: MessageEvent) => {
|
|
64
|
+
if (!isCheckReply(event.data)) return;
|
|
65
|
+
const reply: CheckReply = event.data;
|
|
66
|
+
const waiter = pending.get(reply.id);
|
|
67
|
+
pending.delete(reply.id);
|
|
68
|
+
if (reply.type === CHECK_RESULT) waiter?.resolve({ ...reply.result, durationMs: reply.durationMs });
|
|
69
|
+
else waiter?.reject(new Error(reply.message));
|
|
70
|
+
});
|
|
71
|
+
worker.addEventListener('error', (event: ErrorEvent) => {
|
|
72
|
+
const error = new Error(event.message || 'The check worker failed to start.');
|
|
73
|
+
for (const waiter of pending.values()) waiter.reject(error);
|
|
74
|
+
pending.clear();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
check(files) {
|
|
79
|
+
const id = ++nextId;
|
|
80
|
+
return new Promise<CheckResult>((resolve, reject) => {
|
|
81
|
+
pending.set(id, { resolve, reject });
|
|
82
|
+
worker.postMessage({
|
|
83
|
+
type: CHECK,
|
|
84
|
+
id,
|
|
85
|
+
files: checkInputs(files),
|
|
86
|
+
typescriptUrl: options.typescriptUrl ?? DEFAULT_TYPESCRIPT_URL,
|
|
87
|
+
sources: options.sources,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
dispose() {
|
|
92
|
+
worker.terminate();
|
|
93
|
+
const error = new Error('The checker was disposed.');
|
|
94
|
+
for (const waiter of pending.values()) waiter.reject(error);
|
|
95
|
+
pending.clear();
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages between `createChecker()` (page) and the check worker. Files travel as
|
|
3
|
+
* strings: only source and config files are sent, never assets.
|
|
4
|
+
*/
|
|
5
|
+
import type { TypeSources, TypecheckResult } from '@seanhogg/builderforce-webcontainers-core/check';
|
|
6
|
+
|
|
7
|
+
export const CHECK = 'bfwc:check';
|
|
8
|
+
export const CHECK_RESULT = 'bfwc:check-result';
|
|
9
|
+
export const CHECK_ERROR = 'bfwc:check-error';
|
|
10
|
+
|
|
11
|
+
export interface CheckRequest {
|
|
12
|
+
type: typeof CHECK;
|
|
13
|
+
id: number;
|
|
14
|
+
files: Record<string, string>;
|
|
15
|
+
/** Classic script URL of the TypeScript compiler (sets `self.ts`). */
|
|
16
|
+
typescriptUrl: string;
|
|
17
|
+
sources?: Partial<TypeSources>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type CheckReply =
|
|
21
|
+
| { type: typeof CHECK_RESULT; id: number; result: TypecheckResult; durationMs: number }
|
|
22
|
+
| { type: typeof CHECK_ERROR; id: number; message: string };
|
|
23
|
+
|
|
24
|
+
/** Paths whose contents can affect type-checking. */
|
|
25
|
+
const CHECKED = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|json)$/i;
|
|
26
|
+
|
|
27
|
+
export function isCheckInput(path: string): boolean {
|
|
28
|
+
return CHECKED.test(path) && !/(^|\/)(node_modules|dist)\//.test(path);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isCheckReply(value: unknown): value is CheckReply {
|
|
32
|
+
const v = value as CheckReply | null;
|
|
33
|
+
return !!v && (v.type === CHECK_RESULT || v.type === CHECK_ERROR) && typeof v.id === 'number';
|
|
34
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The check worker (bundled to dist/check/worker.js as one classic script). It
|
|
3
|
+
* loads the TypeScript compiler with `importScripts` — TypeScript's own UMD
|
|
4
|
+
* build, which sets `self.ts` — and keeps one TypeStore for its lifetime, so
|
|
5
|
+
* every check after the first reuses fetched and parsed declarations.
|
|
6
|
+
*
|
|
7
|
+
* Worker globals are typed locally, as in sw.ts: `lib.webworker` cannot share a
|
|
8
|
+
* compilation with the DOM lib the rest of the package needs.
|
|
9
|
+
*/
|
|
10
|
+
import type * as TS from 'typescript';
|
|
11
|
+
import { TypeStore, typecheckProject } from '@seanhogg/builderforce-webcontainers-core/check';
|
|
12
|
+
import { createCachedFetch } from './cachedFetch.js';
|
|
13
|
+
import { CHECK, CHECK_ERROR, CHECK_RESULT, type CheckReply, type CheckRequest } from './protocol.js';
|
|
14
|
+
|
|
15
|
+
interface WorkerGlobalLike {
|
|
16
|
+
ts?: typeof TS;
|
|
17
|
+
importScripts(url: string): void;
|
|
18
|
+
postMessage(message: CheckReply): void;
|
|
19
|
+
addEventListener(type: 'message', listener: (event: { data: unknown }) => void): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const worker = globalThis as unknown as WorkerGlobalLike;
|
|
23
|
+
const store = new TypeStore();
|
|
24
|
+
const fetchTypes = createCachedFetch();
|
|
25
|
+
let loadedFrom: string | undefined;
|
|
26
|
+
|
|
27
|
+
function typescript(url: string): typeof TS {
|
|
28
|
+
if (!worker.ts || loadedFrom !== url) {
|
|
29
|
+
worker.importScripts(url);
|
|
30
|
+
loadedFrom = url;
|
|
31
|
+
}
|
|
32
|
+
if (!worker.ts) throw new Error(`${url} did not define the TypeScript compiler.`);
|
|
33
|
+
return worker.ts;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
worker.addEventListener('message', (event) => {
|
|
37
|
+
const request = event.data as CheckRequest | null;
|
|
38
|
+
if (request?.type !== CHECK) return;
|
|
39
|
+
const started = Date.now();
|
|
40
|
+
void (async () => {
|
|
41
|
+
try {
|
|
42
|
+
const ts = typescript(request.typescriptUrl);
|
|
43
|
+
const result = await typecheckProject(ts, request.files, { fetch: fetchTypes, store, sources: request.sources });
|
|
44
|
+
worker.postMessage({ type: CHECK_RESULT, id: request.id, result, durationMs: Date.now() - started });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
worker.postMessage({ type: CHECK_ERROR, id: request.id, message: error instanceof Error ? error.message : String(error) });
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
});
|