diff --git a/flow-typed/babel.js.flow b/flow-typed/babel.js.flow index 95f1f2f7..634e8188 100644 --- a/flow-typed/babel.js.flow +++ b/flow-typed/babel.js.flow @@ -89,9 +89,9 @@ type __TransformOptions = { type _TransformOptions = __TransformOptions & {env?: {[key: string]: __TransformOptions}}; -declare class _Ast {} +declare class Ast {} type TransformResult = { - ast: _Ast, + ast: Ast, code: ?string, ignored: boolean, map: ?_SourceMap, @@ -101,14 +101,14 @@ type VisitFn = (path: Object, state: State) => any; declare module 'babel-core' { declare type Plugins = _Plugins; declare type SourceMap = _SourceMap; - declare type Ast = _Ast; + declare type Ast = Ast; declare type TransformOptions = _TransformOptions; declare function transform( code: string, options?: _TransformOptions, ): TransformResult; declare function traverse( - ast: _Ast, + ast: Ast, visitor: {[key: string]: VisitFn | {enter?: VisitFn, exit?: VisitFn}}, scope?: ?Object, @@ -117,7 +117,7 @@ declare module 'babel-core' { ): void; declare var types: {[key: string]: Function}; declare function transformFromAst( - ast: _Ast, + ast: Ast, code?: ?string, babelOptions?: _TransformOptions, ): TransformResult; @@ -133,7 +133,7 @@ type RawMapping = { declare module 'babel-generator' { declare type RawMapping = RawMapping; declare function exports( - ast: _Ast, + ast: Ast, options?: GeneratorOptions, ): TransformResult & {rawMappings: ?Array}; } diff --git a/packages/metro-bundler/src/Bundler/__tests__/util-test.js b/packages/metro-bundler/src/Bundler/__tests__/util-test.js new file mode 100644 index 00000000..d9840a5a --- /dev/null +++ b/packages/metro-bundler/src/Bundler/__tests__/util-test.js @@ -0,0 +1,52 @@ +/** + * 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. + * + * @emails oncall+javascript_foundation + * @format + */ + +'use strict'; + +const generator = require('babel-generator').default; +const util = require('../util'); + +it('generates the right AST for remote assets', () => { + const asset = { + __packager_asset: true, + fileSystemLocation: '/js/RKJSModules/Apps/Wilde/AdsPayments/images', + hash: '3e9b7b3c4d4fa37f9eb580dc426412dbde2925ff', + height: 48, + httpServerLocation: '/assets/RKJSModules/Apps/Wilde/AdsPayments/images', + name: 'pending', + scales: [1.5, 2, 3, 4], + type: 'png', + width: 48, + }; + + const map = { + '/js/RKJSModules/Apps/Wilde/AdsPayments/images': { + pending: { + '2': 'img2x', + '3': 'img3x', + }, + }, + }; + + const {ast} = util.generateRemoteAssetCodeFileAst( + 'gen', + asset, + 'https://remote.server.com/', + map, + ); + + const code = generator(ast, {minified: true}).code; + + expect(code).toBe( + 'module.exports={"uri":"https://remote.server.com/"+{"2":"img2x","3":"img3x"}[require("gen").pickScale([2,3])]};', + ); +}); diff --git a/packages/metro-bundler/src/Bundler/index.js b/packages/metro-bundler/src/Bundler/index.js index 8d08d5fe..39bf7026 100644 --- a/packages/metro-bundler/src/Bundler/index.js +++ b/packages/metro-bundler/src/Bundler/index.js @@ -86,7 +86,6 @@ export type AssetDescriptor = { export type ExtendedAssetDescriptor = AssetDescriptor & { +fileSystemLocation: string, - +files: Array, }; const sizeOf = denodeify(imageSize); diff --git a/packages/metro-bundler/src/Bundler/util.js b/packages/metro-bundler/src/Bundler/util.js index 38afb613..6c16658b 100644 --- a/packages/metro-bundler/src/Bundler/util.js +++ b/packages/metro-bundler/src/Bundler/util.js @@ -16,7 +16,7 @@ const babel = require('babel-core'); const babelGenerate = require('babel-generator').default; const babylon = require('babylon'); -import type {AssetDescriptor} from '.'; +import type {AssetDescriptor, ExtendedAssetDescriptor} from '.'; import type {ModuleTransportLike} from '../shared/types.flow'; type SubTree = ( @@ -26,10 +26,104 @@ type SubTree = ( const assetPropertyBlacklist = new Set(['files', 'fileSystemLocation', 'path']); +// Structure of the object: dir.name.scale = asset +export type RemoteFileMap = { + [string]: { + [string]: { + [number]: string, + }, + }, +}; + +// Structure of the object: platform.dir.name.scale = asset +export type PlatformRemoteFileMap = { + ['ios' | 'android']: RemoteFileMap, +}; + +type AssetResult = { + remote: boolean, + ast: Ast, +}; + +/** + * Generates the code involved in requiring an asset, but to be loaded remotely. + * If the asset cannot be found within the map, then it falls back to the + * standard asset. + */ +function generateRemoteAssetCodeFileAst( + assetSourceResolverPath: string, + assetDescriptor: ExtendedAssetDescriptor, + remoteServer: string, + remoteFileMap: RemoteFileMap, +): ?AssetResult { + const t = babel.types; + + const file = remoteFileMap[assetDescriptor.fileSystemLocation]; + const descriptor = file && file[assetDescriptor.name]; + + if (!descriptor) { + return null; + } + + // require('AssetSourceResolver') + const requireCall = t.callExpression(t.identifier('require'), [ + t.stringLiteral(assetSourceResolverPath), + ]); + + // require('AssetSourceResolver').pickScale + const pickScale = t.memberExpression(requireCall, t.identifier('pickScale')); + + // require('AssetSourceResolver').pickScale([2, 3, ...]) + const call = t.callExpression(pickScale, [ + t.arrayExpression( + Object.keys(descriptor) + .map(Number) + .sort((a, b) => a - b) + .map(scale => t.numericLiteral(scale)), + ), + ]); + + // {2: 'path/to/image@2x', 3: 'path/to/image@3x', ...} + const data = babylon.parseExpression(JSON.stringify(descriptor)); + + // ({2: '...', 3: '...'})[require(...).pickScale(...)] + const handler = t.memberExpression(data, call, true); + + // 'https://remote.server.com/' + ({2: ...})[require(...).pickScale(...)] + const result = t.binaryExpression( + '+', + t.stringLiteral(remoteServer), + handler, + ); + + // module.exports + const moduleExports = t.memberExpression( + t.identifier('module'), + t.identifier('exports'), + ); + + return { + remote: true, + ast: t.file( + t.program([ + t.expressionStatement( + t.assignmentExpression( + '=', + moduleExports, + t.objectExpression([ + t.objectProperty(t.stringLiteral('uri'), result), + ]), + ), + ), + ]), + ), + }; +} + function generateAssetCodeFileAst( assetRegistryPath: string, assetDescriptor: AssetDescriptor, -): Object { +): AssetResult { const properDescriptor = filterObject( assetDescriptor, assetPropertyBlacklist, @@ -52,13 +146,16 @@ function generateAssetCodeFileAst( const registerAssetCall = t.callExpression(registerAssetFunction, [ descriptorAst, ]); - return t.file( - t.program([ - t.expressionStatement( - t.assignmentExpression('=', moduleExports, registerAssetCall), - ), - ]), - ); + return { + remote: false, + ast: t.file( + t.program([ + t.expressionStatement( + t.assignmentExpression('=', moduleExports, registerAssetCall), + ), + ]), + ), + }; } function generateAssetTransformResult( @@ -70,7 +167,7 @@ function generateAssetTransformResult( dependencyOffsets: Array, |} { const {code} = babelGenerate( - generateAssetCodeFileAst(assetRegistryPath, assetDescriptor), + generateAssetCodeFileAst(assetRegistryPath, assetDescriptor).ast, {comments: false, compact: true}, ); const dependencies = [assetRegistryPath]; @@ -177,5 +274,6 @@ module.exports = { createRamBundleGroups, generateAssetCodeFileAst, generateAssetTransformResult, + generateRemoteAssetCodeFileAst, isAssetTypeAnImage, }; diff --git a/packages/metro-bundler/src/ModuleGraph/types.flow.js b/packages/metro-bundler/src/ModuleGraph/types.flow.js index b1ecc6f1..6c02609e 100644 --- a/packages/metro-bundler/src/ModuleGraph/types.flow.js +++ b/packages/metro-bundler/src/ModuleGraph/types.flow.js @@ -157,6 +157,7 @@ export type TransformedCodeFile = { +hasteID: ?string, package?: PackageData, +transformed: TransformResults, + +remoteAsset?: boolean, +type: CodeFileTypes, };