Files
react-native/packager/react-packager/src/DependencyResolver/Module.js
Martín Bigio cfcf604b3d [react-packager] Introduce require.ensure
Summary:
This is the first step to add support for splitting the JS bundle into multiple ones. This diff adds support for keeping track of the async dependencies each module has. To do so we introduce the following syntax:

  require.ensure(['dep1', 'dep2, ..., 'depN'], callback);

Where the callback function is asynchronously invoked once all the indicated modules are loaded.

Internally, the packager keeps track of every set of async dependencies a module has. So for instance if a module looks like this:
  require.ensure(['dep1'], () => {...});
  require.ensure(['dep2'], () => {...});

the `Module` object will keep track of each set of dependencies separately (because we might want to put them on separate bundles).
2015-08-12 12:23:42 -08:00

209 lines
5.5 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 Promise = require('promise');
const docblock = require('./DependencyGraph/docblock');
const isAbsolutePath = require('absolute-path');
const path = require('path');
const replacePatterns = require('./replacePatterns');
class Module {
constructor(file, fastfs, moduleCache, cache) {
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;
}
isHaste() {
return this._cache.get(this.path, 'haste', () =>
this._read().then(data => !!data.id)
);
}
getName() {
return this._cache.get(
this.path,
'name',
() => this._read().then(data => {
if (data.id) {
return data.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));
});
})
);
}
getPackage() {
return this._moduleCache.getPackageForModule(this);
}
getDependencies() {
return this._cache.get(this.path, 'dependencies', () =>
this._read().then(data => data.dependencies)
);
}
invalidate() {
this._cache.invalidate(this.path);
}
getAsyncDependencies() {
return this._read().then(data => data.asyncDependencies);
}
_read() {
if (!this._reading) {
this._reading = this._fastfs.readFile(this.path).then(content => {
const data = {};
const moduleDocBlock = docblock.parseAsObject(content);
if (moduleDocBlock.providesModule || moduleDocBlock.provides) {
data.id = /^(\S*)/.exec(
moduleDocBlock.providesModule || moduleDocBlock.provides
)[1];
}
// Ignore requires in generated code. An example of this is prebuilt
// files like the SourceMap library.
if ('extern' in moduleDocBlock) {
data.dependencies = [];
} else {
var dependencies = extractRequires(content);
data.dependencies = dependencies.sync;
data.asyncDependencies = dependencies.async;
}
return data;
});
}
return this._reading;
}
getPlainObject() {
return Promise.all([
this.getName(),
this.getDependencies(),
]).then(([name, dependencies]) => this.addReference({
path: this.path,
isJSON: path.extname(this.path) === '.json',
isAsset: false,
isAsset_DEPRECATED: false,
isPolyfill: false,
resolution: undefined,
id: name,
dependencies
}));
}
hash() {
return `Module : ${this.path}`;
}
addReference(obj) {
Object.defineProperty(obj, '_ref', { value: this });
return obj;
}
}
/**
* Extract all required modules from a `code` string.
*/
const blockCommentRe = /\/\*(.|\n)*?\*\//g;
const lineCommentRe = /\/\/.+(\n|$)/g;
const trailingCommaRe = /,\s*$/g;
const removeSpacesRe = /\s/g;
const quotesRe = /'/g;
function extractRequires(code /*: string*/) /*: Array<string>*/ {
var deps = {
sync: [],
async: [],
};
code
.replace(blockCommentRe, '')
.replace(lineCommentRe, '')
// Parse sync dependencies. See comment below for further detils.
.replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => {
deps.sync.push(dep);
return match;
})
// Parse the sync dependencies this module has. When the module is
// required, all it's sync dependencies will be loaded into memory.
// Sync dependencies can be defined either using `require` or the ES6
// `import` syntax:
// var dep1 = require('dep1');
.replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => {
deps.sync.push(dep);
})
// Parse async dependencies this module has. As opposed to what happens
// with sync dependencies, when the module is required, it's async
// dependencies won't be loaded into memory. This is deferred till the
// code path gets to a `require.ensure` statement. The syntax is similar
// to webpack's one:
// require.ensure(['dep1', 'dep2'], () => {
// var dep1 = require('dep1');
// var dep2 = require('dep2');
// // do something with dep1 and dep2
// });
.replace(replacePatterns.REQUIRE_ENSURE_RE, (match, dep, post) => {
dep = dep
.replace(blockCommentRe, '')
.replace(lineCommentRe, '')
.replace(trailingCommaRe, '')
.replace(removeSpacesRe, '')
.replace(quotesRe, '"');
if (dep) {
try {
dep = JSON.parse('[' + dep + ']');
} catch(e) {
throw 'Error processing `require.ensure` while attemping to parse ' +
'dependencies `[' + dep + ']`: ' + e;
}
dep.forEach(d => {
if (typeof d !== 'string') {
throw 'Error processing `require.ensure`: dependencies `[' +
d + ']` must be string literals';
}
});
deps.async.push(dep);
}
});
return deps;
}
module.exports = Module;