allow fetching any resource under js folder via packager

Summary: This is a simple hook to allow native side to fetch any file under the js root folder via packager. Historically, only the `main.jsbundle` is fetched via the packager. This then allows fetching local file like a json file that lives under the same root js folder

Reviewed By: yungsters

Differential Revision: D4037730

fbshipit-source-id: a2d6eb5e30d148fee573d413fc4036d0189f4938
This commit is contained in:
Kevin Gozali 2016-10-20 11:38:40 -07:00 committed by Facebook Github Bot
parent 4804190be1
commit 150c522be9
6 changed files with 127 additions and 9 deletions

View File

@ -32,6 +32,15 @@ extern const NSUInteger kRCTBundleURLProviderDefaultPort;
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
fallbackResource:(NSString *)resourceName;
/**
* Returns the resourceURL for a given bundle entrypoint and
* the fallback offline resource file if the packager is not running.
*/
- (NSURL *)resourceURLForResourceRoot:(NSString *)root
resourceName:(NSString *)name
resourceExtension:(NSString *)extension
offlineBundle:(NSBundle *)offlineBundle;
/**
* Returns the URL of the packager server.
*/
@ -58,4 +67,13 @@ extern const NSUInteger kRCTBundleURLProviderDefaultPort;
enableDev:(BOOL)enableDev
enableMinification:(BOOL)enableMinification;
/**
* Given a hostname for the packager and a resource path (including "/"), return the URL to the resource.
* In general, please use the instance method to decide if the packager is running and fallback to the pre-packaged
* resource if it is not: -resourceURLForResourceRoot:resourceName:resourceExtension:offlineBundle:
*/
+ (NSURL *)resourceURLForResourcePath:(NSString *)path
packagerHost:(NSString *)packagerHost
query:(NSString *)query;
@end

View File

@ -123,25 +123,52 @@ static NSURL *serverRootWithHost(NSString *host)
if (!packagerServerHost) {
return [[NSBundle mainBundle] URLForResource:resourceName withExtension:@"jsbundle"];
} else {
return [[self class] jsBundleURLForBundleRoot:bundleRoot
packagerHost:packagerServerHost
enableDev:[self enableDev]
enableMinification:[self enableMinification]];
NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
// When we support only iOS 8 and above, use queryItems for a better API.
NSString *query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@",
[self enableDev] ? @"true" : @"false",
[self enableMinification] ? @"true": @"false"];
return [[self class] resourceURLForResourcePath:path packagerHost:packagerServerHost query:query];
}
}
- (NSURL *)resourceURLForResourceRoot:(NSString *)root
resourceName:(NSString *)name
resourceExtension:(NSString *)extension
offlineBundle:(NSBundle *)offlineBundle
{
NSString *packagerServerHost = [self packagerServerHost];
if (!packagerServerHost) {
// Serve offline bundle (local file)
NSBundle *bundle = offlineBundle ?: [NSBundle mainBundle];
return [bundle URLForResource:name withExtension:extension];
}
NSString *path = [NSString stringWithFormat:@"/%@/%@.%@", root, name, extension];
return [[self class] resourceURLForResourcePath:path packagerHost:packagerServerHost query:nil];
}
+ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
packagerHost:(NSString *)packagerHost
enableDev:(BOOL)enableDev
enableMinification:(BOOL)enableMinification
{
NSURLComponents *components = [NSURLComponents componentsWithURL:serverRootWithHost(packagerHost) resolvingAgainstBaseURL:NO];
components.path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
// When we support only iOS 8 and above, use queryItems for a better API.
components.query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@",
NSString *query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@",
enableDev ? @"true" : @"false",
enableMinification ? @"true": @"false"];
return [[self class] resourceURLForResourcePath:path packagerHost:packagerHost query:query];
}
+ (NSURL *)resourceURLForResourcePath:(NSString *)path
packagerHost:(NSString *)packagerHost
query:(NSString *)query
{
NSURLComponents *components = [NSURLComponents componentsWithURL:serverRootWithHost(packagerHost) resolvingAgainstBaseURL:NO];
components.path = path;
if (query != nil) {
components.query = query;
}
return components.URL;
}

