Updates from Mon Aug 31st.
This commit is contained in:
commit
465f539057
|
@ -30,13 +30,15 @@
|
|||
|
||||
[libs]
|
||||
Libraries/react-native/react-native-interface.js
|
||||
Examples/UIExplorer/ImageMocks.js
|
||||
|
||||
[options]
|
||||
module.system=haste
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
|
|
|
@ -36,14 +36,14 @@
|
|||
* on the same Wi-Fi network.
|
||||
*/
|
||||
|
||||
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle"];
|
||||
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.bundle?platform=ios"];
|
||||
|
||||
/**
|
||||
* OPTION 2
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
|
||||
* to your Xcode project folder in the terminal, and run
|
||||
*
|
||||
* $ curl 'http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle' -o main.jsbundle
|
||||
* $ curl 'http://localhost:8081/Examples/2048/Game2048.bundle?platform=ios' -o main.jsbundle
|
||||
*
|
||||
* then add the `main.jsbundle` file to your project and uncomment this line:
|
||||
*/
|
||||
|
|
|
@ -19,9 +19,11 @@ var React = require('react-native');
|
|||
var {
|
||||
Image,
|
||||
PixelRatio,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableNativeFeedback,
|
||||
View
|
||||
} = React;
|
||||
|
||||
|
@ -32,9 +34,13 @@ var getTextFromScore = require('./getTextFromScore');
|
|||
var MovieCell = React.createClass({
|
||||
render: function() {
|
||||
var criticsScore = this.props.movie.ratings.critics_score;
|
||||
var TouchableElement = TouchableHighlight;
|
||||
if (Platform.OS === 'android') {
|
||||
TouchableElement = TouchableNativeFeedback;
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<TouchableHighlight
|
||||
<TouchableElement
|
||||
onPress={this.props.onSelect}
|
||||
onShowUnderlay={this.props.onHighlight}
|
||||
onHideUnderlay={this.props.onUnhighlight}>
|
||||
|
@ -59,7 +65,7 @@ var MovieCell = React.createClass({
|
|||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</TouchableElement>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @providesModule SearchBar
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
ActivityIndicatorIOS,
|
||||
TextInput,
|
||||
StyleSheet,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var SearchBar = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.searchBar}>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChange={this.props.onSearchChange}
|
||||
placeholder="Search a movie..."
|
||||
onFocus={this.props.onFocus}
|
||||
style={styles.searchBarInput}
|
||||
/>
|
||||
<ActivityIndicatorIOS
|
||||
animating={this.props.isLoading}
|
||||
style={styles.spinner}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
searchBar: {
|
||||
marginTop: 64,
|
||||
padding: 3,
|
||||
paddingLeft: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
searchBarInput: {
|
||||
fontSize: 15,
|
||||
flex: 1,
|
||||
height: 30,
|
||||
},
|
||||
spinner: {
|
||||
width: 30,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = SearchBar;
|
|
@ -19,6 +19,8 @@ var React = require('react-native');
|
|||
var {
|
||||
ActivityIndicatorIOS,
|
||||
ListView,
|
||||
Platform,
|
||||
ProgressBarAndroid,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
|
@ -27,9 +29,11 @@ var {
|
|||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var dismissKeyboard = require('dismissKeyboard');
|
||||
|
||||
var MovieCell = require('./MovieCell');
|
||||
var MovieScreen = require('./MovieScreen');
|
||||
var SearchBar = require('SearchBar');
|
||||
|
||||
/**
|
||||
* This is for demo purposes only, and rate limited.
|
||||
|
@ -219,11 +223,20 @@ var SearchScreen = React.createClass({
|
|||
},
|
||||
|
||||
selectMovie: function(movie: Object) {
|
||||
if (Platform.OS === 'ios') {
|
||||
this.props.navigator.push({
|
||||
title: movie.title,
|
||||
component: MovieScreen,
|
||||
passProps: {movie},
|
||||
});
|
||||
} else {
|
||||
dismissKeyboard();
|
||||
this.props.navigator.push({
|
||||
title: movie.title,
|
||||
name: 'movie',
|
||||
movie: movie,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onSearchChange: function(event: Object) {
|
||||
|
@ -237,7 +250,15 @@ var SearchScreen = React.createClass({
|
|||
if (!this.hasMore() || !this.state.isLoadingTail) {
|
||||
return <View style={styles.scrollSpinner} />;
|
||||
}
|
||||
if (Platform.OS === 'ios') {
|
||||
return <ActivityIndicatorIOS style={styles.scrollSpinner} />;
|
||||
} else {
|
||||
return (
|
||||
<View style={{alignItems: 'center'}}>
|
||||
<ProgressBarAndroid styleAttr="Large"/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
renderSeparator: function(
|
||||
|
@ -295,7 +316,8 @@ var SearchScreen = React.createClass({
|
|||
<SearchBar
|
||||
onSearchChange={this.onSearchChange}
|
||||
isLoading={this.state.isLoading}
|
||||
onFocus={() => this.refs.listview.getScrollResponder().scrollTo(0, 0)}
|
||||
onFocus={() =>
|
||||
this.refs.listview && this.refs.listview.getScrollResponder().scrollTo(0, 0)}
|
||||
/>
|
||||
<View style={styles.separator} />
|
||||
{content}
|
||||
|
@ -323,27 +345,6 @@ var NoMovies = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
var SearchBar = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.searchBar}>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChange={this.props.onSearchChange}
|
||||
placeholder="Search a movie..."
|
||||
onFocus={this.props.onFocus}
|
||||
style={styles.searchBarInput}
|
||||
/>
|
||||
<ActivityIndicatorIOS
|
||||
animating={this.props.isLoading}
|
||||
style={styles.spinner}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
|
@ -356,25 +357,10 @@ var styles = StyleSheet.create({
|
|||
marginTop: 80,
|
||||
color: '#888888',
|
||||
},
|
||||
searchBar: {
|
||||
marginTop: 64,
|
||||
padding: 3,
|
||||
paddingLeft: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
searchBarInput: {
|
||||
fontSize: 15,
|
||||
flex: 1,
|
||||
height: 30,
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: '#eeeeee',
|
||||
},
|
||||
spinner: {
|
||||
width: 30,
|
||||
},
|
||||
scrollSpinner: {
|
||||
marginVertical: 20,
|
||||
},
|
||||
|
|
|
@ -34,6 +34,9 @@ module.system=haste
|
|||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
|
|
|
@ -36,14 +36,14 @@
|
|||
* on the same Wi-Fi network.
|
||||
*/
|
||||
|
||||
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle"];
|
||||
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.bundle?platform=ios"];
|
||||
|
||||
/**
|
||||
* OPTION 2
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
|
||||
* to your Xcode project folder in the terminal, and run
|
||||
*
|
||||
* $ curl 'http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle' -o main.jsbundle
|
||||
* $ curl 'http://localhost:8081/Examples/TicTacToe/TicTacToeApp.bundle?platform=ios' -o main.jsbundle
|
||||
*
|
||||
* then add the `main.jsbundle` file to your project and uncomment this line:
|
||||
*/
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing
|
||||
* into a proptype shape */
|
||||
declare module 'image!story-background' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing
|
||||
* into a proptype shape */
|
||||
declare module 'image!uie_comment_highlighted' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing
|
||||
* into a proptype shape */
|
||||
declare module 'image!uie_comment_normal' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing
|
||||
* into a proptype shape */
|
||||
declare module 'image!uie_thumb_normal' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing
|
||||
* into a proptype shape */
|
||||
declare module 'image!uie_thumb_selected' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
declare module 'image!NavBarButtonPlus' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
|
@ -181,8 +181,12 @@ exports.examples = [
|
|||
|
||||
render: function() {
|
||||
if (this.state.showTimer) {
|
||||
var timer =
|
||||
<TimerTester ref="interval" dt={25} type="setInterval" />;
|
||||
var timer = [
|
||||
<TimerTester ref="interval" dt={25} type="setInterval" />,
|
||||
<Button onPress={() => this.refs.interval.clear() }>
|
||||
Clear interval
|
||||
</Button>
|
||||
];
|
||||
var toggleText = 'Unmount timer';
|
||||
} else {
|
||||
var timer = null;
|
||||
|
@ -191,9 +195,6 @@ exports.examples = [
|
|||
return (
|
||||
<View>
|
||||
{timer}
|
||||
<Button onPress={() => this.refs.interval.clear() }>
|
||||
Clear interval
|
||||
</Button>
|
||||
<Button onPress={this._toggleTimer}>
|
||||
{toggleText}
|
||||
</Button>
|
||||
|
|
|
@ -59,14 +59,14 @@
|
|||
* on the same Wi-Fi network.
|
||||
*/
|
||||
|
||||
sourceURL = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.includeRequire.runModule.bundle?dev=true"];
|
||||
sourceURL = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle?dev=true"];
|
||||
|
||||
/**
|
||||
* OPTION 2
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
|
||||
* to your Xcode project folder and run
|
||||
*
|
||||
* $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.includeRequire.runModule.bundle' -o main.jsbundle
|
||||
* $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle' -o main.jsbundle
|
||||
*
|
||||
* then add the `main.jsbundle` file to your project and uncomment this line:
|
||||
*/
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
expectErrorRegex:@"because shouldThrow"];
|
||||
}
|
||||
|
||||
- (void)testTimers
|
||||
- (void)DISABLED_testTimers // #8192477
|
||||
{
|
||||
[_runner runTest:_cmd module:@"TimersTest"];
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a
|
|||
XCTAssertNil(weakMethod, @"RCTModuleMethod should have been deallocated");
|
||||
}
|
||||
|
||||
- (void)testJavaScriptExecutorIsDeallocated
|
||||
- (void)DISABLED_testJavaScriptExecutorIsDeallocated // flaky: #8195866
|
||||
{
|
||||
__weak id<RCTJavaScriptExecutor> weakExecutor;
|
||||
@autoreleasepool {
|
||||
|
|
|
@ -75,7 +75,7 @@ var WebViewExample = React.createClass({
|
|||
<TextInput
|
||||
ref={TEXT_INPUT_REF}
|
||||
autoCapitalize="none"
|
||||
value={this.state.url}
|
||||
defaultValue={this.state.url}
|
||||
onSubmitEditing={this.onSubmitEditing}
|
||||
onChange={this.handleTextInputChange}
|
||||
clearButtonMode="while-editing"
|
||||
|
|
|
@ -6,20 +6,7 @@
|
|||
|
||||
extern "C" {
|
||||
|
||||
JSValueRef nativeProfilerStart(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception);
|
||||
|
||||
JSValueRef nativeProfilerEnd(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception);
|
||||
void nativeProfilerStart(JSContextRef ctx, const char *title);
|
||||
const char *nativeProfilerEnd(JSContextRef ctx, const char *title);
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "OpaqueJSString.h"
|
||||
#include "JSProfilerPrivate.h"
|
||||
#include "JSStringRef.h"
|
||||
#include "String.h"
|
||||
|
||||
#include <YAJL/yajl_gen.h>
|
||||
|
||||
|
@ -114,48 +115,18 @@ static char *convert_to_json(const JSC::Profile *profile) {
|
|||
return json_copy;
|
||||
}
|
||||
|
||||
static char *JSEndProfilingAndRender(JSContextRef ctx, JSStringRef title)
|
||||
static const char *JSEndProfilingAndRender(JSContextRef ctx, const char *title)
|
||||
{
|
||||
JSC::ExecState *exec = toJS(ctx);
|
||||
JSC::LegacyProfiler *profiler = JSC::LegacyProfiler::profiler();
|
||||
RefPtr<JSC::Profile> rawProfile = profiler->stopProfiling(exec, title->string());
|
||||
RefPtr<JSC::Profile> rawProfile = profiler->stopProfiling(exec, WTF::String(title));
|
||||
return convert_to_json(rawProfile.get());
|
||||
}
|
||||
|
||||
JSValueRef nativeProfilerStart(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception) {
|
||||
if (argumentCount < 1) {
|
||||
// Could raise an exception here.
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL);
|
||||
JSStartProfiling(ctx, title);
|
||||
JSStringRelease(title);
|
||||
return JSValueMakeUndefined(ctx);
|
||||
void nativeProfilerStart(JSContextRef ctx, const char *title) {
|
||||
JSStartProfiling(ctx, JSStringCreateWithUTF8CString(title));
|
||||
}
|
||||
|
||||
JSValueRef nativeProfilerEnd(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception) {
|
||||
if (argumentCount < 1) {
|
||||
// Could raise an exception here.
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL);
|
||||
char *rendered = JSEndProfilingAndRender(ctx, title);
|
||||
JSStringRelease(title);
|
||||
JSStringRef profile = JSStringCreateWithUTF8CString(rendered);
|
||||
free(rendered);
|
||||
return JSValueMakeString(ctx, profile);
|
||||
const char *nativeProfilerEnd( JSContextRef ctx, const char *title) {
|
||||
return JSEndProfilingAndRender(ctx, title);
|
||||
}
|
||||
|
|
|
@ -110,5 +110,3 @@ armv7:
|
|||
${HEADER_PATHS} \
|
||||
-undefined dynamic_lookup \
|
||||
./JSCLegacyProfiler.mm ./tmp/yajl.a
|
||||
|
||||
.PHONY: ios8
|
||||
|
|
|
@ -59,21 +59,22 @@ class Easing {
|
|||
return Math.pow(2, 10 * (t - 1));
|
||||
}
|
||||
|
||||
static elastic(a: number, p: number): (t: number) => number {
|
||||
var tau = Math.PI * 2;
|
||||
// flow isn't smart enough to figure out that s is always assigned to a
|
||||
// number before being used in the returned function
|
||||
var s: any;
|
||||
if (arguments.length < 2) {
|
||||
p = 0.45;
|
||||
/**
|
||||
* A simple elastic interaction, similar to a spring. Default bounciness
|
||||
* is 1, which overshoots a little bit once. 0 bounciness doesn't overshoot
|
||||
* at all, and bounciness of N > 1 will overshoot about N times.
|
||||
*
|
||||
* Wolfram Plots:
|
||||
*
|
||||
* http://tiny.cc/elastic_b_1 (default bounciness = 1)
|
||||
* http://tiny.cc/elastic_b_3 (bounciness = 3)
|
||||
*/
|
||||
static elastic(bounciness: number): (t: number) => number {
|
||||
if (arguments.length === 0) {
|
||||
bounciness = 1;
|
||||
}
|
||||
if (arguments.length) {
|
||||
s = p / tau * Math.asin(1 / a);
|
||||
} else {
|
||||
a = 1;
|
||||
s = p / 4;
|
||||
}
|
||||
return (t) => 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * tau / p);
|
||||
var p = bounciness * Math.PI;
|
||||
return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
|
||||
};
|
||||
|
||||
static back(s: number): (t: number) => number {
|
||||
|
|
|
@ -71,6 +71,14 @@ describe('Easing', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should satisfy boundary conditions with elastic', () => {
|
||||
for (var b = 0; b < 4; b += 0.3) {
|
||||
var easing = Easing.elastic(b);
|
||||
expect(easing(0)).toBe(0);
|
||||
expect(easing(1)).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
function sampleEasingFunction(easing) {
|
||||
var DURATION = 300;
|
||||
var tickCount = Math.round(DURATION * 60 / 1000);
|
||||
|
|
|
@ -274,6 +274,29 @@ var View = React.createClass({
|
|||
* @platform android
|
||||
*/
|
||||
collapsable: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Whether this view needs to rendered offscreen and composited with an alpha
|
||||
* in order to preserve 100% correct colors and blending behavior. The default
|
||||
* (false) falls back to drawing the component and its children with an alpha
|
||||
* applied to the paint used to draw each element instead of rendering the full
|
||||
* component offscreen and compositing it back with an alpha value. This default
|
||||
* may be noticeable and undesired in the case where the View you are setting
|
||||
* an opacity on has multiple overlapping elements (e.g. multiple overlapping
|
||||
* Views, or text and a background).
|
||||
*
|
||||
* Rendering offscreen to preserve correct alpha behavior is extremely
|
||||
* expensive and hard to debug for non-native developers, which is why it is
|
||||
* not turned on by default. If you do need to enable this property for an
|
||||
* animation, consider combining it with renderToHardwareTextureAndroid if the
|
||||
* view **contents** are static (i.e. it doesn't need to be redrawn each frame).
|
||||
* If that property is enabled, this View will be rendered off-screen once,
|
||||
* saved in a hardware texture, and then composited onto the screen with an alpha
|
||||
* each frame without having to switch rendering targets on the GPU.
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
needsOffscreenAlphaCompositing: PropTypes.bool,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -210,7 +210,7 @@ var WebView = React.createClass({
|
|||
|
||||
onLoadingError: function(event: Event) {
|
||||
event.persist(); // persist this event because we need to store it
|
||||
console.error('Encountered an error loading page', event.nativeEvent);
|
||||
console.warn('Encountered an error loading page', event.nativeEvent);
|
||||
|
||||
this.setState({
|
||||
lastErrorEvent: event.nativeEvent,
|
||||
|
|
|
@ -2,18 +2,32 @@
|
|||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule AssetRegistry
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var assets = [];
|
||||
export type PackagerAsset = {
|
||||
__packager_asset: boolean,
|
||||
fileSystemLocation: string,
|
||||
httpServerLocation: string,
|
||||
width: number,
|
||||
height: number,
|
||||
scales: Array<number>,
|
||||
hash: string,
|
||||
name: string,
|
||||
type: string,
|
||||
};
|
||||
|
||||
function registerAsset(asset) {
|
||||
|
||||
var assets: Array<PackagerAsset> = [];
|
||||
|
||||
function registerAsset(asset: PackagerAsset): number {
|
||||
// `push` returns new array length, so the first asset will
|
||||
// get id 1 (not 0) to make the value truthy
|
||||
return assets.push(asset);
|
||||
}
|
||||
|
||||
function getAssetByID(assetId) {
|
||||
function getAssetByID(assetId: number): PackagerAsset {
|
||||
return assets[assetId - 1];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* 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 GlobalImageStub
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// This is a stub for flow to make it understand require('image!icon')
|
||||
// See packager/react-packager/src/Bundler/index.js
|
||||
|
||||
module.exports = {
|
||||
__packager_asset: true,
|
||||
isStatic: true,
|
||||
path: '/full/path/to/something.png',
|
||||
uri: 'icon',
|
||||
width: 100,
|
||||
height: 100,
|
||||
deprecated: true,
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* 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 RelativeImageStub
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// This is a stub for flow to make it understand require('./icon.png')
|
||||
// See packager/react-packager/src/Bundler/index.js
|
||||
|
||||
var AssetRegistry = require('AssetRegistry');
|
||||
|
||||
module.exports = AssetRegistry.registerAsset({
|
||||
__packager_asset: true,
|
||||
fileSystemLocation: '/full/path/to/directory',
|
||||
httpServerLocation: '/assets/full/path/to/directory',
|
||||
width: 100,
|
||||
height: 100,
|
||||
scales: [1, 2, 3],
|
||||
hash: 'nonsense',
|
||||
name: 'icon',
|
||||
type: 'png',
|
||||
});
|
|
@ -138,7 +138,10 @@ function setupProfile() {
|
|||
|
||||
function setUpProcessEnv() {
|
||||
GLOBAL.process = GLOBAL.process || {};
|
||||
GLOBAL.process.env = {NODE_ENV: __DEV__ ? 'development' : 'production'};
|
||||
GLOBAL.process.env = GLOBAL.process.env || {};
|
||||
if (!GLOBAL.process.env.NODE_ENV) {
|
||||
GLOBAL.process.env.NODE_ENV = __DEV__ ? 'development' : 'production';
|
||||
}
|
||||
}
|
||||
|
||||
setUpRedBoxErrorHandler();
|
||||
|
|
|
@ -18,6 +18,9 @@ var _portalRef: any;
|
|||
var lastUsedTag = 0;
|
||||
|
||||
/*
|
||||
* Note: Only intended for Android at the moment. Just use Modal in your iOS
|
||||
* code.
|
||||
*
|
||||
* A container that renders all the modals on top of everything else in the application.
|
||||
*
|
||||
* Portal makes it possible for application code to pass modal views all the way up to
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
_scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
RCTAssert(_scriptURL != nil, @"Could not locate main.jsBundle");
|
||||
#else
|
||||
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]];
|
||||
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?dev=true&platform=ios", app]];
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
|
|
|
@ -30,6 +30,7 @@ ReactNativeViewAttributes.UIView = {
|
|||
onAccessibilityTap: true,
|
||||
onMagicTap: true,
|
||||
collapsable: true,
|
||||
needsOffscreenAlphaCompositing: true,
|
||||
};
|
||||
|
||||
ReactNativeViewAttributes.RCTView = merge(
|
||||
|
|
|
@ -65,14 +65,12 @@ class MessageQueue {
|
|||
localModules && this._genLookupTables(
|
||||
localModules, this._moduleTable, this._methodTable);
|
||||
|
||||
if (__DEV__) {
|
||||
this._debugInfo = {};
|
||||
this._remoteModuleTable = {};
|
||||
this._remoteMethodTable = {};
|
||||
this._genLookupTables(
|
||||
remoteModules, this._remoteModuleTable, this._remoteMethodTable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public APIs
|
||||
|
@ -116,13 +114,11 @@ class MessageQueue {
|
|||
*/
|
||||
__nativeCall(module, method, params, onFail, onSucc) {
|
||||
if (onFail || onSucc) {
|
||||
if (__DEV__) {
|
||||
// eventually delete old debug info
|
||||
(this._callbackID > (1 << 5)) &&
|
||||
(this._debugInfo[this._callbackID >> 5] = null);
|
||||
|
||||
this._debugInfo[this._callbackID >> 1] = [module, method];
|
||||
}
|
||||
onFail && params.push(this._callbackID);
|
||||
this._callbacks[this._callbackID++] = onFail;
|
||||
onSucc && params.push(this._callbackID);
|
||||
|
@ -155,13 +151,15 @@ class MessageQueue {
|
|||
BridgeProfiling.profile(
|
||||
() => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`);
|
||||
let callback = this._callbacks[cbID];
|
||||
if (__DEV__) {
|
||||
if (!callback || __DEV__) {
|
||||
let debug = this._debugInfo[cbID >> 1];
|
||||
let module = debug && this._remoteModuleTable[debug[0]];
|
||||
let method = debug && this._remoteMethodTable[debug[0]][debug[1]];
|
||||
if (!callback) {
|
||||
console.error(`Callback with id ${cbID}: ${module}.${method}() not found`);
|
||||
} else if (SPY_MODE) {
|
||||
invariant(
|
||||
callback,
|
||||
`Callback with id ${cbID}: ${module}.${method}() not found`
|
||||
);
|
||||
if (callback && SPY_MODE) {
|
||||
console.log('N->JS : <callback for ' + module + '.' + method + '>(' + JSON.stringify(args) + ')');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
|||
// Components
|
||||
ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
|
||||
DatePickerIOS: require('DatePickerIOS'),
|
||||
DrawerLayoutAndroid: require('DrawerLayoutAndroid'),
|
||||
Image: require('Image'),
|
||||
ListView: require('ListView'),
|
||||
MapView: require('MapView'),
|
||||
|
@ -28,15 +29,19 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
|||
Navigator: require('Navigator'),
|
||||
NavigatorIOS: require('NavigatorIOS'),
|
||||
PickerIOS: require('PickerIOS'),
|
||||
ProgressBarAndroid: require('ProgressBarAndroid'),
|
||||
ProgressViewIOS: require('ProgressViewIOS'),
|
||||
ScrollView: require('ScrollView'),
|
||||
SegmentedControlIOS: require('SegmentedControlIOS'),
|
||||
SliderIOS: require('SliderIOS'),
|
||||
SwitchAndroid: require('SwitchAndroid'),
|
||||
SwitchIOS: require('SwitchIOS'),
|
||||
TabBarIOS: require('TabBarIOS'),
|
||||
Text: require('Text'),
|
||||
TextInput: require('TextInput'),
|
||||
ToolbarAndroid: require('ToolbarAndroid'),
|
||||
TouchableHighlight: require('TouchableHighlight'),
|
||||
TouchableNativeFeedback: require('TouchableNativeFeedback'),
|
||||
TouchableOpacity: require('TouchableOpacity'),
|
||||
TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
|
||||
View: require('View'),
|
||||
|
@ -50,6 +55,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
|||
AppRegistry: require('AppRegistry'),
|
||||
AppStateIOS: require('AppStateIOS'),
|
||||
AsyncStorage: require('AsyncStorage'),
|
||||
BackAndroid: require('BackAndroid'),
|
||||
CameraRoll: require('CameraRoll'),
|
||||
Dimensions: require('Dimensions'),
|
||||
Easing: require('Easing'),
|
||||
|
|
|
@ -407,9 +407,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
|
|||
|
||||
- (NSDictionary *)modules
|
||||
{
|
||||
RCTAssert(!self.isValid || _modulesByName != nil, @"Bridge modules have not yet been initialized. "
|
||||
"You may be trying to access a module too early in the startup procedure.");
|
||||
|
||||
if (RCT_DEBUG && self.isValid && _modulesByName == nil) {
|
||||
RCTLogError(@"Bridge modules have not yet been initialized. You may be "
|
||||
"trying to access a module too early in the startup procedure.");
|
||||
}
|
||||
return _modulesByName;
|
||||
}
|
||||
|
||||
|
|
|
@ -226,7 +226,11 @@ void _RCTLogFormat(
|
|||
}
|
||||
}
|
||||
}];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// red box is thread safe, but by deferring to main queue we avoid a startup
|
||||
// race condition that causes the module to be accessed before it has loaded
|
||||
[[RCTBridge currentBridge].redBox showErrorMessage:message withStack:stack];
|
||||
});
|
||||
}
|
||||
|
||||
// Log to JS executor
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTDevMenu.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTProfile.h"
|
||||
#import "RCTPerformanceLogger.h"
|
||||
|
@ -89,6 +90,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
|||
}
|
||||
|
||||
@synthesize valid = _valid;
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
|
@ -110,7 +112,7 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, __unused JSObjectRe
|
|||
NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, messageRef);
|
||||
JSStringRelease(messageRef);
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
|
||||
@"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).includeRequire.runModule.bundle(:[0-9]+:[0-9]+)"
|
||||
@"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).bundle(:[0-9]+:[0-9]+)"
|
||||
options:NSRegularExpressionCaseInsensitive
|
||||
error:NULL];
|
||||
message = [regex stringByReplacingMatchesInString:message
|
||||
|
@ -285,11 +287,18 @@ static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObje
|
|||
#if RCT_JSC_PROFILER
|
||||
void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW);
|
||||
if (JSCProfiler != NULL) {
|
||||
JSObjectCallAsFunctionCallback nativeProfilerStart = dlsym(JSCProfiler, "nativeProfilerStart");
|
||||
JSObjectCallAsFunctionCallback nativeProfilerEnd = dlsym(JSCProfiler, "nativeProfilerEnd");
|
||||
void (*nativeProfilerStart)(JSContextRef, const char *) = (void (*)(JSContextRef, const char *))dlsym(JSCProfiler, "nativeProfilerStart");
|
||||
const char *(*nativeProfilerEnd)(JSContextRef, const char *) = (const char *(*)(JSContextRef, const char *))dlsym(JSCProfiler, "nativeProfilerEnd");
|
||||
if (nativeProfilerStart != NULL && nativeProfilerEnd != NULL) {
|
||||
[strongSelf _addNativeHook:nativeProfilerStart withName:"nativeProfilerStart"];
|
||||
[strongSelf _addNativeHook:nativeProfilerEnd withName:"nativeProfilerStop"];
|
||||
__block BOOL isProfiling = NO;
|
||||
[_bridge.devMenu addItem:@"Profile" handler:^{
|
||||
if (isProfiling) {
|
||||
RCTLogInfo(@"%s", nativeProfilerEnd(strongSelf->_context.ctx, "profile"));
|
||||
} else {
|
||||
nativeProfilerStart(strongSelf->_context.ctx, "profile");
|
||||
}
|
||||
isProfiling = !isProfiling;
|
||||
}];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -314,7 +314,7 @@ RCT_EXPORT_MODULE()
|
|||
self.liveReloadEnabled = !_liveReloadEnabled;
|
||||
}]];
|
||||
|
||||
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
|
||||
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Systrace" : @"Start Systrace";
|
||||
[items addObject:[[RCTDevMenuItem alloc] initWithTitle:profilingTitle handler:^{
|
||||
self.profilingEnabled = !_profilingEnabled;
|
||||
}]];
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#import "RCTSourceCode.h"
|
||||
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTUtils.h"
|
||||
|
@ -19,20 +20,18 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
#if !RCT_DEV
|
||||
- (void)setScriptText:(NSString *)scriptText {}
|
||||
#endif
|
||||
|
||||
RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback
|
||||
failureCallback:(RCTResponseErrorBlock)failureCallback)
|
||||
{
|
||||
if (self.scriptText && self.scriptURL) {
|
||||
if (RCT_DEV && self.scriptText && self.scriptURL) {
|
||||
successCallback(@[@{@"text": self.scriptText, @"url": self.scriptURL.absoluteString}]);
|
||||
} else {
|
||||
failureCallback(RCTErrorWithMessage(@"Source code is not available"));
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)constantsToExport
|
||||
{
|
||||
NSString *URL = self.bridge.bundleURL.absoluteString ?: @"";
|
||||
return @{@"scriptURL": URL};
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -85,6 +85,10 @@ function connectToDebuggerProxy() {
|
|||
|
||||
ws.onmessage = function(message) {
|
||||
var object = JSON.parse(message.data);
|
||||
if (!object.method) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sendReply = function(result) {
|
||||
ws.send(JSON.stringify({replyID: object.id, result: result}));
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ const fs = require('fs');
|
|||
const path = require('path');
|
||||
const Promise = require('promise');
|
||||
const ProgressBar = require('progress');
|
||||
const BundlesLayout = require('../BundlesLayout');
|
||||
const Cache = require('../Cache');
|
||||
const Transformer = require('../JSTransformer');
|
||||
const DependencyResolver = require('../DependencyResolver');
|
||||
|
@ -104,6 +105,13 @@ class Bundler {
|
|||
cache: this._cache,
|
||||
});
|
||||
|
||||
this._bundlesLayout = new BundlesLayout({
|
||||
dependencyResolver: this._resolver,
|
||||
resetCache: opts.resetCache,
|
||||
cacheVersion: opts.cacheVersion,
|
||||
projectRoots: opts.projectRoots,
|
||||
});
|
||||
|
||||
this._transformer = new Transformer({
|
||||
projectRoots: opts.projectRoots,
|
||||
blacklistRE: opts.blacklistRE,
|
||||
|
@ -120,6 +128,10 @@ class Bundler {
|
|||
return this._cache.end();
|
||||
}
|
||||
|
||||
getLayout(main, isDev) {
|
||||
return this._bundlesLayout.generateLayout(main, isDev);
|
||||
}
|
||||
|
||||
bundle(main, runModule, sourceMapUrl, isDev, platform) {
|
||||
const bundle = new Bundle(sourceMapUrl);
|
||||
const findEventId = Activity.startEvent('find dependencies');
|
||||
|
|
|
@ -8,31 +8,35 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.dontMock('../index');
|
||||
jest.dontMock('../index');
|
||||
jest.mock('fs');
|
||||
|
||||
const Promise = require('promise');
|
||||
|
||||
describe('BundlesLayout', () => {
|
||||
var BundlesLayout;
|
||||
var DependencyResolver;
|
||||
let BundlesLayout;
|
||||
let DependencyResolver;
|
||||
let loadCacheSync;
|
||||
|
||||
beforeEach(() => {
|
||||
BundlesLayout = require('../index');
|
||||
DependencyResolver = require('../../DependencyResolver');
|
||||
loadCacheSync = require('../../lib/loadCacheSync');
|
||||
});
|
||||
|
||||
describe('generate', () => {
|
||||
function newBundlesLayout() {
|
||||
return new BundlesLayout({
|
||||
function newBundlesLayout(options) {
|
||||
return new BundlesLayout(Object.assign({
|
||||
projectRoots: ['/root'],
|
||||
dependencyResolver: new DependencyResolver(),
|
||||
});
|
||||
}, options));
|
||||
}
|
||||
|
||||
describe('layout', () => {
|
||||
function isPolyfill() {
|
||||
return false;
|
||||
}
|
||||
|
||||
describe('getLayout', () => {
|
||||
function dep(path) {
|
||||
return {
|
||||
path: path,
|
||||
|
@ -58,7 +62,9 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout({resetCache: true})
|
||||
.getLayout('/root/index.js')
|
||||
.then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js', '/root/a.js'],
|
||||
|
@ -85,7 +91,9 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout({resetCache: true})
|
||||
.getLayout('/root/index.js')
|
||||
.then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
|
@ -121,7 +129,9 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout({resetCache: true})
|
||||
.getLayout('/root/index.js')
|
||||
.then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
|
@ -161,7 +171,9 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout({resetCache: true})
|
||||
.getLayout('/root/index.js')
|
||||
.then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
|
@ -197,8 +209,10 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(
|
||||
bundles => expect(bundles).toEqual({
|
||||
return newBundlesLayout({resetCache: true})
|
||||
.getLayout('/root/index.js')
|
||||
.then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js', '/root/a.js'],
|
||||
children: [{
|
||||
|
@ -243,8 +257,8 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
var layout = newBundlesLayout();
|
||||
return layout.generateLayout(['/root/index.js']).then(() => {
|
||||
var layout = newBundlesLayout({resetCache: true});
|
||||
return layout.getLayout('/root/index.js').then(() => {
|
||||
expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0');
|
||||
expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0');
|
||||
expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.0.1');
|
||||
|
@ -253,4 +267,61 @@ describe('BundlesLayout', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache', () => {
|
||||
beforeEach(() => {
|
||||
loadCacheSync.mockReturnValue({
|
||||
'/root/index.js': {
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js'],
|
||||
children: [],
|
||||
}],
|
||||
},
|
||||
'/root/b.js': {
|
||||
id: 'bundle.2',
|
||||
modules: ['/root/b.js'],
|
||||
children: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
pit('should load layouts', () => {
|
||||
const layout = newBundlesLayout({ resetCache: false });
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
layout.getLayout('/root/index.js'),
|
||||
layout.getLayout('/root/b.js'),
|
||||
])
|
||||
.then(([layoutIndex, layoutB]) => {
|
||||
expect(layoutIndex).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js'],
|
||||
children: [],
|
||||
}],
|
||||
});
|
||||
|
||||
expect(layoutB).toEqual({
|
||||
id: 'bundle.2',
|
||||
modules: ['/root/b.js'],
|
||||
children: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should load moduleToBundle map', () => {
|
||||
const layout = newBundlesLayout({ resetCache: false });
|
||||
|
||||
expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0');
|
||||
expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0.1');
|
||||
expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -75,7 +75,11 @@ describe('BundlesLayout', () => {
|
|||
assetRoots: ['/root'],
|
||||
});
|
||||
|
||||
return new BundlesLayout({dependencyResolver: resolver});
|
||||
return new BundlesLayout({
|
||||
dependencyResolver: resolver,
|
||||
resetCache: true,
|
||||
projectRoots: ['/root', '/' + __dirname.split('/')[1]],
|
||||
});
|
||||
}
|
||||
|
||||
function stripPolyfills(bundle) {
|
||||
|
@ -114,7 +118,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -140,7 +144,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -166,7 +170,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -201,7 +205,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -242,7 +246,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -282,7 +286,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -323,7 +327,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -370,7 +374,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -408,7 +412,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -446,7 +450,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -480,7 +484,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -512,7 +516,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -539,7 +543,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -576,7 +580,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
|
|
@ -8,14 +8,33 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
const Activity = require('../Activity');
|
||||
|
||||
const _ = require('underscore');
|
||||
const declareOpts = require('../lib/declareOpts');
|
||||
const fs = require('fs');
|
||||
const getCacheFilePath = require('../lib/getCacheFilePath');
|
||||
const loadCacheSync = require('../lib/loadCacheSync');
|
||||
const version = require('../../../../package.json').version;
|
||||
const path = require('path');
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
dependencyResolver: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
resetCache: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
cacheVersion: {
|
||||
type: 'string',
|
||||
default: '1.0',
|
||||
},
|
||||
projectRoots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const BUNDLE_PREFIX = 'bundle';
|
||||
|
@ -29,19 +48,37 @@ class BundlesLayout {
|
|||
const opts = validateOpts(options);
|
||||
this._resolver = opts.dependencyResolver;
|
||||
|
||||
// Cache in which bundle is each module.
|
||||
this._moduleToBundle = Object.create(null);
|
||||
|
||||
// Cache the bundles layouts for each entry point. This entries
|
||||
// are not evicted unless the user explicitly specifies so as
|
||||
// computing them is pretty expensive
|
||||
this._layouts = Object.create(null);
|
||||
|
||||
// TODO: watch for file creations and removals to update this caches
|
||||
|
||||
this._cacheFilePath = this._getCacheFilePath(opts);
|
||||
if (!opts.resetCache) {
|
||||
this._loadCacheSync(this._cacheFilePath);
|
||||
} else {
|
||||
this._persistCacheEventually();
|
||||
}
|
||||
}
|
||||
|
||||
generateLayout(entryPaths, isDev) {
|
||||
getLayout(entryPath, isDev) {
|
||||
if (this._layouts[entryPath]) {
|
||||
return this._layouts[entryPath];
|
||||
}
|
||||
var currentBundleID = 0;
|
||||
const rootBundle = {
|
||||
id: BUNDLE_PREFIX + '.' + currentBundleID++,
|
||||
modules: [],
|
||||
children: [],
|
||||
};
|
||||
var pending = [{paths: entryPaths, bundle: rootBundle}];
|
||||
var pending = [{paths: [entryPath], bundle: rootBundle}];
|
||||
|
||||
return promiseWhile(
|
||||
this._layouts[entryPath] = promiseWhile(
|
||||
() => pending.length > 0,
|
||||
() => rootBundle,
|
||||
() => {
|
||||
|
@ -62,6 +99,9 @@ class BundlesLayout {
|
|||
if (dependencies.length > 0) {
|
||||
bundle.modules = dependencies;
|
||||
}
|
||||
|
||||
// persist changes to layouts
|
||||
this._persistCacheEventually();
|
||||
},
|
||||
index => {
|
||||
const pendingSyncDep = pendingSyncDeps.shift();
|
||||
|
@ -90,11 +130,71 @@ class BundlesLayout {
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
return this._layouts[entryPath];
|
||||
}
|
||||
|
||||
getBundleIDForModule(path) {
|
||||
return this._moduleToBundle[path];
|
||||
}
|
||||
|
||||
_loadCacheSync(cachePath) {
|
||||
const loadCacheId = Activity.startEvent('Loading bundles layout');
|
||||
const cacheOnDisk = loadCacheSync(cachePath);
|
||||
|
||||
// TODO: create single-module bundles for unexistent modules
|
||||
// TODO: remove modules that no longer exist
|
||||
Object.keys(cacheOnDisk).forEach(entryPath => {
|
||||
this._layouts[entryPath] = Promise.resolve(cacheOnDisk[entryPath]);
|
||||
this._fillModuleToBundleMap(cacheOnDisk[entryPath]);
|
||||
});
|
||||
|
||||
Activity.endEvent(loadCacheId);
|
||||
}
|
||||
|
||||
_fillModuleToBundleMap(bundle) {
|
||||
bundle.modules.forEach(module => this._moduleToBundle[module] = bundle.id);
|
||||
bundle.children.forEach(child => this._fillModuleToBundleMap(child));
|
||||
}
|
||||
|
||||
_persistCacheEventually() {
|
||||
_.debounce(
|
||||
this._persistCache.bind(this),
|
||||
2000,
|
||||
);
|
||||
}
|
||||
|
||||
_persistCache() {
|
||||
if (this._persisting !== null) {
|
||||
return this._persisting;
|
||||
}
|
||||
|
||||
this._persisting = Promise
|
||||
.all(_.values(this._layouts))
|
||||
.then(bundlesLayout => {
|
||||
var json = Object.create(null);
|
||||
Object.keys(this._layouts).forEach((p, i) =>
|
||||
json[p] = bundlesLayout[i]
|
||||
);
|
||||
|
||||
return Promise.denodeify(fs.writeFile)(
|
||||
this._cacheFilepath,
|
||||
JSON.stringify(json),
|
||||
);
|
||||
})
|
||||
.then(() => this._persisting = null);
|
||||
|
||||
return this._persisting;
|
||||
}
|
||||
|
||||
_getCacheFilePath(options) {
|
||||
return getCacheFilePath(
|
||||
'react-packager-bundles-cache-',
|
||||
version,
|
||||
options.projectRoots.join(',').split(path.sep).join('-'),
|
||||
options.cacheVersion || '0',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Runs the body Promise meanwhile the condition callback is satisfied.
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
jest
|
||||
.dontMock('underscore')
|
||||
.dontMock('absolute-path')
|
||||
.dontMock('../');
|
||||
.dontMock('../')
|
||||
.dontMock('../../lib/loadCacheSync')
|
||||
.dontMock('../../lib/getCacheFilePath');
|
||||
|
||||
jest
|
||||
.mock('os')
|
||||
|
|
|
@ -8,17 +8,17 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var crypto = require('crypto');
|
||||
var declareOpts = require('../lib/declareOpts');
|
||||
var fs = require('fs');
|
||||
var isAbsolutePath = require('absolute-path');
|
||||
var path = require('path');
|
||||
var Promise = require('promise');
|
||||
var tmpdir = require('os').tmpDir();
|
||||
var version = require('../../../../package.json').version;
|
||||
const Promise = require('promise');
|
||||
const _ = require('underscore');
|
||||
const declareOpts = require('../lib/declareOpts');
|
||||
const fs = require('fs');
|
||||
const getCacheFilePath = require('../lib/getCacheFilePath');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const loadCacheSync = require('../lib/loadCacheSync');
|
||||
const path = require('path');
|
||||
const version = require('../../../../package.json').version;
|
||||
|
||||
var validateOpts = declareOpts({
|
||||
const validateOpts = declareOpts({
|
||||
resetCache: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
|
@ -164,21 +164,7 @@ class Cache {
|
|||
|
||||
_loadCacheSync(cachePath) {
|
||||
var ret = Object.create(null);
|
||||
if (!fs.existsSync(cachePath)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
var cacheOnDisk;
|
||||
try {
|
||||
cacheOnDisk = JSON.parse(fs.readFileSync(cachePath));
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.warn('Unable to parse cache file. Will clear and continue.');
|
||||
fs.unlinkSync(cachePath);
|
||||
return ret;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
var cacheOnDisk = loadCacheSync(cachePath);
|
||||
|
||||
// Filter outdated cache and convert to promises.
|
||||
Object.keys(cacheOnDisk).forEach(key => {
|
||||
|
@ -203,20 +189,13 @@ class Cache {
|
|||
}
|
||||
|
||||
_getCacheFilePath(options) {
|
||||
var hash = crypto.createHash('md5');
|
||||
hash.update(version);
|
||||
|
||||
var roots = options.projectRoots.join(',').split(path.sep).join('-');
|
||||
hash.update(roots);
|
||||
|
||||
var cacheVersion = options.cacheVersion || '0';
|
||||
hash.update(cacheVersion);
|
||||
|
||||
hash.update(options.transformModulePath);
|
||||
|
||||
var name = 'react-packager-cache-' + hash.digest('hex');
|
||||
|
||||
return path.join(tmpdir, name);
|
||||
return getCacheFilePath(
|
||||
'react-packager-cache-',
|
||||
version,
|
||||
options.projectRoots.join(',').split(path.sep).join('-'),
|
||||
options.cacheVersion || '0',
|
||||
options.transformModulePath,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,10 @@ const readFile = Promise.denodeify(fs.readFile);
|
|||
const MAX_CALLS_PER_WORKER = 600;
|
||||
|
||||
// Worker will timeout if one of the callers timeout.
|
||||
const DEFAULT_MAX_CALL_TIME = 60000;
|
||||
const DEFAULT_MAX_CALL_TIME = 120000;
|
||||
|
||||
// How may times can we tolerate failures from the worker.
|
||||
const MAX_RETRIES = 3;
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
|
@ -63,6 +66,7 @@ class Transformer {
|
|||
maxConcurrentCallsPerWorker: 1,
|
||||
maxCallsPerWorker: MAX_CALLS_PER_WORKER,
|
||||
maxCallTime: opts.transformTimeoutInterval,
|
||||
maxRetries: MAX_RETRIES,
|
||||
}, opts.transformModulePath);
|
||||
|
||||
this._transform = Promise.denodeify(this._workers);
|
||||
|
@ -118,6 +122,13 @@ class Transformer {
|
|||
);
|
||||
timeoutErr.type = 'TimeoutError';
|
||||
throw timeoutErr;
|
||||
} else if (err.type === 'ProcessTerminatedError') {
|
||||
const uncaughtError = new Error(
|
||||
'Uncaught error in the transformer worker: ' +
|
||||
this._opts.transformModulePath
|
||||
);
|
||||
uncaughtError.type = 'ProcessTerminatedError';
|
||||
throw uncaughtError;
|
||||
}
|
||||
|
||||
throw formatError(err, filePath);
|
||||
|
|
|
@ -13,6 +13,10 @@ const Promise = require('promise');
|
|||
const bser = require('bser');
|
||||
const debug = require('debug')('ReactPackager:SocketClient');
|
||||
const net = require('net');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpdir();
|
||||
|
||||
const LOG_PATH = path.join(tmpdir, 'react-packager.log');
|
||||
|
||||
class SocketClient {
|
||||
static create(sockPath) {
|
||||
|
@ -81,7 +85,9 @@ class SocketClient {
|
|||
delete this._resolvers[message.id];
|
||||
|
||||
if (message.type === 'error') {
|
||||
resolver.reject(new Error(message.data));
|
||||
resolver.reject(new Error(
|
||||
message.data + '\n' + 'See logs ' + LOG_PATH
|
||||
));
|
||||
} else {
|
||||
resolver.resolve(message.data);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ class SocketServer {
|
|||
options
|
||||
);
|
||||
resolve(this);
|
||||
process.on('exit', () => fs.unlinkSync(sockPath));
|
||||
});
|
||||
});
|
||||
this._server.on('connection', (sock) => this._handleConnection(sock));
|
||||
|
@ -41,8 +42,6 @@ class SocketServer {
|
|||
this._packagerServer = new Server(options);
|
||||
this._jobs = 0;
|
||||
this._dieEventually();
|
||||
|
||||
process.on('exit', () => fs.unlinkSync(sockPath));
|
||||
}
|
||||
|
||||
onReady() {
|
||||
|
@ -72,6 +71,11 @@ class SocketServer {
|
|||
debug('request error', error);
|
||||
this._jobs--;
|
||||
this._reply(sock, m.id, 'error', error.stack);
|
||||
|
||||
// Fatal error from JSTransformer transform workers.
|
||||
if (error.type === 'ProcessTerminatedError') {
|
||||
setImmediate(() => process.exit(1));
|
||||
}
|
||||
};
|
||||
|
||||
switch (m.type) {
|
||||
|
@ -138,12 +142,17 @@ class SocketServer {
|
|||
process.send({ type: 'createdServer' });
|
||||
},
|
||||
error => {
|
||||
debug('error creating server', error.code);
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
// Server already listening, this may happen if multiple
|
||||
// clients where started in quick succussion (buck).
|
||||
process.send({ type: 'createdServer' });
|
||||
|
||||
// Kill this server because some other server with the same
|
||||
// config and socket already started.
|
||||
debug('server already started');
|
||||
setImmediate(() => process.exit());
|
||||
} else {
|
||||
debug('error creating server', error.code);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,6 @@ describe('SocketClient', () => {
|
|||
data: 'some error'
|
||||
});
|
||||
|
||||
return promise.catch(m => expect(m.message).toBe('some error'));
|
||||
return promise.catch(m => expect(m.message).toContain('some error'));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,17 @@ describe('SocketInterface', () => {
|
|||
pit('creates socket path by hashing options', () => {
|
||||
const fs = require('fs');
|
||||
fs.existsSync = jest.genMockFn().mockImpl(() => true);
|
||||
fs.unlinkSync = jest.genMockFn();
|
||||
let callback;
|
||||
|
||||
require('child_process').spawn.mockImpl(() => ({
|
||||
on: (event, cb) => callback = cb,
|
||||
send: (message) => {
|
||||
setImmediate(() => callback({ type: 'createdServer' }));
|
||||
},
|
||||
unref: () => undefined,
|
||||
disconnect: () => undefined,
|
||||
}));
|
||||
|
||||
// Check that given two equivelant server options, we end up with the same
|
||||
// socket path.
|
||||
|
@ -49,6 +60,7 @@ describe('SocketInterface', () => {
|
|||
pit('should fork a server', () => {
|
||||
const fs = require('fs');
|
||||
fs.existsSync = jest.genMockFn().mockImpl(() => false);
|
||||
fs.unlinkSync = jest.genMockFn();
|
||||
let sockPath;
|
||||
let callback;
|
||||
|
||||
|
|
|
@ -13,12 +13,14 @@ const SocketClient = require('./SocketClient');
|
|||
const SocketServer = require('./SocketServer');
|
||||
const _ = require('underscore');
|
||||
const crypto = require('crypto');
|
||||
const debug = require('debug')('ReactPackager:SocketInterface');
|
||||
const fs = require('fs');
|
||||
const net = require('net');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpdir();
|
||||
const {spawn} = require('child_process');
|
||||
|
||||
const CREATE_SERVER_TIMEOUT = 30000;
|
||||
const CREATE_SERVER_TIMEOUT = 60000;
|
||||
|
||||
const SocketInterface = {
|
||||
getOrCreateSocketFor(options) {
|
||||
|
@ -38,10 +40,32 @@ const SocketInterface = {
|
|||
);
|
||||
|
||||
if (fs.existsSync(sockPath)) {
|
||||
var sock = net.connect(sockPath);
|
||||
sock.on('connect', () => {
|
||||
sock.end();
|
||||
resolve(SocketClient.create(sockPath));
|
||||
return;
|
||||
});
|
||||
sock.on('error', (e) => {
|
||||
try {
|
||||
debug('deleting socket for not responding', sockPath);
|
||||
fs.unlinkSync(sockPath);
|
||||
} catch (err) {
|
||||
// Another client might have deleted it first.
|
||||
}
|
||||
createServer(resolve, reject, options, sockPath);
|
||||
});
|
||||
} else {
|
||||
createServer(resolve, reject, options, sockPath);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
listenOnServerMessages() {
|
||||
return SocketServer.listenOnServerIPCMessages();
|
||||
}
|
||||
};
|
||||
|
||||
function createServer(resolve, reject, options, sockPath) {
|
||||
const logPath = path.join(tmpdir, 'react-packager.log');
|
||||
|
||||
const timeout = setTimeout(
|
||||
|
@ -78,11 +102,11 @@ const SocketInterface = {
|
|||
if (m && m.type && m.type === 'createdServer') {
|
||||
clearTimeout(timeout);
|
||||
child.disconnect();
|
||||
|
||||
resolve(SocketClient.create(sockPath));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (options.blacklistRE) {
|
||||
options.blacklistRE = { source: options.blacklistRE.source };
|
||||
}
|
||||
|
@ -91,13 +115,6 @@ const SocketInterface = {
|
|||
type: 'createSocketServer',
|
||||
data: { sockPath, options }
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
listenOnServerMessages() {
|
||||
return SocketServer.listenOnServerIPCMessages();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = SocketInterface;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* 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 crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpDir();
|
||||
|
||||
function getCacheFilePath(args) {
|
||||
args = Array.prototype.slice.call(args);
|
||||
const prefix = args.shift();
|
||||
|
||||
let hash = crypto.createHash('md5');
|
||||
args.forEach(arg => hash.update(arg));
|
||||
|
||||
return path.join(tmpdir, prefix + hash.digest('hex'));
|
||||
}
|
||||
|
||||
module.exports = getCacheFilePath;
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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 fs = require('fs');
|
||||
|
||||
function loadCacheSync(cachePath) {
|
||||
if (!fs.existsSync(cachePath)) {
|
||||
return Object.create(null);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(cachePath));
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.warn('Unable to parse cache file. Will clear and continue.');
|
||||
fs.unlinkSync(cachePath);
|
||||
return Object.create(null);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = loadCacheSync;
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @emails oncall+jsinfra
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
jest.autoMockOff();
|
||||
jest.mock('../../../BundlesLayout');
|
||||
|
||||
const babel = require('babel-core');
|
||||
const BundlesLayout = require('../../../BundlesLayout');
|
||||
|
||||
const testData = {
|
||||
isolated: {
|
||||
input: 'System.import("moduleA");',
|
||||
output: 'loadBundles(["bundle.0"]);'
|
||||
},
|
||||
single: {
|
||||
input: 'System.import("moduleA").then(function (bundleA) {});',
|
||||
output: 'loadBundles(["bundle.0"]).then(function (bundleA) {});'
|
||||
},
|
||||
multiple: {
|
||||
input: [
|
||||
'Promise.all([',
|
||||
'System.import("moduleA"), System.import("moduleB"),',
|
||||
']).then(function (bundlesA, bundlesB) {});',
|
||||
].join('\n'),
|
||||
output: [
|
||||
'Promise.all([',
|
||||
'loadBundles(["bundle.0"]), loadBundles(["bundle.1"])',
|
||||
']).then(function (bundlesA, bundlesB) {});',
|
||||
].join(''),
|
||||
},
|
||||
};
|
||||
|
||||
describe('System.import', () => {
|
||||
let layout = new BundlesLayout();
|
||||
BundlesLayout.prototype.getBundleIDForModule.mockImpl(module => {
|
||||
switch (module) {
|
||||
case 'moduleA': return 'bundle.0';
|
||||
case 'moduleB': return 'bundle.1';
|
||||
}
|
||||
});
|
||||
|
||||
function transform(source) {
|
||||
return babel.transform(source, {
|
||||
plugins: [require('../')],
|
||||
blacklist: ['strict'],
|
||||
extra: { bundlesLayout: layout },
|
||||
}).code;
|
||||
}
|
||||
|
||||
function test(data) {
|
||||
// transform and remove new lines
|
||||
expect(transform(data.input).replace(/(\r\n|\n|\r)/gm,'')).toEqual(data.output);
|
||||
}
|
||||
|
||||
it('should transform isolated `System.import`', () => {
|
||||
test(testData.isolated);
|
||||
});
|
||||
|
||||
it('should transform single `System.import`', () => {
|
||||
test(testData.single);
|
||||
});
|
||||
|
||||
it('should transform multiple `System.import`s', () => {
|
||||
test(testData.multiple);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var t = require('babel-core').types;
|
||||
|
||||
/**
|
||||
* Transforms asynchronous module importing into a function call
|
||||
* that includes which bundle needs to be loaded
|
||||
*
|
||||
* Transforms:
|
||||
*
|
||||
* System.import('moduleA')
|
||||
*
|
||||
* to:
|
||||
*
|
||||
* loadBundles('bundleA')
|
||||
*/
|
||||
module.exports = function systemImportTransform(babel) {
|
||||
return new babel.Transformer('system-import', {
|
||||
CallExpression: function(node, parent, scope, state) {
|
||||
if (!isAppropriateSystemImportCall(node, parent)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
var bundlesLayout = state.opts.extra.bundlesLayout;
|
||||
var bundleID = bundlesLayout.getBundleIDForModule(
|
||||
node.arguments[0].value
|
||||
);
|
||||
|
||||
var bundles = bundleID.split('.');
|
||||
bundles.splice(0, 1);
|
||||
bundles = bundles.map(function(id) {
|
||||
return t.literal('bundle.' + id);
|
||||
});
|
||||
|
||||
return t.callExpression(
|
||||
t.identifier('loadBundles'),
|
||||
[t.arrayExpression(bundles)]
|
||||
);
|
||||
},
|
||||
|
||||
metadata: {
|
||||
group: 'fb'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function isAppropriateSystemImportCall(node) {
|
||||
return (
|
||||
node.callee.type === 'MemberExpression' &&
|
||||
node.callee.object.name === 'System' &&
|
||||
node.callee.property.name === 'import' &&
|
||||
node.arguments.length === 1 &&
|
||||
node.arguments[0].type === 'Literal'
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue