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:
parent
6cae8b7c02
commit
cccb8db175
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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;
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue