From e730a9fdd08e63eabf75963ed9cdc17564cf0bfb Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 6 Jan 2016 08:45:34 -0800 Subject: [PATCH] Load assets from same folder as JSbundle (Android) Summary: https://github.com/facebook/react-native/issues/3679 was only partially fixed as the behaviour only works on iOS. This implements the same behaviour for Android. If the JSBundle was loaded from the assets folder, this will load images from the built-in resources. Else, load the image from the same folder as the JS bundle. EDIT: For added clarity: On iOS, Bundle Location: 'file:///Path/To/Sample.app/main.bundle' httpServerLocation: '/assets/module/a/' Name: 'logo' type: 'png' **Resolved Asset location: '/Path/To/Sample.app/assets/module/a/logo.png'** On Android, Bundle Location: 'file:///sdcard/Path/To/main.bundle' httpServerLocation: '/assets/module/a/', name: 'logo' type: 'png' **Resolved Asset location: 'file:///sdcard/Path/To/drawable_mdpi/module_a_logo.png'** Closes https://github.com/facebook/react-native/pull/4527 Reviewed By: svcscm Differential Revision: D2788005 Pulled By: mkonicek fb-gh-sync-id: 3f6462a7ee6370a92dd6727ac422c5de346c3ff1 --- .../__tests__/resolveAssetSource-test.js | 35 +++++++++++- Libraries/Image/resolveAssetSource.js | 42 +++++++------- .../facebook/react/bridge/JSBundleLoader.java | 2 +- .../__tests__/getAssetDestPathAndroid-test.js | 4 +- local-cli/bundle/assetPathUtils.js | 56 +++++++++++++++++++ local-cli/bundle/getAssetDestPathAndroid.js | 29 +--------- 6 files changed, 115 insertions(+), 53 deletions(-) create mode 100644 local-cli/bundle/assetPathUtils.js diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js index 81fdb2b08..b406316a1 100644 --- a/Libraries/Image/__tests__/resolveAssetSource-test.js +++ b/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -10,7 +10,8 @@ jest .dontMock('AssetRegistry') - .dontMock('../resolveAssetSource'); + .dontMock('../resolveAssetSource') + .dontMock('../../../local-cli/bundle/assetPathUtils'); var AssetRegistry = require('AssetRegistry'); var Platform = require('Platform'); @@ -132,10 +133,10 @@ describe('resolveAssetSource', () => { }); }); - describe('bundle was loaded from file on Android', () => { + describe('bundle was loaded from assets on Android', () => { beforeEach(() => { NativeModules.SourceCode.scriptURL = - 'file:///Path/To/Simulator/main.bundle'; + 'assets://Path/To/Simulator/main.bundle'; Platform.OS = 'android'; }); @@ -159,6 +160,34 @@ describe('resolveAssetSource', () => { }); }); }); + + describe('bundle was loaded from file on Android', () => { + beforeEach(() => { + NativeModules.SourceCode.scriptURL = + 'file:///sdcard/Path/To/Simulator/main.bundle'; + Platform.OS = 'android'; + }); + + it('uses pre-packed image', () => { + expectResolvesAsset({ + __packager_asset: true, + fileSystemLocation: '/root/app/module/a', + httpServerLocation: '/assets/AwesomeModule/Subdir', + width: 100, + height: 200, + scales: [1], + hash: '5b6f00f', + name: '!@Logo#1_€', + type: 'png', + }, { + __packager_asset: true, + width: 100, + height: 200, + uri: 'file:///sdcard/Path/To/Simulator/drawable-mdpi/awesomemodule_subdir_logo1_.png', + scale: 1, + }); + }); + }); }); diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js index 638b72ce0..6527e91df 100644 --- a/Libraries/Image/resolveAssetSource.js +++ b/Libraries/Image/resolveAssetSource.js @@ -25,6 +25,7 @@ var AssetRegistry = require('AssetRegistry'); var PixelRatio = require('PixelRatio'); var Platform = require('Platform'); var SourceCode = require('NativeModules').SourceCode; +var assetPathUtils = require('../../local-cli/bundle/assetPathUtils'); var _serverURL, _offlinePath; @@ -62,18 +63,18 @@ function getOfflinePath() { * Returns the path at which the asset can be found in the archive */ function getPathInArchive(asset) { + var offlinePath = getOfflinePath(); if (Platform.OS === 'android') { - var assetDir = getBasePath(asset); + if (offlinePath) { + // E.g. 'file:///sdcard/AwesomeModule/drawable-mdpi/icon.png' + return 'file://' + offlinePath + getAssetPathInDrawableFolder(asset); + } // E.g. 'assets_awesomemodule_icon' // The Android resource system picks the correct scale. - return (assetDir + '/' + asset.name) - .toLowerCase() - .replace(/\//g, '_') // Encode folder structure in file name - .replace(/([^a-z0-9_])/g, '') // Remove illegal chars - .replace(/^assets_/, ''); // Remove "assets_" prefix + return assetPathUtils.getAndroidResourceIdentifier(asset); } else { - // E.g. 'assets/AwesomeModule/icon@2x.png' - return getOfflinePath() + getScaledAssetPath(asset); + // E.g. '/assets/AwesomeModule/icon@2x.png' + return offlinePath + getScaledAssetPath(asset); } } @@ -86,29 +87,26 @@ function getPathOnDevserver(devServerUrl, asset) { '&hash=' + asset.hash; } -/** - * Returns a path like 'assets/AwesomeModule' - */ -function getBasePath(asset) { - // TODO(frantic): currently httpServerLocation is used both as - // path in http URL and path within IPA. Should we have zipArchiveLocation? - var path = asset.httpServerLocation; - if (path[0] === '/') { - path = path.substr(1); - } - return path; -} - /** * Returns a path like 'assets/AwesomeModule/icon@2x.png' */ function getScaledAssetPath(asset) { var scale = pickScale(asset.scales, PixelRatio.get()); var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x'; - var assetDir = getBasePath(asset); + var assetDir = assetPathUtils.getBasePath(asset); return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type; } +/** + * Returns a path like 'drawable-mdpi/icon.png' + */ +function getAssetPathInDrawableFolder(asset) { + var scale = pickScale(asset.scales, PixelRatio.get()); + var drawbleFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale); + var fileName = assetPathUtils.getAndroidResourceIdentifier(asset); + return drawbleFolder + '/' + fileName + '.' + asset.type; +} + function pickScale(scales: Array, deviceScale: number): number { // Packager guarantees that `scales` array is sorted for (var i = 0; i < scales.length; i++) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java index 90d97e17e..db9fb05df 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java @@ -37,7 +37,7 @@ public abstract class JSBundleLoader { @Override public String getSourceUrl() { - return fileName; + return (fileName.startsWith("assets://") ? "" : "file://") + fileName; } }; } diff --git a/local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js b/local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js index 1613b5b36..3f5829954 100644 --- a/local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js +++ b/local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js @@ -8,7 +8,9 @@ */ 'use strict'; -jest.dontMock('../getAssetDestPathAndroid'); +jest + .dontMock('../getAssetDestPathAndroid') + .dontMock('../assetPathUtils'); const getAssetDestPathAndroid = require('../getAssetDestPathAndroid'); diff --git a/local-cli/bundle/assetPathUtils.js b/local-cli/bundle/assetPathUtils.js new file mode 100644 index 000000000..cf14b2c62 --- /dev/null +++ b/local-cli/bundle/assetPathUtils.js @@ -0,0 +1,56 @@ +/** + * 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'; + +function getAndroidAssetSuffix(scale) { + switch (scale) { + case 0.75: return 'ldpi'; + case 1: return 'mdpi'; + case 1.5: return 'hdpi'; + case 2: return 'xhdpi'; + case 3: return 'xxhdpi'; + case 4: return 'xxxhdpi'; + } +} + +function getAndroidDrawableFolderName(asset, scale) { + var suffix = getAndroidAssetSuffix(scale); + if (!suffix) { + throw new Error( + 'Don\'t know which android drawable suffix to use for asset: ' + + JSON.stringify(asset) + ); + } + const androidFolder = 'drawable-' + suffix; + return androidFolder; +} + +function getAndroidResourceIdentifier(asset) { + var folderPath = getBasePath(asset); + return (folderPath + '/' + asset.name) + .toLowerCase() + .replace(/\//g, '_') // Encode folder structure in file name + .replace(/([^a-z0-9_])/g, '') // Remove illegal chars + .replace(/^assets_/, ''); // Remove "assets_" prefix +} + +function getBasePath(asset) { + var basePath = asset.httpServerLocation; + if (basePath[0] === '/') { + basePath = basePath.substr(1); + } + return basePath; +} + +module.exports = { + getAndroidAssetSuffix: getAndroidAssetSuffix, + getAndroidDrawableFolderName: getAndroidDrawableFolderName, + getAndroidResourceIdentifier: getAndroidResourceIdentifier, + getBasePath: getBasePath +}; \ No newline at end of file diff --git a/local-cli/bundle/getAssetDestPathAndroid.js b/local-cli/bundle/getAssetDestPathAndroid.js index 3e039f54f..f462ba5e8 100644 --- a/local-cli/bundle/getAssetDestPathAndroid.js +++ b/local-cli/bundle/getAssetDestPathAndroid.js @@ -9,34 +9,11 @@ 'use strict'; const path = require('path'); - -function getAndroidAssetSuffix(scale) { - switch (scale) { - case 0.75: return 'ldpi'; - case 1: return 'mdpi'; - case 1.5: return 'hdpi'; - case 2: return 'xhdpi'; - case 3: return 'xxhdpi'; - case 4: return 'xxxhdpi'; - } -} +const assetPathUtils = require('./assetPathUtils'); function getAssetDestPathAndroid(asset, scale) { - var suffix = getAndroidAssetSuffix(scale); - if (!suffix) { - throw new Error( - 'Don\'t know which android drawable suffix to use for asset: ' + - JSON.stringify(asset) - ); - } - const androidFolder = 'drawable-' + suffix; - // TODO: reuse this logic from https://fburl.com/151101135 - const fileName = (asset.httpServerLocation.substr(1) + '/' + asset.name) - .toLowerCase() - .replace(/\//g, '_') // Encode folder structure in file name - .replace(/([^a-z0-9_])/g, '') // Remove illegal chars - .replace(/^assets_/, ''); // Remove "assets_" prefix - + const androidFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale); + const fileName = assetPathUtils.getAndroidResourceIdentifier(asset); return path.join(androidFolder, fileName + '.' + asset.type); }