Skip to content

Commit e730a9f

Browse files
geof90facebook-github-bot-9
authored andcommitted
Load assets from same folder as JSbundle (Android)
Summary: facebook#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 facebook#4527 Reviewed By: svcscm Differential Revision: D2788005 Pulled By: mkonicek fb-gh-sync-id: 3f6462a7ee6370a92dd6727ac422c5de346c3ff1
1 parent ffd4f99 commit e730a9f

File tree

6 files changed

+115
-53
lines changed

6 files changed

+115
-53
lines changed

Libraries/Image/__tests__/resolveAssetSource-test.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
jest
1212
.dontMock('AssetRegistry')
13-
.dontMock('../resolveAssetSource');
13+
.dontMock('../resolveAssetSource')
14+
.dontMock('../../../local-cli/bundle/assetPathUtils');
1415

1516
var AssetRegistry = require('AssetRegistry');
1617
var Platform = require('Platform');
@@ -132,10 +133,10 @@ describe('resolveAssetSource', () => {
132133
});
133134
});
134135

135-
describe('bundle was loaded from file on Android', () => {
136+
describe('bundle was loaded from assets on Android', () => {
136137
beforeEach(() => {
137138
NativeModules.SourceCode.scriptURL =
138-
'file:///Path/To/Simulator/main.bundle';
139+
'assets://Path/To/Simulator/main.bundle';
139140
Platform.OS = 'android';
140141
});
141142

@@ -159,6 +160,34 @@ describe('resolveAssetSource', () => {
159160
});
160161
});
161162
});
163+
164+
describe('bundle was loaded from file on Android', () => {
165+
beforeEach(() => {
166+
NativeModules.SourceCode.scriptURL =
167+
'file:///sdcard/Path/To/Simulator/main.bundle';
168+
Platform.OS = 'android';
169+
});
170+
171+
it('uses pre-packed image', () => {
172+
expectResolvesAsset({
173+
__packager_asset: true,
174+
fileSystemLocation: '/root/app/module/a',
175+
httpServerLocation: '/assets/AwesomeModule/Subdir',
176+
width: 100,
177+
height: 200,
178+
scales: [1],
179+
hash: '5b6f00f',
180+
name: '!@Logo#1_€',
181+
type: 'png',
182+
}, {
183+
__packager_asset: true,
184+
width: 100,
185+
height: 200,
186+
uri: 'file:///sdcard/Path/To/Simulator/drawable-mdpi/awesomemodule_subdir_logo1_.png',
187+
scale: 1,
188+
});
189+
});
190+
});
162191

163192
});
164193

Libraries/Image/resolveAssetSource.js

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var AssetRegistry = require('AssetRegistry');
2525
var PixelRatio = require('PixelRatio');
2626
var Platform = require('Platform');
2727
var SourceCode = require('NativeModules').SourceCode;
28+
var assetPathUtils = require('../../local-cli/bundle/assetPathUtils');
2829

2930
var _serverURL, _offlinePath;
3031

