4
4
import com .google .common .collect .ImmutableList ;
5
5
import com .google .common .collect .ImmutableMap ;
6
6
import com .google .common .reflect .TypeToken ;
7
+ import com .google .debugging .sourcemap .SourceMapConsumerV3 ;
8
+ import com .google .debugging .sourcemap .SourceMapGeneratorV3 ;
7
9
import com .google .gson .Gson ;
8
10
import com .google .gson .GsonBuilder ;
9
11
import com .google .javascript .jscomp .Compiler ;
32
34
import java .io .BufferedReader ;
33
35
import java .io .BufferedWriter ;
34
36
import java .io .File ;
37
+ import java .io .FilterWriter ;
35
38
import java .io .IOException ;
36
39
import java .io .InputStream ;
37
40
import java .io .InputStreamReader ;
38
41
import java .io .OutputStream ;
39
42
import java .io .OutputStreamWriter ;
43
+ import java .io .Writer ;
40
44
import java .lang .reflect .Type ;
41
45
import java .nio .charset .StandardCharsets ;
42
46
import java .nio .file .Files ;
@@ -121,6 +125,7 @@ public Task resolve(Project project, Config config) {
121
125
List <DependencyInfoAndSource > dependencyInfos = new ArrayList <>();
122
126
Compiler jsCompiler = new Compiler (System .err );//TODO before merge, write this to the log
123
127
128
+ Path sourcesPath = context .outputPath ().resolve (Closure .SOURCES_DIRECTORY_NAME );
124
129
if (incrementalEnabled && context .lastSuccessfulOutput ().isPresent ()) {
125
130
// collect any dep info from disk for existing files
126
131
final Map <String , DependencyInfoAndSource > depInfoMap ;
@@ -145,7 +150,7 @@ public Task resolve(Project project, Config config) {
145
150
} else {
146
151
// ADD or MODIFY
147
152
CompilerInput input = new CompilerInput (SourceFile .builder ()
148
- .withPath (context . outputPath (). resolve ( Closure . SOURCES_DIRECTORY_NAME ) .resolve (change .getSourcePath ()))
153
+ .withPath (sourcesPath .resolve (change .getSourcePath ()))
149
154
.withOriginalPath (change .getSourcePath ().toString ())
150
155
.build ());
151
156
input .setCompiler (jsCompiler );
@@ -166,7 +171,7 @@ public Task resolve(Project project, Config config) {
166
171
for (Input jsInput : js ) {
167
172
for (CachedPath path : jsInput .getFilesAndHashes ()) {
168
173
CompilerInput input = new CompilerInput (SourceFile .builder ()
169
- .withPath (context . outputPath (). resolve ( Closure . SOURCES_DIRECTORY_NAME ) .resolve (path .getSourcePath ()))
174
+ .withPath (sourcesPath .resolve (path .getSourcePath ()))
170
175
.withOriginalPath (path .getSourcePath ().toString ())
171
176
.build ());
172
177
input .setCompiler (jsCompiler );
@@ -182,6 +187,8 @@ public Task resolve(Project project, Config config) {
182
187
183
188
// TODO optional/stretch-goal find first change in the list, so we can keep old prefix of bundle output
184
189
190
+ SourceMapGeneratorV3 sourceMapGenerator = new SourceMapGeneratorV3 ();
191
+
185
192
// rebundle all (optional: remaining) files using this already handled sort
186
193
ClosureBundler bundler = new ClosureBundler (Transpiler .NULL , new BaseTranspiler (
187
194
new BaseTranspiler .CompilerSupplier (
@@ -195,27 +202,52 @@ public Task resolve(Project project, Config config) {
195
202
ImmutableMap .of ()
196
203
),
197
204
""
198
- )).useEval (true );
205
+ )).useEval (false );
206
+
207
+ String sourcemapOutFileName = fileNameKey + ".bundle.js.map" ;
199
208
200
- try (OutputStream outputStream = Files .newOutputStream (Paths .get (outputFile ));
201
- BufferedWriter bundleOut = new BufferedWriter (new OutputStreamWriter (outputStream , StandardCharsets .UTF_8 ))) {
209
+ try (OutputStream outputStream = Files .newOutputStream (outputFilePath );
210
+ BufferedWriter bundleOut = new BufferedWriter (new OutputStreamWriter (outputStream , StandardCharsets .UTF_8 ));
211
+ LineCountingWriter writer = new LineCountingWriter (bundleOut )) {
202
212
for (DependencyInfoAndSource info : sorter .getSortedList ()) {
203
213
String code = info .getSource ();
204
214
String name = info .getName ();
215
+ String sourcemapContents = info .loadSourcemap (sourcesPath );
205
216
206
217
//TODO do we actually need this?
207
218
if (Compiler .isFillFileName (name ) && code .isEmpty ()) {
208
219
continue ;
209
220
}
210
221
211
- // append this file and a comment where it came from
212
- bundleOut .append ("//" ).append (name ).append ("\n " );
213
- bundler .withPath (name ).withSourceUrl (Closure .SOURCES_DIRECTORY_NAME + "/" + name ).appendTo (bundleOut , info , code );
214
- bundleOut .append ("\n " );
222
+ writer .append ("//" ).append (name ).append ("\n " );
223
+
224
+ if (sourcemapContents != null ) {
225
+ sourceMapGenerator .setStartingPosition (writer .getLine (), 0 );
226
+ SourceMapConsumerV3 section = new SourceMapConsumerV3 ();
227
+ section .parse (sourcemapContents );
228
+ section .visitMappings ((sourceName , symbolName , sourceStartPosition , startPosition , endPosition ) -> sourceMapGenerator .addMapping (Paths .get (name ).resolveSibling (sourceName ).toString (), symbolName , sourceStartPosition , startPosition , endPosition ));
229
+ for (String source : section .getOriginalSources ()) {
230
+ String content = Files .readString (sourcesPath .resolve (name ).resolveSibling (source ));
231
+ sourceMapGenerator .addSourcesContent (Paths .get (name ).resolveSibling (source ).toString (), content );
232
+ }
233
+ }
215
234
235
+ // append this file and a comment where it came from
236
+ bundler .withPath (name ).appendTo (writer , info , code );
237
+ writer .append ("\n " );
216
238
}
217
239
240
+ // write a reference to our new sourcemaps
241
+ // writer.append("// " + writer.getLine()).append("\n");
242
+ writer .append ("//# sourceMappingURL=" ).append (sourcemapOutFileName ).append ('\n' );
218
243
}
244
+
245
+ // TODO hash in the name
246
+ try (OutputStream outputStream = Files .newOutputStream (outputFilePath .resolveSibling (sourcemapOutFileName ));
247
+ BufferedWriter smOut = new BufferedWriter (new OutputStreamWriter (outputStream , StandardCharsets .UTF_8 ))) {
248
+ sourceMapGenerator .appendTo (smOut , fileNameKey );
249
+ }
250
+
219
251
// append dependency info to deserialize on some incremental rebuild
220
252
try (OutputStream outputStream = Files .newOutputStream (context .outputPath ().resolve ("depInfo.json" ));
221
253
BufferedWriter jsonOut = new BufferedWriter (new OutputStreamWriter (outputStream , StandardCharsets .UTF_8 ))) {
@@ -238,6 +270,56 @@ public Task resolve(Project project, Config config) {
238
270
};
239
271
}
240
272
273
+
274
+ public static class LineCountingWriter extends FilterWriter {
275
+ private int line ;
276
+ protected LineCountingWriter (Writer out ) {
277
+ super (out );
278
+ }
279
+
280
+ public int getLine () {
281
+ return line ;
282
+ }
283
+
284
+ @ Override
285
+ public void write (int c ) throws IOException {
286
+ if (c == '\n' ) {
287
+ line ++;
288
+ }
289
+ super .write (c );
290
+ }
291
+
292
+ @ Override
293
+ public void write (char [] cbuf , int off , int len ) throws IOException {
294
+ for (char c : cbuf ) {
295
+ if (c == '\n' ) {
296
+ line ++;
297
+ }
298
+ }
299
+ super .write (cbuf , off , len );
300
+ }
301
+
302
+ @ Override
303
+ public void write (String str , int off , int len ) throws IOException {
304
+ str .chars ().skip (off ).limit (len ).forEach (c -> {
305
+ if (c == '\n' ) {
306
+ line ++;
307
+ }
308
+ });
309
+ super .write (str , off , len );
310
+ }
311
+
312
+ @ Override
313
+ public void write (char [] cbuf ) throws IOException {
314
+ for (char c : cbuf ) {
315
+ if (c == '\n' ) {
316
+ line ++;
317
+ }
318
+ }
319
+ super .write (cbuf );
320
+ }
321
+ }
322
+
241
323
public interface SourceSupplier {
242
324
String get () throws IOException ;
243
325
}
@@ -309,6 +391,17 @@ public boolean getHasExternsAnnotation() {
309
391
public boolean getHasNoCompileAnnotation () {
310
392
return delegate .getHasNoCompileAnnotation ();
311
393
}
394
+
395
+ public String loadSourcemap (Path outPath ) throws IOException {
396
+ String sourceMappingUrlMarker = "//# sourceMappingURL=" ;
397
+ int offset = getSource ().lastIndexOf (sourceMappingUrlMarker );
398
+ if (offset == -1 ) {
399
+ return null ;
400
+ }
401
+ int urlPos = offset + sourceMappingUrlMarker .length ();
402
+ String sourcemapName = getSource ().substring (urlPos ).split ("\\ s" )[0 ];
403
+ return Files .readString (outPath .resolve (getName ()).resolveSibling (sourcemapName ));
404
+ }
312
405
}
313
406
314
407
public static class DependencyInfoFormat implements DependencyInfo {
0 commit comments