Skip to content

Commit

Permalink
Add additional __STATIC_CONTENT_MANIFEST module for Workers Sites
Browse files Browse the repository at this point in the history
  • Loading branch information
mrbbot committed Jan 7, 2022
1 parent d95761a commit 7854a91
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 15 deletions.
14 changes: 12 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from "fs/promises";
import path from "path";
import {
AdditionalModules,
BeforeSetupResult,
Compatibility,
Context,
Expand Down Expand Up @@ -411,6 +412,7 @@ export class MiniflareCore<

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

// Build global scope and extract script blueprints
// Build global scope, extracting script blueprints and additional modules
const result = this.#setupResults!.get(name);
Object.assign(globals, result?.globals);
Object.assign(bindings, result?.bindings);
Expand All @@ -430,6 +432,9 @@ export class MiniflareCore<
script = result.script;
}
if (result?.requiresModuleExports) requiresModuleExports = true;
if (result?.additionalModules) {
Object.assign(additionalModules, result.additionalModules);
}

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

this.log.verbose("Running script...");
res = await this.#scriptRunner.run(globalScope, script, rules);
res = await this.#scriptRunner.run(
globalScope,
script,
rules,
additionalModules
);

this.#scriptWatchPaths.clear();
this.#scriptWatchPaths.add(script.filePath);
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/standards/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
if (modules) {
// Error when trying to access bindings using the global in modules mode
for (const key of Object.keys(bindings)) {
// @cloudflare/kv-asset-handler checks the typeof these keys which
// triggers an access. We want this typeof to return "undefined", not
// throw, so skip these specific keys.
if (key === "__STATIC_CONTENT" || key === "__STATIC_CONTENT_MANIFEST") {
break;
}

Object.defineProperty(this, key, {
get() {
throw new ReferenceError(
Expand Down
7 changes: 5 additions & 2 deletions packages/runner-vm/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import vm from "vm";
import {
AdditionalModules,
Context,
ProcessedModuleRule,
ScriptBlueprint,
Expand Down Expand Up @@ -41,7 +42,8 @@ export class VMScriptRunner implements ScriptRunner {
async run(
globalScope: Context,
blueprint: ScriptBlueprint,
modulesRules?: ProcessedModuleRule[]
modulesRules?: ProcessedModuleRule[],
additionalModules?: AdditionalModules
): Promise<ScriptRunnerResult> {
// If we're using modules, make sure --experimental-vm-modules is enabled
if (modulesRules && !("SourceTextModule" in vm)) {
Expand All @@ -51,7 +53,8 @@ export class VMScriptRunner implements ScriptRunner {
);
}
// Also build a linker if we're using modules
const linker = modulesRules && new ModuleLinker(modulesRules);
const linker =
modulesRules && new ModuleLinker(modulesRules, additionalModules ?? {});

// Add proxied globals so cross-realm instanceof works correctly.
// globalScope will be fresh for each call of run so it's fine to mutate it.
Expand Down
34 changes: 29 additions & 5 deletions packages/runner-vm/src/linker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from "fs/promises";
import path from "path";
import vm from "vm";
import {
AdditionalModules,
Context,
ProcessedModuleRule,
STRING_SCRIPT_PATH,
Expand All @@ -19,7 +20,10 @@ export class ModuleLinker {
readonly #moduleCache = new Map<string, vm.Module>();
readonly #cjsModuleCache = new Map<string, CommonJSModule>();

constructor(private moduleRules: ProcessedModuleRule[]) {
constructor(
private moduleRules: ProcessedModuleRule[],
private additionalModules: AdditionalModules
) {
this.linker = this.linker.bind(this);
}

Expand All @@ -45,13 +49,35 @@ export class ModuleLinker {
);
}

// Get path to specified module relative to referencing module
const identifier = path.resolve(path.dirname(referencing.identifier), spec);
const additionalModule = this.additionalModules[spec];
const identifier = additionalModule
? spec
: // Get path to specified module relative to referencing module
path.resolve(path.dirname(referencing.identifier), spec);

// If we've already seen a module with the same identifier, return it, to
// handle import cycles
const cached = this.#moduleCache.get(identifier);
if (cached) return cached;

const moduleOptions = { identifier, context: referencing.context };
let module: vm.Module;

// If this is an additional module, construct and return it immediately
if (additionalModule) {
module = new vm.SyntheticModule(
Object.keys(additionalModule),
function () {
for (const [key, value] of Object.entries(additionalModule)) {
this.setExport(key, value);
}
},
moduleOptions
);
this.#moduleCache.set(identifier, module);
return module;
}

// Find first matching module rule ("ignore" requires relative paths)
const relativeIdentifier = path.relative("", identifier);
const rule = this.moduleRules.find((rule) =>
Expand All @@ -67,8 +93,6 @@ export class ModuleLinker {
// Load module based on rule type
const data = await fs.readFile(identifier);
this.#referencedPathSizes.set(identifier, data.byteLength);
const moduleOptions = { identifier, context: referencing.context };
let module: vm.Module;
switch (rule.type) {
case "ESModule":
module = new vm.SourceTextModule(data.toString("utf8"), moduleOptions);
Expand Down
13 changes: 13 additions & 0 deletions packages/runner-vm/test/linker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ test("ModuleLinker: throws error for unsupported module type via ES module", asy
message: /PNG modules are unsupported$/,
});
});
test("ModuleLinker: links additional module via ES module", async (t) => {
const callback = (defaultExport: string, namedExport: number) => {
t.is(defaultExport, "test");
t.is(namedExport, 42);
};
const additionalModules = { ADDITIONAL: { default: "test", n: 42 } };
await runner.run(
{ callback },
{ code: 'import s, { n } from "ADDITIONAL"; callback(s, n);', filePath },
processedModuleRules,
additionalModules
);
});

test("ModuleLinker: throws error when linking ESModule via CommonJS module", async (t) => {
// Technically Workers "supports" this, in that it doesn't throw an error,
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { WranglerConfig } from "./wrangler";

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

// Maps module specifiers to module namespace
export type AdditionalModules = { [key: string]: Context };

export enum OptionType {
NONE, // never
BOOLEAN, // boolean
Expand Down Expand Up @@ -69,6 +72,7 @@ export interface SetupResult extends BeforeSetupResult {
bindings?: Context;
script?: ScriptBlueprint;
requiresModuleExports?: boolean;
additionalModules?: AdditionalModules;
}

export abstract class Plugin<Options extends Context = never> {
Expand Down
5 changes: 3 additions & 2 deletions packages/shared/src/runner.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Matcher } from "./data";
import { Context } from "./plugin";
import { AdditionalModules, Context } from "./plugin";

export type ModuleRuleType =
| "ESModule"
Expand Down Expand Up @@ -37,6 +37,7 @@ export interface ScriptRunner {
run(
globalScope: Context,
blueprint: ScriptBlueprint,
modulesRules?: ProcessedModuleRule[]
modulesRules?: ProcessedModuleRule[],
additionalModules?: AdditionalModules
): Promise<ScriptRunnerResult>;
}
3 changes: 2 additions & 1 deletion packages/sites/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"extends": "../../package.json"
},
"entryPoints": [
"test/fixtures/plugin.assetHandler.js"
"test/fixtures/plugin.assetHandler.js",
"test/fixtures/plugin.assetHandler.modules.js"
],
"dependencies": {
"@miniflare/kv": "2.0.0-next.3",
Expand Down
6 changes: 5 additions & 1 deletion packages/sites/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,14 @@ export class SitesPlugin extends Plugin<SitesOptions> implements SitesOptions {
// path as the file path and won't edge cache files
__STATIC_CONTENT_MANIFEST: {},
};
// Allow `import manifest from "__STATIC_CONTENT_MANIFEST"`
const additionalModules = {
__STATIC_CONTENT_MANIFEST: { default: "{}" },
};

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

async setup(): Promise<SetupResult> {
Expand Down
21 changes: 21 additions & 0 deletions packages/sites/test/fixtures/plugin.assetHandler.modules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getAssetFromKV } from "@cloudflare/kv-asset-handler";
// noinspection NpmUsedModulesInstalled
import manifestJSON from "__STATIC_CONTENT_MANIFEST";
const manifest = JSON.parse(manifestJSON);

export default {
async fetch(request, env, ctx) {
return await getAssetFromKV(
{
request,
waitUntil(promise) {
return ctx.waitUntil(promise);
},
},
{
ASSET_NAMESPACE: env.__STATIC_CONTENT,
ASSET_MANIFEST: manifest,
}
);
},
};
21 changes: 19 additions & 2 deletions packages/sites/test/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ const sitesScriptPath = path.resolve(
"fixtures",
"plugin.assetHandler.js"
);
const sitesModulesScriptPath = path.resolve(
__dirname,
"fixtures",
"plugin.assetHandler.modules.js"
);

type Route = keyof typeof routeContents;
const routeContents = {
Expand Down Expand Up @@ -193,15 +198,27 @@ test("MiniflareCore: doesn't cache files", async (t) => {

await fs.writeFile(testPath, "1", "utf8");
const res1 = await mf.dispatchFetch(
new Request(`http://localhost:8787/test.txt`)
new Request("http://localhost:8787/test.txt")
);
t.false(res1.headers.has("CF-Cache-Status"));
t.is(await res1.text(), "1");

await fs.writeFile(testPath, "2", "utf8");
const res2 = await mf.dispatchFetch(
new Request(`http://localhost:8787/test.txt`)
new Request("http://localhost:8787/test.txt")
);
t.false(res2.headers.has("CF-Cache-Status"));
t.is(await res2.text(), "2");
});

test("MiniflareCore: gets assets with module worker", async (t) => {
const tmp = await useTmp(t);
const testPath = path.join(tmp, "test.txt");
await fs.writeFile(testPath, "test", "utf8");
const mf = useMiniflare(
{ SitesPlugin, CachePlugin },
{ modules: true, scriptPath: sitesModulesScriptPath, sitePath: tmp }
);
const res = await mf.dispatchFetch("http://localhost:8787/test.txt");
t.is(await res.text(), "test");
});
2 changes: 2 additions & 0 deletions scripts/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ const buildOptions = {
// Make sure all Jest packages aren't bundled
"@jest/*",
"jest*",
// Mark sites manifest as external, it's added by SitesPlugin
"__STATIC_CONTENT_MANIFEST",
],
logLevel: watch ? "info" : "warning",
watch,
Expand Down

0 comments on commit 7854a91

Please sign in to comment.