Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Account for injectManifest replacements in sourcemaps #2239

Merged
merged 4 commits into from
Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions packages/workbox-build/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion packages/workbox-build/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@
"@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5",
"@hapi/joi": "^15.1.0",
"@surma/rollup-plugin-off-main-thread": "^1.1.1",
"common-tags": "^1.8.0",
"fast-json-stable-stringify": "^2.0.0",
"fs-extra": "^8.1.0",
"glob": "^7.1.4",
"lodash.template": "^4.5.0",
"pretty-bytes": "^5.2.0",
"rollup": "^1.17.0",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-node-resolve": "^5.2.0",
"@surma/rollup-plugin-off-main-thread": "^1.1.1",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-terser": "^5.1.1",
"source-map": "^0.7.3",
"source-map-url": "^0.4.0",
"stringify-object": "^3.3.0",
"strip-comments": "^1.0.2",
"tempy": "^0.3.0",
Expand Down
72 changes: 56 additions & 16 deletions packages/workbox-build/src/inject-manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@

const assert = require('assert');
const fse = require('fs-extra');
const sourceMapURL = require('source-map-url');
const stringify = require('fast-json-stable-stringify');
const upath = require('upath');

const errors = require('./lib/errors');
const escapeRegexp = require('./lib/escape-regexp');
const getFileManifestEntries = require('./lib/get-file-manifest-entries');
const injectManifestSchema = require('./options/schema/inject-manifest');
const rebasePath = require('./lib/rebase-path');
const replaceAndUpdateSourceMap =
require('./lib/replace-and-update-source-map');
const validate = require('./lib/validate-options');

/**
Expand Down Expand Up @@ -49,8 +53,7 @@ async function injectManifest(config) {
}));
}


if (upath.resolve(config.swSrc) === upath.resolve(config.swDest)) {
if (upath.resolve(options.swSrc) === upath.resolve(options.swDest)) {
throw new Error(errors['same-src-and-dest']);
}

Expand All @@ -60,7 +63,7 @@ async function injectManifest(config) {
await getFileManifestEntries(options);
let swFileContents;
try {
swFileContents = await fse.readFile(config.swSrc, 'utf8');
swFileContents = await fse.readFile(options.swSrc, 'utf8');
} catch (error) {
throw new Error(`${errors['invalid-sw-src']} ${error.message}`);
}
Expand All @@ -71,23 +74,60 @@ async function injectManifest(config) {
assert(injectionResults.length === 1, errors['multiple-injection-points'] +
options.injectionPoint);

const entriesString = JSON.stringify(manifestEntries, null, 2);
swFileContents = swFileContents.replace(globalRegexp, entriesString);

try {
await fse.mkdirp(upath.dirname(options.swDest));
} catch (error) {
throw new Error(errors['unable-to-make-injection-directory'] +
` '${error.message}'`);
const manifestString = stringify(manifestEntries);
const filesToWrite = {};

const url = sourceMapURL.getFrom(swFileContents);
// If our swSrc file contains a sourcemap, we would invalidate that
// mapping if we just replaced injectionPoint with the stringified manifest.
// Instead, we need to update the swDest contents as well as the sourcemap
// at the same time.
// See https://github.com/GoogleChrome/workbox/issues/2235
if (url) {
const sourcemapSrcPath = upath.resolve(upath.dirname(options.swSrc), url);
const sourcemapDestPath = upath.resolve(upath.dirname(options.swDest), url);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will always keep the same source map filename, and if swSrc and swDest are in the same folder, it'll overwrite the original source map file.

Instead, this should just append ".map" to swDest.


let originalMap;
try {
originalMap = await fse.readJSON(sourcemapSrcPath, 'utf8');
} catch (error) {
throw new Error(`${errors['cant-find-sourcemap']} ${error.message}`);
}

const {map, source} = await replaceAndUpdateSourceMap({
originalMap,
jsFilename: upath.basename(options.swDest),
originalSource: swFileContents,
replaceString: manifestString,
searchString: options.injectionPoint,
});

filesToWrite[options.swDest] = source;
filesToWrite[sourcemapDestPath] = map;
} else {
// If there's no sourcemap associated with swSrc, a simple string
// replacement will suffice.
filesToWrite[options.swDest] = swFileContents.replace(
globalRegexp, manifestString);
}

await fse.writeFile(config.swDest, swFileContents);
for (const [file, contents] of Object.entries(filesToWrite)) {
try {
await fse.mkdirp(upath.dirname(file));
} catch (error) {
throw new Error(errors['unable-to-make-injection-directory'] +
` '${error.message}'`);
}

// Wrap the single output file in an array to make the return value consistent
// with generateSW.
const filePaths = [config.swDest];
await fse.writeFile(file, contents);
}

return {count, filePaths, size, warnings};
return {
count,
size,
warnings,
filePaths: Object.keys(filesToWrite),
};
}

module.exports = injectManifest;
2 changes: 2 additions & 0 deletions packages/workbox-build/src/lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,6 @@ module.exports = {
either precaching or runtime caching. Please ensure that the various glob
options are set to match one or more files, and/or configure the
runtimeCaching option.`,
'cant-find-sourcemap': ol`The swSrc file refers to a sourcemap that can't be
opened:`,
};
108 changes: 108 additions & 0 deletions packages/workbox-build/src/lib/replace-and-update-source-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
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.
*/

const {SourceMapConsumer, SourceMapGenerator} = require('source-map');

/**
* Adapted from https://github.com/nsams/sourcemap-aware-replace, with modern
* JavaScript updates, along with additional properties copied from originalMap.
*
* @param {Object} options
* @param {string} options.jsFilename The name for the file whose contents
* correspond to originalSource.
* @param {Object} options.originalMap The sourcemap for originalSource,
* prior to any replacements.
* @param {string} options.originalSource The source code, prior to any
* replacements.
* @param {string} options.replaceString A string to swap in for searchString.
* @param {string} options.searchString A string in originalSource to replace.
* Only the first occurrence will be replaced.
* @return {{source: string, map: string}} An object containing both
* originalSource with the replacement applied, and the modified originalMap.
*
* @private
*/
async function replaceAndUpdateSourceMap({
jsFilename,
originalMap,
originalSource,
replaceString,
searchString,
}) {
const generator = new SourceMapGenerator({
file: jsFilename,
});

const consumer = await new SourceMapConsumer(originalMap);

let pos;
let src = originalSource;
const replacements = [];
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();

const updatedSourceMap = 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,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is returned without changing the URL of the source map to reflect the filename in swDest.

};
}

module.exports = replaceAndUpdateSourceMap;
Loading