@fastly/js-compute
Version:
JavaScript SDK and CLI for building JavaScript applications on [Fastly Compute](https://www.fastly.com/products/edge-compute/serverless).
74 lines (73 loc) • 3.04 kB
JavaScript
import { readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import remapping from '@jridgewell/remapping';
import { TraceMap } from '@jridgewell/trace-mapping';
import picomatch from 'picomatch';
// Compiler Step - Compose Sourcemaps
// This step usually runs at the end.
// This step composes all the source maps up to this point into a single
// source map, and injects it into the bundle.
// Be careful: Do not run any steps after this step. Such steps will not be
// reflected in downstream source maps.
export const composeSourcemapsStep = {
outFilename: '__fastly_bundle_with_sourcemaps.js',
async fn(ctx) {
// Compose source maps
const replaceSourceMapToken = '__FINAL_SOURCE_MAP__';
let excludePatterns = [
'forbid-entry:/**',
'node_modules/**',
];
if (ctx.excludeSources) {
excludePatterns = [() => true];
}
const composed = await composeSourcemaps(ctx.sourceMaps, excludePatterns);
const postBundleContent = await readFile(ctx.inFilepath, {
encoding: 'utf-8',
});
const outputWithSourcemapsContent = postBundleContent.replace(replaceSourceMapToken, () => JSON.stringify(composed));
await writeFile(ctx.outFilepath, outputWithSourcemapsContent);
if (ctx.debugIntermediateFilesDir != null) {
await writeFile(resolve(ctx.debugIntermediateFilesDir, 'fastly_sourcemaps.json'), composed);
}
},
};
async function readSourcemap(e) {
const sourceMapJson = await readFile(e.s, { encoding: 'utf-8' });
return JSON.parse(sourceMapJson);
}
export async function composeSourcemaps(sourceMaps, excludePatterns = []) {
const topSourceMap = sourceMaps.pop();
if (topSourceMap == null) {
throw new Error('Unexpected: composeSourcemaps received empty sourceMaps array.');
}
const top = new TraceMap(await readSourcemap(topSourceMap));
const priors = {};
for (const sourceMap of sourceMaps) {
priors[sourceMap.f] = await readSourcemap(sourceMap);
}
// Loader: given a source name from mapXZ, return a TraceMap for that source (if any).
const loader = (source) => {
const m = priors[source];
if (!m)
return null; // no earlier map for this source
return new TraceMap(m);
};
const raw = JSON.parse(remapping(top, loader, false).toString());
return JSON.stringify(stripSourcesContent(raw, excludePatterns));
}
function stripSourcesContent(map, excludes) {
if (map.sourcesContent == null) {
return map;
}
const matchers = excludes.map((p) => typeof p === 'string' ? picomatch(p) : p);
for (let i = 0; i < map.sources.length; i++) {
const src = map.sources[i];
// [Windows] normalize slashes
const normalized = src?.replace(/\\/g, '/');
if (normalized == null || matchers.some((fn) => fn(normalized))) {
map.sourcesContent[i] = null;
}
}
return map;
}