Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 02f8b9b

Browse files
committed
Add additional __STATIC_CONTENT_MANIFEST module for Workers Sites
Ref: cloudflare/wrangler-legacy#2126
1 parent a94c6bd commit 02f8b9b

File tree

12 files changed

+122
-15
lines changed

12 files changed

+122
-15
lines changed

packages/core/src/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from "fs/promises";
22
import path from "path";
33
import {
4+
AdditionalModules,
45
BeforeSetupResult,
56
Compatibility,
67
Context,
@@ -411,6 +412,7 @@ export class MiniflareCore<
411412

412413
let script: ScriptBlueprint | undefined = undefined;
413414
let requiresModuleExports = false;
415+
const additionalModules: AdditionalModules = {};
414416
for (const [name] of this.#plugins) {
415417
// Run beforeReload hook
416418
const instance = this.#instances![name];
@@ -419,7 +421,7 @@ export class MiniflareCore<
419421
await instance.beforeReload();
420422
}
421423

422-
// Build global scope and extract script blueprints
424+
// Build global scope, extracting script blueprints and additional modules
423425
const result = this.#setupResults!.get(name);
424426
Object.assign(globals, result?.globals);
425427
Object.assign(bindings, result?.bindings);
@@ -430,6 +432,9 @@ export class MiniflareCore<
430432
script = result.script;
431433
}
432434
if (result?.requiresModuleExports) requiresModuleExports = true;
435+
if (result?.additionalModules) {
436+
Object.assign(additionalModules, result.additionalModules);
437+
}
433438

434439
// Extract watch paths
435440
const beforeSetupWatch = this.#beforeSetupWatch!.get(name);
@@ -466,7 +471,12 @@ export class MiniflareCore<
466471
}
467472

468473
this.log.verbose("Running script...");
469-
res = await this.#scriptRunner.run(globalScope, script, rules);
474+
res = await this.#scriptRunner.run(
475+
globalScope,
476+
script,
477+
rules,
478+
additionalModules
479+
);
470480

471481
this.#scriptWatchPaths.clear();
472482
this.#scriptWatchPaths.add(script.filePath);

