Compare commits

...
Author SHA1 Message Date
Hector Ramos eb7d3bae26 [0.46.0-rc.2] Bump version numbers 2017-06-09 13:14:16 -07:00
Adam Miskiewicz 43c06e263b Fix broken default getProjectRoots
Summary:
In <= 0.44, the default implementation of getProjectRoots() came from `local-cli/core/default.config.js`. With changes happening in the CLI and the packager over the course of the last two months, various pieces of this logic (specifically `local-cli/utils/Config.js`) were rewritten, and though default.config.js was still being imported and used in `local-cli/core/index.js`, the default `getProjectRoots()` was being overriden by the defaults specified in `local-cli/utils/Config.js`.

This PR moves the logic from default.config.js into Config.js and index.js, as appropriate. Specifically:

- The `getProjectCommands()`, `getProjectConfig()`, and `getDependencyConfig()` methods, which have traditionally not been part of the rn-cli.config.js spec, are now defined in `local-cli/core/index.js`.
- The `getProjectRoots()` method, which contained logic for properly resolving the _actual_ project root as well as resolving symlinks within that root, has been moved to `local-cli/utils/Config.js`, to match the fact that other default  rn-cli.config.js definitions live there.
Closes https://github.com/facebook/react-native/pull/14412

Differential Revision: D5216887

Pulled By: hramos

fbshipit-source-id: 7a3840ecf0ad8ea3f6d7bbd3d54e4f02950c6a32
2017-06-09 13:13:21 -07:00
Hector Ramos 522f4a5c6a Restore proper ordering of docs in the sidebar
Summary:
Quickfix: The ordering here was lost when a previous PR was merged.
Closes https://github.com/facebook/react-native/pull/14378

Differential Revision: D5203833

Pulled By: hramos

fbshipit-source-id: 9282a5a5c9aa81ae5e1f5fe6692192ac1db57c08
2017-06-07 15:34:59 -07:00
Hector Ramos c62b9f645e New Handling Touches Tutorial
Summary:
Finally, a place where `Button` is properly introduced. This is based on the old Handling Touches guide, which has been simplified (with some content moved over to the scroll views tutorial).

I've also updated the ordering of the guides into something that makes more sense to someone just getting started with React Native.
Closes https://github.com/facebook/react-native/pull/14371

Differential Revision: D5201127

Pulled By: hramos

fbshipit-source-id: 819192e2db9febb8a315f51693dae557752b6002
2017-06-07 12:18:38 -07:00
Hector Ramos a3ea9377c1 Add Components and APIs Overview Guide
Summary:
Essential components such as View, Text, and Button are easily overlooked amongst the long list of components and APIs due to a lack of categorization. The basic components are already introduced as part of the tutorial, but they may be missed by people who prefer scanning over the docs.

Built website on localhost. Preview of the website as seem on Chrome on macOS:

