mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Introduce a Bundler capable of generating the layout of modules for a given entry point. The current algorithm is the most trivial we could come up with: (1)it puts all the sync dependencies into the same bundle and (2) each group of async dependencies with all their dependencies into a separate bundle. For async dependencies we do this recursivelly, meaning that async dependencies could have async dependencies which will end up on separate bundles as well. The output of of the layout is an array of bundles. Each bundle is just an array for now with the dependencies in the order the requires where processed. Using this information we should be able to generate the actual bundles by using the `/path/to/entry/point.bundle` endpoint. We might change the structure of this json in the future, for instance to account for parent/child bundles relationships. The next step will be to improve this algorithm to avoid repeating quite a bit dependencies across bundles.
211 lines
5.6 KiB
JavaScript
211 lines
5.6 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';
|
|
}
|
|
});
|
|
|
|
// TODO: throw error if there are duplicate dependencies
|
|
|
|
deps.async.push(dep);
|
|
}
|
|
});
|
|
|
|
return deps;
|
|
}
|
|
|
|
module.exports = Module;
|