Skip to content

Commit 43e0388

Browse files
author
Amjad Masad
committed
[react-packager] Combine source maps coming from transformer
Summary: @public Fixes [#393](react/react-native#393). Currently the transformer assumes line-preserving compilers and defaults to a super-fast source map generation process. However, we need to support compilers that aren't preserving lines. I had feared this wuold slow down the server but I came about a little known peace of the spec that defines an "indexed source map" just for the purpose of concating files: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit Test Plan: 1. runJestTests.sh 2. run server and click around example apps 3. add a custom transporter like babel 4. add a custom file and a debugger statement 5. debug in chrome and make sure it works redbox still works
1 parent 48b281d commit 43e0388

7 files changed

Lines changed: 308 additions & 79 deletions

File tree

packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
jest
1212
.dontMock('worker-farm')
1313
.dontMock('os')
14+
.dontMock('../../lib/ModuleTransport')
1415
.dontMock('../index');
1516

1617
var OPTIONS = {
@@ -37,7 +38,7 @@ describe('Transformer', function() {
3738

3839
pit('should loadFileAndTransform', function() {
3940
workers.mockImpl(function(data, callback) {
40-
callback(null, { code: 'transformed' });
41+
callback(null, { code: 'transformed', map: 'sourceMap' });
4142
});
4243
require('fs').readFile.mockImpl(function(file, callback) {
4344
callback(null, 'content');
@@ -47,6 +48,7 @@ describe('Transformer', function() {
4748
.then(function(data) {
4849
expect(data).toEqual({
4950
code: 'transformed',
51+
map: 'sourceMap',
5052
sourcePath: 'file',
5153
sourceCode: 'content'
5254
});

packager/react-packager/src/JSTransformer/index.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var Cache = require('./Cache');
1414
var workerFarm = require('worker-farm');
1515
var declareOpts = require('../lib/declareOpts');
1616
var util = require('util');
17+
var ModuleTransport = require('../lib/ModuleTransport');
1718

1819
var readFile = Promise.promisify(fs.readFile);
1920

@@ -100,11 +101,12 @@ Transformer.prototype.loadFileAndTransform = function(filePath) {
100101
throw formatError(res.error, filePath, sourceCode);
101102
}
102103

103-
return {
104+
return new ModuleTransport({
104105
code: res.code,
106+
map: res.map,
105107
sourcePath: filePath,
106-
sourceCode: sourceCode
107-
};
108+
sourceCode: sourceCode,
109+
});
108110
}
109111
);
110112
});

packager/react-packager/src/Packager/Package.js

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var _ = require('underscore');
1212
var base64VLQ = require('./base64-vlq');
1313
var UglifyJS = require('uglify-js');
14+
var ModuleTransport = require('../lib/ModuleTransport');
1415

1516
module.exports = Package;
1617

@@ -19,22 +20,25 @@ function Package(sourceMapUrl) {
1920
this._modules = [];
2021
this._assets = [];
2122
this._sourceMapUrl = sourceMapUrl;
23+
this._shouldCombineSourceMaps = false;
2224
}
2325

2426
Package.prototype.setMainModuleId = function(moduleId) {
2527
this._mainModuleId = moduleId;
2628
};
2729

28-
Package.prototype.addModule = function(
29-
transformedCode,
30-
sourceCode,
31-
sourcePath
32-
) {
33-
this._modules.push({
34-
transformedCode: transformedCode,
35-
sourceCode: sourceCode,
36-
sourcePath: sourcePath
37-
});
30+
Package.prototype.addModule = function(module) {
31+
if (!(module instanceof ModuleTransport)) {
32+
throw new Error('Expeceted a ModuleTransport object');
33+
}
34+
35+
// If we get a map from the transformer we'll switch to a mode
36+
// were we're combining the source maps as opposed to
37+
if (!this._shouldCombineSourceMaps && module.map != null) {
38+
this._shouldCombineSourceMaps = true;
39+
}
40+
41+
this._modules.push(module);
3842
};
3943

4044
Package.prototype.addAsset = function(asset) {
@@ -45,11 +49,12 @@ Package.prototype.finalize = function(options) {
4549
options = options || {};
4650
if (options.runMainModule) {
4751
var runCode = ';require("' + this._mainModuleId + '");';
48-
this.addModule(
49-
runCode,
50-
runCode,
51-
'RunMainModule.js'
52-
);
52+
this.addModule(new ModuleTransport({
53+
code: runCode,
54+
virtual: true,
55+
sourceCode: runCode,
56+
sourcePath: 'RunMainModule.js'
57+
}));
5358
}
5459

5560
Object.freeze(this._modules);
@@ -67,7 +72,7 @@ Package.prototype._assertFinalized = function() {
6772

6873
Package.prototype._getSource = function() {
6974
if (this._source == null) {
70-
this._source = _.pluck(this._modules, 'transformedCode').join('\n');
75+
this._source = _.pluck(this._modules, 'code').join('\n');
7176
}
7277
return this._source;
7378
};
@@ -136,10 +141,50 @@ Package.prototype.getMinifiedSourceAndMap = function() {
136141
}
137142
};
138143

144+
/**
145+
* I found a neat trick in the sourcemap spec that makes it easy
146+
* to concat sourcemaps. The `sections` field allows us to combine
147+
* the sourcemap easily by adding an offset. Tested on chrome.
148+
* Seems like it's not yet in Firefox but that should be fine for
149+
* now.
150+
*/
151+
Package.prototype._getCombinedSourceMaps = function(options) {
152+
var result = {
153+
version: 3,
154+
file: 'bundle.js',
155+
sections: [],
156+
};
157+
158+
var line = 0;
159+
this._modules.forEach(function(module) {
160+
var map = module.map;
161+
if (module.virtual) {
162+
map = generateSourceMapForVirtualModule(module);
163+
}
164+
165+
if (options.excludeSource) {
166+
map = _.extend({}, map, {sourcesContent: []});
167+
}
168+
169+
result.sections.push({
170+
offset: { line: line, column: 0 },
171+
map: map,
172+
});
173+
line += module.code.split('\n').length;
174+
});
175+
176+
return result;
177+
};
178+
139179
Package.prototype.getSourceMap = function(options) {
140180
this._assertFinalized();
141181

142182
options = options || {};
183+
184+
if (this._shouldCombineSourceMaps) {
185+
return this._getCombinedSourceMaps(options);
186+
}
187+
143188
var mappings = this._getMappings();
144189
var map = {
145190
file: 'bundle.js',
@@ -168,13 +213,14 @@ Package.prototype._getMappings = function() {
168213
// except for the lineno mappinp: curLineno - prevLineno = 1; Which is C.
169214
var line = 'AACA';
170215

216+
var moduleLines = Object.create(null);
171217
var mappings = '';
172218
for (var i = 0; i < modules.length; i++) {
173219
var module = modules[i];
174-
var transformedCode = module.transformedCode;
220+
var code = module.code;
175221
var lastCharNewLine = false;
176-
module.lines = 0;
177-
for (var t = 0; t < transformedCode.length; t++) {
222+
moduleLines[module.sourcePath] = 0;
223+
for (var t = 0; t < code.length; t++) {
178224
if (t === 0 && i === 0) {
179225
mappings += firstLine;
180226
} else if (t === 0) {
@@ -183,13 +229,15 @@ Package.prototype._getMappings = function() {
183229
// This is the only place were we actually don't know the mapping ahead
184230
// of time. When it's a new module (and not the first) the lineno
185231
// mapping is 0 (current) - number of lines in prev module.
186-
mappings += base64VLQ.encode(0 - modules[i - 1].lines);
232+
mappings += base64VLQ.encode(
233+
0 - moduleLines[modules[i - 1].sourcePath]
234+
);
187235
mappings += 'A';
188236
} else if (lastCharNewLine) {
189-
module.lines++;
237+
moduleLines[module.sourcePath]++;
190238
mappings += line;
191239
}
192-
lastCharNewLine = transformedCode[t] === '\n';
240+
lastCharNewLine = code[t] === '\n';
193241
if (lastCharNewLine) {
194242
mappings += ';';
195243
}
@@ -218,7 +266,25 @@ Package.prototype.getDebugInfo = function() {
218266
this._modules.map(function(m) {
219267
return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' +
220268
'<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
221-
_.escape(m.transformedCode) + '</pre></code></div>';
269+
_.escape(m.code) + '</pre></code></div>';
222270
}).join('\n'),
223271
].join('\n');
224272
};
273+
274+
function generateSourceMapForVirtualModule(module) {
275+
// All lines map 1-to-1
276+
var mappings = 'AAAA;';
277+
278+
for (var i = 1; i < module.code.split('\n').length; i++) {
279+
mappings += 'AACA;';
280+
}
281+
282+
return {
283+
version: 3,
284+
sources: [ module.sourcePath ],
285+
names: [],
286+
mappings: mappings,
287+
file: module.sourcePath,
288+
sourcesContent: [ module.code ],
289+
};
290+
}

0 commit comments

Comments
 (0)