Skip to content

Commit 1a9a200

Browse files
rahul-kamatcopybara-github
authored andcommitted
Serialize inline source maps (base64-encoded "data url" stored in //# sourceMappingURL= comments)
PiperOrigin-RevId: 516248443
1 parent a970884 commit 1a9a200

File tree

10 files changed

+115
-51
lines changed

10 files changed

+115
-51
lines changed

src/com/google/javascript/jscomp/AbstractCompiler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ public boolean hasColorAndSimplifiedJSDoc() {
528528
*/
529529
public abstract void addInputSourceMap(String name, SourceMapInput sourceMap);
530530

531-
public abstract String getInputSourceMappingURL(String sourceFileName);
531+
public abstract String getBase64SourceMapContents(String sourceFileName);
532532

533533
abstract void addComments(String filename, List<Comment> comments);
534534

src/com/google/javascript/jscomp/Compiler.java

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.google.common.base.Preconditions.checkState;
2121
import static com.google.common.base.Strings.isNullOrEmpty;
2222
import static com.google.common.collect.ImmutableList.toImmutableList;
23+
import static java.nio.charset.StandardCharsets.UTF_8;
2324

2425
import com.google.common.annotations.GwtIncompatible;
2526
import com.google.common.annotations.VisibleForTesting;
@@ -35,6 +36,7 @@
3536
import com.google.common.collect.Multimap;
3637
import com.google.common.collect.Sets;
3738
import com.google.common.collect.Streams;
39+
import com.google.common.io.BaseEncoding;
3840
import com.google.debugging.sourcemap.SourceMapConsumerV3;
3941
import com.google.debugging.sourcemap.proto.Mapping.OriginalMapping;
4042
import com.google.errorprone.annotations.CanIgnoreReturnValue;
@@ -3302,26 +3304,20 @@ public void addInputSourceMap(String sourceFileName, SourceMapInput inputSourceM
33023304
}
33033305

33043306
/**
3305-
* Returns the <url> from a `//# sourceMappingURL=<url>` comment. This sourceMappingURL is a JS
3306-
* file's input source map, embedded into the input JS file with a `//# sourceMappingURL=<url>`
3307-
* comment.
3308-
*
3309-
* <p>We do not want TypedAST to support inline source maps (which are input source maps passed by
3310-
* embedding a `//# sourceMappingURL=<url>` where <url> is a base64-encoded "data url"). If the
3311-
* sourceMappingURL ends with a ".inline.map", return null.
3307+
* Returns the <encoded_source_map> from a `//#
3308+
* sourceMappingURL=data:application/json;base64,<encoded_source_map>` comment. This
3309+
* sourceMappingURL comment is a JS file's inline source map, embedded into the input JS file with
3310+
* a base64-encoded "data url" stored in `//# sourceMappingURL=` comment.
33123311
*/
33133312
@Override
3314-
public @Nullable String getInputSourceMappingURL(String sourceFileName) {
3313+
public @Nullable String getBase64SourceMapContents(String sourceFileName) {
33153314
SourceMapInput sourceMapInput = inputSourceMaps.getOrDefault(sourceFileName, null);
3315+
// This sourceMappingURL ends with a ".inline.map", and we want to get its base4-encoded
3316+
// contents (i.e. "data:application/json;base64,<encoded_source_map>").
33163317
String sourceMappingURL = sourceMapInput != null ? sourceMapInput.getOriginalPath() : null;
3317-
if (sourceMappingURL != null && !sourceMappingURL.endsWith(".inline.map")) {
3318-
// Set sourceMappingURL as the name of the sourcemap file, not the original location/path of
3319-
// the sourcemap file on disk. E.g. turn "blaze-out/.../a.js.map" into "a.js.map"
3320-
sourceMappingURL =
3321-
sourceMappingURL.contains("/")
3322-
? sourceMappingURL.substring(sourceMappingURL.lastIndexOf("/") + 1)
3323-
: sourceMappingURL;
3324-
return sourceMappingURL;
3318+
if (sourceMappingURL != null && sourceMappingURL.endsWith(".inline.map")) {
3319+
String unencoded = sourceMapInput.getRawSourceMapContents(); // This is the sourcemap.
3320+
return BaseEncoding.base64().encode(unencoded.getBytes(UTF_8));
33253321
}
33263322
return null;
33273323
}
@@ -3925,6 +3921,8 @@ protected static class CompilerState implements Serializable {
39253921
private final String idGeneratorMap;
39263922
private final IdGenerator crossModuleIdGenerator;
39273923
private final boolean runJ2clPasses;
3924+
// TODO(b/235404079): Stop serializing input source maps here since we serialize them in
3925+
// TypedAsts
39283926
private final ImmutableMap<String, SourceMapInput> inputSourceMaps;
39293927
private final ImmutableList<InputId> externs;
39303928
private final ImmutableListMultimap<JSChunk, InputId> moduleToInputList;

src/com/google/javascript/jscomp/SourceMapInput.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,12 @@ public SourceMapInput(SourceFile sourceFile) {
7676
public String getOriginalPath() {
7777
return sourceFile.getName();
7878
}
79+
80+
public @Nullable String getRawSourceMapContents() {
81+
try {
82+
return this.sourceFile.getCode();
83+
} catch (IOException e) {
84+
return null;
85+
}
86+
}
7987
}

src/com/google/javascript/jscomp/SourceMapResolver.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,15 @@ private static boolean isAbsolute(String url) {
103103
.build();
104104
}
105105

106+
/**
107+
* Returns the encoded source map with the base64 prefix attached. (i.e. prefix with
108+
* "data:application/json;base64,")
109+
*
110+
* <p>E.g. turns "eyJ2ZXJzaW9uI..." into "data:application/json;base64,eyJ2ZXJzaW9uI..."
111+
*/
112+
public static String addBase64PrefixToEncodedSourceMap(String encoded) {
113+
return BASE64_URL_PREFIX + "application/json;" + BASE64_START + encoded;
114+
}
115+
106116
private SourceMapResolver() {}
107117
}

src/com/google/javascript/jscomp/serialization/TypedAstDeserializer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,11 +351,15 @@ private static void addInputSourceMap(
351351
boolean resolveSourceMapAnnotations,
352352
boolean parseInlineSourceMaps) {
353353
SourceFile sourceFile = deserializer.getSourceFile();
354-
String sourceMappingURL = deserializer.getSourceMappingURL();
354+
String sourceMappingURL = deserializer.getSourceMappingURL(); // This is the encoded source map.
355355

356356
if (sourceMappingURL != null && sourceMappingURL.length() > 0 && resolveSourceMapAnnotations) {
357+
// base64EncodedSourceMap adds "data:application/json;base64," prefix to the sourceMappingURL
358+
String base64EncodedSourceMap =
359+
SourceMapResolver.addBase64PrefixToEncodedSourceMap(sourceMappingURL);
357360
SourceFile sourceMapSourceFile =
358-
SourceMapResolver.extractSourceMap(sourceFile, sourceMappingURL, parseInlineSourceMaps);
361+
SourceMapResolver.extractSourceMap(
362+
sourceFile, base64EncodedSourceMap, parseInlineSourceMaps);
359363
if (sourceMapSourceFile != null) {
360364
compiler.addInputSourceMap(sourceFile.getName(), new SourceMapInput(sourceMapSourceFile));
361365
}

src/com/google/javascript/jscomp/serialization/TypedAstSerializer.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,17 @@ private LazyAst serializeScriptNode(Node script) {
118118
AstNode scriptProto = visit(script);
119119
this.subtreeSourceFiles.clear();
120120

121-
String sourceMappingURL = compiler.getInputSourceMappingURL(script.getSourceFileName());
121+
String encodedSourceMap = compiler.getBase64SourceMapContents(script.getSourceFileName());
122122

123123
LazyAst.Builder lazyAstBuilder =
124124
LazyAst.newBuilder().setScript(scriptProto.toByteString()).setSourceFile(sourceFile);
125125

126-
if (sourceMappingURL != null) {
127-
lazyAstBuilder.setSourceMappingUrl(sourceMappingURL);
126+
if (encodedSourceMap != null) {
127+
// This is the encoded source map taken from the inline sourcemap comment. It does not include
128+
// the base64 prefix.
129+
// E.g. We serialize "eyJ2ZXJzaW9uI..." from the "//# sourceMappingURL=
130+
// data:application/json;base64,eyJ2ZXJzaW9uI..." comment.
131+
lazyAstBuilder.setSourceMappingUrl(encodedSourceMap);
128132
}
129133

130134
return lazyAstBuilder.build();

src/com/google/javascript/rhino/typed_ast/typed_ast.proto

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ message LazyAst {
3434
bytes script = 1;
3535
// 1-based index into TypedAst::source_file_pool
3636
uint32 source_file = 2;
37-
// The URL for a source map associated with this source file (i.e. the URL
38-
// specified in "//# sourceMappingURL=" comments).
37+
// The encoded source map taken from the inline sourcemap comment
38+
// (base64-encoded "data url" stored in `//# sourceMappingURL=` comment).
39+
// E.g. If the last line of a .closure.js file is "//# sourceMappingURL=
40+
// data:application/json;base64,eyJ2ZXJzaW9uI...", then we will serialize
41+
// "eyJ2ZXJzaW9uI..." in this LazyAst's source_mapping_url field. We'll attach
42+
// the "data:application/json;base64," prefix during deserialization.
3943
string source_mapping_url = 3;
4044
}
4145

@@ -64,8 +68,8 @@ message NonLazyAst {
6468
AstNode script = 1;
6569
// 1-based index into TypedAst::source_file_pool
6670
uint32 source_file = 2;
67-
// The URL for a source map associated with this source file (i.e. the URL
68-
// specified in "//# sourceMappingURL=" comments).
71+
// The encoded source map taken from the inline sourcemap comment
72+
// (base64-encoded "data url" stored in `//# sourceMappingURL=` comment).
6973
string source_mapping_url = 3;
7074
}
7175

test/com/google/javascript/jscomp/SourceMapResolverTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,14 @@ public void testIntegration() {
116116
SourceFile.fromCode("somePath/hello.js", ""), url, false);
117117
assertThat(s.getName()).isEqualTo("somePath/relative/path/to/sourcemap/hello.js.map");
118118
}
119+
120+
@Test
121+
public void testAddBase64PrefixToEncodedSourceMap() {
122+
String base64Prefix = "data:application/json;base64,";
123+
String encodedSourceMap =
124+
"eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9vLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZm9vLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0lBR0UsV0FBWSxLQUFhO1FBQ3ZCLElBQUksQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDO0lBQ2pCLENBQUM7SUFDSCxRQUFDO0FBQUQsQ0FBQyxBQU5ELElBTUM7QUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMifQ==";
125+
String base64EncodedSourceMap =
126+
SourceMapResolver.addBase64PrefixToEncodedSourceMap(encodedSourceMap);
127+
assertThat(base64EncodedSourceMap).isEqualTo(base64Prefix + encodedSourceMap);
128+
}
119129
}

test/com/google/javascript/jscomp/serialization/SerializeAndDeserializeAstTest.java

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,33 @@ public void testEsModule() {
404404
});
405405
}
406406

407+
private static final String BASE64_PREFIX = "data:application/json;base64,";
408+
private static final String ENCODED_SOURCE_MAP =
409+
"eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9vLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZm9vLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0lBR0UsV0FBWSxLQUFhO1FBQ3ZCLElBQUksQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDO0lBQ2pCLENBQUM7SUFDSCxRQUFDO0FBQUQsQ0FBQyxBQU5ELElBTUM7QUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMifQ==";
410+
407411
@Test
408-
public void testSourceMaps() {
412+
public void testInlineSourceMaps() {
413+
String sourceMapTestCode =
414+
lines(
415+
"var X = (function () {",
416+
" function X(input) {",
417+
" this.y = input;",
418+
" }",
419+
" return X;",
420+
"}());");
421+
String sourceMappingURLComment = "//# sourceMappingURL=" + BASE64_PREFIX + ENCODED_SOURCE_MAP;
422+
;
423+
String code = sourceMapTestCode + "\n" + sourceMappingURLComment;
424+
425+
Result result = testAndReturnResult(srcs(code), expected(code));
426+
assertThat(result.compiler.getBase64SourceMapContents("testcode"))
427+
.isEqualTo(ENCODED_SOURCE_MAP);
428+
}
429+
430+
@Test
431+
public void testSourceMapsInSeparateMapFiles() {
432+
// Sourcemap URLs should be a base64 encoded data url, not the name of a .js.map file.
433+
// If we see a .js.map file, we will not serialize it.
409434
String sourceMapTestCode =
410435
lines(
411436
"var X = (function () {",
@@ -419,7 +444,7 @@ public void testSourceMaps() {
419444
String code = sourceMapTestCode + "\n" + sourceMappingURLComment;
420445

421446
Result result = testAndReturnResult(srcs(code), expected(code));
422-
assertThat(result.compiler.getInputSourceMappingURL("testcode")).isEqualTo(sourceMappingURL);
447+
assertThat(result.compiler.getBase64SourceMapContents("testcode")).isEqualTo(null);
423448
}
424449

425450
@Test
@@ -433,13 +458,13 @@ public void testSourceMapsWithoutResolvingSourceMapAnnotations() {
433458
" }",
434459
" return X;",
435460
"}());");
436-
String sourceMappingURL = "foo.js.map";
437-
String sourceMappingURLComment = "//# sourceMappingURL=" + sourceMappingURL;
461+
String sourceMappingURLComment = "//# sourceMappingURL=" + BASE64_PREFIX + ENCODED_SOURCE_MAP;
462+
;
438463
String code = sourceMapTestCode + "\n" + sourceMappingURLComment;
439464

440465
Result result = testAndReturnResult(srcs(code), expected(code));
441-
// Input source map not registered because `resolveSourceMapAnnotations = false`
442-
assertThat(result.compiler.getInputSourceMappingURL("testcode")).isNull();
466+
// Source map not registered because `resolveSourceMapAnnotations = false`
467+
assertThat(result.compiler.getBase64SourceMapContents("testcode")).isNull();
443468
}
444469

445470
@Test
@@ -453,23 +478,22 @@ public void testSourceMapsWithoutParsingInlineSourceMaps() {
453478
" }",
454479
" return X;",
455480
"}());");
456-
String sourceMappingURL = "foo.js.map";
457-
String sourceMappingURLComment = "//# sourceMappingURL=" + sourceMappingURL;
481+
String sourceMappingURLComment = "//# sourceMappingURL=" + BASE64_PREFIX + ENCODED_SOURCE_MAP;
482+
;
458483
String code = sourceMapTestCode + "\n" + sourceMappingURLComment;
459484

460485
Result result = testAndReturnResult(srcs(code), expected(code));
461-
// Input source map is registered when `parseInlineSourceMaps = false`, but we won't try to
486+
// Source map is registered when `parseInlineSourceMaps = false`, but we won't try to
462487
// parse it as a Base64 encoded source map.
463-
assertThat(result.compiler.getInputSourceMappingURL("testcode")).isEqualTo(sourceMappingURL);
488+
assertThat(result.compiler.getBase64SourceMapContents("testcode")).isEqualTo(null);
464489
}
465490

466491
@Test
467492
public void testConfiguredDirectorySourceMaps() {
468493
// We do not allow the TypeScript compiler to set "compilerOptions.sourceRoot" (option to
469494
// configure a directory to store
470-
// sourcemaps). Sourcemaps (.js.map) files are placed next to the .js files.
471-
// This means sourcemap URLs should be the name of the sourcemap file, not a path to the
472-
// sourcemap file. If we see a path, we will serialize only the name of the sourcemap file.
495+
// sourcemaps). Sourcemap URLs should be a base64 encoded data url, not a path to the
496+
// sourcemap file. If we see a path, we will not serialize anything.
473497
String sourceMapTestCode =
474498
lines(
475499
"var X = (function () {",
@@ -483,7 +507,7 @@ public void testConfiguredDirectorySourceMaps() {
483507
String code = sourceMapTestCode + "\n" + sourceMappingURLComment;
484508

485509
Result result = testAndReturnResult(srcs(code), expected(code));
486-
assertThat(result.compiler.getInputSourceMappingURL("testcode")).isEqualTo("foo.js.map");
510+
assertThat(result.compiler.getBase64SourceMapContents("testcode")).isEqualTo(null);
487511
}
488512

489513
@Test

test/com/google/javascript/jscomp/serialization/SerializeTypedAstPassTest.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,9 @@ public void serializesExternProperties() throws IOException {
425425
}
426426

427427
@Test
428-
public void serializesSourceMappingURL() throws InvalidProtocolBufferException {
428+
public void serializeInlineSourceMappingURL() throws InvalidProtocolBufferException {
429+
// We want TypedAST to support inline source maps (which are input source maps passed
430+
// by embedding a `//# sourceMappingURL=<url>` where <url> is a base64-encoded "data url").
429431
String sourceMapTestCode =
430432
lines(
431433
"var X = (function () {",
@@ -435,20 +437,23 @@ public void serializesSourceMappingURL() throws InvalidProtocolBufferException {
435437
" return X;",
436438
"}());");
437439

438-
String code = sourceMapTestCode + "\n//# sourceMappingURL=foo.js.map";
440+
String base64Prefix = "data:application/json;base64,";
441+
String encodedSourceMap =
442+
"eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9vLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZm9vLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0lBR0UsV0FBWSxLQUFhO1FBQ3ZCLElBQUksQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDO0lBQ2pCLENBQUM7SUFDSCxRQUFDO0FBQUQsQ0FBQyxBQU5ELElBTUM7QUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMifQ==";
443+
444+
String code = sourceMapTestCode + "\n//# sourceMappingURL=" + base64Prefix + encodedSourceMap;
439445

440446
SerializationResult result = compile(code);
441447

442448
LazyAst lazyAst = result.ast.getCodeAstList().get(0);
443449
String sourceMappingURL = lazyAst.getSourceMappingUrl();
444450

445-
assertThat(sourceMappingURL).isEqualTo("foo.js.map");
451+
assertThat(sourceMappingURL).isEqualTo(encodedSourceMap); // Do not serizile the base-64 prefix.
446452
}
447453

448454
@Test
449-
public void doesNotSerializeInlineSourceMappingURL() throws InvalidProtocolBufferException {
450-
// We do not want TypedAST to support inline source maps (which are input source maps passed
451-
// by embedding a `//# sourceMappingURL=<url>` where <url> is a base64-encoded "data url").
455+
public void doesNotSerializeInputSourceMappingURL() throws InvalidProtocolBufferException {
456+
// We do not want TypedAST to support input source maps (source maps in separate files).
452457
String sourceMapTestCode =
453458
lines(
454459
"var X = (function () {",
@@ -458,17 +463,14 @@ public void doesNotSerializeInlineSourceMappingURL() throws InvalidProtocolBuffe
458463
" return X;",
459464
"}());");
460465

461-
String base64EncodedSourceMappingURL =
462-
"data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9vLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdGVzdC9mb28udHMiXSwic291cmNlc0NvbnRlbnQiOlsidmFyIEEgPSAoZnVuY3Rpb24gKCkge1xuICAgIGZ1bmN0aW9uIEEoaW5wdXQpIHtcbiAgICAgICAgdGhpcy5hID0gaW5wdXQ7XG4gICAgfVxuICAgIHJldHVybiBBO1xufSgpKTtcbmNvbnNvbGUubG9nKG5ldyBBKDEpKTsiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7SUFHRSxXQUFZLEtBQWE7UUFDdkIsSUFBSSxDQUFDLENBQUMsR0FBRyxLQUFLLENBQUM7SUFDakIsQ0FBQztJQUNILFFBQUM7QUFBRCxDQUFDLEFBTkQsSUFNQztBQUVELE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyJ9";
463-
464-
String code = sourceMapTestCode + "\n//# sourceMappingURL=" + base64EncodedSourceMappingURL;
466+
String code = sourceMapTestCode + "\n//# sourceMappingURL=foo.js.map";
465467

466468
SerializationResult result = compile(code);
467469

468470
LazyAst lazyAst = result.ast.getCodeAstList().get(0);
469471
String sourceMappingURL = lazyAst.getSourceMappingUrl();
470472

471-
assertThat(sourceMappingURL).isEmpty(); // Do not serizile this base-64 sourceMappingURL.
473+
assertThat(sourceMappingURL).isEmpty();
472474
}
473475

474476
private AstNode compileToAstNode(String source) {

0 commit comments

Comments
 (0)