Do not cache hash generation in the AssetServer

Reviewed By: davidaurelio

Differential Revision: D6436249

fbshipit-source-id: eebbd92ebfdf1c8f12dbbcf3f5a66166fb1bc828
This commit is contained in:
Rafael Oleza 2017-12-04 16:36:35 -08:00 committed by Facebook Github Bot
parent 5fb41e6fc8
commit 8b28294bbd
4 changed files with 54 additions and 56 deletions

View File

@ -288,7 +288,7 @@ describe('AssetServer', () => {
it('changes the hash when the passed-in file watcher emits an `all` event', () => { it('changes the hash when the passed-in file watcher emits an `all` event', () => {
return server.getAssetData('/root/imgs/b.jpg').then(initialData => { return server.getAssetData('/root/imgs/b.jpg').then(initialData => {
mockFS.root.imgs['b@4x.jpg'] = 'updated data'; mockFS.root.imgs['b@4x.jpg'] = 'updated data';
server.onFileChange('all', '/root/imgs/b@4x.jpg');
return server return server
.getAssetData('/root/imgs/b.jpg') .getAssetData('/root/imgs/b.jpg')
.then(data => expect(data.hash).not.toEqual(initialData.hash)); .then(data => expect(data.hash).not.toEqual(initialData.hash));

View File

@ -14,7 +14,6 @@
const AssetPaths = require('../node-haste/lib/AssetPaths'); const AssetPaths = require('../node-haste/lib/AssetPaths');
const crypto = require('crypto');
const denodeify = require('denodeify'); const denodeify = require('denodeify');
const fs = require('fs'); const fs = require('fs');
const imageSize = require('image-size'); const imageSize = require('image-size');
@ -22,15 +21,13 @@ const path = require('path');
const toLocalPath = require('../node-haste/lib/toLocalPath'); const toLocalPath = require('../node-haste/lib/toLocalPath');
const {isAssetTypeAnImage} = require('../Bundler/util'); const {isAssetTypeAnImage} = require('../Bundler/util');
const {findRoot, getAbsoluteAssetRecord, hashFiles} = require('./util'); const {
findRoot,
getAbsoluteAssetInfo,
getAbsoluteAssetRecord,
} = require('./util');
type AssetInfo = {| import type {AssetInfo} from './util';
files: Array<string>,
hash: string,
name: string,
scales: Array<number>,
type: string,
|};
export type AssetData = {| export type AssetData = {|
__packager_asset: boolean, __packager_asset: boolean,
@ -49,13 +46,9 @@ const readFile = denodeify(fs.readFile);
class AssetServer { class AssetServer {
_roots: $ReadOnlyArray<string>; _roots: $ReadOnlyArray<string>;
_hashes: Map<?string, string>;
_files: Map<string, string>;
constructor(options: {|+projectRoots: $ReadOnlyArray<string>|}) { constructor(options: {|+projectRoots: $ReadOnlyArray<string>|}) {
this._roots = options.projectRoots; this._roots = options.projectRoots;
this._hashes = new Map();
this._files = new Map();
} }
get(assetPath: string, platform: ?string = null): Promise<Buffer> { get(assetPath: string, platform: ?string = null): Promise<Buffer> {
@ -78,29 +71,11 @@ class AssetServer {
assetPath: string, assetPath: string,
platform: ?string = null, platform: ?string = null,
): Promise<AssetInfo> { ): Promise<AssetInfo> {
const nameData = AssetPaths.parse( const dir = await findRoot(this._roots, path.dirname(assetPath), assetPath);
assetPath,
new Set(platform != null ? [platform] : []),
);
const {name, type} = nameData;
const {scales, files} = await this._getAssetRecord(assetPath, platform); const assetAbsolutePath = path.join(dir, path.basename(assetPath));
const hash = this._hashes.get(assetPath); return await getAbsoluteAssetInfo(assetAbsolutePath, platform);
if (hash != null) {
return {files, hash, name, scales, type};
}
const hasher = crypto.createHash('md5');
if (files.length > 0) {
await hashFiles(files.slice(), hasher);
}
const freshHash = hasher.digest('hex');
this._hashes.set(assetPath, freshHash);
files.forEach(f => this._files.set(f, assetPath));
return {files, hash: freshHash, name, scales, type};
} }
async getAssetData( async getAssetData(
@ -116,7 +91,7 @@ class AssetServer {
} }
const isImage = isAssetTypeAnImage(path.extname(assetPath).slice(1)); const isImage = isAssetTypeAnImage(path.extname(assetPath).slice(1));
const assetData = await this._getAssetInfo(localPath, platform); const assetData = await getAbsoluteAssetInfo(assetPath, platform);
const dimensions = isImage ? imageSize(assetData.files[0]) : null; const dimensions = isImage ? imageSize(assetData.files[0]) : null;
const scale = assetData.scales[0]; const scale = assetData.scales[0];
@ -134,10 +109,6 @@ class AssetServer {
}; };
} }
onFileChange(type: string, filePath: string) {
this._hashes.delete(this._files.get(filePath));
}
/** /**
* Given a request for an image by path. That could contain a resolution * Given a request for an image by path. That could contain a resolution
* postfix, we need to find that image (or the closest one to it's resolution) * postfix, we need to find that image (or the closest one to it's resolution)

View File

@ -14,6 +14,7 @@
const AssetPaths = require('../node-haste/lib/AssetPaths'); const AssetPaths = require('../node-haste/lib/AssetPaths');
const crypto = require('crypto');
const denodeify = require('denodeify'); const denodeify = require('denodeify');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@ -23,6 +24,27 @@ const readDir = denodeify(fs.readdir);
import type {AssetPath} from '../node-haste/lib/AssetPaths'; import type {AssetPath} from '../node-haste/lib/AssetPaths';
export type AssetInfo = {|
files: Array<string>,
hash: string,
name: string,
scales: Array<number>,
type: string,
|};
const hashFiles = denodeify(function hashFilesCb(files, hash, callback) {
if (!files.length) {
callback(null);
return;
}
fs
.createReadStream(files.shift())
.on('data', data => hash.update(data))
.once('end', () => hashFilesCb(files, hash, callback))
.once('error', error => callback(error));
});
function buildAssetMap( function buildAssetMap(
dir: string, dir: string,
files: $ReadOnlyArray<string>, files: $ReadOnlyArray<string>,
@ -82,19 +104,6 @@ function getAssetKey(assetName, platform) {
} }
} }
function hashFiles(files, hash, callback) {
if (!files.length) {
callback(null);
return;
}
fs
.createReadStream(files.shift())
.on('data', data => hash.update(data))
.once('end', () => hashFiles(files, hash, callback))
.once('error', error => callback(error));
}
async function getAbsoluteAssetRecord( async function getAbsoluteAssetRecord(
assetPath: string, assetPath: string,
platform: ?string = null, platform: ?string = null,
@ -171,8 +180,28 @@ async function findRoot(
); );
} }
async function getAbsoluteAssetInfo(
assetPath: string,
platform: ?string = null,
): Promise<AssetInfo> {
const nameData = AssetPaths.parse(
assetPath,
new Set(platform != null ? [platform] : []),
);
const {name, type} = nameData;
const {scales, files} = await getAbsoluteAssetRecord(assetPath, platform);
const hasher = crypto.createHash('md5');
if (files.length > 0) {
await hashFiles(files.slice(), hasher);
}
return {files, hash: hasher.digest('hex'), name, scales, type};
}
module.exports = { module.exports = {
findRoot, findRoot,
getAbsoluteAssetInfo,
getAbsoluteAssetRecord, getAbsoluteAssetRecord,
hashFiles: denodeify(hashFiles),
}; };

View File

@ -276,8 +276,6 @@ class Server {
} }
onFileChange(type: string, filePath: string) { onFileChange(type: string, filePath: string) {
this._assetServer.onFileChange(type, filePath);
Promise.all( Promise.all(
this._fileChangeListeners.map(listener => listener(filePath)), this._fileChangeListeners.map(listener => listener(filePath)),
).then( ).then(