react-native/Libraries/Image/resolveAssetSource.js
Alex Kotliarskyi 10b599c343 Load assets from same folder as JS bundle
Summary: Fixes #3679, see https://github.com/facebook/react-native/issues/3679

The idea is to always load images from the same folder that we loaded JS bundle.

This doesn't change the current behavior, since `imageNamed:` inferred the
absolute path to the image from app's bundle, but now we make it explicit.

The benefit for OTA updates implementations is that they can simply ask RN
to load js from some `~/Documents/<build-id>/main.jsbundle` folder and RN will
look for images in `~/Documents/<build-id>/assets/`.

Note that for Android we will have to come out with a different plan, since
in prod we load images from built-in resources.

public

Reviewed By: vjeux

Differential Revision: D2616995

fb-gh-sync-id: 2906c62380280ecb987525edf9a0e3e727a1008b
2015-11-05 12:53:26 -08:00

152 lines
4.1 KiB
JavaScript

/**
* 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 resolveAssetSource
* @flow
*
* Resolves an asset into a `source` for `Image`.
*/
'use strict';
export type ResolvedAssetSource = {
__packager_asset: boolean,
width: number,
height: number,
uri: string,
scale: number,
};
var AssetRegistry = require('AssetRegistry');
var PixelRatio = require('PixelRatio');
var Platform = require('Platform');
var SourceCode = require('NativeModules').SourceCode;
var _serverURL, _offlinePath;
function getDevServerURL() {
if (_serverURL === undefined) {
var scriptURL = SourceCode.scriptURL;
var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//);
if (match) {
// Bundle was loaded from network
_serverURL = match[0];
} else {
// Bundle was loaded from file
_serverURL = null;
}
}
return _serverURL;
}
function getOfflinePath() {
if (_offlinePath === undefined) {
var scriptURL = SourceCode.scriptURL;
var match = scriptURL && scriptURL.match(/^file:\/\/(\/.*\/)/);
if (match) {
_offlinePath = match[1];
} else {
_offlinePath = '';
}
}
return _offlinePath;
}
/**
* Returns the path at which the asset can be found in the archive
*/
function getPathInArchive(asset) {
if (Platform.OS === 'android') {
var assetDir = getBasePath(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
} else {
// E.g. 'assets/AwesomeModule/icon@2x.png'
return getOfflinePath() + getScaledAssetPath(asset);
}
}
/**
* Returns an absolute URL which can be used to fetch the asset
* from the devserver
*/
function getPathOnDevserver(devServerUrl, asset) {
return devServerUrl + getScaledAssetPath(asset) + '?platform=' + Platform.OS +
'&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);
return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type;
}
function pickScale(scales: Array<number>, deviceScale: number): number {
// Packager guarantees that `scales` array is sorted
for (var i = 0; i < scales.length; i++) {
if (scales[i] >= deviceScale) {
return scales[i];
}
}
// If nothing matches, device scale is larger than any available
// scales, so we return the biggest one. Unless the array is empty,
// in which case we default to 1
return scales[scales.length - 1] || 1;
}
function resolveAssetSource(source: any): ?ResolvedAssetSource {
if (typeof source === 'object') {
return source;
}
var asset = AssetRegistry.getAssetByID(source);
if (asset) {
return assetToImageSource(asset);
}
return null;
}
function assetToImageSource(asset): ResolvedAssetSource {
var devServerURL = getDevServerURL();
return {
__packager_asset: true,
width: asset.width,
height: asset.height,
uri: devServerURL ? getPathOnDevserver(devServerURL, asset) : getPathInArchive(asset),
scale: pickScale(asset.scales, PixelRatio.get()),
};
}
module.exports = resolveAssetSource;
module.exports.pickScale = pickScale;