Compare commits

...
19 Commits
Author SHA1 Message Date
James Ide 2237e3b1e7 [0.23.1] Bump version numbers 2016-04-07 12:22:10 -07:00
James Ide 303a264076 Honor the version of "react" under peerDeps when setting up a new project
Summary:We need this since React 15.0.0 is coming and will break `react-native init`, which currently installs the latest version of React. We'll need some changes to React Native to support 15 that Sebastian is actively working on, but till that lands we want `react-native init` to continue working.
Closes https://github.com/facebook/react-native/pull/6846

Differential Revision: D3148182

Pulled By: sebmarkbage

fb-gh-sync-id: 3df5bc184c0b82d2c9c320c620256c7c8568d90b
fbshipit-source-id: 3df5bc184c0b82d2c9c320c620256c7c8568d90b
2016-04-07 12:21:35 -07:00
Martin Konicek db8273e79b [0.23.0] Bump version numbers 2016-04-06 16:31:35 +01:00
Satyajit SahooandMartin Konicek 2c4dda2a70 Add download links for Android NDK to the docs
Summary:We don't support the latest NDK (`r11b`) and Google no longer provides links for the old version on the official page. So add them to the docs for now.

cc kmagiera astreet
Closes https://github.com/facebook/react-native/pull/6668

Differential Revision: D3102125

fb-gh-sync-id: 0a54e1c7f2ff883ca516c30cda279c828bd5a6e2
fbshipit-source-id: 0a54e1c7f2ff883ca516c30cda279c828bd5a6e2
2016-04-06 16:15:43 +01:00
Konstantin RaevandMartin Konicek a5b95d59f2 Fixed images required from node_modules
Summary:This fixes https://github.com/facebook/react-native/issues/6638 by resolving AssetRegistry relatively.
Closes https://github.com/facebook/react-native/pull/6822

Reviewed By: davidaurelio

Differential Revision: D3144463

Pulled By: bestander

fb-gh-sync-id: d3eeb24ae9e08a32f742c50ae5f0314fd33d1b6b
fbshipit-source-id: d3eeb24ae9e08a32f742c50ae5f0314fd33d1b6b
2016-04-06 16:15:31 +01:00
Walter LuhandMartin Konicek 7c031cafe7 RN: Packager URL defaults to port 8081 if no port in bundleURL
Summary:The packager url for the persistent connection relied on a port
in the bundleURL, so we need to insert a sensible default. Otherwise,
issues occur: https://github.com/facebook/react-native/issues/6581

Reviewed By: bestander

Differential Revision: D3113034

fb-gh-sync-id: 4eac52631ad7abd343b75a4488bb591b5caf2145
fbshipit-source-id: 4eac52631ad7abd343b75a4488bb591b5caf2145
2016-04-06 16:13:05 +01:00
Martin Konicek 39832820b3 [0.23.0-rc4] Bump version numbers 2016-04-01 16:54:02 +01:00
Martin BigioandMartin Konicek 464a2e85c2 Use react-native instead of React on hmr config
Summary:Check https://github.com/facebook/react-native/issues/6640 for details
Closes https://github.com/facebook/react-native/pull/6696

Differential Revision: D3109128

fb-gh-sync-id: b80ff44f0dbc063870dc1dc7de9bf3c814ac9a12
fbshipit-source-id: b80ff44f0dbc063870dc1dc7de9bf3c814ac9a12
2016-04-01 16:38:07 +01:00
Satyajit SahooandMartin Konicek 064d57d0b6 Fix fetching sourcemap in genymotion. Fixes #5338
Summary:Source maps are broken on Genymotion right now as they aren't being loaded from the correct URL. refer - https://github.com/facebook/react-native/issues/5338#issuecomment-188232402

**Test plan**

Build and install UIExplorer from master branch in genymotion and enable hot reload. When you change a file and save it, you'll see a Yellow box due to source map fetching failed, as per the referenced comment.

Doing the same for this branch doesn't produce any yellow boxes.
Closes https://github.com/facebook/react-native/pull/6594

Differential Revision: D3088218

Pulled By: martinbigio

fb-gh-sync-id: 0d1c19cc263de5c6c62061c399eef33fa4ac4a7b
shipit-source-id: 0d1c19cc263de5c6c62061c399eef33fa4ac4a7b
2016-04-01 16:35:50 +01:00
Martin Konicek 3e956f6608 [0.23.0-rc3] Bump version numbers 2016-03-31 14:13:34 +01:00
Konstantin RaevandMartin Konicek 9c2c2a5cd2 Version release script
Summary:npm-publish.js is not cohesive enough: besides building and publishing it also modifies some files.

It is better to have a separate script that will bump versions, make a commit and tag it.

scripts/bump-oss-version.js does exactly that.

This simplifies release process and allows manual release to npm if CI is not available.
Closes https://github.com/facebook/react-native/pull/6625

Differential Revision: D3092849

fb-gh-sync-id: 92cf38bd3df31c8c9c433fc5f9e15c129855fe0e
shipit-source-id: 92cf38bd3df31c8c9c433fc5f9e15c129855fe0e
2016-03-31 14:10:58 +01:00
Konstantin RaevandMartin Konicek 213a7b3eb8 Add testing HMR into manual testing steps
Reviewed By: dmmiller

Differential Revision: D3087133

fb-gh-sync-id: cb3b073dd77d40fb051a9bc17b6c24b49a325371
shipit-source-id: cb3b073dd77d40fb051a9bc17b6c24b49a325371
2016-03-31 14:10:12 +01:00
Konstantin RaevandMartin Konicek 298207ce24 Allow react-native init <version> + removed sinopia from being required for e2e testing
Reviewed By: mkonicek, frantic

Differential Revision: D3087524

fb-gh-sync-id: 048e23b55916a6be17fa9fabb6e41b0b2f3f7a16
shipit-source-id: 048e23b55916a6be17fa9fabb6e41b0b2f3f7a16
2016-03-31 14:09:53 +01:00
Konstantin RaevandMartin Konicek 992d39f20d Npm publish
Summary:This work allows automated release deployment.

Previous semi-automation lived in release.sh and I split it into two pieces:
- test-manual-e2e.sh - that just tests that current commit is buildable and makes a quick e2e installation for manual testing
- publish-npm.js - that makes publish based on what current branch and tags are on commit that is tested/deployed by CI

This simplified `Releases.md` guide and requires you to just run
```
git checkout -b 0.22-stable
git tag v0.22.0-rc
git push origin 0.22-stable --tags
```
to have a successful npm release.
Closes https://github.com/facebook/react-native/pull/6453

Reviewed By: mkonicek

Differential Revision: D3047938

Pulled By: bestander

fb-gh-sync-id: dbebf4c3a0bc2c2a0ef75c54595ab5654f91b8ea
shipit-source-id: dbebf4c3a0bc2c2a0ef75c54595ab5654f91b8ea
2016-03-31 14:08:50 +01:00
Martin Konicek ef1fdf8fb1 [0.23.0-rc2] Bump version numbers 2016-03-31 13:54:00 +01:00
Martin Konicek d782af285b Revert "Android support for perspective transform"
This reverts commit 3e760f48c5.
2016-03-31 13:08:27 +01:00
Christopher Chedeau aa83db777f Fix website
Summary:This puts the images inside of the blog folder in the output so we can safely copy them over. And moves the images in /blog/img/ in the source control so it's easier to discover (instead of having to search many levels deep).

Should make the website work!
Closes https://github.com/facebook/react-native/pull/6659

Differential Revision: D3098308

Pulled By: vjeux

fb-gh-sync-id: 99477e27843dd6c88aa854028944f2f0b672db1f
fbshipit-source-id: 99477e27843dd6c88aa854028944f2f0b672db1f
2016-03-28 09:31:34 -07:00
Christopher Chedeau d83308c4f6 Introduce blog
Ported the infrastructure from jest.
2016-03-24 19:20:37 -07:00
Martin Konicek 44a8eab108 Add Modal to the main Java package
Reviewed By: dmmiller

Differential Revision: D3087508