View File

@ -53,6 +53,7 @@ public class DevServerHelper {
private static final String BUNDLE_URL_FORMAT =
"http://%s/%s.bundle?platform=android&dev=%s&hot=%s&minify=%s";
private static final String RESOURCE_URL_FORMAT = "http://%s/%s";
private static final String SOURCE_MAP_URL_FORMAT =
BUNDLE_URL_FORMAT.replaceFirst("\\.bundle", ".map");
private static final String LAUNCH_JS_DEVTOOLS_COMMAND_URL_FORMAT =
@ -217,11 +218,20 @@ public class DevServerHelper {
return String.format(Locale.US, BUNDLE_URL_FORMAT, host, jsModulePath, devMode, hmr, jsMinify);
}
private static String createResourceURL(String host, String resourcePath) {
return String.format(Locale.US, RESOURCE_URL_FORMAT, host, resourcePath);
}
public void downloadBundleFromURL(
final BundleDownloadCallback callback,
final String jsModulePath,
final File outputFile) {
final String bundleURL = createBundleURL(getDebugServerHost(), jsModulePath, getDevMode(), getHMR(), getJSMinifyMode());
final String bundleURL = createBundleURL(
getDebugServerHost(),
jsModulePath,
getDevMode(),
getHMR(),
getJSMinifyMode());
final Request request = new Request.Builder()
.url(bundleURL)
.build();
@ -449,4 +459,46 @@ public class DevServerHelper {
// host itself.
return createBundleURL(getHostForJSProxy(), mainModuleName, getDevMode(), getHMR(), getJSMinifyMode());
}
/**
* This is a debug-only utility to allow fetching a file via packager.
* It's made synchronous for simplicity, but should only be used if it's absolutely
* necessary.
* @return the file with the fetched content, or null if there's any failure.
*/
public @Nullable File downloadBundleResourceFromUrlSync(
final String resourcePath,
final File outputFile) {
final String resourceURL = createResourceURL(getDebugServerHost(), resourcePath);
final Request request = new Request.Builder()
.url(resourceURL)
.build();
try {
Response response = mClient.newCall(request).execute();
if (!response.isSuccessful()) {
return null;
}
Sink output = null;
try {
output = Okio.sink(outputFile);
Okio.buffer(response.body().source()).readAll(output);
} finally {
if (output != null) {
output.close();
}
}
return outputFile;
} catch (Exception ex) {
FLog.e(
ReactConstants.TAG,
"Failed to fetch resource synchronously - resourcePath: \"%s\", outputFile: \"%s\"",
resourcePath,
outputFile.getAbsolutePath(),
ex);
return null;
}
}
}

View File

@ -11,6 +11,8 @@ package com.facebook.react.devsupport;
import javax.annotation.Nullable;
import java.io.File;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
@ -44,6 +46,9 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler {
void reloadSettings();
void handleReloadJS();
void isPackagerRunning(DevServerHelper.PackagerStatusCallback callback);
@Nullable File downloadBundleResourceFromUrlSync(
final String resourceURL,
final File outputFile);
@Nullable String getLastErrorTitle();
@Nullable StackFrame[] getLastErrorStack();
}

View File

@ -655,6 +655,13 @@ public class DevSupportManagerImpl implements DevSupportManager, PackagerCommand
mDevServerHelper.isPackagerRunning(callback);
}
@Override
public @Nullable File downloadBundleResourceFromUrlSync(
final String resourceURL,
final File outputFile) {
return mDevServerHelper.downloadBundleResourceFromUrlSync(resourceURL, outputFile);
}
@Override
public @Nullable String getLastErrorTitle() {
return mLastErrorTitle;

View File

@ -11,6 +11,8 @@ package com.facebook.react.devsupport;
import javax.annotation.Nullable;
import java.io.File;
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
@ -129,6 +131,13 @@ public class DisabledDevSupportManager implements DevSupportManager {
}
@Override
public @Nullable File downloadBundleResourceFromUrlSync(
final String resourceURL,
final File outputFile) {
return null;
}
@Override
public @Nullable String getLastErrorTitle() {
return null;