@serwist/build
Version:
A module that integrates into your build process, helping you generate a manifest of local files that should be precached.
123 lines (109 loc) • 3.4 kB
text/typescript
/*
Copyright 2019 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
import type { RawSourceMap } from "source-map";
import { SourceMapConsumer, SourceMapGenerator } from "source-map";
interface ReplaceAndUpdateSourceMapOptions {
/**
* The name for the file whose contents
* correspond to originalSource.
*/
jsFilename: string;
/**
* The sourcemap for originalSource,
* prior to any replacements.
*/
originalMap: RawSourceMap;
/**
* The source code, prior to any
* replacements.
*/
originalSource: string;
/**
* A string to swap in for searchString.
*/
replaceString: string;
/**
* A string in originalSource to replace.
* Only the first occurrence will be replaced.
*/
searchString: string;
}
/**
* Adapted from https://github.com/nsams/sourcemap-aware-replace, with modern
* JavaScript updates, along with additional properties copied from originalMap.
*
* @param options
* @returns An object containing both
* originalSource with the replacement applied, and the modified originalMap.
* @private
*/
export async function replaceAndUpdateSourceMap({
jsFilename,
originalMap,
originalSource,
replaceString,
searchString,
}: ReplaceAndUpdateSourceMapOptions): Promise<{ map: string; source: string }> {
const generator = new SourceMapGenerator({
file: jsFilename,
});
const consumer = await new SourceMapConsumer(originalMap);
let pos: number;
let src = originalSource;
const replacements: { line: number; column: number }[] = [];
let lineNum = 0;
let filePos = 0;
const lines = src.split("\n");
for (let line of lines) {
lineNum++;
let searchPos = 0;
while ((pos = line.indexOf(searchString, searchPos)) !== -1) {
src = src.substring(0, filePos + pos) + replaceString + src.substring(filePos + pos + searchString.length);
line = line.substring(0, pos) + replaceString + line.substring(pos + searchString.length);
replacements.push({ line: lineNum, column: pos });
searchPos = pos + replaceString.length;
}
filePos += line.length + 1;
}
replacements.reverse();
consumer.eachMapping((mapping) => {
for (const replacement of replacements) {
if (replacement.line === mapping.generatedLine && mapping.generatedColumn > replacement.column) {
const offset = searchString.length - replaceString.length;
mapping.generatedColumn -= offset;
}
}
if (mapping.source) {
const newMapping = {
generated: {
line: mapping.generatedLine,
column: mapping.generatedColumn,
},
original: {
line: mapping.originalLine,
column: mapping.originalColumn,
},
source: mapping.source,
};
return generator.addMapping(newMapping);
}
return mapping;
});
consumer.destroy();
// JSON.parse returns any.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const updatedSourceMap: RawSourceMap = Object.assign(JSON.parse(generator.toString()), {
names: originalMap.names,
sourceRoot: originalMap.sourceRoot,
sources: originalMap.sources,
sourcesContent: originalMap.sourcesContent,
});
return {
map: JSON.stringify(updatedSourceMap),
source: src,
};
}