Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"imports": {
"#packScope": {
"types": "./src/packs/packScope.node.ts",
"workerd": "./build/src/packs/packScope.node.js",
"browser": "./build/src/packs/packScope.browser.js",
"default": "./build/src/packs/packScope.node.js"
}
Expand Down
24 changes: 24 additions & 0 deletions packages/benchmark/src/packs/__tests__/packScope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ describe.each([
});
});

// A Cloudflare build asks for the `browser` condition as well as `workerd`, so
// a map that answers `browser` first hands a worker the stack scope — which
// drops the active pack at the first await, silently, in the one runtime that
// serves concurrent runs. Order is the whole fix, and nothing in a type or a
// unit test would catch it: the app-website worker bundle shipped StackScope
// until `workerd` was added ahead of `browser`.
describe("#packScope conditions", () => {
const conditions = JSON.parse(
readFileSync(new URL("../../../package.json", import.meta.url), "utf8")
).imports["#packScope"] as Record<string, string>;

it("answers workerd with the node scope, before browser is considered", () => {
const order = Object.keys(conditions);

expect(conditions.workerd).toBe(conditions.default);
expect(conditions.workerd).toMatch(/packScope\.node\.js$/);
expect(order.indexOf("workerd")).toBeLessThan(order.indexOf("browser"));
});

it("keeps the browser on the node-free scope", () => {
expect(conditions.browser).toMatch(/packScope\.browser\.js$/);
});
});

// packs.ts is reached from the browser through Mechanism and RiskCategory, so a
// static node builtin in it breaks every client bundle that touches the barrel:
// the build externalizes the builtin to a stub and fails on the named import.
Expand Down
4 changes: 3 additions & 1 deletion packages/benchmark/src/packs/packScope.browser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {PackScope} from "./packScope.js";

/**
* The browser stand-in, selected by the `browser` condition on `#packScope`.
* The browser stand-in, selected by the `browser` condition on `#packScope`
* — which a Cloudflare build also asks for, so the `imports` map answers
* `workerd` with the node implementation before `browser` is considered.
*
* A page renders against one pack, so there is no concurrent work to keep
* apart and a save/restore stack is enough. It is accurate for the only shape
Expand Down
16 changes: 10 additions & 6 deletions packages/benchmark/src/packs/packScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
*
* `packs.ts` reaches an implementation through the `#packScope` subpath
* import, which resolves to `packScope.node.ts` everywhere except a browser
* bundle (see the `imports` map in package.json). Node's `AsyncLocalStorage`
* lives behind `node:async_hooks`, and a static import of that anywhere the
* browser can reach breaks bundlers: the client build externalizes the builtin
* to a stub and then fails on the named import. Every browser consumer of this
* package reaches `Packs.current()` through `Mechanism` and `RiskCategory`, so
* that path has to stay free of node builtins.
* bundle (see the `imports` map in package.json). A Cloudflare build asks for
* the `browser` condition as well as `workerd`, and a worker needs the real
* async-context scope, so `workerd` is listed ahead of `browser` there.
*
* Node's `AsyncLocalStorage` lives behind `node:async_hooks`, and a static
* import of that anywhere the browser can reach breaks bundlers: the client
* build externalizes the builtin to a stub and then fails on the named import.
* Every browser consumer of this package reaches `Packs.current()` through
* `Mechanism` and `RiskCategory`, so that path has to stay free of node
* builtins.
*/
export interface PackScope<T> {
getStore(): T | undefined;
Expand Down
Loading