Unify source map approach for RA bundles on iOS/Android

Reviewed By: javache

Differential Revision: D3229780

fb-gh-sync-id: a3148d7626b32a2e6803ae8c35ac75025a992a32
fbshipit-source-id: a3148d7626b32a2e6803ae8c35ac75025a992a32
This commit is contained in:
David Aurelio 2016-04-29 10:15:26 -07:00 committed by Facebook Github Bot 6
parent 6cae8b7c02
commit cccb8db175
8 changed files with 59 additions and 136 deletions

View File

@ -41,7 +41,6 @@ static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnable
typedef struct ModuleData {
uint32_t offset;
uint32_t length;
uint32_t lineNo;
} ModuleData;
@interface RCTJavaScriptContext : NSObject <RCTInvalidating>
@ -749,10 +748,13 @@ static int readBundle(FILE *fd, size_t offset, size_t length, void *ptr)
}
JSStringRef code = JSStringCreateWithUTF8CString(bytes);
JSValueRef jsError = NULL;
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, code, NULL, strongSelf->_bundleURL, data->lineNo, NULL);
JSStringRef sourceURL = JSStringCreateWithUTF8CString([moduleName stringByAppendingPathExtension:@"js"].UTF8String);
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, code, NULL, sourceURL, 0, NULL);
CFDictionaryRemoveValue(strongSelf->_jsModules, moduleName.UTF8String);
JSStringRelease(code);
JSStringRelease(sourceURL);
RCT_PROFILE_END_EVENT(0, @"js_call", nil);
RCTPerformanceLoggerAppendEnd(RCTPLRAMNativeRequires);
@ -831,7 +833,6 @@ static int readBundle(FILE *fd, size_t offset, size_t length, void *ptr)
moduleData->offset = baseOffset + readUint32((const char **)&tableCursor);
moduleData->length = readUint32((const char **)&tableCursor);
moduleData->lineNo = readUint32((const char **)&tableCursor);
CFDictionarySetValue(_jsModules, name, moduleData);
}

View File