@@ -62,18 +63,18 @@ function getOfflinePath() {
6263
* Returns the path at which the asset can be found in the archive
6364
*/
6465
function getPathInArchive(asset) {
66+
var offlinePath = getOfflinePath();
6567
if (Platform.OS === 'android') {
66-
var assetDir = getBasePath(asset);
68+
if (offlinePath) {
69+
// E.g. 'file:///sdcard/AwesomeModule/drawable-mdpi/icon.png'
70+
return 'file://' + offlinePath + getAssetPathInDrawableFolder(asset);
71+
}
6772
// E.g. 'assets_awesomemodule_icon'
6873
// The Android resource system picks the correct scale.
69-
return (assetDir + '/' + asset.name)
70-
.toLowerCase()
71-
.replace(/\//g, '_') // Encode folder structure in file name
72-
.replace(/([^a-z0-9_])/g, '') // Remove illegal chars
73-
.replace(/^assets_/, ''); // Remove "assets_" prefix
74+
return assetPathUtils.getAndroidResourceIdentifier(asset);
7475
} else {
75-
// E.g. 'assets/AwesomeModule/icon@2x.png'
76-
return getOfflinePath() + getScaledAssetPath(asset);
76+
// E.g. '/assets/AwesomeModule/icon@2x.png'
77+
return offlinePath + getScaledAssetPath(asset);
7778
}
7879
}
7980

@@ -86,29 +87,26 @@ function getPathOnDevserver(devServerUrl, asset) {
8687
'&hash=' + asset.hash;
8788
}
8889

89-
/**
90-
* Returns a path like 'assets/AwesomeModule'
91-
*/
92-
function getBasePath(asset) {
93-
// TODO(frantic): currently httpServerLocation is used both as
94-
// path in http URL and path within IPA. Should we have zipArchiveLocation?
95-
var path = asset.httpServerLocation;
96-
if (path[0] === '/') {
97-
path = path.substr(1);
98-
}
99-
return path;
100-
}
101-
10290
/**
10391
* Returns a path like 'assets/AwesomeModule/icon@2x.png'
10492
*/
10593
function getScaledAssetPath(asset) {
10694
var scale = pickScale(asset.scales, PixelRatio.get());
10795
var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
108-
var assetDir = getBasePath(asset);
96+
var assetDir = assetPathUtils.getBasePath(asset);
10997
return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type;
11098
}
11199

100+
/**
101+
* Returns a path like 'drawable-mdpi/icon.png'
102+
*/
103+
function getAssetPathInDrawableFolder(asset) {
104+
var scale = pickScale(asset.scales, PixelRatio.get());
105+
var drawbleFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale);
106+
var fileName = assetPathUtils.getAndroidResourceIdentifier(asset);
107+
return drawbleFolder + '/' + fileName + '.' + asset.type;
108+
}
109+
112110
function pickScale(scales: Array<number>, deviceScale: number): number {
113111
// Packager guarantees that `scales` array is sorted
114112
for (var i = 0; i < scales.length; i++) {

ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void loadScript(ReactBridge bridge) {
3737

3838
@Override
3939
public String getSourceUrl() {
40-
return fileName;
40+
return (fileName.startsWith("assets://") ? "" : "file://") + fileName;
4141
}
4242
};
4343
}

local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
*/
99
'use strict';
1010

11-
jest.dontMock('../getAssetDestPathAndroid');
11+
jest
12+
.dontMock('../getAssetDestPathAndroid')
13+
.dontMock('../assetPathUtils');
1214

1315
const getAssetDestPathAndroid = require('../getAssetDestPathAndroid');
1416

local-cli/bundle/assetPathUtils.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
'use strict';
10+
11+
function getAndroidAssetSuffix(scale) {
12+
switch (scale) {
13+
case 0.75: return 'ldpi';
14+
case 1: return 'mdpi';
15+
case 1.5: return 'hdpi';
16+
case 2: return 'xhdpi';
17+
case 3: return 'xxhdpi';
18+
case 4: return 'xxxhdpi';
19+
}
20+
}
21+
22+
function getAndroidDrawableFolderName(asset, scale) {
23+
var suffix = getAndroidAssetSuffix(scale);
24+
if (!suffix) {
25+
throw new Error(
26+
'Don\'t know which android drawable suffix to use for asset: ' +
27+
JSON.stringify(asset)
28+
);
29+
}
30+
const androidFolder = 'drawable-' + suffix;
31+
return androidFolder;
32+
}
33+
34+
function getAndroidResourceIdentifier(asset) {
35+
var folderPath = getBasePath(asset);
36+
return (folderPath + '/' + asset.name)
37+
.toLowerCase()
38+
.replace(/\//g, '_') // Encode folder structure in file name
39+
.replace(/([^a-z0-9_])/g, '') // Remove illegal chars
40+
.replace(/^assets_/, ''); // Remove "assets_" prefix
41+
}
42+
43+
function getBasePath(asset) {
44+
var basePath = asset.httpServerLocation;
45+
if (basePath[0] === '/') {
46+
basePath = basePath.substr(1);
47+
}
48+
return basePath;
49+
}
50+
51+
module.exports = {
52+
getAndroidAssetSuffix: getAndroidAssetSuffix,
53+
getAndroidDrawableFolderName: getAndroidDrawableFolderName,
54+
getAndroidResourceIdentifier: getAndroidResourceIdentifier,
55+
getBasePath: getBasePath
56+
};

local-cli/bundle/getAssetDestPathAndroid.js

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,11 @@
99
'use strict';
1010

1111
const path = require('path');
12-
13-
function getAndroidAssetSuffix(scale) {
14-
switch (scale) {
15-
case 0.75: return 'ldpi';
16-
case 1: return 'mdpi';
17-
case 1.5: return 'hdpi';
18-
case 2: return 'xhdpi';
19-
case 3: return 'xxhdpi';
20-
case 4: return 'xxxhdpi';
21-
}
22-
}
12+
const assetPathUtils = require('./assetPathUtils');
2313

2414
function getAssetDestPathAndroid(asset, scale) {
25-
var suffix = getAndroidAssetSuffix(scale);
26-
if (!suffix) {
27-
throw new Error(
28-
'Don\'t know which android drawable suffix to use for asset: ' +
29-
JSON.stringify(asset)
30-
);
31-
}
32-
const androidFolder = 'drawable-' + suffix;
33-
// TODO: reuse this logic from https://fburl.com/151101135
34-
const fileName = (asset.httpServerLocation.substr(1) + '/' + asset.name)
35-
.toLowerCase()
36-
.replace(/\//g, '_') // Encode folder structure in file name
37-
.replace(/([^a-z0-9_])/g, '') // Remove illegal chars
38-
.replace(/^assets_/, ''); // Remove "assets_" prefix
39-
15+
const androidFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale);
16+
const fileName = assetPathUtils.getAndroidResourceIdentifier(asset);
4017
return path.join(androidFolder, fileName + '.' + asset.type);
4118
}
4219

0 commit comments

Comments
 (0)