packager: AssetServer: @flow
Reviewed By: davidaurelio Differential Revision: D4921672 fbshipit-source-id: 6405275bbd04550d7dd87cd5b8ef35a6cf5904f2
This commit is contained in:
parent
d53e61271a
commit
9bbbfb46ea
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue