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

Commit 19cd822

Browse files
authored
Allow sources to be hidden in DevTools sources panel (#662)
Wrangler currently uses the non-standard `x_google_ignoreList` source map field to hide middleware and injected code from the sources panel. As part of the work to enable breakpoint debugging in Wrangler, we now defer to Miniflare to serve local source maps. This ensures source maps have the correct source paths on disk. We'd like to keep this ignoring behaviour though. We could intercept `Network.loadNetworkResource` and rewrite the source map in Wrangler, but that feels wasteful, as we're already parsing/stringifying the source map in Miniflare. Instead, this change adds a `unsafeSourceMapIgnoreSourcePredicate` option, that controls which sources, if any, are added to `x_google_ignoreList`. This allows DevTools to fetch source maps directly from Miniflare, without proxying through Wrangler.
1 parent 0b7a470 commit 19cd822

File tree

4 files changed

+51
-6
lines changed

4 files changed

+51
-6
lines changed

packages/miniflare/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,11 @@ export class Miniflare {
863863

864864
sharedOpts.core.cf = await setupCf(this.#log, sharedOpts.core.cf);
865865

866-
const sourceMapRegistry = new SourceMapRegistry(this.#log, loopbackPort);
866+
const sourceMapRegistry = new SourceMapRegistry(
867+
this.#log,
868+
loopbackPort,
869+
sharedOpts.core.unsafeSourceMapIgnoreSourcePredicate
870+
);
867871
const durableObjectClassNames = getDurableObjectClassNames(allWorkerOpts);
868872
const queueConsumers = getQueueConsumers(allWorkerOpts);
869873
const allWorkerRoutes = getWorkerRoutes(allWorkerOpts);

packages/miniflare/src/plugins/core/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { getCacheServiceName } from "../cache";
2929
import { DURABLE_OBJECTS_STORAGE_SERVICE_NAME } from "../do";
3030
import {
3131
HEADER_CF_BLOB,
32+
IgnoreSourcePredicateSchema,
3233
Plugin,
3334
SERVICE_LOOPBACK,
3435
SourceMapRegistry,
@@ -141,6 +142,8 @@ export const CoreSharedOptionsSchema = z.object({
141142
cf: z.union([z.boolean(), z.string(), z.record(z.any())]).optional(),
142143

143144
liveReload: z.boolean().optional(),
145+
146+
unsafeSourceMapIgnoreSourcePredicate: IgnoreSourcePredicateSchema.optional(),
144147
});
145148

146149
export const CORE_PLUGIN_NAME = "core";

packages/miniflare/src/plugins/shared/registry.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@ import fs from "fs/promises";
33
import path from "path";
44
import { fileURLToPath, pathToFileURL } from "url";
55
import type { RawSourceMap } from "source-map";
6+
import { z } from "zod";
67
import { Response } from "../../http";
78
import { Log } from "../../shared";
89

10+
export const IgnoreSourcePredicateSchema = z
11+
.function()
12+
.args(z.string())
13+
.returns(z.boolean());
14+
export type IgnoreSourcePredicate = z.infer<typeof IgnoreSourcePredicateSchema>;
15+
916
function maybeParseURL(url: string): URL | undefined {
1017
if (path.isAbsolute(url)) return;
1118
try {
@@ -18,7 +25,8 @@ export class SourceMapRegistry {
1825

1926
constructor(
2027
private readonly log: Log,
21-
private readonly loopbackPort: number
28+
private readonly loopbackPort: number,
29+
private readonly ignoreSourcePredicate?: IgnoreSourcePredicate
2230
) {}
2331

2432
readonly #map = new Map<string /* id */, string /* sourceMapPath */>();
@@ -78,7 +86,7 @@ export class SourceMapRegistry {
7886
);
7987
return;
8088
}
81-
let map: RawSourceMap;
89+
let map: RawSourceMap & { x_google_ignoreList?: number[] };
8290
try {
8391
map = JSON.parse(contents);
8492
} catch (e) {
@@ -96,6 +104,18 @@ export class SourceMapRegistry {
96104
? sourceMapDir
97105
: path.resolve(sourceMapDir, map.sourceRoot);
98106

107+
// Allow specific source files to be hidden from the DevTools sources panel.
108+
// (e.g. Wrangler middleware and injected code)
109+
// See https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.mt2g20loc2ct
110+
// for more details.
111+
if (this.ignoreSourcePredicate !== undefined && map.sources !== undefined) {
112+
const ignoreList: number[] = [];
113+
for (let i = 0; i < map.sources.length; i++) {
114+
if (this.ignoreSourcePredicate(map.sources[i])) ignoreList.push(i);
115+
}
116+
map.x_google_ignoreList = ignoreList;
117+
}
118+
99119
return Response.json(map, {
100120
// This source map will be served from the loopback server to DevTools,
101121
// which will likely be on a different origin.

packages/miniflare/test/plugins/core/errors/index.spec.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ test("source maps workers", async (t) => {
6060

6161
const mf = new Miniflare({
6262
inspectorPort,
63+
unsafeSourceMapIgnoreSourcePredicate(source) {
64+
return source.includes("nested/dep.ts");
65+
},
6366
workers: [
6467
{
6568
bindings: { MESSAGE: "unnamed" },
@@ -215,6 +218,13 @@ addEventListener("fetch", (event) => {
215218
// Check does nothing with URL source mapping URLs
216219
const sourceMapURL = await getSourceMapURL(inspectorPort, "core:user:h");
217220
t.regex(sourceMapURL, /^data:application\/json;base64/);
221+
222+
// Check adds ignored sources to `x_google_ignoreList`
223+
const sourceMap = await getSourceMap(inspectorPort, "core:user:g");
224+
assert(sourceMap.sourceRoot !== undefined);
225+
assert(sourceMap.x_google_ignoreList?.length === 1);
226+
const ignoredSource = sourceMap.sources[sourceMap.x_google_ignoreList[0]];
227+
t.is(path.resolve(sourceMap.sourceRoot, ignoredSource), DEP_ENTRY_PATH);
218228
});
219229

220230
function getSourceMapURL(
@@ -250,14 +260,22 @@ function getSourceMapURL(
250260
return promise;
251261
}
252262

253-
async function getSources(inspectorPort: number, serviceName: string) {
263+
async function getSourceMap(inspectorPort: number, serviceName: string) {
254264
const sourceMapURL = await getSourceMapURL(inspectorPort, serviceName);
255265
// The loopback server will be listening on `127.0.0.1`, which
256266
// `localhost` should resolve to, but `undici` only looks at the first
257267
// DNS entry, which will be `::1` on Node 17+.
258-
// noinspection JSObjectNullOrUndefined
259268
const res = await fetch(sourceMapURL.replace("localhost", "127.0.0.1"));
260-
const { sourceRoot, sources } = (await res.json()) as RawSourceMap;
269+
return (await res.json()) as RawSourceMap & {
270+
x_google_ignoreList?: number[];
271+
};
272+
}
273+
274+
async function getSources(inspectorPort: number, serviceName: string) {
275+
const { sourceRoot, sources } = await getSourceMap(
276+
inspectorPort,
277+
serviceName
278+
);
261279
assert(sourceRoot !== undefined);
262280
return sources.map((source) => path.resolve(sourceRoot, source)).sort();
263281
}

0 commit comments

Comments
 (0)