![screencapture-localhost-8079-react-native-docs-components-and-apis-html-1496357491473](https://cloud.githubusercontent.com/assets/165856/26704237/902d0986-46e2-11e7-9624-17db692ae11e.png)

Preview as seen on an iPhone Plus (via Chrome console):
![screen shot 2017-06-01 at 3 57 00 pm](https://cloud.githubusercontent.com/assets/165856/26704309/05c8ff7e-46e3-11e7-99ff-b7402f5265d4.png)
Closes https://github.com/facebook/react-native/pull/14265

Differential Revision: D5192597

Pulled By: hramos

fbshipit-source-id: f281a14230468458f3da674a82df263fb9f064f4
2017-06-07 12:18:31 -07:00
James Ide a98a51325e [0.46.0-rc.1] Bump version numbers 2017-06-07 12:11:26 -07:00
Christoph Pojer 335d56ec55 Remove react-native/packager folder.
Summary: This folder is not necessary any longer. All the code now lives in https://github.com/facebook/metro-bundler

Reviewed By: davidaurelio, jeanlauliac

Differential Revision: D5199196

fbshipit-source-id: 35bf0f10a9163f53426db9a76f8f853dceb69167
2017-06-07 12:10:08 -07:00
Mike Grabowski ee7e4ab1a3 [0.46.0-rc.0] Bump version numbers 2017-06-07 13:37:25 +02:00
233 changed files with 708 additions and 31198 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=1000.0.0-master
VERSION_NAME=0.46.0-rc.2
GROUP=com.facebook.react
POM_NAME=ReactNative
+1 -1
View File
@@ -5,7 +5,7 @@ layout: docs
category: Guides
permalink: docs/accessibility.html
next: timers
previous: debugging
previous: animations
---
## Native App Accessibility (iOS and Android)
+2 -2
View File
@@ -153,11 +153,11 @@ Start by following the `Point Gradle to your Android SDK` section of this page.
./gradlew ReactAndroid:installArchives
```
This will package everything that would typically be included in the `android` directory of your `node_modules/react-native/` installation in the root directory of your React Native checkout.
This will package everything that would typically be included in the `android` directory of your `node_modules/react-native/` installation in the root directory of your React Native checkout.
## Testing
If you made changes to React Native and submit a pull request, all tests will run on your pull request automatically. To run the tests locally, see [Testing](docs/testing.html).
If you made changes to React Native and submit a pull request, all tests will run on your pull request automatically. To run the tests locally, see [Running Tests](docs/testing.html).
## Troubleshooting
+2 -2
View File
@@ -4,8 +4,8 @@ title: Animations
layout: docs
category: Guides
permalink: docs/animations.html
next: navigation
previous: handling-touches
next: accessibility
previous: images
---
Animations are very important to create a great user experience.
+2 -4
View File
@@ -2,7 +2,7 @@
id: building-for-apple-tv
title: Building For Apple TV
layout: docs
category: Guides (Apple TV)
category: Guides (iOS)
permalink: docs/building-for-apple-tv.html
banner: ejected
next: native-modules-android
@@ -84,12 +84,10 @@ class Game2048 extends React.Component {
```
- *TV remote animations*: `RCTTVView` native code implements Apple-recommended parallax animations to help guide the eye as the user navigates through views. The animations can be disabled or adjusted with new optional view properties.
- *TV remote animations*: `RCTTVView` native code implements Apple-recommended parallax animations to help guide the eye as the user navigates through views. The animations can be disabled or adjusted with new optional view properties.
- *Back navigation with the TV remote menu button*: The `BackHandler` component, originally written to support the Android back button, now also supports back navigation on the Apple TV using the menu button on the TV remote.
- *Known issues*:
- [ListView scrolling](https://github.com/facebook/react-native/issues/12793). The issue can be easily worked around by setting `removeClippedSubviews` to false in ListView and similar components. For more discussion of this issue, see [this PR](https://github.com/facebook/react-native/pull/12944).
+3 -3
View File
@@ -1,11 +1,11 @@
---
id: colors
title: Colors
title: Color Reference
layout: docs
category: Guides
permalink: docs/colors.html
next: platform-specific-code
previous: images
next: integration-with-existing-apps
previous: direct-manipulation
---
Components in React Native are [styled using JavaScript](docs/style.html). Color properties usually match how [CSS works on the web](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value).
+235
View File
@@ -0,0 +1,235 @@
---
id: components
title: Components and APIs
layout: docs
category: Guides
permalink: docs/components-and-apis.html
next: platform-specific-code
previous: more-resources
---
React Native provides a number of built-in components. You will find a full list of components and APIs on the sidebar to the left. If you're not sure where to get started, take a look at the following categories:
- [Basic Components](docs/components-and-apis.html#basic-components)
- [User Interface](docs/components-and-apis.html#user-interface)
- [Lists Views](docs/components-and-apis.html#lists-views)
- [iOS-specific](docs/components-and-apis.html#ios-components-and-apis)
- [Android-specific](docs/components-and-apis.html#android-components-and-apis)
- [Others](docs/components-and-apis.html#others)
You're not limited to the components and APIs bundled with React Native. React Native is a community of thousands of developers. If you're looking for a library that does something specific, search the npm registry for packages mentioning [react-native](https://www.npmjs.com/search?q=react-native&page=1&ranking=optimal), or check out [Awesome React Native](http://www.awesome-react-native.com/) for a curated list.
## Basic Components
Most apps will end up using one of these basic components. You'll want to get yourself familiarized with all of these if you're new to React Native.
<div class="component-grid component-grid-border">
<div class="component">
<h3><a href="docs/view.html">View</a></h3>
<p>The most fundamental component for building a UI.</p>
</div>
<div class="component">
<h3><a href="docs/text.html">Text</a></h3>
<p>A component for displaying text.</p>
</div>
<div class="component">
<h3><a href="docs/image.html">Image</a></h3>
<p>A component for displaying images.</p>
</div>
<div class="component">
<h3><a href="docs/textinput.html">TextInput</a></h3>
<p>A component for inputting text into the app via a keyboard.</p>
</div>
<div class="component">
<h3><a href="docs/scrollview.html">ScrollView</a></h3>
<p>Provides a scrolling container that can host multiple components and views.</p>
</div>
<div class="component">
<h3><a href="docs/button.html">Button</a></h3>
<p>A basic button component for handling touches that should render nicely on any platform.</p>
</div>
</div>
## User Interface
Render common user interface controls on any platform using the following components. For platform specific components, keep reading.
<div class="component-grid component-grid-border">
<div class="component">
<h3><a href="docs/picker.html">Picker</a></h3>
<p>Renders the native picker component on iOS and Android.</p>
</div>
<div class="component">
<h3><a href="docs/slider.html">Slider</a></h3>
<p>A component used to select a single value from a range of values.</p>
</div>
<div class="component">
<h3><a href="docs/switch.html">Switch</a></h3>
<p>Renders a boolean input.</p>
</div>
</div>
## List Views
Unlike the more generic `ScrollView`, the following list view components only render elements that are currently showing on the screen. This makes them a great choice for displaying long lists of data.
<div class="component-grid component-grid-border">
<div class="component">
<h3><a href="docs/flatlist.html">FlatList</a></h3>
<p>A component for rendering performant scrollable lists.</p>
</div>
<div class="component">
<h3><a href="docs/sectionlist.html">SectionList</a></h3>
<p>Like <code>FlatList</code>, but for sectioned lists.</p>
</div>
</div>
## iOS Components and APIs
Many of the following components provide wrappers for commonly used UIKit classes.
<div class="component-grid component-grid-border">
<div class="component">
<h3><a href="docs/actionsheetios.html">ActionSheetIOS</a></h3>
<p>API to display an iOS action sheet or share sheet.</p>
</div>
<div class="component">
<h3><a href="docs/adsupportios.html">AdSupportIOS</a></h3>
<p>API to access the "advertising identifier" on iOS.</p>
</div>
<div class="component">
<h3><a href="docs/alertios.html">AlertIOS</a></h3>
<p>Create an iOS alert dialog with a message or create a prompt for user input.</p>
</div>
<div class="component">
<h3><a href="docs/datepickerios.html">DatePickerIOS</a></h3>
<p>Renders a date/time picker (selector) on iOS.</p>
</div>
<div class="component">
<h3><a href="docs/imagepickerios.html">ImagePickerIOS</a></h3>
<p>Renders a image picker on iOS.</p>
</div>
<div class="component">
<h3><a href="docs/navigatorios.html">NavigatorIOS</a></h3>
<p>A wrapper around <code>UINavigationController</code>, enabling you to implement a navigation stack.</p>
</div>
<div class="component">
<h3><a href="docs/progressviewios.html">ProgressViewIOS</a></h3>
<p>Renders a <code>UIProgressView</a></code> on iOS.</p>
</div>
<div class="component">
<h3><a href="docs/pushnotificationios.html">PushNotificationIOS</a></h3>
<p>Handle push notifications for your app, including permission handling and icon badge number.</p>
</div>
<div class="component">
<h3><a href="docs/segmentedcontrolios.html">SegmentedControlIOS</a></h3>
<p>Renders a <code>UISegmentedControl</code> on iOS.</p>
</div>
<div class="component">
<h3><a href="docs/tabbarios.html">TabBarIOS</a></h3>
<p>Renders a <code>UITabViewController</code> on iOS. Use with <a href="docs/tabbarios-item.html">TabBarIOS.Item</a>.</p>
</div>
</div>
## Android Components and APIs
Many of the following components provide wrappers for commonly used Android classes.
<div class="component-grid component-grid-border">
<div class="component">
<h3><a href="docs/backhandler.html">BackHandler</a></h3>
<p>Detect hardware button presses for back navigation.</p>
</div>
<div class="component">
<h3><a href="docs/datepickerandroid.html">DatePickerAndroid</a></h3>
<p>Opens the standard Android date picker dialog.</p>
</div>
<div class="component">
<h3><a href="docs/drawerlayoutandroid.html">DrawerLayoutAndroid</a></h3>
<p>Renders a <code>DrawerLayout</code> on Android.</p>
</div>
<div class="component">
<h3><a href="docs/permissionsandroid.html">PermissionsAndroid</a></h3>
<p>Provides access to the permissions model introduced in Android M.</p>
</div>
<div class="component">
<h3><a href="docs/progressbarandroid.html">ProgressBarAndroid</a></h3>
<p>Renders a <code>ProgressBar</code> on Android.</p>
</div>
<div class="component">
<h3><a href="docs/timepickerandroid.html">TimePickerAndroid</a></h3>
<p>Opens the standard Android time picker dialog.</p>
</div>
<div class="component">
<h3><a href="docs/toastandroid.html">ToastAndroid</a></h3>
<p>Create an Android Toast alert.</p>
</div>
<div class="component">
<h3><a href="docs/toolbarandroid.html">ToolbarAndroid</a></h3>
<p>Renders a <code>Toolbar</code> on Android.</p>
</div>
<div class="component">
<h3><a href="docs/viewpagerandroid.html">ViewPagerAndroid</a></h3>
<p>Container that allows to flip left and right between child views.</p>
</div>
</div>
## Others
These components may come in handy for certain applications. For an exhaustive list of components and APIs, check out the sidebar to the left.
<div class="component-grid">
<div class="component">
<h3><a href="docs/activityindicator.html">ActivityIndicator</a></h3>
<p>Displays a circular loading indicator.</p>
</div>
<div class="component">
<h3><a href="docs/alert.html">Alert</a></h3>
<p>Launches an alert dialog with the specified title and message.</p>
</div>
<div class="component">
<h3><a href="docs/cameraroll.html">CameraRoll</a></h3>
<p>Provides access to the local camera roll / gallery.</p>
</div>
<div class="component">
<h3><a href="docs/clipboard.html">Clipboard</a></h3>
<p>Provides an interface for setting and getting content from the clipboard on both iOS and Android.</p>
</div>
<div class="component">
<h3><a href="docs/dimensions.html">Dimensions</a></h3>
<p>Provides an interface for getting device dimensions.</p>
</div>
<div class="component">
<h3><a href="docs/keyboardavoidingview.html">KeyboardAvoidingView</a></h3>
<p>Provides a view that moves out of the way of the virtual keyboard automatically.</p>
</div>
<div class="component">
<h3><a href="docs/linking.html">Linking</a></h3>
<p>Provides a general interface to interact with both incoming and outgoing app links.</p>
</div>
<div class="component">
<h3><a href="docs/modal.html">Modal</a></h3>
<p>Provides a simple way to present content above an enclosing view.</p>
</div>
<div class="component">
<h3><a href="docs/pixelratio.html">PixelRatio</a></h3>
<p>Provides access to the device pixel density.</p>
</div>
<div class="component">
<h3><a href="docs/refreshcontrol.html">RefreshControl</a></h3>
<p>This component is used inside a <code>ScrollView</code> to add pull to refresh functionality.</p>
</div>
<div class="component">
<h3><a href="docs/statusbar.html">StatusBar</a></h3>
<p>Component to control the app status bar.</p>
</div>
<div class="component">
<h3><a href="docs/stylesheet.html">StyleSheet</a></h3>
<p>Provides an abstraction layer similar to CSS stylesheets.</p>
</div>
<div class="component">
<h3><a href="docs/webview.html">WebView</a></h3>
<p>A component that renders web content in a native view.</p>
</div>
</div>
+2 -2
View File
@@ -4,8 +4,8 @@ title: Debugging
layout: docs
category: Guides
permalink: docs/debugging.html
next: accessibility
previous: platform-specific-code
next: performance
previous: timers
---
## Enabling Keyboard Shortcuts
+1 -2
View File
@@ -4,7 +4,7 @@ title: Direct Manipulation
layout: docs
category: Guides
permalink: docs/direct-manipulation.html
next: performance
next: colors
previous: javascript-environment
---
@@ -267,4 +267,3 @@ Requests focus for the given input or view. The exact behavior triggered will de
### blur()
Removes focus from an input or view. This is the opposite of `focus()`.
+1 -1
View File
@@ -4,7 +4,7 @@ title: Gesture Responder System
layout: docs
category: Guides
permalink: docs/gesture-responder-system.html
next: testing
next: javascript-environment
previous: performance
---
+1 -1
View File
@@ -4,7 +4,7 @@ title: Handling Text Input
layout: docs
category: The Basics
permalink: docs/handling-text-input.html
next: using-a-scrollview
next: handling-touches
previous: flexbox
---
+143 -27
View File
@@ -2,41 +2,97 @@
id: handling-touches
title: Handling Touches
layout: docs
category: Guides
category: The Basics
permalink: docs/handling-touches.html
next: animations
previous: more-resources
next: using-a-scrollview
previous: handling-text-input
---
Users interact with mobile apps mainly through touch. They can use a combination of gestures, such as tapping on a button, scrolling a list, or zooming on a map.
Users interact with mobile apps mainly through touch. They can use a combination of gestures, such as tapping on a button, scrolling a list, or zooming on a map. React Native provides components to handle all sorts of common gestures, as well as a comprehensive [gesture responder system](docs/gesture-responder-system.html) to allow for more advanced gesture recognition, but the one component you will most likely be interested in is the basic Button.
React Native provides components to handle common gestures, such as taps and swipes, as well as a comprehensive [gesture responder system](docs/gesture-responder-system.html) to allow for more advanced gesture recognition.
## Displaying a basic button
## Tappable Components
You can use "Touchable" components when you want to capture a tapping gesture. They take a function through the `onPress` props which will be called when the touch begins and ends within the bounds of the component.
Example:
[Button](docs/button.html) provides a basic button component that is rendered nicely on all platforms. The minimal example to display a button looks like this:
```javascript
class MyButton extends Component {
<Button
onPress={() => { Alert.alert('You tapped the button!')}}
title="Press Me"
/>
```
This will render a blue label on iOS, and a blue rounded rectangle with white text on Android. Pressing the button will call the "onPress" function, which in this case displays an alert popup. If you like, you can specify a "color" prop to change the color of your button.
![](img/Button.png)
Go ahead and play around with the `Button` component using the example below. You can select which platform your app is previewed in by clicking on the toggle in the bottom right, then click on "Tap to Play" to preview the app.
```SnackPlayer?name=Button%20Basics
import React, { Component } from 'react';
import { Alert, AppRegistry, Button, StyleSheet, View } from 'react-native';
export default class ButtonBasics extends Component {
_onPressButton() {
console.log("You tapped the button!");
Alert.alert('You tapped the button!')
}
render() {
return (
<TouchableHighlight onPress={this._onPressButton}>
<Text>Button</Text>
</TouchableHighlight>
<View style={styles.container}>
<View style={styles.buttonContainer}>
<Button
onPress={this._onPressButton}
title="Press Me"
/>
</View>
<View style={styles.buttonContainer}>
<Button
onPress={this._onPressButton}
title="Press Me"
color="#841584"
/>
</View>
<View style={styles.alternativeLayoutButtonContainer}>
<Button
onPress={this._onPressButton}
title="This looks great!"
/>
<Button
onPress={this._onPressButton}
title="OK!"
color="#841584"
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
buttonContainer: {
margin: 20
},
alternativeLayoutButtonContainer: {
margin: 20,
flexDirection: 'row',
justifyContent: 'space-between'
}
})
// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => ButtonBasics);
```
Tappable components should provide feedback that show the user what is handling their touch, and what will happen when they lift their finger. The user should also be able to cancel a tap by dragging their finger away.
Which component you use will depend on what kind of feedback you want to provide:
## Touchables
If the basic button doesn't look right for your app, you can build your own button using any of the "Touchable" components provided by React Native. The "Touchable" components provide the capability to capture tapping gestures, and can display feedback when a gesture is recognized. These components do not provide any default styling, however, so you will need to do a bit of work to get them looking nicely in your app.
Which "Touchable" component you use will depend on what kind of feedback you want to provide:
- Generally, you can use [**TouchableHighlight**](docs/touchablehighlight.html) anywhere you would use a button or link on web. The view's background will be darkened when the user presses down on the button.
@@ -46,22 +102,82 @@ Which component you use will depend on what kind of feedback you want to provide
- If you need to handle a tap gesture but you don't want any feedback to be displayed, use [**TouchableWithoutFeedback**](docs/touchablewithoutfeedback.html).
### Long presses
In some cases, you may want to detect when a user presses and holds a view for a set amount of time. These long presses can be handled by passing a function to the `onLongPress` props of any of the "Touchable" components.
In some cases, you may want to detect when a user presses and holds a view for a set amount of time. These long presses can be handled by passing a function to the `onLongPress` props of any of the touchable components listed above.
Let's see all of these in action:
## Scrolling lists and swiping views
```SnackPlayer?platform=android&name=Touchables
import React, { Component } from 'react';
import { Alert, AppRegistry, Platform, StyleSheet, Text, TouchableHighlight, TouchableOpacity, TouchableNativeFeedback, TouchableWithoutFeedback, View } from 'react-native';
A common pattern to many mobile apps is the scrollable list of items. Users interact with these using panning or swiping gestures. The [ScrollView](docs/using-a-scrollview.html) component displays a list of items that can be scrolled using these gestures.
export default class Touchables extends Component {
_onPressButton() {
Alert.alert('You tapped the button!')
}
ScrollViews can scroll vertically or horizontally, and can be configured to allow paging through views using swiping gestures by using the `pagingEnabled` props. Swiping horizontally between views can also be implemented on Android using the [ViewPagerAndroid](docs/viewpagerandroid.html) component.
_onLongPressButton() {
Alert.alert('You long-pressed the button!')
}
A [FlatList](docs/using-a-listview.html) is a special kind of ScrollView that is best suited for displaying long vertical lists of items. If you want to display section headers and footers, similar to `UITableView`s on iOS, then [SectionList](docs/using-a-listview.html) is the way to go.
### Pinch-to-zoom
render() {
return (
<View style={styles.container}>
<TouchableHighlight onPress={this._onPressButton} underlayColor="white">
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableHighlight</Text>
</View>
</TouchableHighlight>
<TouchableOpacity onPress={this._onPressButton}>
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableOpacity</Text>
</View>
</TouchableOpacity>
<TouchableNativeFeedback
onPress={this._onPressButton}
background={Platform.OS === 'android' ? TouchableNativeFeedback.SelectableBackground() : ''}>
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableNativeFeedback</Text>
</View>
</TouchableNativeFeedback>
<TouchableWithoutFeedback
onPress={this._onPressButton}
>
<View style={styles.button}>
<Text style={styles.buttonText}>TouchableWithoutFeedback</Text>
</View>
</TouchableWithoutFeedback>
<TouchableHighlight onPress={this._onPressButton} onLongPress={this._onLongPressButton} underlayColor="white">
<View style={styles.button}>
<Text style={styles.buttonText}>Touchable with Long Press</Text>
</View>
</TouchableHighlight>
</View>
);
}
}
A ScrollView with a single item can be used to allow the user to zoom content. Set up the `maximumZoomScale` and `minimumZoomScale` props and your user will be able to use pinch and expand gestures to zoom in and out.
const styles = StyleSheet.create({
container: {
paddingTop: 60,
alignItems: 'center'
},
button: {
marginBottom: 30,
width: 260,
alignItems: 'center',
backgroundColor: '#2196F3'
},
buttonText: {
padding: 20,
color: 'white'
}
})
## Handling additional gestures
// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => Touchables);
```
If you want to allow a user to drag a view around the screen, or you want to implement your own custom pan/drag gesture, take a look at the [PanResponder](docs/panresponder.html) API or the [gesture responder system docs](docs/gesture-responder-system.html).
## Scrolling lists, swiping pages, and pinch-to-zoom
Another gesture commonly used in mobile apps is the swipe or pan. This gesture allows the user to scroll through a list of items, or swipe through pages of content. In order to handle these and other gestures, we'll learn [how to use a ScrollView](docs/using-a-scrollview.html) next.
+3 -3
View File
@@ -4,7 +4,7 @@ title: Images
layout: docs
category: Guides
permalink: docs/images.html
next: colors
next: animations
previous: navigation
---
@@ -96,9 +96,9 @@ Many of the images you will display in your app will not be available at compile
```
### Network Requests for Images
If you would like to set such things as the HTTP-Verb, Headers or a Body along with the image request, you may do this by defining these properties on the source object:
```javascript
<Image source={{
uri: 'https://facebook.github.io/react/img/logo_og.png',
+1 -1
View File
@@ -6,7 +6,7 @@ category: Guides
permalink: docs/integration-with-existing-apps.html
banner: ejected
next: running-on-device
previous: testing
previous: colors
---
<style>
+1 -1
View File
@@ -5,7 +5,7 @@ layout: docs
category: Guides
permalink: docs/javascript-environment.html
next: direct-manipulation
previous: timers
previous: gesture-responder-system
---
## JavaScript Runtime
+4 -6
View File
@@ -4,8 +4,8 @@ title: More Resources
layout: docs
category: The Basics
permalink: docs/more-resources.html
next: handling-touches
previous: networking
next: components
previous: network
---
If you just read through this website, you should be able to build a pretty cool React Native app. But React Native isn't just a product made by one company - it's a community of thousands of developers. So if you're interested in React Native, here's some related stuff you might want to check out.
@@ -16,7 +16,7 @@ If you're using React Native, you probably already know about [React](https://fa
One common question is how to handle the "state" of your React Native application. The most popular library for this is [Redux](http://redux.js.org/). Don't be afraid of how often Redux uses the word "reducer" - it's a pretty simple library, and there's also a nice [series of videos](https://egghead.io/courses/getting-started-with-redux) explaining it.
If you're looking for a library that does a specific thing, check out [Awesome React Native](https://github.com/jondot/awesome-react-native), a curated list of components that also has demos, articles, and other stuff.
If you're looking for a library that does a specific thing, check out [Awesome React Native](http://www.awesome-react-native.com/), a curated list of components that also has demos, articles, and other stuff.
## Example Apps
@@ -34,8 +34,6 @@ The folks who built the app for Facebook's F8 conference in 2016 also [open-sour
[Expo](https://docs.expo.io) is a development environment plus application that focuses on letting you build React Native apps in the Expo development environment, without ever touching Xcode or Android Studio. If you wish React Native was even more JavaScripty and webby, check out Expo.
[Deco](https://www.decoide.org) is an all-in-one development environment specifically designed for React Native. It can automatically set up a new project, search for open source components, and insert them. You can also tweak your app graphically in real time. Check it out if you use macOS.
## Where React Native People Hang Out
The [React Native Community](https://www.facebook.com/groups/react.native.community) Facebook group has thousands of developers, and it's pretty active. Come there to show off your project, or ask how other people solved similar problems.
@@ -46,4 +44,4 @@ The [React Twitter account](https://twitter.com/reactjs) covers both React and R
There are a lot of [React Native Meetups](http://www.meetup.com/topics/react-native/) that happen around the world. Often there is React Native content in React meetups as well.
Sometimes we have React conferences. We posted the [videos from React.js Conf 2016](https://www.youtube.com/playlist?list=PLb0IAmt7-GS0M8Q95RIc2lOM6nc77q1IY), and we'll probably have more conferences in the future, too. Stay tuned.
Sometimes we have React conferences. We posted the [videos from React.js Conf 2017](https://www.youtube.com/playlist?list=PLb0IAmt7-GS3fZ46IGFirdqKTIxlws7e0) and [React.js Conf 2016](https://www.youtube.com/playlist?list=PLb0IAmt7-GS0M8Q95RIc2lOM6nc77q1IY), and we'll probably have more conferences in the future, too. Stay tuned. You can also find a list of dedicated React Native conferences [here](http://www.awesome-react-native.com/#conferences).
+1 -1
View File
@@ -6,7 +6,7 @@ category: Guides (Android)
permalink: docs/native-modules-android.html
banner: ejected
next: native-components-android
previous: communication-ios
previous: building-for-apple-tv
---
Sometimes an app needs access to a platform API that React Native doesn't have a corresponding module for yet. Maybe you want to reuse some existing Java code without having to reimplement it in JavaScript, or write some high performance, multi-threaded code such as for image processing, a database, or any number of advanced extensions.
+6 -3
View File
@@ -1,14 +1,17 @@
---
id: navigation
title: Navigation
title: Navigating Between Screens
layout: docs
category: Guides
permalink: docs/navigation.html
next: images
previous: animations
previous: platform-specific-code
---
This guide covers the various navigation components available in React Native. If you are just getting started with navigation, you will probably want to use [React Navigation](docs/navigation.html#react-navigation). React Navigation provides an easy to use navigation solution, with the ability to present common stack navigation and tabbed navigation patterns on both iOS and Android. As this is a JavaScript implementation, it provides the greatest amount of configurability as well as flexibility when integrating with state management libraries such as [redux](https://reactnavigation.org/docs/guides/redux).
Mobile apps are rarely made up of a single screen. Managing the presentation of, and transition between, multiple screens is typically handled by what is known as a navigator.
This guide covers the various navigation components available in React Native.
If you are just getting started with navigation, you will probably want to use [React Navigation](docs/navigation.html#react-navigation). React Navigation provides an easy to use navigation solution, with the ability to present common stack navigation and tabbed navigation patterns on both iOS and Android. As this is a JavaScript implementation, it provides the greatest amount of configurability as well as flexibility when integrating with state management libraries such as [redux](https://reactnavigation.org/docs/guides/redux).
If you're only targeting iOS, you may want to also check out [NavigatorIOS](docs/navigation.html#navigatorios) as a way of providing a native look and feel with minimal configuration, as it provides a wrapper around the native `UINavigationController` class. This component will not work on Android, however.
+1 -2
View File
@@ -160,7 +160,6 @@ var ws = new WebSocket('ws://host.com/path');
ws.onopen = () => {
// connection opened
ws.send('something'); // send a message
};
@@ -180,6 +179,6 @@ ws.onclose = (e) => {
};
```
## High Five!
## High Five!
If you've gotten here by reading linearly through the tutorial, then you are a pretty impressive human being. Congratulations. Next, you might want to check out [all the cool stuff the community does with React Native](docs/more-resources.html).
+1 -1
View File
@@ -5,7 +5,7 @@ layout: docs
category: Guides
permalink: docs/performance.html
next: gesture-responder-system
previous: direct-manipulation
previous: debugging
---
A compelling reason for using React Native instead of WebView-based tools is to achieve 60 frames per second and a native look and feel to your apps.
+4 -4
View File
@@ -4,8 +4,8 @@ title: Platform Specific Code
layout: docs
category: Guides
permalink: docs/platform-specific-code.html
next: debugging
previous: colors
next: navigation
previous: components
---
When building a cross-platform app, you'll want to re-use as much code as possible. Scenarios may arise where it makes sense for the code to be different, for example you may want to implement separate visual components for iOS and Android.
@@ -72,8 +72,8 @@ On Android, the `Platform` module can also be used to detect the version of the
```javascript
import { Platform } from 'react-native';
if(Platform.Version === 21){
console.log('Running on Lollipop!');
if (Platform.Version === 25) {
console.log('Running on Nougat!');
}
```
+2
View File
@@ -80,6 +80,8 @@ previous: integration-with-existing-apps
It's always a good idea to test your app on an actual device before releasing it to your users. This document will guide you through the necessary steps to run your React Native app on a device and to get it ready for production.
If you used Create React Native App to set up your project, you can preview your app on a device by scanning the QR code with the Expo app. In order to build and run your app on a device, you will need to eject and install the native code dependencies from the [Getting Started guide](docs/getting-started.html).
<div class="toggler">
<ul role="tablist" >
+2 -4
View File
@@ -1,17 +1,15 @@
---
id: testing
title: Testing
title: Running Tests and Contributing
layout: docs
category: Guides
permalink: docs/testing.html
next: understanding-cli
previous: gesture-responder-system
previous: upgrading
---
This document is about running tests on React Native itself. If you're interested in testing a React Native app, check out the [React Native Tutorial](http://facebook.github.io/jest/docs/tutorial-react-native.html) on the Jest website.
## Running Tests and Contributing
The React Native repo has several tests you can run to verify you haven't caused a regression with your PR. These tests are run with the [Travis](https://travis-ci.org/facebook/react-native/builds) and [CircleCI](https://circleci.com/gh/facebook/react-native) continuous integration systems, which will automatically annotate pull requests with the test results.
Whenever you are fixing a bug or adding new functionality to React Native, you should add a test that covers it. Depending on the change you're making, there are different types of tests that may be appropriate.
+1 -1
View File
@@ -4,7 +4,7 @@ title: Timers
layout: docs
category: Guides
permalink: docs/timers.html
next: javascript-environment
next: debugging
previous: accessibility
---
+13 -7
View File
@@ -33,23 +33,29 @@ export default class HelloWorldApp extends Component {
AppRegistry.registerComponent('AwesomeProject', () => HelloWorldApp);
```
If you are feeling curious, you can play around with sample code directly in the web simulators. You can also paste it into your `index.ios.js` or `index.android.js` file to create a real app on your local machine.
If you are feeling curious, you can play around with sample code directly in the web simulators. You can also paste it into your `App.js`, `index.ios.js`, or `index.android.js` file to create a real app on your local machine.
## What's going on here?
Some of the things in here might not look like JavaScript to you. Don't panic. This is the future.
Some of the things in here might not look like JavaScript to you. Don't panic. _This is the future_.
First of all, ES2015 (also known as ES6) is a set of improvements to JavaScript that is now part of the official standard, but not yet supported by all browsers, so often it isn't used yet in web development. React Native ships with ES2015 support, so you can use this stuff without worrying about compatibility. `import`, `from`, `class`, `extends`, and the `() =>` syntax in the example above are all ES2015 features. If you aren't familiar with ES2015, you can probably pick it up just by reading through sample code like this tutorial has. If you want, [this page](https://babeljs.io/docs/learn-es2015/) has a good overview of ES2015 features.
First of all, ES2015 (also known as ES6) is a set of improvements to JavaScript that is now part of the official standard, but not yet supported by all browsers, so often it isn't used yet in web development. React Native ships with ES2015 support, so you can use this stuff without worrying about compatibility. `import`, `from`, `class`, `extends`, and the `() =>` syntax in the example above are all ES2015 features. If you aren't familiar with ES2015, you can probably pick it up just by reading through sample code like this tutorial has. If you want, [this page](https://babeljs.io/learn-es2015/) has a good overview of ES2015 features.
The other unusual thing in this code example is `<Text>Hello world!</Text>`. This is JSX - a syntax for embedding XML within JavaScript. Many frameworks use a special templating language which lets you embed code inside markup language. In React, this is reversed. JSX lets you write your markup language inside code. It looks like HTML on the web, except instead of web things like `<div>` or `<span>`, you use React components. In this case, `<Text>`
is a built-in component that just displays some text.
## Component and AppRegistry
## Components
So this code is defining `HelloWorldApp`, a new `Component`, and it's registering it with the `AppRegistry`. When you're building a React Native app, you'll be making new components a lot. Anything you see on the screen is some sort of component. A component can be pretty simple - the only thing that's required is a `render` function which returns some JSX to render.
So this code is defining `HelloWorldApp`, a new `Component`. When you're building a React Native app, you'll be making new components a lot. Anything you see on the screen is some sort of component. A component can be pretty simple - the only thing that's required is a `render` function which returns some JSX to render.
The `AppRegistry` just tells React Native which component is the root one for the whole application. You won't be thinking about `AppRegistry` a lot - there will probably just be one call to `AppRegistry.registerComponent` in your whole app. It's included in these examples so you can paste the whole thing into your `index.ios.js` or `index.android.js` file and get it running. If you have a project from Create React Native App, this is handled for you and it's not necessary to call AppRegistry in your code.
<div class="banner-crna-ejected">
<h3>Projects With Native Code Only</h3>
<p>
In the particular example above, <code>HelloWorldApp</code> is registered with the <code>AppRegistry</code>. The <code>AppRegistry</code> just tells React Native which component is the root one for the whole application. It's included in these examples so you can paste the whole thing into your <code>index.ios.js</code> or <code>index.android.js</code> file and get it running. If you have a project from Create React Native App, this is handled for you and it's not necessary to call <code>AppRegistry</code> in your code.
</p>
</div>
## This App Doesn't Do Very Much
## This app doesn't do very much
Good point. To make components do more interesting things, you need to [learn about Props](docs/props.html).
+2 -2
View File
@@ -5,8 +5,8 @@ layout: docs
category: Guides
permalink: docs/understanding-cli.html
banner: ejected
next: integration-with-existing-apps
previous: running-on-device
next: native-modules-ios
previous: testing
---
Though you may have installed the `react-native-cli` via npm as a separate module, it is a shell for accessing the CLI embedded
+2 -2
View File
@@ -4,8 +4,8 @@ title: Upgrading to new React Native versions
layout: docs
category: Guides
permalink: docs/upgrading.html
next: native-modules-ios
previous: understanding-cli
next: testing
previous: running-on-device
---
Upgrading to new versions of React Native will give you access to more APIs, views, developer tools and other goodies. Upgrading requires a small amount of effort, but we try to make it easy for you. The instructions are a bit different depending on whether you used `create-react-native-app` or `react-native init` to create your project.
+1 -1
View File
@@ -58,7 +58,7 @@ const styles = StyleSheet.create({
AppRegistry.registerComponent('AwesomeProject', () => FlatListBasics);
```
If you want to render a set of data broken into logical sections, maybe with section headers, then a `SectionList` is the way to go.
If you want to render a set of data broken into logical sections, maybe with section headers, similar to `UITableView`s on iOS, then a [SectionList](docs/sectionlist.html) is the way to go.
```SnackPlayer?name=SectionList%20Basics
import React, { Component } from 'react';
+7 -3
View File
@@ -5,10 +5,10 @@ layout: docs
category: The Basics
permalink: docs/using-a-scrollview.html
next: using-a-listview
previous: handling-text-input
previous: handling-touches
---
The [`ScrollView`](docs/scrollview.html) is a generic scrolling container that can host multiple components and views. The scrollable items need not be homogenous, and you can scroll both vertically and horizontally (by setting the `horizontal` property).
The [ScrollView](docs/scrollview.html) is a generic scrolling container that can host multiple components and views. The scrollable items need not be homogenous, and you can scroll both vertically and horizontally (by setting the `horizontal` property).
This example creates a vertical `ScrollView` with both images and text mixed together.
@@ -62,4 +62,8 @@ AppRegistry.registerComponent(
() => IScrolledDownAndWhatHappenedNextShockedMe);
```
`ScrollView` works best to present a small amount of things of a limited size. All the elements and views of a `ScrollView` are rendered, even if they are not currently shown on the screen. If you have a long list of more items that can fit on the screen, you should use a `FlatList` instead. So let's [learn about list views](docs/using-a-listview.html) next.
ScrollViews can be configured to allow paging through views using swiping gestures by using the `pagingEnabled` props. Swiping horizontally between views can also be implemented on Android using the [ViewPagerAndroid](docs/viewpagerandroid.html) component.
A ScrollView with a single item can be used to allow the user to zoom content. Set up the `maximumZoomScale` and `minimumZoomScale` props and your user will be able to use pinch and expand gestures to zoom in and out.
The ScrollView works best to present a small amount of things of a limited size. All the elements and views of a `ScrollView` are rendered, even if they are not currently shown on the screen. If you have a long list of more items that can fit on the screen, you should use a `FlatList` instead. So let's [learn about list views](docs/using-a-listview.html) next.
+2 -5
View File
@@ -13,17 +13,14 @@
const babel = require('babel-core');
const babelRegisterOnly = require('metro-bundler/build/babelRegisterOnly');
const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction');
const path = require('path');
const transformer = require('metro-bundler/build/transformer.js');
const nodeFiles = RegExp([
'/local-cli/',
'/packager/(?!src/Resolver/polyfills/)',
].join('|'));
const nodeOptions = babelRegisterOnly.config([nodeFiles]);
babelRegisterOnly([]);
// has to be required after setting up babelRegisterOnly
const transformer = require('metro-bundler/build/transformer.js');
module.exports = {
process(src/*: string*/, file/*: string*/) {
@@ -49,7 +46,7 @@ module.exports = {
getCacheKey: createCacheKeyFunction([
__filename,
path.join(__dirname, '../packager/src/transformer.js'),
require.resolve('metro-bundler/build/transformer.js'),
require.resolve('babel-core/package.json'),
]),
};
+3 -3
View File
@@ -10,9 +10,9 @@
const mockComponent = require.requireActual('./mockComponent');
require.requireActual('../packager/src/Resolver/polyfills/babelHelpers.js');
require.requireActual('../packager/src/Resolver/polyfills/Object.es7.js');
require.requireActual('../packager/src/Resolver/polyfills/error-guard');
require.requireActual('metro-bundler/build/Resolver/polyfills/babelHelpers.js');
require.requireActual('metro-bundler/build/Resolver/polyfills/Object.es7.js');
require.requireActual('metro-bundler/build/Resolver/polyfills/error-guard');
global.__DEV__ = true;
-111
View File
@@ -1,111 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const path = require('path');
const flatten = require('lodash').flatten;
const android = require('./android');
const findAssets = require('./findAssets');
const ios = require('./ios');
const windows = require('./windows');
const wrapCommands = require('./wrapCommands');
const findPlugins = require('./findPlugins');
const findSymlinksPaths = require('../util/findSymlinksPaths');
function getProjectPath() {
if (__dirname.match(/node_modules[\/\\]react-native[\/\\]local-cli[\/\\]core$/)) {
// Packager is running from node_modules.
// This is the default case for all projects created using 'react-native init'.
return path.resolve(__dirname, '../../../..');
} else if (__dirname.match(/Pods[\/\\]React[\/\\]packager$/)) {
// React Native was installed using CocoaPods.
return path.resolve(__dirname, '../../../..');
}
return path.resolve(__dirname, '../..');
}
const getRNPMConfig = (folder) =>
// $FlowFixMe non-literal require
require(path.join(folder, './package.json')).rnpm || {};
const attachPackage = (command, pkg) => Array.isArray(command)
? command.map(cmd => attachPackage(cmd, pkg))
: { ...command, pkg };
const resolveSymlink = (roots) =>
roots.concat(
findSymlinksPaths(
path.join(getProjectPath(), 'node_modules'),
roots
)
);
/**
* Default configuration for the CLI.
*
* If you need to override any of this functions do so by defining the file
* `rn-cli.config.js` on the root of your project with the functions you need
* to tweak.
*/
const config = {
getProjectCommands() {
const appRoot = process.cwd();
const plugins = findPlugins([appRoot])
.map(pathToCommands => {
const name = pathToCommands.split(path.sep)[0];
return attachPackage(
// $FlowFixMe non-literal require
require(path.join(appRoot, 'node_modules', pathToCommands)),
// $FlowFixMe non-literal require
require(path.join(appRoot, 'node_modules', name, 'package.json'))
);
});
return flatten(plugins);
},
getProjectConfig() {
const folder = process.cwd();
const rnpm = getRNPMConfig(folder);
return Object.assign({}, rnpm, {
ios: ios.projectConfig(folder, rnpm.ios || {}),
android: android.projectConfig(folder, rnpm.android || {}),
windows: windows.projectConfig(folder, rnpm.windows || {}),
assets: findAssets(folder, rnpm.assets),
});
},
getDependencyConfig(packageName: string) {
const folder = path.join(process.cwd(), 'node_modules', packageName);
const rnpm = getRNPMConfig(
path.join(process.cwd(), 'node_modules', packageName)
);
return Object.assign({}, rnpm, {
ios: ios.dependencyConfig(folder, rnpm.ios || {}),
android: android.dependencyConfig(folder, rnpm.android || {}),
windows: windows.dependencyConfig(folder, rnpm.windows || {}),
assets: findAssets(folder, rnpm.assets),
commands: wrapCommands(rnpm.commands),
params: rnpm.params || [],
});
},
getProjectRoots() {
const root = process.env.REACT_NATIVE_APP_ROOT;
if (root) {
return resolveSymlink([path.resolve(root)]);
}
return resolveSymlink([getProjectPath()]);
},
};
module.exports = config;
+63 -2
View File
@@ -10,9 +10,15 @@
*/
'use strict';
const android = require('./android');
const Config = require('../util/Config');
const findPlugins = require('./findPlugins');
const findAssets = require('./findAssets');
const ios = require('./ios');
const windows = require('./windows');
const wrapCommands = require('./wrapCommands');
const defaultConfig = require('./default.config');
const flatten = require('lodash').flatten;
const minimist = require('minimist');
const path = require('path');
@@ -35,6 +41,61 @@ export type RNConfig = {
getDependencyConfig(pkgName: string): Object,
};
const getRNPMConfig = (folder) =>
// $FlowFixMe non-literal require
require(path.join(folder, './package.json')).rnpm || {};
const attachPackage = (command, pkg) => Array.isArray(command)
? command.map(cmd => attachPackage(cmd, pkg))
: { ...command, pkg };
const defaultRNConfig = {
getProjectCommands(): Array<CommandT> {
const appRoot = process.cwd();
const plugins = findPlugins([appRoot])
.map(pathToCommands => {
const name = pathToCommands.split(path.sep)[0];
return attachPackage(
// $FlowFixMe non-literal require
require(path.join(appRoot, 'node_modules', pathToCommands)),
// $FlowFixMe non-literal require
require(path.join(appRoot, 'node_modules', name, 'package.json'))
);
});
return flatten(plugins);
},
getProjectConfig(): Object {
const folder = process.cwd();
const rnpm = getRNPMConfig(folder);
return Object.assign({}, rnpm, {
ios: ios.projectConfig(folder, rnpm.ios || {}),
android: android.projectConfig(folder, rnpm.android || {}),
windows: windows.projectConfig(folder, rnpm.windows || {}),
assets: findAssets(folder, rnpm.assets),
});
},
getDependencyConfig(packageName: string) {
const folder = path.join(process.cwd(), 'node_modules', packageName);
const rnpm = getRNPMConfig(
path.join(process.cwd(), 'node_modules', packageName)
);
return Object.assign({}, rnpm, {
ios: ios.dependencyConfig(folder, rnpm.ios || {}),
android: android.dependencyConfig(folder, rnpm.android || {}),
windows: windows.dependencyConfig(folder, rnpm.windows || {}),
assets: findAssets(folder, rnpm.assets),
commands: wrapCommands(rnpm.commands),
params: rnpm.params || [],
});
},
};
/**
* Loads the CLI configuration
*/
@@ -44,7 +105,7 @@ function getCliConfig(): RNConfig {
? Config.loadFile(path.resolve(__dirname, cliArgs.config))
: Config.findOptional(__dirname);
return {...defaultConfig, ...config};
return {...defaultRNConfig, ...config};
}
module.exports = getCliConfig();
@@ -1,3 +1,12 @@
/**
* Copyright (c) 2013-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';
jest.autoMockOff();
@@ -16,11 +25,7 @@ const writePlist = require('../../ios/writePlist');
const projectPath = path.join(__dirname, '../../__fixtures__/project.pbxproj');
const infoPlistPath = path.join(__dirname, '../../__fixtures__/Info.plist');
fs.__setMockFilesystem({
'Basic': {
'project.pbxproj': readFileSync(projectPath).toString(),
}
});
fs.readFileSync = jest.fn(() => readFileSync(projectPath).toString());
const project = xcode.project('/Basic/project.pbxproj');
+29 -1
View File
@@ -10,6 +10,8 @@
*/
'use strict';
const findSymlinksPaths = require('./findSymlinksPaths');
const blacklist = require('metro-bundler/build/blacklist');
const fs = require('fs');
const invariant = require('fbjs/lib/invariant');
@@ -111,6 +113,26 @@ export type ConfigT = {
transformVariants: () => TransformVariants,
};
function getProjectPath() {
if (__dirname.match(/node_modules[\/\\]react-native[\/\\]local-cli[\/\\]util$/)) {
// Packager is running from node_modules.
// This is the default case for all projects created using 'react-native init'.
return path.resolve(__dirname, '../../../..');
} else if (__dirname.match(/Pods[\/\\]React[\/\\]packager$/)) {
// React Native was installed using CocoaPods.
return path.resolve(__dirname, '../../../..');
}
return path.resolve(__dirname, '../..');
}
const resolveSymlink = (roots) =>
roots.concat(
findSymlinksPaths(
path.join(getProjectPath(), 'node_modules'),
roots
)
);
/**
* Module capable of getting the configuration out of a given file.
*
@@ -126,7 +148,13 @@ const Config = {
getBlacklistRE: () => blacklist(),
getPlatforms: () => [],
getPolyfillModuleNames: () => [],
getProjectRoots: () => [process.cwd()],
getProjectRoots: () => {
const root = process.env.REACT_NATIVE_APP_ROOT;
if (root) {
return resolveSymlink([path.resolve(root)]);
}
return resolveSymlink([getProjectPath()]);
},
getProvidesModuleNodeModules: () => providesModuleNodeModules.slice(),
getSourceExts: () => [],
getTransformModulePath: () => require.resolve('metro-bundler/build/transformer.js'),
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "react-native",
"version": "1000.0.0",
"version": "0.46.0-rc.2",
"description": "A framework for building native apps using React",
"license": "BSD-3-Clause",
"repository": {
@@ -238,4 +238,4 @@
"shelljs": "0.6.0",
"sinon": "^2.2.0"
}
}
}
-10
View File
@@ -1,10 +0,0 @@
{
"rules": {
"extra-arrow-initializer": 0,
"no-alert": 0,
"no-console-disallow": 0
},
"env": {
"node": true
}
}
-22
View File
@@ -1,22 +0,0 @@
Glossary
===
Terminology commonly used in React Native Packager / Metro Bundler is explained
here. This document is work in progress, please help completing it.
## Build Root
Configuration files (`rn-cli.config.js`) support configuring one or more roots
that are watched for file changes during development. In the context of the
integration with the `js_*` rule family in [Buck][], there is only a single root,
the build root used by Buck.
## Local Path
A *local path* / `localPath` is the path to a file relative to a
[*build root*](#build-root).
[Buck]: http://buckbuild.com/
-9
View File
@@ -1,9 +0,0 @@
# metro-bundler
🚇 The JavaScript bundler for React Native.
- **🚅 Fast**: We aim for sub-second reload cycles, fast startup and quick bundling speeds.
- **⚖️ Scalable**: Works with thousands of modules in a single application.
- **⚛️ Integrated**: Supports every React Native project out of the box.
This project was previously part of the [react-native](https://github.com/facebook/react-native) repository. In this smaller repository it is easier for the team working on Metro Bundler to respond to both issues and pull requests. See [react-native#13976](https://github.com/facebook/react-native/issues/13976) for the initial announcement.
-46
View File
@@ -1,46 +0,0 @@
{
"version": "0.7.0",
"name": "metro-bundler",
"description": "🚇 The JavaScript bundler for React Native.",
"main": "src/index.js",
"repository": {
"type": "git",
"url": "git@github.com:facebook/metro-bundler.git"
},
"dependencies": {
"absolute-path": "^0.0.0",
"async": "^2.4.0",
"babel-core": "^6.24.1",
"babel-generator": "^6.24.1",
"babel-plugin-external-helpers": "^6.18.0",
"babel-preset-es2015-node": "^6.1.1",
"babel-preset-fbjs": "^2.1.0",
"babel-preset-react-native": "^1.9.1",
"babel-register": "^6.24.1",
"babylon": "^6.17.0",
"chalk": "^1.1.1",
"concat-stream": "^1.6.0",
"core-js": "^2.2.2",
"debug": "^2.2.0",
"denodeify": "^1.2.1",
"fbjs": "0.8.12",
"graceful-fs": "^4.1.3",
"image-size": "^0.3.5",
"jest-haste-map": "^20.0.4",
"json-stable-stringify": "^1.0.1",
"json5": "^0.4.0",
"left-pad": "^1.1.3",
"lodash": "^4.16.6",
"merge-stream": "^1.0.1",
"mime-types": "2.1.11",
"mkdirp": "^0.5.1",
"request": "^2.79.0",
"rimraf": "^2.5.4",
"source-map": "^0.5.6",
"temp": "0.8.3",
"throat": "^3.0.0",
"uglify-js": "2.7.5",
"write-file-atomic": "^1.2.0",
"xpipe": "^1.0.5"
}
}
-4
View File
@@ -1,4 +0,0 @@
{
"presets": [ "react-native" ],
"plugins": []
}
@@ -1,301 +0,0 @@
/**
* Copyright (c) 2013-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';
jest.disableAutomock();
jest.mock('fs');
const AssetServer = require('../');
const crypto = require('crypto');
const fs = require('fs');
const {objectContaining} = jasmine;
describe('AssetServer', () => {
describe('assetServer.get', () => {
it('should work for the simple case', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.png': 'b image',
'b@2x.png': 'b2 image',
},
},
});
return Promise.all([
server.get('imgs/b.png'),
server.get('imgs/b@1x.png'),
]).then(resp =>
resp.forEach(data =>
expect(data).toBe('b image')
)
);
});
it('should work for the simple case with platform ext', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.ios.png': 'b ios image',
'b.android.png': 'b android image',
'c.png': 'c general image',
'c.android.png': 'c android image',
},
},
});
return Promise.all([
server.get('imgs/b.png', 'ios').then(
data => expect(data).toBe('b ios image')
),
server.get('imgs/b.png', 'android').then(
data => expect(data).toBe('b android image')
),
server.get('imgs/c.png', 'android').then(
data => expect(data).toBe('c android image')
),
server.get('imgs/c.png', 'ios').then(
data => expect(data).toBe('c general image')
),
server.get('imgs/c.png').then(
data => expect(data).toBe('c general image')
),
]);
});
it('should work for the simple case with jpg', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png', 'jpg'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.png': 'png image',
'b.jpg': 'jpeg image',
},
},
});
return Promise.all([
server.get('imgs/b.jpg'),
server.get('imgs/b.png'),
]).then(data =>
expect(data).toEqual([
'jpeg image',
'png image',
])
);
});
it('should pick the bigger one', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
'b@4x.png': 'b4 image',
'b@4.5x.png': 'b4.5 image',
},
},
});
return server.get('imgs/b@3x.png').then(data =>
expect(data).toBe('b4 image')
);
});
it('should pick the bigger one with platform ext', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
'b@4x.png': 'b4 image',
'b@4.5x.png': 'b4.5 image',
'b@1x.ios.png': 'b1 ios image',
'b@2x.ios.png': 'b2 ios image',
'b@4x.ios.png': 'b4 ios image',
'b@4.5x.ios.png': 'b4.5 ios image',
},
},
});
return Promise.all([
server.get('imgs/b@3x.png').then(data =>
expect(data).toBe('b4 image')
),
server.get('imgs/b@3x.png', 'ios').then(data =>
expect(data).toBe('b4 ios image')
),
]);
});
it('should support multiple project roots', () => {
const server = new AssetServer({
projectRoots: ['/root', '/root2'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.png': 'b image',
},
},
'root2': {
'newImages': {
'imgs': {
'b@1x.png': 'b1 image',
},
},
},
});
return server.get('newImages/imgs/b.png').then(data =>
expect(data).toBe('b1 image')
);
});
});
describe('assetServer.getAssetData', () => {
it('should get assetData', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
'b@4x.png': 'b4 image',
'b@4.5x.png': 'b4.5 image',
},
},
});
return server.getAssetData('imgs/b.png').then(data => {
expect(data).toEqual(objectContaining({
type: 'png',
name: 'b',
scales: [1, 2, 4, 4.5],
files: [
'/root/imgs/b@1x.png',
'/root/imgs/b@2x.png',
'/root/imgs/b@4x.png',
'/root/imgs/b@4.5x.png',
],
}));
});
});
it('should get assetData for non-png images', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png', 'jpeg'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b@1x.jpg': 'b1 image',
'b@2x.jpg': 'b2 image',
'b@4x.jpg': 'b4 image',
'b@4.5x.jpg': 'b4.5 image',
},
},
});
return server.getAssetData('imgs/b.jpg').then(data => {
expect(data).toEqual(objectContaining({
type: 'jpg',
name: 'b',
scales: [1, 2, 4, 4.5],
files: [
'/root/imgs/b@1x.jpg',
'/root/imgs/b@2x.jpg',
'/root/imgs/b@4x.jpg',
'/root/imgs/b@4.5x.jpg',
],
}));
});
});
describe('hash:', () => {
let server, mockFS;
beforeEach(() => {
server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['jpg'],
});
mockFS = {
'root': {
imgs: {
'b@1x.jpg': 'b1 image',
'b@2x.jpg': 'b2 image',
'b@4x.jpg': 'b4 image',
'b@4.5x.jpg': 'b4.5 image',
},
},
};
fs.__setMockFilesystem(mockFS);
});
it('uses the file contents to build the hash', () => {
const hash = crypto.createHash('md5');
for (const name in mockFS.root.imgs) {
hash.update(mockFS.root.imgs[name]);
}
return server.getAssetData('imgs/b.jpg').then(data =>
expect(data).toEqual(objectContaining({hash: hash.digest('hex')}))
);
});
it('changes the hash when the passed-in file watcher emits an `all` event', () => {
return server.getAssetData('imgs/b.jpg').then(initialData => {
mockFS.root.imgs['b@4x.jpg'] = 'updated data';
server.onFileChange('all', '/root/imgs/b@4x.jpg');
return server.getAssetData('imgs/b.jpg').then(data =>
expect(data.hash).not.toEqual(initialData.hash)
);
});
});
});
});
});
-253
View File
@@ -1,253 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const AssetPaths = require('../node-haste/lib/AssetPaths');
const crypto = require('crypto');
const denodeify = require('denodeify');
const fs = require('fs');
const path = require('path');
import type {AssetData} from '../node-haste/lib/AssetPaths';
const stat = denodeify(fs.stat);
const readDir = denodeify(fs.readdir);
const readFile = denodeify(fs.readFile);
class AssetServer {
_roots: $ReadOnlyArray<string>;
_assetExts: $ReadOnlyArray<string>;
_hashes: Map<?string, string>;
_files: Map<string, string>;
constructor(options: {|
+assetExts: $ReadOnlyArray<string>,
+projectRoots: $ReadOnlyArray<string>,
|}) {
this._roots = options.projectRoots;
this._assetExts = options.assetExts;
this._hashes = new Map();
this._files = new Map();
}
get(assetPath: string, platform: ?string = null): Promise<Buffer> {
const assetData = AssetPaths.parse(
assetPath,
new Set(platform != null ? [platform] : []),
);
return this._getAssetRecord(assetPath, platform).then(record => {
for (let i = 0; i < record.scales.length; i++) {
if (record.scales[i] >= assetData.resolution) {
return readFile(record.files[i]);
}
}
return readFile(record.files[record.files.length - 1]);
});
}
getAssetData(assetPath: string, platform: ?string = null): Promise<{|
files: Array<string>,
hash: string,
name: string,
scales: Array<number>,
type: string,
|}> {
const nameData = AssetPaths.parse(
assetPath,
new Set(platform != null ? [platform] : []),
);
const {name, type} = nameData;
return this._getAssetRecord(assetPath, platform).then(record => {
const {scales, files} = record;
const hash = this._hashes.get(assetPath);
if (hash != null) {
return {files, hash, name, scales, type};
}
return new Promise((resolve, reject) => {
const hasher = crypto.createHash('md5');
hashFiles(files.slice(), hasher, error => {
if (error) {
reject(error);
} else {
const freshHash = hasher.digest('hex');
this._hashes.set(assetPath, freshHash);
files.forEach(f => this._files.set(f, assetPath));
resolve({files, hash: freshHash, name, scales, type});
}
});
});
});
}
onFileChange(type: string, filePath: string) {
this._hashes.delete(this._files.get(filePath));
}
/**
* Given a request for an image by path. That could contain a resolution
* postfix, we need to find that image (or the closest one to it's resolution)
* in one of the project roots:
*
* 1. We first parse the directory of the asset
* 2. We check to find a matching directory in one of the project roots
* 3. We then build a map of all assets and their scales in this directory
* 4. Then try to pick platform-specific asset records
* 5. Then pick the closest resolution (rounding up) to the requested one
*/
_getAssetRecord(assetPath: string, platform: ?string = null): Promise<{|
files: Array<string>,
scales: Array<number>,
|}> {
const filename = path.basename(assetPath);
return (
this._findRoot(
this._roots,
path.dirname(assetPath),
assetPath,
)
.then(dir => Promise.all([
dir,
readDir(dir),
]))
.then(res => {
const dir = res[0];
const files = res[1];
const assetData = AssetPaths.parse(
filename,
new Set(platform != null ? [platform] : []),
);
const map = this._buildAssetMap(dir, files, platform);
let record;
if (platform != null) {
record = map.get(getAssetKey(assetData.assetName, platform)) ||
map.get(assetData.assetName);
} else {
record = map.get(assetData.assetName);
}
if (!record) {
throw new Error(
/* $FlowFixMe: platform can be null */
`Asset not found: ${assetPath} for platform: ${platform}`
);
}
return record;
})
);
}
_findRoot(roots: $ReadOnlyArray<string>, dir: string, debugInfoFile: string): Promise<string> {
return Promise.all(
roots.map(root => {
const absRoot = path.resolve(root);
// important: we want to resolve root + dir
// to ensure the requested path doesn't traverse beyond root
const absPath = path.resolve(root, dir);
return stat(absPath).then(fstat => {
// keep asset requests from traversing files
// up from the root (e.g. ../../../etc/hosts)
if (!absPath.startsWith(absRoot)) {
return {path: absPath, isValid: false};
}
return {path: absPath, isValid: fstat.isDirectory()};
}, _ => {
return {path: absPath, isValid: false};
});
})
).then(stats => {
for (let i = 0; i < stats.length; i++) {
if (stats[i].isValid) {
return stats[i].path;
}
}
const rootsString = roots.map(s => `'${s}'`).join(', ');
throw new Error(
`'${debugInfoFile}' could not be found, because '${dir}' is not a ` +
`subdirectory of any of the roots (${rootsString})`,
);
});
}
_buildAssetMap(dir: string, files: $ReadOnlyArray<string>, platform: ?string): Map<string, {|
files: Array<string>,
scales: Array<number>,
|}> {
const platforms = new Set(platform != null ? [platform] : []);
const assets = files.map(this._getAssetDataFromName.bind(this, platforms));
const map = new Map();
assets.forEach(function(asset, i) {
if (asset == null) {
return;
}
const file = files[i];
const assetKey = getAssetKey(asset.assetName, asset.platform);
let record = map.get(assetKey);
if (!record) {
record = {
scales: [],
files: [],
};
map.set(assetKey, record);
}
let insertIndex;
const length = record.scales.length;
for (insertIndex = 0; insertIndex < length; insertIndex++) {
if (asset.resolution < record.scales[insertIndex]) {
break;
}
}
record.scales.splice(insertIndex, 0, asset.resolution);
record.files.splice(insertIndex, 0, path.join(dir, file));
});
return map;
}
_getAssetDataFromName(platforms: Set<string>, file: string): ?AssetData {
return AssetPaths.tryParse(file, platforms);
}
}
function getAssetKey(assetName, platform) {
if (platform != null) {
return `${assetName} : ${platform}`;
} else {
return assetName;
}
}
function hashFiles(files, hash, callback) {
if (!files.length) {
callback(null);
return;
}
fs.createReadStream(files.shift())
.on('data', data => hash.update(data))
.once('end', () => hashFiles(files, hash, callback))
.once('error', error => callback(error));
}
module.exports = AssetServer;
-370
View File
@@ -1,370 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const BundleBase = require('./BundleBase');
const ModuleTransport = require('../lib/ModuleTransport');
const _ = require('lodash');
const crypto = require('crypto');
const debug = require('debug')('RNP:Bundle');
const invariant = require('fbjs/lib/invariant');
const {createRamBundleGroups} = require('./util');
const {fromRawMappings} = require('./source-map');
const {isMappingsMap} = require('../lib/SourceMap');
import type {IndexMap, MappingsMap, SourceMap} from '../lib/SourceMap';
import type {GetSourceOptions, FinalizeOptions} from './BundleBase';
export type Unbundle = {
startupModules: Array<*>,
lazyModules: Array<*>,
groups: Map<number, Set<number>>,
};
type SourceMapFormat = 'undetermined' | 'indexed' | 'flattened';
const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL=';
class Bundle extends BundleBase {
_dev: boolean | void;
_inlineSourceMap: string | void;
_minify: boolean | void;
_numRequireCalls: number;
_ramBundle: Unbundle | null;
_ramGroups: ?Array<string>;
_sourceMap: string | null;
_sourceMapFormat: SourceMapFormat;
_sourceMapUrl: ?string;
constructor({sourceMapUrl, dev, minify, ramGroups}: {
sourceMapUrl: ?string,
dev?: boolean,
minify?: boolean,
ramGroups?: Array<string>,
} = {}) {
super();
this._sourceMap = null;
this._sourceMapFormat = 'undetermined';
this._sourceMapUrl = sourceMapUrl;
this._numRequireCalls = 0;
this._dev = dev;
this._minify = minify;
this._ramGroups = ramGroups;
this._ramBundle = null; // cached RAM Bundle
}
addModule(
/**
* $FlowFixMe: this code is inherently incorrect, because it modifies the
* signature of the base class function "addModule". That means callsites
* using an instance typed as the base class would be broken. This must be
* refactored.
*/
resolver: {wrapModule: (options: any) => Promise<{code: any, map: any}>},
resolutionResponse: mixed,
module: mixed,
/* $FlowFixMe: erroneous change of signature. */
moduleTransport: ModuleTransport,
/* $FlowFixMe: erroneous change of signature. */
): Promise<void> {
const index = super.addModule(moduleTransport);
return resolver.wrapModule({
resolutionResponse,
module,
name: moduleTransport.name,
code: moduleTransport.code,
map: moduleTransport.map,
meta: moduleTransport.meta,
minify: this._minify,
dev: this._dev,
}).then(({code, map}) => {
// If we get a map from the transformer we'll switch to a mode
// were we're combining the source maps as opposed to
if (map) {
const usesRawMappings = isRawMappings(map);
if (this._sourceMapFormat === 'undetermined') {
this._sourceMapFormat = usesRawMappings ? 'flattened' : 'indexed';
} else if (usesRawMappings && this._sourceMapFormat === 'indexed') {
throw new Error(
`Got at least one module with a full source map, but ${
moduleTransport.sourcePath} has raw mappings`
);
} else if (!usesRawMappings && this._sourceMapFormat === 'flattened') {
throw new Error(
`Got at least one module with raw mappings, but ${
moduleTransport.sourcePath} has a full source map`
);
}
}
this.replaceModuleAt(
index, new ModuleTransport({...moduleTransport, code, map}));
});
}
finalize(options: FinalizeOptions) {
options = options || {};
if (options.runModule) {
/* $FlowFixMe: this is unsound, as nothing enforces runBeforeMainModule
* to be available if `runModule` is true. Refactor. */
options.runBeforeMainModule.forEach(this._addRequireCall, this);
/* $FlowFixMe: this is unsound, as nothing enforces the module ID to have
* been set beforehand. */
this._addRequireCall(this.getMainModuleId());
}
super.finalize(options);
}
_addRequireCall(moduleId: string) {
const code = `;require(${JSON.stringify(moduleId)});`;
const name = 'require-' + moduleId;
super.addModule(new ModuleTransport({
name,
id: -this._numRequireCalls - 1,
code,
virtual: true,
sourceCode: code,
sourcePath: name + '.js',
meta: {preloaded: true},
}));
this._numRequireCalls += 1;
}
_getInlineSourceMap(dev: ?boolean) {
if (this._inlineSourceMap == null) {
const sourceMap = this.getSourceMapString({excludeSource: true, dev});
/*eslint-env node*/
const encoded = new Buffer(sourceMap).toString('base64');
this._inlineSourceMap = 'data:application/json;base64,' + encoded;
}
return this._inlineSourceMap;
}
getSource(options: GetSourceOptions) {
this.assertFinalized();
options = options || {};
let source = super.getSource(options);
if (options.inlineSourceMap) {
source += SOURCEMAPPING_URL + this._getInlineSourceMap(options.dev);
} else if (this._sourceMapUrl) {
source += SOURCEMAPPING_URL + this._sourceMapUrl;
}
return source;
}
getUnbundle(): Unbundle {
this.assertFinalized();
if (!this._ramBundle) {
const modules = this.getModules().slice();
// separate modules we need to preload from the ones we don't
const [startupModules, lazyModules] = partition(modules, shouldPreload);
const ramGroups = this._ramGroups;
let groups;
this._ramBundle = {
startupModules,
lazyModules,
get groups() {
if (!groups) {
groups = createRamBundleGroups(ramGroups || [], lazyModules, subtree);
}
return groups;
},
};
}
return this._ramBundle;
}
invalidateSource() {
debug('invalidating bundle');
super.invalidateSource();
this._sourceMap = null;
}
/**
* Combine each of the sourcemaps multiple modules have into a single big
* one. This works well thanks to a neat trick defined on the sourcemap spec
* that makes use of of the `sections` field to combine sourcemaps by adding
* an offset. This is supported only by Chrome for now.
*/
_getCombinedSourceMaps(options: {excludeSource?: boolean}): IndexMap {
const result = {
version: 3,
file: this._getSourceMapFile(),
sections: [],
};
let line = 0;
this.getModules().forEach(module => {
invariant(
!Array.isArray(module.map),
`Unexpected raw mappings for ${module.sourcePath}`,
);
let map: SourceMap = module.map == null || module.virtual
? generateSourceMapForVirtualModule(module)
: module.map;
if (options.excludeSource && isMappingsMap(map)) {
map = {...map, sourcesContent: []};
}
result.sections.push({
offset: {line, column: 0},
map,
});
line += module.code.split('\n').length;
});
return result;
}
getSourceMap(options: {excludeSource?: boolean}): SourceMap {
this.assertFinalized();
return this._sourceMapFormat === 'indexed'
? this._getCombinedSourceMaps(options)
: fromRawMappings(this.getModules()).toMap();
}
getSourceMapString(options: {excludeSource?: boolean}): string {
if (this._sourceMapFormat === 'indexed') {
return JSON.stringify(this.getSourceMap(options));
}
// The following code is an optimization specific to the development server:
// 1. generator.toSource() is faster than JSON.stringify(generator.toMap()).
// 2. caching the source map unless there are changes saves time in
// development settings.
let map = this._sourceMap;
if (map == null) {
debug('Start building flat source map');
map = this._sourceMap = fromRawMappings(this.getModules()).toString();
debug('End building flat source map');
} else {
debug('Returning cached source map');
}
return map;
}
getEtag() {
/* $FlowFixMe: we must pass options, or rename the
* base `getSource` function, as it does not actually need options. */
var eTag = crypto.createHash('md5').update(this.getSource()).digest('hex');
return eTag;
}
_getSourceMapFile() {
return this._sourceMapUrl
? this._sourceMapUrl.replace('.map', '.bundle')
: 'bundle.js';
}
getJSModulePaths() {
return this.getModules()
// Filter out non-js files. Like images etc.
.filter(module => !module.virtual)
.map(module => module.sourcePath);
}
getDebugInfo() {
return [
/* $FlowFixMe: this is unsound as the module ID could be unset. */
'<div><h3>Main Module:</h3> ' + this.getMainModuleId() + '</div>',
'<style>',
'pre.collapsed {',
' height: 10px;',
' width: 100px;',
' display: block;',
' text-overflow: ellipsis;',
' overflow: hidden;',
' cursor: pointer;',
'}',
'</style>',
'<h3> Module paths and transformed code: </h3>',
this.getModules().map(function(m) {
return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' +
'<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
_.escape(m.code) + '</pre></code></div>';
}).join('\n'),
].join('\n');
}
setRamGroups(ramGroups: ?Array<string>) {
this._ramGroups = ramGroups;
}
}
function generateSourceMapForVirtualModule(module): MappingsMap {
// All lines map 1-to-1
let mappings = 'AAAA;';
for (let i = 1; i < module.code.split('\n').length; i++) {
mappings += 'AACA;';
}
return {
version: 3,
sources: [module.sourcePath],
names: [],
mappings,
file: module.sourcePath,
sourcesContent: [module.sourceCode],
};
}
function shouldPreload({meta}) {
return meta && meta.preloaded;
}
function partition(array, predicate) {
const included = [];
const excluded = [];
array.forEach(item => (predicate(item) ? included : excluded).push(item));
return [included, excluded];
}
function * subtree(
moduleTransport: ModuleTransport,
moduleTransportsByPath: Map<string, ModuleTransport>,
seen = new Set(),
) {
seen.add(moduleTransport.id);
const {meta} = moduleTransport;
invariant(
meta != null,
'Unexpected module transport without meta information: ' + moduleTransport.sourcePath,
);
for (const [, {path}] of meta.dependencyPairs || []) {
const dependency = moduleTransportsByPath.get(path);
if (dependency && !seen.has(dependency.id)) {
yield dependency.id;
yield * subtree(dependency, moduleTransportsByPath, seen);
}
}
}
const isRawMappings = Array.isArray;
module.exports = Bundle;
-114
View File
@@ -1,114 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const ModuleTransport = require('../lib/ModuleTransport');
export type FinalizeOptions = {
allowUpdates?: boolean,
runBeforeMainModule?: Array<string>,
runModule?: boolean,
};
export type GetSourceOptions = {
inlineSourceMap?: boolean,
dev: boolean,
};
class BundleBase {
_assets: Array<mixed>;
_finalized: boolean;
_mainModuleId: number | void;
_source: ?string;
__modules: Array<ModuleTransport>;
constructor() {
this._finalized = false;
this.__modules = [];
this._assets = [];
this._mainModuleId = undefined;
}
isEmpty() {
return this.__modules.length === 0 && this._assets.length === 0;
}
getMainModuleId() {
return this._mainModuleId;
}
setMainModuleId(moduleId: number) {
this._mainModuleId = moduleId;
}
addModule(module: ModuleTransport) {
if (!(module instanceof ModuleTransport)) {
throw new Error('Expected a ModuleTransport object');
}
return this.__modules.push(module) - 1;
}
replaceModuleAt(index: number, module: ModuleTransport) {
if (!(module instanceof ModuleTransport)) {
throw new Error('Expeceted a ModuleTransport object');
}
this.__modules[index] = module;
}
getModules() {
return this.__modules;
}
getAssets() {
return this._assets;
}
addAsset(asset: mixed) {
this._assets.push(asset);
}
finalize(options: FinalizeOptions) {
if (!options.allowUpdates) {
Object.freeze(this.__modules);
Object.freeze(this._assets);
}
this._finalized = true;
}
getSource(options: GetSourceOptions) {
this.assertFinalized();
if (this._source) {
return this._source;
}
this._source = this.__modules.map(module => module.code).join('\n');
return this._source;
}
invalidateSource() {
this._source = null;
}
assertFinalized(message?: string) {
if (!this._finalized) {
throw new Error(message || 'Bundle needs to be finalized before getting any source');
}
}
setRamGroups(ramGroups: Array<string>) {}
}
module.exports = BundleBase;
-88
View File
@@ -1,88 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
const BundleBase = require('./BundleBase');
const ModuleTransport = require('../lib/ModuleTransport');
import type Resolver from '../Resolver';
import type ResolutionResponse
from '../node-haste/DependencyGraph/ResolutionResponse';
import type Module from '../node-haste/Module';
class HMRBundle extends BundleBase {
_sourceMappingURLFn: (hmrpath: string) => mixed;
_sourceMappingURLs: Array<mixed>;
_sourceURLFn: (hmrpath: string) => mixed;
_sourceURLs: Array<mixed>;
constructor({
sourceURLFn,
sourceMappingURLFn,
}: {
sourceURLFn: (hmrpath: string) => mixed,
sourceMappingURLFn: (hmrpath: string) => mixed,
}) {
super();
this._sourceURLFn = sourceURLFn;
this._sourceMappingURLFn = sourceMappingURLFn;
this._sourceURLs = [];
this._sourceMappingURLs = [];
}
addModule(
/* $FlowFixMe: broken OOP design: function signature should be the same */
resolver: Resolver,
/* $FlowFixMe: broken OOP design: function signature should be the same */
response: ResolutionResponse<Module, {}>,
/* $FlowFixMe: broken OOP design: function signature should be the same */
module: Module,
/* $FlowFixMe: broken OOP design: function signature should be the same */
moduleTransport: ModuleTransport,
) {
const code = resolver.resolveRequires(
response,
module,
moduleTransport.code,
/* $FlowFixMe: may not exist */
moduleTransport.meta.dependencyOffsets,
);
super.addModule(new ModuleTransport({...moduleTransport, code}));
this._sourceMappingURLs.push(
this._sourceMappingURLFn(moduleTransport.sourcePath),
);
this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath));
// inconsistent with parent class return type
return (Promise.resolve(): any);
}
getModulesIdsAndCode(): Array<{id: string, code: string}> {
return this.__modules.map(module => {
return {
id: JSON.stringify(module.id),
code: module.code,
};
});
}
getSourceURLs() {
return this._sourceURLs;
}
getSourceMappingURLs() {
return this._sourceMappingURLs;
}
}
module.exports = HMRBundle;
@@ -1,494 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
*/
'use strict';
jest.disableAutomock();
const Bundle = require('../Bundle');
const ModuleTransport = require('../../lib/ModuleTransport');
const crypto = require('crypto');
describe('Bundle', () => {
var bundle;
beforeEach(() => {
bundle = new Bundle({sourceMapUrl: 'test_url'});
bundle.getSourceMap = jest.fn(() => {
return 'test-source-map';
});
});
describe('source bundle', () => {
it('should create a bundle and get the source', () => {
return Promise.resolve()
.then(() => {
return addModule({
bundle,
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle,
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
});
})
.then(() => {
bundle.finalize({});
expect(bundle.getSource({dev: true})).toBe(
[
'transformed foo;',
'transformed bar;',
'\/\/# sourceMappingURL=test_url',
].join('\n'),
);
});
});
it('should be ok to leave out the source map url', () => {
const otherBundle = new Bundle();
return Promise.resolve()
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
});
})
.then(() => {
otherBundle.finalize({});
expect(otherBundle.getSource({dev: true})).toBe(
['transformed foo;', 'transformed bar;'].join('\n'),
);
});
});
it('should create a bundle and add run module code', () => {
return Promise.resolve()
.then(() => {
return addModule({
bundle,
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle,
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
});
})
.then(() => {
bundle.setMainModuleId('foo');
bundle.finalize({
runBeforeMainModule: ['bar'],
runModule: true,
});
expect(bundle.getSource({dev: true})).toBe(
[
'transformed foo;',
'transformed bar;',
';require("bar");',
';require("foo");',
'\/\/# sourceMappingURL=test_url',
].join('\n'),
);
});
});
it('inserts modules in a deterministic order, independent of timing of the wrapper process', () => {
const moduleTransports = [
createModuleTransport({name: 'module1'}),
createModuleTransport({name: 'module2'}),
createModuleTransport({name: 'module3'}),
];
const resolves = {};
const resolver = {
wrapModule({name}) {
return new Promise(resolve => {
resolves[name] = resolve;
});
},
};
const promise = Promise.all(
moduleTransports.map(m =>
bundle.addModule(resolver, null, {isPolyfill: () => false}, m),
),
).then(() => {
expect(bundle.getModules()).toEqual(moduleTransports);
});
resolves.module2({code: ''});
resolves.module3({code: ''});
resolves.module1({code: ''});
return promise;
});
});
describe('sourcemap bundle', () => {
it('should create sourcemap', () => {
//TODO: #15357872 add a meaningful test here
});
it('should combine sourcemaps', () => {
const otherBundle = new Bundle({sourceMapUrl: 'test_url'});
return Promise.resolve()
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed foo;\n',
sourceCode: 'source foo',
map: {name: 'sourcemap foo'},
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed bar;\n',
sourceCode: 'source bar',
map: {name: 'sourcemap bar'},
sourcePath: 'bar path',
});
})
.then(() => {
return addModule({
bundle: otherBundle,
code: 'image module;\nimage module;',
virtual: true,
sourceCode: 'image module;\nimage module;',
sourcePath: 'image.png',
});
})
.then(() => {
otherBundle.setMainModuleId('foo');
otherBundle.finalize({
runBeforeMainModule: ['InitializeCore'],
runModule: true,
});
const sourceMap = otherBundle.getSourceMap({dev: true});
expect(sourceMap).toEqual({
file: 'test_url',
version: 3,
sections: [
{offset: {line: 0, column: 0}, map: {name: 'sourcemap foo'}},
{offset: {line: 2, column: 0}, map: {name: 'sourcemap bar'}},
{
offset: {
column: 0,
line: 4,
},
map: {
file: 'image.png',
mappings: 'AAAA;AACA;',
names: [],
sources: ['image.png'],
sourcesContent: ['image module;\nimage module;'],
version: 3,
},
},
{
offset: {
column: 0,
line: 6,
},
map: {
file: 'require-InitializeCore.js',
mappings: 'AAAA;',
names: [],
sources: ['require-InitializeCore.js'],
sourcesContent: [';require("InitializeCore");'],
version: 3,
},
},
{
offset: {
column: 0,
line: 7,
},
map: {
file: 'require-foo.js',
mappings: 'AAAA;',
names: [],
sources: ['require-foo.js'],
sourcesContent: [';require("foo");'],
version: 3,
},
},
],
});
});
});
});
describe('getAssets()', () => {
it('should save and return asset objects', () => {
var p = new Bundle({sourceMapUrl: 'test_url'});
var asset1 = {};
var asset2 = {};
p.addAsset(asset1);
p.addAsset(asset2);
p.finalize();
expect(p.getAssets()).toEqual([asset1, asset2]);
});
});
describe('getJSModulePaths()', () => {
it('should return module paths', () => {
var otherBundle = new Bundle({sourceMapUrl: 'test_url'});
return Promise.resolve()
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed foo;\n',
sourceCode: 'source foo',
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle: otherBundle,
code: 'image module;\nimage module;',
virtual: true,
sourceCode: 'image module;\nimage module;',
sourcePath: 'image.png',
});
})
.then(() => {
expect(otherBundle.getJSModulePaths()).toEqual(['foo path']);
});
});
});
describe('getEtag()', function() {
it('should return an etag', function() {
bundle.finalize({});
var eTag = crypto
.createHash('md5')
.update(bundle.getSource())
.digest('hex');
expect(bundle.getEtag()).toEqual(eTag);
});
});
describe('main module id:', function() {
it('can save a main module ID', function() {
const id = 'arbitrary module ID';
bundle.setMainModuleId(id);
expect(bundle.getMainModuleId()).toEqual(id);
});
});
describe('random access bundle groups:', () => {
let moduleTransports;
beforeEach(() => {
moduleTransports = [
transport('Product1', ['React', 'Relay']),
transport('React', ['ReactFoo', 'ReactBar']),
transport('ReactFoo', ['invariant']),
transport('invariant', []),
transport('ReactBar', ['cx']),
transport('cx', []),
transport('OtherFramework', ['OtherFrameworkFoo', 'OtherFrameworkBar']),
transport('OtherFrameworkFoo', ['invariant']),
transport('OtherFrameworkBar', ['crc32']),
transport('crc32', ['OtherFrameworkBar']),
];
});
it('can create a single group', () => {
bundle = createBundle([fsLocation('React')]);
const {groups} = bundle.getUnbundle();
expect(groups).toEqual(
new Map([
[
idFor('React'),
new Set(['ReactFoo', 'invariant', 'ReactBar', 'cx'].map(idFor)),
],
]),
);
});
it('can create two groups', () => {
bundle = createBundle([fsLocation('ReactFoo'), fsLocation('ReactBar')]);
const {groups} = bundle.getUnbundle();
expect(groups).toEqual(
new Map([
[idFor('ReactFoo'), new Set([idFor('invariant')])],
[idFor('ReactBar'), new Set([idFor('cx')])],
]),
);
});
it('can handle circular dependencies', () => {
bundle = createBundle([fsLocation('OtherFramework')]);
const {groups} = bundle.getUnbundle();
expect(groups).toEqual(
new Map([
[
idFor('OtherFramework'),
new Set(
[
'OtherFrameworkFoo',
'invariant',
'OtherFrameworkBar',
'crc32',
].map(idFor),
),
],
]),
);
});
it('omits modules that are contained by more than one group', () => {
bundle = createBundle([
fsLocation('React'),
fsLocation('OtherFramework'),
]);
expect(() => {
const {groups} = bundle.getUnbundle(); //eslint-disable-line no-unused-vars
}).toThrow(
new Error(
`Module ${fsLocation('invariant')} belongs to groups ${fsLocation('React')}` +
`, and ${fsLocation('OtherFramework')}. Ensure that each module is only part of one group.`,
),
);
});
it('ignores missing dependencies', () => {
bundle = createBundle([fsLocation('Product1')]);
const {groups} = bundle.getUnbundle();
expect(groups).toEqual(
new Map([
[
idFor('Product1'),
new Set(
['React', 'ReactFoo', 'invariant', 'ReactBar', 'cx'].map(idFor),
),
],
]),
);
});
it('throws for group roots that do not exist', () => {
bundle = createBundle([fsLocation('DoesNotExist')]);
expect(() => {
const {groups} = bundle.getUnbundle(); //eslint-disable-line no-unused-vars
}).toThrow(
new Error(
`Group root ${fsLocation('DoesNotExist')} is not part of the bundle`,
),
);
});
function idFor(name) {
const {map} = idFor;
if (!map) {
idFor.map = new Map([[name, 0]]);
idFor.next = 1;
return 0;
}
if (map.has(name)) {
return map.get(name);
}
const id = idFor.next++;
map.set(name, id);
return id;
}
function createBundle(ramGroups, options = {}) {
const b = new Bundle(Object.assign(options, {ramGroups}));
moduleTransports.forEach(t => addModule({bundle: b, ...t}));
b.finalize();
return b;
}
function fsLocation(name) {
return `/fs/${name}.js`;
}
function module(name) {
return {path: fsLocation(name)};
}
function transport(name, deps) {
return createModuleTransport({
name,
id: idFor(name),
sourcePath: fsLocation(name),
meta: {dependencyPairs: deps.map(d => [d, module(d)])},
});
}
});
});
function resolverFor(code, map) {
return {
wrapModule: () => Promise.resolve({code, map}),
};
}
function addModule({
bundle,
code,
sourceCode,
sourcePath,
map,
virtual,
polyfill,
meta,
id = '',
}) {
return bundle.addModule(
resolverFor(code, map),
null,
{isPolyfill: () => polyfill},
createModuleTransport({
code,
sourceCode,
sourcePath,
id,
map,
meta,
virtual,
polyfill,
}),
);
}
function createModuleTransport(data) {
return new ModuleTransport({
code: '',
sourceCode: '',
sourcePath: '',
id: 'id' in data ? data.id : '',
...data,
});
}
@@ -1,460 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.disableAutomock();
jest
.setMock('worker-farm', () => () => undefined)
.setMock('../../worker-farm', () => () => undefined)
.setMock('uglify-js')
.mock('image-size')
.mock('fs')
.mock('os')
.mock('assert')
.mock('progress')
.mock('../../node-haste/DependencyGraph')
.mock('../../JSTransformer')
.mock('../../Resolver')
.mock('../Bundle')
.mock('../HMRBundle')
.mock('../../Logger')
.mock('/path/to/transformer.js', () => ({}), {virtual: true})
;
var Bundler = require('../');
var Resolver = require('../../Resolver');
var defaults = require('../../defaults');
var sizeOf = require('image-size');
var fs = require('fs');
const os = require('os');
const path = require('path');
const {any, objectContaining} = expect;
var commonOptions = {
allowBundleUpdates: false,
assetExts: defaults.assetExts,
cacheVersion: 'smth',
extraNodeModules: {},
platforms: defaults.platforms,
resetCache: false,
sourceExts: defaults.sourceExts,
transformModulePath: '/path/to/transformer.js',
watch: false,
};
describe('Bundler', function() {
function createModule({
path,
id,
dependencies,
isAsset,
isJSON,
isPolyfill,
resolution,
}) {
return {
path,
resolution,
getDependencies: () => Promise.resolve(dependencies),
getName: () => Promise.resolve(id),
isJSON: () => isJSON,
isAsset: () => isAsset,
isPolyfill: () => isPolyfill,
read: () => ({
code: 'arbitrary',
source: 'arbitrary',
}),
};
}
var getDependencies;
var getModuleSystemDependencies;
var bundler;
var assetServer;
var modules;
var projectRoots;
beforeEach(function() {
os.cpus.mockReturnValue({length: 1});
// local directory on purpose, because it should not actually write
// anything to the disk during a unit test!
os.tmpDir.mockReturnValue(path.join(__dirname));
getDependencies = jest.fn();
getModuleSystemDependencies = jest.fn();
projectRoots = ['/root'];
Resolver.mockImplementation(function() {
return {
getDependencies,
getModuleSystemDependencies,
};
});
Resolver.load = jest.fn().mockImplementation(opts => Promise.resolve(new Resolver(opts)));
fs.__setMockFilesystem({
'path': {'to': {'transformer.js': ''}},
});
fs.statSync.mockImplementation(function() {
return {
isDirectory: () => true,
};
});
fs.readFile.mockImplementation(function(file, callback) {
callback(null, '{"json":true}');
});
assetServer = {
getAssetData: jest.fn(),
};
bundler = new Bundler({
...commonOptions,
projectRoots,
assetServer,
});
modules = [
createModule({id: 'foo', path: '/root/foo.js', dependencies: []}),
createModule({id: 'bar', path: '/root/bar.js', dependencies: []}),
createModule({
id: 'new_image.png',
path: '/root/img/new_image.png',
isAsset: true,
resolution: 2,
dependencies: [],
}),
createModule({
id: 'package/file.json',
path: '/root/file.json',
isJSON: true,
dependencies: [],
}),
];
getDependencies.mockImplementation((main, options, transformOptions) =>
Promise.resolve({
mainModuleId: 'foo',
dependencies: modules,
options: transformOptions,
getModuleId: () => 123,
getResolvedDependencyPairs: () => [],
})
);
getModuleSystemDependencies.mockImplementation(function() {
return [];
});
sizeOf.mockImplementation(function(path, cb) {
cb(null, {width: 50, height: 100});
});
});
it('gets the list of dependencies from the resolver', function() {
const entryFile = '/root/foo.js';
return bundler.getDependencies({entryFile, recursive: true}).then(() =>
// jest calledWith does not support jasmine.any
expect(getDependencies.mock.calls[0].slice(0, -2)).toEqual([
'/root/foo.js',
{dev: true, platform: undefined, recursive: true},
{
preloadedModules: undefined,
ramGroups: undefined,
transformer: {
dev: true,
minify: false,
platform: undefined,
transform: {
dev: true,
generateSourceMaps: false,
hot: false,
inlineRequires: false,
platform: undefined,
projectRoot: projectRoots[0],
},
},
},
])
);
});
it('allows overriding the platforms array', () => {
expect(bundler._opts.platforms).toEqual(['ios', 'android', 'windows', 'web']);
const b = new Bundler({
...commonOptions,
projectRoots,
assetServer,
platforms: ['android', 'vr'],
});
expect(b._opts.platforms).toEqual(['android', 'vr']);
});
describe('.bundle', () => {
const mockAsset = {
scales: [1, 2, 3],
files: [
'/root/img/img.png',
'/root/img/img@2x.png',
'/root/img/img@3x.png',
],
hash: 'i am a hash',
name: 'img',
type: 'png',
};
beforeEach(() => {
assetServer.getAssetData
.mockImplementation(() => Promise.resolve(mockAsset));
});
it('creates a bundle', function() {
return bundler.bundle({
entryFile: '/root/foo.js',
runBeforeMainModule: [],
runModule: true,
sourceMapUrl: 'source_map_url',
}).then(bundle => {
const ithAddedModule = i => bundle.addModule.mock.calls[i][2].path;
expect(ithAddedModule(0)).toEqual('/root/foo.js');
expect(ithAddedModule(1)).toEqual('/root/bar.js');
expect(ithAddedModule(2)).toEqual('/root/img/new_image.png');
expect(ithAddedModule(3)).toEqual('/root/file.json');
expect(bundle.finalize.mock.calls[0]).toEqual([{
runModule: true,
runBeforeMainModule: [],
allowUpdates: false,
}]);
expect(bundle.addAsset.mock.calls[0]).toEqual([{
__packager_asset: true,
fileSystemLocation: '/root/img',
httpServerLocation: '/assets/img',
width: 50,
height: 100,
scales: [1, 2, 3],
files: [
'/root/img/img.png',
'/root/img/img@2x.png',
'/root/img/img@3x.png',
],
hash: 'i am a hash',
name: 'img',
type: 'png',
}]);
// TODO(amasad) This fails with 0 != 5 in OSS
//expect(ProgressBar.prototype.tick.mock.calls.length).toEqual(modules.length);
});
});
it('loads and runs asset plugins', function() {
jest.mock('mockPlugin1', () => {
return asset => {
asset.extraReverseHash = asset.hash.split('').reverse().join('');
return asset;
};
}, {virtual: true});
jest.mock('asyncMockPlugin2', () => {
return asset => {
expect(asset.extraReverseHash).toBeDefined();
return new Promise(resolve => {
asset.extraPixelCount = asset.width * asset.height;
resolve(asset);
});
};
}, {virtual: true});
return bundler.bundle({
entryFile: '/root/foo.js',
runBeforeMainModule: [],
runModule: true,
sourceMapUrl: 'source_map_url',
assetPlugins: ['mockPlugin1', 'asyncMockPlugin2'],
}).then(bundle => {
expect(bundle.addAsset.mock.calls[0]).toEqual([{
__packager_asset: true,
fileSystemLocation: '/root/img',
httpServerLocation: '/assets/img',
width: 50,
height: 100,
scales: [1, 2, 3],
files: [
'/root/img/img.png',
'/root/img/img@2x.png',
'/root/img/img@3x.png',
],
hash: 'i am a hash',
name: 'img',
type: 'png',
extraReverseHash: 'hsah a ma i',
extraPixelCount: 5000,
}]);
});
});
it('calls the module post-processing function', () => {
const postProcessModules = jest.fn().mockImplementation((ms, e) => ms);
const b = new Bundler({
...commonOptions,
postProcessModules,
projectRoots,
assetServer,
});
const dev = false;
const minify = true;
const platform = 'arbitrary';
const entryFile = '/root/foo.js';
return b.bundle({
dev,
entryFile,
minify,
platform,
runBeforeMainModule: [],
runModule: true,
sourceMapUrl: 'source_map_url',
}).then(() => {
expect(postProcessModules)
.toBeCalledWith(
modules.map(x => objectContaining({
name: any(String),
id: any(Number),
code: any(String),
sourceCode: any(String),
sourcePath: x.path,
meta: any(Object),
polyfill: !!x.isPolyfill(),
})),
entryFile,
{dev, minify, platform},
);
});
});
it('respects the order of modules returned by the post-processing function', () => {
const postProcessModules = jest.fn().mockImplementation((ms, e) => ms.reverse());
const b = new Bundler({
...commonOptions,
postProcessModules,
projectRoots,
assetServer,
});
const entryFile = '/root/foo.js';
return b.bundle({
entryFile,
runBeforeMainModule: [],
runModule: true,
sourceMapUrl: 'source_map_url',
}).then(bundle => {
const ithAddedModule = i => bundle.addModule.mock.calls[i][2].path;
[
'/root/file.json',
'/root/img/new_image.png',
'/root/bar.js',
'/root/foo.js',
].forEach((path, ix) => expect(ithAddedModule(ix)).toEqual(path));
});
});
});
describe('.getOrderedDependencyPaths', () => {
beforeEach(() => {
assetServer.getAssetData.mockImplementation(function(relPath) {
if (relPath === 'img/new_image.png') {
return Promise.resolve({
scales: [1, 2, 3],
files: [
'/root/img/new_image.png',
'/root/img/new_image@2x.png',
'/root/img/new_image@3x.png',
],
hash: 'i am a hash',
name: 'img',
type: 'png',
});
} else if (relPath === 'img/new_image2.png') {
return Promise.resolve({
scales: [1, 2, 3],
files: [
'/root/img/new_image2.png',
'/root/img/new_image2@2x.png',
'/root/img/new_image2@3x.png',
],
hash: 'i am a hash',
name: 'img',
type: 'png',
});
}
throw new Error('unknown image ' + relPath);
});
});
it('should get the concrete list of all dependency files', () => {
modules.push(
createModule({
id: 'new_image2.png',
path: '/root/img/new_image2.png',
isAsset: true,
resolution: 2,
dependencies: [],
}),
);
return bundler.getOrderedDependencyPaths('/root/foo.js', true)
.then(paths => expect(paths).toEqual([
'/root/foo.js',
'/root/bar.js',
'/root/img/new_image.png',
'/root/img/new_image@2x.png',
'/root/img/new_image@3x.png',
'/root/file.json',
'/root/img/new_image2.png',
'/root/img/new_image2@2x.png',
'/root/img/new_image2@3x.png',
]));
});
describe('number of workers', () => {
beforeEach(() => {
delete process.env.REACT_NATIVE_MAX_WORKERS;
});
afterEach(() => {
delete process.env.REACT_NATIVE_MAX_WORKERS;
});
it('return correct number of workers', () => {
os.cpus.mockReturnValue({length: 1});
expect(Bundler.getMaxWorkerCount()).toBe(1);
os.cpus.mockReturnValue({length: 8});
expect(Bundler.getMaxWorkerCount()).toBe(6);
os.cpus.mockReturnValue({length: 24});
expect(Bundler.getMaxWorkerCount()).toBe(14);
process.env.REACT_NATIVE_MAX_WORKERS = 5;
expect(Bundler.getMaxWorkerCount()).toBe(5);
});
});
});
});
-873
View File
@@ -1,873 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const assert = require('assert');
const crypto = require('crypto');
const debug = require('debug')('RNP:Bundler');
const emptyFunction = require('fbjs/lib/emptyFunction');
const fs = require('fs');
const Transformer = require('../JSTransformer');
const Resolver = require('../Resolver');
const Bundle = require('./Bundle');
const HMRBundle = require('./HMRBundle');
const ModuleTransport = require('../lib/ModuleTransport');
const imageSize = require('image-size');
const path = require('path');
const denodeify = require('denodeify');
const defaults = require('../defaults');
const os = require('os');
const invariant = require('fbjs/lib/invariant');
const toLocalPath = require('../node-haste/lib/toLocalPath');
const {generateAssetTransformResult, isAssetTypeAnImage} = require('./util');
const {
sep: pathSeparator,
join: joinPath,
dirname: pathDirname,
extname,
} = require('path');
const VERSION = require('../../package.json').version;
import type AssetServer from '../AssetServer';
import type Module, {HasteImpl} from '../node-haste/Module';
import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse';
import type {MappingsMap} from '../lib/SourceMap';
import type {Options as JSTransformerOptions} from '../JSTransformer/worker';
import type {Reporter} from '../lib/reporting';
import type {TransformCache} from '../lib/TransformCaching';
import type {GlobalTransformCache} from '../lib/GlobalTransformCache';
export type BundlingOptions = {|
+preloadedModules: ?{[string]: true} | false,
+ramGroups: ?Array<string>,
+transformer: JSTransformerOptions,
|};
export type ExtraTransformOptions = {
+preloadedModules?: {[path: string]: true} | false,
+ramGroups?: Array<string>,
+transform?: {+inlineRequires?: {+blacklist: {[string]: true}} | boolean},
};
export type GetTransformOptionsOpts = {|
dev: boolean,
hot: boolean,
platform: ?string,
|};
export type GetTransformOptions = (
mainModuleName: string,
options: GetTransformOptionsOpts,
getDependenciesOf: string => Promise<Array<string>>,
) => Promise<ExtraTransformOptions>;
export type AssetDescriptor = {
+__packager_asset: boolean,
+httpServerLocation: string,
+width: ?number,
+height: ?number,
+scales: Array<number>,
+hash: string,
+name: string,
+type: string,
};
export type ExtendedAssetDescriptor = AssetDescriptor & {
+fileSystemLocation: string,
+files: Array<string>,
};
const sizeOf = denodeify(imageSize);
const {
createActionStartEntry,
createActionEndEntry,
log,
} = require('../Logger');
export type PostProcessModulesOptions = {|
dev: boolean,
minify: boolean,
platform: string,
|};
export type PostProcessModules = (
modules: Array<ModuleTransport>,
entryFile: string,
options: PostProcessModulesOptions,
) => Array<ModuleTransport>;
export type PostMinifyProcess = ({
code: string,
map: MappingsMap,
}) => {code: string, map: MappingsMap};
type Options = {|
+allowBundleUpdates: boolean,
+assetExts: Array<string>,
+assetServer: AssetServer,
+blacklistRE?: RegExp,
+cacheVersion: string,
+extraNodeModules: {},
+getTransformOptions?: GetTransformOptions,
+globalTransformCache: ?GlobalTransformCache,
+hasteImpl?: HasteImpl,
+platforms: Array<string>,
+polyfillModuleNames: Array<string>,
+postMinifyProcess: PostMinifyProcess,
+postProcessModules?: PostProcessModules,
+projectRoots: $ReadOnlyArray<string>,
+providesModuleNodeModules?: Array<string>,
+reporter: Reporter,
+resetCache: boolean,
+sourceExts: Array<string>,
+transformCache: TransformCache,
+transformModulePath: string,
+transformTimeoutInterval: ?number,
+watch: boolean,
+workerPath: ?string,
|};
const {hasOwnProperty} = Object;
class Bundler {
_opts: Options;
_getModuleId: (opts: Module) => number;
_transformer: Transformer;
_resolverPromise: Promise<Resolver>;
_projectRoots: $ReadOnlyArray<string>;
_assetServer: AssetServer;
_getTransformOptions: void | GetTransformOptions;
constructor(opts: Options) {
this._opts = opts;
opts.projectRoots.forEach(verifyRootExists);
const transformModuleStr = fs.readFileSync(opts.transformModulePath);
const transformModuleHash =
crypto.createHash('sha1').update(transformModuleStr).digest('hex');
const stableProjectRoots = opts.projectRoots.map(p => {
return path.relative(path.join(__dirname, '../../../..'), p);
});
const cacheKeyParts = [
'react-packager-cache',
VERSION,
opts.cacheVersion,
stableProjectRoots.join(',').split(pathSeparator).join('-'),
transformModuleHash,
];
this._getModuleId = createModuleIdFactory();
let getCacheKey = (options: mixed) => '';
if (opts.transformModulePath) {
/* $FlowFixMe: dynamic requires prevent static typing :'( */
const transformer = require(opts.transformModulePath);
if (typeof transformer.getCacheKey !== 'undefined') {
getCacheKey = transformer.getCacheKey;
}
}
const transformCacheKey = crypto.createHash('sha1').update(
cacheKeyParts.join('$'),
).digest('hex');
debug(`Using transform cache key "${transformCacheKey}"`);
const maxWorkerCount = Bundler.getMaxWorkerCount();
this._transformer = new Transformer(
opts.transformModulePath,
maxWorkerCount,
{
stdoutChunk: chunk => opts.reporter.update({type: 'worker_stdout_chunk', chunk}),
stderrChunk: chunk => opts.reporter.update({type: 'worker_stderr_chunk', chunk}),
},
opts.workerPath,
);
const getTransformCacheKey = options => {
return transformCacheKey + getCacheKey(options);
};
this._resolverPromise = Resolver.load({
assetExts: opts.assetExts,
blacklistRE: opts.blacklistRE,
extraNodeModules: opts.extraNodeModules,
getTransformCacheKey,
globalTransformCache: opts.globalTransformCache,
hasteImpl: opts.hasteImpl,
maxWorkerCount,
minifyCode: this._transformer.minify,
postMinifyProcess: this._opts.postMinifyProcess,
platforms: new Set(opts.platforms),
polyfillModuleNames: opts.polyfillModuleNames,
projectRoots: opts.projectRoots,
providesModuleNodeModules:
opts.providesModuleNodeModules || defaults.providesModuleNodeModules,
reporter: opts.reporter,
resetCache: opts.resetCache,
sourceExts: opts.sourceExts,
transformCode:
(module, code, transformCodeOptions) => this._transformer.transformFile(
module.path,
module.localPath,
code,
transformCodeOptions,
),
transformCache: opts.transformCache,
watch: opts.watch,
});
this._projectRoots = opts.projectRoots;
this._assetServer = opts.assetServer;
this._getTransformOptions = opts.getTransformOptions;
}
end() {
this._transformer.kill();
return this._resolverPromise.then(
resolver => resolver.getDependencyGraph().getWatcher().end(),
);
}
bundle(options: {
dev: boolean,
minify: boolean,
unbundle: boolean,
sourceMapUrl: ?string,
}): Promise<Bundle> {
const {dev, minify, unbundle} = options;
return this._resolverPromise.then(
resolver => resolver.getModuleSystemDependencies({dev, unbundle}),
).then(moduleSystemDeps => this._bundle({
...options,
bundle: new Bundle({dev, minify, sourceMapUrl: options.sourceMapUrl}),
moduleSystemDeps,
}));
}
_sourceHMRURL(platform: ?string, hmrpath: string) {
return this._hmrURL(
'',
platform,
'bundle',
hmrpath,
);
}
_sourceMappingHMRURL(platform: ?string, hmrpath: string) {
// Chrome expects `sourceURL` when eval'ing code
return this._hmrURL(
'\/\/# sourceURL=',
platform,
'map',
hmrpath,
);
}
_hmrURL(prefix: string, platform: ?string, extensionOverride: string, filePath: string) {
const matchingRoot = this._projectRoots.find(root => filePath.startsWith(root));
if (!matchingRoot) {
throw new Error('No matching project root for ' + filePath);
}
// Replaces '\' with '/' for Windows paths.
if (pathSeparator === '\\') {
filePath = filePath.replace(/\\/g, '/');
}
const extensionStart = filePath.lastIndexOf('.');
const resource = filePath.substring(
matchingRoot.length,
extensionStart !== -1 ? extensionStart : undefined,
);
return (
prefix + resource +
'.' + extensionOverride + '?' +
'platform=' + (platform || '') + '&runModule=false&entryModuleOnly=true&hot=true'
);
}
hmrBundle(options: {platform: ?string}, host: string, port: number): Promise<HMRBundle> {
return this._bundle({
...options,
bundle: new HMRBundle({
sourceURLFn: this._sourceHMRURL.bind(this, options.platform),
sourceMappingURLFn: this._sourceMappingHMRURL.bind(
this,
options.platform,
),
}),
hot: true,
dev: true,
});
}
_bundle({
assetPlugins,
bundle,
dev,
entryFile,
entryModuleOnly,
generateSourceMaps,
hot,
isolateModuleIDs,
minify,
moduleSystemDeps = [],
onProgress,
platform,
resolutionResponse,
runBeforeMainModule,
runModule,
unbundle,
}: {
assetPlugins?: Array<string>,
bundle: Bundle | HMRBundle,
dev: boolean,
entryFile?: string,
entryModuleOnly?: boolean,
generateSourceMaps?: boolean,
hot?: boolean,
isolateModuleIDs?: boolean,
minify?: boolean,
moduleSystemDeps?: Array<Module>,
onProgress?: () => void,
platform?: ?string,
resolutionResponse?: ResolutionResponse<Module, BundlingOptions>,
runBeforeMainModule?: boolean,
runModule?: boolean,
unbundle?: boolean,
}) {
const onResolutionResponse = (response: ResolutionResponse<Module, BundlingOptions>) => {
/* $FlowFixMe: looks like ResolutionResponse is monkey-patched
* with `getModuleId`. */
bundle.setMainModuleId(response.getModuleId(getMainModule(response)));
if (entryModuleOnly && entryFile) {
response.dependencies = response.dependencies.filter(module =>
module.path.endsWith(entryFile || '')
);
} else {
response.dependencies = moduleSystemDeps.concat(response.dependencies);
}
};
const finalizeBundle = ({bundle: finalBundle, transformedModules, response, modulesByName}: {
bundle: Bundle,
transformedModules: Array<{module: Module, transformed: ModuleTransport}>,
response: ResolutionResponse<Module, BundlingOptions>,
modulesByName: {[name: string]: Module},
}) =>
this._resolverPromise.then(resolver => Promise.all(
transformedModules.map(({module, transformed}) =>
finalBundle.addModule(resolver, response, module, transformed)
)
)).then(() => {
const runBeforeMainModuleIds = Array.isArray(runBeforeMainModule)
? runBeforeMainModule
.map(name => modulesByName[name])
.filter(Boolean)
.map(response.getModuleId)
: undefined;
finalBundle.finalize({
runModule,
runBeforeMainModule: runBeforeMainModuleIds,
allowUpdates: this._opts.allowBundleUpdates,
});
return finalBundle;
});
return this._buildBundle({
entryFile,
dev,
minify,
platform,
bundle,
hot,
unbundle,
resolutionResponse,
onResolutionResponse,
finalizeBundle,
isolateModuleIDs,
generateSourceMaps,
assetPlugins,
onProgress,
});
}
_buildBundle({
entryFile,
dev,
minify,
platform,
bundle,
hot,
unbundle,
resolutionResponse,
isolateModuleIDs,
generateSourceMaps,
assetPlugins,
onResolutionResponse = emptyFunction,
onModuleTransformed = emptyFunction,
finalizeBundle = emptyFunction,
onProgress = emptyFunction,
}: *) {
const transformingFilesLogEntry =
log(createActionStartEntry({
action_name: 'Transforming files',
entry_point: entryFile,
environment: dev ? 'dev' : 'prod',
}));
const modulesByName = Object.create(null);
if (!resolutionResponse) {
resolutionResponse = this.getDependencies({
entryFile,
dev,
platform,
hot,
onProgress,
minify,
isolateModuleIDs,
generateSourceMaps: unbundle || minify || generateSourceMaps,
});
}
return Promise.all(
[this._resolverPromise, resolutionResponse],
).then(([resolver, response]) => {
bundle.setRamGroups(response.options.ramGroups);
log(createActionEndEntry(transformingFilesLogEntry));
onResolutionResponse(response);
// get entry file complete path (`entryFile` is a local path, i.e. relative to roots)
let entryFilePath;
if (response.dependencies.length > 1) { // skip HMR requests
const numModuleSystemDependencies =
resolver.getModuleSystemDependencies({dev, unbundle}).length;
const dependencyIndex =
(response.numPrependedDependencies || 0) + numModuleSystemDependencies;
if (dependencyIndex in response.dependencies) {
entryFilePath = response.dependencies[dependencyIndex].path;
}
}
const modulesByTransport: Map<ModuleTransport, Module> = new Map();
const toModuleTransport: Module => Promise<ModuleTransport> =
module =>
this._toModuleTransport({
module,
bundle,
entryFilePath,
assetPlugins,
options: response.options,
/* $FlowFixMe: `getModuleId` is monkey-patched */
getModuleId: (response.getModuleId: () => number),
dependencyPairs: response.getResolvedDependencyPairs(module),
}).then(transformed => {
modulesByTransport.set(transformed, module);
modulesByName[transformed.name] = module;
onModuleTransformed({
module,
response,
bundle,
transformed,
});
return transformed;
});
const p = this._opts.postProcessModules;
const postProcess = p
? modules => p(modules, entryFile, {dev, minify, platform})
: null;
return Promise.all(response.dependencies.map(toModuleTransport))
.then(postProcess)
.then(moduleTransports => {
const transformedModules = moduleTransports.map(transformed => ({
module: modulesByTransport.get(transformed),
transformed,
}));
return finalizeBundle({bundle, transformedModules, response, modulesByName});
}).then(() => bundle);
});
}
getShallowDependencies({
entryFile,
platform,
dev = true,
minify = !dev,
hot = false,
generateSourceMaps = false,
}: {
entryFile: string,
platform: ?string,
dev?: boolean,
minify?: boolean,
hot?: boolean,
generateSourceMaps?: boolean,
}): Promise<Array<Module>> {
return this.getTransformOptions(
entryFile,
{
dev,
generateSourceMaps,
hot,
minify,
platform,
projectRoots: this._projectRoots,
},
).then(bundlingOptions =>
this._resolverPromise.then(resolver =>
resolver.getShallowDependencies(entryFile, bundlingOptions.transformer),
)
);
}
getModuleForPath(entryFile: string): Promise<Module> {
return this._resolverPromise.then(resolver => resolver.getModuleForPath(entryFile));
}
async getDependencies({
entryFile,
platform,
dev = true,
minify = !dev,
hot = false,
recursive = true,
generateSourceMaps = false,
isolateModuleIDs = false,
onProgress,
}: {
entryFile: string,
platform: ?string,
dev?: boolean,
minify?: boolean,
hot?: boolean,
recursive?: boolean,
generateSourceMaps?: boolean,
isolateModuleIDs?: boolean,
onProgress?: ?(finishedModules: number, totalModules: number) => mixed,
}): Promise<ResolutionResponse<Module, BundlingOptions>> {
const bundlingOptions: BundlingOptions = await this.getTransformOptions(
entryFile,
{
dev,
platform,
hot,
generateSourceMaps,
minify,
projectRoots: this._projectRoots,
},
);
const resolver = await this._resolverPromise;
const response = await resolver.getDependencies(
entryFile,
{dev, platform, recursive},
bundlingOptions,
onProgress,
isolateModuleIDs ? createModuleIdFactory() : this._getModuleId,
);
return response;
}
getOrderedDependencyPaths({entryFile, dev, platform, minify, generateSourceMaps}: {
+entryFile: string,
+dev: boolean,
+platform: string,
+minify: boolean,
+generateSourceMaps: boolean,
}) {
return this.getDependencies({entryFile, dev, platform, minify, generateSourceMaps}).then(
({dependencies}) => {
const ret = [];
const promises = [];
const placeHolder = {};
dependencies.forEach(dep => {
if (dep.isAsset()) {
const localPath = toLocalPath(
this._projectRoots,
dep.path
);
promises.push(
this._assetServer.getAssetData(localPath, platform)
);
ret.push(placeHolder);
} else {
ret.push(dep.path);
}
});
return Promise.all(promises).then(assetsData => {
assetsData.forEach(({files}) => {
const index = ret.indexOf(placeHolder);
ret.splice(index, 1, ...files);
});
return ret;
});
}
);
}
_toModuleTransport({
module,
bundle,
entryFilePath,
options,
getModuleId,
dependencyPairs,
assetPlugins,
}: {
module: Module,
bundle: Bundle,
entryFilePath: string,
options: BundlingOptions,
getModuleId: (module: Module) => number,
dependencyPairs: Array<[string, Module]>,
assetPlugins: Array<string>,
}): Promise<ModuleTransport> {
let moduleTransport;
const moduleId = getModuleId(module);
const transformOptions = options.transformer;
if (module.isAsset()) {
moduleTransport = this._generateAssetModule(
bundle, module, moduleId, assetPlugins, transformOptions.platform);
}
if (moduleTransport) {
return Promise.resolve(moduleTransport);
}
return Promise.all([
module.getName(),
module.read(transformOptions),
]).then((
[name, {code, dependencies, dependencyOffsets, map, source}]
) => {
const {preloadedModules} = options;
const isPolyfill = module.isPolyfill();
const preloaded =
module.path === entryFilePath ||
isPolyfill ||
preloadedModules && hasOwnProperty.call(preloadedModules, module.path);
return new ModuleTransport({
name,
id: moduleId,
code,
map,
meta: {dependencies, dependencyOffsets, preloaded, dependencyPairs},
polyfill: isPolyfill,
sourceCode: source,
sourcePath: module.path,
});
});
}
_generateAssetObjAndCode(
module: Module,
assetPlugins: Array<string>,
platform: ?string = null,
) {
const localPath = toLocalPath(this._projectRoots, module.path);
var assetUrlPath = joinPath('/assets', pathDirname(localPath));
// On Windows, change backslashes to slashes to get proper URL path from file path.
if (pathSeparator === '\\') {
assetUrlPath = assetUrlPath.replace(/\\/g, '/');
}
const isImage = isAssetTypeAnImage(extname(module.path).slice(1));
return this._assetServer.getAssetData(localPath, platform).then(assetData => {
return Promise.all([isImage ? sizeOf(assetData.files[0]) : null, assetData]);
}).then(res => {
const dimensions = res[0];
const assetData = res[1];
const scale = assetData.scales[0];
const asset = {
__packager_asset: true,
fileSystemLocation: pathDirname(module.path),
httpServerLocation: assetUrlPath,
width: dimensions ? dimensions.width / scale : undefined,
height: dimensions ? dimensions.height / scale : undefined,
scales: assetData.scales,
files: assetData.files,
hash: assetData.hash,
name: assetData.name,
type: assetData.type,
};
return this._applyAssetPlugins(assetPlugins, asset);
}).then(asset => {
const {code, dependencies, dependencyOffsets} = generateAssetTransformResult(asset);
return {
asset,
code,
meta: {dependencies, dependencyOffsets, preloaded: null},
};
});
}
_applyAssetPlugins(
assetPlugins: Array<string>,
asset: ExtendedAssetDescriptor,
) {
if (!assetPlugins.length) {
return asset;
}
const [currentAssetPlugin, ...remainingAssetPlugins] = assetPlugins;
/* $FlowFixMe: dynamic requires prevent static typing :'( */
const assetPluginFunction = require(currentAssetPlugin);
const result = assetPluginFunction(asset);
// If the plugin was an async function, wait for it to fulfill before
// applying the remaining plugins
if (typeof result.then === 'function') {
return result.then(resultAsset =>
this._applyAssetPlugins(remainingAssetPlugins, resultAsset)
);
} else {
return this._applyAssetPlugins(remainingAssetPlugins, result);
}
}
_generateAssetModule(
bundle: Bundle,
module: Module,
moduleId: number,
assetPlugins: Array<string> = [],
platform: ?string = null,
) {
return Promise.all([
module.getName(),
this._generateAssetObjAndCode(module, assetPlugins, platform),
]).then(([name, {asset, code, meta}]) => {
bundle.addAsset(asset);
return new ModuleTransport({
name,
id: moduleId,
code,
meta,
sourceCode: code,
sourcePath: module.path,
virtual: true,
});
});
}
async getTransformOptions(
mainModuleName: string,
options: {|
dev: boolean,
generateSourceMaps: boolean,
hot: boolean,
minify: boolean,
platform: ?string,
projectRoots: $ReadOnlyArray<string>,
|},
): Promise<BundlingOptions> {
const getDependencies = (entryFile: string) =>
this.getDependencies({...options, entryFile})
.then(r => r.dependencies.map(d => d.path));
const {dev, hot, platform} = options;
const extraOptions: ExtraTransformOptions = this._getTransformOptions
? await this._getTransformOptions(mainModuleName, {dev, hot, platform}, getDependencies)
: {};
const {transform = {}} = extraOptions;
return {
transformer: {
dev,
minify: options.minify,
platform,
transform: {
dev,
generateSourceMaps: options.generateSourceMaps,
hot,
inlineRequires: transform.inlineRequires || false,
platform,
projectRoot: options.projectRoots[0],
},
},
preloadedModules: extraOptions.preloadedModules,
ramGroups: extraOptions.ramGroups,
};
}
getResolver(): Promise<Resolver> {
return this._resolverPromise;
}
/**
* Unless overriden, we use a diminishing amount of workers per core, because
* using more and more of them does not scale much. Ex. 6 workers for 8
* cores, or 14 workers for 24 cores.
*/
static getMaxWorkerCount() {
const cores = os.cpus().length;
const envStr = process.env.REACT_NATIVE_MAX_WORKERS;
if (envStr == null) {
return Math.max(1, Math.ceil(cores * (0.5 + 0.5 * Math.exp(-cores * 0.07)) - 1));
}
const envCount = parseInt(process.env.REACT_NATIVE_MAX_WORKERS, 10);
invariant(
Number.isInteger(envCount),
'environment variable `REACT_NATIVE_MAX_WORKERS` must be a valid integer',
);
return Math.min(cores, envCount);
}
}
function verifyRootExists(root) {
// Verify that the root exists.
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
}
function createModuleIdFactory() {
const fileToIdMap = Object.create(null);
let nextId = 0;
return ({path: modulePath}) => {
if (!(modulePath in fileToIdMap)) {
fileToIdMap[modulePath] = nextId;
nextId += 1;
}
return fileToIdMap[modulePath];
};
}
function getMainModule({dependencies, numPrependedDependencies = 0}) {
return dependencies[numPrependedDependencies];
}
module.exports = Bundler;
@@ -1,108 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const encode = require('./encode');
const MAX_SEGMENT_LENGTH = 7;
const ONE_MEG = 1024 * 1024;
const COMMA = 0x2c;
const SEMICOLON = 0x3b;
/**
* Efficient builder for base64 VLQ mappings strings.
*
* This class uses a buffer that is preallocated with one megabyte and is
* reallocated dynamically as needed, doubling its size.
*
* Encoding never creates any complex value types (strings, objects), and only
* writes character values to the buffer.
*
* For details about source map terminology and specification, check
* https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
*/
class B64Builder {
buffer: Buffer;
pos: number;
hasSegment: boolean;
constructor() {
this.buffer = new Buffer(ONE_MEG);
this.pos = 0;
this.hasSegment = false;
}
/**
* Adds `n` markers for generated lines to the mappings.
*/
markLines(n: number) {
if (n < 1) {
return this;
}
this.hasSegment = false;
if (this.pos + n >= this.buffer.length) {
this._realloc();
}
while (n--) {
this.buffer[this.pos++] = SEMICOLON;
}
return this;
}
/**
* Starts a segment at the specified column offset in the current line.
*/
startSegment(column: number) {
if (this.hasSegment) {
this._writeByte(COMMA);
} else {
this.hasSegment = true;
}
this.append(column);
return this;
}
/**
* Appends a single number to the mappings.
*/
append(value: number) {
if (this.pos + MAX_SEGMENT_LENGTH >= this.buffer.length) {
this._realloc();
}
this.pos = encode(value, this.buffer, this.pos);
return this;
}
/**
* Returns the string representation of the mappings.
*/
toString() {
return this.buffer.toString('ascii', 0, this.pos);
}
_writeByte(byte: number) {
if (this.pos === this.buffer.length) {
this._realloc();
}
this.buffer[this.pos++] = byte;
}
_realloc() {
const {buffer} = this;
this.buffer = new Buffer(buffer.length * 2);
buffer.copy(this.buffer);
}
}
module.exports = B64Builder;
@@ -1,195 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const B64Builder = require('./B64Builder');
import type {MappingsMap} from '../../lib/SourceMap';
/**
* Generates a source map from raw mappings.
*
* Raw mappings are a set of 2, 4, or five elements:
*
* - line and column number in the generated source
* - line and column number in the original source
* - symbol name in the original source
*
* Mappings have to be passed in the order appearance in the generated source.
*/
class Generator {
builder: B64Builder;
last: {|
generatedColumn: number,
generatedLine: number,
name: number,
source: number,
sourceColumn: number,
sourceLine: number,
|};
names: IndexedSet;
source: number;
sources: Array<string>;
sourcesContent: Array<?string>;
constructor() {
this.builder = new B64Builder();
this.last = {
generatedColumn: 0,
generatedLine: 1, // lines are passed in 1-indexed
name: 0,
source: 0,
sourceColumn: 0,
sourceLine: 1,
};
this.names = new IndexedSet();
this.source = -1;
this.sources = [];
this.sourcesContent = [];
}
/**
* Mark the beginning of a new source file.
*/
startFile(file: string, code: string) {
this.source = this.sources.push(file) - 1;
this.sourcesContent.push(code);
}
/**
* Mark the end of the current source file
*/
endFile() {
this.source = -1;
}
/**
* Adds a mapping for generated code without a corresponding source location.
*/
addSimpleMapping(generatedLine: number, generatedColumn: number): void {
const last = this.last;
if (this.source === -1 ||
generatedLine === last.generatedLine &&
generatedColumn < last.generatedColumn ||
generatedLine < last.generatedLine) {
const msg = this.source === -1
? 'Cannot add mapping before starting a file with `addFile()`'
: 'Mapping is for a position preceding an earlier mapping';
throw new Error(msg);
}
if (generatedLine > last.generatedLine) {
this.builder.markLines(generatedLine - last.generatedLine);
last.generatedLine = generatedLine;
last.generatedColumn = 0;
}
this.builder.startSegment(generatedColumn - last.generatedColumn);
last.generatedColumn = generatedColumn;
}
/**
* Adds a mapping for generated code with a corresponding source location.
*/
addSourceMapping(
generatedLine: number,
generatedColumn: number,
sourceLine: number,
sourceColumn: number,
): void {
this.addSimpleMapping(generatedLine, generatedColumn);
const last = this.last;
this.builder
.append(this.source - last.source)
.append(sourceLine - last.sourceLine)
.append(sourceColumn - last.sourceColumn);
last.source = this.source;
last.sourceColumn = sourceColumn;
last.sourceLine = sourceLine;
}
/**
* Adds a mapping for code with a corresponding source location + symbol name.
*/
addNamedSourceMapping(
generatedLine: number,
generatedColumn: number,
sourceLine: number,
sourceColumn: number,
name: string,
): void {
this.addSourceMapping(
generatedLine, generatedColumn, sourceLine, sourceColumn);
const last = this.last;
const nameIndex = this.names.indexFor(name);
this.builder.append(nameIndex - last.name);
last.name = nameIndex;
}
/**
* Return the source map as object.
*/
toMap(file?: string): MappingsMap {
return {
version: 3,
file,
sources: this.sources.slice(),
sourcesContent: this.sourcesContent.slice(),
names: this.names.items(),
mappings: this.builder.toString(),
};
}
/**
* Return the source map as string.
*
* This is ~2.5x faster than calling `JSON.stringify(generator.toMap())`
*/
toString(file?: string): string {
return ('{' +
'"version":3,' +
(file ? `"file":${JSON.stringify(file)},` : '') +
`"sources":${JSON.stringify(this.sources)},` +
`"sourcesContent":${JSON.stringify(this.sourcesContent)},` +
`"names":${JSON.stringify(this.names.items())},` +
`"mappings":"${this.builder.toString()}"` +
'}');
}
}
class IndexedSet {
map: Map<string, number>;
nextIndex: number;
constructor() {
this.map = new Map();
this.nextIndex = 0;
}
indexFor(x: string) {
let index = this.map.get(x);
if (index == null) {
index = this.nextIndex++;
this.map.set(x, index);
}
return index;
}
items() {
return Array.from(this.map.keys());
}
}
module.exports = Generator;
@@ -1,126 +0,0 @@
/**
* Copyright (c) 2017-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';
jest.disableAutomock();
const B64Builder = require('../B64Builder');
let builder;
beforeEach(() => {
builder = new B64Builder();
});
it('exposes a fluent interface', () => {
expect(builder.markLines(0)).toBe(builder);
expect(builder.markLines(3)).toBe(builder);
expect(builder.startSegment()).toBe(builder);
expect(builder.append(4)).toBe(builder);
});
it('can create an empty string', () => {
expect(builder.toString()).toEqual('');
});
it('can mark a new line in the generated code', () => {
builder.markLines(1);
expect(builder.toString()).toEqual(';');
});
it('can mark multiple new lines in the generated code', () => {
builder.markLines(4);
expect(builder.toString()).toEqual(';;;;');
});
it('can mark zero new lines in the generated code', () => {
builder.markLines(0);
expect(builder.toString()).toEqual('');
});
it('does not add commas when just starting a segment', () => {
builder.startSegment(0);
expect(builder.toString()).toEqual('A');
});
it('adds a comma when starting a segment after another segment', () => {
builder.startSegment(0);
builder.startSegment(1);
expect(builder.toString()).toEqual('A,C');
});
it('does not add a comma when starting a segment after marking a line', () => {
builder.startSegment(0);
builder.markLines(1);
builder.startSegment(0);
expect(builder.toString()).toEqual('A;A');
});
it('adds a comma when starting a segment after calling `markLines(0)`', () => {
builder.startSegment(0);
builder.markLines(0);
builder.startSegment(1);
expect(builder.toString()).toEqual('A,C');
});
it('can append values that fit within 5 bits (including sign bit)', () => {
builder.append(0b1111);
builder.append(-0b1111);
expect(builder.toString()).toEqual('ef');
});
it('can append values that fit within 10 bits (including sign bit)', () => {
builder.append(0b111100110);
builder.append(-0b110110011);
expect(builder.toString()).toEqual('senb');
});
it('can append values that fit within 15 bits (including sign bit)', () => {
builder.append(0b10011111011001);
builder.append(-0b11001010001001);
expect(builder.toString()).toEqual('y9TzoZ');
});
it('can append values that fit within 20 bits (including sign bit)', () => {
builder.append(0b1110010011101110110);
builder.append(-0b1011000010100100110);
expect(builder.toString()).toEqual('s3zctyiW');
});
it('can append values that fit within 25 bits (including sign bit)', () => {
builder.append(0b100010001111011010110111);
builder.append(-0b100100111100001110101111);
expect(builder.toString()).toEqual('ur7jR/6hvS');
});
it('can append values that fit within 30 bits (including sign bit)', () => {
builder.append(0b10001100100001101010001011111);
builder.append(-0b11111000011000111110011111101);
expect(builder.toString()).toEqual('+lqjyR7v+xhf');
});
it('can append values that fit within 32 bits (including sign bit)', () => {
builder.append(0b1001100101000101001011111110011);
builder.append(-0b1101101101011000110011001110000);
expect(builder.toString()).toEqual('m/rq0sChnzx1tD');
});
it('can handle multiple operations', () => {
builder
.markLines(3)
.startSegment(4)
.append(2)
.append(2)
.append(0)
.append(2345)
.startSegment(12)
.append(987543)
.markLines(1)
.startSegment(0);
expect(builder.toString()).toEqual(';;;IEEAyyE,Yu5o8B;A');
});
@@ -1,113 +0,0 @@
/**
* Copyright (c) 2017-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';
jest.disableAutomock();
const Generator = require('../Generator');
const {objectContaining} = expect;
let generator;
beforeEach(() => {
generator = new Generator();
});
it('adds file name and source code when starting a file', () => {
const file1 = 'just/a/file';
const file2 = 'another/file';
const source1 = 'var a = 1;';
const source2 = 'var a = 2;';
generator.startFile(file1, source1);
generator.startFile(file2, source2);
expect(generator.toMap())
.toEqual(objectContaining({
sources: [file1, file2],
sourcesContent: [source1, source2],
}));
});
it('throws when adding a mapping without starting a file', () => {
expect(() => generator.addSimpleMapping(1, 2)).toThrow();
});
it('throws when adding a mapping after ending a file', () => {
generator.startFile('apples', 'pears');
generator.endFile();
expect(() => generator.addSimpleMapping(1, 2)).toThrow();
});
it('can add a mapping for generated code without corresponding original source', () => {
generator.startFile('apples', 'pears');
generator.addSimpleMapping(12, 87);
expect(generator.toMap())
.toEqual(objectContaining({
mappings: ';;;;;;;;;;;uF',
}));
});
it('can add a mapping with corresponding location in the original source', () => {
generator.startFile('apples', 'pears');
generator.addSourceMapping(2, 3, 456, 7);
expect(generator.toMap())
.toEqual(objectContaining({
mappings: ';GAucO',
}));
});
it('can add a mapping with source location and symbol name', () => {
generator.startFile('apples', 'pears');
generator.addNamedSourceMapping(9, 876, 54, 3, 'arbitrary');
expect(generator.toMap())
.toEqual(objectContaining({
mappings: ';;;;;;;;42BAqDGA',
names: ['arbitrary'],
}));
});
describe('full map generation', () => {
beforeEach(() => {
generator.startFile('apples', 'pears');
generator.addSimpleMapping(1, 2);
generator.addNamedSourceMapping(3, 4, 5, 6, 'plums');
generator.endFile();
generator.startFile('lemons', 'oranges');
generator.addNamedSourceMapping(7, 8, 9, 10, 'tangerines');
generator.addNamedSourceMapping(11, 12, 13, 14, 'tangerines');
generator.addSimpleMapping(15, 16);
});
it('can add multiple mappings for each file', () => {
expect(generator.toMap()).toEqual({
version: 3,
mappings: 'E;;IAIMA;;;;QCIIC;;;;YAIIA;;;;gB',
sources: ['apples', 'lemons'],
sourcesContent: ['pears', 'oranges'],
names: ['plums', 'tangerines'],
});
});
it('can add a `file` property to the map', () => {
expect(generator.toMap('arbitrary'))
.toEqual(objectContaining({
file: 'arbitrary',
}));
});
it('supports direct JSON serialization', () => {
expect(JSON.parse(generator.toString())).toEqual(generator.toMap());
});
it('supports direct JSON serialization with a file name', () => {
const file = 'arbitrary/file';
expect(JSON.parse(generator.toString(file))).toEqual(generator.toMap(file));
});
});
@@ -1,85 +0,0 @@
/**
* Copyright (c) 2017-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';
jest.disableAutomock();
const Generator = require('../Generator');
const {compactMapping, fromRawMappings} = require('..');
describe('flattening mappings / compacting', () => {
it('flattens simple mappings', () => {
expect(compactMapping({generated: {line: 12, column: 34}}))
.toEqual([12, 34]);
});
it('flattens mappings with a source location', () => {
expect(compactMapping({
generated: {column: 34, line: 12},
original: {column: 78, line: 56},
})).toEqual([12, 34, 56, 78]);
});
it('flattens mappings with a source location and a symbol name', () => {
expect(compactMapping({
generated: {column: 34, line: 12},
name: 'arbitrary',
original: {column: 78, line: 56},
})).toEqual([12, 34, 56, 78, 'arbitrary']);
});
});
describe('build map from raw mappings', () => {
it('returns a `Generator` instance', () => {
expect(fromRawMappings([])).toBeInstanceOf(Generator);
});
it('returns a working source map containing all mappings', () => {
const input = [{
code: lines(11),
map: [
[1, 2],
[3, 4, 5, 6, 'apples'],
[7, 8, 9, 10],
[11, 12, 13, 14, 'pears'],
],
sourceCode: 'code1',
sourcePath: 'path1',
}, {
code: lines(3),
map: [
[1, 2],
[3, 4, 15, 16, 'bananas'],
],
sourceCode: 'code2',
sourcePath: 'path2',
}, {
code: lines(23),
map: [
[11, 12],
[13, 14, 15, 16, 'bananas'],
[17, 18, 19, 110],
[21, 112, 113, 114, 'pears'],
],
sourceCode: 'code3',
sourcePath: 'path3',
}];
expect(fromRawMappings(input).toMap())
.toEqual({
mappings: 'E;;IAIMA;;;;QAII;;;;YAIIC;E;;ICEEC;;;;;;;;;;;Y;;cCAAA;;;;kBAI8F;;;;gHA8FID',
names: ['apples', 'pears', 'bananas'],
sources: ['path1', 'path2', 'path3'],
sourcesContent: ['code1', 'code2', 'code3'],
version: 3,
});
});
});
const lines = n => Array(n).join('\n');
-127
View File
@@ -1,127 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
/**
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*
* Based on the Base 64 VLQ implementation in Closure Compiler:
* https://git.io/vymuA
*
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @copyright
*/
/* eslint-disable no-bitwise */
'use strict';
// A map of values to characters for the b64 encoding
const CHAR_MAP = [
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f,
];
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
// length quantities we use in the source map spec, the first bit is the sign,
// the next four bits are the actual value, and the 6th bit is the
// continuation bit. The continuation bit tells us whether there are more
// digits in this value following this digit.
//
// Continuation
// | Sign
// | |
// V V
// 101011
const VLQ_BASE_SHIFT = 5;
// binary: 100000
const VLQ_BASE = 1 << VLQ_BASE_SHIFT;
// binary: 011111
const VLQ_BASE_MASK = VLQ_BASE - 1;
// binary: 100000
const VLQ_CONTINUATION_BIT = VLQ_BASE;
/**
* Converts from a two-complement value to a value where the sign bit is
* placed in the least significant bit. For example, as decimals:
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
*/
function toVLQSigned(value) {
return value < 0
? ((-value) << 1) + 1
: (value << 1) + 0;
}
/**
* Encodes a number to base64 VLQ format and appends it to the passed-in buffer
*
* DON'T USE COMPOUND OPERATORS (eg `>>>=`) ON `let`-DECLARED VARIABLES!
* V8 WILL DEOPTIMIZE THIS FUNCTION AND MAP CREATION WILL BE 25% SLOWER!
*
* DON'T ADD MORE COMMENTS TO THIS FUNCTION TO KEEP ITS LENGTH SHORT ENOUGH FOR
* V8 OPTIMIZATION!
*/
function encode(value: number, buffer: Buffer, position: number): number {
let vlq = toVLQSigned(value);
let digit;
do {
digit = vlq & VLQ_BASE_MASK;
vlq >>>= VLQ_BASE_SHIFT;
if (vlq > 0) {
// There are still more digits in this value, so we must make sure the
// continuation bit is marked.
digit |= VLQ_CONTINUATION_BIT;
}
buffer[position++] = CHAR_MAP[digit];
} while (vlq > 0);
return position;
}
module.exports = encode;
@@ -1 +0,0 @@
{"main": "source-map.js"}
@@ -1,104 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const Generator = require('./Generator');
import type ModuleTransport from '../../lib/ModuleTransport';
import type {RawMapping as BabelRawMapping} from 'babel-generator';
type GeneratedCodeMapping = [number, number];
type SourceMapping = [number, number, number, number];
type SourceMappingWithName = [number, number, number, number, string];
export type RawMapping =
SourceMappingWithName | SourceMapping | GeneratedCodeMapping;
/**
* Creates a source map from modules with "raw mappings", i.e. an array of
* tuples with either 2, 4, or 5 elements:
* generated line, generated column, source line, source line, symbol name.
*/
function fromRawMappings(modules: Array<ModuleTransport>): Generator {
const generator = new Generator();
let carryOver = 0;
for (var j = 0, o = modules.length; j < o; ++j) {
var module = modules[j];
var {code, map} = module;
if (Array.isArray(map)) {
addMappingsForFile(generator, map, module, carryOver);
} else if (map != null) {
throw new Error(
`Unexpected module with full source map found: ${module.sourcePath}`
);
}
carryOver += countLines(code);
}
return generator;
}
function compactMapping(mapping: BabelRawMapping): RawMapping {
const {column, line} = mapping.generated;
const {name, original} = mapping;
if (original == null) {
return [line, column];
}
if (typeof name !== 'string') {
return [line, column, original.line, original.column];
}
return [line, column, original.line, original.column, name];
}
function addMappingsForFile(generator, mappings, module, carryOver) {
generator.startFile(module.sourcePath, module.sourceCode);
const columnOffset = module.code.indexOf('{') + 1;
for (let i = 0, n = mappings.length; i < n; ++i) {
addMapping(generator, mappings[i], carryOver, columnOffset);
}
generator.endFile();
}
function addMapping(generator, mapping, carryOver, columnOffset) {
const n = mapping.length;
const line = mapping[0] + carryOver;
// lines start at 1, columns start at 0
const column = mapping[0] === 1 ? mapping[1] + columnOffset : mapping[1];
if (n === 2) {
generator.addSimpleMapping(line, column);
} else if (n === 4) {
// $FlowIssue #15579526
generator.addSourceMapping(line, column, mapping[2], mapping[3]);
} else if (n === 5) {
generator.addNamedSourceMapping(
// $FlowIssue #15579526
line, column, mapping[2], mapping[3], mapping[4]);
} else {
throw new Error(`Invalid mapping: [${mapping.join(', ')}]`);
}
}
function countLines(string) {
return string.split('\n').length;
}
exports.fromRawMappings = fromRawMappings;
exports.compactMapping = compactMapping;
-157
View File
@@ -1,157 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const babel = require('babel-core');
const babelGenerate = require('babel-generator').default;
const babylon = require('babylon');
import type {AssetDescriptor} from '.';
import type {ModuleTransportLike} from '../shared/types.flow';
type SubTree<T: ModuleTransportLike> = (
moduleTransport: T,
moduleTransportsByPath: Map<string, T>,
) => Generator<number, void, void>;
const assetPropertyBlacklist = new Set([
'files',
'fileSystemLocation',
'path',
]);
const ASSET_REGISTRY_PATH = 'react-native/Libraries/Image/AssetRegistry';
function generateAssetCodeFileAst(assetDescriptor: AssetDescriptor): Object {
const properDescriptor = filterObject(assetDescriptor, assetPropertyBlacklist);
const descriptorAst = babylon.parseExpression(JSON.stringify(properDescriptor));
const t = babel.types;
const moduleExports = t.memberExpression(t.identifier('module'), t.identifier('exports'));
const requireCall =
t.callExpression(t.identifier('require'), [t.stringLiteral(ASSET_REGISTRY_PATH)]);
const registerAssetFunction = t.memberExpression(requireCall, t.identifier('registerAsset'));
const registerAssetCall = t.callExpression(registerAssetFunction, [descriptorAst]);
return t.file(t.program([
t.expressionStatement(t.assignmentExpression('=', moduleExports, registerAssetCall)),
]));
}
function generateAssetTransformResult(assetDescriptor: AssetDescriptor): {|
code: string,
dependencies: Array<string>,
dependencyOffsets: Array<number>,
|} {
const {code} = babelGenerate(
generateAssetCodeFileAst(assetDescriptor),
{comments: false, compact: true},
);
const dependencies = [ASSET_REGISTRY_PATH];
const dependencyOffsets = [code.indexOf(ASSET_REGISTRY_PATH) - 1];
return {code, dependencies, dependencyOffsets};
}
// Test extension against all types supported by image-size module.
// If it's not one of these, we won't treat it as an image.
function isAssetTypeAnImage(type: string): boolean {
return [
'png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff',
].indexOf(type) !== -1;
}
function filterObject(object, blacklist) {
const copied = Object.assign({}, object);
for (const key of blacklist) {
delete copied[key];
}
return copied;
}
function createRamBundleGroups<T: ModuleTransportLike>(
ramGroups: $ReadOnlyArray<string>,
groupableModules: $ReadOnlyArray<T>,
subtree: SubTree<T>,
): Map<number, Set<number>> {
// build two maps that allow to lookup module data
// by path or (numeric) module id;
const byPath = new Map();
const byId = new Map();
groupableModules.forEach(m => {
byPath.set(m.sourcePath, m);
byId.set(m.id, m.sourcePath);
});
// build a map of group root IDs to an array of module IDs in the group
const result: Map<number, Set<number>> = new Map(
ramGroups
.map(modulePath => {
const root = byPath.get(modulePath);
if (root == null) {
throw Error(`Group root ${modulePath} is not part of the bundle`);
}
return [
root.id,
// `subtree` yields the IDs of all transitive dependencies of a module
new Set(subtree(root, byPath)),
];
})
);
if (ramGroups.length > 1) {
// build a map of all grouped module IDs to an array of group root IDs
const all = new ArrayMap();
for (const [parent, children] of result) {
for (const module of children) {
all.get(module).push(parent);
}
}
// find all module IDs that are part of more than one group
const doubles = filter(all, ([, parents]) => parents.length > 1);
for (const [moduleId, parents] of doubles) {
const parentNames = parents.map(byId.get, byId);
const lastName = parentNames.pop();
throw new Error(
`Module ${byId.get(moduleId) || moduleId} belongs to groups ${
parentNames.join(', ')}, and ${String(lastName)
}. Ensure that each module is only part of one group.`
);
}
}
return result;
}
function * filter(iterator, predicate) {
for (const value of iterator) {
if (predicate(value)) {
yield value;
}
}
}
class ArrayMap extends Map {
get(key) {
let array = super.get(key);
if (!array) {
array = [];
this.set(key, array);
}
return array;
}
}
module.exports = {
createRamBundleGroups,
generateAssetCodeFileAst,
generateAssetTransformResult,
isAssetTypeAnImage,
};
@@ -1,13 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
// Bug with Jest because we're going to the node_modules that is a sibling
// of what jest thinks our root (the dir with the package.json) should be.
module.exports = require.requireActual('lodash');
@@ -1,13 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
module.exports = function(data, callback) {
callback(null, {});
};
@@ -1,95 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest
.unmock('stream')
.unmock('crypto')
.unmock('../../lib/ModuleTransport')
.unmock('../');
const fs = {writeFileSync: jest.fn()};
const temp = {path: () => '/arbitrary/path'};
const workerFarm = jest.fn();
jest.setMock('fs', fs);
jest.setMock('temp', temp);
jest.setMock('worker-farm', workerFarm);
jest.setMock('../../worker-farm', workerFarm);
var Transformer = require('../');
const {any} = jasmine;
const {Readable} = require('stream');
describe('Transformer', function() {
let workers, Cache;
const fileName = '/an/arbitrary/file.js';
const localPath = 'arbitrary/file.js';
const transformModulePath = __filename;
beforeEach(function() {
Cache = jest.fn();
Cache.prototype.get = jest.fn((a, b, c) => c());
fs.writeFileSync.mockClear();
workerFarm.mockClear();
workerFarm.mockImplementation((opts, path, methods) => {
const api = workers = {};
methods.forEach(method => {api[method] = jest.fn();});
return {methods: api, stdout: new Readable({read() {}}), stderr: new Readable({read() {}})};
});
});
it('passes transform module path, file path, source code' +
' to the worker farm when transforming', () => {
const transformOptions = {arbitrary: 'options'};
const code = 'arbitrary(code)';
new Transformer(transformModulePath).transformFile(fileName, localPath, code, transformOptions);
expect(workers.transformAndExtractDependencies).toBeCalledWith(
transformModulePath,
fileName,
localPath,
code,
transformOptions,
any(Function),
);
});
it('should add file info to parse errors', function() {
const transformer = new Transformer(transformModulePath);
var message = 'message';
var snippet = 'snippet';
workers.transformAndExtractDependencies.mockImplementation(
function(transformPath, filename, localPth, code, opts, callback) {
var babelError = new SyntaxError(message);
babelError.type = 'SyntaxError';
babelError.description = message;
babelError.loc = {
line: 2,
column: 15,
};
babelError.codeFrame = snippet;
callback(babelError);
},
);
expect.assertions(7);
return transformer.transformFile(fileName, localPath, '', {})
.catch(function(error) {
expect(error.type).toEqual('TransformError');
expect(error.message).toBe('SyntaxError ' + message);
expect(error.lineNumber).toBe(2);
expect(error.column).toBe(15);
expect(error.filename).toBe(fileName);
expect(error.description).toBe(message);
expect(error.snippet).toBe(snippet);
});
});
});
-204
View File
@@ -1,204 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const Logger = require('../Logger');
const debug = require('debug')('RNP:JStransformer');
const denodeify: Denodeify = require('denodeify');
const invariant = require('fbjs/lib/invariant');
const path = require('path');
const util = require('util');
const workerFarm = require('../worker-farm');
import type {Data as TransformData, Options as WorkerOptions} from './worker';
import type {LocalPath} from '../node-haste/lib/toLocalPath';
import type {MappingsMap} from '../lib/SourceMap';
import typeof {minify as Minify, transformAndExtractDependencies as TransformAndExtractDependencies} from './worker';
type CB<T> = (?Error, ?T) => mixed;
type Denodeify =
& (<A, B, C, T>((A, B, C, CB<T>) => void) => (A, B, C) => Promise<T>)
& (<A, B, C, D, E, T>((A, B, C, D, E, CB<T>) => void) => (A, B, C, D, E) => Promise<T>);
// Avoid memory leaks caused in workers. This number seems to be a good enough number
// to avoid any memory leak while not slowing down initial builds.
// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more.
const MAX_CALLS_PER_WORKER = 600;
// Worker will timeout if one of the callers timeout.
const TRANSFORM_TIMEOUT_INTERVAL = 301000;
// How may times can we tolerate failures from the worker.
const MAX_RETRIES = 2;
function makeFarm(worker, methods, timeout, maxConcurrentWorkers) {
return workerFarm(
{
autoStart: true,
execArgv: [],
maxConcurrentCallsPerWorker: 1,
maxConcurrentWorkers,
maxCallsPerWorker: MAX_CALLS_PER_WORKER,
maxCallTime: timeout,
maxRetries: MAX_RETRIES,
},
worker,
methods,
);
}
type Reporters = {
+stdoutChunk: (chunk: string) => mixed,
+stderrChunk: (chunk: string) => mixed,
};
class Transformer {
_workers: {[name: string]: Function};
_transformModulePath: string;
_transform: (
transform: string,
filename: string,
localPath: LocalPath,
sourceCode: string,
options: WorkerOptions,
) => Promise<TransformData>;
minify: (
filename: string,
code: string,
sourceMap: MappingsMap,
) => Promise<{code: string, map: MappingsMap}>;
constructor(
transformModulePath: string,
maxWorkerCount: number,
reporters: Reporters,
workerPath: ?string,
) {
invariant(path.isAbsolute(transformModulePath), 'transform module path should be absolute');
this._transformModulePath = transformModulePath;
const farm = makeFarm(
workerPath || require.resolve('./worker'),
['minify', 'transformAndExtractDependencies'],
TRANSFORM_TIMEOUT_INTERVAL,
maxWorkerCount,
);
farm.stdout.on('data', chunk => {
reporters.stdoutChunk(chunk.toString('utf8'));
});
farm.stderr.on('data', chunk => {
reporters.stderrChunk(chunk.toString('utf8'));
});
this._workers = farm.methods;
this._transform = denodeify((this._workers.transformAndExtractDependencies: TransformAndExtractDependencies));
this.minify = denodeify((this._workers.minify: Minify));
}
kill() {
this._workers && workerFarm.end(this._workers);
}
transformFile(
fileName: string,
localPath: LocalPath,
code: string,
options: WorkerOptions) {
if (!this._transform) {
return Promise.reject(new Error('No transform module'));
}
debug('transforming file', fileName);
return this
._transform(
this._transformModulePath,
fileName,
localPath,
code,
options,
)
.then(data => {
Logger.log(data.transformFileStartLogEntry);
Logger.log(data.transformFileEndLogEntry);
debug('done transforming file', fileName);
return data.result;
})
.catch(error => {
if (error.type === 'TimeoutError') {
const timeoutErr = new Error(
`TimeoutError: transforming ${fileName} took longer than ` +
`${TRANSFORM_TIMEOUT_INTERVAL / 1000} seconds.\n` +
'You can adjust timeout via the \'transformTimeoutInterval\' option'
);
/* $FlowFixMe: monkey-patch Error */
timeoutErr.type = 'TimeoutError';
throw timeoutErr;
} else if (error.type === 'ProcessTerminatedError') {
const uncaughtError = new Error(
'Uncaught error in the transformer worker: ' +
this._transformModulePath
);
/* $FlowFixMe: monkey-patch Error */
uncaughtError.type = 'ProcessTerminatedError';
throw uncaughtError;
}
throw formatError(error, fileName);
});
}
static TransformError;
}
Transformer.TransformError = TransformError;
function TransformError() {
Error.captureStackTrace && Error.captureStackTrace(this, TransformError);
}
util.inherits(TransformError, SyntaxError);
function formatError(err, filename) {
if (err.loc) {
return formatBabelError(err, filename);
} else {
return formatGenericError(err, filename);
}
}
function formatGenericError(err, filename) {
var msg = 'TransformError: ' + filename + ': ' + err.message;
var error = new TransformError();
var stack = (err.stack || '').split('\n').slice(0, -1);
stack.push(msg);
error.stack = stack.join('\n');
error.message = msg;
error.type = 'TransformError';
error.lineNumber = 0;
error.description = '';
return error;
}
function formatBabelError(err, filename) {
var error = new TransformError();
error.type = 'TransformError';
error.message = (err.type || error.type) + ' ' + err.message;
error.stack = err.stack;
error.snippet = err.codeFrame;
error.lineNumber = err.loc.line;
error.column = err.loc.column;
error.filename = filename;
error.description = err.message;
return error;
}
module.exports = Transformer;
@@ -1,19 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const UGLIFY_JS_OUTPUT_OPTIONS = {
ascii_only: true,
screw_ie8: true,
};
module.exports = {UGLIFY_JS_OUTPUT_OPTIONS};
@@ -1,122 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.disableAutomock();
const babel = require('babel-core');
const constantFolding = require('../constant-folding');
function parse(code) {
return babel.transform(code, {code: false, babelrc: false, compact: true});
}
const babelOptions = {
babelrc: false,
compact: true,
retainLines: false,
};
function normalize({code}) {
return babel.transform(code, babelOptions).code;
}
describe('constant expressions', () => {
it('can optimize conditional expressions with constant conditions', () => {
const code = `
a(
'production'=="production",
'production'!=='development',
false && 1 || 0 || 2,
true || 3,
'android'==='ios' ? null : {},
'android'==='android' ? {a:1} : {a:0},
'foo'==='bar' ? b : c,
f() ? g() : h()
);`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('a(true,true,2,true,{},{a:1},c,f()?g():h());');
});
it('can optimize ternary expressions with constant conditions', () => {
const code =
`var a = true ? 1 : 2;
var b = 'android' == 'android'
? ('production' != 'production' ? 'a' : 'A')
: 'i';`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('var a=1;var b=\'A\';');
});
it('can optimize logical operator expressions with constant conditions', () => {
const code = `
var a = true || 1;
var b = 'android' == 'android' &&
'production' != 'production' || null || "A";`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('var a=true;var b="A";');
});
it('can optimize logical operators with partly constant operands', () => {
const code = `
var a = "truthy" || z();
var b = "truthy" && z();
var c = null && z();
var d = null || z();
var e = !1 && z();
`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('var a="truthy";var b=z();var c=null;var d=z();var e=false;');
});
it('can remode an if statement with a falsy constant test', () => {
const code = `
if ('production' === 'development' || false) {
var a = 1;
}
`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('');
});
it('can optimize if-else-branches with constant conditions', () => {
const code = `
if ('production' == 'development') {
var a = 1;
var b = a + 2;
} else if ('development' == 'development') {
var a = 3;
var b = a + 4;
} else {
var a = 'b';
}
`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('{var a=3;var b=a+4;}');
});
it('can optimize nested if-else constructs', () => {
const code = `
if ('ios' === "android") {
if (true) {
require('a');
} else {
require('b');
}
} else if ('android' === 'android') {
if (true) {
require('c');
} else {
require('d');
}
}
`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('{{require(\'c\');}}');
});
});
@@ -1,112 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.disableAutomock();
const extractDependencies = require('../extract-dependencies');
describe('Dependency extraction:', () => {
it('can extract calls to require', () => {
const code = `require('foo/bar');
var React = require("React");
var A = React.createClass({
render: function() {
return require ( "Component" );
}
});
require
('more');`;
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies)
.toEqual(['foo/bar', 'React', 'Component', 'more']);
expect(dependencyOffsets).toEqual([8, 46, 147, 203]);
});
it('does not extract require method calls', () => {
const code = `
require('a');
foo.require('b');
bar.
require ( 'c').require('d');require('e')`;
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['a', 'e']);
expect(dependencyOffsets).toEqual([15, 98]);
});
it('does not extract require calls from strings', () => {
const code = `require('foo');
var React = '\\'require("React")';
var a = ' // require("yadda")';
var a = ' /* require("yadda") */';
var A = React.createClass({
render: function() {
return require ( "Component" );
}
});
" \\" require('more')";`;
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['foo', 'Component']);
expect(dependencyOffsets).toEqual([8, 226]);
});
it('does not extract require calls in comments', () => {
const code = `require('foo')//require("not/this")
/* A comment here with a require('call') that should not be extracted */require('bar')
// ending comment without newline require("baz")`;
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['foo', 'bar']);
expect(dependencyOffsets).toEqual([8, 122]);
});
it('deduplicates dependencies', () => {
const code = `require('foo');require( "foo" );
require("foo");`;
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['foo']);
expect(dependencyOffsets).toEqual([8, 24, 47]);
});
it('does not extract calls to function with names that start with "require"', () => {
const code = 'arbitraryrequire(\'foo\');';
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual([]);
expect(dependencyOffsets).toEqual([]);
});
it('does not extract calls to require with non-static arguments', () => {
const code = 'require(\'foo/\' + bar)';
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual([]);
expect(dependencyOffsets).toEqual([]);
});
it('does not get confused by previous states', () => {
// yes, this was a bug
const code = 'require("a");/* a comment */ var a = /[a]/.test(\'a\');';
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['a']);
expect(dependencyOffsets).toEqual([8]);
});
it('can handle regular expressions', () => {
const code = 'require(\'a\'); /["\']/.test(\'foo\'); require("b");';
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['a', 'b']);
expect(dependencyOffsets).toEqual([8, 42]);
});
});
@@ -1,337 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
/* eslint-disable max-len */
jest.disableAutomock();
const inline = require('../inline');
const {transform, transformFromAst} = require('babel-core');
const babelOptions = {
babelrc: false,
compact: true,
};
function toString(ast) {
return normalize(transformFromAst(ast, babelOptions).code);
}
function normalize(code) {
return transform(code, babelOptions).code;
}
function toAst(code) {
return transform(code, {...babelOptions, code: false}).ast;
}
describe('inline constants', () => {
it('replaces __DEV__ in the code', () => {
const code = `function a() {
var a = __DEV__ ? 1 : 2;
var b = a.__DEV__;
var c = function __DEV__(__DEV__) {};
}`;
const {ast} = inline('arbitrary.js', {code}, {dev: true});
expect(toString(ast)).toEqual(normalize(code.replace(/__DEV__/, 'true')));
});
it('replaces Platform.OS in the code if Platform is a global', () => {
const code = `function a() {
var a = Platform.OS;
var b = a.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"')));
});
it('replaces Platform.OS in the code if Platform is a top level import', () => {
const code = `
var Platform = require('Platform');
function a() {
if (Platform.OS === 'android') a = function() {};
var b = a.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"')));
});
it('replaces Platform.OS in the code if Platform is a top level import from react-native', () => {
const code = `
var Platform = require('react-native').Platform;
function a() {
if (Platform.OS === 'android') a = function() {};
var b = a.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"')));
});
it('replaces require("Platform").OS in the code', () => {
const code = `function a() {
var a = require('Platform').OS;
var b = a.require('Platform').OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('Platform'\)\.OS/, '"android"')));
});
it('replaces React.Platform.OS in the code if React is a global', () => {
const code = `function a() {
var a = React.Platform.OS;
var b = a.React.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.OS/, '"ios"')));
});
it('replaces ReactNative.Platform.OS in the code if ReactNative is a global', () => {
const code = `function a() {
var a = ReactNative.Platform.OS;
var b = a.ReactNative.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative\.Platform\.OS/, '"ios"')));
});
it('replaces React.Platform.OS in the code if React is a top level import', () => {
const code = `
var React = require('React');
function a() {
if (React.Platform.OS === 'android') a = function() {};
var b = a.React.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/React.Platform\.OS/, '"ios"')));
});
it('replaces require("React").Platform.OS in the code', () => {
const code = `function a() {
var a = require('React').Platform.OS;
var b = a.require('React').Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('React'\)\.Platform\.OS/, '"android"')));
});
it('replaces ReactNative.Platform.OS in the code if ReactNative is a top level import', () => {
const code = `
var ReactNative = require('react-native');
function a() {
if (ReactNative.Platform.OS === 'android') a = function() {};
var b = a.ReactNative.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative.Platform\.OS/, '"android"')));
});
it('replaces require("react-native").Platform.OS in the code', () => {
const code = `function a() {
var a = require('react-native').Platform.OS;
var b = a.require('react-native').Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('react-native'\)\.Platform\.OS/, '"android"')));
});
it('inlines Platform.select in the code if Platform is a global and the argument is an object literal', () => {
const code = `function a() {
var a = Platform.select({ios: 1, android: 2});
var b = a.Platform.select({ios: 1, android: 2});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1')));
});
it('inlines Platform.select in the code if Platform is a global and the argument doesn\'t have target platform in it\'s keys', () => {
const code = `function a() {
var a = Platform.select({ios: 1, default: 2});
var b = a.Platform.select({ios: 1, default: 2});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2')));
});
it('replaces Platform.select in the code if Platform is a top level import', () => {
const code = `
var Platform = require('Platform');
function a() {
Platform.select({ios: 1, android: 2});
var b = a.Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2')));
});
it('replaces Platform.select in the code if Platform is a top level import from react-native', () => {
const code = `
var Platform = require('react-native').Platform;
function a() {
Platform.select({ios: 1, android: 2});
var b = a.Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1')));
});
it('replaces require("Platform").select in the code', () => {
const code = `function a() {
var a = require('Platform').select({ios: 1, android: 2});
var b = a.require('Platform').select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2')));
});
it('replaces React.Platform.select in the code if React is a global', () => {
const code = `function a() {
var a = React.Platform.select({ios: 1, android: 2});
var b = a.React.Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1')));
});
it('replaces ReactNative.Platform.select in the code if ReactNative is a global', () => {
const code = `function a() {
var a = ReactNative.Platform.select({ios: 1, android: 2});
var b = a.ReactNative.Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(
normalize(code.replace(/ReactNative\.Platform\.select[^;]+/, '1')),
);
});
it('replaces React.Platform.select in the code if React is a top level import', () => {
const code = `
var React = require('React');
function a() {
var a = React.Platform.select({ios: 1, android: 2});
var b = a.React.Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1')));
});
it('replaces require("React").Platform.select in the code', () => {
const code = `function a() {
var a = require('React').Platform.select({ios: 1, android: 2});
var b = a.require('React').Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('React'\)\.Platform\.select[^;]+/, '2')));
});
it('replaces ReactNative.Platform.select in the code if ReactNative is a top level import', () => {
const code = `
var ReactNative = require('react-native');
function a() {
var a = ReactNative.Plaftform.select({ios: 1, android: 2});
var b = a.ReactNative.Platform.select;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/ReactNative.Platform\.select[^;]+/, '2')),
);
});
it('replaces require("react-native").Platform.select in the code', () => {
const code = `
var a = require('react-native').Platform.select({ios: 1, android: 2});
var b = a.require('react-native').Platform.select({});
`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('react-native'\)\.Platform\.select[^;]+/, '2')));
});
it('replaces non-existing properties with `undefined`', () => {
const code = 'var a = Platform.select({ios: 1, android: 2})';
const {ast} = inline('arbitrary.js', {code}, {platform: 'doesnotexist'});
expect(toString(ast)).toEqual(
normalize(code.replace(/Platform\.select[^;]+/, 'undefined')));
});
it('replaces process.env.NODE_ENV in the code', () => {
const code = `function a() {
if (process.env.NODE_ENV === 'production') {
return require('Prod');
}
return require('Dev');
}`;
const {ast} = inline('arbitrary.js', {code}, {dev: false});
expect(toString(ast)).toEqual(
normalize(code.replace(/process\.env\.NODE_ENV/, '"production"')));
});
it('accepts an AST as input', function() {
const code = 'function ifDev(a,b){return __DEV__?a:b;}';
const {ast} = inline('arbitrary.hs', {ast: toAst(code)}, {dev: false});
expect(toString(ast)).toEqual(code.replace(/__DEV__/, 'false'));
});
it('can work with wrapped modules', () => {
const code = `__arbitrary(function() {
var Platform = require('react-native').Platform;
var a = Platform.OS, b = Platform.select({android: 1, ios: 2});
});`;
const {ast} = inline(
'arbitrary', {code}, {dev: true, platform: 'android', isWrapped: true});
expect(toString(ast)).toEqual(
normalize(
code
.replace(/Platform\.OS/, '"android"')
.replace(/Platform\.select[^)]+\)/, 1)
)
);
});
it('can work with transformed require calls', () => {
const code = `__arbitrary(require, function(arbitraryMapName) {
var a = require(arbitraryMapName[123], 'react-native').Platform.OS;
});`;
const {ast} = inline(
'arbitrary', {code}, {dev: true, platform: 'android', isWrapped: true});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\([^)]+\)\.Platform\.OS/, '"android"')));
});
it('works with flow-declared variables', () => {
const stripFlow = require('babel-plugin-transform-flow-strip-types');
const code = `declare var __DEV__;
const a: boolean = __DEV__;`;
const transformed = transform(
code,
{...babelOptions, plugins: [stripFlow, [inline.plugin, {dev: false}]]},
).code;
expect(transformed).toEqual('const a=false;');
});
it('works with flow-declared variables in wrapped modules', () => {
const stripFlow = require('babel-plugin-transform-flow-strip-types');
const code = `__d(() => {
declare var __DEV__;
const a: boolean = __DEV__;
});`;
const transformed = transform(
code,
{...babelOptions, plugins: [stripFlow, [inline.plugin, {dev: true}]]},
).code;
expect(transformed).toEqual('__d(()=>{const a=true;});');
});
});
@@ -1,57 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.disableAutomock();
const uglify = {
minify: jest.fn(code => {
return {
code: code.replace(/(^|\W)\s+/g, '$1'),
map: {},
};
}),
};
jest.setMock('uglify-js', uglify);
const minify = require('../minify');
const {objectContaining} = jasmine;
describe('Minification:', () => {
const filename = '/arbitrary/file.js';
const code = 'arbitrary(code)';
let map;
beforeEach(() => {
uglify.minify.mockClear();
uglify.minify.mockReturnValue({code: '', map: '{}'});
map = {version: 3, sources: ['?'], mappings: ''};
});
it('passes file name, code, and source map to `uglify`', () => {
minify(filename, code, map);
expect(uglify.minify).toBeCalledWith(code, objectContaining({
fromString: true,
inSourceMap: map,
outSourceMap: true,
}));
});
it('returns the code provided by uglify', () => {
uglify.minify.mockReturnValue({code, map: '{}'});
const result = minify('', '', {});
expect(result.code).toBe(code);
});
it('parses the source map object provided by uglify and sets the sources property', () => {
uglify.minify.mockReturnValue({map: JSON.stringify(map), code: ''});
const result = minify(filename, '', {});
expect(result.map).toEqual({...map, sources: [filename]});
});
});
@@ -1,226 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.disableAutomock();
jest.mock('../constant-folding');
jest.mock('../extract-dependencies');
jest.mock('../inline');
jest.mock('../minify');
const {objectContaining} = jasmine;
describe('code transformation worker:', () => {
let transformCode;
let extractDependencies, transformer;
beforeEach(() => {
jest.resetModules();
({transformCode} = require('..'));
extractDependencies =
require('../extract-dependencies').mockReturnValue({});
transformer = {
transform: jest.fn(({filename, options, src}) => ({
code: src,
map: {},
})),
};
});
it('calls the transform with file name, source code, and transform options', function() {
const filename = 'arbitrary/file.js';
const localPath = `local/${filename}`;
const sourceCode = 'arbitrary(code)';
const transformOptions = {arbitrary: 'options'};
transformCode(transformer, filename, localPath, sourceCode, {transform: transformOptions}, () => {});
expect(transformer.transform).toBeCalledWith({
filename,
localPath,
options: transformOptions,
src: sourceCode,
});
});
it('prefixes JSON files with an assignment to module.exports to make the code valid', function() {
const filename = 'arbitrary/file.json';
const localPath = `local/${filename}`;
const sourceCode = '{"arbitrary":"property"}';
transformCode(transformer, filename, localPath, sourceCode, {}, () => {});
expect(transformer.transform).toBeCalledWith({
filename,
localPath,
options: undefined,
src: `module.exports=${sourceCode}`,
});
});
it('calls back with the result of the transform in the cache', done => {
const result = {
code: 'some.other(code)',
map: {},
};
transformCode(transformer, 'filename', 'local/filename', result.code, {}, (error, data) => {
expect(error).toBeNull();
expect(data.result).toEqual(objectContaining(result));
done();
});
});
it(
'removes the leading assignment to `module.exports` before passing ' +
'on the result if the file is a JSON file, even if minified',
done => {
const code = '{a:1,b:2}';
const filePath = 'arbitrary/file.json';
transformCode(transformer, filePath, filePath, code, {}, (error, data) => {
expect(error).toBeNull();
expect(data.result.code).toEqual(code);
done();
},
);
}
);
it('removes shebang when present', done => {
const shebang = '#!/usr/bin/env node';
const result = {
code: `${shebang} \n arbitrary(code)`,
};
const filePath = 'arbitrary/file.js';
transformCode(transformer, filePath, filePath, result.code, {}, (error, data) => {
expect(error).toBeNull();
const {code} = data.result;
expect(code).not.toContain(shebang);
expect(code.split('\n').length).toEqual(result.code.split('\n').length);
done();
});
});
it('calls back with any error yielded by the transform', done => {
const message = 'SyntaxError: this code is broken.';
transformer.transform.mockImplementation(() => {
throw new Error(message);
});
transformCode(transformer, 'filename', 'local/filename', 'code', {}, error => {
expect(error.message).toBe(message);
done();
});
});
describe('dependency extraction', () => {
it('passes the transformed code the `extractDependencies`', done => {
const code = 'arbitrary(code)';
transformCode(transformer, 'filename', 'local/filename', code, {}, error => {
expect(error).toBeNull();
expect(extractDependencies).toBeCalledWith(code);
done();
});
});
it(
'uses `dependencies` and `dependencyOffsets` ' +
'provided by `extractDependencies` for the result',
done => {
const dependencyData = {
dependencies: ['arbitrary', 'list', 'of', 'dependencies'],
dependencyOffsets: [12, 119, 185, 328, 471],
};
extractDependencies.mockReturnValue(dependencyData);
transformCode(transformer, 'filename', 'local/filename', 'code', {}, (error, data) => {
expect(error).toBeNull();
expect(data.result).toEqual(objectContaining(dependencyData));
done();
});
}
);
it('does not extract requires of JSON files', done => {
const jsonStr = '{"arbitrary":"json"}';
transformCode(transformer, 'arbitrary.json', 'local/arbitrary.json', jsonStr, {}, (error, data) => {
expect(error).toBeNull();
const {dependencies, dependencyOffsets} = data.result;
expect(extractDependencies).not.toBeCalled();
expect(dependencies).toEqual([]);
expect(dependencyOffsets).toEqual([]);
done();
}
);
});
});
describe('Minifications:', () => {
let constantFolding, inline, options;
let transformResult, dependencyData;
const filename = 'arbitrary/file.js';
const foldedCode = 'arbitrary(folded(code));';
const foldedMap = {version: 3, sources: ['fold.js']};
beforeEach(() => {
constantFolding = require('../constant-folding')
.mockReturnValue({code: foldedCode, map: foldedMap});
extractDependencies = require('../extract-dependencies');
inline = require('../inline');
options = {minify: true, transform: {generateSourceMaps: true}};
dependencyData = {
dependencies: ['a', 'b', 'c'],
dependencyOffsets: [100, 120, 140],
};
extractDependencies.mockImplementation(
code => code === foldedCode ? dependencyData : {});
transformer.transform.mockImplementation((src, fileName, _) => transformResult);
});
it('passes the transform result to `inline` for constant inlining', done => {
transformResult = {map: {version: 3}, code: 'arbitrary(code)'};
transformCode(transformer, filename, filename, 'code', options, () => {
expect(inline).toBeCalledWith(filename, transformResult, options);
done();
});
});
it('passes the result obtained from `inline` on to `constant-folding`', done => {
const inlineResult = {map: {version: 3, sources: []}, ast: {}};
inline.mockReturnValue(inlineResult);
transformCode(transformer, filename, filename, 'code', options, () => {
expect(constantFolding).toBeCalledWith(filename, inlineResult);
done();
});
});
it('Uses the code obtained from `constant-folding` to extract dependencies', done => {
transformCode(transformer, filename, filename, 'code', options, () => {
expect(extractDependencies).toBeCalledWith(foldedCode);
done();
});
});
it('uses the dependencies obtained from the optimized result', done => {
transformCode(transformer, filename, filename, 'code', options, (_, data) => {
const result = data.result;
expect(result.dependencies).toEqual(dependencyData.dependencies);
done();
});
});
it('uses data produced by `constant-folding` for the result', done => {
transformCode(transformer, 'filename', 'local/filename', 'code', options, (_, data) => {
expect(data.result)
.toEqual(objectContaining({code: foldedCode, map: foldedMap}));
done();
});
});
});
});
@@ -1,91 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const babel = require('babel-core');
import type {Ast, SourceMap as MappingsMap} from 'babel-core';
const t = babel.types;
const Conditional = {
exit(path) {
const node = path.node;
const test = node.test;
if (t.isLiteral(test)) {
if (test.value || node.alternate) {
path.replaceWith(test.value ? node.consequent : node.alternate);
} else if (!test.value) {
path.remove();
}
}
},
};
const plugin = {
visitor: {
BinaryExpression: {
exit(path) {
const node = path.node;
if (t.isLiteral(node.left) && t.isLiteral(node.right)) {
const result = path.evaluate();
if (result.confident) {
path.replaceWith(t.valueToNode(result.value));
}
}
},
},
ConditionalExpression: Conditional,
IfStatement: Conditional,
LogicalExpression: {
exit(path) {
const node = path.node;
const left = node.left;
if (t.isLiteral(left)) {
const value = t.isNullLiteral(left) ? null : left.value;
if (node.operator === '||') {
path.replaceWith(value ? left : node.right);
} else {
path.replaceWith(value ? node.right : left);
}
}
},
},
UnaryExpression: {
exit(path) {
const node = path.node;
if (node.operator === '!' && t.isLiteral(node.argument)) {
path.replaceWith(t.valueToNode(!node.argument.value));
}
},
},
},
};
function constantFolding(filename: string, transformResult: {
ast: Ast,
code?: ?string,
map: ?MappingsMap,
}) {
return babel.transformFromAst(transformResult.ast, transformResult.code, {
filename,
plugins: [plugin],
inputSourceMap: transformResult.map,
sourceMaps: true,
sourceFileName: filename,
babelrc: false,
compact: true,
retainLines: true,
});
}
constantFolding.plugin = plugin;
module.exports = constantFolding;
@@ -1,52 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const babel = require('babel-core');
const babylon = require('babylon');
/**
* Extracts dependencies (module IDs imported with the `require` function) from
* a string containing code. This walks the full AST for correctness (versus
* using, for example, regular expressions, that would be faster but inexact.)
*
* The result of the dependency extraction is an de-duplicated array of
* dependencies, and an array of offsets to the string literals with module IDs.
* The index points to the opening quote.
*/
function extractDependencies(code: string) {
const ast = babylon.parse(code);
const dependencies = new Set();
const dependencyOffsets = [];
babel.traverse(ast, {
CallExpression(path) {
const node = path.node;
const callee = node.callee;
const arg = node.arguments[0];
if (
callee.type !== 'Identifier' ||
callee.name !== 'require' ||
!arg ||
arg.type !== 'StringLiteral'
) {
return;
}
dependencyOffsets.push(arg.start);
dependencies.add(arg.value);
},
});
return {dependencyOffsets, dependencies: Array.from(dependencies)};
}
module.exports = extractDependencies;
-191
View File
@@ -1,191 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const babelRegisterOnly = require('../../babelRegisterOnly');
const constantFolding = require('./constant-folding');
const extractDependencies = require('./extract-dependencies');
const inline = require('./inline');
const invariant = require('fbjs/lib/invariant');
const minify = require('./minify');
import type {LogEntry} from '../../Logger/Types';
import type {MappingsMap} from '../../lib/SourceMap';
import type {LocalPath} from '../../node-haste/lib/toLocalPath';
import type {Ast, Plugins as BabelPlugins} from 'babel-core';
export type TransformedCode = {
code: string,
dependencies: Array<string>,
dependencyOffsets: Array<number>,
map?: ?MappingsMap,
};
export type Transformer<ExtraOptions: {} = {}> = {
transform: ({|
filename: string,
localPath: string,
options: ExtraOptions & TransformOptions,
plugins?: BabelPlugins,
src: string,
|}) => {ast: ?Ast, code: string, map: ?MappingsMap},
getCacheKey: () => string,
};
export type TransformOptionsStrict = {|
+dev: boolean,
+generateSourceMaps: boolean,
+hot: boolean,
+inlineRequires: {+blacklist: {[string]: true}} | boolean,
+platform: ?string,
+projectRoot: string,
|};
export type TransformOptions = {
+dev?: boolean,
+generateSourceMaps?: boolean,
+hot?: boolean,
+inlineRequires?: {+blacklist: {[string]: true}} | boolean,
+platform: ?string,
+projectRoot: string,
};
export type Options = {|
+dev: boolean,
+minify: boolean,
+platform: ?string,
+transform: TransformOptionsStrict,
|};
export type Data = {
result: TransformedCode,
transformFileStartLogEntry: LogEntry,
transformFileEndLogEntry: LogEntry,
};
type Callback<T> = (
error: ?Error,
data: ?T,
) => mixed;
function transformCode(
transformer: Transformer<*>,
filename: string,
localPath: LocalPath,
sourceCode: string,
options: Options,
callback: Callback<Data>,
) {
invariant(
!options.minify || options.transform.generateSourceMaps,
'Minifying source code requires the `generateSourceMaps` option to be `true`',
);
const isJson = filename.endsWith('.json');
if (isJson) {
sourceCode = 'module.exports=' + sourceCode;
}
const transformFileStartLogEntry = {
action_name: 'Transforming file',
action_phase: 'start',
file_name: filename,
log_entry_label: 'Transforming file',
start_timestamp: process.hrtime(),
};
let transformed;
try {
transformed = transformer.transform({
filename,
localPath,
options: options.transform,
src: sourceCode,
});
} catch (error) {
callback(error);
return;
}
invariant(
transformed != null,
'Missing transform results despite having no error.',
);
var code, map;
if (options.minify) {
({code, map} =
constantFolding(filename, inline(filename, transformed, options)));
invariant(code != null, 'Missing code from constant-folding transform.');
} else {
({code, map} = transformed);
}
if (isJson) {
code = code.replace(/^\w+\.exports=/, '');
} else {
// Remove shebang
code = code.replace(/^#!.*/, '');
}
const depsResult = isJson
? {dependencies: [], dependencyOffsets: []}
: extractDependencies(code);
const timeDelta = process.hrtime(transformFileStartLogEntry.start_timestamp);
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
const transformFileEndLogEntry = {
action_name: 'Transforming file',
action_phase: 'end',
file_name: filename,
duration_ms,
log_entry_label: 'Transforming file',
};
callback(null, {
result: {...depsResult, code, map},
transformFileStartLogEntry,
transformFileEndLogEntry,
});
}
exports.transformAndExtractDependencies = (
transform: string,
filename: string,
localPath: LocalPath,
sourceCode: string,
options: Options,
callback: Callback<Data>,
) => {
babelRegisterOnly([transform]);
/* $FlowFixMe: impossible to type a dynamic require */
const transformModule: Transformer<*> = require(transform);
transformCode(transformModule, filename, localPath, sourceCode, options, callback);
};
exports.minify = (
filename: string,
code: string,
sourceMap: MappingsMap,
callback: Callback<{code: string, map: MappingsMap}>,
) => {
var result;
try {
result = minify(filename, code, sourceMap);
} catch (error) {
callback(error);
}
callback(null, result);
};
exports.transformCode = transformCode; // for easier testing
-196
View File
@@ -1,196 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const babel = require('babel-core');
const invariant = require('fbjs/lib/invariant');
import type {Ast, SourceMap as MappingsMap} from 'babel-core';
const t = babel.types;
const React = {name: 'React'};
const ReactNative = {name: 'ReactNative'};
const platform = {name: 'Platform'};
const os = {name: 'OS'};
const select = {name: 'select'};
const requirePattern = {name: 'require'};
const env = {name: 'env'};
const nodeEnv = {name: 'NODE_ENV'};
const processId = {name: 'process'};
const dev = {name: '__DEV__'};
const importMap = new Map([['ReactNative', 'react-native']]);
const isGlobal = binding => !binding;
const isFlowDeclared = binding =>
t.isDeclareVariable(binding.path);
const isGlobalOrFlowDeclared = binding =>
isGlobal(binding) || isFlowDeclared(binding);
const isToplevelBinding = (binding, isWrappedModule) =>
isGlobal(binding) ||
!binding.scope.parent ||
isWrappedModule && !binding.scope.parent.parent;
const isRequireCall = (node, dependencyId, scope) =>
t.isCallExpression(node) &&
t.isIdentifier(node.callee, requirePattern) &&
checkRequireArgs(node.arguments, dependencyId);
const isImport = (node, scope, patterns) =>
patterns.some(pattern => {
const importName = importMap.get(pattern.name) || pattern.name;
return isRequireCall(node, importName, scope);
});
function isImportOrGlobal(node, scope, patterns, isWrappedModule) {
const identifier = patterns.find(pattern => t.isIdentifier(node, pattern));
return (
identifier &&
isToplevelBinding(scope.getBinding(identifier.name), isWrappedModule) ||
isImport(node, scope, patterns)
);
}
const isPlatformOS = (node, scope, isWrappedModule) =>
t.isIdentifier(node.property, os) &&
isImportOrGlobal(node.object, scope, [platform], isWrappedModule);
const isReactPlatformOS = (node, scope, isWrappedModule) =>
t.isIdentifier(node.property, os) &&
t.isMemberExpression(node.object) &&
t.isIdentifier(node.object.property, platform) &&
isImportOrGlobal(
node.object.object, scope, [React, ReactNative], isWrappedModule);
const isProcessEnvNodeEnv = (node, scope) =>
t.isIdentifier(node.property, nodeEnv) &&
t.isMemberExpression(node.object) &&
t.isIdentifier(node.object.property, env) &&
t.isIdentifier(node.object.object, processId) &&
isGlobal(scope.getBinding(processId.name));
const isPlatformSelect = (node, scope, isWrappedModule) =>
t.isMemberExpression(node.callee) &&
t.isIdentifier(node.callee.object, platform) &&
t.isIdentifier(node.callee.property, select) &&
isImportOrGlobal(node.callee.object, scope, [platform], isWrappedModule);
const isReactPlatformSelect = (node, scope, isWrappedModule) =>
t.isMemberExpression(node.callee) &&
t.isIdentifier(node.callee.property, select) &&
t.isMemberExpression(node.callee.object) &&
t.isIdentifier(node.callee.object.property, platform) &&
isImportOrGlobal(
node.callee.object.object, scope, [React, ReactNative], isWrappedModule);
const isDev = (node, parent, scope) =>
t.isIdentifier(node, dev) &&
isGlobalOrFlowDeclared(scope.getBinding(dev.name)) &&
!(t.isMemberExpression(parent));
function findProperty(objectExpression, key, fallback) {
const property = objectExpression.properties.find(p => p.key.name === key);
return property ? property.value : fallback();
}
const inlinePlugin = {
visitor: {
Identifier(path, state) {
if (isDev(path.node, path.parent, path.scope)) {
path.replaceWith(t.booleanLiteral(state.opts.dev));
}
},
MemberExpression(path, state) {
const node = path.node;
const scope = path.scope;
const opts = state.opts;
if (
isPlatformOS(node, scope, opts.isWrapped) ||
isReactPlatformOS(node, scope, opts.isWrapped)
) {
path.replaceWith(t.stringLiteral(opts.platform));
} else if (isProcessEnvNodeEnv(node, scope)) {
path.replaceWith(
t.stringLiteral(opts.dev ? 'development' : 'production'));
}
},
CallExpression(path, state) {
const node = path.node;
const scope = path.scope;
const arg = node.arguments[0];
const opts = state.opts;
if (
isPlatformSelect(node, scope, opts.isWrapped) ||
isReactPlatformSelect(node, scope, opts.isWrapped)
) {
const fallback = () =>
findProperty(arg, 'default', () => t.identifier('undefined'));
const replacement = t.isObjectExpression(arg)
? findProperty(arg, opts.platform, fallback)
: node;
path.replaceWith(replacement);
}
},
},
};
const plugin = () => inlinePlugin;
function checkRequireArgs(args, dependencyId) {
const pattern = t.stringLiteral(dependencyId);
return t.isStringLiteral(args[0], pattern) ||
t.isMemberExpression(args[0]) &&
t.isNumericLiteral(args[0].property) &&
t.isStringLiteral(args[1], pattern);
}
type AstResult = {
ast: Ast,
code: ?string,
map: ?MappingsMap,
};
function inline(
filename: string,
transformResult: {ast?: ?Ast, code: string, map: ?MappingsMap},
options: {+dev: boolean, +platform: ?string},
): AstResult {
const code = transformResult.code;
const babelOptions = {
filename,
plugins: [[plugin, options]],
inputSourceMap: transformResult.map,
sourceMaps: true,
sourceFileName: filename,
code: false,
babelrc: false,
compact: true,
};
const result = transformResult.ast
? babel.transformFromAst(transformResult.ast, code, babelOptions)
: babel.transform(code, babelOptions);
const {ast} = result;
invariant(ast != null, 'Missing AST in babel transform results.');
return {ast, code: result.code, map: result.map};
}
inline.plugin = inlinePlugin;
module.exports = inline;
@@ -1,34 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const uglify = require('uglify-js');
const {UGLIFY_JS_OUTPUT_OPTIONS} = require('./JsMinification');
import type {MappingsMap} from '../../lib/SourceMap';
function minify(filename: string, inputCode: string, sourceMap: ?MappingsMap) {
const result = uglify.minify(inputCode, {
fromString: true,
inSourceMap: sourceMap,
outSourceMap: true,
output: UGLIFY_JS_OUTPUT_OPTIONS,
});
const code = result.code;
const map = JSON.parse(result.map);
map.sources = [filename];
return {code, map};
}
module.exports = minify;
-33
View File
@@ -1,33 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*
*/
'use strict';
export type ActionLogEntryData = {
action_name: string,
};
export type ActionStartLogEntry = {
action_name?: string,
action_phase?: string,
log_entry_label: string,
log_session?: string,
start_timestamp?: [number, number],
};
export type LogEntry = {
action_name?: string,
action_phase?: string,
duration_ms?: number,
log_entry_label: string,
log_session?: string,
start_timestamp?: [number, number],
};
-37
View File
@@ -1,37 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const mockColor = () => {
return {
bold: () => { return { }; },
};
};
mockColor.bold = function() {
return {};
};
mockColor.bgRed = function() {
return {};
};
module.exports = {
dim: s => s,
magenta: mockColor,
white: mockColor,
blue: mockColor,
yellow: mockColor,
green: mockColor,
bold: mockColor,
red: mockColor,
cyan: mockColor,
gray: mockColor,
black: mockColor,
};
@@ -1,66 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* eslint-disable no-console-disallow
*
*/
'use strict';
jest.disableAutomock();
const {
createEntry,
createActionStartEntry,
createActionEndEntry,
} = require('../');
describe('Logger', () => {
const originalConsoleLog = console.log;
beforeEach(() => {
console.log = jest.fn();
});
afterEach(() => {
console.log = originalConsoleLog;
});
it('creates simple log entries', () => {
const logEntry = createEntry('Test');
expect(logEntry).toEqual({
log_entry_label: 'Test',
log_session: jasmine.any(String),
packager_version: jasmine.any(String),
});
});
it('creates action start log entries', () => {
const actionStartLogEntry = createActionStartEntry('Test');
expect(actionStartLogEntry).toEqual({
action_name: 'Test',
action_phase: 'start',
log_entry_label: 'Test',
log_session: jasmine.any(String),
packager_version: jasmine.any(String),
start_timestamp: jasmine.any(Object),
});
});
it('creates action end log entries', () => {
const actionEndLogEntry = createActionEndEntry(createActionStartEntry('Test'));
expect(actionEndLogEntry).toEqual({
action_name: 'Test',
action_phase: 'end',
duration_ms: jasmine.any(Number),
log_entry_label: 'Test',
log_session: jasmine.any(String),
packager_version: jasmine.any(String),
start_timestamp: jasmine.any(Object),
});
});
});
-90
View File
@@ -1,90 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const os = require('os');
const {EventEmitter} = require('events');
const VERSION = require('../../package.json').version;
import type {
ActionLogEntryData,
ActionStartLogEntry,
LogEntry,
} from './Types';
const log_session = `${os.hostname()}-${Date.now()}`;
const eventEmitter = new EventEmitter();
function on(event: string, handler: (logEntry: LogEntry) => void): void {
eventEmitter.on(event, handler);
}
function createEntry(data: LogEntry | string): LogEntry {
const logEntry = typeof data === 'string' ? {log_entry_label: data} : data;
return {
...logEntry,
log_session,
packager_version: VERSION,
};
}
function createActionStartEntry(data: ActionLogEntryData | string): LogEntry {
const logEntry = typeof data === 'string' ? {action_name: data} : data;
const {action_name} = logEntry;
return createEntry({
...logEntry,
action_name,
action_phase: 'start',
log_entry_label: action_name,
start_timestamp: process.hrtime(),
});
}
function createActionEndEntry(logEntry: ActionStartLogEntry): LogEntry {
const {
action_name,
action_phase,
start_timestamp,
} = logEntry;
if (action_phase !== 'start' || !Array.isArray(start_timestamp)) {
throw new Error('Action has not started or has already ended');
}
const timeDelta = process.hrtime(start_timestamp);
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
return createEntry({
...logEntry,
action_name,
action_phase: 'end',
duration_ms,
log_entry_label: action_name,
});
}
function log(logEntry: LogEntry): LogEntry {
eventEmitter.emit('log', logEntry);
return logEntry;
}
module.exports = {
on,
createEntry,
createActionStartEntry,
createActionEndEntry,
log,
};
-170
View File
@@ -1,170 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const emptyFunction = require('fbjs/lib/emptyFunction');
const invariant = require('fbjs/lib/invariant');
const memoize = require('async/memoize');
const emptyModule = require('./module').empty;
const nullthrows = require('fbjs/lib/nullthrows');
const queue = require('async/queue');
const seq = require('async/seq');
import type {
Callback,
File,
GraphFn,
LoadFn,
ResolveFn,
} from './types.flow';
type Async$Queue<T, C> = {
buffer: number,
concurrency: number,
drain: () => mixed,
empty: () => mixed,
error: (Error, T) => mixed,
idle(): boolean,
kill(): void,
length(): number,
pause(): void,
paused: boolean,
push(T | Array<T>, void | C): void,
resume(): void,
running(): number,
saturated: () => mixed,
started: boolean,
unsaturated: () => mixed,
unshift(T, void | C): void,
workersList(): Array<T>,
};
type LoadQueue =
Async$Queue<{id: string, parent: ?string}, Callback<File, Array<string>>>;
const NO_OPTIONS = {};
exports.create = function create(resolve: ResolveFn, load: LoadFn): GraphFn {
function Graph(entryPoints, platform, options, callback = emptyFunction) {
const {
log = (console: any),
optimize = false,
skip,
} = options || NO_OPTIONS;
if (typeof platform !== 'string') {
log.error('`Graph`, called without a platform');
callback(Error('The target platform has to be passed'));
return;
}
const loadQueue: LoadQueue = queue(seq(
({id, parent}, cb) => resolve(id, parent, platform, options || NO_OPTIONS, cb),
memoize((file, cb) => load(file, {log, optimize}, cb)),
), Number.MAX_SAFE_INTEGER);
const {collect, loadModule} = createGraphHelpers(loadQueue, skip);
loadQueue.drain = () => {
loadQueue.kill();
callback(null, collect());
};
loadQueue.error = error => {
loadQueue.error = emptyFunction;
loadQueue.kill();
callback(error);
};
let i = 0;
for (const entryPoint of entryPoints) {
loadModule(entryPoint, null, i++);
}
if (i === 0) {
log.error('`Graph` called without any entry points');
loadQueue.kill();
callback(Error('At least one entry point has to be passed.'));
}
}
return Graph;
};
function createGraphHelpers(loadQueue, skip) {
const modules = new Map([[null, emptyModule()]]);
function collect(
path = null,
serialized = {entryModules: [], modules: []},
seen = new Set(),
) {
const module = modules.get(path);
if (module == null || seen.has(path)) {
return serialized;
}
const {dependencies} = module;
if (path === null) {
serialized.entryModules =
dependencies.map(dep => nullthrows(modules.get(dep.path)));
} else {
serialized.modules.push(module);
seen.add(path);
}
for (const dependency of dependencies) {
collect(dependency.path, serialized, seen);
}
return serialized;
}
function loadModule(id, parent, parentDepIndex) {
loadQueue.push(
{id, parent},
(error, file, dependencyIDs) =>
onFileLoaded(error, file, dependencyIDs, id, parent, parentDepIndex),
);
}
function onFileLoaded(
error,
file,
dependencyIDs,
id,
parent,
parentDependencyIndex,
) {
if (error) {
return;
}
const {path} = nullthrows(file);
dependencyIDs = nullthrows(dependencyIDs);
const parentModule = modules.get(parent);
invariant(parentModule, 'Invalid parent module: ' + String(parent));
parentModule.dependencies[parentDependencyIndex] = {id, path};
if ((!skip || !skip.has(path)) && !modules.has(path)) {
const module = {
dependencies: Array(dependencyIDs.length),
file: nullthrows(file),
};
modules.set(path, module);
for (let i = 0; i < dependencyIDs.length; ++i) {
loadModule(dependencyIDs[i], path, i);
}
}
}
return {collect, loadModule};
}
-113
View File
@@ -1,113 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const defaults = require('../defaults');
const nullthrows = require('fbjs/lib/nullthrows');
const parallel = require('async/parallel');
const seq = require('async/seq');
const virtualModule = require('./module').virtual;
import type {
BuildResult,
Callback,
GraphFn,
GraphResult,
Module,
PostProcessModules,
} from './types.flow';
type BuildFn = (
entryPoints: Iterable<string>,
options: BuildOptions,
callback: Callback<BuildResult>,
) => void;
type BuildOptions = {|
optimize: boolean,
platform: string,
|};
exports.createBuildSetup = (
graph: GraphFn,
postProcessModules: PostProcessModules,
translateDefaultsPath: string => string = x => x,
): BuildFn =>
(entryPoints, options, callback) => {
const {
optimize = false,
platform = defaults.platforms[0],
} = options;
const graphOptions = {optimize};
const graphWithOptions =
(entry, cb) => graph(entry, platform, graphOptions, cb);
const graphOnlyModules = seq(graphWithOptions, getModules);
parallel({
graph: cb => graphWithOptions(entryPoints, (error, result) => {
if (error) {
cb(error);
return;
}
/* $FlowFixMe: not undefined if there is no error */
const {modules, entryModules} = result;
const prModules = postProcessModules(modules, [...entryPoints]);
cb(null, {modules: prModules, entryModules});
}),
moduleSystem: cb => graphOnlyModules(
[translateDefaultsPath(defaults.moduleSystem)],
cb,
),
polyfills: cb => graphOnlyModules(
defaults.polyfills.map(translateDefaultsPath),
cb,
),
}, (
error: ?Error,
result?: {graph: GraphResult, moduleSystem: Array<Module>, polyfills: Array<Module>},
) => {
if (error) {
callback(error);
return;
}
const {
graph: {modules, entryModules},
moduleSystem,
polyfills,
} = nullthrows(result);
const preludeScript = prelude(optimize);
const prependedScripts = [preludeScript, ...moduleSystem, ...polyfills];
callback(null, {
entryModules,
modules: concat(prependedScripts, modules),
prependedScripts,
});
});
};
const getModules = (x, cb) => cb(null, x.modules);
function* concat<T>(...iterables: Array<Iterable<T>>): Iterable<T> {
for (const it of iterables) {
yield* it;
}
}
function prelude(optimize) {
return virtualModule(
`var __DEV__=${String(!optimize)},` +
'__BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now();'
);
}
@@ -1,378 +0,0 @@
/**
* Copyright (c) 2016-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';
jest
.disableAutomock()
.useRealTimers()
.mock('console');
const {Console} = require('console');
const Graph = require('../Graph');
const {fn} = require('../test-helpers');
const {any, objectContaining} = jasmine;
const quiet = new Console();
describe('Graph:', () => {
const anyEntry = ['arbitrary/entry/point'];
const anyPlatform = 'arbitrary platform';
const noOpts = undefined;
let graph, load, resolve;
beforeEach(() => {
load = fn();
resolve = fn();
resolve.stub.yields(null, 'arbitrary file');
load.stub.yields(null, createFile('arbitrary file'), []);
graph = Graph.create(resolve, load);
});
it('calls back an error when called without any entry point', done => {
graph([], anyPlatform, {log: quiet}, error => {
expect(error).toEqual(any(Error));
done();
});
});
it('resolves the entry point with the passed-in `resolve` function', done => {
const entryPoint = '/arbitrary/path';
graph([entryPoint], anyPlatform, noOpts, () => {
expect(resolve).toBeCalledWith(
entryPoint, null, any(String), any(Object), any(Function));
done();
});
});
it('allows to specify multiple entry points', done => {
const entryPoints = ['Arbitrary', '../entry.js'];
graph(entryPoints, anyPlatform, noOpts, () => {
expect(resolve).toBeCalledWith(
entryPoints[0], null, any(String), any(Object), any(Function));
expect(resolve).toBeCalledWith(
entryPoints[1], null, any(String), any(Object), any(Function));
done();
});
});
it('calls back with an error when called without `platform` option', done => {
graph(anyEntry, undefined, {log: quiet}, error => {
expect(error).toEqual(any(Error));
done();
});
});
it('forwards a passed-in `platform` to `resolve`', done => {
const platform = 'any';
graph(anyEntry, platform, noOpts, () => {
expect(resolve).toBeCalledWith(
any(String), null, platform, any(Object), any(Function));
done();
});
});
it('forwards a passed-in `log` option to `resolve`', done => {
const log = new Console();
graph(anyEntry, anyPlatform, {log}, () => {
expect(resolve).toBeCalledWith(
any(String), null, any(String), objectContaining({log}), any(Function));
done();
});
});
it('calls back with every error produced by `resolve`', done => {
const error = Error();
resolve.stub.yields(error);
graph(anyEntry, anyPlatform, noOpts, e => {
expect(e).toBe(error);
done();
});
});
it('only calls back once if two parallel invocations of `resolve` fail', done => {
load.stub.yields(null, createFile('with two deps'), ['depA', 'depB']);
resolve.stub
.withArgs('depA').yieldsAsync(new Error())
.withArgs('depB').yieldsAsync(new Error());
let calls = 0;
function callback() {
if (calls === 0) {
process.nextTick(() => {
expect(calls).toEqual(1);
done();
});
}
++calls;
}
graph(['entryA', 'entryB'], anyPlatform, noOpts, callback);
});
it('passes the files returned by `resolve` on to the `load` function', done => {
const modules = new Map([
['Arbitrary', '/absolute/path/to/Arbitrary.js'],
['../entry.js', '/whereever/is/entry.js'],
]);
for (const [id, file] of modules) {
resolve.stub.withArgs(id).yields(null, file);
}
const [file1, file2] = modules.values();
graph(modules.keys(), anyPlatform, noOpts, () => {
expect(load).toBeCalledWith(file1, any(Object), any(Function));
expect(load).toBeCalledWith(file2, any(Object), any(Function));
done();
});
});
it('passes the `optimize` flag on to `load`', done => {
graph(anyEntry, anyPlatform, {optimize: true}, () => {
expect(load).toBeCalledWith(
any(String), objectContaining({optimize: true}), any(Function));
done();
});
});
it('uses `false` as the default for the `optimize` flag', done => {
graph(anyEntry, anyPlatform, noOpts, () => {
expect(load).toBeCalledWith(
any(String), objectContaining({optimize: false}), any(Function));
done();
});
});
it('forwards a passed-in `log` to `load`', done => {
const log = new Console();
graph(anyEntry, anyPlatform, {log}, () => {
expect(load)
.toBeCalledWith(any(String), objectContaining({log}), any(Function));
done();
});
});
it('calls back with every error produced by `load`', done => {
const error = Error();
load.stub.yields(error);
graph(anyEntry, anyPlatform, noOpts, e => {
expect(e).toBe(error);
done();
});
});
it('resolves any dependencies provided by `load`', done => {
const entryPath = '/path/to/entry.js';
const id1 = 'required/id';
const id2 = './relative/import';
resolve.stub.withArgs('entry').yields(null, entryPath);
load.stub.withArgs(entryPath)
.yields(null, {path: entryPath}, [id1, id2]);
graph(['entry'], anyPlatform, noOpts, () => {
expect(resolve).toBeCalledWith(
id1, entryPath, any(String), any(Object), any(Function));
expect(resolve).toBeCalledWith(
id2, entryPath, any(String), any(Object), any(Function));
done();
});
});
it('loads transitive dependencies', done => {
const entryPath = '/path/to/entry.js';
const id1 = 'required/id';
const id2 = './relative/import';
const path1 = '/path/to/dep/1';
const path2 = '/path/to/dep/2';
resolve.stub
.withArgs(id1).yields(null, path1)
.withArgs(id2).yields(null, path2)
.withArgs('entry').yields(null, entryPath);
load.stub
.withArgs(entryPath).yields(null, {path: entryPath}, [id1])
.withArgs(path1).yields(null, {path: path1}, [id2]);
graph(['entry'], anyPlatform, noOpts, () => {
expect(resolve).toBeCalledWith(id2, path1, any(String), any(Object), any(Function));
expect(load).toBeCalledWith(path1, any(Object), any(Function));
expect(load).toBeCalledWith(path2, any(Object), any(Function));
done();
});
});
it('resolves modules in depth-first traversal order, regardless of the order of resolution',
done => {
load.stub.reset();
resolve.stub.reset();
const ids = [
'a',
'b',
'c', 'd',
'e',
'f', 'g',
'h',
];
ids.forEach(id => {
const path = idToPath(id);
resolve.stub.withArgs(id).yields(null, path);
load.stub.withArgs(path).yields(null, createFile(id), []);
});
load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b', 'e', 'h']);
load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), ['c', 'd']);
load.stub.withArgs(idToPath('e')).yields(null, createFile('e'), ['f', 'g']);
// load certain ids later
['b', 'e', 'h'].forEach(id => resolve.stub.withArgs(id).resetBehavior());
resolve.stub.withArgs('h').func = (a, b, c, d, callback) => {
callback(null, idToPath('h'));
['e', 'b'].forEach(
id => resolve.stub.withArgs(id).yield(null, idToPath(id)));
};
graph(['a'], anyPlatform, noOpts, (error, result) => {
expect(error).toEqual(null);
expect(result.modules).toEqual([
createModule('a', ['b', 'e', 'h']),
createModule('b', ['c', 'd']),
createModule('c'),
createModule('d'),
createModule('e', ['f', 'g']),
createModule('f'),
createModule('g'),
createModule('h'),
]);
done();
});
},
);
it('calls back with the resolved modules of the entry points', done => {
load.stub.reset();
resolve.stub.reset();
load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b']);
load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), []);
load.stub.withArgs(idToPath('c')).yields(null, createFile('c'), ['d']);
load.stub.withArgs(idToPath('d')).yields(null, createFile('d'), []);
'abcd'.split('')
.forEach(id => resolve.stub.withArgs(id).yields(null, idToPath(id)));
graph(['a', 'c'], anyPlatform, noOpts, (error, result) => {
expect(result.entryModules).toEqual([
createModule('a', ['b']),
createModule('c', ['d']),
]);
done();
});
});
it('resolves modules for all entry points correctly if one is a dependency of another', done => {
load.stub.reset();
resolve.stub.reset();
load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b']);
load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), []);
'ab'.split('')
.forEach(id => resolve.stub.withArgs(id).yields(null, idToPath(id)));
graph(['a', 'b'], anyPlatform, noOpts, (error, result) => {
expect(result.entryModules).toEqual([
createModule('a', ['b']),
createModule('b', []),
]);
done();
});
});
it('does not include dependencies more than once', done => {
const ids = ['a', 'b', 'c', 'd'];
ids.forEach(id => {
const path = idToPath(id);
resolve.stub.withArgs(id).yields(null, path);
load.stub.withArgs(path).yields(null, createFile(id), []);
});
['a', 'd'].forEach(id =>
load.stub
.withArgs(idToPath(id)).yields(null, createFile(id), ['b', 'c']));
graph(['a', 'd', 'b'], anyPlatform, noOpts, (error, result) => {
expect(error).toEqual(null);
expect(result.modules).toEqual([
createModule('a', ['b', 'c']),
createModule('b'),
createModule('c'),
createModule('d', ['b', 'c']),
]);
done();
});
});
it('handles dependency cycles', done => {
resolve.stub
.withArgs('a').yields(null, idToPath('a'))
.withArgs('b').yields(null, idToPath('b'))
.withArgs('c').yields(null, idToPath('c'));
load.stub
.withArgs(idToPath('a')).yields(null, createFile('a'), ['b'])
.withArgs(idToPath('b')).yields(null, createFile('b'), ['c'])
.withArgs(idToPath('c')).yields(null, createFile('c'), ['a']);
graph(['a'], anyPlatform, noOpts, (error, result) => {
expect(result.modules).toEqual([
createModule('a', ['b']),
createModule('b', ['c']),
createModule('c', ['a']),
]);
done();
});
});
it('can skip files', done => {
['a', 'b', 'c', 'd', 'e'].forEach(
id => resolve.stub.withArgs(id).yields(null, idToPath(id)));
load.stub
.withArgs(idToPath('a')).yields(null, createFile('a'), ['b', 'c', 'd'])
.withArgs(idToPath('b')).yields(null, createFile('b'), ['e']);
['c', 'd', 'e'].forEach(id =>
load.stub.withArgs(idToPath(id)).yields(null, createFile(id), []));
const skip = new Set([idToPath('b'), idToPath('c')]);
graph(['a'], anyPlatform, {skip}, (error, result) => {
expect(result.modules).toEqual([
createModule('a', ['b', 'c', 'd']),
createModule('d', []),
]);
done();
});
});
});
function createDependency(id) {
return {id, path: idToPath(id)};
}
function createFile(id) {
return {ast: {}, path: idToPath(id)};
}
function createModule(id, dependencies = []): Module {
return {
file: createFile(id),
dependencies: dependencies.map(createDependency),
};
}
function idToPath(id) {
return '/path/to/' + id;
}
@@ -1,109 +0,0 @@
/**
* Copyright (c) 2017-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';
jest.disableAutomock();
const ModuleGraph = require('../ModuleGraph');
const defaults = require('../../defaults');
const FILE_TYPE = 'module';
describe('build setup', () => {
const buildSetup = ModuleGraph.createBuildSetup(graph, mds => {
return [...mds].sort((l, r) => l.file.path > r.file.path);
});
const noOptions = {};
const noEntryPoints = [];
it('adds a prelude containing start time and `__DEV__` to the build', done => {
buildSetup(noEntryPoints, noOptions, (error, result) => {
expect(error).toEqual(null);
const [prelude] = result.modules;
expect(prelude).toEqual({
dependencies: [],
file: {
code: 'var __DEV__=true,__BUNDLE_START_TIME__=' +
'this.nativePerformanceNow?nativePerformanceNow():Date.now();',
map: null,
path: '',
type: 'script',
},
});
done();
});
});
it('sets `__DEV__` to false in the prelude if optimization is enabled', done => {
buildSetup(noEntryPoints, {optimize: true}, (error, result) => {
const [prelude] = result.modules;
expect(prelude.file.code)
.toEqual('var __DEV__=false,__BUNDLE_START_TIME__=' +
'this.nativePerformanceNow?nativePerformanceNow():Date.now();');
done();
});
});
it('places the module system implementation directly after the prelude', done => {
buildSetup(noEntryPoints, noOptions, (error, result) => {
const [, moduleSystem] = result.modules;
expect(moduleSystem).toEqual({
dependencies: [],
file: {
code: '',
path: defaults.moduleSystem,
type: FILE_TYPE,
},
});
done();
});
});
it('places polyfills after the module system', done => {
buildSetup(noEntryPoints, noOptions, (error, result) => {
const polyfills =
Array.from(result.modules).slice(2, 2 + defaults.polyfills.length);
expect(polyfills).toEqual(defaults.polyfills.map(moduleFromPath));
done();
});
});
it('places all entry points and dependencies at the end, post-processed', done => {
const entryPoints = ['b', 'c', 'd'];
buildSetup(entryPoints, noOptions, (error, result) => {
expect(Array.from(result.modules).slice(-4))
.toEqual(['a', 'b', 'c', 'd'].map(moduleFromPath));
done();
});
});
});
function moduleFromPath(path) {
return {
dependencies: path === 'b' ? ['a'] : [],
file: {
code: '',
path,
type: FILE_TYPE,
},
};
}
function graph(entryPoints, platform, options, callback) {
const modules = Array.from(entryPoints, moduleFromPath);
const depModules = Array.prototype.concat.apply(
[],
modules.map(x => x.dependencies.map(moduleFromPath)),
);
callback(null, {
entryModules: modules,
modules: modules.concat(depModules),
});
}
-28
View File
@@ -1,28 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
import type {Module} from './types.flow';
exports.empty = (): Module => virtual('');
// creates a virtual module (i.e. not corresponding to a file on disk)
// with the given source code.
const virtual = exports.virtual = (code: string): Module => ({
dependencies: [],
file: {
code,
map: null,
path: '',
type: 'script',
},
});
@@ -1,90 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const {dirname, join, parse} = require('path');
module.exports = class HasteFS {
directories: Set<string>;
directoryEntries: Map<string, Array<string>>;
files: Set<string>;
constructor(files: Array<string>) {
this.directories = buildDirectorySet(files);
this.directoryEntries = buildDirectoryEntries(files.map(parse));
this.files = new Set(files);
}
closest(path: string, fileName: string): ?string {
const parsedPath = parse(path);
const root = parsedPath.root;
let dir = parsedPath.dir;
do {
const candidate = join(dir, fileName);
if (this.files.has(candidate)) {
return candidate;
}
dir = dirname(dir);
} while (dir !== '.' && dir !== root);
return null;
}
dirExists(path: string) {
return this.directories.has(path);
}
exists(path: string) {
return this.files.has(path);
}
getAllFiles() {
return Array.from(this.files.keys());
}
matchFiles() {
throw new Error(
'HasteFS.matchFiles is not implemented yet.'
);
}
matches(directory: string, pattern: RegExp) {
const entries = this.directoryEntries.get(directory);
return entries ? entries.filter(pattern.test, pattern) : [];
}
};
function buildDirectorySet(files) {
const directories = new Set();
files.forEach(path => {
const parsedPath = parse(path);
const root = parsedPath.root;
let dir = parsedPath.dir;
while (dir !== '.' && dir !== root && !directories.has(dir)) {
directories.add(dir);
dir = dirname(dir);
}
});
return directories;
}
function buildDirectoryEntries(files) {
const directoryEntries = new Map();
files.forEach(({base, dir}) => {
const entries = directoryEntries.get(dir);
if (entries) {
entries.push(base);
} else {
directoryEntries.set(dir, [base]);
}
});
return directoryEntries;
}
@@ -1,64 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
import type {CachedReadResult, ReadResult} from '../../node-haste/Module';
import type {TransformedCodeFile} from '../types.flow';
import type ModuleCache from './ModuleCache';
module.exports = class Module {
hasteID: ?string;
moduleCache: ModuleCache;
name: Promise<string>;
path: string;
type: 'Module';
constructor(
path: string,
moduleCache: ModuleCache,
info: TransformedCodeFile,
) {
this.hasteID = info.hasteID;
this.moduleCache = moduleCache;
this.name = Promise.resolve(this.hasteID || getName(path));
this.path = path;
this.type = 'Module';
}
readCached(): CachedReadResult {
throw new Error('not implemented');
}
readFresh(): Promise<ReadResult> {
return Promise.reject(new Error('not implemented'));
}
getName() {
return this.name;
}
getPackage() {
return this.moduleCache.getPackageOf(this.path);
}
isHaste() {
return Boolean(this.hasteID);
}
hash() {
throw new Error('not implemented');
}
};
function getName(path) {
return path.replace(/^.*[\/\\]node_modules[\///]/, '');
}
@@ -1,71 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const Module = require('./Module');
const Package = require('./Package');
import type {PackageData, TransformedCodeFile} from '../types.flow';
type GetClosestPackageFn = (filePath: string) => ?string;
module.exports = class ModuleCache {
_getClosestPackage: GetClosestPackageFn;
getTransformedFile: string => TransformedCodeFile;
modules: Map<string, Module>;
packages: Map<string, Package>;
constructor(
getClosestPackage: GetClosestPackageFn,
getTransformedFile: string => TransformedCodeFile,
) {
this._getClosestPackage = getClosestPackage;
this.getTransformedFile = getTransformedFile;
this.modules = new Map();
this.packages = new Map();
}
getAssetModule(path: string): Module {
return this.getModule(path);
}
getModule(path: string): Module {
let m = this.modules.get(path);
if (!m) {
m = new Module(path, this, this.getTransformedFile(path));
this.modules.set(path, m);
}
return m;
}
getPackage(path: string): Package {
let p = this.packages.get(path);
if (!p) {
p = new Package(path, this.getPackageData(path));
this.packages.set(path, p);
}
return p;
}
getPackageData(path: string): PackageData {
const pkg = this.getTransformedFile(path).package;
if (!pkg) {
throw new Error(`"${path}" does not exist`);
}
return pkg;
}
getPackageOf(filePath: string): ?Package {
const candidate = this._getClosestPackage(filePath);
return candidate != null ? this.getPackage(candidate) : null;
}
};
@@ -1,135 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const nullthrows = require('fbjs/lib/nullthrows');
const path = require('path');
import type {PackageData} from '../types.flow';
module.exports = class Package {
data: PackageData;
path: string;
root: string;
type: 'Package';
constructor(packagePath: string, data: PackageData) {
this.data = data;
this.path = packagePath;
this.root = path.dirname(packagePath);
this.type = 'Package';
}
getMain() {
// Copied from node-haste/Package.js
const replacements = getReplacements(this.data);
if (typeof replacements === 'string') {
return path.join(this.root, replacements);
}
let main = getMain(this.data);
if (replacements && typeof replacements === 'object') {
main = replacements[main] ||
replacements[main + '.js'] ||
replacements[main + '.json'] ||
replacements[main.replace(/(\.js|\.json)$/, '')] ||
main;
}
return path.join(this.root, main);
}
getName(): Promise<string> {
return Promise.resolve(nullthrows(this.data.name));
}
isHaste(): boolean {
return !!this.data.name;
}
redirectRequire(name: string) {
// Copied from node-haste/Package.js
const replacements = getReplacements(this.data);
if (!replacements || typeof replacements !== 'object') {
return name;
}
if (!path.isAbsolute(name)) {
const replacement = replacements[name];
// support exclude with "someDependency": false
return replacement === false
? false
: replacement || name;
}
let relPath = './' + path.relative(this.root, name);
if (path.sep !== '/') {
relPath = relPath.replace(new RegExp('\\' + path.sep, 'g'), '/');
}
let redirect = replacements[relPath];
// false is a valid value
if (redirect == null) {
redirect = replacements[relPath + '.js'];
if (redirect == null) {
redirect = replacements[relPath + '.json'];
}
}
// support exclude with "./someFile": false
if (redirect === false) {
return false;
}
if (redirect) {
return path.join(
this.root,
redirect
);
}
return name;
}
};
function getMain(pkg) {
return pkg.main || 'index';
}
// Copied from node-haste/Package.js
function getReplacements(pkg) {
let rn = pkg['react-native'];
let browser = pkg.browser;
if (rn == null) {
return browser;
}
if (browser == null) {
return rn;
}
const main = getMain(pkg);
if (typeof rn !== 'object') {
rn = {[main]: rn};
}
if (typeof browser !== 'object') {
browser = {[main]: browser};
}
// merge with "browser" as default,
// "react-native" as override
return {...browser, ...rn};
}
@@ -1,70 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
'use strict';
import DependencyGraphHelpers from '../../node-haste/DependencyGraph/DependencyGraphHelpers';
type ModuleID = string;
export type Path = string;
type Platform = string;
type Platforms = Set<Platform>;
export type Extensions = Array<string>;
export type Module = {
path: Path,
type: 'Module',
getName(): Promise<ModuleID>,
getPackage(): ?Package,
isHaste(): Promise<boolean>,
};
export type Package = {
path: Path,
root: Path,
type: 'Package',
getMain(): Path,
getName(): Promise<ModuleID>,
isHaste(): Promise<boolean>,
redirectRequire(id: ModuleID): Path | false,
};
export type ModuleCache = {
getAssetModule(path: Path): Module,
getModule(path: Path): Module,
getPackage(path: Path): Package,
getPackageOf(path: Path): ?Package,
}
export type FastFS = {
dirExists(path: Path): boolean,
closest(path: string, fileName: string): ?string,
fileExists(path: Path): boolean,
getAllFiles(): Array<Path>,
matches(directory: Path, pattern: RegExp): Array<Path>,
};
type HasteMapOptions = {|
extensions: Extensions,
files: Array<string>,
helpers: DependencyGraphHelpers,
moduleCache: ModuleCache,
platforms: Platforms,
preferNativePlatform: true,
|};
declare class HasteMap {
// node-haste/DependencyGraph/HasteMap.js
build(): Promise<Object>,
constructor(options: HasteMapOptions): void,
}
@@ -1,149 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
import type { // eslint-disable-line sort-requires
Extensions,
Path,
} from './node-haste.flow';
import type {
ResolveFn,
TransformedCodeFile,
} from '../types.flow';
const AssetResolutionCache = require('../../node-haste/AssetResolutionCache');
const DependencyGraphHelpers = require('../../node-haste/DependencyGraph/DependencyGraphHelpers');
const FilesByDirNameIndex = require('../../node-haste/FilesByDirNameIndex');
const HasteFS = require('./HasteFS');
const HasteMap = require('../../node-haste/DependencyGraph/HasteMap');
const Module = require('./Module');
const ModuleCache = require('./ModuleCache');
const ResolutionRequest = require('../../node-haste/DependencyGraph/ResolutionRequest');
const defaults = require('../../defaults');
import type {Moduleish, Packageish} from '../../node-haste/DependencyGraph/ResolutionRequest';
type ResolveOptions = {|
assetExts: Extensions,
extraNodeModules: {[id: string]: string},
+sourceExts: Extensions,
transformedFiles: {[path: Path]: TransformedCodeFile},
|};
const platforms = new Set(defaults.platforms);
/**
* We don't need to crawl the filesystem all over again so we just mock
* a jest-haste-map's ModuleMap instance. Eventually, though, we'll
* want to figure out how to reunify and get rid of `HasteMap`.
*/
function getFakeModuleMap(hasteMap: HasteMap<Module, Packageish>) {
return {
getModule(name: string, platform: ?string): ?string {
const module = hasteMap.getModule(name, platform);
return module && module.type === 'Module' ? module.path : null;
},
getPackage(name: string, platform: ?string): ?string {
const pkg = hasteMap.getPackage(name);
return pkg && pkg.path;
},
};
}
const nullModule: Moduleish = {
path: '/',
getPackage() {},
hash() {
throw new Error('not implemented');
},
readCached() { throw new Error('not implemented'); },
readFresh() { return Promise.reject(new Error('not implemented')); },
isHaste() { throw new Error('not implemented'); },
getName() { throw new Error('not implemented'); },
};
exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
const {
assetExts,
extraNodeModules,
transformedFiles,
sourceExts,
} = options;
const files = Object.keys(transformedFiles);
function getTransformedFile(path) {
const result = transformedFiles[path];
if (!result) {
throw new Error(`"${path} does not exist`);
}
return result;
}
const helpers = new DependencyGraphHelpers({
assetExts,
providesModuleNodeModules: defaults.providesModuleNodeModules,
});
const hasteFS = new HasteFS(files);
const moduleCache = new ModuleCache(
filePath => hasteFS.closest(filePath, 'package.json'),
getTransformedFile,
);
const hasteMap = new HasteMap({
extensions: sourceExts,
files,
helpers,
moduleCache,
platforms,
preferNativePlatform: true,
});
const hasteMapBuilt = hasteMap.build();
const resolutionRequests = {};
const filesByDirNameIndex = new FilesByDirNameIndex(hasteMap.getAllFiles());
const assetResolutionCache = new AssetResolutionCache({
assetExtensions: new Set(assetExts),
getDirFiles: dirPath => filesByDirNameIndex.getAllFiles(dirPath),
platforms,
});
return (id, source, platform, _, callback) => {
let resolutionRequest = resolutionRequests[platform];
if (!resolutionRequest) {
resolutionRequest = resolutionRequests[platform] = new ResolutionRequest({
dirExists: filePath => hasteFS.dirExists(filePath),
entryPath: '',
extraNodeModules,
hasteFS,
helpers,
moduleCache,
moduleMap: getFakeModuleMap(hasteMap),
platform,
preferNativePlatform: true,
resolveAsset: (dirPath, assetName) =>
assetResolutionCache.resolve(dirPath, assetName, platform),
sourceExts,
});
}
const from = source != null
? new Module(source, moduleCache, getTransformedFile(source))
: nullModule;
hasteMapBuilt
.then(() => resolutionRequest.resolveDependency(from, id))
.then(
// nextTick to escape promise error handling
module => process.nextTick(callback, null, module.path),
error => process.nextTick(callback, error),
);
};
};
@@ -1 +0,0 @@
{"main":"node-haste.js"}
@@ -1,297 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
declare var jest: any;
jest.disableAutomock();
const indexedRamBundle = require('../indexed-ram-bundle');
const {addModuleIdsToModuleWrapper} = require('../util');
declare var describe: any;
declare var expect: any;
declare var it: (string, () => ?Promise<any>) => void;
declare var beforeAll: (() => ?Promise<any>) => void;
let code: Buffer;
let map;
let ids, modules, requireCall;
const idForPath = ({path}) => getId(path);
beforeAll(() => {
modules = [
makeModule('a', [], 'script'),
makeModule('b', ['c']),
makeModule('c', ['f']),
makeModule('d', ['e']),
makeModule('e'),
makeModule('f'),
];
requireCall = makeModule('r', [], 'script', 'require(1);');
ids = new Map(modules.map(({file}, i) => [file.path, i]));
({code, map} = createRamBundle());
});
it('starts the bundle file with the magic number', () => {
expect(code.readUInt32LE(0)).toBe(0xFB0BD1E5);
});
it('contains the number of modules in the module table', () => {
expect(code.readUInt32LE(SIZEOF_INT32)).toBe(modules.length);
});
it('has the length correct of the startup section', () => {
expect(code.readUInt32LE(SIZEOF_INT32 * 2))
.toBe(requireCall.file.code.length + 1);
});
it('contains the code after the offset table', () => {
const {codeOffset, startupSectionLength, table} = parseOffsetTable(code);
const startupSection =
code.slice(codeOffset, codeOffset + startupSectionLength - 1);
expect(startupSection.toString()).toBe(requireCall.file.code);
table.forEach(([offset, length], i) => {
const moduleCode =
code.slice(codeOffset + offset, codeOffset + offset + length - 1);
expect(moduleCode.toString()).toBe(expectedCode(modules[i]));
});
});
it('creates a source map', () => {
let line = countLines(requireCall);
expect(map.sections.slice(1)).toEqual(modules.map(m => {
const section = {
map: m.file.map || lineByLineMap(m.file.path),
offset: {column: 0, line},
};
line += countLines(m);
return section;
}));
expect(map.x_facebook_offsets).toEqual([1, 2, 3, 4, 5, 6]);
});
describe('Startup section optimization', () => {
let last, preloaded;
beforeAll(() => {
last = modules[modules.length - 1];
preloaded = [modules[2], modules[3], last];
({code, map} = createRamBundle(new Set(preloaded.map(getPath))));
});
it('supports additional modules in the startup section', () => {
const {codeOffset, startupSectionLength, table} = parseOffsetTable(code);
const startupSection =
code.slice(codeOffset, codeOffset + startupSectionLength - 1);
expect(startupSection.toString())
.toBe(preloaded.concat([requireCall]).map(expectedCode).join('\n'));
preloaded.forEach(m => {
const idx = idForPath(m.file);
expect(table[idx]).toEqual(m === last ? undefined : [0, 0]);
});
table.forEach(([offset, length], i) => {
if (offset !== 0 && length !== 0) {
const moduleCode =
code.slice(codeOffset + offset, codeOffset + offset + length - 1);
expect(moduleCode.toString()).toBe(expectedCode(modules[i]));
}
});
});
it('reflects additional sources in the startup section in the source map', () => {
let line = preloaded.reduce(
(l, m) => l + countLines(m),
countLines(requireCall),
);
expect(map.x_facebook_offsets).toEqual([4, 5,,, 6]); // eslint-disable-line no-sparse-arrays
expect(map.sections.slice(1)).toEqual(
modules
.filter(not(Set.prototype.has), new Set(preloaded))
.map(m => {
const section = {
map: m.file.map || lineByLineMap(m.file.path),
offset: {column: 0, line},
};
line += countLines(m);
return section;
}
));
});
});
describe('RAM groups / common sections', () => {
let groups, groupHeads;
beforeAll(() => {
groups = [
[modules[1], modules[2], modules[5]],
[modules[3], modules[4]],
];
groupHeads = groups.map(g => g[0]);
({code, map} = createRamBundle(undefined, groupHeads.map(getPath)));
});
it('supports grouping the transitive dependencies of files into common sections', () => {
const {codeOffset, table} = parseOffsetTable(code);
groups.forEach(group => {
const [head, ...deps] = group.map(x => idForPath(x.file));
const groupEntry = table[head];
deps.forEach(id => expect(table[id]).toEqual(groupEntry));
const [offset, length] = groupEntry;
const groupCode = code.slice(codeOffset + offset, codeOffset + offset + length - 1);
expect(groupCode.toString())
.toEqual(group.map(expectedCode).join('\n'));
});
});
it('reflects section groups in the source map', () => {
expect(map.x_facebook_offsets).toEqual([1, 2, 2, 5, 5, 2]);
const maps = map.sections.slice(-2);
const toplevelOffsets = [2, 5];
maps.map((groupMap, i) => [groups[i], groupMap]).forEach(([group, groupMap], i) => {
const offsets = group.reduce(moduleLineOffsets, [])[0];
expect(groupMap).toEqual({
map: {
version: 3,
sections: group.map((module, j) => ({
map: module.file.map,
offset: {line: offsets[j], column: 0},
})),
},
offset: {line: toplevelOffsets[i], column: 0},
});
});
});
function moduleLineOffsets([offsets = [], line = 0], module) {
return [[...offsets, line], line + countLines(module)];
}
});
function createRamBundle(preloadedModules = new Set(), ramGroups) {
const build = indexedRamBundle.createBuilder(preloadedModules, ramGroups);
const result = build({
filename: 'arbitrary/filename.js',
idForPath,
modules,
requireCalls: [requireCall],
});
if (typeof result.code === 'string') {
throw new Error('Expected a buffer, not a string');
}
return {code: result.code, map: result.map};
}
function makeModule(name, deps = [], type = 'module', moduleCode = `var ${name};`) {
const path = makeModulePath(name);
return {
dependencies: deps.map(makeDependency),
file: {
code: type === 'module' ? makeModuleCode(moduleCode) : moduleCode,
map: type !== 'module'
? null
: makeModuleMap(name, path),
path,
type,
},
};
}
function makeModuleMap(name, path) {
return {
version: 3,
mappings: Array(parseInt(name, 36) + 1).join(','),
names: [name],
sources: [path],
};
}
function makeModuleCode(moduleCode) {
return `__d(() => {${moduleCode}})`;
}
function makeModulePath(name) {
return `/${name}.js`;
}
function makeDependency(name) {
const path = makeModulePath(name);
return {
id: name,
path,
};
}
function expectedCode(module) {
const {file} = module;
return file.type === 'module'
? addModuleIdsToModuleWrapper(module, idForPath)
: file.code;
}
function getId(path) {
if (path === requireCall.file.path) {
return -1;
}
const id = ids.get(path);
if (id == null) {
throw new Error(`Unknown file: ${path}`);
}
return id;
}
function getPath(module) {
return module.file.path;
}
const SIZEOF_INT32 = 4;
function parseOffsetTable(buffer) {
const n = buffer.readUInt32LE(SIZEOF_INT32);
const startupSectionLength = buffer.readUInt32LE(SIZEOF_INT32 * 2);
const baseOffset = SIZEOF_INT32 * 3;
const table = Array(n);
for (let i = 0; i < n; ++i) {
const offset = baseOffset + i * 2 * SIZEOF_INT32;
table[i] = [buffer.readUInt32LE(offset), buffer.readUInt32LE(offset + SIZEOF_INT32)];
}
return {
codeOffset: baseOffset + n * 2 * SIZEOF_INT32,
startupSectionLength,
table,
};
}
function countLines(module) {
return module.file.code.split('\n').length;
}
function lineByLineMap(file) {
return {
file,
mappings: 'AAAA;',
names: [],
sources: [file],
version: 3,
};
}
const not = fn => function() { return !fn.apply(this, arguments); };
@@ -1,83 +0,0 @@
/**
* Copyright (c) 2016-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';
jest.disableAutomock();
const {match} = require('sinon');
const {fn} = require('../../test-helpers');
const {
addModuleIdsToModuleWrapper,
createIdForPathFn,
} = require('../util');
const {any} = jasmine;
describe('`addModuleIdsToModuleWrapper`:', () => {
const path = 'path/to/file';
const createModule = (dependencies = []) => ({
dependencies,
file: {code: '__d(function(){});', isModule: true, path},
});
it('completes the module wrapped with module ID, and an array of dependency IDs', () => {
const dependencies = [
{id: 'a', path: 'path/to/a.js'},
{id: 'b', path: 'location/of/b.js'},
];
const module = createModule(dependencies);
const idForPath = fn();
idForPath.stub
.withArgs(match({path})).returns(12)
.withArgs(match({path: dependencies[0].path})).returns(345)
.withArgs(match({path: dependencies[1].path})).returns(6);
expect(addModuleIdsToModuleWrapper(module, idForPath))
.toEqual('__d(function(){},12,[345,6]);');
});
it('omits the array of dependency IDs if it is empty', () => {
const module = createModule();
expect(addModuleIdsToModuleWrapper(module, () => 98))
.toEqual(`__d(function(){},${98});`);
});
});
describe('`createIdForPathFn`', () => {
let idForPath;
beforeEach(() => {
idForPath = createIdForPathFn();
});
it('returns a number for a string', () => {
expect(idForPath({path: 'arbitrary'})).toEqual(any(Number));
});
it('returns consecutive numbers', () => {
const strings = [
'arbitrary string',
'looking/like/a/path',
'/absolute/path/to/file.js',
'/more files/are here',
];
strings.forEach((string, i) => {
expect(idForPath({path: string})).toEqual(i);
});
});
it('returns the same id if the same string is passed in again', () => {
const path = 'this/is/an/arbitrary/path.js';
const id = idForPath({path});
idForPath({path: '/other/file'});
idForPath({path: 'and/another/file'});
expect(idForPath({path})).toEqual(id);
});
});
@@ -1,106 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const buildSourceMapWithMetaData = require('../../shared/output/unbundle/build-unbundle-sourcemap-with-metadata.js');
const nullthrows = require('fbjs/lib/nullthrows');
const {createRamBundleGroups} = require('../../Bundler/util');
const {buildTableAndContents, createModuleGroups} = require('../../shared/output/unbundle/as-indexed-file');
const {addModuleIdsToModuleWrapper, concat} = require('./util');
import type {FBIndexMap} from '../../lib/SourceMap.js';
import type {OutputFn} from '../types.flow';
function asIndexedRamBundle({
filename,
idForPath,
modules,
preloadedModules,
ramGroupHeads,
requireCalls,
}) {
const [startup, deferred] = partition(modules, preloadedModules);
const startupModules = Array.from(concat(startup, requireCalls));
const deferredModules = deferred.map(m => toModuleTransport(m, idForPath));
const ramGroups = createRamBundleGroups(ramGroupHeads || [], deferredModules, subtree);
const moduleGroups = createModuleGroups(ramGroups, deferredModules);
const tableAndContents = buildTableAndContents(
startupModules.map(m => getModuleCode(m, idForPath)).join('\n'),
deferredModules,
moduleGroups,
'utf8',
);
return {
code: Buffer.concat(tableAndContents),
map: buildSourceMapWithMetaData({
fixWrapperOffset: false,
lazyModules: deferredModules,
moduleGroups,
startupModules: startupModules.map(m => toModuleTransport(m, idForPath)),
}),
};
}
function toModuleTransport(module, idForPath) {
const {dependencies, file} = module;
return {
code: getModuleCode(module, idForPath),
dependencies,
id: idForPath(file),
map: file.map,
name: file.path,
sourcePath: file.path,
};
}
function getModuleCode(module, idForPath) {
const {file} = module;
return file.type === 'module'
? addModuleIdsToModuleWrapper(module, idForPath)
: file.code;
}
function partition(modules, preloadedModules) {
const startup = [];
const deferred = [];
for (const module of modules) {
(preloadedModules.has(module.file.path) ? startup : deferred).push(module);
}
return [startup, deferred];
}
function *subtree(
moduleTransport,
moduleTransportsByPath,
seen = new Set(),
) {
seen.add(moduleTransport.id);
for (const {path} of moduleTransport.dependencies) {
const dependency = nullthrows(moduleTransportsByPath.get(path));
if (!seen.has(dependency.id)) {
yield dependency.id;
yield *subtree(dependency, moduleTransportsByPath, seen);
}
}
}
function createBuilder(
preloadedModules: Set<string>,
ramGroupHeads: ?$ReadOnlyArray<string>,
): OutputFn<FBIndexMap> {
return x => asIndexedRamBundle({...x, preloadedModules, ramGroupHeads});
}
exports.createBuilder = createBuilder;
@@ -1,64 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const meta = require('../../shared/output/meta');
const {createIndexMap} = require('./source-map');
const {addModuleIdsToModuleWrapper, concat} = require('./util');
import type {OutputFn} from '../types.flow';
function asPlainBundle({
filename,
idForPath,
modules,
requireCalls,
sourceMapPath,
}) {
let code = '';
let line = 0;
const sections = [];
for (const module of concat(modules, requireCalls)) {
const {file} = module;
const moduleCode = file.type === 'module'
? addModuleIdsToModuleWrapper(module, idForPath)
: file.code;
code += moduleCode + '\n';
if (file.map) {
sections.push({
map: file.map,
offset: {column: 0, line},
});
}
line += countLines(moduleCode);
}
if (sourceMapPath) {
code += `//# sourceMappingURL=${sourceMapPath}`;
}
return {
code,
extraFiles: [[`${filename}.meta`, meta(code)]],
map: createIndexMap({file: filename, sections}),
};
}
module.exports = (asPlainBundle: OutputFn<>);
const reLine = /^/gm;
function countLines(string: string): number {
//$FlowFixMe This regular expression always matches
return string.match(reLine).length;
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
import type {FBSourceMap, IndexMapSection, IndexMap} from '../../lib/SourceMap';
export type {FBSourceMap};
type CreateIndexMapOptions = {|
file?: string,
sections?: Array<IndexMapSection>
|};
exports.createIndexMap = (opts?: CreateIndexMapOptions): IndexMap => ({
version: 3,
file: opts && opts.file,
sections: opts && opts.sections || [],
});
-81
View File
@@ -1,81 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
const virtualModule = require('../module').virtual;
import type {IdForPathFn, Module} from '../types.flow';
// Transformed modules have the form
// __d(function(require, module, global, exports, dependencyMap) {
// /* code */
// });
//
// This function adds the numeric module ID, and an array with dependencies of
// the dependencies of the module before the closing parenthesis.
exports.addModuleIdsToModuleWrapper = (
module: Module,
idForPath: {path: string} => number,
): string => {
const {dependencies, file} = module;
const {code} = file;
const index = code.lastIndexOf(')');
// calling `idForPath` on the module itself first gives us a lower module id
// for the file itself than for its dependencies. That reflects their order
// in the bundle.
const fileId = idForPath(file);
// This code runs for both development and production builds, after
// minification. That's why we leave out all spaces.
const depencyIds =
dependencies.length ? `,[${dependencies.map(idForPath).join(',')}]` : '';
return (
code.slice(0, index) +
`,${fileId}` +
depencyIds +
code.slice(index)
);
};
exports.concat = function* concat<T>(
...iterables: Array<Iterable<T>>
): Iterable<T> {
for (const it of iterables) {
yield* it;
}
};
// Creates an idempotent function that returns numeric IDs for objects based
// on their `path` property.
exports.createIdForPathFn = (): ({path: string} => number) => {
const seen = new Map();
let next = 0;
return ({path}) => {
let id = seen.get(path);
if (id == null) {
id = next++;
seen.set(path, id);
}
return id;
};
};
// creates a series of virtual modules with require calls to the passed-in
// modules.
exports.requireCallsTo = function* (
modules: Iterable<Module>,
idForPath: IdForPathFn,
): Iterable<Module> {
for (const module of modules) {
yield virtualModule(`require(${idForPath(module.file)});`);
}
};
-1
View File
@@ -1 +0,0 @@
{"main": "ModuleGraph.js"}
@@ -1,15 +0,0 @@
/**
* Copyright (c) 2016-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 {Console} = require('console');
const {Writable} = require('stream');
const write = (_, __, callback) => callback();
module.exports = new Console(new Writable({write, writev: write}));
-23
View File
@@ -1,23 +0,0 @@
/**
* Copyright (c) 2016-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 generate = require('babel-generator').default;
const stub = require('sinon/lib/sinon/stub');
exports.fn = () => {
const s = stub();
const f = jest.fn(s);
f.stub = s;
return f;
};
const generateOptions = {concise: true};
exports.codeFromAst = ast => generate(ast, generateOptions).code;
exports.comparableCode = code => code.trim().replace(/\s\s+/g, ' ');
-172
View File
@@ -1,172 +0,0 @@
/**
* Copyright (c) 2016-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.
*
* @flow
*/
'use strict';
import type {FBSourceMap, MappingsMap, SourceMap} from '../lib/SourceMap';
import type {Ast} from 'babel-core';
import type {Console} from 'console';
export type {Transformer} from '../JSTransformer/worker';
export type BuildResult = {|
...GraphResult,
prependedScripts: $ReadOnlyArray<Module>,
|};
export type Callback<A = void, B = void>
= (Error => void)
& ((null | void, A, B) => void);
type Dependency = {|
id: string,
path: string,
|};
export type File = {|
code: string,
map: ?MappingsMap,
path: string,
type: CodeFileTypes,
|};
type CodeFileTypes = 'module' | 'script';
export type GraphFn = (
entryPoints: Iterable<string>,
platform: string,
options?: ?GraphOptions,
callback?: Callback<GraphResult>,
) => void;
type GraphOptions = {|
log?: Console,
optimize?: boolean,
skip?: Set<string>,
|};
export type GraphResult = {|
entryModules: Iterable<Module>,
modules: Iterable<Module>,
|};
export type IdForPathFn = {path: string} => number;
export type LoadFn = (
file: string,
options: LoadOptions,
callback: Callback<File, Array<string>>,
) => void;
type LoadOptions = {|
log?: Console,
optimize?: boolean,
platform?: string,
|};
export type Module = {|
dependencies: Array<Dependency>,
file: File,
|};
export type PostProcessModules = (
modules: Iterable<Module>,
entryPoints: Array<string>,
) => Iterable<Module>;
export type OutputFn<M: FBSourceMap | SourceMap = FBSourceMap | SourceMap> = ({|
filename: string,
idForPath: IdForPathFn,
modules: Iterable<Module>,
requireCalls: Iterable<Module>,
sourceMapPath?: string,
|}) => OutputResult<M>;
type OutputResult<M: FBSourceMap | SourceMap> = {|
code: string | Buffer,
extraFiles?: Iterable<[string, string | Buffer]>,
map: M,
|};
export type PackageData = {|
browser?: Object | string,
main?: string,
name?: string,
'react-native'?: Object | string,
|};
export type ResolveFn = (
id: string,
source: ?string,
platform: string,
options?: ResolveOptions,
callback: Callback<string>,
) => void;
type ResolveOptions = {
log?: Console,
};
export type TransformerResult = {|
ast: ?Ast,
code: string,
map: ?MappingsMap,
|};
export type TransformResult = {|
code: string,
dependencies: Array<string>,
dependencyMapName?: string,
map: ?MappingsMap,
|};
export type TransformResults = {[string]: TransformResult};
export type TransformVariants = {+[name: string]: {}, +default: {}};
export type TransformedCodeFile = {
+code: string,
+file: string,
+hasteID: ?string,
package?: PackageData,
+transformed: TransformResults,
+type: CodeFileTypes,
};
export type AssetFile = {|
+assetContentBase64: string,
+filePath: string,
|};
export type TransformedSourceFile =
| {|
+type: 'code',
+details: TransformedCodeFile,
|}
| {|
+type: 'asset',
+details: AssetFile,
|}
;
export type LibraryOptions = {|
dependencies?: Array<string>,
optimize: boolean,
platform?: string,
rebasePath: string => string,
|};
export type Base64Content = string;
export type AssetContentsByPath = {[destFilePath: string]: Base64Content};
export type Library = {|
+files: Array<TransformedCodeFile>,
/* cannot be a Map because it's JSONified later on */
+assets: AssetContentsByPath,
|};

Some files were not shown because too many files have changed in this diff Show More