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 30b1e17ba5
commit 3e9063fba6
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
* 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.
*
* @flow
*/
'use strict';
const crypto = require('crypto');
const declareOpts = require('../lib/declareOpts');
const denodeify = require('denodeify');
const fs = require('fs');
const getAssetDataFromName = require('../node-haste').getAssetDataFromName;
const path = require('path');
import type {AssetData} from '../node-haste/lib/getAssetDataFromName';
const createTimeoutPromise = timeout => new Promise((resolve, reject) => {
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 readFile = timeoutableDenodeify(fs.readFile, FS_OP_TIMEOUT);
const validateOpts = declareOpts({
projectRoots: {
type: 'array',
required: true,
},
assetExts: {
type: 'array',
required: true,
},
});
class AssetServer {
constructor(options) {
const opts = validateOpts(options);
this._roots = opts.projectRoots;
this._assetExts = opts.assetExts;
_roots: $ReadOnlyArray<string>;
_assetExts: $ReadOnlyArray<string>;
_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._files = new Map();
}
get(assetPath, platform = null) {
get(assetPath: string, platform: ?string = null): Promise<Buffer> {
const assetData = getAssetDataFromName(assetPath, new Set([platform]));
return this._getAssetRecord(assetPath, platform).then(record => {
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 data = {
name: nameData.name,
type: nameData.type,
};
const {name, type} = nameData;
return this._getAssetRecord(assetPath, platform).then(record => {
data.scales = record.scales;
data.files = record.files;
const {scales, files} = record;
if (this._hashes.has(assetPath)) {
data.hash = this._hashes.get(assetPath);
return data;
const hash = this._hashes.get(assetPath);
if (hash != null) {
return {files, hash, name, scales, type};
}
return new Promise((resolve, reject) => {
const hash = crypto.createHash('md5');
hashFiles(data.files.slice(), hash, error => {
const hasher = crypto.createHash('md5');
hashFiles(files.slice(), hasher, error => {
if (error) {
reject(error);
} else {
data.hash = hash.digest('hex');
this._hashes.set(assetPath, data.hash);
data.files.forEach(f => this._files.set(f, assetPath));
resolve(data);
const freshHash = hasher.digest('hex');
this._hashes.set(assetPath, freshHash);
files.forEach(f => this._files.set(f, assetPath));
resolve({files, hash: freshHash, name, scales, type});
}
});
});
});
}
onFileChange(type, filePath) {
onFileChange(type: string, filePath: string) {
this._hashes.delete(this._files.get(filePath));
}
@ -113,7 +116,10 @@ class AssetServer {
* 4. Then try to pick platform-specific asset records
* 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);
return (
@ -135,14 +141,15 @@ class AssetServer {
let record;
if (platform != null) {
record = map[getAssetKey(assetData.assetName, platform)] ||
map[assetData.assetName];
record = map.get(getAssetKey(assetData.assetName, platform)) ||
map.get(assetData.assetName);
} else {
record = map[assetData.assetName];
record = map.get(assetData.assetName);
}
if (!record) {
throw new Error(
/* $FlowFixMe: platform can be null */
`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(
roots.map(root => {
const absRoot = path.resolve(root);
@ -185,18 +192,23 @@ class AssetServer {
});
}
_buildAssetMap(dir, files, platform) {
const assets = files.map(this._getAssetDataFromName.bind(this, new Set([platform])));
const map = Object.create(null);
_buildAssetMap(dir: string, files: $ReadOnlyArray<string>, platform: ?string): Map<string, {|
files: Array<string>,
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) {
const file = files[i];
const assetKey = getAssetKey(asset.assetName, asset.platform);
let record = map[assetKey];
let record = map.get(assetKey);
if (!record) {
record = map[assetKey] = {
record = {
scales: [],
files: [],
};
map.set(assetKey, record);
}
let insertIndex;
@ -214,8 +226,8 @@ class AssetServer {
return map;
}
_getAssetDataFromName(platform, file) {
return getAssetDataFromName(file, platform);
_getAssetDataFromName(platforms: Set<string>, file: string): AssetData {
return getAssetDataFromName(file, platforms);
}
}

View File

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

View File

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

View File

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