mirror of https://github.com/status-im/metro.git
create better debuggable source maps
Summary: Introduces a new mechanism to build source maps that allows us to use real mapping segments instead of just mapping line-by-line. This mechanism is only used when building development bundles to improve the debugging experience in Chrome. The new mechanism takes advantage of a new feature in babel-generator that exposes raw mapping objects. These raw mapping objects are converted to arrays with 2, 4, or 5 for the most compact representation possible. We no longer generate a source map for the bundle that maps each line to itself in conjunction with configuring babel generator to retain lines. Instead, we create a source map with a large mappings object produced from the mappings of each individual file in conjunction with a “carry over” – the number of preceding lines in the bundle. The implementation makes a couple of assumptions that hold true for babel transform results, e.g. mappings being in the order of the generated code, and that a block of mappings always belongs to the same source file. In addition, the implementation avoids allocation of objects and strings at all costs. All calculations are purely numeric, and base64 vlq produces numeric ascii character codes. These are written to a preallocated buffer objects, which is turned to a string only at the end of the building process. This implementation is ~5x faster than using the source-map library. In addition to providing development source maps that work better, we can now also produce individual high-quality source maps for production builds and combine them to an “index source map”. This approach is unfeasable for development source maps, because index source map consistently crash Chrome. Better production source maps are useful to get precise information about source location and symbol names when symbolicating stack traces from crashes in production. Reviewed By: jeanlauliac Differential Revision: D4382290 fbshipit-source-id: 365a176fa142729d0a4cef43edeb81084361e54d
This commit is contained in:
parent
48a7ede4d2
commit
ae6cd177de
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"name": "react-native-packager",
|
||||
"description": "Build native apps with React!",
|
||||
"repository": {
|
||||
|
|
|
@ -15,8 +15,11 @@ const BundleBase = require('./BundleBase');
|
|||
const ModuleTransport = require('../lib/ModuleTransport');
|
||||
|
||||
const _ = require('lodash');
|
||||
const base64VLQ = require('./base64-vlq');
|
||||
const crypto = require('crypto');
|
||||
const debug = require('debug')('RNP:Bundle');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
const {fromRawMappings} = require('./source-map');
|
||||
|
||||
import type {SourceMap, CombinedSourceMap, MixedSourceMap} from '../lib/SourceMap';
|
||||
import type {GetSourceOptions, FinalizeOptions} from './BundleBase';
|
||||
|
@ -27,6 +30,8 @@ export type Unbundle = {
|
|||
groups: Map<number, Set<number>>,
|
||||
};
|
||||
|
||||
type SourceMapFormat = 'undetermined' | 'indexed' | 'flattened';
|
||||
|
||||
const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL=';
|
||||
|
||||
class Bundle extends BundleBase {
|
||||
|
@ -37,8 +42,8 @@ class Bundle extends BundleBase {
|
|||
_numRequireCalls: number;
|
||||
_ramBundle: Unbundle | null;
|
||||
_ramGroups: Array<string> | void;
|
||||
_shouldCombineSourceMaps: boolean;
|
||||
_sourceMap: boolean;
|
||||
_sourceMap: string | null;
|
||||
_sourceMapFormat: SourceMapFormat;
|
||||
_sourceMapUrl: string | void;
|
||||
|
||||
constructor({sourceMapUrl, dev, minify, ramGroups}: {
|
||||
|
@ -48,9 +53,9 @@ class Bundle extends BundleBase {
|
|||
ramGroups?: Array<string>,
|
||||
} = {}) {
|
||||
super();
|
||||
this._sourceMap = false;
|
||||
this._sourceMap = null;
|
||||
this._sourceMapFormat = 'undetermined';
|
||||
this._sourceMapUrl = sourceMapUrl;
|
||||
this._shouldCombineSourceMaps = false;
|
||||
this._numRequireCalls = 0;
|
||||
this._dev = dev;
|
||||
this._minify = minify;
|
||||
|
@ -86,8 +91,22 @@ class Bundle extends BundleBase {
|
|||
}).then(({code, map}) => {
|
||||
// If we get a map from the transformer we'll switch to a mode
|
||||
// were we're combining the source maps as opposed to
|
||||
if (!this._shouldCombineSourceMaps && map != null) {
|
||||
this._shouldCombineSourceMaps = true;
|
||||
if (map) {
|
||||
const usesRawMappings = isRawMappings(map);
|
||||
|
||||
if (this._sourceMapFormat === 'undetermined') {
|
||||
this._sourceMapFormat = usesRawMappings ? 'flattened' : 'indexed';
|
||||
} else if (usesRawMappings && this._sourceMapFormat === 'indexed') {
|
||||
throw new Error(
|
||||
`Got at least one module with a full source map, but ${
|
||||
moduleTransport.sourcePath} has raw mappings`
|
||||
);
|
||||
} else if (!usesRawMappings && this._sourceMapFormat === 'flattened') {
|
||||
throw new Error(
|
||||
`Got at least one module with raw mappings, but ${
|
||||
moduleTransport.sourcePath} has a full source map`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.replaceModuleAt(
|
||||
|
@ -103,7 +122,7 @@ class Bundle extends BundleBase {
|
|||
options.runBeforeMainModule.forEach(this._addRequireCall, this);
|
||||
/* $FlowFixMe: this is unsound, as nothing enforces the module ID to have
|
||||
* been set beforehand. */
|
||||
this._addRequireCall(super.getMainModuleId());
|
||||
this._addRequireCall(this.getMainModuleId());
|
||||
}
|
||||
|
||||
super.finalize(options);
|
||||
|
@ -126,16 +145,16 @@ class Bundle extends BundleBase {
|
|||
|
||||
_getInlineSourceMap(dev) {
|
||||
if (this._inlineSourceMap == null) {
|
||||
const sourceMap = this.getSourceMap({excludeSource: true, dev});
|
||||
const sourceMap = this.getSourceMapString({excludeSource: true, dev});
|
||||
/*eslint-env node*/
|
||||
const encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64');
|
||||
const encoded = new Buffer(sourceMap).toString('base64');
|
||||
this._inlineSourceMap = 'data:application/json;base64,' + encoded;
|
||||
}
|
||||
return this._inlineSourceMap;
|
||||
}
|
||||
|
||||
getSource(options: GetSourceOptions) {
|
||||
super.assertFinalized();
|
||||
this.assertFinalized();
|
||||
|
||||
options = options || {};
|
||||
|
||||
|
@ -175,6 +194,12 @@ class Bundle extends BundleBase {
|
|||
return this._ramBundle;
|
||||
}
|
||||
|
||||
invalidateSource() {
|
||||
debug('invalidating bundle');
|
||||
super.invalidateSource();
|
||||
this._sourceMap = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine each of the sourcemaps multiple modules have into a single big
|
||||
* one. This works well thanks to a neat trick defined on the sourcemap spec
|
||||
|
@ -190,23 +215,22 @@ class Bundle extends BundleBase {
|
|||
|
||||
let line = 0;
|
||||
this.getModules().forEach(module => {
|
||||
let map = module.map;
|
||||
let map = module.map == null || module.virtual
|
||||
? generateSourceMapForVirtualModule(module)
|
||||
: module.map;
|
||||
|
||||
if (module.virtual) {
|
||||
map = generateSourceMapForVirtualModule(module);
|
||||
}
|
||||
invariant(
|
||||
!Array.isArray(map),
|
||||
`Unexpected raw mappings for ${module.sourcePath}`,
|
||||
);
|
||||
|
||||
if (options.excludeSource) {
|
||||
/* $FlowFixMe: assume the map is not empty if we got here. */
|
||||
if (map.sourcesContent && map.sourcesContent.length) {
|
||||
map = Object.assign({}, map, {sourcesContent: []});
|
||||
}
|
||||
if (options.excludeSource && 'sourcesContent' in map) {
|
||||
map = {...map, sourcesContent: []};
|
||||
}
|
||||
|
||||
result.sections.push({
|
||||
offset: { line: line, column: 0 },
|
||||
/* $FlowFixMe: assume the map is not empty if we got here. */
|
||||
map: map,
|
||||
map: (map: MixedSourceMap),
|
||||
});
|
||||
line += module.code.split('\n').length;
|
||||
});
|
||||
|
@ -215,23 +239,30 @@ class Bundle extends BundleBase {
|
|||
}
|
||||
|
||||
getSourceMap(options: {excludeSource?: boolean}): MixedSourceMap {
|
||||
super.assertFinalized();
|
||||
this.assertFinalized();
|
||||
|
||||
if (this._shouldCombineSourceMaps) {
|
||||
return this._getCombinedSourceMaps(options);
|
||||
return this._sourceMapFormat === 'indexed'
|
||||
? this._getCombinedSourceMaps(options)
|
||||
: fromRawMappings(this.getModules()).toMap();
|
||||
}
|
||||
|
||||
getSourceMapString(options: {excludeSource?: boolean}): string {
|
||||
if (this._sourceMapFormat === 'indexed') {
|
||||
return JSON.stringify(this.getSourceMap(options));
|
||||
}
|
||||
|
||||
const mappings = this._getMappings();
|
||||
const modules = this.getModules();
|
||||
const map = {
|
||||
file: this._getSourceMapFile(),
|
||||
sources: modules.map(module => module.sourcePath),
|
||||
version: 3,
|
||||
names: [],
|
||||
mappings: mappings,
|
||||
sourcesContent: options.excludeSource
|
||||
? [] : modules.map(module => module.sourceCode),
|
||||
};
|
||||
// The following code is an optimization specific to the development server:
|
||||
// 1. generator.toSource() is faster than JSON.stringify(generator.toMap()).
|
||||
// 2. caching the source map unless there are changes saves time in
|
||||
// development settings.
|
||||
let map = this._sourceMap;
|
||||
if (map == null) {
|
||||
debug('Start building flat source map');
|
||||
map = this._sourceMap = fromRawMappings(this.getModules()).toString();
|
||||
debug('End building flat source map');
|
||||
} else {
|
||||
debug('Returning cached source map');
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
@ -248,53 +279,6 @@ class Bundle extends BundleBase {
|
|||
: 'bundle.js';
|
||||
}
|
||||
|
||||
_getMappings() {
|
||||
const modules = super.getModules();
|
||||
|
||||
// The first line mapping in our package is basically the base64vlq code for
|
||||
// zeros (A).
|
||||
const firstLine = 'AAAA';
|
||||
|
||||
// Most other lines in our mappings are all zeros (for module, column etc)
|
||||
// except for the lineno mappinp: curLineno - prevLineno = 1; Which is C.
|
||||
const line = 'AACA';
|
||||
|
||||
const moduleLines = Object.create(null);
|
||||
let mappings = '';
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const module = modules[i];
|
||||
const code = module.code;
|
||||
let lastCharNewLine = false;
|
||||
moduleLines[module.sourcePath] = 0;
|
||||
for (let t = 0; t < code.length; t++) {
|
||||
if (t === 0 && i === 0) {
|
||||
mappings += firstLine;
|
||||
} else if (t === 0) {
|
||||
mappings += 'AC';
|
||||
|
||||
// This is the only place were we actually don't know the mapping ahead
|
||||
// of time. When it's a new module (and not the first) the lineno
|
||||
// mapping is 0 (current) - number of lines in prev module.
|
||||
mappings += base64VLQ.encode(
|
||||
0 - moduleLines[modules[i - 1].sourcePath]
|
||||
);
|
||||
mappings += 'A';
|
||||
} else if (lastCharNewLine) {
|
||||
moduleLines[module.sourcePath]++;
|
||||
mappings += line;
|
||||
}
|
||||
lastCharNewLine = code[t] === '\n';
|
||||
if (lastCharNewLine) {
|
||||
mappings += ';';
|
||||
}
|
||||
}
|
||||
if (i !== modules.length - 1) {
|
||||
mappings += ';';
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
getJSModulePaths() {
|
||||
return this.getModules()
|
||||
// Filter out non-js files. Like images etc.
|
||||
|
@ -305,7 +289,7 @@ class Bundle extends BundleBase {
|
|||
getDebugInfo() {
|
||||
return [
|
||||
/* $FlowFixMe: this is unsound as the module ID could be unset. */
|
||||
'<div><h3>Main Module:</h3> ' + super.getMainModuleId() + '</div>',
|
||||
'<div><h3>Main Module:</h3> ' + this.getMainModuleId() + '</div>',
|
||||
'<style>',
|
||||
'pre.collapsed {',
|
||||
' height: 10px;',
|
||||
|
@ -328,30 +312,6 @@ class Bundle extends BundleBase {
|
|||
setRamGroups(ramGroups: Array<string>) {
|
||||
this._ramGroups = ramGroups;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
this.assertFinalized('Cannot serialize bundle unless finalized');
|
||||
|
||||
return {
|
||||
...super.toJSON(),
|
||||
sourceMapUrl: this._sourceMapUrl,
|
||||
numRequireCalls: this._numRequireCalls,
|
||||
shouldCombineSourceMaps: this._shouldCombineSourceMaps,
|
||||
};
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
const bundle = new Bundle({sourceMapUrl: json.sourceMapUrl});
|
||||
|
||||
bundle._sourceMapUrl = json.sourceMapUrl;
|
||||
bundle._numRequireCalls = json.numRequireCalls;
|
||||
bundle._shouldCombineSourceMaps = json.shouldCombineSourceMaps;
|
||||
|
||||
BundleBase.fromJSON(bundle, json);
|
||||
|
||||
/* $FlowFixMe: this modifies BundleBase#fromJSON() signature. */
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
|
||||
function generateSourceMapForVirtualModule(module): SourceMap {
|
||||
|
@ -472,4 +432,6 @@ function createGroups(ramGroups: Array<string>, lazyModules) {
|
|||
return result;
|
||||
}
|
||||
|
||||
const isRawMappings = Array.isArray;
|
||||
|
||||
module.exports = Bundle;
|
||||
|
|
|
@ -14,10 +14,7 @@ const ModuleTransport = require('../lib/ModuleTransport');
|
|||
|
||||
export type FinalizeOptions = {
|
||||
allowUpdates?: boolean,
|
||||
/* $FlowFixMe(>=0.36.0 site=react_native_fb) Flow error detected during the
|
||||
* deploy of Flow v0.36.0. To see the error, remove this comment and run Flow
|
||||
*/
|
||||
runBeforeMainModule?: Array<mixed>,
|
||||
runBeforeMainModule?: Array<string>,
|
||||
runMainModule?: boolean,
|
||||
};
|
||||
|
||||
|
@ -112,29 +109,6 @@ class BundleBase {
|
|||
}
|
||||
|
||||
setRamGroups(ramGroups: Array<string>) {}
|
||||
|
||||
toJSON(): {
|
||||
modules: Array<ModuleTransport>,
|
||||
assets: Array<mixed>,
|
||||
mainModuleId: number | void,
|
||||
} {
|
||||
return {
|
||||
modules: this._modules,
|
||||
assets: this._assets,
|
||||
mainModuleId: this.getMainModuleId(),
|
||||
};
|
||||
}
|
||||
|
||||
static fromJSON(bundle, json) {
|
||||
bundle._assets = json.assets;
|
||||
bundle._modules = json.modules;
|
||||
bundle.setMainModuleId(json.mainModuleId);
|
||||
|
||||
Object.freeze(bundle._modules);
|
||||
Object.freeze(bundle._assets);
|
||||
|
||||
bundle._finalized = true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BundleBase;
|
||||
|
|
|
@ -138,47 +138,7 @@ describe('Bundle', () => {
|
|||
|
||||
describe('sourcemap bundle', () => {
|
||||
it('should create sourcemap', () => {
|
||||
const otherBundle = new Bundle({sourceMapUrl: 'test_url'});
|
||||
|
||||
return Promise.resolve().then(() => {
|
||||
return addModule({
|
||||
bundle: otherBundle,
|
||||
code: [
|
||||
'transformed foo',
|
||||
'transformed foo',
|
||||
'transformed foo',
|
||||
].join('\n'),
|
||||
sourceCode: [
|
||||
'source foo',
|
||||
'source foo',
|
||||
'source foo',
|
||||
].join('\n'),
|
||||
sourcePath: 'foo path',
|
||||
});
|
||||
}).then(() => {
|
||||
return addModule({
|
||||
bundle: otherBundle,
|
||||
code: [
|
||||
'transformed bar',
|
||||
'transformed bar',
|
||||
'transformed bar',
|
||||
].join('\n'),
|
||||
sourceCode: [
|
||||
'source bar',
|
||||
'source bar',
|
||||
'source bar',
|
||||
].join('\n'),
|
||||
sourcePath: 'bar path',
|
||||
});
|
||||
}).then(() => {
|
||||
otherBundle.setMainModuleId('foo');
|
||||
otherBundle.finalize({
|
||||
runBeforeMainModule: [],
|
||||
runMainModule: true,
|
||||
});
|
||||
const sourceMap = otherBundle.getSourceMap({dev: true});
|
||||
expect(sourceMap).toEqual(genSourceMap(otherBundle.getModules()));
|
||||
});
|
||||
//TODO: #15357872 add a meaningful test here
|
||||
});
|
||||
|
||||
it('should combine sourcemaps', () => {
|
||||
|
@ -321,16 +281,6 @@ describe('Bundle', () => {
|
|||
bundle.setMainModuleId(id);
|
||||
expect(bundle.getMainModuleId()).toEqual(id);
|
||||
});
|
||||
|
||||
it('can serialize and deserialize the module ID', function() {
|
||||
const id = 'arbitrary module ID';
|
||||
bundle.setMainModuleId(id);
|
||||
bundle.finalize({});
|
||||
|
||||
const deserialized = Bundle.fromJSON(bundle.toJSON());
|
||||
|
||||
expect(deserialized.getMainModuleId()).toEqual(id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('random access bundle groups:', () => {
|
||||
|
@ -442,40 +392,6 @@ describe('Bundle', () => {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
function genSourceMap(modules) {
|
||||
var sourceMapGen = new SourceMapGenerator({file: 'test_url', version: 3});
|
||||
var bundleLineNo = 0;
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
var module = modules[i];
|
||||
var transformedCode = module.code;
|
||||
var sourcePath = module.sourcePath;
|
||||
var sourceCode = module.sourceCode;
|
||||
var transformedLineCount = 0;
|
||||
var lastCharNewLine = false;
|
||||
for (var t = 0; t < transformedCode.length; t++) {
|
||||
if (t === 0 || lastCharNewLine) {
|
||||
sourceMapGen.addMapping({
|
||||
generated: {line: bundleLineNo + 1, column: 0},
|
||||
original: {line: transformedLineCount + 1, column: 0},
|
||||
source: sourcePath
|
||||
});
|
||||
}
|
||||
lastCharNewLine = transformedCode[t] === '\n';
|
||||
if (lastCharNewLine) {
|
||||
transformedLineCount++;
|
||||
bundleLineNo++;
|
||||
}
|
||||
}
|
||||
bundleLineNo++;
|
||||
sourceMapGen.setSourceContent(
|
||||
sourcePath,
|
||||
sourceCode
|
||||
);
|
||||
}
|
||||
return sourceMapGen.toJSON();
|
||||
}
|
||||
|
||||
function resolverFor(code, map) {
|
||||
return {
|
||||
wrapModule: () => Promise.resolve({code, map}),
|
||||
|
|
|
@ -1,174 +0,0 @@
|
|||
/**
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Based on the Base 64 VLQ implementation in Closure Compiler:
|
||||
* https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
|
||||
*
|
||||
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @copyright
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/* eslint-disable no-bitwise, quotes, global-strict */
|
||||
|
||||
'use strict';
|
||||
|
||||
var charToIntMap = {};
|
||||
var intToCharMap = {};
|
||||
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
.split('')
|
||||
.forEach(function (ch, index) {
|
||||
charToIntMap[ch] = index;
|
||||
intToCharMap[index] = ch;
|
||||
});
|
||||
|
||||
var base64 = {};
|
||||
/**
|
||||
* Encode an integer in the range of 0 to 63 to a single base 64 digit.
|
||||
*/
|
||||
base64.encode = function base64_encode(aNumber) {
|
||||
if (aNumber in intToCharMap) {
|
||||
return intToCharMap[aNumber];
|
||||
}
|
||||
throw new TypeError("Must be between 0 and 63: " + aNumber);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode a single base 64 digit to an integer.
|
||||
*/
|
||||
base64.decode = function base64_decode(aChar) {
|
||||
if (aChar in charToIntMap) {
|
||||
return charToIntMap[aChar];
|
||||
}
|
||||
throw new TypeError("Not a valid base 64 digit: " + aChar);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
|
||||
// length quantities we use in the source map spec, the first bit is the sign,
|
||||
// the next four bits are the actual value, and the 6th bit is the
|
||||
// continuation bit. The continuation bit tells us whether there are more
|
||||
// digits in this value following this digit.
|
||||
//
|
||||
// Continuation
|
||||
// | Sign
|
||||
// | |
|
||||
// V V
|
||||
// 101011
|
||||
|
||||
var VLQ_BASE_SHIFT = 5;
|
||||
|
||||
// binary: 100000
|
||||
var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
|
||||
|
||||
// binary: 011111
|
||||
var VLQ_BASE_MASK = VLQ_BASE - 1;
|
||||
|
||||
// binary: 100000
|
||||
var VLQ_CONTINUATION_BIT = VLQ_BASE;
|
||||
|
||||
/**
|
||||
* Converts from a two-complement value to a value where the sign bit is
|
||||
* placed in the least significant bit. For example, as decimals:
|
||||
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
|
||||
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
|
||||
*/
|
||||
function toVLQSigned(aValue) {
|
||||
return aValue < 0
|
||||
? ((-aValue) << 1) + 1
|
||||
: (aValue << 1) + 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts to a two-complement value from a value where the sign bit is
|
||||
* placed in the least significant bit. For example, as decimals:
|
||||
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
|
||||
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
|
||||
*/
|
||||
function fromVLQSigned(aValue) {
|
||||
var isNegative = (aValue & 1) === 1;
|
||||
var shifted = aValue >> 1;
|
||||
return isNegative
|
||||
? -shifted
|
||||
: shifted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base 64 VLQ encoded value.
|
||||
*/
|
||||
exports.encode = function base64VLQ_encode(aValue) {
|
||||
var encoded = "";
|
||||
var digit;
|
||||
|
||||
var vlq = toVLQSigned(aValue);
|
||||
|
||||
do {
|
||||
digit = vlq & VLQ_BASE_MASK;
|
||||
vlq >>>= VLQ_BASE_SHIFT;
|
||||
if (vlq > 0) {
|
||||
// There are still more digits in this value, so we must make sure the
|
||||
// continuation bit is marked.
|
||||
digit |= VLQ_CONTINUATION_BIT;
|
||||
}
|
||||
encoded += base64.encode(digit);
|
||||
} while (vlq > 0);
|
||||
|
||||
return encoded;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes the next base 64 VLQ value from the given string and returns the
|
||||
* value and the rest of the string via the out parameter.
|
||||
*/
|
||||
exports.decode = function base64VLQ_decode(aStr, aOutParam) {
|
||||
var i = 0;
|
||||
var strLen = aStr.length;
|
||||
var result = 0;
|
||||
var shift = 0;
|
||||
var continuation, digit;
|
||||
|
||||
do {
|
||||
if (i >= strLen) {
|
||||
throw new Error("Expected more digits in base 64 VLQ value.");
|
||||
}
|
||||
digit = base64.decode(aStr.charAt(i++));
|
||||
continuation = !!(digit & VLQ_CONTINUATION_BIT);
|
||||
digit &= VLQ_BASE_MASK;
|
||||
result = result + (digit << shift);
|
||||
shift += VLQ_BASE_SHIFT;
|
||||
} while (continuation);
|
||||
|
||||
aOutParam.value = fromVLQSigned(result);
|
||||
aOutParam.rest = aStr.slice(i);
|
||||
};
|
|
@ -364,7 +364,7 @@ class Bundler {
|
|||
onProgress,
|
||||
minify,
|
||||
isolateModuleIDs,
|
||||
generateSourceMaps: unbundle || generateSourceMaps,
|
||||
generateSourceMaps: unbundle || minify || generateSourceMaps,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -14,13 +14,21 @@
|
|||
const Generator = require('./Generator');
|
||||
|
||||
import type ModuleTransport from '../../lib/ModuleTransport';
|
||||
import type {RawMapping as BabelRawMapping} from 'babel-generator';
|
||||
|
||||
type GeneratedCodeMapping = [number, number];
|
||||
type SourceMapping = [number, number, number, number, void];
|
||||
type SourceMappingWithName = [number, number, number, number, string];
|
||||
|
||||
export type RawMapping =
|
||||
SourceMappingWithName | SourceMapping | GeneratedCodeMapping;
|
||||
|
||||
/**
|
||||
* Creates a source map from modules with "raw mappings", i.e. an array of
|
||||
* tuples with either 2, 4, or 5 elements:
|
||||
* generated line, generated column, source line, source line, symbol name.
|
||||
*/
|
||||
function fromRawMappings(modules: Array<ModuleTransport>): string {
|
||||
function fromRawMappings(modules: Array<ModuleTransport>): Generator {
|
||||
const generator = new Generator();
|
||||
let carryOver = 0;
|
||||
|
||||
|
@ -39,7 +47,24 @@ function fromRawMappings(modules: Array<ModuleTransport>): string {
|
|||
carryOver = carryOver + countLines(code);
|
||||
}
|
||||
|
||||
return generator.toString();
|
||||
return generator;
|
||||
}
|
||||
|
||||
function compactMapping(mapping: BabelRawMapping): RawMapping {
|
||||
const {column, line} = mapping.generated;
|
||||
const {name, original} = mapping;
|
||||
|
||||
if (original == null) {
|
||||
return [line, column];
|
||||
}
|
||||
|
||||
if (typeof name !== 'string') {
|
||||
return ([line, column, original.line, original.column]: SourceMapping);
|
||||
}
|
||||
|
||||
return (
|
||||
[line, column, original.line, original.column, name]: SourceMappingWithName
|
||||
);
|
||||
}
|
||||
|
||||
function addMappingsForFile(generator, mappings, module, carryOver) {
|
||||
|
@ -54,6 +79,7 @@ function addMappingsForFile(generator, mappings, module, carryOver) {
|
|||
mapping[0] === 1 && mapping[1] + columnOffset || mapping[1],
|
||||
mapping[2],
|
||||
mapping[3],
|
||||
//$FlowIssue #15417846
|
||||
mapping[4],
|
||||
);
|
||||
}
|
||||
|
@ -66,3 +92,4 @@ function countLines(string) {
|
|||
}
|
||||
|
||||
exports.fromRawMappings = fromRawMappings;
|
||||
exports.compactMapping = compactMapping;
|
||||
|
|
|
@ -178,7 +178,7 @@ describe('code transformation worker:', () => {
|
|||
extractDependencies = require('../extract-dependencies');
|
||||
inline = require('../inline');
|
||||
|
||||
options = {minify: true};
|
||||
options = {minify: true, transform: {generateSourceMaps: true}};
|
||||
dependencyData = {
|
||||
dependencies: ['a', 'b', 'c'],
|
||||
dependencyOffsets: [100, 120, 140]
|
||||
|
|
|
@ -20,10 +20,17 @@ const minify = require('./minify');
|
|||
import type {LogEntry} from '../../Logger/Types';
|
||||
import type {Ast, SourceMap, TransformOptions as BabelTransformOptions} from 'babel-core';
|
||||
|
||||
function makeTransformParams(filename, sourceCode, options) {
|
||||
function makeTransformParams(filename, sourceCode, options, willMinify) {
|
||||
invariant(
|
||||
!willMinify || options.generateSourceMaps,
|
||||
'Minifying source code requires the `generateSourceMaps` option to be `true`',
|
||||
);
|
||||
|
||||
|
||||
if (filename.endsWith('.json')) {
|
||||
sourceCode = 'module.exports=' + sourceCode;
|
||||
}
|
||||
|
||||
return {filename, sourceCode, options};
|
||||
}
|
||||
|
||||
|
@ -47,6 +54,7 @@ type Transform = (
|
|||
) => void;
|
||||
|
||||
export type TransformOptions = {
|
||||
generateSourceMaps: boolean,
|
||||
platform: string,
|
||||
preloadedModules?: Array<string>,
|
||||
projectRoots: Array<string>,
|
||||
|
@ -54,10 +62,10 @@ export type TransformOptions = {
|
|||
} & BabelTransformOptions;
|
||||
|
||||
export type Options = {
|
||||
transform: TransformOptions,
|
||||
platform: string,
|
||||
+dev: boolean,
|
||||
+minify: boolean,
|
||||
platform: string,
|
||||
transform: TransformOptions,
|
||||
};
|
||||
|
||||
export type Data = {
|
||||
|
@ -78,7 +86,12 @@ function transformCode(
|
|||
options: Options,
|
||||
callback: Callback,
|
||||
) {
|
||||
const params = makeTransformParams(filename, sourceCode, options.transform);
|
||||
const params = makeTransformParams(
|
||||
filename,
|
||||
sourceCode,
|
||||
options.transform,
|
||||
options.minify,
|
||||
);
|
||||
const isJson = filename.endsWith('.json');
|
||||
|
||||
const transformFileStartLogEntry = {
|
||||
|
|
|
@ -68,7 +68,8 @@ describe('processRequest', () => {
|
|||
Bundler.prototype.bundle = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
getSource: () => 'this is the source',
|
||||
getSourceMap: () => 'this is the source map',
|
||||
getSourceMap: () => {},
|
||||
getSourceMapString: () => 'this is the source map',
|
||||
getEtag: () => 'this is an etag',
|
||||
}));
|
||||
|
||||
|
@ -142,6 +143,7 @@ describe('processRequest', () => {
|
|||
entryFile: 'index.ios.js',
|
||||
inlineSourceMap: false,
|
||||
minify: false,
|
||||
generateSourceMaps: false,
|
||||
hot: false,
|
||||
runModule: true,
|
||||
sourceMapUrl: 'index.ios.includeRequire.map',
|
||||
|
@ -167,6 +169,7 @@ describe('processRequest', () => {
|
|||
entryFile: 'index.js',
|
||||
inlineSourceMap: false,
|
||||
minify: false,
|
||||
generateSourceMaps: false,
|
||||
hot: false,
|
||||
runModule: true,
|
||||
sourceMapUrl: 'index.map?platform=ios',
|
||||
|
@ -192,6 +195,7 @@ describe('processRequest', () => {
|
|||
entryFile: 'index.js',
|
||||
inlineSourceMap: false,
|
||||
minify: false,
|
||||
generateSourceMaps: false,
|
||||
hot: false,
|
||||
runModule: true,
|
||||
sourceMapUrl: 'index.map?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2',
|
||||
|
@ -225,6 +229,7 @@ describe('processRequest', () => {
|
|||
Promise.resolve({
|
||||
getSource: () => 'this is the first source',
|
||||
getSourceMap: () => {},
|
||||
getSourceMapString: () => 'this is the source map',
|
||||
getEtag: () => () => 'this is an etag',
|
||||
})
|
||||
)
|
||||
|
@ -232,6 +237,7 @@ describe('processRequest', () => {
|
|||
Promise.resolve({
|
||||
getSource: () => 'this is the rebuilt source',
|
||||
getSourceMap: () => {},
|
||||
getSourceMapString: () => 'this is the source map',
|
||||
getEtag: () => () => 'this is an etag',
|
||||
})
|
||||
);
|
||||
|
@ -273,6 +279,7 @@ describe('processRequest', () => {
|
|||
Promise.resolve({
|
||||
getSource: () => 'this is the first source',
|
||||
getSourceMap: () => {},
|
||||
getSourceMapString: () => 'this is the source map',
|
||||
getEtag: () => () => 'this is an etag',
|
||||
})
|
||||
)
|
||||
|
@ -280,6 +287,7 @@ describe('processRequest', () => {
|
|||
Promise.resolve({
|
||||
getSource: () => 'this is the rebuilt source',
|
||||
getSourceMap: () => {},
|
||||
getSourceMapString: () => 'this is the source map',
|
||||
getEtag: () => () => 'this is an etag',
|
||||
})
|
||||
);
|
||||
|
@ -434,6 +442,7 @@ describe('processRequest', () => {
|
|||
entryFile: 'path/to/foo.js',
|
||||
inlineSourceMap: false,
|
||||
minify: false,
|
||||
generateSourceMaps: false,
|
||||
hot: false,
|
||||
runModule: false,
|
||||
sourceMapUrl: '/path/to/foo.map?dev=false&runModule=false',
|
||||
|
|
|
@ -744,15 +744,11 @@ class Server {
|
|||
debug('Finished response');
|
||||
log(createActionEndEntry(requestingBundleLogEntry));
|
||||
} else if (requestType === 'map') {
|
||||
let sourceMap = p.getSourceMap({
|
||||
const sourceMap = p.getSourceMapString({
|
||||
minify: options.minify,
|
||||
dev: options.dev,
|
||||
});
|
||||
|
||||
if (typeof sourceMap !== 'string') {
|
||||
sourceMap = JSON.stringify(sourceMap);
|
||||
}
|
||||
|
||||
mres.setHeader('Content-Type', 'application/json');
|
||||
mres.end(sourceMap);
|
||||
log(createActionEndEntry(requestingBundleLogEntry));
|
||||
|
@ -945,8 +941,7 @@ class Server {
|
|||
'entryModuleOnly',
|
||||
false,
|
||||
),
|
||||
/* $FlowFixMe: missing defaultVal */
|
||||
generateSourceMaps: this._getBoolOptionFromQuery(urlObj.query, 'babelSourcemap'),
|
||||
generateSourceMaps: this._getBoolOptionFromQuery(urlObj.query, 'babelSourcemap', false),
|
||||
assetPlugins,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,8 +11,11 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
import type {RawMapping} from '../Bundler/source-map';
|
||||
import type {MixedSourceMap} from './SourceMap';
|
||||
|
||||
type SourceMapOrMappings = MixedSourceMap | Array<RawMapping>;
|
||||
|
||||
type Metadata = {
|
||||
dependencyPairs?: Array<[mixed, {path: string}]>,
|
||||
preloaded?: boolean,
|
||||
|
@ -28,7 +31,7 @@ class ModuleTransport {
|
|||
virtual: ?boolean;
|
||||
meta: ?Metadata;
|
||||
polyfill: ?boolean;
|
||||
map: ?MixedSourceMap;
|
||||
map: ?SourceMapOrMappings;
|
||||
|
||||
constructor(data: {
|
||||
name: string,
|
||||
|
@ -39,7 +42,7 @@ class ModuleTransport {
|
|||
virtual?: ?boolean,
|
||||
meta?: ?Metadata,
|
||||
polyfill?: ?boolean,
|
||||
map?: ?MixedSourceMap,
|
||||
map?: ?SourceMapOrMappings,
|
||||
}) {
|
||||
this.name = data.name;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export type SourceMap = BabelSourceMap;
|
|||
|
||||
export type CombinedSourceMap = {
|
||||
version: number,
|
||||
file: string,
|
||||
file?: string,
|
||||
sections: Array<{
|
||||
offset: {line: number, column: number},
|
||||
map: MixedSourceMap,
|
||||
|
|
|
@ -373,16 +373,6 @@ class Module {
|
|||
isPolyfill() {
|
||||
return false;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
hash: this.hash(),
|
||||
isJSON: this.isJSON(),
|
||||
isAsset: this.isAsset(),
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Module._globalCacheRetries = 4;
|
||||
|
|
|
@ -13,11 +13,14 @@
|
|||
const babel = require('babel-core');
|
||||
const externalHelpersPlugin = require('babel-plugin-external-helpers');
|
||||
const fs = require('fs');
|
||||
const makeHMRConfig = require('babel-preset-react-native/configs/hmr');
|
||||
const resolvePlugins = require('babel-preset-react-native/lib/resolvePlugins');
|
||||
const generate = require('babel-generator').default;
|
||||
const inlineRequiresPlugin = require('babel-preset-fbjs/plugins/inline-requires');
|
||||
const json5 = require('json5');
|
||||
const makeHMRConfig = require('babel-preset-react-native/configs/hmr');
|
||||
const path = require('path');
|
||||
const resolvePlugins = require('babel-preset-react-native/lib/resolvePlugins');
|
||||
|
||||
const {compactMapping} = require('./react-packager/src/Bundler/source-map');
|
||||
|
||||
/**
|
||||
* Return a memoized function that checks for the existence of a
|
||||
|
@ -70,8 +73,8 @@ function buildBabelConfig(filename, options) {
|
|||
const babelRC = getBabelRC(options.projectRoots);
|
||||
|
||||
const extraConfig = {
|
||||
code: false,
|
||||
filename,
|
||||
sourceFileName: filename,
|
||||
};
|
||||
|
||||
let config = Object.assign({}, babelRC, extraConfig);
|
||||
|
@ -103,13 +106,20 @@ function transform(src, filename, options) {
|
|||
|
||||
try {
|
||||
const babelConfig = buildBabelConfig(filename, options);
|
||||
const result = babel.transform(src, babelConfig);
|
||||
const {ast} = babel.transform(src, babelConfig);
|
||||
const result = generate(ast, {
|
||||
comments: false,
|
||||
compact: false,
|
||||
filename,
|
||||
sourceFileName: filename,
|
||||
sourceMaps: true,
|
||||
}, src);
|
||||
|
||||
return {
|
||||
ast: result.ast,
|
||||
ast,
|
||||
code: result.code,
|
||||
map: result.map,
|
||||
filename: filename,
|
||||
filename,
|
||||
map: options.generateSourceMaps ? result.map : result.rawMappings.map(compactMapping),
|
||||
};
|
||||
} finally {
|
||||
process.env.BABEL_ENV = OLD_BABEL_ENV;
|
||||
|
|
Loading…
Reference in New Issue