[react-packager] Rename 'Package' to 'Bundle'

Summary:
The word Package is overloaded, it may mean npm package, or may mean a collection of bundles. Neither is what we mean. We mean `bundle`.

This renames it and modernize some of the Bundler code.
This commit is contained in:
Amjad Masad 2015-08-12 11:47:02 -07:00
parent d1772eebe6
commit 3f1619b158
11 changed files with 702 additions and 689 deletions

View File

@ -22,18 +22,23 @@ exports.middleware = function(options) {
return server.processRequest.bind(server);
};
exports.buildPackage = function(options, packageOptions) {
// Renamed "package" to "bundle". But maintain backwards
// compat.
exports.buildPackage =
exports.buildBundle = function(options, bundleOptions) {
var server = createServer(options);
return server.buildPackage(packageOptions)
return server.buildBundle(bundleOptions)
.then(function(p) {
server.end();
return p;
});
};
exports.buildPackageFromUrl = function(options, reqUrl) {
exports.buildPackageFromUrl =
exports.buildBundleFromUrl = function(options, reqUrl) {
var server = createServer(options);
return server.buildPackageFromUrl(reqUrl)
return server.buildBundleFromUrl(reqUrl)
.then(function(p) {
server.end();
return p;

307
react-packager/src/Bundler/Bundle.js vendored Normal file
View File

@ -0,0 +1,307 @@
/**
* 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 _ = require('underscore');
const base64VLQ = require('./base64-vlq');
const UglifyJS = require('uglify-js');
const ModuleTransport = require('../lib/ModuleTransport');
const SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL=';
class Bundle {
constructor(sourceMapUrl) {
this._finalized = false;
this._modules = [];
this._assets = [];
this._sourceMapUrl = sourceMapUrl;
this._shouldCombineSourceMaps = false;
}
setMainModuleId(moduleId) {
this._mainModuleId = moduleId;
}
addModule(module) {
if (!(module instanceof ModuleTransport)) {
throw new Error('Expeceted a ModuleTransport object');
}
// If we get a map from the transformer we'll switch to a mode
// were we're combining the source maps as opposed to
if (!this._shouldCombineSourceMaps && module.map != null) {
this._shouldCombineSourceMaps = true;
}
this._modules.push(module);
}
getModules() {
return this._modules;
}
addAsset(asset) {
this._assets.push(asset);
}
finalize(options) {
options = options || {};
if (options.runMainModule) {
const runCode = ';require("' + this._mainModuleId + '");';
this.addModule(new ModuleTransport({
code: runCode,
virtual: true,
sourceCode: runCode,
sourcePath: 'RunMainModule.js'
}));
}
Object.freeze(this._modules);
Object.seal(this._modules);
Object.freeze(this._assets);
Object.seal(this._assets);
this._finalized = true;
}
_assertFinalized() {
if (!this._finalized) {
throw new Error('Bundle needs to be finalized before getting any source');
}
}
_getSource() {
if (this._source == null) {
this._source = _.pluck(this._modules, 'code').join('\n');
}
return this._source;
}
_getInlineSourceMap() {
if (this._inlineSourceMap == null) {
const sourceMap = this.getSourceMap({excludeSource: true});
/*eslint-env node*/
const encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64');
this._inlineSourceMap = 'data:application/json;base64,' + encoded;
}
return this._inlineSourceMap;
}
getSource(options) {
this._assertFinalized();
options = options || {};
if (options.minify) {
return this.getMinifiedSourceAndMap().code;
}
let source = this._getSource();
if (options.inlineSourceMap) {
source += SOURCEMAPPING_URL + this._getInlineSourceMap();
} else if (this._sourceMapUrl) {
source += SOURCEMAPPING_URL + this._sourceMapUrl;
}
return source;
}
getMinifiedSourceAndMap() {
this._assertFinalized();
const source = this._getSource();
try {
return UglifyJS.minify(source, {
fromString: true,
outSourceMap: 'bundle.js',
inSourceMap: this.getSourceMap(),
});
} catch(e) {
// Sometimes, when somebody is using a new syntax feature that we
// don't yet have transform for, the untransformed line is sent to
// uglify, and it chokes on it. This code tries to print the line
// and the module for easier debugging
let errorMessage = 'Error while minifying JS\n';
if (e.line) {
errorMessage += 'Transformed code line: "' +
source.split('\n')[e.line - 1] + '"\n';
}
if (e.pos) {
let fromIndex = source.lastIndexOf('__d(\'', e.pos);
if (fromIndex > -1) {
fromIndex += '__d(\''.length;
const toIndex = source.indexOf('\'', fromIndex);
errorMessage += 'Module name (best guess): ' +
source.substring(fromIndex, toIndex) + '\n';
}
}
errorMessage += e.toString();
throw new Error(errorMessage);
}
}
/**
* I found a neat trick in the sourcemap spec that makes it easy
* to concat sourcemaps. The `sections` field allows us to combine
* the sourcemap easily by adding an offset. Tested on chrome.
* Seems like it's not yet in Firefox but that should be fine for
* now.
*/
_getCombinedSourceMaps(options) {
const result = {
version: 3,
file: 'bundle.js',
sections: [],
};
let line = 0;
this._modules.forEach(function(module) {
let map = module.map;
if (module.virtual) {
map = generateSourceMapForVirtualModule(module);
}
if (options.excludeSource) {
map = _.extend({}, map, {sourcesContent: []});
}
result.sections.push({
offset: { line: line, column: 0 },
map: map,
});
line += module.code.split('\n').length;
});
return result;
}
getSourceMap(options) {
this._assertFinalized();
options = options || {};
if (this._shouldCombineSourceMaps) {
return this._getCombinedSourceMaps(options);
}
const mappings = this._getMappings();
const map = {
file: 'bundle.js',
sources: _.pluck(this._modules, 'sourcePath'),
version: 3,
names: [],
mappings: mappings,
sourcesContent: options.excludeSource
? [] : _.pluck(this._modules, 'sourceCode')
};
return map;
}
getAssets() {
return this._assets;
}
_getMappings() {
const modules = this._modules;
// The first line mapping in our package is basically the base64vlq code for
// zeros (A).
const firstLine = 'AAAA';
// Most other lines in our mappings are all zeros (for module, column etc)
// except for the lineno mappinp: curLineno - prevLineno = 1; Which is C.
const line = 'AACA';
const moduleLines = Object.create(null);
let mappings = '';
for (let i = 0; i < modules.length; i++) {
const module = modules[i];
const code = module.code;
let lastCharNewLine = false;
moduleLines[module.sourcePath] = 0;
for (let t = 0; t < code.length; t++) {
if (t === 0 && i === 0) {
mappings += firstLine;
} else if (t === 0) {
mappings += 'AC';
// This is the only place were we actually don't know the mapping ahead
// of time. When it's a new module (and not the first) the lineno
// mapping is 0 (current) - number of lines in prev module.
mappings += base64VLQ.encode(
0 - moduleLines[modules[i - 1].sourcePath]
);
mappings += 'A';
} else if (lastCharNewLine) {
moduleLines[module.sourcePath]++;
mappings += line;
}
lastCharNewLine = code[t] === '\n';
if (lastCharNewLine) {
mappings += ';';
}
}
if (i !== modules.length - 1) {
mappings += ';';
}
}
return mappings;
}
getJSModulePaths() {
return this._modules.filter(function(module) {
// Filter out non-js files. Like images etc.
return !module.virtual;
}).map(function(module) {
return module.sourcePath;
});
}
getDebugInfo() {
return [
'<div><h3>Main Module:</h3> ' + this._mainModuleId + '</div>',
'<style>',
'pre.collapsed {',
' height: 10px;',
' width: 100px;',
' display: block;',
' text-overflow: ellipsis;',
' overflow: hidden;',
' cursor: pointer;',
'}',
'</style>',
'<h3> Module paths and transformed code: </h3>',
this._modules.map(function(m) {
return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' +
'<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
_.escape(m.code) + '</pre></code></div>';
}).join('\n'),
].join('\n');
}
}
function generateSourceMapForVirtualModule(module) {
// All lines map 1-to-1
let mappings = 'AAAA;';
for (let i = 1; i < module.code.split('\n').length; i++) {
mappings += 'AACA;';
}
return {
version: 3,
sources: [ module.sourcePath ],
names: [],
mappings: mappings,
file: module.sourcePath,
sourcesContent: [ module.sourceCode ],
};
}
module.exports = Bundle;

View File

@ -12,35 +12,35 @@ jest.autoMockOff();
var SourceMapGenerator = require('source-map').SourceMapGenerator;
describe('Package', function() {
describe('Bundle', function() {
var ModuleTransport;
var Package;
var ppackage;
var Bundle;
var bundle;
beforeEach(function() {
Package = require('../Package');
Bundle = require('../Bundle');
ModuleTransport = require('../../lib/ModuleTransport');
ppackage = new Package('test_url');
ppackage.getSourceMap = jest.genMockFn().mockImpl(function() {
bundle = new Bundle('test_url');
bundle.getSourceMap = jest.genMockFn().mockImpl(function() {
return 'test-source-map';
});
});
describe('source package', function() {
it('should create a package and get the source', function() {
ppackage.addModule(new ModuleTransport({
describe('source bundle', function() {
it('should create a bundle and get the source', function() {
bundle.addModule(new ModuleTransport({
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
}));
ppackage.addModule(new ModuleTransport({
bundle.addModule(new ModuleTransport({
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
}));
ppackage.finalize({});
expect(ppackage.getSource()).toBe([
bundle.finalize({});
expect(bundle.getSource()).toBe([
'transformed foo;',
'transformed bar;',
'\/\/@ sourceMappingURL=test_url'
@ -48,7 +48,7 @@ describe('Package', function() {
});
it('should be ok to leave out the source map url', function() {
var p = new Package();
var p = new Bundle();
p.addModule(new ModuleTransport({
code: 'transformed foo;',
sourceCode: 'source foo',
@ -67,22 +67,22 @@ describe('Package', function() {
].join('\n'));
});
it('should create a package and add run module code', function() {
ppackage.addModule(new ModuleTransport({
it('should create a bundle and add run module code', function() {
bundle.addModule(new ModuleTransport({
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
ppackage.addModule(new ModuleTransport({
bundle.addModule(new ModuleTransport({
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path'
}));
ppackage.setMainModuleId('foo');
ppackage.finalize({runMainModule: true});
expect(ppackage.getSource()).toBe([
bundle.setMainModuleId('foo');
bundle.finalize({runMainModule: true});
expect(bundle.getSource()).toBe([
'transformed foo;',
'transformed bar;',
';require("foo");',
@ -100,19 +100,19 @@ describe('Package', function() {
return minified;
};
ppackage.addModule(new ModuleTransport({
bundle.addModule(new ModuleTransport({
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
ppackage.finalize();
expect(ppackage.getMinifiedSourceAndMap()).toBe(minified);
bundle.finalize();
expect(bundle.getMinifiedSourceAndMap()).toBe(minified);
});
});
describe('sourcemap package', function() {
describe('sourcemap bundle', function() {
it('should create sourcemap', function() {
var p = new Package('test_url');
var p = new Bundle('test_url');
p.addModule(new ModuleTransport({
code: [
'transformed foo',
@ -143,11 +143,11 @@ describe('Package', function() {
p.setMainModuleId('foo');
p.finalize({runMainModule: true});
var s = p.getSourceMap();
expect(s).toEqual(genSourceMap(p._modules));
expect(s).toEqual(genSourceMap(p.getModules()));
});
it('should combine sourcemaps', function() {
var p = new Package('test_url');
var p = new Bundle('test_url');
p.addModule(new ModuleTransport({
code: 'transformed foo;\n',
@ -215,7 +215,7 @@ describe('Package', function() {
describe('getAssets()', function() {
it('should save and return asset objects', function() {
var p = new Package('test_url');
var p = new Bundle('test_url');
var asset1 = {};
var asset2 = {};
p.addAsset(asset1);
@ -227,7 +227,7 @@ describe('Package', function() {
describe('getJSModulePaths()', function() {
it('should return module paths', function() {
var p = new Package('test_url');
var p = new Bundle('test_url');
p.addModule(new ModuleTransport({
code: 'transformed foo;\n',
sourceCode: 'source foo',
@ -248,7 +248,7 @@ describe('Package', function() {
function genSourceMap(modules) {
var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3});
var packageLineNo = 0;
var bundleLineNo = 0;
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
var transformedCode = module.code;
@ -259,7 +259,7 @@ describe('Package', function() {
for (var t = 0; t < transformedCode.length; t++) {
if (t === 0 || lastCharNewLine) {
sourceMapGen.addMapping({
generated: {line: packageLineNo + 1, column: 0},
generated: {line: bundleLineNo + 1, column: 0},
original: {line: transformedLineCount + 1, column: 0},
source: sourcePath
});
@ -267,10 +267,10 @@ describe('Package', function() {
lastCharNewLine = transformedCode[t] === '\n';
if (lastCharNewLine) {
transformedLineCount++;
packageLineNo++;
bundleLineNo++;
}
}
packageLineNo++;
bundleLineNo++;
sourceMapGen.setSourceContent(
sourcePath,
sourceCode

View File

@ -9,7 +9,7 @@
'use strict';
jest
.setMock('worker-farm', function() { return function() {};})
.setMock('worker-farm', () => () => undefined)
.dontMock('underscore')
.dontMock('../../lib/ModuleTransport')
.setMock('uglify-js')
@ -19,11 +19,11 @@ jest.mock('fs');
var Promise = require('promise');
describe('Packager', function() {
describe('Bundler', function() {
var getDependencies;
var wrapModule;
var Packager;
var packager;
var Bundler;
var bundler;
var assetServer;
var modules;
@ -37,7 +37,7 @@ describe('Packager', function() {
};
});
Packager = require('../');
Bundler = require('../');
require('fs').statSync.mockImpl(function() {
return {
@ -53,7 +53,7 @@ describe('Packager', function() {
getAssetData: jest.genMockFn(),
};
packager = new Packager({
bundler = new Bundler({
projectRoots: ['/root'],
assetServer: assetServer,
});
@ -118,8 +118,8 @@ describe('Packager', function() {
});
});
pit('create a package', function() {
return packager.package('/root/foo.js', true, 'source_map_url')
pit('create a bundle', function() {
return bundler.bundle('/root/foo.js', true, 'source_map_url')
.then(function(p) {
expect(p.addModule.mock.calls[0][0]).toEqual({
code: 'lol transformed /root/foo.js lol',
@ -204,7 +204,7 @@ describe('Packager', function() {
});
pit('gets the list of dependencies', function() {
return packager.getDependencies('/root/foo.js', true)
return bundler.getDependencies('/root/foo.js', true)
.then(({dependencies}) => {
expect(dependencies).toEqual([
{

291
react-packager/src/Bundler/index.js vendored Normal file
View File

@ -0,0 +1,291 @@
/**
* 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 assert = require('assert');
const fs = require('fs');
const path = require('path');
const Promise = require('promise');
const Cache = require('../Cache');
const Transformer = require('../JSTransformer');
const DependencyResolver = require('../DependencyResolver');
const Bundle = require('./Bundle');
const Activity = require('../Activity');
const ModuleTransport = require('../lib/ModuleTransport');
const declareOpts = require('../lib/declareOpts');
const imageSize = require('image-size');
const sizeOf = Promise.denodeify(imageSize);
const readFile = Promise.denodeify(fs.readFile);
const validateOpts = declareOpts({
projectRoots: {
type: 'array',
required: true,
},
blacklistRE: {
type: 'object', // typeof regex is object
},
moduleFormat: {
type: 'string',
default: 'haste',
},
polyfillModuleNames: {
type: 'array',
default: [],
},
cacheVersion: {
type: 'string',
default: '1.0',
},
resetCache: {
type: 'boolean',
default: false,
},
transformModulePath: {
type:'string',
required: false,
},
nonPersistent: {
type: 'boolean',
default: false,
},
assetRoots: {
type: 'array',
required: false,
},
assetExts: {
type: 'array',
default: ['png'],
},
fileWatcher: {
type: 'object',
required: true,
},
assetServer: {
type: 'object',
required: true,
}
});
class Bundler {
constructor(options) {
const opts = this._opts = validateOpts(options);
opts.projectRoots.forEach(verifyRootExists);
this._cache = opts.nonPersistent
? new DummyCache()
: new Cache({
resetCache: opts.resetCache,
cacheVersion: opts.cacheVersion,
projectRoots: opts.projectRoots,
transformModulePath: opts.transformModulePath,
});
this._resolver = new DependencyResolver({
projectRoots: opts.projectRoots,
blacklistRE: opts.blacklistRE,
polyfillModuleNames: opts.polyfillModuleNames,
nonPersistent: opts.nonPersistent,
moduleFormat: opts.moduleFormat,
assetRoots: opts.assetRoots,
fileWatcher: opts.fileWatcher,
assetExts: opts.assetExts,
cache: this._cache,
});
this._transformer = new Transformer({
projectRoots: opts.projectRoots,
blacklistRE: opts.blacklistRE,
cache: this._cache,
transformModulePath: opts.transformModulePath,
});
this._projectRoots = opts.projectRoots;
this._assetServer = opts.assetServer;
}
kill() {
this._transformer.kill();
return this._cache.end();
}
bundle(main, runModule, sourceMapUrl, isDev) {
const bundle = new Bundle(sourceMapUrl);
const transformModule = this._transformModule.bind(this, bundle);
const findEventId = Activity.startEvent('find dependencies');
let transformEventId;
return this.getDependencies(main, isDev)
.then(function(result) {
Activity.endEvent(findEventId);
transformEventId = Activity.startEvent('transform');
bundle.setMainModuleId(result.mainModuleId);
return Promise.all(
result.dependencies.map(transformModule)
);
})
.then(function(transformedModules) {
Activity.endEvent(transformEventId);
transformedModules.forEach(function(moduleTransport) {
bundle.addModule(moduleTransport);
});
bundle.finalize({ runMainModule: runModule });
return bundle;
});
}
invalidateFile(filePath) {
this._transformer.invalidateFile(filePath);
}
getDependencies(main, isDev) {
return this._resolver.getDependencies(main, { dev: isDev });
}
_transformModule(bundle, module) {
let transform;
if (module.isAsset_DEPRECATED) {
transform = this.generateAssetModule_DEPRECATED(bundle, module);
} else if (module.isAsset) {
transform = this.generateAssetModule(bundle, module);
} else if (module.isJSON) {
transform = generateJSONModule(module);
} else {
transform = this._transformer.loadFileAndTransform(
path.resolve(module.path)
);
}
const resolver = this._resolver;
return transform.then(
transformed => resolver.wrapModule(module, transformed.code).then(
code => new ModuleTransport({
code: code,
map: transformed.map,
sourceCode: transformed.sourceCode,
sourcePath: transformed.sourcePath,
virtual: transformed.virtual,
})
)
);
}
getGraphDebugInfo() {
return this._resolver.getDebugInfo();
}
generateAssetModule_DEPRECATED(bundle, module) {
return sizeOf(module.path).then(function(dimensions) {
const img = {
__packager_asset: true,
isStatic: true,
path: module.path,
uri: module.id.replace(/^[^!]+!/, ''),
width: dimensions.width / module.resolution,
height: dimensions.height / module.resolution,
deprecated: true,
};
bundle.addAsset(img);
const code = 'module.exports = ' + JSON.stringify(img) + ';';
return new ModuleTransport({
code: code,
sourceCode: code,
sourcePath: module.path,
virtual: true,
});
});
}
generateAssetModule(bundle, module) {
const relPath = getPathRelativeToRoot(this._projectRoots, module.path);
return Promise.all([
sizeOf(module.path),
this._assetServer.getAssetData(relPath),
]).then(function(res) {
const dimensions = res[0];
const assetData = res[1];
const img = {
__packager_asset: true,
fileSystemLocation: path.dirname(module.path),
httpServerLocation: path.join('/assets', path.dirname(relPath)),
width: dimensions.width / module.resolution,
height: dimensions.height / module.resolution,
scales: assetData.scales,
hash: assetData.hash,
name: assetData.name,
type: assetData.type,
};
bundle.addAsset(img);
const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);';
const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img));
return new ModuleTransport({
code: code,
sourceCode: code,
sourcePath: module.path,
virtual: true,
});
});
}
}
function generateJSONModule(module) {
return readFile(module.path).then(function(data) {
const code = 'module.exports = ' + data.toString('utf8') + ';';
return new ModuleTransport({
code: code,
sourceCode: code,
sourcePath: module.path,
virtual: true,
});
});
}
function getPathRelativeToRoot(roots, absPath) {
for (let i = 0; i < roots.length; i++) {
const relPath = path.relative(roots[i], absPath);
if (relPath[0] !== '.') {
return relPath;
}
}
throw new Error(
'Expected root module to be relative to one of the project roots'
);
}
function verifyRootExists(root) {
// Verify that the root exists.
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
}
class DummyCache {
get(filepath, field, loaderCb) {
return loaderCb();
}
end(){}
invalidate(filepath){}
}
module.exports = Bundler;

View File

@ -13,7 +13,6 @@ const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
const Fastfs = require('../fastfs');
const ModuleCache = require('../ModuleCache');
const Promise = require('promise');
const _ = require('underscore');
const crawl = require('../crawlers');
const debug = require('debug')('DependencyGraph');
const declareOpts = require('../../lib/declareOpts');

View File

@ -1,300 +0,0 @@
/**
* 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';
var _ = require('underscore');
var base64VLQ = require('./base64-vlq');
var UglifyJS = require('uglify-js');
var ModuleTransport = require('../lib/ModuleTransport');
module.exports = Package;
var SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL=';
function Package(sourceMapUrl) {
this._finalized = false;
this._modules = [];
this._assets = [];
this._sourceMapUrl = sourceMapUrl;
this._shouldCombineSourceMaps = false;
}
Package.prototype.setMainModuleId = function(moduleId) {
this._mainModuleId = moduleId;
};
Package.prototype.addModule = function(module) {
if (!(module instanceof ModuleTransport)) {
throw new Error('Expeceted a ModuleTransport object');
}
// If we get a map from the transformer we'll switch to a mode
// were we're combining the source maps as opposed to
if (!this._shouldCombineSourceMaps && module.map != null) {
this._shouldCombineSourceMaps = true;
}
this._modules.push(module);
};
Package.prototype.addAsset = function(asset) {
this._assets.push(asset);
};
Package.prototype.finalize = function(options) {
options = options || {};
if (options.runMainModule) {
var runCode = ';require("' + this._mainModuleId + '");';
this.addModule(new ModuleTransport({
code: runCode,
virtual: true,
sourceCode: runCode,
sourcePath: 'RunMainModule.js'
}));
}
Object.freeze(this._modules);
Object.seal(this._modules);
Object.freeze(this._assets);
Object.seal(this._assets);
this._finalized = true;
};
Package.prototype._assertFinalized = function() {
if (!this._finalized) {
throw new Error('Package need to be finalized before getting any source');
}
};
Package.prototype._getSource = function() {
if (this._source == null) {
this._source = _.pluck(this._modules, 'code').join('\n');
}
return this._source;
};
Package.prototype._getInlineSourceMap = function() {
if (this._inlineSourceMap == null) {
var sourceMap = this.getSourceMap({excludeSource: true});
var encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64');
this._inlineSourceMap = 'data:application/json;base64,' + encoded;
}
return this._inlineSourceMap;
};
Package.prototype.getSource = function(options) {
this._assertFinalized();
options = options || {};
if (options.minify) {
return this.getMinifiedSourceAndMap().code;
}
var source = this._getSource();
if (options.inlineSourceMap) {
source += SOURCEMAPPING_URL + this._getInlineSourceMap();
} else if (this._sourceMapUrl) {
source += SOURCEMAPPING_URL + this._sourceMapUrl;
}
return source;
};
Package.prototype.getMinifiedSourceAndMap = function() {
this._assertFinalized();
var source = this._getSource();
try {
return UglifyJS.minify(source, {
fromString: true,
outSourceMap: 'bundle.js',
inSourceMap: this.getSourceMap(),
});
} catch(e) {
// Sometimes, when somebody is using a new syntax feature that we
// don't yet have transform for, the untransformed line is sent to
// uglify, and it chokes on it. This code tries to print the line
// and the module for easier debugging
var errorMessage = 'Error while minifying JS\n';
if (e.line) {
errorMessage += 'Transformed code line: "' +
source.split('\n')[e.line - 1] + '"\n';
}
if (e.pos) {
var fromIndex = source.lastIndexOf('__d(\'', e.pos);
if (fromIndex > -1) {
fromIndex += '__d(\''.length;
var toIndex = source.indexOf('\'', fromIndex);
errorMessage += 'Module name (best guess): ' +
source.substring(fromIndex, toIndex) + '\n';
}
}
errorMessage += e.toString();
throw new Error(errorMessage);
}
};
/**
* I found a neat trick in the sourcemap spec that makes it easy
* to concat sourcemaps. The `sections` field allows us to combine
* the sourcemap easily by adding an offset. Tested on chrome.
* Seems like it's not yet in Firefox but that should be fine for
* now.
*/
Package.prototype._getCombinedSourceMaps = function(options) {
var result = {
version: 3,
file: 'bundle.js',
sections: [],
};
var line = 0;
this._modules.forEach(function(module) {
var map = module.map;
if (module.virtual) {
map = generateSourceMapForVirtualModule(module);
}
if (options.excludeSource) {
map = _.extend({}, map, {sourcesContent: []});
}
result.sections.push({
offset: { line: line, column: 0 },
map: map,
});
line += module.code.split('\n').length;
});
return result;
};
Package.prototype.getSourceMap = function(options) {
this._assertFinalized();
options = options || {};
if (this._shouldCombineSourceMaps) {
return this._getCombinedSourceMaps(options);
}
var mappings = this._getMappings();
var map = {
file: 'bundle.js',
sources: _.pluck(this._modules, 'sourcePath'),
version: 3,
names: [],
mappings: mappings,
sourcesContent: options.excludeSource
? [] : _.pluck(this._modules, 'sourceCode')
};
return map;
};
Package.prototype.getAssets = function() {
return this._assets;
};
Package.prototype._getMappings = function() {
var modules = this._modules;
// The first line mapping in our package is basically the base64vlq code for
// zeros (A).
var firstLine = 'AAAA';
// Most other lines in our mappings are all zeros (for module, column etc)
// except for the lineno mappinp: curLineno - prevLineno = 1; Which is C.
var line = 'AACA';
var moduleLines = Object.create(null);
var mappings = '';
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
var code = module.code;
var lastCharNewLine = false;
moduleLines[module.sourcePath] = 0;
for (var t = 0; t < code.length; t++) {
if (t === 0 && i === 0) {
mappings += firstLine;
} else if (t === 0) {
mappings += 'AC';
// This is the only place were we actually don't know the mapping ahead
// of time. When it's a new module (and not the first) the lineno
// mapping is 0 (current) - number of lines in prev module.
mappings += base64VLQ.encode(
0 - moduleLines[modules[i - 1].sourcePath]
);
mappings += 'A';
} else if (lastCharNewLine) {
moduleLines[module.sourcePath]++;
mappings += line;
}
lastCharNewLine = code[t] === '\n';
if (lastCharNewLine) {
mappings += ';';
}
}
if (i !== modules.length - 1) {
mappings += ';';
}
}
return mappings;
};
Package.prototype.getJSModulePaths = function() {
return this._modules.filter(function(module) {
// Filter out non-js files. Like images etc.
return !module.virtual;
}).map(function(module) {
return module.sourcePath;
});
};
Package.prototype.getDebugInfo = function() {
return [
'<div><h3>Main Module:</h3> ' + this._mainModuleId + '</div>',
'<style>',
'pre.collapsed {',
' height: 10px;',
' width: 100px;',
' display: block;',
' text-overflow: ellipsis;',
' overflow: hidden;',
' cursor: pointer;',
'}',
'</style>',
'<h3> Module paths and transformed code: </h3>',
this._modules.map(function(m) {
return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' +
'<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
_.escape(m.code) + '</pre></code></div>';
}).join('\n'),
].join('\n');
};
function generateSourceMapForVirtualModule(module) {
// All lines map 1-to-1
var mappings = 'AAAA;';
for (var i = 1; i < module.code.split('\n').length; i++) {
mappings += 'AACA;';
}
return {
version: 3,
sources: [ module.sourcePath ],
names: [],
mappings: mappings,
file: module.sourcePath,
sourcesContent: [ module.sourceCode ],
};
}

View File

@ -1,289 +0,0 @@
/**
* 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';
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var Promise = require('promise');
var Cache = require('../Cache');
var Transformer = require('../JSTransformer');
var DependencyResolver = require('../DependencyResolver');
var Package = require('./Package');
var Activity = require('../Activity');
var ModuleTransport = require('../lib/ModuleTransport');
var declareOpts = require('../lib/declareOpts');
var imageSize = require('image-size');
var sizeOf = Promise.denodeify(imageSize);
var readFile = Promise.denodeify(fs.readFile);
var validateOpts = declareOpts({
projectRoots: {
type: 'array',
required: true,
},
blacklistRE: {
type: 'object', // typeof regex is object
},
moduleFormat: {
type: 'string',
default: 'haste',
},
polyfillModuleNames: {
type: 'array',
default: [],
},
cacheVersion: {
type: 'string',
default: '1.0',
},
resetCache: {
type: 'boolean',
default: false,
},
transformModulePath: {
type:'string',
required: false,
},
nonPersistent: {
type: 'boolean',
default: false,
},
assetRoots: {
type: 'array',
required: false,
},
assetExts: {
type: 'array',
default: ['png'],
},
fileWatcher: {
type: 'object',
required: true,
},
assetServer: {
type: 'object',
required: true,
}
});
function Packager(options) {
var opts = this._opts = validateOpts(options);
opts.projectRoots.forEach(verifyRootExists);
this._cache = opts.nonPersistent
? new DummyCache()
: new Cache({
resetCache: opts.resetCache,
cacheVersion: opts.cacheVersion,
projectRoots: opts.projectRoots,
transformModulePath: opts.transformModulePath,
});
this._resolver = new DependencyResolver({
projectRoots: opts.projectRoots,
blacklistRE: opts.blacklistRE,
polyfillModuleNames: opts.polyfillModuleNames,
nonPersistent: opts.nonPersistent,
moduleFormat: opts.moduleFormat,
assetRoots: opts.assetRoots,
fileWatcher: opts.fileWatcher,
assetExts: opts.assetExts,
cache: this._cache,
});
this._transformer = new Transformer({
projectRoots: opts.projectRoots,
blacklistRE: opts.blacklistRE,
cache: this._cache,
transformModulePath: opts.transformModulePath,
});
this._projectRoots = opts.projectRoots;
this._assetServer = opts.assetServer;
}
Packager.prototype.kill = function() {
this._transformer.kill();
return this._cache.end();
};
Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) {
var ppackage = new Package(sourceMapUrl);
var transformModule = this._transformModule.bind(this, ppackage);
var findEventId = Activity.startEvent('find dependencies');
var transformEventId;
return this.getDependencies(main, isDev)
.then(function(result) {
Activity.endEvent(findEventId);
transformEventId = Activity.startEvent('transform');
ppackage.setMainModuleId(result.mainModuleId);
return Promise.all(
result.dependencies.map(transformModule)
);
})
.then(function(transformedModules) {
Activity.endEvent(transformEventId);
transformedModules.forEach(function(moduleTransport) {
ppackage.addModule(moduleTransport);
});
ppackage.finalize({ runMainModule: runModule });
return ppackage;
});
};
Packager.prototype.invalidateFile = function(filePath) {
this._transformer.invalidateFile(filePath);
};
Packager.prototype.getDependencies = function(main, isDev) {
return this._resolver.getDependencies(main, { dev: isDev });
};
Packager.prototype._transformModule = function(ppackage, module) {
var transform;
if (module.isAsset_DEPRECATED) {
transform = this.generateAssetModule_DEPRECATED(ppackage, module);
} else if (module.isAsset) {
transform = this.generateAssetModule(ppackage, module);
} else if (module.isJSON) {
transform = generateJSONModule(module);
} else {
transform = this._transformer.loadFileAndTransform(
path.resolve(module.path)
);
}
var resolver = this._resolver;
return transform.then(
transformed => resolver.wrapModule(module, transformed.code).then(
code => new ModuleTransport({
code: code,
map: transformed.map,
sourceCode: transformed.sourceCode,
sourcePath: transformed.sourcePath,
virtual: transformed.virtual,
})
)
);
};
Packager.prototype.getGraphDebugInfo = function() {
return this._resolver.getDebugInfo();
};
Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) {
return sizeOf(module.path).then(function(dimensions) {
var img = {
__packager_asset: true,
isStatic: true,
path: module.path,
uri: module.id.replace(/^[^!]+!/, ''),
width: dimensions.width / module.resolution,
height: dimensions.height / module.resolution,
deprecated: true,
};
ppackage.addAsset(img);
var code = 'module.exports = ' + JSON.stringify(img) + ';';
return new ModuleTransport({
code: code,
sourceCode: code,
sourcePath: module.path,
virtual: true,
});
});
};
Packager.prototype.generateAssetModule = function(ppackage, module) {
var relPath = getPathRelativeToRoot(this._projectRoots, module.path);
return Promise.all([
sizeOf(module.path),
this._assetServer.getAssetData(relPath),
]).then(function(res) {
var dimensions = res[0];
var assetData = res[1];
var img = {
__packager_asset: true,
fileSystemLocation: path.dirname(module.path),
httpServerLocation: path.join('/assets', path.dirname(relPath)),
width: dimensions.width / module.resolution,
height: dimensions.height / module.resolution,
scales: assetData.scales,
hash: assetData.hash,
name: assetData.name,
type: assetData.type,
};
ppackage.addAsset(img);
var ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);';
var code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img));
return new ModuleTransport({
code: code,
sourceCode: code,
sourcePath: module.path,
virtual: true,
});
});
};
function generateJSONModule(module) {
return readFile(module.path).then(function(data) {
var code = 'module.exports = ' + data.toString('utf8') + ';';
return new ModuleTransport({
code: code,
sourceCode: code,
sourcePath: module.path,
virtual: true,
});
});
}
function getPathRelativeToRoot(roots, absPath) {
for (var i = 0; i < roots.length; i++) {
var relPath = path.relative(roots[i], absPath);
if (relPath[0] !== '.') {
return relPath;
}
}
throw new Error(
'Expected root module to be relative to one of the project roots'
);
}
function verifyRootExists(root) {
// Verify that the root exists.
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
}
class DummyCache {
get(filepath, field, loaderCb) {
return loaderCb();
}
end(){}
invalidate(filepath){}
}
module.exports = Packager;

View File

@ -24,7 +24,7 @@ var Promise = require('promise');
describe('processRequest', function() {
var server;
var Packager;
var Bundler;
var FileWatcher;
var options = {
@ -57,10 +57,10 @@ describe('processRequest', function() {
var triggerFileChange;
beforeEach(function() {
Packager = require('../../Packager');
Bundler = require('../../Bundler');
FileWatcher = require('../../FileWatcher');
Packager.prototype.package = jest.genMockFunction().mockImpl(function() {
Bundler.prototype.bundle = jest.genMockFunction().mockImpl(function() {
return Promise.resolve({
getSource: function() {
return 'this is the source';
@ -81,7 +81,7 @@ describe('processRequest', function() {
return this;
};
Packager.prototype.invalidateFile = invalidatorFunc;
Bundler.prototype.invalidateFile = invalidatorFunc;
var Server = require('../');
server = new Server(options);
@ -121,7 +121,7 @@ describe('processRequest', function() {
'index.ios.includeRequire.bundle'
).then(function(response) {
expect(response).toEqual('this is the source');
expect(Packager.prototype.package).toBeCalledWith(
expect(Bundler.prototype.bundle).toBeCalledWith(
'index.ios.js',
true,
'index.ios.includeRequire.map',
@ -142,7 +142,7 @@ describe('processRequest', function() {
describe('file changes', function() {
pit('invalides files in package when file is updated', function() {
pit('invalides files in bundle when file is updated', function() {
return makeRequest(
requestHandler,
'mybundle.bundle?runModule=true'
@ -153,9 +153,9 @@ describe('processRequest', function() {
});
});
pit('rebuilds the packages that contain a file when that file is changed', function() {
var packageFunc = jest.genMockFunction();
packageFunc
pit('rebuilds the bundles that contain a file when that file is changed', function() {
var bundleFunc = jest.genMockFunction();
bundleFunc
.mockReturnValueOnce(
Promise.resolve({
getSource: function() {
@ -173,7 +173,7 @@ describe('processRequest', function() {
})
);
Packager.prototype.package = packageFunc;
Bundler.prototype.bundle = bundleFunc;
var Server = require('../../Server');
server = new Server(options);
@ -184,13 +184,13 @@ describe('processRequest', function() {
return makeRequest(requestHandler, 'mybundle.bundle?runModule=true')
.then(function(response) {
expect(response).toEqual('this is the first source');
expect(packageFunc.mock.calls.length).toBe(1);
expect(bundleFunc.mock.calls.length).toBe(1);
triggerFileChange('all','path/file.js', options.projectRoots[0]);
jest.runAllTimers();
jest.runAllTimers();
})
.then(function() {
expect(packageFunc.mock.calls.length).toBe(2);
expect(bundleFunc.mock.calls.length).toBe(2);
return makeRequest(requestHandler, 'mybundle.bundle?runModule=true')
.then(function(response) {
expect(response).toEqual('this is the rebuilt source');
@ -259,12 +259,12 @@ describe('processRequest', function() {
});
});
describe('buildPackage(options)', function() {
it('Calls the packager with the correct args', function() {
server.buildPackage({
describe('buildBundle(options)', function() {
it('Calls the bundler with the correct args', function() {
server.buildBundle({
entryFile: 'foo file'
});
expect(Packager.prototype.package).toBeCalledWith(
expect(Bundler.prototype.bundle).toBeCalledWith(
'foo file',
true,
undefined,
@ -273,10 +273,10 @@ describe('processRequest', function() {
});
});
describe('buildPackageFromUrl(options)', function() {
it('Calls the packager with the correct args', function() {
server.buildPackageFromUrl('/path/to/foo.bundle?dev=false&runModule=false');
expect(Packager.prototype.package).toBeCalledWith(
describe('buildBundleFromUrl(options)', function() {
it('Calls the bundler with the correct args', function() {
server.buildBundleFromUrl('/path/to/foo.bundle?dev=false&runModule=false');
expect(Bundler.prototype.bundle).toBeCalledWith(
'path/to/foo.js',
false,
'/path/to/foo.map',

View File

@ -12,7 +12,7 @@ var url = require('url');
var path = require('path');
var declareOpts = require('../lib/declareOpts');
var FileWatcher = require('../FileWatcher');
var Packager = require('../Packager');
var Bundler = require('../Bundler');
var Activity = require('../Activity');
var AssetServer = require('../AssetServer');
var Promise = require('promise');
@ -68,7 +68,7 @@ function Server(options) {
var opts = validateOpts(options);
this._projectRoots = opts.projectRoots;
this._packages = Object.create(null);
this._bundles = Object.create(null);
this._changeWatchers = [];
var assetGlobs = opts.assetExts.map(function(ext) {
@ -105,40 +105,40 @@ function Server(options) {
assetExts: opts.assetExts,
});
var packagerOpts = Object.create(opts);
packagerOpts.fileWatcher = this._fileWatcher;
packagerOpts.assetServer = this._assetServer;
this._packager = new Packager(packagerOpts);
var bundlerOpts = Object.create(opts);
bundlerOpts.fileWatcher = this._fileWatcher;
bundlerOpts.assetServer = this._assetServer;
this._bundler = new Bundler(bundlerOpts);
var onFileChange = this._onFileChange.bind(this);
this._fileWatcher.on('all', onFileChange);
var self = this;
this._debouncedFileChangeHandler = _.debounce(function(filePath) {
self._rebuildPackages(filePath);
self._rebuildBundles(filePath);
self._informChangeWatchers();
}, 50);
}
Server.prototype._onFileChange = function(type, filepath, root) {
var absPath = path.join(root, filepath);
this._packager.invalidateFile(absPath);
this._bundler.invalidateFile(absPath);
// Make sure the file watcher event runs through the system before
// we rebuild the packages.
// we rebuild the bundles.
this._debouncedFileChangeHandler(absPath);
};
Server.prototype._rebuildPackages = function() {
var buildPackage = this.buildPackage.bind(this);
var packages = this._packages;
Server.prototype._rebuildBundles = function() {
var buildBundle = this.buildBundle.bind(this);
var bundles = this._bundles;
Object.keys(packages).forEach(function(optionsJson) {
Object.keys(bundles).forEach(function(optionsJson) {
var options = JSON.parse(optionsJson);
// Wait for a previous build (if exists) to finish.
packages[optionsJson] = (packages[optionsJson] || Promise.resolve()).finally(function() {
bundles[optionsJson] = (bundles[optionsJson] || Promise.resolve()).finally(function() {
// With finally promise callback we can't change the state of the promise
// so we need to reassign the promise.
packages[optionsJson] = buildPackage(options).then(function(p) {
bundles[optionsJson] = buildBundle(options).then(function(p) {
// Make a throwaway call to getSource to cache the source string.
p.getSource({
inlineSourceMap: options.inlineSourceMap,
@ -147,7 +147,7 @@ Server.prototype._rebuildPackages = function() {
return p;
});
});
return packages[optionsJson];
return bundles[optionsJson];
});
};
@ -168,11 +168,11 @@ Server.prototype._informChangeWatchers = function() {
Server.prototype.end = function() {
Promise.all([
this._fileWatcher.end(),
this._packager.kill(),
this._bundler.kill(),
]);
};
var packageOpts = declareOpts({
var bundleOpts = declareOpts({
sourceMapUrl: {
type: 'string',
required: false,
@ -199,10 +199,10 @@ var packageOpts = declareOpts({
},
});
Server.prototype.buildPackage = function(options) {
var opts = packageOpts(options);
Server.prototype.buildBundle = function(options) {
var opts = bundleOpts(options);
return this._packager.package(
return this._bundler.bundle(
opts.entryFile,
opts.runModule,
opts.sourceMapUrl,
@ -210,13 +210,13 @@ Server.prototype.buildPackage = function(options) {
);
};
Server.prototype.buildPackageFromUrl = function(reqUrl) {
Server.prototype.buildBundleFromUrl = function(reqUrl) {
var options = getOptionsFromUrl(reqUrl);
return this.buildPackage(options);
return this.buildBundle(options);
};
Server.prototype.getDependencies = function(main) {
return this._packager.getDependencies(main);
return this._bundler.getDependencies(main);
};
Server.prototype._processDebugRequest = function(reqUrl, res) {
@ -224,13 +224,13 @@ Server.prototype._processDebugRequest = function(reqUrl, res) {
var pathname = url.parse(reqUrl).pathname;
var parts = pathname.split('/').filter(Boolean);
if (parts.length === 1) {
ret += '<div><a href="/debug/packages">Cached Packages</a></div>';
ret += '<div><a href="/debug/bundles">Cached Bundles</a></div>';
ret += '<div><a href="/debug/graph">Dependency Graph</a></div>';
res.end(ret);
} else if (parts[1] === 'packages') {
ret += '<h1> Cached Packages </h1>';
Promise.all(Object.keys(this._packages).map(function(optionsJson) {
return this._packages[optionsJson].then(function(p) {
} else if (parts[1] === 'bundles') {
ret += '<h1> Cached Bundles </h1>';
Promise.all(Object.keys(this._bundles).map(function(optionsJson) {
return this._bundles[optionsJson].then(function(p) {
ret += '<div><h2>' + optionsJson + '</h2>';
ret += p.getDebugInfo();
});
@ -244,7 +244,7 @@ Server.prototype._processDebugRequest = function(reqUrl, res) {
);
} else if (parts[1] === 'graph'){
ret += '<h1> Dependency Graph </h2>';
ret += this._packager.getGraphDebugInfo();
ret += this._bundler.getGraphDebugInfo();
res.end(ret);
} else {
res.writeHead('404');
@ -352,9 +352,9 @@ Server.prototype.processRequest = function(req, res, next) {
var startReqEventId = Activity.startEvent('request:' + req.url);
var options = getOptionsFromUrl(req.url);
var optionsJson = JSON.stringify(options);
var building = this._packages[optionsJson] || this.buildPackage(options);
var building = this._bundles[optionsJson] || this.buildBundle(options);
this._packages[optionsJson] = building;
this._bundles[optionsJson] = building;
building.then(
function(p) {
if (requestType === 'bundle') {
@ -376,7 +376,7 @@ Server.prototype.processRequest = function(req, res, next) {
).done();
};
Server.prototype._handleError = function(res, packageID, error) {
Server.prototype._handleError = function(res, bundleID, error) {
res.writeHead(error.status || 500, {
'Content-Type': 'application/json; charset=UTF-8',
});
@ -390,7 +390,7 @@ Server.prototype._handleError = function(res, packageID, error) {
res.end(JSON.stringify(error));
if (error.type === 'NotFoundError') {
delete this._packages[packageID];
delete this._bundles[bundleID];
}
} else {
console.error(error.stack || error);