packages/core/src/standards/event.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
210210
if (modules) {
211211
// Error when trying to access bindings using the global in modules mode
212212
for (const key of Object.keys(bindings)) {
213+
// @cloudflare/kv-asset-handler checks the typeof these keys which
214+
// triggers an access. We want this typeof to return "undefined", not
215+
// throw, so skip these specific keys.
216+
if (key === "__STATIC_CONTENT" || key === "__STATIC_CONTENT_MANIFEST") {
217+
break;
218+
}
219+
213220
Object.defineProperty(this, key, {
214221
get() {
215222
throw new ReferenceError(

packages/runner-vm/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import vm from "vm";
22
import {
3+
AdditionalModules,
34
Context,
45
ProcessedModuleRule,
56
ScriptBlueprint,
@@ -41,7 +42,8 @@ export class VMScriptRunner implements ScriptRunner {
4142
async run(
4243
globalScope: Context,
4344
blueprint: ScriptBlueprint,
44-
modulesRules?: ProcessedModuleRule[]
45+
modulesRules?: ProcessedModuleRule[],
46+
additionalModules?: AdditionalModules
4547
): Promise<ScriptRunnerResult> {
4648
// If we're using modules, make sure --experimental-vm-modules is enabled
4749
if (modulesRules && !("SourceTextModule" in vm)) {
@@ -51,7 +53,8 @@ export class VMScriptRunner implements ScriptRunner {
5153
);
5254
}
5355
// Also build a linker if we're using modules
54-
const linker = modulesRules && new ModuleLinker(modulesRules);
56+
const linker =
57+
modulesRules && new ModuleLinker(modulesRules, additionalModules ?? {});
5558

5659
// Add proxied globals so cross-realm instanceof works correctly.
5760
// globalScope will be fresh for each call of run so it's fine to mutate it.

packages/runner-vm/src/linker.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fs from "fs/promises";
33
import path from "path";
44
import vm from "vm";
55
import {
6+
AdditionalModules,
67
Context,
78
ProcessedModuleRule,
89
STRING_SCRIPT_PATH,
@@ -19,7 +20,10 @@ export class ModuleLinker {
1920
readonly #moduleCache = new Map<string, vm.Module>();
2021
readonly #cjsModuleCache = new Map<string, CommonJSModule>();
2122

22-
constructor(private moduleRules: ProcessedModuleRule[]) {
23+
constructor(
24+
private moduleRules: ProcessedModuleRule[],
25+
private additionalModules: AdditionalModules
26+
) {
2327
this.linker = this.linker.bind(this);
2428
}
2529

@@ -45,13 +49,35 @@ export class ModuleLinker {
4549
);
4650
}
4751

48-
// Get path to specified module relative to referencing module
49-
const identifier = path.resolve(path.dirname(referencing.identifier), spec);
52+
const additionalModule = this.additionalModules[spec];
53+
const identifier = additionalModule
54+
? spec
55+
: // Get path to specified module relative to referencing module
56+
path.resolve(path.dirname(referencing.identifier), spec);
57+
5058
// If we've already seen a module with the same identifier, return it, to
5159
// handle import cycles
5260
const cached = this.#moduleCache.get(identifier);
5361
if (cached) return cached;
5462

63+
const moduleOptions = { identifier, context: referencing.context };
64+
let module: vm.Module;
65+
66+
// If this is an additional module, construct and return it immediately
67+
if (additionalModule) {
68+
module = new vm.SyntheticModule(
69+
Object.keys(additionalModule),
70+
function () {
71+
for (const [key, value] of Object.entries(additionalModule)) {
72+
this.setExport(key, value);
73+
}
74+
},
75+
moduleOptions
76+
);
77+
this.#moduleCache.set(identifier, module);
78+
return module;
79+
}
80+
5581
// Find first matching module rule ("ignore" requires relative paths)
5682
const relativeIdentifier = path.relative("", identifier);
5783
const rule = this.moduleRules.find((rule) =>
@@ -67,8 +93,6 @@ export class ModuleLinker {
6793
// Load module based on rule type
6894
const data = await fs.readFile(identifier);
6995
this.#referencedPathSizes.set(identifier, data.byteLength);
70-
const moduleOptions = { identifier, context: referencing.context };
71-
let module: vm.Module;
7296
switch (rule.type) {
7397
case "ESModule":
7498
module = new vm.SourceTextModule(data.toString("utf8"), moduleOptions);

packages/runner-vm/test/linker.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ test("ModuleLinker: throws error for unsupported module type via ES module", asy
124124
message: /PNG modules are unsupported$/,
125125
});
126126
});
127+
test("ModuleLinker: links additional module via ES module", async (t) => {
128+
const callback = (defaultExport: string, namedExport: number) => {
129+
t.is(defaultExport, "test");
130+
t.is(namedExport, 42);
131+
};
132+
const additionalModules = { ADDITIONAL: { default: "test", n: 42 } };
133+
await runner.run(
134+
{ callback },
135+
{ code: 'import s, { n } from "ADDITIONAL"; callback(s, n);', filePath },
136+
processedModuleRules,
137+
additionalModules
138+
);
139+
});
127140

128141
test("ModuleLinker: throws error when linking ESModule via CommonJS module", async (t) => {
129142
// Technically Workers "supports" this, in that it doesn't throw an error,

packages/shared/src/plugin.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { WranglerConfig } from "./wrangler";
88

99
export type Context = { [key: string | symbol]: any };
1010

11+
// Maps module specifiers to module namespace
12+
export type AdditionalModules = { [key: string]: Context };
13+
1114
export enum OptionType {
1215
NONE, // never
1316
BOOLEAN, // boolean
@@ -69,6 +72,7 @@ export interface SetupResult extends BeforeSetupResult {
6972
bindings?: Context;
7073
script?: ScriptBlueprint;
7174
requiresModuleExports?: boolean;
75+
additionalModules?: AdditionalModules;
7276
}
7377

7478
export abstract class Plugin<Options extends Context = never> {

packages/shared/src/runner.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Matcher } from "./data";
2-
import { Context } from "./plugin";
2+
import { AdditionalModules, Context } from "./plugin";
33

44
export type ModuleRuleType =
55
| "ESModule"
@@ -37,6 +37,7 @@ export interface ScriptRunner {
3737
run(
3838
globalScope: Context,
3939
blueprint: ScriptBlueprint,
40-
modulesRules?: ProcessedModuleRule[]
40+
modulesRules?: ProcessedModuleRule[],
41+
additionalModules?: AdditionalModules
4142
): Promise<ScriptRunnerResult>;
4243
}

packages/sites/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"extends": "../../package.json"
3737
},
3838
"entryPoints": [
39-
"test/fixtures/plugin.assetHandler.js"
39+
"test/fixtures/plugin.assetHandler.js",
40+
"test/fixtures/plugin.assetHandler.modules.js"
4041
],
4142
"dependencies": {
4243
"@miniflare/kv": "2.0.0-next.3",

packages/sites/src/plugin.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,14 @@ export class SitesPlugin extends Plugin<SitesOptions> implements SitesOptions {
7171
// path as the file path and won't edge cache files
7272
__STATIC_CONTENT_MANIFEST: {},
7373
};
74+
// Allow `import manifest from "__STATIC_CONTENT_MANIFEST"`
75+
const additionalModules = {
76+
__STATIC_CONTENT_MANIFEST: { default: "{}" },
77+
};
7478

7579
// Whilst FileStorage will always serve the latest files, we want to
7680
// force a reload when these files change for live reload.
77-
return { bindings, watch: [this.sitePath] };
81+
return { bindings, watch: [this.sitePath], additionalModules };
7882
}
7983

8084
async setup(): Promise<SetupResult> {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { getAssetFromKV } from "@cloudflare/kv-asset-handler";
2+
// noinspection NpmUsedModulesInstalled
3+
import manifestJSON from "__STATIC_CONTENT_MANIFEST";
4+
const manifest = JSON.parse(manifestJSON);
5+
6+
export default {
7+
async fetch(request, env, ctx) {
8+
return await getAssetFromKV(
9+
{
10+
request,
11+
waitUntil(promise) {
12+
return ctx.waitUntil(promise);
13+
},
14+
},
15+
{
16+
ASSET_NAMESPACE: env.__STATIC_CONTENT,
17+
ASSET_MANIFEST: manifest,
18+
}
19+
);
20+
},
21+
};

0 commit comments

Comments
 (0)