packager: AssetServer: @flow

Reviewed By: davidaurelio

Differential Revision: D4921672

fbshipit-source-id: 6405275bbd04550d7dd87cd5b8ef35a6cf5904f2
This commit is contained in:
Jean Lauliac 2017-04-21 03:56:49 -07:00 committed by Facebook Github Bot
parent d53e61271a
commit 9bbbfb46ea
4 changed files with 76 additions and 62 deletions

View File

@ -5,16 +5,20 @@
* This source code is licensed under the BSD-style license found in the * 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 * 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. * of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/ */
'use strict'; 'use strict';
const crypto = require('crypto'); const crypto = require('crypto');
const declareOpts = require('../lib/declareOpts');
const denodeify = require('denodeify'); const denodeify = require('denodeify');
const fs = require('fs'); const fs = require('fs');
const getAssetDataFromName = require('../node-haste').getAssetDataFromName; const getAssetDataFromName = require('../node-haste').getAssetDataFromName;
const path = require('path'); const path = require('path');
import type {AssetData} from '../node-haste/lib/getAssetDataFromName';
const createTimeoutPromise = timeout => new Promise((resolve, reject) => { const createTimeoutPromise = timeout => new Promise((resolve, reject) => {
setTimeout(reject, timeout, 'fs operation timeout'); setTimeout(reject, timeout, 'fs operation timeout');
}); });
@ -33,27 +37,24 @@ const stat = timeoutableDenodeify(fs.stat, FS_OP_TIMEOUT);
const readDir = timeoutableDenodeify(fs.readdir, FS_OP_TIMEOUT); const readDir = timeoutableDenodeify(fs.readdir, FS_OP_TIMEOUT);
const readFile = timeoutableDenodeify(fs.readFile, FS_OP_TIMEOUT); const readFile = timeoutableDenodeify(fs.readFile, FS_OP_TIMEOUT);
const validateOpts = declareOpts({
projectRoots: {
type: 'array',
required: true,
},
assetExts: {
type: 'array',
required: true,
},
});
class AssetServer { class AssetServer {
constructor(options) {
const opts = validateOpts(options); _roots: $ReadOnlyArray<string>;
this._roots = opts.projectRoots; _assetExts: $ReadOnlyArray<string>;
this._assetExts = opts.assetExts; _hashes: Map<?string, string>;
_files: Map<string, string>;
constructor(options: {|
+assetExts: $ReadOnlyArray<string>,
+projectRoots: $ReadOnlyArray<string>,
|}) {
this._roots = options.projectRoots;
this._assetExts = options.assetExts;
this._hashes = new Map(); this._hashes = new Map();
this._files = new Map(); this._files = new Map();
} }
get(assetPath, platform = null) { get(assetPath: string, platform: ?string = null): Promise<Buffer> {
const assetData = getAssetDataFromName(assetPath, new Set([platform])); const assetData = getAssetDataFromName(assetPath, new Set([platform]));
return this._getAssetRecord(assetPath, platform).then(record => { return this._getAssetRecord(assetPath, platform).then(record => {
for (let i = 0; i < record.scales.length; i++) { for (let i = 0; i < record.scales.length; i++) {
@ -66,39 +67,41 @@ class AssetServer {
}); });
} }
getAssetData(assetPath, platform = null) { getAssetData(assetPath: string, platform: ?string = null): Promise<{|
files: Array<string>,
hash: string,
name: string,
scales: Array<number>,
type: string,
|}> {
const nameData = getAssetDataFromName(assetPath, new Set([platform])); const nameData = getAssetDataFromName(assetPath, new Set([platform]));
const data = { const {name, type} = nameData;
name: nameData.name,
type: nameData.type,
};
return this._getAssetRecord(assetPath, platform).then(record => { return this._getAssetRecord(assetPath, platform).then(record => {
data.scales = record.scales; const {scales, files} = record;
data.files = record.files;
if (this._hashes.has(assetPath)) { const hash = this._hashes.get(assetPath);
data.hash = this._hashes.get(assetPath); if (hash != null) {
return data; return {files, hash, name, scales, type};
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const hash = crypto.createHash('md5'); const hasher = crypto.createHash('md5');
hashFiles(data.files.slice(), hash, error => { hashFiles(files.slice(), hasher, error => {
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
data.hash = hash.digest('hex'); const freshHash = hasher.digest('hex');
this._hashes.set(assetPath, data.hash); this._hashes.set(assetPath, freshHash);
data.files.forEach(f => this._files.set(f, assetPath)); files.forEach(f => this._files.set(f, assetPath));
resolve(data); resolve({files, hash: freshHash, name, scales, type});
} }
}); });
}); });
}); });
} }
onFileChange(type, filePath) { onFileChange(type: string, filePath: string) {
this._hashes.delete(this._files.get(filePath)); this._hashes.delete(this._files.get(filePath));
} }
@ -113,7 +116,10 @@ class AssetServer {
* 4. Then try to pick platform-specific asset records * 4. Then try to pick platform-specific asset records
* 5. Then pick the closest resolution (rounding up) to the requested one * 5. Then pick the closest resolution (rounding up) to the requested one
*/ */
_getAssetRecord(assetPath, platform = null) { _getAssetRecord(assetPath: string, platform: ?string = null): Promise<{|
files: Array<string>,
scales: Array<number>,
|}> {
const filename = path.basename(assetPath); const filename = path.basename(assetPath);
return ( return (
@ -135,14 +141,15 @@ class AssetServer {
let record; let record;
if (platform != null) { if (platform != null) {
record = map[getAssetKey(assetData.assetName, platform)] || record = map.get(getAssetKey(assetData.assetName, platform)) ||
map[assetData.assetName]; map.get(assetData.assetName);
} else { } else {
record = map[assetData.assetName]; record = map.get(assetData.assetName);
} }
if (!record) { if (!record) {
throw new Error( throw new Error(
/* $FlowFixMe: platform can be null */
`Asset not found: ${assetPath} for platform: ${platform}` `Asset not found: ${assetPath} for platform: ${platform}`
); );
} }
@ -152,7 +159,7 @@ class AssetServer {
); );
} }
_findRoot(roots, dir, debugInfoFile) { _findRoot(roots: $ReadOnlyArray<string>, dir: string, debugInfoFile: string): Promise<string> {
return Promise.all( return Promise.all(
roots.map(root => { roots.map(root => {
const absRoot = path.resolve(root); const absRoot = path.resolve(root);
@ -185,18 +192,23 @@ class AssetServer {
}); });
} }
_buildAssetMap(dir, files, platform) { _buildAssetMap(dir: string, files: $ReadOnlyArray<string>, platform: ?string): Map<string, {|
const assets = files.map(this._getAssetDataFromName.bind(this, new Set([platform]))); files: Array<string>,
const map = Object.create(null); scales: Array<number>,
|}> {
const platforms = new Set(platform != null ? [platform] : []);
const assets = files.map(this._getAssetDataFromName.bind(this, platforms));
const map = new Map();
assets.forEach(function(asset, i) { assets.forEach(function(asset, i) {
const file = files[i]; const file = files[i];
const assetKey = getAssetKey(asset.assetName, asset.platform); const assetKey = getAssetKey(asset.assetName, asset.platform);
let record = map[assetKey]; let record = map.get(assetKey);
if (!record) { if (!record) {
record = map[assetKey] = { record = {
scales: [], scales: [],
files: [], files: [],
}; };
map.set(assetKey, record);
} }
let insertIndex; let insertIndex;
@ -214,8 +226,8 @@ class AssetServer {
return map; return map;
} }
_getAssetDataFromName(platform, file) { _getAssetDataFromName(platforms: Set<string>, file: string): AssetData {
return getAssetDataFromName(file, platform); return getAssetDataFromName(file, platforms);
} }
} }

View File

@ -59,18 +59,18 @@ export type GetTransformOptions = (
getDependencies: string => Promise<Array<string>>, getDependencies: string => Promise<Array<string>>,
) => Promise<ExtraTransformOptions>; ) => Promise<ExtraTransformOptions>;
type Asset = { type Asset = {|
__packager_asset: boolean, +__packager_asset: boolean,
fileSystemLocation: string, +fileSystemLocation: string,
httpServerLocation: string, +httpServerLocation: string,
width: ?number, +width: ?number,
height: ?number, +height: ?number,
scales: number, +scales: Array<number>,
files: Array<string>, +files: Array<string>,
hash: string, +hash: string,
name: string, +name: string,
type: string, +type: string,
}; |};
const sizeOf = denodeify(imageSize); const sizeOf = denodeify(imageSize);
@ -696,7 +696,7 @@ class Bundler {
return { return {
asset, asset,
code, code,
meta: {dependencies, dependencyOffsets}, meta: {dependencies, dependencyOffsets, preloaded: null},
}; };
}); });
} }

View File

@ -428,7 +428,7 @@ class Server {
_rangeRequestMiddleware( _rangeRequestMiddleware(
req: IncomingMessage, req: IncomingMessage,
res: ServerResponse, res: ServerResponse,
data: string, data: string | Buffer,
assetPath: string, assetPath: string,
) { ) {
if (req.headers && req.headers.range) { if (req.headers && req.headers.range) {

View File

@ -14,13 +14,15 @@
const getPlatformExtension = require('./getPlatformExtension'); const getPlatformExtension = require('./getPlatformExtension');
const path = require('path'); const path = require('path');
function getAssetDataFromName(filename: string, platforms: Set<string>): {| export type AssetData = {|
assetName: string, assetName: string,
name: string, name: string,
platform: ?string, platform: ?string,
resolution: number, resolution: number,
type: string, type: string,
|} { |};
function getAssetDataFromName(filename: string, platforms: Set<string>): AssetData {
const ext = path.extname(filename); const ext = path.extname(filename);
const platformExt = getPlatformExtension(filename, platforms); const platformExt = getPlatformExtension(filename, platforms);