From b87ba87e476f46d87e319b6b7429930406364df7 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 4 Mar 2015 21:06:49 -0800 Subject: [PATCH] Updates from Wed Mar 4 - [react-packager] Start converting options to query params | Amjad Masad - [ReactNative] Replace js long constants with strings | Tadeu Zagallo - React Native: Remove Unnecessary `document.body` Shim | Tim Yung - [ReactNative] Use spread operator and .propTypes for ScrollView/ListView | Christopher Chedeau --- Examples/Movies/SearchScreen.js | 2 +- .../UIExplorer/ActivityIndicatorExample.js | 12 +- Examples/UIExplorer/PointerEventsExample.js | 28 ++-- Examples/UIExplorer/TextInputExample.js | 18 +-- .../ActivityIndicatorIOS.ios.js | 21 +-- Libraries/Components/ListView/ListView.js | 148 +++++++++--------- .../Components/ScrollView/ScrollView.ios.js | 15 +- .../Components/TextInput/TextInput.ios.js | 66 ++++---- Libraries/Components/View/View.js | 15 +- .../InitializeJavaScriptAppEngine.js | 9 +- ReactKit/Base/RCTConvert.h | 1 - ReactKit/Base/RCTConvert.m | 7 +- ReactKit/Modules/RCTUIManager.m | 22 +-- .../src/Server/__tests__/Server-test.js | 25 ++- packager/react-packager/src/Server/index.js | 44 ++++-- 15 files changed, 212 insertions(+), 221 deletions(-) diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index ea58deefba..3c451fd67b 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -282,7 +282,7 @@ var SearchBar = React.createClass({ return ( ); } @@ -98,7 +98,7 @@ exports.examples = [ ); } @@ -109,19 +109,19 @@ exports.examples = [ return ( diff --git a/Examples/UIExplorer/PointerEventsExample.js b/Examples/UIExplorer/PointerEventsExample.js index 0bc6c2b13d..10600d54dd 100644 --- a/Examples/UIExplorer/PointerEventsExample.js +++ b/Examples/UIExplorer/PointerEventsExample.js @@ -61,7 +61,7 @@ var NoneExample = React.createClass({ A: unspecified this.props.onLog('B none touched')} style={[styles.box, styles.boxPassedThrough]}> @@ -87,7 +87,7 @@ var NoneExample = React.createClass({ var DemoText = React.createClass({ render: function() { return ( - + {this.props.children} @@ -107,11 +107,11 @@ var BoxNoneExample = React.createClass({ A: unspecified this.props.onLog('B boxNone touched')} + pointerEvents="box-none" + onTouchStart={() => this.props.onLog('B box-none touched')} style={[styles.box, styles.boxPassedThrough]}> - B: boxNone + B: box-none this.props.onLog('C unspecified touched')} @@ -121,7 +121,7 @@ var BoxNoneExample = React.createClass({ this.props.onLog('C explicitly unspecified touched')} style={[styles.box]}> @@ -144,11 +144,11 @@ var BoxOnlyExample = React.createClass({ A: unspecified this.props.onLog('B boxOnly touched')} + pointerEvents="box-only" + onTouchStart={() => this.props.onLog('B box-only touched')} style={styles.box}> - B: boxOnly + B: box-only this.props.onLog('C unspecified touched')} @@ -158,7 +158,7 @@ var BoxOnlyExample = React.createClass({ this.props.onLog('C explicitly unspecified touched')} style={[styles.box, styles.boxPassedThrough]}> @@ -179,13 +179,13 @@ var exampleClasses = [ }, { Component: BoxNoneExample, - title: '`boxNone`', - description: '`boxNone` causes touch events on the container to pass through and will only detect touch events on its child components.', + title: '`box-none`', + description: '`box-none` causes touch events on the container to pass through and will only detect touch events on its child components.', }, { Component: BoxOnlyExample, - title: '`boxOnly`', - description: '`boxOnly` causes touch events on the container\'s child components to pass through and will only detect touch events on the container itself.', + title: '`box-only`', + description: '`box-only` causes touch events on the container\'s child components to pass through and will only detect touch events on the container itself.', } ]; diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js index e10dbc0f1f..e1f28895be 100644 --- a/Examples/UIExplorer/TextInputExample.js +++ b/Examples/UIExplorer/TextInputExample.js @@ -45,7 +45,7 @@ var TextEventsExample = React.createClass({ return ( this.updateText('onFocus')} @@ -123,25 +123,25 @@ exports.examples = [ @@ -193,25 +193,25 @@ exports.examples = [ diff --git a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js index a281cf97aa..5bba898303 100644 --- a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js +++ b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js @@ -6,7 +6,7 @@ 'use strict'; var NativeMethodsMixin = require('NativeMethodsMixin'); -var NativeModulesDeprecated = require('NativeModulesDeprecated'); +var NativeModules = require('NativeModules'); var PropTypes = require('ReactPropTypes'); var React = require('React'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); @@ -37,12 +37,11 @@ var ActivityIndicatorIOS = React.createClass({ * The foreground color of the spinner (default is gray). */ color: PropTypes.string, - /** - * The size of the spinner, must be one of: - * - ActivityIndicatorIOS.size.large - * - ActivityIndicatorIOS.size.small (default) - */ - size: PropTypes.oneOf([SpinnerSize.large, SpinnerSize.small]), + + size: PropTypes.oneOf([ + 'small', // default + 'large', + ]), }, getDefaultProps: function() { @@ -53,15 +52,11 @@ var ActivityIndicatorIOS = React.createClass({ }; }, - statics: { - size: SpinnerSize, - }, - render: function() { var style = styles.sizeSmall; - var NativeConstants = NativeModulesDeprecated.RKUIManager.UIActivityIndicatorView.Constants; + var NativeConstants = NativeModules.RKUIManager.UIActivityIndicatorView.Constants; var activityIndicatorViewStyle = NativeConstants.StyleWhite; - if (this.props.size == SpinnerSize.large) { + if (this.props.size === 'large') { style = styles.sizeLarge; activityIndicatorViewStyle = NativeConstants.StyleWhiteLarge; } diff --git a/Libraries/Components/ListView/ListView.js b/Libraries/Components/ListView/ListView.js index 086112724e..aa2f82e282 100644 --- a/Libraries/Components/ListView/ListView.js +++ b/Libraries/Components/ListView/ListView.js @@ -88,80 +88,80 @@ var ListView = React.createClass({ * - renderRow(rowData, sectionID, rowID); * - renderSectionHeader(sectionData, sectionID); */ - propTypes: - merge( - ScrollView.PropTypes, { - dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, - /** - * (rowData, sectionID, rowID) => renderable - * Takes a data entry from the data source and its ids and should return - * a renderable component to be rendered as the row. By default the data - * is exactly what was put into the data source, but it's also possible to - * provide custom extractors. - */ - renderRow: PropTypes.func.isRequired, - /** - * How many rows to render on initial component mount. Use this to make - * it so that the first screen worth of data apears at one time instead of - * over the course of multiple frames. - */ - initialListSize: PropTypes.number, - /** - * Called when all rows have been rendered and the list has been scrolled - * to within onEndReachedThreshold of the bottom. The native scroll - * event is provided. - */ - onEndReached: PropTypes.func, - /** - * Threshold in pixels for onEndReached. - */ - onEndReachedThreshold: PropTypes.number, - /** - * Number of rows to render per event loop. - */ - pageSize: PropTypes.number, - /** - * () => renderable - * - * The header and footer are always rendered (if these props are provided) - * on every render pass. If they are expensive to re-render, wrap them - * in StaticContainer or other mechanism as appropriate. Footer is always - * at the bottom of the list, and header at the top, on every render pass. - */ - renderFooter: PropTypes.func, - renderHeader: PropTypes.func, - /** - * (sectionData, sectionID) => renderable - * - * If provided, a sticky header is rendered for this section. The sticky - * behavior means that it will scroll with the content at the top of the - * section until it reaches the top of the screen, at which point it will - * stick to the top until it is pushed off the screen by the next section - * header. - */ - renderSectionHeader: PropTypes.func, - /** - * How early to start rendering rows before they come on screen, in - * pixels. - */ - scrollRenderAheadDistance: React.PropTypes.number, - /** - * (visibleRows, changedRows) => void - * - * Called when the set of visible rows changes. `visibleRows` maps - * { sectionID: { rowID: true }} for all the visible rows, and - * `changedRows` maps { sectionID: { rowID: true | false }} for the rows - * that have changed their visibility, with true indicating visible, and - * false indicating the view has moved out of view. - */ - onChangeVisibleRows: React.PropTypes.func, - /** - * An experimental performance optimization for improving scroll perf of - * large lists, used in conjunction with overflow: 'hidden' on the row - * containers. Use at your own risk. - */ - removeClippedSubviews: React.PropTypes.bool, - }), + propTypes: { + ...ScrollView.propTypes, + + dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, + /** + * (rowData, sectionID, rowID) => renderable + * Takes a data entry from the data source and its ids and should return + * a renderable component to be rendered as the row. By default the data + * is exactly what was put into the data source, but it's also possible to + * provide custom extractors. + */ + renderRow: PropTypes.func.isRequired, + /** + * How many rows to render on initial component mount. Use this to make + * it so that the first screen worth of data apears at one time instead of + * over the course of multiple frames. + */ + initialListSize: PropTypes.number, + /** + * Called when all rows have been rendered and the list has been scrolled + * to within onEndReachedThreshold of the bottom. The native scroll + * event is provided. + */ + onEndReached: PropTypes.func, + /** + * Threshold in pixels for onEndReached. + */ + onEndReachedThreshold: PropTypes.number, + /** + * Number of rows to render per event loop. + */ + pageSize: PropTypes.number, + /** + * () => renderable + * + * The header and footer are always rendered (if these props are provided) + * on every render pass. If they are expensive to re-render, wrap them + * in StaticContainer or other mechanism as appropriate. Footer is always + * at the bottom of the list, and header at the top, on every render pass. + */ + renderFooter: PropTypes.func, + renderHeader: PropTypes.func, + /** + * (sectionData, sectionID) => renderable + * + * If provided, a sticky header is rendered for this section. The sticky + * behavior means that it will scroll with the content at the top of the + * section until it reaches the top of the screen, at which point it will + * stick to the top until it is pushed off the screen by the next section + * header. + */ + renderSectionHeader: PropTypes.func, + /** + * How early to start rendering rows before they come on screen, in + * pixels. + */ + scrollRenderAheadDistance: React.PropTypes.number, + /** + * (visibleRows, changedRows) => void + * + * Called when the set of visible rows changes. `visibleRows` maps + * { sectionID: { rowID: true }} for all the visible rows, and + * `changedRows` maps { sectionID: { rowID: true | false }} for the rows + * that have changed their visibility, with true indicating visible, and + * false indicating the view has moved out of view. + */ + onChangeVisibleRows: React.PropTypes.func, + /** + * An experimental performance optimization for improving scroll perf of + * large lists, used in conjunction with overflow: 'hidden' on the row + * containers. Use at your own risk. + */ + removeClippedSubviews: React.PropTypes.bool, + }, /** * Exports some data, e.g. for perf investigations or analytics. diff --git a/Libraries/Components/ScrollView/ScrollView.ios.js b/Libraries/Components/ScrollView/ScrollView.ios.js index 317b986864..08202f4459 100644 --- a/Libraries/Components/ScrollView/ScrollView.ios.js +++ b/Libraries/Components/ScrollView/ScrollView.ios.js @@ -45,9 +45,10 @@ var keyboardDismissModeConstants = { * view from becoming the responder. */ - var RKScrollViewPropTypes = merge( - ScrollViewPropTypes, - { +var ScrollView = React.createClass({ + propTypes: { + ...ScrollViewPropTypes, + /** * When true, the scroll view bounces horizontally when it reaches the end * even if the content is smaller than the scroll view itself. The default @@ -153,16 +154,8 @@ var keyboardDismissModeConstants = { * The current scale of the scroll view content. The default value is 1.0. */ zoomScale: nativePropType(PropTypes.number), - } -); - -var ScrollView = React.createClass({ - statics: { - PropTypes: RKScrollViewPropTypes, }, - propTypes: RKScrollViewPropTypes, - mixins: [ScrollResponder.Mixin], getInitialState: function() { diff --git a/Libraries/Components/TextInput/TextInput.ios.js b/Libraries/Components/TextInput/TextInput.ios.js index fef9c16f01..aadd6c94a1 100644 --- a/Libraries/Components/TextInput/TextInput.ios.js +++ b/Libraries/Components/TextInput/TextInput.ios.js @@ -8,7 +8,7 @@ var DocumentSelectionState = require('DocumentSelectionState'); var EventEmitter = require('EventEmitter'); var NativeMethodsMixin = require('NativeMethodsMixin'); -var NativeModulesDeprecated = require('NativeModulesDeprecated'); +var RKUIManager = require('NativeModules').RKUIManager; var PropTypes = require('ReactPropTypes'); var React = require('React'); var ReactChildren = require('ReactChildren'); @@ -59,28 +59,8 @@ var merge = require('merge'); * More example code in `TextInputExample.js`. */ -var nativeConstants = NativeModulesDeprecated.RKUIManager.UIText.AutocapitalizationType; - -var autoCapitalizeMode = { - none: nativeConstants.None, - sentences: nativeConstants.Sentences, - words: nativeConstants.Words, - characters: nativeConstants.AllCharacters -}; - -var clearButtonModeConstants = NativeModulesDeprecated.RKUIManager.UITextField.clearButtonMode; - -var clearButtonModeTypes = { - never: clearButtonModeConstants.Never, - whileEditing: clearButtonModeConstants.WhileEditing, - unlessEditing: clearButtonModeConstants.UnlessEditing, - always: clearButtonModeConstants.Always, -}; - -var keyboardType = { - default: 'default', - numeric: 'numeric', -}; +var autoCapitalizeConsts = RKUIManager.UIText.AutocapitalizationType; +var clearButtonModeConsts = RKUIManager.UITextField.clearButtonMode; var RKTextViewAttributes = merge(ReactIOSViewAttributes.UIView, { autoCorrect: true, @@ -113,12 +93,6 @@ var notMultiline = { }; var TextInput = React.createClass({ - statics: { - autoCapitalizeMode: autoCapitalizeMode, - clearButtonModeTypes: clearButtonModeTypes, - keyboardType: keyboardType, - }, - propTypes: { /** * Can tell TextInput to automatically capitalize certain characters. @@ -127,11 +101,13 @@ var TextInput = React.createClass({ * - words: first letter of each word * - sentences: first letter of each sentence (default) * - none: don't auto capitalize anything - * - * example: - * autoCapitalize={TextInput.autoCapitalizeMode.words} */ - autoCapitalize: PropTypes.oneOf(getObjectValues(autoCapitalizeMode)), + autoCapitalize: PropTypes.oneOf([ + 'none', + 'sentences', + 'words', + 'characters', + ]), /** * If false, disables auto-correct. Default value is true. */ @@ -145,9 +121,12 @@ var TextInput = React.createClass({ */ editable: PropTypes.bool, /** - * Determines which keyboard to open, e.g.`TextInput.keyboardType.numeric`. + * Determines which keyboard to open, e.g.`numeric`. */ - keyboardType: PropTypes.oneOf(getObjectValues(keyboardType)), + keyboardType: PropTypes.oneOf([ + 'default', + 'numeric', + ]), /** * If true, the text input can be multiple lines. Default value is false. */ @@ -202,7 +181,12 @@ var TextInput = React.createClass({ /** * When the clear button should appear on the right side of the text view */ - clearButtonMode: PropTypes.oneOf(getObjectValues(clearButtonModeTypes)), + clearButtonMode: PropTypes.oneOf([ + 'never', + 'while-editing', + 'unless-editing', + 'always', + ]), style: Text.stylePropType, }, @@ -307,6 +291,9 @@ var TextInput = React.createClass({ render: function() { var textContainer; + var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize]; + var clearButtonMode = clearButtonModeConsts[this.props.clearButtonMode]; + if (!this.props.multiline) { for (var propKey in onlyMultiline) { if (this.props[propKey]) { @@ -329,9 +316,9 @@ var TextInput = React.createClass({ onSelectionChangeShouldSetResponder={() => true} placeholder={this.props.placeholder} text={this.state.bufferedValue} - autoCapitalize={this.props.autoCapitalize} + autoCapitalize={autoCapitalize} autoCorrect={this.props.autoCorrect} - clearButtonMode={this.props.clearButtonMode} + clearButtonMode={clearButtonMode} />; } else { for (var propKey in notMultiline) { @@ -372,8 +359,9 @@ var TextInput = React.createClass({ placeholder={this.props.placeholder} placeholderTextColor={this.props.placeholderTextColor} text={this.state.bufferedValue} - autoCapitalize={this.props.autoCapitalize} + autoCapitalize={autoCapitalize} autoCorrect={this.props.autoCorrect} + clearButtonMode={clearButtonMode} />; } diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 643eef68a3..060125ea79 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -6,7 +6,6 @@ 'use strict'; var NativeMethodsMixin = require('NativeMethodsMixin'); -var NativeModules = require('NativeModules'); var PropTypes = require('ReactPropTypes'); var React = require('React'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); @@ -45,15 +44,12 @@ var ViewStylePropTypes = require('ViewStylePropTypes'); * examples. */ -var StyleConstants = NativeModules.RKUIManager.StyleConstants; - var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); var stylePropType = StyleSheetPropType(ViewStylePropTypes); var View = React.createClass({ statics: { - pointerEvents: StyleConstants.PointerEventsValues, stylePropType, }, @@ -96,7 +92,7 @@ var View = React.createClass({ /** * In the absence of `auto` property, `none` is much like `CSS`'s `none` - * value. `boxNone` is as if you had applied the `CSS` class: + * value. `box-none` is as if you had applied the `CSS` class: * * .cantTouchThis * { * pointer-events: auto; @@ -112,10 +108,10 @@ var View = React.createClass({ * implementation detail of the platform. */ pointerEvents: PropTypes.oneOf([ - StyleConstants.PointerEventsValues.boxNone, - StyleConstants.PointerEventsValues.none, - StyleConstants.PointerEventsValues.boxOnly, - StyleConstants.PointerEventsValues.unspecified + 'box-none', + 'none', + 'box-only', + 'auto', ]), /** @@ -151,7 +147,6 @@ if (__DEV__) { ViewToExport = View; } -ViewToExport.pointerEvents = View.pointerEvents; ViewToExport.stylePropType = stylePropType; module.exports = ViewToExport; diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index d3d8637858..3c507129b5 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -51,13 +51,10 @@ function setupDocumentShim() { throw getInvalidGlobalUseError('Image'); } }; - if (!GLOBAL.document) { - // This shouldn't be needed but scroller library fails without it. If - // we fixed the scroller, we wouldn't need this. - GLOBAL.document = {body: {}}; - } // Force `ExecutionEnvironment.canUseDOM` to be false. - GLOBAL.document.createElement = null; + if (GLOBAL.document) { + GLOBAL.document.createElement = null; + } } function handleErrorWithRedBox(e) { diff --git a/ReactKit/Base/RCTConvert.h b/ReactKit/Base/RCTConvert.h index 51a6b76cea..25b8cecc24 100644 --- a/ReactKit/Base/RCTConvert.h +++ b/ReactKit/Base/RCTConvert.h @@ -33,7 +33,6 @@ + (NSTextAlignment)NSTextAlignment:(id)json; + (NSWritingDirection)NSWritingDirection:(id)json; -+ (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json; + (UIKeyboardType)UIKeyboardType:(id)json; + (CGFloat)CGFloat:(id)json; diff --git a/ReactKit/Base/RCTConvert.m b/ReactKit/Base/RCTConvert.m index 4f3e8b05c6..39b0c6874c 100644 --- a/ReactKit/Base/RCTConvert.m +++ b/ReactKit/Base/RCTConvert.m @@ -161,7 +161,7 @@ RCT_ENUM_CONVERTER(UITextAutocapitalizationType, (@{ @"none": @(UITextAutocapitalizationTypeNone), @"words": @(UITextAutocapitalizationTypeWords), @"sentences": @(UITextAutocapitalizationTypeSentences), - @"all": @(UITextAutocapitalizationTypeAllCharacters) + @"characters": @(UITextAutocapitalizationTypeAllCharacters) }), UITextAutocapitalizationTypeSentences, integerValue) RCT_ENUM_CONVERTER(UIKeyboardType, (@{ @@ -658,8 +658,9 @@ RCT_ENUM_CONVERTER(css_wrap_type_t, (@{ RCT_ENUM_CONVERTER(RCTPointerEvents, (@{ @"none": @(RCTPointerEventsNone), - @"boxonly": @(RCTPointerEventsBoxOnly), - @"boxnone": @(RCTPointerEventsBoxNone) + @"box-only": @(RCTPointerEventsBoxOnly), + @"box-none": @(RCTPointerEventsBoxNone), + @"auto": @(RCTPointerEventsUnspecified) }), RCTPointerEventsUnspecified, integerValue) RCT_ENUM_CONVERTER(RCTAnimationType, (@{ diff --git a/ReactKit/Modules/RCTUIManager.m b/ReactKit/Modules/RCTUIManager.m index 9510139696..5a65669cd1 100644 --- a/ReactKit/Modules/RCTUIManager.m +++ b/ReactKit/Modules/RCTUIManager.m @@ -1282,25 +1282,25 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView @"StyleConstants": @{ @"PointerEventsValues": @{ @"none": @(RCTPointerEventsNone), - @"boxNone": @(RCTPointerEventsBoxNone), - @"boxOnly": @(RCTPointerEventsBoxOnly), - @"unspecified": @(RCTPointerEventsUnspecified), + @"box-none": @(RCTPointerEventsBoxNone), + @"box-only": @(RCTPointerEventsBoxOnly), + @"auto": @(RCTPointerEventsUnspecified), }, }, @"UIText": @{ @"AutocapitalizationType": @{ - @"AllCharacters": @(UITextAutocapitalizationTypeAllCharacters), - @"Sentences": @(UITextAutocapitalizationTypeSentences), - @"Words": @(UITextAutocapitalizationTypeWords), - @"None": @(UITextAutocapitalizationTypeNone), + @"characters": @(UITextAutocapitalizationTypeAllCharacters), + @"sentences": @(UITextAutocapitalizationTypeSentences), + @"words": @(UITextAutocapitalizationTypeWords), + @"none": @(UITextAutocapitalizationTypeNone), }, }, @"UITextField": @{ @"clearButtonMode": @{ - @"Never": @(UITextFieldViewModeNever), - @"WhileEditing": @(UITextFieldViewModeWhileEditing), - @"UnlessEditing": @(UITextFieldViewModeUnlessEditing), - @"Always": @(UITextFieldViewModeAlways), + @"never": @(UITextFieldViewModeNever), + @"while-editing": @(UITextFieldViewModeWhileEditing), + @"unless-editing": @(UITextFieldViewModeUnlessEditing), + @"always": @(UITextFieldViewModeAlways), }, }, @"UIView": @{ diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index 5a5d62b02a..f55df7c28f 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -73,7 +73,16 @@ describe('processRequest', function() { pit('returns JS bundle source on request of *.bundle',function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle' + 'mybundle.bundle?runModule=true' + ).then(function(response) { + expect(response).toEqual('this is the source'); + }); + }); + + pit('returns JS bundle source on request of *.bundle (compat)',function() { + return makeRequest( + requestHandler, + 'mybundle.runModule.bundle' ).then(function(response) { expect(response).toEqual('this is the source'); }); @@ -82,7 +91,7 @@ describe('processRequest', function() { pit('returns sourcemap on request of *.map', function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle.map' + 'mybundle.map?runModule=true' ).then(function(response) { expect(response).toEqual('"this is the source map"'); }); @@ -91,8 +100,8 @@ describe('processRequest', function() { pit('watches all files in projectRoot', function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle' - ).then(function(response) { + 'mybundle.bundle?runModule=true' + ).then(function() { expect(watcherFunc.mock.calls[0][0]).toEqual('all'); expect(watcherFunc.mock.calls[0][1]).not.toBe(null); }); @@ -114,8 +123,8 @@ describe('processRequest', function() { pit('invalides files in package when file is updated', function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle' - ).then(function(response) { + 'mybundle.bundle?runModule=true' + ).then(function() { var onFileChange = watcherFunc.mock.calls[0][1]; onFileChange('all','path/file.js', options.projectRoots[0]); expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js'); @@ -150,7 +159,7 @@ describe('processRequest', function() { requestHandler = server.processRequest.bind(server); - return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + return makeRequest(requestHandler, 'mybundle.bundle?runModule=true') .then(function(response) { expect(response).toEqual('this is the first source'); expect(packageFunc.mock.calls.length).toBe(1); @@ -159,7 +168,7 @@ describe('processRequest', function() { }) .then(function() { expect(packageFunc.mock.calls.length).toBe(2); - return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + return makeRequest(requestHandler, 'mybundle.bundle?runModule=true') .then(function(response) { expect(response).toEqual('this is the rebuilt source'); }); diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 83592fea9a..406357217f 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -77,7 +77,7 @@ Server.prototype._rebuildPackages = function() { var buildPackage = this._buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { - var options = getOptionsFromPath(url.parse(key).pathname); + var options = getOptionsFromUrl(key); packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({inlineSourceMap: dev}); @@ -102,7 +102,7 @@ Server.prototype._buildPackage = function(options) { }; Server.prototype.buildPackageFromUrl = function(reqUrl) { - var options = getOptionsFromPath(url.parse(reqUrl).pathname); + var options = getOptionsFromUrl(reqUrl); return this._buildPackage(options); }; @@ -145,21 +145,26 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { }; Server.prototype.processRequest = function(req, res, next) { + var urlObj = url.parse(req.url, true); + var pathname = urlObj.pathname; + var requestType; - if (req.url.match(/\.bundle$/)) { + if (pathname.match(/\.bundle$/)) { requestType = 'bundle'; - } else if (req.url.match(/\.map$/)) { + } else if (pathname.match(/\.map$/)) { requestType = 'map'; - } else if (req.url.match(/^\/debug/)) { + } else if (pathname.match(/^\/debug/)) { this._processDebugRequest(req.url, res); return; } else { - return next(); + next(); + return; } var startReqEventId = Activity.startEvent('request:' + req.url); - var options = getOptionsFromPath(url.parse(req.url).pathname); + var options = getOptionsFromUrl(req.url); var building = this._packages[req.url] || this._buildPackage(options); + this._packages[req.url] = building; var dev = this._dev; building.then( @@ -178,16 +183,25 @@ Server.prototype.processRequest = function(req, res, next) { ).done(); }; -function getOptionsFromPath(pathname) { - var parts = pathname.split('.'); - // Remove the leading slash. - var main = parts[0].slice(1) + '.js'; +function getOptionsFromUrl(reqUrl) { + // `true` to parse the query param as an object. + var urlObj = url.parse(reqUrl, true); + + var match = urlObj.pathname.match(/^\/?([^\.]+)\..*(bundle|map)$/); + if (!(match && match[1])) { + throw new Error('Invalid url format, expected "/path/to/file.bundle"'); + } + var main = match[1] + '.js'; + return { - runModule: parts.slice(1).some(function(part) { - return part === 'runModule'; - }), + sourceMapUrl: urlObj.pathname.replace(/\.bundle$/, '.map'), main: main, - sourceMapUrl: parts.slice(0, -1).join('.') + '.map' + runModule: urlObj.query.runModule === 'true' || + urlObj.query.runModule === '1' || + // Backwards compatibility. + urlObj.pathname.split('.').some(function(part) { + return part === 'runModule'; + }), }; }