Files
react-native/packager/react-packager/src/DependencyResolver/Module.js
David Aurelio 8c62ed7c95 Use fastfs.readWhile to parse module docblocks
Summary:
Uses `fastfs.readWhile` to build the haste map

- cuts the time needed to build the haste map in half
- we don’t need to allocate memory for all JS files in the tree
- we only read in as much as necessary during startup
- we only read files completely that are part of the bundle
- we will be able to move the transform before dependency extraction

public

Reviewed By: martinbigio

Differential Revision: D2890933

fb-gh-sync-id: 5fef6b53458e8bc95d0251d0bcf16821581a3362
2016-02-02 15:48:37 -08:00

221 lines
5.2 KiB
JavaScript

/**
* 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.
*/
'use strict';
const docblock = require('./DependencyGraph/docblock');
const isAbsolutePath = require('absolute-path');
const path = require('path');
const extractRequires = require('./lib/extractRequires');
class Module {
constructor({
file,
fastfs,
moduleCache,
cache,
extractor = extractRequires,
transformCode,
depGraphHelpers,
}) {
if (!isAbsolutePath(file)) {
throw new Error('Expected file to be absolute path but got ' + file);
}
this.path = path.resolve(file);
this.type = 'Module';
this._fastfs = fastfs;
this._moduleCache = moduleCache;
this._cache = cache;
this._extractor = extractor;
this._transformCode = transformCode;
this._depGraphHelpers = depGraphHelpers;
}
isHaste() {
return this._cache.get(
this.path,
'isHaste',
() => this._readDocBlock().then(data => !!data.id)
);
}
getCode() {
return this.read().then(({code}) => code);
}
getName() {
return this._cache.get(
this.path,
'name',
() => this._readDocBlock().then(({id}) => {
if (id) {
return id;
}
const p = this.getPackage();
if (!p) {
// Name is full path
return this.path;
}
return p.getName()
.then(name => {
if (!name) {
return this.path;
}
return path.join(name, path.relative(p.root, this.path)).replace(/\\/g, '/');
});
})
);
}
getPackage() {
return this._moduleCache.getPackageForModule(this);
}
getDependencies() {
return this._cache.get(
this.path,
'dependencies',
() => this.read().then(data => data.dependencies)
);
}
getAsyncDependencies() {
return this._cache.get(
this.path,
'asyncDependencies',
() => this.read().then(data => data.asyncDependencies)
);
}
invalidate() {
this._cache.invalidate(this.path);
}
_parseDocBlock(docBlock) {
// Extract an id for the module if it's using @providesModule syntax
// and if it's NOT in node_modules (and not a whitelisted node_module).
// This handles the case where a project may have a dep that has @providesModule
// docblock comments, but doesn't want it to conflict with whitelisted @providesModule
// modules, such as react-haste, fbjs-haste, or react-native or with non-dependency,
// project-specific code that is using @providesModule.
const moduleDocBlock = docblock.parseAsObject(docBlock);
const provides = moduleDocBlock.providesModule || moduleDocBlock.provides;
const id = provides && !this._depGraphHelpers.isNodeModulesDir(this.path)
? /^\S+/.exec(provides)[0]
: undefined;
return [id, moduleDocBlock];
}
_readDocBlock() {
const reading = this._reading || this._docBlock;
if (reading) {
return reading;
}
this._docBlock = this._fastfs.readWhile(this.path, whileInDocBlock)
.then(docBlock => {
const [id] = this._parseDocBlock(docBlock);
return {id};
});
return this._docBlock;
}
read() {
if (this._reading) {
return this._reading;
}
this._reading = this._fastfs.readFile(this.path).then(content => {
const [id, moduleDocBlock] = this._parseDocBlock(content);
// Ignore requires in JSON files or generated code. An example of this
// is prebuilt files like the SourceMap library.
if (this.isJSON() || 'extern' in moduleDocBlock) {
return {
id,
dependencies: [],
asyncDependencies: [],
code: content,
};
} else {
const transformCode = this._transformCode;
const codePromise = transformCode
? transformCode(this, content)
: Promise.resolve({code: content});
return codePromise.then(({code, dependencies, asyncDependencies}) => {
const {deps} = this._extractor(code);
return {
id,
code,
dependencies: dependencies || deps.sync,
asyncDependencies: asyncDependencies || deps.async,
};
});
}
});
return this._reading;
}
hash() {
return `Module : ${this.path}`;
}
isJSON() {
return path.extname(this.path) === '.json';
}
isAsset() {
return false;
}
isPolyfill() {
return false;
}
isAsset_DEPRECATED() {
return false;
}
toJSON() {
return {
hash: this.hash(),
isJSON: this.isJSON(),
isAsset: this.isAsset(),
isAsset_DEPRECATED: this.isAsset_DEPRECATED(),
type: this.type,
path: this.path,
};
}
}
function whileInDocBlock(chunk, i, result) {
// consume leading whitespace
if (!/\S/.test(result)) {
return true;
}
// check for start of doc block
if (!/^\s*\/(\*{2}|\*?$)/.test(result)) {
return false;
}
// check for end of doc block
return !/\*\//.test(result);
}
module.exports = Module;