Introduce custom asset resolver to resolveAssetSource(..)
Reviewed By: frantic Differential Revision: D2989112 fb-gh-sync-id: a678d091aeb6904448c890653f57dd7944ce95c3 shipit-source-id: a678d091aeb6904448c890653f57dd7944ce95c3
This commit is contained in:
parent
a97127b7bb
commit
2209131933
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
* 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 AssetSourceResolver
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type ResolvedAssetSource = {
|
||||
__packager_asset: boolean,
|
||||
width: number,
|
||||
height: number,
|
||||
uri: string,
|
||||
scale: number,
|
||||
};
|
||||
|
||||
import type { PackagerAsset } from 'AssetRegistry';
|
||||
|
||||
const PixelRatio = require('PixelRatio');
|
||||
const Platform = require('Platform');
|
||||
|
||||
const assetPathUtils = require('../../local-cli/bundle/assetPathUtils');
|
||||
const invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* Returns a path like 'assets/AwesomeModule/icon@2x.png'
|
||||
*/
|
||||
function getScaledAssetPath(asset): string {
|
||||
var scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get());
|
||||
var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
|
||||
var assetDir = assetPathUtils.getBasePath(asset);
|
||||
return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a path like 'drawable-mdpi/icon.png'
|
||||
*/
|
||||
function getAssetPathInDrawableFolder(asset): string {
|
||||
var scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get());
|
||||
var drawbleFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale);
|
||||
var fileName = assetPathUtils.getAndroidResourceIdentifier(asset);
|
||||
return drawbleFolder + '/' + fileName + '.' + asset.type;
|
||||
}
|
||||
|
||||
class AssetSourceResolver {
|
||||
|
||||
serverUrl: ?string;
|
||||
// where the bundle is being run from
|
||||
bundlePath: ?string;
|
||||
// the asset to resolve
|
||||
asset: PackagerAsset;
|
||||
|
||||
constructor(serverUrl: ?string, bundlePath: ?string, asset: PackagerAsset) {
|
||||
this.serverUrl = serverUrl;
|
||||
this.bundlePath = bundlePath;
|
||||
this.asset = asset;
|
||||
}
|
||||
|
||||
isLoadedFromServer(): boolean {
|
||||
return !!this.serverUrl;
|
||||
}
|
||||
|
||||
isLoadedFromFileSystem(): boolean {
|
||||
return !!this.bundlePath;
|
||||
}
|
||||
|
||||
defaultAsset(): ResolvedAssetSource {
|
||||
if (this.isLoadedFromServer()) {
|
||||
return this.assetServerURL();
|
||||
}
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
return this.isLoadedFromFileSystem() ?
|
||||
this.drawableFolderInBundle() :
|
||||
this.resourceIdentifierWithoutScale();
|
||||
} else {
|
||||
return this.scaledAssetPathInBundle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an absolute URL which can be used to fetch the asset
|
||||
* from the devserver
|
||||
*/
|
||||
assetServerURL(): ResolvedAssetSource {
|
||||
invariant(!!this.serverUrl, 'need server to load from');
|
||||
return this.fromSource(
|
||||
this.serverUrl + getScaledAssetPath(this.asset) +
|
||||
'?platform=' + Platform.OS + '&hash=' + this.asset.hash
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to just the scaled asset filename
|
||||
* E.g. 'assets/AwesomeModule/icon@2x.png'
|
||||
*/
|
||||
scaledAssetPath(): ResolvedAssetSource {
|
||||
return this.fromSource(getScaledAssetPath(this.asset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to where the bundle is running from, with a scaled asset filename
|
||||
* E.g. '/sdcard/bundle/assets/AwesomeModule/icon@2x.png'
|
||||
*/
|
||||
scaledAssetPathInBundle(): ResolvedAssetSource {
|
||||
const path = this.bundlePath || '';
|
||||
return this.fromSource(path + getScaledAssetPath(this.asset));
|
||||
}
|
||||
|
||||
/**
|
||||
* The default location of assets bundled with the app, located by
|
||||
* resource identifier
|
||||
* The Android resource system picks the correct scale.
|
||||
* E.g. 'assets_awesomemodule_icon'
|
||||
*/
|
||||
resourceIdentifierWithoutScale(): ResolvedAssetSource {
|
||||
invariant(Platform.OS === 'android', 'resource identifiers work on Android');
|
||||
return this.fromSource(assetPathUtils.getAndroidResourceIdentifier(this.asset));
|
||||
}
|
||||
|
||||
/**
|
||||
* If the jsbundle is running from a sideload location, this resolves assets
|
||||
* relative to its location
|
||||
* E.g. 'file:///sdcard/AwesomeModule/drawable-mdpi/icon.png'
|
||||
*/
|
||||
drawableFolderInBundle(): ResolvedAssetSource {
|
||||
const path = this.bundlePath || '';
|
||||
return this.fromSource(
|
||||
'file://' + path + getAssetPathInDrawableFolder(this.asset)
|
||||
);
|
||||
}
|
||||
|
||||
fromSource(source: string): ResolvedAssetSource {
|
||||
return {
|
||||
__packager_asset: true,
|
||||
width: this.asset.width,
|
||||
height: this.asset.height,
|
||||
uri: source,
|
||||
scale: AssetSourceResolver.pickScale(this.asset.scales, PixelRatio.get()),
|
||||
};
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = AssetSourceResolver;
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
jest
|
||||
.dontMock('AssetRegistry')
|
||||
.dontMock('AssetSourceResolver')
|
||||
.dontMock('../resolveAssetSource')
|
||||
.dontMock('../../../local-cli/bundle/assetPathUtils');
|
||||
|
||||
|
@ -217,6 +218,60 @@ describe('resolveAssetSource', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('source resolver can be customized', () => {
|
||||
beforeEach(() => {
|
||||
NativeModules.SourceCode.scriptURL =
|
||||
'file:///sdcard/Path/To/Simulator/main.bundle';
|
||||
Platform.OS = 'android';
|
||||
});
|
||||
|
||||
it('uses bundled source, event when js is sideloaded', () => {
|
||||
resolveAssetSource.setCustomSourceTransformer(
|
||||
(resolver) => resolver.resourceIdentifierWithoutScale(),
|
||||
);
|
||||
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: 'awesomemodule_subdir_logo1_',
|
||||
scale: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('allows any customization', () => {
|
||||
resolveAssetSource.setCustomSourceTransformer(
|
||||
(resolver) => resolver.fromSource('TEST')
|
||||
);
|
||||
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: 'TEST',
|
||||
scale: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('resolveAssetSource.pickScale', () => {
|
||||
|
|
|
@ -13,23 +13,15 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
export type ResolvedAssetSource = {
|
||||
__packager_asset: boolean,
|
||||
width: number,
|
||||
height: number,
|
||||
uri: string,
|
||||
scale: number,
|
||||
};
|
||||
import type { ResolvedAssetSource } from 'AssetSourceResolver';
|
||||
|
||||
var AssetRegistry = require('AssetRegistry');
|
||||
var PixelRatio = require('PixelRatio');
|
||||
var Platform = require('Platform');
|
||||
var SourceCode = require('NativeModules').SourceCode;
|
||||
var assetPathUtils = require('../../local-cli/bundle/assetPathUtils');
|
||||
const AssetRegistry = require('AssetRegistry');
|
||||
const AssetSourceResolver = require('AssetSourceResolver');
|
||||
const { SourceCode } = require('NativeModules');
|
||||
|
||||
var _serverURL, _offlinePath;
|
||||
let _customSourceTransformer, _serverURL, _bundleSourcePath;
|
||||
|
||||
function getDevServerURL() {
|
||||
function getDevServerURL(): ?string {
|
||||
if (_serverURL === undefined) {
|
||||
var scriptURL = SourceCode.scriptURL;
|
||||
var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//);
|
||||
|
@ -41,119 +33,60 @@ function getDevServerURL() {
|
|||
_serverURL = null;
|
||||
}
|
||||
}
|
||||
|
||||
return _serverURL;
|
||||
}
|
||||
|
||||
function getOfflinePath() {
|
||||
if (_offlinePath === undefined) {
|
||||
function getBundleSourcePath(): ?string {
|
||||
if (_bundleSourcePath === undefined) {
|
||||
const scriptURL = SourceCode.scriptURL;
|
||||
if (!scriptURL) {
|
||||
// scriptURL is falsy, we have nothing to go on here
|
||||
_offlinePath = '';
|
||||
return _offlinePath;
|
||||
_bundleSourcePath = null;
|
||||
return _bundleSourcePath;
|
||||
}
|
||||
if (scriptURL.startsWith('assets://')) {
|
||||
// running from within assets, no offline path to use
|
||||
_offlinePath = '';
|
||||
return _offlinePath;
|
||||
_bundleSourcePath = null;
|
||||
return _bundleSourcePath;
|
||||
}
|
||||
if (scriptURL.startsWith('file://')) {
|
||||
// cut off the protocol
|
||||
_offlinePath = scriptURL.substring(7, scriptURL.lastIndexOf('/') + 1);
|
||||
_bundleSourcePath = scriptURL.substring(7, scriptURL.lastIndexOf('/') + 1);
|
||||
} else {
|
||||
_offlinePath = scriptURL.substring(0, scriptURL.lastIndexOf('/') + 1);
|
||||
_bundleSourcePath = scriptURL.substring(0, scriptURL.lastIndexOf('/') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return _offlinePath;
|
||||
return _bundleSourcePath;
|
||||
}
|
||||
|
||||
function setCustomSourceTransformer(
|
||||
transformer: (resolver: AssetSourceResolver) => ResolvedAssetSource,
|
||||
): void {
|
||||
_customSourceTransformer = transformer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path at which the asset can be found in the archive
|
||||
* `source` is either a number (opaque type returned by require('./foo.png'))
|
||||
* or an `ImageSource` like { uri: '<http location || file path>' }
|
||||
*/
|
||||
function getPathInArchive(asset) {
|
||||
var offlinePath = getOfflinePath();
|
||||
if (Platform.OS === 'android') {
|
||||
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 assetPathUtils.getAndroidResourceIdentifier(asset);
|
||||
} else {
|
||||
// E.g. '/assets/AwesomeModule/icon@2x.png'
|
||||
return offlinePath + 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/icon@2x.png'
|
||||
*/
|
||||
function getScaledAssetPath(asset) {
|
||||
var scale = pickScale(asset.scales, PixelRatio.get());
|
||||
var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
|
||||
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<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);
|
||||
if (!asset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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()),
|
||||
};
|
||||
const resolver = new AssetSourceResolver(getDevServerURL(), getBundleSourcePath(), asset);
|
||||
if (_customSourceTransformer) {
|
||||
return _customSourceTransformer(resolver);
|
||||
}
|
||||
return resolver.defaultAsset();
|
||||
}
|
||||
|
||||
module.exports = resolveAssetSource;
|
||||
module.exports.pickScale = pickScale;
|
||||
module.exports.pickScale = AssetSourceResolver.pickScale;
|
||||
module.exports.setCustomSourceTransformer = setCustomSourceTransformer;
|
||||
|
|
Loading…
Reference in New Issue