@ -16,6 +16,8 @@ const buildSourceMapWithMetaData = require('./build-unbundle-sourcemap-with-meta
const writeFile = require('../writeFile');
const writeSourceMap = require('./write-sourcemap');
const MAGIC_UNBUNDLE_NUMBER = require('./magic-number');
const {joinModules} = require('./util');
const MAGIC_UNBUNDLE_FILENAME = 'UNBUNDLE'; // must not start with a dot, as that won't go into the apk
const MODULES_DIR = 'js-modules';
@ -34,15 +36,16 @@ function saveAsAssets(bundle, options, log) {
} = options;
log('start');
const {startupCode, startupModules, modules} = bundle.getUnbundle('ASSETS');
const {startupModules, lazyModules} = bundle.getUnbundle();
log('finish');
const startupCode = joinModules(startupModules);
log('Writing bundle output to:', bundleOutput);
const modulesDir = path.join(path.dirname(bundleOutput), MODULES_DIR);
const writeUnbundle =
createDir(modulesDir).then( // create the modules directory first
() => Promise.all([
writeModules(modules, modulesDir, encoding),
writeModules(lazyModules, modulesDir, encoding),
writeFile(bundleOutput, startupCode, encoding),
writeMagicFlagFile(modulesDir),
])
@ -50,7 +53,7 @@ function saveAsAssets(bundle, options, log) {
writeUnbundle.then(() => log('Done writing unbundle output'));
const sourceMap =
buildSourceMapWithMetaData({startupModules, modules});
buildSourceMapWithMetaData({startupModules, lazyModules});
return Promise.all([
writeUnbundle,

View File

@ -8,10 +8,11 @@
*/
'use strict';
const buildUnbundleSourcemap = require('./buildUnbundleSourcemap');
const buildSourceMapWithMetaData = require('./build-unbundle-sourcemap-with-metadata');
const fs = require('fs');
const Promise = require('promise');
const writeSourceMap = require('./write-sourcemap');
const {joinModules} = require('./util');
const MAGIC_UNBUNDLE_FILE_HEADER = require('./magic-number');
const MAGIC_STARTUP_MODULE_ID = '';
@ -31,22 +32,23 @@ function saveAsIndexedFile(bundle, options, log) {
} = options;
log('start');
const {startupCode, modules} = bundle.getUnbundle('INDEX');
const {startupModules, lazyModules} = bundle.getUnbundle();
log('finish');
const startupCode = joinModules(startupModules);
log('Writing unbundle output to:', bundleOutput);
const writeUnbundle = writeBuffers(
fs.createWriteStream(bundleOutput),
buildTableAndContents(startupCode, modules, encoding)
buildTableAndContents(startupCode, lazyModules, encoding)
).then(() => log('Done writing unbundle output'));
const sourceMap =
buildSourceMapWithMetaData({startupModules, lazyModules});
return Promise.all([
writeUnbundle,
writeSourceMap(
sourcemapOutput,
buildUnbundleSourcemap(bundle),
log,
),
writeSourceMap(sourcemapOutput, JSON.stringify(sourceMap), log),
]);
}
@ -68,7 +70,6 @@ function writeBuffers(stream, buffers) {
function moduleToBuffer(id, code, encoding) {
return {
id,
linesCount: code.split('\n').length,
buffer: Buffer.concat([
Buffer(code, encoding),
nullByteBuffer // create \0-terminated strings
@ -91,29 +92,24 @@ function buildModuleTable(buffers) {
// - module_id: NUL terminated utf8 string
// - module_offset: uint_32 offset into the module string
// - module_length: uint_32 length in bytes of the module
// - module_line: uint_32 line on which module starts on the bundle
const numBuffers = buffers.length;
const tableLengthBuffer = uInt32Buffer(0);
let tableLength = 4; // the table length itself, 4 == tableLengthBuffer.length
let currentOffset = 0;
let currentLine = 1;
const offsetTable = [tableLengthBuffer];
for (let i = 0; i < numBuffers; i++) {
const {id, linesCount, buffer: {length}} = buffers[i];
const {id, buffer: {length}} = buffers[i];
const entry = Buffer.concat([
Buffer(i === 0 ? MAGIC_STARTUP_MODULE_ID : id, 'utf8'),
nullByteBuffer,
uInt32Buffer(currentOffset),
uInt32Buffer(length),
uInt32Buffer(currentLine),
]);
currentLine += linesCount;
currentOffset += length;
tableLength += entry.length;
offsetTable.push(entry);

View File

@ -8,15 +8,15 @@
*/
'use strict';
const {combineSourceMaps} = require('./util');
const {combineSourceMaps, joinModules} = require('./util');
module.exports = ({startupModules, modules}) => {
module.exports = ({startupModules, lazyModules}) => {
const startupModule = {
code: startupModules.map(m => m.code).join('\n'),
code: joinModules(startupModules),
map: combineSourceMaps({modules: startupModules}),
};
return combineSourceMaps({
modules: [startupModule].concat(modules),
modules: [startupModule].concat(lazyModules),
withCustomOffsets: true,
});
};

View File

@ -1,56 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const sourceMap = require('source-map');
const SourceMapConsumer = sourceMap.SourceMapConsumer;
/**
* Builds the sourcemaps for any type of unbundle provided the Bundle that
* contains the modules reachable from the entry point.
*
* The generated sourcemaps correspond to a regular bundle on which each module
* starts on a new line. Depending on the type of unbundle you're using, you
* will have to pipe the line number to native and use it when injecting the
* module's code into JSC. This way, we'll trick JSC to believe all the code is
* on a single big regular bundle where as it could be on an indexed bundle or
* as sparsed assets.
*/
function buildUnbundleSourcemap(bundle) {
const generator = new sourceMap.SourceMapGenerator({});
let offset = 0;
bundle.getUnbundle('INDEX').allModules.forEach(module => {
if (module.map) { // assets have no sourcemap
const consumer = new SourceMapConsumer(module.map);
consumer.eachMapping(mapping => {
generator.addMapping({
original: {
line: mapping.originalLine,
column: mapping.originalColumn,
},
generated: {
line: mapping.generatedLine + offset,
column: mapping.generatedColumn,
},
source: module.sourcePath,
});
});
generator.setSourceContent(module.sourcePath, module.sourceCode);
}
// some modules span more than 1 line
offset += module.code.split('\n').length;
});
return generator.toString();
}
module.exports = buildUnbundleSourcemap;

View File

@ -57,4 +57,11 @@ function combineSourceMaps({modules, withCustomOffsets}) {
return sourceMap;
}
module.exports = {countLines, lineToLineSourceMap, combineSourceMaps};
const joinModules = modules => modules.map(m => m.code).join('\n');
module.exports = {
countLines,
lineToLineSourceMap,
combineSourceMaps,
joinModules,
};

View File

@ -107,61 +107,23 @@ class Bundle extends BundleBase {
}
getUnbundle(type) {
if (this._ramBundle) {
return this._ramBundle;
}
this.assertFinalized();
if (!this._ramBundle) {
const modules = this.getModules().slice();
switch (type) {
case 'INDEX':
this._ramBundle = this._getAsIndexedFileUnbundle();
break;
case 'ASSETS':
this._ramBundle = this._getAsAssetsUnbundle();
break;
default:
throw new Error('Unkown RAM Bundle type:', type);
// separate modules we need to preload from the ones we don't
const [startupModules, lazyModules] = partition(modules, shouldPreload);
this._ramBundle = {
startupModules,
lazyModules,
allModules: modules,
};
}
return this._ramBundle;
}
_getAsIndexedFileUnbundle() {
const modules = this.getModules();
// separate modules we need to preload from the ones we don't
const shouldPreload = (module) => module.meta && module.meta.preloaded;
const preloaded = modules.filter(module => shouldPreload(module));
const notPreloaded = modules.filter(module => !shouldPreload(module));
// code that will be executed on bridge start up
const startupCode = preloaded.map(({code}) => code).join('\n');
return {
// include copy of all modules on the order they're writen on the bundle:
// polyfills, preloaded, additional requires, non preloaded
allModules: preloaded.concat(notPreloaded),
startupCode, // no entries on the index for these modules, only the code
modules: notPreloaded, // we include both the code and entries on the index
};
}
_getAsAssetsUnbundle() {
const allModules = this.getModules().slice();
const prependedModules = this._numPrependedModules;
const requireCalls = this._numRequireCalls;
const modules =
allModules
.splice(prependedModules, allModules.length - requireCalls - prependedModules);
const startupCode = allModules.map(({code}) => code).join('\n');
return {
startupCode,
startupModules: allModules,
modules,
};
}
/**
* 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
@ -351,4 +313,15 @@ function generateSourceMapForVirtualModule(module) {
};
}
function shouldPreload({meta}) {
return meta && meta.preloaded;
}
function partition(array, predicate) {
const included = [];
const excluded = [];
array.forEach(item => (predicate(item) ? included : excluded).push(item));
return [included, excluded];
}
module.exports = Bundle;

View File

@ -556,12 +556,11 @@ class Bundler {
]).then((
[name, {code, dependencies, dependencyOffsets, map, source}]
) => {
const {preloadedModules} = transformOptions.transform;
const preloaded =
module.path === entryFilePath ||
module.isPolyfill() || (
transformOptions.transform.preloadedModules &&
transformOptions.transform.preloadedModules.hasOwnProperty(module.path)
);
module.isPolyfill() ||
preloadedModules && preloadedModules.hasOwnProperty(module.path);
return new ModuleTransport({
name,
@ -571,7 +570,7 @@ class Bundler {
meta: {dependencies, dependencyOffsets, preloaded},
sourceCode: source,
sourcePath: module.path
})
});
});
}