From d1772eebe6a71a00a63b862f6c28c6aee32573c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 11 Aug 2015 11:04:34 -0700 Subject: [PATCH 01/44] [react-packager] Fix Cache-test --- react-packager/src/Cache/__tests__/Cache-test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/react-packager/src/Cache/__tests__/Cache-test.js b/react-packager/src/Cache/__tests__/Cache-test.js index 8172a243..f4aef914 100644 --- a/react-packager/src/Cache/__tests__/Cache-test.js +++ b/react-packager/src/Cache/__tests__/Cache-test.js @@ -52,6 +52,14 @@ describe('JSTransformer Cache', () => { }); pit('supports storing multiple fields', () => { + require('fs').stat.mockImpl((file, callback) => { + callback(null, { + mtime: { + getTime: () => {} + } + }); + }); + var cache = new Cache({ projectRoots: ['/rootDir'], transformModulePath: 'x.js', From 3f1619b158f2b2abb45a9ff3c7fe63d258159c75 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 12 Aug 2015 11:47:02 -0700 Subject: [PATCH 02/44] [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. --- react-packager/index.js | 13 +- react-packager/src/Bundler/Bundle.js | 307 ++++++++++++++++++ .../__tests__/Bundle-test.js} | 64 ++-- .../__tests__/Bundler-test.js} | 18 +- .../src/{Packager => Bundler}/base64-vlq.js | 0 react-packager/src/Bundler/index.js | 291 +++++++++++++++++ .../DependencyGraph/index.js | 1 - react-packager/src/Packager/Package.js | 300 ----------------- react-packager/src/Packager/index.js | 289 ----------------- .../src/Server/__tests__/Server-test.js | 40 +-- react-packager/src/Server/index.js | 68 ++-- 11 files changed, 702 insertions(+), 689 deletions(-) create mode 100644 react-packager/src/Bundler/Bundle.js rename react-packager/src/{Packager/__tests__/Package-test.js => Bundler/__tests__/Bundle-test.js} (82%) rename react-packager/src/{Packager/__tests__/Packager-test.js => Bundler/__tests__/Bundler-test.js} (94%) rename react-packager/src/{Packager => Bundler}/base64-vlq.js (100%) create mode 100644 react-packager/src/Bundler/index.js delete mode 100644 react-packager/src/Packager/Package.js delete mode 100644 react-packager/src/Packager/index.js diff --git a/react-packager/index.js b/react-packager/index.js index c47d762a..83f31228 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -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; diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js new file mode 100644 index 00000000..418f7a9e --- /dev/null +++ b/react-packager/src/Bundler/Bundle.js @@ -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 [ + '

Main Module:

' + this._mainModuleId + '
', + '', + '

Module paths and transformed code:

', + this._modules.map(function(m) { + return '

Path:

' + m.sourcePath + '

Source:

' + + '
'; + }).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; diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js similarity index 82% rename from react-packager/src/Packager/__tests__/Package-test.js rename to react-packager/src/Bundler/__tests__/Bundle-test.js index d43c65c0..74d18924 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -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 diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js similarity index 94% rename from react-packager/src/Packager/__tests__/Packager-test.js rename to react-packager/src/Bundler/__tests__/Bundler-test.js index 216e9009..a66bc059 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -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([ { diff --git a/react-packager/src/Packager/base64-vlq.js b/react-packager/src/Bundler/base64-vlq.js similarity index 100% rename from react-packager/src/Packager/base64-vlq.js rename to react-packager/src/Bundler/base64-vlq.js diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js new file mode 100644 index 00000000..e7ea1249 --- /dev/null +++ b/react-packager/src/Bundler/index.js @@ -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; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 7e718582..d4cced0c 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -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'); diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js deleted file mode 100644 index 6b538946..00000000 --- a/react-packager/src/Packager/Package.js +++ /dev/null @@ -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 [ - '

Main Module:

' + this._mainModuleId + '
', - '', - '

Module paths and transformed code:

', - this._modules.map(function(m) { - return '

Path:

' + m.sourcePath + '

Source:

' + - '
'; - }).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 ], - }; -} diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js deleted file mode 100644 index a718bd26..00000000 --- a/react-packager/src/Packager/index.js +++ /dev/null @@ -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; diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 7d399cb2..5461b89f 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -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', diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index fdeffc65..fcb6e1a9 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -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 += '
Cached Packages
'; + ret += '
Cached Bundles
'; ret += '
Dependency Graph
'; res.end(ret); - } else if (parts[1] === 'packages') { - ret += '

Cached Packages

'; - Promise.all(Object.keys(this._packages).map(function(optionsJson) { - return this._packages[optionsJson].then(function(p) { + } else if (parts[1] === 'bundles') { + ret += '

Cached Bundles

'; + Promise.all(Object.keys(this._bundles).map(function(optionsJson) { + return this._bundles[optionsJson].then(function(p) { ret += '

' + optionsJson + '

'; ret += p.getDebugInfo(); }); @@ -244,7 +244,7 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { ); } else if (parts[1] === 'graph'){ ret += '

Dependency Graph

'; - 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); From ce6acf15862b17fb3852a57396bd8f365778e2dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 12 Aug 2015 12:00:58 -0700 Subject: [PATCH 03/44] [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). --- .../src/DependencyResolver/AssetModule.js | 4 + .../AssetModule_DEPRECATED.js | 4 + .../src/DependencyResolver/Module.js | 76 +++++++++- .../__tests__/HasteDependencyResolver-test.js | 4 +- .../__tests__/Module-test.js | 133 ++++++++++++++++++ .../src/DependencyResolver/index.js | 3 +- .../DependencyResolver/polyfills/require.js | 69 ++------- .../src/DependencyResolver/replacePatterns.js | 1 + 8 files changed, 227 insertions(+), 67 deletions(-) create mode 100644 react-packager/src/DependencyResolver/__tests__/Module-test.js diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js index bfe4b6f8..7a45addb 100644 --- a/react-packager/src/DependencyResolver/AssetModule.js +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -14,6 +14,10 @@ class AssetModule extends Module { return Promise.resolve([]); } + getAsyncDependencies() { + return Promise.resolve([]); + } + _read() { return Promise.resolve({}); } diff --git a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js index fd4cb708..2adb73d5 100644 --- a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js +++ b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -17,6 +17,10 @@ class AssetModule_DEPRECATED extends Module { return Promise.resolve([]); } + getAsyncDependencies() { + return Promise.resolve([]); + } + getPlainObject() { const {name, resolution} = getAssetDataFromName(this.path); diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 3f1b13ef..b1fc58d6 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -1,3 +1,11 @@ +/** + * 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'); @@ -69,6 +77,10 @@ class Module { 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 => { @@ -85,7 +97,9 @@ class Module { if ('extern' in moduleDocBlock) { data.dependencies = []; } else { - data.dependencies = extractRequires(content); + var dependencies = extractRequires(content); + data.dependencies = dependencies.sync; + data.asyncDependencies = dependencies.async; } return data; @@ -124,20 +138,68 @@ class Module { /** * Extract all required modules from a `code` string. */ -var blockCommentRe = /\/\*(.|\n)*?\*\//g; -var lineCommentRe = /\/\/.+(\n|$)/g; +const blockCommentRe = /\/\*(.|\n)*?\*\//g; +const lineCommentRe = /\/\/.+(\n|$)/g; +const trailingCommaRe = /,\s*$/g; +const removeSpacesRe = /\s/g; +const quotesRe = /'/g; function extractRequires(code /*: string*/) /*: Array*/ { - var deps = []; + 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.push(dep); + deps.sync.push(dep); return match; }) - .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { - deps.push(dep); + // 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; diff --git a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index da159b5e..3242c677 100644 --- a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -479,8 +479,8 @@ describe('HasteDependencyResolver', function() { }, code).then(processedCode => { expect(processedCode).toEqual([ - '__d(\'test module\',["changed","Y"],function(global,' + - ' require, requireDynamic, requireLazy, module, exports) { ' + + '__d(\'test module\',["changed","Y"],function(global, require,' + + ' module, exports) { ' + "import'x';", "import 'changed';", "import 'changed' ;", diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js new file mode 100644 index 00000000..637e84cb --- /dev/null +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -0,0 +1,133 @@ +/** + * 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'; + +jest + .dontMock('absolute-path') + .dontMock('../fastfs') + .dontMock('../replacePatterns') + .dontMock('../DependencyGraph/docblock') + .dontMock('../../FileWatcher') + .dontMock('../Module'); + +jest + .mock('fs'); + +describe('Module', () => { + var Fastfs; + var Module; + var ModuleCache; + var Promise; + var fs; + + const FileWatcher = require('../../FileWatcher'); + const fileWatcher = new FileWatcher(['/root']); + + beforeEach(function() { + Fastfs = require('../fastfs'); + Module = require('../Module'); + ModuleCache = require('../ModuleCache'); + Promise = require('promise'); + fs = require('fs'); + }); + + describe('Async Dependencies', () => { + function expectAsyncDependenciesToEqual(expected) { + var fastfs = new Fastfs( + ['/root'], + fileWatcher, + {crawling: Promise.resolve(['/root/index.js']), ignore: []}, + ); + + return fastfs.build().then(() => { + var module = new Module('/root/index.js', fastfs, new ModuleCache(fastfs)); + + return module.getAsyncDependencies().then(actual => + expect(actual).toEqual(expected) + ); + }); + } + + pit('should recognize single dependency', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1"], function() {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1']]); + }); + + pit('should parse single quoted dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure([\'dep1\'], function() {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1']]); + }); + + pit('should recognize multiple dependencies on the same statement', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1", "dep2"], function() {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1', 'dep2']]); + }); + + pit('should group async dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + 'require.ensure(["dep1", "dep2"], function() {});', + 'require.ensure(["dep3", "dep4"], function() {});', + ].join('\n'), + } + }); + + return expectAsyncDependenciesToEqual([ + ['dep1', 'dep2'], + ['dep3', 'dep4'] + ]); + }); + + pit('shouldn\'t throw with ES6 arrow functions', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1", "dep2"], () => {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1', 'dep2']]); + }); + + pit('parse fine new lines', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1", \n"dep2"], () => {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1', 'dep2']]); + }); + + pit('ignore comments', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1", /*comment*/"dep2"], () => {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1', 'dep2']]); + }); + }); +}); diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index eae2e3da..33b9c781 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -176,8 +176,7 @@ function defineModuleCode({moduleName, code, deps}) { `__d(`, `'${moduleName}',`, `${deps},`, - 'function(global, require, ', - 'requireDynamic, requireLazy, module, exports) {', + 'function(global, require, module, exports) {', ` ${code}`, '\n});', ].join(''); diff --git a/react-packager/src/DependencyResolver/polyfills/require.js b/react-packager/src/DependencyResolver/polyfills/require.js index 04a0bff7..daedb4ea 100644 --- a/react-packager/src/DependencyResolver/polyfills/require.js +++ b/react-packager/src/DependencyResolver/polyfills/require.js @@ -303,6 +303,18 @@ return _totalFactories; }; + /** + * Asynchronously loads any missing dependency and executes the provided + * callback once all of them are satisfied. + * + * Note that the dependencies on the provided array must be string literals + * as the packager uses this information to figure out how the modules are + * packaged into different bundles. + */ + require.ensure = function(dependencies, callback) { + throw '`require.ensure` is still not supported'; + }; + /** * The define function conforming to CommonJS proposal: * http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition @@ -464,56 +476,6 @@ } } - /** - * Special version of define that executes the factory as soon as all - * dependencies are met. - * - * define() does just that, defines a module. Module's factory will not be - * called until required by other module. This makes sense for most of our - * library modules: we do not want to execute the factory unless it's being - * used by someone. - * - * On the other hand there are modules, that you can call "entrance points". - * You want to run the "factory" method for them as soon as all dependencies - * are met. - * - * @example - * - * define('BaseClass', [], function() { return ... }); - * // ^^ factory for BaseClass was just stored in modulesMap - * - * define('SubClass', ['BaseClass'], function() { ... }); - * // SubClass module is marked as ready (waiting == 0), factory is just - * // stored - * - * define('OtherClass, ['BaseClass'], function() { ... }); - * // OtherClass module is marked as ready (waiting == 0), factory is just - * // stored - * - * requireLazy(['SubClass', 'ChatConfig'], - * function() { ... }); - * // ChatRunner is waiting for ChatConfig to come - * - * define('ChatConfig', [], { foo: 'bar' }); - * // at this point ChatRunner is marked as ready, and its factory - * // executed + all dependent factories are executed too: BaseClass, - * // SubClass, ChatConfig notice that OtherClass's factory won't be - * // executed unless explicitly required by someone - * - * @param {Array} dependencies - * @param {Object|Function} factory - */ - function requireLazy(dependencies, factory, context) { - return define( - dependencies, - factory, - undefined, - REQUIRE_WHEN_READY, - context, - 1 - ); - } - function _uid() { return '__mod__' + _counter++; } @@ -595,12 +557,8 @@ _register('global', global); _register('require', require); - _register('requireDynamic', require); - _register('requireLazy', requireLazy); global.require = require; - global.requireDynamic = require; - global.requireLazy = requireLazy; require.__debug = { modules: modulesMap, @@ -621,8 +579,7 @@ * out for every module which would be a lot of extra bytes. */ global.__d = function(id, deps, factory, _special, _inlineRequires) { - var defaultDeps = ['global', 'require', 'requireDynamic', 'requireLazy', - 'module', 'exports']; + var defaultDeps = ['global', 'require', 'module', 'exports']; define(id, defaultDeps.concat(deps), factory, _special || USED_AS_TRANSPORT, null, null, _inlineRequires); }; diff --git a/react-packager/src/DependencyResolver/replacePatterns.js b/react-packager/src/DependencyResolver/replacePatterns.js index cde2d873..f683331a 100644 --- a/react-packager/src/DependencyResolver/replacePatterns.js +++ b/react-packager/src/DependencyResolver/replacePatterns.js @@ -11,3 +11,4 @@ exports.IMPORT_RE = /(\bimport\s+?(?:.+\s+?from\s+?)?)(['"])([^'"]+)(\2)/g; exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; +exports.REQUIRE_ENSURE_RE = /\brequire\.ensure\s*\(\s*(?:\[([^\]]+)\])?/g; From 1751d1c6fc6a430ca65707ca97b4f79f0ec8640f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 12 Aug 2015 19:04:54 -0700 Subject: [PATCH 04/44] [react-packager] Set a lifetime on workers to avoid memory leaks --- react-packager/src/JSTransformer/index.js | 87 +++++++++++++---------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index f7884016..52bb24ad 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -8,19 +8,16 @@ */ 'use strict'; -var fs = require('fs'); -var Promise = require('promise'); -var workerFarm = require('worker-farm'); -var declareOpts = require('../lib/declareOpts'); -var util = require('util'); -var ModuleTransport = require('../lib/ModuleTransport'); +const ModuleTransport = require('../lib/ModuleTransport'); +const Promise = require('promise'); +const declareOpts = require('../lib/declareOpts'); +const fs = require('fs'); +const util = require('util'); +const workerFarm = require('worker-farm'); -var readFile = Promise.denodeify(fs.readFile); +const readFile = Promise.denodeify(fs.readFile); -module.exports = Transformer; -Transformer.TransformError = TransformError; - -var validateOpts = declareOpts({ +const validateOpts = declareOpts({ projectRoots: { type: 'array', required: true, @@ -42,38 +39,45 @@ var validateOpts = declareOpts({ }, }); -function Transformer(options) { - var opts = validateOpts(options); +// Avoid memory leaks caused in workers. This number seems to be a good enough number +// to avoid any memory leak while not slowing down initial builds. +// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more. +const MAX_CALLS_PER_WORKER = 600; - this._cache = opts.cache; +class Transformer { + constructor(options) { + const opts = validateOpts(options); - if (options.transformModulePath != null) { - this._workers = workerFarm( - {autoStart: true, maxConcurrentCallsPerWorker: 1}, - options.transformModulePath - ); + this._cache = opts.cache; - this._transform = Promise.denodeify(this._workers); - } -} + if (opts.transformModulePath != null) { + this._workers = workerFarm({ + autoStart: true, + maxConcurrentCallsPerWorker: 1, + maxCallsPerWorker: MAX_CALLS_PER_WORKER, + }, opts.transformModulePath); -Transformer.prototype.kill = function() { - this._workers && workerFarm.end(this._workers); -}; - -Transformer.prototype.invalidateFile = function(filePath) { - this._cache.invalidate(filePath); -}; - -Transformer.prototype.loadFileAndTransform = function(filePath) { - if (this._transform == null) { - return Promise.reject(new Error('No transfrom module')); + this._transform = Promise.denodeify(this._workers); + } } - var transform = this._transform; - return this._cache.get(filePath, 'transformedSource', function() { - // TODO: use fastfs to avoid reading file from disk again - return readFile(filePath) + kill() { + this._workers && workerFarm.end(this._workers); + } + + invalidateFile(filePath) { + this._cache.invalidate(filePath); + } + + loadFileAndTransform(filePath) { + if (this._transform == null) { + return Promise.reject(new Error('No transfrom module')); + } + + var transform = this._transform; + return this._cache.get(filePath, 'transformedSource', function() { + // TODO: use fastfs to avoid reading file from disk again + return readFile(filePath) .then(function(buffer) { var sourceCode = buffer.toString(); @@ -102,8 +106,13 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { }).catch(function(err) { throw formatError(err, filePath); }); - }); -}; + }); + } +} + +module.exports = Transformer; + +Transformer.TransformError = TransformError; function TransformError() { Error.captureStackTrace && Error.captureStackTrace(this, TransformError); From 8644bfd09ca8593ddf9e1a6842782211aa1f9e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 13 Aug 2015 12:31:57 -0700 Subject: [PATCH 05/44] [react-packager] Introduce Bundler 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. --- .../__tests__/BundlesLayout-test.js | 150 +++++ .../BundlesLayoutIntegration-test.js | 512 ++++++++++++++++++ react-packager/src/BundlesLayout/index.js | 76 +++ .../DependencyGraph/index.js | 76 ++- .../src/DependencyResolver/Module.js | 2 + .../src/DependencyResolver/index.js | 18 +- 6 files changed, 799 insertions(+), 35 deletions(-) create mode 100644 react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js create mode 100644 react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js create mode 100644 react-packager/src/BundlesLayout/index.js diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js new file mode 100644 index 00000000..154a29c3 --- /dev/null +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -0,0 +1,150 @@ +/** + * 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'; + +jest + .dontMock('../index'); + +const Promise = require('promise'); + +describe('BundlesLayout', () => { + var BundlesLayout; + var DependencyResolver; + + beforeEach(() => { + BundlesLayout = require('../index'); + DependencyResolver = require('../../DependencyResolver'); + }); + + describe('generate', () => { + function newBundlesLayout() { + return new BundlesLayout({ + dependencyResolver: new DependencyResolver(), + }); + } + + function dep(path) { + return {path}; + } + + pit('should bundle sync dependencies', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js'), dep('/root/a.js')], + asyncDependencies: [], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(bundles).toEqual([ + [dep('/root/index.js'), dep('/root/a.js')], + ]) + ); + }); + + pit('should separate async dependencies into different bundle', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js')], + asyncDependencies: [['/root/a.js']], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(bundles).toEqual([ + [dep('/root/index.js')], + [dep('/root/a.js')], + ]) + ); + }); + + pit('separate async dependencies of async dependencies', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js')], + asyncDependencies: [['/root/a.js']], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [['/root/b.js']], + }); + case '/root/b.js': + return Promise.resolve({ + dependencies: [dep('/root/b.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(bundles).toEqual([ + [dep('/root/index.js')], + [dep('/root/a.js')], + [dep('/root/b.js')], + ]) + ); + }); + + pit('separate bundle sync dependencies of async ones on same bundle', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js')], + asyncDependencies: [['/root/a.js']], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js'), dep('/root/b.js')], + asyncDependencies: [], + }); + case '/root/b.js': + return Promise.resolve({ + dependencies: [dep('/root/b.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(bundles).toEqual([ + [dep('/root/index.js')], + [dep('/root/a.js'), dep('/root/b.js')], + ]) + ); + }); + }); +}); diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js new file mode 100644 index 00000000..02379ca9 --- /dev/null +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -0,0 +1,512 @@ +/** + * 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'; + +jest + .dontMock('absolute-path') + .dontMock('crypto') + .dontMock('underscore') + .dontMock('../index') + .dontMock('../../lib/getAssetDataFromName') + .dontMock('../../DependencyResolver/crawlers') + .dontMock('../../DependencyResolver/crawlers/node') + .dontMock('../../DependencyResolver/DependencyGraph/docblock') + .dontMock('../../DependencyResolver/fastfs') + .dontMock('../../DependencyResolver/replacePatterns') + .dontMock('../../DependencyResolver') + .dontMock('../../DependencyResolver/DependencyGraph') + .dontMock('../../DependencyResolver/AssetModule_DEPRECATED') + .dontMock('../../DependencyResolver/AssetModule') + .dontMock('../../DependencyResolver/Module') + .dontMock('../../DependencyResolver/Package') + .dontMock('../../DependencyResolver/ModuleCache'); + +const Promise = require('promise'); + +jest.mock('fs'); + +describe('BundlesLayout', () => { + var BundlesLayout; + var Cache; + var DependencyResolver; + var fileWatcher; + var fs; + + beforeEach(() => { + fs = require('fs'); + BundlesLayout = require('../index'); + Cache = require('../../Cache'); + DependencyResolver = require('../../DependencyResolver'); + + fileWatcher = { + on: () => this, + isWatchman: () => Promise.resolve(false) + }; + }); + + describe('generate', () => { + const polyfills = [ + 'polyfills/prelude_dev.js', + 'polyfills/prelude.js', + 'polyfills/require.js', + 'polyfills/polyfills.js', + 'polyfills/console.js', + 'polyfills/error-guard.js', + 'polyfills/String.prototype.es6.js', + 'polyfills/Array.prototype.es6.js', + ]; + + function newBundlesLayout() { + const resolver = new DependencyResolver({ + projectRoots: ['/root'], + fileWatcher: fileWatcher, + cache: new Cache(), + assetExts: ['js', 'png'], + assetRoots: ['/root'], + }); + + return new BundlesLayout({dependencyResolver: resolver}); + } + + function modulePaths(bundles) { + if (!bundles) { + return null; + } + + return bundles.map(bundle => { + return bundle + .filter(module => { // filter polyfills + for (let p of polyfills) { + if (module.id.indexOf(p) !== -1) { + return false; + } + } + return true; + }) + .map(module => module.path); + }); + } + + pit('should bundle dependant modules', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require("a");`, + 'a.js': ` + /**, + * @providesModule a + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js', '/root/a.js'], + ]) + ); + }); + + pit('should split bundles for async dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js': ` + /**, + * @providesModule a + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js'], + ]) + ); + }); + + pit('should split into multiple bundles separate async dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]); + require.ensure(["b"]);`, + 'a.js': ` + /**, + * @providesModule a + */`, + 'b.js': ` + /** + * @providesModule b + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js'], + ['/root/b.js'], + ]) + ); + }); + + pit('should put related async dependencies into the same bundle', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a", "b"]);`, + 'a.js': ` + /**, + * @providesModule a + */`, + 'b.js': ` + /** + * @providesModule b + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/b.js'], + ]) + ); + }); + + pit('should fully traverse sync dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require("a"); + require.ensure(["b"]);`, + 'a.js': ` + /**, + * @providesModule a + */`, + 'b.js': ` + /** + * @providesModule b + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js', '/root/a.js'], + ['/root/b.js'], + ]) + ); + }); + + pit('should include sync dependencies async dependencies might have', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js': ` + /**, + * @providesModule a + */, + require("b");`, + 'b.js': ` + /** + * @providesModule b + */ + require("c");`, + 'c.js': ` + /** + * @providesModule c + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/b.js', '/root/c.js'], + ]) + ); + }); + + pit('should allow duplicated dependencies across bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]); + require.ensure(["b"]);`, + 'a.js': ` + /**, + * @providesModule a + */, + require("c");`, + 'b.js': ` + /** + * @providesModule b + */ + require("c");`, + 'c.js': ` + /** + * @providesModule c + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/c.js'], + ['/root/b.js', '/root/c.js'], + ]) + ); + }); + + pit('should put in separate bundles async dependencies of async dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js': ` + /**, + * @providesModule a + */, + require.ensure(["b"]);`, + 'b.js': ` + /** + * @providesModule b + */ + require("c");`, + 'c.js': ` + /** + * @providesModule c + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js'], + ['/root/b.js', '/root/c.js'], + ]) + ); + }); + + pit('should dedup same async bundle duplicated dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a", "b"]);`, + 'a.js': ` + /**, + * @providesModule a + */, + require("c");`, + 'b.js': ` + /** + * @providesModule b + */ + require("c");`, + 'c.js': ` + /** + * @providesModule c + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/c.js', '/root/b.js'], + ]) + ); + }); + + pit('should put image dependencies into separate bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js':` + /**, + * @providesModule a + */, + require("./img.png");`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/img.png'], + ]) + ); + }); + + pit('should put image dependencies across bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]); + require.ensure(["b"]);`, + 'a.js':` + /**, + * @providesModule a + */, + require("./img.png");`, + 'b.js':` + /**, + * @providesModule b + */, + require("./img.png");`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/img.png'], + ['/root/b.js', '/root/img.png'], + ]) + ); + }); + + pit('could async require asset', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["./img.png"]);`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/img.png'], + ]) + ); + }); + + pit('should include deprecated assets into separate bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js':` + /**, + * @providesModule a + */, + require("image!img");`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/img.png'], + ]) + ); + }); + + pit('could async require deprecated asset', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["image!img"]);`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/img.png'], + ]) + ); + }); + + pit('should put packages into bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["aPackage"]);`, + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main.js': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + }, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/aPackage/client.js'], + ]) + ); + }); + }); +}); diff --git a/react-packager/src/BundlesLayout/index.js b/react-packager/src/BundlesLayout/index.js new file mode 100644 index 00000000..88e616c0 --- /dev/null +++ b/react-packager/src/BundlesLayout/index.js @@ -0,0 +1,76 @@ +/** + * 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 declareOpts = require('../lib/declareOpts'); + +const validateOpts = declareOpts({ + dependencyResolver: { + type: 'object', + required: true, + }, +}); + +/** + * Class that takes care of separating the graph of dependencies into + * separate bundles + */ +class BundlesLayout { + constructor(options) { + const opts = validateOpts(options); + this._resolver = opts.dependencyResolver; + } + + generateLayout(entryPaths, isDev) { + const bundles = []; + var pending = [entryPaths]; + + return promiseWhile( + () => pending.length > 0, + () => bundles, + () => { + const pendingPaths = pending.shift(); + return Promise + .all(pendingPaths.map(path => + this._resolver.getDependencies(path, {dev: isDev}) + )) + .then(modulesDeps => { + let syncDependencies = Object.create(null); + modulesDeps.forEach(moduleDeps => { + moduleDeps.dependencies.forEach(dep => + syncDependencies[dep.path] = dep + ); + pending = pending.concat(moduleDeps.asyncDependencies); + }); + + syncDependencies = _.values(syncDependencies); + if (syncDependencies.length > 0) { + bundles.push(syncDependencies); + } + + return Promise.resolve(bundles); + }); + }, + ); + } +} + +// Runs the body Promise meanwhile the condition callback is satisfied. +// Once it's not satisfied anymore, it returns what the results callback +// indicates +function promiseWhile(condition, result, body) { + if (!condition()) { + return Promise.resolve(result()); + } + + return body().then(() => promiseWhile(condition, result, body)); +} + +module.exports = BundlesLayout; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index d4cced0c..d2f9fcf6 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -162,33 +162,7 @@ class DependencyGraph { getOrderedDependencies(entryPath) { return this.load().then(() => { - const absPath = this._getAbsolutePath(entryPath); - - if (absPath == null) { - throw new NotFoundError( - 'Could not find source file at %s', - entryPath - ); - } - - const absolutePath = path.resolve(absPath); - - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._opts.roots - ); - } - - const platformExt = getPlatformExt(entryPath); - if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { - this._platformExt = platformExt; - } else { - this._platformExt = null; - } - - const entry = this._moduleCache.getModule(absolutePath); + const entry = this._getModuleForEntryPath(entryPath); const deps = []; const visited = Object.create(null); visited[entry.hash()] = true; @@ -225,7 +199,23 @@ class DependencyGraph { }; return collect(entry) - .then(() => Promise.all(deps.map(dep => dep.getPlainObject()))); + .then(() => Promise.all(deps.map(dep => dep.getPlainObject()))) + .then(); + }); + } + + getAsyncDependencies(entryPath) { + return this.load().then(() => { + const mod = this._getModuleForEntryPath(entryPath); + return mod.getAsyncDependencies().then(bundles => + Promise + .all(bundles.map(bundle => + Promise.all(bundle.map( + dep => this.resolveDependency(mod, dep) + )) + )) + .then(bs => bs.map(bundle => bundle.map(dep => dep.path))) + ); }); } @@ -245,6 +235,36 @@ class DependencyGraph { return null; } + _getModuleForEntryPath(entryPath) { + const absPath = this._getAbsolutePath(entryPath); + + if (absPath == null) { + throw new NotFoundError( + 'Could not find source file at %s', + entryPath + ); + } + + const absolutePath = path.resolve(absPath); + + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._opts.roots + ); + } + + const platformExt = getPlatformExt(entryPath); + if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { + this._platformExt = platformExt; + } else { + this._platformExt = null; + } + + return this._moduleCache.getModule(absolutePath); + } + _resolveHasteDependency(fromModule, toModuleName) { toModuleName = normalizePath(toModuleName); diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index b1fc58d6..3b3526b6 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -198,6 +198,8 @@ function extractRequires(code /*: string*/) /*: Array*/ { } }); + // TODO: throw error if there are duplicate dependencies + deps.async.push(dep); } }); diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index 33b9c781..c4f63c1b 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -83,22 +83,26 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph.load().then( - () => depGraph.getOrderedDependencies(main).then( - dependencies => { + return depGraph + .load() + .then(() => Promise.all([ + depGraph.getOrderedDependencies(main), + depGraph.getAsyncDependencies(main), + ])) + .then(([dependencies, asyncDependencies]) => { const mainModuleId = dependencies[0].id; self._prependPolyfillDependencies( dependencies, - opts.dev + opts.dev, ); return { mainModuleId: mainModuleId, - dependencies: dependencies + dependencies: dependencies, + asyncDependencies: asyncDependencies, }; } - ) - ); + ); }; HasteDependencyResolver.prototype._prependPolyfillDependencies = function( From a372af9956da4da9e7170e86183a573be092e1fd Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 13 Aug 2015 12:30:59 -0700 Subject: [PATCH 06/44] [react-packager] Remove horribly outdated example_project Summary: Removes some old unused code. --- react-packager/example_project/bar.js | 12 -- react-packager/example_project/config.json | 10 -- react-packager/example_project/foo/foo.js | 30 ----- react-packager/example_project/index.js | 16 --- react-packager/example_project/js/Channel.js | 53 --------- react-packager/example_project/js/XHR.js | 29 ----- react-packager/example_project/js/code.js | 58 ---------- react-packager/example_project/js/main.js | 64 ----------- .../example_project/public/css/index.css | 104 ------------------ .../example_project/public/index.html | 38 ------- 10 files changed, 414 deletions(-) delete mode 100644 react-packager/example_project/bar.js delete mode 100644 react-packager/example_project/config.json delete mode 100644 react-packager/example_project/foo/foo.js delete mode 100644 react-packager/example_project/index.js delete mode 100644 react-packager/example_project/js/Channel.js delete mode 100644 react-packager/example_project/js/XHR.js delete mode 100644 react-packager/example_project/js/code.js delete mode 100644 react-packager/example_project/js/main.js delete mode 100644 react-packager/example_project/public/css/index.css delete mode 100644 react-packager/example_project/public/index.html diff --git a/react-packager/example_project/bar.js b/react-packager/example_project/bar.js deleted file mode 100644 index 6653bdf7..00000000 --- a/react-packager/example_project/bar.js +++ /dev/null @@ -1,12 +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. - * - * @providesModule bar - */ - - module.exports = setInterval; diff --git a/react-packager/example_project/config.json b/react-packager/example_project/config.json deleted file mode 100644 index 0acdcb51..00000000 --- a/react-packager/example_project/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "port": 3000, - "devPort": 3001, - "publicDir": "./public", - "rootPath": "../example_project", - "moduleOptions": { - "format": "haste", - "main": "index.js" - } -} diff --git a/react-packager/example_project/foo/foo.js b/react-packager/example_project/foo/foo.js deleted file mode 100644 index fe3c8cd1..00000000 --- a/react-packager/example_project/foo/foo.js +++ /dev/null @@ -1,30 +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. - * - * @providesModule foo - */ - - -var bar = require('bar'); - -class Logger { - log() { - console.log('youll have to change me lol'); - } -} - -class SecretLogger extends Logger { - log(secret) { - console.log('logging ', secret); - } -} - -module.exports = (secret) => { - if (secret !== 'secret') throw new Error('wrong secret'); - bar(new SecretLogger().log.bind(SecretLogger, secret), 400); -}; diff --git a/react-packager/example_project/index.js b/react-packager/example_project/index.js deleted file mode 100644 index d63b5193..00000000 --- a/react-packager/example_project/index.js +++ /dev/null @@ -1,16 +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. - * - * @providesModule index - */ - -require('main'); -require('code'); - -var foo = require('foo'); -foo('secret'); diff --git a/react-packager/example_project/js/Channel.js b/react-packager/example_project/js/Channel.js deleted file mode 100644 index 6cbfce6f..00000000 --- a/react-packager/example_project/js/Channel.js +++ /dev/null @@ -1,53 +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. - * - * @providesModule Channel - */ - -var XHR = require('XHR'); - -/** - * Client implementation of a server-push channel. - * - * @see Channel.js for full documentation - */ -var channel = null, at = null, delay = 0; -var Channel = {}; - -Channel.connect = function() { - var url = '/pull'; - if (channel) { - url += '?channel=' + channel + '&at=' + at; - } - XHR.get(url, function(err, xhr) { - if (err) { - delay = Math.min(Math.max(1000, delay * 2), 30000); - } else { - var res = xhr.responseText; - res = JSON.parse(res); - - delay = 0; - - // Cache channel state - channel = res.channel; - at = res.at; - - var messages = res.messages; - messages.forEach(function(message) { - var ev = document.createEvent('CustomEvent'); - ev.initCustomEvent(message.event, true, true, message.detail); - window.dispatchEvent(ev); - }); - } - - // Reconnect - setTimeout(Channel.connect, delay); - }); -}; - -module.exports = Channel; diff --git a/react-packager/example_project/js/XHR.js b/react-packager/example_project/js/XHR.js deleted file mode 100644 index bede8ca5..00000000 --- a/react-packager/example_project/js/XHR.js +++ /dev/null @@ -1,29 +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. - * - * @providesModule XHR - */ - -function request(method, url, callback) { - var xhr = new XMLHttpRequest(); - xhr.open(method, url); - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - callback(null, xhr); - } else { - callback(new Error('status = ' + xhr.status, xhr)); - } - } - }; - xhr.send(); -} - -exports.get = function(url, callback) { - request('GET', url, callback); -}; diff --git a/react-packager/example_project/js/code.js b/react-packager/example_project/js/code.js deleted file mode 100644 index f99a90c9..00000000 --- a/react-packager/example_project/js/code.js +++ /dev/null @@ -1,58 +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. - * - * @providesModule code - */ -var XHR = require('XHR'); - -var $ = function(sel) {return document.querySelector(sel);}; - -function getListItems(files) { - var items = []; - files.forEach(function(file) { - var displayName = file.name + (file.type == 1 ? '/' : ''); - items.push( - React.DOM.li({ - className: 'type' + file.type, - key: file.ino - }, displayName) - ); - if (file.type === 1) { - items.push(getListItems(file.nodes)); - } - }); - - return React.DOM.ol(null, items); -} - -var FileList = React.createClass({ - getInitialState: function() { - return {files: []}; - }, - - componentDidMount: function() { - XHR.get( - this.props.source, - function(err, xhr) { - if (err) {throw err;} - - var files = JSON.parse(xhr.responseText); - this.setState({files: files}); - }.bind(this) - ); - }, - - render: function() { - return getListItems(this.state.files); - } -}); - -window.addEventListener('load', function() { - React.render(React.createElement(FileList, {source: '/files'}), - $('#code')); -}); diff --git a/react-packager/example_project/js/main.js b/react-packager/example_project/js/main.js deleted file mode 100644 index 405d015e..00000000 --- a/react-packager/example_project/js/main.js +++ /dev/null @@ -1,64 +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. - * - * @providesModule main - */ -var Channel = require('Channel'); - -function toArray(arr) {return Array.prototype.slice.apply(arr);} -function $(sel) {return document.querySelector(sel);} -function $$(sel) {return toArray(document.querySelectorAll(sel));} - -window.addEventListener('load', function() { - function channelLog() { - var args = Array.prototype.slice.apply(arguments); - var ts = new Date(); - var el = document.createElement('li'); - args.unshift(ts.getHours() + ':' + - ('0' + ts.getMinutes()).substr(0,2) + ':' + - ('0' + ts.getSeconds()).substr(0,2)); - el.className = 'console-entry'; - el.innerHTML = args.join(' '); - $('#console').appendChild(el); - el.scrollIntoView(); - } - - global.addEventListener('ChannelInit', function(event) { - $('#console').innerHTML = ''; - channelLog(event.type); - }); - - global.addEventListener('ChannelLog', function(event) { - channelLog.apply(null, event.detail); - }); - - // Tab pane support - function showTab(paneId) { - paneId = paneId.replace(/\W/g, ''); - if (paneId) { - $$('#nav-panes > div').forEach(function(pane) { - pane.classList.toggle('active', pane.id === paneId); - }); - $$('#nav-tabs li').forEach(function(tab) { - tab.classList.toggle('active', - tab.getAttribute('data-pane') === paneId); - }); - global.history.replaceState(null, null, '#' + paneId); - } - } - - $('#nav-tabs').onclick = function(e) { - showTab(e.target.getAttribute('data-pane')); - }; - - // Show current pane - showTab(location.hash); - - // Connect to server-push channel - Channel.connect(); -}); diff --git a/react-packager/example_project/public/css/index.css b/react-packager/example_project/public/css/index.css deleted file mode 100644 index 651f3326..00000000 --- a/react-packager/example_project/public/css/index.css +++ /dev/null @@ -1,104 +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. - */ - - -html { - font-family: sans-serif; -} -body { - margin-right: 200px -} - -#nav-tabs { - margin: 0; - padding: 0; - position: absolute; - top: 0px; - left: 0px; - right: 0px; - background-color: #eee; - border-bottom: solid 1px black; - font-size: 10pt; - font-weight: bold; - vertical-align: bottom; - line-height: 20px; - height: 29px; -} -#nav-tabs li { - padding: 0 10px; - margin: 0; - border-bottom-width: 0; - display:inline-block; - cursor: pointer; - line-height: 29px; -} -#nav-tabs li:first-child { - color: #666; -} -#nav-tabs li.active { - background-color: #fff; -} - -#nav-panes { - position: absolute; - top: 30px; - left: 0px; - right: 0px; - bottom: 0px; - scroll: auto; - overflow: auto; - background-color: #fff; -} - -#nav-panes .pane { - display: none; -} -#nav-panes .active { - display: block; -} - -.pane { - padding: 10px; -} - -#console { - padding-left: 5px; -} -#console li { - font-size: 10pt; - font-family: monospace; - white-space: nowrap; - margin: 0; - list-style: none; -} - -#code > ol { - font-size: 10pt; - font-family: monospace; - margin: 0; - padding: 0; - cursor: pointer; -} -#code ol ol { - margin-left: 1em; - padding-left: 1em; - border-left: dashed 1px #ddd; -} -#code li { - color: #000; - font-weight: normal; - list-style: none; - line-height: 1.2em; -} -#code .type1 { - color: #009; -} -#code .type2 { - color: #909; -} diff --git a/react-packager/example_project/public/index.html b/react-packager/example_project/public/index.html deleted file mode 100644 index e0e2ce7f..00000000 --- a/react-packager/example_project/public/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - From 7664d5fb763115687fb1f7c3765860fd7c1c9bd3 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 13 Aug 2015 15:30:02 -0700 Subject: [PATCH 07/44] [react-packager] Use module objects across the codebase (rid of getPlainObject etc) Summary: Instead of using plain objects and having to convert to and from them we just use the `Module` class across the codebase. This seems cleaner and can enforce the type as opposed to fuzzy objects. --- .../src/Bundler/__tests__/Bundler-test.js | 79 ++++---- react-packager/src/Bundler/index.js | 45 ++--- .../BundlesLayoutIntegration-test.js | 28 +-- .../src/DependencyResolver/AssetModule.js | 35 ++-- .../AssetModule_DEPRECATED.js | 37 ++-- .../__tests__/DependencyGraph-test.js | 152 +++++++------- .../DependencyGraph/index.js | 7 +- .../src/DependencyResolver/Module.js | 33 ++-- .../src/DependencyResolver/Polyfill.js | 38 ++++ .../__tests__/HasteDependencyResolver-test.js | 185 +++--------------- .../src/DependencyResolver/index.js | 67 ++++--- 11 files changed, 310 insertions(+), 396 deletions(-) create mode 100644 react-packager/src/DependencyResolver/Polyfill.js diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index a66bc059..20bb1d4a 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -58,29 +58,50 @@ describe('Bundler', function() { assetServer: assetServer, }); + + function createModule({ + path, + id, + dependencies, + isAsset, + isAsset_DEPRECATED, + isJSON, + resolution, + }) { + return { + path, + resolution, + getDependencies() { return Promise.resolve(dependencies); }, + getName() { return Promise.resolve(id); }, + isJSON() { return isJSON; }, + isAsset() { return isAsset; }, + isAsset_DEPRECATED() { return isAsset_DEPRECATED; }, + }; + } + modules = [ - {id: 'foo', path: '/root/foo.js', dependencies: []}, - {id: 'bar', path: '/root/bar.js', dependencies: []}, - { - id: 'image!img', + createModule({id: 'foo', path: '/root/foo.js', dependencies: []}), + createModule({id: 'bar', path: '/root/bar.js', dependencies: []}), + createModule({ path: '/root/img/img.png', + id: 'image!img', isAsset_DEPRECATED: true, dependencies: [], resolution: 2, - }, - { + }), + createModule({ id: 'new_image.png', path: '/root/img/new_image.png', isAsset: true, resolution: 2, dependencies: [] - }, - { + }), + createModule({ id: 'package/file.json', path: '/root/file.json', isJSON: true, dependencies: [], - }, + }), ]; getDependencies.mockImpl(function() { @@ -203,41 +224,11 @@ describe('Bundler', function() { }); }); - pit('gets the list of dependencies', function() { + pit('gets the list of dependencies from the resolver', function() { return bundler.getDependencies('/root/foo.js', true) - .then(({dependencies}) => { - expect(dependencies).toEqual([ - { - dependencies: [], - id: 'foo', - path: '/root/foo.js', - }, - { - dependencies: [], - id: 'bar', - path: '/root/bar.js', - }, - { - dependencies: [], - id: 'image!img', - isAsset_DEPRECATED: true, - path: '/root/img/img.png', - resolution: 2, - }, - { - dependencies: [], - id: 'new_image.png', - isAsset: true, - path: '/root/img/new_image.png', - resolution: 2, - }, - { - dependencies: [], - id: 'package/file.json', - isJSON: true, - path: '/root/file.json', - }, - ]); - }); + .then( + () => expect(getDependencies) + .toBeCalledWith('/root/foo.js', { dev: true }) + ); }); }); diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index e7ea1249..4c60924b 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -125,26 +125,24 @@ class Bundler { const findEventId = Activity.startEvent('find dependencies'); let transformEventId; - return this.getDependencies(main, isDev) - .then(function(result) { - Activity.endEvent(findEventId); - transformEventId = Activity.startEvent('transform'); + return this.getDependencies(main, isDev).then((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); + bundle.setMainModuleId(result.mainModuleId); + return Promise.all( + result.dependencies.map(transformModule) + ); + }).then((transformedModules) => { + Activity.endEvent(transformEventId); - transformedModules.forEach(function(moduleTransport) { - bundle.addModule(moduleTransport); - }); + transformedModules.forEach(function(moduleTransport) { + bundle.addModule(moduleTransport); + }); - bundle.finalize({ runMainModule: runModule }); - return bundle; - }); + bundle.finalize({ runMainModule: runModule }); + return bundle; + }); } invalidateFile(filePath) { @@ -158,11 +156,11 @@ class Bundler { _transformModule(bundle, module) { let transform; - if (module.isAsset_DEPRECATED) { + if (module.isAsset_DEPRECATED()) { transform = this.generateAssetModule_DEPRECATED(bundle, module); - } else if (module.isAsset) { + } else if (module.isAsset()) { transform = this.generateAssetModule(bundle, module); - } else if (module.isJSON) { + } else if (module.isJSON()) { transform = generateJSONModule(module); } else { transform = this._transformer.loadFileAndTransform( @@ -189,12 +187,15 @@ class Bundler { } generateAssetModule_DEPRECATED(bundle, module) { - return sizeOf(module.path).then(function(dimensions) { + return Promise.all([ + sizeOf(module.path), + module.getName(), + ]).then(([dimensions, id]) => { const img = { __packager_asset: true, isStatic: true, path: module.path, - uri: module.id.replace(/^[^!]+!/, ''), + uri: id.replace(/^[^!]+!/, ''), width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, deprecated: true, diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 02379ca9..672829c2 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -25,6 +25,7 @@ jest .dontMock('../../DependencyResolver/AssetModule') .dontMock('../../DependencyResolver/Module') .dontMock('../../DependencyResolver/Package') + .dontMock('../../DependencyResolver/Polyfill') .dontMock('../../DependencyResolver/ModuleCache'); const Promise = require('promise'); @@ -51,17 +52,6 @@ describe('BundlesLayout', () => { }); describe('generate', () => { - const polyfills = [ - 'polyfills/prelude_dev.js', - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/String.prototype.es6.js', - 'polyfills/Array.prototype.es6.js', - ]; - function newBundlesLayout() { const resolver = new DependencyResolver({ projectRoots: ['/root'], @@ -79,18 +69,10 @@ describe('BundlesLayout', () => { return null; } - return bundles.map(bundle => { - return bundle - .filter(module => { // filter polyfills - for (let p of polyfills) { - if (module.id.indexOf(p) !== -1) { - return false; - } - } - return true; - }) - .map(module => module.path); - }); + return bundles.map( + bundle => bundle.filter(module => !module.isPolyfill()) + .map(module => module.path) + ); } pit('should bundle dependant modules', () => { diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js index 7a45addb..2e103159 100644 --- a/react-packager/src/DependencyResolver/AssetModule.js +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -5,6 +5,13 @@ const Promise = require('promise'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); class AssetModule extends Module { + constructor(...args) { + super(...args); + const { resolution, name, type } = getAssetDataFromName(this.path); + this.resolution = resolution; + this._name = name; + this._type = type; + } isHaste() { return Promise.resolve(false); @@ -23,28 +30,22 @@ class AssetModule extends Module { } getName() { - return super.getName().then(id => { - const {name, type} = getAssetDataFromName(this.path); - return id.replace(/\/[^\/]+$/, `/${name}.${type}`); - }); - } - - getPlainObject() { - return this.getName().then(name => this.addReference({ - path: this.path, - isJSON: false, - isAsset: true, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: getAssetDataFromName(this.path).resolution, - id: name, - dependencies: [], - })); + return super.getName().then( + id => id.replace(/\/[^\/]+$/, `/${this._name}.${this._type}`) + ); } hash() { return `AssetModule : ${this.path}`; } + + isJSON() { + return false; + } + + isAsset() { + return true; + } } module.exports = AssetModule; diff --git a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js index 2adb73d5..19817d4b 100644 --- a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js +++ b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -5,12 +5,19 @@ const Promise = require('promise'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); class AssetModule_DEPRECATED extends Module { + constructor(...args) { + super(...args); + const {resolution, name} = getAssetDataFromName(this.path); + this.resolution = resolution; + this.name = name; + } + isHaste() { return Promise.resolve(false); } getName() { - return Promise.resolve(this.name); + return Promise.resolve(`image!${this.name}`); } getDependencies() { @@ -21,24 +28,22 @@ class AssetModule_DEPRECATED extends Module { return Promise.resolve([]); } - getPlainObject() { - const {name, resolution} = getAssetDataFromName(this.path); - - return Promise.resolve(this.addReference({ - path: this.path, - id: `image!${name}`, - resolution, - isAsset_DEPRECATED: true, - dependencies: [], - isJSON: false, - isPolyfill: false, - isAsset: false, - })); - } - hash() { return `AssetModule_DEPRECATED : ${this.path}`; } + + isJSON() { + return false; + } + + isAsset_DEPRECATED() { + return true; + } + + resolution() { + return getAssetDataFromName(this.path).resolution; + } + } module.exports = AssetModule_DEPRECATED; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 595b1f7c..bf4f6596 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -35,6 +35,24 @@ describe('DependencyGraph', function() { var fileWatcher; var fs; + function getOrderedDependenciesAsJSON(dgraph, entry) { + return dgraph.getOrderedDependencies(entry).then( + deps => Promise.all(deps.map(dep => Promise.all([ + dep.getName(), + dep.getDependencies(), + ]).then(([name, dependencies]) => ({ + path: dep.path, + isJSON: dep.isJSON(), + isAsset: dep.isAsset(), + isAsset_DEPRECATED: dep.isAsset_DEPRECATED(), + isPolyfill: dep.isPolyfill(), + resolution: dep.resolution, + id: name, + dependencies + }))) + )); + } + beforeEach(function() { fs = require('fs'); Cache = require('../../../Cache'); @@ -75,7 +93,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -133,7 +151,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -185,7 +203,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -245,7 +263,7 @@ describe('DependencyGraph', function() { assetRoots_DEPRECATED: ['/root/imgs'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -297,7 +315,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -354,7 +372,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -432,7 +450,7 @@ describe('DependencyGraph', function() { assetRoots_DEPRECATED: ['/root/imgs'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -494,7 +512,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -547,7 +565,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -600,7 +618,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -661,7 +679,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -718,7 +736,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -769,7 +787,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -819,7 +837,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -866,7 +884,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -917,7 +935,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -966,7 +984,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1020,7 +1038,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/somedir/somefile.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1078,7 +1096,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1126,7 +1144,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1173,7 +1191,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1232,7 +1250,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1291,7 +1309,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1370,7 +1388,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1427,7 +1445,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1484,7 +1502,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1541,7 +1559,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1613,7 +1631,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { id: 'index', @@ -1716,7 +1734,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { id: 'index', @@ -1797,7 +1815,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1879,7 +1897,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1962,7 +1980,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2059,7 +2077,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2145,7 +2163,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2250,7 +2268,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2319,7 +2337,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/react-tools/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/react-tools/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2376,7 +2394,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2421,7 +2439,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2482,7 +2500,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2538,7 +2556,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2587,7 +2605,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2671,11 +2689,11 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2736,11 +2754,11 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2801,10 +2819,10 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2865,7 +2883,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -2877,7 +2895,7 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2948,7 +2966,7 @@ describe('DependencyGraph', function() { cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2967,7 +2985,7 @@ describe('DependencyGraph', function() { filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { expect(deps2) .toEqual([ { @@ -3020,7 +3038,7 @@ describe('DependencyGraph', function() { cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { id: 'index', @@ -3038,7 +3056,7 @@ describe('DependencyGraph', function() { filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { expect(deps2) .toEqual([ { @@ -3107,7 +3125,7 @@ describe('DependencyGraph', function() { }, cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -3119,7 +3137,7 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3192,11 +3210,11 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3263,7 +3281,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); triggerFileChange('change', 'index.js', root, mockStat); @@ -3273,7 +3291,7 @@ describe('DependencyGraph', function() { }); triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3330,7 +3348,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ name: 'aPackage', main: 'main.js', @@ -3338,7 +3356,7 @@ describe('DependencyGraph', function() { }); triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3395,14 +3413,14 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ name: 'bPackage', main: 'main.js', }); triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3458,7 +3476,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3499,7 +3517,7 @@ describe('DependencyGraph', function() { filesystem.root.node_modules.foo['main.js'] = 'lol'; triggerFileChange('change', 'main.js', '/root/node_modules/foo', mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { expect(deps2) .toEqual([ { @@ -3558,7 +3576,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ name: 'foo', main: 'main.js', @@ -3566,7 +3584,7 @@ describe('DependencyGraph', function() { }); triggerFileChange('change', 'package.json', '/root/node_modules/foo', mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { expect(deps2) .toEqual([ { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index d2f9fcf6..3825507c 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -110,10 +110,6 @@ class DependencyGraph { } resolveDependency(fromModule, toModuleName) { - if (fromModule._ref) { - fromModule = fromModule._ref; - } - const resHash = resolutionHash(fromModule.path, toModuleName); if (this._immediateResolutionCache[resHash]) { @@ -199,8 +195,7 @@ class DependencyGraph { }; return collect(entry) - .then(() => Promise.all(deps.map(dep => dep.getPlainObject()))) - .then(); + .then(() => deps); }); } diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 3b3526b6..f7ae802a 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -109,29 +109,24 @@ class Module { 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; + isJSON() { + return path.extname(this.path) === '.json'; + } + + isAsset() { + return false; + } + + isPolyfill() { + return false; + } + + isAsset_DEPRECATED() { + return false; } } diff --git a/react-packager/src/DependencyResolver/Polyfill.js b/react-packager/src/DependencyResolver/Polyfill.js new file mode 100644 index 00000000..752b864b --- /dev/null +++ b/react-packager/src/DependencyResolver/Polyfill.js @@ -0,0 +1,38 @@ +'use strict'; + +const Promise = require('promise'); +const Module = require('./Module'); + +class Polyfill extends Module { + constructor({ path, id, dependencies }) { + super(path); + this._id = id; + this._dependencies = dependencies; + } + + isHaste() { + return Promise.resolve(false); + } + + getName() { + return Promise.resolve(this._id); + } + + getPackage() { + return null; + } + + getDependencies() { + return Promise.resolve(this._dependencies); + } + + isJSON() { + return false; + } + + isPolyfill() { + return true; + } +} + +module.exports = Polyfill; diff --git a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index 3242c677..be71b3a0 100644 --- a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -9,22 +9,24 @@ 'use strict'; jest.dontMock('../') - .dontMock('q') + .dontMock('underscore') .dontMock('../replacePatterns'); jest.mock('path'); var Promise = require('promise'); +var _ = require('underscore'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; - - function createModule(o) { - o.getPlainObject = () => Promise.resolve(o); - return o; - } + var Module; + var Polyfill; beforeEach(function() { + Module = require('../Module'); + Polyfill = require('../Polyfill'); + Polyfill.mockClear(); + // For the polyfillDeps require('path').join.mockImpl(function(a, b) { return b; @@ -32,12 +34,16 @@ describe('HasteDependencyResolver', function() { HasteDependencyResolver = require('../'); }); + function createModule(id, dependencies) { + var module = new Module(); + module.getName.mockImpl(() => Promise.resolve(id)); + module.getDependencies.mockImpl(() => Promise.resolve(dependencies)); + return module; + } + describe('getDependencies', function() { pit('should get dependencies with polyfills', function() { - var module = createModule({ - id: 'index', - path: '/root/index.js', dependencies: ['a'] - }); + var module = createModule('index'); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -56,7 +62,8 @@ describe('HasteDependencyResolver', function() { return depResolver.getDependencies('/root/index.js', { dev: false }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); - expect(result.dependencies).toEqual([ + expect(result.dependencies[result.dependencies.length - 1]).toBe(module); + expect(_.pluck(Polyfill.mock.calls, 0)).toEqual([ { path: 'polyfills/prelude.js', id: 'polyfills/prelude.js', isPolyfill: true, @@ -114,18 +121,12 @@ describe('HasteDependencyResolver', function() { 'polyfills/String.prototype.es6.js', ], }, - module ]); }); }); pit('should get dependencies with polyfills', function() { - var module = createModule({ - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - }); - + var module = createModule('index'); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -144,75 +145,15 @@ describe('HasteDependencyResolver', function() { return depResolver.getDependencies('/root/index.js', { dev: true }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); - expect(result.dependencies).toEqual([ - { path: 'polyfills/prelude_dev.js', - id: 'polyfills/prelude_dev.js', - isPolyfill: true, - dependencies: [] - }, - { path: 'polyfills/require.js', - id: 'polyfills/require.js', - isPolyfill: true, - dependencies: ['polyfills/prelude_dev.js'] - }, - { path: 'polyfills/polyfills.js', - id: 'polyfills/polyfills.js', - isPolyfill: true, - dependencies: ['polyfills/prelude_dev.js', 'polyfills/require.js'] - }, - { id: 'polyfills/console.js', - isPolyfill: true, - path: 'polyfills/console.js', - dependencies: [ - 'polyfills/prelude_dev.js', - 'polyfills/require.js', - 'polyfills/polyfills.js' - ], - }, - { id: 'polyfills/error-guard.js', - isPolyfill: true, - path: 'polyfills/error-guard.js', - dependencies: [ - 'polyfills/prelude_dev.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js' - ], - }, - { id: 'polyfills/String.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/String.prototype.es6.js', - dependencies: [ - 'polyfills/prelude_dev.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js' - ], - }, - { id: 'polyfills/Array.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/Array.prototype.es6.js', - dependencies: [ - 'polyfills/prelude_dev.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/String.prototype.es6.js' - ], - }, - module - ]); + expect(depGraph.getOrderedDependencies).toBeCalledWith('/root/index.js'); + expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]); + expect(result.dependencies[result.dependencies.length - 1]) + .toBe(module); }); }); pit('should pass in more polyfills', function() { - var module = createModule({ - id: 'index', - path: '/root/index.js', - dependencies: ['a'] - }); + var module = createModule('index'); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -230,66 +171,9 @@ describe('HasteDependencyResolver', function() { }); return depResolver.getDependencies('/root/index.js', { dev: false }) - .then(function(result) { + .then((result) => { expect(result.mainModuleId).toEqual('index'); - expect(result.dependencies).toEqual([ - { path: 'polyfills/prelude.js', - id: 'polyfills/prelude.js', - isPolyfill: true, - dependencies: [] - }, - { path: 'polyfills/require.js', - id: 'polyfills/require.js', - isPolyfill: true, - dependencies: ['polyfills/prelude.js'] - }, - { path: 'polyfills/polyfills.js', - id: 'polyfills/polyfills.js', - isPolyfill: true, - dependencies: ['polyfills/prelude.js', 'polyfills/require.js'] - }, - { id: 'polyfills/console.js', - isPolyfill: true, - path: 'polyfills/console.js', - dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js' - ], - }, - { id: 'polyfills/error-guard.js', - isPolyfill: true, - path: 'polyfills/error-guard.js', - dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js' - ], - }, - { id: 'polyfills/String.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/String.prototype.es6.js', - dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js' - ], - }, - { id: 'polyfills/Array.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/Array.prototype.es6.js', - dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/String.prototype.es6.js', - ], - }, + expect(Polyfill.mock.calls[result.dependencies.length - 2]).toEqual([ { path: 'some module', id: 'some module', isPolyfill: true, @@ -303,7 +187,6 @@ describe('HasteDependencyResolver', function() { 'polyfills/Array.prototype.es6.js' ] }, - module ]); }); }); @@ -462,22 +345,18 @@ describe('HasteDependencyResolver', function() { depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { if (toModuleName === 'x') { - return Promise.resolve(createModule({ - id: 'changed' - })); + return Promise.resolve(createModule('changed')); } else if (toModuleName === 'y') { - return Promise.resolve(createModule({ id: 'Y' })); + return Promise.resolve(createModule('Y')); } return Promise.resolve(null); }); - return depResolver.wrapModule({ - id: 'test module', - path: '/root/test.js', - dependencies: dependencies - }, code).then(processedCode => { - + return depResolver.wrapModule( + createModule('test module', ['x', 'y']), + code + ).then(processedCode => { expect(processedCode).toEqual([ '__d(\'test module\',["changed","Y"],function(global, require,' + ' module, exports) { ' + diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index c4f63c1b..3fdb19fb 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -11,6 +11,7 @@ var path = require('path'); var DependencyGraph = require('./DependencyGraph'); var replacePatterns = require('./replacePatterns'); +var Polyfill = require('./Polyfill'); var declareOpts = require('../lib/declareOpts'); var Promise = require('promise'); @@ -83,25 +84,28 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; + return depGraph .load() .then(() => Promise.all([ depGraph.getOrderedDependencies(main), depGraph.getAsyncDependencies(main), ])) - .then(([dependencies, asyncDependencies]) => { - const mainModuleId = dependencies[0].id; - self._prependPolyfillDependencies( - dependencies, - opts.dev, - ); + .then( + ([dependencies, asyncDependencies]) => dependencies[0].getName().then( + mainModuleId => { + self._prependPolyfillDependencies( + dependencies, + opts.dev, + ); - return { - mainModuleId: mainModuleId, - dependencies: dependencies, - asyncDependencies: asyncDependencies, - }; - } + return { + mainModuleId, + dependencies, + asyncDependencies, + }; + } + ) ); }; @@ -122,7 +126,7 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function( ].concat(this._polyfillModuleNames); var polyfillModules = polyfillModuleNames.map( - (polyfillModuleName, idx) => ({ + (polyfillModuleName, idx) => new Polyfill({ path: polyfillModuleName, id: polyfillModuleName, dependencies: polyfillModuleNames.slice(0, idx), @@ -134,23 +138,26 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function( }; HasteDependencyResolver.prototype.wrapModule = function(module, code) { - if (module.isPolyfill) { + if (module.isPolyfill()) { return Promise.resolve(code); } const resolvedDeps = Object.create(null); const resolvedDepsArr = []; - return Promise.all( - module.dependencies.map(depName => { - return this._depGraph.resolveDependency(module, depName) - .then((dep) => dep && dep.getPlainObject().then(mod => { - if (mod) { - resolvedDeps[depName] = mod.id; - resolvedDepsArr.push(mod.id); - } - })); - }) + return module.getDependencies().then( + dependencies => Promise.all(dependencies.map( + depName => this._depGraph.resolveDependency(module, depName) + .then(depModule => { + if (depModule) { + return depModule.getName().then(name => { + resolvedDeps[depName] = name; + resolvedDepsArr.push(name); + }); + } + }) + ) + ) ).then(() => { const relativizeCode = (codeMatch, pre, quot, depName, post) => { const depId = resolvedDeps[depName]; @@ -161,13 +168,15 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { } }; - return defineModuleCode({ - code: code + return module.getName().then( + name => defineModuleCode({ + code: code .replace(replacePatterns.IMPORT_RE, relativizeCode) .replace(replacePatterns.REQUIRE_RE, relativizeCode), - deps: JSON.stringify(resolvedDepsArr), - moduleName: module.id, - }); + deps: JSON.stringify(resolvedDepsArr), + moduleName: name, + }) + ); }); }; From c3be08e66b1552f69c7892e0366da3b61a077bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 14 Aug 2015 12:08:05 -0700 Subject: [PATCH 08/44] [react-packager] Modernize `Server-test` by using ES6 features --- .../src/Server/__tests__/Server-test.js | 171 ++++++++---------- 1 file changed, 72 insertions(+), 99 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 5461b89f..1677776d 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -8,69 +8,54 @@ */ 'use strict'; -jest.setMock('worker-farm', function() { return function() {}; }) +jest.setMock('worker-farm', function() { return () => {}; }) .dontMock('os') .dontMock('path') .dontMock('url') - .setMock('timers', { - setImmediate: function(fn) { - return setTimeout(fn, 0); - } - }) + .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) }) .setMock('uglify-js') .dontMock('../'); -var Promise = require('promise'); +const Promise = require('promise'); -describe('processRequest', function() { +describe('processRequest', () => { var server; var Bundler; var FileWatcher; - var options = { + const options = { projectRoots: ['root'], blacklistRE: null, cacheVersion: null, polyfillModuleNames: null }; - var makeRequest = function(requestHandler, requrl) { - return new Promise(function(resolve) { - requestHandler( - { url: requrl }, - { - setHeader: jest.genMockFunction(), - end: function(res) { - resolve(res); - } - }, - { - next: function() {} - } - ); - }); - }; + const makeRequest = (reqHandler, requrl) => new Promise(resolve => + reqHandler( + { url: requrl }, + { + setHeader: jest.genMockFunction(), + end: res => resolve(res), + }, + { next: () => {} }, + ) + ); - var invalidatorFunc = jest.genMockFunction(); - var watcherFunc = jest.genMockFunction(); + const invalidatorFunc = jest.genMockFunction(); + const watcherFunc = jest.genMockFunction(); var requestHandler; var triggerFileChange; - beforeEach(function() { + beforeEach(() => { Bundler = require('../../Bundler'); FileWatcher = require('../../FileWatcher'); - Bundler.prototype.bundle = jest.genMockFunction().mockImpl(function() { - return Promise.resolve({ - getSource: function() { - return 'this is the source'; - }, - getSourceMap: function() { - return 'this is the source map'; - }, - }); - }); - + Bundler.prototype.bundle = jest.genMockFunction().mockImpl(() => + Promise.resolve({ + getSource: () => 'this is the source', + getSourceMap: () => 'this is the source map', + }) + ); FileWatcher.prototype.on = function(eventType, callback) { if (eventType !== 'all') { @@ -83,43 +68,43 @@ describe('processRequest', function() { Bundler.prototype.invalidateFile = invalidatorFunc; - var Server = require('../'); + const Server = require('../'); server = new Server(options); requestHandler = server.processRequest.bind(server); }); - pit('returns JS bundle source on request of *.bundle',function() { + pit('returns JS bundle source on request of *.bundle', () => { return makeRequest( requestHandler, 'mybundle.bundle?runModule=true' - ).then(function(response) { - expect(response).toEqual('this is the source'); - }); + ).then(response => + expect(response).toEqual('this is the source') + ); }); - pit('returns JS bundle source on request of *.bundle (compat)',function() { + pit('returns JS bundle source on request of *.bundle (compat)', () => { return makeRequest( requestHandler, 'mybundle.runModule.bundle' - ).then(function(response) { - expect(response).toEqual('this is the source'); - }); + ).then(response => + expect(response).toEqual('this is the source') + ); }); - pit('returns sourcemap on request of *.map', function() { + pit('returns sourcemap on request of *.map', () => { return makeRequest( requestHandler, 'mybundle.map?runModule=true' - ).then(function(response) { - expect(response).toEqual('"this is the source map"'); - }); + ).then(response => + expect(response).toEqual('"this is the source map"') + ); }); - pit('works with .ios.js extension', function() { + pit('works with .ios.js extension', () => { return makeRequest( requestHandler, 'index.ios.includeRequire.bundle' - ).then(function(response) { + ).then(response => { expect(response).toEqual('this is the source'); expect(Bundler.prototype.bundle).toBeCalledWith( 'index.ios.js', @@ -130,81 +115,75 @@ describe('processRequest', function() { }); }); - pit('watches all files in projectRoot', function() { + pit('watches all files in projectRoot', () => { return makeRequest( requestHandler, 'mybundle.bundle?runModule=true' - ).then(function() { + ).then(() => { expect(watcherFunc.mock.calls[0][0]).toEqual('all'); expect(watcherFunc.mock.calls[0][1]).not.toBe(null); }); }); - - describe('file changes', function() { - pit('invalides files in bundle when file is updated', function() { + describe('file changes', () => { + pit('invalides files in bundle when file is updated', () => { return makeRequest( requestHandler, 'mybundle.bundle?runModule=true' - ).then(function() { - var onFileChange = watcherFunc.mock.calls[0][1]; + ).then(() => { + const onFileChange = watcherFunc.mock.calls[0][1]; onFileChange('all','path/file.js', options.projectRoots[0]); expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js'); }); }); - pit('rebuilds the bundles that contain a file when that file is changed', function() { - var bundleFunc = jest.genMockFunction(); + pit('rebuilds the bundles that contain a file when that file is changed', () => { + const bundleFunc = jest.genMockFunction(); bundleFunc .mockReturnValueOnce( Promise.resolve({ - getSource: function() { - return 'this is the first source'; - }, - getSourceMap: function() {}, + getSource: () => 'this is the first source', + getSourceMap: () => {}, }) ) .mockReturnValue( Promise.resolve({ - getSource: function() { - return 'this is the rebuilt source'; - }, - getSourceMap: function() {}, + getSource: () => 'this is the rebuilt source', + getSourceMap: () => {}, }) ); Bundler.prototype.bundle = bundleFunc; - var Server = require('../../Server'); + const Server = require('../../Server'); server = new Server(options); requestHandler = server.processRequest.bind(server); - return makeRequest(requestHandler, 'mybundle.bundle?runModule=true') - .then(function(response) { + .then(response => { expect(response).toEqual('this is the first source'); expect(bundleFunc.mock.calls.length).toBe(1); triggerFileChange('all','path/file.js', options.projectRoots[0]); jest.runAllTimers(); jest.runAllTimers(); }) - .then(function() { + .then(() => { 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'); - }); + .then(response => + expect(response).toEqual('this is the rebuilt source') + ); }); }); }); - describe('/onchange endpoint', function() { + describe('/onchange endpoint', () => { var EventEmitter; var req; var res; - beforeEach(function() { + beforeEach(() => { EventEmitter = require.requireActual('events').EventEmitter; req = new EventEmitter(); req.url = '/onchange'; @@ -214,14 +193,14 @@ describe('processRequest', function() { }; }); - it('should hold on to request and inform on change', function() { + it('should hold on to request and inform on change', () => { server.processRequest(req, res); triggerFileChange('all', 'path/file.js', options.projectRoots[0]); jest.runAllTimers(); expect(res.end).toBeCalledWith(JSON.stringify({changed: true})); }); - it('should not inform changes on disconnected clients', function() { + it('should not inform changes on disconnected clients', () => { server.processRequest(req, res); req.emit('close'); jest.runAllTimers(); @@ -231,36 +210,30 @@ describe('processRequest', function() { }); }); - describe('/assets endpoint', function() { + describe('/assets endpoint', () => { var AssetServer; - beforeEach(function() { + beforeEach(() => { AssetServer = require('../../AssetServer'); }); - it('should serve simple case', function() { - var req = { - url: '/assets/imgs/a.png', - }; - var res = { - end: jest.genMockFn(), - }; + it('should serve simple case', () => { + const req = {url: '/assets/imgs/a.png'}; + const res = {end: jest.genMockFn()}; - AssetServer.prototype.get.mockImpl(function() { - return Promise.resolve('i am image'); - }); + AssetServer.prototype.get.mockImpl(() => Promise.resolve('i am image')); server.processRequest(req, res); jest.runAllTimers(); expect(res.end).toBeCalledWith('i am image'); }); - it('should return 404', function() { + it('should return 404', () => { }); }); - describe('buildBundle(options)', function() { - it('Calls the bundler with the correct args', function() { + describe('buildbundle(options)', () => { + it('Calls the bundler with the correct args', () => { server.buildBundle({ entryFile: 'foo file' }); @@ -273,8 +246,8 @@ describe('processRequest', function() { }); }); - describe('buildBundleFromUrl(options)', function() { - it('Calls the bundler with the correct args', function() { + describe('buildBundleFromUrl(options)', () => { + it('Calls the bundler with the correct args', () => { server.buildBundleFromUrl('/path/to/foo.bundle?dev=false&runModule=false'); expect(Bundler.prototype.bundle).toBeCalledWith( 'path/to/foo.js', From fec5d8d4d4d6bf5e316f7c04044b6aa95beea7ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 14 Aug 2015 12:10:13 -0700 Subject: [PATCH 09/44] [react-packager] Cache in which bundle is each module Summary: Not that at the moment a module can be present in multiple bundles, so the new API will return only one of them. In the near future we'll impose the invariant that a module can only be present in a single bundle so this API will return the exact bundle in which it is. --- .../__tests__/BundlesLayout-test.js | 37 +++++++++++++++++++ react-packager/src/BundlesLayout/index.js | 11 +++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js index 154a29c3..fce23226 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -146,5 +146,42 @@ describe('BundlesLayout', () => { ]) ); }); + + pit('separate cache in which bundle is each dependency', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js'), dep('/root/a.js')], + asyncDependencies: [['/root/b.js']], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [], + }); + case '/root/b.js': + return Promise.resolve({ + dependencies: [dep('/root/b.js')], + asyncDependencies: [['/root/c.js']], + }); + case '/root/c.js': + return Promise.resolve({ + dependencies: [dep('/root/c.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + var layout = newBundlesLayout(); + return layout.generateLayout(['/root/index.js']).then(() => { + expect(layout.getBundleIDForModule('/root/index.js')).toBe(0); + expect(layout.getBundleIDForModule('/root/a.js')).toBe(0); + expect(layout.getBundleIDForModule('/root/b.js')).toBe(1); + expect(layout.getBundleIDForModule('/root/c.js')).toBe(2); + }); + }); }); }); diff --git a/react-packager/src/BundlesLayout/index.js b/react-packager/src/BundlesLayout/index.js index 88e616c0..165a33b3 100644 --- a/react-packager/src/BundlesLayout/index.js +++ b/react-packager/src/BundlesLayout/index.js @@ -26,6 +26,8 @@ class BundlesLayout { constructor(options) { const opts = validateOpts(options); this._resolver = opts.dependencyResolver; + + this._moduleToBundle = Object.create(null); } generateLayout(entryPaths, isDev) { @@ -44,9 +46,10 @@ class BundlesLayout { .then(modulesDeps => { let syncDependencies = Object.create(null); modulesDeps.forEach(moduleDeps => { - moduleDeps.dependencies.forEach(dep => + moduleDeps.dependencies.forEach(dep => { syncDependencies[dep.path] = dep - ); + this._moduleToBundle[dep.path] = bundles.length; + }); pending = pending.concat(moduleDeps.asyncDependencies); }); @@ -60,6 +63,10 @@ class BundlesLayout { }, ); } + + getBundleIDForModule(path) { + return this._moduleToBundle[path]; + } } // Runs the body Promise meanwhile the condition callback is satisfied. From daf56c32eb32fb0b0e7ef4a5a33985685548f425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 14 Aug 2015 12:08:46 -0700 Subject: [PATCH 10/44] [react-packager] Modernize `Server` to ES6 --- react-packager/src/Server/index.js | 695 ++++++++++++++--------------- 1 file changed, 346 insertions(+), 349 deletions(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index fcb6e1a9..8ff8fda4 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -8,21 +8,20 @@ */ 'use strict'; -var url = require('url'); -var path = require('path'); -var declareOpts = require('../lib/declareOpts'); -var FileWatcher = require('../FileWatcher'); -var Bundler = require('../Bundler'); -var Activity = require('../Activity'); -var AssetServer = require('../AssetServer'); -var Promise = require('promise'); -var _ = require('underscore'); -var exec = require('child_process').exec; -var fs = require('fs'); +const Activity = require('../Activity'); +const AssetServer = require('../AssetServer'); +const FileWatcher = require('../FileWatcher'); +const Bundler = require('../Bundler'); +const Promise = require('promise'); -module.exports = Server; +const _ = require('underscore'); +const declareOpts = require('../lib/declareOpts'); +const exec = require('child_process').exec; +const fs = require('fs'); +const path = require('path'); +const url = require('url'); -var validateOpts = declareOpts({ +const validateOpts = declareOpts({ projectRoots: { type: 'array', required: true, @@ -64,115 +63,7 @@ var validateOpts = declareOpts({ }, }); -function Server(options) { - var opts = validateOpts(options); - - this._projectRoots = opts.projectRoots; - this._bundles = Object.create(null); - this._changeWatchers = []; - - var assetGlobs = opts.assetExts.map(function(ext) { - return '**/*.' + ext; - }); - - var watchRootConfigs = opts.projectRoots.map(function(dir) { - return { - dir: dir, - globs: [ - '**/*.js', - '**/*.json', - ].concat(assetGlobs), - }; - }); - - if (opts.assetRoots != null) { - watchRootConfigs = watchRootConfigs.concat( - opts.assetRoots.map(function(dir) { - return { - dir: dir, - globs: assetGlobs, - }; - }) - ); - } - - this._fileWatcher = options.nonPersistent - ? FileWatcher.createDummyWatcher() - : new FileWatcher(watchRootConfigs); - - this._assetServer = new AssetServer({ - projectRoots: opts.projectRoots, - assetExts: opts.assetExts, - }); - - 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._rebuildBundles(filePath); - self._informChangeWatchers(); - }, 50); -} - -Server.prototype._onFileChange = function(type, filepath, root) { - var absPath = path.join(root, filepath); - this._bundler.invalidateFile(absPath); - // Make sure the file watcher event runs through the system before - // we rebuild the bundles. - this._debouncedFileChangeHandler(absPath); -}; - -Server.prototype._rebuildBundles = function() { - var buildBundle = this.buildBundle.bind(this); - var bundles = this._bundles; - - Object.keys(bundles).forEach(function(optionsJson) { - var options = JSON.parse(optionsJson); - // Wait for a previous build (if exists) to finish. - 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. - bundles[optionsJson] = buildBundle(options).then(function(p) { - // Make a throwaway call to getSource to cache the source string. - p.getSource({ - inlineSourceMap: options.inlineSourceMap, - minify: options.minify, - }); - return p; - }); - }); - return bundles[optionsJson]; - }); -}; - -Server.prototype._informChangeWatchers = function() { - var watchers = this._changeWatchers; - var headers = { - 'Content-Type': 'application/json; charset=UTF-8', - }; - - watchers.forEach(function(w) { - w.res.writeHead(205, headers); - w.res.end(JSON.stringify({ changed: true })); - }); - - this._changeWatchers = []; -}; - -Server.prototype.end = function() { - Promise.all([ - this._fileWatcher.end(), - this._bundler.kill(), - ]); -}; - -var bundleOpts = declareOpts({ +const bundleOpts = declareOpts({ sourceMapUrl: { type: 'string', required: false, @@ -199,245 +90,351 @@ var bundleOpts = declareOpts({ }, }); -Server.prototype.buildBundle = function(options) { - var opts = bundleOpts(options); +class Server { + constructor(options) { + const opts = validateOpts(options); - return this._bundler.bundle( - opts.entryFile, - opts.runModule, - opts.sourceMapUrl, - opts.dev - ); -}; + this._projectRoots = opts.projectRoots; + this._bundles = Object.create(null); + this._changeWatchers = []; -Server.prototype.buildBundleFromUrl = function(reqUrl) { - var options = getOptionsFromUrl(reqUrl); - return this.buildBundle(options); -}; + const assetGlobs = opts.assetExts.map(ext => '**/*.' + ext); -Server.prototype.getDependencies = function(main) { - return this._bundler.getDependencies(main); -}; + var watchRootConfigs = opts.projectRoots.map(dir => { + return { + dir: dir, + globs: [ + '**/*.js', + '**/*.json', + ].concat(assetGlobs), + }; + }); -Server.prototype._processDebugRequest = function(reqUrl, res) { - var ret = ''; - var pathname = url.parse(reqUrl).pathname; - var parts = pathname.split('/').filter(Boolean); - if (parts.length === 1) { - ret += ''; - ret += ''; - res.end(ret); - } else if (parts[1] === 'bundles') { - ret += '

Cached Bundles

'; - Promise.all(Object.keys(this._bundles).map(function(optionsJson) { - return this._bundles[optionsJson].then(function(p) { - ret += '

' + optionsJson + '

'; - ret += p.getDebugInfo(); - }); - }, this)).then( - function() { res.end(ret); }, - function(e) { - res.writeHead(500); - res.end('Internal Error'); - console.log(e.stack); - } - ); - } else if (parts[1] === 'graph'){ - ret += '

Dependency Graph

'; - ret += this._bundler.getGraphDebugInfo(); - res.end(ret); - } else { - res.writeHead('404'); - res.end('Invalid debug request'); - return; - } -}; - -Server.prototype._processOnChangeRequest = function(req, res) { - var watchers = this._changeWatchers; - - watchers.push({ - req: req, - res: res, - }); - - req.on('close', function() { - for (var i = 0; i < watchers.length; i++) { - if (watchers[i] && watchers[i].req === req) { - watchers.splice(i, 1); - break; - } + if (opts.assetRoots != null) { + watchRootConfigs = watchRootConfigs.concat( + opts.assetRoots.map(dir => { + return { + dir: dir, + globs: assetGlobs, + }; + }) + ); } - }); -}; -Server.prototype._processAssetsRequest = function(req, res) { - var urlObj = url.parse(req.url, true); - var assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); - this._assetServer.get(assetPath[1]) - .then( - function(data) { - res.end(data); - }, - function(error) { - console.error(error.stack); - res.writeHead('404'); - res.end('Asset not found'); - } - ).done(); -}; + this._fileWatcher = options.nonPersistent + ? FileWatcher.createDummyWatcher() + : new FileWatcher(watchRootConfigs); -Server.prototype._processProfile = function(req, res) { - console.log('Dumping profile information...'); - var dumpName = '/tmp/dump_' + Date.now() + '.json'; - var prefix = process.env.TRACE_VIEWER_PATH || ''; - var cmd = path.join(prefix, 'trace2html') + ' ' + dumpName; - fs.writeFileSync(dumpName, req.rawBody); - exec(cmd, function (error) { - if (error) { - if (error.code === 127) { - console.error( - '\n** Failed executing `' + cmd + '` **\n\n' + - 'Google trace-viewer is required to visualize the data, do you have it installled?\n\n' + - 'You can get it at:\n\n' + - ' https://github.com/google/trace-viewer\n\n' + - 'If it\'s not in your path, you can set a custom path with:\n\n' + - ' TRACE_VIEWER_PATH=/path/to/trace-viewer\n\n' + - 'NOTE: Your profile data was kept at:\n\n' + - ' ' + dumpName - ); - } else { - console.error('Unknown error', error); - } - res.end(); - return; + this._assetServer = new AssetServer({ + projectRoots: opts.projectRoots, + assetExts: opts.assetExts, + }); + + const bundlerOpts = Object.create(opts); + bundlerOpts.fileWatcher = this._fileWatcher; + bundlerOpts.assetServer = this._assetServer; + this._bundler = new Bundler(bundlerOpts); + + this._fileWatcher.on('all', this._onFileChange.bind(this)); + + this._debouncedFileChangeHandler = _.debounce(filePath => { + this._rebuildBundles(filePath); + this._informChangeWatchers(); + }, 50); + } + + end() { + Promise.all([ + this._fileWatcher.end(), + this._bundler.kill(), + ]); + } + + buildBundle(options) { + const opts = bundleOpts(options); + + return this._bundler.bundle( + opts.entryFile, + opts.runModule, + opts.sourceMapUrl, + opts.dev + ); + } + + buildBundleFromUrl(reqUrl) { + const options = this._getOptionsFromUrl(reqUrl); + return this.buildBundle(options); + } + + getDependencies(main) { + return this._bundler.getDependencies(main); + } + + _onFileChange(type, filepath, root) { + const absPath = path.join(root, filepath); + this._bundler.invalidateFile(absPath); + // Make sure the file watcher event runs through the system before + // we rebuild the bundles. + this._debouncedFileChangeHandler(absPath); + } + + _rebuildBundles() { + const buildBundle = this.buildBundle.bind(this); + const bundles = this._bundles; + + Object.keys(bundles).forEach(function(optionsJson) { + const options = JSON.parse(optionsJson); + // Wait for a previous build (if exists) to finish. + 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. + bundles[optionsJson] = buildBundle(options).then(function(p) { + // Make a throwaway call to getSource to cache the source string. + p.getSource({ + inlineSourceMap: options.inlineSourceMap, + minify: options.minify, + }); + return p; + }); + }); + return bundles[optionsJson]; + }); + } + + _informChangeWatchers() { + const watchers = this._changeWatchers; + const headers = { + 'Content-Type': 'application/json; charset=UTF-8', + }; + + watchers.forEach(function(w) { + w.res.writeHead(205, headers); + w.res.end(JSON.stringify({ changed: true })); + }); + + this._changeWatchers = []; + } + + _processDebugRequest(reqUrl, res) { + var ret = ''; + const pathname = url.parse(reqUrl).pathname; + const parts = pathname.split('/').filter(Boolean); + if (parts.length === 1) { + ret += ''; + ret += ''; + res.end(ret); + } else if (parts[1] === 'bundles') { + ret += '

Cached Bundles

'; + Promise.all(Object.keys(this._bundles).map(optionsJson => + this._bundles[optionsJson].then(p => { + ret += '

' + optionsJson + '

'; + ret += p.getDebugInfo(); + }) + )).then( + () => res.end(ret), + e => { + res.writeHead(500); + res.end('Internal Error'); + console.log(e.stack); + } + ); + } else if (parts[1] === 'graph'){ + ret += '

Dependency Graph

'; + ret += this._bundler.getGraphDebugInfo(); + res.end(ret); } else { - exec('rm ' + dumpName); - exec('open ' + dumpName.replace(/json$/, 'html'), function (error) { - if (error) { - console.error(error); + res.writeHead('404'); + res.end('Invalid debug request'); + return; + } + } + + _processOnChangeRequest(req, res) { + const watchers = this._changeWatchers; + + watchers.push({ + req: req, + res: res, + }); + + req.on('close', () => { + for (let i = 0; i < watchers.length; i++) { + if (watchers[i] && watchers[i].req === req) { + watchers.splice(i, 1); + break; + } + } + }); + } + + _processAssetsRequest(req, res) { + const urlObj = url.parse(req.url, true); + const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); + this._assetServer.get(assetPath[1]) + .then( + data => res.end(data), + error => { + console.error(error.stack); + res.writeHead('404'); + res.end('Asset not found'); + } + ).done(); + } + + _processProfile(req, res) { + console.log('Dumping profile information...'); + const dumpName = '/tmp/dump_' + Date.now() + '.json'; + const prefix = process.env.TRACE_VIEWER_PATH || ''; + const cmd = path.join(prefix, 'trace2html') + ' ' + dumpName; + fs.writeFileSync(dumpName, req.rawBody); + exec(cmd, error => { + if (error) { + if (error.code === 127) { + console.error( + '\n** Failed executing `' + cmd + '` **\n\n' + + 'Google trace-viewer is required to visualize the data, do you have it installled?\n\n' + + 'You can get it at:\n\n' + + ' https://github.com/google/trace-viewer\n\n' + + 'If it\'s not in your path, you can set a custom path with:\n\n' + + ' TRACE_VIEWER_PATH=/path/to/trace-viewer\n\n' + + 'NOTE: Your profile data was kept at:\n\n' + + ' ' + dumpName + ); + } else { + console.error('Unknown error', error); } res.end(); - }); - } - }); -}; - -Server.prototype.processRequest = function(req, res, next) { - var urlObj = url.parse(req.url, true); - var pathname = urlObj.pathname; - - var requestType; - if (pathname.match(/\.bundle$/)) { - requestType = 'bundle'; - } else if (pathname.match(/\.map$/)) { - requestType = 'map'; - } else if (pathname.match(/^\/debug/)) { - this._processDebugRequest(req.url, res); - return; - } else if (pathname.match(/^\/onchange\/?$/)) { - this._processOnChangeRequest(req, res); - return; - } else if (pathname.match(/^\/assets\//)) { - this._processAssetsRequest(req, res); - return; - } else if (pathname.match(/^\/profile\/?$/)) { - this._processProfile(req, res); - return; - } else { - next(); - return; - } - - var startReqEventId = Activity.startEvent('request:' + req.url); - var options = getOptionsFromUrl(req.url); - var optionsJson = JSON.stringify(options); - var building = this._bundles[optionsJson] || this.buildBundle(options); - - this._bundles[optionsJson] = building; - building.then( - function(p) { - if (requestType === 'bundle') { - var bundleSource = p.getSource({ - inlineSourceMap: options.inlineSourceMap, - minify: options.minify, + return; + } else { + exec('rm ' + dumpName); + exec('open ' + dumpName.replace(/json$/, 'html'), err => { + if (err) { + console.error(err); + } + res.end(); }); - res.setHeader('Content-Type', 'application/javascript'); - res.end(bundleSource); - Activity.endEvent(startReqEventId); - } else if (requestType === 'map') { - var sourceMap = JSON.stringify(p.getSourceMap()); - res.setHeader('Content-Type', 'application/json'); - res.end(sourceMap); - Activity.endEvent(startReqEventId); } - }, - this._handleError.bind(this, res, optionsJson) - ).done(); -}; - -Server.prototype._handleError = function(res, bundleID, error) { - res.writeHead(error.status || 500, { - 'Content-Type': 'application/json; charset=UTF-8', - }); - - if (error.type === 'TransformError' || error.type === 'NotFoundError') { - error.errors = [{ - description: error.description, - filename: error.filename, - lineNumber: error.lineNumber, - }]; - res.end(JSON.stringify(error)); - - if (error.type === 'NotFoundError') { - delete this._bundles[bundleID]; - } - } else { - console.error(error.stack || error); - res.end(JSON.stringify({ - type: 'InternalError', - message: 'react-packager has encountered an internal error, ' + - 'please check your terminal error output for more details', - })); - } -}; - -function getOptionsFromUrl(reqUrl) { - // `true` to parse the query param as an object. - var urlObj = url.parse(reqUrl, true); - // node v0.11.14 bug see https://github.com/facebook/react-native/issues/218 - urlObj.query = urlObj.query || {}; - - var pathname = decodeURIComponent(urlObj.pathname); - - // Backwards compatibility. Options used to be as added as '.' to the - // entry module name. We can safely remove these options. - var entryFile = pathname.replace(/^\//, '').split('.').filter(function(part) { - if (part === 'includeRequire' || part === 'runModule' || - part === 'bundle' || part === 'map') { - return false; - } - return true; - }).join('.') + '.js'; - - return { - sourceMapUrl: pathname.replace(/\.bundle$/, '.map'), - entryFile: entryFile, - dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), - minify: getBoolOptionFromQuery(urlObj.query, 'minify'), - runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true), - inlineSourceMap: getBoolOptionFromQuery( - urlObj.query, - 'inlineSourceMap', - false - ), - }; -} - -function getBoolOptionFromQuery(query, opt, defaultVal) { - if (query[opt] == null && defaultVal != null) { - return defaultVal; + }); } - return query[opt] === 'true' || query[opt] === '1'; + processRequest(req, res, next) { + const urlObj = url.parse(req.url, true); + var pathname = urlObj.pathname; + + var requestType; + if (pathname.match(/\.bundle$/)) { + requestType = 'bundle'; + } else if (pathname.match(/\.map$/)) { + requestType = 'map'; + } else if (pathname.match(/^\/debug/)) { + this._processDebugRequest(req.url, res); + return; + } else if (pathname.match(/^\/onchange\/?$/)) { + this._processOnChangeRequest(req, res); + return; + } else if (pathname.match(/^\/assets\//)) { + this._processAssetsRequest(req, res); + return; + } else if (pathname.match(/^\/profile\/?$/)) { + this._processProfile(req, res); + return; + } else { + next(); + return; + } + + const startReqEventId = Activity.startEvent('request:' + req.url); + const options = this._getOptionsFromUrl(req.url); + const optionsJson = JSON.stringify(options); + const building = this._bundles[optionsJson] || this.buildBundle(options); + + this._bundles[optionsJson] = building; + building.then( + p => { + if (requestType === 'bundle') { + var bundleSource = p.getSource({ + inlineSourceMap: options.inlineSourceMap, + minify: options.minify, + }); + res.setHeader('Content-Type', 'application/javascript'); + res.end(bundleSource); + Activity.endEvent(startReqEventId); + } else if (requestType === 'map') { + var sourceMap = JSON.stringify(p.getSourceMap()); + res.setHeader('Content-Type', 'application/json'); + res.end(sourceMap); + Activity.endEvent(startReqEventId); + } + }, + this._handleError.bind(this, res, optionsJson) + ).done(); + } + + _handleError(res, bundleID, error) { + res.writeHead(error.status || 500, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + + if (error.type === 'TransformError' || error.type === 'NotFoundError') { + error.errors = [{ + description: error.description, + filename: error.filename, + lineNumber: error.lineNumber, + }]; + res.end(JSON.stringify(error)); + + if (error.type === 'NotFoundError') { + delete this._bundles[bundleID]; + } + } else { + console.error(error.stack || error); + res.end(JSON.stringify({ + type: 'InternalError', + message: 'react-packager has encountered an internal error, ' + + 'please check your terminal error output for more details', + })); + } + } + + _getOptionsFromUrl(reqUrl) { + // `true` to parse the query param as an object. + const urlObj = url.parse(reqUrl, true); + // node v0.11.14 bug see https://github.com/facebook/react-native/issues/218 + urlObj.query = urlObj.query || {}; + + const pathname = decodeURIComponent(urlObj.pathname); + + // Backwards compatibility. Options used to be as added as '.' to the + // entry module name. We can safely remove these options. + const entryFile = pathname.replace(/^\//, '').split('.').filter(part => { + if (part === 'includeRequire' || part === 'runModule' || + part === 'bundle' || part === 'map') { + return false; + } + return true; + }).join('.') + '.js'; + + return { + sourceMapUrl: pathname.replace(/\.bundle$/, '.map'), + entryFile: entryFile, + dev: this._getBoolOptionFromQuery(urlObj.query, 'dev', true), + minify: this._getBoolOptionFromQuery(urlObj.query, 'minify'), + runModule: this._getBoolOptionFromQuery(urlObj.query, 'runModule', true), + inlineSourceMap: this._getBoolOptionFromQuery( + urlObj.query, + 'inlineSourceMap', + false + ), + }; + } + + _getBoolOptionFromQuery(query, opt, defaultVal) { + if (query[opt] == null && defaultVal != null) { + return defaultVal; + } + + return query[opt] === 'true' || query[opt] === '1'; + } } + +module.exports = Server; From 44b97249e9996c11c3e609c784ee6d8b525b58b6 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 14 Aug 2015 13:58:19 -0700 Subject: [PATCH 11/44] [ReactNative] Add ability to listen for Packager events --- react-packager/index.js | 1 + react-packager/src/Activity/index.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/react-packager/index.js b/react-packager/index.js index 83f31228..4d87733f 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -22,6 +22,7 @@ exports.middleware = function(options) { return server.processRequest.bind(server); }; +exports.activityEvents = Activity.eventEmitter; // Renamed "package" to "bundle". But maintain backwards // compat. diff --git a/react-packager/src/Activity/index.js b/react-packager/src/Activity/index.js index 8e593f9f..aeaf3143 100644 --- a/react-packager/src/Activity/index.js +++ b/react-packager/src/Activity/index.js @@ -9,6 +9,7 @@ 'use strict'; var chalk = require('chalk'); +var events = require('events'); var COLLECTION_PERIOD = 1000; @@ -18,6 +19,7 @@ var _queuedActions = []; var _scheduledCollectionTimer = null; var _uuid = 1; var _enabled = true; +var _eventEmitter = new events.EventEmitter(); function endEvent(eventId) { var eventEndTime = Date.now(); @@ -98,6 +100,7 @@ function _runCollection() { function _scheduleAction(action) { _queuedActions.push(action); + _eventEmitter.emit(action.action, action); if (_scheduledCollectionTimer === null) { _scheduledCollectionTimer = setTimeout(_runCollection, COLLECTION_PERIOD); @@ -171,3 +174,4 @@ exports.endEvent = endEvent; exports.signal = signal; exports.startEvent = startEvent; exports.disable = disable; +exports.eventEmitter = _eventEmitter; From cc01b6daf4381f6fac4cf77be2594f6f1f3a6659 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sat, 15 Aug 2015 12:21:50 -0700 Subject: [PATCH 12/44] [react-native] Set NODE_ENV and use node-env-inline plugin Summary: This sets NODE_ENV based on the value of the `dev` option when bundling the apps. This would then be inlined by the node-env-inline babel plugin. And finally -- if unreachable -- will be dead-code-eliminated by uglify. This is not used in development because we set NODE_ENV to the value of __DEV__, which can be switched via a query param. However, the plugin has minimal overhead and to avoid complexity in the transformers I just enabled it by default. --- transformer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/transformer.js b/transformer.js index a7711931..0c0a98a9 100644 --- a/transformer.js +++ b/transformer.js @@ -35,6 +35,7 @@ function transform(srcTxt, filename, options) { 'react', 'regenerator', ], + plugins: ['node-env-inline'], sourceFileName: filename, sourceMaps: false, extra: options || {}, From 3555afb66dccdbdbddf5a9383d551de18f7cd636 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sat, 15 Aug 2015 12:29:35 -0700 Subject: [PATCH 13/44] [reat-packager] Switch platform resolution based on query param Summary: Currently the platform selection is controlled by the blacklist. However, since we want to use the same server instance for cross-platform development, we need this to be controlled per request. One outstanding issue, is that the DependencyGraph class wasn't designed that way and it doesn't have a per-request state. This means that with the current design race conditions is possible. If we got a request for a different platfrom while processing the previous request, we may change the outcome of the previous request. To fix this a larger refactor is needed. I'll follow up a diff to do that. Finally, so I don't break the universe like last time, I'll leave it up to the RN guys to update the call sites. --- react-packager/src/Bundler/index.js | 8 ++-- .../DependencyGraph/index.js | 35 ++++++++++++--- .../src/DependencyResolver/index.js | 45 ++++++++++--------- .../src/Server/__tests__/Server-test.js | 25 +++++++++-- react-packager/src/Server/index.js | 9 +++- 5 files changed, 85 insertions(+), 37 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 4c60924b..854bd714 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -118,14 +118,14 @@ class Bundler { return this._cache.end(); } - bundle(main, runModule, sourceMapUrl, isDev) { + bundle(main, runModule, sourceMapUrl, isDev, platform) { 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((result) => { + return this.getDependencies(main, isDev, platform).then((result) => { Activity.endEvent(findEventId); transformEventId = Activity.startEvent('transform'); @@ -149,8 +149,8 @@ class Bundler { this._transformer.invalidateFile(filePath); } - getDependencies(main, isDev) { - return this._resolver.getDependencies(main, { dev: isDev }); + getDependencies(main, isDev, platform) { + return this._resolver.getDependencies(main, { dev: isDev, platform }); } _transformModule(bundle, module) { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 3825507c..4cdf0c2e 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -69,7 +69,7 @@ class DependencyGraph { constructor(options) { this._opts = validateOpts(options); this._hasteMap = Object.create(null); - this._immediateResolutionCache = Object.create(null); + this._resetResolutionCache(); this._cache = this._opts.cache; this.load(); } @@ -109,6 +109,20 @@ class DependencyGraph { return this._loading; } + setup({ platform }) { + if (platform && this._opts.platforms.indexOf(platform) === -1) { + throw new Error('Unrecognized platform: ' + platform); + } + + // TODO(amasad): This is a potential race condition. Mutliple requests could + // interfere with each other. This needs a refactor to fix -- which will + // follow this diff. + if (this._platformExt !== platform) { + this._resetResolutionCache(); + } + this._platformExt = platform; + } + resolveDependency(fromModule, toModuleName) { const resHash = resolutionHash(fromModule.path, toModuleName); @@ -250,11 +264,14 @@ class DependencyGraph { ); } - const platformExt = getPlatformExt(entryPath); - if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { - this._platformExt = platformExt; - } else { - this._platformExt = null; + // `platformExt` could be set in the `setup` method. + if (!this._platformExt) { + const platformExt = getPlatformExt(entryPath); + if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { + this._platformExt = platformExt; + } else { + this._platformExt = null; + } } return this._moduleCache.getModule(absolutePath); @@ -563,7 +580,7 @@ class DependencyGraph { _processFileChange(type, filePath, root, fstat) { // It's really hard to invalidate the right module resolution cache // so we just blow it up with every file change. - this._immediateResolutionCache = Object.create(null); + this._resetResolutionCache(); const absPath = path.join(root, filePath); if ((fstat && fstat.isDirectory()) || @@ -599,6 +616,10 @@ class DependencyGraph { }); } } + + _resetResolutionCache() { + this._immediateResolutionCache = Object.create(null); + } } function assetName(file, ext) { diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index 3fdb19fb..329ae8b7 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -77,6 +77,10 @@ var getDependenciesValidateOpts = declareOpts({ type: 'boolean', default: true, }, + platform: { + type: 'string', + required: false, + }, }); HasteDependencyResolver.prototype.getDependencies = function(main, options) { @@ -85,28 +89,27 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph - .load() - .then(() => Promise.all([ - depGraph.getOrderedDependencies(main), - depGraph.getAsyncDependencies(main), - ])) - .then( - ([dependencies, asyncDependencies]) => dependencies[0].getName().then( - mainModuleId => { - self._prependPolyfillDependencies( - dependencies, - opts.dev, - ); + depGraph.setup({ platform: opts.platform }); - return { - mainModuleId, - dependencies, - asyncDependencies, - }; - } - ) - ); + return Promise.all([ + depGraph.getOrderedDependencies(main), + depGraph.getAsyncDependencies(main), + ]).then( + ([dependencies, asyncDependencies]) => dependencies[0].getName().then( + mainModuleId => { + self._prependPolyfillDependencies( + dependencies, + opts.dev, + ); + + return { + mainModuleId, + dependencies, + asyncDependencies, + }; + } + ) + ); }; HasteDependencyResolver.prototype._prependPolyfillDependencies = function( diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 1677776d..9e0a6890 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -110,7 +110,24 @@ describe('processRequest', () => { 'index.ios.js', true, 'index.ios.includeRequire.map', - true + true, + undefined + ); + }); + }); + + pit('passes in the platform param', function() { + return makeRequest( + requestHandler, + 'index.bundle?platform=ios' + ).then(function(response) { + expect(response).toEqual('this is the source'); + expect(Bundler.prototype.bundle).toBeCalledWith( + 'index.js', + true, + 'index.map', + true, + 'ios', ); }); }); @@ -241,7 +258,8 @@ describe('processRequest', () => { 'foo file', true, undefined, - true + true, + undefined ); }); }); @@ -253,7 +271,8 @@ describe('processRequest', () => { 'path/to/foo.js', false, '/path/to/foo.map', - false + false, + undefined ); }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 8ff8fda4..8f11be3c 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -88,6 +88,10 @@ const bundleOpts = declareOpts({ type: 'boolean', default: false, }, + platform: { + type: 'string', + required: false, + } }); class Server { @@ -152,12 +156,12 @@ class Server { buildBundle(options) { const opts = bundleOpts(options); - return this._bundler.bundle( opts.entryFile, opts.runModule, opts.sourceMapUrl, - opts.dev + opts.dev, + opts.platform ); } @@ -425,6 +429,7 @@ class Server { 'inlineSourceMap', false ), + platform: urlObj.query.platform, }; } From 9c9814c13427a04039633362712100a764a811b1 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sat, 15 Aug 2015 15:59:37 -0700 Subject: [PATCH 14/44] [react-packager] In production resolve __DEV__ to NODE_ENV === 'development' --- transformer.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/transformer.js b/transformer.js index 0c0a98a9..8f7a48c2 100644 --- a/transformer.js +++ b/transformer.js @@ -13,6 +13,12 @@ var babel = require('babel-core'); function transform(srcTxt, filename, options) { + var plugins = []; + + if (process.env.NODE_ENV === 'production') { + plugins = plugins.concat(['node-env-inline', 'dunderscore-dev-inline']); + } + var result = babel.transform(srcTxt, { retainLines: true, compact: true, @@ -35,7 +41,7 @@ function transform(srcTxt, filename, options) { 'react', 'regenerator', ], - plugins: ['node-env-inline'], + plugins: plugins, sourceFileName: filename, sourceMaps: false, extra: options || {}, From 4a0ff6a7d1dec568cfe063da01064cd06df6c4c3 Mon Sep 17 00:00:00 2001 From: Ludo Fardel Date: Mon, 17 Aug 2015 02:05:29 -0700 Subject: [PATCH 15/44] Make flow check async --- getFlowTypeCheckMiddleware.js | 51 +++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js index c7f3e2b1..3b659761 100644 --- a/getFlowTypeCheckMiddleware.js +++ b/getFlowTypeCheckMiddleware.js @@ -10,17 +10,23 @@ var chalk = require('chalk'); var exec = require('child_process').exec; +var url = require('url'); var Activity = require('./react-packager/src/Activity'); var hasWarned = {}; -var DISABLE_FLOW_CHECK = true; // temporarily disable while we figure out versioning issues. function getFlowTypeCheckMiddleware(options) { return function(req, res, next) { - var isBundle = req.url.indexOf('.bundle') !== -1; - if (DISABLE_FLOW_CHECK || options.skipflow || !isBundle) { + var reqObj = url.parse(req.url); + var isFlowCheck = (reqObj.path.match(/^\/flow\//)); + + if (!isFlowCheck) { return next(); } + if (options.skipflow) { + _endSkipFlow(res); + return; + } if (options.flowroot || options.projectRoots.length === 1) { var flowroot = options.flowroot || options.projectRoots[0]; } else { @@ -28,7 +34,8 @@ function getFlowTypeCheckMiddleware(options) { hasWarned.noRoot = true; console.warn('flow: No suitable root'); } - return next(); + _endFlowBad(res); + return; } exec('command -v flow >/dev/null 2>&1', function(error, stdout) { if (error) { @@ -37,7 +44,8 @@ function getFlowTypeCheckMiddleware(options) { console.warn(chalk.yellow('flow: Skipping because not installed. Install with ' + '`brew install flow`.')); } - return next(); + _endFlowBad(res); + return; } else { return doFlowTypecheck(res, flowroot, next); } @@ -51,7 +59,8 @@ function doFlowTypecheck(res, flowroot, next) { exec(flowCmd, function(flowError, stdout, stderr) { Activity.endEvent(eventId); if (!flowError) { - return next(); + _endFlowOk(res); + return; } else { try { var flowResponse = JSON.parse(stdout); @@ -73,7 +82,7 @@ function doFlowTypecheck(res, flowroot, next) { errorNum++; }); var error = { - status: 500, + status: 200, message: 'Flow found type errors. If you think these are wrong, ' + 'make sure your flow bin and .flowconfig are up to date, or ' + 'disable with --skipflow.', @@ -102,10 +111,36 @@ function doFlowTypecheck(res, flowroot, next) { )); } } - return next(); + _endFlowBad(res); + return; } } }); } +function _endRes(res, message, code, silentError) { + res.writeHead(code, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + res.end(JSON.stringify({ + message: message, + errors: [], + silentError: silentError, + })); +} + +function _endFlowOk(res) { + _endRes(res, 'No Flow Error', '200', true); +} + +function _endFlowBad(res) { + // we want to show that flow failed + // status 200 is need for the fetch to not be rejected + _endRes(res, 'Flow failed to run! Please look at the console for more details.', '200', false); +} + +function _endSkipFlow(res) { + _endRes(res, 'Flow was skipped, check the server options', '200', true); +} + module.exports = getFlowTypeCheckMiddleware; From 801dd2d133898749b662a537ac3c0183a384a353 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Mon, 17 Aug 2015 14:56:10 -0700 Subject: [PATCH 16/44] [ReactNative] Don't redbox for React warnings when not using Chrome executor --- react-packager/src/DependencyResolver/polyfills/console.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/react-packager/src/DependencyResolver/polyfills/console.js b/react-packager/src/DependencyResolver/polyfills/console.js index ff2ff39f..e0459740 100644 --- a/react-packager/src/DependencyResolver/polyfills/console.js +++ b/react-packager/src/DependencyResolver/polyfills/console.js @@ -376,6 +376,12 @@ var str = Array.prototype.map.call(arguments, function(arg) { return inspect(arg, {depth: 10}); }).join(', '); + if (str.slice(0, 10) === "'Warning: " && level >= LOG_LEVELS.error) { + // React warnings use console.error so that a stack trace is shown, + // but we don't (currently) want these to show a redbox + // (Note: Logic duplicated in ExceptionsManager.js.) + level = LOG_LEVELS.warn; + } global.nativeLoggingHook(str, level); }; } From f77f48c61c4df10b9b67f135b5b49ab96b99d64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 18 Aug 2015 13:34:23 -0700 Subject: [PATCH 17/44] [react-packager] Modernize AssetServer to ES6 --- .../AssetServer/__tests__/AssetServer-test.js | 92 +++---- react-packager/src/AssetServer/index.js | 251 +++++++++--------- 2 files changed, 168 insertions(+), 175 deletions(-) diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 95916c9e..54ef7012 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -8,22 +8,22 @@ jest .mock('crypto') .mock('fs'); -var Promise = require('promise'); +const Promise = require('promise'); -describe('AssetServer', function() { - var AssetServer; - var crypto; - var fs; +describe('AssetServer', () => { + let AssetServer; + let crypto; + let fs; - beforeEach(function() { + beforeEach(() => { AssetServer = require('../'); crypto = require('crypto'); fs = require('fs'); }); - describe('assetServer.get', function() { - pit('should work for the simple case', function() { - var server = new AssetServer({ + describe('assetServer.get', () => { + pit('should work for the simple case', () => { + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png'], }); @@ -40,15 +40,15 @@ describe('AssetServer', function() { return Promise.all([ server.get('imgs/b.png'), server.get('imgs/b@1x.png'), - ]).then(function(resp) { - resp.forEach(function(data) { - expect(data).toBe('b image'); - }); - }); + ]).then(resp => + resp.forEach(data => + expect(data).toBe('b image') + ) + ); }); - pit('should work for the simple case with jpg', function() { - var server = new AssetServer({ + pit('should work for the simple case with jpg', () => { + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png', 'jpg'], }); @@ -65,16 +65,16 @@ describe('AssetServer', function() { return Promise.all([ server.get('imgs/b.jpg'), server.get('imgs/b.png'), - ]).then(function(data) { + ]).then(data => expect(data).toEqual([ 'jpeg image', 'png image', - ]); - }); + ]) + ); }); - pit('should pick the bigger one', function() { - var server = new AssetServer({ + pit('should pick the bigger one', () => { + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png'], }); @@ -90,13 +90,13 @@ describe('AssetServer', function() { } }); - return server.get('imgs/b@3x.png').then(function(data) { - expect(data).toBe('b4 image'); - }); + return server.get('imgs/b@3x.png').then(data => + expect(data).toBe('b4 image') + ); }); - pit('should support multiple project roots', function() { - var server = new AssetServer({ + pit('should support multiple project roots', () => { + const server = new AssetServer({ projectRoots: ['/root', '/root2'], assetExts: ['png'], }); @@ -116,27 +116,23 @@ describe('AssetServer', function() { }, }); - return server.get('newImages/imgs/b.png').then(function(data) { - expect(data).toBe('b1 image'); - }); + return server.get('newImages/imgs/b.png').then(data => + expect(data).toBe('b1 image') + ); }); }); - describe('assetSerer.getAssetData', function() { - pit('should get assetData', function() { - var hash = { + describe('assetSerer.getAssetData', () => { + pit('should get assetData', () => { + const hash = { update: jest.genMockFn(), digest: jest.genMockFn(), }; - hash.digest.mockImpl(function() { - return 'wow such hash'; - }); - crypto.createHash.mockImpl(function() { - return hash; - }); + hash.digest.mockImpl(() => 'wow such hash'); + crypto.createHash.mockImpl(() => hash); - var server = new AssetServer({ + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png'], }); @@ -152,7 +148,7 @@ describe('AssetServer', function() { } }); - return server.getAssetData('imgs/b.png').then(function(data) { + return server.getAssetData('imgs/b.png').then(data => { expect(hash.update.mock.calls.length).toBe(4); expect(data).toEqual({ type: 'png', @@ -163,20 +159,16 @@ describe('AssetServer', function() { }); }); - pit('should get assetData for non-png images', function() { - var hash = { + pit('should get assetData for non-png images', () => { + const hash = { update: jest.genMockFn(), digest: jest.genMockFn(), }; - hash.digest.mockImpl(function() { - return 'wow such hash'; - }); - crypto.createHash.mockImpl(function() { - return hash; - }); + hash.digest.mockImpl(() => 'wow such hash'); + crypto.createHash.mockImpl(() => hash); - var server = new AssetServer({ + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png', 'jpeg'], }); @@ -192,7 +184,7 @@ describe('AssetServer', function() { } }); - return server.getAssetData('imgs/b.jpg').then(function(data) { + return server.getAssetData('imgs/b.jpg').then(data => { expect(hash.update.mock.calls.length).toBe(4); expect(data).toEqual({ type: 'jpg', diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 2cd365fd..f442f6b8 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -8,20 +8,20 @@ */ 'use strict'; -var declareOpts = require('../lib/declareOpts'); -var getAssetDataFromName = require('../lib/getAssetDataFromName'); -var path = require('path'); -var Promise = require('promise'); -var fs = require('fs'); -var crypto = require('crypto'); +const Promise = require('promise'); -var stat = Promise.denodeify(fs.stat); -var readDir = Promise.denodeify(fs.readdir); -var readFile = Promise.denodeify(fs.readFile); +const crypto = require('crypto'); +const declareOpts = require('../lib/declareOpts'); +const fs = require('fs'); +const getAssetDataFromName = require('../lib/getAssetDataFromName'); +const path = require('path'); -module.exports = AssetServer; +const stat = Promise.denodeify(fs.stat); +const readDir = Promise.denodeify(fs.readdir); +const readFile = Promise.denodeify(fs.readFile); -var validateOpts = declareOpts({ + +const validateOpts = declareOpts({ projectRoots: { type: 'array', required: true, @@ -32,135 +32,136 @@ var validateOpts = declareOpts({ }, }); -function AssetServer(options) { - var opts = validateOpts(options); - this._roots = opts.projectRoots; - this._assetExts = opts.assetExts; -} +class AssetServer { + constructor(options) { + const opts = validateOpts(options); + this._roots = opts.projectRoots; + this._assetExts = opts.assetExts; + } -/** - * 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) - * in one of the project roots: - * - * 1. We first parse the directory of the asset - * 2. We check to find a matching directory in one of the project roots - * 3. We then build a map of all assets and their scales in this directory - * 4. Then pick the closest resolution (rounding up) to the requested one - */ - -AssetServer.prototype._getAssetRecord = function(assetPath) { - var filename = path.basename(assetPath); - - return findRoot( - this._roots, - path.dirname(assetPath) - ).then(function(dir) { - return Promise.all([ - dir, - readDir(dir), - ]); - }).then(function(res) { - var dir = res[0]; - var files = res[1]; - var assetData = getAssetDataFromName(filename); - - var map = buildAssetMap(dir, files); - var record = map[assetData.assetName]; - - if (!record) { - throw new Error('Asset not found'); - } - - return record; - }); -}; - -AssetServer.prototype.get = function(assetPath) { - var assetData = getAssetDataFromName(assetPath); - return this._getAssetRecord(assetPath).then(function(record) { - for (var i = 0; i < record.scales.length; i++) { - if (record.scales[i] >= assetData.resolution) { - return readFile(record.files[i]); + get(assetPath) { + const assetData = getAssetDataFromName(assetPath); + return this._getAssetRecord(assetPath).then(record => { + for (let i = 0; i < record.scales.length; i++) { + if (record.scales[i] >= assetData.resolution) { + return readFile(record.files[i]); + } } - } - return readFile(record.files[record.files.length - 1]); - }); -}; + return readFile(record.files[record.files.length - 1]); + }); + } -AssetServer.prototype.getAssetData = function(assetPath) { - var nameData = getAssetDataFromName(assetPath); - var data = { - name: nameData.name, - type: nameData.type, - }; + getAssetData(assetPath) { + const nameData = getAssetDataFromName(assetPath); + const data = { + name: nameData.name, + type: nameData.type, + }; - return this._getAssetRecord(assetPath).then(function(record) { - data.scales = record.scales; + return this._getAssetRecord(assetPath).then(record => { + data.scales = record.scales; - return Promise.all( - record.files.map(function(file) { - return stat(file); + return Promise.all( + record.files.map(file => stat(file)) + ); + }).then(stats => { + const hash = crypto.createHash('md5'); + + stats.forEach(fstat => + hash.update(fstat.mtime.getTime().toString()) + ); + + data.hash = hash.digest('hex'); + return data; + }); + } + + /** + * 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) + * in one of the project roots: + * + * 1. We first parse the directory of the asset + * 2. We check to find a matching directory in one of the project roots + * 3. We then build a map of all assets and their scales in this directory + * 4. Then pick the closest resolution (rounding up) to the requested one + */ + _getAssetRecord(assetPath) { + const filename = path.basename(assetPath); + + return ( + this._findRoot( + this._roots, + path.dirname(assetPath) + ) + .then(dir => Promise.all([ + dir, + readDir(dir), + ])) + .then(res => { + const dir = res[0]; + const files = res[1]; + const assetData = getAssetDataFromName(filename); + + const map = this._buildAssetMap(dir, files); + const record = map[assetData.assetName]; + + if (!record) { + throw new Error('Asset not found'); + } + + return record; }) ); - }).then(function(stats) { - var hash = crypto.createHash('md5'); + } - stats.forEach(function(fstat) { - hash.update(fstat.mtime.getTime().toString()); - }); - - data.hash = hash.digest('hex'); - return data; - }); -}; - -function findRoot(roots, dir) { - return Promise.all( - roots.map(function(root) { - var absPath = path.join(root, dir); - return stat(absPath).then(function(fstat) { - return {path: absPath, isDirectory: fstat.isDirectory()}; - }, function (err) { - return {path: absPath, isDirectory: false}; - }); - }) - ).then( - function(stats) { - for (var i = 0; i < stats.length; i++) { + _findRoot(roots, dir) { + return Promise.all( + roots.map(root => { + const absPath = path.join(root, dir); + return stat(absPath).then(fstat => { + return {path: absPath, isDirectory: fstat.isDirectory()}; + }, err => { + return {path: absPath, isDirectory: false}; + }); + }) + ).then(stats => { + for (let i = 0; i < stats.length; i++) { if (stats[i].isDirectory) { return stats[i].path; } } throw new Error('Could not find any directories'); - } - ); -} + }); + } -function buildAssetMap(dir, files) { - var assets = files.map(getAssetDataFromName); - var map = Object.create(null); - assets.forEach(function(asset, i) { - var file = files[i]; - var record = map[asset.assetName]; - if (!record) { - record = map[asset.assetName] = { - scales: [], - files: [], - }; - } - - var insertIndex; - var length = record.scales.length; - for (insertIndex = 0; insertIndex < length; insertIndex++) { - if (asset.resolution < record.scales[insertIndex]) { - break; + _buildAssetMap(dir, files) { + const assets = files.map(getAssetDataFromName); + const map = Object.create(null); + assets.forEach(function(asset, i) { + const file = files[i]; + let record = map[asset.assetName]; + if (!record) { + record = map[asset.assetName] = { + scales: [], + files: [], + }; } - } - record.scales.splice(insertIndex, 0, asset.resolution); - record.files.splice(insertIndex, 0, path.join(dir, file)); - }); - return map; + let insertIndex; + const length = record.scales.length; + for (insertIndex = 0; insertIndex < length; insertIndex++) { + if (asset.resolution < record.scales[insertIndex]) { + break; + } + } + record.scales.splice(insertIndex, 0, asset.resolution); + record.files.splice(insertIndex, 0, path.join(dir, file)); + }); + + return map; + } } + +module.exports = AssetServer; From 02b8f264ef56e9989bc6d08c1d5a0351c8e1ff17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 18 Aug 2015 13:33:07 -0700 Subject: [PATCH 18/44] [react-packager] Modernize Activity to ES6 --- .../src/Activity/__mocks__/chalk.js | 2 +- .../src/Activity/__tests__/Activity-test.js | 58 +++++++++---------- react-packager/src/Activity/index.js | 45 +++++++------- 3 files changed, 52 insertions(+), 53 deletions(-) diff --git a/react-packager/src/Activity/__mocks__/chalk.js b/react-packager/src/Activity/__mocks__/chalk.js index 2981f979..58bf03a6 100644 --- a/react-packager/src/Activity/__mocks__/chalk.js +++ b/react-packager/src/Activity/__mocks__/chalk.js @@ -9,5 +9,5 @@ 'use strict'; module.exports = { - dim: function(s) { return s; }, + dim: s => s, }; diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index c854aa33..90ac43e8 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -10,64 +10,63 @@ jest.autoMockOff(); -describe('Activity', function() { - var Activity; +describe('Activity', () => { + const origConsoleLog = console.log; + let Activity; - var origConsoleLog = console.log; - - beforeEach(function() { + beforeEach(() => { console.log = jest.genMockFn(); Activity = require('../'); jest.runOnlyPendingTimers(); }); - afterEach(function() { + afterEach(() => { console.log = origConsoleLog; }); - describe('startEvent', function() { - it('writes a START event out to the console', function() { - var EVENT_NAME = 'EVENT_NAME'; - var DATA = {someData: 42}; + describe('startEvent', () => { + it('writes a START event out to the console', () => { + const EVENT_NAME = 'EVENT_NAME'; + const DATA = {someData: 42}; Activity.startEvent(EVENT_NAME, DATA); jest.runOnlyPendingTimers(); expect(console.log.mock.calls.length).toBe(1); - var consoleMsg = console.log.mock.calls[0][0]; + const consoleMsg = console.log.mock.calls[0][0]; expect(consoleMsg).toContain('START'); expect(consoleMsg).toContain(EVENT_NAME); expect(consoleMsg).toContain(JSON.stringify(DATA)); }); }); - describe('endEvent', function() { - it('writes an END event out to the console', function() { - var EVENT_NAME = 'EVENT_NAME'; - var DATA = {someData: 42}; + describe('endEvent', () => { + it('writes an END event out to the console', () => { + const EVENT_NAME = 'EVENT_NAME'; + const DATA = {someData: 42}; - var eventID = Activity.startEvent(EVENT_NAME, DATA); + const eventID = Activity.startEvent(EVENT_NAME, DATA); Activity.endEvent(eventID); jest.runOnlyPendingTimers(); expect(console.log.mock.calls.length).toBe(2); - var consoleMsg = console.log.mock.calls[1][0]; + const consoleMsg = console.log.mock.calls[1][0]; expect(consoleMsg).toContain('END'); expect(consoleMsg).toContain(EVENT_NAME); expect(consoleMsg).toContain(JSON.stringify(DATA)); }); - it('throws when called with an invalid eventId', function() { - expect(function() { - Activity.endEvent(42); - }).toThrow('event(42) is not a valid event id!'); + it('throws when called with an invalid eventId', () => { + expect(() => Activity.endEvent(42)).toThrow( + 'event(42) is not a valid event id!', + ); }); - it('throws when called with an expired eventId', function() { - var eid = Activity.startEvent('', ''); + it('throws when called with an expired eventId', () => { + const eid = Activity.startEvent('', ''); Activity.endEvent(eid); - expect(function() { + expect(() => { Activity.endEvent(eid); }).toThrow('event(3) has already ended!'); @@ -75,17 +74,16 @@ describe('Activity', function() { }); }); - describe('signal', function() { - it('writes a SIGNAL event out to the console', function() { - - var EVENT_NAME = 'EVENT_NAME'; - var DATA = {someData: 42}; + describe('signal', () => { + it('writes a SIGNAL event out to the console', () => { + const EVENT_NAME = 'EVENT_NAME'; + const DATA = {someData: 42}; Activity.signal(EVENT_NAME, DATA); jest.runOnlyPendingTimers(); expect(console.log.mock.calls.length).toBe(1); - var consoleMsg = console.log.mock.calls[0][0]; + const consoleMsg = console.log.mock.calls[0][0]; expect(consoleMsg).toContain(EVENT_NAME); expect(consoleMsg).toContain(JSON.stringify(DATA)); }); diff --git a/react-packager/src/Activity/index.js b/react-packager/src/Activity/index.js index aeaf3143..eccebd28 100644 --- a/react-packager/src/Activity/index.js +++ b/react-packager/src/Activity/index.js @@ -8,21 +8,22 @@ */ 'use strict'; -var chalk = require('chalk'); -var events = require('events'); +const chalk = require('chalk'); +const events = require('events'); -var COLLECTION_PERIOD = 1000; +const COLLECTION_PERIOD = 1000; -var _endedEvents = Object.create(null); -var _eventStarts = Object.create(null); -var _queuedActions = []; -var _scheduledCollectionTimer = null; -var _uuid = 1; -var _enabled = true; -var _eventEmitter = new events.EventEmitter(); +const _endedEvents = Object.create(null); +const _eventStarts = Object.create(null); +const _queuedActions = []; +const _eventEmitter = new events.EventEmitter(); + +let _scheduledCollectionTimer = null; +let _uuid = 1; +let _enabled = true; function endEvent(eventId) { - var eventEndTime = Date.now(); + const eventEndTime = Date.now(); if (!_eventStarts[eventId]) { _throw('event(' + eventId + ') is not a valid event id!'); @@ -41,7 +42,7 @@ function endEvent(eventId) { } function signal(eventName, data) { - var signalTime = Date.now(); + const signalTime = Date.now(); if (eventName == null) { _throw('No event name specified'); @@ -60,7 +61,7 @@ function signal(eventName, data) { } function startEvent(eventName, data) { - var eventStartTime = Date.now(); + const eventStartTime = Date.now(); if (eventName == null) { _throw('No event name specified'); @@ -70,8 +71,8 @@ function startEvent(eventName, data) { data = null; } - var eventId = _uuid++; - var action = { + const eventId = _uuid++; + const action = { action: 'startEvent', data: data, eventId: eventId, @@ -90,7 +91,7 @@ function disable() { function _runCollection() { /* jshint -W084 */ - var action; + let action; while ((action = _queuedActions.shift())) { _writeAction(action); } @@ -117,10 +118,10 @@ function _scheduleAction(action) { * won't be adding such a non-trivial optimization anytime soon) */ function _throw(msg) { - var err = new Error(msg); + const err = new Error(msg); // Strip off the call to _throw() - var stack = err.stack.split('\n'); + const stack = err.stack.split('\n'); stack.splice(1, 1); err.stack = stack.join('\n'); @@ -132,8 +133,8 @@ function _writeAction(action) { return; } - var data = action.data ? ': ' + JSON.stringify(action.data) : ''; - var fmtTime = new Date(action.tstamp).toLocaleTimeString(); + const data = action.data ? ': ' + JSON.stringify(action.data) : ''; + const fmtTime = new Date(action.tstamp).toLocaleTimeString(); switch (action.action) { case 'startEvent': @@ -145,8 +146,8 @@ function _writeAction(action) { break; case 'endEvent': - var startAction = _eventStarts[action.eventId]; - var startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : ''; + const startAction = _eventStarts[action.eventId]; + const startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : ''; console.log(chalk.dim( '[' + fmtTime + '] ' + ' ' + startAction.eventName + From 90ca49cbd6c593987522346f6fb1204e7f9c05e2 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 18 Aug 2015 17:36:35 -0700 Subject: [PATCH 19/44] [RN] Unbreak packager --- getFlowTypeCheckMiddleware.js | 2 +- react-packager/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js index 3b659761..4ef2fd0c 100644 --- a/getFlowTypeCheckMiddleware.js +++ b/getFlowTypeCheckMiddleware.js @@ -11,7 +11,7 @@ var chalk = require('chalk'); var exec = require('child_process').exec; var url = require('url'); -var Activity = require('./react-packager/src/Activity'); +var Activity = require('./react-packager').Activity; var hasWarned = {}; diff --git a/react-packager/index.js b/react-packager/index.js index 4d87733f..23a15c09 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -22,7 +22,7 @@ exports.middleware = function(options) { return server.processRequest.bind(server); }; -exports.activityEvents = Activity.eventEmitter; +exports.Activity = Activity; // Renamed "package" to "bundle". But maintain backwards // compat. From d8c8993758bcd7022c6b7b434486aae641dae5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 19 Aug 2015 12:27:27 -0700 Subject: [PATCH 20/44] [react-packager] Fix bug on Bundles Layout algorithm Summary: The layout algorithm wasn't getting deep into the sync dependencies recursively to async dependencies. --- .../__tests__/BundlesLayout-test.js | 31 +++++++ .../BundlesLayoutIntegration-test.js | 89 +++++++++++++++---- react-packager/src/BundlesLayout/index.js | 50 ++++++----- .../src/DependencyResolver/fastfs.js | 1 - 4 files changed, 133 insertions(+), 38 deletions(-) diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js index fce23226..2531b115 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -147,6 +147,37 @@ describe('BundlesLayout', () => { ); }); + pit('separate cache in which bundle is each dependency', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js'), dep('/root/a.js')], + asyncDependencies: [], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [['/root/b.js']], + }); + case '/root/b.js': + return Promise.resolve({ + dependencies: [dep('/root/b.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then( + bundles => expect(bundles).toEqual([ + [dep('/root/index.js'), dep('/root/a.js')], + [dep('/root/b.js')], + ]) + ); + }); + pit('separate cache in which bundle is each dependency', () => { DependencyResolver.prototype.getDependencies.mockImpl((path) => { switch (path) { diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 672829c2..5ad4b523 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -12,6 +12,7 @@ jest .dontMock('absolute-path') .dontMock('crypto') .dontMock('underscore') + .dontMock('path') .dontMock('../index') .dontMock('../../lib/getAssetDataFromName') .dontMock('../../DependencyResolver/crawlers') @@ -29,6 +30,7 @@ jest .dontMock('../../DependencyResolver/ModuleCache'); const Promise = require('promise'); +const path = require('path'); jest.mock('fs'); @@ -39,6 +41,18 @@ describe('BundlesLayout', () => { var fileWatcher; var fs; + const polyfills = [ + 'polyfills/prelude_dev.js', + 'polyfills/prelude.js', + 'polyfills/require.js', + 'polyfills/polyfills.js', + 'polyfills/console.js', + 'polyfills/error-guard.js', + 'polyfills/String.prototype.es6.js', + 'polyfills/Array.prototype.es6.js', + ]; + const baseFs = getBaseFs(); + beforeEach(() => { fs = require('fs'); BundlesLayout = require('../index'); @@ -54,7 +68,7 @@ describe('BundlesLayout', () => { describe('generate', () => { function newBundlesLayout() { const resolver = new DependencyResolver({ - projectRoots: ['/root'], + projectRoots: ['/root', '/' + __dirname.split('/')[1]], fileWatcher: fileWatcher, cache: new Cache(), assetExts: ['js', 'png'], @@ -75,8 +89,31 @@ describe('BundlesLayout', () => { ); } + function setMockFilesystem(mockFs) { + fs.__setMockFilesystem(Object.assign(mockFs, baseFs)); + } + + pit('should bundle single-module app', () => { + setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + modulePaths(bundles).then(paths => + expect(paths).toEqual([ + ['/root/index.js'], + ]) + ) + ); + }); + pit('should bundle dependant modules', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -84,7 +121,7 @@ describe('BundlesLayout', () => { */ require("a");`, 'a.js': ` - /**, + /** * @providesModule a */`, } @@ -98,7 +135,7 @@ describe('BundlesLayout', () => { }); pit('should split bundles for async dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -121,7 +158,7 @@ describe('BundlesLayout', () => { }); pit('should split into multiple bundles separate async dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -150,7 +187,7 @@ describe('BundlesLayout', () => { }); pit('should put related async dependencies into the same bundle', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -177,7 +214,7 @@ describe('BundlesLayout', () => { }); pit('should fully traverse sync dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -205,7 +242,7 @@ describe('BundlesLayout', () => { }); pit('should include sync dependencies async dependencies might have', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -238,7 +275,7 @@ describe('BundlesLayout', () => { }); pit('should allow duplicated dependencies across bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -273,7 +310,7 @@ describe('BundlesLayout', () => { }); pit('should put in separate bundles async dependencies of async dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -307,7 +344,7 @@ describe('BundlesLayout', () => { }); pit('should dedup same async bundle duplicated dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -340,7 +377,7 @@ describe('BundlesLayout', () => { }); pit('should put image dependencies into separate bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -365,7 +402,7 @@ describe('BundlesLayout', () => { }); pit('should put image dependencies across bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -397,7 +434,7 @@ describe('BundlesLayout', () => { }); pit('could async require asset', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -417,7 +454,7 @@ describe('BundlesLayout', () => { }); pit('should include deprecated assets into separate bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -442,7 +479,7 @@ describe('BundlesLayout', () => { }); pit('could async require deprecated asset', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -462,7 +499,7 @@ describe('BundlesLayout', () => { }); pit('should put packages into bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -491,4 +528,22 @@ describe('BundlesLayout', () => { ); }); }); + + function getBaseFs() { + const p = path.join(__dirname, '../../../DependencyResolver/polyfills').substring(1); + const root = {}; + let currentPath = root; + + p.split('/').forEach(part => { + const child = {}; + currentPath[part] = child; + currentPath = child; + }); + + polyfills.forEach(polyfill => + currentPath[polyfill.split('/')[1]] = '' + ); + + return root; + } }); diff --git a/react-packager/src/BundlesLayout/index.js b/react-packager/src/BundlesLayout/index.js index 165a33b3..50715554 100644 --- a/react-packager/src/BundlesLayout/index.js +++ b/react-packager/src/BundlesLayout/index.js @@ -38,28 +38,38 @@ class BundlesLayout { () => pending.length > 0, () => bundles, () => { - const pendingPaths = pending.shift(); - return Promise - .all(pendingPaths.map(path => - this._resolver.getDependencies(path, {dev: isDev}) - )) - .then(modulesDeps => { - let syncDependencies = Object.create(null); - modulesDeps.forEach(moduleDeps => { - moduleDeps.dependencies.forEach(dep => { - syncDependencies[dep.path] = dep - this._moduleToBundle[dep.path] = bundles.length; - }); - pending = pending.concat(moduleDeps.asyncDependencies); - }); + // pending sync dependencies we still need to explore for the current + // pending dependency + let pendingSyncDeps = pending.shift(); - syncDependencies = _.values(syncDependencies); - if (syncDependencies.length > 0) { - bundles.push(syncDependencies); + // accum variable for sync dependencies of the current pending + // dependency we're processing + const syncDependencies = Object.create(null); + + return promiseWhile( + () => pendingSyncDeps.length > 0, + () => { + const dependencies = _.values(syncDependencies); + if (dependencies.length > 0) { + bundles.push(dependencies); } - - return Promise.resolve(bundles); - }); + }, + () => { + const pendingSyncDep = pendingSyncDeps.shift(); + return this._resolver + .getDependencies(pendingSyncDep, {dev: isDev}) + .then(deps => { + deps.dependencies.forEach(dep => { + if (dep.path !== pendingSyncDep && !dep.isPolyfill) { + pendingSyncDeps.push(dep.path); + } + syncDependencies[dep.path] = dep; + this._moduleToBundle[dep.path] = bundles.length; + }); + pending = pending.concat(deps.asyncDependencies); + }); + }, + ); }, ); } diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 88429602..944d411a 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -155,7 +155,6 @@ class Fastfs extends EventEmitter { this._getAndAssertRoot(file.path).addChild(file); } - _processFileChange(type, filePath, root, fstat) { const absPath = path.join(root, filePath); if (this._ignore(absPath) || (fstat && fstat.isDirectory())) { From 2efc7eb35dacf905db1c66843fb435444bcc4c9b Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 19 Aug 2015 15:42:27 -0700 Subject: [PATCH 21/44] RN: Style Tweaks to Chrome Debugger UI --- debugger.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/debugger.html b/debugger.html index 8d377f64..24f8aea5 100644 --- a/debugger.html +++ b/debugger.html @@ -23,7 +23,7 @@ window.localStorage.removeItem('sessionID'); window.onbeforeunload = function() { if (sessionID) { return 'If you reload this page, it is going to break the debugging session. ' + - 'You should ⌘+R in the iOS simulator to reload.'; + 'You should press ⌘R in simulator to reload.'; } }; @@ -76,10 +76,10 @@ function connectToDebuggerProxy() { ws.onopen = function() { if (sessionID) { - setStatus('Debugger session #' + sessionID + ' active'); + setStatus('Debugger session #' + sessionID + ' active.'); ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)})); } else { - setStatus('Waiting, press ⌘R in simulator to reload and connect'); + setStatus('Waiting, press ⌘R in simulator to reload and connect.'); } }; @@ -126,7 +126,8 @@ function loadScript(src, callback) { font-weight: 200; } .shortcut { - font-family: monospace; + font-family: "Monaco", monospace; + font-size: medium; color: #eee; background-color: #333; padding: 4px; @@ -175,10 +176,10 @@ function loadScript(src, callback) {

- React Native JS code runs inside this Chrome tab + React Native JS code runs inside this Chrome tab.

Press ⌘⌥J to open Developer Tools. Enable Pause On Caught Exceptions for a better debugging experience.

-

Status: Loading

+

Status: Loading...

From 866df6d37c7199932e17a8213e680c751ce4e088 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 19 Aug 2015 15:15:48 -0700 Subject: [PATCH 22/44] [react-packager] Wait for haste map before accepting any requests Summary: D2319999 introduced a regression where we stopped waiting for the "build haste map" step to finish before we accept any requests. This makes sure that we block on that. Need to unbreak with this, but will follow up with a test to catch this in the future. --- react-packager/src/DependencyResolver/DependencyGraph/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 4cdf0c2e..050ab214 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -101,7 +101,7 @@ class DependencyGraph { this._fastfs.build() .then(() => { const hasteActivity = Activity.startEvent('Building Haste Map'); - this._buildHasteMap().then(() => Activity.endEvent(hasteActivity)); + return this._buildHasteMap().then(() => Activity.endEvent(hasteActivity)); }), this._buildAssetMap_DEPRECATED(), ]); From 208f54002f6a083fa938969dbfe49bcafa3e8bf1 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 19 Aug 2015 16:11:50 -0700 Subject: [PATCH 23/44] [react-packager] Make the fs mock async --- react-packager/src/__mocks__/fs.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/react-packager/src/__mocks__/fs.js b/react-packager/src/__mocks__/fs.js index b5251a44..ced46a98 100644 --- a/react-packager/src/__mocks__/fs.js +++ b/react-packager/src/__mocks__/fs.js @@ -10,7 +10,14 @@ var fs = jest.genMockFromModule('fs'); +function asyncCallback(callback) { + return function() { + setImmediate(() => callback.apply(this, arguments)); + }; +} + fs.realpath.mockImpl(function(filepath, callback) { + callback = asyncCallback(callback); var node; try { node = getToNode(filepath); @@ -24,6 +31,7 @@ fs.realpath.mockImpl(function(filepath, callback) { }); fs.readdir.mockImpl(function(filepath, callback) { + callback = asyncCallback(callback); var node; try { node = getToNode(filepath); @@ -42,6 +50,7 @@ fs.readdir.mockImpl(function(filepath, callback) { }); fs.readFile.mockImpl(function(filepath, encoding, callback) { + callback = asyncCallback(callback); if (arguments.length === 2) { callback = encoding; encoding = null; @@ -60,6 +69,7 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) { }); fs.stat.mockImpl(function(filepath, callback) { + callback = asyncCallback(callback); var node; try { node = getToNode(filepath); From c5b2dcaf9d385d796996a39424b1b22b9baf3873 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 19 Aug 2015 16:19:31 -0700 Subject: [PATCH 24/44] [react-packager] Remove unnecessary 'chalk' module mock --- react-packager/src/Activity/__mocks__/chalk.js | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 react-packager/src/Activity/__mocks__/chalk.js diff --git a/react-packager/src/Activity/__mocks__/chalk.js b/react-packager/src/Activity/__mocks__/chalk.js deleted file mode 100644 index 58bf03a6..00000000 --- a/react-packager/src/Activity/__mocks__/chalk.js +++ /dev/null @@ -1,13 +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'; - -module.exports = { - dim: s => s, -}; From 54a8fe915624cd48e4b64e1f5edf98025d31677d Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 19 Aug 2015 16:35:52 -0700 Subject: [PATCH 25/44] [react-packger] Add a timeout on transform jobs Summary: There's been a case where Babel can hang indefinitely on a file parse/transform. Possibly related to https://github.com/babel/babel/issues/2211 This adds a timeout to transform jobs and throws an error informing the user of the offending file. The timeout interval defaults to 10 seconds, but can be changed via an option. --- react-packager/src/Bundler/index.js | 6 ++- react-packager/src/JSTransformer/index.js | 64 +++++++++++++++-------- react-packager/src/Server/index.js | 4 ++ 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 854bd714..c9842b72 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -71,7 +71,11 @@ const validateOpts = declareOpts({ assetServer: { type: 'object', required: true, - } + }, + transformTimeoutInterval: { + type: 'number', + required: false, + }, }); class Bundler { diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 52bb24ad..5f5ca950 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -17,6 +17,14 @@ const workerFarm = require('worker-farm'); const readFile = Promise.denodeify(fs.readFile); +// Avoid memory leaks caused in workers. This number seems to be a good enough number +// to avoid any memory leak while not slowing down initial builds. +// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more. +const MAX_CALLS_PER_WORKER = 600; + +// Worker will timeout if one of the callers timeout. +const DEFAULT_MAX_CALL_TIME = 10000; + const validateOpts = declareOpts({ projectRoots: { type: 'array', @@ -37,16 +45,15 @@ const validateOpts = declareOpts({ type: 'object', required: true, }, + transformTimeoutInterval: { + type: 'number', + default: DEFAULT_MAX_CALL_TIME, + } }); -// Avoid memory leaks caused in workers. This number seems to be a good enough number -// to avoid any memory leak while not slowing down initial builds. -// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more. -const MAX_CALLS_PER_WORKER = 600; - class Transformer { constructor(options) { - const opts = validateOpts(options); + const opts = this._opts = validateOpts(options); this._cache = opts.cache; @@ -55,6 +62,7 @@ class Transformer { autoStart: true, maxConcurrentCallsPerWorker: 1, maxCallsPerWorker: MAX_CALLS_PER_WORKER, + maxCallTime: opts.transformTimeoutInterval, }, opts.transformModulePath); this._transform = Promise.denodeify(this._workers); @@ -74,21 +82,21 @@ class Transformer { return Promise.reject(new Error('No transfrom module')); } - var transform = this._transform; - return this._cache.get(filePath, 'transformedSource', function() { + return this._cache.get( + filePath, + 'transformedSource', // TODO: use fastfs to avoid reading file from disk again - return readFile(filePath) - .then(function(buffer) { - var sourceCode = buffer.toString(); + () => readFile(filePath).then( + buffer => { + const sourceCode = buffer.toString('utf8'); - return transform({ - sourceCode: sourceCode, - filename: filePath, - }).then( - function(res) { + return this._transform({ + sourceCode, + filename: filePath, + }).then(res => { if (res.error) { console.warn( - 'Error property on the result value form the transformer', + 'Error property on the result value from the transformer', 'module is deprecated and will be removed in future versions.', 'Please pass an error object as the first argument to the callback' ); @@ -101,15 +109,25 @@ class Transformer { sourcePath: filePath, sourceCode: sourceCode, }); - } - ); - }).catch(function(err) { - throw formatError(err, filePath); - }); - }); + }).catch(err => { + if (err.type === 'TimeoutError') { + const timeoutErr = new Error( + `TimeoutError: transforming ${filePath} took longer than ` + `${this._opts.transformTimeoutInterval / 1000} seconds.\n` + + `You can adjust timeout via the 'transformTimeoutInterval' option` + ); + timeoutErr.type = 'TimeoutError'; + throw timeoutErr; + } + + throw formatError(err, filePath); + }); + }) + ); } } + module.exports = Transformer; Transformer.TransformError = TransformError; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 8f11be3c..ba7e3d5f 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -61,6 +61,10 @@ const validateOpts = declareOpts({ type: 'array', default: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'], }, + transformTimeoutInterval: { + type: 'number', + required: false, + }, }); const bundleOpts = declareOpts({ From 06eb63b54d6aa24a4e49dfbac4e90db97a60dd37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 20 Aug 2015 08:40:09 -0700 Subject: [PATCH 26/44] [react-packager] Introduce bundle IDs and keep track of parent/child Summary: Since JS doesn't have the guarantee that once a bundle is loaded it will stay in memory (and this is something we actually don't want to enforce to keep memmory usage low), we need to keep track of parent/child relationships on the packager to pass it down to native. As part of this diff, we also introduced an ID for each bundle. The ID for a child bundle is shynthetized as the bundleID of the parent module + an index which gets incremented every time a new bundle is created. For instance given this tree: a,b c f d e g the ID for `d` will be `bundle.0.1.2`, the one for e will be `bundle.0.1.3` and the one for `g` will be `bundle.0.5.6`. This information will be useful to figure out which bundles need to be loaded when a `require.ensure` is re-written. --- .../__tests__/BundlesLayout-test.js | 92 ++++-- .../BundlesLayoutIntegration-test.js | 298 +++++++++++++----- react-packager/src/BundlesLayout/index.js | 44 ++- 3 files changed, 322 insertions(+), 112 deletions(-) diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js index 2531b115..3a792a44 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -29,8 +29,15 @@ describe('BundlesLayout', () => { }); } + function isPolyfill() { + return false; + } + function dep(path) { - return {path}; + return { + path: path, + isPolyfill: isPolyfill, + }; } pit('should bundle sync dependencies', () => { @@ -52,9 +59,11 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(bundles).toEqual([ - [dep('/root/index.js'), dep('/root/a.js')], - ]) + expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js'), dep('/root/a.js')], + children: [], + }) ); }); @@ -77,10 +86,15 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(bundles).toEqual([ - [dep('/root/index.js')], - [dep('/root/a.js')], - ]) + expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js')], + children: [{ + id:'bundle.0.1', + modules: [dep('/root/a.js')], + children: [], + }], + }) ); }); @@ -108,11 +122,19 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(bundles).toEqual([ - [dep('/root/index.js')], - [dep('/root/a.js')], - [dep('/root/b.js')], - ]) + expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js')], + children: [{ + id: 'bundle.0.1', + modules: [dep('/root/a.js')], + children: [{ + id: 'bundle.0.1.2', + modules: [dep('/root/b.js')], + children: [], + }], + }], + }) ); }); @@ -140,10 +162,15 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(bundles).toEqual([ - [dep('/root/index.js')], - [dep('/root/a.js'), dep('/root/b.js')], - ]) + expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js')], + children: [{ + id: 'bundle.0.1', + modules: [dep('/root/a.js'), dep('/root/b.js')], + children: [], + }], + }) ); }); @@ -171,10 +198,15 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then( - bundles => expect(bundles).toEqual([ - [dep('/root/index.js'), dep('/root/a.js')], - [dep('/root/b.js')], - ]) + bundles => expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js'), dep('/root/a.js')], + children: [{ + id: 'bundle.0.1', + modules: [dep('/root/b.js')], + children: [], + }], + }) ); }); @@ -184,7 +216,7 @@ describe('BundlesLayout', () => { case '/root/index.js': return Promise.resolve({ dependencies: [dep('/root/index.js'), dep('/root/a.js')], - asyncDependencies: [['/root/b.js']], + asyncDependencies: [['/root/b.js'], ['/root/c.js']], }); case '/root/a.js': return Promise.resolve({ @@ -194,13 +226,18 @@ describe('BundlesLayout', () => { case '/root/b.js': return Promise.resolve({ dependencies: [dep('/root/b.js')], - asyncDependencies: [['/root/c.js']], + asyncDependencies: [['/root/d.js']], }); case '/root/c.js': return Promise.resolve({ dependencies: [dep('/root/c.js')], asyncDependencies: [], }); + case '/root/d.js': + return Promise.resolve({ + dependencies: [dep('/root/d.js')], + asyncDependencies: [], + }); default: throw 'Undefined path: ' + path; } @@ -208,10 +245,11 @@ describe('BundlesLayout', () => { var layout = newBundlesLayout(); return layout.generateLayout(['/root/index.js']).then(() => { - expect(layout.getBundleIDForModule('/root/index.js')).toBe(0); - expect(layout.getBundleIDForModule('/root/a.js')).toBe(0); - expect(layout.getBundleIDForModule('/root/b.js')).toBe(1); - expect(layout.getBundleIDForModule('/root/c.js')).toBe(2); + expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0'); + expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0'); + expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.0.1'); + expect(layout.getBundleIDForModule('/root/c.js')).toBe('bundle.0.2'); + expect(layout.getBundleIDForModule('/root/d.js')).toBe('bundle.0.1.3'); }); }); }); diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 5ad4b523..9ff0f435 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -78,15 +78,37 @@ describe('BundlesLayout', () => { return new BundlesLayout({dependencyResolver: resolver}); } - function modulePaths(bundles) { - if (!bundles) { - return null; - } + function stripPolyfills(bundle) { + return Promise + .all([ + Promise.all( + bundle.modules.map(module => module + .getName() + .then(name => [module, name]) + ), + ), + Promise.all( + bundle.children.map(childModule => stripPolyfills(childModule)), + ), + ]) + .then(([modules, children]) => { + modules = modules + .filter(([module, name]) => { // filter polyfills + for (let p of polyfills) { + if (name.indexOf(p) !== -1) { + return false; + } + } + return true; + }) + .map(([module, name]) => module.path); - return bundles.map( - bundle => bundle.filter(module => !module.isPolyfill()) - .map(module => module.path) - ); + return { + id: bundle.id, + modules: modules, + children: children, + }; + }); } function setMockFilesystem(mockFs) { @@ -104,10 +126,12 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - modulePaths(bundles).then(paths => - expect(paths).toEqual([ - ['/root/index.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [], + }) ) ); }); @@ -128,9 +152,13 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js', '/root/a.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js', '/root/a.js'], + children: [], + }) + ) ); }); @@ -150,10 +178,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js'], + children: [], + }], + }) + ) ); }); @@ -178,11 +213,23 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js'], - ['/root/b.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [ + { + id: 'bundle.0.1', + modules: ['/root/a.js'], + children: [], + }, { + id: 'bundle.0.2', + modules: ['/root/b.js'], + children: [], + }, + ], + }) + ) ); }); @@ -206,10 +253,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/b.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/b.js'], + children: [], + }], + }) + ) ); }); @@ -234,10 +288,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js', '/root/a.js'], - ['/root/b.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js', '/root/a.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/b.js'], + children: [], + }], + }) + ) ); }); @@ -267,10 +328,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/b.js', '/root/c.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/b.js', '/root/c.js'], + children: [], + }], + }) + ) ); }); @@ -301,11 +369,24 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/c.js'], - ['/root/b.js', '/root/c.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [ + { + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/c.js'], + children: [], + }, + { + id: 'bundle.0.2', + modules: ['/root/b.js', '/root/c.js'], + children: [], + }, + ], + }) + ) ); }); @@ -335,11 +416,23 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js'], - ['/root/b.js', '/root/c.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [ + { + id: 'bundle.0.1', + modules: ['/root/a.js'], + children: [{ + id: 'bundle.0.1.2', + modules: ['/root/b.js', '/root/c.js'], + children: [], + }], + }, + ], + }) + ) ); }); @@ -369,10 +462,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/c.js', '/root/b.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/c.js', '/root/b.js'], + children: [], + }], + }) + ) ); }); @@ -394,10 +494,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/img.png'], + children: [], + }], + }) + ) ); }); @@ -425,11 +532,24 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/img.png'], - ['/root/b.js', '/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [ + { + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/img.png'], + children: [], + }, + { + id: 'bundle.0.2', + modules: ['/root/b.js', '/root/img.png'], + children: [], + }, + ], + }) + ) ); }); @@ -446,10 +566,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/img.png'], + children: [], + }], + }) + ) ); }); @@ -471,10 +598,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/img.png'], + children: [], + }], + }) + ) ); }); @@ -491,10 +625,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/img.png'], + children: [], + }], + }) + ) ); }); @@ -521,10 +662,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/aPackage/client.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/aPackage/client.js'], + children: [], + }], + }) + ) ); }); }); diff --git a/react-packager/src/BundlesLayout/index.js b/react-packager/src/BundlesLayout/index.js index 50715554..d7693a73 100644 --- a/react-packager/src/BundlesLayout/index.js +++ b/react-packager/src/BundlesLayout/index.js @@ -18,6 +18,8 @@ const validateOpts = declareOpts({ }, }); +const BUNDLE_PREFIX = 'bundle'; + /** * Class that takes care of separating the graph of dependencies into * separate bundles @@ -31,16 +33,23 @@ class BundlesLayout { } generateLayout(entryPaths, isDev) { - const bundles = []; - var pending = [entryPaths]; + var currentBundleID = 0; + const rootBundle = { + id: BUNDLE_PREFIX + '.' + currentBundleID++, + modules: [], + children: [], + }; + var pending = [{paths: entryPaths, bundle: rootBundle}]; return promiseWhile( () => pending.length > 0, - () => bundles, + () => rootBundle, () => { + const {paths, bundle} = pending.shift(); + // pending sync dependencies we still need to explore for the current // pending dependency - let pendingSyncDeps = pending.shift(); + const pendingSyncDeps = paths; // accum variable for sync dependencies of the current pending // dependency we're processing @@ -51,22 +60,31 @@ class BundlesLayout { () => { const dependencies = _.values(syncDependencies); if (dependencies.length > 0) { - bundles.push(dependencies); + bundle.modules = dependencies; } }, - () => { + index => { const pendingSyncDep = pendingSyncDeps.shift(); return this._resolver .getDependencies(pendingSyncDep, {dev: isDev}) .then(deps => { deps.dependencies.forEach(dep => { - if (dep.path !== pendingSyncDep && !dep.isPolyfill) { + if (dep.path !== pendingSyncDep && !dep.isPolyfill()) { pendingSyncDeps.push(dep.path); } syncDependencies[dep.path] = dep; - this._moduleToBundle[dep.path] = bundles.length; + this._moduleToBundle[dep.path] = bundle.id; + }); + deps.asyncDependencies.forEach(asyncDeps => { + const childBundle = { + id: bundle.id + '.' + currentBundleID++, + modules: [], + children: [], + }; + + bundle.children.push(childBundle); + pending.push({paths: asyncDeps, bundle: childBundle}); }); - pending = pending.concat(deps.asyncDependencies); }); }, ); @@ -83,11 +101,17 @@ class BundlesLayout { // Once it's not satisfied anymore, it returns what the results callback // indicates function promiseWhile(condition, result, body) { + return _promiseWhile(condition, result, body, 0); +} + +function _promiseWhile(condition, result, body, index) { if (!condition()) { return Promise.resolve(result()); } - return body().then(() => promiseWhile(condition, result, body)); + return body(index).then(() => + _promiseWhile(condition, result, body, index + 1) + ); } module.exports = BundlesLayout; From 2330fa6bce15f3b1dcf43c8e81f721b32913fbd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 20 Aug 2015 10:53:28 -0700 Subject: [PATCH 27/44] [react-packager] Integration test for `runServerHere.sh` --- .../src/DependencyResolver/DependencyGraph/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 050ab214..2daab227 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -79,7 +79,8 @@ class DependencyGraph { return this._loading; } - const crawlActivity = Activity.startEvent('fs crawl'); + const depGraphActivity = Activity.startEvent('Building Dependency Graph'); + const crawlActivity = Activity.startEvent('Crawling File System'); const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED); this._crawling = crawl(allRoots, { ignore: this._opts.ignoreFilePath, @@ -104,7 +105,9 @@ class DependencyGraph { return this._buildHasteMap().then(() => Activity.endEvent(hasteActivity)); }), this._buildAssetMap_DEPRECATED(), - ]); + ]).then(() => + Activity.endEvent(depGraphActivity) + ); return this._loading; } From c786446983322a0befd13257127d0606504b94cb Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 20 Aug 2015 12:57:34 -0700 Subject: [PATCH 28/44] [Logs] Don't print flow error twice Summary: Flow errors are already throwing an exception in js, no need to also console.error in the packager --- getFlowTypeCheckMiddleware.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js index 4ef2fd0c..bc89b70a 100644 --- a/getFlowTypeCheckMiddleware.js +++ b/getFlowTypeCheckMiddleware.js @@ -89,9 +89,6 @@ function doFlowTypecheck(res, flowroot, next) { type: 'FlowError', errors: errors, }; - console.error(chalk.yellow('flow: Error running command `' + flowCmd + - '`:\n' + JSON.stringify(error)) - ); res.writeHead(error.status, { 'Content-Type': 'application/json; charset=UTF-8', }); From 4a423d7bbacb6145660ec9eb512ddc24143564e5 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 20 Aug 2015 14:39:43 -0700 Subject: [PATCH 29/44] [react-packager] Fix error in template string and bump timeout Summary: Fix error in the template string (no plus, thinks it's a function). And bump the timeout to 30 seconds because a file is taking more than 10 seconds `js/RKJSModules/Libraries/FBComponents/FBFed/FeedStoryFragments.js` --- react-packager/src/JSTransformer/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 5f5ca950..6906d69f 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -23,7 +23,7 @@ const readFile = Promise.denodeify(fs.readFile); const MAX_CALLS_PER_WORKER = 600; // Worker will timeout if one of the callers timeout. -const DEFAULT_MAX_CALL_TIME = 10000; +const DEFAULT_MAX_CALL_TIME = 30000; const validateOpts = declareOpts({ projectRoots: { @@ -112,7 +112,7 @@ class Transformer { }).catch(err => { if (err.type === 'TimeoutError') { const timeoutErr = new Error( - `TimeoutError: transforming ${filePath} took longer than ` + `TimeoutError: transforming ${filePath} took longer than ` + `${this._opts.transformTimeoutInterval / 1000} seconds.\n` + `You can adjust timeout via the 'transformTimeoutInterval' option` ); From 1d7b858d74d0a6eb6b72983811f55f35e73845a2 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 20 Aug 2015 12:57:05 -0700 Subject: [PATCH 30/44] [react-native] Update graceful-fs and use it in _build_bundle.js --- react-packager/index.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/react-packager/index.js b/react-packager/index.js index 23a15c09..21bb1d67 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -58,14 +58,7 @@ exports.getDependencies = function(options, main) { function useGracefulFs() { var fs = require('fs'); var gracefulFs = require('graceful-fs'); - - // A bit sneaky but it's not straightforward to update all the - // modules we depend on. - Object.keys(fs).forEach(function(method) { - if (typeof fs[method] === 'function' && gracefulFs[method]) { - fs[method] = gracefulFs[method]; - } - }); + gracefulFs.gracefulify(fs); } function createServer(options) { From ef7dc94907027233784a01b28f8e5562e3ed2816 Mon Sep 17 00:00:00 2001 From: Harrison Harnisch Date: Fri, 21 Aug 2015 00:57:43 -0700 Subject: [PATCH 31/44] UI CPU and memory utilization graphs in Chrome debugging mode Summary: Chrome debugging UI is currently only showing connection state and logs in the console, leaving room for plenty of interesting information. I've pushed the UI (using the same convention set by FPS -- UI/JS) CPU and memory utilization data over the debug Websocket and tapped into the existing stream of JS calls that get ran in V8. The number of JS calls in a time interval is counted for all sub calls in a batch https://github.com/hharnisc/react-native/blob/master/packager/debugger.html#L150 The last 5 batches of JS calls are displayed in a list format. screen shot 2015-07-19 at 7 34 00 pm Charts are created with [Chart.JS](https://github.com/nnnick/Chart.js) (MIT licensed). Closes https://github.com/facebook/react-native/pull/2050 Github Author: Harrison Harnisch --- debugger.html | 302 +- packager.js | 7 + static/Chart.min.js | 11 + static/JSXTransformer-0.13.3.js | 15919 ++++++++++++++++++++++++++++++ static/react-0.13.3.min.js | 16 + 5 files changed, 16250 insertions(+), 5 deletions(-) create mode 100644 static/Chart.min.js create mode 100644 static/JSXTransformer-0.13.3.js create mode 100644 static/react-0.13.3.min.js diff --git a/debugger.html b/debugger.html index 24f8aea5..f152d9e1 100644 --- a/debugger.html +++ b/debugger.html @@ -14,8 +14,197 @@ React Native Debugger - + + + - - -