mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-01-30 22:48:23 +08:00
Hot Loading Sourcemaps
Summary: public To make sourcemaps work on Hot Loading work, we'll need to be able to serve them for each module that is dynamically replaced. To do so we introduced a new parameter to the bundler, namely `entryModuleOnly` to decide whether or not to process the full dependency tree or just the module associated to the entry file. Also we need to add `//sourceMappingURL` to the HMR updates so that in case of an error the runtime retrieves the sourcemaps for the file on which an error occurred from the server. Finally, we need to refactor a bit how we load the HMR updates into JSC. Unfortunately, if the code is eval'ed when an error is thrown, the line and column number are missing. This is a bug/missing feature in JSC. To walkaround the issue we need to eval the code on native. This adds a bit of complexity to HMR as for both platforms we'll have to have a thin module to inject code but I don't see any other alternative. when debugging this is not needed as Chrome supports sourceMappingURLs on eval'ed code Reviewed By: javache Differential Revision: D2841788 fb-gh-sync-id: ad9370d26894527a151cea722463e694c670227e
This commit is contained in:
committed by
facebook-github-bot-0
parent
b85a52a461
commit
f2438b440d
@@ -11,37 +11,37 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var sourceMapPromise;
|
||||
|
||||
var exceptionID = 0;
|
||||
let exceptionID = 0;
|
||||
|
||||
/**
|
||||
* Handles the developer-visible aspect of errors and exceptions
|
||||
*/
|
||||
function reportException(e: Error, isFatal: bool) {
|
||||
var loadSourceMap = require('loadSourceMap');
|
||||
var parseErrorStack = require('parseErrorStack');
|
||||
var RCTExceptionsManager = require('NativeModules').ExceptionsManager;
|
||||
const parseErrorStack = require('parseErrorStack');
|
||||
const RCTExceptionsManager = require('NativeModules').ExceptionsManager;
|
||||
|
||||
var currentExceptionID = ++exceptionID;
|
||||
const currentExceptionID = ++exceptionID;
|
||||
if (RCTExceptionsManager) {
|
||||
var stack = parseErrorStack(e);
|
||||
const stack = parseErrorStack(e);
|
||||
if (isFatal) {
|
||||
RCTExceptionsManager.reportFatalException(e.message, stack, currentExceptionID);
|
||||
} else {
|
||||
RCTExceptionsManager.reportSoftException(e.message, stack, currentExceptionID);
|
||||
}
|
||||
if (__DEV__) {
|
||||
(sourceMapPromise = sourceMapPromise || loadSourceMap())
|
||||
.then(map => {
|
||||
var prettyStack = parseErrorStack(e, map);
|
||||
RCTExceptionsManager.updateExceptionMessage(e.message, prettyStack, currentExceptionID);
|
||||
})
|
||||
.catch(error => {
|
||||
// This can happen in a variety of normal situations, such as
|
||||
// Network module not being available, or when running locally
|
||||
console.warn('Unable to load source map: ' + error.message);
|
||||
});
|
||||
require('SourceMapsCache').getSourceMaps().then(sourceMaps => {
|
||||
const prettyStack = parseErrorStack(e, sourceMaps);
|
||||
RCTExceptionsManager.updateExceptionMessage(
|
||||
e.message,
|
||||
prettyStack,
|
||||
currentExceptionID,
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
// This can happen in a variety of normal situations, such as
|
||||
// Network module not being available, or when running locally
|
||||
console.warn('Unable to load source map: ' + error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,15 +81,15 @@ function installConsoleErrorReporter() {
|
||||
if (arguments[0] && arguments[0].stack) {
|
||||
reportException(arguments[0], /* isFatal */ false);
|
||||
} else {
|
||||
var stringifySafe = require('stringifySafe');
|
||||
var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
|
||||
const stringifySafe = require('stringifySafe');
|
||||
const str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
|
||||
if (str.slice(0, 10) === '"Warning: ') {
|
||||
// React warnings use console.error so that a stack trace is shown, but
|
||||
// we don't (currently) want these to show a redbox
|
||||
// (Note: Logic duplicated in polyfills/console.js.)
|
||||
return;
|
||||
}
|
||||
var error : any = new Error('console.error: ' + str);
|
||||
const error : any = new Error('console.error: ' + str);
|
||||
error.framesToPop = 1;
|
||||
reportException(error, /* isFatal */ false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule SourceMapsCache
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const getObjectValues = require('getObjectValues');
|
||||
const SourceMapsUtils = require('SourceMapsUtils');
|
||||
|
||||
const sourceMapsCache = {};
|
||||
|
||||
const SourceMapsCache = {
|
||||
mainSourceMapID: 'main',
|
||||
|
||||
fetch({text, url, fullSourceMappingURL}) {
|
||||
const sourceMappingURL = fullSourceMappingURL
|
||||
? fullSourceMappingURL
|
||||
: SourceMapsUtils.extractSourceMapURL({text, url});
|
||||
|
||||
sourceMapsCache[sourceMappingURL] = SourceMapsUtils.fetchSourceMap(
|
||||
sourceMappingURL
|
||||
);
|
||||
},
|
||||
|
||||
getSourceMaps() {
|
||||
fetchMainSourceMap();
|
||||
return Promise.all(getObjectValues(sourceMapsCache));
|
||||
},
|
||||
};
|
||||
|
||||
function fetchMainSourceMap() {
|
||||
if (!sourceMapsCache[SourceMapsCache.mainSourceMapID]) {
|
||||
sourceMapsCache[SourceMapsCache.mainSourceMapID] =
|
||||
SourceMapsUtils.fetchMainSourceMap();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SourceMapsCache;
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule SourceMapsUtils
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var HMRClient = require('../../Utilities/HMRClient');
|
||||
var Promise = require('Promise');
|
||||
var NativeModules = require('NativeModules');
|
||||
var SourceMapConsumer = require('SourceMap').SourceMapConsumer;
|
||||
var SourceMapURL = require('./source-map-url');
|
||||
|
||||
var RCTSourceCode = NativeModules.SourceCode;
|
||||
var RCTNetworking = NativeModules.Networking;
|
||||
|
||||
var SourceMapsUtils = {
|
||||
fetchMainSourceMap(): Promise {
|
||||
return SourceMapsUtils._getMainSourceMapURL().then(url =>
|
||||
SourceMapsUtils.fetchSourceMap(url)
|
||||
);
|
||||
},
|
||||
|
||||
fetchSourceMap(sourceMappingURL: string): Promise {
|
||||
return fetch(sourceMappingURL)
|
||||
.then(response => response.text())
|
||||
.then(map => new SourceMapConsumer(map));
|
||||
},
|
||||
|
||||
extractSourceMapURL(data: ({url:string, text:string})): ?string {
|
||||
const url = data.url;
|
||||
const text = data.text;
|
||||
var mapURL = SourceMapURL.getFrom(text);
|
||||
if (!mapURL) {
|
||||
return null;
|
||||
}
|
||||
var baseURLs = url.match(/(.+:\/\/.*?)\//);
|
||||
if (!baseURLs || baseURLs.length < 2) {
|
||||
return null;
|
||||
}
|
||||
return baseURLs[1] + mapURL;
|
||||
},
|
||||
|
||||
_getMainSourceMapURL(): Promise {
|
||||
if (global.RAW_SOURCE_MAP) {
|
||||
return Promise.resolve(global.RAW_SOURCE_MAP);
|
||||
}
|
||||
|
||||
if (!RCTSourceCode) {
|
||||
return Promise.reject(new Error('RCTSourceCode module is not available'));
|
||||
}
|
||||
|
||||
if (!RCTNetworking) {
|
||||
// Used internally by fetch
|
||||
return Promise.reject(new Error('RCTNetworking module is not available'));
|
||||
}
|
||||
|
||||
return RCTSourceCode.getScriptText()
|
||||
.then(SourceMapsUtils.extractSourceMapURL)
|
||||
.then((url) => {
|
||||
if (url === null) {
|
||||
return Promise.reject(new Error('No source map URL found. May be running from bundled file.'));
|
||||
}
|
||||
return Promise.resolve(url);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = SourceMapsUtils;
|
||||
@@ -1,66 +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.
|
||||
*
|
||||
* @providesModule loadSourceMap
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var Promise = require('Promise');
|
||||
var NativeModules = require('NativeModules');
|
||||
var SourceMapConsumer = require('SourceMap').SourceMapConsumer;
|
||||
var SourceMapURL = require('./source-map-url');
|
||||
|
||||
var RCTSourceCode = NativeModules.SourceCode;
|
||||
var RCTNetworking = NativeModules.Networking;
|
||||
|
||||
function loadSourceMap(): Promise {
|
||||
return fetchSourceMap()
|
||||
.then(map => new SourceMapConsumer(map));
|
||||
}
|
||||
|
||||
function fetchSourceMap(): Promise {
|
||||
if (global.RAW_SOURCE_MAP) {
|
||||
return Promise.resolve(global.RAW_SOURCE_MAP);
|
||||
}
|
||||
|
||||
if (!RCTSourceCode) {
|
||||
return Promise.reject(new Error('RCTSourceCode module is not available'));
|
||||
}
|
||||
|
||||
if (!RCTNetworking) {
|
||||
// Used internally by fetch
|
||||
return Promise.reject(new Error('RCTNetworking module is not available'));
|
||||
}
|
||||
|
||||
return RCTSourceCode.getScriptText()
|
||||
.then(extractSourceMapURL)
|
||||
.then((url) => {
|
||||
if (url === null) {
|
||||
return Promise.reject(new Error('No source map URL found. May be running from bundled file.'));
|
||||
}
|
||||
return Promise.resolve(url);
|
||||
})
|
||||
.then(fetch)
|
||||
.then(response => response.text());
|
||||
}
|
||||
|
||||
function extractSourceMapURL({url, text, fullSourceMappingURL}): ?string {
|
||||
if (fullSourceMappingURL) {
|
||||
return fullSourceMappingURL;
|
||||
}
|
||||
var mapURL = SourceMapURL.getFrom(text);
|
||||
if (!mapURL) {
|
||||
return null;
|
||||
}
|
||||
var baseURL = url.match(/(.+:\/\/.*?)\//)[1];
|
||||
return baseURL + mapURL;
|
||||
}
|
||||
|
||||
module.exports = loadSourceMap;
|
||||
@@ -19,7 +19,11 @@ function resolveSourceMaps(sourceMapInstance, stackFrame) {
|
||||
column: stackFrame.column,
|
||||
});
|
||||
if (orig) {
|
||||
stackFrame.file = orig.source;
|
||||
// remove query string if any
|
||||
const queryStringStartIndex = orig.source.indexOf('?');
|
||||
stackFrame.file = queryStringStartIndex === -1
|
||||
? orig.source
|
||||
: orig.source.substring(0, queryStringStartIndex);
|
||||
stackFrame.lineNumber = orig.line;
|
||||
stackFrame.column = orig.column;
|
||||
}
|
||||
@@ -27,7 +31,7 @@ function resolveSourceMaps(sourceMapInstance, stackFrame) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseErrorStack(e, sourceMapInstance) {
|
||||
function parseErrorStack(e, sourceMaps) {
|
||||
if (!e || !e.stack) {
|
||||
return [];
|
||||
}
|
||||
@@ -39,8 +43,17 @@ function parseErrorStack(e, sourceMapInstance) {
|
||||
stack.shift();
|
||||
}
|
||||
|
||||
if (sourceMapInstance) {
|
||||
stack.forEach(resolveSourceMaps.bind(null, sourceMapInstance));
|
||||
if (sourceMaps) {
|
||||
sourceMaps.forEach((sourceMap, index) => {
|
||||
stack.forEach(frame => {
|
||||
if (frame.file.indexOf(sourceMap.file) !== -1 ||
|
||||
frame.file.replace('.map', '.bundle').indexOf(
|
||||
sourceMap.file
|
||||
) !== -1) {
|
||||
resolveSourceMaps(sourceMap, frame);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return stack;
|
||||
|
||||
@@ -29,7 +29,7 @@ void (function(root, factory) {
|
||||
}
|
||||
}(this, function() {
|
||||
|
||||
var innerRegex = /[#@] sourceMappingURL=([^\s'"]*)/
|
||||
var innerRegex = /[#@] source(?:Mapping)?URL=([^\s'"]*)/
|
||||
|
||||
var regex = RegExp(
|
||||
"(?:" +
|
||||
|
||||
Reference in New Issue
Block a user