fb-gh-sync-id: 066b8c4102f9669ed58e6cda1b0013e74a3627f8
shipit-source-id: 066b8c4102f9669ed58e6cda1b0013e74a3627f8
2016-03-23 16:44:14 +00:00
44 changed files with 813 additions and 297 deletions
+9 -1
View File
@@ -98,12 +98,20 @@ Error: ${e.message}`
RCTExceptionsManager && RCTExceptionsManager.dismissRedbox && RCTExceptionsManager.dismissRedbox();
}
let serverHost;
if (Platform.OS === 'android') {
serverHost = require('NativeModules').AndroidConstants.ServerHost;
} else {
serverHost = port ? `${host}:${port}` : host;
}
modules.forEach(({id, code}, i) => {
code = code + '\n\n' + sourceMappingURLs[i];
require('SourceMapsCache').fetch({
text: code,
url: sourceURLs[i],
url: `http://${serverHost}${sourceURLs[i]}`,
sourceMappingURL: sourceMappingURLs[i],
});
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "React"
s.version = "0.0.1-master"
s.version = "0.23.1"
s.summary = "Build high quality mobile apps using React."
s.description = <<-DESC
React Native apps are built using the React JS
+3
View File
@@ -239,6 +239,9 @@ RCT_EXPORT_MODULE()
NSString *scheme = [_bridge.bundleURL scheme];
NSNumber *port = [_bridge.bundleURL port];
if (!port) {
port = @8081; // Packager default port
}
return [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@:%@/message?role=shell", scheme, host, port]];
}
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=0.12.0-SNAPSHOT
VERSION_NAME=0.23.1
GROUP=com.facebook.react
POM_NAME=ReactNative
@@ -9,6 +9,7 @@ android_library(
react_native_target('java/com/facebook/react/bridge:bridge'),
react_native_target('java/com/facebook/react/common:common'),
react_native_target('java/com/facebook/react/modules/debug:debug'),
react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'),
react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
react_native_dep('third-party/java/jsr-305:jsr-305'),
@@ -9,16 +9,7 @@
package com.facebook.react.devsupport;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.text.TextUtils;
@@ -26,6 +17,7 @@ import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.modules.systeminfo.AndroidInfoHelpers;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.ConnectionPool;
@@ -34,6 +26,13 @@ import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import okio.Okio;
import okio.Sink;
@@ -51,10 +50,6 @@ public class DevServerHelper {
public static final String RELOAD_APP_EXTRA_JS_PROXY = "jsproxy";
private static final String RELOAD_APP_ACTION_SUFFIX = ".RELOAD_APP_ACTION";
private static final String EMULATOR_LOCALHOST = "10.0.2.2:8081";
private static final String GENYMOTION_LOCALHOST = "10.0.3.2:8081";
private static final String DEVICE_LOCALHOST = "localhost:8081";
private static final String BUNDLE_URL_FORMAT =
"http://%s/%s.bundle?platform=android&dev=%s&hot=%s&minify=%s";
private static final String SOURCE_MAP_URL_FORMAT =
@@ -118,7 +113,7 @@ public class DevServerHelper {
* @return the host to use when connecting to the bundle server from the host itself.
*/
private static String getHostForJSProxy() {
return DEVICE_LOCALHOST;
return AndroidInfoHelpers.DEVICE_LOCALHOST;
}
/**
@@ -149,31 +144,21 @@ public class DevServerHelper {
// Check debug server host setting first. If empty try to detect emulator type and use default
// hostname for those
String hostFromSettings = mSettings.getDebugServerHost();
if (!TextUtils.isEmpty(hostFromSettings)) {
return Assertions.assertNotNull(hostFromSettings);
}
// Since genymotion runs in vbox it use different hostname to refer to adb host.
// We detect whether app runs on genymotion and replace js bundle server hostname accordingly
if (isRunningOnGenymotion()) {
return GENYMOTION_LOCALHOST;
}
if (isRunningOnStockEmulator()) {
return EMULATOR_LOCALHOST;
}
FLog.w(
String host = AndroidInfoHelpers.getServerHost();
if (host.equals(AndroidInfoHelpers.DEVICE_LOCALHOST)) {
FLog.w(
ReactConstants.TAG,
"You seem to be running on device. Run 'adb reverse tcp:8081 tcp:8081' " +
"to forward the debug server's port to the device.");
return DEVICE_LOCALHOST;
}
"to forward the debug server's port to the device.");
}
private boolean isRunningOnGenymotion() {
return Build.FINGERPRINT.contains("vbox");
}
private boolean isRunningOnStockEmulator() {
return Build.FINGERPRINT.contains("generic");
return host;
}
private static String createBundleURL(String host, String jsModulePath, boolean devMode, boolean hmr, boolean jsMinify) {
@@ -0,0 +1,33 @@
package com.facebook.react.modules.systeminfo;
import android.os.Build;
public class AndroidInfoHelpers {
public static final String EMULATOR_LOCALHOST = "10.0.2.2:8081";
public static final String GENYMOTION_LOCALHOST = "10.0.3.2:8081";
public static final String DEVICE_LOCALHOST = "localhost:8081";
private static boolean isRunningOnGenymotion() {
return Build.FINGERPRINT.contains("vbox");
}
private static boolean isRunningOnStockEmulator() {
return Build.FINGERPRINT.contains("generic");
}
public static String getServerHost() {
// Since genymotion runs in vbox it use different hostname to refer to adb host.
// We detect whether app runs on genymotion and replace js bundle server hostname accordingly
if (isRunningOnGenymotion()) {
return GENYMOTION_LOCALHOST;
}
if (isRunningOnStockEmulator()) {
return EMULATOR_LOCALHOST;
}
return DEVICE_LOCALHOST;
}
}
@@ -9,14 +9,14 @@
package com.facebook.react.modules.systeminfo;
import javax.annotation.Nullable;
import android.os.Build;
import com.facebook.react.bridge.BaseJavaModule;
import java.util.HashMap;
import java.util.Map;
import android.os.Build;
import com.facebook.react.bridge.BaseJavaModule;
import javax.annotation.Nullable;
/**
* Module that exposes Android Constants to JS.
@@ -32,6 +32,7 @@ public class AndroidInfoModule extends BaseJavaModule {
public @Nullable Map<String, Object> getConstants() {
HashMap<String, Object> constants = new HashMap<String, Object>();
constants.put("Version", Build.VERSION.SDK_INT);
constants.put("ServerHost", AndroidInfoHelpers.getServerHost());
return constants;
}
}
@@ -12,6 +12,7 @@ android_library(
react_native_target('java/com/facebook/react/views/art:art'),
react_native_target('java/com/facebook/react/views/drawer:drawer'),
react_native_target('java/com/facebook/react/views/image:image'),
react_native_target('java/com/facebook/react/views/modal:modal'),
react_native_target('java/com/facebook/react/views/picker:picker'),
react_native_target('java/com/facebook/react/views/progressbar:progressbar'),
react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'),
@@ -40,6 +40,7 @@ import com.facebook.react.views.art.ARTRenderableViewManager;
import com.facebook.react.views.art.ARTSurfaceViewManager;
import com.facebook.react.views.drawer.ReactDrawerLayoutManager;
import com.facebook.react.views.image.ReactImageManager;
import com.facebook.react.views.modal.ReactModalHostManager;
import com.facebook.react.views.picker.ReactDialogPickerManager;
import com.facebook.react.views.picker.ReactDropdownPickerManager;
import com.facebook.react.views.progressbar.ReactProgressBarViewManager;
@@ -104,6 +105,7 @@ public class MainReactPackage implements ReactPackage {
new ReactDropdownPickerManager(),
new ReactHorizontalScrollViewManager(),
new ReactImageManager(),
new ReactModalHostManager(),
new ReactProgressBarViewManager(),
new ReactRawTextManager(),
new ReactScrollViewManager(),
@@ -6,10 +6,8 @@ import android.graphics.Color;
import android.os.Build;
import android.view.View;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.DisplayMetricsHolder;
/**
* Base class that should be suitable for the majority of subclasses of {@link ViewManager}.
@@ -27,7 +25,6 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
private static final String PROP_DECOMPOSED_MATRIX_SCALE_Y = "scaleY";
private static final String PROP_DECOMPOSED_MATRIX_TRANSLATE_X = "translateX";
private static final String PROP_DECOMPOSED_MATRIX_TRANSLATE_Y = "translateY";
private static final String PROP_DECOMPOSED_MATRIX_PERSPECTIVE = "perspective";
private static final String PROP_OPACITY = "opacity";
private static final String PROP_ELEVATION = "elevation";
private static final String PROP_RENDER_TO_HARDWARE_TEXTURE = "renderToHardwareTextureAndroid";
@@ -35,8 +32,6 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
private static final String PROP_ACCESSIBILITY_COMPONENT_TYPE = "accessibilityComponentType";
private static final String PROP_ACCESSIBILITY_LIVE_REGION = "accessibilityLiveRegion";
private static final String PROP_IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility";
private static final int PERSPECTIVE_ARRAY_INVERTED_CAMERA_DISTANCE_INDEX = 2;
private static final float CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER = 5;
// DEPRECATED
private static final String PROP_ROTATION = "rotation";
@@ -169,20 +164,6 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_SCALE_X));
view.setScaleY(
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_SCALE_Y));
if (matrix.hasKey(PROP_DECOMPOSED_MATRIX_PERSPECTIVE)) {
ReadableArray perspectiveArray = matrix.getArray(PROP_DECOMPOSED_MATRIX_PERSPECTIVE);
if (perspectiveArray.size() > PERSPECTIVE_ARRAY_INVERTED_CAMERA_DISTANCE_INDEX) {
float cameraDistance = (float)(-1 / perspectiveArray.getDouble(PERSPECTIVE_ARRAY_INVERTED_CAMERA_DISTANCE_INDEX));
float scale = DisplayMetricsHolder.getScreenDisplayMetrics().density;
// The following converts the matrix's perspective to a camera distance
// such that the camera perspective looks the same on Android and iOS
float normalizedCameraDistance = scale * cameraDistance * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER;
view.setCameraDistance(normalizedCameraDistance);
}
}
}
private static void resetTransformMatrix(View view) {
@@ -194,4 +175,4 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
view.setScaleX(1);
view.setScaleY(1);
}
}
}
+9 -8
View File
@@ -13,10 +13,6 @@ Release schedule:
- 0.24.0 - Apr 18
- ...
## One time setup
Set up Sinopia: https://github.com/facebook/react-native/tree/master/react-native-cli
#### Check that everything works
Make absolutely sure a basic iOS and Android workflow works on the commit you are going to use for release.
@@ -38,13 +34,16 @@ Run:
```
git checkout -b <version_you_are_releasing>-stable # e.g. git checkout -b 0.22-stable
git tag v<version_you_are_releasing>.0-rc # e.g. git tag v0.22.0-rc
node ./scripts/bump-oss-version.js <exact-version_you_are_releasing> # e.g. git node ./scripts/bump-oss-version.js 0.22.0-rc
./scripts/test-manual-e2e.sh # to double check that e2e process works
git push origin <version_you_are_releasing>-stable --tags # e.g. git push origin 0.22-stable --tags
```
Circle CI will run the tests and publish to npm with version `0.22.0-rc` and tag `next` meaning that
this version will not be installed for users by default.
Go to [Circle CI](https://circleci.com/gh/facebook/react-native), look for your branch on the left side and look the npm publish step.
** Note ** CI won't publish to npm if the `last` commit on the new branch does not have a tag `v<branch-name-without-stable>.0-[rc]`.
## Make sure we have release notes
@@ -79,7 +78,7 @@ git pull origin 0.version_you_are_releasing-stable # e.g. git pull origin 0.22-
# Cherry-pick those commits
git cherry-pick commitHash1
# test everything again using sinopia
# test everything again
./scripts/test-manual-e2e.sh
# Check that you can Reload JS and the Chrome debugger works
@@ -88,9 +87,11 @@ git cherry-pick commitHash1
If everything worked:
```
git tag v-version_you_are_releasing # e.g. git tag v0.22.0, git tag v0.22.1
node ./scripts/bump-oss-version.js <exact_version_you_are_releasing> # e.g. git node ./scripts/bump-oss-version.js 0.22.0
git tag -d latest
git push origin :latest
git tag latest # for docs [website](https://facebook.github.io/react-native) to be generated
git push origin 0.22-stable --tags
git push origin version_you_are_releasing-stable --tags # e.g. git push origin 0.22-stable --tags
```
Once you see the version in the top left corner of the website has been updated:
+1 -1
View File
@@ -25,7 +25,7 @@ module.exports = function(options, filename) {
{
transforms: [{
transform: transform,
imports: ['React'],
imports: ['react-native'],
locals: ['module'],
}]
},
@@ -0,0 +1,209 @@
---
title: Introducing Hot Reloading
author: Martín Bigio
---
React Native goal is to give you the best possible developer experience. A big part of it is the time it takes between you save a file and be able to see the changes. Our goal is to get this feedback loop to be under 1 second, even as your app grows.
We got close to this ideal via three main features:
* Use JavaScript as the language doesn't have a long compilation cycle time.
* Implement a tool called Packager that transforms es6/flow/jsx files into normal JavaScript that the VM can understand. It was designed as a server that keeps intermediate state in memory to enable fast incremental changes and uses multiple cores.
* Build a feature called Live Reload that reloads the app on save.
At this point, the bottleneck for developers is no longer the time it takes to reload the app but losing the state of your app. A common scenario is to work on a feature that is multiple screens away from the launch screen. Every time you reload, you've got to click on the same path again and again to get back to your feature, making the cycle multiple-seconds long.
## Hot Reloading
The idea behind hot reloading is to keep the app running and to inject new versions of the files that you edited at runtime. This way, you don't lose any of your state which is especially useful if you are tweaking the UI.
A video is worth a thousand words. Check out the difference between Live Reload (current) and Hot Reload (new).
<iframe width="100%" height="315" src="https://www.youtube.com/embed/2uQzVi-KFuc" frameborder="0" allowfullscreen></iframe>
If you look closely, you can notice that it is possible to recover from a red box and you can also start importing modules that were not previously there without having to do a full reload.
**Word of warning:** because JavaScript is a very stateful language, hot reloading cannot be perfectly implemented. In practice, we found out that the current setup is working well for a large amount of usual use cases and a full reload is always available in case something gets messed up.
Hot reloading is available as of 0.22, you can enable it:
* Open the developper menu
* Tap on "Enable Hot Reloading"
## Implementation in a nutshell
Now that we've seen why we want it and how to use it, the fun part begins: how does it actually works.
Hot Reloading is built on top of a feature [Hot Module Replacement](https://webpack.github.io/docs/hot-module-replacement-with-webpack.html), or HMR. It was first introduced by Webpack and we implemented it inside of React Native Packager. HMR makes the Packager watch for file changes and send HMR updates to a thin HMR runtime included on the app.
In a nutshell, the HMR update contains the new code of the JS modules that changed. When the runtime receives them, it replaces the old modules' code with the new one:
![](/react-native/blog/img/hmr-architecture.png)
The HMR update contains a bit more than just the module's code we want to change because replacing it, it's not enough for the runtime to pick up the changes. The problem is that the module system may have already cached the *exports* of the module we want to update. For instance, say you have an app composed by these two modules:
```
// log.js
function log(message) {
const time = require('./time');
console.log(`[${time()}] ${message}`);
}
module.exports = log;
```
```
// time.js
function time() {
return new Date().getTime();
}
module.exports = time;
```
The module `log`, prints out the provided message including the current date provided by the module `time`.
When the app is bundled, React Native registers each module on the module system using the `__d` function. For this app, among many `__d` definitions, there will one for `log`:
```
__d('log', function() {
... // module's code
});
```
This invocation wraps each module's code into an anonymous function which we generally refer to as the factory function. The module system runtime keeps track of each module's factory function, whether it has already been executed, and the result of such execution (exports). When a module is required, the module system either provides the already cached exports or executes the module's factory function for the first time and saves the result.
So say you start your app and require `log`. At this point, neither `log` nor `time`'s factory functions have been executed so no exports have been cached. Then, the user modifies `time` to return the date in `MM/DD`:
```
// time.js
function bar() {
var date = new Date();
return `${date.getMonth() + 1}/${date.getDate()}`;
}
module.exports = bar;
```
The Packager will send time's new code to the runtime (step 1), and when `log` gets eventually required the exported function gets executed it will do so with `time`'s changes (step 2):
![](/react-native/blog/img/hmr-step.png)
Now say the code of `log` requires `time` as a top level require:
```
const time = require('./time'); // top level require
// log.js
function log(message) {
console.log(`[${time()}] ${message}`);
}
module.exports = log;
```
When `log` is required, the runtime will cache its exports and `time`'s one. (step 1). Then, when `time` is modified, the HMR process cannot simply finish after replacing `time`'s code. If it did, when `log` gets executed, it would do so with a cached copy of `time` (old code).
For `log` to pick up `time` changes, we'll need to clear its cached exports because one of the modules it depends on was hot swapped (step 3). Finally, when `log` gets required again, its factory function will get executed requiring `time` and getting its new code.
![](/react-native/blog/img/hmr-log.png)
## HMR API
HMR in React Native extends the module system by introducing the `hot` object. This API is based on [Webpack](https://webpack.github.io/docs/hot-module-replacement.html)'s one. The `hot` object exposes a function called `accept` which allows you to define a callback that will be executed when the module needs to be hot swapped. For instance, if we would change `time`'s code as follows, every time we save time, we'll see “time changed” in the console:
```
// time.js
function time() {
... // new code
}
module.hot.accept(() => {
console.log('time changed');
});
module.exports = time;
```
Note that only in rare cases you would need to use this API manually. Hot Reloading should work out of the box for the most common use cases.
## HMR Runtime
As we've seen before, sometimes it's not enough only accepting the HMR update because a module that uses the one being hot swapped may have been already executed and its imports cached. For instance, suppose the dependency tree for the movies app example had a top-level `MovieRouter` that depended on the `MovieSearch` and `MovieScreen` views, which depended on the `log` and `time` modules from the previous examples:
![](/react-native/blog/img/hmr-diamond.png)
If the user access the movies' search view but not the other one, all the modules but `MovieScreen` would have cached exports. If a change is made to module `time`, the runtime will have to clear the exports of `log` for it to pick up `time`'s changes. The process wouldn't finish there: the runtime will repeat this process recursively up until all the parents have been accepted. So, it'll grab the modules that depend on `log` and try to accept them. For `MovieScreen` it can bail, as it hasn't been required yet. For `MovieSearch`, it will have to clear its exports and process its parents recursively. Finally, it will do the same thing for `MovieRouter` and finish there as no modules depends on it.
In order to walk the dependency tree, the runtime receives the inverse dependency tree from the Packager on the HMR update. For this example the runtime will receive a JSON object like this one:
```
{
modules: [
{
name: 'time',
code: /* time's new code */
}
],
inverseDependencies: {
MovieRouter: [],
MovieScreen: ['MovieRouter'],
MovieSearch: ['MovieRouter'],
log: ['MovieScreen', 'MovieSearch'],
time: ['log'],
}
}
```
## React Components
React components are a bit harder to get to work with Hot Reloading. The problem is that we can't simply replace the old code with the new one as we'd loose the component's state. For React web applications, [Dan Abramov](https://twitter.com/dan_abramov) implemented a babel [transform](http://gaearon.github.io/react-hot-loader/) that uses Webpack's HMR API to solve this issue. In a nutshell, his solution works by creating a proxy for every single React component on *transform time*. The proxies hold the component's state and delegate the lifecycle methods to the actual components, which are the ones we hot reload:
![](/react-native/blog/img/hmr-proxy.png)
Besides creating the proxy component, the transform also defines the `accept` function with a piece of code to force React to re-render the component. This way, we can hot reload rendering code without losing any of the app's state.
The default [transformer](https://github.com/facebook/react-native/blob/master/packager/transformer.js#L92-L95) that comes with React Native uses the `babel-preset-react-native`, which is [configured](https://github.com/facebook/react-native/blob/master/babel-preset/configs/hmr.js#L24-L31) to use `react-transform` the same way you'd use it on a React web project that uses Webpack.
## Redux Stores
To enable Hot Reloading on [Redux](http://redux.js.org/) stores you will just need to use the HMR API similarly to what you'd do on a web project that uses Webpack:
```
// configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from '../reducers';
export default function configureStore(initialState) {
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk),
);
if (module.hot) {
module.hot.accept(() => {
const nextRootReducer = require('../reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
};
```
When you change a reducer, the code to accept that reducer will be sent to the client. Then the client will realize that the reducer doesn't know how to accept itself, so it will look for all the modules that refer it and try to accept them. Eventually, the flow will get to the single store, the `configureStore` module, which will accept the HMR update.
## Conclusion
If you are interested in helping making hot reloading better, I encourage you to read [Dan Abramov's post around the future of hot reloading](https://medium.com/@dan_abramov/hot-reloading-in-react-1140438583bf#.jmivpvmz4) and to contribute. For example, Johny Days is going to [make it work with multiple connected clients](https://github.com/facebook/react-native/pull/6179). We're relying on you all to maintain and improve this feature.
With React Native, we have the opportunity to rethink the way we build apps in order to make it a great developer experience. Hot reloading is only one piece of the puzzle, what other crazy hacks can we do to make it better?
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

-3
View File
@@ -32,8 +32,6 @@ dependencies:
- npm install
# for eslint bot
- npm install github@0.2.4
# for deployment scripts
- npm install shelljs@0.6.0
- cd website && npm install
cache_directories:
- "ReactAndroid/build/downloads"
@@ -78,7 +76,6 @@ test:
- ./gradlew :ReactAndroid:installDebugAndroidTest
- source scripts/circle-ci-android-setup.sh && retry3 ./scripts/run-android-instrumentation-tests.sh com.facebook.react.tests.gradle
# Publish to Sinopia, create a new app using 'react-native init' and check the packager starts
- ./scripts/e2e-test.sh --packager
# testing docs generation is not broken
+9 -1
View File
@@ -18,7 +18,7 @@ Make sure you have the following installed:
1. Android SDK version 23 (compileSdkVersion in [`build.gradle`](https://github.com/facebook/react-native/blob/master/ReactAndroid/build.gradle))
2. SDK build tools version 23.0.1 (buildToolsVersion in [`build.gradle`](https://github.com/facebook/react-native/blob/master/ReactAndroid/build.gradle))
3. Android Support Repository >= 17 (for Android Support Library)
4. Android NDK (download & extraction instructions [here](http://developer.android.com/ndk/downloads/index.html))
4. Android NDK (download links and installation instructions below)
Point Gradle to your Android SDK: either have `$ANDROID_SDK` and `$ANDROID_NDK ` defined, or create a local.properties file in the root of your react-native checkout with the following contents:
@@ -34,6 +34,14 @@ sdk.dir=/Users/your_unix_name/android-sdk-macosx
ndk.dir=/Users/your_unix_name/android-ndk/android-ndk-r10e
```
### Download links for Android NDK
1. Mac OS (64-bit) - http://dl.google.com/android/repository/android-ndk-r10e-darwin-x86_64.zip
2. Linux (64-bit) - http://dl.google.com/android/repository/android-ndk-r10e-linux-x86_64.zip
3. Windows (64-bit) - http://dl.google.com/android/repository/android-ndk-r10e-windows-x86_64.zip
4. Windows (32-bit) - http://dl.google.com/android/repository/android-ndk-r10e-windows-x86.zip
You can find further instructions on the [official page](http://developer.android.com/ndk/downloads/index.html).
## Building the source
+8
View File
@@ -0,0 +1,8 @@
---
id: newsletter
title: Newsletter
layout: docs
category: Community Resources
permalink: http://reactnative.cc/
next: style
---
+3 -3
View File
@@ -2,9 +2,9 @@
id: videos
title: Videos
layout: docs
category: Quick Start
category: Community Resources
permalink: docs/videos.html
next: style
next: newsletter
---
### React.js Conf 2016
@@ -34,7 +34,7 @@ next: style
<iframe width="650" height="315" src="//www.youtube.com/embed/Zoerbz5Mu5U" frameborder="0" allowfullscreen></iframe>
### React.js Conf 2015
### React.js Conf 2015
<iframe width="650" height="315" src="//www.youtube.com/embed/KVZ-P-ZI6W4" frameborder="0" allowfullscreen></iframe>
+12 -1
View File
@@ -92,6 +92,17 @@ module.exports = yeoman.generators.NamedBase.extend({
return;
}
this.npmInstall('react', { '--save': true });
var reactNativePackageJson = require('../../package.json');
var { peerDependencies } = reactNativePackageJson;
if (!peerDependencies) {
return;
}
var reactVersion = peerDependencies.react;
if (!reactVersion) {
return;
}
this.npmInstall(`react@${reactVersion}`, { '--save': true });
}
});
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "react-native",
"version": "0.0.0-master",
"version": "0.23.1",
"description": "A framework for building native apps using React",
"license": "BSD-3-Clause",
"repository": {
@@ -156,7 +156,7 @@
"mkdirp": "^0.5.1",
"module-deps": "^3.9.1",
"node-fetch": "^1.3.3",
"node-haste": "~2.9.2",
"node-haste": "~2.9.6",
"opn": "^3.0.2",
"optimist": "^0.6.1",
"progress": "^1.1.8",
@@ -186,6 +186,7 @@
"flow-bin": "0.22.0",
"jest-cli": "0.9.2",
"portfinder": "0.4.0",
"react": "^0.14.5"
"react": "^0.14.5",
"shelljs": "0.6.0"
}
}
}
+7 -6
View File
@@ -170,9 +170,9 @@ class Bundler {
});
}
_sourceHMRURL(platform, host, port, path) {
_sourceHMRURL(platform, path) {
return this._hmrURL(
`http://${host}:${port}`,
'',
platform,
'bundle',
path,
@@ -217,7 +217,7 @@ class Bundler {
return this._bundle({
...options,
bundle: new HMRBundle({
sourceURLFn: this._sourceHMRURL.bind(this, options.platform, host, port),
sourceURLFn: this._sourceHMRURL.bind(this, options.platform),
sourceMappingURLFn: this._sourceMappingHMRURL.bind(
this,
options.platform,
@@ -588,10 +588,11 @@ class Bundler {
};
const json = JSON.stringify(asset);
const assetRegistryPath = 'react-native/Libraries/Image/AssetRegistry';
const code =
`module.exports = require('AssetRegistry').registerAsset(${json});`;
const dependencies = ['AssetRegistry'];
const dependencyOffsets = [code.indexOf('AssetRegistry') - 1];
`module.exports = require(${JSON.stringify(assetRegistryPath)}).registerAsset(${json});`;
const dependencies = [assetRegistryPath];
const dependencyOffsets = [code.indexOf(assetRegistryPath) - 1];
return {
asset,
+1 -1
View File
@@ -107,7 +107,7 @@ class Resolver {
cache: opts.cache,
shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios',
transformCode: opts.transformCode,
assetDependencies: ['AssetRegistry'],
assetDependencies: ['react-native/Libraries/Image/AssetRegistry'],
});
this._getModuleId = options.getModuleId;
+46 -21
View File
@@ -42,6 +42,19 @@ var spawn = require('child_process').spawn;
var chalk = require('chalk');
var prompt = require('prompt');
var semver = require('semver');
/**
* Used arguments:
* -v --version - to print current version of react-native-cli and react-native dependency
* if you are in a RN app folder
* init - to create a new project and npm install it
* --verbose - to print logs while init
* --version <alternative react-native package> - override default (https://registry.npmjs.org/react-native@latest),
* package to install, examples:
* - "0.22.0-rc1" - A new app will be created using a specific version of React Native from npm repo
* - "https://registry.npmjs.org/react-native/-/react-native-0.20.0.tgz" - a .tgz archive from any npm repo
* - "/Users/home/react-native/react-native-0.22.0.tgz" - for package prepared with `npm pack`, useful for e2e tests
*/
var argv = require('minimist')(process.argv.slice(2));
var CLI_MODULE_PATH = function() {
return path.resolve(
@@ -69,34 +82,34 @@ if (fs.existsSync(cliPath)) {
cli = require(cliPath);
}
// minimist api
var commands = argv._;
if (cli) {
cli.run();
} else {
var args = process.argv.slice(2);
if (args.length === 0) {
if (commands.length === 0) {
console.error(
'You did not pass any commands, did you mean to run `react-native init`?'
);
process.exit(1);
}
switch (args[0]) {
switch (commands[0]) {
case 'init':
if (args[1]) {
var verbose = process.argv.indexOf('--verbose') >= 0;
init(args[1], verbose);
} else {
if (!commands[1]) {
console.error(
'Usage: react-native init <ProjectName> [--verbose]'
);
process.exit(1);
} else {
init(commands[1], argv.verbose, argv.version);
}
break;
default:
console.error(
'Command `%s` unrecognized. ' +
'Did you mean to run this inside a react-native project?',
args[0]
commands[0]
);
process.exit(1);
break;
@@ -123,17 +136,17 @@ function validatePackageName(name) {
}
}
function init(name, verbose) {
function init(name, verbose, rnPackage) {
validatePackageName(name);
if (fs.existsSync(name)) {
createAfterConfirmation(name, verbose);
createAfterConfirmation(name, verbose, rnPackage);
} else {
createProject(name, verbose);
createProject(name, verbose, rnPackage);
}
}
function createAfterConfirmation(name, verbose) {
function createAfterConfirmation(name, verbose, rnPackage) {
prompt.start();
var property = {
@@ -146,7 +159,7 @@ function createAfterConfirmation(name, verbose) {
prompt.get(property, function (err, result) {
if (result.yesno[0] === 'y') {
createProject(name, verbose);
createProject(name, verbose, rnPackage);
} else {
console.log('Project initialization canceled');
process.exit();
@@ -154,7 +167,7 @@ function createAfterConfirmation(name, verbose) {
});
}
function createProject(name, verbose) {
function createProject(name, verbose, rnPackage) {
var root = path.resolve(name);
var projectName = path.basename(root);
@@ -181,14 +194,26 @@ function createProject(name, verbose) {
console.log('Installing react-native package from npm...');
if (verbose) {
runVerbose(root, projectName);
runVerbose(root, projectName, rnPackage);
} else {
run(root, projectName);
run(root, projectName, rnPackage);
}
}
function run(root, projectName) {
exec('npm install --save react-native', function(e, stdout, stderr) {
function getInstallPackage(rnPackage) {
var packageToInstall = 'react-native';
var valideSemver = semver.valid(rnPackage);
if (valideSemver) {
packageToInstall += '@' + valideSemver;
} else if (rnPackage) {
// for tar.gz or alternative paths
packageToInstall = rnPackage;
}
return packageToInstall;
}
function run(root, projectName, rnPackage) {
exec('npm install --save ' + getInstallPackage(rnPackage), function(e, stdout, stderr) {
if (e) {
console.log(stdout);
console.error(stderr);
@@ -203,8 +228,8 @@ function run(root, projectName) {
});
}
function runVerbose(root, projectName) {
var proc = spawn('npm', ['install', '--verbose', '--save', 'react-native'], {stdio: 'inherit'});
function runVerbose(root, projectName, rnPackage) {
var proc = spawn('npm', ['install', '--verbose', '--save', getInstallPackage(rnPackage)], {stdio: 'inherit'});
proc.on('close', function (code) {
if (code !== 0) {
console.error('`npm install --save react-native` failed');
@@ -233,7 +258,7 @@ function checkNodeVersion() {
}
function checkForVersionArgument() {
if (process.argv.indexOf('-v') >= 0 || process.argv.indexOf('--version') >= 0) {
if (argv._.length === 0 && (argv.v || argv.version)) {
console.log('react-native-cli: ' + require('./package.json').version);
try {
console.log('react-native: ' + require(REACT_NATIVE_PACKAGE_JSON_PATH()).version);
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "react-native-cli",
"version": "0.1.10",
"license" : "BSD-3-Clause",
"version": "0.2.0",
"license": "BSD-3-Clause",
"description": "The React Native CLI tools",
"main": "index.js",
"engines": {
@@ -19,6 +19,7 @@
},
"dependencies": {
"chalk": "^1.1.1",
"minimist": "^1.2.0",
"prompt": "^0.2.14",
"semver": "^5.0.3"
}
+80
View File
@@ -0,0 +1,80 @@
/**
* 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';
/**
* This script bumps a new version for open source releases.
* It updates the version in podspec/json/gradle files and makes sure they are consistent between each other
* After changing the files it makes a commit and tags it.
* All you have to do is push changes to remote and CI will make a new build.
*/
/*eslint-disable no-undef */
require(`shelljs/global`);
// - check we are in release branch, e.g. 0.33-stable
let branch = exec(`git symbolic-ref --short HEAD`, {silent: true}).stdout.trim();
if (branch.indexOf(`-stable`) === -1) {
echo(`You must be in 0.XX-stable branch to bump a version`);
exit(1);
}
// e.g. 0.33
let versionMajor = branch.slice(0, branch.indexOf(`-stable`));
// - check that argument version matches branch
// e.g. 0.33.1 or 0.33.0-rc4
let version = process.argv[2];
if (!version || version.indexOf(versionMajor) !== 0) {
echo(`You must pass a tag like ${versionMajor}.[X]-rc[Y] to bump a version`);
exit(1);
}
let packageJson = JSON.parse(cat(`package.json`));
packageJson.version = version;
JSON.stringify(packageJson, null, 2).to(`package.json`);
// - change ReactAndroid/gradle.properties
if (sed(`-i`, /^VERSION_NAME=.*/, `VERSION_NAME=${version}`, `ReactAndroid/gradle.properties`).code) {
echo(`Couldn't update version for Gradle`);
exit(1);
}
// - change React.podspec
if (sed(`-i`, /s.version\s*=.*/, `s.version = \"${version}\"`, `React.podspec`).code) {
echo(`Couldn't update version for React.podspec`);
exit(1);
}
// verify that files changed, we just do a git diff and check how many times version is added across files
let numberOfChangedLinesWithNewVersion = exec(`git diff -U0 | grep '^[+]' | grep -c ${version} `, {silent: true})
.stdout.trim();
if (+numberOfChangedLinesWithNewVersion !== 3) {
echo(`Failed to update all the files. React.podspec, package.json and gradle.properties must have versions in them`);
echo(`Fix the issue, revert and try again`);
exec(`git diff`);
exit(1);
}
// - make commit [0.21.0-rc] Bump version numbers
if (exec(`git commit -a -m "[${version}] Bump version numbers"`).code) {
echo(`failed to commit`);
exit(1);
}
// - add tag v0.21.0-rc
if (exec(`git tag v${version}`).code) {
echo(`failed to tag the commit with v${version}, are you sure this release wasn't made earlier?`);
echo(`You may want to rollback the last commit`);
echo(`git reset --hard HEAD~1`);
exit(1);
}
exit(0);
/*eslint-enable no-undef */
-5
View File
@@ -1,5 +0,0 @@
; travis:travis
_auth=dHJhdmlzOnRyYXZpcw==
email=travis@example.com
spin=false
registry=http://localhost:4873/
-23
View File
@@ -1,23 +0,0 @@
storage: /tmp/sinopia-package-cache
uplinks:
npmjs:
url: http://registry.npmjs.org/
packages:
'react-native':
allow_access: $all
allow_publish: $all
'react-native-cli':
allow_access: $all
allow_publish: $all
'*':
allow_access: $all
proxy: npmjs
logs:
- {type: file, path: sinopia.log, format: pretty, level: http}
max_body_size: '50mb'
-23
View File
@@ -1,23 +0,0 @@
storage: /tmp/sinopia-package-cache
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'react-native':
allow_access: $all
allow_publish: $all
'react-native-cli':
allow_access: $all
allow_publish: $all
'*':
allow_access: $all
proxy: npmjs
logs:
- {type: file, path: sinopia.log, format: pretty, level: http}
max_body_size: '50mb'
+12 -27
View File
@@ -43,39 +43,23 @@ function cleanup {
rm $MARKER_IOS
rm $MARKER_ANDROID
[ $SINOPIA_PID ] && kill -9 $SINOPIA_PID
[ $SERVER_PID ] && kill -9 $SERVER_PID
[ -f ~/.npmrc.bak ] && mv ~/.npmrc.bak ~/.npmrc
}
trap cleanup EXIT
# pack react-native into a .tgz file
npm pack
PACKAGE=$(pwd)/react-native-*.tgz
# get react-native-cli dependencies
cd react-native-cli
npm pack
CLI_PACKAGE=$(pwd)/react-native-cli-*.tgz
cd $TEMP
# sinopia is npm registry proxy, it is used to make npm
# think react-native and react-native-cli are actually
# published on npm
# Temporarily installing sinopia from a github fork
# TODO t10060166 use npm repo when bug is fixed
which sinopia || npm install -g git://github.com/bestander/sinopia.git#68707efbab90569d5f52193138936f9281cc410c
# but in order to make npm use sinopia we temporarily
# replace its config file
[ -f ~/.npmrc ] && cp ~/.npmrc ~/.npmrc.bak
cp $SCRIPTS/e2e-npmrc ~/.npmrc
sinopia --config $SCRIPTS/e2e-sinopia.config.yml &
SINOPIA_PID=$!
# Make sure to remove old version of react-native in
# case it was cached
npm cache clear
npm unpublish react-native --force
npm unpublish react-native-cli --force
npm publish $ROOT
npm publish $ROOT/react-native-cli
npm install -g react-native-cli
react-native init EndToEndTest
npm install -g $CLI_PACKAGE
react-native init EndToEndTest --version $PACKAGE
cd EndToEndTest
case $1 in
@@ -83,6 +67,7 @@ case $1 in
echo "Running a basic packager test"
# Check the packager produces a bundle (doesn't throw an error)
react-native bundle --platform android --dev true --entry-file index.android.js --bundle-output android-bundle.js
# TODO do flow check
;;
"--ios"*)
echo "Running an iOS app"
+3 -25
View File
@@ -90,11 +90,6 @@ if (javaVersion.indexOf(requiredJavaVersion) === -1) {
exit(1);
}
if (sed(`-i`, /^VERSION_NAME=[0-9\.]*-SNAPSHOT/, `VERSION_NAME=${releaseVersion}`, `ReactAndroid/gradle.properties`).code) {
echo(`Couldn't update version for Gradle`);
exit(1);
}
// Uncomment Javadoc generation
if (sed(`-i`, `// archives androidJavadocJar`, `archives androidJavadocJar`, `ReactAndroid/release.gradle`).code) {
echo(`Couldn't enable Javadoc generation`);
@@ -106,6 +101,9 @@ if (exec(`./gradlew :ReactAndroid:installArchives`).code) {
exit(1);
}
// undo uncommenting javadoc setting
exec(`git checkout ReactAndroid/gradle.properties`);
echo("Generated artifacts for Maven");
let artifacts = ['-javadoc.jar', '-sources.jar', '.aar', '.pom'].map((suffix) => {
@@ -119,23 +117,6 @@ artifacts.forEach((name) => {
}
});
// ----------- Reverting changes to local files
exec(`git checkout ReactAndroid/gradle.properties`);
exec(`git checkout ReactAndroid/release.gradle`);
if (exec(`npm version --no-git-tag-version ${releaseVersion}`).code) {
echo(`Couldn't update version for npm`);
exit(1);
}
if (sed(`-i`, `s.version = "0.0.1-master"`, `s.version = \"${releaseVersion}\"`, `React.podspec`).code) {
echo(`Couldn't update version for React.podspec`);
exit(1);
}
// shrinkwrapping without dev dependencies
exec(`npm shrinkwrap`);
if (releaseVersion.indexOf(`-rc`) === -1) {
// release, package will be installed by default
exec(`npm publish`);
@@ -144,9 +125,6 @@ if (releaseVersion.indexOf(`-rc`) === -1) {
exec(`npm publish --tag next`);
}
exec(`git checkout package.json`);
exec(`git checkout React.podspec`);
echo(`Published to npm ${releaseVersion}`);
exit(0);
+23 -36
View File
@@ -1,6 +1,5 @@
#! /bin/bash
RELEASE="$1"
JAVA_VERSION="1.7"
RED="\033[0;31m"
@@ -21,43 +20,34 @@ info() {
echo -e $BLUE"$@"$ENDCOLOR
}
[[ -z $RELEASE ]] && error "Please specify a version. Example usage: release.sh 0.18"
PACKAGE_VERSION=$(cat package.json \
| grep version \
| head -1 \
| awk -F: '{ print $2 }' \
| sed 's/[",]//g' \
| tr -d '[[:space:]]')
success "Preparing version $PACKAGE_VERSION"
repo_root=$(pwd)
sed -i.bak s/^VERSION_NAME=[0-9\.]*-SNAPSHOT/VERSION_NAME=${RELEASE}.0/g "ReactAndroid/gradle.properties" || error "Couldn't update version for Gradle"
./gradlew :ReactAndroid:installArchives || error "Couldn't generate artifacts"
success "Generated artifacts for Maven"
npm_registry="http://localhost:4873/"
npm pack
npm set registry "${npm_registry}" && [[ $(npm config list | grep registry) == "registry = \"${npm_registry}\"" ]] || error "Couldn't set registry to ${npm_registry}"
info "npm registry set. Run 'sinopia' in a new Terminal"
info " - Make sure it prints 'http address - ${npm_registry}'"
info " - Make sure ${npm_registry} shows no old versions"
info ""
info "Press any key to continue"
read -n 1
sed -i.bak -E "s/(\"version\":[[:space:]]*\").+(\")/\"version\": \"${RELEASE}.0-rc\"/g" "package.json" || error "Couldn't update version for npm"
sed -i.bak -E "s/(s.version[[:space:]]{13}=[[:space:]].+)/s.version = \"${RELEASE}.0-rc\"/g" "React.podspec" || error "Couldn't update version for CocoaPods"
npm unpublish --force || error "Couldn't unpublish package from sinopia (${npm_registry})"
npm publish || error "Couldn't publish package to sinopia (${npm_registry})"
success "Published package to sinopia (${npm_registry})"
PACKAGE=$(pwd)/react-native-$PACKAGE_VERSION.tgz
success "Package bundled ($PACKAGE)"
project_name="RNTestProject"
cd /tmp/
rm -rf "$project_name"
react-native init "$project_name"
react-native init "$project_name" --version $PACKAGE
info "Double checking the versions in package.json and build.gradle are correct:"
grep "\"react-native\": \"\^${RELEASE}.0-rc\"" "/tmp/${project_name}/package.json" || error "Incorrect version number in /tmp/${project_name}/package.json"
info "Double checking the versions in package.json are correct:"
grep "\"react-native\": \".*react-native-$PACKAGE_VERSION.tgz\"" "/tmp/${project_name}/package.json" || error "Incorrect version number in /tmp/${project_name}/package.json"
grep -E "com.facebook.react:react-native:\\+" "${project_name}/android/app/build.gradle" || error "Dependency in /tmp/${project_name}/android/app/build.gradle must be com.facebook.react:react-native:+"
success "New sample project generated at /tmp/${project_name}"
@@ -65,28 +55,25 @@ success "New sample project generated at /tmp/${project_name}"
info "Test the following both on Android and iOS:"
info " - Verify that packager opens in new Window"
info " - Verify that you see the 'Welcome to React Native' screen"
info " - Disable Hot Reloading. It might be enabled from last time (the setting is stored on the device)"
info " - Verify 'Reload JS' works"
info " - Test Chrome debugger by adding breakpoints. We don't have tests for Chrome debugging."
info " - Test Chrome debugger by adding breakpoints and reloading JS. We don't have tests for Chrome debugging."
info " - Disable Chrome debugging."
info " - Enable Hot Reloading, change a file (index.ios.js, index.android.js) and save. The UI should refresh."
info " - Disable Hot Reloading."
info ""
info "Press any key to run the sample in Android emulator/device"
info ""
read -n 1
cd "${project_name}" && react-native run-android
cd "/tmp/${project_name}" && react-native run-android
info "Press any key to open the project in XCode"
info ""
read -n 1
open "/tmp/${project_name}/ios/${project_name}.xcodeproj"
cd "$repo_root"
# undo changes to files
git checkout package.json
git checkout React.podspec
git checkout ReactAndroid/gradle.properties
find . -path "*.bak" | xargs rm
npm set registry "https://registry.npmjs.org/" || error "Couldn't set registry to ${npm_registry}"
info "Next steps:"
info " - git tag v${RELEASE}.0-rc"
info " - git push origin ${RELEASE}-stable --tags"
info " - https://github.com/facebook/react-native/blob/master/Releases.md"
+2 -1
View File
@@ -1,4 +1,5 @@
src/react-native/docs/**
core/metadata.js
src/react-native/blog/**
core/metadata*
*.log
/build/
+45
View File
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule BlogPost
*/
'use strict';
var Marked = require('Marked');
var React = require('React');
var BlogPost = React.createClass({
render: function() {
var post = this.props.post;
var content = this.props.content;
var match = post.path.match(/([0-9]+)\/([0-9]+)\/([0-9]+)/);
// Because JavaScript sucks at date handling :(
var year = match[1];
var month = [
'January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December'
][parseInt(match[2], 10) - 1];
var day = parseInt(match[3], 10);
return (
<div>
<h1>{post.title}</h1>
<p className="meta">
{month} {day}, {year} by{' '}
<a href={post.authorURL} target="_blank">{post.author}</a>
</p>
<hr />
<Marked>{content}</Marked>
</div>
);
}
});
module.exports = BlogPost;
+42
View File
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule BlogSidebar
*/
'use strict';
var MetadataBlog = require('MetadataBlog');
var React = require('React');
var BlogSidebar = React.createClass({
render: function() {
return (
<div className="nav-docs">
<div className="nav-docs-section">
<h3>Recent Posts</h3>
<ul>
{MetadataBlog.files.map(function(post) {
return (
<li key={post.path}>
<a
className={this.props.title === post.title ? 'active' : ''}
href={'/react-native/blog/' + post.path}>
{post.title}
</a>
</li>
);
}.bind(this))}
</ul>
</div>
</div>
);
}
});
module.exports = BlogSidebar;
+1 -1
View File
@@ -16,8 +16,8 @@ var HeaderLinks = React.createClass({
linksInternal: [
{section: 'docs', href: 'docs/getting-started.html', text: 'Docs'},
{section: 'support', href: 'support.html', text: 'Support'},
{section: 'newsletter', href: 'http://reactnative.cc', text: 'Newsletter'},
{section: 'showcase', href: 'showcase.html', text: 'Showcase'},
{section: 'blog', href: 'blog/', text: 'Blog'},
],
linksExternal: [
{section: 'github', href: 'https://github.com/facebook/react-native', text: 'GitHub'},
+58
View File
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule BlogPageLayout
*/
'use strict';
var BlogPost = require('BlogPost');
var BlogSidebar = require('BlogSidebar');
var MetadataBlog = require('MetadataBlog');
var React = require('React');
var Site = require('Site');
var BlogPageLayout = React.createClass({
getPageURL: function(page) {
var url = '/jest/blog/';
if (page > 0) {
url += 'page' + (page + 1) + '/';
}
return url + '#content';
},
render: function() {
var perPage = this.props.metadata.perPage;
var page = this.props.metadata.page;
return (
<Site
section="blog"
title="Blog">
<section className="content wrap documentationContent">
<BlogSidebar />
<div className="inner-content">
{MetadataBlog.files
.slice(page * perPage, (page + 1) * perPage)
.map((post) => {
return <BlogPost post={post} content={post.content} />
})
}
<div className="docs-prevnext">
{page > 0 &&
<a className="docs-prev" href={this.getPageURL(page - 1)}>&larr; Prev</a>}
{MetadataBlog.files.length > (page + 1) * perPage &&
<a className="docs-next" href={this.getPageURL(page + 1)}>Next &rarr;</a>}
</div>
</div>
</section>
</Site>
);
}
});
module.exports = BlogPageLayout;
+39
View File
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule BlogPostLayout
*/
'use strict';
var BlogPost = require('BlogPost');
var BlogSidebar = require('BlogSidebar');
var Marked = require('Marked');
var MetadataBlog = require('MetadataBlog');
var React = require('React');
var Site = require('Site');
var BlogPostLayout = React.createClass({
render: function() {
return (
<Site
section="blog"
title={this.props.metadata.title}
description={this.props.children.trim().split('\n')[0]}>
<section className="content wrap documentationContent">
<BlogSidebar title={this.props.metadata.title} />
<div className="inner-content">
<BlogPost post={this.props.metadata} content={this.props.children} />
</div>
</section>
</Site>
);
}
});
module.exports = BlogPostLayout;
+120 -45
View File
@@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
var fs = require('fs')
var glob = require('glob');
var mkdirp = require('mkdirp');
@@ -29,6 +31,14 @@ function splitHeader(content) {
};
}
function rmFile(file) {
try {
fs.unlinkSync(file);
} catch(e) {
/* seriously, unlink throws when the file doesn't exist :( */
}
}
function backtickify(str) {
var escaped = '`' + str.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/{/g, '\\{') + '`';
// Replace require( with require\( so node-haste doesn't replace example
@@ -36,16 +46,60 @@ function backtickify(str) {
return escaped.replace(/require\(/g, 'require\\(');
}
function execute() {
var MD_DIR = '../docs/';
function writeFileAndCreateFolder(file, content) {
mkdirp.sync(file.replace(new RegExp('/[^/]*$'), ''));
fs.writeFileSync(file, content);
}
var files = glob.sync('src/react-native/docs/*.*');
files.forEach(function(file) {
try {
fs.unlinkSync(file);
} catch(e) {
/* seriously, unlink throws when the file doesn't exist :( */
}
// Extract markdown metadata header
function extractMetadata(content) {
var metadata = {};
var both = splitHeader(content);
var lines = both.header.split('\n');
for (var i = 0; i < lines.length - 1; ++i) {
var keyvalue = lines[i].split(':');
var key = keyvalue[0].trim();
var value = keyvalue.slice(1).join(':').trim();
// Handle the case where you have "Community #10"
try { value = JSON.parse(value); } catch(e) { }
metadata[key] = value;
}
return {metadata: metadata, rawContent: both.content};
}
function buildFile(layout, metadata, rawContent) {
return [
'/**',
' * @generated',
' */',
'var React = require("React");',
'var Layout = require("' + layout + '");',
rawContent && 'var content = ' + backtickify(rawContent) + ';',
'var Post = React.createClass({',
rawContent && ' statics: { content: content },',
' render: function() {',
' return (',
' <Layout metadata={' + JSON.stringify(metadata) + '}>',
rawContent && ' {content}',
' </Layout>',
' );',
' }',
'});',
'module.exports = Post;'
].filter(e => e).join('\n');
}
function execute() {
var DOCS_MD_DIR = '../docs/';
var BLOG_MD_DIR = '../blog/';
glob.sync('src/react-native/docs/*.*').forEach(rmFile);
glob.sync('src/react-native/blog/*.*').forEach(rmFile);
glob.sync('../blog/img/*.*').forEach(file => {
writeFileAndCreateFolder(
'src/react-native/blog/img/' + path.basename(file),
fs.readFileSync(file)
);
});
var metadatas = {
@@ -53,22 +107,13 @@ function execute() {
};
function handleMarkdown(content, filename) {
var metadata = {};
// Extract markdown metadata header
var both = splitHeader(content);
if (!both.header) {
if (content.slice(0, 3) !== '---') {
return;
}
var lines = both.header.split('\n');
for (var i = 0; i < lines.length - 1; ++i) {
var keyvalue = lines[i].split(':');
var key = keyvalue[0].trim();
var value = keyvalue.slice(1).join(':').trim();
// Handle the case where you have "Community #10"
try { value = JSON.parse(value); } catch(e) { }
metadata[key] = value;
}
const res = extractMetadata(content);
const metadata = res.metadata;
const rawContent = res.rawContent;
if (metadata.sidebar !== false) {
metadatas.files.push(metadata);
@@ -83,35 +128,17 @@ function execute() {
// Create a dummy .js version that just calls the associated layout
var layout = metadata.layout[0].toUpperCase() + metadata.layout.substr(1) + 'Layout';
var content = (
'/**\n' +
' * @generated\n' +
' * @jsx React.DOM\n' +
' */\n' +
'var React = require("React");\n' +
'var Layout = require("' + layout + '");\n' +
'var content = ' + backtickify(both.content) + '\n' +
'var Post = React.createClass({\n' +
' statics: {\n' +
' content: content\n' +
' },\n' +
' render: function() {\n' +
' return <Layout metadata={' + JSON.stringify(metadata) + '}>{content}</Layout>;\n' +
' }\n' +
'});\n' +
'module.exports = Post;\n'
writeFileAndCreateFolder(
'src/react-native/' + metadata.permalink.replace(/\.html$/, '.js'),
buildFile(layout, metadata, rawContent)
);
var targetFile = 'src/react-native/' + metadata.permalink.replace(/\.html$/, '.js');
mkdirp.sync(targetFile.replace(new RegExp('/[^/]*$'), ''));
fs.writeFileSync(targetFile, content);
}
extractDocs().forEach(function(content) {
handleMarkdown(content, null);
});
var files = glob.sync(MD_DIR + '**/*.*');
var files = glob.sync(DOCS_MD_DIR + '**/*.*');
files.forEach(function(file) {
var extension = path.extname(file);
if (extension === '.md' || extension === '.markdown') {
@@ -144,6 +171,54 @@ function execute() {
' */\n' +
'module.exports = ' + JSON.stringify(metadatas, null, 2) + ';'
);
files = glob.sync(BLOG_MD_DIR + '**/*.md');
const metadatasBlog = {
files: [],
};
files.sort().reverse().forEach(file => {
// Transform
// 2015-08-13-blog-post-name-0.5.md
// into
// 2015/08/13/blog-post-name-0-5.html
var filePath = path.basename(file)
.replace('-', '/')
.replace('-', '/')
.replace('-', '/')
// react-middleware is broken with files that contains multiple . like react-0.14.js
.replace(/\./g, '-')
.replace(/\-md$/, '.html');
var res = extractMetadata(fs.readFileSync(file, {encoding: 'utf8'}));
var rawContent = res.rawContent;
var metadata = Object.assign({path: filePath, content: rawContent}, res.metadata);
metadatasBlog.files.push(metadata);
writeFileAndCreateFolder(
'src/react-native/blog/' + filePath.replace(/\.html$/, '.js'),
buildFile('BlogPostLayout', metadata, rawContent)
);
});
var perPage = 5;
for (var page = 0; page < Math.ceil(metadatasBlog.files.length / perPage); ++page) {
writeFileAndCreateFolder(
'src/react-native/blog' + (page > 0 ? '/page' + (page + 1) : '') + '/index.js',
buildFile('BlogPageLayout', { page: page, perPage: perPage })
);
}
fs.writeFileSync(
'core/metadata-blog.js',
'/**\n' +
' * @generated\n' +
' * @providesModule MetadataBlog\n' +
' */\n' +
'module.exports = ' + JSON.stringify(metadatasBlog, null, 2) + ';'
);
}
if (argv.convert) {
+1 -1
View File
@@ -45,7 +45,7 @@ var app = connect()
.use(function(req, res, next) {
// convert all the md files on every request. This is not optimal
// but fast enough that we don't really need to care right now.
if (!server.noconvert && req.url.match(/\.html$/)) {
if (!server.noconvert && req.url.match(/\.html|\/$/)) {
convert();
}
next();