From ddaccf41d860642a92d8b1aa2325d1a1de3ba7cf Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Thu, 29 Jan 2015 17:10:49 -0800 Subject: [PATCH 001/936] Initial commit --- blacklist.js | 45 ++ launchEditor.js | 40 + launchPackager.command | 10 + packager.js | 96 +++ packager.sh | 6 + parseCommandLine.js | 52 ++ polyfill/console.js | 141 ++++ polyfill/error-guard.js | 82 ++ react-packager/.jshintrc | 86 +++ react-packager/.npmignore | 8 + react-packager/__mocks__/debug.js | 5 + react-packager/__mocks__/net.js | 26 + react-packager/example_project/bar.js | 5 + react-packager/example_project/config.json | 10 + react-packager/example_project/foo/foo.js | 23 + react-packager/example_project/index.js | 10 + react-packager/example_project/js/Channel.js | 46 ++ react-packager/example_project/js/XHR.js | 22 + react-packager/example_project/js/code.js | 51 ++ react-packager/example_project/js/main.js | 57 ++ .../example_project/public/css/index.css | 94 +++ .../example_project/public/index.html | 30 + react-packager/index.js | 100 +++ react-packager/package.json | 6 + .../src/Activity/__tests__/Activity-test.js | 79 ++ react-packager/src/Activity/index.js | 160 ++++ .../DependencyResolver/ModuleDescriptor.js | 34 + .../haste/DependencyGraph/__mocks__/fs.js | 101 +++ .../__tests__/DependencyGraph-test.js | 713 ++++++++++++++++++ .../haste/DependencyGraph/docblock.js | 88 +++ .../haste/DependencyGraph/example.js | 25 + .../haste/DependencyGraph/index.js | 450 +++++++++++ .../__tests__/HasteDependencyResolver-test.js | 152 ++++ .../src/DependencyResolver/haste/index.js | 121 +++ .../haste/polyfills/polyfills.js | 75 ++ .../haste/polyfills/prelude.js | 1 + .../haste/polyfills/prelude_dev.js | 1 + .../haste/polyfills/require.js | 626 +++++++++++++++ .../src/DependencyResolver/index.js | 12 + .../src/DependencyResolver/node/index.js | 48 ++ .../FileWatcher/__tests__/FileWatcher-test.js | 37 + react-packager/src/FileWatcher/index.js | 56 ++ react-packager/src/JSTransformer/Cache.js | 128 ++++ react-packager/src/JSTransformer/README.md | 0 .../src/JSTransformer/__tests__/Cache-test.js | 244 ++++++ .../__tests__/Transformer-test.js | 67 ++ react-packager/src/JSTransformer/index.js | 100 +++ .../src/JSTransformer/transformer.js | 33 + react-packager/src/JSTransformer/worker.js | 26 + react-packager/src/Packager/Package.js | 108 +++ .../src/Packager/__mocks__/source-map.js | 5 + .../src/Packager/__tests__/Package-test.js | 95 +++ .../src/Packager/__tests__/Packager-test.js | 83 ++ react-packager/src/Packager/base64-vlq.js | 168 +++++ react-packager/src/Packager/index.js | 115 +++ react-packager/src/fb-path-utils/index.js | 14 + 56 files changed, 5016 insertions(+) create mode 100644 blacklist.js create mode 100644 launchEditor.js create mode 100755 launchPackager.command create mode 100644 packager.js create mode 100755 packager.sh create mode 100644 parseCommandLine.js create mode 100644 polyfill/console.js create mode 100644 polyfill/error-guard.js create mode 100644 react-packager/.jshintrc create mode 100644 react-packager/.npmignore create mode 100644 react-packager/__mocks__/debug.js create mode 100644 react-packager/__mocks__/net.js create mode 100644 react-packager/example_project/bar.js create mode 100644 react-packager/example_project/config.json create mode 100644 react-packager/example_project/foo/foo.js create mode 100644 react-packager/example_project/index.js create mode 100644 react-packager/example_project/js/Channel.js create mode 100644 react-packager/example_project/js/XHR.js create mode 100644 react-packager/example_project/js/code.js create mode 100644 react-packager/example_project/js/main.js create mode 100644 react-packager/example_project/public/css/index.css create mode 100644 react-packager/example_project/public/index.html create mode 100644 react-packager/index.js create mode 100644 react-packager/package.json create mode 100644 react-packager/src/Activity/__tests__/Activity-test.js create mode 100644 react-packager/src/Activity/index.js create mode 100644 react-packager/src/DependencyResolver/ModuleDescriptor.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/example.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/index.js create mode 100644 react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js create mode 100644 react-packager/src/DependencyResolver/haste/index.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/polyfills.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/prelude.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/require.js create mode 100644 react-packager/src/DependencyResolver/index.js create mode 100644 react-packager/src/DependencyResolver/node/index.js create mode 100644 react-packager/src/FileWatcher/__tests__/FileWatcher-test.js create mode 100644 react-packager/src/FileWatcher/index.js create mode 100644 react-packager/src/JSTransformer/Cache.js create mode 100644 react-packager/src/JSTransformer/README.md create mode 100644 react-packager/src/JSTransformer/__tests__/Cache-test.js create mode 100644 react-packager/src/JSTransformer/__tests__/Transformer-test.js create mode 100644 react-packager/src/JSTransformer/index.js create mode 100644 react-packager/src/JSTransformer/transformer.js create mode 100644 react-packager/src/JSTransformer/worker.js create mode 100644 react-packager/src/Packager/Package.js create mode 100644 react-packager/src/Packager/__mocks__/source-map.js create mode 100644 react-packager/src/Packager/__tests__/Package-test.js create mode 100644 react-packager/src/Packager/__tests__/Packager-test.js create mode 100644 react-packager/src/Packager/base64-vlq.js create mode 100644 react-packager/src/Packager/index.js create mode 100644 react-packager/src/fb-path-utils/index.js diff --git a/blacklist.js b/blacklist.js new file mode 100644 index 00000000..2b710af6 --- /dev/null +++ b/blacklist.js @@ -0,0 +1,45 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ +'use strict'; + +// Don't forget to everything listed here to `testConfig.json` +// modulePathIgnorePatterns. +var sharedBlacklist = [ + 'node_modules/JSAppServer', + 'packager/react-packager', + 'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js', + 'node_modules/react-tools/src/utils/ImmutableObject.js', + 'node_modules/react-tools/src/core/ReactInstanceHandles.js', + 'node_modules/react-tools/src/event/EventPropagators.js', + 'node_modules/jest-cli', +]; + +var webBlacklist = [ + '.ios.js' +]; + +var iosBlacklist = [ + 'node_modules/react-tools/src/browser/ui/React.js', + 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', + 'node_modules/react-tools/src/browser/ReactTextComponent.js', + // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + '.web.js', + '.android.js', +]; + +function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); +} + +function blacklist(isWeb) { + return new RegExp('(' + + sharedBlacklist + .concat(isWeb ? webBlacklist : iosBlacklist) + .map(escapeRegExp) + .join('|') + + ')$' + ); +} + +module.exports = blacklist; diff --git a/launchEditor.js b/launchEditor.js new file mode 100644 index 00000000..93db9bfc --- /dev/null +++ b/launchEditor.js @@ -0,0 +1,40 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ +'use strict'; + +var fs = require('fs'); +var spawn = require('child_process').spawn; + +var firstLaunch = true; + +function guessEditor() { + if (firstLaunch) { + console.log('When you see Red Box with stack trace, you can click any ' + + 'stack frame to jump to the source file. The packager will launch your ' + + 'editor of choice. It will first look at REACT_EDITOR environment ' + + 'variable, then at EDITOR. To set it up, you can add something like ' + + 'REACT_EDITOR=atom to your .bashrc.'); + firstLaunch = false; + } + + var editor = process.env.REACT_EDITOR || process.env.EDITOR || 'subl'; + return editor; +} + +function launchEditor(fileName, lineNumber) { + if (!fs.existsSync(fileName)) { + return; + } + + var argument = fileName; + if (lineNumber) { + argument += ':' + lineNumber; + } + + var editor = guessEditor(); + console.log('Opening ' + fileName + ' with ' + editor); + spawn(editor, [argument], { stdio: ['pipe', 'pipe', process.stderr] }); +} + +module.exports = launchEditor; diff --git a/launchPackager.command b/launchPackager.command new file mode 100755 index 00000000..dc56d7ff --- /dev/null +++ b/launchPackager.command @@ -0,0 +1,10 @@ +#!/bin/bash + +# Set terminal title +echo -en "\033]0;React Packager\a" +clear + +THIS_DIR=$(dirname "$0") +$THIS_DIR/packager.sh +echo "Process terminated. Press to close the window" +read diff --git a/packager.js b/packager.js new file mode 100644 index 00000000..9d9491ac --- /dev/null +++ b/packager.js @@ -0,0 +1,96 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ +'use strict'; + +var ReactPackager = require('./react-packager'); +var blacklist = require('./blacklist.js'); +var connect = require('connect'); +var http = require('http'); +var launchEditor = require('./launchEditor.js'); +var parseCommandLine = require('./parseCommandLine.js'); +var path = require('path'); + +var options = parseCommandLine([{ + command: 'port', + default: 8081, +}]); + +if (!options.projectRoot) { + options.projectRoot = path.resolve(__dirname, '..'); +} + +console.log('\n' + +' ===============================================================\n' + +' | Running packager on port ' + options.port + '. \n' + +' | Keep this packager running while developing on any JS \n' + +' | projects. Feel free to close this tab and run your own \n' + +' | packager instance if you prefer. \n' + +' | \n' + +' | https://github.com/facebook/react-native \n' + +' | \n' + +' ===============================================================\n' +); + +process.on('uncaughtException', function(e) { + console.error(e); + console.error(e.stack); + console.error('\n >>> ERROR: could not create packager - please shut down ' + + 'any existing instances that are already running.\n\n'); +}); + +runServer(options, function() { + console.log('\nReact packager ready.\n'); +}); + +function loadRawBody(req, res, next) { + req.rawBody = ''; + req.setEncoding('utf8'); + + req.on('data', function(chunk) { + req.rawBody += chunk; + }); + + req.on('end', function() { + next(); + }); +} + +function openStackFrameInEditor(req, res, next) { + if (req.url === '/open-stack-frame') { + var frame = JSON.parse(req.rawBody); + launchEditor(frame.file, frame.lineNumber); + res.end('OK'); + } else { + next(); + } +} + +function getAppMiddleware(options) { + return ReactPackager.catalystMiddleware({ + dev: true, + projectRoot: options.projectRoot, + blacklistRE: blacklist(false), + cacheVersion: '2', + polyfillModuleNames: [ + path.resolve(__dirname, 'polyfill/console.js'), + path.resolve(__dirname, 'polyfill/error-guard.js'), + ] + }); +} + +function runServer( + options, /* {string projectRoot, bool web, bool dev} */ + readyCallback +) { + var app = connect() + .use(loadRawBody) + .use(openStackFrameInEditor) + .use(getAppMiddleware(options)) + .use(connect.static(options.projectRoot)) + .use(connect.logger()) + .use(connect.compress()) + .use(connect.errorHandler()); + + return http.createServer(app).listen(options.port, readyCallback); +} diff --git a/packager.sh b/packager.sh new file mode 100755 index 00000000..94cc7171 --- /dev/null +++ b/packager.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +ulimit -n 4096 + +THIS_DIR=$(dirname "$0") +node $THIS_DIR/packager.js diff --git a/parseCommandLine.js b/parseCommandLine.js new file mode 100644 index 00000000..5240d37d --- /dev/null +++ b/parseCommandLine.js @@ -0,0 +1,52 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * Wrapper on-top of `optimist` in order to properly support boolean flags + * and have a slightly less akward API. + * + * Usage example: + * var argv = parseCommandLine([{ + * command: 'web', + * description: 'Run in a web browser instead of iOS', + * default: true + * }]) + */ +'use strict'; + +var optimist = require('optimist'); + +function parseCommandLine(config) { + // optimist default API requires you to write the command name three time + // This is a small wrapper to accept an object instead + for (var i = 0; i < config.length; ++i) { + optimist + .boolean(config[i].command) + .default(config[i].command, config[i].default) + .describe(config[i].command, config[i].description); + } + var argv = optimist.argv; + + // optimist doesn't have support for --dev=false, instead it returns 'false' + for (var i = 0; i < config.length; ++i) { + var command = config[i].command; + if (argv[command] === undefined) { + argv[command] = config[i].default; + } + if (argv[command] === 'true') { + argv[command] = true; + } + if (argv[command] === 'false') { + argv[command] = false; + } + } + + // Show --help + if (argv.help || argv.h) { + optimist.showHelp(); + process.exit(); + } + + return argv; +} + +module.exports = parseCommandLine; diff --git a/polyfill/console.js b/polyfill/console.js new file mode 100644 index 00000000..4c9ddce1 --- /dev/null +++ b/polyfill/console.js @@ -0,0 +1,141 @@ +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This pipes all of our console logging functions to native logging so that + * JavaScript errors in required modules show up in Xcode via NSLog. + * + * @provides console + * @polyfill + */ + +(function(global) { + + var OBJECT_COLUMN_NAME = '(index)'; + + function setupConsole(global) { + + if (!global.nativeLoggingHook) { + return; + } + + function doNativeLog() { + var str = Array.prototype.map.call(arguments, function(arg) { + if (arg == null) { + return arg === null ? 'null' : 'undefined'; + } else if (typeof arg === 'string') { + return '"' + arg + '"'; + } else { + // Perform a try catch, just in case the object has a circular + // reference or stringify throws for some other reason. + try { + return JSON.stringify(arg); + } catch (e) { + if (typeof arg.toString === 'function') { + try { + return arg.toString(); + } catch (e) { + return 'unknown'; + } + } + } + } + }).join(', '); + global.nativeLoggingHook(str); + }; + + var repeat = function(element, n) { + return Array.apply(null, Array(n)).map(function() { return element; }); + }; + + function consoleTablePolyfill(rows) { + // convert object -> array + if (!Array.isArray(rows)) { + var data = rows; + rows = []; + for (var key in data) { + if (data.hasOwnProperty(key)) { + var row = data[key]; + row[OBJECT_COLUMN_NAME] = key; + rows.push(row); + } + } + } + if (rows.length === 0) { + global.nativeLoggingHook(''); + return; + } + + var columns = Object.keys(rows[0]).sort(); + var stringRows = []; + var columnWidths = []; + + // Convert each cell to a string. Also + // figure out max cell width for each column + columns.forEach(function(k, i) { + columnWidths[i] = k.length; + for (var j = 0; j < rows.length; j++) { + var cellStr = rows[j][k].toString(); + stringRows[j] = stringRows[j] || []; + stringRows[j][i] = cellStr; + columnWidths[i] = Math.max(columnWidths[i], cellStr.length); + } + }); + + // Join all elements in the row into a single string with | separators + // (appends extra spaces to each cell to make separators | alligned) + var joinRow = function(row, space) { + var cells = row.map(function(cell, i) { + var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join(''); + return cell + extraSpaces; + }); + space = space || ' '; + return cells.join(space + '|' + space); + }; + + var separators = columnWidths.map(function(columnWidth) { + return repeat('-', columnWidth).join(''); + }); + var separatorRow = joinRow(separators, '-'); + var header = joinRow(columns); + var table = [header, separatorRow]; + + for (var i = 0; i < rows.length; i++) { + table.push(joinRow(stringRows[i])); + } + + // Notice extra empty line at the beginning. + // Native logging hook adds "RCTLog >" at the front of every + // logged string, which would shift the header and screw up + // the table + global.nativeLoggingHook('\n' + table.join('\n')); + }; + + global.console = { + error: doNativeLog, + info: doNativeLog, + log: doNativeLog, + warn: doNativeLog, + table: consoleTablePolyfill + }; + + }; + + if (typeof module !== 'undefined') { + module.exports = setupConsole; + } else { + setupConsole(global); + } + +})(this); diff --git a/polyfill/error-guard.js b/polyfill/error-guard.js new file mode 100644 index 00000000..687a4a19 --- /dev/null +++ b/polyfill/error-guard.js @@ -0,0 +1,82 @@ + +/** + * The particular require runtime that we are using looks for a global + * `ErrorUtils` object and if it exists, then it requires modules with the + * error handler specified via ErrorUtils.setGlobalHandler by calling the + * require function with applyWithGuard. Since the require module is loaded + * before any of the modules, this ErrorUtils must be defined (and the handler + * set) globally before requiring anything. + */ +/* eslint global-strict:0 */ +(function(global) { + var ErrorUtils = { + _inGuard: 0, + _globalHandler: null, + setGlobalHandler: function(fun) { + ErrorUtils._globalHandler = fun; + }, + reportError: function(error) { + Error._globalHandler && ErrorUtils._globalHandler(error); + }, + applyWithGuard: function(fun, context, args) { + try { + ErrorUtils._inGuard++; + return fun.apply(context, args); + } catch (e) { + ErrorUtils._globalHandler && ErrorUtils._globalHandler(e); + } finally { + ErrorUtils._inGuard--; + } + }, + applyWithGuardIfNeeded: function(fun, context, args) { + if (ErrorUtils.inGuard()) { + return fun.apply(context, args); + } else { + ErrorUtils.applyWithGuard(fun, context, args); + } + }, + inGuard: function() { + return ErrorUtils._inGuard; + }, + guard: function(fun, name, context) { + if (typeof fun !== "function") { + console.warn('A function must be passed to ErrorUtils.guard, got ', fun); + return null; + } + name = name || fun.name || ''; + function guarded() { + return ( + ErrorUtils.applyWithGuard( + fun, + context || this, + arguments, + null, + name + ) + ); + } + + return guarded; + } + }; + global.ErrorUtils = ErrorUtils; + + /** + * This is the error handler that is called when we encounter an exception + * when loading a module. + */ + function setupErrorGuard() { + var onError = function(e) { + global.console.error( + 'Error: ' + + '\n stack: ' + e.stack + + '\n line: ' + e.line + + '\n message: ' + e.message, + e + ); + }; + global.ErrorUtils.setGlobalHandler(onError); + } + + setupErrorGuard(); +})(this); diff --git a/react-packager/.jshintrc b/react-packager/.jshintrc new file mode 100644 index 00000000..7a3f79a7 --- /dev/null +++ b/react-packager/.jshintrc @@ -0,0 +1,86 @@ +{ + "-W093": true, + "asi": false, + "bitwise": true, + "boss": false, + "browser": false, + "camelcase": true, + "couch": false, + "curly": true, + "debug": false, + "devel": true, + "dojo": false, + "eqeqeq": true, + "eqnull": true, + "esnext": true, + "evil": false, + "expr": true, + "forin": false, + "freeze": true, + "funcscope": true, + "gcl": false, + "globals": { + "Promise": true, + "React": true, + "XMLHttpRequest": true, + "document": true, + "location": true, + "window": true + }, + "globalstrict": true, + "immed": false, + "indent": 2, + "iterator": false, + "jquery": false, + "lastsemic": false, + "latedef": false, + "laxbreak": true, + "laxcomma": false, + "loopfunc": false, + "maxcomplexity": false, + "maxdepth": false, + "maxerr": 50, + "maxlen": 80, + "maxparams": false, + "maxstatements": false, + "mootools": false, + "moz": false, + "multistr": false, + "newcap": true, + "noarg": true, + "node": true, + "noempty": false, + "nonbsp": true, + "nonew": true, + "nonstandard": false, + "notypeof": false, + "noyield": false, + "phantom": false, + "plusplus": false, + "predef": [ + "afterEach", + "beforeEach", + "describe", + "expect", + "it", + "jest", + "pit" + ], + "proto": false, + "prototypejs": false, + "quotmark": true, + "rhino": false, + "scripturl": false, + "shadow": false, + "smarttabs": false, + "strict": false, + "sub": false, + "supernew": false, + "trailing": true, + "undef": true, + "unused": true, + "validthis": false, + "worker": false, + "wsh": false, + "yui": false +} diff --git a/react-packager/.npmignore b/react-packager/.npmignore new file mode 100644 index 00000000..2113f106 --- /dev/null +++ b/react-packager/.npmignore @@ -0,0 +1,8 @@ +*~ +*.swm +*.swn +*.swp +*.DS_STORE +npm-debug.log +.cache +node_modules diff --git a/react-packager/__mocks__/debug.js b/react-packager/__mocks__/debug.js new file mode 100644 index 00000000..d35fffd4 --- /dev/null +++ b/react-packager/__mocks__/debug.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function() { + return function() {}; +}; diff --git a/react-packager/__mocks__/net.js b/react-packager/__mocks__/net.js new file mode 100644 index 00000000..661fb196 --- /dev/null +++ b/react-packager/__mocks__/net.js @@ -0,0 +1,26 @@ +var EventEmitter = require('events').EventEmitter; +var servers = {}; +exports.createServer = function(listener) { + var server = { + _listener: listener, + + socket: new EventEmitter(), + + listen: function(path) { + listener(this.socket); + servers[path] = this; + } + }; + + server.socket.setEncoding = function() {}; + server.socket.write = function(data) { + this.emit('data', data); + }; + + return server; +}; + +exports.connect = function(options) { + var server = servers[options.path || options.port]; + return server.socket; +}; diff --git a/react-packager/example_project/bar.js b/react-packager/example_project/bar.js new file mode 100644 index 00000000..cc56ce6e --- /dev/null +++ b/react-packager/example_project/bar.js @@ -0,0 +1,5 @@ +/** + * @providesModule bar + */ + + module.exports = setInterval; \ No newline at end of file diff --git a/react-packager/example_project/config.json b/react-packager/example_project/config.json new file mode 100644 index 00000000..0acdcb51 --- /dev/null +++ b/react-packager/example_project/config.json @@ -0,0 +1,10 @@ +{ + "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 new file mode 100644 index 00000000..c45d9aba --- /dev/null +++ b/react-packager/example_project/foo/foo.js @@ -0,0 +1,23 @@ +/** + * @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 new file mode 100644 index 00000000..2943d187 --- /dev/null +++ b/react-packager/example_project/index.js @@ -0,0 +1,10 @@ +/** + * @providesModule index + * @jsx React.DOM + */ + +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 new file mode 100644 index 00000000..d3cbae1c --- /dev/null +++ b/react-packager/example_project/js/Channel.js @@ -0,0 +1,46 @@ +/** + * @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 new file mode 100644 index 00000000..d9e0563f --- /dev/null +++ b/react-packager/example_project/js/XHR.js @@ -0,0 +1,22 @@ +/** + * @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 new file mode 100644 index 00000000..70067859 --- /dev/null +++ b/react-packager/example_project/js/code.js @@ -0,0 +1,51 @@ +/** + * @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 new file mode 100644 index 00000000..58847092 --- /dev/null +++ b/react-packager/example_project/js/main.js @@ -0,0 +1,57 @@ +/** + * @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 new file mode 100644 index 00000000..7d36bf2c --- /dev/null +++ b/react-packager/example_project/public/css/index.css @@ -0,0 +1,94 @@ +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 new file mode 100644 index 00000000..b19685d5 --- /dev/null +++ b/react-packager/example_project/public/index.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/react-packager/index.js b/react-packager/index.js new file mode 100644 index 00000000..52562cfe --- /dev/null +++ b/react-packager/index.js @@ -0,0 +1,100 @@ +var Packager = require('./src/Packager'); +var Activity = require('./src/Activity'); +var url = require('url'); + +exports.buildPackageFromUrl = function(options, reqUrl) { + Activity.disable(); + var packager = createPackager(options); + var params = getOptionsFromPath(url.parse(reqUrl).pathname); + return packager.package( + params.main, + params.runModule, + params.sourceMapUrl + ).then(function(p) { + packager.kill(); + return p; + }); +}; + +exports.catalystMiddleware = function(options) { + var packager = createPackager(options); + + return function(req, res, next) { + var options; + if (req.url.match(/\.bundle$/)) { + options = getOptionsFromPath(url.parse(req.url).pathname); + packager.package( + options.main, + options.runModule, + options.sourceMapUrl + ).then( + function(package) { + res.end(package.getSource()); + }, + function(error) { + handleError(res, error); + } + ).done(); + } else if (req.url.match(/\.map$/)) { + options = getOptionsFromPath(url.parse(req.url).pathname); + packager.package( + options.main, + options.runModule, + options.sourceMapUrl + ).then( + function(package) { + res.end(JSON.stringify(package.getSourceMap())); + }, + function(error) { + handleError(res, error); + } + ).done(); + } else { + next(); + } + }; +}; + +function getOptionsFromPath(pathname) { + var parts = pathname.split('.'); + // Remove the leading slash. + var main = parts[0].slice(1) + '.js'; + return { + runModule: parts.slice(1).some(function(part) { + return part === 'runModule'; + }), + main: main, + sourceMapUrl: parts.slice(0, -1).join('.') + '.map' + }; +} + +function handleError(res, error) { + res.writeHead(500, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + + if (error.type === 'TransformError') { + res.end(JSON.stringify(error)); + } 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 createPackager(options) { + return new Packager({ + projectRoot: options.projectRoot, + blacklistRE: options.blacklistRE, + polyfillModuleNames: options.polyfillModuleNames || [], + runtimeCode: options.runtimeCode, + cacheVersion: options.cacheVersion, + resetCache: options.resetCache, + dev: options.dev, + }); +} + +exports.kill = Packager.kill; diff --git a/react-packager/package.json b/react-packager/package.json new file mode 100644 index 00000000..db3e9e14 --- /dev/null +++ b/react-packager/package.json @@ -0,0 +1,6 @@ +{ + "name": "react-packager", + "version": "0.0.1", + "description": "", + "main": "index.js" +} diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js new file mode 100644 index 00000000..7a2bdf48 --- /dev/null +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -0,0 +1,79 @@ +jest.autoMockOff(); + +describe('Activity', function() { + var Activity; + + var origConsoleLog = console.log; + + beforeEach(function() { + console.log = jest.genMockFn(); + Activity = require('../'); + }); + + afterEach(function() { + 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}; + + Activity.startEvent(EVENT_NAME, DATA); + jest.runOnlyPendingTimers(); + + expect(console.log.mock.calls.length).toBe(1); + var 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}; + + var 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]; + 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 expired eventId', function() { + var eid = Activity.startEvent('', ''); + Activity.endEvent(eid); + + expect(function() { + Activity.endEvent(eid); + }).toThrow('event(1) has already ended!'); + }); + }); + + describe('signal', function() { + it('writes a SIGNAL event out to the console', function() { + var EVENT_NAME = 'EVENT_NAME'; + var 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]; + 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 new file mode 100644 index 00000000..5387cbd4 --- /dev/null +++ b/react-packager/src/Activity/index.js @@ -0,0 +1,160 @@ +var COLLECTION_PERIOD = 1000; + +var _endedEvents = Object.create(null); +var _eventStarts = Object.create(null); +var _queuedActions = []; +var _scheduledCollectionTimer = null; +var _uuid = 1; +var _enabled = true; + +function endEvent(eventId) { + var eventEndTime = Date.now(); + + if (!_eventStarts[eventId]) { + _throw('event(' + eventId + ') is not a valid event id!'); + } + + if (_endedEvents[eventId]) { + _throw('event(' + eventId + ') has already ended!'); + } + + _scheduleAction({ + action: 'endEvent', + eventId: eventId, + tstamp: eventEndTime + }); + _endedEvents[eventId] = true; +} + +function signal(eventName, data) { + var signalTime = Date.now(); + + if (eventName == null) { + _throw('No event name specified'); + } + + if (data == null) { + data = null; + } + + _scheduleAction({ + action: 'signal', + data: data, + eventName: eventName, + tstamp: signalTime + }); +} + +function startEvent(eventName, data) { + var eventStartTime = Date.now(); + + if (eventName == null) { + _throw('No event name specified'); + } + + if (data == null) { + data = null; + } + + var eventId = _uuid++; + var action = { + action: 'startEvent', + data: data, + eventId: eventId, + eventName: eventName, + tstamp: eventStartTime, + }; + _scheduleAction(action); + _eventStarts[eventId] = action; + + return eventId; +} + +function disable() { + _enabled = false; +} + +function _runCollection() { + /* jshint -W084 */ + var action; + while ((action = _queuedActions.shift())) { + _writeAction(action); + } + + _scheduledCollectionTimer = null; +} + +function _scheduleAction(action) { + _queuedActions.push(action); + + if (_scheduledCollectionTimer === null) { + _scheduledCollectionTimer = setTimeout(_runCollection, COLLECTION_PERIOD); + } +} + +/** + * This a utility function that throws an error message. + * + * The only purpose of this utility is to make APIs like + * startEvent/endEvent/signal inlineable in the JIT. + * + * (V8 can't inline functions that statically contain a `throw`, and probably + * won't be adding such a non-trivial optimization anytime soon) + */ +function _throw(msg) { + var err = new Error(msg); + + // Strip off the call to _throw() + var stack = err.stack.split('\n'); + stack.splice(1, 1); + err.stack = stack.join('\n'); + + throw err; +} + +function _writeAction(action) { + if (!_enabled) { + return; + } + + var data = action.data ? ': ' + JSON.stringify(action.data) : ''; + var fmtTime = new Date(action.tstamp).toLocaleTimeString(); + + switch (action.action) { + case 'startEvent': + console.log( + '[' + fmtTime + '] ' + + ' ' + action.eventName + + data + ); + break; + + case 'endEvent': + var startAction = _eventStarts[action.eventId]; + console.log( + '[' + fmtTime + '] ' + + ' ' + startAction.eventName + + '(' + (action.tstamp - startAction.tstamp) + 'ms)' + + data + ); + delete _eventStarts[action.eventId]; + break; + + case 'signal': + console.log( + '[' + fmtTime + '] ' + + ' ' + action.eventName + '' + + data + ); + break; + + default: + _throw('Unexpected scheduled action type: ' + action.action); + } +} + + +exports.endEvent = endEvent; +exports.signal = signal; +exports.startEvent = startEvent; +exports.disable = disable; diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js new file mode 100644 index 00000000..0898767a --- /dev/null +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -0,0 +1,34 @@ +function ModuleDescriptor(fields) { + if (!fields.id) { + throw new Error('Missing required fields id'); + } + this.id = fields.id; + + if (!fields.path) { + throw new Error('Missing required fields path'); + } + this.path = fields.path; + + if (!fields.dependencies) { + throw new Error('Missing required fields dependencies'); + } + this.dependencies = fields.dependencies; + + this.resolveDependency = fields.resolveDependency; + + this.entry = fields.entry || false; + + this.isPolyfill = fields.isPolyfill || false; + + this._fields = fields; +} + +ModuleDescriptor.prototype.toJSON = function() { + return { + id: this.id, + path: this.path, + dependencies: this.dependencies + } +}; + +module.exports = ModuleDescriptor; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js new file mode 100644 index 00000000..de3622d9 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js @@ -0,0 +1,101 @@ +'use strict'; + +var fs = jest.genMockFromModule('fs'); + +fs.realpath.mockImpl(function(filepath, callback) { + var node; + try { + node = getToNode(filepath); + } catch (e) { + return callback(e); + } + if (node && typeof node === 'object' && node.SYMLINK != null) { + return callback(null, node.SYMLINK); + } + callback(null, filepath); +}); + +fs.readdir.mockImpl(function(filepath, callback) { + var node; + try { + node = getToNode(filepath); + if (node && typeof node === 'object' && node.SYMLINK != null) { + node = getToNode(node.SYMLINK); + } + } catch (e) { + return callback(e); + } + + if (!(node && typeof node === 'object' && node.SYMLINK == null)) { + return callback(new Error(filepath + ' is not a directory.')); + } + + callback(null, Object.keys(node)); +}); + +fs.readFile.mockImpl(function(filepath, encoding, callback) { + try { + var node = getToNode(filepath); + // dir check + if (node && typeof node === 'object' && node.SYMLINK == null) { + callback(new Error('Trying to read a dir, ESIDR, or whatever')); + } + return callback(null, node); + } catch (e) { + return callback(e); + } +}); + +fs.lstat.mockImpl(function(filepath, callback) { + var node; + try { + node = getToNode(filepath); + } catch (e) { + return callback(e); + } + + if (node && typeof node === 'object' && node.SYMLINK == null) { + callback(null, { + isDirectory: function() { + return true; + }, + isSymbolicLink: function() { + return false; + } + }); + } else { + callback(null, { + isDirectory: function() { + return false; + }, + isSymbolicLink: function() { + if (typeof node === 'object' && node.SYMLINK) { + return true; + } + return false; + } + }); + } +}); + +var filesystem; + +fs.__setMockFilesystem = function(object) { + filesystem = object; + return filesystem; +}; + +function getToNode(filepath) { + var parts = filepath.split('/'); + if (parts[0] !== '') { + throw new Error('Make sure all paths are absolute.'); + } + var node = filesystem; + parts.slice(1).forEach(function(part) { + node = node[part]; + }); + + return node; +} + +module.exports = fs; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js new file mode 100644 index 00000000..9eeb9685 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -0,0 +1,713 @@ +'use strict'; + +jest + .dontMock('../index') + .dontMock('q') + .dontMock('path') + .dontMock('absolute-path') + .dontMock('../../../../fb-path-utils') + .dontMock('../docblock') + .setMock('../../../ModuleDescriptor', function(data) {return data;}); + +var q = require('q'); + +describe('DependencyGraph', function() { + var DependencyGraph; + var fileWatcher; + var fs; + + beforeEach(function() { + fs = require('fs'); + DependencyGraph = require('../index'); + + fileWatcher = { + getWatcher: function() { + return q({ + on: function() { + return this; + } + }); + } + }; + }); + + describe('getOrderedDependencies', function() { + pit('should get dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', path: '/root/a.js', dependencies: []}, + ]); + }); + }); + + pit('should get recursive dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")', + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + 'require("index")', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', path: '/root/a.js', dependencies: ['index']}, + ]); + }); + }); + + pit('should work with packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); + }); + }); + + pit('can have multiple modules with the same name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("b")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', + ].join('\n'), + 'c.js': [ + '/**', + ' * @providesModule c', + ' */', + ].join('\n'), + 'somedir': { + 'somefile.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("c")', + ].join('\n') + } + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) + .toEqual([ + { id: 'index', + path: '/root/somedir/somefile.js', + dependencies: ['c'] + }, + { id: 'c', + path: '/root/c.js', + dependencies: [] + }, + ]); + }); + }); + + pit('providesModule wins when conflict with package', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule aPackage', + ' */', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage', + path: '/root/b.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should be forgiving with missing requires', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("lolomg")', + ].join('\n') + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['lolomg'] + } + ]); + }); + }); + + pit('should work with packages with subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + } + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'] + }, + { id: 'aPackage/subdir/lolynot', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should work with packages with symlinked subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'symlinkedPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + }, + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { SYMLINK: '/symlinkedPackage' }, + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'] + }, + { id: 'aPackage/subdir/lolynot', + path: '/symlinkedPackage/subdir/lolynot.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should work with relative modules in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'require("./subdir/lolynot")', + 'subdir': { + 'lolynot.js': 'require("../other")' + }, + 'other.js': 'some code' + } + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['./subdir/lolynot'] + }, + { id: 'aPackage/subdir/lolynot', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: ['../other'] + }, + { id: 'aPackage/other', + path: '/root/aPackage/other.js', + dependencies: [] + }, + ]); + }); + }); + }); + + describe('file watch updating', function() { + var fileWatcher; + var triggerFileChange; + + beforeEach(function() { + fileWatcher = { + getWatcher: function() { + return q({ + on: function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + triggerFileChange = callback; + return this; + } + }); + } + }; + }); + + pit('updates module dependencies', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + filesystem.root['index.js'] = + filesystem.root['index.js'].replace('require("foo")', ''); + triggerFileChange('change', 'index.js'); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file change', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + filesystem.root['index.js'] = + filesystem.root['index.js'].replace('require("foo")', ''); + triggerFileChange('change', 'index.js'); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file delete', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + delete filesystem.root.foo; + triggerFileChange('delete', 'foo.js'); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + filesystem.root['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js'); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js'); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'] + }, + { id: 'bar', + path: '/root/bar.js', + dependencies: ['foo'] + }, + { id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'] + }, + ]); + }); + }); + }); + + pit('runs changes through ignore filter', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + root: root, + fileWatcher: fileWatcher, + ignoreFilePath: function(filePath) { + if (filePath === '/root/bar.js') { + return true; + } + return false; + } + }); + return dgraph.load().then(function() { + filesystem.root['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js'); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js'); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'] + }, + { id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'] + }, + ]); + }); + }); + }); + + pit('should ignore directory updates', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + triggerFileChange('change', 'aPackage', '/root', { + isDirectory: function(){ return true; } + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + { id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'] + }, + ]); + }); + }); + }); + }); +}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js new file mode 100644 index 00000000..52cac03b --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js @@ -0,0 +1,88 @@ +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/; + +var ltrimRe = /^\s*/; +/** + * @param {String} contents + * @return {String} + */ +function extract(contents) { + var match = contents.match(docblockRe); + if (match) { + return match[0].replace(ltrimRe, '') || ''; + } + return ''; +} + + +var commentStartRe = /^\/\*\*?/; +var commentEndRe = /\*\/$/; +var wsRe = /[\t ]+/g; +var stringStartRe = /(\r?\n|^) *\*/g; +var multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g; +var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g; + +/** + * @param {String} contents + * @return {Array} + */ +function parse(docblock) { + docblock = docblock + .replace(commentStartRe, '') + .replace(commentEndRe, '') + .replace(wsRe, ' ') + .replace(stringStartRe, '$1'); + + // Normalize multi-line directives + var prev = ''; + while (prev != docblock) { + prev = docblock; + docblock = docblock.replace(multilineRe, "\n$1 $2\n"); + } + docblock = docblock.trim(); + + var result = []; + var match; + while (match = propertyRe.exec(docblock)) { + result.push([match[1], match[2]]); + } + + return result; +} + +/** + * Same as parse but returns an object of prop: value instead of array of paris + * If a property appers more than once the last one will be returned + * + * @param {String} contents + * @return {Object} + */ +function parseAsObject(docblock) { + var pairs = parse(docblock); + var result = {}; + for (var i = 0; i < pairs.length; i++) { + result[pairs[i][0]] = pairs[i][1]; + } + return result; +} + + +exports.extract = extract; +exports.parse = parse; +exports.parseAsObject = parseAsObject; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js new file mode 100644 index 00000000..02e6c592 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js @@ -0,0 +1,25 @@ +var path = require('path'); +var DependecyGraph = require('./'); + +var example_project = path.resolve(__dirname, '../../../../example_project'); +var watcher = new (require('../../../FileWatcher'))({projectRoot: example_project}); +var graph = new DependecyGraph({ + fileWatcher: watcher, + root: example_project +}); + +graph.load().then(function() { + var index = path.join(example_project, 'index.js'); + console.log(graph.getOrderedDependencies(index)); +}).done(); + +watcher.getWatcher().then(function(watcher) { + watcher.on('all', function() { + setImmediate(function() { + graph.load().then(function() { + var index = path.join(example_project, 'index.js'); + console.log(graph.getOrderedDependencies(index)); + }); + }) + }); +}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js new file mode 100644 index 00000000..d255a3c3 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -0,0 +1,450 @@ +'use strict'; + +var ModuleDescriptor = require('../../ModuleDescriptor'); +var q = require('q'); +var fs = require('fs'); +var docblock = require('./docblock'); +var path = require('path'); +var isAbsolutePath = require('absolute-path'); +var debug = require('debug')('DependecyGraph'); + +var readFile = q.nfbind(fs.readFile); +var readDir = q.nfbind(fs.readdir); +var lstat = q.nfbind(fs.lstat); +var realpath = q.nfbind(fs.realpath); + +function DependecyGraph(options) { + this._root = options.root; + this._ignoreFilePath = options.ignoreFilePath || function(){}; + this._loaded = false; + this._queue = [this._root]; + this._graph = Object.create(null); + this._packageByRoot = Object.create(null); + this._packagesById = Object.create(null); + this._moduleById = Object.create(null); + this._fileWatcher = options.fileWatcher; + + // Kick off the search process to precompute the dependency graph. + this._init(); +} + +DependecyGraph.prototype.load = function() { + return this._loading || (this._loading = this._search()); +}; + +/** + * Given an entry file return an array of all the dependent module descriptors. + */ +DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { + var absolutePath; + if (!isAbsolutePath(entryPath)) { + absolutePath = path.join(this._root, entryPath); + } else { + absolutePath = entryPath; + } + + var module = this._graph[absolutePath]; + if (module == null) { + throw new Error('Module with path "' + absolutePath + '" is not in graph'); + } + + var self = this; + var deps = []; + var visited = Object.create(null); + + // Node haste sucks. Id's aren't unique. So to make sure our entry point + // is the thing that ends up in our dependency list. + var graphMap = Object.create(this._moduleById); + graphMap[module.id] = module; + + // Recursively collect the dependency list. + function collect(module) { + deps.push(module); + + module.dependencies.forEach(function(name) { + var id = sansExtJs(name); + var dep = self.resolveDependency(module, id); + + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + name, + module.id + ); + return; + } + + if (!visited[dep.id]) { + visited[dep.id] = true; + collect(dep); + } + }); + } + + visited[module.id] = true; + collect(module); + + return deps; +}; + +/** + * Given a module descriptor `fromModule` return the module descriptor for + * the required module `depModuleId`. It could be top-level or relative, + * or both. + */ +DependecyGraph.prototype.resolveDependency = function( + fromModule, + depModuleId +) { + var packageJson, modulePath, dep; + + // Package relative modules starts with '.' or '..'. + if (depModuleId[0] !== '.') { + + // 1. `depModuleId` is simply a top-level `providesModule`. + // 2. `depModuleId` is a package module but given the full path from the + // package, i.e. package_name/module_name + if (this._moduleById[sansExtJs(depModuleId)]) { + return this._moduleById[sansExtJs(depModuleId)]; + } + + // 3. `depModuleId` is a package and it's depending on the "main" + // resolution. + packageJson = this._packagesById[depModuleId]; + + // We are being forgiving here and raising an error because we could be + // processing a file that uses it's own require system. + if (packageJson == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + depModuleId, + fromModule.id + ); + return; + } + + var main = packageJson.main || 'index'; + modulePath = withExtJs(path.join(packageJson._root, main)); + dep = this._graph[modulePath]; + if (dep == null) { + throw new Error( + 'Cannot find package main file for pacakge: ' + packageJson._root + ); + } + return dep; + } else { + + // 4. `depModuleId` is a module defined in a package relative to + // `fromModule`. + packageJson = this._lookupPackage(fromModule.path); + + if (packageJson == null) { + throw new Error( + 'Expected relative module lookup from ' + fromModule.id + ' to ' + + depModuleId + ' to be within a package but no package.json found.' + ); + } + + // Example: depModuleId: ../a/b + // fromModule.path: /x/y/z + // modulePath: /x/y/a/b + var dir = path.dirname(fromModule.path); + modulePath = withExtJs(path.join(dir, depModuleId)); + + dep = this._graph[modulePath]; + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.' + + ' Inferred required module path is %s', + depModuleId, + fromModule.id, + modulePath + ); + return null; + } + + return dep; + } +}; + +/** + * Intiates the filewatcher and kicks off the search process. + */ +DependecyGraph.prototype._init = function() { + var processChange = this._processFileChange.bind(this); + var loadingWatcher = this._fileWatcher.getWatcher(); + + this._loading = this.load() + .then(function() { + return loadingWatcher; + }) + .then(function(watcher) { + watcher.on('all', processChange); + }); +}; + +/** + * Implements a DFS over the file system looking for modules and packages. + */ +DependecyGraph.prototype._search = function() { + var self = this; + var dir = this._queue.shift(); + + if (dir == null) { + return q.Promise.resolve(this._graph); + } + + // Steps: + // 1. Read a dir and stat all the entries. + // 2. Filter the files and queue up the directories. + // 3. Process any package.json in the files + // 4. recur. + return readDir(dir) + .then(function(files){ + return q.all(files.map(function(filePath) { + return realpath(path.join(dir, filePath)); + })); + }) + .then(function(filePaths) { + filePaths = filePaths.filter(function(filePath) { + return !self._ignoreFilePath(filePath); + }); + + var statsP = filePaths.map(function(filePath) { + return lstat(filePath); + }); + + return [ + filePaths, + q.all(statsP) + ]; + }) + .spread(function(files, stats) { + var modulePaths = files.filter(function(filePath, i) { + if (stats[i].isDirectory()) { + self._queue.push(filePath); + return false; + } + + if (stats[i].isSymbolicLink()) { + return false; + } + + return filePath.match(/\.js$/); + }); + + var processing = self._findAndProcessPackage(files, dir) + .then(function() { + return q.all(modulePaths.map(self._processModule.bind(self))); + }); + + return q.all([ + processing, + self._search() + ]); + }) + .then(function() { + return self; + }); +}; + +/** + * Given a list of files find a `package.json` file, and if found parse it + * and update indices. + */ +DependecyGraph.prototype._findAndProcessPackage = function(files, root) { + var self = this; + + var packagePath; + for (var i = 0; i < files.length ; i++) { + var file = files[i]; + if (path.basename(file) === 'package.json') { + packagePath = file; + break; + } + } + + if (packagePath != null) { + return readFile(packagePath, 'utf8') + .then(function(content) { + var packageJson = JSON.parse(content); + if (packageJson.name == null) { + + debug( + 'WARNING: package.json `%s` is missing a name field', + packagePath + ); + return q(); + } + + packageJson._root = root; + self._packageByRoot[root] = packageJson; + self._packagesById[packageJson.name] = packageJson; + + return packageJson; + }); + } else { + return q(); + } +}; + +/** + * Parse a module and update indices. + */ +DependecyGraph.prototype._processModule = function(modulePath) { + var self = this; + return readFile(modulePath, 'utf8') + .then(function(content) { + var moduleDocBlock = docblock.parseAsObject(content); + var moduleData = { path: path.resolve(modulePath) }; + if (moduleDocBlock.providesModule || moduleDocBlock.provides) { + moduleData.id = + moduleDocBlock.providesModule || moduleDocBlock.provides; + } else { + moduleData.id = self._lookupName(modulePath); + } + moduleData.dependencies = extractRequires(content); + + var module = new ModuleDescriptor(moduleData); + self._updateGraphWithModule(module); + return module; + }); +}; + +/** + * Compute the name of module relative to a package it may belong to. + */ +DependecyGraph.prototype._lookupName = function(modulePath) { + var packageJson = this._lookupPackage(modulePath); + if (packageJson == null) { + return path.resolve(modulePath); + } else { + var relativePath = + sansExtJs(path.relative(packageJson._root, modulePath)); + return path.join(packageJson.name, relativePath); + } +}; + +/** + * Update the graph and idices with the module. + */ +DependecyGraph.prototype._updateGraphWithModule = function(module) { + this._graph[module.path] = module; + + if (this._moduleById[module.id]) { + debug( + 'WARNING: Top-level module name conflict `%s`.\n' + + 'module with path `%s` will replace `%s`', + module.id, + module.path, + this._moduleById[module.id].path + ); + } + + this._moduleById[module.id] = module; +}; + +/** + * Find the nearest package to a module. + */ +DependecyGraph.prototype._lookupPackage = function(modulePath) { + var root = this._root; + var packageByRoot = this._packageByRoot; + + /** + * Auxiliary function to recursively lookup a package. + */ + function lookupPackage(currDir) { + // ideally we stop once we're outside root and this can be a simple child + // dir check. However, we have to support modules that was symlinked inside + // our project root. + if (!path.relative(root, currDir) === '' || currDir === '.' + || currDir === '/') { + return null; + } else { + var packageJson = packageByRoot[currDir]; + if (packageJson) { + return packageJson; + } else { + return lookupPackage(path.dirname(currDir)); + } + } + } + + return lookupPackage(path.dirname(modulePath)); +}; + +/** + * Process a filewatcher change event. + */ +DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) { + var absPath = path.join(this._root, filePath); + if (this._ignoreFilePath(absPath)) { + return; + } + + if (eventType === 'delete') { + var module = this._graph[absPath]; + if (module == null) { + return; + } + + delete this._graph[module]; + + // Others may keep a reference so we mark it as deleted. + module.deleted = true; + + // Modules may have same id. + if (this._moduleById[module.id] === module) { + delete this._moduleById[module.id]; + } + } else if (!(stat && stat.isDirectory())) { + var self = this; + this._loading = this._loading.then(function() { + return self._processModule(absPath); + }); + } +}; + +/** + * Extract all required modules from a `code` string. + */ +var requireRe = /\brequire\s*\(\s*[\'"]([^"\']+)["\']\s*\)/g; +var blockCommentRe = /\/\*(.|\n)*?\*\//g; +var lineCommentRe = /\/\/.+(\n|$)/g; +function extractRequires(code) { + var deps = []; + + code + .replace(blockCommentRe, '') + .replace(lineCommentRe, '') + .replace(requireRe, function(match, dep) { + deps.push(dep); + }); + + return deps; +} + +/** + * `file` without the .js extension. + */ +function sansExtJs(file) { + if (file.match(/\.js$/)) { + return file.slice(0, -3); + } else { + return file; + } +} + +/** + * `file` with the .js extension. + */ +function withExtJs(file) { + if (file.match(/\.js$/)) { + return file; + } else { + return file + '.js'; + } +} + +module.exports = DependecyGraph; diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js new file mode 100644 index 00000000..ca180929 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -0,0 +1,152 @@ + +jest.dontMock('../') + .dontMock('q') + .setMock('../../ModuleDescriptor', function(data) {return data;}); + +var q = require('q'); + +describe('HasteDependencyResolver', function() { + var HasteDependencyResolver; + var DependencyGraph; + + beforeEach(function() { + // For the polyfillDeps + require('path').join.mockImpl(function(a, b) { + return b; + }); + HasteDependencyResolver = require('../'); + DependencyGraph = require('../DependencyGraph'); + }); + + describe('getDependencies', function() { + pit('should get dependencies with polyfills', function() { + var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var deps = [module]; + + var depResolver = new HasteDependencyResolver({ + projectRoot: '/root' + }); + + // Is there a better way? How can I mock the prototype instead? + var depGraph = depResolver._depGraph; + depGraph.getOrderedDependencies.mockImpl(function() { + return deps; + }); + depGraph.load.mockImpl(function() { + return q(); + }); + + return depResolver.getDependencies('/root/index.js') + .then(function(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'] + }, + module + ]); + }); + }); + + pit('should pass in more polyfills', function() { + var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var deps = [module]; + + var depResolver = new HasteDependencyResolver({ + projectRoot: '/root', + polyfillModuleNames: ['some module'] + }); + + // Is there a better way? How can I mock the prototype instead? + var depGraph = depResolver._depGraph; + depGraph.getOrderedDependencies.mockImpl(function() { + return deps; + }); + depGraph.load.mockImpl(function() { + return q(); + }); + + return depResolver.getDependencies('/root/index.js') + .then(function(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'] + }, + { path: 'some module', + id: 'some module', + isPolyfill: true, + dependencies: [ 'polyfills/prelude.js', 'polyfills/require.js', + 'polyfills/polyfills.js'] + }, + module + ]); + }); + }); + }); + + describe('wrapModule', function() { + it('should ', function() { + var depResolver = new HasteDependencyResolver({ + projectRoot: '/root' + }); + + var depGraph = depResolver._depGraph; + var dependencies = ['x', 'y', 'z'] + var code = [ + 'require("x")', + 'require("y")', + 'require("z")', + ].join('\n'); + + depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { + if (toModuleName === 'x') { + return { + id: 'changed' + }; + } else if (toModuleName === 'y') { + return { id: 'y' }; + } + return null; + }); + + var processedCode = depResolver.wrapModule({ + id: 'test module', + path: '/root/test.js', + dependencies: dependencies + }, code); + + expect(processedCode).toEqual([ + "__d('test module',[\"changed\",\"y\"],function(global," + + " require, requireDynamic, requireLazy, module, exports) {" + + " require('changed')", + "require('y')", + 'require("z")});', + ].join('\n')); + }); + }); +}); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js new file mode 100644 index 00000000..960e49fe --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -0,0 +1,121 @@ +'use strict'; + +var path = require('path'); +var FileWatcher = require('../../FileWatcher'); +var DependencyGraph = require('./DependencyGraph'); +var ModuleDescriptor = require('../ModuleDescriptor'); + +var DEFAULT_POLYFILLS = [ + +]; + +var DEFINE_MODULE_CODE = + '__d(' + + '\'_moduleName_\',' + + '_deps_,' + + 'function(global, require, requireDynamic, requireLazy, module, exports) {'+ + ' _code_' + + '}' + + ');'; + +var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; + +var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; + +function HasteDependencyResolver(config) { + this._depGraph = new DependencyGraph({ + root: config.projectRoot, + ignoreFilePath: function(filepath) { + return filepath.indexOf('__tests__') !== -1 || + (config.blacklistRE && config.blacklistRE.test(filepath)); + }, + fileWatcher: new FileWatcher(config) + }); + + this._polyfillModuleNames = [ + config.dev + ? path.join(__dirname, 'polyfills/prelude_dev.js') + : path.join(__dirname, 'polyfills/prelude.js'), + path.join(__dirname, 'polyfills/require.js'), + path.join(__dirname, 'polyfills/polyfills.js'), + ].concat( + config.polyfillModuleNames || [] + ); +} + +HasteDependencyResolver.prototype.getDependencies = function(main) { + var depGraph = this._depGraph; + var self = this; + + return depGraph.load() + .then(function() { + var dependencies = depGraph.getOrderedDependencies(main); + var mainModuleId = dependencies[0].id; + + self._prependPolyfillDependencies(dependencies); + + return { + mainModuleId: mainModuleId, + dependencies: dependencies + }; + }); +}; + +HasteDependencyResolver.prototype._prependPolyfillDependencies = function( + dependencies +) { + var polyfillModuleNames = this._polyfillModuleNames; + if (polyfillModuleNames.length > 0) { + var polyfillModules = polyfillModuleNames.map( + function(polyfillModuleName, idx) { + return new ModuleDescriptor({ + path: polyfillModuleName, + id: polyfillModuleName, + dependencies: polyfillModuleNames.slice(0, idx), + isPolyfill: true + }); + } + ); + dependencies.unshift.apply(dependencies, polyfillModules); + } +}; + +HasteDependencyResolver.prototype.wrapModule = function(module, code) { + if (module.isPolyfill) { + return code; + } + + var depGraph = this._depGraph; + var resolvedDeps = Object.create(null); + var resolvedDepsArr = []; + + for (var i = 0; i < module.dependencies.length; i++) { + var depName = module.dependencies[i]; + var dep = this._depGraph.resolveDependency(module, depName); + if (dep) { + resolvedDeps[depName] = dep.id; + resolvedDepsArr.push(dep.id); + } + } + + + var relativizedCode = + code.replace(REL_REQUIRE_STMT, function(codeMatch, depName) { + var dep = resolvedDeps[depName]; + if (dep != null) { + return 'require(\'' + dep + '\')'; + } else { + return codeMatch; + } + }); + + return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) { + return { + '_moduleName_': module.id, + '_code_': relativizedCode, + '_deps_': JSON.stringify(resolvedDepsArr), + }[key]; + }); +}; + +module.exports = HasteDependencyResolver; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js new file mode 100644 index 00000000..2fd32246 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js @@ -0,0 +1,75 @@ +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This pipes all of our console logging functions to native logging so that + * JavaScript errors in required modules show up in Xcode via NSLog. + * + * @provides Object.es6 + * @polyfill + */ + +// WARNING: This is an optimized version that fails on hasOwnProperty checks +// and non objects. It's not spec-compliant. It's a perf optimization. + +Object.assign = function(target, sources) { + if (__DEV__) { + if (target == null) { + throw new TypeError('Object.assign target cannot be null or undefined'); + } + if (typeof target !== 'object' && typeof target !== 'function') { + throw new TypeError( + 'In this environment the target of assign MUST be an object.' + + 'This error is a performance optimization and not spec compliant.' + ); + } + } + + for (var nextIndex = 1; nextIndex < arguments.length; nextIndex++) { + var nextSource = arguments[nextIndex]; + if (nextSource == null) { + continue; + } + + if (__DEV__) { + if (typeof nextSource !== 'object' && + typeof nextSource !== 'function') { + throw new TypeError( + 'In this environment the target of assign MUST be an object.' + + 'This error is a performance optimization and not spec compliant.' + ); + } + } + + // We don't currently support accessors nor proxies. Therefore this + // copy cannot throw. If we ever supported this then we must handle + // exceptions and side-effects. + + for (var key in nextSource) { + if (__DEV__) { + var hasOwnProperty = Object.prototype.hasOwnProperty; + if (!hasOwnProperty.call(nextSource, key)) { + throw new TypeError( + 'One of the sources to assign has an enumerable key on the ' + + 'prototype chain. This is an edge case that we do not support. ' + + 'This error is a performance optimization and not spec compliant.' + ); + } + } + target[key] = nextSource[key]; + } + } + + return target; +}; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude.js b/react-packager/src/DependencyResolver/haste/polyfills/prelude.js new file mode 100644 index 00000000..95c66983 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/prelude.js @@ -0,0 +1 @@ +__DEV__ = false; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js b/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js new file mode 100644 index 00000000..a5ca53b7 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js @@ -0,0 +1 @@ +__DEV__ = true; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/haste/polyfills/require.js new file mode 100644 index 00000000..3b5d6d87 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -0,0 +1,626 @@ +(function(global) { + + // avoid redefining require() + if (global.require) { + return; + } + + var __DEV__ = global.__DEV__; + + var toString = Object.prototype.toString; + + /** + * module index: { + * mod1: { + * exports: { ... }, + * id: 'mod1', + * dependencies: ['mod1', 'mod2'], + * factory: function() { ... }, + * waitingMap: { mod1: 1, mod3: 1, mod4: 1 }, + * waiting: 2 + * } + * } + */ + var modulesMap = {}, + /** + * inverse index: { + * mod1: [modules, waiting for mod1], + * mod2: [modules, waiting for mod2] + * } + */ + dependencyMap = {}, + /** + * modules whose reference counts are set out of order + */ + predefinedRefCounts = {}, + + _counter = 0, + + REQUIRE_WHEN_READY = 1, + USED_AS_TRANSPORT = 2, + + hop = Object.prototype.hasOwnProperty; + + function _debugUnresolvedDependencies(names) { + var unresolved = Array.prototype.slice.call(names); + var visited = {}; + var ii, name, module, dependency; + + while (unresolved.length) { + name = unresolved.shift(); + if (visited[name]) { + continue; + } + visited[name] = true; + + module = modulesMap[name]; + if (!module || !module.waiting) { + continue; + } + + for (ii = 0; ii < module.dependencies.length; ii++) { + dependency = module.dependencies[ii]; + if (!modulesMap[dependency] || modulesMap[dependency].waiting) { + unresolved.push(dependency); + } + } + } + + for (name in visited) if (hop.call(visited, name)) { + unresolved.push(name); + } + + var messages = []; + for (ii = 0; ii < unresolved.length; ii++) { + name = unresolved[ii]; + var message = name; + module = modulesMap[name]; + if (!module) { + message += ' is not defined'; + } else if (!module.waiting) { + message += ' is ready'; + } else { + var unresolvedDependencies = []; + for (var jj = 0; jj < module.dependencies.length; jj++) { + dependency = module.dependencies[jj]; + if (!modulesMap[dependency] || modulesMap[dependency].waiting) { + unresolvedDependencies.push(dependency); + } + } + message += ' is waiting for ' + unresolvedDependencies.join(', '); + } + messages.push(message); + } + return messages.join('\n'); + } + + /** + * This is mainly for logging in ModuleErrorLogger. + */ + function ModuleError(msg) { + this.name = 'ModuleError'; + this.message = msg; + this.stack = Error(msg).stack; + this.framesToPop = 2; + } + ModuleError.prototype = Object.create(Error.prototype); + ModuleError.prototype.constructor = ModuleError; + + var _performance = + global.performance || + global.msPerformance || + global.webkitPerformance || {}; + + if (!_performance.now) { + _performance = global.Date; + } + + var _now = _performance ? + _performance.now.bind(_performance) : function(){return 0;}; + + var _factoryStackCount = 0; + var _factoryTime = 0; + var _totalFactories = 0; + + /** + * The require function conforming to CommonJS spec: + * http://wiki.commonjs.org/wiki/Modules/1.1.1 + * + * To define a CommonJS-compliant module add the providesModule + * Haste header to your file instead of @provides. Your file is going + * to be executed in a separate context. Every variable/function you + * define will be local (private) to that module. To export local members + * use "exports" variable or return the exported value at the end of your + * file. Your code will have access to the "module" object. + * The "module" object will have an "id" property that is the id of your + * current module. "module" object will also have "exports" property that + * is the same as "exports" variable passed into your module context. + * You can require other modules using their ids. + * + * Haste will automatically pick dependencies from require() calls. So + * you don't have to manually specify @requires in your header. + * + * You cannot require() modules from non-CommonJS files. Write a legacy stub + * (@providesLegacy) and use @requires instead. + * + * @example + * + * / ** + * * @providesModule math + * * / + * exports.add = function() { + * var sum = 0, i = 0, args = arguments, l = args.length; + * while (i < l) { + * sum += args[i++]; + * } + * return sum; + * }; + * + * / ** + * * @providesModule increment + * * / + * var add = require('math').add; + * return function(val) { + * return add(val, 1); + * }; + * + * / ** + * * @providesModule program + * * / + * var inc = require('increment'); + * var a = 1; + * inc(a); // 2 + * + * module.id == "program"; + * + * + * @param {String} id + * @throws when module is not loaded or not ready to be required + */ + function require(id) { + var module = modulesMap[id], dep, i, msg; + if (module && module.exports) { + // If ref count is 1, this was the last call, so undefine the module. + // The ref count can be null or undefined, but those are never === 1. + if (module.refcount-- === 1) { + delete modulesMap[id]; + } + return module.exports; + } + + if (global.ErrorUtils && !global.ErrorUtils.inGuard()) { + return ErrorUtils.applyWithGuard(require, this, arguments); + } + + if (!module) { + msg = 'Requiring unknown module "' + id + '"'; + if (__DEV__) { + msg += '. It may not be loaded yet. Did you forget to run arc build?'; + } + throw new ModuleError(msg); + } + + if (module.hasError) { + throw new ModuleError( + 'Requiring module "' + id + '" which threw an exception' + ); + } + + if (module.waiting) { + throw new ModuleError( + 'Requiring module "' + id + '" with unresolved dependencies: ' + + _debugUnresolvedDependencies([id]) + ); + } + + var exports = module.exports = {}; + var factory = module.factory; + if (toString.call(factory) === '[object Function]') { + var args = [], + dependencies = module.dependencies, + length = dependencies.length, + ret; + if (module.special & USED_AS_TRANSPORT) { + length = Math.min(length, factory.length); + } + try { + for (i = 0; args.length < length; i++) { + dep = dependencies[i]; + if (!module.inlineRequires[dep]) { + args.push(dep === 'module' ? module : + (dep === 'exports' ? exports : + require.call(null, dep))); + } + } + + ++_totalFactories; + if (_factoryStackCount++ === 0) { + _factoryTime -= _now(); + } + try { + ret = factory.apply(module.context || global, args); + } catch (e) { + if (modulesMap.ex && modulesMap.erx) { + // when ErrorUtils is ready, ex and erx are ready. otherwise, we + // don't append module id to the error message but still throw it + var ex = require.call(null, 'ex'); + var erx = require.call(null, 'erx'); + var messageWithParams = erx(e.message); + if (messageWithParams[0].indexOf(' from module "%s"') < 0) { + messageWithParams[0] += ' from module "%s"'; + messageWithParams[messageWithParams.length] = id; + } + e.message = ex.apply(null, messageWithParams); + } + throw e; + } finally { + if (--_factoryStackCount === 0) { + _factoryTime += _now(); + } + } + } catch (e) { + module.hasError = true; + module.exports = null; + throw e; + } + if (ret) { + if (__DEV__) { + if (typeof ret != 'object' && typeof ret != 'function') { + throw new ModuleError( + 'Factory for module "' + id + '" returned ' + + 'an invalid value "' + ret + '". ' + + 'Returned value should be either a function or an object.' + ); + } + } + module.exports = ret; + } + } else { + module.exports = factory; + } + + // If ref count is 1, this was the last call, so undefine the module. + // The ref count can be null or undefined, but those are never === 1. + if (module.refcount-- === 1) { + delete modulesMap[id]; + } + return module.exports; + } + + require.__getFactoryTime = function() { + return (_factoryStackCount ? _now() : 0) + _factoryTime; + }; + + require.__getTotalFactories = function() { + return _totalFactories; + }; + + /** + * The define function conforming to CommonJS proposal: + * http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition + * + * define() allows you to explicitly state dependencies of your module + * in javascript. It's most useful in non-CommonJS files. + * + * define() is used internally by haste as a transport for CommonJS + * modules. So there's no need to use define() if you use providesModule + * + * @example + * / ** + * * @provides alpha + * * / + * + * // Sets up the module with ID of "alpha", that uses require, + * // exports and the module with ID of "beta": + * define("alpha", ["require", "exports", "beta"], + * function (require, exports, beta) { + * exports.verb = function() { + * return beta.verb(); + * //Or: + * return require("beta").verb(); + * } + * }); + * + * / ** + * * @provides alpha + * * / + * // An anonymous module could be defined (module id derived from filename) + * // that returns an object literal: + * + * define(["alpha"], function (alpha) { + * return { + * verb: function(){ + * return alpha.verb() + 2; + * } + * }; + * }); + * + * / ** + * * @provides alpha + * * / + * // A dependency-free module can define a direct object literal: + * + * define({ + * add: function(x, y){ + * return x + y; + * } + * }); + * + * @param {String} id optional + * @param {Array} dependencies optional + * @param {Object|Function} factory + */ + function define(id, dependencies, factory, + _special, _context, _refCount, _inlineRequires) { + if (dependencies === undefined) { + dependencies = []; + factory = id; + id = _uid(); + } else if (factory === undefined) { + factory = dependencies; + if (toString.call(id) === '[object Array]') { + dependencies = id; + id = _uid(); + } else { + dependencies = []; + } + } + + // Non-standard: we allow modules to be undefined. This is designed for + // temporary modules. + var canceler = { cancel: _undefine.bind(this, id) }; + + var record = modulesMap[id]; + + // Nonstandard hack: we call define with null deps and factory, but a + // non-null reference count (e.g. define('name', null, null, 0, null, 4)) + // when this module is defined elsewhere and we just need to update the + // reference count. We use this hack to avoid having to expose another + // global function to increment ref counts. + if (record) { + if (_refCount) { + record.refcount += _refCount; + } + // Calling define() on a pre-existing module does not redefine it + return canceler; + } else if (!dependencies && !factory && _refCount) { + // If this module hasn't been defined yet, store the ref count. We'll use + // it when the module is defined later. + predefinedRefCounts[id] = (predefinedRefCounts[id] || 0) + _refCount; + return canceler; + } else { + // Defining a new module + record = { id: id }; + record.refcount = (predefinedRefCounts[id] || 0) + (_refCount || 0); + delete predefinedRefCounts[id]; + } + + if (__DEV__) { + if ( + !factory || + (typeof factory != 'object' && typeof factory != 'function' && + typeof factory != 'string')) { + throw new ModuleError( + 'Invalid factory "' + factory + '" for module "' + id + '". ' + + 'Factory should be either a function or an object.' + ); + } + + if (toString.call(dependencies) !== '[object Array]') { + throw new ModuleError( + 'Invalid dependencies for module "' + id + '". ' + + 'Dependencies must be passed as an array.' + ); + } + } + + record.factory = factory; + record.dependencies = dependencies; + record.context = _context; + record.special = _special; + record.inlineRequires = _inlineRequires || {}; + record.waitingMap = {}; + record.waiting = 0; + record.hasError = false; + modulesMap[id] = record; + _initDependencies(id); + + return canceler; + } + + function _undefine(id) { + if (!modulesMap[id]) { + return; + } + + var module = modulesMap[id]; + delete modulesMap[id]; + + for (var dep in module.waitingMap) { + if (module.waitingMap[dep]) { + delete dependencyMap[dep][id]; + } + } + + for (var ii = 0; ii < module.dependencies.length; ii++) { + dep = module.dependencies[ii]; + if (modulesMap[dep]) { + if (modulesMap[dep].refcount-- === 1) { + _undefine(dep); + } + } else if (predefinedRefCounts[dep]) { + predefinedRefCounts[dep]--; + } + // Subtle: we won't account for this one fewer reference if we don't have + // the dependency's definition or reference count yet. + } + } + + /** + * 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++; + } + + function _addDependency(module, dep) { + // do not add duplicate dependencies and circ deps + if (!module.waitingMap[dep] && module.id !== dep) { + module.waiting++; + module.waitingMap[dep] = 1; + dependencyMap[dep] || (dependencyMap[dep] = {}); + dependencyMap[dep][module.id] = 1; + } + } + + function _initDependencies(id) { + var modulesToRequire = []; + var module = modulesMap[id]; + var dep, i, subdep; + + // initialize id's waitingMap + for (i = 0; i < module.dependencies.length; i++) { + dep = module.dependencies[i]; + if (!modulesMap[dep]) { + _addDependency(module, dep); + } else if (modulesMap[dep].waiting) { + for (subdep in modulesMap[dep].waitingMap) { + if (modulesMap[dep].waitingMap[subdep]) { + _addDependency(module, subdep); + } + } + } + } + if (module.waiting === 0 && module.special & REQUIRE_WHEN_READY) { + modulesToRequire.push(id); + } + + // update modules depending on id + if (dependencyMap[id]) { + var deps = dependencyMap[id]; + var submodule; + dependencyMap[id] = undefined; + for (dep in deps) { + submodule = modulesMap[dep]; + + // add all deps of id + for (subdep in module.waitingMap) { + if (module.waitingMap[subdep]) { + _addDependency(submodule, subdep); + } + } + // remove id itself + if (submodule.waitingMap[id]) { + submodule.waitingMap[id] = undefined; + submodule.waiting--; + } + if (submodule.waiting === 0 && + submodule.special & REQUIRE_WHEN_READY) { + modulesToRequire.push(dep); + } + } + } + + // run everything that's ready + for (i = 0; i < modulesToRequire.length; i++) { + require.call(null, modulesToRequire[i]); + } + } + + function _register(id, exports) { + var module = modulesMap[id] = { id: id }; + module.exports = exports; + module.refcount = 0; + } + + // pseudo name used in common-require + // see require() function for more info + _register('module', 0); + _register('exports', 0); + + _register('define', define); + _register('global', global); + _register('require', require); + _register('requireDynamic', require); + _register('requireLazy', requireLazy); + + define.amd = {}; + + global.define = define; + global.require = require; + global.requireDynamic = require; + global.requireLazy = requireLazy; + + require.__debug = { + modules: modulesMap, + deps: dependencyMap, + printDependencyInfo: function() { + if (!global.console) { + return; + } + var names = Object.keys(require.__debug.deps); + global.console.log(_debugUnresolvedDependencies(names)); + } + }; + + /** + * All @providesModule files are wrapped by this function by makehaste. It + * is a convenience function around define() that prepends a bunch of required + * modules (global, require, module, etc) so that we don't have to spit that + * 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']; + define(id, defaultDeps.concat(deps), factory, _special || USED_AS_TRANSPORT, + null, null, _inlineRequires); + }; + +})(this); diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js new file mode 100644 index 00000000..79eb48c1 --- /dev/null +++ b/react-packager/src/DependencyResolver/index.js @@ -0,0 +1,12 @@ +var HasteDependencyResolver = require('./haste'); +var NodeDependencyResolver = require('./node'); + +module.exports = function createDependencyResolver(options) { + if (options.moduleFormat === 'haste') { + return new HasteDependencyResolver(options); + } else if (options.moduleFormat === 'node') { + return new NodeDependencyResolver(options); + } else { + throw new Error('unsupported'); + } +}; diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js new file mode 100644 index 00000000..0d3b807e --- /dev/null +++ b/react-packager/src/DependencyResolver/node/index.js @@ -0,0 +1,48 @@ +var Promise = require('q').Promise; +var ModuleDescriptor = require('../ModuleDescriptor'); + +var mdeps = require('module-deps'); +var path = require('path'); +var fs = require('fs'); + +// var REQUIRE_RUNTIME = fs.readFileSync( +// path.join(__dirname, 'require.js') +// ).toString(); + +exports.getRuntimeCode = function() { + return REQUIRE_RUNTIME; +}; + +exports.wrapModule = function(id, source) { + return Promise.resolve( + 'define(' + JSON.stringify(id) + ',' + ' function(exports, module) {\n' + + source + '\n});' + ); +}; + +exports.getDependencies = function(root, fileEntryPath) { + return new Promise(function(resolve, reject) { + fileEntryPath = path.join(process.cwd(), root, fileEntryPath); + + var md = mdeps(); + + md.end({file: fileEntryPath}); + + var deps = []; + + md.on('data', function(data) { + deps.push( + new ModuleDescriptor({ + id: data.id, + deps: data.deps, + path: data.file, + entry: data.entry + }) + ); + }); + + md.on('end', function() { + resolve(deps); + }); + }); +}; diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js new file mode 100644 index 00000000..84471517 --- /dev/null +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -0,0 +1,37 @@ +'use strict'; + +jest.dontMock('../') + .dontMock('q') + .setMock('child_process', { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman') } }); + +describe('FileWatcher', function() { + var FileWatcher; + var Watcher; + + beforeEach(function() { + FileWatcher = require('../'); + Watcher = require('sane').WatchmanWatcher; + Watcher.prototype.once.mockImplementation(function(type, callback) { + callback(); + }); + }); + + pit('it should get the watcher instance when ready', function() { + var fileWatcher = new FileWatcher({projectRoot: 'rootDir'}); + return fileWatcher.getWatcher().then(function(watcher) { + expect(watcher instanceof Watcher).toBe(true); + }); + }); + + pit('it should end the watcher', function() { + var fileWatcher = new FileWatcher({projectRoot: 'rootDir'}); + Watcher.prototype.close.mockImplementation(function(callback) { + callback(); + }); + + return fileWatcher.end().then(function() { + expect(Watcher.prototype.close).toBeCalled(); + }); + }); + +}); diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js new file mode 100644 index 00000000..d7e1b82f --- /dev/null +++ b/react-packager/src/FileWatcher/index.js @@ -0,0 +1,56 @@ +'use strict'; + +var sane = require('sane'); +var q = require('q'); +var exec = require('child_process').exec; + +var Promise = q.Promise; + +var detectingWatcherClass = new Promise(function(resolve, reject) { + exec('which watchman', function(err, out) { + if (err || out.length === 0) { + resolve(sane.NodeWatcher); + } else { + resolve(sane.WatchmanWatcher); + } + }); +}); + +module.exports = FileWatcher; + +var MAX_WAIT_TIME = 3000; + +var memoizedInstances = Object.create(null); + +function FileWatcher(projectConfig) { + if (memoizedInstances[projectConfig.projectRoot]) { + return memoizedInstances[projectConfig.projectRoot]; + } else { + memoizedInstances[projectConfig.projectRoot] = this; + } + + this._loadingWatcher = detectingWatcherClass.then(function(Watcher) { + var watcher = new Watcher(projectConfig.projectRoot, {glob: '**/*.js'}); + + return new Promise(function(resolve, reject) { + var rejectTimeout = setTimeout(function() { + reject(new Error('Watcher took too long to load.')); + }, MAX_WAIT_TIME); + + watcher.once('ready', function() { + clearTimeout(rejectTimeout); + resolve(watcher); + }); + }); + }); +} + +FileWatcher.prototype.getWatcher = function() { + return this._loadingWatcher; +}; + +FileWatcher.prototype.end = function() { + return this._loadingWatcher.then(function(watcher) { + return q.ninvoke(watcher, 'close'); + }); +}; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js new file mode 100644 index 00000000..0c5bb942 --- /dev/null +++ b/react-packager/src/JSTransformer/Cache.js @@ -0,0 +1,128 @@ +'use strict'; + +var path = require('path'); +var version = require('../../package.json').version; +var tmpdir = require('os').tmpDir(); +var pathUtils = require('../fb-path-utils'); +var FileWatcher = require('../FileWatcher'); +var fs = require('fs'); +var _ = require('underscore'); +var q = require('q'); + +var Promise = q.Promise; + +module.exports = Cache; + +function Cache(projectConfig) { + this._cacheFilePath = path.join( + tmpdir, + 'React-Packager-JSTransformer-' + version + '-' + + projectConfig.projectRoot.split(path.sep).join('-') + + '-' + (projectConfig.cacheVersion || '0') + ); + + var data; + if (!projectConfig.resetCache) { + data = loadCacheSync(this._cacheFilePath); + } else { + data = Object.create(null); + } + this._data = data; + + this._has = Object.prototype.hasOwnProperty.bind(data); + this._persistEventually = _.debounce( + this._persistCache.bind(this), + 2000 + ); + + this._fileWatcher = new FileWatcher(projectConfig); + this._fileWatcher + .getWatcher() + .done(function(watcher) { + watcher.on('all', function(type, filepath) { + var absPath = path.join(projectConfig.projectRoot, filepath); + delete data[absPath]; + }); + }.bind(this)); +} + +Cache.prototype.get = function(filepath, loaderCb) { + if (!pathUtils.isAbsolutePath(filepath)) { + throw new Error('Use absolute paths'); + } + + var recordP = this._has(filepath) + ? this._data[filepath] + : this._set(filepath, loaderCb(filepath)); + + return recordP.then(function(record) { + return record.data; + }); +}; + +Cache.prototype._set = function(filepath, loaderPromise) { + return this._data[filepath] = loaderPromise.then(function(data) { + return [ + data, + q.nfbind(fs.stat)(filepath) + ]; + }).spread(function(data, stat) { + this._persistEventually(); + return { + data: data, + mtime: stat.mtime.getTime(), + }; + }.bind(this)); +}; + +Cache.prototype.end = function() { + return q.all([ + this._persistCache(), + this._fileWatcher.end() + ]); +}; + +Cache.prototype._persistCache = function() { + if (this._persisting != null) { + return this._persisting; + } + + var data = this._data; + var cacheFilepath = this._cacheFilePath; + + return this._persisting = q.all(_.values(data)) + .then(function(values) { + var json = Object.create(null); + Object.keys(data).forEach(function(key, i) { + json[key] = values[i]; + }); + return q.nfbind(fs.writeFile)(cacheFilepath, JSON.stringify(json)); + }) + .then(function() { + this._persisting = null; + return true; + }.bind(this)); +}; + +function loadCacheSync(cacheFilepath) { + var ret = Object.create(null); + if (!fs.existsSync(cacheFilepath)) { + return ret; + } + + var cacheOnDisk = JSON.parse(fs.readFileSync(cacheFilepath)); + + // Filter outdated cache and convert to promises. + Object.keys(cacheOnDisk).forEach(function(key) { + if (!fs.existsSync(key)) { + return; + } + var value = cacheOnDisk[key]; + var stat = fs.statSync(key); + if (stat.mtime.getTime() === value.mtime) { + ret[key] = Promise.resolve(value); + } + }); + + return ret; +} diff --git a/react-packager/src/JSTransformer/README.md b/react-packager/src/JSTransformer/README.md new file mode 100644 index 00000000..e69de29b diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js new file mode 100644 index 00000000..1ae537f1 --- /dev/null +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -0,0 +1,244 @@ +'use strict'; + +jest + .dontMock('underscore') + .dontMock('path') + .dontMock('q') + .dontMock('absolute-path') + .dontMock('../../fb-path-utils') + .dontMock('../Cache'); + +var q = require('q'); + +describe('JSTransformer Cache', function() { + var Cache; + + beforeEach(function() { + require('os').tmpDir.mockImpl(function() { + return 'tmpDir'; + }); + + Cache = require('../Cache'); + + var FileWatcher = require('../../FileWatcher'); + FileWatcher.prototype.getWatcher = function() { + return q({ + on: function() {} + }); + }; + }); + + describe('getting/settig', function() { + it('calls loader callback for uncached file', function() { + var cache = new Cache({projectRoot: '/rootDir'}); + var loaderCb = jest.genMockFn().mockImpl(function() { + return q(); + }); + cache.get('/rootDir/someFile', loaderCb); + expect(loaderCb).toBeCalledWith('/rootDir/someFile'); + }); + + pit('gets the value from the loader callback', function() { + require('fs').stat.mockImpl(function(file, callback) { + callback(null, { + mtime: { + getTime: function() {} + } + }); + }); + var cache = new Cache({projectRoot: '/rootDir'}); + var loaderCb = jest.genMockFn().mockImpl(function() { + return q('lol'); + }); + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { + expect(value).toBe('lol'); + }); + }); + + pit('caches the value after the first call', function() { + require('fs').stat.mockImpl(function(file, callback) { + callback(null, { + mtime: { + getTime: function() {} + } + }); + }); + var cache = new Cache({projectRoot: '/rootDir'}); + var loaderCb = jest.genMockFn().mockImpl(function() { + return q('lol'); + }); + return cache.get('/rootDir/someFile', loaderCb).then(function() { + var shouldNotBeCalled = jest.genMockFn(); + return cache.get('/rootDir/someFile', shouldNotBeCalled) + .then(function(value) { + expect(shouldNotBeCalled).not.toBeCalled(); + expect(value).toBe('lol'); + }); + }); + }); + + pit('it invalidates cache after a file has changed', function() { + require('fs').stat.mockImpl(function(file, callback) { + callback(null, { + mtime: { + getTime: function() {} + } + }); + }); + var FileWatcher = require('../../FileWatcher'); + var triggerChangeFile; + FileWatcher.prototype.getWatcher = function() { + return q({ + on: function(type, callback) { + triggerChangeFile = callback; + } + }); + }; + + var cache = new Cache({projectRoot: '/rootDir'}); + var loaderCb = jest.genMockFn().mockImpl(function() { + return q('lol'); + }); + + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { + expect(value).toBe('lol'); + triggerChangeFile('change', 'someFile'); + var loaderCb2 = jest.genMockFn().mockImpl(function() { + return q('lol2'); + }); + return cache.get('/rootDir/someFile', loaderCb2).then(function(value2) { + expect(value2).toBe('lol2'); + }); + }); + }); + }); + + describe('loading cache from disk', function() { + var fileStats; + + beforeEach(function() { + fileStats = { + '/rootDir/someFile': { + mtime: { + getTime: function() { + return 22; + } + } + }, + '/rootDir/foo': { + mtime: { + getTime: function() { + return 11; + } + } + } + }; + + var fs = require('fs'); + + fs.existsSync.mockImpl(function() { + return true; + }); + + fs.statSync.mockImpl(function(filePath) { + return fileStats[filePath]; + }); + + fs.readFileSync.mockImpl(function() { + return JSON.stringify({ + '/rootDir/someFile': { + mtime: 22, + data: 'oh hai' + }, + '/rootDir/foo': { + mtime: 11, + data: 'lol wat' + } + }); + }); + }); + + pit('should load cache from disk', function() { + var cache = new Cache({projectRoot: '/rootDir'}); + var loaderCb = jest.genMockFn(); + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('oh hai'); + + return cache.get('/rootDir/foo', loaderCb).then(function(value) { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('lol wat'); + }); + }); + }); + + pit('should not load outdated cache', function() { + require('fs').stat.mockImpl(function(file, callback) { + callback(null, { + mtime: { + getTime: function() {} + } + }); + }); + + fileStats['/rootDir/foo'].mtime.getTime = function() { + return 123; + }; + + var cache = new Cache({projectRoot: '/rootDir'}); + var loaderCb = jest.genMockFn().mockImpl(function() { + return q('new value'); + }); + + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('oh hai'); + + return cache.get('/rootDir/foo', loaderCb).then(function(value) { + expect(loaderCb).toBeCalled(); + expect(value).toBe('new value'); + }); + }); + }); + }); + + describe('writing cache to disk', function() { + it('should write cache to disk', function() { + var index = 0; + var mtimes = [10, 20, 30]; + var debounceIndex = 0; + require('underscore').debounce = function(callback) { + return function () { + if (++debounceIndex === 3) { + callback(); + } + }; + }; + + var fs = require('fs'); + fs.stat.mockImpl(function(file, callback) { + callback(null, { + mtime: { + getTime: function() { + return mtimes[index++]; + } + } + }); + }); + + var cache = new Cache({projectRoot: '/rootDir'}); + cache.get('/rootDir/bar', function() { + return q('bar value'); + }); + cache.get('/rootDir/foo', function() { + return q('foo value'); + }); + cache.get('/rootDir/baz', function() { + return q('baz value'); + }); + + jest.runAllTimers(); + expect(fs.writeFile).toBeCalled(); + }); + }); +}); diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js new file mode 100644 index 00000000..fb33a834 --- /dev/null +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -0,0 +1,67 @@ +'use strict'; + +jest + .dontMock('worker-farm') + .dontMock('q') + .dontMock('os') + .dontMock('../index'); + +describe('Transformer', function() { + var Transformer; + var workers; + + beforeEach(function() { + workers = jest.genMockFn(); + jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() { + return workers; + })); + require('../Cache').prototype.get.mockImpl(function(filePath, callback) { + return callback(); + }); + require('fs').readFile.mockImpl(function(file, callback) { + callback(null, 'content'); + }); + Transformer = require('../'); + }); + + pit('should loadFileAndTransform', function() { + workers.mockImpl(function(data, callback) { + callback(null, { code: 'transformed' }); + }); + require('fs').readFile.mockImpl(function(file, callback) { + callback(null, 'content'); + }); + + return new Transformer({}).loadFileAndTransform([], 'file', {}) + .then(function(data) { + expect(data).toEqual({ + code: 'transformed', + sourcePath: 'file', + sourceCode: 'content' + }); + }); + }); + + pit('should add file info to parse errors', function() { + require('fs').readFile.mockImpl(function(file, callback) { + callback(null, 'var x;\nvar answer = 1 = x;'); + }); + + workers.mockImpl(function(data, callback) { + var esprimaError = new Error('Error: Line 2: Invalid left-hand side in assignment'); + esprimaError.description = 'Invalid left-hand side in assignment'; + esprimaError.lineNumber = 2; + esprimaError.column = 15; + callback(null, {error: esprimaError}); + }); + + return new Transformer({}).loadFileAndTransform([], 'foo-file.js', {}) + .catch(function(error) { + expect(error.type).toEqual('TransformError'); + expect(error.snippet).toEqual([ + 'var answer = 1 = x;', + ' ^', + ].join('\n')); + }); + }); +}); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js new file mode 100644 index 00000000..95d7948b --- /dev/null +++ b/react-packager/src/JSTransformer/index.js @@ -0,0 +1,100 @@ + +'use strict'; + +var os = require('os'); +var fs = require('fs'); +var q = require('q'); +var Cache = require('./Cache'); +var _ = require('underscore'); +var workerFarm = require('worker-farm'); + +var workers = workerFarm(require.resolve('./worker')); +warmupWorkers(); + +var readFile = q.nfbind(fs.readFile); + +module.exports = Transformer; +Transformer.TransformError = TransformError; + +function Transformer(projectConfig) { + this._cache = new Cache(projectConfig); +} + +Transformer.prototype.kill = function() { + workerFarm.end(workers); + return this._cache.end(); +}; + +Transformer.prototype.loadFileAndTransform = function( + transformSets, + filePath, + options +) { + return this._cache.get(filePath, function() { + return readFile(filePath) + .then(function(buffer) { + var sourceCode = buffer.toString(); + var opts = _.extend({}, options, {filename: filePath}); + return q.nfbind(workers)({ + transformSets: transformSets, + sourceCode: sourceCode, + options: opts, + }).then( + function(res) { + if (res.error) { + throw formatEsprimaError(res.error, filePath, sourceCode); + } + + return { + code: res.code, + sourcePath: filePath, + sourceCode: sourceCode + }; + } + ); + }); + }); +}; + +// worker-farm module starts workers lazily. But we want them to take time +// to initialize so we send a dummy request. +// see https://github.com/rvagg/node-worker-farm/issues/23 +function warmupWorkers() { + os.cpus().forEach(function() { + workers({ + transformSets: ['es6'], + sourceCode: '\n', + options: {} + }, function() {}); + }); +} + +function TransformError() {} +TransformError.__proto__ = SyntaxError.prototype; + +function formatEsprimaError(err, filename, source) { + if (!(err.lineNumber && err.column)) { + return err; + } + + var stack = err.stack.split('\n'); + stack.shift(); + + var msg = 'TransformError: ' + err.description + ' ' + filename + ':' + + err.lineNumber + ':' + err.column; + var sourceLine = source.split('\n')[err.lineNumber - 1]; + var snippet = sourceLine + '\n' + new Array(err.column - 1).join(' ') + '^'; + + stack.unshift(msg); + + var error = new TransformError(); + error.message = msg; + error.type = 'TransformError'; + error.stack = stack.join('\n'); + error.snippet = snippet; + error.filename = filename; + error.lineNumber = err.lineNumber; + error.column = err.column; + error.description = err.description; + return error; +} diff --git a/react-packager/src/JSTransformer/transformer.js b/react-packager/src/JSTransformer/transformer.js new file mode 100644 index 00000000..39e409b3 --- /dev/null +++ b/react-packager/src/JSTransformer/transformer.js @@ -0,0 +1,33 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * Note: This is a fork of the fb-specific transform.js + */ +'use strict'; + +var jstransform = require('jstransform').transform; + +var reactVisitors = + require('react-tools/vendor/fbtransform/visitors').getAllVisitors(); +var staticTypeSyntax = + require('jstransform/visitors/type-syntax').visitorList; +// Note that reactVisitors now handles ES6 classes, rest parameters, arrow +// functions, template strings, and object short notation. +var visitorList = reactVisitors; + + +function transform(transformSets, srcTxt, options) { + options = options || {}; + + // These tranforms mostly just erase type annotations and static typing + // related statements, but they were conflicting with other tranforms. + // Running them first solves that problem + var staticTypeSyntaxResult = jstransform( + staticTypeSyntax, + srcTxt + ); + + return jstransform(visitorList, staticTypeSyntaxResult.code); +} + +exports.transform = transform; diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js new file mode 100644 index 00000000..26f789e4 --- /dev/null +++ b/react-packager/src/JSTransformer/worker.js @@ -0,0 +1,26 @@ +'use strict'; + +var transformer = require('./transformer'); + +module.exports = function (data, callback) { + var result; + try { + result = transformer.transform( + data.transformSets, + data.sourceCode, + data.options + ); + } catch (e) { + return callback(null, { + error: { + lineNumber: e.lineNumber, + column: e.column, + message: e.message, + stack: e.stack, + description: e.description + } + }); + } + + callback(null, result); +}; diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js new file mode 100644 index 00000000..f6807397 --- /dev/null +++ b/react-packager/src/Packager/Package.js @@ -0,0 +1,108 @@ +'use strict'; + +var _ = require('underscore'); +var SourceMapGenerator = require('source-map').SourceMapGenerator; +var base64VLQ = require('./base64-vlq'); + +module.exports = Package; + +function Package(sourceMapUrl) { + this._modules = []; + this._sourceMapUrl = sourceMapUrl; +} + +Package.prototype.setMainModuleId = function(moduleId) { + this._mainModuleId = moduleId; +}; + +Package.prototype.addModule = function( + transformedCode, + sourceCode, + sourcePath +) { + this._modules.push({ + transformedCode: transformedCode, + sourceCode: sourceCode, + sourcePath: sourcePath + }); +}; + +Package.prototype.finalize = function(options) { + if (options.runMainModule) { + var runCode = ';require("' + this._mainModuleId + '");'; + this.addModule( + runCode, + runCode, + 'RunMainModule.js' + ); + } + + Object.freeze(this._modules); + Object.seal(this._modules); +}; + +Package.prototype.getSource = function() { + return _.pluck(this._modules, 'transformedCode').join('\n') + '\n' + + 'RAW_SOURCE_MAP = ' + JSON.stringify(this.getSourceMap({excludeSource: true})) + ';\n' + + '\/\/@ sourceMappingURL=' + this._sourceMapUrl; +}; + +Package.prototype.getSourceMap = function(options) { + options = 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._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 mappings = ''; + for (var i = 0; i < modules.length; i++) { + var module = modules[i]; + var transformedCode = module.transformedCode; + var lastCharNewLine = false; + module.lines = 0; + for (var t = 0; t < transformedCode.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 - modules[i - 1].lines); + mappings += 'A'; + } else if (lastCharNewLine) { + module.lines++; + mappings += line; + } + lastCharNewLine = transformedCode[t] === '\n'; + if (lastCharNewLine) { + mappings += ';'; + } + } + if (i != modules.length - 1) { + mappings += ';'; + } + } + return mappings; +}; diff --git a/react-packager/src/Packager/__mocks__/source-map.js b/react-packager/src/Packager/__mocks__/source-map.js new file mode 100644 index 00000000..08c127f6 --- /dev/null +++ b/react-packager/src/Packager/__mocks__/source-map.js @@ -0,0 +1,5 @@ +var SourceMapGenerator = jest.genMockFn(); +SourceMapGenerator.prototype.addMapping = jest.genMockFn(); +SourceMapGenerator.prototype.setSourceContent = jest.genMockFn(); +SourceMapGenerator.prototype.toJSON = jest.genMockFn(); +exports.SourceMapGenerator = SourceMapGenerator; diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js new file mode 100644 index 00000000..d18bb4d6 --- /dev/null +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -0,0 +1,95 @@ +'use strict'; + +jest + .dontMock('underscore') + .dontMock('../base64-vlq') + .dontMock('source-map') + .dontMock('../Package'); + +var SourceMapGenerator = require('source-map').SourceMapGenerator; + +describe('Package', function() { + var Package; + var ppackage; + + beforeEach(function() { + Package = require('../Package'); + ppackage = new Package('test_url'); + ppackage.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('transformed foo;', 'source foo', 'foo path'); + ppackage.addModule('transformed bar;', 'source bar', 'bar path'); + ppackage.finalize({}); + expect(ppackage.getSource()).toBe([ + 'transformed foo;', + 'transformed bar;', + 'RAW_SOURCE_MAP = "test-source-map";', + '\/\/@ sourceMappingURL=test_url', + ].join('\n')); + }); + + it('should create a package and add run module code', function() { + ppackage.addModule('transformed foo;', 'source foo', 'foo path'); + ppackage.addModule('transformed bar;', 'source bar', 'bar path'); + ppackage.setMainModuleId('foo'); + ppackage.finalize({runMainModule: true}); + expect(ppackage.getSource()).toBe([ + 'transformed foo;', + 'transformed bar;', + ';require("foo");', + 'RAW_SOURCE_MAP = "test-source-map";', + '\/\/@ sourceMappingURL=test_url', + ].join('\n')); + }); + }); + + describe('sourcemap package', function() { + it('should create sourcemap', function() { + var ppackage = new Package('test_url'); + ppackage.addModule('transformed foo;\n', 'source foo', 'foo path'); + ppackage.addModule('transformed bar;\n', 'source bar', 'bar path'); + ppackage.setMainModuleId('foo'); + ppackage.finalize({runMainModule: true}); + var s = ppackage.getSourceMap(); + expect(s).toEqual(genSourceMap(ppackage._modules)); + }); + }); +}); + + function genSourceMap(modules) { + var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3}); + var packageLineNo = 0; + for (var i = 0; i < modules.length; i++) { + var module = modules[i]; + var transformedCode = module.transformedCode; + var sourcePath = module.sourcePath; + var sourceCode = module.sourceCode; + var transformedLineCount = 0; + var lastCharNewLine = false; + for (var t = 0; t < transformedCode.length; t++) { + if (t === 0 || lastCharNewLine) { + sourceMapGen.addMapping({ + generated: {line: packageLineNo + 1, column: 0}, + original: {line: transformedLineCount + 1, column: 0}, + source: sourcePath + }); + } + lastCharNewLine = transformedCode[t] === '\n'; + if (lastCharNewLine) { + transformedLineCount++; + packageLineNo++; + } + } + packageLineNo++; + sourceMapGen.setSourceContent( + sourcePath, + sourceCode + ); + } + return sourceMapGen.toJSON(); +}; diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js new file mode 100644 index 00000000..335db930 --- /dev/null +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -0,0 +1,83 @@ +'use strict'; + +jest + .setMock('worker-farm', function() { return function() {};}) + .dontMock('path') + .dontMock('q') + .dontMock('os') + .dontMock('underscore') + .dontMock('../'); + +var q = require('q'); + +describe('Packager', function() { + var getDependencies; + var wrapModule; + var Packager; + + beforeEach(function() { + getDependencies = jest.genMockFn(); + wrapModule = jest.genMockFn(); + require('../../DependencyResolver').mockImpl(function() { + return { + getDependencies: getDependencies, + wrapModule: wrapModule, + }; + }); + + Packager = require('../'); + }); + + pit('create a package', function() { + require('fs').statSync.mockImpl(function() { + return { + isDirectory: function() {return true;} + }; + }); + + var packager = new Packager({}); + var modules = [ + {id: 'foo', path: '/root/foo.js', dependencies: []}, + {id: 'bar', path: '/root/bar.js', dependencies: []}, + ]; + + getDependencies.mockImpl(function() { + return q({ + mainModuleId: 'foo', + dependencies: modules + }); + }); + + require('../../JSTransformer').prototype.loadFileAndTransform + .mockImpl(function(tsets, path) { + return q({ + code: 'transformed ' + path, + sourceCode: 'source ' + path, + sourcePath: path + }); + }); + + wrapModule.mockImpl(function(module, code) { + return 'lol ' + code + ' lol'; + }); + + return packager.package('/root/foo.js', true, 'source_map_url') + .then(function(p) { + expect(p.addModule.mock.calls[0]).toEqual([ + 'lol transformed /root/foo.js lol', + 'source /root/foo.js', + '/root/foo.js' + ]); + expect(p.addModule.mock.calls[1]).toEqual([ + 'lol transformed /root/bar.js lol', + 'source /root/bar.js', + '/root/bar.js' + ]); + + expect(p.finalize.mock.calls[0]).toEqual([ + {runMainModule: true} + ]); + }); + }); + +}); diff --git a/react-packager/src/Packager/base64-vlq.js b/react-packager/src/Packager/base64-vlq.js new file mode 100644 index 00000000..91d490b7 --- /dev/null +++ b/react-packager/src/Packager/base64-vlq.js @@ -0,0 +1,168 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + * + * Based on the Base 64 VLQ implementation in Closure Compiler: + * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java + * + * Copyright 2011 The Closure Compiler Authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var charToIntMap = {}; +var intToCharMap = {}; + +'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + .split('') + .forEach(function (ch, index) { + charToIntMap[ch] = index; + intToCharMap[index] = ch; + }); + +var base64 = {}; +/** + * Encode an integer in the range of 0 to 63 to a single base 64 digit. + */ +base64.encode = function base64_encode(aNumber) { + if (aNumber in intToCharMap) { + return intToCharMap[aNumber]; + } + throw new TypeError("Must be between 0 and 63: " + aNumber); +}; + +/** + * Decode a single base 64 digit to an integer. + */ +base64.decode = function base64_decode(aChar) { + if (aChar in charToIntMap) { + return charToIntMap[aChar]; + } + throw new TypeError("Not a valid base 64 digit: " + aChar); +}; + + + +// A single base 64 digit can contain 6 bits of data. For the base 64 variable +// length quantities we use in the source map spec, the first bit is the sign, +// the next four bits are the actual value, and the 6th bit is the +// continuation bit. The continuation bit tells us whether there are more +// digits in this value following this digit. +// +// Continuation +// | Sign +// | | +// V V +// 101011 + +var VLQ_BASE_SHIFT = 5; + +// binary: 100000 +var VLQ_BASE = 1 << VLQ_BASE_SHIFT; + +// binary: 011111 +var VLQ_BASE_MASK = VLQ_BASE - 1; + +// binary: 100000 +var VLQ_CONTINUATION_BIT = VLQ_BASE; + +/** + * Converts from a two-complement value to a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) + * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) + */ +function toVLQSigned(aValue) { + return aValue < 0 + ? ((-aValue) << 1) + 1 + : (aValue << 1) + 0; +} + +/** + * Converts to a two-complement value from a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 + * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 + */ +function fromVLQSigned(aValue) { + var isNegative = (aValue & 1) === 1; + var shifted = aValue >> 1; + return isNegative + ? -shifted + : shifted; +} + +/** + * Returns the base 64 VLQ encoded value. + */ +exports.encode = function base64VLQ_encode(aValue) { + var encoded = ""; + var digit; + + var vlq = toVLQSigned(aValue); + + do { + digit = vlq & VLQ_BASE_MASK; + vlq >>>= VLQ_BASE_SHIFT; + if (vlq > 0) { + // There are still more digits in this value, so we must make sure the + // continuation bit is marked. + digit |= VLQ_CONTINUATION_BIT; + } + encoded += base64.encode(digit); + } while (vlq > 0); + + return encoded; +}; + +/** + * Decodes the next base 64 VLQ value from the given string and returns the + * value and the rest of the string via the out parameter. + */ +exports.decode = function base64VLQ_decode(aStr, aOutParam) { + var i = 0; + var strLen = aStr.length; + var result = 0; + var shift = 0; + var continuation, digit; + + do { + if (i >= strLen) { + throw new Error("Expected more digits in base 64 VLQ value."); + } + digit = base64.decode(aStr.charAt(i++)); + continuation = !!(digit & VLQ_CONTINUATION_BIT); + digit &= VLQ_BASE_MASK; + result = result + (digit << shift); + shift += VLQ_BASE_SHIFT; + } while (continuation); + + aOutParam.value = fromVLQSigned(result); + aOutParam.rest = aStr.slice(i); +}; + diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js new file mode 100644 index 00000000..0cecca2e --- /dev/null +++ b/react-packager/src/Packager/index.js @@ -0,0 +1,115 @@ +'use strict'; + +var assert = require('assert'); +var fs = require('fs'); +var path = require('path'); +var Promise = require('q').Promise; +var Transformer = require('../JSTransformer'); +var DependencyResolver = require('../DependencyResolver'); +var _ = require('underscore'); +var Package = require('./Package'); +var Activity = require('../Activity'); + +var DEFAULT_CONFIG = { + /** + * RegExp used to ignore paths when scanning the filesystem to calculate the + * dependency graph. + */ + blacklistRE: null, + + /** + * The kind of module system/transport wrapper to use for the modules bundled + * in the package. + */ + moduleFormat: 'haste', + + /** + * An ordered list of module names that should be considered as dependencies + * of every module in the system. The list is ordered because every item in + * the list will have an implicit dependency on all items before it. + * + * (This ordering is necessary to build, for example, polyfills that build on + * each other) + */ + polyfillModuleNames: [], + + /** + * DEPRECATED + * + * A string of code to be appended to the top of a package. + * + * TODO: THIS RUINS SOURCE MAPS. THIS OPTION SHOULD BE REMOVED ONCE WE GET + * config.polyfillModuleNames WORKING! + */ + runtimeCode: '' +}; + +function Packager(projectConfig) { + // Verify that the root exists. + var root = projectConfig.projectRoot; + assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); + this._rootPath = root; + + this._config = Object.create(DEFAULT_CONFIG); + for (var key in projectConfig) { + this._config[key] = projectConfig[key]; + } + + this._resolver = new DependencyResolver(this._config); + + this._transformer = new Transformer(projectConfig); +} + +Packager.prototype.kill = function() { + return this._transformer.kill(); +}; + +Packager.prototype.package = function(main, runModule, sourceMapUrl) { + var transformModule = this._transformModule.bind(this); + var ppackage = new Package(sourceMapUrl); + + var findEventId = Activity.startEvent('find dependencies'); + var transformEventId; + + return this._resolver.getDependencies(main) + .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(transformed) { + ppackage.addModule( + transformed.code, + transformed.sourceCode, + transformed.sourcePath + ); + }); + + ppackage.finalize({ runMainModule: runModule }); + return ppackage; + }); +}; + +Packager.prototype._transformModule = function(module) { + var resolver = this._resolver; + return this._transformer.loadFileAndTransform( + ['es6'], + path.resolve(module.path), + this._config.transformer || {} + ).then(function(transformed) { + return _.extend( + {}, + transformed, + {code: resolver.wrapModule(module, transformed.code)} + ); + }); +}; + +module.exports = Packager; diff --git a/react-packager/src/fb-path-utils/index.js b/react-packager/src/fb-path-utils/index.js new file mode 100644 index 00000000..b4a1cb96 --- /dev/null +++ b/react-packager/src/fb-path-utils/index.js @@ -0,0 +1,14 @@ +var absolutePath = require('absolute-path'); +var path = require('path'); +var pathIsInside = require('path-is-inside'); + +function isAbsolutePath(pathStr) { + return absolutePath(pathStr); +} + +function isChildPath(parentPath, childPath) { + return pathIsInside(parentPath, childPath); +} + +exports.isAbsolutePath = isAbsolutePath; +exports.isChildPath = isChildPath; From 4fb7489ee9942ba493e25998a829e4fa19695cf1 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Fri, 6 Feb 2015 15:45:28 -0800 Subject: [PATCH 002/936] 2015-02-05 updates - [ReactServer] Fix newly introduced bug | Amjad Masad - [ReactServer] Last sync from github | Amjad Masad - [RFC-ReactNative] Subscribable overhaul, clean up AppState/Reachability | Eric Vicenti - [ReactKit] Enable tappable subnodes | Alex Akers --- react-packager/index.js | 109 ++---------- react-packager/package.json | 8 +- react-packager/src/Activity/index.js | 3 +- .../src/DependencyResolver/haste/index.js | 2 +- .../FileWatcher/__tests__/FileWatcher-test.js | 4 +- react-packager/src/FileWatcher/index.js | 10 +- react-packager/src/JSTransformer/Cache.js | 22 +-- .../src/JSTransformer/__mocks__/worker.js | 5 + .../src/JSTransformer/__tests__/Cache-test.js | 42 ----- react-packager/src/JSTransformer/index.js | 6 + react-packager/src/Packager/Package.js | 8 +- react-packager/src/Packager/index.js | 4 + .../src/Server/__tests__/Server-test.js | 166 ++++++++++++++++++ react-packager/src/Server/index.js | 128 ++++++++++++++ 14 files changed, 351 insertions(+), 166 deletions(-) create mode 100644 react-packager/src/JSTransformer/__mocks__/worker.js create mode 100644 react-packager/src/Server/__tests__/Server-test.js create mode 100644 react-packager/src/Server/index.js diff --git a/react-packager/index.js b/react-packager/index.js index 52562cfe..59a22d6e 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -1,100 +1,19 @@ -var Packager = require('./src/Packager'); +'use strict'; + var Activity = require('./src/Activity'); -var url = require('url'); +var Server = require('./src/Server'); + +exports.middleware = function(options) { + var server = new Server(options); + return server.processRequest.bind(server); +}; exports.buildPackageFromUrl = function(options, reqUrl) { Activity.disable(); - var packager = createPackager(options); - var params = getOptionsFromPath(url.parse(reqUrl).pathname); - return packager.package( - params.main, - params.runModule, - params.sourceMapUrl - ).then(function(p) { - packager.kill(); - return p; - }); + var server = new Server(options); + return server.buildPackageFromUrl(reqUrl) + .then(function(p) { + server.kill(); + return p; + }); }; - -exports.catalystMiddleware = function(options) { - var packager = createPackager(options); - - return function(req, res, next) { - var options; - if (req.url.match(/\.bundle$/)) { - options = getOptionsFromPath(url.parse(req.url).pathname); - packager.package( - options.main, - options.runModule, - options.sourceMapUrl - ).then( - function(package) { - res.end(package.getSource()); - }, - function(error) { - handleError(res, error); - } - ).done(); - } else if (req.url.match(/\.map$/)) { - options = getOptionsFromPath(url.parse(req.url).pathname); - packager.package( - options.main, - options.runModule, - options.sourceMapUrl - ).then( - function(package) { - res.end(JSON.stringify(package.getSourceMap())); - }, - function(error) { - handleError(res, error); - } - ).done(); - } else { - next(); - } - }; -}; - -function getOptionsFromPath(pathname) { - var parts = pathname.split('.'); - // Remove the leading slash. - var main = parts[0].slice(1) + '.js'; - return { - runModule: parts.slice(1).some(function(part) { - return part === 'runModule'; - }), - main: main, - sourceMapUrl: parts.slice(0, -1).join('.') + '.map' - }; -} - -function handleError(res, error) { - res.writeHead(500, { - 'Content-Type': 'application/json; charset=UTF-8', - }); - - if (error.type === 'TransformError') { - res.end(JSON.stringify(error)); - } 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 createPackager(options) { - return new Packager({ - projectRoot: options.projectRoot, - blacklistRE: options.blacklistRE, - polyfillModuleNames: options.polyfillModuleNames || [], - runtimeCode: options.runtimeCode, - cacheVersion: options.cacheVersion, - resetCache: options.resetCache, - dev: options.dev, - }); -} - -exports.kill = Packager.kill; diff --git a/react-packager/package.json b/react-packager/package.json index db3e9e14..84230dec 100644 --- a/react-packager/package.json +++ b/react-packager/package.json @@ -1,6 +1,10 @@ { "name": "react-packager", - "version": "0.0.1", + "version": "0.0.2", "description": "", - "main": "index.js" + "main": "index.js", + "jest": { + "unmockedModulePathPatterns": ["source-map"], + "testPathIgnorePatterns": ["JSAppServer/node_modules"] + } } diff --git a/react-packager/src/Activity/index.js b/react-packager/src/Activity/index.js index 5387cbd4..a60f87b0 100644 --- a/react-packager/src/Activity/index.js +++ b/react-packager/src/Activity/index.js @@ -131,11 +131,12 @@ function _writeAction(action) { case 'endEvent': var startAction = _eventStarts[action.eventId]; + var startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : ''; console.log( '[' + fmtTime + '] ' + ' ' + startAction.eventName + '(' + (action.tstamp - startAction.tstamp) + 'ms)' + - data + startData ); delete _eventStarts[action.eventId]; break; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 960e49fe..40e4a1d4 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -29,7 +29,7 @@ function HasteDependencyResolver(config) { return filepath.indexOf('__tests__') !== -1 || (config.blacklistRE && config.blacklistRE.test(filepath)); }, - fileWatcher: new FileWatcher(config) + fileWatcher: new FileWatcher(config.projectRoot) }); this._polyfillModuleNames = [ diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index 84471517..af7a4083 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -17,14 +17,14 @@ describe('FileWatcher', function() { }); pit('it should get the watcher instance when ready', function() { - var fileWatcher = new FileWatcher({projectRoot: 'rootDir'}); + var fileWatcher = new FileWatcher('rootDir'); return fileWatcher.getWatcher().then(function(watcher) { expect(watcher instanceof Watcher).toBe(true); }); }); pit('it should end the watcher', function() { - var fileWatcher = new FileWatcher({projectRoot: 'rootDir'}); + var fileWatcher = new FileWatcher('rootDir'); Watcher.prototype.close.mockImplementation(function(callback) { callback(); }); diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index d7e1b82f..d8b17277 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -22,15 +22,15 @@ var MAX_WAIT_TIME = 3000; var memoizedInstances = Object.create(null); -function FileWatcher(projectConfig) { - if (memoizedInstances[projectConfig.projectRoot]) { - return memoizedInstances[projectConfig.projectRoot]; +function FileWatcher(projectRoot) { + if (memoizedInstances[projectRoot]) { + return memoizedInstances[projectRoot]; } else { - memoizedInstances[projectConfig.projectRoot] = this; + memoizedInstances[projectRoot] = this; } this._loadingWatcher = detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(projectConfig.projectRoot, {glob: '**/*.js'}); + var watcher = new Watcher(projectRoot, {glob: '**/*.js'}); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index 0c5bb942..4aefd6eb 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -4,7 +4,6 @@ var path = require('path'); var version = require('../../package.json').version; var tmpdir = require('os').tmpDir(); var pathUtils = require('../fb-path-utils'); -var FileWatcher = require('../FileWatcher'); var fs = require('fs'); var _ = require('underscore'); var q = require('q'); @@ -34,16 +33,6 @@ function Cache(projectConfig) { this._persistCache.bind(this), 2000 ); - - this._fileWatcher = new FileWatcher(projectConfig); - this._fileWatcher - .getWatcher() - .done(function(watcher) { - watcher.on('all', function(type, filepath) { - var absPath = path.join(projectConfig.projectRoot, filepath); - delete data[absPath]; - }); - }.bind(this)); } Cache.prototype.get = function(filepath, loaderCb) { @@ -75,11 +64,14 @@ Cache.prototype._set = function(filepath, loaderPromise) { }.bind(this)); }; +Cache.prototype.invalidate = function(filepath){ + if(this._has(filepath)) { + delete this._data[filepath]; + } +} + Cache.prototype.end = function() { - return q.all([ - this._persistCache(), - this._fileWatcher.end() - ]); + return this._persistCache(); }; Cache.prototype._persistCache = function() { diff --git a/react-packager/src/JSTransformer/__mocks__/worker.js b/react-packager/src/JSTransformer/__mocks__/worker.js new file mode 100644 index 00000000..04a24e8d --- /dev/null +++ b/react-packager/src/JSTransformer/__mocks__/worker.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function (data, callback) { + callback(null, {}); +}; diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 1ae537f1..85278c25 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -19,13 +19,6 @@ describe('JSTransformer Cache', function() { }); Cache = require('../Cache'); - - var FileWatcher = require('../../FileWatcher'); - FileWatcher.prototype.getWatcher = function() { - return q({ - on: function() {} - }); - }; }); describe('getting/settig', function() { @@ -76,41 +69,6 @@ describe('JSTransformer Cache', function() { }); }); }); - - pit('it invalidates cache after a file has changed', function() { - require('fs').stat.mockImpl(function(file, callback) { - callback(null, { - mtime: { - getTime: function() {} - } - }); - }); - var FileWatcher = require('../../FileWatcher'); - var triggerChangeFile; - FileWatcher.prototype.getWatcher = function() { - return q({ - on: function(type, callback) { - triggerChangeFile = callback; - } - }); - }; - - var cache = new Cache({projectRoot: '/rootDir'}); - var loaderCb = jest.genMockFn().mockImpl(function() { - return q('lol'); - }); - - return cache.get('/rootDir/someFile', loaderCb).then(function(value) { - expect(value).toBe('lol'); - triggerChangeFile('change', 'someFile'); - var loaderCb2 = jest.genMockFn().mockImpl(function() { - return q('lol2'); - }); - return cache.get('/rootDir/someFile', loaderCb2).then(function(value2) { - expect(value2).toBe('lol2'); - }); - }); - }); }); describe('loading cache from disk', function() { diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 95d7948b..b748c32e 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -25,6 +25,12 @@ Transformer.prototype.kill = function() { return this._cache.end(); }; +Transformer.prototype.invalidateFile = function(filePath) { + this._cache.invalidate(filePath); + //TODO: We can read the file and put it into the cache right here + // This would simplify some caching logic as we can be sure that the cache is up to date +} + Transformer.prototype.loadFileAndTransform = function( transformSets, filePath, diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index f6807397..f3f5d992 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -42,9 +42,11 @@ Package.prototype.finalize = function(options) { }; Package.prototype.getSource = function() { - return _.pluck(this._modules, 'transformedCode').join('\n') + '\n' + - 'RAW_SOURCE_MAP = ' + JSON.stringify(this.getSourceMap({excludeSource: true})) + ';\n' + - '\/\/@ sourceMappingURL=' + this._sourceMapUrl; + return this._source || ( + this._source = _.pluck(this._modules, 'transformedCode').join('\n') + '\n' + + 'RAW_SOURCE_MAP = ' + JSON.stringify(this.getSourceMap({excludeSource: true})) + + ';\n' + '\/\/@ sourceMappingURL=' + this._sourceMapUrl + ); }; Package.prototype.getSourceMap = function(options) { diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 0cecca2e..83af7c22 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -97,6 +97,10 @@ Packager.prototype.package = function(main, runModule, sourceMapUrl) { }); }; +Packager.prototype.invalidateFile = function(filePath){ + this._transformer.invalidateFile(filePath); +} + Packager.prototype._transformModule = function(module) { var resolver = this._resolver; return this._transformer.loadFileAndTransform( diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js new file mode 100644 index 00000000..0482d4f2 --- /dev/null +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -0,0 +1,166 @@ + 'use strict'; + +jest.dontMock('worker-farm') + .dontMock('q') + .dontMock('os') + .dontMock('errno/custom') + .dontMock('path') + .dontMock('url') + .dontMock('../'); + + +var server = require('../'); +var q = require('q'); + +describe('processRequest', function(){ + var server; + var Activity; + var Packager; + var FileWatcher; + + var options = { + projectRoot: 'root', + blacklistRE: null, + cacheVersion: null, + polyfillModuleNames: null + }; + + var makeRequest = function(requestHandler, requrl){ + var deferred = q.defer(); + requestHandler({ + url: requrl + },{ + end: function(res){ + deferred.resolve(res); + } + },{ + next: function(){} + } + ); + return deferred.promise; + }; + + var invalidatorFunc = jest.genMockFunction(); + var watcherFunc = jest.genMockFunction(); + var requestHandler; + + beforeEach(function(){ + Activity = require('../../Activity'); + Packager = require('../../Packager'); + FileWatcher = require('../../FileWatcher') + + Packager.prototype.package = function(main, runModule, sourceMapUrl) { + return q({ + getSource: function(){ + return "this is the source" + }, + getSourceMap: function(){ + return "this is the source map" + } + }) + }; + FileWatcher.prototype.getWatcher = function() { + return q({ + on: watcherFunc + }); + }; + + Packager.prototype.invalidateFile = invalidatorFunc; + + var Server = require('../'); + server = new Server(options); + requestHandler = server.processRequest.bind(server); + }); + + pit('returns JS bundle source on request of *.bundle',function(){ + result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); + return result.then(function(response){ + expect(response).toEqual("this is the source"); + }); + }); + + pit('returns sourcemap on request of *.map', function(){ + result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle.map'); + return result.then(function(response){ + expect(response).toEqual('"this is the source map"'); + }); + }); + + pit('watches all files in projectRoot', function(){ + result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); + return result.then(function(response){ + expect(watcherFunc.mock.calls[0][0]).toEqual('all'); + expect(watcherFunc.mock.calls[0][1]).not.toBe(null); + }) + }); + + + describe('file changes', function() { + var triggerFileChange; + beforeEach(function() { + FileWatcher.prototype.getWatcher = function() { + return q({ + on: function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + triggerFileChange = callback; + return this; + } + }); + }; + }); + + pit('invalides files in package when file is updated', function() { + result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); + return result.then(function(response){ + var onFileChange = watcherFunc.mock.calls[0][1]; + onFileChange('all','path/file.js'); + expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js'); + }); + }); + + pit('rebuilds the packages that contain a file when that file is changed', function() { + var packageFunc = jest.genMockFunction(); + packageFunc + .mockReturnValueOnce( + q({ + getSource: function(){ + return "this is the first source" + }, + getSourceMap: function(){}, + }) + ) + .mockReturnValue( + q({ + getSource: function(){ + return "this is the rebuilt source" + }, + getSourceMap: function(){}, + }) + ); + + Packager.prototype.package = packageFunc; + + var Server = require('../../Server'); + var server = new Server(options); + + requestHandler = server.processRequest.bind(server); + + + return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + .then(function(response){ + expect(response).toEqual("this is the first source"); + expect(packageFunc.mock.calls.length).toBe(1); + triggerFileChange('all','path/file.js'); + }) + .then(function(){ + expect(packageFunc.mock.calls.length).toBe(2); + return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + .then(function(response){ + expect(response).toEqual("this is the rebuilt source"); + }); + }); + }); + }); +}); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js new file mode 100644 index 00000000..877b48ca --- /dev/null +++ b/react-packager/src/Server/index.js @@ -0,0 +1,128 @@ +var url = require('url'); +var path = require('path'); +var FileWatcher = require('../FileWatcher') +var Packager = require('../Packager'); +var Activity = require('../Activity'); +var q = require('q'); + +module.exports = Server; + +function Server(options) { + this._projectRoot = options.projectRoot; + this._packages = Object.create(null); + this._packager = new Packager({ + projectRoot: options.projectRoot, + blacklistRE: options.blacklistRE, + polyfillModuleNames: options.polyfillModuleNames || [], + runtimeCode: options.runtimeCode, + cacheVersion: options.cacheVersion, + resetCache: options.resetCache, + dev: options.dev, + }); + + this._fileWatcher = new FileWatcher(options.projectRoot); + + var onFileChange = this._onFileChange.bind(this); + this._fileWatcher.getWatcher().done(function(watcher) { + watcher.on('all', onFileChange); + }); +} + +Server.prototype._onFileChange = function(type, filepath) { + var absPath = path.join(this._projectRoot, filepath); + this._packager.invalidateFile(absPath); + this._rebuildPackages(absPath); +}; + +Server.prototype._rebuildPackages = function(filepath) { + var buildPackage = this._buildPackage.bind(this); + var packages = this._packages; + Object.keys(packages).forEach(function(key) { + var options = getOptionsFromPath(url.parse(key).pathname); + packages[key] = buildPackage(options).then(function(p) { + // Make a throwaway call to getSource to cache the source string. + p.getSource(); + return p; + }); + }); +}; + +Server.prototype.kill = function() { + q.all([ + this._fileWatcher.end(), + this._packager.kill(), + ]); +}; + +Server.prototype._buildPackage = function(options) { + return this._packager.package( + options.main, + options.runModule, + options.sourceMapUrl + ); +}; + +Server.prototype.buildPackageFromUrl = function(reqUrl) { + var options = getOptionsFromPath(url.parse(reqUrl).pathname); + return this._buildPackage(options); +}; + +Server.prototype.processRequest = function(req, res, next) { + var requestType; + if (req.url.match(/\.bundle$/)) { + requestType = 'bundle'; + } else if (req.url.match(/\.map$/)) { + requestType = 'map'; + } else { + return next(); + } + + var startReqEventId = Activity.startEvent('request:' + req.url); + var options = getOptionsFromPath(url.parse(req.url).pathname); + var building = this._packages[req.url] || this._buildPackage(options) + this._packages[req.url] = building; + building.then( + function(p) { + if (requestType === 'bundle') { + res.end(p.getSource()); + Activity.endEvent(startReqEventId); + } else if (requestType === 'map') { + res.end(JSON.stringify(p.getSourceMap())); + Activity.endEvent(startReqEventId); + } + }, + function(error) { + handleError(res, error); + } + ).done(); +}; + +function getOptionsFromPath(pathname) { + var parts = pathname.split('.'); + // Remove the leading slash. + var main = parts[0].slice(1) + '.js'; + return { + runModule: parts.slice(1).some(function(part) { + return part === 'runModule'; + }), + main: main, + sourceMapUrl: parts.slice(0, -1).join('.') + '.map' + }; +} + +function handleError(res, error) { + res.writeHead(500, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + + if (error.type === 'TransformError') { + res.end(JSON.stringify(error)); + } 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', + })); + } +} From 6f95d915e1ff4e44a4a43d88db871d705714b94a Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Fri, 6 Feb 2015 15:46:31 -0800 Subject: [PATCH 003/936] 2015-02-06 updates - [ReactServer] Fix graph update | Amjad Masad - Added RCTStatusBarManager module | Andrew McCloud --- .../haste/DependencyGraph/index.js | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index d255a3c3..f80bd0b3 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -325,10 +325,26 @@ DependecyGraph.prototype._lookupName = function(modulePath) { } }; +DependecyGraph.prototype._deleteModule = function(module) { + delete this._graph[module.path]; + + // Others may keep a reference so we mark it as deleted. + module.deleted = true; + + // Haste allows different module to have the same id. + if (this._moduleById[module.id] === module) { + delete this._moduleById[module.id]; + } +}; + /** - * Update the graph and idices with the module. + * Update the graph and indices with the module. */ DependecyGraph.prototype._updateGraphWithModule = function(module) { + if (this._graph[module.path]) { + this._deleteModule(this._graph[module.path]); + } + this._graph[module.path] = module; if (this._moduleById[module.id]) { @@ -389,15 +405,7 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root return; } - delete this._graph[module]; - - // Others may keep a reference so we mark it as deleted. - module.deleted = true; - - // Modules may have same id. - if (this._moduleById[module.id] === module) { - delete this._moduleById[module.id]; - } + this._deleteModule(module); } else if (!(stat && stat.isDirectory())) { var self = this; this._loading = this._loading.then(function() { From 931041f3d2ea3b64ea4c3fab4022cb05fd9ebb27 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Sat, 7 Feb 2015 11:22:53 -0800 Subject: [PATCH 004/936] 2015-02-07 updates - Fixes reference to ReactPackager middleware | Tyler Smalley - [ReactKit] Fix bug where did not refresh (in RCTText) | Alex Akers - Removed duplication in react-native.js | Nick Lockwood - [ReactNative] Patch up ReactPerf to work again | Ben Alpert --- packager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 9d9491ac..4fcee0a3 100644 --- a/packager.js +++ b/packager.js @@ -67,7 +67,7 @@ function openStackFrameInEditor(req, res, next) { } function getAppMiddleware(options) { - return ReactPackager.catalystMiddleware({ + return ReactPackager.middleware({ dev: true, projectRoot: options.projectRoot, blacklistRE: blacklist(false), From 63e329e3884aee0fdfc4089401174c9b769116ef Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Sat, 7 Feb 2015 17:23:25 -0800 Subject: [PATCH 005/936] 2015-02-07 updates - Random little fixes | Andres Suarez - Add backButtonTitle to Navigator | Nick Poulden - [react-pacakger] Ignore malformed package.json | Amjad Masad - Renamed hasMove to hasMark | Rich Seymour - Update XMLHttpRequest.ios.js | Nick Poulden - Warn about missing dependencies for issue #16 | Andrew McCloud --- packager.js | 14 +++++++++- .../__tests__/DependencyGraph-test.js | 26 +++++++++++++++++++ .../haste/DependencyGraph/index.js | 10 +++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packager.js b/packager.js index 4fcee0a3..b59fbb18 100644 --- a/packager.js +++ b/packager.js @@ -3,13 +3,25 @@ */ 'use strict'; +var fs = require('fs'); +var path = require('path'); + +if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { + console.log( + '\n' + + 'Could not find dependencies.\n' + + 'Ensure dependencies are installed - ' + + 'run \'npm install\' from project root.\n' + ); + process.exit(); +} + var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); var connect = require('connect'); var http = require('http'); var launchEditor = require('./launchEditor.js'); var parseCommandLine = require('./parseCommandLine.js'); -var path = require('path'); var options = parseCommandLine([{ command: 'port', diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 9eeb9685..fbd01725 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -122,6 +122,32 @@ describe('DependencyGraph', function() { }); }); + pit('should ignore malformed packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': 'lol', + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['aPackage']}, + ]); + }); + }); + pit('can have multiple modules with the same name', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index f80bd0b3..130708b5 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -267,9 +267,15 @@ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { if (packagePath != null) { return readFile(packagePath, 'utf8') .then(function(content) { - var packageJson = JSON.parse(content); - if (packageJson.name == null) { + var packageJson; + try { + packageJson = JSON.parse(content); + } catch (e) { + debug('WARNING: malformed package.json: ', packagePath); + return q(); + } + if (packageJson.name == null) { debug( 'WARNING: package.json `%s` is missing a name field', packagePath From f7bd1e329a8fa59c8fb2ee54f83139061f3379a0 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Tue, 10 Feb 2015 16:27:57 -0800 Subject: [PATCH 006/936] 2015-02-09 updates - [react-server] Support multiple roots | Amjad Masad - [react-packager] Add a helpful error message when watcher fails to start | Amjad Masad - [madman] Fix review screen button and animation | Eric Vicenti --- packager.js | 4 +- react-packager/, | 0 .../__tests__/DependencyGraph-test.js | 68 ++++++++----------- .../haste/DependencyGraph/index.js | 50 ++++++++------ .../src/DependencyResolver/haste/index.js | 9 ++- .../FileWatcher/__tests__/FileWatcher-test.js | 12 ++-- react-packager/src/FileWatcher/index.js | 60 ++++++++++------ react-packager/src/JSTransformer/Cache.js | 21 ++++-- .../src/JSTransformer/__tests__/Cache-test.js | 12 ++-- .../src/Packager/__tests__/Packager-test.js | 2 +- react-packager/src/Packager/index.js | 16 +++-- .../src/Server/__tests__/Server-test.js | 33 ++++----- react-packager/src/Server/index.js | 18 ++--- 13 files changed, 171 insertions(+), 134 deletions(-) create mode 100644 react-packager/, diff --git a/packager.js b/packager.js index b59fbb18..e20bb99d 100644 --- a/packager.js +++ b/packager.js @@ -28,8 +28,8 @@ var options = parseCommandLine([{ default: 8081, }]); -if (!options.projectRoot) { - options.projectRoot = path.resolve(__dirname, '..'); +if (!options.projectRoots) { + options.projectRoots = [path.resolve(__dirname, '..')]; } console.log('\n' + diff --git a/react-packager/, b/react-packager/, new file mode 100644 index 00000000..e69de29b diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index fbd01725..fe8a18b6 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -21,12 +21,8 @@ describe('DependencyGraph', function() { DependencyGraph = require('../index'); fileWatcher = { - getWatcher: function() { - return q({ - on: function() { - return this; - } - }); + on: function() { + return this; } }; }); @@ -50,7 +46,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -79,7 +75,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -109,7 +105,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -179,7 +175,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) .toEqual([ @@ -220,7 +216,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -249,7 +245,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -284,7 +280,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -324,7 +320,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -364,7 +360,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -395,16 +391,12 @@ describe('DependencyGraph', function() { beforeEach(function() { fileWatcher = { - getWatcher: function() { - return q({ - on: function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - triggerFileChange = callback; - return this; - } - }); + on: function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + triggerFileChange = callback; + return this; } }; }); @@ -436,11 +428,11 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); - triggerFileChange('change', 'index.js'); + triggerFileChange('change', 'index.js', root); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -484,11 +476,11 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); - triggerFileChange('change', 'index.js'); + triggerFileChange('change', 'index.js', root); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -532,10 +524,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { delete filesystem.root.foo; - triggerFileChange('delete', 'foo.js'); + triggerFileChange('delete', 'foo.js', root); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -579,7 +571,7 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { filesystem.root['bar.js'] = [ '/**', @@ -587,10 +579,10 @@ describe('DependencyGraph', function() { ' */', 'require("foo")' ].join('\n'); - triggerFileChange('add', 'bar.js'); + triggerFileChange('add', 'bar.js', root); filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js'); + triggerFileChange('change', 'aPackage/main.js', root); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -644,7 +636,7 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ - root: root, + roots: [root], fileWatcher: fileWatcher, ignoreFilePath: function(filePath) { if (filePath === '/root/bar.js') { @@ -660,10 +652,10 @@ describe('DependencyGraph', function() { ' */', 'require("foo")' ].join('\n'); - triggerFileChange('add', 'bar.js'); + triggerFileChange('add', 'bar.js', root); filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js'); + triggerFileChange('change', 'aPackage/main.js', root); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -711,7 +703,7 @@ describe('DependencyGraph', function() { } } }); - var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); return dgraph.load().then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 130708b5..0cf05411 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -14,10 +14,10 @@ var lstat = q.nfbind(fs.lstat); var realpath = q.nfbind(fs.realpath); function DependecyGraph(options) { - this._root = options.root; + this._roots = options.roots; this._ignoreFilePath = options.ignoreFilePath || function(){}; this._loaded = false; - this._queue = [this._root]; + this._queue = this._roots.slice(); this._graph = Object.create(null); this._packageByRoot = Object.create(null); this._packagesById = Object.create(null); @@ -36,16 +36,14 @@ DependecyGraph.prototype.load = function() { * Given an entry file return an array of all the dependent module descriptors. */ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { - var absolutePath; - if (!isAbsolutePath(entryPath)) { - absolutePath = path.join(this._root, entryPath); - } else { - absolutePath = entryPath; + var absolutePath = this._getAbsolutePath(entryPath); + if (absolutePath == null) { + throw new Error('Cannot find entry file in any of the roots: ' + entryPath); } var module = this._graph[absolutePath]; if (module == null) { - throw new Error('Module with path "' + absolutePath + '" is not in graph'); + throw new Error('Module with path "' + entryPath + '" is not in graph'); } var self = this; @@ -172,15 +170,11 @@ DependecyGraph.prototype.resolveDependency = function( */ DependecyGraph.prototype._init = function() { var processChange = this._processFileChange.bind(this); - var loadingWatcher = this._fileWatcher.getWatcher(); + var watcher = this._fileWatcher; - this._loading = this.load() - .then(function() { - return loadingWatcher; - }) - .then(function(watcher) { - watcher.on('all', processChange); - }); + this._loading = this.load().then(function() { + watcher.on('all', processChange); + }); }; /** @@ -370,7 +364,6 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { * Find the nearest package to a module. */ DependecyGraph.prototype._lookupPackage = function(modulePath) { - var root = this._root; var packageByRoot = this._packageByRoot; /** @@ -380,8 +373,7 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) { // ideally we stop once we're outside root and this can be a simple child // dir check. However, we have to support modules that was symlinked inside // our project root. - if (!path.relative(root, currDir) === '' || currDir === '.' - || currDir === '/') { + if (currDir === '/') { return null; } else { var packageJson = packageByRoot[currDir]; @@ -400,7 +392,7 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) { * Process a filewatcher change event. */ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) { - var absPath = path.join(this._root, filePath); + var absPath = path.join(root, filePath); if (this._ignoreFilePath(absPath)) { return; } @@ -420,6 +412,24 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root } }; +/** + * Searches all roots for the file and returns the first one that has file of the same path. + */ +DependecyGraph.prototype._getAbsolutePath = function(filePath) { + if (isAbsolutePath(filePath)) { + return filePath; + } + + for (var i = 0, root; root = this._roots[i]; i++) { + var absPath = path.join(root, filePath); + if (this._graph[absPath]) { + return absPath; + } + } + + return null; +}; + /** * Extract all required modules from a `code` string. */ diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 40e4a1d4..feb69cce 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -23,13 +23,14 @@ var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; function HasteDependencyResolver(config) { + this._fileWatcher = new FileWatcher(config.projectRoots); this._depGraph = new DependencyGraph({ - root: config.projectRoot, + roots: config.projectRoots, ignoreFilePath: function(filepath) { return filepath.indexOf('__tests__') !== -1 || (config.blacklistRE && config.blacklistRE.test(filepath)); }, - fileWatcher: new FileWatcher(config.projectRoot) + fileWatcher: this._fileWatcher }); this._polyfillModuleNames = [ @@ -118,4 +119,8 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { }); }; +HasteDependencyResolver.prototype.end = function() { + return this._fileWatcher.end(); +}; + module.exports = HasteDependencyResolver; diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index af7a4083..8baae9e1 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -16,15 +16,17 @@ describe('FileWatcher', function() { }); }); - pit('it should get the watcher instance when ready', function() { - var fileWatcher = new FileWatcher('rootDir'); - return fileWatcher.getWatcher().then(function(watcher) { - expect(watcher instanceof Watcher).toBe(true); + it('it should get the watcher instance when ready', function() { + var fileWatcher = new FileWatcher(['rootDir']); + return fileWatcher._loading.then(function(watchers) { + watchers.forEach(function(watcher) { + expect(watcher instanceof Watcher).toBe(true); + }); }); }); pit('it should end the watcher', function() { - var fileWatcher = new FileWatcher('rootDir'); + var fileWatcher = new FileWatcher(['rootDir']); Watcher.prototype.close.mockImplementation(function(callback) { callback(); }); diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index d8b17277..c9a48058 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -1,7 +1,9 @@ 'use strict'; +var EventEmitter = require('events').EventEmitter; var sane = require('sane'); var q = require('q'); +var util = require('util'); var exec = require('child_process').exec; var Promise = q.Promise; @@ -20,37 +22,57 @@ module.exports = FileWatcher; var MAX_WAIT_TIME = 3000; -var memoizedInstances = Object.create(null); +function FileWatcher(projectRoots) { + var self = this; + this._loading = q.all( + projectRoots.map(createWatcher) + ).then(function(watchers) { + watchers.forEach(function(watcher) { + watcher.on('all', function(type, filepath, root) { + self.emit('all', type, filepath, root); + }); + }); + return watchers; + }); + this._loading.done(); +} -function FileWatcher(projectRoot) { - if (memoizedInstances[projectRoot]) { - return memoizedInstances[projectRoot]; - } else { - memoizedInstances[projectRoot] = this; +util.inherits(FileWatcher, EventEmitter); + +FileWatcher.prototype.end = function() { + return this._loading.then(function(watchers) { + watchers.forEach(function(watcher) { + delete watchersByRoot[watcher._root]; + return q.ninvoke(watcher, 'close'); + }); + }); +}; + +var watchersByRoot = Object.create(null); + +function createWatcher(root) { + if (watchersByRoot[root] != null) { + return Promise.resolve(watchersByRoot[root]); } - this._loadingWatcher = detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(projectRoot, {glob: '**/*.js'}); + return detectingWatcherClass.then(function(Watcher) { + var watcher = new Watcher(root, {glob: '**/*.js'}); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { - reject(new Error('Watcher took too long to load.')); + reject(new Error([ + 'Watcher took too long to load', + 'Try running `watchman` from your terminal', + 'https://facebook.github.io/watchman/docs/troubleshooting.html', + ].join('\n'))); }, MAX_WAIT_TIME); watcher.once('ready', function() { clearTimeout(rejectTimeout); + watchersByRoot[root] = watcher; + watcher._root = root; resolve(watcher); }); }); }); } - -FileWatcher.prototype.getWatcher = function() { - return this._loadingWatcher; -}; - -FileWatcher.prototype.end = function() { - return this._loadingWatcher.then(function(watcher) { - return q.ninvoke(watcher, 'close'); - }); -}; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index 4aefd6eb..577af696 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -13,12 +13,7 @@ var Promise = q.Promise; module.exports = Cache; function Cache(projectConfig) { - this._cacheFilePath = path.join( - tmpdir, - 'React-Packager-JSTransformer-' + version + '-' + - projectConfig.projectRoot.split(path.sep).join('-') + - '-' + (projectConfig.cacheVersion || '0') - ); + this._cacheFilePath = cacheFilePath(projectConfig); var data; if (!projectConfig.resetCache) { @@ -118,3 +113,17 @@ function loadCacheSync(cacheFilepath) { return ret; } + +function cacheFilePath(projectConfig) { + var roots = projectConfig.projectRoots.join(',').split(path.sep).join('-'); + var cacheVersion = projectConfig.cacheVersion || '0'; + return path.join( + tmpdir, + [ + 'react-packager-cache', + version, + cacheVersion, + roots, + ].join('-') + ); +} diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 85278c25..c77c6384 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -23,7 +23,7 @@ describe('JSTransformer Cache', function() { describe('getting/settig', function() { it('calls loader callback for uncached file', function() { - var cache = new Cache({projectRoot: '/rootDir'}); + var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { return q(); }); @@ -39,7 +39,7 @@ describe('JSTransformer Cache', function() { } }); }); - var cache = new Cache({projectRoot: '/rootDir'}); + var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { return q('lol'); }); @@ -56,7 +56,7 @@ describe('JSTransformer Cache', function() { } }); }); - var cache = new Cache({projectRoot: '/rootDir'}); + var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { return q('lol'); }); @@ -117,7 +117,7 @@ describe('JSTransformer Cache', function() { }); pit('should load cache from disk', function() { - var cache = new Cache({projectRoot: '/rootDir'}); + var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn(); return cache.get('/rootDir/someFile', loaderCb).then(function(value) { expect(loaderCb).not.toBeCalled(); @@ -143,7 +143,7 @@ describe('JSTransformer Cache', function() { return 123; }; - var cache = new Cache({projectRoot: '/rootDir'}); + var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { return q('new value'); }); @@ -184,7 +184,7 @@ describe('JSTransformer Cache', function() { }); }); - var cache = new Cache({projectRoot: '/rootDir'}); + var cache = new Cache({projectRoots: ['/rootDir']}); cache.get('/rootDir/bar', function() { return q('bar value'); }); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 335db930..21af12ca 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -35,7 +35,7 @@ describe('Packager', function() { }; }); - var packager = new Packager({}); + var packager = new Packager({projectRoots: []}); var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 83af7c22..0bf3e7d8 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -3,6 +3,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); +var q = require('q'); var Promise = require('q').Promise; var Transformer = require('../JSTransformer'); var DependencyResolver = require('../DependencyResolver'); @@ -45,10 +46,7 @@ var DEFAULT_CONFIG = { }; function Packager(projectConfig) { - // Verify that the root exists. - var root = projectConfig.projectRoot; - assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); - this._rootPath = root; + projectConfig.projectRoots.forEach(verifyRootExists); this._config = Object.create(DEFAULT_CONFIG); for (var key in projectConfig) { @@ -61,7 +59,10 @@ function Packager(projectConfig) { } Packager.prototype.kill = function() { - return this._transformer.kill(); + return q.all([ + this._transformer.kill(), + this._resolver.end(), + ]); }; Packager.prototype.package = function(main, runModule, sourceMapUrl) { @@ -116,4 +117,9 @@ Packager.prototype._transformModule = function(module) { }); }; +function verifyRootExists(root) { + // Verify that the root exists. + assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); +} + module.exports = Packager; diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 0482d4f2..3b37c76e 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -1,6 +1,4 @@ - 'use strict'; - -jest.dontMock('worker-farm') +jest.setMock('worker-farm', function(){ return function(){}; }) .dontMock('q') .dontMock('os') .dontMock('errno/custom') @@ -19,7 +17,7 @@ describe('processRequest', function(){ var FileWatcher; var options = { - projectRoot: 'root', + projectRoots: ['root'], blacklistRE: null, cacheVersion: null, polyfillModuleNames: null @@ -59,11 +57,8 @@ describe('processRequest', function(){ } }) }; - FileWatcher.prototype.getWatcher = function() { - return q({ - on: watcherFunc - }); - }; + + FileWatcher.prototype.on = watcherFunc; Packager.prototype.invalidateFile = invalidatorFunc; @@ -98,16 +93,12 @@ describe('processRequest', function(){ describe('file changes', function() { var triggerFileChange; beforeEach(function() { - FileWatcher.prototype.getWatcher = function() { - return q({ - on: function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - triggerFileChange = callback; - return this; - } - }); + FileWatcher.prototype.on = function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + triggerFileChange = callback; + return this; }; }); @@ -115,7 +106,7 @@ describe('processRequest', function(){ result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); return result.then(function(response){ var onFileChange = watcherFunc.mock.calls[0][1]; - onFileChange('all','path/file.js'); + onFileChange('all','path/file.js', options.projectRoots[0]); expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js'); }); }); @@ -152,7 +143,7 @@ describe('processRequest', function(){ .then(function(response){ expect(response).toEqual("this is the first source"); expect(packageFunc.mock.calls.length).toBe(1); - triggerFileChange('all','path/file.js'); + triggerFileChange('all','path/file.js', options.projectRoots[0]); }) .then(function(){ expect(packageFunc.mock.calls.length).toBe(2); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 877b48ca..36681d63 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -8,10 +8,10 @@ var q = require('q'); module.exports = Server; function Server(options) { - this._projectRoot = options.projectRoot; + this._projectRoots = options.projectRoots; this._packages = Object.create(null); this._packager = new Packager({ - projectRoot: options.projectRoot, + projectRoots: options.projectRoots, blacklistRE: options.blacklistRE, polyfillModuleNames: options.polyfillModuleNames || [], runtimeCode: options.runtimeCode, @@ -20,18 +20,18 @@ function Server(options) { dev: options.dev, }); - this._fileWatcher = new FileWatcher(options.projectRoot); + this._fileWatcher = new FileWatcher(options.projectRoots); var onFileChange = this._onFileChange.bind(this); - this._fileWatcher.getWatcher().done(function(watcher) { - watcher.on('all', onFileChange); - }); + this._fileWatcher.on('all', onFileChange); } -Server.prototype._onFileChange = function(type, filepath) { - var absPath = path.join(this._projectRoot, filepath); +Server.prototype._onFileChange = function(type, filepath, root) { + var absPath = path.join(root, filepath); this._packager.invalidateFile(absPath); - this._rebuildPackages(absPath); + // Make sure the file watcher event runs through the system before + // we rebuild the packages. + setImmediate(this._rebuildPackages.bind(this, absPath)) }; Server.prototype._rebuildPackages = function(filepath) { From 8f5d3678d8bb9adfccd29eecc525bde343d3315d Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 11 Feb 2015 16:37:51 -0800 Subject: [PATCH 007/936] 2015-02-09 updates [react-packager] Update other instances of projectRoot to projectRoots | Amjad Masad [react-packager] Debug page | Amjad Masad --- packager.js | 12 ++++-- .../haste/DependencyGraph/index.js | 24 +++++++++++- .../src/DependencyResolver/haste/index.js | 5 +++ react-packager/src/Packager/Package.js | 22 +++++++++++ react-packager/src/Packager/index.js | 6 +++ .../src/Server/__tests__/Server-test.js | 1 + react-packager/src/Server/index.js | 37 +++++++++++++++++++ 7 files changed, 101 insertions(+), 6 deletions(-) diff --git a/packager.js b/packager.js index e20bb99d..58f93a58 100644 --- a/packager.js +++ b/packager.js @@ -81,7 +81,7 @@ function openStackFrameInEditor(req, res, next) { function getAppMiddleware(options) { return ReactPackager.middleware({ dev: true, - projectRoot: options.projectRoot, + projectRoots: options.projectRoots, blacklistRE: blacklist(false), cacheVersion: '2', polyfillModuleNames: [ @@ -98,9 +98,13 @@ function runServer( var app = connect() .use(loadRawBody) .use(openStackFrameInEditor) - .use(getAppMiddleware(options)) - .use(connect.static(options.projectRoot)) - .use(connect.logger()) + .use(getAppMiddleware(options)); + + options.projectRoots.forEach(function(root) { + app.use(connect.static(root)); + }); + + app.use(connect.logger()) .use(connect.compress()) .use(connect.errorHandler()); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 0cf05411..9a939620 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -7,6 +7,7 @@ var docblock = require('./docblock'); var path = require('path'); var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); +var util = require('util'); var readFile = q.nfbind(fs.readFile); var readDir = q.nfbind(fs.readdir); @@ -22,6 +23,7 @@ function DependecyGraph(options) { this._packageByRoot = Object.create(null); this._packagesById = Object.create(null); this._moduleById = Object.create(null); + this._debugUpdateEvents = []; this._fileWatcher = options.fileWatcher; // Kick off the search process to precompute the dependency graph. @@ -196,16 +198,20 @@ DependecyGraph.prototype._search = function() { return readDir(dir) .then(function(files){ return q.all(files.map(function(filePath) { - return realpath(path.join(dir, filePath)); + return realpath(path.join(dir, filePath)).catch(handleBrokenLink); })); }) .then(function(filePaths) { filePaths = filePaths.filter(function(filePath) { + if (filePath == null) { + return false + } + return !self._ignoreFilePath(filePath); }); var statsP = filePaths.map(function(filePath) { - return lstat(filePath); + return lstat(filePath).catch(handleBrokenLink); }); return [ @@ -397,6 +403,8 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root return; } + this._debugUpdateEvents.push({event: eventType, path: filePath}); + if (eventType === 'delete') { var module = this._graph[absPath]; if (module == null) { @@ -412,6 +420,13 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root } }; +DependecyGraph.prototype.getDebugInfo = function() { + return '

FileWatcher Update Events

' + + '
' + util.inspect(this._debugUpdateEvents) + '
' + + '

Graph dump

' + + '
' + util.inspect(this._graph) + '
'; +}; + /** * Searches all roots for the file and returns the first one that has file of the same path. */ @@ -471,4 +486,9 @@ function withExtJs(file) { } } +function handleBrokenLink(e) { + debug('WARNING: error stating, possibly broken symlink', e.message); + return q(); +} + module.exports = DependecyGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index feb69cce..211da365 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -119,8 +119,13 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { }); }; + HasteDependencyResolver.prototype.end = function() { return this._fileWatcher.end(); }; +HasteDependencyResolver.prototype.getDebugInfo = function() { + return this._depGraph.getDebugInfo(); +}; + module.exports = HasteDependencyResolver; diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index f3f5d992..787684bc 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -108,3 +108,25 @@ Package.prototype._getMappings = function() { } return mappings; }; + +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'); +}; diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 0bf3e7d8..3aef649d 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -117,9 +117,15 @@ Packager.prototype._transformModule = function(module) { }); }; + function verifyRootExists(root) { // Verify that the root exists. assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); } +Packager.prototype.getGraphDebugInfo = function() { + return this._resolver.getDebugInfo(); +}; + + module.exports = Packager; diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 3b37c76e..511ec8a3 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -144,6 +144,7 @@ describe('processRequest', function(){ expect(response).toEqual("this is the first source"); expect(packageFunc.mock.calls.length).toBe(1); triggerFileChange('all','path/file.js', options.projectRoots[0]); + jest.runAllTimers(); }) .then(function(){ expect(packageFunc.mock.calls.length).toBe(2); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 36681d63..e71572ed 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -67,12 +67,49 @@ Server.prototype.buildPackageFromUrl = function(reqUrl) { return this._buildPackage(options); }; +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] === 'packages') { + ret += '

Cached Packages

'; + q.all(Object.keys(this._packages).map(function(url) { + return this._packages[url].then(function(p) { + ret += '

' + url + '

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

Dependency Graph

'; + ret += this._packager.getGraphDebugInfo(); + res.end(ret); + } else { + res.writeHead('404'); + res.end('Invalid debug request'); + return; + } +}; + Server.prototype.processRequest = function(req, res, next) { var requestType; if (req.url.match(/\.bundle$/)) { requestType = 'bundle'; } else if (req.url.match(/\.map$/)) { requestType = 'map'; + } else if (req.url.match(/^\/debug/)) { + this._processDebugRequest(req.url, res); + return; } else { return next(); } From 0bbd32514ed07557f90ccf369c1ad974160425c8 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 18 Feb 2015 17:39:09 -0800 Subject: [PATCH 008/936] Updates from Fri Feb 13 - [ReactNative] Fix throttle warning and warn in callback instead of render | Christopher Chedeau - [react-packager][streamline oss] Remove react-page-middleware | Amjad Masad - [ReactNative] Turn on perf measurement around a group feed load | Jing Chen - Implemented Layout animations | Nick Lockwood - [ReactNative] Revert D1815137 - avoid dropping touch start on missing target | Eric Vicenti - Moved RKPOPAnimationManager into FBReactKitComponents | Nick Lockwood - Extracted RKAnimationManager | Nick Lockwood --- react-packager/src/DependencyResolver/haste/index.js | 6 ++---- .../src/DependencyResolver/haste/polyfills}/console.js | 0 .../src/DependencyResolver/haste/polyfills}/error-guard.js | 0 3 files changed, 2 insertions(+), 4 deletions(-) rename {polyfill => react-packager/src/DependencyResolver/haste/polyfills}/console.js (100%) rename {polyfill => react-packager/src/DependencyResolver/haste/polyfills}/error-guard.js (100%) diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 211da365..13525bda 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -5,10 +5,6 @@ var FileWatcher = require('../../FileWatcher'); var DependencyGraph = require('./DependencyGraph'); var ModuleDescriptor = require('../ModuleDescriptor'); -var DEFAULT_POLYFILLS = [ - -]; - var DEFINE_MODULE_CODE = '__d(' + '\'_moduleName_\',' + @@ -39,6 +35,8 @@ function HasteDependencyResolver(config) { : path.join(__dirname, 'polyfills/prelude.js'), path.join(__dirname, 'polyfills/require.js'), path.join(__dirname, 'polyfills/polyfills.js'), + path.join(__dirname, 'polyfills/console.js'), + path.join(__dirname, 'polyfills/error-guard.js'), ].concat( config.polyfillModuleNames || [] ); diff --git a/polyfill/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js similarity index 100% rename from polyfill/console.js rename to react-packager/src/DependencyResolver/haste/polyfills/console.js diff --git a/polyfill/error-guard.js b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js similarity index 100% rename from polyfill/error-guard.js rename to react-packager/src/DependencyResolver/haste/polyfills/error-guard.js From 6cbd5fb9415d328bcbea7695a2cc52997c8572b8 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 18 Feb 2015 17:43:36 -0800 Subject: [PATCH 009/936] Updates from Sun Feb 15 - [react-packager][streamline oss] Injectable transformer | Amjad Masad --- packager.js | 5 +--- react-packager/src/JSTransformer/index.js | 23 +++++----------- react-packager/src/Server/index.js | 1 + .../transformer.js => transformer.js | 26 ++++++++++++++++++- 4 files changed, 33 insertions(+), 22 deletions(-) rename react-packager/src/JSTransformer/transformer.js => transformer.js (66%) diff --git a/packager.js b/packager.js index 58f93a58..fd0b14f7 100644 --- a/packager.js +++ b/packager.js @@ -84,10 +84,7 @@ function getAppMiddleware(options) { projectRoots: options.projectRoots, blacklistRE: blacklist(false), cacheVersion: '2', - polyfillModuleNames: [ - path.resolve(__dirname, 'polyfill/console.js'), - path.resolve(__dirname, 'polyfill/error-guard.js'), - ] + transformModulePath: require.resolve('./transformer.js'), }); } diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index b748c32e..ce7de143 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -8,9 +8,6 @@ var Cache = require('./Cache'); var _ = require('underscore'); var workerFarm = require('worker-farm'); -var workers = workerFarm(require.resolve('./worker')); -warmupWorkers(); - var readFile = q.nfbind(fs.readFile); module.exports = Transformer; @@ -18,10 +15,14 @@ Transformer.TransformError = TransformError; function Transformer(projectConfig) { this._cache = new Cache(projectConfig); + this._workers = workerFarm( + {autoStart: true}, + projectConfig.transformModulePath + ); } Transformer.prototype.kill = function() { - workerFarm.end(workers); + workerFarm.end(this._workers); return this._cache.end(); }; @@ -36,6 +37,7 @@ Transformer.prototype.loadFileAndTransform = function( filePath, options ) { + var workers = this._workers; return this._cache.get(filePath, function() { return readFile(filePath) .then(function(buffer) { @@ -62,19 +64,6 @@ Transformer.prototype.loadFileAndTransform = function( }); }; -// worker-farm module starts workers lazily. But we want them to take time -// to initialize so we send a dummy request. -// see https://github.com/rvagg/node-worker-farm/issues/23 -function warmupWorkers() { - os.cpus().forEach(function() { - workers({ - transformSets: ['es6'], - sourceCode: '\n', - options: {} - }, function() {}); - }); -} - function TransformError() {} TransformError.__proto__ = SyntaxError.prototype; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index e71572ed..5545b349 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -18,6 +18,7 @@ function Server(options) { cacheVersion: options.cacheVersion, resetCache: options.resetCache, dev: options.dev, + transformModulePath: options.transformModulePath }); this._fileWatcher = new FileWatcher(options.projectRoots); diff --git a/react-packager/src/JSTransformer/transformer.js b/transformer.js similarity index 66% rename from react-packager/src/JSTransformer/transformer.js rename to transformer.js index 39e409b3..df5e7143 100644 --- a/react-packager/src/JSTransformer/transformer.js +++ b/transformer.js @@ -30,4 +30,28 @@ function transform(transformSets, srcTxt, options) { return jstransform(visitorList, staticTypeSyntaxResult.code); } -exports.transform = transform; +module.exports = function(data, callback) { + var result; + try { + result = transform( + data.transformSets, + data.sourceCode, + data.options + ); + } catch (e) { + return callback(null, { + error: { + lineNumber: e.lineNumber, + column: e.column, + message: e.message, + stack: e.stack, + description: e.description + } + }); + } + + callback(null, result); +}; + +// export for use in jest +module.exports.transform = transform; From 6dafafeea13bbb94e9fdd5ea473ce5898ec7afd4 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 18 Feb 2015 17:51:14 -0800 Subject: [PATCH 010/936] Updates from Wed Feb 18 - [reactnative] s/SpinnerIOS/ActivityIndicatorIOS/ | Dan Witte - [react-packager] Add a non-persistent mode for static builds | Amjad Masad - [React Native] Fix stored file rejection when initializing cache | Alex Akers - [React Native] Consolidate network requests in image downloader | Alex Akers - [React Native] Update RCTCache | Alex Akers - Converted all low-hanging RKBridgeModules in FBReactKit to RCTBridgeModules | Nick Lockwood --- react-packager/index.js | 22 ++++++++- .../__tests__/HasteDependencyResolver-test.js | 47 ++++++++++++++++++- .../src/DependencyResolver/haste/index.js | 7 +-- react-packager/src/FileWatcher/index.js | 8 ++++ .../__tests__/Transformer-test.js | 8 +++- react-packager/src/JSTransformer/index.js | 29 +++++++++--- react-packager/src/Packager/index.js | 18 +++---- react-packager/src/Server/index.js | 13 +++-- 8 files changed, 124 insertions(+), 28 deletions(-) diff --git a/react-packager/index.js b/react-packager/index.js index 59a22d6e..65ae88d8 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -10,10 +10,30 @@ exports.middleware = function(options) { exports.buildPackageFromUrl = function(options, reqUrl) { Activity.disable(); + // Don't start the filewatcher or the cache. + if (options.nonPersistent == null) { + options.nonPersistent = true; + } + var server = new Server(options); return server.buildPackageFromUrl(reqUrl) .then(function(p) { - server.kill(); + server.end(); return p; }); }; + +exports.getDependencies = function(options, main) { + Activity.disable(); + // Don't start the filewatcher or the cache. + if (options.nonPersistent == null) { + options.nonPersistent = true; + } + + var server = new Server(options); + return server.getDependencies(main) + .then(function(r) { + server.end(); + return r.dependencies; + }); +}; diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index ca180929..9b43f97e 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -55,6 +55,25 @@ describe('HasteDependencyResolver', function() { 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' + ], + }, module ]); }); @@ -97,11 +116,35 @@ describe('HasteDependencyResolver', function() { 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' + ], + }, { path: 'some module', id: 'some module', isPolyfill: true, - dependencies: [ 'polyfills/prelude.js', 'polyfills/require.js', - 'polyfills/polyfills.js'] + dependencies: [ + 'polyfills/prelude.js', + 'polyfills/require.js', + 'polyfills/polyfills.js', + 'polyfills/console.js', + 'polyfills/error-guard.js', + ] }, module ]); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 13525bda..6e2cd6fc 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -19,7 +19,10 @@ var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; function HasteDependencyResolver(config) { - this._fileWatcher = new FileWatcher(config.projectRoots); + this._fileWatcher = config.nonPersistent + ? FileWatcher.createDummyWatcher() + : new FileWatcher(config.projectRoots); + this._depGraph = new DependencyGraph({ roots: config.projectRoots, ignoreFilePath: function(filepath) { @@ -97,7 +100,6 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { } } - var relativizedCode = code.replace(REL_REQUIRE_STMT, function(codeMatch, depName) { var dep = resolvedDeps[depName]; @@ -117,7 +119,6 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { }); }; - HasteDependencyResolver.prototype.end = function() { return this._fileWatcher.end(); }; diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index c9a48058..f2721d8c 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -76,3 +76,11 @@ function createWatcher(root) { }); }); } + +FileWatcher.createDummyWatcher = function() { + var ev = new EventEmitter(); + ev.end = function() { + return q(); + }; + return ev; +}; diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index fb33a834..6c9c6644 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -6,6 +6,10 @@ jest .dontMock('os') .dontMock('../index'); +var OPTIONS = { + transformModulePath: '/foo/bar' +}; + describe('Transformer', function() { var Transformer; var workers; @@ -32,7 +36,7 @@ describe('Transformer', function() { callback(null, 'content'); }); - return new Transformer({}).loadFileAndTransform([], 'file', {}) + return new Transformer(OPTIONS).loadFileAndTransform([], 'file', {}) .then(function(data) { expect(data).toEqual({ code: 'transformed', @@ -55,7 +59,7 @@ describe('Transformer', function() { callback(null, {error: esprimaError}); }); - return new Transformer({}).loadFileAndTransform([], 'foo-file.js', {}) + return new Transformer(OPTIONS).loadFileAndTransform([], 'foo-file.js', {}) .catch(function(error) { expect(error.type).toEqual('TransformError'); expect(error.snippet).toEqual([ diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index ce7de143..7b01d961 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -14,15 +14,21 @@ module.exports = Transformer; Transformer.TransformError = TransformError; function Transformer(projectConfig) { - this._cache = new Cache(projectConfig); - this._workers = workerFarm( - {autoStart: true}, - projectConfig.transformModulePath - ); + this._cache = projectConfig.nonPersistent + ? new DummyCache() : new Cache(projectConfig); + + if (projectConfig.transformModulePath == null) { + this._failedToStart = q.Promise.reject(new Error('No transfrom module')); + } else { + this._workers = workerFarm( + {autoStart: true}, + projectConfig.transformModulePath + ); + } } Transformer.prototype.kill = function() { - workerFarm.end(this._workers); + this._workers && workerFarm.end(this._workers); return this._cache.end(); }; @@ -37,6 +43,10 @@ Transformer.prototype.loadFileAndTransform = function( filePath, options ) { + if (this._failedToStart) { + return this._failedToStart; + } + var workers = this._workers; return this._cache.get(filePath, function() { return readFile(filePath) @@ -93,3 +103,10 @@ function formatEsprimaError(err, filename, source) { error.description = err.description; return error; } + +function DummyCache() {} +DummyCache.prototype.get = function(filePath, loaderCb) { + return loaderCb(); +}; +DummyCache.prototype.end = +DummyCache.prototype.invalidate = function(){}; diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 3aef649d..3ec4e378 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -34,15 +34,7 @@ var DEFAULT_CONFIG = { */ polyfillModuleNames: [], - /** - * DEPRECATED - * - * A string of code to be appended to the top of a package. - * - * TODO: THIS RUINS SOURCE MAPS. THIS OPTION SHOULD BE REMOVED ONCE WE GET - * config.polyfillModuleNames WORKING! - */ - runtimeCode: '' + nonPersistent: false, }; function Packager(projectConfig) { @@ -72,7 +64,7 @@ Packager.prototype.package = function(main, runModule, sourceMapUrl) { var findEventId = Activity.startEvent('find dependencies'); var transformEventId; - return this._resolver.getDependencies(main) + return this.getDependencies(main) .then(function(result) { Activity.endEvent(findEventId); transformEventId = Activity.startEvent('transform'); @@ -98,10 +90,14 @@ Packager.prototype.package = function(main, runModule, sourceMapUrl) { }); }; -Packager.prototype.invalidateFile = function(filePath){ +Packager.prototype.invalidateFile = function(filePath) { this._transformer.invalidateFile(filePath); } +Packager.prototype.getDependencies = function(main) { + return this._resolver.getDependencies(main); +}; + Packager.prototype._transformModule = function(module) { var resolver = this._resolver; return this._transformer.loadFileAndTransform( diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 5545b349..26929ebb 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -18,10 +18,13 @@ function Server(options) { cacheVersion: options.cacheVersion, resetCache: options.resetCache, dev: options.dev, - transformModulePath: options.transformModulePath + transformModulePath: options.transformModulePath, + nonPersistent: options.nonPersistent, }); - this._fileWatcher = new FileWatcher(options.projectRoots); + this._fileWatcher = options.nonPersistent + ? FileWatcher.createDummyWatcher() + : new FileWatcher(options.projectRoots); var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); @@ -48,7 +51,7 @@ Server.prototype._rebuildPackages = function(filepath) { }); }; -Server.prototype.kill = function() { +Server.prototype.end = function() { q.all([ this._fileWatcher.end(), this._packager.kill(), @@ -68,6 +71,10 @@ Server.prototype.buildPackageFromUrl = function(reqUrl) { return this._buildPackage(options); }; +Server.prototype.getDependencies = function(main) { + return this._packager.getDependencies(main); +}; + Server.prototype._processDebugRequest = function(reqUrl, res) { var ret = ''; var pathname = url.parse(reqUrl).pathname; From adbd8170caa12a2c8ad2da8fcfebbf277b84db20 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 19 Feb 2015 11:21:34 -0800 Subject: [PATCH 011/936] [react-packager] Add root to packager rootProjects via cli | Boopathi Rajaa --- packager.js | 13 +++++++++++++ packager.sh | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index fd0b14f7..6d5336ef 100644 --- a/packager.js +++ b/packager.js @@ -26,12 +26,25 @@ var parseCommandLine = require('./parseCommandLine.js'); var options = parseCommandLine([{ command: 'port', default: 8081, +}, { + command: 'root', + description: 'add another root(s) to be used by the packager in this project', }]); if (!options.projectRoots) { options.projectRoots = [path.resolve(__dirname, '..')]; } +if (options.root) { + if (typeof options.root === 'string') { + options.projectRoots.push(path.resolve(options.root)); + } else { + options.root.forEach(function(root) { + options.projectRoots.push(path.resolve(root)); + }); + } +} + console.log('\n' + ' ===============================================================\n' + ' | Running packager on port ' + options.port + '. \n' + diff --git a/packager.sh b/packager.sh index 94cc7171..98e42184 100755 --- a/packager.sh +++ b/packager.sh @@ -3,4 +3,4 @@ ulimit -n 4096 THIS_DIR=$(dirname "$0") -node $THIS_DIR/packager.js +node $THIS_DIR/packager.js "$@" From 382e1553af61fc4e22924b7ad188390958cc33a0 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Thu, 19 Feb 2015 20:10:52 -0800 Subject: [PATCH 012/936] [react-packager][streamline oss] Move open sourced JS source to react-native-github --- blacklist.js | 45 ++ launchEditor.js | 40 + launchPackager.command | 10 + packager.js | 122 +++ packager.sh | 6 + parseCommandLine.js | 52 ++ react-packager/, | 0 react-packager/.jshintrc | 86 +++ react-packager/.npmignore | 8 + react-packager/__mocks__/debug.js | 5 + react-packager/__mocks__/net.js | 26 + react-packager/example_project/bar.js | 5 + react-packager/example_project/config.json | 10 + react-packager/example_project/foo/foo.js | 23 + react-packager/example_project/index.js | 10 + react-packager/example_project/js/Channel.js | 46 ++ react-packager/example_project/js/XHR.js | 22 + react-packager/example_project/js/code.js | 51 ++ react-packager/example_project/js/main.js | 57 ++ .../example_project/public/css/index.css | 94 +++ .../example_project/public/index.html | 30 + react-packager/index.js | 39 + react-packager/package.json | 10 + .../src/Activity/__tests__/Activity-test.js | 79 ++ react-packager/src/Activity/index.js | 161 ++++ .../DependencyResolver/ModuleDescriptor.js | 34 + .../haste/DependencyGraph/__mocks__/fs.js | 101 +++ .../__tests__/DependencyGraph-test.js | 731 ++++++++++++++++++ .../haste/DependencyGraph/docblock.js | 88 +++ .../haste/DependencyGraph/example.js | 25 + .../haste/DependencyGraph/index.js | 494 ++++++++++++ .../__tests__/HasteDependencyResolver-test.js | 195 +++++ .../src/DependencyResolver/haste/index.js | 130 ++++ .../haste/polyfills/console.js | 141 ++++ .../haste/polyfills/error-guard.js | 82 ++ .../haste/polyfills/polyfills.js | 75 ++ .../haste/polyfills/prelude.js | 1 + .../haste/polyfills/prelude_dev.js | 1 + .../haste/polyfills/require.js | 626 +++++++++++++++ .../src/DependencyResolver/index.js | 12 + .../src/DependencyResolver/node/index.js | 48 ++ .../FileWatcher/__tests__/FileWatcher-test.js | 39 + react-packager/src/FileWatcher/index.js | 86 +++ react-packager/src/JSTransformer/Cache.js | 129 ++++ react-packager/src/JSTransformer/README.md | 0 .../src/JSTransformer/__mocks__/worker.js | 5 + .../src/JSTransformer/__tests__/Cache-test.js | 202 +++++ .../__tests__/Transformer-test.js | 71 ++ react-packager/src/JSTransformer/index.js | 112 +++ react-packager/src/JSTransformer/worker.js | 26 + react-packager/src/Packager/Package.js | 132 ++++ .../src/Packager/__mocks__/source-map.js | 5 + .../src/Packager/__tests__/Package-test.js | 95 +++ .../src/Packager/__tests__/Packager-test.js | 83 ++ react-packager/src/Packager/base64-vlq.js | 168 ++++ react-packager/src/Packager/index.js | 127 +++ .../src/Server/__tests__/Server-test.js | 158 ++++ react-packager/src/Server/index.js | 173 +++++ react-packager/src/fb-path-utils/index.js | 14 + transformer.js | 57 ++ 60 files changed, 5503 insertions(+) create mode 100644 blacklist.js create mode 100644 launchEditor.js create mode 100755 launchPackager.command create mode 100644 packager.js create mode 100755 packager.sh create mode 100644 parseCommandLine.js create mode 100644 react-packager/, create mode 100644 react-packager/.jshintrc create mode 100644 react-packager/.npmignore create mode 100644 react-packager/__mocks__/debug.js create mode 100644 react-packager/__mocks__/net.js create mode 100644 react-packager/example_project/bar.js create mode 100644 react-packager/example_project/config.json create mode 100644 react-packager/example_project/foo/foo.js create mode 100644 react-packager/example_project/index.js create mode 100644 react-packager/example_project/js/Channel.js create mode 100644 react-packager/example_project/js/XHR.js create mode 100644 react-packager/example_project/js/code.js create mode 100644 react-packager/example_project/js/main.js create mode 100644 react-packager/example_project/public/css/index.css create mode 100644 react-packager/example_project/public/index.html create mode 100644 react-packager/index.js create mode 100644 react-packager/package.json create mode 100644 react-packager/src/Activity/__tests__/Activity-test.js create mode 100644 react-packager/src/Activity/index.js create mode 100644 react-packager/src/DependencyResolver/ModuleDescriptor.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/example.js create mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/index.js create mode 100644 react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js create mode 100644 react-packager/src/DependencyResolver/haste/index.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/console.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/error-guard.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/polyfills.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/prelude.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/require.js create mode 100644 react-packager/src/DependencyResolver/index.js create mode 100644 react-packager/src/DependencyResolver/node/index.js create mode 100644 react-packager/src/FileWatcher/__tests__/FileWatcher-test.js create mode 100644 react-packager/src/FileWatcher/index.js create mode 100644 react-packager/src/JSTransformer/Cache.js create mode 100644 react-packager/src/JSTransformer/README.md create mode 100644 react-packager/src/JSTransformer/__mocks__/worker.js create mode 100644 react-packager/src/JSTransformer/__tests__/Cache-test.js create mode 100644 react-packager/src/JSTransformer/__tests__/Transformer-test.js create mode 100644 react-packager/src/JSTransformer/index.js create mode 100644 react-packager/src/JSTransformer/worker.js create mode 100644 react-packager/src/Packager/Package.js create mode 100644 react-packager/src/Packager/__mocks__/source-map.js create mode 100644 react-packager/src/Packager/__tests__/Package-test.js create mode 100644 react-packager/src/Packager/__tests__/Packager-test.js create mode 100644 react-packager/src/Packager/base64-vlq.js create mode 100644 react-packager/src/Packager/index.js create mode 100644 react-packager/src/Server/__tests__/Server-test.js create mode 100644 react-packager/src/Server/index.js create mode 100644 react-packager/src/fb-path-utils/index.js create mode 100644 transformer.js diff --git a/blacklist.js b/blacklist.js new file mode 100644 index 00000000..2b710af6 --- /dev/null +++ b/blacklist.js @@ -0,0 +1,45 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ +'use strict'; + +// Don't forget to everything listed here to `testConfig.json` +// modulePathIgnorePatterns. +var sharedBlacklist = [ + 'node_modules/JSAppServer', + 'packager/react-packager', + 'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js', + 'node_modules/react-tools/src/utils/ImmutableObject.js', + 'node_modules/react-tools/src/core/ReactInstanceHandles.js', + 'node_modules/react-tools/src/event/EventPropagators.js', + 'node_modules/jest-cli', +]; + +var webBlacklist = [ + '.ios.js' +]; + +var iosBlacklist = [ + 'node_modules/react-tools/src/browser/ui/React.js', + 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', + 'node_modules/react-tools/src/browser/ReactTextComponent.js', + // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + '.web.js', + '.android.js', +]; + +function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); +} + +function blacklist(isWeb) { + return new RegExp('(' + + sharedBlacklist + .concat(isWeb ? webBlacklist : iosBlacklist) + .map(escapeRegExp) + .join('|') + + ')$' + ); +} + +module.exports = blacklist; diff --git a/launchEditor.js b/launchEditor.js new file mode 100644 index 00000000..93db9bfc --- /dev/null +++ b/launchEditor.js @@ -0,0 +1,40 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ +'use strict'; + +var fs = require('fs'); +var spawn = require('child_process').spawn; + +var firstLaunch = true; + +function guessEditor() { + if (firstLaunch) { + console.log('When you see Red Box with stack trace, you can click any ' + + 'stack frame to jump to the source file. The packager will launch your ' + + 'editor of choice. It will first look at REACT_EDITOR environment ' + + 'variable, then at EDITOR. To set it up, you can add something like ' + + 'REACT_EDITOR=atom to your .bashrc.'); + firstLaunch = false; + } + + var editor = process.env.REACT_EDITOR || process.env.EDITOR || 'subl'; + return editor; +} + +function launchEditor(fileName, lineNumber) { + if (!fs.existsSync(fileName)) { + return; + } + + var argument = fileName; + if (lineNumber) { + argument += ':' + lineNumber; + } + + var editor = guessEditor(); + console.log('Opening ' + fileName + ' with ' + editor); + spawn(editor, [argument], { stdio: ['pipe', 'pipe', process.stderr] }); +} + +module.exports = launchEditor; diff --git a/launchPackager.command b/launchPackager.command new file mode 100755 index 00000000..dc56d7ff --- /dev/null +++ b/launchPackager.command @@ -0,0 +1,10 @@ +#!/bin/bash + +# Set terminal title +echo -en "\033]0;React Packager\a" +clear + +THIS_DIR=$(dirname "$0") +$THIS_DIR/packager.sh +echo "Process terminated. Press to close the window" +read diff --git a/packager.js b/packager.js new file mode 100644 index 00000000..6d5336ef --- /dev/null +++ b/packager.js @@ -0,0 +1,122 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { + console.log( + '\n' + + 'Could not find dependencies.\n' + + 'Ensure dependencies are installed - ' + + 'run \'npm install\' from project root.\n' + ); + process.exit(); +} + +var ReactPackager = require('./react-packager'); +var blacklist = require('./blacklist.js'); +var connect = require('connect'); +var http = require('http'); +var launchEditor = require('./launchEditor.js'); +var parseCommandLine = require('./parseCommandLine.js'); + +var options = parseCommandLine([{ + command: 'port', + default: 8081, +}, { + command: 'root', + description: 'add another root(s) to be used by the packager in this project', +}]); + +if (!options.projectRoots) { + options.projectRoots = [path.resolve(__dirname, '..')]; +} + +if (options.root) { + if (typeof options.root === 'string') { + options.projectRoots.push(path.resolve(options.root)); + } else { + options.root.forEach(function(root) { + options.projectRoots.push(path.resolve(root)); + }); + } +} + +console.log('\n' + +' ===============================================================\n' + +' | Running packager on port ' + options.port + '. \n' + +' | Keep this packager running while developing on any JS \n' + +' | projects. Feel free to close this tab and run your own \n' + +' | packager instance if you prefer. \n' + +' | \n' + +' | https://github.com/facebook/react-native \n' + +' | \n' + +' ===============================================================\n' +); + +process.on('uncaughtException', function(e) { + console.error(e); + console.error(e.stack); + console.error('\n >>> ERROR: could not create packager - please shut down ' + + 'any existing instances that are already running.\n\n'); +}); + +runServer(options, function() { + console.log('\nReact packager ready.\n'); +}); + +function loadRawBody(req, res, next) { + req.rawBody = ''; + req.setEncoding('utf8'); + + req.on('data', function(chunk) { + req.rawBody += chunk; + }); + + req.on('end', function() { + next(); + }); +} + +function openStackFrameInEditor(req, res, next) { + if (req.url === '/open-stack-frame') { + var frame = JSON.parse(req.rawBody); + launchEditor(frame.file, frame.lineNumber); + res.end('OK'); + } else { + next(); + } +} + +function getAppMiddleware(options) { + return ReactPackager.middleware({ + dev: true, + projectRoots: options.projectRoots, + blacklistRE: blacklist(false), + cacheVersion: '2', + transformModulePath: require.resolve('./transformer.js'), + }); +} + +function runServer( + options, /* {string projectRoot, bool web, bool dev} */ + readyCallback +) { + var app = connect() + .use(loadRawBody) + .use(openStackFrameInEditor) + .use(getAppMiddleware(options)); + + options.projectRoots.forEach(function(root) { + app.use(connect.static(root)); + }); + + app.use(connect.logger()) + .use(connect.compress()) + .use(connect.errorHandler()); + + return http.createServer(app).listen(options.port, readyCallback); +} diff --git a/packager.sh b/packager.sh new file mode 100755 index 00000000..98e42184 --- /dev/null +++ b/packager.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +ulimit -n 4096 + +THIS_DIR=$(dirname "$0") +node $THIS_DIR/packager.js "$@" diff --git a/parseCommandLine.js b/parseCommandLine.js new file mode 100644 index 00000000..5240d37d --- /dev/null +++ b/parseCommandLine.js @@ -0,0 +1,52 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * Wrapper on-top of `optimist` in order to properly support boolean flags + * and have a slightly less akward API. + * + * Usage example: + * var argv = parseCommandLine([{ + * command: 'web', + * description: 'Run in a web browser instead of iOS', + * default: true + * }]) + */ +'use strict'; + +var optimist = require('optimist'); + +function parseCommandLine(config) { + // optimist default API requires you to write the command name three time + // This is a small wrapper to accept an object instead + for (var i = 0; i < config.length; ++i) { + optimist + .boolean(config[i].command) + .default(config[i].command, config[i].default) + .describe(config[i].command, config[i].description); + } + var argv = optimist.argv; + + // optimist doesn't have support for --dev=false, instead it returns 'false' + for (var i = 0; i < config.length; ++i) { + var command = config[i].command; + if (argv[command] === undefined) { + argv[command] = config[i].default; + } + if (argv[command] === 'true') { + argv[command] = true; + } + if (argv[command] === 'false') { + argv[command] = false; + } + } + + // Show --help + if (argv.help || argv.h) { + optimist.showHelp(); + process.exit(); + } + + return argv; +} + +module.exports = parseCommandLine; diff --git a/react-packager/, b/react-packager/, new file mode 100644 index 00000000..e69de29b diff --git a/react-packager/.jshintrc b/react-packager/.jshintrc new file mode 100644 index 00000000..7a3f79a7 --- /dev/null +++ b/react-packager/.jshintrc @@ -0,0 +1,86 @@ +{ + "-W093": true, + "asi": false, + "bitwise": true, + "boss": false, + "browser": false, + "camelcase": true, + "couch": false, + "curly": true, + "debug": false, + "devel": true, + "dojo": false, + "eqeqeq": true, + "eqnull": true, + "esnext": true, + "evil": false, + "expr": true, + "forin": false, + "freeze": true, + "funcscope": true, + "gcl": false, + "globals": { + "Promise": true, + "React": true, + "XMLHttpRequest": true, + "document": true, + "location": true, + "window": true + }, + "globalstrict": true, + "immed": false, + "indent": 2, + "iterator": false, + "jquery": false, + "lastsemic": false, + "latedef": false, + "laxbreak": true, + "laxcomma": false, + "loopfunc": false, + "maxcomplexity": false, + "maxdepth": false, + "maxerr": 50, + "maxlen": 80, + "maxparams": false, + "maxstatements": false, + "mootools": false, + "moz": false, + "multistr": false, + "newcap": true, + "noarg": true, + "node": true, + "noempty": false, + "nonbsp": true, + "nonew": true, + "nonstandard": false, + "notypeof": false, + "noyield": false, + "phantom": false, + "plusplus": false, + "predef": [ + "afterEach", + "beforeEach", + "describe", + "expect", + "it", + "jest", + "pit" + ], + "proto": false, + "prototypejs": false, + "quotmark": true, + "rhino": false, + "scripturl": false, + "shadow": false, + "smarttabs": false, + "strict": false, + "sub": false, + "supernew": false, + "trailing": true, + "undef": true, + "unused": true, + "validthis": false, + "worker": false, + "wsh": false, + "yui": false +} diff --git a/react-packager/.npmignore b/react-packager/.npmignore new file mode 100644 index 00000000..2113f106 --- /dev/null +++ b/react-packager/.npmignore @@ -0,0 +1,8 @@ +*~ +*.swm +*.swn +*.swp +*.DS_STORE +npm-debug.log +.cache +node_modules diff --git a/react-packager/__mocks__/debug.js b/react-packager/__mocks__/debug.js new file mode 100644 index 00000000..d35fffd4 --- /dev/null +++ b/react-packager/__mocks__/debug.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function() { + return function() {}; +}; diff --git a/react-packager/__mocks__/net.js b/react-packager/__mocks__/net.js new file mode 100644 index 00000000..661fb196 --- /dev/null +++ b/react-packager/__mocks__/net.js @@ -0,0 +1,26 @@ +var EventEmitter = require('events').EventEmitter; +var servers = {}; +exports.createServer = function(listener) { + var server = { + _listener: listener, + + socket: new EventEmitter(), + + listen: function(path) { + listener(this.socket); + servers[path] = this; + } + }; + + server.socket.setEncoding = function() {}; + server.socket.write = function(data) { + this.emit('data', data); + }; + + return server; +}; + +exports.connect = function(options) { + var server = servers[options.path || options.port]; + return server.socket; +}; diff --git a/react-packager/example_project/bar.js b/react-packager/example_project/bar.js new file mode 100644 index 00000000..cc56ce6e --- /dev/null +++ b/react-packager/example_project/bar.js @@ -0,0 +1,5 @@ +/** + * @providesModule bar + */ + + module.exports = setInterval; \ No newline at end of file diff --git a/react-packager/example_project/config.json b/react-packager/example_project/config.json new file mode 100644 index 00000000..0acdcb51 --- /dev/null +++ b/react-packager/example_project/config.json @@ -0,0 +1,10 @@ +{ + "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 new file mode 100644 index 00000000..c45d9aba --- /dev/null +++ b/react-packager/example_project/foo/foo.js @@ -0,0 +1,23 @@ +/** + * @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 new file mode 100644 index 00000000..2943d187 --- /dev/null +++ b/react-packager/example_project/index.js @@ -0,0 +1,10 @@ +/** + * @providesModule index + * @jsx React.DOM + */ + +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 new file mode 100644 index 00000000..d3cbae1c --- /dev/null +++ b/react-packager/example_project/js/Channel.js @@ -0,0 +1,46 @@ +/** + * @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 new file mode 100644 index 00000000..d9e0563f --- /dev/null +++ b/react-packager/example_project/js/XHR.js @@ -0,0 +1,22 @@ +/** + * @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 new file mode 100644 index 00000000..70067859 --- /dev/null +++ b/react-packager/example_project/js/code.js @@ -0,0 +1,51 @@ +/** + * @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 new file mode 100644 index 00000000..58847092 --- /dev/null +++ b/react-packager/example_project/js/main.js @@ -0,0 +1,57 @@ +/** + * @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 new file mode 100644 index 00000000..7d36bf2c --- /dev/null +++ b/react-packager/example_project/public/css/index.css @@ -0,0 +1,94 @@ +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 new file mode 100644 index 00000000..b19685d5 --- /dev/null +++ b/react-packager/example_project/public/index.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/react-packager/index.js b/react-packager/index.js new file mode 100644 index 00000000..65ae88d8 --- /dev/null +++ b/react-packager/index.js @@ -0,0 +1,39 @@ +'use strict'; + +var Activity = require('./src/Activity'); +var Server = require('./src/Server'); + +exports.middleware = function(options) { + var server = new Server(options); + return server.processRequest.bind(server); +}; + +exports.buildPackageFromUrl = function(options, reqUrl) { + Activity.disable(); + // Don't start the filewatcher or the cache. + if (options.nonPersistent == null) { + options.nonPersistent = true; + } + + var server = new Server(options); + return server.buildPackageFromUrl(reqUrl) + .then(function(p) { + server.end(); + return p; + }); +}; + +exports.getDependencies = function(options, main) { + Activity.disable(); + // Don't start the filewatcher or the cache. + if (options.nonPersistent == null) { + options.nonPersistent = true; + } + + var server = new Server(options); + return server.getDependencies(main) + .then(function(r) { + server.end(); + return r.dependencies; + }); +}; diff --git a/react-packager/package.json b/react-packager/package.json new file mode 100644 index 00000000..ad7b7602 --- /dev/null +++ b/react-packager/package.json @@ -0,0 +1,10 @@ +{ + "name": "react-packager", + "version": "0.1.0", + "description": "", + "main": "index.js", + "jest": { + "unmockedModulePathPatterns": ["source-map"], + "testPathIgnorePatterns": ["JSAppServer/node_modules"] + } +} diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js new file mode 100644 index 00000000..7a2bdf48 --- /dev/null +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -0,0 +1,79 @@ +jest.autoMockOff(); + +describe('Activity', function() { + var Activity; + + var origConsoleLog = console.log; + + beforeEach(function() { + console.log = jest.genMockFn(); + Activity = require('../'); + }); + + afterEach(function() { + 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}; + + Activity.startEvent(EVENT_NAME, DATA); + jest.runOnlyPendingTimers(); + + expect(console.log.mock.calls.length).toBe(1); + var 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}; + + var 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]; + 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 expired eventId', function() { + var eid = Activity.startEvent('', ''); + Activity.endEvent(eid); + + expect(function() { + Activity.endEvent(eid); + }).toThrow('event(1) has already ended!'); + }); + }); + + describe('signal', function() { + it('writes a SIGNAL event out to the console', function() { + var EVENT_NAME = 'EVENT_NAME'; + var 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]; + 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 new file mode 100644 index 00000000..a60f87b0 --- /dev/null +++ b/react-packager/src/Activity/index.js @@ -0,0 +1,161 @@ +var COLLECTION_PERIOD = 1000; + +var _endedEvents = Object.create(null); +var _eventStarts = Object.create(null); +var _queuedActions = []; +var _scheduledCollectionTimer = null; +var _uuid = 1; +var _enabled = true; + +function endEvent(eventId) { + var eventEndTime = Date.now(); + + if (!_eventStarts[eventId]) { + _throw('event(' + eventId + ') is not a valid event id!'); + } + + if (_endedEvents[eventId]) { + _throw('event(' + eventId + ') has already ended!'); + } + + _scheduleAction({ + action: 'endEvent', + eventId: eventId, + tstamp: eventEndTime + }); + _endedEvents[eventId] = true; +} + +function signal(eventName, data) { + var signalTime = Date.now(); + + if (eventName == null) { + _throw('No event name specified'); + } + + if (data == null) { + data = null; + } + + _scheduleAction({ + action: 'signal', + data: data, + eventName: eventName, + tstamp: signalTime + }); +} + +function startEvent(eventName, data) { + var eventStartTime = Date.now(); + + if (eventName == null) { + _throw('No event name specified'); + } + + if (data == null) { + data = null; + } + + var eventId = _uuid++; + var action = { + action: 'startEvent', + data: data, + eventId: eventId, + eventName: eventName, + tstamp: eventStartTime, + }; + _scheduleAction(action); + _eventStarts[eventId] = action; + + return eventId; +} + +function disable() { + _enabled = false; +} + +function _runCollection() { + /* jshint -W084 */ + var action; + while ((action = _queuedActions.shift())) { + _writeAction(action); + } + + _scheduledCollectionTimer = null; +} + +function _scheduleAction(action) { + _queuedActions.push(action); + + if (_scheduledCollectionTimer === null) { + _scheduledCollectionTimer = setTimeout(_runCollection, COLLECTION_PERIOD); + } +} + +/** + * This a utility function that throws an error message. + * + * The only purpose of this utility is to make APIs like + * startEvent/endEvent/signal inlineable in the JIT. + * + * (V8 can't inline functions that statically contain a `throw`, and probably + * won't be adding such a non-trivial optimization anytime soon) + */ +function _throw(msg) { + var err = new Error(msg); + + // Strip off the call to _throw() + var stack = err.stack.split('\n'); + stack.splice(1, 1); + err.stack = stack.join('\n'); + + throw err; +} + +function _writeAction(action) { + if (!_enabled) { + return; + } + + var data = action.data ? ': ' + JSON.stringify(action.data) : ''; + var fmtTime = new Date(action.tstamp).toLocaleTimeString(); + + switch (action.action) { + case 'startEvent': + console.log( + '[' + fmtTime + '] ' + + ' ' + action.eventName + + data + ); + break; + + case 'endEvent': + var startAction = _eventStarts[action.eventId]; + var startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : ''; + console.log( + '[' + fmtTime + '] ' + + ' ' + startAction.eventName + + '(' + (action.tstamp - startAction.tstamp) + 'ms)' + + startData + ); + delete _eventStarts[action.eventId]; + break; + + case 'signal': + console.log( + '[' + fmtTime + '] ' + + ' ' + action.eventName + '' + + data + ); + break; + + default: + _throw('Unexpected scheduled action type: ' + action.action); + } +} + + +exports.endEvent = endEvent; +exports.signal = signal; +exports.startEvent = startEvent; +exports.disable = disable; diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js new file mode 100644 index 00000000..0898767a --- /dev/null +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -0,0 +1,34 @@ +function ModuleDescriptor(fields) { + if (!fields.id) { + throw new Error('Missing required fields id'); + } + this.id = fields.id; + + if (!fields.path) { + throw new Error('Missing required fields path'); + } + this.path = fields.path; + + if (!fields.dependencies) { + throw new Error('Missing required fields dependencies'); + } + this.dependencies = fields.dependencies; + + this.resolveDependency = fields.resolveDependency; + + this.entry = fields.entry || false; + + this.isPolyfill = fields.isPolyfill || false; + + this._fields = fields; +} + +ModuleDescriptor.prototype.toJSON = function() { + return { + id: this.id, + path: this.path, + dependencies: this.dependencies + } +}; + +module.exports = ModuleDescriptor; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js new file mode 100644 index 00000000..de3622d9 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js @@ -0,0 +1,101 @@ +'use strict'; + +var fs = jest.genMockFromModule('fs'); + +fs.realpath.mockImpl(function(filepath, callback) { + var node; + try { + node = getToNode(filepath); + } catch (e) { + return callback(e); + } + if (node && typeof node === 'object' && node.SYMLINK != null) { + return callback(null, node.SYMLINK); + } + callback(null, filepath); +}); + +fs.readdir.mockImpl(function(filepath, callback) { + var node; + try { + node = getToNode(filepath); + if (node && typeof node === 'object' && node.SYMLINK != null) { + node = getToNode(node.SYMLINK); + } + } catch (e) { + return callback(e); + } + + if (!(node && typeof node === 'object' && node.SYMLINK == null)) { + return callback(new Error(filepath + ' is not a directory.')); + } + + callback(null, Object.keys(node)); +}); + +fs.readFile.mockImpl(function(filepath, encoding, callback) { + try { + var node = getToNode(filepath); + // dir check + if (node && typeof node === 'object' && node.SYMLINK == null) { + callback(new Error('Trying to read a dir, ESIDR, or whatever')); + } + return callback(null, node); + } catch (e) { + return callback(e); + } +}); + +fs.lstat.mockImpl(function(filepath, callback) { + var node; + try { + node = getToNode(filepath); + } catch (e) { + return callback(e); + } + + if (node && typeof node === 'object' && node.SYMLINK == null) { + callback(null, { + isDirectory: function() { + return true; + }, + isSymbolicLink: function() { + return false; + } + }); + } else { + callback(null, { + isDirectory: function() { + return false; + }, + isSymbolicLink: function() { + if (typeof node === 'object' && node.SYMLINK) { + return true; + } + return false; + } + }); + } +}); + +var filesystem; + +fs.__setMockFilesystem = function(object) { + filesystem = object; + return filesystem; +}; + +function getToNode(filepath) { + var parts = filepath.split('/'); + if (parts[0] !== '') { + throw new Error('Make sure all paths are absolute.'); + } + var node = filesystem; + parts.slice(1).forEach(function(part) { + node = node[part]; + }); + + return node; +} + +module.exports = fs; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js new file mode 100644 index 00000000..fe8a18b6 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -0,0 +1,731 @@ +'use strict'; + +jest + .dontMock('../index') + .dontMock('q') + .dontMock('path') + .dontMock('absolute-path') + .dontMock('../../../../fb-path-utils') + .dontMock('../docblock') + .setMock('../../../ModuleDescriptor', function(data) {return data;}); + +var q = require('q'); + +describe('DependencyGraph', function() { + var DependencyGraph; + var fileWatcher; + var fs; + + beforeEach(function() { + fs = require('fs'); + DependencyGraph = require('../index'); + + fileWatcher = { + on: function() { + return this; + } + }; + }); + + describe('getOrderedDependencies', function() { + pit('should get dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', path: '/root/a.js', dependencies: []}, + ]); + }); + }); + + pit('should get recursive dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")', + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + 'require("index")', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', path: '/root/a.js', dependencies: ['index']}, + ]); + }); + }); + + pit('should work with packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should ignore malformed packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': 'lol', + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['aPackage']}, + ]); + }); + }); + + pit('can have multiple modules with the same name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("b")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', + ].join('\n'), + 'c.js': [ + '/**', + ' * @providesModule c', + ' */', + ].join('\n'), + 'somedir': { + 'somefile.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("c")', + ].join('\n') + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) + .toEqual([ + { id: 'index', + path: '/root/somedir/somefile.js', + dependencies: ['c'] + }, + { id: 'c', + path: '/root/c.js', + dependencies: [] + }, + ]); + }); + }); + + pit('providesModule wins when conflict with package', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule aPackage', + ' */', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage', + path: '/root/b.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should be forgiving with missing requires', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("lolomg")', + ].join('\n') + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['lolomg'] + } + ]); + }); + }); + + pit('should work with packages with subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'] + }, + { id: 'aPackage/subdir/lolynot', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should work with packages with symlinked subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'symlinkedPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + }, + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { SYMLINK: '/symlinkedPackage' }, + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'] + }, + { id: 'aPackage/subdir/lolynot', + path: '/symlinkedPackage/subdir/lolynot.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should work with relative modules in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'require("./subdir/lolynot")', + 'subdir': { + 'lolynot.js': 'require("../other")' + }, + 'other.js': 'some code' + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['./subdir/lolynot'] + }, + { id: 'aPackage/subdir/lolynot', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: ['../other'] + }, + { id: 'aPackage/other', + path: '/root/aPackage/other.js', + dependencies: [] + }, + ]); + }); + }); + }); + + describe('file watch updating', function() { + var fileWatcher; + var triggerFileChange; + + beforeEach(function() { + fileWatcher = { + on: function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + triggerFileChange = callback; + return this; + } + }; + }); + + pit('updates module dependencies', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + filesystem.root['index.js'] = + filesystem.root['index.js'].replace('require("foo")', ''); + triggerFileChange('change', 'index.js', root); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file change', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + filesystem.root['index.js'] = + filesystem.root['index.js'].replace('require("foo")', ''); + triggerFileChange('change', 'index.js', root); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file delete', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + delete filesystem.root.foo; + triggerFileChange('delete', 'foo.js', root); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + filesystem.root['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js', root); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js', root); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'] + }, + { id: 'bar', + path: '/root/bar.js', + dependencies: ['foo'] + }, + { id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'] + }, + ]); + }); + }); + }); + + pit('runs changes through ignore filter', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + ignoreFilePath: function(filePath) { + if (filePath === '/root/bar.js') { + return true; + } + return false; + } + }); + return dgraph.load().then(function() { + filesystem.root['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js', root); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js', root); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'] + }, + { id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'] + }, + ]); + }); + }); + }); + + pit('should ignore directory updates', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + return dgraph.load().then(function() { + triggerFileChange('change', 'aPackage', '/root', { + isDirectory: function(){ return true; } + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + { id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'] + }, + ]); + }); + }); + }); + }); +}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js new file mode 100644 index 00000000..52cac03b --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js @@ -0,0 +1,88 @@ +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/; + +var ltrimRe = /^\s*/; +/** + * @param {String} contents + * @return {String} + */ +function extract(contents) { + var match = contents.match(docblockRe); + if (match) { + return match[0].replace(ltrimRe, '') || ''; + } + return ''; +} + + +var commentStartRe = /^\/\*\*?/; +var commentEndRe = /\*\/$/; +var wsRe = /[\t ]+/g; +var stringStartRe = /(\r?\n|^) *\*/g; +var multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g; +var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g; + +/** + * @param {String} contents + * @return {Array} + */ +function parse(docblock) { + docblock = docblock + .replace(commentStartRe, '') + .replace(commentEndRe, '') + .replace(wsRe, ' ') + .replace(stringStartRe, '$1'); + + // Normalize multi-line directives + var prev = ''; + while (prev != docblock) { + prev = docblock; + docblock = docblock.replace(multilineRe, "\n$1 $2\n"); + } + docblock = docblock.trim(); + + var result = []; + var match; + while (match = propertyRe.exec(docblock)) { + result.push([match[1], match[2]]); + } + + return result; +} + +/** + * Same as parse but returns an object of prop: value instead of array of paris + * If a property appers more than once the last one will be returned + * + * @param {String} contents + * @return {Object} + */ +function parseAsObject(docblock) { + var pairs = parse(docblock); + var result = {}; + for (var i = 0; i < pairs.length; i++) { + result[pairs[i][0]] = pairs[i][1]; + } + return result; +} + + +exports.extract = extract; +exports.parse = parse; +exports.parseAsObject = parseAsObject; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js new file mode 100644 index 00000000..02e6c592 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js @@ -0,0 +1,25 @@ +var path = require('path'); +var DependecyGraph = require('./'); + +var example_project = path.resolve(__dirname, '../../../../example_project'); +var watcher = new (require('../../../FileWatcher'))({projectRoot: example_project}); +var graph = new DependecyGraph({ + fileWatcher: watcher, + root: example_project +}); + +graph.load().then(function() { + var index = path.join(example_project, 'index.js'); + console.log(graph.getOrderedDependencies(index)); +}).done(); + +watcher.getWatcher().then(function(watcher) { + watcher.on('all', function() { + setImmediate(function() { + graph.load().then(function() { + var index = path.join(example_project, 'index.js'); + console.log(graph.getOrderedDependencies(index)); + }); + }) + }); +}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js new file mode 100644 index 00000000..9a939620 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -0,0 +1,494 @@ +'use strict'; + +var ModuleDescriptor = require('../../ModuleDescriptor'); +var q = require('q'); +var fs = require('fs'); +var docblock = require('./docblock'); +var path = require('path'); +var isAbsolutePath = require('absolute-path'); +var debug = require('debug')('DependecyGraph'); +var util = require('util'); + +var readFile = q.nfbind(fs.readFile); +var readDir = q.nfbind(fs.readdir); +var lstat = q.nfbind(fs.lstat); +var realpath = q.nfbind(fs.realpath); + +function DependecyGraph(options) { + this._roots = options.roots; + this._ignoreFilePath = options.ignoreFilePath || function(){}; + this._loaded = false; + this._queue = this._roots.slice(); + this._graph = Object.create(null); + this._packageByRoot = Object.create(null); + this._packagesById = Object.create(null); + this._moduleById = Object.create(null); + this._debugUpdateEvents = []; + this._fileWatcher = options.fileWatcher; + + // Kick off the search process to precompute the dependency graph. + this._init(); +} + +DependecyGraph.prototype.load = function() { + return this._loading || (this._loading = this._search()); +}; + +/** + * Given an entry file return an array of all the dependent module descriptors. + */ +DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { + var absolutePath = this._getAbsolutePath(entryPath); + if (absolutePath == null) { + throw new Error('Cannot find entry file in any of the roots: ' + entryPath); + } + + var module = this._graph[absolutePath]; + if (module == null) { + throw new Error('Module with path "' + entryPath + '" is not in graph'); + } + + var self = this; + var deps = []; + var visited = Object.create(null); + + // Node haste sucks. Id's aren't unique. So to make sure our entry point + // is the thing that ends up in our dependency list. + var graphMap = Object.create(this._moduleById); + graphMap[module.id] = module; + + // Recursively collect the dependency list. + function collect(module) { + deps.push(module); + + module.dependencies.forEach(function(name) { + var id = sansExtJs(name); + var dep = self.resolveDependency(module, id); + + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + name, + module.id + ); + return; + } + + if (!visited[dep.id]) { + visited[dep.id] = true; + collect(dep); + } + }); + } + + visited[module.id] = true; + collect(module); + + return deps; +}; + +/** + * Given a module descriptor `fromModule` return the module descriptor for + * the required module `depModuleId`. It could be top-level or relative, + * or both. + */ +DependecyGraph.prototype.resolveDependency = function( + fromModule, + depModuleId +) { + var packageJson, modulePath, dep; + + // Package relative modules starts with '.' or '..'. + if (depModuleId[0] !== '.') { + + // 1. `depModuleId` is simply a top-level `providesModule`. + // 2. `depModuleId` is a package module but given the full path from the + // package, i.e. package_name/module_name + if (this._moduleById[sansExtJs(depModuleId)]) { + return this._moduleById[sansExtJs(depModuleId)]; + } + + // 3. `depModuleId` is a package and it's depending on the "main" + // resolution. + packageJson = this._packagesById[depModuleId]; + + // We are being forgiving here and raising an error because we could be + // processing a file that uses it's own require system. + if (packageJson == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + depModuleId, + fromModule.id + ); + return; + } + + var main = packageJson.main || 'index'; + modulePath = withExtJs(path.join(packageJson._root, main)); + dep = this._graph[modulePath]; + if (dep == null) { + throw new Error( + 'Cannot find package main file for pacakge: ' + packageJson._root + ); + } + return dep; + } else { + + // 4. `depModuleId` is a module defined in a package relative to + // `fromModule`. + packageJson = this._lookupPackage(fromModule.path); + + if (packageJson == null) { + throw new Error( + 'Expected relative module lookup from ' + fromModule.id + ' to ' + + depModuleId + ' to be within a package but no package.json found.' + ); + } + + // Example: depModuleId: ../a/b + // fromModule.path: /x/y/z + // modulePath: /x/y/a/b + var dir = path.dirname(fromModule.path); + modulePath = withExtJs(path.join(dir, depModuleId)); + + dep = this._graph[modulePath]; + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.' + + ' Inferred required module path is %s', + depModuleId, + fromModule.id, + modulePath + ); + return null; + } + + return dep; + } +}; + +/** + * Intiates the filewatcher and kicks off the search process. + */ +DependecyGraph.prototype._init = function() { + var processChange = this._processFileChange.bind(this); + var watcher = this._fileWatcher; + + this._loading = this.load().then(function() { + watcher.on('all', processChange); + }); +}; + +/** + * Implements a DFS over the file system looking for modules and packages. + */ +DependecyGraph.prototype._search = function() { + var self = this; + var dir = this._queue.shift(); + + if (dir == null) { + return q.Promise.resolve(this._graph); + } + + // Steps: + // 1. Read a dir and stat all the entries. + // 2. Filter the files and queue up the directories. + // 3. Process any package.json in the files + // 4. recur. + return readDir(dir) + .then(function(files){ + return q.all(files.map(function(filePath) { + return realpath(path.join(dir, filePath)).catch(handleBrokenLink); + })); + }) + .then(function(filePaths) { + filePaths = filePaths.filter(function(filePath) { + if (filePath == null) { + return false + } + + return !self._ignoreFilePath(filePath); + }); + + var statsP = filePaths.map(function(filePath) { + return lstat(filePath).catch(handleBrokenLink); + }); + + return [ + filePaths, + q.all(statsP) + ]; + }) + .spread(function(files, stats) { + var modulePaths = files.filter(function(filePath, i) { + if (stats[i].isDirectory()) { + self._queue.push(filePath); + return false; + } + + if (stats[i].isSymbolicLink()) { + return false; + } + + return filePath.match(/\.js$/); + }); + + var processing = self._findAndProcessPackage(files, dir) + .then(function() { + return q.all(modulePaths.map(self._processModule.bind(self))); + }); + + return q.all([ + processing, + self._search() + ]); + }) + .then(function() { + return self; + }); +}; + +/** + * Given a list of files find a `package.json` file, and if found parse it + * and update indices. + */ +DependecyGraph.prototype._findAndProcessPackage = function(files, root) { + var self = this; + + var packagePath; + for (var i = 0; i < files.length ; i++) { + var file = files[i]; + if (path.basename(file) === 'package.json') { + packagePath = file; + break; + } + } + + if (packagePath != null) { + return readFile(packagePath, 'utf8') + .then(function(content) { + var packageJson; + try { + packageJson = JSON.parse(content); + } catch (e) { + debug('WARNING: malformed package.json: ', packagePath); + return q(); + } + + if (packageJson.name == null) { + debug( + 'WARNING: package.json `%s` is missing a name field', + packagePath + ); + return q(); + } + + packageJson._root = root; + self._packageByRoot[root] = packageJson; + self._packagesById[packageJson.name] = packageJson; + + return packageJson; + }); + } else { + return q(); + } +}; + +/** + * Parse a module and update indices. + */ +DependecyGraph.prototype._processModule = function(modulePath) { + var self = this; + return readFile(modulePath, 'utf8') + .then(function(content) { + var moduleDocBlock = docblock.parseAsObject(content); + var moduleData = { path: path.resolve(modulePath) }; + if (moduleDocBlock.providesModule || moduleDocBlock.provides) { + moduleData.id = + moduleDocBlock.providesModule || moduleDocBlock.provides; + } else { + moduleData.id = self._lookupName(modulePath); + } + moduleData.dependencies = extractRequires(content); + + var module = new ModuleDescriptor(moduleData); + self._updateGraphWithModule(module); + return module; + }); +}; + +/** + * Compute the name of module relative to a package it may belong to. + */ +DependecyGraph.prototype._lookupName = function(modulePath) { + var packageJson = this._lookupPackage(modulePath); + if (packageJson == null) { + return path.resolve(modulePath); + } else { + var relativePath = + sansExtJs(path.relative(packageJson._root, modulePath)); + return path.join(packageJson.name, relativePath); + } +}; + +DependecyGraph.prototype._deleteModule = function(module) { + delete this._graph[module.path]; + + // Others may keep a reference so we mark it as deleted. + module.deleted = true; + + // Haste allows different module to have the same id. + if (this._moduleById[module.id] === module) { + delete this._moduleById[module.id]; + } +}; + +/** + * Update the graph and indices with the module. + */ +DependecyGraph.prototype._updateGraphWithModule = function(module) { + if (this._graph[module.path]) { + this._deleteModule(this._graph[module.path]); + } + + this._graph[module.path] = module; + + if (this._moduleById[module.id]) { + debug( + 'WARNING: Top-level module name conflict `%s`.\n' + + 'module with path `%s` will replace `%s`', + module.id, + module.path, + this._moduleById[module.id].path + ); + } + + this._moduleById[module.id] = module; +}; + +/** + * Find the nearest package to a module. + */ +DependecyGraph.prototype._lookupPackage = function(modulePath) { + var packageByRoot = this._packageByRoot; + + /** + * Auxiliary function to recursively lookup a package. + */ + function lookupPackage(currDir) { + // ideally we stop once we're outside root and this can be a simple child + // dir check. However, we have to support modules that was symlinked inside + // our project root. + if (currDir === '/') { + return null; + } else { + var packageJson = packageByRoot[currDir]; + if (packageJson) { + return packageJson; + } else { + return lookupPackage(path.dirname(currDir)); + } + } + } + + return lookupPackage(path.dirname(modulePath)); +}; + +/** + * Process a filewatcher change event. + */ +DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) { + var absPath = path.join(root, filePath); + if (this._ignoreFilePath(absPath)) { + return; + } + + this._debugUpdateEvents.push({event: eventType, path: filePath}); + + if (eventType === 'delete') { + var module = this._graph[absPath]; + if (module == null) { + return; + } + + this._deleteModule(module); + } else if (!(stat && stat.isDirectory())) { + var self = this; + this._loading = this._loading.then(function() { + return self._processModule(absPath); + }); + } +}; + +DependecyGraph.prototype.getDebugInfo = function() { + return '

FileWatcher Update Events

' + + '
' + util.inspect(this._debugUpdateEvents) + '
' + + '

Graph dump

' + + '
' + util.inspect(this._graph) + '
'; +}; + +/** + * Searches all roots for the file and returns the first one that has file of the same path. + */ +DependecyGraph.prototype._getAbsolutePath = function(filePath) { + if (isAbsolutePath(filePath)) { + return filePath; + } + + for (var i = 0, root; root = this._roots[i]; i++) { + var absPath = path.join(root, filePath); + if (this._graph[absPath]) { + return absPath; + } + } + + return null; +}; + +/** + * Extract all required modules from a `code` string. + */ +var requireRe = /\brequire\s*\(\s*[\'"]([^"\']+)["\']\s*\)/g; +var blockCommentRe = /\/\*(.|\n)*?\*\//g; +var lineCommentRe = /\/\/.+(\n|$)/g; +function extractRequires(code) { + var deps = []; + + code + .replace(blockCommentRe, '') + .replace(lineCommentRe, '') + .replace(requireRe, function(match, dep) { + deps.push(dep); + }); + + return deps; +} + +/** + * `file` without the .js extension. + */ +function sansExtJs(file) { + if (file.match(/\.js$/)) { + return file.slice(0, -3); + } else { + return file; + } +} + +/** + * `file` with the .js extension. + */ +function withExtJs(file) { + if (file.match(/\.js$/)) { + return file; + } else { + return file + '.js'; + } +} + +function handleBrokenLink(e) { + debug('WARNING: error stating, possibly broken symlink', e.message); + return q(); +} + +module.exports = DependecyGraph; diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js new file mode 100644 index 00000000..9b43f97e --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -0,0 +1,195 @@ + +jest.dontMock('../') + .dontMock('q') + .setMock('../../ModuleDescriptor', function(data) {return data;}); + +var q = require('q'); + +describe('HasteDependencyResolver', function() { + var HasteDependencyResolver; + var DependencyGraph; + + beforeEach(function() { + // For the polyfillDeps + require('path').join.mockImpl(function(a, b) { + return b; + }); + HasteDependencyResolver = require('../'); + DependencyGraph = require('../DependencyGraph'); + }); + + describe('getDependencies', function() { + pit('should get dependencies with polyfills', function() { + var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var deps = [module]; + + var depResolver = new HasteDependencyResolver({ + projectRoot: '/root' + }); + + // Is there a better way? How can I mock the prototype instead? + var depGraph = depResolver._depGraph; + depGraph.getOrderedDependencies.mockImpl(function() { + return deps; + }); + depGraph.load.mockImpl(function() { + return q(); + }); + + return depResolver.getDependencies('/root/index.js') + .then(function(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' + ], + }, + module + ]); + }); + }); + + pit('should pass in more polyfills', function() { + var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var deps = [module]; + + var depResolver = new HasteDependencyResolver({ + projectRoot: '/root', + polyfillModuleNames: ['some module'] + }); + + // Is there a better way? How can I mock the prototype instead? + var depGraph = depResolver._depGraph; + depGraph.getOrderedDependencies.mockImpl(function() { + return deps; + }); + depGraph.load.mockImpl(function() { + return q(); + }); + + return depResolver.getDependencies('/root/index.js') + .then(function(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' + ], + }, + { path: 'some module', + id: 'some module', + isPolyfill: true, + dependencies: [ + 'polyfills/prelude.js', + 'polyfills/require.js', + 'polyfills/polyfills.js', + 'polyfills/console.js', + 'polyfills/error-guard.js', + ] + }, + module + ]); + }); + }); + }); + + describe('wrapModule', function() { + it('should ', function() { + var depResolver = new HasteDependencyResolver({ + projectRoot: '/root' + }); + + var depGraph = depResolver._depGraph; + var dependencies = ['x', 'y', 'z'] + var code = [ + 'require("x")', + 'require("y")', + 'require("z")', + ].join('\n'); + + depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { + if (toModuleName === 'x') { + return { + id: 'changed' + }; + } else if (toModuleName === 'y') { + return { id: 'y' }; + } + return null; + }); + + var processedCode = depResolver.wrapModule({ + id: 'test module', + path: '/root/test.js', + dependencies: dependencies + }, code); + + expect(processedCode).toEqual([ + "__d('test module',[\"changed\",\"y\"],function(global," + + " require, requireDynamic, requireLazy, module, exports) {" + + " require('changed')", + "require('y')", + 'require("z")});', + ].join('\n')); + }); + }); +}); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js new file mode 100644 index 00000000..6e2cd6fc --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -0,0 +1,130 @@ +'use strict'; + +var path = require('path'); +var FileWatcher = require('../../FileWatcher'); +var DependencyGraph = require('./DependencyGraph'); +var ModuleDescriptor = require('../ModuleDescriptor'); + +var DEFINE_MODULE_CODE = + '__d(' + + '\'_moduleName_\',' + + '_deps_,' + + 'function(global, require, requireDynamic, requireLazy, module, exports) {'+ + ' _code_' + + '}' + + ');'; + +var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; + +var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; + +function HasteDependencyResolver(config) { + this._fileWatcher = config.nonPersistent + ? FileWatcher.createDummyWatcher() + : new FileWatcher(config.projectRoots); + + this._depGraph = new DependencyGraph({ + roots: config.projectRoots, + ignoreFilePath: function(filepath) { + return filepath.indexOf('__tests__') !== -1 || + (config.blacklistRE && config.blacklistRE.test(filepath)); + }, + fileWatcher: this._fileWatcher + }); + + this._polyfillModuleNames = [ + config.dev + ? path.join(__dirname, 'polyfills/prelude_dev.js') + : path.join(__dirname, 'polyfills/prelude.js'), + path.join(__dirname, 'polyfills/require.js'), + path.join(__dirname, 'polyfills/polyfills.js'), + path.join(__dirname, 'polyfills/console.js'), + path.join(__dirname, 'polyfills/error-guard.js'), + ].concat( + config.polyfillModuleNames || [] + ); +} + +HasteDependencyResolver.prototype.getDependencies = function(main) { + var depGraph = this._depGraph; + var self = this; + + return depGraph.load() + .then(function() { + var dependencies = depGraph.getOrderedDependencies(main); + var mainModuleId = dependencies[0].id; + + self._prependPolyfillDependencies(dependencies); + + return { + mainModuleId: mainModuleId, + dependencies: dependencies + }; + }); +}; + +HasteDependencyResolver.prototype._prependPolyfillDependencies = function( + dependencies +) { + var polyfillModuleNames = this._polyfillModuleNames; + if (polyfillModuleNames.length > 0) { + var polyfillModules = polyfillModuleNames.map( + function(polyfillModuleName, idx) { + return new ModuleDescriptor({ + path: polyfillModuleName, + id: polyfillModuleName, + dependencies: polyfillModuleNames.slice(0, idx), + isPolyfill: true + }); + } + ); + dependencies.unshift.apply(dependencies, polyfillModules); + } +}; + +HasteDependencyResolver.prototype.wrapModule = function(module, code) { + if (module.isPolyfill) { + return code; + } + + var depGraph = this._depGraph; + var resolvedDeps = Object.create(null); + var resolvedDepsArr = []; + + for (var i = 0; i < module.dependencies.length; i++) { + var depName = module.dependencies[i]; + var dep = this._depGraph.resolveDependency(module, depName); + if (dep) { + resolvedDeps[depName] = dep.id; + resolvedDepsArr.push(dep.id); + } + } + + var relativizedCode = + code.replace(REL_REQUIRE_STMT, function(codeMatch, depName) { + var dep = resolvedDeps[depName]; + if (dep != null) { + return 'require(\'' + dep + '\')'; + } else { + return codeMatch; + } + }); + + return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) { + return { + '_moduleName_': module.id, + '_code_': relativizedCode, + '_deps_': JSON.stringify(resolvedDepsArr), + }[key]; + }); +}; + +HasteDependencyResolver.prototype.end = function() { + return this._fileWatcher.end(); +}; + +HasteDependencyResolver.prototype.getDebugInfo = function() { + return this._depGraph.getDebugInfo(); +}; + +module.exports = HasteDependencyResolver; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js new file mode 100644 index 00000000..4c9ddce1 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -0,0 +1,141 @@ +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This pipes all of our console logging functions to native logging so that + * JavaScript errors in required modules show up in Xcode via NSLog. + * + * @provides console + * @polyfill + */ + +(function(global) { + + var OBJECT_COLUMN_NAME = '(index)'; + + function setupConsole(global) { + + if (!global.nativeLoggingHook) { + return; + } + + function doNativeLog() { + var str = Array.prototype.map.call(arguments, function(arg) { + if (arg == null) { + return arg === null ? 'null' : 'undefined'; + } else if (typeof arg === 'string') { + return '"' + arg + '"'; + } else { + // Perform a try catch, just in case the object has a circular + // reference or stringify throws for some other reason. + try { + return JSON.stringify(arg); + } catch (e) { + if (typeof arg.toString === 'function') { + try { + return arg.toString(); + } catch (e) { + return 'unknown'; + } + } + } + } + }).join(', '); + global.nativeLoggingHook(str); + }; + + var repeat = function(element, n) { + return Array.apply(null, Array(n)).map(function() { return element; }); + }; + + function consoleTablePolyfill(rows) { + // convert object -> array + if (!Array.isArray(rows)) { + var data = rows; + rows = []; + for (var key in data) { + if (data.hasOwnProperty(key)) { + var row = data[key]; + row[OBJECT_COLUMN_NAME] = key; + rows.push(row); + } + } + } + if (rows.length === 0) { + global.nativeLoggingHook(''); + return; + } + + var columns = Object.keys(rows[0]).sort(); + var stringRows = []; + var columnWidths = []; + + // Convert each cell to a string. Also + // figure out max cell width for each column + columns.forEach(function(k, i) { + columnWidths[i] = k.length; + for (var j = 0; j < rows.length; j++) { + var cellStr = rows[j][k].toString(); + stringRows[j] = stringRows[j] || []; + stringRows[j][i] = cellStr; + columnWidths[i] = Math.max(columnWidths[i], cellStr.length); + } + }); + + // Join all elements in the row into a single string with | separators + // (appends extra spaces to each cell to make separators | alligned) + var joinRow = function(row, space) { + var cells = row.map(function(cell, i) { + var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join(''); + return cell + extraSpaces; + }); + space = space || ' '; + return cells.join(space + '|' + space); + }; + + var separators = columnWidths.map(function(columnWidth) { + return repeat('-', columnWidth).join(''); + }); + var separatorRow = joinRow(separators, '-'); + var header = joinRow(columns); + var table = [header, separatorRow]; + + for (var i = 0; i < rows.length; i++) { + table.push(joinRow(stringRows[i])); + } + + // Notice extra empty line at the beginning. + // Native logging hook adds "RCTLog >" at the front of every + // logged string, which would shift the header and screw up + // the table + global.nativeLoggingHook('\n' + table.join('\n')); + }; + + global.console = { + error: doNativeLog, + info: doNativeLog, + log: doNativeLog, + warn: doNativeLog, + table: consoleTablePolyfill + }; + + }; + + if (typeof module !== 'undefined') { + module.exports = setupConsole; + } else { + setupConsole(global); + } + +})(this); diff --git a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js new file mode 100644 index 00000000..687a4a19 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js @@ -0,0 +1,82 @@ + +/** + * The particular require runtime that we are using looks for a global + * `ErrorUtils` object and if it exists, then it requires modules with the + * error handler specified via ErrorUtils.setGlobalHandler by calling the + * require function with applyWithGuard. Since the require module is loaded + * before any of the modules, this ErrorUtils must be defined (and the handler + * set) globally before requiring anything. + */ +/* eslint global-strict:0 */ +(function(global) { + var ErrorUtils = { + _inGuard: 0, + _globalHandler: null, + setGlobalHandler: function(fun) { + ErrorUtils._globalHandler = fun; + }, + reportError: function(error) { + Error._globalHandler && ErrorUtils._globalHandler(error); + }, + applyWithGuard: function(fun, context, args) { + try { + ErrorUtils._inGuard++; + return fun.apply(context, args); + } catch (e) { + ErrorUtils._globalHandler && ErrorUtils._globalHandler(e); + } finally { + ErrorUtils._inGuard--; + } + }, + applyWithGuardIfNeeded: function(fun, context, args) { + if (ErrorUtils.inGuard()) { + return fun.apply(context, args); + } else { + ErrorUtils.applyWithGuard(fun, context, args); + } + }, + inGuard: function() { + return ErrorUtils._inGuard; + }, + guard: function(fun, name, context) { + if (typeof fun !== "function") { + console.warn('A function must be passed to ErrorUtils.guard, got ', fun); + return null; + } + name = name || fun.name || ''; + function guarded() { + return ( + ErrorUtils.applyWithGuard( + fun, + context || this, + arguments, + null, + name + ) + ); + } + + return guarded; + } + }; + global.ErrorUtils = ErrorUtils; + + /** + * This is the error handler that is called when we encounter an exception + * when loading a module. + */ + function setupErrorGuard() { + var onError = function(e) { + global.console.error( + 'Error: ' + + '\n stack: ' + e.stack + + '\n line: ' + e.line + + '\n message: ' + e.message, + e + ); + }; + global.ErrorUtils.setGlobalHandler(onError); + } + + setupErrorGuard(); +})(this); diff --git a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js new file mode 100644 index 00000000..2fd32246 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js @@ -0,0 +1,75 @@ +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This pipes all of our console logging functions to native logging so that + * JavaScript errors in required modules show up in Xcode via NSLog. + * + * @provides Object.es6 + * @polyfill + */ + +// WARNING: This is an optimized version that fails on hasOwnProperty checks +// and non objects. It's not spec-compliant. It's a perf optimization. + +Object.assign = function(target, sources) { + if (__DEV__) { + if (target == null) { + throw new TypeError('Object.assign target cannot be null or undefined'); + } + if (typeof target !== 'object' && typeof target !== 'function') { + throw new TypeError( + 'In this environment the target of assign MUST be an object.' + + 'This error is a performance optimization and not spec compliant.' + ); + } + } + + for (var nextIndex = 1; nextIndex < arguments.length; nextIndex++) { + var nextSource = arguments[nextIndex]; + if (nextSource == null) { + continue; + } + + if (__DEV__) { + if (typeof nextSource !== 'object' && + typeof nextSource !== 'function') { + throw new TypeError( + 'In this environment the target of assign MUST be an object.' + + 'This error is a performance optimization and not spec compliant.' + ); + } + } + + // We don't currently support accessors nor proxies. Therefore this + // copy cannot throw. If we ever supported this then we must handle + // exceptions and side-effects. + + for (var key in nextSource) { + if (__DEV__) { + var hasOwnProperty = Object.prototype.hasOwnProperty; + if (!hasOwnProperty.call(nextSource, key)) { + throw new TypeError( + 'One of the sources to assign has an enumerable key on the ' + + 'prototype chain. This is an edge case that we do not support. ' + + 'This error is a performance optimization and not spec compliant.' + ); + } + } + target[key] = nextSource[key]; + } + } + + return target; +}; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude.js b/react-packager/src/DependencyResolver/haste/polyfills/prelude.js new file mode 100644 index 00000000..95c66983 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/prelude.js @@ -0,0 +1 @@ +__DEV__ = false; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js b/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js new file mode 100644 index 00000000..a5ca53b7 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js @@ -0,0 +1 @@ +__DEV__ = true; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/haste/polyfills/require.js new file mode 100644 index 00000000..3b5d6d87 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -0,0 +1,626 @@ +(function(global) { + + // avoid redefining require() + if (global.require) { + return; + } + + var __DEV__ = global.__DEV__; + + var toString = Object.prototype.toString; + + /** + * module index: { + * mod1: { + * exports: { ... }, + * id: 'mod1', + * dependencies: ['mod1', 'mod2'], + * factory: function() { ... }, + * waitingMap: { mod1: 1, mod3: 1, mod4: 1 }, + * waiting: 2 + * } + * } + */ + var modulesMap = {}, + /** + * inverse index: { + * mod1: [modules, waiting for mod1], + * mod2: [modules, waiting for mod2] + * } + */ + dependencyMap = {}, + /** + * modules whose reference counts are set out of order + */ + predefinedRefCounts = {}, + + _counter = 0, + + REQUIRE_WHEN_READY = 1, + USED_AS_TRANSPORT = 2, + + hop = Object.prototype.hasOwnProperty; + + function _debugUnresolvedDependencies(names) { + var unresolved = Array.prototype.slice.call(names); + var visited = {}; + var ii, name, module, dependency; + + while (unresolved.length) { + name = unresolved.shift(); + if (visited[name]) { + continue; + } + visited[name] = true; + + module = modulesMap[name]; + if (!module || !module.waiting) { + continue; + } + + for (ii = 0; ii < module.dependencies.length; ii++) { + dependency = module.dependencies[ii]; + if (!modulesMap[dependency] || modulesMap[dependency].waiting) { + unresolved.push(dependency); + } + } + } + + for (name in visited) if (hop.call(visited, name)) { + unresolved.push(name); + } + + var messages = []; + for (ii = 0; ii < unresolved.length; ii++) { + name = unresolved[ii]; + var message = name; + module = modulesMap[name]; + if (!module) { + message += ' is not defined'; + } else if (!module.waiting) { + message += ' is ready'; + } else { + var unresolvedDependencies = []; + for (var jj = 0; jj < module.dependencies.length; jj++) { + dependency = module.dependencies[jj]; + if (!modulesMap[dependency] || modulesMap[dependency].waiting) { + unresolvedDependencies.push(dependency); + } + } + message += ' is waiting for ' + unresolvedDependencies.join(', '); + } + messages.push(message); + } + return messages.join('\n'); + } + + /** + * This is mainly for logging in ModuleErrorLogger. + */ + function ModuleError(msg) { + this.name = 'ModuleError'; + this.message = msg; + this.stack = Error(msg).stack; + this.framesToPop = 2; + } + ModuleError.prototype = Object.create(Error.prototype); + ModuleError.prototype.constructor = ModuleError; + + var _performance = + global.performance || + global.msPerformance || + global.webkitPerformance || {}; + + if (!_performance.now) { + _performance = global.Date; + } + + var _now = _performance ? + _performance.now.bind(_performance) : function(){return 0;}; + + var _factoryStackCount = 0; + var _factoryTime = 0; + var _totalFactories = 0; + + /** + * The require function conforming to CommonJS spec: + * http://wiki.commonjs.org/wiki/Modules/1.1.1 + * + * To define a CommonJS-compliant module add the providesModule + * Haste header to your file instead of @provides. Your file is going + * to be executed in a separate context. Every variable/function you + * define will be local (private) to that module. To export local members + * use "exports" variable or return the exported value at the end of your + * file. Your code will have access to the "module" object. + * The "module" object will have an "id" property that is the id of your + * current module. "module" object will also have "exports" property that + * is the same as "exports" variable passed into your module context. + * You can require other modules using their ids. + * + * Haste will automatically pick dependencies from require() calls. So + * you don't have to manually specify @requires in your header. + * + * You cannot require() modules from non-CommonJS files. Write a legacy stub + * (@providesLegacy) and use @requires instead. + * + * @example + * + * / ** + * * @providesModule math + * * / + * exports.add = function() { + * var sum = 0, i = 0, args = arguments, l = args.length; + * while (i < l) { + * sum += args[i++]; + * } + * return sum; + * }; + * + * / ** + * * @providesModule increment + * * / + * var add = require('math').add; + * return function(val) { + * return add(val, 1); + * }; + * + * / ** + * * @providesModule program + * * / + * var inc = require('increment'); + * var a = 1; + * inc(a); // 2 + * + * module.id == "program"; + * + * + * @param {String} id + * @throws when module is not loaded or not ready to be required + */ + function require(id) { + var module = modulesMap[id], dep, i, msg; + if (module && module.exports) { + // If ref count is 1, this was the last call, so undefine the module. + // The ref count can be null or undefined, but those are never === 1. + if (module.refcount-- === 1) { + delete modulesMap[id]; + } + return module.exports; + } + + if (global.ErrorUtils && !global.ErrorUtils.inGuard()) { + return ErrorUtils.applyWithGuard(require, this, arguments); + } + + if (!module) { + msg = 'Requiring unknown module "' + id + '"'; + if (__DEV__) { + msg += '. It may not be loaded yet. Did you forget to run arc build?'; + } + throw new ModuleError(msg); + } + + if (module.hasError) { + throw new ModuleError( + 'Requiring module "' + id + '" which threw an exception' + ); + } + + if (module.waiting) { + throw new ModuleError( + 'Requiring module "' + id + '" with unresolved dependencies: ' + + _debugUnresolvedDependencies([id]) + ); + } + + var exports = module.exports = {}; + var factory = module.factory; + if (toString.call(factory) === '[object Function]') { + var args = [], + dependencies = module.dependencies, + length = dependencies.length, + ret; + if (module.special & USED_AS_TRANSPORT) { + length = Math.min(length, factory.length); + } + try { + for (i = 0; args.length < length; i++) { + dep = dependencies[i]; + if (!module.inlineRequires[dep]) { + args.push(dep === 'module' ? module : + (dep === 'exports' ? exports : + require.call(null, dep))); + } + } + + ++_totalFactories; + if (_factoryStackCount++ === 0) { + _factoryTime -= _now(); + } + try { + ret = factory.apply(module.context || global, args); + } catch (e) { + if (modulesMap.ex && modulesMap.erx) { + // when ErrorUtils is ready, ex and erx are ready. otherwise, we + // don't append module id to the error message but still throw it + var ex = require.call(null, 'ex'); + var erx = require.call(null, 'erx'); + var messageWithParams = erx(e.message); + if (messageWithParams[0].indexOf(' from module "%s"') < 0) { + messageWithParams[0] += ' from module "%s"'; + messageWithParams[messageWithParams.length] = id; + } + e.message = ex.apply(null, messageWithParams); + } + throw e; + } finally { + if (--_factoryStackCount === 0) { + _factoryTime += _now(); + } + } + } catch (e) { + module.hasError = true; + module.exports = null; + throw e; + } + if (ret) { + if (__DEV__) { + if (typeof ret != 'object' && typeof ret != 'function') { + throw new ModuleError( + 'Factory for module "' + id + '" returned ' + + 'an invalid value "' + ret + '". ' + + 'Returned value should be either a function or an object.' + ); + } + } + module.exports = ret; + } + } else { + module.exports = factory; + } + + // If ref count is 1, this was the last call, so undefine the module. + // The ref count can be null or undefined, but those are never === 1. + if (module.refcount-- === 1) { + delete modulesMap[id]; + } + return module.exports; + } + + require.__getFactoryTime = function() { + return (_factoryStackCount ? _now() : 0) + _factoryTime; + }; + + require.__getTotalFactories = function() { + return _totalFactories; + }; + + /** + * The define function conforming to CommonJS proposal: + * http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition + * + * define() allows you to explicitly state dependencies of your module + * in javascript. It's most useful in non-CommonJS files. + * + * define() is used internally by haste as a transport for CommonJS + * modules. So there's no need to use define() if you use providesModule + * + * @example + * / ** + * * @provides alpha + * * / + * + * // Sets up the module with ID of "alpha", that uses require, + * // exports and the module with ID of "beta": + * define("alpha", ["require", "exports", "beta"], + * function (require, exports, beta) { + * exports.verb = function() { + * return beta.verb(); + * //Or: + * return require("beta").verb(); + * } + * }); + * + * / ** + * * @provides alpha + * * / + * // An anonymous module could be defined (module id derived from filename) + * // that returns an object literal: + * + * define(["alpha"], function (alpha) { + * return { + * verb: function(){ + * return alpha.verb() + 2; + * } + * }; + * }); + * + * / ** + * * @provides alpha + * * / + * // A dependency-free module can define a direct object literal: + * + * define({ + * add: function(x, y){ + * return x + y; + * } + * }); + * + * @param {String} id optional + * @param {Array} dependencies optional + * @param {Object|Function} factory + */ + function define(id, dependencies, factory, + _special, _context, _refCount, _inlineRequires) { + if (dependencies === undefined) { + dependencies = []; + factory = id; + id = _uid(); + } else if (factory === undefined) { + factory = dependencies; + if (toString.call(id) === '[object Array]') { + dependencies = id; + id = _uid(); + } else { + dependencies = []; + } + } + + // Non-standard: we allow modules to be undefined. This is designed for + // temporary modules. + var canceler = { cancel: _undefine.bind(this, id) }; + + var record = modulesMap[id]; + + // Nonstandard hack: we call define with null deps and factory, but a + // non-null reference count (e.g. define('name', null, null, 0, null, 4)) + // when this module is defined elsewhere and we just need to update the + // reference count. We use this hack to avoid having to expose another + // global function to increment ref counts. + if (record) { + if (_refCount) { + record.refcount += _refCount; + } + // Calling define() on a pre-existing module does not redefine it + return canceler; + } else if (!dependencies && !factory && _refCount) { + // If this module hasn't been defined yet, store the ref count. We'll use + // it when the module is defined later. + predefinedRefCounts[id] = (predefinedRefCounts[id] || 0) + _refCount; + return canceler; + } else { + // Defining a new module + record = { id: id }; + record.refcount = (predefinedRefCounts[id] || 0) + (_refCount || 0); + delete predefinedRefCounts[id]; + } + + if (__DEV__) { + if ( + !factory || + (typeof factory != 'object' && typeof factory != 'function' && + typeof factory != 'string')) { + throw new ModuleError( + 'Invalid factory "' + factory + '" for module "' + id + '". ' + + 'Factory should be either a function or an object.' + ); + } + + if (toString.call(dependencies) !== '[object Array]') { + throw new ModuleError( + 'Invalid dependencies for module "' + id + '". ' + + 'Dependencies must be passed as an array.' + ); + } + } + + record.factory = factory; + record.dependencies = dependencies; + record.context = _context; + record.special = _special; + record.inlineRequires = _inlineRequires || {}; + record.waitingMap = {}; + record.waiting = 0; + record.hasError = false; + modulesMap[id] = record; + _initDependencies(id); + + return canceler; + } + + function _undefine(id) { + if (!modulesMap[id]) { + return; + } + + var module = modulesMap[id]; + delete modulesMap[id]; + + for (var dep in module.waitingMap) { + if (module.waitingMap[dep]) { + delete dependencyMap[dep][id]; + } + } + + for (var ii = 0; ii < module.dependencies.length; ii++) { + dep = module.dependencies[ii]; + if (modulesMap[dep]) { + if (modulesMap[dep].refcount-- === 1) { + _undefine(dep); + } + } else if (predefinedRefCounts[dep]) { + predefinedRefCounts[dep]--; + } + // Subtle: we won't account for this one fewer reference if we don't have + // the dependency's definition or reference count yet. + } + } + + /** + * 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++; + } + + function _addDependency(module, dep) { + // do not add duplicate dependencies and circ deps + if (!module.waitingMap[dep] && module.id !== dep) { + module.waiting++; + module.waitingMap[dep] = 1; + dependencyMap[dep] || (dependencyMap[dep] = {}); + dependencyMap[dep][module.id] = 1; + } + } + + function _initDependencies(id) { + var modulesToRequire = []; + var module = modulesMap[id]; + var dep, i, subdep; + + // initialize id's waitingMap + for (i = 0; i < module.dependencies.length; i++) { + dep = module.dependencies[i]; + if (!modulesMap[dep]) { + _addDependency(module, dep); + } else if (modulesMap[dep].waiting) { + for (subdep in modulesMap[dep].waitingMap) { + if (modulesMap[dep].waitingMap[subdep]) { + _addDependency(module, subdep); + } + } + } + } + if (module.waiting === 0 && module.special & REQUIRE_WHEN_READY) { + modulesToRequire.push(id); + } + + // update modules depending on id + if (dependencyMap[id]) { + var deps = dependencyMap[id]; + var submodule; + dependencyMap[id] = undefined; + for (dep in deps) { + submodule = modulesMap[dep]; + + // add all deps of id + for (subdep in module.waitingMap) { + if (module.waitingMap[subdep]) { + _addDependency(submodule, subdep); + } + } + // remove id itself + if (submodule.waitingMap[id]) { + submodule.waitingMap[id] = undefined; + submodule.waiting--; + } + if (submodule.waiting === 0 && + submodule.special & REQUIRE_WHEN_READY) { + modulesToRequire.push(dep); + } + } + } + + // run everything that's ready + for (i = 0; i < modulesToRequire.length; i++) { + require.call(null, modulesToRequire[i]); + } + } + + function _register(id, exports) { + var module = modulesMap[id] = { id: id }; + module.exports = exports; + module.refcount = 0; + } + + // pseudo name used in common-require + // see require() function for more info + _register('module', 0); + _register('exports', 0); + + _register('define', define); + _register('global', global); + _register('require', require); + _register('requireDynamic', require); + _register('requireLazy', requireLazy); + + define.amd = {}; + + global.define = define; + global.require = require; + global.requireDynamic = require; + global.requireLazy = requireLazy; + + require.__debug = { + modules: modulesMap, + deps: dependencyMap, + printDependencyInfo: function() { + if (!global.console) { + return; + } + var names = Object.keys(require.__debug.deps); + global.console.log(_debugUnresolvedDependencies(names)); + } + }; + + /** + * All @providesModule files are wrapped by this function by makehaste. It + * is a convenience function around define() that prepends a bunch of required + * modules (global, require, module, etc) so that we don't have to spit that + * 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']; + define(id, defaultDeps.concat(deps), factory, _special || USED_AS_TRANSPORT, + null, null, _inlineRequires); + }; + +})(this); diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js new file mode 100644 index 00000000..79eb48c1 --- /dev/null +++ b/react-packager/src/DependencyResolver/index.js @@ -0,0 +1,12 @@ +var HasteDependencyResolver = require('./haste'); +var NodeDependencyResolver = require('./node'); + +module.exports = function createDependencyResolver(options) { + if (options.moduleFormat === 'haste') { + return new HasteDependencyResolver(options); + } else if (options.moduleFormat === 'node') { + return new NodeDependencyResolver(options); + } else { + throw new Error('unsupported'); + } +}; diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js new file mode 100644 index 00000000..0d3b807e --- /dev/null +++ b/react-packager/src/DependencyResolver/node/index.js @@ -0,0 +1,48 @@ +var Promise = require('q').Promise; +var ModuleDescriptor = require('../ModuleDescriptor'); + +var mdeps = require('module-deps'); +var path = require('path'); +var fs = require('fs'); + +// var REQUIRE_RUNTIME = fs.readFileSync( +// path.join(__dirname, 'require.js') +// ).toString(); + +exports.getRuntimeCode = function() { + return REQUIRE_RUNTIME; +}; + +exports.wrapModule = function(id, source) { + return Promise.resolve( + 'define(' + JSON.stringify(id) + ',' + ' function(exports, module) {\n' + + source + '\n});' + ); +}; + +exports.getDependencies = function(root, fileEntryPath) { + return new Promise(function(resolve, reject) { + fileEntryPath = path.join(process.cwd(), root, fileEntryPath); + + var md = mdeps(); + + md.end({file: fileEntryPath}); + + var deps = []; + + md.on('data', function(data) { + deps.push( + new ModuleDescriptor({ + id: data.id, + deps: data.deps, + path: data.file, + entry: data.entry + }) + ); + }); + + md.on('end', function() { + resolve(deps); + }); + }); +}; diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js new file mode 100644 index 00000000..8baae9e1 --- /dev/null +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -0,0 +1,39 @@ +'use strict'; + +jest.dontMock('../') + .dontMock('q') + .setMock('child_process', { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman') } }); + +describe('FileWatcher', function() { + var FileWatcher; + var Watcher; + + beforeEach(function() { + FileWatcher = require('../'); + Watcher = require('sane').WatchmanWatcher; + Watcher.prototype.once.mockImplementation(function(type, callback) { + callback(); + }); + }); + + it('it should get the watcher instance when ready', function() { + var fileWatcher = new FileWatcher(['rootDir']); + return fileWatcher._loading.then(function(watchers) { + watchers.forEach(function(watcher) { + expect(watcher instanceof Watcher).toBe(true); + }); + }); + }); + + pit('it should end the watcher', function() { + var fileWatcher = new FileWatcher(['rootDir']); + Watcher.prototype.close.mockImplementation(function(callback) { + callback(); + }); + + return fileWatcher.end().then(function() { + expect(Watcher.prototype.close).toBeCalled(); + }); + }); + +}); diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js new file mode 100644 index 00000000..f2721d8c --- /dev/null +++ b/react-packager/src/FileWatcher/index.js @@ -0,0 +1,86 @@ +'use strict'; + +var EventEmitter = require('events').EventEmitter; +var sane = require('sane'); +var q = require('q'); +var util = require('util'); +var exec = require('child_process').exec; + +var Promise = q.Promise; + +var detectingWatcherClass = new Promise(function(resolve, reject) { + exec('which watchman', function(err, out) { + if (err || out.length === 0) { + resolve(sane.NodeWatcher); + } else { + resolve(sane.WatchmanWatcher); + } + }); +}); + +module.exports = FileWatcher; + +var MAX_WAIT_TIME = 3000; + +function FileWatcher(projectRoots) { + var self = this; + this._loading = q.all( + projectRoots.map(createWatcher) + ).then(function(watchers) { + watchers.forEach(function(watcher) { + watcher.on('all', function(type, filepath, root) { + self.emit('all', type, filepath, root); + }); + }); + return watchers; + }); + this._loading.done(); +} + +util.inherits(FileWatcher, EventEmitter); + +FileWatcher.prototype.end = function() { + return this._loading.then(function(watchers) { + watchers.forEach(function(watcher) { + delete watchersByRoot[watcher._root]; + return q.ninvoke(watcher, 'close'); + }); + }); +}; + +var watchersByRoot = Object.create(null); + +function createWatcher(root) { + if (watchersByRoot[root] != null) { + return Promise.resolve(watchersByRoot[root]); + } + + return detectingWatcherClass.then(function(Watcher) { + var watcher = new Watcher(root, {glob: '**/*.js'}); + + return new Promise(function(resolve, reject) { + var rejectTimeout = setTimeout(function() { + reject(new Error([ + 'Watcher took too long to load', + 'Try running `watchman` from your terminal', + 'https://facebook.github.io/watchman/docs/troubleshooting.html', + ].join('\n'))); + }, MAX_WAIT_TIME); + + watcher.once('ready', function() { + clearTimeout(rejectTimeout); + watchersByRoot[root] = watcher; + watcher._root = root; + resolve(watcher); + }); + }); + }); +} + +FileWatcher.createDummyWatcher = function() { + var ev = new EventEmitter(); + ev.end = function() { + return q(); + }; + return ev; +}; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js new file mode 100644 index 00000000..577af696 --- /dev/null +++ b/react-packager/src/JSTransformer/Cache.js @@ -0,0 +1,129 @@ +'use strict'; + +var path = require('path'); +var version = require('../../package.json').version; +var tmpdir = require('os').tmpDir(); +var pathUtils = require('../fb-path-utils'); +var fs = require('fs'); +var _ = require('underscore'); +var q = require('q'); + +var Promise = q.Promise; + +module.exports = Cache; + +function Cache(projectConfig) { + this._cacheFilePath = cacheFilePath(projectConfig); + + var data; + if (!projectConfig.resetCache) { + data = loadCacheSync(this._cacheFilePath); + } else { + data = Object.create(null); + } + this._data = data; + + this._has = Object.prototype.hasOwnProperty.bind(data); + this._persistEventually = _.debounce( + this._persistCache.bind(this), + 2000 + ); +} + +Cache.prototype.get = function(filepath, loaderCb) { + if (!pathUtils.isAbsolutePath(filepath)) { + throw new Error('Use absolute paths'); + } + + var recordP = this._has(filepath) + ? this._data[filepath] + : this._set(filepath, loaderCb(filepath)); + + return recordP.then(function(record) { + return record.data; + }); +}; + +Cache.prototype._set = function(filepath, loaderPromise) { + return this._data[filepath] = loaderPromise.then(function(data) { + return [ + data, + q.nfbind(fs.stat)(filepath) + ]; + }).spread(function(data, stat) { + this._persistEventually(); + return { + data: data, + mtime: stat.mtime.getTime(), + }; + }.bind(this)); +}; + +Cache.prototype.invalidate = function(filepath){ + if(this._has(filepath)) { + delete this._data[filepath]; + } +} + +Cache.prototype.end = function() { + return this._persistCache(); +}; + +Cache.prototype._persistCache = function() { + if (this._persisting != null) { + return this._persisting; + } + + var data = this._data; + var cacheFilepath = this._cacheFilePath; + + return this._persisting = q.all(_.values(data)) + .then(function(values) { + var json = Object.create(null); + Object.keys(data).forEach(function(key, i) { + json[key] = values[i]; + }); + return q.nfbind(fs.writeFile)(cacheFilepath, JSON.stringify(json)); + }) + .then(function() { + this._persisting = null; + return true; + }.bind(this)); +}; + +function loadCacheSync(cacheFilepath) { + var ret = Object.create(null); + if (!fs.existsSync(cacheFilepath)) { + return ret; + } + + var cacheOnDisk = JSON.parse(fs.readFileSync(cacheFilepath)); + + // Filter outdated cache and convert to promises. + Object.keys(cacheOnDisk).forEach(function(key) { + if (!fs.existsSync(key)) { + return; + } + var value = cacheOnDisk[key]; + var stat = fs.statSync(key); + if (stat.mtime.getTime() === value.mtime) { + ret[key] = Promise.resolve(value); + } + }); + + return ret; +} + +function cacheFilePath(projectConfig) { + var roots = projectConfig.projectRoots.join(',').split(path.sep).join('-'); + var cacheVersion = projectConfig.cacheVersion || '0'; + return path.join( + tmpdir, + [ + 'react-packager-cache', + version, + cacheVersion, + roots, + ].join('-') + ); +} diff --git a/react-packager/src/JSTransformer/README.md b/react-packager/src/JSTransformer/README.md new file mode 100644 index 00000000..e69de29b diff --git a/react-packager/src/JSTransformer/__mocks__/worker.js b/react-packager/src/JSTransformer/__mocks__/worker.js new file mode 100644 index 00000000..04a24e8d --- /dev/null +++ b/react-packager/src/JSTransformer/__mocks__/worker.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function (data, callback) { + callback(null, {}); +}; diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js new file mode 100644 index 00000000..c77c6384 --- /dev/null +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -0,0 +1,202 @@ +'use strict'; + +jest + .dontMock('underscore') + .dontMock('path') + .dontMock('q') + .dontMock('absolute-path') + .dontMock('../../fb-path-utils') + .dontMock('../Cache'); + +var q = require('q'); + +describe('JSTransformer Cache', function() { + var Cache; + + beforeEach(function() { + require('os').tmpDir.mockImpl(function() { + return 'tmpDir'; + }); + + Cache = require('../Cache'); + }); + + describe('getting/settig', function() { + it('calls loader callback for uncached file', function() { + var cache = new Cache({projectRoots: ['/rootDir']}); + var loaderCb = jest.genMockFn().mockImpl(function() { + return q(); + }); + cache.get('/rootDir/someFile', loaderCb); + expect(loaderCb).toBeCalledWith('/rootDir/someFile'); + }); + + pit('gets the value from the loader callback', function() { + require('fs').stat.mockImpl(function(file, callback) { + callback(null, { + mtime: { + getTime: function() {} + } + }); + }); + var cache = new Cache({projectRoots: ['/rootDir']}); + var loaderCb = jest.genMockFn().mockImpl(function() { + return q('lol'); + }); + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { + expect(value).toBe('lol'); + }); + }); + + pit('caches the value after the first call', function() { + require('fs').stat.mockImpl(function(file, callback) { + callback(null, { + mtime: { + getTime: function() {} + } + }); + }); + var cache = new Cache({projectRoots: ['/rootDir']}); + var loaderCb = jest.genMockFn().mockImpl(function() { + return q('lol'); + }); + return cache.get('/rootDir/someFile', loaderCb).then(function() { + var shouldNotBeCalled = jest.genMockFn(); + return cache.get('/rootDir/someFile', shouldNotBeCalled) + .then(function(value) { + expect(shouldNotBeCalled).not.toBeCalled(); + expect(value).toBe('lol'); + }); + }); + }); + }); + + describe('loading cache from disk', function() { + var fileStats; + + beforeEach(function() { + fileStats = { + '/rootDir/someFile': { + mtime: { + getTime: function() { + return 22; + } + } + }, + '/rootDir/foo': { + mtime: { + getTime: function() { + return 11; + } + } + } + }; + + var fs = require('fs'); + + fs.existsSync.mockImpl(function() { + return true; + }); + + fs.statSync.mockImpl(function(filePath) { + return fileStats[filePath]; + }); + + fs.readFileSync.mockImpl(function() { + return JSON.stringify({ + '/rootDir/someFile': { + mtime: 22, + data: 'oh hai' + }, + '/rootDir/foo': { + mtime: 11, + data: 'lol wat' + } + }); + }); + }); + + pit('should load cache from disk', function() { + var cache = new Cache({projectRoots: ['/rootDir']}); + var loaderCb = jest.genMockFn(); + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('oh hai'); + + return cache.get('/rootDir/foo', loaderCb).then(function(value) { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('lol wat'); + }); + }); + }); + + pit('should not load outdated cache', function() { + require('fs').stat.mockImpl(function(file, callback) { + callback(null, { + mtime: { + getTime: function() {} + } + }); + }); + + fileStats['/rootDir/foo'].mtime.getTime = function() { + return 123; + }; + + var cache = new Cache({projectRoots: ['/rootDir']}); + var loaderCb = jest.genMockFn().mockImpl(function() { + return q('new value'); + }); + + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('oh hai'); + + return cache.get('/rootDir/foo', loaderCb).then(function(value) { + expect(loaderCb).toBeCalled(); + expect(value).toBe('new value'); + }); + }); + }); + }); + + describe('writing cache to disk', function() { + it('should write cache to disk', function() { + var index = 0; + var mtimes = [10, 20, 30]; + var debounceIndex = 0; + require('underscore').debounce = function(callback) { + return function () { + if (++debounceIndex === 3) { + callback(); + } + }; + }; + + var fs = require('fs'); + fs.stat.mockImpl(function(file, callback) { + callback(null, { + mtime: { + getTime: function() { + return mtimes[index++]; + } + } + }); + }); + + var cache = new Cache({projectRoots: ['/rootDir']}); + cache.get('/rootDir/bar', function() { + return q('bar value'); + }); + cache.get('/rootDir/foo', function() { + return q('foo value'); + }); + cache.get('/rootDir/baz', function() { + return q('baz value'); + }); + + jest.runAllTimers(); + expect(fs.writeFile).toBeCalled(); + }); + }); +}); diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js new file mode 100644 index 00000000..6c9c6644 --- /dev/null +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -0,0 +1,71 @@ +'use strict'; + +jest + .dontMock('worker-farm') + .dontMock('q') + .dontMock('os') + .dontMock('../index'); + +var OPTIONS = { + transformModulePath: '/foo/bar' +}; + +describe('Transformer', function() { + var Transformer; + var workers; + + beforeEach(function() { + workers = jest.genMockFn(); + jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() { + return workers; + })); + require('../Cache').prototype.get.mockImpl(function(filePath, callback) { + return callback(); + }); + require('fs').readFile.mockImpl(function(file, callback) { + callback(null, 'content'); + }); + Transformer = require('../'); + }); + + pit('should loadFileAndTransform', function() { + workers.mockImpl(function(data, callback) { + callback(null, { code: 'transformed' }); + }); + require('fs').readFile.mockImpl(function(file, callback) { + callback(null, 'content'); + }); + + return new Transformer(OPTIONS).loadFileAndTransform([], 'file', {}) + .then(function(data) { + expect(data).toEqual({ + code: 'transformed', + sourcePath: 'file', + sourceCode: 'content' + }); + }); + }); + + pit('should add file info to parse errors', function() { + require('fs').readFile.mockImpl(function(file, callback) { + callback(null, 'var x;\nvar answer = 1 = x;'); + }); + + workers.mockImpl(function(data, callback) { + var esprimaError = new Error('Error: Line 2: Invalid left-hand side in assignment'); + esprimaError.description = 'Invalid left-hand side in assignment'; + esprimaError.lineNumber = 2; + esprimaError.column = 15; + callback(null, {error: esprimaError}); + }); + + return new Transformer(OPTIONS).loadFileAndTransform([], 'foo-file.js', {}) + .catch(function(error) { + expect(error.type).toEqual('TransformError'); + expect(error.snippet).toEqual([ + 'var answer = 1 = x;', + ' ^', + ].join('\n')); + }); + }); +}); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js new file mode 100644 index 00000000..7b01d961 --- /dev/null +++ b/react-packager/src/JSTransformer/index.js @@ -0,0 +1,112 @@ + +'use strict'; + +var os = require('os'); +var fs = require('fs'); +var q = require('q'); +var Cache = require('./Cache'); +var _ = require('underscore'); +var workerFarm = require('worker-farm'); + +var readFile = q.nfbind(fs.readFile); + +module.exports = Transformer; +Transformer.TransformError = TransformError; + +function Transformer(projectConfig) { + this._cache = projectConfig.nonPersistent + ? new DummyCache() : new Cache(projectConfig); + + if (projectConfig.transformModulePath == null) { + this._failedToStart = q.Promise.reject(new Error('No transfrom module')); + } else { + this._workers = workerFarm( + {autoStart: true}, + projectConfig.transformModulePath + ); + } +} + +Transformer.prototype.kill = function() { + this._workers && workerFarm.end(this._workers); + return this._cache.end(); +}; + +Transformer.prototype.invalidateFile = function(filePath) { + this._cache.invalidate(filePath); + //TODO: We can read the file and put it into the cache right here + // This would simplify some caching logic as we can be sure that the cache is up to date +} + +Transformer.prototype.loadFileAndTransform = function( + transformSets, + filePath, + options +) { + if (this._failedToStart) { + return this._failedToStart; + } + + var workers = this._workers; + return this._cache.get(filePath, function() { + return readFile(filePath) + .then(function(buffer) { + var sourceCode = buffer.toString(); + var opts = _.extend({}, options, {filename: filePath}); + return q.nfbind(workers)({ + transformSets: transformSets, + sourceCode: sourceCode, + options: opts, + }).then( + function(res) { + if (res.error) { + throw formatEsprimaError(res.error, filePath, sourceCode); + } + + return { + code: res.code, + sourcePath: filePath, + sourceCode: sourceCode + }; + } + ); + }); + }); +}; + +function TransformError() {} +TransformError.__proto__ = SyntaxError.prototype; + +function formatEsprimaError(err, filename, source) { + if (!(err.lineNumber && err.column)) { + return err; + } + + var stack = err.stack.split('\n'); + stack.shift(); + + var msg = 'TransformError: ' + err.description + ' ' + filename + ':' + + err.lineNumber + ':' + err.column; + var sourceLine = source.split('\n')[err.lineNumber - 1]; + var snippet = sourceLine + '\n' + new Array(err.column - 1).join(' ') + '^'; + + stack.unshift(msg); + + var error = new TransformError(); + error.message = msg; + error.type = 'TransformError'; + error.stack = stack.join('\n'); + error.snippet = snippet; + error.filename = filename; + error.lineNumber = err.lineNumber; + error.column = err.column; + error.description = err.description; + return error; +} + +function DummyCache() {} +DummyCache.prototype.get = function(filePath, loaderCb) { + return loaderCb(); +}; +DummyCache.prototype.end = +DummyCache.prototype.invalidate = function(){}; diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js new file mode 100644 index 00000000..26f789e4 --- /dev/null +++ b/react-packager/src/JSTransformer/worker.js @@ -0,0 +1,26 @@ +'use strict'; + +var transformer = require('./transformer'); + +module.exports = function (data, callback) { + var result; + try { + result = transformer.transform( + data.transformSets, + data.sourceCode, + data.options + ); + } catch (e) { + return callback(null, { + error: { + lineNumber: e.lineNumber, + column: e.column, + message: e.message, + stack: e.stack, + description: e.description + } + }); + } + + callback(null, result); +}; diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js new file mode 100644 index 00000000..787684bc --- /dev/null +++ b/react-packager/src/Packager/Package.js @@ -0,0 +1,132 @@ +'use strict'; + +var _ = require('underscore'); +var SourceMapGenerator = require('source-map').SourceMapGenerator; +var base64VLQ = require('./base64-vlq'); + +module.exports = Package; + +function Package(sourceMapUrl) { + this._modules = []; + this._sourceMapUrl = sourceMapUrl; +} + +Package.prototype.setMainModuleId = function(moduleId) { + this._mainModuleId = moduleId; +}; + +Package.prototype.addModule = function( + transformedCode, + sourceCode, + sourcePath +) { + this._modules.push({ + transformedCode: transformedCode, + sourceCode: sourceCode, + sourcePath: sourcePath + }); +}; + +Package.prototype.finalize = function(options) { + if (options.runMainModule) { + var runCode = ';require("' + this._mainModuleId + '");'; + this.addModule( + runCode, + runCode, + 'RunMainModule.js' + ); + } + + Object.freeze(this._modules); + Object.seal(this._modules); +}; + +Package.prototype.getSource = function() { + return this._source || ( + this._source = _.pluck(this._modules, 'transformedCode').join('\n') + '\n' + + 'RAW_SOURCE_MAP = ' + JSON.stringify(this.getSourceMap({excludeSource: true})) + + ';\n' + '\/\/@ sourceMappingURL=' + this._sourceMapUrl + ); +}; + +Package.prototype.getSourceMap = function(options) { + options = 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._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 mappings = ''; + for (var i = 0; i < modules.length; i++) { + var module = modules[i]; + var transformedCode = module.transformedCode; + var lastCharNewLine = false; + module.lines = 0; + for (var t = 0; t < transformedCode.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 - modules[i - 1].lines); + mappings += 'A'; + } else if (lastCharNewLine) { + module.lines++; + mappings += line; + } + lastCharNewLine = transformedCode[t] === '\n'; + if (lastCharNewLine) { + mappings += ';'; + } + } + if (i != modules.length - 1) { + mappings += ';'; + } + } + return mappings; +}; + +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'); +}; diff --git a/react-packager/src/Packager/__mocks__/source-map.js b/react-packager/src/Packager/__mocks__/source-map.js new file mode 100644 index 00000000..08c127f6 --- /dev/null +++ b/react-packager/src/Packager/__mocks__/source-map.js @@ -0,0 +1,5 @@ +var SourceMapGenerator = jest.genMockFn(); +SourceMapGenerator.prototype.addMapping = jest.genMockFn(); +SourceMapGenerator.prototype.setSourceContent = jest.genMockFn(); +SourceMapGenerator.prototype.toJSON = jest.genMockFn(); +exports.SourceMapGenerator = SourceMapGenerator; diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js new file mode 100644 index 00000000..d18bb4d6 --- /dev/null +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -0,0 +1,95 @@ +'use strict'; + +jest + .dontMock('underscore') + .dontMock('../base64-vlq') + .dontMock('source-map') + .dontMock('../Package'); + +var SourceMapGenerator = require('source-map').SourceMapGenerator; + +describe('Package', function() { + var Package; + var ppackage; + + beforeEach(function() { + Package = require('../Package'); + ppackage = new Package('test_url'); + ppackage.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('transformed foo;', 'source foo', 'foo path'); + ppackage.addModule('transformed bar;', 'source bar', 'bar path'); + ppackage.finalize({}); + expect(ppackage.getSource()).toBe([ + 'transformed foo;', + 'transformed bar;', + 'RAW_SOURCE_MAP = "test-source-map";', + '\/\/@ sourceMappingURL=test_url', + ].join('\n')); + }); + + it('should create a package and add run module code', function() { + ppackage.addModule('transformed foo;', 'source foo', 'foo path'); + ppackage.addModule('transformed bar;', 'source bar', 'bar path'); + ppackage.setMainModuleId('foo'); + ppackage.finalize({runMainModule: true}); + expect(ppackage.getSource()).toBe([ + 'transformed foo;', + 'transformed bar;', + ';require("foo");', + 'RAW_SOURCE_MAP = "test-source-map";', + '\/\/@ sourceMappingURL=test_url', + ].join('\n')); + }); + }); + + describe('sourcemap package', function() { + it('should create sourcemap', function() { + var ppackage = new Package('test_url'); + ppackage.addModule('transformed foo;\n', 'source foo', 'foo path'); + ppackage.addModule('transformed bar;\n', 'source bar', 'bar path'); + ppackage.setMainModuleId('foo'); + ppackage.finalize({runMainModule: true}); + var s = ppackage.getSourceMap(); + expect(s).toEqual(genSourceMap(ppackage._modules)); + }); + }); +}); + + function genSourceMap(modules) { + var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3}); + var packageLineNo = 0; + for (var i = 0; i < modules.length; i++) { + var module = modules[i]; + var transformedCode = module.transformedCode; + var sourcePath = module.sourcePath; + var sourceCode = module.sourceCode; + var transformedLineCount = 0; + var lastCharNewLine = false; + for (var t = 0; t < transformedCode.length; t++) { + if (t === 0 || lastCharNewLine) { + sourceMapGen.addMapping({ + generated: {line: packageLineNo + 1, column: 0}, + original: {line: transformedLineCount + 1, column: 0}, + source: sourcePath + }); + } + lastCharNewLine = transformedCode[t] === '\n'; + if (lastCharNewLine) { + transformedLineCount++; + packageLineNo++; + } + } + packageLineNo++; + sourceMapGen.setSourceContent( + sourcePath, + sourceCode + ); + } + return sourceMapGen.toJSON(); +}; diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js new file mode 100644 index 00000000..21af12ca --- /dev/null +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -0,0 +1,83 @@ +'use strict'; + +jest + .setMock('worker-farm', function() { return function() {};}) + .dontMock('path') + .dontMock('q') + .dontMock('os') + .dontMock('underscore') + .dontMock('../'); + +var q = require('q'); + +describe('Packager', function() { + var getDependencies; + var wrapModule; + var Packager; + + beforeEach(function() { + getDependencies = jest.genMockFn(); + wrapModule = jest.genMockFn(); + require('../../DependencyResolver').mockImpl(function() { + return { + getDependencies: getDependencies, + wrapModule: wrapModule, + }; + }); + + Packager = require('../'); + }); + + pit('create a package', function() { + require('fs').statSync.mockImpl(function() { + return { + isDirectory: function() {return true;} + }; + }); + + var packager = new Packager({projectRoots: []}); + var modules = [ + {id: 'foo', path: '/root/foo.js', dependencies: []}, + {id: 'bar', path: '/root/bar.js', dependencies: []}, + ]; + + getDependencies.mockImpl(function() { + return q({ + mainModuleId: 'foo', + dependencies: modules + }); + }); + + require('../../JSTransformer').prototype.loadFileAndTransform + .mockImpl(function(tsets, path) { + return q({ + code: 'transformed ' + path, + sourceCode: 'source ' + path, + sourcePath: path + }); + }); + + wrapModule.mockImpl(function(module, code) { + return 'lol ' + code + ' lol'; + }); + + return packager.package('/root/foo.js', true, 'source_map_url') + .then(function(p) { + expect(p.addModule.mock.calls[0]).toEqual([ + 'lol transformed /root/foo.js lol', + 'source /root/foo.js', + '/root/foo.js' + ]); + expect(p.addModule.mock.calls[1]).toEqual([ + 'lol transformed /root/bar.js lol', + 'source /root/bar.js', + '/root/bar.js' + ]); + + expect(p.finalize.mock.calls[0]).toEqual([ + {runMainModule: true} + ]); + }); + }); + +}); diff --git a/react-packager/src/Packager/base64-vlq.js b/react-packager/src/Packager/base64-vlq.js new file mode 100644 index 00000000..91d490b7 --- /dev/null +++ b/react-packager/src/Packager/base64-vlq.js @@ -0,0 +1,168 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + * + * Based on the Base 64 VLQ implementation in Closure Compiler: + * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java + * + * Copyright 2011 The Closure Compiler Authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var charToIntMap = {}; +var intToCharMap = {}; + +'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + .split('') + .forEach(function (ch, index) { + charToIntMap[ch] = index; + intToCharMap[index] = ch; + }); + +var base64 = {}; +/** + * Encode an integer in the range of 0 to 63 to a single base 64 digit. + */ +base64.encode = function base64_encode(aNumber) { + if (aNumber in intToCharMap) { + return intToCharMap[aNumber]; + } + throw new TypeError("Must be between 0 and 63: " + aNumber); +}; + +/** + * Decode a single base 64 digit to an integer. + */ +base64.decode = function base64_decode(aChar) { + if (aChar in charToIntMap) { + return charToIntMap[aChar]; + } + throw new TypeError("Not a valid base 64 digit: " + aChar); +}; + + + +// A single base 64 digit can contain 6 bits of data. For the base 64 variable +// length quantities we use in the source map spec, the first bit is the sign, +// the next four bits are the actual value, and the 6th bit is the +// continuation bit. The continuation bit tells us whether there are more +// digits in this value following this digit. +// +// Continuation +// | Sign +// | | +// V V +// 101011 + +var VLQ_BASE_SHIFT = 5; + +// binary: 100000 +var VLQ_BASE = 1 << VLQ_BASE_SHIFT; + +// binary: 011111 +var VLQ_BASE_MASK = VLQ_BASE - 1; + +// binary: 100000 +var VLQ_CONTINUATION_BIT = VLQ_BASE; + +/** + * Converts from a two-complement value to a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) + * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) + */ +function toVLQSigned(aValue) { + return aValue < 0 + ? ((-aValue) << 1) + 1 + : (aValue << 1) + 0; +} + +/** + * Converts to a two-complement value from a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 + * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 + */ +function fromVLQSigned(aValue) { + var isNegative = (aValue & 1) === 1; + var shifted = aValue >> 1; + return isNegative + ? -shifted + : shifted; +} + +/** + * Returns the base 64 VLQ encoded value. + */ +exports.encode = function base64VLQ_encode(aValue) { + var encoded = ""; + var digit; + + var vlq = toVLQSigned(aValue); + + do { + digit = vlq & VLQ_BASE_MASK; + vlq >>>= VLQ_BASE_SHIFT; + if (vlq > 0) { + // There are still more digits in this value, so we must make sure the + // continuation bit is marked. + digit |= VLQ_CONTINUATION_BIT; + } + encoded += base64.encode(digit); + } while (vlq > 0); + + return encoded; +}; + +/** + * Decodes the next base 64 VLQ value from the given string and returns the + * value and the rest of the string via the out parameter. + */ +exports.decode = function base64VLQ_decode(aStr, aOutParam) { + var i = 0; + var strLen = aStr.length; + var result = 0; + var shift = 0; + var continuation, digit; + + do { + if (i >= strLen) { + throw new Error("Expected more digits in base 64 VLQ value."); + } + digit = base64.decode(aStr.charAt(i++)); + continuation = !!(digit & VLQ_CONTINUATION_BIT); + digit &= VLQ_BASE_MASK; + result = result + (digit << shift); + shift += VLQ_BASE_SHIFT; + } while (continuation); + + aOutParam.value = fromVLQSigned(result); + aOutParam.rest = aStr.slice(i); +}; + diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js new file mode 100644 index 00000000..3ec4e378 --- /dev/null +++ b/react-packager/src/Packager/index.js @@ -0,0 +1,127 @@ +'use strict'; + +var assert = require('assert'); +var fs = require('fs'); +var path = require('path'); +var q = require('q'); +var Promise = require('q').Promise; +var Transformer = require('../JSTransformer'); +var DependencyResolver = require('../DependencyResolver'); +var _ = require('underscore'); +var Package = require('./Package'); +var Activity = require('../Activity'); + +var DEFAULT_CONFIG = { + /** + * RegExp used to ignore paths when scanning the filesystem to calculate the + * dependency graph. + */ + blacklistRE: null, + + /** + * The kind of module system/transport wrapper to use for the modules bundled + * in the package. + */ + moduleFormat: 'haste', + + /** + * An ordered list of module names that should be considered as dependencies + * of every module in the system. The list is ordered because every item in + * the list will have an implicit dependency on all items before it. + * + * (This ordering is necessary to build, for example, polyfills that build on + * each other) + */ + polyfillModuleNames: [], + + nonPersistent: false, +}; + +function Packager(projectConfig) { + projectConfig.projectRoots.forEach(verifyRootExists); + + this._config = Object.create(DEFAULT_CONFIG); + for (var key in projectConfig) { + this._config[key] = projectConfig[key]; + } + + this._resolver = new DependencyResolver(this._config); + + this._transformer = new Transformer(projectConfig); +} + +Packager.prototype.kill = function() { + return q.all([ + this._transformer.kill(), + this._resolver.end(), + ]); +}; + +Packager.prototype.package = function(main, runModule, sourceMapUrl) { + var transformModule = this._transformModule.bind(this); + var ppackage = new Package(sourceMapUrl); + + var findEventId = Activity.startEvent('find dependencies'); + var transformEventId; + + return this.getDependencies(main) + .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(transformed) { + ppackage.addModule( + transformed.code, + transformed.sourceCode, + transformed.sourcePath + ); + }); + + ppackage.finalize({ runMainModule: runModule }); + return ppackage; + }); +}; + +Packager.prototype.invalidateFile = function(filePath) { + this._transformer.invalidateFile(filePath); +} + +Packager.prototype.getDependencies = function(main) { + return this._resolver.getDependencies(main); +}; + +Packager.prototype._transformModule = function(module) { + var resolver = this._resolver; + return this._transformer.loadFileAndTransform( + ['es6'], + path.resolve(module.path), + this._config.transformer || {} + ).then(function(transformed) { + return _.extend( + {}, + transformed, + {code: resolver.wrapModule(module, transformed.code)} + ); + }); +}; + + +function verifyRootExists(root) { + // Verify that the root exists. + assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); +} + +Packager.prototype.getGraphDebugInfo = function() { + return this._resolver.getDebugInfo(); +}; + + +module.exports = Packager; diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js new file mode 100644 index 00000000..511ec8a3 --- /dev/null +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -0,0 +1,158 @@ +jest.setMock('worker-farm', function(){ return function(){}; }) + .dontMock('q') + .dontMock('os') + .dontMock('errno/custom') + .dontMock('path') + .dontMock('url') + .dontMock('../'); + + +var server = require('../'); +var q = require('q'); + +describe('processRequest', function(){ + var server; + var Activity; + var Packager; + var FileWatcher; + + var options = { + projectRoots: ['root'], + blacklistRE: null, + cacheVersion: null, + polyfillModuleNames: null + }; + + var makeRequest = function(requestHandler, requrl){ + var deferred = q.defer(); + requestHandler({ + url: requrl + },{ + end: function(res){ + deferred.resolve(res); + } + },{ + next: function(){} + } + ); + return deferred.promise; + }; + + var invalidatorFunc = jest.genMockFunction(); + var watcherFunc = jest.genMockFunction(); + var requestHandler; + + beforeEach(function(){ + Activity = require('../../Activity'); + Packager = require('../../Packager'); + FileWatcher = require('../../FileWatcher') + + Packager.prototype.package = function(main, runModule, sourceMapUrl) { + return q({ + getSource: function(){ + return "this is the source" + }, + getSourceMap: function(){ + return "this is the source map" + } + }) + }; + + FileWatcher.prototype.on = watcherFunc; + + Packager.prototype.invalidateFile = invalidatorFunc; + + var Server = require('../'); + server = new Server(options); + requestHandler = server.processRequest.bind(server); + }); + + pit('returns JS bundle source on request of *.bundle',function(){ + result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); + return result.then(function(response){ + expect(response).toEqual("this is the source"); + }); + }); + + pit('returns sourcemap on request of *.map', function(){ + result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle.map'); + return result.then(function(response){ + expect(response).toEqual('"this is the source map"'); + }); + }); + + pit('watches all files in projectRoot', function(){ + result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); + return result.then(function(response){ + expect(watcherFunc.mock.calls[0][0]).toEqual('all'); + expect(watcherFunc.mock.calls[0][1]).not.toBe(null); + }) + }); + + + describe('file changes', function() { + var triggerFileChange; + beforeEach(function() { + FileWatcher.prototype.on = function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + triggerFileChange = callback; + return this; + }; + }); + + pit('invalides files in package when file is updated', function() { + result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); + return result.then(function(response){ + var 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 packages that contain a file when that file is changed', function() { + var packageFunc = jest.genMockFunction(); + packageFunc + .mockReturnValueOnce( + q({ + getSource: function(){ + return "this is the first source" + }, + getSourceMap: function(){}, + }) + ) + .mockReturnValue( + q({ + getSource: function(){ + return "this is the rebuilt source" + }, + getSourceMap: function(){}, + }) + ); + + Packager.prototype.package = packageFunc; + + var Server = require('../../Server'); + var server = new Server(options); + + requestHandler = server.processRequest.bind(server); + + + return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + .then(function(response){ + expect(response).toEqual("this is the first source"); + expect(packageFunc.mock.calls.length).toBe(1); + triggerFileChange('all','path/file.js', options.projectRoots[0]); + jest.runAllTimers(); + }) + .then(function(){ + expect(packageFunc.mock.calls.length).toBe(2); + return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + .then(function(response){ + expect(response).toEqual("this is the rebuilt source"); + }); + }); + }); + }); +}); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js new file mode 100644 index 00000000..26929ebb --- /dev/null +++ b/react-packager/src/Server/index.js @@ -0,0 +1,173 @@ +var url = require('url'); +var path = require('path'); +var FileWatcher = require('../FileWatcher') +var Packager = require('../Packager'); +var Activity = require('../Activity'); +var q = require('q'); + +module.exports = Server; + +function Server(options) { + this._projectRoots = options.projectRoots; + this._packages = Object.create(null); + this._packager = new Packager({ + projectRoots: options.projectRoots, + blacklistRE: options.blacklistRE, + polyfillModuleNames: options.polyfillModuleNames || [], + runtimeCode: options.runtimeCode, + cacheVersion: options.cacheVersion, + resetCache: options.resetCache, + dev: options.dev, + transformModulePath: options.transformModulePath, + nonPersistent: options.nonPersistent, + }); + + this._fileWatcher = options.nonPersistent + ? FileWatcher.createDummyWatcher() + : new FileWatcher(options.projectRoots); + + var onFileChange = this._onFileChange.bind(this); + this._fileWatcher.on('all', onFileChange); +} + +Server.prototype._onFileChange = function(type, filepath, root) { + var absPath = path.join(root, filepath); + this._packager.invalidateFile(absPath); + // Make sure the file watcher event runs through the system before + // we rebuild the packages. + setImmediate(this._rebuildPackages.bind(this, absPath)) +}; + +Server.prototype._rebuildPackages = function(filepath) { + var buildPackage = this._buildPackage.bind(this); + var packages = this._packages; + Object.keys(packages).forEach(function(key) { + var options = getOptionsFromPath(url.parse(key).pathname); + packages[key] = buildPackage(options).then(function(p) { + // Make a throwaway call to getSource to cache the source string. + p.getSource(); + return p; + }); + }); +}; + +Server.prototype.end = function() { + q.all([ + this._fileWatcher.end(), + this._packager.kill(), + ]); +}; + +Server.prototype._buildPackage = function(options) { + return this._packager.package( + options.main, + options.runModule, + options.sourceMapUrl + ); +}; + +Server.prototype.buildPackageFromUrl = function(reqUrl) { + var options = getOptionsFromPath(url.parse(reqUrl).pathname); + return this._buildPackage(options); +}; + +Server.prototype.getDependencies = function(main) { + return this._packager.getDependencies(main); +}; + +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] === 'packages') { + ret += '

Cached Packages

'; + q.all(Object.keys(this._packages).map(function(url) { + return this._packages[url].then(function(p) { + ret += '

' + url + '

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

Dependency Graph

'; + ret += this._packager.getGraphDebugInfo(); + res.end(ret); + } else { + res.writeHead('404'); + res.end('Invalid debug request'); + return; + } +}; + +Server.prototype.processRequest = function(req, res, next) { + var requestType; + if (req.url.match(/\.bundle$/)) { + requestType = 'bundle'; + } else if (req.url.match(/\.map$/)) { + requestType = 'map'; + } else if (req.url.match(/^\/debug/)) { + this._processDebugRequest(req.url, res); + return; + } else { + return next(); + } + + var startReqEventId = Activity.startEvent('request:' + req.url); + var options = getOptionsFromPath(url.parse(req.url).pathname); + var building = this._packages[req.url] || this._buildPackage(options) + this._packages[req.url] = building; + building.then( + function(p) { + if (requestType === 'bundle') { + res.end(p.getSource()); + Activity.endEvent(startReqEventId); + } else if (requestType === 'map') { + res.end(JSON.stringify(p.getSourceMap())); + Activity.endEvent(startReqEventId); + } + }, + function(error) { + handleError(res, error); + } + ).done(); +}; + +function getOptionsFromPath(pathname) { + var parts = pathname.split('.'); + // Remove the leading slash. + var main = parts[0].slice(1) + '.js'; + return { + runModule: parts.slice(1).some(function(part) { + return part === 'runModule'; + }), + main: main, + sourceMapUrl: parts.slice(0, -1).join('.') + '.map' + }; +} + +function handleError(res, error) { + res.writeHead(500, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + + if (error.type === 'TransformError') { + res.end(JSON.stringify(error)); + } 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', + })); + } +} diff --git a/react-packager/src/fb-path-utils/index.js b/react-packager/src/fb-path-utils/index.js new file mode 100644 index 00000000..b4a1cb96 --- /dev/null +++ b/react-packager/src/fb-path-utils/index.js @@ -0,0 +1,14 @@ +var absolutePath = require('absolute-path'); +var path = require('path'); +var pathIsInside = require('path-is-inside'); + +function isAbsolutePath(pathStr) { + return absolutePath(pathStr); +} + +function isChildPath(parentPath, childPath) { + return pathIsInside(parentPath, childPath); +} + +exports.isAbsolutePath = isAbsolutePath; +exports.isChildPath = isChildPath; diff --git a/transformer.js b/transformer.js new file mode 100644 index 00000000..df5e7143 --- /dev/null +++ b/transformer.js @@ -0,0 +1,57 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * Note: This is a fork of the fb-specific transform.js + */ +'use strict'; + +var jstransform = require('jstransform').transform; + +var reactVisitors = + require('react-tools/vendor/fbtransform/visitors').getAllVisitors(); +var staticTypeSyntax = + require('jstransform/visitors/type-syntax').visitorList; +// Note that reactVisitors now handles ES6 classes, rest parameters, arrow +// functions, template strings, and object short notation. +var visitorList = reactVisitors; + + +function transform(transformSets, srcTxt, options) { + options = options || {}; + + // These tranforms mostly just erase type annotations and static typing + // related statements, but they were conflicting with other tranforms. + // Running them first solves that problem + var staticTypeSyntaxResult = jstransform( + staticTypeSyntax, + srcTxt + ); + + return jstransform(visitorList, staticTypeSyntaxResult.code); +} + +module.exports = function(data, callback) { + var result; + try { + result = transform( + data.transformSets, + data.sourceCode, + data.options + ); + } catch (e) { + return callback(null, { + error: { + lineNumber: e.lineNumber, + column: e.column, + message: e.message, + stack: e.stack, + description: e.description + } + }); + } + + callback(null, result); +}; + +// export for use in jest +module.exports.transform = transform; From ef2a4f99e25a62b09f26622e11a06eb2a16f634e Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Fri, 20 Feb 2015 10:33:17 -0800 Subject: [PATCH 013/936] Updates from Fri Feb 20 - [react-packager][streamline oss] Move open sourced JS source to react-native-github | Spencer Ahrens - [react-packager] Bump version | Amjad Masad - Converted remaining modules and removed RKBridgeModule | Nick Lockwood --- react-packager/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/package.json b/react-packager/package.json index 84230dec..ad7b7602 100644 --- a/react-packager/package.json +++ b/react-packager/package.json @@ -1,6 +1,6 @@ { "name": "react-packager", - "version": "0.0.2", + "version": "0.1.0", "description": "", "main": "index.js", "jest": { From e781e469d1601a382cbdb4f0230c8e9bda597a50 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Thu, 19 Feb 2015 18:04:10 -0800 Subject: [PATCH 014/936] [React-Native] Update jstransform/esprima --- transformer.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/transformer.js b/transformer.js index df5e7143..ffcb80e2 100644 --- a/transformer.js +++ b/transformer.js @@ -16,18 +16,26 @@ var staticTypeSyntax = var visitorList = reactVisitors; -function transform(transformSets, srcTxt, options) { - options = options || {}; +function transform(transformSets, srcTxt) { + var options = { + es3: true, + sourceType: 'nonStrictModule' + }; // These tranforms mostly just erase type annotations and static typing // related statements, but they were conflicting with other tranforms. // Running them first solves that problem var staticTypeSyntaxResult = jstransform( staticTypeSyntax, - srcTxt + srcTxt, + options ); - return jstransform(visitorList, staticTypeSyntaxResult.code); + return jstransform( + visitorList, + staticTypeSyntaxResult.code, + options + ); } module.exports = function(data, callback) { @@ -35,8 +43,7 @@ module.exports = function(data, callback) { try { result = transform( data.transformSets, - data.sourceCode, - data.options + data.sourceCode ); } catch (e) { return callback(null, { From 61b7183ae0452d262c552d0adc4e7fb9f97e4f4f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 19 Feb 2015 13:11:11 -0800 Subject: [PATCH 015/936] [react-packager][cleanup options 1/2] add npm installed joi validation library --- react-packager/package.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/react-packager/package.json b/react-packager/package.json index ad7b7602..0ac47c25 100644 --- a/react-packager/package.json +++ b/react-packager/package.json @@ -4,7 +4,11 @@ "description": "", "main": "index.js", "jest": { - "unmockedModulePathPatterns": ["source-map"], - "testPathIgnorePatterns": ["JSAppServer/node_modules"] + "unmockedModulePathPatterns": [ + "source-map" + ], + "testPathIgnorePatterns": [ + "JSAppServer/node_modules" + ] } } From 4a3025da0cbad911e6f0d61e384713d695481e84 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 24 Feb 2015 14:22:13 -0800 Subject: [PATCH 016/936] [react-packager] Cleanup option passing and validation --- .../haste/DependencyGraph/index.js | 25 ++++- .../__tests__/HasteDependencyResolver-test.js | 70 +++++++++++++- .../src/DependencyResolver/haste/index.js | 43 +++++++-- react-packager/src/JSTransformer/Cache.js | 31 ++++-- react-packager/src/JSTransformer/index.js | 53 +++++++++-- react-packager/src/Packager/index.js | 95 ++++++++++++------- .../src/Server/__tests__/Server-test.js | 16 ++-- react-packager/src/Server/index.js | 60 +++++++++--- .../src/lib/__mocks__/declareOpts.js | 10 ++ .../src/lib/__tests__/declareOpts-test.js | 82 ++++++++++++++++ react-packager/src/lib/declareOpts.js | 53 +++++++++++ 11 files changed, 453 insertions(+), 85 deletions(-) create mode 100644 react-packager/src/lib/__mocks__/declareOpts.js create mode 100644 react-packager/src/lib/__tests__/declareOpts-test.js create mode 100644 react-packager/src/lib/declareOpts.js diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 9a939620..6a7d8bba 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -8,15 +8,35 @@ var path = require('path'); var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); var util = require('util'); +var declareOpts = require('../../../lib/declareOpts'); var readFile = q.nfbind(fs.readFile); var readDir = q.nfbind(fs.readdir); var lstat = q.nfbind(fs.lstat); var realpath = q.nfbind(fs.realpath); +var validateOpts = declareOpts({ + roots: { + type: 'array', + required: true, + }, + ignoreFilePath: { + type: 'function', + default: function(){} + }, + fileWatcher: { + type: 'object', + required: true, + }, +}); + function DependecyGraph(options) { - this._roots = options.roots; - this._ignoreFilePath = options.ignoreFilePath || function(){}; + var opts = validateOpts(options); + + this._roots = opts.roots; + this._ignoreFilePath = opts.ignoreFilePath; + this._fileWatcher = options.fileWatcher; + this._loaded = false; this._queue = this._roots.slice(); this._graph = Object.create(null); @@ -24,7 +44,6 @@ function DependecyGraph(options) { this._packagesById = Object.create(null); this._moduleById = Object.create(null); this._debugUpdateEvents = []; - this._fileWatcher = options.fileWatcher; // Kick off the search process to precompute the dependency graph. this._init(); diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 9b43f97e..d3c4a7d9 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -24,7 +24,8 @@ describe('HasteDependencyResolver', function() { var deps = [module]; var depResolver = new HasteDependencyResolver({ - projectRoot: '/root' + projectRoot: '/root', + dev: false, }); // Is there a better way? How can I mock the prototype instead? @@ -79,13 +80,75 @@ describe('HasteDependencyResolver', function() { }); }); + pit('should get dependencies with polyfills', function() { + var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var deps = [module]; + + var depResolver = new HasteDependencyResolver({ + projectRoot: '/root', + dev: true, + }); + + // Is there a better way? How can I mock the prototype instead? + var depGraph = depResolver._depGraph; + depGraph.getOrderedDependencies.mockImpl(function() { + return deps; + }); + depGraph.load.mockImpl(function() { + return q(); + }); + + return depResolver.getDependencies('/root/index.js') + .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' + ], + }, + module + ]); + }); + }); + pit('should pass in more polyfills', function() { var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; var deps = [module]; var depResolver = new HasteDependencyResolver({ projectRoot: '/root', - polyfillModuleNames: ['some module'] + polyfillModuleNames: ['some module'], + dev: false, }); // Is there a better way? How can I mock the prototype instead? @@ -155,7 +218,8 @@ describe('HasteDependencyResolver', function() { describe('wrapModule', function() { it('should ', function() { var depResolver = new HasteDependencyResolver({ - projectRoot: '/root' + projectRoot: '/root', + dev: false, }); var depGraph = depResolver._depGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 6e2cd6fc..dc497649 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -4,6 +4,7 @@ var path = require('path'); var FileWatcher = require('../../FileWatcher'); var DependencyGraph = require('./DependencyGraph'); var ModuleDescriptor = require('../ModuleDescriptor'); +var declareOpts = require('../../lib/declareOpts'); var DEFINE_MODULE_CODE = '__d(' + @@ -18,22 +19,50 @@ var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; -function HasteDependencyResolver(config) { - this._fileWatcher = config.nonPersistent +var validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + blacklistRE: { + type: 'object', // typeof regex is object + }, + polyfillModuleNames: { + type: 'array', + default: [], + }, + dev: { + type: 'boolean', + default: true, + }, + nonPersistent: { + type: 'boolean', + default: false, + }, + moduleFormat: { + type: 'string', + default: 'haste', + }, +}); + +function HasteDependencyResolver(options) { + var opts = validateOpts(options); + + this._fileWatcher = opts.nonPersistent ? FileWatcher.createDummyWatcher() - : new FileWatcher(config.projectRoots); + : new FileWatcher(opts.projectRoots); this._depGraph = new DependencyGraph({ - roots: config.projectRoots, + roots: opts.projectRoots, ignoreFilePath: function(filepath) { return filepath.indexOf('__tests__') !== -1 || - (config.blacklistRE && config.blacklistRE.test(filepath)); + (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, fileWatcher: this._fileWatcher }); this._polyfillModuleNames = [ - config.dev + opts.dev ? path.join(__dirname, 'polyfills/prelude_dev.js') : path.join(__dirname, 'polyfills/prelude.js'), path.join(__dirname, 'polyfills/require.js'), @@ -41,7 +70,7 @@ function HasteDependencyResolver(config) { path.join(__dirname, 'polyfills/console.js'), path.join(__dirname, 'polyfills/error-guard.js'), ].concat( - config.polyfillModuleNames || [] + opts.polyfillModuleNames || [] ); } diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index 577af696..f04ffe9f 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -4,19 +4,36 @@ var path = require('path'); var version = require('../../package.json').version; var tmpdir = require('os').tmpDir(); var pathUtils = require('../fb-path-utils'); +var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); var _ = require('underscore'); var q = require('q'); var Promise = q.Promise; +var validateOpts = declareOpts({ + resetCache: { + type: 'boolean', + default: false, + }, + cacheVersion: { + type: 'string', + default: '1.0', + }, + projectRoots: { + type: 'array', + required: true, + }, +}); module.exports = Cache; -function Cache(projectConfig) { - this._cacheFilePath = cacheFilePath(projectConfig); +function Cache(options) { + var opts = validateOpts(options); + + this._cacheFilePath = cacheFilePath(opts); var data; - if (!projectConfig.resetCache) { + if (!opts.resetCache) { data = loadCacheSync(this._cacheFilePath); } else { data = Object.create(null); @@ -63,7 +80,7 @@ Cache.prototype.invalidate = function(filepath){ if(this._has(filepath)) { delete this._data[filepath]; } -} +}; Cache.prototype.end = function() { return this._persistCache(); @@ -114,9 +131,9 @@ function loadCacheSync(cacheFilepath) { return ret; } -function cacheFilePath(projectConfig) { - var roots = projectConfig.projectRoots.join(',').split(path.sep).join('-'); - var cacheVersion = projectConfig.cacheVersion || '0'; +function cacheFilePath(options) { + var roots = options.projectRoots.join(',').split(path.sep).join('-'); + var cacheVersion = options.cacheVersion || '0'; return path.join( tmpdir, [ diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 7b01d961..ade206a7 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -1,28 +1,69 @@ 'use strict'; -var os = require('os'); var fs = require('fs'); var q = require('q'); var Cache = require('./Cache'); var _ = require('underscore'); var workerFarm = require('worker-farm'); +var declareOpts = require('../lib/declareOpts'); var readFile = q.nfbind(fs.readFile); module.exports = Transformer; Transformer.TransformError = TransformError; -function Transformer(projectConfig) { - this._cache = projectConfig.nonPersistent - ? new DummyCache() : new Cache(projectConfig); +var validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + blacklistRE: { + type: 'object', // typeof regex is object + }, + polyfillModuleNames: { + type: 'array', + default: [], + }, + cacheVersion: { + type: 'string', + default: '1.0', + }, + resetCache: { + type: 'boolean', + default: false, + }, + dev: { + type: 'boolean', + default: true, + }, + transformModulePath: { + type:'string', + required: true, + }, + nonPersistent: { + type: 'boolean', + default: false, + }, +}); - if (projectConfig.transformModulePath == null) { +function Transformer(options) { + var opts = validateOpts(options); + + this._cache = opts.nonPersistent + ? new DummyCache() + : new Cache({ + resetCache: options.resetCache, + cacheVersion: options.cacheVersion, + projectRoots: options.projectRoots, + }); + + if (options.transformModulePath == null) { this._failedToStart = q.Promise.reject(new Error('No transfrom module')); } else { this._workers = workerFarm( {autoStart: true}, - projectConfig.transformModulePath + options.transformModulePath ); } } diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 3ec4e378..ddcab6ee 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -10,44 +10,69 @@ var DependencyResolver = require('../DependencyResolver'); var _ = require('underscore'); var Package = require('./Package'); var Activity = require('../Activity'); +var declareOpts = require('../lib/declareOpts'); -var DEFAULT_CONFIG = { - /** - * RegExp used to ignore paths when scanning the filesystem to calculate the - * dependency graph. - */ - blacklistRE: null, +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, + }, + dev: { + type: 'boolean', + default: true, + }, + transformModulePath: { + type:'string', + required: true, + }, + nonPersistent: { + type: 'boolean', + default: false, + }, +}); - /** - * The kind of module system/transport wrapper to use for the modules bundled - * in the package. - */ - moduleFormat: 'haste', +function Packager(options) { + var opts = this._opts = validateOpts(options); - /** - * An ordered list of module names that should be considered as dependencies - * of every module in the system. The list is ordered because every item in - * the list will have an implicit dependency on all items before it. - * - * (This ordering is necessary to build, for example, polyfills that build on - * each other) - */ - polyfillModuleNames: [], + opts.projectRoots.forEach(verifyRootExists); - nonPersistent: false, -}; + this._resolver = new DependencyResolver({ + projectRoots: opts.projectRoots, + blacklistRE: opts.blacklistRE, + polyfillModuleNames: opts.polyfillModuleNames, + dev: opts.dev, + nonPersistent: opts.nonPersistent, + moduleFormat: opts.moduleFormat + }); -function Packager(projectConfig) { - projectConfig.projectRoots.forEach(verifyRootExists); - - this._config = Object.create(DEFAULT_CONFIG); - for (var key in projectConfig) { - this._config[key] = projectConfig[key]; - } - - this._resolver = new DependencyResolver(this._config); - - this._transformer = new Transformer(projectConfig); + this._transformer = new Transformer({ + projectRoots: opts.projectRoots, + blacklistRE: opts.blacklistRE, + cacheVersion: opts.cacheVersion, + resetCache: opts.resetCache, + dev: opts.dev, + transformModulePath: opts.transformModulePath, + nonPersistent: opts.nonPersistent, + }); } Packager.prototype.kill = function() { @@ -92,7 +117,7 @@ Packager.prototype.package = function(main, runModule, sourceMapUrl) { Packager.prototype.invalidateFile = function(filePath) { this._transformer.invalidateFile(filePath); -} +}; Packager.prototype.getDependencies = function(main) { return this._resolver.getDependencies(main); @@ -103,7 +128,7 @@ Packager.prototype._transformModule = function(module) { return this._transformer.loadFileAndTransform( ['es6'], path.resolve(module.path), - this._config.transformer || {} + this._opts.transformer || {} ).then(function(transformed) { return _.extend( {}, diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 511ec8a3..690c7e06 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -6,8 +6,6 @@ jest.setMock('worker-farm', function(){ return function(){}; }) .dontMock('url') .dontMock('../'); - -var server = require('../'); var q = require('q'); describe('processRequest', function(){ @@ -45,17 +43,17 @@ describe('processRequest', function(){ beforeEach(function(){ Activity = require('../../Activity'); Packager = require('../../Packager'); - FileWatcher = require('../../FileWatcher') + FileWatcher = require('../../FileWatcher'); - Packager.prototype.package = function(main, runModule, sourceMapUrl) { + Packager.prototype.package = function() { return q({ - getSource: function(){ - return "this is the source" + getSource: function() { + return 'this is the source'; }, getSourceMap: function(){ - return "this is the source map" - } - }) + return 'this is the source map'; + }, + }); }; FileWatcher.prototype.on = watcherFunc; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 26929ebb..611d703e 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -1,26 +1,56 @@ var url = require('url'); var path = require('path'); -var FileWatcher = require('../FileWatcher') +var declareOpts = require('../lib/declareOpts'); +var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); var q = require('q'); module.exports = Server; +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, + }, + dev: { + type: 'boolean', + default: true, + }, + transformModulePath: { + type:'string', + required: true, + }, + nonPersistent: { + type: 'boolean', + default: false, + }, +}); + function Server(options) { - this._projectRoots = options.projectRoots; + var opts = validateOpts(options); + this._projectRoots = opts.projectRoots; this._packages = Object.create(null); - this._packager = new Packager({ - projectRoots: options.projectRoots, - blacklistRE: options.blacklistRE, - polyfillModuleNames: options.polyfillModuleNames || [], - runtimeCode: options.runtimeCode, - cacheVersion: options.cacheVersion, - resetCache: options.resetCache, - dev: options.dev, - transformModulePath: options.transformModulePath, - nonPersistent: options.nonPersistent, - }); + this._packager = new Packager(opts); this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() @@ -35,10 +65,10 @@ Server.prototype._onFileChange = function(type, filepath, root) { this._packager.invalidateFile(absPath); // Make sure the file watcher event runs through the system before // we rebuild the packages. - setImmediate(this._rebuildPackages.bind(this, absPath)) + setImmediate(this._rebuildPackages.bind(this, absPath)); }; -Server.prototype._rebuildPackages = function(filepath) { +Server.prototype._rebuildPackages = function() { var buildPackage = this._buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { diff --git a/react-packager/src/lib/__mocks__/declareOpts.js b/react-packager/src/lib/__mocks__/declareOpts.js new file mode 100644 index 00000000..2f7ae1f6 --- /dev/null +++ b/react-packager/src/lib/__mocks__/declareOpts.js @@ -0,0 +1,10 @@ +module.exports = function(declared) { + return function(opts) { + for (var p in declared) { + if (opts[p] == null && declared[p].default != null){ + opts[p] = declared[p].default; + } + } + return opts; + }; +}; diff --git a/react-packager/src/lib/__tests__/declareOpts-test.js b/react-packager/src/lib/__tests__/declareOpts-test.js new file mode 100644 index 00000000..044e3a1c --- /dev/null +++ b/react-packager/src/lib/__tests__/declareOpts-test.js @@ -0,0 +1,82 @@ +jest.autoMockOff(); + +var declareOpts = require('../declareOpts'); + +describe('declareOpts', function() { + it('should declare and validate simple opts', function() { + var validate = declareOpts({ + name: { + required: true, + type: 'string', + }, + age: { + type: 'number', + default: 21, + } + }); + var opts = validate({ name: 'fooer' }); + + expect(opts).toEqual({ + name: 'fooer', + age: 21 + }); + }); + + it('should work with complex types', function() { + var validate = declareOpts({ + things: { + required: true, + type: 'array', + }, + stuff: { + type: 'object', + required: true, + } + }); + + var opts = validate({ things: [1, 2, 3], stuff: {hai: 1} }); + expect(opts).toEqual({ + things: [1,2,3], + stuff: {hai: 1}, + }); + }); + + it('should throw when a required option is not present', function() { + var validate = declareOpts({ + foo: { + required: true, + type: 'number', + } + }); + + expect(function() { + validate({}); + }).toThrow('Error validating module options: foo is required'); + }); + + it('should throw on invalid type', function() { + var validate = declareOpts({ + foo: { + required: true, + type: 'number' + } + }); + + expect(function() { + validate({foo: 'lol'}); + }).toThrow('Error validating module options: foo must be a number'); + }); + + it('should throw on extra options', function() { + var validate = declareOpts({ + foo: { + required: true, + type: 'number', + } + }); + + expect(function() { + validate({foo: 1, lol: 1}); + }).toThrow('Error validating module options: lol is not allowed'); + }); +}); diff --git a/react-packager/src/lib/declareOpts.js b/react-packager/src/lib/declareOpts.js new file mode 100644 index 00000000..2bac59f3 --- /dev/null +++ b/react-packager/src/lib/declareOpts.js @@ -0,0 +1,53 @@ +/** + * Declares, validates and defaults options. + * var validate = declareOpts({ + * foo: { + * type: 'bool', + * required: true, + * } + * }); + * + * var myOptions = validate(someOptions); + */ + +var Joi = require('joi'); + +module.exports = function(descriptor) { + var joiKeys = {}; + Object.keys(descriptor).forEach(function(prop) { + var record = descriptor[prop]; + if (record.type == null) { + throw new Error('Type is required'); + } + + if (record.type === 'function') { + record.type = 'func'; + } + + var propValidator = Joi[record.type](); + + if (record.required) { + propValidator = propValidator.required(); + } + + if (record.default) { + propValidator = propValidator.default(record.default); + } + + joiKeys[prop] = propValidator; + }); + + var schema = Joi.object().keys(joiKeys); + + return function(opts) { + var res = Joi.validate(opts, schema, { + abortEarly: true, + allowUnknown: false, + }); + + if (res.error) { + throw new Error('Error validating module options: ' + res.error.message); + } + return res.value; + }; +}; From ae0c45e82c8114d08b0f265b71f8d10aa125b5d3 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 25 Feb 2015 09:54:45 -0800 Subject: [PATCH 017/936] rebase --- react-packager/package.json | 8 ++++++-- transformer.js | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/react-packager/package.json b/react-packager/package.json index ad7b7602..0ac47c25 100644 --- a/react-packager/package.json +++ b/react-packager/package.json @@ -4,7 +4,11 @@ "description": "", "main": "index.js", "jest": { - "unmockedModulePathPatterns": ["source-map"], - "testPathIgnorePatterns": ["JSAppServer/node_modules"] + "unmockedModulePathPatterns": [ + "source-map" + ], + "testPathIgnorePatterns": [ + "JSAppServer/node_modules" + ] } } diff --git a/transformer.js b/transformer.js index df5e7143..ffcb80e2 100644 --- a/transformer.js +++ b/transformer.js @@ -16,18 +16,26 @@ var staticTypeSyntax = var visitorList = reactVisitors; -function transform(transformSets, srcTxt, options) { - options = options || {}; +function transform(transformSets, srcTxt) { + var options = { + es3: true, + sourceType: 'nonStrictModule' + }; // These tranforms mostly just erase type annotations and static typing // related statements, but they were conflicting with other tranforms. // Running them first solves that problem var staticTypeSyntaxResult = jstransform( staticTypeSyntax, - srcTxt + srcTxt, + options ); - return jstransform(visitorList, staticTypeSyntaxResult.code); + return jstransform( + visitorList, + staticTypeSyntaxResult.code, + options + ); } module.exports = function(data, callback) { @@ -35,8 +43,7 @@ module.exports = function(data, callback) { try { result = transform( data.transformSets, - data.sourceCode, - data.options + data.sourceCode ); } catch (e) { return callback(null, { From 611b8203d9bfd0644120c370e0fea43cf0f2b0fb Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 25 Feb 2015 12:36:51 -0800 Subject: [PATCH 018/936] Updates from Mon Feb 22 - [ReactNative] remove docs and website from fbobjc and improve oss_export | Spencer Ahrens - [ReactNative] Bring back ReactKit proj files | Spencer Ahrens - [React-Native] Update jstransform/esprima | Christoph Pojer --- react-packager/package.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/react-packager/package.json b/react-packager/package.json index 0ac47c25..ad7b7602 100644 --- a/react-packager/package.json +++ b/react-packager/package.json @@ -4,11 +4,7 @@ "description": "", "main": "index.js", "jest": { - "unmockedModulePathPatterns": [ - "source-map" - ], - "testPathIgnorePatterns": [ - "JSAppServer/node_modules" - ] + "unmockedModulePathPatterns": ["source-map"], + "testPathIgnorePatterns": ["JSAppServer/node_modules"] } } From 2d6e9ba48b5f90dade118292fff85c1b3ff3512b Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 25 Feb 2015 12:40:49 -0800 Subject: [PATCH 019/936] Updates from Tue Feb 24 - Only update Redbox when offscreen | Nick Lockwood - [react-packager] Cleanup option passing and validation | Amjad Masad - Unified bridge implementation between OSS and private React forks | Nick Lockwood - [react-packager][cleanup options 1/2] add npm installed joi validation library | Amjad Masad --- react-packager/package.json | 8 +- .../haste/DependencyGraph/index.js | 25 ++++- .../__tests__/HasteDependencyResolver-test.js | 70 +++++++++++++- .../src/DependencyResolver/haste/index.js | 43 +++++++-- react-packager/src/JSTransformer/Cache.js | 31 ++++-- react-packager/src/JSTransformer/index.js | 53 +++++++++-- react-packager/src/Packager/index.js | 95 ++++++++++++------- .../src/Server/__tests__/Server-test.js | 16 ++-- react-packager/src/Server/index.js | 60 +++++++++--- .../src/lib/__mocks__/declareOpts.js | 10 ++ .../src/lib/__tests__/declareOpts-test.js | 82 ++++++++++++++++ react-packager/src/lib/declareOpts.js | 53 +++++++++++ 12 files changed, 459 insertions(+), 87 deletions(-) create mode 100644 react-packager/src/lib/__mocks__/declareOpts.js create mode 100644 react-packager/src/lib/__tests__/declareOpts-test.js create mode 100644 react-packager/src/lib/declareOpts.js diff --git a/react-packager/package.json b/react-packager/package.json index ad7b7602..0ac47c25 100644 --- a/react-packager/package.json +++ b/react-packager/package.json @@ -4,7 +4,11 @@ "description": "", "main": "index.js", "jest": { - "unmockedModulePathPatterns": ["source-map"], - "testPathIgnorePatterns": ["JSAppServer/node_modules"] + "unmockedModulePathPatterns": [ + "source-map" + ], + "testPathIgnorePatterns": [ + "JSAppServer/node_modules" + ] } } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 9a939620..6a7d8bba 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -8,15 +8,35 @@ var path = require('path'); var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); var util = require('util'); +var declareOpts = require('../../../lib/declareOpts'); var readFile = q.nfbind(fs.readFile); var readDir = q.nfbind(fs.readdir); var lstat = q.nfbind(fs.lstat); var realpath = q.nfbind(fs.realpath); +var validateOpts = declareOpts({ + roots: { + type: 'array', + required: true, + }, + ignoreFilePath: { + type: 'function', + default: function(){} + }, + fileWatcher: { + type: 'object', + required: true, + }, +}); + function DependecyGraph(options) { - this._roots = options.roots; - this._ignoreFilePath = options.ignoreFilePath || function(){}; + var opts = validateOpts(options); + + this._roots = opts.roots; + this._ignoreFilePath = opts.ignoreFilePath; + this._fileWatcher = options.fileWatcher; + this._loaded = false; this._queue = this._roots.slice(); this._graph = Object.create(null); @@ -24,7 +44,6 @@ function DependecyGraph(options) { this._packagesById = Object.create(null); this._moduleById = Object.create(null); this._debugUpdateEvents = []; - this._fileWatcher = options.fileWatcher; // Kick off the search process to precompute the dependency graph. this._init(); diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 9b43f97e..d3c4a7d9 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -24,7 +24,8 @@ describe('HasteDependencyResolver', function() { var deps = [module]; var depResolver = new HasteDependencyResolver({ - projectRoot: '/root' + projectRoot: '/root', + dev: false, }); // Is there a better way? How can I mock the prototype instead? @@ -79,13 +80,75 @@ describe('HasteDependencyResolver', function() { }); }); + pit('should get dependencies with polyfills', function() { + var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var deps = [module]; + + var depResolver = new HasteDependencyResolver({ + projectRoot: '/root', + dev: true, + }); + + // Is there a better way? How can I mock the prototype instead? + var depGraph = depResolver._depGraph; + depGraph.getOrderedDependencies.mockImpl(function() { + return deps; + }); + depGraph.load.mockImpl(function() { + return q(); + }); + + return depResolver.getDependencies('/root/index.js') + .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' + ], + }, + module + ]); + }); + }); + pit('should pass in more polyfills', function() { var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; var deps = [module]; var depResolver = new HasteDependencyResolver({ projectRoot: '/root', - polyfillModuleNames: ['some module'] + polyfillModuleNames: ['some module'], + dev: false, }); // Is there a better way? How can I mock the prototype instead? @@ -155,7 +218,8 @@ describe('HasteDependencyResolver', function() { describe('wrapModule', function() { it('should ', function() { var depResolver = new HasteDependencyResolver({ - projectRoot: '/root' + projectRoot: '/root', + dev: false, }); var depGraph = depResolver._depGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 6e2cd6fc..dc497649 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -4,6 +4,7 @@ var path = require('path'); var FileWatcher = require('../../FileWatcher'); var DependencyGraph = require('./DependencyGraph'); var ModuleDescriptor = require('../ModuleDescriptor'); +var declareOpts = require('../../lib/declareOpts'); var DEFINE_MODULE_CODE = '__d(' + @@ -18,22 +19,50 @@ var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; -function HasteDependencyResolver(config) { - this._fileWatcher = config.nonPersistent +var validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + blacklistRE: { + type: 'object', // typeof regex is object + }, + polyfillModuleNames: { + type: 'array', + default: [], + }, + dev: { + type: 'boolean', + default: true, + }, + nonPersistent: { + type: 'boolean', + default: false, + }, + moduleFormat: { + type: 'string', + default: 'haste', + }, +}); + +function HasteDependencyResolver(options) { + var opts = validateOpts(options); + + this._fileWatcher = opts.nonPersistent ? FileWatcher.createDummyWatcher() - : new FileWatcher(config.projectRoots); + : new FileWatcher(opts.projectRoots); this._depGraph = new DependencyGraph({ - roots: config.projectRoots, + roots: opts.projectRoots, ignoreFilePath: function(filepath) { return filepath.indexOf('__tests__') !== -1 || - (config.blacklistRE && config.blacklistRE.test(filepath)); + (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, fileWatcher: this._fileWatcher }); this._polyfillModuleNames = [ - config.dev + opts.dev ? path.join(__dirname, 'polyfills/prelude_dev.js') : path.join(__dirname, 'polyfills/prelude.js'), path.join(__dirname, 'polyfills/require.js'), @@ -41,7 +70,7 @@ function HasteDependencyResolver(config) { path.join(__dirname, 'polyfills/console.js'), path.join(__dirname, 'polyfills/error-guard.js'), ].concat( - config.polyfillModuleNames || [] + opts.polyfillModuleNames || [] ); } diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index 577af696..f04ffe9f 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -4,19 +4,36 @@ var path = require('path'); var version = require('../../package.json').version; var tmpdir = require('os').tmpDir(); var pathUtils = require('../fb-path-utils'); +var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); var _ = require('underscore'); var q = require('q'); var Promise = q.Promise; +var validateOpts = declareOpts({ + resetCache: { + type: 'boolean', + default: false, + }, + cacheVersion: { + type: 'string', + default: '1.0', + }, + projectRoots: { + type: 'array', + required: true, + }, +}); module.exports = Cache; -function Cache(projectConfig) { - this._cacheFilePath = cacheFilePath(projectConfig); +function Cache(options) { + var opts = validateOpts(options); + + this._cacheFilePath = cacheFilePath(opts); var data; - if (!projectConfig.resetCache) { + if (!opts.resetCache) { data = loadCacheSync(this._cacheFilePath); } else { data = Object.create(null); @@ -63,7 +80,7 @@ Cache.prototype.invalidate = function(filepath){ if(this._has(filepath)) { delete this._data[filepath]; } -} +}; Cache.prototype.end = function() { return this._persistCache(); @@ -114,9 +131,9 @@ function loadCacheSync(cacheFilepath) { return ret; } -function cacheFilePath(projectConfig) { - var roots = projectConfig.projectRoots.join(',').split(path.sep).join('-'); - var cacheVersion = projectConfig.cacheVersion || '0'; +function cacheFilePath(options) { + var roots = options.projectRoots.join(',').split(path.sep).join('-'); + var cacheVersion = options.cacheVersion || '0'; return path.join( tmpdir, [ diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 7b01d961..ade206a7 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -1,28 +1,69 @@ 'use strict'; -var os = require('os'); var fs = require('fs'); var q = require('q'); var Cache = require('./Cache'); var _ = require('underscore'); var workerFarm = require('worker-farm'); +var declareOpts = require('../lib/declareOpts'); var readFile = q.nfbind(fs.readFile); module.exports = Transformer; Transformer.TransformError = TransformError; -function Transformer(projectConfig) { - this._cache = projectConfig.nonPersistent - ? new DummyCache() : new Cache(projectConfig); +var validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + blacklistRE: { + type: 'object', // typeof regex is object + }, + polyfillModuleNames: { + type: 'array', + default: [], + }, + cacheVersion: { + type: 'string', + default: '1.0', + }, + resetCache: { + type: 'boolean', + default: false, + }, + dev: { + type: 'boolean', + default: true, + }, + transformModulePath: { + type:'string', + required: true, + }, + nonPersistent: { + type: 'boolean', + default: false, + }, +}); - if (projectConfig.transformModulePath == null) { +function Transformer(options) { + var opts = validateOpts(options); + + this._cache = opts.nonPersistent + ? new DummyCache() + : new Cache({ + resetCache: options.resetCache, + cacheVersion: options.cacheVersion, + projectRoots: options.projectRoots, + }); + + if (options.transformModulePath == null) { this._failedToStart = q.Promise.reject(new Error('No transfrom module')); } else { this._workers = workerFarm( {autoStart: true}, - projectConfig.transformModulePath + options.transformModulePath ); } } diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 3ec4e378..ddcab6ee 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -10,44 +10,69 @@ var DependencyResolver = require('../DependencyResolver'); var _ = require('underscore'); var Package = require('./Package'); var Activity = require('../Activity'); +var declareOpts = require('../lib/declareOpts'); -var DEFAULT_CONFIG = { - /** - * RegExp used to ignore paths when scanning the filesystem to calculate the - * dependency graph. - */ - blacklistRE: null, +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, + }, + dev: { + type: 'boolean', + default: true, + }, + transformModulePath: { + type:'string', + required: true, + }, + nonPersistent: { + type: 'boolean', + default: false, + }, +}); - /** - * The kind of module system/transport wrapper to use for the modules bundled - * in the package. - */ - moduleFormat: 'haste', +function Packager(options) { + var opts = this._opts = validateOpts(options); - /** - * An ordered list of module names that should be considered as dependencies - * of every module in the system. The list is ordered because every item in - * the list will have an implicit dependency on all items before it. - * - * (This ordering is necessary to build, for example, polyfills that build on - * each other) - */ - polyfillModuleNames: [], + opts.projectRoots.forEach(verifyRootExists); - nonPersistent: false, -}; + this._resolver = new DependencyResolver({ + projectRoots: opts.projectRoots, + blacklistRE: opts.blacklistRE, + polyfillModuleNames: opts.polyfillModuleNames, + dev: opts.dev, + nonPersistent: opts.nonPersistent, + moduleFormat: opts.moduleFormat + }); -function Packager(projectConfig) { - projectConfig.projectRoots.forEach(verifyRootExists); - - this._config = Object.create(DEFAULT_CONFIG); - for (var key in projectConfig) { - this._config[key] = projectConfig[key]; - } - - this._resolver = new DependencyResolver(this._config); - - this._transformer = new Transformer(projectConfig); + this._transformer = new Transformer({ + projectRoots: opts.projectRoots, + blacklistRE: opts.blacklistRE, + cacheVersion: opts.cacheVersion, + resetCache: opts.resetCache, + dev: opts.dev, + transformModulePath: opts.transformModulePath, + nonPersistent: opts.nonPersistent, + }); } Packager.prototype.kill = function() { @@ -92,7 +117,7 @@ Packager.prototype.package = function(main, runModule, sourceMapUrl) { Packager.prototype.invalidateFile = function(filePath) { this._transformer.invalidateFile(filePath); -} +}; Packager.prototype.getDependencies = function(main) { return this._resolver.getDependencies(main); @@ -103,7 +128,7 @@ Packager.prototype._transformModule = function(module) { return this._transformer.loadFileAndTransform( ['es6'], path.resolve(module.path), - this._config.transformer || {} + this._opts.transformer || {} ).then(function(transformed) { return _.extend( {}, diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 511ec8a3..690c7e06 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -6,8 +6,6 @@ jest.setMock('worker-farm', function(){ return function(){}; }) .dontMock('url') .dontMock('../'); - -var server = require('../'); var q = require('q'); describe('processRequest', function(){ @@ -45,17 +43,17 @@ describe('processRequest', function(){ beforeEach(function(){ Activity = require('../../Activity'); Packager = require('../../Packager'); - FileWatcher = require('../../FileWatcher') + FileWatcher = require('../../FileWatcher'); - Packager.prototype.package = function(main, runModule, sourceMapUrl) { + Packager.prototype.package = function() { return q({ - getSource: function(){ - return "this is the source" + getSource: function() { + return 'this is the source'; }, getSourceMap: function(){ - return "this is the source map" - } - }) + return 'this is the source map'; + }, + }); }; FileWatcher.prototype.on = watcherFunc; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 26929ebb..611d703e 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -1,26 +1,56 @@ var url = require('url'); var path = require('path'); -var FileWatcher = require('../FileWatcher') +var declareOpts = require('../lib/declareOpts'); +var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); var q = require('q'); module.exports = Server; +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, + }, + dev: { + type: 'boolean', + default: true, + }, + transformModulePath: { + type:'string', + required: true, + }, + nonPersistent: { + type: 'boolean', + default: false, + }, +}); + function Server(options) { - this._projectRoots = options.projectRoots; + var opts = validateOpts(options); + this._projectRoots = opts.projectRoots; this._packages = Object.create(null); - this._packager = new Packager({ - projectRoots: options.projectRoots, - blacklistRE: options.blacklistRE, - polyfillModuleNames: options.polyfillModuleNames || [], - runtimeCode: options.runtimeCode, - cacheVersion: options.cacheVersion, - resetCache: options.resetCache, - dev: options.dev, - transformModulePath: options.transformModulePath, - nonPersistent: options.nonPersistent, - }); + this._packager = new Packager(opts); this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() @@ -35,10 +65,10 @@ Server.prototype._onFileChange = function(type, filepath, root) { this._packager.invalidateFile(absPath); // Make sure the file watcher event runs through the system before // we rebuild the packages. - setImmediate(this._rebuildPackages.bind(this, absPath)) + setImmediate(this._rebuildPackages.bind(this, absPath)); }; -Server.prototype._rebuildPackages = function(filepath) { +Server.prototype._rebuildPackages = function() { var buildPackage = this._buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { diff --git a/react-packager/src/lib/__mocks__/declareOpts.js b/react-packager/src/lib/__mocks__/declareOpts.js new file mode 100644 index 00000000..2f7ae1f6 --- /dev/null +++ b/react-packager/src/lib/__mocks__/declareOpts.js @@ -0,0 +1,10 @@ +module.exports = function(declared) { + return function(opts) { + for (var p in declared) { + if (opts[p] == null && declared[p].default != null){ + opts[p] = declared[p].default; + } + } + return opts; + }; +}; diff --git a/react-packager/src/lib/__tests__/declareOpts-test.js b/react-packager/src/lib/__tests__/declareOpts-test.js new file mode 100644 index 00000000..044e3a1c --- /dev/null +++ b/react-packager/src/lib/__tests__/declareOpts-test.js @@ -0,0 +1,82 @@ +jest.autoMockOff(); + +var declareOpts = require('../declareOpts'); + +describe('declareOpts', function() { + it('should declare and validate simple opts', function() { + var validate = declareOpts({ + name: { + required: true, + type: 'string', + }, + age: { + type: 'number', + default: 21, + } + }); + var opts = validate({ name: 'fooer' }); + + expect(opts).toEqual({ + name: 'fooer', + age: 21 + }); + }); + + it('should work with complex types', function() { + var validate = declareOpts({ + things: { + required: true, + type: 'array', + }, + stuff: { + type: 'object', + required: true, + } + }); + + var opts = validate({ things: [1, 2, 3], stuff: {hai: 1} }); + expect(opts).toEqual({ + things: [1,2,3], + stuff: {hai: 1}, + }); + }); + + it('should throw when a required option is not present', function() { + var validate = declareOpts({ + foo: { + required: true, + type: 'number', + } + }); + + expect(function() { + validate({}); + }).toThrow('Error validating module options: foo is required'); + }); + + it('should throw on invalid type', function() { + var validate = declareOpts({ + foo: { + required: true, + type: 'number' + } + }); + + expect(function() { + validate({foo: 'lol'}); + }).toThrow('Error validating module options: foo must be a number'); + }); + + it('should throw on extra options', function() { + var validate = declareOpts({ + foo: { + required: true, + type: 'number', + } + }); + + expect(function() { + validate({foo: 1, lol: 1}); + }).toThrow('Error validating module options: lol is not allowed'); + }); +}); diff --git a/react-packager/src/lib/declareOpts.js b/react-packager/src/lib/declareOpts.js new file mode 100644 index 00000000..2bac59f3 --- /dev/null +++ b/react-packager/src/lib/declareOpts.js @@ -0,0 +1,53 @@ +/** + * Declares, validates and defaults options. + * var validate = declareOpts({ + * foo: { + * type: 'bool', + * required: true, + * } + * }); + * + * var myOptions = validate(someOptions); + */ + +var Joi = require('joi'); + +module.exports = function(descriptor) { + var joiKeys = {}; + Object.keys(descriptor).forEach(function(prop) { + var record = descriptor[prop]; + if (record.type == null) { + throw new Error('Type is required'); + } + + if (record.type === 'function') { + record.type = 'func'; + } + + var propValidator = Joi[record.type](); + + if (record.required) { + propValidator = propValidator.required(); + } + + if (record.default) { + propValidator = propValidator.default(record.default); + } + + joiKeys[prop] = propValidator; + }); + + var schema = Joi.object().keys(joiKeys); + + return function(opts) { + var res = Joi.validate(opts, schema, { + abortEarly: true, + allowUnknown: false, + }); + + if (res.error) { + throw new Error('Error validating module options: ' + res.error.message); + } + return res.value; + }; +}; From 4e8a8e5a0e1a219f7de294afd7b28f0649ef35b7 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 25 Feb 2015 12:57:33 -0800 Subject: [PATCH 020/936] [react-packager] Fix jest tests --- react-packager/, | 0 react-packager/package.json | 14 -------------- react-packager/src/JSTransformer/Cache.js | 2 +- 3 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 react-packager/, delete mode 100644 react-packager/package.json diff --git a/react-packager/, b/react-packager/, deleted file mode 100644 index e69de29b..00000000 diff --git a/react-packager/package.json b/react-packager/package.json deleted file mode 100644 index 0ac47c25..00000000 --- a/react-packager/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "react-packager", - "version": "0.1.0", - "description": "", - "main": "index.js", - "jest": { - "unmockedModulePathPatterns": [ - "source-map" - ], - "testPathIgnorePatterns": [ - "JSAppServer/node_modules" - ] - } -} diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index f04ffe9f..b98bedd9 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -1,7 +1,7 @@ 'use strict'; var path = require('path'); -var version = require('../../package.json').version; +var version = require('../../../../package.json').version; var tmpdir = require('os').tmpDir(); var pathUtils = require('../fb-path-utils'); var declareOpts = require('../lib/declareOpts'); From 3d65001426083a708f1b0666276e094f635a0bc8 Mon Sep 17 00:00:00 2001 From: Chengyin Liu Date: Wed, 25 Feb 2015 15:40:22 -0800 Subject: [PATCH 021/936] [react-packager] fix a typo s/pacakge/package Summary: Closes https://github.com/facebook/react-native/pull/83 Github Author: Chengyin Liu Test Plan: Imported from GitHub, without a `Test Plan:` line. --- .../src/DependencyResolver/haste/DependencyGraph/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 6a7d8bba..f6af54d0 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -147,7 +147,7 @@ DependecyGraph.prototype.resolveDependency = function( dep = this._graph[modulePath]; if (dep == null) { throw new Error( - 'Cannot find package main file for pacakge: ' + packageJson._root + 'Cannot find package main file for package: ' + packageJson._root ); } return dep; From d740d17d426f27b4bd94c117c47d5166f51d8cbd Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 25 Feb 2015 20:29:14 -0800 Subject: [PATCH 022/936] [react-packager] Fix lint errors --- react-packager/.jshintrc | 86 ------------------- .../src/Activity/__tests__/Activity-test.js | 2 + react-packager/src/Activity/index.js | 2 + .../DependencyResolver/ModuleDescriptor.js | 4 +- .../__tests__/DependencyGraph-test.js | 80 +++++++++++++---- .../haste/DependencyGraph/docblock.js | 10 ++- .../haste/DependencyGraph/example.js | 25 ------ .../haste/DependencyGraph/index.js | 7 +- .../__tests__/HasteDependencyResolver-test.js | 13 ++- .../src/DependencyResolver/haste/index.js | 24 +++--- .../haste/polyfills/console.js | 10 ++- .../haste/polyfills/error-guard.js | 2 +- .../haste/polyfills/polyfills.js | 2 +- .../haste/polyfills/prelude.js | 1 + .../haste/polyfills/prelude_dev.js | 1 + .../haste/polyfills/require.js | 1 + .../src/DependencyResolver/index.js | 2 + .../src/DependencyResolver/node/index.js | 13 +-- .../FileWatcher/__tests__/FileWatcher-test.js | 10 ++- react-packager/src/JSTransformer/Cache.js | 14 +-- .../src/JSTransformer/__tests__/Cache-test.js | 1 - react-packager/src/JSTransformer/index.js | 7 +- react-packager/src/Packager/Package.js | 3 +- .../src/Packager/__mocks__/source-map.js | 5 -- .../src/Packager/__tests__/Package-test.js | 16 ++-- react-packager/src/Packager/base64-vlq.js | 9 +- .../src/Server/__tests__/Server-test.js | 78 +++++++++-------- react-packager/src/Server/index.js | 4 +- react-packager/src/fb-path-utils/index.js | 14 --- .../src/lib/__mocks__/declareOpts.js | 2 + .../src/lib/__tests__/declareOpts-test.js | 2 + react-packager/src/lib/declareOpts.js | 2 + 32 files changed, 198 insertions(+), 254 deletions(-) delete mode 100644 react-packager/.jshintrc delete mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/example.js delete mode 100644 react-packager/src/Packager/__mocks__/source-map.js delete mode 100644 react-packager/src/fb-path-utils/index.js diff --git a/react-packager/.jshintrc b/react-packager/.jshintrc deleted file mode 100644 index 7a3f79a7..00000000 --- a/react-packager/.jshintrc +++ /dev/null @@ -1,86 +0,0 @@ -{ - "-W093": true, - "asi": false, - "bitwise": true, - "boss": false, - "browser": false, - "camelcase": true, - "couch": false, - "curly": true, - "debug": false, - "devel": true, - "dojo": false, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "evil": false, - "expr": true, - "forin": false, - "freeze": true, - "funcscope": true, - "gcl": false, - "globals": { - "Promise": true, - "React": true, - "XMLHttpRequest": true, - "document": true, - "location": true, - "window": true - }, - "globalstrict": true, - "immed": false, - "indent": 2, - "iterator": false, - "jquery": false, - "lastsemic": false, - "latedef": false, - "laxbreak": true, - "laxcomma": false, - "loopfunc": false, - "maxcomplexity": false, - "maxdepth": false, - "maxerr": 50, - "maxlen": 80, - "maxparams": false, - "maxstatements": false, - "mootools": false, - "moz": false, - "multistr": false, - "newcap": true, - "noarg": true, - "node": true, - "noempty": false, - "nonbsp": true, - "nonew": true, - "nonstandard": false, - "notypeof": false, - "noyield": false, - "phantom": false, - "plusplus": false, - "predef": [ - "afterEach", - "beforeEach", - "describe", - "expect", - "it", - "jest", - "pit" - ], - "proto": false, - "prototypejs": false, - "quotmark": true, - "rhino": false, - "scripturl": false, - "shadow": false, - "smarttabs": false, - "strict": false, - "sub": false, - "supernew": false, - "trailing": true, - "undef": true, - "unused": true, - "validthis": false, - "worker": false, - "wsh": false, - "yui": false -} diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index 7a2bdf48..bd0265f9 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -1,3 +1,5 @@ +'use strict'; + jest.autoMockOff(); describe('Activity', function() { diff --git a/react-packager/src/Activity/index.js b/react-packager/src/Activity/index.js index a60f87b0..611ccb0b 100644 --- a/react-packager/src/Activity/index.js +++ b/react-packager/src/Activity/index.js @@ -1,3 +1,5 @@ +'use strict'; + var COLLECTION_PERIOD = 1000; var _endedEvents = Object.create(null); diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index 0898767a..f1a30545 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -1,3 +1,5 @@ +'use strict'; + function ModuleDescriptor(fields) { if (!fields.id) { throw new Error('Missing required fields id'); @@ -28,7 +30,7 @@ ModuleDescriptor.prototype.toJSON = function() { id: this.id, path: this.path, dependencies: this.dependencies - } + }; }; module.exports = ModuleDescriptor; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index fe8a18b6..1c268c6b 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -9,8 +9,6 @@ jest .dontMock('../docblock') .setMock('../../../ModuleDescriptor', function(data) {return data;}); -var q = require('q'); - describe('DependencyGraph', function() { var DependencyGraph; var fileWatcher; @@ -46,7 +44,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -75,7 +76,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -105,7 +109,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -135,7 +142,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -175,7 +185,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) .toEqual([ @@ -216,7 +229,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -245,7 +261,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -280,7 +299,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -320,7 +342,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -360,7 +385,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -386,7 +414,6 @@ describe('DependencyGraph', function() { }); describe('file watch updating', function() { - var fileWatcher; var triggerFileChange; beforeEach(function() { @@ -428,7 +455,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); @@ -476,7 +506,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); @@ -524,7 +557,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); @@ -571,7 +607,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { filesystem.root['bar.js'] = [ '/**', @@ -679,7 +718,7 @@ describe('DependencyGraph', function() { pit('should ignore directory updates', function() { var root = '/root'; - var filesystem = fs.__setMockFilesystem({ + fs.__setMockFilesystem({ 'root': { 'index.js': [ '/**', @@ -703,7 +742,10 @@ describe('DependencyGraph', function() { } } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js index 52cac03b..c2b6ac98 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js @@ -14,6 +14,7 @@ * limitations under the License. */ +'use strict'; var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/; @@ -35,7 +36,8 @@ var commentStartRe = /^\/\*\*?/; var commentEndRe = /\*\/$/; var wsRe = /[\t ]+/g; var stringStartRe = /(\r?\n|^) *\*/g; -var multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g; +var multilineRe = + /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g; var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g; /** @@ -51,15 +53,15 @@ function parse(docblock) { // Normalize multi-line directives var prev = ''; - while (prev != docblock) { + while (prev !== docblock) { prev = docblock; - docblock = docblock.replace(multilineRe, "\n$1 $2\n"); + docblock = docblock.replace(multilineRe, '\n$1 $2\n'); } docblock = docblock.trim(); var result = []; var match; - while (match = propertyRe.exec(docblock)) { + while ((match = propertyRe.exec(docblock))) { result.push([match[1], match[2]]); } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js deleted file mode 100644 index 02e6c592..00000000 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js +++ /dev/null @@ -1,25 +0,0 @@ -var path = require('path'); -var DependecyGraph = require('./'); - -var example_project = path.resolve(__dirname, '../../../../example_project'); -var watcher = new (require('../../../FileWatcher'))({projectRoot: example_project}); -var graph = new DependecyGraph({ - fileWatcher: watcher, - root: example_project -}); - -graph.load().then(function() { - var index = path.join(example_project, 'index.js'); - console.log(graph.getOrderedDependencies(index)); -}).done(); - -watcher.getWatcher().then(function(watcher) { - watcher.on('all', function() { - setImmediate(function() { - graph.load().then(function() { - var index = path.join(example_project, 'index.js'); - console.log(graph.getOrderedDependencies(index)); - }); - }) - }); -}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index f6af54d0..ce631856 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -139,7 +139,7 @@ DependecyGraph.prototype.resolveDependency = function( depModuleId, fromModule.id ); - return; + return null; } var main = packageJson.main || 'index'; @@ -223,7 +223,7 @@ DependecyGraph.prototype._search = function() { .then(function(filePaths) { filePaths = filePaths.filter(function(filePath) { if (filePath == null) { - return false + return false; } return !self._ignoreFilePath(filePath); @@ -454,7 +454,8 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) { return filePath; } - for (var i = 0, root; root = this._roots[i]; i++) { + for (var i = 0; i < this._roots.length; i++) { + var root = this._roots[i]; var absPath = path.join(root, filePath); if (this._graph[absPath]) { return absPath; diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index d3c4a7d9..9704c5b5 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -1,3 +1,4 @@ +'use strict'; jest.dontMock('../') .dontMock('q') @@ -7,7 +8,6 @@ var q = require('q'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; - var DependencyGraph; beforeEach(function() { // For the polyfillDeps @@ -15,7 +15,6 @@ describe('HasteDependencyResolver', function() { return b; }); HasteDependencyResolver = require('../'); - DependencyGraph = require('../DependencyGraph'); }); describe('getDependencies', function() { @@ -223,7 +222,7 @@ describe('HasteDependencyResolver', function() { }); var depGraph = depResolver._depGraph; - var dependencies = ['x', 'y', 'z'] + var dependencies = ['x', 'y', 'z']; var code = [ 'require("x")', 'require("y")', @@ -248,10 +247,10 @@ describe('HasteDependencyResolver', function() { }, code); expect(processedCode).toEqual([ - "__d('test module',[\"changed\",\"y\"],function(global," + - " require, requireDynamic, requireLazy, module, exports) {" + - " require('changed')", - "require('y')", + '__d(\'test module\',["changed","y"],function(global,' + + ' require, requireDynamic, requireLazy, module, exports) {' + + ' require(\'changed\')', + 'require(\'y\')', 'require("z")});', ].join('\n')); }); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index dc497649..9cb0661a 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -6,14 +6,15 @@ var DependencyGraph = require('./DependencyGraph'); var ModuleDescriptor = require('../ModuleDescriptor'); var declareOpts = require('../../lib/declareOpts'); -var DEFINE_MODULE_CODE = - '__d(' + - '\'_moduleName_\',' + - '_deps_,' + - 'function(global, require, requireDynamic, requireLazy, module, exports) {'+ - ' _code_' + - '}' + - ');'; +var DEFINE_MODULE_CODE = [ + '__d(', + '\'_moduleName_\',', + '_deps_,', + 'function(global, require, requireDynamic, requireLazy, module, exports) {', + ' _code_', + '}', + ');', +].join(''); var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; @@ -116,7 +117,6 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { return code; } - var depGraph = this._depGraph; var resolvedDeps = Object.create(null); var resolvedDepsArr = []; @@ -131,9 +131,9 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { var relativizedCode = code.replace(REL_REQUIRE_STMT, function(codeMatch, depName) { - var dep = resolvedDeps[depName]; - if (dep != null) { - return 'require(\'' + dep + '\')'; + var depId = resolvedDeps[depName]; + if (depId != null) { + return 'require(\'' + depId + '\')'; } else { return codeMatch; } diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index 4c9ddce1..bb83822d 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -20,7 +20,9 @@ * @polyfill */ +/*eslint global-strict:0*/ (function(global) { + 'use strict'; var OBJECT_COLUMN_NAME = '(index)'; @@ -45,7 +47,7 @@ if (typeof arg.toString === 'function') { try { return arg.toString(); - } catch (e) { + } catch (E) { return 'unknown'; } } @@ -53,7 +55,7 @@ } }).join(', '); global.nativeLoggingHook(str); - }; + } var repeat = function(element, n) { return Array.apply(null, Array(n)).map(function() { return element; }); @@ -120,7 +122,7 @@ // logged string, which would shift the header and screw up // the table global.nativeLoggingHook('\n' + table.join('\n')); - }; + } global.console = { error: doNativeLog, @@ -130,7 +132,7 @@ table: consoleTablePolyfill }; - }; + } if (typeof module !== 'undefined') { module.exports = setupConsole; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js index 687a4a19..745d650e 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js @@ -39,7 +39,7 @@ return ErrorUtils._inGuard; }, guard: function(fun, name, context) { - if (typeof fun !== "function") { + if (typeof fun !== 'function') { console.warn('A function must be passed to ErrorUtils.guard, got ', fun); return null; } diff --git a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js index 2fd32246..75f74279 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js @@ -22,7 +22,7 @@ // WARNING: This is an optimized version that fails on hasOwnProperty checks // and non objects. It's not spec-compliant. It's a perf optimization. - +/* eslint global-strict:0 */ Object.assign = function(target, sources) { if (__DEV__) { if (target == null) { diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude.js b/react-packager/src/DependencyResolver/haste/polyfills/prelude.js index 95c66983..9f4db44e 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/prelude.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/prelude.js @@ -1 +1,2 @@ +/* eslint global-strict:0 */ __DEV__ = false; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js b/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js index a5ca53b7..26b26a07 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js @@ -1 +1,2 @@ +/* eslint global-strict:0 */ __DEV__ = true; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/haste/polyfills/require.js index 3b5d6d87..e7fdde25 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/require.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -1,3 +1,4 @@ +/* eslint global-strict:0,eqeqeq:0,no-bitwise:0,no-undef:0 */ (function(global) { // avoid redefining require() diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index 79eb48c1..f42ecb8a 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -1,3 +1,5 @@ +'use strict'; + var HasteDependencyResolver = require('./haste'); var NodeDependencyResolver = require('./node'); diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js index 0d3b807e..da03cc7e 100644 --- a/react-packager/src/DependencyResolver/node/index.js +++ b/react-packager/src/DependencyResolver/node/index.js @@ -1,17 +1,12 @@ +'use strict'; + var Promise = require('q').Promise; var ModuleDescriptor = require('../ModuleDescriptor'); var mdeps = require('module-deps'); var path = require('path'); -var fs = require('fs'); -// var REQUIRE_RUNTIME = fs.readFileSync( -// path.join(__dirname, 'require.js') -// ).toString(); - -exports.getRuntimeCode = function() { - return REQUIRE_RUNTIME; -}; +exports.getRuntimeCode = function() {}; exports.wrapModule = function(id, source) { return Promise.resolve( @@ -21,7 +16,7 @@ exports.wrapModule = function(id, source) { }; exports.getDependencies = function(root, fileEntryPath) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { fileEntryPath = path.join(process.cwd(), root, fileEntryPath); var md = mdeps(); diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index 8baae9e1..fc8a7a41 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -1,8 +1,12 @@ 'use strict'; -jest.dontMock('../') - .dontMock('q') - .setMock('child_process', { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman') } }); +jest + .dontMock('../') + .dontMock('q') + .setMock( + 'child_process', + { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman'); } } + ); describe('FileWatcher', function() { var FileWatcher; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index b98bedd9..f43418e3 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -3,7 +3,7 @@ var path = require('path'); var version = require('../../../../package.json').version; var tmpdir = require('os').tmpDir(); -var pathUtils = require('../fb-path-utils'); +var isAbsolutePath = require('absolute-path'); var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); var _ = require('underscore'); @@ -48,7 +48,7 @@ function Cache(options) { } Cache.prototype.get = function(filepath, loaderCb) { - if (!pathUtils.isAbsolutePath(filepath)) { + if (!isAbsolutePath(filepath)) { throw new Error('Use absolute paths'); } @@ -62,7 +62,7 @@ Cache.prototype.get = function(filepath, loaderCb) { }; Cache.prototype._set = function(filepath, loaderPromise) { - return this._data[filepath] = loaderPromise.then(function(data) { + this._data[filepath] = loaderPromise.then(function(data) { return [ data, q.nfbind(fs.stat)(filepath) @@ -74,10 +74,12 @@ Cache.prototype._set = function(filepath, loaderPromise) { mtime: stat.mtime.getTime(), }; }.bind(this)); + + return this._data[filepath]; }; Cache.prototype.invalidate = function(filepath){ - if(this._has(filepath)) { + if (this._has(filepath)) { delete this._data[filepath]; } }; @@ -94,7 +96,7 @@ Cache.prototype._persistCache = function() { var data = this._data; var cacheFilepath = this._cacheFilePath; - return this._persisting = q.all(_.values(data)) + this._persisting = q.all(_.values(data)) .then(function(values) { var json = Object.create(null); Object.keys(data).forEach(function(key, i) { @@ -106,6 +108,8 @@ Cache.prototype._persistCache = function() { this._persisting = null; return true; }.bind(this)); + + return this._persisting; }; function loadCacheSync(cacheFilepath) { diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index c77c6384..232d6ff4 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -5,7 +5,6 @@ jest .dontMock('path') .dontMock('q') .dontMock('absolute-path') - .dontMock('../../fb-path-utils') .dontMock('../Cache'); var q = require('q'); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index ade206a7..4f98d588 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -7,6 +7,7 @@ var Cache = require('./Cache'); var _ = require('underscore'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); +var util = require('util'); var readFile = q.nfbind(fs.readFile); @@ -75,9 +76,7 @@ Transformer.prototype.kill = function() { Transformer.prototype.invalidateFile = function(filePath) { this._cache.invalidate(filePath); - //TODO: We can read the file and put it into the cache right here - // This would simplify some caching logic as we can be sure that the cache is up to date -} +}; Transformer.prototype.loadFileAndTransform = function( transformSets, @@ -116,7 +115,7 @@ Transformer.prototype.loadFileAndTransform = function( }; function TransformError() {} -TransformError.__proto__ = SyntaxError.prototype; +util.inherits(TransformError, SyntaxError); function formatEsprimaError(err, filename, source) { if (!(err.lineNumber && err.column)) { diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 787684bc..a4080d3b 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -1,7 +1,6 @@ 'use strict'; var _ = require('underscore'); -var SourceMapGenerator = require('source-map').SourceMapGenerator; var base64VLQ = require('./base64-vlq'); module.exports = Package; @@ -102,7 +101,7 @@ Package.prototype._getMappings = function() { mappings += ';'; } } - if (i != modules.length - 1) { + if (i !== modules.length - 1) { mappings += ';'; } } diff --git a/react-packager/src/Packager/__mocks__/source-map.js b/react-packager/src/Packager/__mocks__/source-map.js deleted file mode 100644 index 08c127f6..00000000 --- a/react-packager/src/Packager/__mocks__/source-map.js +++ /dev/null @@ -1,5 +0,0 @@ -var SourceMapGenerator = jest.genMockFn(); -SourceMapGenerator.prototype.addMapping = jest.genMockFn(); -SourceMapGenerator.prototype.setSourceContent = jest.genMockFn(); -SourceMapGenerator.prototype.toJSON = jest.genMockFn(); -exports.SourceMapGenerator = SourceMapGenerator; diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index d18bb4d6..d269eb57 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -50,13 +50,13 @@ describe('Package', function() { describe('sourcemap package', function() { it('should create sourcemap', function() { - var ppackage = new Package('test_url'); - ppackage.addModule('transformed foo;\n', 'source foo', 'foo path'); - ppackage.addModule('transformed bar;\n', 'source bar', 'bar path'); - ppackage.setMainModuleId('foo'); - ppackage.finalize({runMainModule: true}); - var s = ppackage.getSourceMap(); - expect(s).toEqual(genSourceMap(ppackage._modules)); + var p = new Package('test_url'); + p.addModule('transformed foo;\n', 'source foo', 'foo path'); + p.addModule('transformed bar;\n', 'source bar', 'bar path'); + p.setMainModuleId('foo'); + p.finalize({runMainModule: true}); + var s = p.getSourceMap(); + expect(s).toEqual(genSourceMap(p._modules)); }); }); }); @@ -92,4 +92,4 @@ describe('Package', function() { ); } return sourceMapGen.toJSON(); -}; + } diff --git a/react-packager/src/Packager/base64-vlq.js b/react-packager/src/Packager/base64-vlq.js index 91d490b7..4483a507 100644 --- a/react-packager/src/Packager/base64-vlq.js +++ b/react-packager/src/Packager/base64-vlq.js @@ -35,9 +35,11 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/*eslint no-bitwise:0,quotes:0,global-strict:0*/ + var charToIntMap = {}; var intToCharMap = {}; - + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' .split('') .forEach(function (ch, index) { @@ -55,7 +57,7 @@ base64.encode = function base64_encode(aNumber) { } throw new TypeError("Must be between 0 and 63: " + aNumber); }; - + /** * Decode a single base 64 digit to an integer. */ @@ -65,7 +67,7 @@ base64.decode = function base64_decode(aChar) { } throw new TypeError("Not a valid base 64 digit: " + aChar); }; - + // A single base 64 digit can contain 6 bits of data. For the base 64 variable @@ -165,4 +167,3 @@ exports.decode = function base64VLQ_decode(aStr, aOutParam) { aOutParam.value = fromVLQSigned(result); aOutParam.rest = aStr.slice(i); }; - diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 690c7e06..a9951f60 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -1,4 +1,6 @@ -jest.setMock('worker-farm', function(){ return function(){}; }) +'use strict'; + +jest.setMock('worker-farm', function() { return function() {}; }) .dontMock('q') .dontMock('os') .dontMock('errno/custom') @@ -8,9 +10,8 @@ jest.setMock('worker-farm', function(){ return function(){}; }) var q = require('q'); -describe('processRequest', function(){ +describe('processRequest', function() { var server; - var Activity; var Packager; var FileWatcher; @@ -21,16 +22,16 @@ describe('processRequest', function(){ polyfillModuleNames: null }; - var makeRequest = function(requestHandler, requrl){ + var makeRequest = function(requestHandler, requrl) { var deferred = q.defer(); requestHandler({ url: requrl },{ - end: function(res){ + end: function(res) { deferred.resolve(res); } },{ - next: function(){} + next: function() {} } ); return deferred.promise; @@ -40,8 +41,7 @@ describe('processRequest', function(){ var watcherFunc = jest.genMockFunction(); var requestHandler; - beforeEach(function(){ - Activity = require('../../Activity'); + beforeEach(function() { Packager = require('../../Packager'); FileWatcher = require('../../FileWatcher'); @@ -50,7 +50,7 @@ describe('processRequest', function(){ getSource: function() { return 'this is the source'; }, - getSourceMap: function(){ + getSourceMap: function() { return 'this is the source map'; }, }); @@ -65,26 +65,32 @@ describe('processRequest', function(){ requestHandler = server.processRequest.bind(server); }); - pit('returns JS bundle source on request of *.bundle',function(){ - result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); - return result.then(function(response){ - expect(response).toEqual("this is the source"); + pit('returns JS bundle source on request of *.bundle',function() { + return makeRequest( + requestHandler, + 'mybundle.includeRequire.runModule.bundle' + ).then(function(response) { + expect(response).toEqual('this is the source'); }); }); - pit('returns sourcemap on request of *.map', function(){ - result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle.map'); - return result.then(function(response){ - expect(response).toEqual('"this is the source map"'); + pit('returns sourcemap on request of *.map', function() { + makeRequest( + requestHandler, + 'mybundle.includeRequire.runModule.bundle.map' + ).then(function(response) { + expect(response).toEqual('this is the source map'); }); }); - pit('watches all files in projectRoot', function(){ - result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); - return result.then(function(response){ + pit('watches all files in projectRoot', function() { + makeRequest( + requestHandler, + 'mybundle.includeRequire.runModule.bundle' + ).then(function(response) { expect(watcherFunc.mock.calls[0][0]).toEqual('all'); expect(watcherFunc.mock.calls[0][1]).not.toBe(null); - }) + }); }); @@ -101,8 +107,10 @@ describe('processRequest', function(){ }); pit('invalides files in package when file is updated', function() { - result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); - return result.then(function(response){ + makeRequest( + requestHandler, + 'mybundle.includeRequire.runModule.bundle' + ).then(function(response) { var 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'); @@ -114,41 +122,41 @@ describe('processRequest', function(){ packageFunc .mockReturnValueOnce( q({ - getSource: function(){ - return "this is the first source" + getSource: function() { + return 'this is the first source'; }, - getSourceMap: function(){}, + getSourceMap: function() {}, }) ) .mockReturnValue( q({ - getSource: function(){ - return "this is the rebuilt source" + getSource: function() { + return 'this is the rebuilt source'; }, - getSourceMap: function(){}, + getSourceMap: function() {}, }) ); Packager.prototype.package = packageFunc; var Server = require('../../Server'); - var server = new Server(options); + server = new Server(options); requestHandler = server.processRequest.bind(server); return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') - .then(function(response){ - expect(response).toEqual("this is the first source"); + .then(function(response) { + expect(response).toEqual('this is the first source'); expect(packageFunc.mock.calls.length).toBe(1); triggerFileChange('all','path/file.js', options.projectRoots[0]); jest.runAllTimers(); }) - .then(function(){ + .then(function() { expect(packageFunc.mock.calls.length).toBe(2); return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') - .then(function(response){ - expect(response).toEqual("this is the rebuilt source"); + .then(function(response) { + expect(response).toEqual('this is the rebuilt source'); }); }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 611d703e..1f5b7ff1 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -1,3 +1,5 @@ +'use strict'; + var url = require('url'); var path = require('path'); var declareOpts = require('../lib/declareOpts'); @@ -154,7 +156,7 @@ Server.prototype.processRequest = function(req, res, next) { var startReqEventId = Activity.startEvent('request:' + req.url); var options = getOptionsFromPath(url.parse(req.url).pathname); - var building = this._packages[req.url] || this._buildPackage(options) + var building = this._packages[req.url] || this._buildPackage(options); this._packages[req.url] = building; building.then( function(p) { diff --git a/react-packager/src/fb-path-utils/index.js b/react-packager/src/fb-path-utils/index.js deleted file mode 100644 index b4a1cb96..00000000 --- a/react-packager/src/fb-path-utils/index.js +++ /dev/null @@ -1,14 +0,0 @@ -var absolutePath = require('absolute-path'); -var path = require('path'); -var pathIsInside = require('path-is-inside'); - -function isAbsolutePath(pathStr) { - return absolutePath(pathStr); -} - -function isChildPath(parentPath, childPath) { - return pathIsInside(parentPath, childPath); -} - -exports.isAbsolutePath = isAbsolutePath; -exports.isChildPath = isChildPath; diff --git a/react-packager/src/lib/__mocks__/declareOpts.js b/react-packager/src/lib/__mocks__/declareOpts.js index 2f7ae1f6..1afe4e29 100644 --- a/react-packager/src/lib/__mocks__/declareOpts.js +++ b/react-packager/src/lib/__mocks__/declareOpts.js @@ -1,3 +1,5 @@ +'use strict'; + module.exports = function(declared) { return function(opts) { for (var p in declared) { diff --git a/react-packager/src/lib/__tests__/declareOpts-test.js b/react-packager/src/lib/__tests__/declareOpts-test.js index 044e3a1c..66ae174f 100644 --- a/react-packager/src/lib/__tests__/declareOpts-test.js +++ b/react-packager/src/lib/__tests__/declareOpts-test.js @@ -1,3 +1,5 @@ +'use strict'; + jest.autoMockOff(); var declareOpts = require('../declareOpts'); diff --git a/react-packager/src/lib/declareOpts.js b/react-packager/src/lib/declareOpts.js index 2bac59f3..ddd06061 100644 --- a/react-packager/src/lib/declareOpts.js +++ b/react-packager/src/lib/declareOpts.js @@ -10,6 +10,8 @@ * var myOptions = validate(someOptions); */ +'use strict'; + var Joi = require('joi'); module.exports = function(descriptor) { From 887cb9ff71aeb38e07e8b9d3dceb0c58af7fa633 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 27 Feb 2015 10:23:54 -0800 Subject: [PATCH 023/936] [react-packager] transformModulePath option is not actually required --- react-packager/src/JSTransformer/index.js | 2 +- react-packager/src/Packager/index.js | 2 +- react-packager/src/Server/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 4f98d588..e3e713ee 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -40,7 +40,7 @@ var validateOpts = declareOpts({ }, transformModulePath: { type:'string', - required: true, + required: false, }, nonPersistent: { type: 'boolean', diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index ddcab6ee..42295acc 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -42,7 +42,7 @@ var validateOpts = declareOpts({ }, transformModulePath: { type:'string', - required: true, + required: false, }, nonPersistent: { type: 'boolean', diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 1f5b7ff1..14b18c96 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -40,7 +40,7 @@ var validateOpts = declareOpts({ }, transformModulePath: { type:'string', - required: true, + required: false, }, nonPersistent: { type: 'boolean', From c435ce74b6880ed4b020009a498c5d9c432ed402 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sat, 28 Feb 2015 14:30:34 -0800 Subject: [PATCH 024/936] [React Native][react-packager] Fix test runner and fialing tests --- .../src/Activity/__tests__/Activity-test.js | 6 +++++- .../__tests__/DependencyGraph-test.js | 1 - react-packager/src/FileWatcher/__mocks__/sane.js | 5 +++++ react-packager/src/JSTransformer/__mocks__/q.js | 6 ++++++ .../src/JSTransformer/__mocks__/underscore.js | 5 +++++ .../src/JSTransformer/__tests__/Cache-test.js | 3 +-- .../JSTransformer/__tests__/Transformer-test.js | 1 - .../src/Packager/__tests__/Package-test.js | 6 +----- react-packager/src/Server/__tests__/Server-test.js | 14 +++++++++----- react-packager/src/Server/index.js | 1 + 10 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 react-packager/src/FileWatcher/__mocks__/sane.js create mode 100644 react-packager/src/JSTransformer/__mocks__/q.js create mode 100644 react-packager/src/JSTransformer/__mocks__/underscore.js diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index bd0265f9..7fe31614 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -10,6 +10,7 @@ describe('Activity', function() { beforeEach(function() { console.log = jest.genMockFn(); Activity = require('../'); + jest.runOnlyPendingTimers(); }); afterEach(function() { @@ -60,12 +61,15 @@ describe('Activity', function() { expect(function() { Activity.endEvent(eid); - }).toThrow('event(1) has already ended!'); + }).toThrow('event(3) has already ended!'); + + jest.runOnlyPendingTimers(); }); }); describe('signal', function() { it('writes a SIGNAL event out to the console', function() { + var EVENT_NAME = 'EVENT_NAME'; var DATA = {someData: 42}; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 1c268c6b..eb839296 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -5,7 +5,6 @@ jest .dontMock('q') .dontMock('path') .dontMock('absolute-path') - .dontMock('../../../../fb-path-utils') .dontMock('../docblock') .setMock('../../../ModuleDescriptor', function(data) {return data;}); diff --git a/react-packager/src/FileWatcher/__mocks__/sane.js b/react-packager/src/FileWatcher/__mocks__/sane.js new file mode 100644 index 00000000..20dda2a2 --- /dev/null +++ b/react-packager/src/FileWatcher/__mocks__/sane.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher') +}; diff --git a/react-packager/src/JSTransformer/__mocks__/q.js b/react-packager/src/JSTransformer/__mocks__/q.js new file mode 100644 index 00000000..3d4d21f1 --- /dev/null +++ b/react-packager/src/JSTransformer/__mocks__/q.js @@ -0,0 +1,6 @@ +'use strict'; + +// Bug with Jest because we're going to the node_modules that is a sibling +// of what jest thinks our root (the dir with the package.json) should be. + +module.exports = require.requireActual('q'); diff --git a/react-packager/src/JSTransformer/__mocks__/underscore.js b/react-packager/src/JSTransformer/__mocks__/underscore.js new file mode 100644 index 00000000..a985ab20 --- /dev/null +++ b/react-packager/src/JSTransformer/__mocks__/underscore.js @@ -0,0 +1,5 @@ +'use strict'; + +// Bug with Jest because we're going to the node_modules that is a sibling +// of what jest thinks our root (the dir with the package.json) should be. +module.exports = require.requireActual('underscore'); diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 232d6ff4..97a50097 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -3,7 +3,6 @@ jest .dontMock('underscore') .dontMock('path') - .dontMock('q') .dontMock('absolute-path') .dontMock('../Cache'); @@ -194,7 +193,7 @@ describe('JSTransformer Cache', function() { return q('baz value'); }); - jest.runAllTimers(); + jest.runAllTicks(); expect(fs.writeFile).toBeCalled(); }); }); diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 6c9c6644..cdba0b8b 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -2,7 +2,6 @@ jest .dontMock('worker-farm') - .dontMock('q') .dontMock('os') .dontMock('../index'); diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index d269eb57..74a2f437 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -1,10 +1,6 @@ 'use strict'; -jest - .dontMock('underscore') - .dontMock('../base64-vlq') - .dontMock('source-map') - .dontMock('../Package'); +jest.autoMockOff(); var SourceMapGenerator = require('source-map').SourceMapGenerator; diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index a9951f60..e6020c79 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -3,9 +3,13 @@ jest.setMock('worker-farm', function() { return function() {}; }) .dontMock('q') .dontMock('os') - .dontMock('errno/custom') .dontMock('path') .dontMock('url') + .setMock('timers', { + setImmediate: function(fn) { + return setTimeout(fn, 0); + } + }) .dontMock('../'); var q = require('q'); @@ -75,16 +79,16 @@ describe('processRequest', function() { }); pit('returns sourcemap on request of *.map', function() { - makeRequest( + return makeRequest( requestHandler, 'mybundle.includeRequire.runModule.bundle.map' ).then(function(response) { - expect(response).toEqual('this is the source map'); + expect(response).toEqual('"this is the source map"'); }); }); pit('watches all files in projectRoot', function() { - makeRequest( + return makeRequest( requestHandler, 'mybundle.includeRequire.runModule.bundle' ).then(function(response) { @@ -107,7 +111,7 @@ describe('processRequest', function() { }); pit('invalides files in package when file is updated', function() { - makeRequest( + return makeRequest( requestHandler, 'mybundle.includeRequire.runModule.bundle' ).then(function(response) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 14b18c96..accce205 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -6,6 +6,7 @@ var declareOpts = require('../lib/declareOpts'); var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); +var setImmediate = require('timers').setImmediate; var q = require('q'); module.exports = Server; From 68b02a843c2a7968ff768b39812a6b2bca26f214 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sat, 28 Feb 2015 17:13:18 -0800 Subject: [PATCH 025/936] [react-packager] Better transform errors --- .../__tests__/Transformer-test.js | 4 +-- react-packager/src/JSTransformer/index.js | 34 ++++++++++++------- .../src/Packager/__tests__/Packager-test.js | 2 +- react-packager/src/Packager/index.js | 4 +-- transformer.js | 9 ++--- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index cdba0b8b..36d81d8f 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -35,7 +35,7 @@ describe('Transformer', function() { callback(null, 'content'); }); - return new Transformer(OPTIONS).loadFileAndTransform([], 'file', {}) + return new Transformer(OPTIONS).loadFileAndTransform('file') .then(function(data) { expect(data).toEqual({ code: 'transformed', @@ -58,7 +58,7 @@ describe('Transformer', function() { callback(null, {error: esprimaError}); }); - return new Transformer(OPTIONS).loadFileAndTransform([], 'foo-file.js', {}) + return new Transformer(OPTIONS).loadFileAndTransform('foo-file.js') .catch(function(error) { expect(error.type).toEqual('TransformError'); expect(error.snippet).toEqual([ diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index e3e713ee..87cb6e1a 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -78,11 +78,7 @@ Transformer.prototype.invalidateFile = function(filePath) { this._cache.invalidate(filePath); }; -Transformer.prototype.loadFileAndTransform = function( - transformSets, - filePath, - options -) { +Transformer.prototype.loadFileAndTransform = function(filePath) { if (this._failedToStart) { return this._failedToStart; } @@ -92,15 +88,14 @@ Transformer.prototype.loadFileAndTransform = function( return readFile(filePath) .then(function(buffer) { var sourceCode = buffer.toString(); - var opts = _.extend({}, options, {filename: filePath}); + return q.nfbind(workers)({ - transformSets: transformSets, sourceCode: sourceCode, - options: opts, + filename: filePath, }).then( function(res) { if (res.error) { - throw formatEsprimaError(res.error, filePath, sourceCode); + throw formatError(res.error, filePath, sourceCode); } return { @@ -117,11 +112,26 @@ Transformer.prototype.loadFileAndTransform = function( function TransformError() {} util.inherits(TransformError, SyntaxError); -function formatEsprimaError(err, filename, source) { - if (!(err.lineNumber && err.column)) { - return err; +function formatError(err, filename, source) { + if (err.lineNumber && err.column) { + return formatEsprimaError(err, filename, source); + } else { + return formatGenericError(err, filename, source); } +} +function formatGenericError(err, filename) { + var msg = 'TransformError: ' + filename + ': ' + err.message; + var error = new TransformError(); + var stack = err.stack.split('\n').slice(0, -1); + stack.push(msg); + error.stack = stack.join('\n'); + error.message = msg; + error.type = 'TransformError'; + return error; +} + +function formatEsprimaError(err, filename, source) { var stack = err.stack.split('\n'); stack.shift(); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 21af12ca..2e43e91a 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -49,7 +49,7 @@ describe('Packager', function() { }); require('../../JSTransformer').prototype.loadFileAndTransform - .mockImpl(function(tsets, path) { + .mockImpl(function(path) { return q({ code: 'transformed ' + path, sourceCode: 'source ' + path, diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 42295acc..123a3913 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -126,9 +126,7 @@ Packager.prototype.getDependencies = function(main) { Packager.prototype._transformModule = function(module) { var resolver = this._resolver; return this._transformer.loadFileAndTransform( - ['es6'], - path.resolve(module.path), - this._opts.transformer || {} + path.resolve(module.path) ).then(function(transformed) { return _.extend( {}, diff --git a/transformer.js b/transformer.js index ffcb80e2..acb586d7 100644 --- a/transformer.js +++ b/transformer.js @@ -16,10 +16,11 @@ var staticTypeSyntax = var visitorList = reactVisitors; -function transform(transformSets, srcTxt) { +function transform(srcTxt, filename) { var options = { es3: true, - sourceType: 'nonStrictModule' + sourceType: 'nonStrictModule', + filename: filename, }; // These tranforms mostly just erase type annotations and static typing @@ -42,8 +43,8 @@ module.exports = function(data, callback) { var result; try { result = transform( - data.transformSets, - data.sourceCode + data.sourceCode, + data.filename ); } catch (e) { return callback(null, { From 258c714cef87e275fc1e816a7e6e0fe71a3bf437 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 2 Mar 2015 10:42:31 -0800 Subject: [PATCH 026/936] Updates from Wed Feb 25 - [react-packager] Fix lint errors | Amjad Masad - [react-packager] fix a typo s/pacakge/package | Chengyin Liu - [react-packager] Fix jest tests | Amjad Masad - [Image] Really improve the quality of mis-sized images w/trilinear filtering | James Ide --- react-packager/, | 0 react-packager/.jshintrc | 86 ------------------- react-packager/package.json | 14 --- .../src/Activity/__tests__/Activity-test.js | 2 + react-packager/src/Activity/index.js | 2 + .../DependencyResolver/ModuleDescriptor.js | 4 +- .../__tests__/DependencyGraph-test.js | 80 +++++++++++++---- .../haste/DependencyGraph/docblock.js | 10 ++- .../haste/DependencyGraph/example.js | 25 ------ .../haste/DependencyGraph/index.js | 9 +- .../__tests__/HasteDependencyResolver-test.js | 13 ++- .../src/DependencyResolver/haste/index.js | 24 +++--- .../haste/polyfills/console.js | 10 ++- .../haste/polyfills/error-guard.js | 2 +- .../haste/polyfills/polyfills.js | 2 +- .../haste/polyfills/prelude.js | 1 + .../haste/polyfills/prelude_dev.js | 1 + .../haste/polyfills/require.js | 1 + .../src/DependencyResolver/index.js | 2 + .../src/DependencyResolver/node/index.js | 13 +-- .../FileWatcher/__tests__/FileWatcher-test.js | 10 ++- react-packager/src/JSTransformer/Cache.js | 16 ++-- .../src/JSTransformer/__tests__/Cache-test.js | 1 - react-packager/src/JSTransformer/index.js | 7 +- react-packager/src/Packager/Package.js | 3 +- .../src/Packager/__mocks__/source-map.js | 5 -- .../src/Packager/__tests__/Package-test.js | 16 ++-- react-packager/src/Packager/base64-vlq.js | 9 +- .../src/Server/__tests__/Server-test.js | 78 +++++++++-------- react-packager/src/Server/index.js | 4 +- react-packager/src/fb-path-utils/index.js | 14 --- .../src/lib/__mocks__/declareOpts.js | 2 + .../src/lib/__tests__/declareOpts-test.js | 2 + react-packager/src/lib/declareOpts.js | 2 + 34 files changed, 200 insertions(+), 270 deletions(-) delete mode 100644 react-packager/, delete mode 100644 react-packager/.jshintrc delete mode 100644 react-packager/package.json delete mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/example.js delete mode 100644 react-packager/src/Packager/__mocks__/source-map.js delete mode 100644 react-packager/src/fb-path-utils/index.js diff --git a/react-packager/, b/react-packager/, deleted file mode 100644 index e69de29b..00000000 diff --git a/react-packager/.jshintrc b/react-packager/.jshintrc deleted file mode 100644 index 7a3f79a7..00000000 --- a/react-packager/.jshintrc +++ /dev/null @@ -1,86 +0,0 @@ -{ - "-W093": true, - "asi": false, - "bitwise": true, - "boss": false, - "browser": false, - "camelcase": true, - "couch": false, - "curly": true, - "debug": false, - "devel": true, - "dojo": false, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "evil": false, - "expr": true, - "forin": false, - "freeze": true, - "funcscope": true, - "gcl": false, - "globals": { - "Promise": true, - "React": true, - "XMLHttpRequest": true, - "document": true, - "location": true, - "window": true - }, - "globalstrict": true, - "immed": false, - "indent": 2, - "iterator": false, - "jquery": false, - "lastsemic": false, - "latedef": false, - "laxbreak": true, - "laxcomma": false, - "loopfunc": false, - "maxcomplexity": false, - "maxdepth": false, - "maxerr": 50, - "maxlen": 80, - "maxparams": false, - "maxstatements": false, - "mootools": false, - "moz": false, - "multistr": false, - "newcap": true, - "noarg": true, - "node": true, - "noempty": false, - "nonbsp": true, - "nonew": true, - "nonstandard": false, - "notypeof": false, - "noyield": false, - "phantom": false, - "plusplus": false, - "predef": [ - "afterEach", - "beforeEach", - "describe", - "expect", - "it", - "jest", - "pit" - ], - "proto": false, - "prototypejs": false, - "quotmark": true, - "rhino": false, - "scripturl": false, - "shadow": false, - "smarttabs": false, - "strict": false, - "sub": false, - "supernew": false, - "trailing": true, - "undef": true, - "unused": true, - "validthis": false, - "worker": false, - "wsh": false, - "yui": false -} diff --git a/react-packager/package.json b/react-packager/package.json deleted file mode 100644 index 0ac47c25..00000000 --- a/react-packager/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "react-packager", - "version": "0.1.0", - "description": "", - "main": "index.js", - "jest": { - "unmockedModulePathPatterns": [ - "source-map" - ], - "testPathIgnorePatterns": [ - "JSAppServer/node_modules" - ] - } -} diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index 7a2bdf48..bd0265f9 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -1,3 +1,5 @@ +'use strict'; + jest.autoMockOff(); describe('Activity', function() { diff --git a/react-packager/src/Activity/index.js b/react-packager/src/Activity/index.js index a60f87b0..611ccb0b 100644 --- a/react-packager/src/Activity/index.js +++ b/react-packager/src/Activity/index.js @@ -1,3 +1,5 @@ +'use strict'; + var COLLECTION_PERIOD = 1000; var _endedEvents = Object.create(null); diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index 0898767a..f1a30545 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -1,3 +1,5 @@ +'use strict'; + function ModuleDescriptor(fields) { if (!fields.id) { throw new Error('Missing required fields id'); @@ -28,7 +30,7 @@ ModuleDescriptor.prototype.toJSON = function() { id: this.id, path: this.path, dependencies: this.dependencies - } + }; }; module.exports = ModuleDescriptor; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index fe8a18b6..1c268c6b 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -9,8 +9,6 @@ jest .dontMock('../docblock') .setMock('../../../ModuleDescriptor', function(data) {return data;}); -var q = require('q'); - describe('DependencyGraph', function() { var DependencyGraph; var fileWatcher; @@ -46,7 +44,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -75,7 +76,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -105,7 +109,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -135,7 +142,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -175,7 +185,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) .toEqual([ @@ -216,7 +229,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -245,7 +261,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -280,7 +299,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -320,7 +342,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -360,7 +385,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ @@ -386,7 +414,6 @@ describe('DependencyGraph', function() { }); describe('file watch updating', function() { - var fileWatcher; var triggerFileChange; beforeEach(function() { @@ -428,7 +455,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); @@ -476,7 +506,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); @@ -524,7 +557,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); @@ -571,7 +607,10 @@ describe('DependencyGraph', function() { } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { filesystem.root['bar.js'] = [ '/**', @@ -679,7 +718,7 @@ describe('DependencyGraph', function() { pit('should ignore directory updates', function() { var root = '/root'; - var filesystem = fs.__setMockFilesystem({ + fs.__setMockFilesystem({ 'root': { 'index.js': [ '/**', @@ -703,7 +742,10 @@ describe('DependencyGraph', function() { } } }); - var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher}); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); return dgraph.load().then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js index 52cac03b..c2b6ac98 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js @@ -14,6 +14,7 @@ * limitations under the License. */ +'use strict'; var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/; @@ -35,7 +36,8 @@ var commentStartRe = /^\/\*\*?/; var commentEndRe = /\*\/$/; var wsRe = /[\t ]+/g; var stringStartRe = /(\r?\n|^) *\*/g; -var multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g; +var multilineRe = + /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g; var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g; /** @@ -51,15 +53,15 @@ function parse(docblock) { // Normalize multi-line directives var prev = ''; - while (prev != docblock) { + while (prev !== docblock) { prev = docblock; - docblock = docblock.replace(multilineRe, "\n$1 $2\n"); + docblock = docblock.replace(multilineRe, '\n$1 $2\n'); } docblock = docblock.trim(); var result = []; var match; - while (match = propertyRe.exec(docblock)) { + while ((match = propertyRe.exec(docblock))) { result.push([match[1], match[2]]); } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js deleted file mode 100644 index 02e6c592..00000000 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/example.js +++ /dev/null @@ -1,25 +0,0 @@ -var path = require('path'); -var DependecyGraph = require('./'); - -var example_project = path.resolve(__dirname, '../../../../example_project'); -var watcher = new (require('../../../FileWatcher'))({projectRoot: example_project}); -var graph = new DependecyGraph({ - fileWatcher: watcher, - root: example_project -}); - -graph.load().then(function() { - var index = path.join(example_project, 'index.js'); - console.log(graph.getOrderedDependencies(index)); -}).done(); - -watcher.getWatcher().then(function(watcher) { - watcher.on('all', function() { - setImmediate(function() { - graph.load().then(function() { - var index = path.join(example_project, 'index.js'); - console.log(graph.getOrderedDependencies(index)); - }); - }) - }); -}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 6a7d8bba..ce631856 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -139,7 +139,7 @@ DependecyGraph.prototype.resolveDependency = function( depModuleId, fromModule.id ); - return; + return null; } var main = packageJson.main || 'index'; @@ -147,7 +147,7 @@ DependecyGraph.prototype.resolveDependency = function( dep = this._graph[modulePath]; if (dep == null) { throw new Error( - 'Cannot find package main file for pacakge: ' + packageJson._root + 'Cannot find package main file for package: ' + packageJson._root ); } return dep; @@ -223,7 +223,7 @@ DependecyGraph.prototype._search = function() { .then(function(filePaths) { filePaths = filePaths.filter(function(filePath) { if (filePath == null) { - return false + return false; } return !self._ignoreFilePath(filePath); @@ -454,7 +454,8 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) { return filePath; } - for (var i = 0, root; root = this._roots[i]; i++) { + for (var i = 0; i < this._roots.length; i++) { + var root = this._roots[i]; var absPath = path.join(root, filePath); if (this._graph[absPath]) { return absPath; diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index d3c4a7d9..9704c5b5 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -1,3 +1,4 @@ +'use strict'; jest.dontMock('../') .dontMock('q') @@ -7,7 +8,6 @@ var q = require('q'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; - var DependencyGraph; beforeEach(function() { // For the polyfillDeps @@ -15,7 +15,6 @@ describe('HasteDependencyResolver', function() { return b; }); HasteDependencyResolver = require('../'); - DependencyGraph = require('../DependencyGraph'); }); describe('getDependencies', function() { @@ -223,7 +222,7 @@ describe('HasteDependencyResolver', function() { }); var depGraph = depResolver._depGraph; - var dependencies = ['x', 'y', 'z'] + var dependencies = ['x', 'y', 'z']; var code = [ 'require("x")', 'require("y")', @@ -248,10 +247,10 @@ describe('HasteDependencyResolver', function() { }, code); expect(processedCode).toEqual([ - "__d('test module',[\"changed\",\"y\"],function(global," + - " require, requireDynamic, requireLazy, module, exports) {" + - " require('changed')", - "require('y')", + '__d(\'test module\',["changed","y"],function(global,' + + ' require, requireDynamic, requireLazy, module, exports) {' + + ' require(\'changed\')', + 'require(\'y\')', 'require("z")});', ].join('\n')); }); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index dc497649..9cb0661a 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -6,14 +6,15 @@ var DependencyGraph = require('./DependencyGraph'); var ModuleDescriptor = require('../ModuleDescriptor'); var declareOpts = require('../../lib/declareOpts'); -var DEFINE_MODULE_CODE = - '__d(' + - '\'_moduleName_\',' + - '_deps_,' + - 'function(global, require, requireDynamic, requireLazy, module, exports) {'+ - ' _code_' + - '}' + - ');'; +var DEFINE_MODULE_CODE = [ + '__d(', + '\'_moduleName_\',', + '_deps_,', + 'function(global, require, requireDynamic, requireLazy, module, exports) {', + ' _code_', + '}', + ');', +].join(''); var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; @@ -116,7 +117,6 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { return code; } - var depGraph = this._depGraph; var resolvedDeps = Object.create(null); var resolvedDepsArr = []; @@ -131,9 +131,9 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { var relativizedCode = code.replace(REL_REQUIRE_STMT, function(codeMatch, depName) { - var dep = resolvedDeps[depName]; - if (dep != null) { - return 'require(\'' + dep + '\')'; + var depId = resolvedDeps[depName]; + if (depId != null) { + return 'require(\'' + depId + '\')'; } else { return codeMatch; } diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index 4c9ddce1..bb83822d 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -20,7 +20,9 @@ * @polyfill */ +/*eslint global-strict:0*/ (function(global) { + 'use strict'; var OBJECT_COLUMN_NAME = '(index)'; @@ -45,7 +47,7 @@ if (typeof arg.toString === 'function') { try { return arg.toString(); - } catch (e) { + } catch (E) { return 'unknown'; } } @@ -53,7 +55,7 @@ } }).join(', '); global.nativeLoggingHook(str); - }; + } var repeat = function(element, n) { return Array.apply(null, Array(n)).map(function() { return element; }); @@ -120,7 +122,7 @@ // logged string, which would shift the header and screw up // the table global.nativeLoggingHook('\n' + table.join('\n')); - }; + } global.console = { error: doNativeLog, @@ -130,7 +132,7 @@ table: consoleTablePolyfill }; - }; + } if (typeof module !== 'undefined') { module.exports = setupConsole; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js index 687a4a19..745d650e 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js @@ -39,7 +39,7 @@ return ErrorUtils._inGuard; }, guard: function(fun, name, context) { - if (typeof fun !== "function") { + if (typeof fun !== 'function') { console.warn('A function must be passed to ErrorUtils.guard, got ', fun); return null; } diff --git a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js index 2fd32246..75f74279 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js @@ -22,7 +22,7 @@ // WARNING: This is an optimized version that fails on hasOwnProperty checks // and non objects. It's not spec-compliant. It's a perf optimization. - +/* eslint global-strict:0 */ Object.assign = function(target, sources) { if (__DEV__) { if (target == null) { diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude.js b/react-packager/src/DependencyResolver/haste/polyfills/prelude.js index 95c66983..9f4db44e 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/prelude.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/prelude.js @@ -1 +1,2 @@ +/* eslint global-strict:0 */ __DEV__ = false; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js b/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js index a5ca53b7..26b26a07 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js @@ -1 +1,2 @@ +/* eslint global-strict:0 */ __DEV__ = true; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/haste/polyfills/require.js index 3b5d6d87..e7fdde25 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/require.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -1,3 +1,4 @@ +/* eslint global-strict:0,eqeqeq:0,no-bitwise:0,no-undef:0 */ (function(global) { // avoid redefining require() diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index 79eb48c1..f42ecb8a 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -1,3 +1,5 @@ +'use strict'; + var HasteDependencyResolver = require('./haste'); var NodeDependencyResolver = require('./node'); diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js index 0d3b807e..da03cc7e 100644 --- a/react-packager/src/DependencyResolver/node/index.js +++ b/react-packager/src/DependencyResolver/node/index.js @@ -1,17 +1,12 @@ +'use strict'; + var Promise = require('q').Promise; var ModuleDescriptor = require('../ModuleDescriptor'); var mdeps = require('module-deps'); var path = require('path'); -var fs = require('fs'); -// var REQUIRE_RUNTIME = fs.readFileSync( -// path.join(__dirname, 'require.js') -// ).toString(); - -exports.getRuntimeCode = function() { - return REQUIRE_RUNTIME; -}; +exports.getRuntimeCode = function() {}; exports.wrapModule = function(id, source) { return Promise.resolve( @@ -21,7 +16,7 @@ exports.wrapModule = function(id, source) { }; exports.getDependencies = function(root, fileEntryPath) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { fileEntryPath = path.join(process.cwd(), root, fileEntryPath); var md = mdeps(); diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index 8baae9e1..fc8a7a41 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -1,8 +1,12 @@ 'use strict'; -jest.dontMock('../') - .dontMock('q') - .setMock('child_process', { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman') } }); +jest + .dontMock('../') + .dontMock('q') + .setMock( + 'child_process', + { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman'); } } + ); describe('FileWatcher', function() { var FileWatcher; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index f04ffe9f..f43418e3 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -1,9 +1,9 @@ 'use strict'; var path = require('path'); -var version = require('../../package.json').version; +var version = require('../../../../package.json').version; var tmpdir = require('os').tmpDir(); -var pathUtils = require('../fb-path-utils'); +var isAbsolutePath = require('absolute-path'); var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); var _ = require('underscore'); @@ -48,7 +48,7 @@ function Cache(options) { } Cache.prototype.get = function(filepath, loaderCb) { - if (!pathUtils.isAbsolutePath(filepath)) { + if (!isAbsolutePath(filepath)) { throw new Error('Use absolute paths'); } @@ -62,7 +62,7 @@ Cache.prototype.get = function(filepath, loaderCb) { }; Cache.prototype._set = function(filepath, loaderPromise) { - return this._data[filepath] = loaderPromise.then(function(data) { + this._data[filepath] = loaderPromise.then(function(data) { return [ data, q.nfbind(fs.stat)(filepath) @@ -74,10 +74,12 @@ Cache.prototype._set = function(filepath, loaderPromise) { mtime: stat.mtime.getTime(), }; }.bind(this)); + + return this._data[filepath]; }; Cache.prototype.invalidate = function(filepath){ - if(this._has(filepath)) { + if (this._has(filepath)) { delete this._data[filepath]; } }; @@ -94,7 +96,7 @@ Cache.prototype._persistCache = function() { var data = this._data; var cacheFilepath = this._cacheFilePath; - return this._persisting = q.all(_.values(data)) + this._persisting = q.all(_.values(data)) .then(function(values) { var json = Object.create(null); Object.keys(data).forEach(function(key, i) { @@ -106,6 +108,8 @@ Cache.prototype._persistCache = function() { this._persisting = null; return true; }.bind(this)); + + return this._persisting; }; function loadCacheSync(cacheFilepath) { diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index c77c6384..232d6ff4 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -5,7 +5,6 @@ jest .dontMock('path') .dontMock('q') .dontMock('absolute-path') - .dontMock('../../fb-path-utils') .dontMock('../Cache'); var q = require('q'); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index ade206a7..4f98d588 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -7,6 +7,7 @@ var Cache = require('./Cache'); var _ = require('underscore'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); +var util = require('util'); var readFile = q.nfbind(fs.readFile); @@ -75,9 +76,7 @@ Transformer.prototype.kill = function() { Transformer.prototype.invalidateFile = function(filePath) { this._cache.invalidate(filePath); - //TODO: We can read the file and put it into the cache right here - // This would simplify some caching logic as we can be sure that the cache is up to date -} +}; Transformer.prototype.loadFileAndTransform = function( transformSets, @@ -116,7 +115,7 @@ Transformer.prototype.loadFileAndTransform = function( }; function TransformError() {} -TransformError.__proto__ = SyntaxError.prototype; +util.inherits(TransformError, SyntaxError); function formatEsprimaError(err, filename, source) { if (!(err.lineNumber && err.column)) { diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 787684bc..a4080d3b 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -1,7 +1,6 @@ 'use strict'; var _ = require('underscore'); -var SourceMapGenerator = require('source-map').SourceMapGenerator; var base64VLQ = require('./base64-vlq'); module.exports = Package; @@ -102,7 +101,7 @@ Package.prototype._getMappings = function() { mappings += ';'; } } - if (i != modules.length - 1) { + if (i !== modules.length - 1) { mappings += ';'; } } diff --git a/react-packager/src/Packager/__mocks__/source-map.js b/react-packager/src/Packager/__mocks__/source-map.js deleted file mode 100644 index 08c127f6..00000000 --- a/react-packager/src/Packager/__mocks__/source-map.js +++ /dev/null @@ -1,5 +0,0 @@ -var SourceMapGenerator = jest.genMockFn(); -SourceMapGenerator.prototype.addMapping = jest.genMockFn(); -SourceMapGenerator.prototype.setSourceContent = jest.genMockFn(); -SourceMapGenerator.prototype.toJSON = jest.genMockFn(); -exports.SourceMapGenerator = SourceMapGenerator; diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index d18bb4d6..d269eb57 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -50,13 +50,13 @@ describe('Package', function() { describe('sourcemap package', function() { it('should create sourcemap', function() { - var ppackage = new Package('test_url'); - ppackage.addModule('transformed foo;\n', 'source foo', 'foo path'); - ppackage.addModule('transformed bar;\n', 'source bar', 'bar path'); - ppackage.setMainModuleId('foo'); - ppackage.finalize({runMainModule: true}); - var s = ppackage.getSourceMap(); - expect(s).toEqual(genSourceMap(ppackage._modules)); + var p = new Package('test_url'); + p.addModule('transformed foo;\n', 'source foo', 'foo path'); + p.addModule('transformed bar;\n', 'source bar', 'bar path'); + p.setMainModuleId('foo'); + p.finalize({runMainModule: true}); + var s = p.getSourceMap(); + expect(s).toEqual(genSourceMap(p._modules)); }); }); }); @@ -92,4 +92,4 @@ describe('Package', function() { ); } return sourceMapGen.toJSON(); -}; + } diff --git a/react-packager/src/Packager/base64-vlq.js b/react-packager/src/Packager/base64-vlq.js index 91d490b7..4483a507 100644 --- a/react-packager/src/Packager/base64-vlq.js +++ b/react-packager/src/Packager/base64-vlq.js @@ -35,9 +35,11 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/*eslint no-bitwise:0,quotes:0,global-strict:0*/ + var charToIntMap = {}; var intToCharMap = {}; - + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' .split('') .forEach(function (ch, index) { @@ -55,7 +57,7 @@ base64.encode = function base64_encode(aNumber) { } throw new TypeError("Must be between 0 and 63: " + aNumber); }; - + /** * Decode a single base 64 digit to an integer. */ @@ -65,7 +67,7 @@ base64.decode = function base64_decode(aChar) { } throw new TypeError("Not a valid base 64 digit: " + aChar); }; - + // A single base 64 digit can contain 6 bits of data. For the base 64 variable @@ -165,4 +167,3 @@ exports.decode = function base64VLQ_decode(aStr, aOutParam) { aOutParam.value = fromVLQSigned(result); aOutParam.rest = aStr.slice(i); }; - diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 690c7e06..a9951f60 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -1,4 +1,6 @@ -jest.setMock('worker-farm', function(){ return function(){}; }) +'use strict'; + +jest.setMock('worker-farm', function() { return function() {}; }) .dontMock('q') .dontMock('os') .dontMock('errno/custom') @@ -8,9 +10,8 @@ jest.setMock('worker-farm', function(){ return function(){}; }) var q = require('q'); -describe('processRequest', function(){ +describe('processRequest', function() { var server; - var Activity; var Packager; var FileWatcher; @@ -21,16 +22,16 @@ describe('processRequest', function(){ polyfillModuleNames: null }; - var makeRequest = function(requestHandler, requrl){ + var makeRequest = function(requestHandler, requrl) { var deferred = q.defer(); requestHandler({ url: requrl },{ - end: function(res){ + end: function(res) { deferred.resolve(res); } },{ - next: function(){} + next: function() {} } ); return deferred.promise; @@ -40,8 +41,7 @@ describe('processRequest', function(){ var watcherFunc = jest.genMockFunction(); var requestHandler; - beforeEach(function(){ - Activity = require('../../Activity'); + beforeEach(function() { Packager = require('../../Packager'); FileWatcher = require('../../FileWatcher'); @@ -50,7 +50,7 @@ describe('processRequest', function(){ getSource: function() { return 'this is the source'; }, - getSourceMap: function(){ + getSourceMap: function() { return 'this is the source map'; }, }); @@ -65,26 +65,32 @@ describe('processRequest', function(){ requestHandler = server.processRequest.bind(server); }); - pit('returns JS bundle source on request of *.bundle',function(){ - result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); - return result.then(function(response){ - expect(response).toEqual("this is the source"); + pit('returns JS bundle source on request of *.bundle',function() { + return makeRequest( + requestHandler, + 'mybundle.includeRequire.runModule.bundle' + ).then(function(response) { + expect(response).toEqual('this is the source'); }); }); - pit('returns sourcemap on request of *.map', function(){ - result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle.map'); - return result.then(function(response){ - expect(response).toEqual('"this is the source map"'); + pit('returns sourcemap on request of *.map', function() { + makeRequest( + requestHandler, + 'mybundle.includeRequire.runModule.bundle.map' + ).then(function(response) { + expect(response).toEqual('this is the source map'); }); }); - pit('watches all files in projectRoot', function(){ - result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); - return result.then(function(response){ + pit('watches all files in projectRoot', function() { + makeRequest( + requestHandler, + 'mybundle.includeRequire.runModule.bundle' + ).then(function(response) { expect(watcherFunc.mock.calls[0][0]).toEqual('all'); expect(watcherFunc.mock.calls[0][1]).not.toBe(null); - }) + }); }); @@ -101,8 +107,10 @@ describe('processRequest', function(){ }); pit('invalides files in package when file is updated', function() { - result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle'); - return result.then(function(response){ + makeRequest( + requestHandler, + 'mybundle.includeRequire.runModule.bundle' + ).then(function(response) { var 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'); @@ -114,41 +122,41 @@ describe('processRequest', function(){ packageFunc .mockReturnValueOnce( q({ - getSource: function(){ - return "this is the first source" + getSource: function() { + return 'this is the first source'; }, - getSourceMap: function(){}, + getSourceMap: function() {}, }) ) .mockReturnValue( q({ - getSource: function(){ - return "this is the rebuilt source" + getSource: function() { + return 'this is the rebuilt source'; }, - getSourceMap: function(){}, + getSourceMap: function() {}, }) ); Packager.prototype.package = packageFunc; var Server = require('../../Server'); - var server = new Server(options); + server = new Server(options); requestHandler = server.processRequest.bind(server); return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') - .then(function(response){ - expect(response).toEqual("this is the first source"); + .then(function(response) { + expect(response).toEqual('this is the first source'); expect(packageFunc.mock.calls.length).toBe(1); triggerFileChange('all','path/file.js', options.projectRoots[0]); jest.runAllTimers(); }) - .then(function(){ + .then(function() { expect(packageFunc.mock.calls.length).toBe(2); return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') - .then(function(response){ - expect(response).toEqual("this is the rebuilt source"); + .then(function(response) { + expect(response).toEqual('this is the rebuilt source'); }); }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 611d703e..1f5b7ff1 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -1,3 +1,5 @@ +'use strict'; + var url = require('url'); var path = require('path'); var declareOpts = require('../lib/declareOpts'); @@ -154,7 +156,7 @@ Server.prototype.processRequest = function(req, res, next) { var startReqEventId = Activity.startEvent('request:' + req.url); var options = getOptionsFromPath(url.parse(req.url).pathname); - var building = this._packages[req.url] || this._buildPackage(options) + var building = this._packages[req.url] || this._buildPackage(options); this._packages[req.url] = building; building.then( function(p) { diff --git a/react-packager/src/fb-path-utils/index.js b/react-packager/src/fb-path-utils/index.js deleted file mode 100644 index b4a1cb96..00000000 --- a/react-packager/src/fb-path-utils/index.js +++ /dev/null @@ -1,14 +0,0 @@ -var absolutePath = require('absolute-path'); -var path = require('path'); -var pathIsInside = require('path-is-inside'); - -function isAbsolutePath(pathStr) { - return absolutePath(pathStr); -} - -function isChildPath(parentPath, childPath) { - return pathIsInside(parentPath, childPath); -} - -exports.isAbsolutePath = isAbsolutePath; -exports.isChildPath = isChildPath; diff --git a/react-packager/src/lib/__mocks__/declareOpts.js b/react-packager/src/lib/__mocks__/declareOpts.js index 2f7ae1f6..1afe4e29 100644 --- a/react-packager/src/lib/__mocks__/declareOpts.js +++ b/react-packager/src/lib/__mocks__/declareOpts.js @@ -1,3 +1,5 @@ +'use strict'; + module.exports = function(declared) { return function(opts) { for (var p in declared) { diff --git a/react-packager/src/lib/__tests__/declareOpts-test.js b/react-packager/src/lib/__tests__/declareOpts-test.js index 044e3a1c..66ae174f 100644 --- a/react-packager/src/lib/__tests__/declareOpts-test.js +++ b/react-packager/src/lib/__tests__/declareOpts-test.js @@ -1,3 +1,5 @@ +'use strict'; + jest.autoMockOff(); var declareOpts = require('../declareOpts'); diff --git a/react-packager/src/lib/declareOpts.js b/react-packager/src/lib/declareOpts.js index 2bac59f3..ddd06061 100644 --- a/react-packager/src/lib/declareOpts.js +++ b/react-packager/src/lib/declareOpts.js @@ -10,6 +10,8 @@ * var myOptions = validate(someOptions); */ +'use strict'; + var Joi = require('joi'); module.exports = function(descriptor) { From 3260cc6a9ada4b8cbb22c87c46d7b155049ca5c7 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 2 Mar 2015 10:52:16 -0800 Subject: [PATCH 027/936] Updates from Fri Feb 27 - [react-packager] transformModulePath option is not actually required | Amjad Masad - Implement TextInput.clearButtonMode added by D1875684 on OSS fork + example | Tadeu Zagallo - [ReactNative] Use local CocoaPod config for ReactNative modules | Spencer Ahrens - [ReactNative] Pull out some OSS modules into separate libs | Spencer Ahrens - Enqueue events at 60fps + profiling helpers | Tadeu Zagallo --- react-packager/src/JSTransformer/index.js | 2 +- react-packager/src/Packager/index.js | 2 +- react-packager/src/Server/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 4f98d588..e3e713ee 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -40,7 +40,7 @@ var validateOpts = declareOpts({ }, transformModulePath: { type:'string', - required: true, + required: false, }, nonPersistent: { type: 'boolean', diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index ddcab6ee..42295acc 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -42,7 +42,7 @@ var validateOpts = declareOpts({ }, transformModulePath: { type:'string', - required: true, + required: false, }, nonPersistent: { type: 'boolean', diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 1f5b7ff1..14b18c96 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -40,7 +40,7 @@ var validateOpts = declareOpts({ }, transformModulePath: { type:'string', - required: true, + required: false, }, nonPersistent: { type: 'boolean', From 9dd01d4a1779307ab3c15941dab0d84bad1f2e0c Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 2 Mar 2015 11:36:55 -0800 Subject: [PATCH 028/936] Updates from Mon Mar 2 - [ReactNative] Move merge & mergeInto from downstream to vendor | Christopher Chedeau - [ReactNative] Replace all the call sites of mergeInto by Object.assign | Christopher Chedeau - [WIP] Migrated View Managers over to new architecture | Nick Lockwood - [ReactNative] Replace all the call sites of copyProperties by Object.assign | Christopher Chedeau - [ReactNative] Migrate navigator.geolocation to open source | Christopher Chedeau - [ReactNative] Remove README.md, LICENSE and .travis.yml from fbobjc | Christopher Chedeau - [react-packager] Better transform errors | Amjad Masad - [React Native][react-packager] Fix test runner and fialing tests | Amjad Masad --- .../src/Activity/__tests__/Activity-test.js | 6 +++- .../__tests__/DependencyGraph-test.js | 1 - .../src/FileWatcher/__mocks__/sane.js | 5 +++ .../src/JSTransformer/__mocks__/q.js | 6 ++++ .../src/JSTransformer/__mocks__/underscore.js | 5 +++ .../src/JSTransformer/__tests__/Cache-test.js | 3 +- .../__tests__/Transformer-test.js | 5 ++- react-packager/src/JSTransformer/index.js | 34 ++++++++++++------- .../src/Packager/__tests__/Package-test.js | 6 +--- .../src/Packager/__tests__/Packager-test.js | 2 +- react-packager/src/Packager/index.js | 4 +-- .../src/Server/__tests__/Server-test.js | 14 +++++--- react-packager/src/Server/index.js | 1 + transformer.js | 9 ++--- 14 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 react-packager/src/FileWatcher/__mocks__/sane.js create mode 100644 react-packager/src/JSTransformer/__mocks__/q.js create mode 100644 react-packager/src/JSTransformer/__mocks__/underscore.js diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index bd0265f9..7fe31614 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -10,6 +10,7 @@ describe('Activity', function() { beforeEach(function() { console.log = jest.genMockFn(); Activity = require('../'); + jest.runOnlyPendingTimers(); }); afterEach(function() { @@ -60,12 +61,15 @@ describe('Activity', function() { expect(function() { Activity.endEvent(eid); - }).toThrow('event(1) has already ended!'); + }).toThrow('event(3) has already ended!'); + + jest.runOnlyPendingTimers(); }); }); describe('signal', function() { it('writes a SIGNAL event out to the console', function() { + var EVENT_NAME = 'EVENT_NAME'; var DATA = {someData: 42}; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 1c268c6b..eb839296 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -5,7 +5,6 @@ jest .dontMock('q') .dontMock('path') .dontMock('absolute-path') - .dontMock('../../../../fb-path-utils') .dontMock('../docblock') .setMock('../../../ModuleDescriptor', function(data) {return data;}); diff --git a/react-packager/src/FileWatcher/__mocks__/sane.js b/react-packager/src/FileWatcher/__mocks__/sane.js new file mode 100644 index 00000000..20dda2a2 --- /dev/null +++ b/react-packager/src/FileWatcher/__mocks__/sane.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher') +}; diff --git a/react-packager/src/JSTransformer/__mocks__/q.js b/react-packager/src/JSTransformer/__mocks__/q.js new file mode 100644 index 00000000..3d4d21f1 --- /dev/null +++ b/react-packager/src/JSTransformer/__mocks__/q.js @@ -0,0 +1,6 @@ +'use strict'; + +// Bug with Jest because we're going to the node_modules that is a sibling +// of what jest thinks our root (the dir with the package.json) should be. + +module.exports = require.requireActual('q'); diff --git a/react-packager/src/JSTransformer/__mocks__/underscore.js b/react-packager/src/JSTransformer/__mocks__/underscore.js new file mode 100644 index 00000000..a985ab20 --- /dev/null +++ b/react-packager/src/JSTransformer/__mocks__/underscore.js @@ -0,0 +1,5 @@ +'use strict'; + +// Bug with Jest because we're going to the node_modules that is a sibling +// of what jest thinks our root (the dir with the package.json) should be. +module.exports = require.requireActual('underscore'); diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 232d6ff4..97a50097 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -3,7 +3,6 @@ jest .dontMock('underscore') .dontMock('path') - .dontMock('q') .dontMock('absolute-path') .dontMock('../Cache'); @@ -194,7 +193,7 @@ describe('JSTransformer Cache', function() { return q('baz value'); }); - jest.runAllTimers(); + jest.runAllTicks(); expect(fs.writeFile).toBeCalled(); }); }); diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 6c9c6644..36d81d8f 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -2,7 +2,6 @@ jest .dontMock('worker-farm') - .dontMock('q') .dontMock('os') .dontMock('../index'); @@ -36,7 +35,7 @@ describe('Transformer', function() { callback(null, 'content'); }); - return new Transformer(OPTIONS).loadFileAndTransform([], 'file', {}) + return new Transformer(OPTIONS).loadFileAndTransform('file') .then(function(data) { expect(data).toEqual({ code: 'transformed', @@ -59,7 +58,7 @@ describe('Transformer', function() { callback(null, {error: esprimaError}); }); - return new Transformer(OPTIONS).loadFileAndTransform([], 'foo-file.js', {}) + return new Transformer(OPTIONS).loadFileAndTransform('foo-file.js') .catch(function(error) { expect(error.type).toEqual('TransformError'); expect(error.snippet).toEqual([ diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index e3e713ee..87cb6e1a 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -78,11 +78,7 @@ Transformer.prototype.invalidateFile = function(filePath) { this._cache.invalidate(filePath); }; -Transformer.prototype.loadFileAndTransform = function( - transformSets, - filePath, - options -) { +Transformer.prototype.loadFileAndTransform = function(filePath) { if (this._failedToStart) { return this._failedToStart; } @@ -92,15 +88,14 @@ Transformer.prototype.loadFileAndTransform = function( return readFile(filePath) .then(function(buffer) { var sourceCode = buffer.toString(); - var opts = _.extend({}, options, {filename: filePath}); + return q.nfbind(workers)({ - transformSets: transformSets, sourceCode: sourceCode, - options: opts, + filename: filePath, }).then( function(res) { if (res.error) { - throw formatEsprimaError(res.error, filePath, sourceCode); + throw formatError(res.error, filePath, sourceCode); } return { @@ -117,11 +112,26 @@ Transformer.prototype.loadFileAndTransform = function( function TransformError() {} util.inherits(TransformError, SyntaxError); -function formatEsprimaError(err, filename, source) { - if (!(err.lineNumber && err.column)) { - return err; +function formatError(err, filename, source) { + if (err.lineNumber && err.column) { + return formatEsprimaError(err, filename, source); + } else { + return formatGenericError(err, filename, source); } +} +function formatGenericError(err, filename) { + var msg = 'TransformError: ' + filename + ': ' + err.message; + var error = new TransformError(); + var stack = err.stack.split('\n').slice(0, -1); + stack.push(msg); + error.stack = stack.join('\n'); + error.message = msg; + error.type = 'TransformError'; + return error; +} + +function formatEsprimaError(err, filename, source) { var stack = err.stack.split('\n'); stack.shift(); diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index d269eb57..74a2f437 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -1,10 +1,6 @@ 'use strict'; -jest - .dontMock('underscore') - .dontMock('../base64-vlq') - .dontMock('source-map') - .dontMock('../Package'); +jest.autoMockOff(); var SourceMapGenerator = require('source-map').SourceMapGenerator; diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 21af12ca..2e43e91a 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -49,7 +49,7 @@ describe('Packager', function() { }); require('../../JSTransformer').prototype.loadFileAndTransform - .mockImpl(function(tsets, path) { + .mockImpl(function(path) { return q({ code: 'transformed ' + path, sourceCode: 'source ' + path, diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 42295acc..123a3913 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -126,9 +126,7 @@ Packager.prototype.getDependencies = function(main) { Packager.prototype._transformModule = function(module) { var resolver = this._resolver; return this._transformer.loadFileAndTransform( - ['es6'], - path.resolve(module.path), - this._opts.transformer || {} + path.resolve(module.path) ).then(function(transformed) { return _.extend( {}, diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index a9951f60..e6020c79 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -3,9 +3,13 @@ jest.setMock('worker-farm', function() { return function() {}; }) .dontMock('q') .dontMock('os') - .dontMock('errno/custom') .dontMock('path') .dontMock('url') + .setMock('timers', { + setImmediate: function(fn) { + return setTimeout(fn, 0); + } + }) .dontMock('../'); var q = require('q'); @@ -75,16 +79,16 @@ describe('processRequest', function() { }); pit('returns sourcemap on request of *.map', function() { - makeRequest( + return makeRequest( requestHandler, 'mybundle.includeRequire.runModule.bundle.map' ).then(function(response) { - expect(response).toEqual('this is the source map'); + expect(response).toEqual('"this is the source map"'); }); }); pit('watches all files in projectRoot', function() { - makeRequest( + return makeRequest( requestHandler, 'mybundle.includeRequire.runModule.bundle' ).then(function(response) { @@ -107,7 +111,7 @@ describe('processRequest', function() { }); pit('invalides files in package when file is updated', function() { - makeRequest( + return makeRequest( requestHandler, 'mybundle.includeRequire.runModule.bundle' ).then(function(response) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 14b18c96..accce205 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -6,6 +6,7 @@ var declareOpts = require('../lib/declareOpts'); var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); +var setImmediate = require('timers').setImmediate; var q = require('q'); module.exports = Server; diff --git a/transformer.js b/transformer.js index ffcb80e2..acb586d7 100644 --- a/transformer.js +++ b/transformer.js @@ -16,10 +16,11 @@ var staticTypeSyntax = var visitorList = reactVisitors; -function transform(transformSets, srcTxt) { +function transform(srcTxt, filename) { var options = { es3: true, - sourceType: 'nonStrictModule' + sourceType: 'nonStrictModule', + filename: filename, }; // These tranforms mostly just erase type annotations and static typing @@ -42,8 +43,8 @@ module.exports = function(data, callback) { var result; try { result = transform( - data.transformSets, - data.sourceCode + data.sourceCode, + data.filename ); } catch (e) { return callback(null, { From ee00675dab9c28c687128b300e0fd90d069bf5ce Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 2 Mar 2015 20:46:45 -0800 Subject: [PATCH 029/936] [react-packager] Add dev option to CLI | James Ide Summary: Exposes the dev option that is already there to the CLI so that you can turn off invariant checks, etc. I also made it omit the inlined source map when dev=false which made it a lot faster to run on a phone, both due to smaller download size and fewer bytes to copy from Obj-C to JS and evaluate. Closes https://github.com/facebook/react-native/pull/112 Github Author: James Ide Test Plan: * ./runJestTests.sh * test bundle creation with `bundle.sh` * test `load_dependencies.js` script * start the server and click around shell app --- packager.js | 6 +++++- react-packager/src/Packager/Package.js | 17 +++++++++++------ .../src/Packager/__tests__/Package-test.js | 4 ++-- react-packager/src/Server/index.js | 7 +++++-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packager.js b/packager.js index 6d5336ef..08bab38b 100644 --- a/packager.js +++ b/packager.js @@ -29,6 +29,10 @@ var options = parseCommandLine([{ }, { command: 'root', description: 'add another root(s) to be used by the packager in this project', +}, { + command: 'dev', + default: true, + description: 'produce development packages with extra warnings enabled', }]); if (!options.projectRoots) { @@ -93,7 +97,7 @@ function openStackFrameInEditor(req, res, next) { function getAppMiddleware(options) { return ReactPackager.middleware({ - dev: true, + dev: options.dev, projectRoots: options.projectRoots, blacklistRE: blacklist(false), cacheVersion: '2', diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index a4080d3b..cc0c3292 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -40,12 +40,17 @@ Package.prototype.finalize = function(options) { Object.seal(this._modules); }; -Package.prototype.getSource = function() { - return this._source || ( - this._source = _.pluck(this._modules, 'transformedCode').join('\n') + '\n' + - 'RAW_SOURCE_MAP = ' + JSON.stringify(this.getSourceMap({excludeSource: true})) + - ';\n' + '\/\/@ sourceMappingURL=' + this._sourceMapUrl - ); +Package.prototype.getSource = function(options) { + if (!this._source) { + options = options || {}; + this._source = _.pluck(this._modules, 'transformedCode').join('\n'); + if (options.inlineSourceMap) { + var sourceMap = this.getSourceMap({excludeSource: true}); + this._source += '\nRAW_SOURCE_MAP = ' + JSON.stringify(sourceMap) + ';'; + } + this._source += '\n\/\/@ sourceMappingURL=' + this._sourceMapUrl; + } + return this._source; }; Package.prototype.getSourceMap = function(options) { diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index 74a2f437..572516c0 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -21,7 +21,7 @@ describe('Package', function() { ppackage.addModule('transformed foo;', 'source foo', 'foo path'); ppackage.addModule('transformed bar;', 'source bar', 'bar path'); ppackage.finalize({}); - expect(ppackage.getSource()).toBe([ + expect(ppackage.getSource({inlineSourceMap: true})).toBe([ 'transformed foo;', 'transformed bar;', 'RAW_SOURCE_MAP = "test-source-map";', @@ -34,7 +34,7 @@ describe('Package', function() { ppackage.addModule('transformed bar;', 'source bar', 'bar path'); ppackage.setMainModuleId('foo'); ppackage.finalize({runMainModule: true}); - expect(ppackage.getSource()).toBe([ + expect(ppackage.getSource({inlineSourceMap: true})).toBe([ 'transformed foo;', 'transformed bar;', ';require("foo");', diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index accce205..83592fea 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -51,6 +51,7 @@ var validateOpts = declareOpts({ function Server(options) { var opts = validateOpts(options); + this._dev = opts.dev; this._projectRoots = opts.projectRoots; this._packages = Object.create(null); this._packager = new Packager(opts); @@ -72,13 +73,14 @@ Server.prototype._onFileChange = function(type, filepath, root) { }; Server.prototype._rebuildPackages = function() { + var dev = this._dev; var buildPackage = this._buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { var options = getOptionsFromPath(url.parse(key).pathname); packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. - p.getSource(); + p.getSource({inlineSourceMap: dev}); return p; }); }); @@ -159,10 +161,11 @@ Server.prototype.processRequest = function(req, res, next) { var options = getOptionsFromPath(url.parse(req.url).pathname); var building = this._packages[req.url] || this._buildPackage(options); this._packages[req.url] = building; + var dev = this._dev; building.then( function(p) { if (requestType === 'bundle') { - res.end(p.getSource()); + res.end(p.getSource({inlineSourceMap: dev})); Activity.endEvent(startReqEventId); } else if (requestType === 'map') { res.end(JSON.stringify(p.getSourceMap())); From c2898441440824454e07f3787eb8f72e6d133529 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 2 Mar 2015 22:52:17 -0800 Subject: [PATCH 030/936] [react-packager] Implement bundle minification --- react-packager/src/Packager/Package.js | 34 +++++++++++++++++++ .../src/Packager/__tests__/Package-test.js | 15 ++++++++ 2 files changed, 49 insertions(+) diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index cc0c3292..3ef9c528 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -2,6 +2,7 @@ var _ = require('underscore'); var base64VLQ = require('./base64-vlq'); +var UglifyJS = require('uglify-js'); module.exports = Package; @@ -27,6 +28,7 @@ Package.prototype.addModule = function( }; Package.prototype.finalize = function(options) { + options = options || {}; if (options.runMainModule) { var runCode = ';require("' + this._mainModuleId + '");'; this.addModule( @@ -53,6 +55,38 @@ Package.prototype.getSource = function(options) { return this._source; }; +Package.prototype.getMinifiedSourceAndMap = function() { + var source = this.getSource({inlineSourceMap: false}); + 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); + } +}; + Package.prototype.getSourceMap = function(options) { options = options || {}; var mappings = this._getMappings(); diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index 572516c0..41630fc4 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -42,6 +42,21 @@ describe('Package', function() { '\/\/@ sourceMappingURL=test_url', ].join('\n')); }); + + it('should get minified source', function() { + var minified = { + code: 'minified', + map: 'map', + }; + + require('uglify-js').minify = function() { + return minified; + }; + + ppackage.addModule('transformed foo;', 'source foo', 'foo path'); + ppackage.finalize(); + expect(ppackage.getMinifiedSourceAndMap()).toBe(minified); + }); }); describe('sourcemap package', function() { From 37cab152b593d37c2768ee4b77323f35c60e97f0 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 3 Mar 2015 01:52:38 -0800 Subject: [PATCH 031/936] [react-packager] check-in node_modules and update tests --- react-packager/src/Packager/__tests__/Packager-test.js | 1 + react-packager/src/Server/__tests__/Server-test.js | 1 + 2 files changed, 2 insertions(+) diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 2e43e91a..d8348be8 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -6,6 +6,7 @@ jest .dontMock('q') .dontMock('os') .dontMock('underscore') + .setMock('uglify-js') .dontMock('../'); var q = require('q'); diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index e6020c79..5a5d62b0 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -10,6 +10,7 @@ jest.setMock('worker-farm', function() { return function() {}; }) return setTimeout(fn, 0); } }) + .setMock('uglify-js') .dontMock('../'); var q = require('q'); From f76e0b593d4d92d1ec4d0fcf3b760eb5015579dc Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Tue, 3 Mar 2015 08:38:50 -0800 Subject: [PATCH 032/936] Updates from Tuesday, March 3rd - [ReactNative] Fix OSS Projects | Spencer Ahrens - [react-packager] check-in node_modules and update tests | Amjad Masad - [react-packager] Cleanup package.json | Amjad Masad - [react-packager] Implement bundle minification | Amjad Masad - [react-packager] Add dev option to CLI | James Ide | Amjad Masad - [react-packager] Add uglify-js library | Amjad Masad - [f8] Make map zoomable on double-tap | Alex Kotliarskyi --- packager.js | 6 ++- react-packager/src/Packager/Package.js | 51 ++++++++++++++++--- .../src/Packager/__tests__/Package-test.js | 19 ++++++- .../src/Packager/__tests__/Packager-test.js | 1 + .../src/Server/__tests__/Server-test.js | 1 + react-packager/src/Server/index.js | 7 ++- 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/packager.js b/packager.js index 6d5336ef..08bab38b 100644 --- a/packager.js +++ b/packager.js @@ -29,6 +29,10 @@ var options = parseCommandLine([{ }, { command: 'root', description: 'add another root(s) to be used by the packager in this project', +}, { + command: 'dev', + default: true, + description: 'produce development packages with extra warnings enabled', }]); if (!options.projectRoots) { @@ -93,7 +97,7 @@ function openStackFrameInEditor(req, res, next) { function getAppMiddleware(options) { return ReactPackager.middleware({ - dev: true, + dev: options.dev, projectRoots: options.projectRoots, blacklistRE: blacklist(false), cacheVersion: '2', diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index a4080d3b..3ef9c528 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -2,6 +2,7 @@ var _ = require('underscore'); var base64VLQ = require('./base64-vlq'); +var UglifyJS = require('uglify-js'); module.exports = Package; @@ -27,6 +28,7 @@ Package.prototype.addModule = function( }; Package.prototype.finalize = function(options) { + options = options || {}; if (options.runMainModule) { var runCode = ';require("' + this._mainModuleId + '");'; this.addModule( @@ -40,12 +42,49 @@ Package.prototype.finalize = function(options) { Object.seal(this._modules); }; -Package.prototype.getSource = function() { - return this._source || ( - this._source = _.pluck(this._modules, 'transformedCode').join('\n') + '\n' + - 'RAW_SOURCE_MAP = ' + JSON.stringify(this.getSourceMap({excludeSource: true})) + - ';\n' + '\/\/@ sourceMappingURL=' + this._sourceMapUrl - ); +Package.prototype.getSource = function(options) { + if (!this._source) { + options = options || {}; + this._source = _.pluck(this._modules, 'transformedCode').join('\n'); + if (options.inlineSourceMap) { + var sourceMap = this.getSourceMap({excludeSource: true}); + this._source += '\nRAW_SOURCE_MAP = ' + JSON.stringify(sourceMap) + ';'; + } + this._source += '\n\/\/@ sourceMappingURL=' + this._sourceMapUrl; + } + return this._source; +}; + +Package.prototype.getMinifiedSourceAndMap = function() { + var source = this.getSource({inlineSourceMap: false}); + 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); + } }; Package.prototype.getSourceMap = function(options) { diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index 74a2f437..41630fc4 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -21,7 +21,7 @@ describe('Package', function() { ppackage.addModule('transformed foo;', 'source foo', 'foo path'); ppackage.addModule('transformed bar;', 'source bar', 'bar path'); ppackage.finalize({}); - expect(ppackage.getSource()).toBe([ + expect(ppackage.getSource({inlineSourceMap: true})).toBe([ 'transformed foo;', 'transformed bar;', 'RAW_SOURCE_MAP = "test-source-map";', @@ -34,7 +34,7 @@ describe('Package', function() { ppackage.addModule('transformed bar;', 'source bar', 'bar path'); ppackage.setMainModuleId('foo'); ppackage.finalize({runMainModule: true}); - expect(ppackage.getSource()).toBe([ + expect(ppackage.getSource({inlineSourceMap: true})).toBe([ 'transformed foo;', 'transformed bar;', ';require("foo");', @@ -42,6 +42,21 @@ describe('Package', function() { '\/\/@ sourceMappingURL=test_url', ].join('\n')); }); + + it('should get minified source', function() { + var minified = { + code: 'minified', + map: 'map', + }; + + require('uglify-js').minify = function() { + return minified; + }; + + ppackage.addModule('transformed foo;', 'source foo', 'foo path'); + ppackage.finalize(); + expect(ppackage.getMinifiedSourceAndMap()).toBe(minified); + }); }); describe('sourcemap package', function() { diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 2e43e91a..d8348be8 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -6,6 +6,7 @@ jest .dontMock('q') .dontMock('os') .dontMock('underscore') + .setMock('uglify-js') .dontMock('../'); var q = require('q'); diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index e6020c79..5a5d62b0 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -10,6 +10,7 @@ jest.setMock('worker-farm', function() { return function() {}; }) return setTimeout(fn, 0); } }) + .setMock('uglify-js') .dontMock('../'); var q = require('q'); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index accce205..83592fea 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -51,6 +51,7 @@ var validateOpts = declareOpts({ function Server(options) { var opts = validateOpts(options); + this._dev = opts.dev; this._projectRoots = opts.projectRoots; this._packages = Object.create(null); this._packager = new Packager(opts); @@ -72,13 +73,14 @@ Server.prototype._onFileChange = function(type, filepath, root) { }; Server.prototype._rebuildPackages = function() { + var dev = this._dev; var buildPackage = this._buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { var options = getOptionsFromPath(url.parse(key).pathname); packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. - p.getSource(); + p.getSource({inlineSourceMap: dev}); return p; }); }); @@ -159,10 +161,11 @@ Server.prototype.processRequest = function(req, res, next) { var options = getOptionsFromPath(url.parse(req.url).pathname); var building = this._packages[req.url] || this._buildPackage(options); this._packages[req.url] = building; + var dev = this._dev; building.then( function(p) { if (requestType === 'bundle') { - res.end(p.getSource()); + res.end(p.getSource({inlineSourceMap: dev})); Activity.endEvent(startReqEventId); } else if (requestType === 'map') { res.end(JSON.stringify(p.getSourceMap())); From 41743e5987c21162efb813bb55f4f4d8877da61b Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 3 Mar 2015 17:22:21 -0800 Subject: [PATCH 033/936] [react-packager] Recover and warn from corrupted cache file --- react-packager/src/JSTransformer/Cache.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index f43418e3..b9c5b8c1 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -112,13 +112,23 @@ Cache.prototype._persistCache = function() { return this._persisting; }; -function loadCacheSync(cacheFilepath) { +function loadCacheSync(cachePath) { var ret = Object.create(null); - if (!fs.existsSync(cacheFilepath)) { + if (!fs.existsSync(cachePath)) { return ret; } - var cacheOnDisk = JSON.parse(fs.readFileSync(cacheFilepath)); + var cacheOnDisk; + try { + cacheOnDisk = JSON.parse(fs.readFileSync(cachePath)); + } catch (e) { + if (e instanceof SyntaxError) { + console.warn('Unable to parse cache file. Will clear and continue.'); + fs.unlinkSync(cachePath); + return ret; + } + throw e; + } // Filter outdated cache and convert to promises. Object.keys(cacheOnDisk).forEach(function(key) { From 164c3ea712d9484994abfff21a0d935ea25c0c7e Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 4 Mar 2015 08:05:38 -0800 Subject: [PATCH 034/936] Updates from Wed Mar 4 - [ReactNative] modernize DatePicker | Spencer Ahrens - React Native: Force !ExecutionEnvironment.canUseDOM | Tim Yung - [react-packager] Recover and warn from corrupted cache file | Amjad Masad - [ReactNative] Github repo's gitignore is written at export | Amjad Masad - [ReactNative] Use strings instead of constants for keyboardDismissMode | Christopher Chedeau --- react-packager/src/JSTransformer/Cache.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index f43418e3..b9c5b8c1 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -112,13 +112,23 @@ Cache.prototype._persistCache = function() { return this._persisting; }; -function loadCacheSync(cacheFilepath) { +function loadCacheSync(cachePath) { var ret = Object.create(null); - if (!fs.existsSync(cacheFilepath)) { + if (!fs.existsSync(cachePath)) { return ret; } - var cacheOnDisk = JSON.parse(fs.readFileSync(cacheFilepath)); + var cacheOnDisk; + try { + cacheOnDisk = JSON.parse(fs.readFileSync(cachePath)); + } catch (e) { + if (e instanceof SyntaxError) { + console.warn('Unable to parse cache file. Will clear and continue.'); + fs.unlinkSync(cachePath); + return ret; + } + throw e; + } // Filter outdated cache and convert to promises. Object.keys(cacheOnDisk).forEach(function(key) { From 5bd8155f0bb0486b5da2149cc6ba8874389ce899 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 4 Mar 2015 18:37:32 -0800 Subject: [PATCH 035/936] [react-packager] Start converting options to query params --- .../src/Server/__tests__/Server-test.js | 25 +++++++---- react-packager/src/Server/index.js | 44 ++++++++++++------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 5a5d62b0..f55df7c2 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -73,7 +73,16 @@ describe('processRequest', function() { pit('returns JS bundle source on request of *.bundle',function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle' + 'mybundle.bundle?runModule=true' + ).then(function(response) { + expect(response).toEqual('this is the source'); + }); + }); + + pit('returns JS bundle source on request of *.bundle (compat)',function() { + return makeRequest( + requestHandler, + 'mybundle.runModule.bundle' ).then(function(response) { expect(response).toEqual('this is the source'); }); @@ -82,7 +91,7 @@ describe('processRequest', function() { pit('returns sourcemap on request of *.map', function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle.map' + 'mybundle.map?runModule=true' ).then(function(response) { expect(response).toEqual('"this is the source map"'); }); @@ -91,8 +100,8 @@ describe('processRequest', function() { pit('watches all files in projectRoot', function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle' - ).then(function(response) { + 'mybundle.bundle?runModule=true' + ).then(function() { expect(watcherFunc.mock.calls[0][0]).toEqual('all'); expect(watcherFunc.mock.calls[0][1]).not.toBe(null); }); @@ -114,8 +123,8 @@ describe('processRequest', function() { pit('invalides files in package when file is updated', function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle' - ).then(function(response) { + 'mybundle.bundle?runModule=true' + ).then(function() { var 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'); @@ -150,7 +159,7 @@ describe('processRequest', function() { requestHandler = server.processRequest.bind(server); - return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + 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); @@ -159,7 +168,7 @@ describe('processRequest', function() { }) .then(function() { expect(packageFunc.mock.calls.length).toBe(2); - return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + return makeRequest(requestHandler, 'mybundle.bundle?runModule=true') .then(function(response) { expect(response).toEqual('this is the rebuilt source'); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 83592fea..40635721 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -77,7 +77,7 @@ Server.prototype._rebuildPackages = function() { var buildPackage = this._buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { - var options = getOptionsFromPath(url.parse(key).pathname); + var options = getOptionsFromUrl(key); packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({inlineSourceMap: dev}); @@ -102,7 +102,7 @@ Server.prototype._buildPackage = function(options) { }; Server.prototype.buildPackageFromUrl = function(reqUrl) { - var options = getOptionsFromPath(url.parse(reqUrl).pathname); + var options = getOptionsFromUrl(reqUrl); return this._buildPackage(options); }; @@ -145,21 +145,26 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { }; Server.prototype.processRequest = function(req, res, next) { + var urlObj = url.parse(req.url, true); + var pathname = urlObj.pathname; + var requestType; - if (req.url.match(/\.bundle$/)) { + if (pathname.match(/\.bundle$/)) { requestType = 'bundle'; - } else if (req.url.match(/\.map$/)) { + } else if (pathname.match(/\.map$/)) { requestType = 'map'; - } else if (req.url.match(/^\/debug/)) { + } else if (pathname.match(/^\/debug/)) { this._processDebugRequest(req.url, res); return; } else { - return next(); + next(); + return; } var startReqEventId = Activity.startEvent('request:' + req.url); - var options = getOptionsFromPath(url.parse(req.url).pathname); + var options = getOptionsFromUrl(req.url); var building = this._packages[req.url] || this._buildPackage(options); + this._packages[req.url] = building; var dev = this._dev; building.then( @@ -178,16 +183,25 @@ Server.prototype.processRequest = function(req, res, next) { ).done(); }; -function getOptionsFromPath(pathname) { - var parts = pathname.split('.'); - // Remove the leading slash. - var main = parts[0].slice(1) + '.js'; +function getOptionsFromUrl(reqUrl) { + // `true` to parse the query param as an object. + var urlObj = url.parse(reqUrl, true); + + var match = urlObj.pathname.match(/^\/?([^\.]+)\..*(bundle|map)$/); + if (!(match && match[1])) { + throw new Error('Invalid url format, expected "/path/to/file.bundle"'); + } + var main = match[1] + '.js'; + return { - runModule: parts.slice(1).some(function(part) { - return part === 'runModule'; - }), + sourceMapUrl: urlObj.pathname.replace(/\.bundle$/, '.map'), main: main, - sourceMapUrl: parts.slice(0, -1).join('.') + '.map' + runModule: urlObj.query.runModule === 'true' || + urlObj.query.runModule === '1' || + // Backwards compatibility. + urlObj.pathname.split('.').some(function(part) { + return part === 'runModule'; + }), }; } From 7485aaf5fd13321a793b4af4dac919c1a229f441 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 4 Mar 2015 21:06:49 -0800 Subject: [PATCH 036/936] Updates from Wed Mar 4 - [react-packager] Start converting options to query params | Amjad Masad - [ReactNative] Replace js long constants with strings | Tadeu Zagallo - React Native: Remove Unnecessary `document.body` Shim | Tim Yung - [ReactNative] Use spread operator and .propTypes for ScrollView/ListView | Christopher Chedeau --- .../src/Server/__tests__/Server-test.js | 25 +++++++---- react-packager/src/Server/index.js | 44 ++++++++++++------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 5a5d62b0..f55df7c2 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -73,7 +73,16 @@ describe('processRequest', function() { pit('returns JS bundle source on request of *.bundle',function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle' + 'mybundle.bundle?runModule=true' + ).then(function(response) { + expect(response).toEqual('this is the source'); + }); + }); + + pit('returns JS bundle source on request of *.bundle (compat)',function() { + return makeRequest( + requestHandler, + 'mybundle.runModule.bundle' ).then(function(response) { expect(response).toEqual('this is the source'); }); @@ -82,7 +91,7 @@ describe('processRequest', function() { pit('returns sourcemap on request of *.map', function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle.map' + 'mybundle.map?runModule=true' ).then(function(response) { expect(response).toEqual('"this is the source map"'); }); @@ -91,8 +100,8 @@ describe('processRequest', function() { pit('watches all files in projectRoot', function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle' - ).then(function(response) { + 'mybundle.bundle?runModule=true' + ).then(function() { expect(watcherFunc.mock.calls[0][0]).toEqual('all'); expect(watcherFunc.mock.calls[0][1]).not.toBe(null); }); @@ -114,8 +123,8 @@ describe('processRequest', function() { pit('invalides files in package when file is updated', function() { return makeRequest( requestHandler, - 'mybundle.includeRequire.runModule.bundle' - ).then(function(response) { + 'mybundle.bundle?runModule=true' + ).then(function() { var 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'); @@ -150,7 +159,7 @@ describe('processRequest', function() { requestHandler = server.processRequest.bind(server); - return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + 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); @@ -159,7 +168,7 @@ describe('processRequest', function() { }) .then(function() { expect(packageFunc.mock.calls.length).toBe(2); - return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle') + return makeRequest(requestHandler, 'mybundle.bundle?runModule=true') .then(function(response) { expect(response).toEqual('this is the rebuilt source'); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 83592fea..40635721 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -77,7 +77,7 @@ Server.prototype._rebuildPackages = function() { var buildPackage = this._buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { - var options = getOptionsFromPath(url.parse(key).pathname); + var options = getOptionsFromUrl(key); packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({inlineSourceMap: dev}); @@ -102,7 +102,7 @@ Server.prototype._buildPackage = function(options) { }; Server.prototype.buildPackageFromUrl = function(reqUrl) { - var options = getOptionsFromPath(url.parse(reqUrl).pathname); + var options = getOptionsFromUrl(reqUrl); return this._buildPackage(options); }; @@ -145,21 +145,26 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { }; Server.prototype.processRequest = function(req, res, next) { + var urlObj = url.parse(req.url, true); + var pathname = urlObj.pathname; + var requestType; - if (req.url.match(/\.bundle$/)) { + if (pathname.match(/\.bundle$/)) { requestType = 'bundle'; - } else if (req.url.match(/\.map$/)) { + } else if (pathname.match(/\.map$/)) { requestType = 'map'; - } else if (req.url.match(/^\/debug/)) { + } else if (pathname.match(/^\/debug/)) { this._processDebugRequest(req.url, res); return; } else { - return next(); + next(); + return; } var startReqEventId = Activity.startEvent('request:' + req.url); - var options = getOptionsFromPath(url.parse(req.url).pathname); + var options = getOptionsFromUrl(req.url); var building = this._packages[req.url] || this._buildPackage(options); + this._packages[req.url] = building; var dev = this._dev; building.then( @@ -178,16 +183,25 @@ Server.prototype.processRequest = function(req, res, next) { ).done(); }; -function getOptionsFromPath(pathname) { - var parts = pathname.split('.'); - // Remove the leading slash. - var main = parts[0].slice(1) + '.js'; +function getOptionsFromUrl(reqUrl) { + // `true` to parse the query param as an object. + var urlObj = url.parse(reqUrl, true); + + var match = urlObj.pathname.match(/^\/?([^\.]+)\..*(bundle|map)$/); + if (!(match && match[1])) { + throw new Error('Invalid url format, expected "/path/to/file.bundle"'); + } + var main = match[1] + '.js'; + return { - runModule: parts.slice(1).some(function(part) { - return part === 'runModule'; - }), + sourceMapUrl: urlObj.pathname.replace(/\.bundle$/, '.map'), main: main, - sourceMapUrl: parts.slice(0, -1).join('.') + '.map' + runModule: urlObj.query.runModule === 'true' || + urlObj.query.runModule === '1' || + // Backwards compatibility. + urlObj.pathname.split('.').some(function(part) { + return part === 'runModule'; + }), }; } From a4d041ce625717d3e7b44891b450f1be05b299d1 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 4 Mar 2015 20:56:56 -0800 Subject: [PATCH 037/936] [react-packager] Make dev a query param option --- packager.js | 7 +- .../__tests__/HasteDependencyResolver-test.js | 10 +-- .../src/DependencyResolver/haste/index.js | 68 ++++++++++--------- react-packager/src/JSTransformer/index.js | 4 -- react-packager/src/Packager/index.js | 14 ++-- react-packager/src/Server/index.js | 18 ++--- react-packager/src/lib/declareOpts.js | 2 + 7 files changed, 53 insertions(+), 70 deletions(-) diff --git a/packager.js b/packager.js index 08bab38b..ca4e5c67 100644 --- a/packager.js +++ b/packager.js @@ -29,10 +29,6 @@ var options = parseCommandLine([{ }, { command: 'root', description: 'add another root(s) to be used by the packager in this project', -}, { - command: 'dev', - default: true, - description: 'produce development packages with extra warnings enabled', }]); if (!options.projectRoots) { @@ -97,7 +93,6 @@ function openStackFrameInEditor(req, res, next) { function getAppMiddleware(options) { return ReactPackager.middleware({ - dev: options.dev, projectRoots: options.projectRoots, blacklistRE: blacklist(false), cacheVersion: '2', @@ -106,7 +101,7 @@ function getAppMiddleware(options) { } function runServer( - options, /* {string projectRoot, bool web, bool dev} */ + options, /* {[]string projectRoot, bool web} */ readyCallback ) { var app = connect() diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 9704c5b5..b25fd821 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -24,7 +24,6 @@ describe('HasteDependencyResolver', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', - dev: false, }); // Is there a better way? How can I mock the prototype instead? @@ -36,7 +35,7 @@ describe('HasteDependencyResolver', function() { return q(); }); - return depResolver.getDependencies('/root/index.js') + return depResolver.getDependencies('/root/index.js', { dev: false }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies).toEqual([ @@ -85,7 +84,6 @@ describe('HasteDependencyResolver', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', - dev: true, }); // Is there a better way? How can I mock the prototype instead? @@ -97,7 +95,7 @@ describe('HasteDependencyResolver', function() { return q(); }); - return depResolver.getDependencies('/root/index.js') + return depResolver.getDependencies('/root/index.js', { dev: true }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies).toEqual([ @@ -147,7 +145,6 @@ describe('HasteDependencyResolver', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', polyfillModuleNames: ['some module'], - dev: false, }); // Is there a better way? How can I mock the prototype instead? @@ -159,7 +156,7 @@ describe('HasteDependencyResolver', function() { return q(); }); - return depResolver.getDependencies('/root/index.js') + return depResolver.getDependencies('/root/index.js', { dev: false }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies).toEqual([ @@ -218,7 +215,6 @@ describe('HasteDependencyResolver', function() { it('should ', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', - dev: false, }); var depGraph = depResolver._depGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 9cb0661a..6aada00b 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -32,10 +32,6 @@ var validateOpts = declareOpts({ type: 'array', default: [], }, - dev: { - type: 'boolean', - default: true, - }, nonPersistent: { type: 'boolean', default: false, @@ -62,20 +58,20 @@ function HasteDependencyResolver(options) { fileWatcher: this._fileWatcher }); - this._polyfillModuleNames = [ - opts.dev - ? path.join(__dirname, 'polyfills/prelude_dev.js') - : path.join(__dirname, 'polyfills/prelude.js'), - path.join(__dirname, 'polyfills/require.js'), - path.join(__dirname, 'polyfills/polyfills.js'), - path.join(__dirname, 'polyfills/console.js'), - path.join(__dirname, 'polyfills/error-guard.js'), - ].concat( - opts.polyfillModuleNames || [] - ); + + this._polyfillModuleNames = opts.polyfillModuleNames || []; } -HasteDependencyResolver.prototype.getDependencies = function(main) { +var getDependenciesValidateOpts = declareOpts({ + dev: { + type: 'boolean', + default: true, + }, +}); + +HasteDependencyResolver.prototype.getDependencies = function(main, options) { + var opts = getDependenciesValidateOpts(options); + var depGraph = this._depGraph; var self = this; @@ -84,7 +80,7 @@ HasteDependencyResolver.prototype.getDependencies = function(main) { var dependencies = depGraph.getOrderedDependencies(main); var mainModuleId = dependencies[0].id; - self._prependPolyfillDependencies(dependencies); + self._prependPolyfillDependencies(dependencies, opts.dev); return { mainModuleId: mainModuleId, @@ -94,22 +90,30 @@ HasteDependencyResolver.prototype.getDependencies = function(main) { }; HasteDependencyResolver.prototype._prependPolyfillDependencies = function( - dependencies + dependencies, + isDev ) { - var polyfillModuleNames = this._polyfillModuleNames; - if (polyfillModuleNames.length > 0) { - var polyfillModules = polyfillModuleNames.map( - function(polyfillModuleName, idx) { - return new ModuleDescriptor({ - path: polyfillModuleName, - id: polyfillModuleName, - dependencies: polyfillModuleNames.slice(0, idx), - isPolyfill: true - }); - } - ); - dependencies.unshift.apply(dependencies, polyfillModules); - } + var polyfillModuleNames = [ + isDev + ? path.join(__dirname, 'polyfills/prelude_dev.js') + : path.join(__dirname, 'polyfills/prelude.js'), + path.join(__dirname, 'polyfills/require.js'), + path.join(__dirname, 'polyfills/polyfills.js'), + path.join(__dirname, 'polyfills/console.js'), + path.join(__dirname, 'polyfills/error-guard.js'), + ].concat(this._polyfillModuleNames); + + var polyfillModules = polyfillModuleNames.map( + function(polyfillModuleName, idx) { + return new ModuleDescriptor({ + path: polyfillModuleName, + id: polyfillModuleName, + dependencies: polyfillModuleNames.slice(0, idx), + isPolyfill: true + }); + } + ); + dependencies.unshift.apply(dependencies, polyfillModules); }; HasteDependencyResolver.prototype.wrapModule = function(module, code) { diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 87cb6e1a..35785e6e 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -34,10 +34,6 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, - dev: { - type: 'boolean', - default: true, - }, transformModulePath: { type:'string', required: false, diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 123a3913..75cccdb2 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -36,10 +36,6 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, - dev: { - type: 'boolean', - default: true, - }, transformModulePath: { type:'string', required: false, @@ -59,7 +55,6 @@ function Packager(options) { projectRoots: opts.projectRoots, blacklistRE: opts.blacklistRE, polyfillModuleNames: opts.polyfillModuleNames, - dev: opts.dev, nonPersistent: opts.nonPersistent, moduleFormat: opts.moduleFormat }); @@ -69,7 +64,6 @@ function Packager(options) { blacklistRE: opts.blacklistRE, cacheVersion: opts.cacheVersion, resetCache: opts.resetCache, - dev: opts.dev, transformModulePath: opts.transformModulePath, nonPersistent: opts.nonPersistent, }); @@ -82,14 +76,14 @@ Packager.prototype.kill = function() { ]); }; -Packager.prototype.package = function(main, runModule, sourceMapUrl) { +Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { var transformModule = this._transformModule.bind(this); var ppackage = new Package(sourceMapUrl); var findEventId = Activity.startEvent('find dependencies'); var transformEventId; - return this.getDependencies(main) + return this.getDependencies(main, isDev) .then(function(result) { Activity.endEvent(findEventId); transformEventId = Activity.startEvent('transform'); @@ -119,8 +113,8 @@ Packager.prototype.invalidateFile = function(filePath) { this._transformer.invalidateFile(filePath); }; -Packager.prototype.getDependencies = function(main) { - return this._resolver.getDependencies(main); +Packager.prototype.getDependencies = function(main, isDev) { + return this._resolver.getDependencies(main, { dev: isDev }); }; Packager.prototype._transformModule = function(module) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 40635721..54eb8e22 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -35,10 +35,6 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, - dev: { - type: 'boolean', - default: true, - }, transformModulePath: { type:'string', required: false, @@ -51,7 +47,6 @@ var validateOpts = declareOpts({ function Server(options) { var opts = validateOpts(options); - this._dev = opts.dev; this._projectRoots = opts.projectRoots; this._packages = Object.create(null); this._packager = new Packager(opts); @@ -73,14 +68,13 @@ Server.prototype._onFileChange = function(type, filepath, root) { }; Server.prototype._rebuildPackages = function() { - var dev = this._dev; var buildPackage = this._buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { var options = getOptionsFromUrl(key); packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. - p.getSource({inlineSourceMap: dev}); + p.getSource({inlineSourceMap: options.dev}); return p; }); }); @@ -97,7 +91,8 @@ Server.prototype._buildPackage = function(options) { return this._packager.package( options.main, options.runModule, - options.sourceMapUrl + options.sourceMapUrl, + options.dev ); }; @@ -166,11 +161,10 @@ Server.prototype.processRequest = function(req, res, next) { var building = this._packages[req.url] || this._buildPackage(options); this._packages[req.url] = building; - var dev = this._dev; - building.then( + building.then( function(p) { if (requestType === 'bundle') { - res.end(p.getSource({inlineSourceMap: dev})); + res.end(p.getSource({inlineSourceMap: options.dev})); Activity.endEvent(startReqEventId); } else if (requestType === 'map') { res.end(JSON.stringify(p.getSourceMap())); @@ -196,6 +190,8 @@ function getOptionsFromUrl(reqUrl) { return { sourceMapUrl: urlObj.pathname.replace(/\.bundle$/, '.map'), main: main, + dev: urlObj.query.dev === 'true' || + urlObj.query.dev === '1', runModule: urlObj.query.runModule === 'true' || urlObj.query.runModule === '1' || // Backwards compatibility. diff --git a/react-packager/src/lib/declareOpts.js b/react-packager/src/lib/declareOpts.js index ddd06061..3b80da51 100644 --- a/react-packager/src/lib/declareOpts.js +++ b/react-packager/src/lib/declareOpts.js @@ -42,6 +42,8 @@ module.exports = function(descriptor) { var schema = Joi.object().keys(joiKeys); return function(opts) { + opts = opts || {}; + var res = Joi.validate(opts, schema, { abortEarly: true, allowUnknown: false, From 3d92dd422c49ffd67daef473e3c92c34846ed18f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 5 Mar 2015 11:45:10 -0800 Subject: [PATCH 038/936] [react-packager] Add minify option as query param --- react-packager/src/Packager/Package.js | 55 +++++++++++++++++++++----- react-packager/src/Server/index.js | 21 +++++++--- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 3ef9c528..5d9b201c 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -7,6 +7,7 @@ var UglifyJS = require('uglify-js'); module.exports = Package; function Package(sourceMapUrl) { + this._finalized = false; this._modules = []; this._sourceMapUrl = sourceMapUrl; } @@ -40,23 +41,56 @@ Package.prototype.finalize = function(options) { Object.freeze(this._modules); Object.seal(this._modules); + this._finalized = true; }; -Package.prototype.getSource = function(options) { - if (!this._source) { - options = options || {}; +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, 'transformedCode').join('\n'); - if (options.inlineSourceMap) { - var sourceMap = this.getSourceMap({excludeSource: true}); - this._source += '\nRAW_SOURCE_MAP = ' + JSON.stringify(sourceMap) + ';'; - } - this._source += '\n\/\/@ sourceMappingURL=' + this._sourceMapUrl; } return this._source; }; +Package.prototype._getInlineSourceMap = function() { + if (this._inlineSourceMap == null) { + var sourceMap = this.getSourceMap({excludeSource: true}); + this._inlineSourceMap = '\nRAW_SOURCE_MAP = ' + + JSON.stringify(sourceMap) + ';'; + } + + 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 += this._getInlineSourceMap(); + } + + source += '\n\/\/@ sourceMappingURL=' + this._sourceMapUrl; + + return source; +}; + Package.prototype.getMinifiedSourceAndMap = function() { - var source = this.getSource({inlineSourceMap: false}); + this._assertFinalized(); + + var source = this._getSource(); try { return UglifyJS.minify(source, { fromString: true, @@ -88,6 +122,8 @@ Package.prototype.getMinifiedSourceAndMap = function() { }; Package.prototype.getSourceMap = function(options) { + this._assertFinalized(); + options = options || {}; var mappings = this._getMappings(); var map = { @@ -102,7 +138,6 @@ Package.prototype.getSourceMap = function(options) { return map; }; - Package.prototype._getMappings = function() { var modules = this._modules; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 54eb8e22..053aa033 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -74,7 +74,10 @@ Server.prototype._rebuildPackages = function() { var options = getOptionsFromUrl(key); packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. - p.getSource({inlineSourceMap: options.dev}); + p.getSource({ + inlineSourceMap: options.dev, + minify: options.minify, + }); return p; }); }); @@ -164,7 +167,10 @@ Server.prototype.processRequest = function(req, res, next) { building.then( function(p) { if (requestType === 'bundle') { - res.end(p.getSource({inlineSourceMap: options.dev})); + res.end(p.getSource({ + inlineSourceMap: options.dev, + minify: options.minify, + })); Activity.endEvent(startReqEventId); } else if (requestType === 'map') { res.end(JSON.stringify(p.getSourceMap())); @@ -190,10 +196,9 @@ function getOptionsFromUrl(reqUrl) { return { sourceMapUrl: urlObj.pathname.replace(/\.bundle$/, '.map'), main: main, - dev: urlObj.query.dev === 'true' || - urlObj.query.dev === '1', - runModule: urlObj.query.runModule === 'true' || - urlObj.query.runModule === '1' || + dev: getBoolOptionFromQuery(urlObj.query, 'dev'), + minify: getBoolOptionFromQuery(urlObj.query, 'minify'), + runModule: getBoolOptionFromQuery(urlObj.query, 'runModule') || // Backwards compatibility. urlObj.pathname.split('.').some(function(part) { return part === 'runModule'; @@ -201,6 +206,10 @@ function getOptionsFromUrl(reqUrl) { }; } +function getBoolOptionFromQuery(query, opt) { + return query[opt] === 'true' || query[opt] === '1'; +} + function handleError(res, error) { res.writeHead(500, { 'Content-Type': 'application/json; charset=UTF-8', From 806e82795ae229625a19a56b2d0e908cd6f925c6 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Fri, 6 Mar 2015 09:54:10 -0800 Subject: [PATCH 039/936] Updates from Thu Mar 5 - [react_native] JS files for D1885531 | Martin Konicek - Ported TabBarIOS to OSS and unified implementation | Nick Lockwood - [react-packager] Add minify option as query param | Amjad Masad - [ReactNative] Fix ExpandingText prop types | Christopher Chedeau - [react-packager] Make dev a query param option | Amjad Masad --- packager.js | 7 +- .../__tests__/HasteDependencyResolver-test.js | 10 +-- .../src/DependencyResolver/haste/index.js | 68 ++++++++++--------- react-packager/src/JSTransformer/index.js | 4 -- react-packager/src/Packager/Package.js | 55 ++++++++++++--- react-packager/src/Packager/index.js | 14 ++-- react-packager/src/Server/index.js | 31 +++++---- react-packager/src/lib/declareOpts.js | 2 + 8 files changed, 109 insertions(+), 82 deletions(-) diff --git a/packager.js b/packager.js index 08bab38b..ca4e5c67 100644 --- a/packager.js +++ b/packager.js @@ -29,10 +29,6 @@ var options = parseCommandLine([{ }, { command: 'root', description: 'add another root(s) to be used by the packager in this project', -}, { - command: 'dev', - default: true, - description: 'produce development packages with extra warnings enabled', }]); if (!options.projectRoots) { @@ -97,7 +93,6 @@ function openStackFrameInEditor(req, res, next) { function getAppMiddleware(options) { return ReactPackager.middleware({ - dev: options.dev, projectRoots: options.projectRoots, blacklistRE: blacklist(false), cacheVersion: '2', @@ -106,7 +101,7 @@ function getAppMiddleware(options) { } function runServer( - options, /* {string projectRoot, bool web, bool dev} */ + options, /* {[]string projectRoot, bool web} */ readyCallback ) { var app = connect() diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 9704c5b5..b25fd821 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -24,7 +24,6 @@ describe('HasteDependencyResolver', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', - dev: false, }); // Is there a better way? How can I mock the prototype instead? @@ -36,7 +35,7 @@ describe('HasteDependencyResolver', function() { return q(); }); - return depResolver.getDependencies('/root/index.js') + return depResolver.getDependencies('/root/index.js', { dev: false }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies).toEqual([ @@ -85,7 +84,6 @@ describe('HasteDependencyResolver', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', - dev: true, }); // Is there a better way? How can I mock the prototype instead? @@ -97,7 +95,7 @@ describe('HasteDependencyResolver', function() { return q(); }); - return depResolver.getDependencies('/root/index.js') + return depResolver.getDependencies('/root/index.js', { dev: true }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies).toEqual([ @@ -147,7 +145,6 @@ describe('HasteDependencyResolver', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', polyfillModuleNames: ['some module'], - dev: false, }); // Is there a better way? How can I mock the prototype instead? @@ -159,7 +156,7 @@ describe('HasteDependencyResolver', function() { return q(); }); - return depResolver.getDependencies('/root/index.js') + return depResolver.getDependencies('/root/index.js', { dev: false }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies).toEqual([ @@ -218,7 +215,6 @@ describe('HasteDependencyResolver', function() { it('should ', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', - dev: false, }); var depGraph = depResolver._depGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 9cb0661a..6aada00b 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -32,10 +32,6 @@ var validateOpts = declareOpts({ type: 'array', default: [], }, - dev: { - type: 'boolean', - default: true, - }, nonPersistent: { type: 'boolean', default: false, @@ -62,20 +58,20 @@ function HasteDependencyResolver(options) { fileWatcher: this._fileWatcher }); - this._polyfillModuleNames = [ - opts.dev - ? path.join(__dirname, 'polyfills/prelude_dev.js') - : path.join(__dirname, 'polyfills/prelude.js'), - path.join(__dirname, 'polyfills/require.js'), - path.join(__dirname, 'polyfills/polyfills.js'), - path.join(__dirname, 'polyfills/console.js'), - path.join(__dirname, 'polyfills/error-guard.js'), - ].concat( - opts.polyfillModuleNames || [] - ); + + this._polyfillModuleNames = opts.polyfillModuleNames || []; } -HasteDependencyResolver.prototype.getDependencies = function(main) { +var getDependenciesValidateOpts = declareOpts({ + dev: { + type: 'boolean', + default: true, + }, +}); + +HasteDependencyResolver.prototype.getDependencies = function(main, options) { + var opts = getDependenciesValidateOpts(options); + var depGraph = this._depGraph; var self = this; @@ -84,7 +80,7 @@ HasteDependencyResolver.prototype.getDependencies = function(main) { var dependencies = depGraph.getOrderedDependencies(main); var mainModuleId = dependencies[0].id; - self._prependPolyfillDependencies(dependencies); + self._prependPolyfillDependencies(dependencies, opts.dev); return { mainModuleId: mainModuleId, @@ -94,22 +90,30 @@ HasteDependencyResolver.prototype.getDependencies = function(main) { }; HasteDependencyResolver.prototype._prependPolyfillDependencies = function( - dependencies + dependencies, + isDev ) { - var polyfillModuleNames = this._polyfillModuleNames; - if (polyfillModuleNames.length > 0) { - var polyfillModules = polyfillModuleNames.map( - function(polyfillModuleName, idx) { - return new ModuleDescriptor({ - path: polyfillModuleName, - id: polyfillModuleName, - dependencies: polyfillModuleNames.slice(0, idx), - isPolyfill: true - }); - } - ); - dependencies.unshift.apply(dependencies, polyfillModules); - } + var polyfillModuleNames = [ + isDev + ? path.join(__dirname, 'polyfills/prelude_dev.js') + : path.join(__dirname, 'polyfills/prelude.js'), + path.join(__dirname, 'polyfills/require.js'), + path.join(__dirname, 'polyfills/polyfills.js'), + path.join(__dirname, 'polyfills/console.js'), + path.join(__dirname, 'polyfills/error-guard.js'), + ].concat(this._polyfillModuleNames); + + var polyfillModules = polyfillModuleNames.map( + function(polyfillModuleName, idx) { + return new ModuleDescriptor({ + path: polyfillModuleName, + id: polyfillModuleName, + dependencies: polyfillModuleNames.slice(0, idx), + isPolyfill: true + }); + } + ); + dependencies.unshift.apply(dependencies, polyfillModules); }; HasteDependencyResolver.prototype.wrapModule = function(module, code) { diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 87cb6e1a..35785e6e 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -34,10 +34,6 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, - dev: { - type: 'boolean', - default: true, - }, transformModulePath: { type:'string', required: false, diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 3ef9c528..5d9b201c 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -7,6 +7,7 @@ var UglifyJS = require('uglify-js'); module.exports = Package; function Package(sourceMapUrl) { + this._finalized = false; this._modules = []; this._sourceMapUrl = sourceMapUrl; } @@ -40,23 +41,56 @@ Package.prototype.finalize = function(options) { Object.freeze(this._modules); Object.seal(this._modules); + this._finalized = true; }; -Package.prototype.getSource = function(options) { - if (!this._source) { - options = options || {}; +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, 'transformedCode').join('\n'); - if (options.inlineSourceMap) { - var sourceMap = this.getSourceMap({excludeSource: true}); - this._source += '\nRAW_SOURCE_MAP = ' + JSON.stringify(sourceMap) + ';'; - } - this._source += '\n\/\/@ sourceMappingURL=' + this._sourceMapUrl; } return this._source; }; +Package.prototype._getInlineSourceMap = function() { + if (this._inlineSourceMap == null) { + var sourceMap = this.getSourceMap({excludeSource: true}); + this._inlineSourceMap = '\nRAW_SOURCE_MAP = ' + + JSON.stringify(sourceMap) + ';'; + } + + 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 += this._getInlineSourceMap(); + } + + source += '\n\/\/@ sourceMappingURL=' + this._sourceMapUrl; + + return source; +}; + Package.prototype.getMinifiedSourceAndMap = function() { - var source = this.getSource({inlineSourceMap: false}); + this._assertFinalized(); + + var source = this._getSource(); try { return UglifyJS.minify(source, { fromString: true, @@ -88,6 +122,8 @@ Package.prototype.getMinifiedSourceAndMap = function() { }; Package.prototype.getSourceMap = function(options) { + this._assertFinalized(); + options = options || {}; var mappings = this._getMappings(); var map = { @@ -102,7 +138,6 @@ Package.prototype.getSourceMap = function(options) { return map; }; - Package.prototype._getMappings = function() { var modules = this._modules; diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 123a3913..75cccdb2 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -36,10 +36,6 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, - dev: { - type: 'boolean', - default: true, - }, transformModulePath: { type:'string', required: false, @@ -59,7 +55,6 @@ function Packager(options) { projectRoots: opts.projectRoots, blacklistRE: opts.blacklistRE, polyfillModuleNames: opts.polyfillModuleNames, - dev: opts.dev, nonPersistent: opts.nonPersistent, moduleFormat: opts.moduleFormat }); @@ -69,7 +64,6 @@ function Packager(options) { blacklistRE: opts.blacklistRE, cacheVersion: opts.cacheVersion, resetCache: opts.resetCache, - dev: opts.dev, transformModulePath: opts.transformModulePath, nonPersistent: opts.nonPersistent, }); @@ -82,14 +76,14 @@ Packager.prototype.kill = function() { ]); }; -Packager.prototype.package = function(main, runModule, sourceMapUrl) { +Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { var transformModule = this._transformModule.bind(this); var ppackage = new Package(sourceMapUrl); var findEventId = Activity.startEvent('find dependencies'); var transformEventId; - return this.getDependencies(main) + return this.getDependencies(main, isDev) .then(function(result) { Activity.endEvent(findEventId); transformEventId = Activity.startEvent('transform'); @@ -119,8 +113,8 @@ Packager.prototype.invalidateFile = function(filePath) { this._transformer.invalidateFile(filePath); }; -Packager.prototype.getDependencies = function(main) { - return this._resolver.getDependencies(main); +Packager.prototype.getDependencies = function(main, isDev) { + return this._resolver.getDependencies(main, { dev: isDev }); }; Packager.prototype._transformModule = function(module) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 40635721..053aa033 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -35,10 +35,6 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, - dev: { - type: 'boolean', - default: true, - }, transformModulePath: { type:'string', required: false, @@ -51,7 +47,6 @@ var validateOpts = declareOpts({ function Server(options) { var opts = validateOpts(options); - this._dev = opts.dev; this._projectRoots = opts.projectRoots; this._packages = Object.create(null); this._packager = new Packager(opts); @@ -73,14 +68,16 @@ Server.prototype._onFileChange = function(type, filepath, root) { }; Server.prototype._rebuildPackages = function() { - var dev = this._dev; var buildPackage = this._buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { var options = getOptionsFromUrl(key); packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. - p.getSource({inlineSourceMap: dev}); + p.getSource({ + inlineSourceMap: options.dev, + minify: options.minify, + }); return p; }); }); @@ -97,7 +94,8 @@ Server.prototype._buildPackage = function(options) { return this._packager.package( options.main, options.runModule, - options.sourceMapUrl + options.sourceMapUrl, + options.dev ); }; @@ -166,11 +164,13 @@ Server.prototype.processRequest = function(req, res, next) { var building = this._packages[req.url] || this._buildPackage(options); this._packages[req.url] = building; - var dev = this._dev; - building.then( + building.then( function(p) { if (requestType === 'bundle') { - res.end(p.getSource({inlineSourceMap: dev})); + res.end(p.getSource({ + inlineSourceMap: options.dev, + minify: options.minify, + })); Activity.endEvent(startReqEventId); } else if (requestType === 'map') { res.end(JSON.stringify(p.getSourceMap())); @@ -196,8 +196,9 @@ function getOptionsFromUrl(reqUrl) { return { sourceMapUrl: urlObj.pathname.replace(/\.bundle$/, '.map'), main: main, - runModule: urlObj.query.runModule === 'true' || - urlObj.query.runModule === '1' || + dev: getBoolOptionFromQuery(urlObj.query, 'dev'), + minify: getBoolOptionFromQuery(urlObj.query, 'minify'), + runModule: getBoolOptionFromQuery(urlObj.query, 'runModule') || // Backwards compatibility. urlObj.pathname.split('.').some(function(part) { return part === 'runModule'; @@ -205,6 +206,10 @@ function getOptionsFromUrl(reqUrl) { }; } +function getBoolOptionFromQuery(query, opt) { + return query[opt] === 'true' || query[opt] === '1'; +} + function handleError(res, error) { res.writeHead(500, { 'Content-Type': 'application/json; charset=UTF-8', diff --git a/react-packager/src/lib/declareOpts.js b/react-packager/src/lib/declareOpts.js index ddd06061..3b80da51 100644 --- a/react-packager/src/lib/declareOpts.js +++ b/react-packager/src/lib/declareOpts.js @@ -42,6 +42,8 @@ module.exports = function(descriptor) { var schema = Joi.object().keys(joiKeys); return function(opts) { + opts = opts || {}; + var res = Joi.validate(opts, schema, { abortEarly: true, allowUnknown: false, From 50220f1384c8a1688d90e9408c631191d65b0896 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 6 Mar 2015 14:11:18 -0800 Subject: [PATCH 040/936] [react-packager] dev option needs to default to true for backwards compat --- react-packager/src/Server/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 053aa033..a918662e 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -196,7 +196,7 @@ function getOptionsFromUrl(reqUrl) { return { sourceMapUrl: urlObj.pathname.replace(/\.bundle$/, '.map'), main: main, - dev: getBoolOptionFromQuery(urlObj.query, 'dev'), + dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), minify: getBoolOptionFromQuery(urlObj.query, 'minify'), runModule: getBoolOptionFromQuery(urlObj.query, 'runModule') || // Backwards compatibility. @@ -206,7 +206,11 @@ function getOptionsFromUrl(reqUrl) { }; } -function getBoolOptionFromQuery(query, opt) { +function getBoolOptionFromQuery(query, opt, defaultVal) { + if (query[opt] == null && defaultVal != null) { + return defaultVal; + } + return query[opt] === 'true' || query[opt] === '1'; } From 7874698b6b520bd12b7d6613921424ecac072b20 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 6 Mar 2015 14:26:01 -0800 Subject: [PATCH 041/936] [react-packager] onchange endpoint that informs of changes --- .../src/Server/__tests__/Server-test.js | 54 ++++++++++++++----- react-packager/src/Server/index.js | 37 +++++++++++++ 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index f55df7c2..71b7e400 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -45,6 +45,7 @@ describe('processRequest', function() { var invalidatorFunc = jest.genMockFunction(); var watcherFunc = jest.genMockFunction(); var requestHandler; + var triggerFileChange; beforeEach(function() { Packager = require('../../Packager'); @@ -61,7 +62,15 @@ describe('processRequest', function() { }); }; - FileWatcher.prototype.on = watcherFunc; + + FileWatcher.prototype.on = function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + watcherFunc.apply(this, arguments); + triggerFileChange = callback; + return this; + }; Packager.prototype.invalidateFile = invalidatorFunc; @@ -109,17 +118,6 @@ describe('processRequest', function() { describe('file changes', function() { - var triggerFileChange; - beforeEach(function() { - FileWatcher.prototype.on = function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - triggerFileChange = callback; - return this; - }; - }); - pit('invalides files in package when file is updated', function() { return makeRequest( requestHandler, @@ -175,4 +173,36 @@ describe('processRequest', function() { }); }); }); + + describe('/onchange endpoint', function() { + var EventEmitter; + var req; + var res; + + beforeEach(function() { + EventEmitter = require.requireActual('events').EventEmitter; + req = new EventEmitter(); + req.url = '/onchange'; + res = { + writeHead: jest.genMockFn(), + end: jest.genMockFn() + }; + }); + + it('should hold on to request and inform on change', function() { + 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() { + server.processRequest(req, res); + req.emit('close'); + jest.runAllTimers(); + triggerFileChange('all', 'path/file.js', options.projectRoots[0]); + jest.runAllTimers(); + expect(res.end).not.toBeCalled(); + }); + }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index a918662e..8982bc91 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -50,6 +50,7 @@ function Server(options) { this._projectRoots = opts.projectRoots; this._packages = Object.create(null); this._packager = new Packager(opts); + this._changeWatchers = []; this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() @@ -65,6 +66,7 @@ Server.prototype._onFileChange = function(type, filepath, root) { // Make sure the file watcher event runs through the system before // we rebuild the packages. setImmediate(this._rebuildPackages.bind(this, absPath)); + setImmediate(this._informChangeWatchers.bind(this)); }; Server.prototype._rebuildPackages = function() { @@ -83,6 +85,20 @@ Server.prototype._rebuildPackages = function() { }); }; +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() { q.all([ this._fileWatcher.end(), @@ -142,6 +158,24 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { } }; +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; + } + } + }); +}; + Server.prototype.processRequest = function(req, res, next) { var urlObj = url.parse(req.url, true); var pathname = urlObj.pathname; @@ -154,6 +188,9 @@ Server.prototype.processRequest = function(req, res, next) { } else if (pathname.match(/^\/debug/)) { this._processDebugRequest(req.url, res); return; + } else if (pathname.match(/^\/onchange\/?$/)) { + this._processOnChangeRequest(req, res); + return; } else { next(); return; From 70d748bf6443aad18ce38e2e2544b18964c82008 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Fri, 6 Mar 2015 16:29:59 -0800 Subject: [PATCH 042/936] [React Native] Update core modules for React 0.13 --- blacklist.js | 1 - 1 file changed, 1 deletion(-) diff --git a/blacklist.js b/blacklist.js index 2b710af6..eb81f452 100644 --- a/blacklist.js +++ b/blacklist.js @@ -22,7 +22,6 @@ var webBlacklist = [ var iosBlacklist = [ 'node_modules/react-tools/src/browser/ui/React.js', 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', - 'node_modules/react-tools/src/browser/ReactTextComponent.js', // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.android.js', From a854ce37c12135e07d6989261abb12ed7b364317 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Fri, 6 Mar 2015 17:42:07 -0800 Subject: [PATCH 043/936] Minor packager updates Splitting this out from the next commit for clarity. --- .../src/Server/__tests__/Server-test.js | 54 ++++++++++++++----- react-packager/src/Server/index.js | 45 +++++++++++++++- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index f55df7c2..71b7e400 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -45,6 +45,7 @@ describe('processRequest', function() { var invalidatorFunc = jest.genMockFunction(); var watcherFunc = jest.genMockFunction(); var requestHandler; + var triggerFileChange; beforeEach(function() { Packager = require('../../Packager'); @@ -61,7 +62,15 @@ describe('processRequest', function() { }); }; - FileWatcher.prototype.on = watcherFunc; + + FileWatcher.prototype.on = function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + watcherFunc.apply(this, arguments); + triggerFileChange = callback; + return this; + }; Packager.prototype.invalidateFile = invalidatorFunc; @@ -109,17 +118,6 @@ describe('processRequest', function() { describe('file changes', function() { - var triggerFileChange; - beforeEach(function() { - FileWatcher.prototype.on = function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - triggerFileChange = callback; - return this; - }; - }); - pit('invalides files in package when file is updated', function() { return makeRequest( requestHandler, @@ -175,4 +173,36 @@ describe('processRequest', function() { }); }); }); + + describe('/onchange endpoint', function() { + var EventEmitter; + var req; + var res; + + beforeEach(function() { + EventEmitter = require.requireActual('events').EventEmitter; + req = new EventEmitter(); + req.url = '/onchange'; + res = { + writeHead: jest.genMockFn(), + end: jest.genMockFn() + }; + }); + + it('should hold on to request and inform on change', function() { + 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() { + server.processRequest(req, res); + req.emit('close'); + jest.runAllTimers(); + triggerFileChange('all', 'path/file.js', options.projectRoots[0]); + jest.runAllTimers(); + expect(res.end).not.toBeCalled(); + }); + }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 053aa033..8982bc91 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -50,6 +50,7 @@ function Server(options) { this._projectRoots = opts.projectRoots; this._packages = Object.create(null); this._packager = new Packager(opts); + this._changeWatchers = []; this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() @@ -65,6 +66,7 @@ Server.prototype._onFileChange = function(type, filepath, root) { // Make sure the file watcher event runs through the system before // we rebuild the packages. setImmediate(this._rebuildPackages.bind(this, absPath)); + setImmediate(this._informChangeWatchers.bind(this)); }; Server.prototype._rebuildPackages = function() { @@ -83,6 +85,20 @@ Server.prototype._rebuildPackages = function() { }); }; +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() { q.all([ this._fileWatcher.end(), @@ -142,6 +158,24 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { } }; +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; + } + } + }); +}; + Server.prototype.processRequest = function(req, res, next) { var urlObj = url.parse(req.url, true); var pathname = urlObj.pathname; @@ -154,6 +188,9 @@ Server.prototype.processRequest = function(req, res, next) { } else if (pathname.match(/^\/debug/)) { this._processDebugRequest(req.url, res); return; + } else if (pathname.match(/^\/onchange\/?$/)) { + this._processOnChangeRequest(req, res); + return; } else { next(); return; @@ -196,7 +233,7 @@ function getOptionsFromUrl(reqUrl) { return { sourceMapUrl: urlObj.pathname.replace(/\.bundle$/, '.map'), main: main, - dev: getBoolOptionFromQuery(urlObj.query, 'dev'), + dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), minify: getBoolOptionFromQuery(urlObj.query, 'minify'), runModule: getBoolOptionFromQuery(urlObj.query, 'runModule') || // Backwards compatibility. @@ -206,7 +243,11 @@ function getOptionsFromUrl(reqUrl) { }; } -function getBoolOptionFromQuery(query, opt) { +function getBoolOptionFromQuery(query, opt, defaultVal) { + if (query[opt] == null && defaultVal != null) { + return defaultVal; + } + return query[opt] === 'true' || query[opt] === '1'; } From 3cf0f2aa6f31a1e440e68537c989029ba6b599ea Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Sun, 8 Mar 2015 22:39:23 -0700 Subject: [PATCH 044/936] Upgrade to React 0.13 - [React Native] Move copyProperties and mergeHelpers to github dir | Ben Alpert - [React Native] Update core modules for React 0.13 | Ben Alpert - [React Native] Update React to v0.13.0-rc2 | Ben Alpert --- blacklist.js | 1 - 1 file changed, 1 deletion(-) diff --git a/blacklist.js b/blacklist.js index 2b710af6..eb81f452 100644 --- a/blacklist.js +++ b/blacklist.js @@ -22,7 +22,6 @@ var webBlacklist = [ var iosBlacklist = [ 'node_modules/react-tools/src/browser/ui/React.js', 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', - 'node_modules/react-tools/src/browser/ReactTextComponent.js', // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.android.js', From 449ed5854698cc7bb15d4f79a7a069a6f3900578 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 11 Mar 2015 17:44:49 -0700 Subject: [PATCH 045/936] [react-packager] package.json cleanup (seperate packager into it's own package) --- blacklist.js | 10 ++++------ package.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 package.json diff --git a/blacklist.js b/blacklist.js index eb81f452..468a6040 100644 --- a/blacklist.js +++ b/blacklist.js @@ -6,13 +6,11 @@ // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ - 'node_modules/JSAppServer', - 'packager/react-packager', + __dirname, 'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js', 'node_modules/react-tools/src/utils/ImmutableObject.js', 'node_modules/react-tools/src/core/ReactInstanceHandles.js', - 'node_modules/react-tools/src/event/EventPropagators.js', - 'node_modules/jest-cli', + 'node_modules/react-tools/src/event/EventPropagators.js' ]; var webBlacklist = [ @@ -31,9 +29,9 @@ function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); } -function blacklist(isWeb) { +function blacklist(isWeb, additionalBlacklist) { return new RegExp('(' + - sharedBlacklist + (additionalBlacklist || []).concat(sharedBlacklist) .concat(isWeb ? webBlacklist : iosBlacklist) .map(escapeRegExp) .join('|') + diff --git a/package.json b/package.json new file mode 100644 index 00000000..6033c216 --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "react-native", + "version": "0.1.0", + "description": "Build native apps with React!", + "repository": { + "type": "git", + "url": "git@github.com:facebook/react-native.git" + }, + "jest": { + "setupEnvScriptFile": "jestSupport/env.js", + "testPathIgnorePatterns": [ + "/node_modules/" + ], + "testFileExtensions": [ + "js" + ], + "unmockedModulePathPatterns": [ + "source-map" + ] + }, + "scripts": { + "test": "jest", + "lint": "node linter.js Examples/", + "start": "./packager/packager.sh" + }, + "dependencies": { + "absolute-path": "0.0.0", + "debug": "~2.1.0", + "joi": "~5.1.0", + "module-deps": "3.5.6", + "optimist": "0.6.1", + "q": "1.0.1", + "sane": "1.0.1", + "source-map": "0.1.31", + "stacktrace-parser": "0.1.1", + "uglify-js": "~2.4.16", + "underscore": "1.7.0", + "worker-farm": "1.1.0", + "yargs": "1.3.2" + }, + "devDependencies": { + "jest-cli": "0.2.1", + "eslint": "0.9.2" + } +} From 63eca091b907e94315dac167b822ffe2fd3ca890 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 12 Mar 2015 12:51:44 -0700 Subject: [PATCH 046/936] Updates from Thu Mar 12 - Fixed sticky section headers in ListView | Nick Lockwood - [ReactNative] AppState cleanup, remove Subscribable, add in OSS examples | Eric Vicenti - [react-packager] package.json cleanup (seperate packager into it's own package) | Amjad Masad - [ReactNative] Move PushNotificationIOS to oss | Tadeu Zagallo - [ReactNative] Fix shake gesture after RedBox is dismissed | Alex Kotliarskyi - [catlyst|madman] fix prop type warning | Jiajie Zhu - [ReactNative] Remove Subscribable from TextInput | Eric Vicenti - Unforked ExceptionsManager, AlertManager and AppState | Nick Lockwood - [ReactNative|MAdMan] Notification Subscribable | Eric Vicenti - [ReactNative] OSS AsyncStorage with example | Spencer Ahrens --- blacklist.js | 10 ++++------ package.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 package.json diff --git a/blacklist.js b/blacklist.js index eb81f452..468a6040 100644 --- a/blacklist.js +++ b/blacklist.js @@ -6,13 +6,11 @@ // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ - 'node_modules/JSAppServer', - 'packager/react-packager', + __dirname, 'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js', 'node_modules/react-tools/src/utils/ImmutableObject.js', 'node_modules/react-tools/src/core/ReactInstanceHandles.js', - 'node_modules/react-tools/src/event/EventPropagators.js', - 'node_modules/jest-cli', + 'node_modules/react-tools/src/event/EventPropagators.js' ]; var webBlacklist = [ @@ -31,9 +29,9 @@ function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); } -function blacklist(isWeb) { +function blacklist(isWeb, additionalBlacklist) { return new RegExp('(' + - sharedBlacklist + (additionalBlacklist || []).concat(sharedBlacklist) .concat(isWeb ? webBlacklist : iosBlacklist) .map(escapeRegExp) .join('|') + diff --git a/package.json b/package.json new file mode 100644 index 00000000..6033c216 --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "react-native", + "version": "0.1.0", + "description": "Build native apps with React!", + "repository": { + "type": "git", + "url": "git@github.com:facebook/react-native.git" + }, + "jest": { + "setupEnvScriptFile": "jestSupport/env.js", + "testPathIgnorePatterns": [ + "/node_modules/" + ], + "testFileExtensions": [ + "js" + ], + "unmockedModulePathPatterns": [ + "source-map" + ] + }, + "scripts": { + "test": "jest", + "lint": "node linter.js Examples/", + "start": "./packager/packager.sh" + }, + "dependencies": { + "absolute-path": "0.0.0", + "debug": "~2.1.0", + "joi": "~5.1.0", + "module-deps": "3.5.6", + "optimist": "0.6.1", + "q": "1.0.1", + "sane": "1.0.1", + "source-map": "0.1.31", + "stacktrace-parser": "0.1.1", + "uglify-js": "~2.4.16", + "underscore": "1.7.0", + "worker-farm": "1.1.0", + "yargs": "1.3.2" + }, + "devDependencies": { + "jest-cli": "0.2.1", + "eslint": "0.9.2" + } +} From d2cf0de12e3f3c44b3aa653309bdd144551afaff Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 13 Mar 2015 16:42:18 -0700 Subject: [PATCH 047/936] [react-packager] Implement image loading i.e. ix('img') -> require('image!img'); --- .../DependencyResolver/ModuleDescriptor.js | 2 + .../__tests__/DependencyGraph-test.js | 34 +++++ .../haste/DependencyGraph/index.js | 136 ++++++++++++++---- .../src/DependencyResolver/haste/index.js | 8 +- .../src/Packager/__tests__/Packager-test.js | 14 ++ react-packager/src/Packager/index.js | 33 ++++- react-packager/src/Server/index.js | 4 + 7 files changed, 201 insertions(+), 30 deletions(-) diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index f1a30545..df29e57c 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -22,6 +22,8 @@ function ModuleDescriptor(fields) { this.isPolyfill = fields.isPolyfill || false; + this.isAsset = fields.isAsset || false; + this._fields = fields; } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index eb839296..6dee93ec 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -56,6 +56,40 @@ describe('DependencyGraph', function() { }); }); + pit('should get dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!a")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetRoots: ['/root/imgs'] + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['image!a']}, + { id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true + }, + ]); + }); + }); + pit('should get recursive dependencies', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index ce631856..122701d5 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -28,12 +28,22 @@ var validateOpts = declareOpts({ type: 'object', required: true, }, + assetRoots: { + type: 'array', + default: [], + }, + assetExts: { + type: 'array', + default: ['png'], + } }); function DependecyGraph(options) { var opts = validateOpts(options); this._roots = opts.roots; + this._assetRoots = opts.assetRoots; + this._assetExts = opts.assetExts; this._ignoreFilePath = opts.ignoreFilePath; this._fileWatcher = options.fileWatcher; @@ -50,7 +60,16 @@ function DependecyGraph(options) { } DependecyGraph.prototype.load = function() { - return this._loading || (this._loading = this._search()); + if (this._loading != null) { + return this._loading; + } + + this._loading = q.all([ + this._search(), + this._buildAssetMap(), + ]); + + return this._loading; }; /** @@ -115,6 +134,15 @@ DependecyGraph.prototype.resolveDependency = function( fromModule, depModuleId ) { + // Process asset requires. + var assetMatch = depModuleId.match(/^image!(.+)/); + if (assetMatch && assetMatch[1]) { + if (!this._assetMap[assetMatch[1]]) { + throw new Error('Cannot find asset: ' + assetMatch[1]); + } + return this._assetMap[assetMatch[1]]; + } + var packageJson, modulePath, dep; // Package relative modules starts with '.' or '..'. @@ -214,32 +242,13 @@ DependecyGraph.prototype._search = function() { // 2. Filter the files and queue up the directories. // 3. Process any package.json in the files // 4. recur. - return readDir(dir) - .then(function(files){ - return q.all(files.map(function(filePath) { - return realpath(path.join(dir, filePath)).catch(handleBrokenLink); - })); - }) - .then(function(filePaths) { - filePaths = filePaths.filter(function(filePath) { - if (filePath == null) { + return readAndStatDir(dir) + .spread(function(files, stats) { + var modulePaths = files.filter(function(filePath, i) { + if (self._ignoreFilePath(filePath)) { return false; } - return !self._ignoreFilePath(filePath); - }); - - var statsP = filePaths.map(function(filePath) { - return lstat(filePath).catch(handleBrokenLink); - }); - - return [ - filePaths, - q.all(statsP) - ]; - }) - .spread(function(files, stats) { - var modulePaths = files.filter(function(filePath, i) { if (stats[i].isDirectory()) { self._queue.push(filePath); return false; @@ -465,6 +474,19 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) { return null; }; +DependecyGraph.prototype._buildAssetMap = function() { + if (this._assetRoots == null || this._assetRoots.length === 0) { + return q(); + } + + var self = this; + return buildAssetMap(this._assetRoots, this._assetExts) + .then(function(map) { + self._assetMap = map; + return map; + }); +}; + /** * Extract all required modules from a `code` string. */ @@ -511,4 +533,70 @@ function handleBrokenLink(e) { return q(); } +function readAndStatDir(dir) { + return readDir(dir) + .then(function(files){ + return q.all(files.map(function(filePath) { + return realpath(path.join(dir, filePath)).catch(handleBrokenLink); + })); + }).then(function(files) { + files = files.filter(function(f) { + return !!f; + }); + + var stats = files.map(function(filePath) { + return lstat(filePath).catch(handleBrokenLink); + }); + + return [ + files, + q.all(stats), + ]; + }); +} + +/** + * Given a list of roots and list of extensions find all the files in + * the directory with that extension and build a map of those assets. + */ +function buildAssetMap(roots, exts) { + var queue = roots.slice(0); + var map = Object.create(null); + + function search() { + var root = queue.shift(); + + if (root == null) { + return q(map); + } + + return readAndStatDir(root).spread(function(files, stats) { + files.forEach(function(file, i) { + if (stats[i].isDirectory()) { + queue.push(file); + } else { + var ext = path.extname(file).replace(/^\./, ''); + if (exts.indexOf(ext) !== -1) { + var assetName = path.basename(file, '.' + ext); + if (map[assetName] != null) { + debug('Conflcting assets', assetName); + } + + map[assetName] = new ModuleDescriptor({ + id: 'image!' + assetName, + path: path.resolve(file), + isAsset: true, + dependencies: [], + }); + } + } + }); + + return search(); + }); + } + + return search(); +} + module.exports = DependecyGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 6aada00b..fdc779ed 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -17,7 +17,6 @@ var DEFINE_MODULE_CODE = [ ].join(''); var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; - var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; var validateOpts = declareOpts({ @@ -40,6 +39,10 @@ var validateOpts = declareOpts({ type: 'string', default: 'haste', }, + assetRoots: { + type: 'array', + default: [], + }, }); function HasteDependencyResolver(options) { @@ -51,11 +54,12 @@ function HasteDependencyResolver(options) { this._depGraph = new DependencyGraph({ roots: opts.projectRoots, + assetRoots: opts.assetRoots, ignoreFilePath: function(filepath) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, - fileWatcher: this._fileWatcher + fileWatcher: this._fileWatcher, }); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index d8348be8..498faea3 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -40,6 +40,11 @@ describe('Packager', function() { var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, + { id: 'image!img', + path: '/root/img/img.png', + isAsset: true, + dependencies: [], + } ]; getDependencies.mockImpl(function() { @@ -74,6 +79,15 @@ describe('Packager', function() { 'source /root/bar.js', '/root/bar.js' ]); + expect(p.addModule.mock.calls[2]).toEqual([ + 'lol module.exports = ' + + JSON.stringify({ uri: 'img', isStatic: true}) + + '; lol', + 'module.exports = ' + + JSON.stringify({ uri: 'img', isStatic: true}) + + ';', + '/root/img/img.png' + ]); expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 75cccdb2..c21889b4 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -44,6 +44,10 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, + assetRoots: { + type: 'array', + required: false, + }, }); function Packager(options) { @@ -56,7 +60,8 @@ function Packager(options) { blacklistRE: opts.blacklistRE, polyfillModuleNames: opts.polyfillModuleNames, nonPersistent: opts.nonPersistent, - moduleFormat: opts.moduleFormat + moduleFormat: opts.moduleFormat, + assetRoots: opts.assetRoots, }); this._transformer = new Transformer({ @@ -118,10 +123,18 @@ Packager.prototype.getDependencies = function(main, isDev) { }; Packager.prototype._transformModule = function(module) { + var transform; + + if (module.isAsset) { + transform = q(generateAssetModule(module)); + } else { + transform = this._transformer.loadFileAndTransform( + path.resolve(module.path) + ); + } + var resolver = this._resolver; - return this._transformer.loadFileAndTransform( - path.resolve(module.path) - ).then(function(transformed) { + return transform.then(function(transformed) { return _.extend( {}, transformed, @@ -140,5 +153,17 @@ Packager.prototype.getGraphDebugInfo = function() { return this._resolver.getDebugInfo(); }; +function generateAssetModule(module) { + var code = 'module.exports = ' + JSON.stringify({ + uri: module.id.replace(/^[^!]+!/, ''), + isStatic: true, + }) + ';'; + + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; +} module.exports = Packager; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 8982bc91..7df686ad 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -43,6 +43,10 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, + assetRoots: { + type: 'array', + required: false, + }, }); function Server(options) { From 6d7464efd5385c46adb787fdb28a57a8b97fa8e9 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Sat, 14 Mar 2015 11:35:55 -0700 Subject: [PATCH 048/936] [Tests] Fix FileWatcher Need to use `pit` instead of `it`. I changed .toBe(false) instead of .toBe(true) just to make sure that the content is actually executed --- react-packager/src/FileWatcher/__tests__/FileWatcher-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index fc8a7a41..11b9f4a3 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -20,7 +20,7 @@ describe('FileWatcher', function() { }); }); - it('it should get the watcher instance when ready', function() { + pit('it should get the watcher instance when ready', function() { var fileWatcher = new FileWatcher(['rootDir']); return fileWatcher._loading.then(function(watchers) { watchers.forEach(function(watcher) { From ba40b0c49b480810cc1cc678e774f1de5c39a7f4 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Sat, 14 Mar 2015 10:52:40 -0700 Subject: [PATCH 049/936] Updates from Sat 14 Mar - Unforked RKWebView | Nick Lockwood - [ReactNative] Add integration test stuff | Spencer Ahrens - [ReactNative] AlertIOS.alert and examples | Eric Vicenti - [react-packager] Implement image loading i.e. ix('img') -> require('image!img'); | Amjad Masad - Fixed scrollOffset bug | Nick Lockwood - [React Native] Update 2048 | Alex Akers - deepDiffer should support explicitly undefined values | Thomas Aylott --- .../DependencyResolver/ModuleDescriptor.js | 2 + .../__tests__/DependencyGraph-test.js | 34 +++++ .../haste/DependencyGraph/index.js | 136 ++++++++++++++---- .../src/DependencyResolver/haste/index.js | 8 +- .../src/Packager/__tests__/Packager-test.js | 14 ++ react-packager/src/Packager/index.js | 33 ++++- react-packager/src/Server/index.js | 4 + 7 files changed, 201 insertions(+), 30 deletions(-) diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index f1a30545..df29e57c 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -22,6 +22,8 @@ function ModuleDescriptor(fields) { this.isPolyfill = fields.isPolyfill || false; + this.isAsset = fields.isAsset || false; + this._fields = fields; } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index eb839296..6dee93ec 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -56,6 +56,40 @@ describe('DependencyGraph', function() { }); }); + pit('should get dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!a")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetRoots: ['/root/imgs'] + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', path: '/root/index.js', dependencies: ['image!a']}, + { id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true + }, + ]); + }); + }); + pit('should get recursive dependencies', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index ce631856..122701d5 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -28,12 +28,22 @@ var validateOpts = declareOpts({ type: 'object', required: true, }, + assetRoots: { + type: 'array', + default: [], + }, + assetExts: { + type: 'array', + default: ['png'], + } }); function DependecyGraph(options) { var opts = validateOpts(options); this._roots = opts.roots; + this._assetRoots = opts.assetRoots; + this._assetExts = opts.assetExts; this._ignoreFilePath = opts.ignoreFilePath; this._fileWatcher = options.fileWatcher; @@ -50,7 +60,16 @@ function DependecyGraph(options) { } DependecyGraph.prototype.load = function() { - return this._loading || (this._loading = this._search()); + if (this._loading != null) { + return this._loading; + } + + this._loading = q.all([ + this._search(), + this._buildAssetMap(), + ]); + + return this._loading; }; /** @@ -115,6 +134,15 @@ DependecyGraph.prototype.resolveDependency = function( fromModule, depModuleId ) { + // Process asset requires. + var assetMatch = depModuleId.match(/^image!(.+)/); + if (assetMatch && assetMatch[1]) { + if (!this._assetMap[assetMatch[1]]) { + throw new Error('Cannot find asset: ' + assetMatch[1]); + } + return this._assetMap[assetMatch[1]]; + } + var packageJson, modulePath, dep; // Package relative modules starts with '.' or '..'. @@ -214,32 +242,13 @@ DependecyGraph.prototype._search = function() { // 2. Filter the files and queue up the directories. // 3. Process any package.json in the files // 4. recur. - return readDir(dir) - .then(function(files){ - return q.all(files.map(function(filePath) { - return realpath(path.join(dir, filePath)).catch(handleBrokenLink); - })); - }) - .then(function(filePaths) { - filePaths = filePaths.filter(function(filePath) { - if (filePath == null) { + return readAndStatDir(dir) + .spread(function(files, stats) { + var modulePaths = files.filter(function(filePath, i) { + if (self._ignoreFilePath(filePath)) { return false; } - return !self._ignoreFilePath(filePath); - }); - - var statsP = filePaths.map(function(filePath) { - return lstat(filePath).catch(handleBrokenLink); - }); - - return [ - filePaths, - q.all(statsP) - ]; - }) - .spread(function(files, stats) { - var modulePaths = files.filter(function(filePath, i) { if (stats[i].isDirectory()) { self._queue.push(filePath); return false; @@ -465,6 +474,19 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) { return null; }; +DependecyGraph.prototype._buildAssetMap = function() { + if (this._assetRoots == null || this._assetRoots.length === 0) { + return q(); + } + + var self = this; + return buildAssetMap(this._assetRoots, this._assetExts) + .then(function(map) { + self._assetMap = map; + return map; + }); +}; + /** * Extract all required modules from a `code` string. */ @@ -511,4 +533,70 @@ function handleBrokenLink(e) { return q(); } +function readAndStatDir(dir) { + return readDir(dir) + .then(function(files){ + return q.all(files.map(function(filePath) { + return realpath(path.join(dir, filePath)).catch(handleBrokenLink); + })); + }).then(function(files) { + files = files.filter(function(f) { + return !!f; + }); + + var stats = files.map(function(filePath) { + return lstat(filePath).catch(handleBrokenLink); + }); + + return [ + files, + q.all(stats), + ]; + }); +} + +/** + * Given a list of roots and list of extensions find all the files in + * the directory with that extension and build a map of those assets. + */ +function buildAssetMap(roots, exts) { + var queue = roots.slice(0); + var map = Object.create(null); + + function search() { + var root = queue.shift(); + + if (root == null) { + return q(map); + } + + return readAndStatDir(root).spread(function(files, stats) { + files.forEach(function(file, i) { + if (stats[i].isDirectory()) { + queue.push(file); + } else { + var ext = path.extname(file).replace(/^\./, ''); + if (exts.indexOf(ext) !== -1) { + var assetName = path.basename(file, '.' + ext); + if (map[assetName] != null) { + debug('Conflcting assets', assetName); + } + + map[assetName] = new ModuleDescriptor({ + id: 'image!' + assetName, + path: path.resolve(file), + isAsset: true, + dependencies: [], + }); + } + } + }); + + return search(); + }); + } + + return search(); +} + module.exports = DependecyGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 6aada00b..fdc779ed 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -17,7 +17,6 @@ var DEFINE_MODULE_CODE = [ ].join(''); var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; - var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; var validateOpts = declareOpts({ @@ -40,6 +39,10 @@ var validateOpts = declareOpts({ type: 'string', default: 'haste', }, + assetRoots: { + type: 'array', + default: [], + }, }); function HasteDependencyResolver(options) { @@ -51,11 +54,12 @@ function HasteDependencyResolver(options) { this._depGraph = new DependencyGraph({ roots: opts.projectRoots, + assetRoots: opts.assetRoots, ignoreFilePath: function(filepath) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, - fileWatcher: this._fileWatcher + fileWatcher: this._fileWatcher, }); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index d8348be8..498faea3 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -40,6 +40,11 @@ describe('Packager', function() { var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, + { id: 'image!img', + path: '/root/img/img.png', + isAsset: true, + dependencies: [], + } ]; getDependencies.mockImpl(function() { @@ -74,6 +79,15 @@ describe('Packager', function() { 'source /root/bar.js', '/root/bar.js' ]); + expect(p.addModule.mock.calls[2]).toEqual([ + 'lol module.exports = ' + + JSON.stringify({ uri: 'img', isStatic: true}) + + '; lol', + 'module.exports = ' + + JSON.stringify({ uri: 'img', isStatic: true}) + + ';', + '/root/img/img.png' + ]); expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 75cccdb2..c21889b4 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -44,6 +44,10 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, + assetRoots: { + type: 'array', + required: false, + }, }); function Packager(options) { @@ -56,7 +60,8 @@ function Packager(options) { blacklistRE: opts.blacklistRE, polyfillModuleNames: opts.polyfillModuleNames, nonPersistent: opts.nonPersistent, - moduleFormat: opts.moduleFormat + moduleFormat: opts.moduleFormat, + assetRoots: opts.assetRoots, }); this._transformer = new Transformer({ @@ -118,10 +123,18 @@ Packager.prototype.getDependencies = function(main, isDev) { }; Packager.prototype._transformModule = function(module) { + var transform; + + if (module.isAsset) { + transform = q(generateAssetModule(module)); + } else { + transform = this._transformer.loadFileAndTransform( + path.resolve(module.path) + ); + } + var resolver = this._resolver; - return this._transformer.loadFileAndTransform( - path.resolve(module.path) - ).then(function(transformed) { + return transform.then(function(transformed) { return _.extend( {}, transformed, @@ -140,5 +153,17 @@ Packager.prototype.getGraphDebugInfo = function() { return this._resolver.getDebugInfo(); }; +function generateAssetModule(module) { + var code = 'module.exports = ' + JSON.stringify({ + uri: module.id.replace(/^[^!]+!/, ''), + isStatic: true, + }) + ';'; + + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; +} module.exports = Packager; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 8982bc91..7df686ad 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -43,6 +43,10 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, + assetRoots: { + type: 'array', + required: false, + }, }); function Server(options) { From 6dfa8c0e4d369bc42c2739f5a6e230668a5de733 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Sat, 14 Mar 2015 17:50:34 -0700 Subject: [PATCH 050/936] [ReactNative] Fix File Watcher test --- react-packager/src/FileWatcher/__tests__/FileWatcher-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index fc8a7a41..11b9f4a3 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -20,7 +20,7 @@ describe('FileWatcher', function() { }); }); - it('it should get the watcher instance when ready', function() { + pit('it should get the watcher instance when ready', function() { var fileWatcher = new FileWatcher(['rootDir']); return fileWatcher._loading.then(function(watchers) { watchers.forEach(function(watcher) { From 89385b8cf54ee0bedefd3b366f0f42f0129ecf48 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Sun, 15 Mar 2015 18:33:51 -0700 Subject: [PATCH 051/936] [ReactNative] Add website to blacklist --- blacklist.js | 1 + 1 file changed, 1 insertion(+) diff --git a/blacklist.js b/blacklist.js index 468a6040..850a8724 100644 --- a/blacklist.js +++ b/blacklist.js @@ -7,6 +7,7 @@ // modulePathIgnorePatterns. var sharedBlacklist = [ __dirname, + 'website', 'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js', 'node_modules/react-tools/src/utils/ImmutableObject.js', 'node_modules/react-tools/src/core/ReactInstanceHandles.js', From 13f48931f2dccb1120058d563c69572a31ad9ecd Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Sun, 15 Mar 2015 20:55:39 -0700 Subject: [PATCH 052/936] Updates from Sun 15 Mar - [ReactNative] Add website to blacklist | Christopher Chedeau - Ported ART to new UIManager | Nick Lockwood - [ReactNative] Fix File Watcher test | Christopher Chedeau - [ReactNative] OSS Interaction Manager | Christopher Chedeau --- blacklist.js | 1 + 1 file changed, 1 insertion(+) diff --git a/blacklist.js b/blacklist.js index 468a6040..850a8724 100644 --- a/blacklist.js +++ b/blacklist.js @@ -7,6 +7,7 @@ // modulePathIgnorePatterns. var sharedBlacklist = [ __dirname, + 'website', 'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js', 'node_modules/react-tools/src/utils/ImmutableObject.js', 'node_modules/react-tools/src/core/ReactInstanceHandles.js', From be992c90e98afcbc3e7b1b1a8a163bb2dbd51ac6 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 16 Mar 2015 15:15:07 -0700 Subject: [PATCH 053/936] [react-packager] small fixes to image loader --- .../haste/DependencyGraph/index.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 122701d5..a7bf1f53 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -134,13 +134,17 @@ DependecyGraph.prototype.resolveDependency = function( fromModule, depModuleId ) { - // Process asset requires. - var assetMatch = depModuleId.match(/^image!(.+)/); - if (assetMatch && assetMatch[1]) { - if (!this._assetMap[assetMatch[1]]) { - throw new Error('Cannot find asset: ' + assetMatch[1]); + + if (this._assetMap != null) { + // Process asset requires. + var assetMatch = depModuleId.match(/^image!(.+)/); + if (assetMatch && assetMatch[1]) { + if (!this._assetMap[assetMatch[1]]) { + console.warn('Cannot find asset: ' + assetMatch[1]); + return null; + } + return this._assetMap[assetMatch[1]]; } - return this._assetMap[assetMatch[1]]; } var packageJson, modulePath, dep; @@ -577,7 +581,8 @@ function buildAssetMap(roots, exts) { } else { var ext = path.extname(file).replace(/^\./, ''); if (exts.indexOf(ext) !== -1) { - var assetName = path.basename(file, '.' + ext); + var assetName = path.basename(file, '.' + ext) + .replace(/@[\d\.]+x/, ''); if (map[assetName] != null) { debug('Conflcting assets', assetName); } From a689113fb452f2357d576a5744e1bdaa45357314 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 16 Mar 2015 19:01:28 -0700 Subject: [PATCH 054/936] Updates from Mon 16 Mar - [ReactNative] Share same server port for debugger proxy | Alex Kotliarskyi - [react-packager] small fixes to image loader | Amjad Masad - [ReactNative] NetworkInformation.reachability API w/ example | Eric Vicenti - [ReactNative] Put launchOptions in RCTPushNotificationManager | Andrew Rasmussen - [ReactNative] Improve PixelRatio documentation | Christopher Chedeau --- .../haste/DependencyGraph/index.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 122701d5..a7bf1f53 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -134,13 +134,17 @@ DependecyGraph.prototype.resolveDependency = function( fromModule, depModuleId ) { - // Process asset requires. - var assetMatch = depModuleId.match(/^image!(.+)/); - if (assetMatch && assetMatch[1]) { - if (!this._assetMap[assetMatch[1]]) { - throw new Error('Cannot find asset: ' + assetMatch[1]); + + if (this._assetMap != null) { + // Process asset requires. + var assetMatch = depModuleId.match(/^image!(.+)/); + if (assetMatch && assetMatch[1]) { + if (!this._assetMap[assetMatch[1]]) { + console.warn('Cannot find asset: ' + assetMatch[1]); + return null; + } + return this._assetMap[assetMatch[1]]; } - return this._assetMap[assetMatch[1]]; } var packageJson, modulePath, dep; @@ -577,7 +581,8 @@ function buildAssetMap(roots, exts) { } else { var ext = path.extname(file).replace(/^\./, ''); if (exts.indexOf(ext) !== -1) { - var assetName = path.basename(file, '.' + ext); + var assetName = path.basename(file, '.' + ext) + .replace(/@[\d\.]+x/, ''); if (map[assetName] != null) { debug('Conflcting assets', assetName); } From 8a1b71c287b557c8b001936d831f50779f8a4638 Mon Sep 17 00:00:00 2001 From: Martin Kosiba Date: Wed, 18 Mar 2015 07:56:18 -0700 Subject: [PATCH 055/936] [react_native] JS files from D1919491: Improve JS logging --- .../haste/polyfills/console.js | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index bb83822d..1b2604e3 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -25,6 +25,13 @@ 'use strict'; var OBJECT_COLUMN_NAME = '(index)'; + var LOG_LEVELS = { + trace: 0, + log: 1, + info: 2, + warn: 3, + error: 4 + }; function setupConsole(global) { @@ -32,29 +39,31 @@ return; } - function doNativeLog() { - var str = Array.prototype.map.call(arguments, function(arg) { - if (arg == null) { - return arg === null ? 'null' : 'undefined'; - } else if (typeof arg === 'string') { - return '"' + arg + '"'; - } else { - // Perform a try catch, just in case the object has a circular - // reference or stringify throws for some other reason. - try { - return JSON.stringify(arg); - } catch (e) { - if (typeof arg.toString === 'function') { - try { - return arg.toString(); - } catch (E) { - return 'unknown'; + function getNativeLogFunction(level) { + return function() { + var str = Array.prototype.map.call(arguments, function(arg) { + if (arg == null) { + return arg === null ? 'null' : 'undefined'; + } else if (typeof arg === 'string') { + return '"' + arg + '"'; + } else { + // Perform a try catch, just in case the object has a circular + // reference or stringify throws for some other reason. + try { + return JSON.stringify(arg); + } catch (e) { + if (typeof arg.toString === 'function') { + try { + return arg.toString(); + } catch (E) { + return 'unknown'; + } } } } - } - }).join(', '); - global.nativeLoggingHook(str); + }).join(', '); + global.nativeLoggingHook(str, level); + }; } var repeat = function(element, n) { @@ -75,7 +84,7 @@ } } if (rows.length === 0) { - global.nativeLoggingHook(''); + global.nativeLoggingHook('', LOG_LEVELS.log); return; } @@ -121,14 +130,15 @@ // Native logging hook adds "RCTLog >" at the front of every // logged string, which would shift the header and screw up // the table - global.nativeLoggingHook('\n' + table.join('\n')); + global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.log); } global.console = { - error: doNativeLog, - info: doNativeLog, - log: doNativeLog, - warn: doNativeLog, + error: getNativeLogFunction(LOG_LEVELS.error), + info: getNativeLogFunction(LOG_LEVELS.info), + log: getNativeLogFunction(LOG_LEVELS.log), + warn: getNativeLogFunction(LOG_LEVELS.warn), + trace: getNativeLogFunction(LOG_LEVELS.trace), table: consoleTablePolyfill }; From e3025bb624cd4478c15caf3ba39bef8750ce171a Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 18 Mar 2015 15:57:49 -0700 Subject: [PATCH 056/936] Updates from Wed 18 Mar - [ReactNative] Add AsyncStorageTest | Spencer Ahrens - [ReactNative] Add timers integration test | Spencer Ahrens - [ReactNative] Remove ExpandingText | Tadeu Zagallo - [TouchableHighlight] Preserve underlay style when restoring inactive props | Christopher Chedeau - clean flow errors in react-native-github | Basil Hosmer - [ReactNative] Sort React Native exports into two groups, Components and APIs | Christopher Chedeau - [ReactNative] Rename Slider to SliderIOS | Tadeu Zagallo - [react_native] JS files from D1919491: Improve JS logging | Martin Kosiba - [ReactNative] Add TimerExample | Spencer Ahrens - [RFC][ReactNative] increase timer resolution | Spencer Ahrens - [ReactNative] Strip prefixes from NativeModules keys | Spencer Ahrens - [ReactNative] Small docs cleanup in ActivityIndicatorIOS and DatePickerIOS | Christopher Chedeau - [ReactNative] Improvements on perf measurement output | Jing Chen - [ReactNative] Clean up Touchable PropTypes | Christopher Chedeau - [ReactKit] Fail tests when redbox shows up | Alex Kotliarskyi --- .../haste/polyfills/console.js | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index bb83822d..1b2604e3 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -25,6 +25,13 @@ 'use strict'; var OBJECT_COLUMN_NAME = '(index)'; + var LOG_LEVELS = { + trace: 0, + log: 1, + info: 2, + warn: 3, + error: 4 + }; function setupConsole(global) { @@ -32,29 +39,31 @@ return; } - function doNativeLog() { - var str = Array.prototype.map.call(arguments, function(arg) { - if (arg == null) { - return arg === null ? 'null' : 'undefined'; - } else if (typeof arg === 'string') { - return '"' + arg + '"'; - } else { - // Perform a try catch, just in case the object has a circular - // reference or stringify throws for some other reason. - try { - return JSON.stringify(arg); - } catch (e) { - if (typeof arg.toString === 'function') { - try { - return arg.toString(); - } catch (E) { - return 'unknown'; + function getNativeLogFunction(level) { + return function() { + var str = Array.prototype.map.call(arguments, function(arg) { + if (arg == null) { + return arg === null ? 'null' : 'undefined'; + } else if (typeof arg === 'string') { + return '"' + arg + '"'; + } else { + // Perform a try catch, just in case the object has a circular + // reference or stringify throws for some other reason. + try { + return JSON.stringify(arg); + } catch (e) { + if (typeof arg.toString === 'function') { + try { + return arg.toString(); + } catch (E) { + return 'unknown'; + } } } } - } - }).join(', '); - global.nativeLoggingHook(str); + }).join(', '); + global.nativeLoggingHook(str, level); + }; } var repeat = function(element, n) { @@ -75,7 +84,7 @@ } } if (rows.length === 0) { - global.nativeLoggingHook(''); + global.nativeLoggingHook('', LOG_LEVELS.log); return; } @@ -121,14 +130,15 @@ // Native logging hook adds "RCTLog >" at the front of every // logged string, which would shift the header and screw up // the table - global.nativeLoggingHook('\n' + table.join('\n')); + global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.log); } global.console = { - error: doNativeLog, - info: doNativeLog, - log: doNativeLog, - warn: doNativeLog, + error: getNativeLogFunction(LOG_LEVELS.error), + info: getNativeLogFunction(LOG_LEVELS.info), + log: getNativeLogFunction(LOG_LEVELS.log), + warn: getNativeLogFunction(LOG_LEVELS.warn), + trace: getNativeLogFunction(LOG_LEVELS.trace), table: consoleTablePolyfill }; From ace7e3613f588e816acf08bf97ebce866c29f830 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 18 Mar 2015 19:13:30 -0700 Subject: [PATCH 057/936] [react-packager] Add assetRoots option --- packager.js | 5 +++++ .../src/DependencyResolver/haste/DependencyGraph/index.js | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packager.js b/packager.js index ca4e5c67..bed45201 100644 --- a/packager.js +++ b/packager.js @@ -45,6 +45,10 @@ if (options.root) { } } +if (!options.assetRoots) { + options.assetRoots = [path.resolve(__dirname, '..')]; +} + console.log('\n' + ' ===============================================================\n' + ' | Running packager on port ' + options.port + '. \n' + @@ -97,6 +101,7 @@ function getAppMiddleware(options) { blacklistRE: blacklist(false), cacheVersion: '2', transformModulePath: require.resolve('./transformer.js'), + assetRoots: options.assetRoots, }); } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index a7bf1f53..918b1e06 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -134,13 +134,12 @@ DependecyGraph.prototype.resolveDependency = function( fromModule, depModuleId ) { - if (this._assetMap != null) { // Process asset requires. var assetMatch = depModuleId.match(/^image!(.+)/); if (assetMatch && assetMatch[1]) { if (!this._assetMap[assetMatch[1]]) { - console.warn('Cannot find asset: ' + assetMatch[1]); + debug('WARINING: Cannot find asset:', assetMatch[1]); return null; } return this._assetMap[assetMatch[1]]; From bd9766c18ede1d17d369d211a60ded20d3a7dffa Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 19 Mar 2015 09:26:03 -0700 Subject: [PATCH 058/936] [ReactNative] Remove duplicate package.json with the same name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6033c216..66f7b1b2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "react-native", + "name": "react-native-cli", "version": "0.1.0", "description": "Build native apps with React!", "repository": { From 72ad4e5be8f1ac4b1c3839f337dcc1ad0ba22424 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Thu, 19 Mar 2015 12:10:41 -0700 Subject: [PATCH 059/936] [ReactNative] Bring Chrome debugger to OSS. Part 2 --- debugger.html | 112 +++++++++++++++++++++++++++++++ launchChromeDevTools.applescript | 41 +++++++++++ packager.js | 31 ++++++++- webSocketProxy.js | 41 +++++++++++ 4 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 debugger.html create mode 100755 launchChromeDevTools.applescript create mode 100644 webSocketProxy.js diff --git a/debugger.html b/debugger.html new file mode 100644 index 00000000..f7cabc5f --- /dev/null +++ b/debugger.html @@ -0,0 +1,112 @@ + + + + + + +React Native Debugger + + + + +

+ 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

+ + diff --git a/launchChromeDevTools.applescript b/launchChromeDevTools.applescript new file mode 100755 index 00000000..4384b3ae --- /dev/null +++ b/launchChromeDevTools.applescript @@ -0,0 +1,41 @@ +#!/usr/bin/env osascript + + +on run argv + set theURL to item 1 of argv + + tell application "Google Chrome" + activate + + if (count every window) = 0 then + make new window + end if + + -- Find a tab currently running the debugger + set found to false + set theTabIndex to -1 + repeat with theWindow in every window + set theTabIndex to 0 + repeat with theTab in every tab of theWindow + set theTabIndex to theTabIndex + 1 + if theTab's URL is theURL then + set found to true + exit repeat + end if + end repeat + + if found then + exit repeat + end if + end repeat + + if found then + set index of theWindow to 1 + set theWindow's active tab index to theTabIndex + else + tell window 1 + make new tab with properties {URL:theURL} + end tell + end if + end tell +end run diff --git a/packager.js b/packager.js index bed45201..9920667e 100644 --- a/packager.js +++ b/packager.js @@ -16,12 +16,14 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { process.exit(); } +var exec = require('child_process').exec; var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); var connect = require('connect'); var http = require('http'); var launchEditor = require('./launchEditor.js'); var parseCommandLine = require('./parseCommandLine.js'); +var webSocketProxy = require('./webSocketProxy.js'); var options = parseCommandLine([{ command: 'port', @@ -68,10 +70,12 @@ process.on('uncaughtException', function(e) { 'any existing instances that are already running.\n\n'); }); -runServer(options, function() { +var server = runServer(options, function() { console.log('\nReact packager ready.\n'); }); +webSocketProxy.attachToServer(server, '/debugger-proxy'); + function loadRawBody(req, res, next) { req.rawBody = ''; req.setEncoding('utf8'); @@ -95,6 +99,30 @@ function openStackFrameInEditor(req, res, next) { } } +function getDevToolsLauncher(options) { + return function(req, res, next) { + if (req.url === '/debugger-ui') { + var debuggerPath = path.join(__dirname, 'debugger.html'); + res.writeHead(200, {'Content-Type': 'text/html'}); + fs.createReadStream(debuggerPath).pipe(res); + } else if (req.url === '/launch-chrome-devtools') { + var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui'; + var script = 'launchChromeDevTools.applescript'; + console.log('Launching Dev Tools...'); + exec(path.join(__dirname, script) + ' ' + debuggerURL, function(err, stdout, stderr) { + if (err) { + console.log('Failed to run ' + script, err); + } + console.log(stdout); + console.warn(stderr); + }); + res.end('OK'); + } else { + next(); + } + }; +} + function getAppMiddleware(options) { return ReactPackager.middleware({ projectRoots: options.projectRoots, @@ -112,6 +140,7 @@ function runServer( var app = connect() .use(loadRawBody) .use(openStackFrameInEditor) + .use(getDevToolsLauncher(options)) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { diff --git a/webSocketProxy.js b/webSocketProxy.js new file mode 100644 index 00000000..dada059a --- /dev/null +++ b/webSocketProxy.js @@ -0,0 +1,41 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ + +'use strict'; + +var WebSocketServer = require('ws').Server; + +function attachToServer(server, path) { + var wss = new WebSocketServer({ + server: server, + path: path + }); + var clients = []; + + wss.on('connection', function(ws) { + clients.push(ws); + + var allClientsExcept = function(ws) { + return clients.filter(function(cn) { return cn !== ws; }); + }; + + ws.onerror = function() { + clients = allClientsExcept(ws); + }; + + ws.onclose = function() { + clients = allClientsExcept(ws); + }; + + ws.on('message', function(message) { + allClientsExcept(ws).forEach(function(cn) { + cn.send(message); + }); + }); + }); +} + +module.exports = { + attachToServer: attachToServer +}; From 566a1774643a213cde06677940f83448cb1432e5 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 19 Mar 2015 12:05:48 -0700 Subject: [PATCH 060/936] [react-packager] Fix OOM --- react-packager/src/JSTransformer/index.js | 2 +- react-packager/src/Server/index.js | 25 +++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 35785e6e..00e49d5d 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -59,7 +59,7 @@ function Transformer(options) { this._failedToStart = q.Promise.reject(new Error('No transfrom module')); } else { this._workers = workerFarm( - {autoStart: true}, + {autoStart: true, maxConcurrentCallsPerWorker: 1}, options.transformModulePath ); } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 7df686ad..09a3c640 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -8,6 +8,7 @@ var Packager = require('../Packager'); var Activity = require('../Activity'); var setImmediate = require('timers').setImmediate; var q = require('q'); +var _ = require('underscore'); module.exports = Server; @@ -62,6 +63,12 @@ function Server(options) { var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); + + var self = this; + this._debouncedFileChangeHandler = _.debounce(function(filePath) { + self._rebuildPackages(filePath); + self._informChangeWatchers(); + }, 50, true); } Server.prototype._onFileChange = function(type, filepath, root) { @@ -69,8 +76,7 @@ Server.prototype._onFileChange = function(type, filepath, root) { this._packager.invalidateFile(absPath); // Make sure the file watcher event runs through the system before // we rebuild the packages. - setImmediate(this._rebuildPackages.bind(this, absPath)); - setImmediate(this._informChangeWatchers.bind(this)); + this._debouncedFileChangeHandler(absPath); }; Server.prototype._rebuildPackages = function() { @@ -78,13 +84,16 @@ Server.prototype._rebuildPackages = function() { var packages = this._packages; Object.keys(packages).forEach(function(key) { var options = getOptionsFromUrl(key); - packages[key] = buildPackage(options).then(function(p) { - // Make a throwaway call to getSource to cache the source string. - p.getSource({ - inlineSourceMap: options.dev, - minify: options.minify, + // Wait for a previous build (if exists) to finish. + packages[key] = (packages[key] || q()).then(function() { + return buildPackage(options).then(function(p) { + // Make a throwaway call to getSource to cache the source string. + p.getSource({ + inlineSourceMap: options.dev, + minify: options.minify, + }); + return p; }); - return p; }); }); }; From 2e2f3b3d9b5c49f5a5f6ce930fff3bc810fa752b Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 19 Mar 2015 17:03:24 -0700 Subject: [PATCH 061/936] [react-packager] Hash cache file name information to avoid long names --- package.json | 2 +- react-packager/src/JSTransformer/Cache.js | 29 ++++++++++--------- .../src/JSTransformer/__tests__/Cache-test.js | 3 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 66f7b1b2..0afcd3c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-cli", - "version": "0.1.0", + "version": "0.1.1", "description": "Build native apps with React!", "repository": { "type": "git", diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index b9c5b8c1..bad0dadb 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -1,13 +1,14 @@ 'use strict'; -var path = require('path'); -var version = require('../../../../package.json').version; -var tmpdir = require('os').tmpDir(); -var isAbsolutePath = require('absolute-path'); +var _ = require('underscore'); +var crypto = require('crypto'); var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); -var _ = require('underscore'); +var isAbsolutePath = require('absolute-path'); +var path = require('path'); var q = require('q'); +var tmpdir = require('os').tmpDir(); +var version = require('../../../../package.json').version; var Promise = q.Promise; @@ -146,15 +147,15 @@ function loadCacheSync(cachePath) { } function cacheFilePath(options) { + var hash = crypto.createHash('md5'); + hash.update(version); + var roots = options.projectRoots.join(',').split(path.sep).join('-'); + hash.update(roots); + var cacheVersion = options.cacheVersion || '0'; - return path.join( - tmpdir, - [ - 'react-packager-cache', - version, - cacheVersion, - roots, - ].join('-') - ); + hash.update(cacheVersion); + + var name = 'react-packager-cache-' + hash.digest('hex'); + return path.join(tmpdir, name); } diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 97a50097..f5b55f05 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -4,6 +4,7 @@ jest .dontMock('underscore') .dontMock('path') .dontMock('absolute-path') + .dontMock('crypto') .dontMock('../Cache'); var q = require('q'); @@ -19,7 +20,7 @@ describe('JSTransformer Cache', function() { Cache = require('../Cache'); }); - describe('getting/settig', function() { + describe('getting/setting', function() { it('calls loader callback for uncached file', function() { var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { From 12233ae65fa4046bd1d6d68c98f408f7a082cf28 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 20 Mar 2015 14:02:42 -0700 Subject: [PATCH 062/936] [ReactNative] Init script that bootstraps new Xcode project --- init.sh | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100755 init.sh diff --git a/init.sh b/init.sh new file mode 100755 index 00000000..6a42b9ae --- /dev/null +++ b/init.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby + +def cp(src, dest, app_name) + if File.directory?(src) + Dir.mkdir(dest) unless Dir.exists?(dest) + else + content = File.read(src) + .gsub("SampleApp", app_name) + .gsub("Examples/#{app_name}/", "") + .gsub("../../Libraries/", "node_modules/react-native/Libraries/") + .gsub("../../ReactKit/", "node_modules/react-native/ReactKit/") + File.write(dest, content) + end +end + +def main(dest, app_name) + source = File.expand_path("../../Examples/SampleApp", __FILE__) + files = Dir.chdir(source) { Dir["**/*"] } + .reject { |file| file["project.xcworkspace"] || file["xcuserdata"] } + .each { |file| + new_file = file.gsub("SampleApp", app_name) + cp File.join(source, file), File.join(dest, new_file), app_name + } +end + +if ARGV.count == 0 + puts "Usage: #{__FILE__} " + puts "" + puts "This script will bootstrap new React Native app in current folder" +else + app_name = ARGV.first + dest = Dir.pwd + puts "Setting up new React Native app in #{dest}" + puts "" + + main(dest, app_name) + + puts "Next steps:" + puts "" + puts " Open #{app_name}.xcproject in Xcode" + puts " Hit Run button" + puts "" +end + From 98ed9422c263f417824504b12eb6fd828e6d54f0 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 20 Mar 2015 14:29:42 -0700 Subject: [PATCH 063/936] [react-packager] Make sure projectRoots is converted to an array --- packager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 9920667e..26917237 100644 --- a/packager.js +++ b/packager.js @@ -33,7 +33,11 @@ var options = parseCommandLine([{ description: 'add another root(s) to be used by the packager in this project', }]); -if (!options.projectRoots) { +if (options.projectRoots) { + if (!Array.isArray(options.projectRoots)) { + options.projectRoots = options.projectRoots.split(','); + } +} else { options.projectRoots = [path.resolve(__dirname, '..')]; } From d11511f5ddab1ded698cda9660b003a8da2047bc Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Fri, 20 Mar 2015 08:43:51 -0700 Subject: [PATCH 064/936] Updates from Thu 19 Mar - [ReactNative] Add root package.json name back | Tadeu Zagallo - [react-packager] Make sure projectRoots is converted to an array | Amjad Masad - [ReactNative] Init script that bootstraps new Xcode project | Alex Kotliarskyi - [ReactNative] New SampleApp | Alex Kotliarskyi - [ReactNative] Touchable invoke press on longPress when longPress handler missing | Eric Vicenti - [ReactNative] Commit missing RCTWebSocketDebugger.xcodeproj | Alex Kotliarskyi - [ReactNative] Add Custom Components folder | Christopher Chedeau - [react-packager] Hash cache file name information to avoid long names | Amjad Masad - [ReactNative] Put all iOS-related files in a subfolder | Alex Kotliarskyi - [react-packager] Fix OOM | Amjad Masad - [ReactNative] Bring Chrome debugger to OSS. Part 2 | Alex Kotliarskyi - [ReactNative] Add search to UIExplorer | Tadeu Zagallo - [ReactNative][RFC] Bring Chrome debugger to OSS. Part 1 | Alex Kotliarskyi - [ReactNative] Return the appropriate status code from XHR | Tadeu Zagallo - [ReactNative] Make JS stack traces in Xcode prettier | Alex Kotliarskyi - [ReactNative] Remove duplicate package.json with the same name | Christopher Chedeau - [ReactNative] Remove invariant from require('react-native') | Christopher Chedeau - [ReactNative] Remove ListViewDataSource from require('react-native') | Christopher Chedeau - [react-packager] Add assetRoots option | Amjad Masad - Convert UIExplorer to ListView | Christopher Chedeau - purge rni | Basil Hosmer - [ReactNative] s/render*View/render/ in | Christopher Chedeau --- debugger.html | 112 ++++++++++++++++++ init.sh | 44 +++++++ launchChromeDevTools.applescript | 41 +++++++ package.json | 4 +- packager.js | 42 ++++++- .../haste/DependencyGraph/index.js | 3 +- react-packager/src/JSTransformer/Cache.js | 29 ++--- .../src/JSTransformer/__tests__/Cache-test.js | 3 +- react-packager/src/JSTransformer/index.js | 2 +- react-packager/src/Server/index.js | 25 ++-- webSocketProxy.js | 41 +++++++ 11 files changed, 316 insertions(+), 30 deletions(-) create mode 100644 debugger.html create mode 100755 init.sh create mode 100755 launchChromeDevTools.applescript create mode 100644 webSocketProxy.js diff --git a/debugger.html b/debugger.html new file mode 100644 index 00000000..f7cabc5f --- /dev/null +++ b/debugger.html @@ -0,0 +1,112 @@ + + + + + + +React Native Debugger + + + + +

+ 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

+ + diff --git a/init.sh b/init.sh new file mode 100755 index 00000000..6a42b9ae --- /dev/null +++ b/init.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby + +def cp(src, dest, app_name) + if File.directory?(src) + Dir.mkdir(dest) unless Dir.exists?(dest) + else + content = File.read(src) + .gsub("SampleApp", app_name) + .gsub("Examples/#{app_name}/", "") + .gsub("../../Libraries/", "node_modules/react-native/Libraries/") + .gsub("../../ReactKit/", "node_modules/react-native/ReactKit/") + File.write(dest, content) + end +end + +def main(dest, app_name) + source = File.expand_path("../../Examples/SampleApp", __FILE__) + files = Dir.chdir(source) { Dir["**/*"] } + .reject { |file| file["project.xcworkspace"] || file["xcuserdata"] } + .each { |file| + new_file = file.gsub("SampleApp", app_name) + cp File.join(source, file), File.join(dest, new_file), app_name + } +end + +if ARGV.count == 0 + puts "Usage: #{__FILE__} " + puts "" + puts "This script will bootstrap new React Native app in current folder" +else + app_name = ARGV.first + dest = Dir.pwd + puts "Setting up new React Native app in #{dest}" + puts "" + + main(dest, app_name) + + puts "Next steps:" + puts "" + puts " Open #{app_name}.xcproject in Xcode" + puts " Hit Run button" + puts "" +end + diff --git a/launchChromeDevTools.applescript b/launchChromeDevTools.applescript new file mode 100755 index 00000000..4384b3ae --- /dev/null +++ b/launchChromeDevTools.applescript @@ -0,0 +1,41 @@ +#!/usr/bin/env osascript + + +on run argv + set theURL to item 1 of argv + + tell application "Google Chrome" + activate + + if (count every window) = 0 then + make new window + end if + + -- Find a tab currently running the debugger + set found to false + set theTabIndex to -1 + repeat with theWindow in every window + set theTabIndex to 0 + repeat with theTab in every tab of theWindow + set theTabIndex to theTabIndex + 1 + if theTab's URL is theURL then + set found to true + exit repeat + end if + end repeat + + if found then + exit repeat + end if + end repeat + + if found then + set index of theWindow to 1 + set theWindow's active tab index to theTabIndex + else + tell window 1 + make new tab with properties {URL:theURL} + end tell + end if + end tell +end run diff --git a/package.json b/package.json index 6033c216..0afcd3c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-native", - "version": "0.1.0", + "name": "react-native-cli", + "version": "0.1.1", "description": "Build native apps with React!", "repository": { "type": "git", diff --git a/packager.js b/packager.js index ca4e5c67..26917237 100644 --- a/packager.js +++ b/packager.js @@ -16,12 +16,14 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { process.exit(); } +var exec = require('child_process').exec; var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); var connect = require('connect'); var http = require('http'); var launchEditor = require('./launchEditor.js'); var parseCommandLine = require('./parseCommandLine.js'); +var webSocketProxy = require('./webSocketProxy.js'); var options = parseCommandLine([{ command: 'port', @@ -31,7 +33,11 @@ var options = parseCommandLine([{ description: 'add another root(s) to be used by the packager in this project', }]); -if (!options.projectRoots) { +if (options.projectRoots) { + if (!Array.isArray(options.projectRoots)) { + options.projectRoots = options.projectRoots.split(','); + } +} else { options.projectRoots = [path.resolve(__dirname, '..')]; } @@ -45,6 +51,10 @@ if (options.root) { } } +if (!options.assetRoots) { + options.assetRoots = [path.resolve(__dirname, '..')]; +} + console.log('\n' + ' ===============================================================\n' + ' | Running packager on port ' + options.port + '. \n' + @@ -64,10 +74,12 @@ process.on('uncaughtException', function(e) { 'any existing instances that are already running.\n\n'); }); -runServer(options, function() { +var server = runServer(options, function() { console.log('\nReact packager ready.\n'); }); +webSocketProxy.attachToServer(server, '/debugger-proxy'); + function loadRawBody(req, res, next) { req.rawBody = ''; req.setEncoding('utf8'); @@ -91,12 +103,37 @@ function openStackFrameInEditor(req, res, next) { } } +function getDevToolsLauncher(options) { + return function(req, res, next) { + if (req.url === '/debugger-ui') { + var debuggerPath = path.join(__dirname, 'debugger.html'); + res.writeHead(200, {'Content-Type': 'text/html'}); + fs.createReadStream(debuggerPath).pipe(res); + } else if (req.url === '/launch-chrome-devtools') { + var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui'; + var script = 'launchChromeDevTools.applescript'; + console.log('Launching Dev Tools...'); + exec(path.join(__dirname, script) + ' ' + debuggerURL, function(err, stdout, stderr) { + if (err) { + console.log('Failed to run ' + script, err); + } + console.log(stdout); + console.warn(stderr); + }); + res.end('OK'); + } else { + next(); + } + }; +} + function getAppMiddleware(options) { return ReactPackager.middleware({ projectRoots: options.projectRoots, blacklistRE: blacklist(false), cacheVersion: '2', transformModulePath: require.resolve('./transformer.js'), + assetRoots: options.assetRoots, }); } @@ -107,6 +144,7 @@ function runServer( var app = connect() .use(loadRawBody) .use(openStackFrameInEditor) + .use(getDevToolsLauncher(options)) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index a7bf1f53..918b1e06 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -134,13 +134,12 @@ DependecyGraph.prototype.resolveDependency = function( fromModule, depModuleId ) { - if (this._assetMap != null) { // Process asset requires. var assetMatch = depModuleId.match(/^image!(.+)/); if (assetMatch && assetMatch[1]) { if (!this._assetMap[assetMatch[1]]) { - console.warn('Cannot find asset: ' + assetMatch[1]); + debug('WARINING: Cannot find asset:', assetMatch[1]); return null; } return this._assetMap[assetMatch[1]]; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index b9c5b8c1..bad0dadb 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -1,13 +1,14 @@ 'use strict'; -var path = require('path'); -var version = require('../../../../package.json').version; -var tmpdir = require('os').tmpDir(); -var isAbsolutePath = require('absolute-path'); +var _ = require('underscore'); +var crypto = require('crypto'); var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); -var _ = require('underscore'); +var isAbsolutePath = require('absolute-path'); +var path = require('path'); var q = require('q'); +var tmpdir = require('os').tmpDir(); +var version = require('../../../../package.json').version; var Promise = q.Promise; @@ -146,15 +147,15 @@ function loadCacheSync(cachePath) { } function cacheFilePath(options) { + var hash = crypto.createHash('md5'); + hash.update(version); + var roots = options.projectRoots.join(',').split(path.sep).join('-'); + hash.update(roots); + var cacheVersion = options.cacheVersion || '0'; - return path.join( - tmpdir, - [ - 'react-packager-cache', - version, - cacheVersion, - roots, - ].join('-') - ); + hash.update(cacheVersion); + + var name = 'react-packager-cache-' + hash.digest('hex'); + return path.join(tmpdir, name); } diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 97a50097..f5b55f05 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -4,6 +4,7 @@ jest .dontMock('underscore') .dontMock('path') .dontMock('absolute-path') + .dontMock('crypto') .dontMock('../Cache'); var q = require('q'); @@ -19,7 +20,7 @@ describe('JSTransformer Cache', function() { Cache = require('../Cache'); }); - describe('getting/settig', function() { + describe('getting/setting', function() { it('calls loader callback for uncached file', function() { var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 35785e6e..00e49d5d 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -59,7 +59,7 @@ function Transformer(options) { this._failedToStart = q.Promise.reject(new Error('No transfrom module')); } else { this._workers = workerFarm( - {autoStart: true}, + {autoStart: true, maxConcurrentCallsPerWorker: 1}, options.transformModulePath ); } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 7df686ad..09a3c640 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -8,6 +8,7 @@ var Packager = require('../Packager'); var Activity = require('../Activity'); var setImmediate = require('timers').setImmediate; var q = require('q'); +var _ = require('underscore'); module.exports = Server; @@ -62,6 +63,12 @@ function Server(options) { var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); + + var self = this; + this._debouncedFileChangeHandler = _.debounce(function(filePath) { + self._rebuildPackages(filePath); + self._informChangeWatchers(); + }, 50, true); } Server.prototype._onFileChange = function(type, filepath, root) { @@ -69,8 +76,7 @@ Server.prototype._onFileChange = function(type, filepath, root) { this._packager.invalidateFile(absPath); // Make sure the file watcher event runs through the system before // we rebuild the packages. - setImmediate(this._rebuildPackages.bind(this, absPath)); - setImmediate(this._informChangeWatchers.bind(this)); + this._debouncedFileChangeHandler(absPath); }; Server.prototype._rebuildPackages = function() { @@ -78,13 +84,16 @@ Server.prototype._rebuildPackages = function() { var packages = this._packages; Object.keys(packages).forEach(function(key) { var options = getOptionsFromUrl(key); - packages[key] = buildPackage(options).then(function(p) { - // Make a throwaway call to getSource to cache the source string. - p.getSource({ - inlineSourceMap: options.dev, - minify: options.minify, + // Wait for a previous build (if exists) to finish. + packages[key] = (packages[key] || q()).then(function() { + return buildPackage(options).then(function(p) { + // Make a throwaway call to getSource to cache the source string. + p.getSource({ + inlineSourceMap: options.dev, + minify: options.minify, + }); + return p; }); - return p; }); }); }; diff --git a/webSocketProxy.js b/webSocketProxy.js new file mode 100644 index 00000000..dada059a --- /dev/null +++ b/webSocketProxy.js @@ -0,0 +1,41 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ + +'use strict'; + +var WebSocketServer = require('ws').Server; + +function attachToServer(server, path) { + var wss = new WebSocketServer({ + server: server, + path: path + }); + var clients = []; + + wss.on('connection', function(ws) { + clients.push(ws); + + var allClientsExcept = function(ws) { + return clients.filter(function(cn) { return cn !== ws; }); + }; + + ws.onerror = function() { + clients = allClientsExcept(ws); + }; + + ws.onclose = function() { + clients = allClientsExcept(ws); + }; + + ws.on('message', function(message) { + allClientsExcept(ws).forEach(function(cn) { + cn.send(message); + }); + }); + }); +} + +module.exports = { + attachToServer: attachToServer +}; From 601b21fa79b40f7ef0e26602cc0a9e4924529877 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 20 Mar 2015 16:31:31 -0700 Subject: [PATCH 065/936] [ReactNative] Adjust packager default root when running from within node_modules --- packager.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 26917237..ce4dbd25 100644 --- a/packager.js +++ b/packager.js @@ -38,7 +38,12 @@ if (options.projectRoots) { options.projectRoots = options.projectRoots.split(','); } } else { - options.projectRoots = [path.resolve(__dirname, '..')]; + if (__dirname.match(/node_modules\/react-native\/packager$/)) { + // packager is running from node_modules of another project + options.projectRoots = [path.resolve(__dirname, '../../..')]; + } else { + options.projectRoots = [path.resolve(__dirname, '..')]; + } } if (options.root) { From 01c8933949b267831c262ace4477435bb27f219d Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 20 Mar 2015 17:42:54 -0700 Subject: [PATCH 066/936] [react-packager] Allow entry point extensions like .ios.js --- .../src/Server/__tests__/Server-test.js | 19 ++++++++++++-- react-packager/src/Server/index.js | 26 +++++++++---------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 71b7e400..f4905806 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -51,7 +51,7 @@ describe('processRequest', function() { Packager = require('../../Packager'); FileWatcher = require('../../FileWatcher'); - Packager.prototype.package = function() { + Packager.prototype.package = jest.genMockFunction().mockImpl(function() { return q({ getSource: function() { return 'this is the source'; @@ -60,7 +60,7 @@ describe('processRequest', function() { return 'this is the source map'; }, }); - }; + }); FileWatcher.prototype.on = function(eventType, callback) { @@ -106,6 +106,21 @@ describe('processRequest', function() { }); }); + pit('works with .ios.js extension', function() { + return makeRequest( + requestHandler, + 'index.ios.includeRequire.bundle' + ).then(function(response) { + expect(response).toEqual('this is the source'); + expect(Packager.prototype.package).toBeCalledWith( + 'index.ios.js', + true, + 'index.ios.includeRequire.map', + true + ); + }); + }); + pit('watches all files in projectRoot', function() { return makeRequest( requestHandler, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 09a3c640..3043903d 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -6,7 +6,6 @@ var declareOpts = require('../lib/declareOpts'); var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); -var setImmediate = require('timers').setImmediate; var q = require('q'); var _ = require('underscore'); @@ -236,23 +235,24 @@ Server.prototype.processRequest = function(req, res, next) { function getOptionsFromUrl(reqUrl) { // `true` to parse the query param as an object. var urlObj = url.parse(reqUrl, true); + var pathname = urlObj.pathname; - var match = urlObj.pathname.match(/^\/?([^\.]+)\..*(bundle|map)$/); - if (!(match && match[1])) { - throw new Error('Invalid url format, expected "/path/to/file.bundle"'); - } - var main = match[1] + '.js'; + // 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: urlObj.pathname.replace(/\.bundle$/, '.map'), - main: main, + sourceMapUrl: pathname.replace(/\.bundle$/, '.map'), + main: entryFile, dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), minify: getBoolOptionFromQuery(urlObj.query, 'minify'), - runModule: getBoolOptionFromQuery(urlObj.query, 'runModule') || - // Backwards compatibility. - urlObj.pathname.split('.').some(function(part) { - return part === 'runModule'; - }), + runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true), }; } From f0aeb066c802f5152ed3622a8c1576392993444c Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Sat, 21 Mar 2015 10:07:45 -0700 Subject: [PATCH 067/936] Updates from Fri 20 Mar - declare timeoutID | Basil Hosmer - [react-packager] Allow entry point extensions like .ios.js | Amjad Masad - [react-native] Use SpreadProperty to make react-docgen happy | Felix Kling - clean Examples/2048 | Basil Hosmer - [ReactNative] Adjust packager default root when running from within node_modules | Alex Kotliarskyi - [ReactNative] Add missing websocket dependency | Alex Kotliarskyi - [react-packager] change all but one `ix` to `require` | Amjad Masad --- packager.js | 7 ++++- .../src/Server/__tests__/Server-test.js | 19 ++++++++++++-- react-packager/src/Server/index.js | 26 +++++++++---------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/packager.js b/packager.js index 26917237..ce4dbd25 100644 --- a/packager.js +++ b/packager.js @@ -38,7 +38,12 @@ if (options.projectRoots) { options.projectRoots = options.projectRoots.split(','); } } else { - options.projectRoots = [path.resolve(__dirname, '..')]; + if (__dirname.match(/node_modules\/react-native\/packager$/)) { + // packager is running from node_modules of another project + options.projectRoots = [path.resolve(__dirname, '../../..')]; + } else { + options.projectRoots = [path.resolve(__dirname, '..')]; + } } if (options.root) { diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 71b7e400..f4905806 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -51,7 +51,7 @@ describe('processRequest', function() { Packager = require('../../Packager'); FileWatcher = require('../../FileWatcher'); - Packager.prototype.package = function() { + Packager.prototype.package = jest.genMockFunction().mockImpl(function() { return q({ getSource: function() { return 'this is the source'; @@ -60,7 +60,7 @@ describe('processRequest', function() { return 'this is the source map'; }, }); - }; + }); FileWatcher.prototype.on = function(eventType, callback) { @@ -106,6 +106,21 @@ describe('processRequest', function() { }); }); + pit('works with .ios.js extension', function() { + return makeRequest( + requestHandler, + 'index.ios.includeRequire.bundle' + ).then(function(response) { + expect(response).toEqual('this is the source'); + expect(Packager.prototype.package).toBeCalledWith( + 'index.ios.js', + true, + 'index.ios.includeRequire.map', + true + ); + }); + }); + pit('watches all files in projectRoot', function() { return makeRequest( requestHandler, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 09a3c640..3043903d 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -6,7 +6,6 @@ var declareOpts = require('../lib/declareOpts'); var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); -var setImmediate = require('timers').setImmediate; var q = require('q'); var _ = require('underscore'); @@ -236,23 +235,24 @@ Server.prototype.processRequest = function(req, res, next) { function getOptionsFromUrl(reqUrl) { // `true` to parse the query param as an object. var urlObj = url.parse(reqUrl, true); + var pathname = urlObj.pathname; - var match = urlObj.pathname.match(/^\/?([^\.]+)\..*(bundle|map)$/); - if (!(match && match[1])) { - throw new Error('Invalid url format, expected "/path/to/file.bundle"'); - } - var main = match[1] + '.js'; + // 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: urlObj.pathname.replace(/\.bundle$/, '.map'), - main: main, + sourceMapUrl: pathname.replace(/\.bundle$/, '.map'), + main: entryFile, dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), minify: getBoolOptionFromQuery(urlObj.query, 'minify'), - runModule: getBoolOptionFromQuery(urlObj.query, 'runModule') || - // Backwards compatibility. - urlObj.pathname.split('.').some(function(part) { - return part === 'runModule'; - }), + runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true), }; } From 4dec3cd76eb61d2915819c494e05aa6affcea3e2 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Sun, 22 Mar 2015 14:04:55 -0700 Subject: [PATCH 068/936] Fix few paths for react-native cli scripts --- init.sh | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100755 init.sh diff --git a/init.sh b/init.sh deleted file mode 100755 index 6a42b9ae..00000000 --- a/init.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env ruby - -def cp(src, dest, app_name) - if File.directory?(src) - Dir.mkdir(dest) unless Dir.exists?(dest) - else - content = File.read(src) - .gsub("SampleApp", app_name) - .gsub("Examples/#{app_name}/", "") - .gsub("../../Libraries/", "node_modules/react-native/Libraries/") - .gsub("../../ReactKit/", "node_modules/react-native/ReactKit/") - File.write(dest, content) - end -end - -def main(dest, app_name) - source = File.expand_path("../../Examples/SampleApp", __FILE__) - files = Dir.chdir(source) { Dir["**/*"] } - .reject { |file| file["project.xcworkspace"] || file["xcuserdata"] } - .each { |file| - new_file = file.gsub("SampleApp", app_name) - cp File.join(source, file), File.join(dest, new_file), app_name - } -end - -if ARGV.count == 0 - puts "Usage: #{__FILE__} " - puts "" - puts "This script will bootstrap new React Native app in current folder" -else - app_name = ARGV.first - dest = Dir.pwd - puts "Setting up new React Native app in #{dest}" - puts "" - - main(dest, app_name) - - puts "Next steps:" - puts "" - puts " Open #{app_name}.xcproject in Xcode" - puts " Hit Run button" - puts "" -end - From d1db7b275cecc4a4688b477353bebc79b5f2fbfb Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Sun, 22 Mar 2015 20:41:23 -0700 Subject: [PATCH 069/936] [ReactNative] Move packager/init.sh to GitHub --- init.sh | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100755 init.sh diff --git a/init.sh b/init.sh deleted file mode 100755 index 6a42b9ae..00000000 --- a/init.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env ruby - -def cp(src, dest, app_name) - if File.directory?(src) - Dir.mkdir(dest) unless Dir.exists?(dest) - else - content = File.read(src) - .gsub("SampleApp", app_name) - .gsub("Examples/#{app_name}/", "") - .gsub("../../Libraries/", "node_modules/react-native/Libraries/") - .gsub("../../ReactKit/", "node_modules/react-native/ReactKit/") - File.write(dest, content) - end -end - -def main(dest, app_name) - source = File.expand_path("../../Examples/SampleApp", __FILE__) - files = Dir.chdir(source) { Dir["**/*"] } - .reject { |file| file["project.xcworkspace"] || file["xcuserdata"] } - .each { |file| - new_file = file.gsub("SampleApp", app_name) - cp File.join(source, file), File.join(dest, new_file), app_name - } -end - -if ARGV.count == 0 - puts "Usage: #{__FILE__} " - puts "" - puts "This script will bootstrap new React Native app in current folder" -else - app_name = ARGV.first - dest = Dir.pwd - puts "Setting up new React Native app in #{dest}" - puts "" - - main(dest, app_name) - - puts "Next steps:" - puts "" - puts " Open #{app_name}.xcproject in Xcode" - puts " Hit Run button" - puts "" -end - From 098d7bfa454e72352088e58194918733d0eeec68 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Sun, 22 Mar 2015 22:56:41 -0700 Subject: [PATCH 070/936] [ReactNative] Print directories packager is serving files from --- packager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packager.js b/packager.js index ce4dbd25..7214aaaf 100644 --- a/packager.js +++ b/packager.js @@ -72,6 +72,8 @@ console.log('\n' + ' ===============================================================\n' ); +console.log('Looking for JS files in\n ', options.projectRoots.join('\n ')); + process.on('uncaughtException', function(e) { console.error(e); console.error(e.stack); From fef4119be0ef60e71b8e7fdc7bcd47eee8f21529 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 23 Mar 2015 10:06:16 -0700 Subject: [PATCH 071/936] Updates from Mon 23 Mar - [React Native] Fix iOS 7 crashes b/c missing Photos.fmwk | Alex Akers - UIExplorer flowification | Basil Hosmer - Add clearImmediate module | Marshall Roch - [ReactNative] Print directories packager is serving files from | Alex Kotliarskyi - Work around flow bug with exports | Marshall Roch - [ReactNative] Move packager/init.sh to GitHub | Alex Kotliarskyi - [ReactNative] Remove react-native/package.json | Christopher Chedeau - [ReactNative] Returning actual contentSize for RCTScrollViewManager | Henry Lung --- packager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packager.js b/packager.js index ce4dbd25..7214aaaf 100644 --- a/packager.js +++ b/packager.js @@ -72,6 +72,8 @@ console.log('\n' + ' ===============================================================\n' ); +console.log('Looking for JS files in\n ', options.projectRoots.join('\n ')); + process.on('uncaughtException', function(e) { console.error(e); console.error(e.stack); From 375797ae36aa7397c5b8111addd93f2c32c7d113 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 23 Mar 2015 11:36:20 -0700 Subject: [PATCH 072/936] [react-packager] Pick up package changes while running --- blacklist.js | 1 - .../haste/DependencyGraph/index.js | 84 ++++++++++++------- react-packager/src/FileWatcher/index.js | 2 +- react-packager/src/Server/index.js | 2 +- 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/blacklist.js b/blacklist.js index 850a8724..60560312 100644 --- a/blacklist.js +++ b/blacklist.js @@ -8,7 +8,6 @@ var sharedBlacklist = [ __dirname, 'website', - 'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js', 'node_modules/react-tools/src/utils/ImmutableObject.js', 'node_modules/react-tools/src/core/ReactInstanceHandles.js', 'node_modules/react-tools/src/event/EventPropagators.js' diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 918b1e06..42a15c00 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -296,35 +296,50 @@ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { } if (packagePath != null) { - return readFile(packagePath, 'utf8') - .then(function(content) { - var packageJson; - try { - packageJson = JSON.parse(content); - } catch (e) { - debug('WARNING: malformed package.json: ', packagePath); - return q(); - } - - if (packageJson.name == null) { - debug( - 'WARNING: package.json `%s` is missing a name field', - packagePath - ); - return q(); - } - - packageJson._root = root; - self._packageByRoot[root] = packageJson; - self._packagesById[packageJson.name] = packageJson; - - return packageJson; - }); + return this._processPackage(packagePath); } else { return q(); } }; +DependecyGraph.prototype._processPackage = function(packagePath) { + var packageRoot = path.dirname(packagePath); + var self = this; + return readFile(packagePath, 'utf8') + .then(function(content) { + var packageJson; + try { + packageJson = JSON.parse(content); + } catch (e) { + debug('WARNING: malformed package.json: ', packagePath); + return q(); + } + + if (packageJson.name == null) { + debug( + 'WARNING: package.json `%s` is missing a name field', + packagePath + ); + return q(); + } + + packageJson._root = packageRoot; + self._addPackageToIndices(packageJson); + + return packageJson; + }); +}; + +DependecyGraph.prototype._addPackageToIndices = function(packageJson) { + this._packageByRoot[packageJson._root] = packageJson; + this._packagesById[packageJson.name] = packageJson; +}; + +DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { + delete this._packageByRoot[packageJson._root]; + delete this._packagesById[packageJson.name]; +}; + /** * Parse a module and update indices. */ @@ -436,16 +451,27 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root this._debugUpdateEvents.push({event: eventType, path: filePath}); + var isPackage = path.basename(filePath) === 'package.json'; if (eventType === 'delete') { - var module = this._graph[absPath]; - if (module == null) { - return; - } + if (isPackage) { + var packageJson = this._packageByRoot[path.dirname(absPath)]; + if (packageJson) { + this._removePackageFromIndices(packageJson); + } + } else { + var module = this._graph[absPath]; + if (module == null) { + return; + } - this._deleteModule(module); + this._deleteModule(module); + } } else if (!(stat && stat.isDirectory())) { var self = this; this._loading = this._loading.then(function() { + if (isPackage) { + return self._processPackage(absPath); + } return self._processModule(absPath); }); } diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index f2721d8c..03e5452f 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -56,7 +56,7 @@ function createWatcher(root) { } return detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(root, {glob: '**/*.js'}); + var watcher = new Watcher(root, {glob: ['**/*.js', '**/package.json']}); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 3043903d..a144c9bc 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -67,7 +67,7 @@ function Server(options) { this._debouncedFileChangeHandler = _.debounce(function(filePath) { self._rebuildPackages(filePath); self._informChangeWatchers(); - }, 50, true); + }, 50); } Server.prototype._onFileChange = function(type, filepath, root) { From a2c32d4d29420abe29d8b5f491cd036fa373a7c8 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 23 Mar 2015 11:48:02 -0700 Subject: [PATCH 073/936] [ReactNative] Expanded license on js packager files --- blacklist.js | 7 ++++++- debugger.html | 9 +++++++++ launchChromeDevTools.applescript | 6 ++++++ launchEditor.js | 7 ++++++- launchPackager.command | 7 +++++++ packager.js | 7 ++++++- packager.sh | 7 +++++++ parseCommandLine.js | 7 ++++++- react-packager/__mocks__/debug.js | 8 ++++++++ react-packager/__mocks__/net.js | 9 +++++++++ react-packager/example_project/bar.js | 9 ++++++++- react-packager/example_project/foo/foo.js | 7 +++++++ react-packager/example_project/index.js | 8 +++++++- react-packager/example_project/js/Channel.js | 7 +++++++ react-packager/example_project/js/XHR.js | 7 +++++++ react-packager/example_project/js/code.js | 7 +++++++ react-packager/example_project/js/main.js | 7 +++++++ .../example_project/public/css/index.css | 10 ++++++++++ .../example_project/public/index.html | 8 ++++++++ react-packager/index.js | 8 ++++++++ .../src/Activity/__tests__/Activity-test.js | 8 ++++++++ react-packager/src/Activity/index.js | 8 ++++++++ .../src/DependencyResolver/ModuleDescriptor.js | 8 ++++++++ .../haste/DependencyGraph/__mocks__/fs.js | 8 ++++++++ .../__tests__/DependencyGraph-test.js | 8 ++++++++ .../haste/DependencyGraph/docblock.js | 17 +++++------------ .../haste/DependencyGraph/index.js | 8 ++++++++ .../__tests__/HasteDependencyResolver-test.js | 8 ++++++++ .../src/DependencyResolver/haste/index.js | 8 ++++++++ .../haste/polyfills/console.js | 17 +++++------------ .../haste/polyfills/error-guard.js | 8 +++++++- .../haste/polyfills/polyfills.js | 17 +++++------------ react-packager/src/DependencyResolver/index.js | 8 ++++++++ .../src/DependencyResolver/node/index.js | 8 ++++++++ .../src/FileWatcher/__mocks__/sane.js | 8 ++++++++ .../FileWatcher/__tests__/FileWatcher-test.js | 8 ++++++++ react-packager/src/FileWatcher/index.js | 8 ++++++++ react-packager/src/JSTransformer/Cache.js | 8 ++++++++ react-packager/src/JSTransformer/__mocks__/q.js | 8 ++++++++ .../src/JSTransformer/__mocks__/underscore.js | 8 ++++++++ .../src/JSTransformer/__mocks__/worker.js | 8 ++++++++ .../src/JSTransformer/__tests__/Cache-test.js | 8 ++++++++ .../JSTransformer/__tests__/Transformer-test.js | 8 ++++++++ react-packager/src/JSTransformer/index.js | 9 ++++++++- react-packager/src/JSTransformer/worker.js | 8 ++++++++ react-packager/src/Packager/Package.js | 8 ++++++++ .../src/Packager/__tests__/Package-test.js | 8 ++++++++ .../src/Packager/__tests__/Packager-test.js | 8 ++++++++ react-packager/src/Packager/index.js | 8 ++++++++ .../src/Server/__tests__/Server-test.js | 8 ++++++++ react-packager/src/Server/index.js | 8 ++++++++ react-packager/src/lib/__mocks__/declareOpts.js | 8 ++++++++ .../src/lib/__tests__/declareOpts-test.js | 8 ++++++++ react-packager/src/lib/declareOpts.js | 7 +++++++ transformer.js | 7 ++++++- webSocketProxy.js | 8 ++++++-- 56 files changed, 419 insertions(+), 47 deletions(-) diff --git a/blacklist.js b/blacklist.js index 60560312..b5ba4185 100644 --- a/blacklist.js +++ b/blacklist.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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'; diff --git a/debugger.html b/debugger.html index f7cabc5f..38e9e6cd 100644 --- a/debugger.html +++ b/debugger.html @@ -1,4 +1,13 @@ + + diff --git a/launchChromeDevTools.applescript b/launchChromeDevTools.applescript index 4384b3ae..1fe6f4b0 100755 --- a/launchChromeDevTools.applescript +++ b/launchChromeDevTools.applescript @@ -1,5 +1,11 @@ #!/usr/bin/env osascript +-- 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. on run argv set theURL to item 1 of argv diff --git a/launchEditor.js b/launchEditor.js index 93db9bfc..cf89ed4f 100644 --- a/launchEditor.js +++ b/launchEditor.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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'; diff --git a/launchPackager.command b/launchPackager.command index dc56d7ff..eb777493 100755 --- a/launchPackager.command +++ b/launchPackager.command @@ -1,5 +1,12 @@ #!/bin/bash +# 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. + # Set terminal title echo -en "\033]0;React Packager\a" clear diff --git a/packager.js b/packager.js index 7214aaaf..12ef9ddd 100644 --- a/packager.js +++ b/packager.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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'; diff --git a/packager.sh b/packager.sh index 98e42184..969ca1b2 100755 --- a/packager.sh +++ b/packager.sh @@ -1,5 +1,12 @@ #!/bin/bash +# 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. + ulimit -n 4096 THIS_DIR=$(dirname "$0") diff --git a/parseCommandLine.js b/parseCommandLine.js index 5240d37d..65061e3f 100644 --- a/parseCommandLine.js +++ b/parseCommandLine.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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. * * Wrapper on-top of `optimist` in order to properly support boolean flags * and have a slightly less akward API. diff --git a/react-packager/__mocks__/debug.js b/react-packager/__mocks__/debug.js index d35fffd4..41955326 100644 --- a/react-packager/__mocks__/debug.js +++ b/react-packager/__mocks__/debug.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'; module.exports = function() { diff --git a/react-packager/__mocks__/net.js b/react-packager/__mocks__/net.js index 661fb196..43f51828 100644 --- a/react-packager/__mocks__/net.js +++ b/react-packager/__mocks__/net.js @@ -1,3 +1,12 @@ +/** + * 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. + */ + var EventEmitter = require('events').EventEmitter; var servers = {}; exports.createServer = function(listener) { diff --git a/react-packager/example_project/bar.js b/react-packager/example_project/bar.js index cc56ce6e..6653bdf7 100644 --- a/react-packager/example_project/bar.js +++ b/react-packager/example_project/bar.js @@ -1,5 +1,12 @@ /** + * 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; \ No newline at end of file + module.exports = setInterval; diff --git a/react-packager/example_project/foo/foo.js b/react-packager/example_project/foo/foo.js index c45d9aba..fe3c8cd1 100644 --- a/react-packager/example_project/foo/foo.js +++ b/react-packager/example_project/foo/foo.js @@ -1,4 +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. + * * @providesModule foo */ diff --git a/react-packager/example_project/index.js b/react-packager/example_project/index.js index 2943d187..d63b5193 100644 --- a/react-packager/example_project/index.js +++ b/react-packager/example_project/index.js @@ -1,6 +1,12 @@ /** + * 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 - * @jsx React.DOM */ require('main'); diff --git a/react-packager/example_project/js/Channel.js b/react-packager/example_project/js/Channel.js index d3cbae1c..6cbfce6f 100644 --- a/react-packager/example_project/js/Channel.js +++ b/react-packager/example_project/js/Channel.js @@ -1,4 +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. + * * @providesModule Channel */ diff --git a/react-packager/example_project/js/XHR.js b/react-packager/example_project/js/XHR.js index d9e0563f..bede8ca5 100644 --- a/react-packager/example_project/js/XHR.js +++ b/react-packager/example_project/js/XHR.js @@ -1,4 +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. + * * @providesModule XHR */ diff --git a/react-packager/example_project/js/code.js b/react-packager/example_project/js/code.js index 70067859..f99a90c9 100644 --- a/react-packager/example_project/js/code.js +++ b/react-packager/example_project/js/code.js @@ -1,4 +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. + * * @providesModule code */ var XHR = require('XHR'); diff --git a/react-packager/example_project/js/main.js b/react-packager/example_project/js/main.js index 58847092..405d015e 100644 --- a/react-packager/example_project/js/main.js +++ b/react-packager/example_project/js/main.js @@ -1,4 +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. + * * @providesModule main */ var Channel = require('Channel'); diff --git a/react-packager/example_project/public/css/index.css b/react-packager/example_project/public/css/index.css index 7d36bf2c..651f3326 100644 --- a/react-packager/example_project/public/css/index.css +++ b/react-packager/example_project/public/css/index.css @@ -1,3 +1,13 @@ +/** + * 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; } diff --git a/react-packager/example_project/public/index.html b/react-packager/example_project/public/index.html index b19685d5..e0e2ce7f 100644 --- a/react-packager/example_project/public/index.html +++ b/react-packager/example_project/public/index.html @@ -1,4 +1,12 @@ + diff --git a/react-packager/index.js b/react-packager/index.js index 65ae88d8..3a70659c 100644 --- a/react-packager/index.js +++ b/react-packager/index.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'; var Activity = require('./src/Activity'); diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index 7fe31614..c854aa33 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.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'; jest.autoMockOff(); diff --git a/react-packager/src/Activity/index.js b/react-packager/src/Activity/index.js index 611ccb0b..05285d0f 100644 --- a/react-packager/src/Activity/index.js +++ b/react-packager/src/Activity/index.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'; var COLLECTION_PERIOD = 1000; diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index df29e57c..1388a610 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.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'; function ModuleDescriptor(fields) { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js index de3622d9..3ebee183 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.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'; var fs = jest.genMockFromModule('fs'); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 6dee93ec..bbdb0641 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.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'; jest diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js index c2b6ac98..131f09d1 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js @@ -1,17 +1,10 @@ /** - * Copyright 2013 Facebook, Inc. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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'; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 42a15c00..14139159 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.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'; var ModuleDescriptor = require('../../ModuleDescriptor'); diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index b25fd821..90ae5888 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.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'; jest.dontMock('../') diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index fdc779ed..2edb3b52 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.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'; var path = require('path'); diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index 1b2604e3..91fb970f 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -1,17 +1,10 @@ /** - * Copyright 2013 Facebook, Inc. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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. * * This pipes all of our console logging functions to native logging so that * JavaScript errors in required modules show up in Xcode via NSLog. diff --git a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js index 745d650e..3816617f 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js @@ -1,5 +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. + * * The particular require runtime that we are using looks for a global * `ErrorUtils` object and if it exists, then it requires modules with the * error handler specified via ErrorUtils.setGlobalHandler by calling the diff --git a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js index 75f74279..84bf2d3d 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js @@ -1,17 +1,10 @@ /** - * Copyright 2013 Facebook, Inc. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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. * * This pipes all of our console logging functions to native logging so that * JavaScript errors in required modules show up in Xcode via NSLog. diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index f42ecb8a..ca80ab0b 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.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'; var HasteDependencyResolver = require('./haste'); diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js index da03cc7e..46c93c2f 100644 --- a/react-packager/src/DependencyResolver/node/index.js +++ b/react-packager/src/DependencyResolver/node/index.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'; var Promise = require('q').Promise; diff --git a/react-packager/src/FileWatcher/__mocks__/sane.js b/react-packager/src/FileWatcher/__mocks__/sane.js index 20dda2a2..9823a930 100644 --- a/react-packager/src/FileWatcher/__mocks__/sane.js +++ b/react-packager/src/FileWatcher/__mocks__/sane.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'; module.exports = { diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index 11b9f4a3..213033c5 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.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'; jest diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index 03e5452f..c1633683 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.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'; var EventEmitter = require('events').EventEmitter; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index bad0dadb..363be7b9 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.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'; var _ = require('underscore'); diff --git a/react-packager/src/JSTransformer/__mocks__/q.js b/react-packager/src/JSTransformer/__mocks__/q.js index 3d4d21f1..7ee0beac 100644 --- a/react-packager/src/JSTransformer/__mocks__/q.js +++ b/react-packager/src/JSTransformer/__mocks__/q.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'; // Bug with Jest because we're going to the node_modules that is a sibling diff --git a/react-packager/src/JSTransformer/__mocks__/underscore.js b/react-packager/src/JSTransformer/__mocks__/underscore.js index a985ab20..45754e1a 100644 --- a/react-packager/src/JSTransformer/__mocks__/underscore.js +++ b/react-packager/src/JSTransformer/__mocks__/underscore.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'; // Bug with Jest because we're going to the node_modules that is a sibling diff --git a/react-packager/src/JSTransformer/__mocks__/worker.js b/react-packager/src/JSTransformer/__mocks__/worker.js index 04a24e8d..e36b27e4 100644 --- a/react-packager/src/JSTransformer/__mocks__/worker.js +++ b/react-packager/src/JSTransformer/__mocks__/worker.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'; module.exports = function (data, callback) { diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index f5b55f05..6af1ff94 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.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'; jest diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 36d81d8f..72845a2e 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.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'; jest diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 00e49d5d..c7f7bb7f 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -1,4 +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'; var fs = require('fs'); diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js index 26f789e4..b6b4f363 100644 --- a/react-packager/src/JSTransformer/worker.js +++ b/react-packager/src/JSTransformer/worker.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'; var transformer = require('./transformer'); diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 5d9b201c..99edbbe0 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.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'; var _ = require('underscore'); diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index 41630fc4..ee94437d 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.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'; jest.autoMockOff(); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 498faea3..bed6fac3 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.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'; jest diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index c21889b4..ac7bd2e2 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.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'; var assert = require('assert'); diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index f4905806..8e638311 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.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'; jest.setMock('worker-farm', function() { return function() {}; }) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index a144c9bc..cc4f0bdf 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.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'; var url = require('url'); diff --git a/react-packager/src/lib/__mocks__/declareOpts.js b/react-packager/src/lib/__mocks__/declareOpts.js index 1afe4e29..7b54b27d 100644 --- a/react-packager/src/lib/__mocks__/declareOpts.js +++ b/react-packager/src/lib/__mocks__/declareOpts.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'; module.exports = function(declared) { diff --git a/react-packager/src/lib/__tests__/declareOpts-test.js b/react-packager/src/lib/__tests__/declareOpts-test.js index 66ae174f..cb9a35dd 100644 --- a/react-packager/src/lib/__tests__/declareOpts-test.js +++ b/react-packager/src/lib/__tests__/declareOpts-test.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'; jest.autoMockOff(); diff --git a/react-packager/src/lib/declareOpts.js b/react-packager/src/lib/declareOpts.js index 3b80da51..c20ae6d1 100644 --- a/react-packager/src/lib/declareOpts.js +++ b/react-packager/src/lib/declareOpts.js @@ -1,4 +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. + * * Declares, validates and defaults options. * var validate = declareOpts({ * foo: { diff --git a/transformer.js b/transformer.js index acb586d7..31cb8d31 100644 --- a/transformer.js +++ b/transformer.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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. * * Note: This is a fork of the fb-specific transform.js */ diff --git a/webSocketProxy.js b/webSocketProxy.js index dada059a..8223bbf2 100644 --- a/webSocketProxy.js +++ b/webSocketProxy.js @@ -1,7 +1,11 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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 WebSocketServer = require('ws').Server; From 9a6db3d6e689f8e3bf955bfd1dc439473c33da7f Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 23 Mar 2015 15:07:33 -0700 Subject: [PATCH 074/936] Second Updates from Mon 23 Mar - [ReactNative] Use deprecated ix in TabBarExample | Amjad Masad - [ReactNative] Expanded license on obj-c files | Christopher Chedeau - [ReactNative] Expanded license on js files | Christopher Chedeau - [ReactNative] Fix React Devtools integration | Alex Kotliarskyi - [Text] Account for font leading so descenders are not clipped | James Ide - [ReactNative] Expanded license on js packager files | Christopher Chedeau - more UIExplorer flow | Basil Hosmer - [react-packager] Pick up package changes while running | Amjad Masad - Added a graph view and a ReactNative metric that displays current queue and execution time for the JS thread. | Bryce Redd - [ReactNative] Add NativeModules and DeviceEventEmitter to react-native exports | Alex Kotliarskyi --- blacklist.js | 8 +- debugger.html | 9 ++ launchChromeDevTools.applescript | 6 ++ launchEditor.js | 7 +- launchPackager.command | 7 ++ packager.js | 7 +- packager.sh | 7 ++ parseCommandLine.js | 7 +- react-packager/__mocks__/debug.js | 8 ++ react-packager/__mocks__/net.js | 9 ++ react-packager/example_project/bar.js | 9 +- react-packager/example_project/foo/foo.js | 7 ++ react-packager/example_project/index.js | 8 +- react-packager/example_project/js/Channel.js | 7 ++ react-packager/example_project/js/XHR.js | 7 ++ react-packager/example_project/js/code.js | 7 ++ react-packager/example_project/js/main.js | 7 ++ .../example_project/public/css/index.css | 10 ++ .../example_project/public/index.html | 8 ++ react-packager/index.js | 8 ++ .../src/Activity/__tests__/Activity-test.js | 8 ++ react-packager/src/Activity/index.js | 8 ++ .../DependencyResolver/ModuleDescriptor.js | 8 ++ .../haste/DependencyGraph/__mocks__/fs.js | 8 ++ .../__tests__/DependencyGraph-test.js | 8 ++ .../haste/DependencyGraph/docblock.js | 17 +--- .../haste/DependencyGraph/index.js | 92 +++++++++++++------ .../__tests__/HasteDependencyResolver-test.js | 8 ++ .../src/DependencyResolver/haste/index.js | 8 ++ .../haste/polyfills/console.js | 17 +--- .../haste/polyfills/error-guard.js | 8 +- .../haste/polyfills/polyfills.js | 17 +--- .../src/DependencyResolver/index.js | 8 ++ .../src/DependencyResolver/node/index.js | 8 ++ .../src/FileWatcher/__mocks__/sane.js | 8 ++ .../FileWatcher/__tests__/FileWatcher-test.js | 8 ++ react-packager/src/FileWatcher/index.js | 10 +- react-packager/src/JSTransformer/Cache.js | 8 ++ .../src/JSTransformer/__mocks__/q.js | 8 ++ .../src/JSTransformer/__mocks__/underscore.js | 8 ++ .../src/JSTransformer/__mocks__/worker.js | 8 ++ .../src/JSTransformer/__tests__/Cache-test.js | 8 ++ .../__tests__/Transformer-test.js | 8 ++ react-packager/src/JSTransformer/index.js | 9 +- react-packager/src/JSTransformer/worker.js | 8 ++ react-packager/src/Packager/Package.js | 8 ++ .../src/Packager/__tests__/Package-test.js | 8 ++ .../src/Packager/__tests__/Packager-test.js | 8 ++ react-packager/src/Packager/index.js | 8 ++ .../src/Server/__tests__/Server-test.js | 8 ++ react-packager/src/Server/index.js | 10 +- .../src/lib/__mocks__/declareOpts.js | 8 ++ .../src/lib/__tests__/declareOpts-test.js | 8 ++ react-packager/src/lib/declareOpts.js | 7 ++ transformer.js | 7 +- webSocketProxy.js | 8 +- 56 files changed, 476 insertions(+), 79 deletions(-) diff --git a/blacklist.js b/blacklist.js index 850a8724..b5ba4185 100644 --- a/blacklist.js +++ b/blacklist.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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'; @@ -8,7 +13,6 @@ var sharedBlacklist = [ __dirname, 'website', - 'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js', 'node_modules/react-tools/src/utils/ImmutableObject.js', 'node_modules/react-tools/src/core/ReactInstanceHandles.js', 'node_modules/react-tools/src/event/EventPropagators.js' diff --git a/debugger.html b/debugger.html index f7cabc5f..38e9e6cd 100644 --- a/debugger.html +++ b/debugger.html @@ -1,4 +1,13 @@ + + diff --git a/launchChromeDevTools.applescript b/launchChromeDevTools.applescript index 4384b3ae..1fe6f4b0 100755 --- a/launchChromeDevTools.applescript +++ b/launchChromeDevTools.applescript @@ -1,5 +1,11 @@ #!/usr/bin/env osascript +-- 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. on run argv set theURL to item 1 of argv diff --git a/launchEditor.js b/launchEditor.js index 93db9bfc..cf89ed4f 100644 --- a/launchEditor.js +++ b/launchEditor.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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'; diff --git a/launchPackager.command b/launchPackager.command index dc56d7ff..eb777493 100755 --- a/launchPackager.command +++ b/launchPackager.command @@ -1,5 +1,12 @@ #!/bin/bash +# 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. + # Set terminal title echo -en "\033]0;React Packager\a" clear diff --git a/packager.js b/packager.js index 7214aaaf..12ef9ddd 100644 --- a/packager.js +++ b/packager.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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'; diff --git a/packager.sh b/packager.sh index 98e42184..969ca1b2 100755 --- a/packager.sh +++ b/packager.sh @@ -1,5 +1,12 @@ #!/bin/bash +# 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. + ulimit -n 4096 THIS_DIR=$(dirname "$0") diff --git a/parseCommandLine.js b/parseCommandLine.js index 5240d37d..65061e3f 100644 --- a/parseCommandLine.js +++ b/parseCommandLine.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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. * * Wrapper on-top of `optimist` in order to properly support boolean flags * and have a slightly less akward API. diff --git a/react-packager/__mocks__/debug.js b/react-packager/__mocks__/debug.js index d35fffd4..41955326 100644 --- a/react-packager/__mocks__/debug.js +++ b/react-packager/__mocks__/debug.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'; module.exports = function() { diff --git a/react-packager/__mocks__/net.js b/react-packager/__mocks__/net.js index 661fb196..43f51828 100644 --- a/react-packager/__mocks__/net.js +++ b/react-packager/__mocks__/net.js @@ -1,3 +1,12 @@ +/** + * 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. + */ + var EventEmitter = require('events').EventEmitter; var servers = {}; exports.createServer = function(listener) { diff --git a/react-packager/example_project/bar.js b/react-packager/example_project/bar.js index cc56ce6e..6653bdf7 100644 --- a/react-packager/example_project/bar.js +++ b/react-packager/example_project/bar.js @@ -1,5 +1,12 @@ /** + * 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; \ No newline at end of file + module.exports = setInterval; diff --git a/react-packager/example_project/foo/foo.js b/react-packager/example_project/foo/foo.js index c45d9aba..fe3c8cd1 100644 --- a/react-packager/example_project/foo/foo.js +++ b/react-packager/example_project/foo/foo.js @@ -1,4 +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. + * * @providesModule foo */ diff --git a/react-packager/example_project/index.js b/react-packager/example_project/index.js index 2943d187..d63b5193 100644 --- a/react-packager/example_project/index.js +++ b/react-packager/example_project/index.js @@ -1,6 +1,12 @@ /** + * 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 - * @jsx React.DOM */ require('main'); diff --git a/react-packager/example_project/js/Channel.js b/react-packager/example_project/js/Channel.js index d3cbae1c..6cbfce6f 100644 --- a/react-packager/example_project/js/Channel.js +++ b/react-packager/example_project/js/Channel.js @@ -1,4 +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. + * * @providesModule Channel */ diff --git a/react-packager/example_project/js/XHR.js b/react-packager/example_project/js/XHR.js index d9e0563f..bede8ca5 100644 --- a/react-packager/example_project/js/XHR.js +++ b/react-packager/example_project/js/XHR.js @@ -1,4 +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. + * * @providesModule XHR */ diff --git a/react-packager/example_project/js/code.js b/react-packager/example_project/js/code.js index 70067859..f99a90c9 100644 --- a/react-packager/example_project/js/code.js +++ b/react-packager/example_project/js/code.js @@ -1,4 +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. + * * @providesModule code */ var XHR = require('XHR'); diff --git a/react-packager/example_project/js/main.js b/react-packager/example_project/js/main.js index 58847092..405d015e 100644 --- a/react-packager/example_project/js/main.js +++ b/react-packager/example_project/js/main.js @@ -1,4 +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. + * * @providesModule main */ var Channel = require('Channel'); diff --git a/react-packager/example_project/public/css/index.css b/react-packager/example_project/public/css/index.css index 7d36bf2c..651f3326 100644 --- a/react-packager/example_project/public/css/index.css +++ b/react-packager/example_project/public/css/index.css @@ -1,3 +1,13 @@ +/** + * 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; } diff --git a/react-packager/example_project/public/index.html b/react-packager/example_project/public/index.html index b19685d5..e0e2ce7f 100644 --- a/react-packager/example_project/public/index.html +++ b/react-packager/example_project/public/index.html @@ -1,4 +1,12 @@ + diff --git a/react-packager/index.js b/react-packager/index.js index 65ae88d8..3a70659c 100644 --- a/react-packager/index.js +++ b/react-packager/index.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'; var Activity = require('./src/Activity'); diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index 7fe31614..c854aa33 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.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'; jest.autoMockOff(); diff --git a/react-packager/src/Activity/index.js b/react-packager/src/Activity/index.js index 611ccb0b..05285d0f 100644 --- a/react-packager/src/Activity/index.js +++ b/react-packager/src/Activity/index.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'; var COLLECTION_PERIOD = 1000; diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index df29e57c..1388a610 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.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'; function ModuleDescriptor(fields) { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js index de3622d9..3ebee183 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.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'; var fs = jest.genMockFromModule('fs'); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 6dee93ec..bbdb0641 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.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'; jest diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js index c2b6ac98..131f09d1 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js @@ -1,17 +1,10 @@ /** - * Copyright 2013 Facebook, Inc. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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'; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 918b1e06..14139159 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.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'; var ModuleDescriptor = require('../../ModuleDescriptor'); @@ -296,35 +304,50 @@ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { } if (packagePath != null) { - return readFile(packagePath, 'utf8') - .then(function(content) { - var packageJson; - try { - packageJson = JSON.parse(content); - } catch (e) { - debug('WARNING: malformed package.json: ', packagePath); - return q(); - } - - if (packageJson.name == null) { - debug( - 'WARNING: package.json `%s` is missing a name field', - packagePath - ); - return q(); - } - - packageJson._root = root; - self._packageByRoot[root] = packageJson; - self._packagesById[packageJson.name] = packageJson; - - return packageJson; - }); + return this._processPackage(packagePath); } else { return q(); } }; +DependecyGraph.prototype._processPackage = function(packagePath) { + var packageRoot = path.dirname(packagePath); + var self = this; + return readFile(packagePath, 'utf8') + .then(function(content) { + var packageJson; + try { + packageJson = JSON.parse(content); + } catch (e) { + debug('WARNING: malformed package.json: ', packagePath); + return q(); + } + + if (packageJson.name == null) { + debug( + 'WARNING: package.json `%s` is missing a name field', + packagePath + ); + return q(); + } + + packageJson._root = packageRoot; + self._addPackageToIndices(packageJson); + + return packageJson; + }); +}; + +DependecyGraph.prototype._addPackageToIndices = function(packageJson) { + this._packageByRoot[packageJson._root] = packageJson; + this._packagesById[packageJson.name] = packageJson; +}; + +DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { + delete this._packageByRoot[packageJson._root]; + delete this._packagesById[packageJson.name]; +}; + /** * Parse a module and update indices. */ @@ -436,16 +459,27 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root this._debugUpdateEvents.push({event: eventType, path: filePath}); + var isPackage = path.basename(filePath) === 'package.json'; if (eventType === 'delete') { - var module = this._graph[absPath]; - if (module == null) { - return; - } + if (isPackage) { + var packageJson = this._packageByRoot[path.dirname(absPath)]; + if (packageJson) { + this._removePackageFromIndices(packageJson); + } + } else { + var module = this._graph[absPath]; + if (module == null) { + return; + } - this._deleteModule(module); + this._deleteModule(module); + } } else if (!(stat && stat.isDirectory())) { var self = this; this._loading = this._loading.then(function() { + if (isPackage) { + return self._processPackage(absPath); + } return self._processModule(absPath); }); } diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index b25fd821..90ae5888 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.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'; jest.dontMock('../') diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index fdc779ed..2edb3b52 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.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'; var path = require('path'); diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index 1b2604e3..91fb970f 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -1,17 +1,10 @@ /** - * Copyright 2013 Facebook, Inc. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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. * * This pipes all of our console logging functions to native logging so that * JavaScript errors in required modules show up in Xcode via NSLog. diff --git a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js index 745d650e..3816617f 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js @@ -1,5 +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. + * * The particular require runtime that we are using looks for a global * `ErrorUtils` object and if it exists, then it requires modules with the * error handler specified via ErrorUtils.setGlobalHandler by calling the diff --git a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js index 75f74279..84bf2d3d 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js @@ -1,17 +1,10 @@ /** - * Copyright 2013 Facebook, Inc. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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. * * This pipes all of our console logging functions to native logging so that * JavaScript errors in required modules show up in Xcode via NSLog. diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index f42ecb8a..ca80ab0b 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.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'; var HasteDependencyResolver = require('./haste'); diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js index da03cc7e..46c93c2f 100644 --- a/react-packager/src/DependencyResolver/node/index.js +++ b/react-packager/src/DependencyResolver/node/index.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'; var Promise = require('q').Promise; diff --git a/react-packager/src/FileWatcher/__mocks__/sane.js b/react-packager/src/FileWatcher/__mocks__/sane.js index 20dda2a2..9823a930 100644 --- a/react-packager/src/FileWatcher/__mocks__/sane.js +++ b/react-packager/src/FileWatcher/__mocks__/sane.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'; module.exports = { diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index 11b9f4a3..213033c5 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.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'; jest diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index f2721d8c..c1633683 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.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'; var EventEmitter = require('events').EventEmitter; @@ -56,7 +64,7 @@ function createWatcher(root) { } return detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(root, {glob: '**/*.js'}); + var watcher = new Watcher(root, {glob: ['**/*.js', '**/package.json']}); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index bad0dadb..363be7b9 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.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'; var _ = require('underscore'); diff --git a/react-packager/src/JSTransformer/__mocks__/q.js b/react-packager/src/JSTransformer/__mocks__/q.js index 3d4d21f1..7ee0beac 100644 --- a/react-packager/src/JSTransformer/__mocks__/q.js +++ b/react-packager/src/JSTransformer/__mocks__/q.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'; // Bug with Jest because we're going to the node_modules that is a sibling diff --git a/react-packager/src/JSTransformer/__mocks__/underscore.js b/react-packager/src/JSTransformer/__mocks__/underscore.js index a985ab20..45754e1a 100644 --- a/react-packager/src/JSTransformer/__mocks__/underscore.js +++ b/react-packager/src/JSTransformer/__mocks__/underscore.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'; // Bug with Jest because we're going to the node_modules that is a sibling diff --git a/react-packager/src/JSTransformer/__mocks__/worker.js b/react-packager/src/JSTransformer/__mocks__/worker.js index 04a24e8d..e36b27e4 100644 --- a/react-packager/src/JSTransformer/__mocks__/worker.js +++ b/react-packager/src/JSTransformer/__mocks__/worker.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'; module.exports = function (data, callback) { diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index f5b55f05..6af1ff94 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.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'; jest diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 36d81d8f..72845a2e 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.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'; jest diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 00e49d5d..c7f7bb7f 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -1,4 +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'; var fs = require('fs'); diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js index 26f789e4..b6b4f363 100644 --- a/react-packager/src/JSTransformer/worker.js +++ b/react-packager/src/JSTransformer/worker.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'; var transformer = require('./transformer'); diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 5d9b201c..99edbbe0 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.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'; var _ = require('underscore'); diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index 41630fc4..ee94437d 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.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'; jest.autoMockOff(); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 498faea3..bed6fac3 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.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'; jest diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index c21889b4..ac7bd2e2 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.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'; var assert = require('assert'); diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index f4905806..8e638311 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.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'; jest.setMock('worker-farm', function() { return function() {}; }) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 3043903d..cc4f0bdf 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.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'; var url = require('url'); @@ -67,7 +75,7 @@ function Server(options) { this._debouncedFileChangeHandler = _.debounce(function(filePath) { self._rebuildPackages(filePath); self._informChangeWatchers(); - }, 50, true); + }, 50); } Server.prototype._onFileChange = function(type, filepath, root) { diff --git a/react-packager/src/lib/__mocks__/declareOpts.js b/react-packager/src/lib/__mocks__/declareOpts.js index 1afe4e29..7b54b27d 100644 --- a/react-packager/src/lib/__mocks__/declareOpts.js +++ b/react-packager/src/lib/__mocks__/declareOpts.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'; module.exports = function(declared) { diff --git a/react-packager/src/lib/__tests__/declareOpts-test.js b/react-packager/src/lib/__tests__/declareOpts-test.js index 66ae174f..cb9a35dd 100644 --- a/react-packager/src/lib/__tests__/declareOpts-test.js +++ b/react-packager/src/lib/__tests__/declareOpts-test.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'; jest.autoMockOff(); diff --git a/react-packager/src/lib/declareOpts.js b/react-packager/src/lib/declareOpts.js index 3b80da51..c20ae6d1 100644 --- a/react-packager/src/lib/declareOpts.js +++ b/react-packager/src/lib/declareOpts.js @@ -1,4 +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. + * * Declares, validates and defaults options. * var validate = declareOpts({ * foo: { diff --git a/transformer.js b/transformer.js index acb586d7..31cb8d31 100644 --- a/transformer.js +++ b/transformer.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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. * * Note: This is a fork of the fb-specific transform.js */ diff --git a/webSocketProxy.js b/webSocketProxy.js index dada059a..8223bbf2 100644 --- a/webSocketProxy.js +++ b/webSocketProxy.js @@ -1,7 +1,11 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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 WebSocketServer = require('ws').Server; From 81634084bc5365b8bbae7bde01880e367f3f7cb9 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 23 Mar 2015 15:12:34 -0700 Subject: [PATCH 075/936] [react-packager] Default to index.js from main if it's a dir --- .../__tests__/DependencyGraph-test.js | 63 +++++++++++++++++++ .../haste/DependencyGraph/index.js | 6 ++ 2 files changed, 69 insertions(+) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index bbdb0641..8ebe9c5a 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -166,6 +166,69 @@ describe('DependencyGraph', function() { }); }); + pit('should default main package to index.js', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': 'lol', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/index', + path: '/root/aPackage/index.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should default use index.js if main is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'lib', + }), + lib: { + 'index.js': 'lol', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/lib/index', + path: '/root/aPackage/lib/index.js', + dependencies: [] + }, + ]); + }); + }); + pit('should ignore malformed packages', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 14139159..4622864d 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -184,6 +184,12 @@ DependecyGraph.prototype.resolveDependency = function( var main = packageJson.main || 'index'; modulePath = withExtJs(path.join(packageJson._root, main)); dep = this._graph[modulePath]; + + // Some packages use just a dir and rely on an index.js inside that dir. + if (dep == null) { + dep = this._graph[path.join(packageJson._root, main, 'index.js')]; + } + if (dep == null) { throw new Error( 'Cannot find package main file for package: ' + packageJson._root From 7e2b9bfd77c4c7bcb36ca4ab1e822fb3cecf0855 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 23 Mar 2015 17:40:51 -0700 Subject: [PATCH 076/936] [ReactNative] Remove `arc build` instructions from require --- .../src/DependencyResolver/haste/polyfills/require.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/haste/polyfills/require.js index e7fdde25..00955ba4 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/require.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -196,7 +196,7 @@ if (!module) { msg = 'Requiring unknown module "' + id + '"'; if (__DEV__) { - msg += '. It may not be loaded yet. Did you forget to run arc build?'; + msg += '. If you are sure the module is there, try restarting the packager.'; } throw new ModuleError(msg); } From fc92348d8bd7c5b68e158af5d40a44bada67b34f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 23 Mar 2015 19:17:22 -0700 Subject: [PATCH 077/936] [react-packager] Fix regression with transform errors --- react-packager/src/Server/__tests__/Server-test.js | 1 + react-packager/src/Server/index.js | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 8e638311..aa7ca349 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -186,6 +186,7 @@ describe('processRequest', function() { expect(packageFunc.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); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index cc4f0bdf..19ec0039 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -92,8 +92,10 @@ Server.prototype._rebuildPackages = function() { Object.keys(packages).forEach(function(key) { var options = getOptionsFromUrl(key); // Wait for a previous build (if exists) to finish. - packages[key] = (packages[key] || q()).then(function() { - return buildPackage(options).then(function(p) { + packages[key] = (packages[key] || q()).finally(function() { + // With finally promise callback we can't change the state of the promise + // so we need to reassign the promise. + packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({ inlineSourceMap: options.dev, @@ -102,6 +104,7 @@ Server.prototype._rebuildPackages = function() { return p; }); }); + return packages[key]; }); }; From 074065331ae8c627c4dd6c687ad39cabd9224d6f Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Tue, 24 Mar 2015 09:26:16 -0700 Subject: [PATCH 078/936] Updates from Tue 24 Mar - [ReactNative] Open Source PushNotifications and move Badge Number methods and permission into it | Tadeu Zagallo - [react-packager] Fix regression with transform errors | Amjad Masad - Flowify TextStylePropTypes and fix a bug with unsupported props | Marshall Roch - [ReactNative] Remove `arc build` instructions from require | Alex Kotliarskyi - Flowify Library/Utilities/ | Marshall Roch - [react-packager] Default to index.js from main if it's a dir | Amjad Masad --- .../__tests__/DependencyGraph-test.js | 63 +++++++++++++++++++ .../haste/DependencyGraph/index.js | 6 ++ .../haste/polyfills/require.js | 2 +- .../src/Server/__tests__/Server-test.js | 1 + react-packager/src/Server/index.js | 7 ++- 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index bbdb0641..8ebe9c5a 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -166,6 +166,69 @@ describe('DependencyGraph', function() { }); }); + pit('should default main package to index.js', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': 'lol', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/index', + path: '/root/aPackage/index.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should default use index.js if main is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'lib', + }), + lib: { + 'index.js': 'lol', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/lib/index', + path: '/root/aPackage/lib/index.js', + dependencies: [] + }, + ]); + }); + }); + pit('should ignore malformed packages', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 14139159..4622864d 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -184,6 +184,12 @@ DependecyGraph.prototype.resolveDependency = function( var main = packageJson.main || 'index'; modulePath = withExtJs(path.join(packageJson._root, main)); dep = this._graph[modulePath]; + + // Some packages use just a dir and rely on an index.js inside that dir. + if (dep == null) { + dep = this._graph[path.join(packageJson._root, main, 'index.js')]; + } + if (dep == null) { throw new Error( 'Cannot find package main file for package: ' + packageJson._root diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/haste/polyfills/require.js index e7fdde25..00955ba4 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/require.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -196,7 +196,7 @@ if (!module) { msg = 'Requiring unknown module "' + id + '"'; if (__DEV__) { - msg += '. It may not be loaded yet. Did you forget to run arc build?'; + msg += '. If you are sure the module is there, try restarting the packager.'; } throw new ModuleError(msg); } diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 8e638311..aa7ca349 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -186,6 +186,7 @@ describe('processRequest', function() { expect(packageFunc.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); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index cc4f0bdf..19ec0039 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -92,8 +92,10 @@ Server.prototype._rebuildPackages = function() { Object.keys(packages).forEach(function(key) { var options = getOptionsFromUrl(key); // Wait for a previous build (if exists) to finish. - packages[key] = (packages[key] || q()).then(function() { - return buildPackage(options).then(function(p) { + packages[key] = (packages[key] || q()).finally(function() { + // With finally promise callback we can't change the state of the promise + // so we need to reassign the promise. + packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({ inlineSourceMap: options.dev, @@ -102,6 +104,7 @@ Server.prototype._rebuildPackages = function() { return p; }); }); + return packages[key]; }); }; From bef3d46cee289bf0a2c4d6c5affded479d564ca0 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 24 Mar 2015 16:55:27 -0700 Subject: [PATCH 079/936] [react-packager] Fix more issues with node modules --- .../DependencyResolver/ModuleDescriptor.js | 2 + .../__tests__/DependencyGraph-test.js | 108 +++++++++++++++--- .../haste/DependencyGraph/index.js | 21 ++++ .../__tests__/HasteDependencyResolver-test.js | 2 +- 4 files changed, 114 insertions(+), 19 deletions(-) diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index 1388a610..c56593cf 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -32,6 +32,8 @@ function ModuleDescriptor(fields) { this.isAsset = fields.isAsset || false; + this.altId = fields.altId; + this._fields = fields; } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 8ebe9c5a..962a0694 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -58,8 +58,8 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', path: '/root/a.js', dependencies: []}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, ]); }); }); @@ -88,7 +88,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['image!a']}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], @@ -124,8 +124,8 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', path: '/root/a.js', dependencies: ['index']}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, ]); }); }); @@ -157,7 +157,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['aPackage']}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, { id: 'aPackage/main', path: '/root/aPackage/main.js', dependencies: [] @@ -196,6 +196,41 @@ describe('DependencyGraph', function() { }); }); + pit('should have altId for a package with providesModule', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': [ + '/**', + ' * @providesModule EpicModule', + ' */', + ].join('\n'), + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'EpicModule', + altId: 'aPackage/index', + path: '/root/aPackage/index.js', + dependencies: [] + }, + ]); + }); + }); + pit('should default use index.js if main is a dir', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -229,6 +264,36 @@ describe('DependencyGraph', function() { }); }); + pit('should resolve require to index if it is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'index.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, + { id: 'test/lib/index', + path: '/root/lib/index.js', + dependencies: [] + }, + ]); + }); + }); + pit('should ignore malformed packages', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -253,7 +318,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['aPackage']}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, ]); }); }); @@ -297,10 +362,12 @@ describe('DependencyGraph', function() { expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) .toEqual([ { id: 'index', + altId: '/root/somedir/somefile.js', path: '/root/somedir/somefile.js', dependencies: ['c'] }, { id: 'c', + altId: '/root/c.js', path: '/root/c.js', dependencies: [] }, @@ -340,11 +407,12 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage'] }, { id: 'aPackage', + altId: '/root/b.js', path: '/root/b.js', dependencies: [] }, @@ -372,7 +440,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['lolomg'] } @@ -410,7 +478,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage/subdir/lolynot'] }, @@ -453,7 +521,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage/subdir/lolynot'] }, @@ -496,7 +564,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage'] }, @@ -570,7 +638,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage'] }, @@ -621,7 +689,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage'] }, @@ -671,7 +739,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage', 'foo'] }, @@ -730,7 +798,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage', 'foo'] }, @@ -739,10 +807,12 @@ describe('DependencyGraph', function() { dependencies: ['bar'] }, { id: 'bar', + altId: '/root/bar.js', path: '/root/bar.js', dependencies: ['foo'] }, { id: 'foo', + altId: '/root/foo.js', path: '/root/foo.js', dependencies: ['aPackage'] }, @@ -803,7 +873,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage', 'foo'] }, @@ -812,6 +882,7 @@ describe('DependencyGraph', function() { dependencies: ['bar'] }, { id: 'foo', + altId: '/root/foo.js', path: '/root/foo.js', dependencies: ['aPackage'] }, @@ -857,7 +928,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage', 'foo'] }, @@ -866,6 +937,7 @@ describe('DependencyGraph', function() { dependencies: [] }, { id: 'foo', + altId: '/root/foo.js', path: '/root/foo.js', dependencies: ['aPackage'] }, diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 4622864d..adb01282 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -216,6 +216,13 @@ DependecyGraph.prototype.resolveDependency = function( modulePath = withExtJs(path.join(dir, depModuleId)); dep = this._graph[modulePath]; + + if (dep == null) { + modulePath = path.join(dir, depModuleId, 'index.js'); + } + + dep = this._graph[modulePath]; + if (dep == null) { debug( 'WARNING: Cannot find required module `%s` from module `%s`.' + @@ -366,6 +373,10 @@ DependecyGraph.prototype._processModule = function(modulePath) { if (moduleDocBlock.providesModule || moduleDocBlock.provides) { moduleData.id = moduleDocBlock.providesModule || moduleDocBlock.provides; + + // Incase someone wants to require this module via + // packageName/path/to/module + moduleData.altId = self._lookupName(modulePath); } else { moduleData.id = self._lookupName(modulePath); } @@ -401,6 +412,10 @@ DependecyGraph.prototype._deleteModule = function(module) { if (this._moduleById[module.id] === module) { delete this._moduleById[module.id]; } + + if (module.altId && this._moduleById[module.altId] === module) { + delete this._moduleById[module.altId]; + } }; /** @@ -424,6 +439,12 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { } this._moduleById[module.id] = module; + + // Some module maybe refrenced by both @providesModule and + // require(package/moduleName). + if (module.altId != null && this._moduleById[module.altId] == null) { + this._moduleById[module.altId] = module; + } }; /** diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 90ae5888..69354590 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -220,7 +220,7 @@ describe('HasteDependencyResolver', function() { }); describe('wrapModule', function() { - it('should ', function() { + it('should resolve modules', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', }); From ec1b9ec735d7ef7916810c92e72ce97b06676974 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 24 Mar 2015 17:32:03 -0700 Subject: [PATCH 080/936] [react-packager] kill non-standard RAW_SOURCE_MAP --- react-packager/src/Packager/Package.js | 10 +++++----- react-packager/src/Packager/__tests__/Package-test.js | 8 +++----- react-packager/src/Server/index.js | 9 +++++++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 99edbbe0..0f55c8ed 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -68,10 +68,9 @@ Package.prototype._getSource = function() { Package.prototype._getInlineSourceMap = function() { if (this._inlineSourceMap == null) { var sourceMap = this.getSourceMap({excludeSource: true}); - this._inlineSourceMap = '\nRAW_SOURCE_MAP = ' + - JSON.stringify(sourceMap) + ';'; + var encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64'); + this._inlineSourceMap = 'data:application/json;base64,' + encoded; } - return this._inlineSourceMap; }; @@ -85,13 +84,14 @@ Package.prototype.getSource = function(options) { } var source = this._getSource(); + source += '\n\/\/@ sourceMappingURL='; if (options.inlineSourceMap) { source += this._getInlineSourceMap(); + } else { + source += this._sourceMapUrl; } - source += '\n\/\/@ sourceMappingURL=' + this._sourceMapUrl; - return source; }; diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index ee94437d..5a7438d2 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -29,11 +29,10 @@ describe('Package', function() { ppackage.addModule('transformed foo;', 'source foo', 'foo path'); ppackage.addModule('transformed bar;', 'source bar', 'bar path'); ppackage.finalize({}); - expect(ppackage.getSource({inlineSourceMap: true})).toBe([ + expect(ppackage.getSource()).toBe([ 'transformed foo;', 'transformed bar;', - 'RAW_SOURCE_MAP = "test-source-map";', - '\/\/@ sourceMappingURL=test_url', + '\/\/@ sourceMappingURL=test_url' ].join('\n')); }); @@ -42,11 +41,10 @@ describe('Package', function() { ppackage.addModule('transformed bar;', 'source bar', 'bar path'); ppackage.setMainModuleId('foo'); ppackage.finalize({runMainModule: true}); - expect(ppackage.getSource({inlineSourceMap: true})).toBe([ + expect(ppackage.getSource()).toBe([ 'transformed foo;', 'transformed bar;', ';require("foo");', - 'RAW_SOURCE_MAP = "test-source-map";', '\/\/@ sourceMappingURL=test_url', ].join('\n')); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 19ec0039..f40ebeea 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -98,7 +98,7 @@ Server.prototype._rebuildPackages = function() { packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({ - inlineSourceMap: options.dev, + inlineSourceMap: options.inlineSourceMap, minify: options.minify, }); return p; @@ -228,7 +228,7 @@ Server.prototype.processRequest = function(req, res, next) { function(p) { if (requestType === 'bundle') { res.end(p.getSource({ - inlineSourceMap: options.dev, + inlineSourceMap: options.inlineSourceMap, minify: options.minify, })); Activity.endEvent(startReqEventId); @@ -264,6 +264,11 @@ function getOptionsFromUrl(reqUrl) { dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), minify: getBoolOptionFromQuery(urlObj.query, 'minify'), runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true), + inlineSourceMap: getBoolOptionFromQuery( + urlObj.query, + 'inlineSourceMap', + false + ), }; } From c086b0c34334b2475975ce94cf54481795b166a3 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Tue, 24 Mar 2015 19:34:12 -0700 Subject: [PATCH 081/936] Second Update from Tue 24 Mar --- .../DependencyResolver/ModuleDescriptor.js | 2 + .../__tests__/DependencyGraph-test.js | 108 +++++++++++++++--- .../haste/DependencyGraph/index.js | 21 ++++ .../__tests__/HasteDependencyResolver-test.js | 2 +- react-packager/src/Packager/Package.js | 10 +- .../src/Packager/__tests__/Package-test.js | 8 +- react-packager/src/Server/index.js | 9 +- 7 files changed, 129 insertions(+), 31 deletions(-) diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index 1388a610..c56593cf 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -32,6 +32,8 @@ function ModuleDescriptor(fields) { this.isAsset = fields.isAsset || false; + this.altId = fields.altId; + this._fields = fields; } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 8ebe9c5a..962a0694 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -58,8 +58,8 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', path: '/root/a.js', dependencies: []}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, ]); }); }); @@ -88,7 +88,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['image!a']}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], @@ -124,8 +124,8 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', path: '/root/a.js', dependencies: ['index']}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, ]); }); }); @@ -157,7 +157,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['aPackage']}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, { id: 'aPackage/main', path: '/root/aPackage/main.js', dependencies: [] @@ -196,6 +196,41 @@ describe('DependencyGraph', function() { }); }); + pit('should have altId for a package with providesModule', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': [ + '/**', + ' * @providesModule EpicModule', + ' */', + ].join('\n'), + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'EpicModule', + altId: 'aPackage/index', + path: '/root/aPackage/index.js', + dependencies: [] + }, + ]); + }); + }); + pit('should default use index.js if main is a dir', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -229,6 +264,36 @@ describe('DependencyGraph', function() { }); }); + pit('should resolve require to index if it is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'index.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, + { id: 'test/lib/index', + path: '/root/lib/index.js', + dependencies: [] + }, + ]); + }); + }); + pit('should ignore malformed packages', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -253,7 +318,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - {id: 'index', path: '/root/index.js', dependencies: ['aPackage']}, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, ]); }); }); @@ -297,10 +362,12 @@ describe('DependencyGraph', function() { expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) .toEqual([ { id: 'index', + altId: '/root/somedir/somefile.js', path: '/root/somedir/somefile.js', dependencies: ['c'] }, { id: 'c', + altId: '/root/c.js', path: '/root/c.js', dependencies: [] }, @@ -340,11 +407,12 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage'] }, { id: 'aPackage', + altId: '/root/b.js', path: '/root/b.js', dependencies: [] }, @@ -372,7 +440,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['lolomg'] } @@ -410,7 +478,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage/subdir/lolynot'] }, @@ -453,7 +521,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage/subdir/lolynot'] }, @@ -496,7 +564,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage'] }, @@ -570,7 +638,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage'] }, @@ -621,7 +689,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage'] }, @@ -671,7 +739,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage', 'foo'] }, @@ -730,7 +798,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage', 'foo'] }, @@ -739,10 +807,12 @@ describe('DependencyGraph', function() { dependencies: ['bar'] }, { id: 'bar', + altId: '/root/bar.js', path: '/root/bar.js', dependencies: ['foo'] }, { id: 'foo', + altId: '/root/foo.js', path: '/root/foo.js', dependencies: ['aPackage'] }, @@ -803,7 +873,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage', 'foo'] }, @@ -812,6 +882,7 @@ describe('DependencyGraph', function() { dependencies: ['bar'] }, { id: 'foo', + altId: '/root/foo.js', path: '/root/foo.js', dependencies: ['aPackage'] }, @@ -857,7 +928,7 @@ describe('DependencyGraph', function() { return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { id: 'index', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage', 'foo'] }, @@ -866,6 +937,7 @@ describe('DependencyGraph', function() { dependencies: [] }, { id: 'foo', + altId: '/root/foo.js', path: '/root/foo.js', dependencies: ['aPackage'] }, diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 4622864d..adb01282 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -216,6 +216,13 @@ DependecyGraph.prototype.resolveDependency = function( modulePath = withExtJs(path.join(dir, depModuleId)); dep = this._graph[modulePath]; + + if (dep == null) { + modulePath = path.join(dir, depModuleId, 'index.js'); + } + + dep = this._graph[modulePath]; + if (dep == null) { debug( 'WARNING: Cannot find required module `%s` from module `%s`.' + @@ -366,6 +373,10 @@ DependecyGraph.prototype._processModule = function(modulePath) { if (moduleDocBlock.providesModule || moduleDocBlock.provides) { moduleData.id = moduleDocBlock.providesModule || moduleDocBlock.provides; + + // Incase someone wants to require this module via + // packageName/path/to/module + moduleData.altId = self._lookupName(modulePath); } else { moduleData.id = self._lookupName(modulePath); } @@ -401,6 +412,10 @@ DependecyGraph.prototype._deleteModule = function(module) { if (this._moduleById[module.id] === module) { delete this._moduleById[module.id]; } + + if (module.altId && this._moduleById[module.altId] === module) { + delete this._moduleById[module.altId]; + } }; /** @@ -424,6 +439,12 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { } this._moduleById[module.id] = module; + + // Some module maybe refrenced by both @providesModule and + // require(package/moduleName). + if (module.altId != null && this._moduleById[module.altId] == null) { + this._moduleById[module.altId] = module; + } }; /** diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 90ae5888..69354590 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -220,7 +220,7 @@ describe('HasteDependencyResolver', function() { }); describe('wrapModule', function() { - it('should ', function() { + it('should resolve modules', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', }); diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 99edbbe0..0f55c8ed 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -68,10 +68,9 @@ Package.prototype._getSource = function() { Package.prototype._getInlineSourceMap = function() { if (this._inlineSourceMap == null) { var sourceMap = this.getSourceMap({excludeSource: true}); - this._inlineSourceMap = '\nRAW_SOURCE_MAP = ' + - JSON.stringify(sourceMap) + ';'; + var encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64'); + this._inlineSourceMap = 'data:application/json;base64,' + encoded; } - return this._inlineSourceMap; }; @@ -85,13 +84,14 @@ Package.prototype.getSource = function(options) { } var source = this._getSource(); + source += '\n\/\/@ sourceMappingURL='; if (options.inlineSourceMap) { source += this._getInlineSourceMap(); + } else { + source += this._sourceMapUrl; } - source += '\n\/\/@ sourceMappingURL=' + this._sourceMapUrl; - return source; }; diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index ee94437d..5a7438d2 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -29,11 +29,10 @@ describe('Package', function() { ppackage.addModule('transformed foo;', 'source foo', 'foo path'); ppackage.addModule('transformed bar;', 'source bar', 'bar path'); ppackage.finalize({}); - expect(ppackage.getSource({inlineSourceMap: true})).toBe([ + expect(ppackage.getSource()).toBe([ 'transformed foo;', 'transformed bar;', - 'RAW_SOURCE_MAP = "test-source-map";', - '\/\/@ sourceMappingURL=test_url', + '\/\/@ sourceMappingURL=test_url' ].join('\n')); }); @@ -42,11 +41,10 @@ describe('Package', function() { ppackage.addModule('transformed bar;', 'source bar', 'bar path'); ppackage.setMainModuleId('foo'); ppackage.finalize({runMainModule: true}); - expect(ppackage.getSource({inlineSourceMap: true})).toBe([ + expect(ppackage.getSource()).toBe([ 'transformed foo;', 'transformed bar;', ';require("foo");', - 'RAW_SOURCE_MAP = "test-source-map";', '\/\/@ sourceMappingURL=test_url', ].join('\n')); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 19ec0039..f40ebeea 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -98,7 +98,7 @@ Server.prototype._rebuildPackages = function() { packages[key] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({ - inlineSourceMap: options.dev, + inlineSourceMap: options.inlineSourceMap, minify: options.minify, }); return p; @@ -228,7 +228,7 @@ Server.prototype.processRequest = function(req, res, next) { function(p) { if (requestType === 'bundle') { res.end(p.getSource({ - inlineSourceMap: options.dev, + inlineSourceMap: options.inlineSourceMap, minify: options.minify, })); Activity.endEvent(startReqEventId); @@ -264,6 +264,11 @@ function getOptionsFromUrl(reqUrl) { dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), minify: getBoolOptionFromQuery(urlObj.query, 'minify'), runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true), + inlineSourceMap: getBoolOptionFromQuery( + urlObj.query, + 'inlineSourceMap', + false + ), }; } From 789516b9a8c613e1bc7c493fded672a471271dcb Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 25 Mar 2015 15:31:36 -0700 Subject: [PATCH 082/936] [react-packager] Readme --- README.md | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..8f9f649b --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +React Native Packager +-------------------- + +React Native Packager is a project similar in scope to browserify or +webpack, it provides a CommonJS-like module system, JavaScript +compilation (ES6, Flow, JSX), bundling, and asset loading. + +The main difference is the Packager's focus on compilation and +bundling speed. We aim for a sub-second edit-reload +cycles. Additionally, we don't want users -- with large code bases -- +to wait more than a few seconds after starting the packager. + +The main deviation from the node module system is the support for our +proprietary module format known as `@providesModule`. However, we +discourage people to use this module format because going forward, we +want to completely separate our infrastructure from React Native and +provide an experience most JavaScript developers are familiar with, +namely the node module format. We want to even go further, and let you +choose your own packager and asset pipeline or even integrate into +your existing infrastructure. + +React Native users need not to understand how the packager work, +however, this documentation might be useful for advanced users and +people who want to fix bugs or add features to the packager (patches +welcome!). + +## HTTP interface + +The main way you'd interact with the packager is via the HTTP +interface. The following is the list of endpoints and their respective +functions. + +### /path/to/moduleName.bundle + +Does the following in order: + +* parse out `path/to/moduleName` +* add a `.js` suffix to the path +* looks in your project root(s) for the file +* recursively collects all the dependencies from an in memory graph +* runs the modules through the transformer (might just be cached) +* concatenate the modules' content into a bundle +* responds to the client with the bundle (and a SourceMap URL) + +### /path/to/moduleName.map + +* if the package has been previously generated via the `.bundle` + endpoint then the source map will be generated from that package +* if the package has not been previously asked for, this will go + through the same steps outlined in the `.bundle` endpoint then + generate the source map. + +Note that source map generation currently assumes that the code has +been compiled with jstransform, which preserves line and column +numbers which allows us to generate source maps super fast. + +### /path/to/moduleName.(map|bundle) query params + +You can pass options for the bundle creation through the query params, +if the option is boolean `1/0` or `true/false` is accepted. + +Here are the current options the packager accepts: + +* `dev` boolean, defaults to true: sets a global `__DEV__` variable + which will effect how the React Nativeg core libraries behave. +* `minify` boolean, defaults to false: whether to minify the bundle. +* `runModule` boolean, defaults to true: whether to require your entry + point module. So if you requested `moduleName`, this option will add + a `require('moduleName')` the end of your bundle. +* `inlineSourceMap` boolean, defaults to false: whether to inline + source maps. + +### /debug + +This is a page used for debugging, it has links to two pages: + +* Cached Packages: which shows you the packages that's been already + generated and cached +* Dependency Graph: is the in-memory graph of all the modules and + their dependencies + +## Programmatic API + +The packager is made of two things: + +* The core packager (which we're calling ReactPackager) +* The scripts, devtools launcher, server run etc. + +ReactPackager is how you mainly interact with the API. + +```js +var ReactPackager = require('./react-packager'); +``` + +### ReactPackager.middleware(options) + +Returns a function that can be used in a connect-like +middleware. Takes the following options: + +* `projectRoots` array (required): Is the roots where your JavaScript + file will exist +* `blacklistRE` regexp: Is a patter to ignore certain paths from the + packager +* `polyfillModuleName` array: Paths to polyfills you want to be + included at the start of the bundle +* `cacheVersion` string: used in creating the cache file +* `resetCache` boolean, defaults to false: whether to use the cache on + disk +* `transformModulePath` string: Path to the module used as a + JavaScript transformer +* `nonPersistent` boolean, defaults to false: Whether the server + should be used as a persistent deamon to watch files and update + itself +* `assetRoots` array: Where should the packager look for assets + +### ReactPackager.buildPackageFromUrl(options, url) + +Build a package from a url (see the `.bundle` endpoint). `options` is +the same options that is passed to `ReactPackager.middleware` + +### ReactPackager.getDependencies(options, main) + +Given an entry point module. Recursively collect all the dependent +modules and return it as an array. `options` is the same options that +is passed to `ReactPackager.middleware` + +## FAQ + +### Can I use this in my own non-React Native project? + +Yes. It's not really tied to React Native, however feature development +is informed by React Native needs. + +### Why didn't you use webpack? + +We love webpack, however, when we tried on our codebase it was slower +than our developers would like it to be. You find can more discussion about +the subject [here](https://github.com/facebook/react-native/issues/5) From 8e71fc536224c2769bdf9f4a9b8e678f451aae32 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 25 Mar 2015 18:19:28 -0700 Subject: [PATCH 083/936] Updates from Wed 25 Mar - [RFC][ReactNative] Integrate dev menu directly into RootView | Alex Kotliarskyi - flowify Libraries/ReactIOS | Marshall Roch - [WIP] Added support for italics and additional font weights | Nick Lockwood - [ReactNative] Improve View documentation | Christopher Chedeau - [react-packager] Readme | Amjad Masad - Fix for incorrect contentSize reported by RCTScrollView | Nick Lockwood - [ReactNative] Flow and doc formatting for NetInfo | Eric Vicenti - [ReactNative] Document AppStateIOS | Eric Vicenti --- README.md | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..8f9f649b --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +React Native Packager +-------------------- + +React Native Packager is a project similar in scope to browserify or +webpack, it provides a CommonJS-like module system, JavaScript +compilation (ES6, Flow, JSX), bundling, and asset loading. + +The main difference is the Packager's focus on compilation and +bundling speed. We aim for a sub-second edit-reload +cycles. Additionally, we don't want users -- with large code bases -- +to wait more than a few seconds after starting the packager. + +The main deviation from the node module system is the support for our +proprietary module format known as `@providesModule`. However, we +discourage people to use this module format because going forward, we +want to completely separate our infrastructure from React Native and +provide an experience most JavaScript developers are familiar with, +namely the node module format. We want to even go further, and let you +choose your own packager and asset pipeline or even integrate into +your existing infrastructure. + +React Native users need not to understand how the packager work, +however, this documentation might be useful for advanced users and +people who want to fix bugs or add features to the packager (patches +welcome!). + +## HTTP interface + +The main way you'd interact with the packager is via the HTTP +interface. The following is the list of endpoints and their respective +functions. + +### /path/to/moduleName.bundle + +Does the following in order: + +* parse out `path/to/moduleName` +* add a `.js` suffix to the path +* looks in your project root(s) for the file +* recursively collects all the dependencies from an in memory graph +* runs the modules through the transformer (might just be cached) +* concatenate the modules' content into a bundle +* responds to the client with the bundle (and a SourceMap URL) + +### /path/to/moduleName.map + +* if the package has been previously generated via the `.bundle` + endpoint then the source map will be generated from that package +* if the package has not been previously asked for, this will go + through the same steps outlined in the `.bundle` endpoint then + generate the source map. + +Note that source map generation currently assumes that the code has +been compiled with jstransform, which preserves line and column +numbers which allows us to generate source maps super fast. + +### /path/to/moduleName.(map|bundle) query params + +You can pass options for the bundle creation through the query params, +if the option is boolean `1/0` or `true/false` is accepted. + +Here are the current options the packager accepts: + +* `dev` boolean, defaults to true: sets a global `__DEV__` variable + which will effect how the React Nativeg core libraries behave. +* `minify` boolean, defaults to false: whether to minify the bundle. +* `runModule` boolean, defaults to true: whether to require your entry + point module. So if you requested `moduleName`, this option will add + a `require('moduleName')` the end of your bundle. +* `inlineSourceMap` boolean, defaults to false: whether to inline + source maps. + +### /debug + +This is a page used for debugging, it has links to two pages: + +* Cached Packages: which shows you the packages that's been already + generated and cached +* Dependency Graph: is the in-memory graph of all the modules and + their dependencies + +## Programmatic API + +The packager is made of two things: + +* The core packager (which we're calling ReactPackager) +* The scripts, devtools launcher, server run etc. + +ReactPackager is how you mainly interact with the API. + +```js +var ReactPackager = require('./react-packager'); +``` + +### ReactPackager.middleware(options) + +Returns a function that can be used in a connect-like +middleware. Takes the following options: + +* `projectRoots` array (required): Is the roots where your JavaScript + file will exist +* `blacklistRE` regexp: Is a patter to ignore certain paths from the + packager +* `polyfillModuleName` array: Paths to polyfills you want to be + included at the start of the bundle +* `cacheVersion` string: used in creating the cache file +* `resetCache` boolean, defaults to false: whether to use the cache on + disk +* `transformModulePath` string: Path to the module used as a + JavaScript transformer +* `nonPersistent` boolean, defaults to false: Whether the server + should be used as a persistent deamon to watch files and update + itself +* `assetRoots` array: Where should the packager look for assets + +### ReactPackager.buildPackageFromUrl(options, url) + +Build a package from a url (see the `.bundle` endpoint). `options` is +the same options that is passed to `ReactPackager.middleware` + +### ReactPackager.getDependencies(options, main) + +Given an entry point module. Recursively collect all the dependent +modules and return it as an array. `options` is the same options that +is passed to `ReactPackager.middleware` + +## FAQ + +### Can I use this in my own non-React Native project? + +Yes. It's not really tied to React Native, however feature development +is informed by React Native needs. + +### Why didn't you use webpack? + +We love webpack, however, when we tried on our codebase it was slower +than our developers would like it to be. You find can more discussion about +the subject [here](https://github.com/facebook/react-native/issues/5) From ffee46819e85fcd9b4ae50765a8bbce9b42507a5 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 25 Mar 2015 18:18:41 -0700 Subject: [PATCH 084/936] [ReactNative] Update package.json to be npm-ready --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0afcd3c3..6bfb6a2f 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "uglify-js": "~2.4.16", "underscore": "1.7.0", "worker-farm": "1.1.0", - "yargs": "1.3.2" + "yargs": "1.3.2", + "ws": "0.4.31" }, "devDependencies": { "jest-cli": "0.2.1", From 4be2d7c7377eee25f7489540183ac7939699caad Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 25 Mar 2015 20:09:04 -0700 Subject: [PATCH 085/936] Updates from Wed 25 Mar - [ReactNative] Add deep linking api | Tadeu Zagallo - [ReactNative] Add gitignore example for SampleApp | Alex Kotliarskyi - [ReactNative] Add react-native-start bin to react-native packge | Alex Kotliarskyi - [ReactNative] Update package.json to be npm-ready | Christopher Chedeau --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0afcd3c3..6bfb6a2f 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "uglify-js": "~2.4.16", "underscore": "1.7.0", "worker-farm": "1.1.0", - "yargs": "1.3.2" + "yargs": "1.3.2", + "ws": "0.4.31" }, "devDependencies": { "jest-cli": "0.2.1", From ecfc78f47ec70217d26b9178b90c511ed507b98f Mon Sep 17 00:00:00 2001 From: James Ide Date: Thu, 26 Mar 2015 02:11:53 -0700 Subject: [PATCH 086/936] [Assets] Allow scripts to override assetRoots Summary: The CLI parse was accepting a string but assetRoots should be an array, so split on commas. Tested by specifying a root directory that was at least two folders up (../../stuff). Closes https://github.com/facebook/react-native/pull/189 Github Author: James Ide Test Plan: * export to open source * started server passing --assetRoots array --- packager.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 12ef9ddd..119336ed 100644 --- a/packager.js +++ b/packager.js @@ -36,6 +36,9 @@ var options = parseCommandLine([{ }, { command: 'root', description: 'add another root(s) to be used by the packager in this project', +}, { + command: 'assetRoots', + description: 'specify the root directories of app assets' }]); if (options.projectRoots) { @@ -61,7 +64,11 @@ if (options.root) { } } -if (!options.assetRoots) { +if (options.assetRoots) { + if (!Array.isArray(options.assetRoots)) { + options.assetRoots = options.assetRoots.split(','); + } +} else { options.assetRoots = [path.resolve(__dirname, '..')]; } From 6166228846b757650e6a4cc99de5d641673e5568 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 26 Mar 2015 06:32:01 -0700 Subject: [PATCH 087/936] Updates from Thu 26 Mar - [React Native] Fix incorrect if-statement in RCTGeolocation | Alex Akers - [ReactNative] s/ReactKit/React/g | Tadeu Zagallo - [React Native] View border support | Nick Lockwood - [Assets] Allow scripts to override assetRoots | Amjad Masad - [ReactNative] Navigator docs | Eric Vicenti - [ReactNative] License headers and renaming | Eric Vicenti - [React Native] Add CocoaPods spec | Tadeu Zagallo - Added explicit types for all view properties | Nick Lockwood - [ReactNative] s/ReactNavigator/Navigator/ | Tadeu Zagallo - [ReactNative] Add copyright header for code copied from the jQuery UI project | Martin Konicek - [ReactNative] PanResponder documentation | Eric Vicenti --- packager.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 12ef9ddd..119336ed 100644 --- a/packager.js +++ b/packager.js @@ -36,6 +36,9 @@ var options = parseCommandLine([{ }, { command: 'root', description: 'add another root(s) to be used by the packager in this project', +}, { + command: 'assetRoots', + description: 'specify the root directories of app assets' }]); if (options.projectRoots) { @@ -61,7 +64,11 @@ if (options.root) { } } -if (!options.assetRoots) { +if (options.assetRoots) { + if (!Array.isArray(options.assetRoots)) { + options.assetRoots = options.assetRoots.split(','); + } +} else { options.assetRoots = [path.resolve(__dirname, '..')]; } From f68c24069e7b3a778652058383602d66fdf12898 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 26 Mar 2015 10:45:12 -0700 Subject: [PATCH 088/936] [react-packager] better error when main file not found --- .../haste/DependencyGraph/index.js | 17 ++++++++++++++++- react-packager/src/Server/index.js | 4 ++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index adb01282..f517236d 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -86,7 +86,11 @@ DependecyGraph.prototype.load = function() { DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { var absolutePath = this._getAbsolutePath(entryPath); if (absolutePath == null) { - throw new Error('Cannot find entry file in any of the roots: ' + entryPath); + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); } var module = this._graph[absolutePath]; @@ -664,4 +668,15 @@ function buildAssetMap(roots, exts) { return search(); } +function NotFoundError() { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + var msg = util.format.apply(util, arguments); + this.message = msg; + this.type = this.name = 'NotFoundError'; + this.status = 404; +} + +NotFoundError.__proto__ = Error.prototype; + module.exports = DependecyGraph; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index f40ebeea..ad417de1 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -281,11 +281,11 @@ function getBoolOptionFromQuery(query, opt, defaultVal) { } function handleError(res, error) { - res.writeHead(500, { + res.writeHead(error.status || 500, { 'Content-Type': 'application/json; charset=UTF-8', }); - if (error.type === 'TransformError') { + if (error.type === 'TransformError' || error.type === 'NotFoundError') { res.end(JSON.stringify(error)); } else { console.error(error.stack || error); From 53fbe58190bff4187f224ba8dc17d3861fa38515 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Thu, 26 Mar 2015 12:11:15 -0700 Subject: [PATCH 089/936] Bump react-native-cli version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6bfb6a2f..8ad2e871 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-cli", - "version": "0.1.1", + "version": "0.1.2", "description": "Build native apps with React!", "repository": { "type": "git", From 113d7eeafe116a81cde6984b7429e631e6bc832b Mon Sep 17 00:00:00 2001 From: Kevin Kwok Date: Thu, 26 Mar 2015 12:46:20 -0700 Subject: [PATCH 090/936] [CLI] react-native start won't run from dir with spaces Summary: Running "react-native start" from /Users/kevin/Dropbox (Personal)/Projects/AwesomeProject/ produces the following error Error: Cannot find module '/Users/kevin/Dropbox' at Function.Module._resolveFilename (module.js:322:15) at Function.Module._load (module.js:264:25) at Function.Module.runMain (module.js:487:10) at startup (node.js:111:16) at node.js:809:3 Closes https://github.com/facebook/react-native/pull/214 Github Author: Kevin Kwok Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager.sh b/packager.sh index 969ca1b2..93a017c3 100755 --- a/packager.sh +++ b/packager.sh @@ -10,4 +10,4 @@ ulimit -n 4096 THIS_DIR=$(dirname "$0") -node $THIS_DIR/packager.js "$@" +node "$THIS_DIR/packager.js" "$@" From b2eba1072b3fbf5cdb3d9c7f50a6a5acfcaa2064 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 26 Mar 2015 14:41:56 -0700 Subject: [PATCH 091/936] [react-packager] Fix node v0.11.14 query parse bug --- react-packager/src/Server/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index ad417de1..40281c69 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -246,6 +246,9 @@ Server.prototype.processRequest = function(req, res, next) { 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 = urlObj.pathname; // Backwards compatibility. Options used to be as added as '.' to the From 8dc2c30a187bb1f46b0afdedee6bea059ea71b89 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 26 Mar 2015 17:26:51 -0700 Subject: [PATCH 092/936] Use only one package for dependencies, for now. Fixes #225 --- package.json | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 8ad2e871..9ffaf9e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-native-cli", "version": "0.1.2", + "name": "react-native-packager", "description": "Build native apps with React!", "repository": { "type": "git", @@ -23,22 +23,7 @@ "lint": "node linter.js Examples/", "start": "./packager/packager.sh" }, - "dependencies": { - "absolute-path": "0.0.0", - "debug": "~2.1.0", - "joi": "~5.1.0", - "module-deps": "3.5.6", - "optimist": "0.6.1", - "q": "1.0.1", - "sane": "1.0.1", - "source-map": "0.1.31", - "stacktrace-parser": "0.1.1", - "uglify-js": "~2.4.16", - "underscore": "1.7.0", - "worker-farm": "1.1.0", - "yargs": "1.3.2", - "ws": "0.4.31" - }, + "dependencies": {}, "devDependencies": { "jest-cli": "0.2.1", "eslint": "0.9.2" From 4db001a9254f0fae3079faf4565716db2fbee347 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 26 Mar 2015 17:42:32 -0700 Subject: [PATCH 093/936] [react-packager] Fix assetRoots when starting in node_modules, fixes #282 #252 --- packager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 119336ed..9503df6e 100644 --- a/packager.js +++ b/packager.js @@ -69,7 +69,11 @@ if (options.assetRoots) { options.assetRoots = options.assetRoots.split(','); } } else { - options.assetRoots = [path.resolve(__dirname, '..')]; + if (__dirname.match(/node_modules\/react-native\/packager$/)) { + options.assetRoots = [path.resolve(__dirname, '../../..')]; + } else { + options.assetRoots = [path.resolve(__dirname, '..')]; + } } console.log('\n' + From c74da17c7c5e9e3d699e545a7f19bf7e5a94a749 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 26 Mar 2015 22:45:23 -0700 Subject: [PATCH 094/936] [react-packager] move dependencies to root package.json --- package.json | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 6bfb6a2f..28134ef2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "react-native-cli", + "name": "react-native-packager", "version": "0.1.1", "description": "Build native apps with React!", "repository": { @@ -23,22 +23,7 @@ "lint": "node linter.js Examples/", "start": "./packager/packager.sh" }, - "dependencies": { - "absolute-path": "0.0.0", - "debug": "~2.1.0", - "joi": "~5.1.0", - "module-deps": "3.5.6", - "optimist": "0.6.1", - "q": "1.0.1", - "sane": "1.0.1", - "source-map": "0.1.31", - "stacktrace-parser": "0.1.1", - "uglify-js": "~2.4.16", - "underscore": "1.7.0", - "worker-farm": "1.1.0", - "yargs": "1.3.2", - "ws": "0.4.31" - }, + "dependencies": {}, "devDependencies": { "jest-cli": "0.2.1", "eslint": "0.9.2" From 4a67c84426b9930c617912a03f5fa09969737c7e Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 27 Mar 2015 09:34:37 -0700 Subject: [PATCH 095/936] [react-packager] Watch asset roots for changes to update dependency graph --- .../__tests__/DependencyGraph-test.js | 49 +++++++++++ .../haste/DependencyGraph/index.js | 86 +++++++++++++------ .../src/DependencyResolver/haste/index.js | 14 ++- .../FileWatcher/__tests__/FileWatcher-test.js | 1 + react-packager/src/FileWatcher/index.js | 32 +++---- react-packager/src/Packager/index.js | 14 ++- react-packager/src/Server/index.js | 35 +++++++- 7 files changed, 175 insertions(+), 56 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 962a0694..69ed11e6 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -821,6 +821,55 @@ describe('DependencyGraph', function() { }); }); + pit('updates module dependencies on asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!foo")' + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetRoots: [root], + assetExts: ['png'], + fileWatcher: fileWatcher + }); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + }, + { id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + }, + ]); + }); + }); + }); + pit('runs changes through ignore filter', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index f517236d..6f09c5e8 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -482,7 +482,12 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) { /** * Process a filewatcher change event. */ -DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) { +DependecyGraph.prototype._processFileChange = function( + eventType, + filePath, + root, + stat +) { var absPath = path.join(root, filePath); if (this._ignoreFilePath(absPath)) { return; @@ -490,6 +495,11 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root this._debugUpdateEvents.push({event: eventType, path: filePath}); + if (this._assetExts.indexOf(extname(filePath)) > -1) { + this._processAssetChange(eventType, absPath); + return; + } + var isPackage = path.basename(filePath) === 'package.json'; if (eventType === 'delete') { if (isPackage) { @@ -524,7 +534,8 @@ DependecyGraph.prototype.getDebugInfo = function() { }; /** - * Searches all roots for the file and returns the first one that has file of the same path. + * Searches all roots for the file and returns the first one that has file of + * the same path. */ DependecyGraph.prototype._getAbsolutePath = function(filePath) { if (isAbsolutePath(filePath)) { @@ -547,12 +558,43 @@ DependecyGraph.prototype._buildAssetMap = function() { return q(); } - var self = this; - return buildAssetMap(this._assetRoots, this._assetExts) - .then(function(map) { - self._assetMap = map; - return map; + this._assetMap = Object.create(null); + return buildAssetMap( + this._assetRoots, + this._processAsset.bind(this) + ); +}; + +DependecyGraph.prototype._processAsset = function(file) { + var ext = extname(file); + if (this._assetExts.indexOf(ext) !== -1) { + var name = assetName(file, ext); + if (this._assetMap[name] != null) { + debug('Conflcting assets', name); + } + + this._assetMap[name] = new ModuleDescriptor({ + id: 'image!' + name, + path: path.resolve(file), + isAsset: true, + dependencies: [], }); + } +}; + +DependecyGraph.prototype._processAssetChange = function(eventType, file) { + if (this._assetMap == null) { + return; + } + + var name = assetName(file, extname(file)); + if (eventType === 'change' || eventType === 'delete') { + delete this._assetMap[name]; + } + + if (eventType === 'change' || eventType === 'add') { + this._processAsset(file); + } }; /** @@ -627,15 +669,14 @@ function readAndStatDir(dir) { * Given a list of roots and list of extensions find all the files in * the directory with that extension and build a map of those assets. */ -function buildAssetMap(roots, exts) { +function buildAssetMap(roots, processAsset) { var queue = roots.slice(0); - var map = Object.create(null); function search() { var root = queue.shift(); if (root == null) { - return q(map); + return q(); } return readAndStatDir(root).spread(function(files, stats) { @@ -643,21 +684,7 @@ function buildAssetMap(roots, exts) { if (stats[i].isDirectory()) { queue.push(file); } else { - var ext = path.extname(file).replace(/^\./, ''); - if (exts.indexOf(ext) !== -1) { - var assetName = path.basename(file, '.' + ext) - .replace(/@[\d\.]+x/, ''); - if (map[assetName] != null) { - debug('Conflcting assets', assetName); - } - - map[assetName] = new ModuleDescriptor({ - id: 'image!' + assetName, - path: path.resolve(file), - isAsset: true, - dependencies: [], - }); - } + processAsset(file); } }); @@ -668,6 +695,15 @@ function buildAssetMap(roots, exts) { return search(); } +function assetName(file, ext) { + return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); +} + +function extname(name) { + return path.extname(name).replace(/^\./, ''); +} + + function NotFoundError() { Error.call(this); Error.captureStackTrace(this, this.constructor); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 2edb3b52..0e46d5e8 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -51,15 +51,15 @@ var validateOpts = declareOpts({ type: 'array', default: [], }, + fileWatcher: { + type: 'object', + required: true, + }, }); function HasteDependencyResolver(options) { var opts = validateOpts(options); - this._fileWatcher = opts.nonPersistent - ? FileWatcher.createDummyWatcher() - : new FileWatcher(opts.projectRoots); - this._depGraph = new DependencyGraph({ roots: opts.projectRoots, assetRoots: opts.assetRoots, @@ -67,7 +67,7 @@ function HasteDependencyResolver(options) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, - fileWatcher: this._fileWatcher, + fileWatcher: opts.fileWatcher, }); @@ -164,10 +164,6 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { }); }; -HasteDependencyResolver.prototype.end = function() { - return this._fileWatcher.end(); -}; - HasteDependencyResolver.prototype.getDebugInfo = function() { return this._depGraph.getDebugInfo(); }; diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index 213033c5..e24618dc 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -21,6 +21,7 @@ describe('FileWatcher', function() { var Watcher; beforeEach(function() { + require('mock-modules').dumpCache(); FileWatcher = require('../'); Watcher = require('sane').WatchmanWatcher; Watcher.prototype.once.mockImplementation(function(type, callback) { diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index c1633683..86ec962b 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -16,7 +16,7 @@ var exec = require('child_process').exec; var Promise = q.Promise; -var detectingWatcherClass = new Promise(function(resolve, reject) { +var detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { resolve(sane.NodeWatcher); @@ -30,14 +30,23 @@ module.exports = FileWatcher; var MAX_WAIT_TIME = 3000; -function FileWatcher(projectRoots) { - var self = this; +// Singleton +var fileWatcher = null; + +function FileWatcher(rootConfigs) { + if (fileWatcher) { + // This allows us to optimize watching in the future by merging roots etc. + throw new Error('FileWatcher can only be instantiated once'); + } + + fileWatcher = this; + this._loading = q.all( - projectRoots.map(createWatcher) + rootConfigs.map(createWatcher) ).then(function(watchers) { watchers.forEach(function(watcher) { watcher.on('all', function(type, filepath, root) { - self.emit('all', type, filepath, root); + fileWatcher.emit('all', type, filepath, root); }); }); return watchers; @@ -50,21 +59,14 @@ util.inherits(FileWatcher, EventEmitter); FileWatcher.prototype.end = function() { return this._loading.then(function(watchers) { watchers.forEach(function(watcher) { - delete watchersByRoot[watcher._root]; return q.ninvoke(watcher, 'close'); }); }); }; -var watchersByRoot = Object.create(null); - -function createWatcher(root) { - if (watchersByRoot[root] != null) { - return Promise.resolve(watchersByRoot[root]); - } - +function createWatcher(rootConfig) { return detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(root, {glob: ['**/*.js', '**/package.json']}); + var watcher = new Watcher(rootConfig.dir, rootConfig.globs); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { @@ -77,8 +79,6 @@ function createWatcher(root) { watcher.once('ready', function() { clearTimeout(rejectTimeout); - watchersByRoot[root] = watcher; - watcher._root = root; resolve(watcher); }); }); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index ac7bd2e2..843efe75 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -56,6 +56,14 @@ var validateOpts = declareOpts({ type: 'array', required: false, }, + assetExts: { + type: 'array', + default: ['png'], + }, + fileWatcher: { + type: 'object', + required: true, + }, }); function Packager(options) { @@ -70,6 +78,7 @@ function Packager(options) { nonPersistent: opts.nonPersistent, moduleFormat: opts.moduleFormat, assetRoots: opts.assetRoots, + fileWatcher: opts.fileWatcher, }); this._transformer = new Transformer({ @@ -83,10 +92,7 @@ function Packager(options) { } Packager.prototype.kill = function() { - return q.all([ - this._transformer.kill(), - this._resolver.end(), - ]); + return this._transformer.kill(); }; Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 40281c69..23af55db 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -55,18 +55,49 @@ var validateOpts = declareOpts({ type: 'array', required: false, }, + assetExts: { + type: 'array', + default: ['png'], + }, }); function Server(options) { var opts = validateOpts(options); + this._projectRoots = opts.projectRoots; this._packages = Object.create(null); - this._packager = new Packager(opts); this._changeWatchers = []; + var watchRootConfigs = opts.projectRoots.map(function(dir) { + return { + dir: dir, + globs: [ + '**/*.js', + '**/package.json', + ] + }; + }); + + if (opts.assetRoots != null) { + watchRootConfigs = watchRootConfigs.concat( + opts.assetRoots.map(function(dir) { + return { + dir: dir, + globs: opts.assetExts.map(function(ext) { + return '**/*.' + ext; + }), + }; + }) + ); + } + this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() - : new FileWatcher(options.projectRoots); + : new FileWatcher(watchRootConfigs); + + var packagerOpts = Object.create(opts); + packagerOpts.fileWatcher = this._fileWatcher; + this._packager = new Packager(packagerOpts); var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); From e7d1d95d0de92a8861d9a3d52e14fa1f435821fb Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 27 Mar 2015 11:46:17 -0700 Subject: [PATCH 096/936] Update from Friday March 27 - [React Native] Sync from github | Amjad Masad - [react-packager] Watch asset roots for changes to update dependency graph | Amjad Masad - Fix sourceTree of RCTActionSheet.xcodeproj | Alex Kotliarskyi - Cancel contents animation before setting new contents in RCTNetworkImageView | Alex Akers - [react-packager] move dependencies to root package.json | Amjad Masad - Fix font crash on iOS < 8.2 | Nick Lockwood - [react-packager] Fix node v0.11.14 query parse bug | Amjad Masad - [ReactNative][Docs] Remove references to ReactNavigator from docs | Tadeu Zagallo - [CLI] react-native start won't run from dir with spaces | Amjad Masad - Revert .buckversion bumps. | Jakub Zika - [react_native] Update default bundle name to org.reactjs.native.* | Krzysztof Magiera - [react-packager] better error when main file not found | Amjad Masad - [React Kit] Remove embarrassing TODOs | Alex Akers - [ReactNative][MAdMan] Clean up after D1942269 | Philipp von Weitershausen - flowify a few more Libraries | Basil Hosmer - [ReactNative] PushNotificationIOS documentation | Eric Vicenti - [ReactNative][CustomComponents] Update old headers | Tadeu Zagallo - [ReactNative] UIViewControllerBasedStatusBarAppearance = NO in SampleApp | Alex Kotliarskyi - [React Native] Fix CocoaPods spec | Alex Akers - [ReactNative] Navigator Example Overhaul | Eric Vicenti - [React Native] Fix incorrect if-statement in RCTGeolocation | Alex Akers - [ReactNative] s/ReactKit/React/g | Tadeu Zagallo - [React Native] [FRC - Don't accept] View border support | Nick Lockwood - [Assets] Allow scripts to override assetRoots | Amjad Masad - [ReactNative] Navigator docs | Eric Vicenti - [ReactNative] License headers and renaming | Eric Vicenti - [React Native] Add CocoaPods spec | Tadeu Zagallo - Added explicit types for all view properties | Nick Lockwood - [ReactNative] s/ReactNavigator/Navigator/ | Tadeu Zagallo - [ReactNative] Add copyright header for code copied from the jQuery UI project | Martin Konicek - [ReactNative] PanResponder documentation | Eric Vicenti - [ReactNative] Add deep linking api | Tadeu Zagallo - [ReactNative] Add gitignore example for SampleApp | Alex Kotliarskyi - [ReactNative] Add react-native-start bin to react-native packge | Alex Kotliarskyi - [ReactNative] Update package.json to be npm-ready | Christopher Chedeau - [RFC][ReactNative] Integrate dev menu directly into RootView | Alex Kotliarskyi - flowify Libraries/ReactIOS | Marshall Roch - [WIP] Added support for italics and additional font weights | Nick Lockwood - [ReactNative] Improve View documentation | Christopher Chedeau - [react-packager] Readme | Amjad Masad - Fix for incorrect contentSize reported by RCTScrollView | Nick Lockwood - [ReactNative] Flow and doc formatting for NetInfo | Eric Vicenti - [ReactNative] Document AppStateIOS | Eric Vicenti - [MAdMan][Android] Make things look more Androidy | Philipp von Weitershausen - flowified Libraries from Avik | Basil Hosmer - flowify some Libraries | Basil Hosmer - [ReactKit] Add shake development menu | Alex Kotliarskyi - [ReactNative] Add debugger and change SampleApp files structure | Alex Kotliarskyi - Flowify ReactIOSEventEmitter | Marshall Roch - [react_native] JS files from D1941151: Allow fontWeight to be 100,200,...,900 | Krzysztof Magiera - [ReactNative] Add snapshot tests for examples | Spencer Ahrens - [ReactNative] bring back some native modules | Spencer Ahrens - [ReactNative] Rename JSNavigationStack to ReactNavigator, rename scene config | Eric Vicenti - [ReactNative] cleanup view example | Spencer Ahrens - Flowify a bunch of Libraries | Marshall Roch - [ReactNative] JSNavigationStack - Use key to blow away old scenes | Eric Vicenti - [ReactNative] Add more logging to RCTJSONParse | Sumeet Vaidya - Unfork UIManager | Nick Lockwood - [react-packager] kill non-standard RAW_SOURCE_MAP | Amjad Masad - Flowify Libraries/StyleSheet and Libraries/Text | Marshall Roch - [ReactNative] Fix OSS Dependency Issues | Eric Vicenti - [react-packager] Fix more issues with node modules | Amjad Masad - [ReactNative] rename navigationOperations to navigator | Eric Vicenti - JS files from D1936817: Add to XMLHttpRequest android and share code with ios | Olivia Bishop - flowify some Libraries | Basil Hosmer - last batch of UIExplorer flowification | Basil Hosmer - [ReactNative] JSNavigationStack rename routeMapper to renderSceneForRoute | Eric Vicenti - Flowify renderApplication | Marshall Roch - [ReactNative] OSS Responder example | Eric Vicenti - [ReactNative] Use oss TimerMixin | Tadeu Zagallo - [ReactNative] Remove auto permission request from setAppIconBadgeNumber | Tadeu Zagallo - [ReactNative] OSS snapshot tests | Spencer Ahrens - [ReactNative] OSS JSNavigationStack w/ Examples | Eric Vicenti - Fix build - remove relative import path | Jakub Zika - Bump .buckversion to a5b8b8ef45d714018ba3542cf98d48ef6aab7088. | Jakub Zika - [ReactNative] Open Source PushNotifications and move Badge Number methods and permission into it | Tadeu Zagallo - [react-packager] Fix regression with transform errors | Amjad Masad - Flowify TextStylePropTypes and fix a bug with unsupported props | Marshall Roch - [ReactNative] Remove `arc build` instructions from require | Alex Kotliarskyi - Flowify Library/Utilities/ | Marshall Roch - [react-packager] Default to index.js from main if it's a dir | Amjad Masad - [ReactNative] Use deprecated ix in TabBarExample | Amjad Masad - [ReactNative] Expanded license on obj-c files | Christopher Chedeau - [ReactNative] Expanded license on js files | Christopher Chedeau - [ReactNative] Fix React Devtools integration | Alex Kotliarskyi - [Text] Account for font leading so descenders are not clipped | Alex Kotliarskyi - [ReactNative] Expanded license on js packager files | Christopher Chedeau - more UIExplorer flow | Basil Hosmer - [react-packager] Pick up package changes while running | Amjad Masad - Added a graph view and a ReactNative metric that displays current queue and execution time for the JS thread. | Bryce Redd - [ReactNative] Add NativeModules and DeviceEventEmitter to react-native exports | Alex Kotliarskyi - [React Native] Fix iOS 7 crashes b/c missing Photos.fmwk | Alex Akers - UIExplorer flowification | Basil Hosmer - Add clearImmediate module | Marshall Roch - [ReactNative] Print directories packager is serving files from | Alex Kotliarskyi - Work around flow bug with exports | Marshall Roch - [ReactNative] Move packager/init.sh to GitHub | Alex Kotliarskyi - [ReactNative] Remove react-native/package.json | Christopher Chedeau - [ReactNative] Returning actual contentSize for RCTScrollViewManager | Henry Lung - declare timeoutID | Basil Hosmer - [ReactNative] Add root package.json name back | Tadeu Zagallo - [react-packager] Allow entry point extensions like .ios.js | Amjad Masad - [react-native] Use SpreadProperty to make react-docgen happy | Felix Kling - clean Examples/2048 | Basil Hosmer - [ReactNative] Adjust packager default root when running from within node_modules | Alex Kotliarskyi - [ReactNative] Add missing websocket dependency | Alex Kotliarskyi - [react-packager] change all but one `ix` to `require` | Amjad Masad - [react-packager] Make sure projectRoots is converted to an array | Amjad Masad - [ReactNative] Init script that bootstraps new Xcode project | Alex Kotliarskyi - [ReactNative] New SampleApp | Alex Kotliarskyi - [ReactNative] Touchable invoke press on longPress when longPress handler missing | Eric Vicenti - [ReactNative] Commit missing RCTWebSocketDebugger.xcodeproj | Alex Kotliarskyi --- packager.js | 6 +- packager.sh | 2 +- .../__tests__/DependencyGraph-test.js | 49 +++++++++ .../haste/DependencyGraph/index.js | 103 +++++++++++++----- .../src/DependencyResolver/haste/index.js | 14 +-- .../FileWatcher/__tests__/FileWatcher-test.js | 1 + react-packager/src/FileWatcher/index.js | 32 +++--- react-packager/src/Packager/index.js | 14 ++- react-packager/src/Server/index.js | 42 ++++++- 9 files changed, 198 insertions(+), 65 deletions(-) diff --git a/packager.js b/packager.js index 9503df6e..119336ed 100644 --- a/packager.js +++ b/packager.js @@ -69,11 +69,7 @@ if (options.assetRoots) { options.assetRoots = options.assetRoots.split(','); } } else { - if (__dirname.match(/node_modules\/react-native\/packager$/)) { - options.assetRoots = [path.resolve(__dirname, '../../..')]; - } else { - options.assetRoots = [path.resolve(__dirname, '..')]; - } + options.assetRoots = [path.resolve(__dirname, '..')]; } console.log('\n' + diff --git a/packager.sh b/packager.sh index 969ca1b2..93a017c3 100755 --- a/packager.sh +++ b/packager.sh @@ -10,4 +10,4 @@ ulimit -n 4096 THIS_DIR=$(dirname "$0") -node $THIS_DIR/packager.js "$@" +node "$THIS_DIR/packager.js" "$@" diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 962a0694..69ed11e6 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -821,6 +821,55 @@ describe('DependencyGraph', function() { }); }); + pit('updates module dependencies on asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!foo")' + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetRoots: [root], + assetExts: ['png'], + fileWatcher: fileWatcher + }); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + }, + { id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + }, + ]); + }); + }); + }); + pit('runs changes through ignore filter', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index adb01282..6f09c5e8 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -86,7 +86,11 @@ DependecyGraph.prototype.load = function() { DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { var absolutePath = this._getAbsolutePath(entryPath); if (absolutePath == null) { - throw new Error('Cannot find entry file in any of the roots: ' + entryPath); + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); } var module = this._graph[absolutePath]; @@ -478,7 +482,12 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) { /** * Process a filewatcher change event. */ -DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) { +DependecyGraph.prototype._processFileChange = function( + eventType, + filePath, + root, + stat +) { var absPath = path.join(root, filePath); if (this._ignoreFilePath(absPath)) { return; @@ -486,6 +495,11 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root this._debugUpdateEvents.push({event: eventType, path: filePath}); + if (this._assetExts.indexOf(extname(filePath)) > -1) { + this._processAssetChange(eventType, absPath); + return; + } + var isPackage = path.basename(filePath) === 'package.json'; if (eventType === 'delete') { if (isPackage) { @@ -520,7 +534,8 @@ DependecyGraph.prototype.getDebugInfo = function() { }; /** - * Searches all roots for the file and returns the first one that has file of the same path. + * Searches all roots for the file and returns the first one that has file of + * the same path. */ DependecyGraph.prototype._getAbsolutePath = function(filePath) { if (isAbsolutePath(filePath)) { @@ -543,12 +558,43 @@ DependecyGraph.prototype._buildAssetMap = function() { return q(); } - var self = this; - return buildAssetMap(this._assetRoots, this._assetExts) - .then(function(map) { - self._assetMap = map; - return map; + this._assetMap = Object.create(null); + return buildAssetMap( + this._assetRoots, + this._processAsset.bind(this) + ); +}; + +DependecyGraph.prototype._processAsset = function(file) { + var ext = extname(file); + if (this._assetExts.indexOf(ext) !== -1) { + var name = assetName(file, ext); + if (this._assetMap[name] != null) { + debug('Conflcting assets', name); + } + + this._assetMap[name] = new ModuleDescriptor({ + id: 'image!' + name, + path: path.resolve(file), + isAsset: true, + dependencies: [], }); + } +}; + +DependecyGraph.prototype._processAssetChange = function(eventType, file) { + if (this._assetMap == null) { + return; + } + + var name = assetName(file, extname(file)); + if (eventType === 'change' || eventType === 'delete') { + delete this._assetMap[name]; + } + + if (eventType === 'change' || eventType === 'add') { + this._processAsset(file); + } }; /** @@ -623,15 +669,14 @@ function readAndStatDir(dir) { * Given a list of roots and list of extensions find all the files in * the directory with that extension and build a map of those assets. */ -function buildAssetMap(roots, exts) { +function buildAssetMap(roots, processAsset) { var queue = roots.slice(0); - var map = Object.create(null); function search() { var root = queue.shift(); if (root == null) { - return q(map); + return q(); } return readAndStatDir(root).spread(function(files, stats) { @@ -639,21 +684,7 @@ function buildAssetMap(roots, exts) { if (stats[i].isDirectory()) { queue.push(file); } else { - var ext = path.extname(file).replace(/^\./, ''); - if (exts.indexOf(ext) !== -1) { - var assetName = path.basename(file, '.' + ext) - .replace(/@[\d\.]+x/, ''); - if (map[assetName] != null) { - debug('Conflcting assets', assetName); - } - - map[assetName] = new ModuleDescriptor({ - id: 'image!' + assetName, - path: path.resolve(file), - isAsset: true, - dependencies: [], - }); - } + processAsset(file); } }); @@ -664,4 +695,24 @@ function buildAssetMap(roots, exts) { return search(); } +function assetName(file, ext) { + return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); +} + +function extname(name) { + return path.extname(name).replace(/^\./, ''); +} + + +function NotFoundError() { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + var msg = util.format.apply(util, arguments); + this.message = msg; + this.type = this.name = 'NotFoundError'; + this.status = 404; +} + +NotFoundError.__proto__ = Error.prototype; + module.exports = DependecyGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 2edb3b52..0e46d5e8 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -51,15 +51,15 @@ var validateOpts = declareOpts({ type: 'array', default: [], }, + fileWatcher: { + type: 'object', + required: true, + }, }); function HasteDependencyResolver(options) { var opts = validateOpts(options); - this._fileWatcher = opts.nonPersistent - ? FileWatcher.createDummyWatcher() - : new FileWatcher(opts.projectRoots); - this._depGraph = new DependencyGraph({ roots: opts.projectRoots, assetRoots: opts.assetRoots, @@ -67,7 +67,7 @@ function HasteDependencyResolver(options) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, - fileWatcher: this._fileWatcher, + fileWatcher: opts.fileWatcher, }); @@ -164,10 +164,6 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { }); }; -HasteDependencyResolver.prototype.end = function() { - return this._fileWatcher.end(); -}; - HasteDependencyResolver.prototype.getDebugInfo = function() { return this._depGraph.getDebugInfo(); }; diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index 213033c5..e24618dc 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -21,6 +21,7 @@ describe('FileWatcher', function() { var Watcher; beforeEach(function() { + require('mock-modules').dumpCache(); FileWatcher = require('../'); Watcher = require('sane').WatchmanWatcher; Watcher.prototype.once.mockImplementation(function(type, callback) { diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index c1633683..86ec962b 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -16,7 +16,7 @@ var exec = require('child_process').exec; var Promise = q.Promise; -var detectingWatcherClass = new Promise(function(resolve, reject) { +var detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { resolve(sane.NodeWatcher); @@ -30,14 +30,23 @@ module.exports = FileWatcher; var MAX_WAIT_TIME = 3000; -function FileWatcher(projectRoots) { - var self = this; +// Singleton +var fileWatcher = null; + +function FileWatcher(rootConfigs) { + if (fileWatcher) { + // This allows us to optimize watching in the future by merging roots etc. + throw new Error('FileWatcher can only be instantiated once'); + } + + fileWatcher = this; + this._loading = q.all( - projectRoots.map(createWatcher) + rootConfigs.map(createWatcher) ).then(function(watchers) { watchers.forEach(function(watcher) { watcher.on('all', function(type, filepath, root) { - self.emit('all', type, filepath, root); + fileWatcher.emit('all', type, filepath, root); }); }); return watchers; @@ -50,21 +59,14 @@ util.inherits(FileWatcher, EventEmitter); FileWatcher.prototype.end = function() { return this._loading.then(function(watchers) { watchers.forEach(function(watcher) { - delete watchersByRoot[watcher._root]; return q.ninvoke(watcher, 'close'); }); }); }; -var watchersByRoot = Object.create(null); - -function createWatcher(root) { - if (watchersByRoot[root] != null) { - return Promise.resolve(watchersByRoot[root]); - } - +function createWatcher(rootConfig) { return detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(root, {glob: ['**/*.js', '**/package.json']}); + var watcher = new Watcher(rootConfig.dir, rootConfig.globs); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { @@ -77,8 +79,6 @@ function createWatcher(root) { watcher.once('ready', function() { clearTimeout(rejectTimeout); - watchersByRoot[root] = watcher; - watcher._root = root; resolve(watcher); }); }); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index ac7bd2e2..843efe75 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -56,6 +56,14 @@ var validateOpts = declareOpts({ type: 'array', required: false, }, + assetExts: { + type: 'array', + default: ['png'], + }, + fileWatcher: { + type: 'object', + required: true, + }, }); function Packager(options) { @@ -70,6 +78,7 @@ function Packager(options) { nonPersistent: opts.nonPersistent, moduleFormat: opts.moduleFormat, assetRoots: opts.assetRoots, + fileWatcher: opts.fileWatcher, }); this._transformer = new Transformer({ @@ -83,10 +92,7 @@ function Packager(options) { } Packager.prototype.kill = function() { - return q.all([ - this._transformer.kill(), - this._resolver.end(), - ]); + return this._transformer.kill(); }; Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index f40ebeea..23af55db 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -55,18 +55,49 @@ var validateOpts = declareOpts({ type: 'array', required: false, }, + assetExts: { + type: 'array', + default: ['png'], + }, }); function Server(options) { var opts = validateOpts(options); + this._projectRoots = opts.projectRoots; this._packages = Object.create(null); - this._packager = new Packager(opts); this._changeWatchers = []; + var watchRootConfigs = opts.projectRoots.map(function(dir) { + return { + dir: dir, + globs: [ + '**/*.js', + '**/package.json', + ] + }; + }); + + if (opts.assetRoots != null) { + watchRootConfigs = watchRootConfigs.concat( + opts.assetRoots.map(function(dir) { + return { + dir: dir, + globs: opts.assetExts.map(function(ext) { + return '**/*.' + ext; + }), + }; + }) + ); + } + this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() - : new FileWatcher(options.projectRoots); + : new FileWatcher(watchRootConfigs); + + var packagerOpts = Object.create(opts); + packagerOpts.fileWatcher = this._fileWatcher; + this._packager = new Packager(packagerOpts); var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); @@ -246,6 +277,9 @@ Server.prototype.processRequest = function(req, res, next) { 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 = urlObj.pathname; // Backwards compatibility. Options used to be as added as '.' to the @@ -281,11 +315,11 @@ function getBoolOptionFromQuery(query, opt, defaultVal) { } function handleError(res, error) { - res.writeHead(500, { + res.writeHead(error.status || 500, { 'Content-Type': 'application/json; charset=UTF-8', }); - if (error.type === 'TransformError') { + if (error.type === 'TransformError' || error.type === 'NotFoundError') { res.end(JSON.stringify(error)); } else { console.error(error.stack || error); From e2404cd3dd33c4803e210afc3d6a02035af6d1b6 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Fri, 27 Mar 2015 12:43:09 -0700 Subject: [PATCH 097/936] Bump packager version to match GitHub --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 28134ef2..bce615b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-packager", - "version": "0.1.1", + "version": "0.1.2", "description": "Build native apps with React!", "repository": { "type": "git", From a895e62e6d828e78b8f6ca56094a8cf289ff2a81 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 27 Mar 2015 19:27:17 -0700 Subject: [PATCH 098/936] [react-packager] Fix assetRoots when starting in node_modules --- packager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 119336ed..9503df6e 100644 --- a/packager.js +++ b/packager.js @@ -69,7 +69,11 @@ if (options.assetRoots) { options.assetRoots = options.assetRoots.split(','); } } else { - options.assetRoots = [path.resolve(__dirname, '..')]; + if (__dirname.match(/node_modules\/react-native\/packager$/)) { + options.assetRoots = [path.resolve(__dirname, '../../..')]; + } else { + options.assetRoots = [path.resolve(__dirname, '..')]; + } } console.log('\n' + From 3ec02183ae05f1454b6000bbd478d7a6f4903c4f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 27 Mar 2015 22:11:33 -0700 Subject: [PATCH 099/936] Bring back accidentally reverted changes from 89fc8a4d7f9fa1c23ac235e74731beabb3f9594d --- packager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 119336ed..9503df6e 100644 --- a/packager.js +++ b/packager.js @@ -69,7 +69,11 @@ if (options.assetRoots) { options.assetRoots = options.assetRoots.split(','); } } else { - options.assetRoots = [path.resolve(__dirname, '..')]; + if (__dirname.match(/node_modules\/react-native\/packager$/)) { + options.assetRoots = [path.resolve(__dirname, '../../..')]; + } else { + options.assetRoots = [path.resolve(__dirname, '..')]; + } } console.log('\n' + From e306f4e8d50db81b1a21081eb24634d8409e9b4b Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 27 Mar 2015 21:56:38 -0700 Subject: [PATCH 100/936] [react-packager] Inherit from Error correctly --- .../src/DependencyResolver/haste/DependencyGraph/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 6f09c5e8..00c4a5c5 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -713,6 +713,6 @@ function NotFoundError() { this.status = 404; } -NotFoundError.__proto__ = Error.prototype; +util.inherits(NotFoundError, Error); module.exports = DependecyGraph; From d51e402a2acb0f4d2b695b286af8185c334f2b24 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 27 Mar 2015 22:18:47 -0700 Subject: [PATCH 101/936] [React Native] Sync from github --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bce615b8..9ffaf9e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-native-packager", "version": "0.1.2", + "name": "react-native-packager", "description": "Build native apps with React!", "repository": { "type": "git", From e76f175acbf3a134ce0fe9006b67e291e5583b55 Mon Sep 17 00:00:00 2001 From: David Rabkin Date: Sun, 29 Mar 2015 11:21:27 -0700 Subject: [PATCH 102/936] (cleanup/ fix grammer & typos) packager/README.md -fix a few typos -edit grammar on a few sentences, adding punctuation or keeping tense consistent -make capitalization consistent in ReactPackager.middleware(options) section --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8f9f649b..17c811c7 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ React Native Packager -------------------- React Native Packager is a project similar in scope to browserify or -webpack, it provides a CommonJS-like module system, JavaScript +webpack; it provides a CommonJS-like module system, JavaScript compilation (ES6, Flow, JSX), bundling, and asset loading. The main difference is the Packager's focus on compilation and bundling speed. We aim for a sub-second edit-reload -cycles. Additionally, we don't want users -- with large code bases -- +cycle. Additionally, we don't want users -- with large code bases -- to wait more than a few seconds after starting the packager. The main deviation from the node module system is the support for our @@ -19,7 +19,7 @@ namely the node module format. We want to even go further, and let you choose your own packager and asset pipeline or even integrate into your existing infrastructure. -React Native users need not to understand how the packager work, +React Native users need not to understand how the packager works; however, this documentation might be useful for advanced users and people who want to fix bugs or add features to the packager (patches welcome!). @@ -45,10 +45,10 @@ Does the following in order: ### /path/to/moduleName.map * if the package has been previously generated via the `.bundle` - endpoint then the source map will be generated from that package + endpoint, then the source map will be generated from that package * if the package has not been previously asked for, this will go - through the same steps outlined in the `.bundle` endpoint then - generate the source map. + through the same steps outlined in the `.bundle` endpoint, then + generate the source map Note that source map generation currently assumes that the code has been compiled with jstransform, which preserves line and column @@ -66,15 +66,15 @@ Here are the current options the packager accepts: * `minify` boolean, defaults to false: whether to minify the bundle. * `runModule` boolean, defaults to true: whether to require your entry point module. So if you requested `moduleName`, this option will add - a `require('moduleName')` the end of your bundle. + a `require('moduleName')` to the end of your bundle. * `inlineSourceMap` boolean, defaults to false: whether to inline source maps. ### /debug -This is a page used for debugging, it has links to two pages: +This is a page used for debugging; it has links to two pages: -* Cached Packages: which shows you the packages that's been already +* Cached Packages: which shows you the packages that have already been generated and cached * Dependency Graph: is the in-memory graph of all the modules and their dependencies @@ -103,8 +103,8 @@ middleware. Takes the following options: packager * `polyfillModuleName` array: Paths to polyfills you want to be included at the start of the bundle -* `cacheVersion` string: used in creating the cache file -* `resetCache` boolean, defaults to false: whether to use the cache on +* `cacheVersion` string: Used in creating the cache file +* `resetCache` boolean, defaults to false: Whether to use the cache on disk * `transformModulePath` string: Path to the module used as a JavaScript transformer @@ -133,6 +133,6 @@ is informed by React Native needs. ### Why didn't you use webpack? -We love webpack, however, when we tried on our codebase it was slower -than our developers would like it to be. You find can more discussion about -the subject [here](https://github.com/facebook/react-native/issues/5) +We love webpack; however, when we tried it on our codebase, it was slower +than our developers wanted it to be. You find can more discussion about +the subject [here](https://github.com/facebook/react-native/issues/5). From 9723503ff73e895271e3c12db2df8ad3558a368b Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 30 Mar 2015 20:12:32 -0700 Subject: [PATCH 103/936] Updates from Mon 30 Mar - [ReactNative] Clean up no longer needed reference to NavigationBarClass | Philipp von Weitershausen - [TextInput] returnKeyType, enablesReturnKeyAutomatically, secureTextEntry, more keyboardTypes | Tadeu Zagallo - [ReactNative] PropTypes for NavigationBars | Philipp von Weitershausen - Changed LayoutAnimation to use ms instead of seconds for consistency | Nick Lockwood - Better date support | Nick Lockwood - Renamed throttleScrollCallbackMS to scrollEventThrottle | Nick Lockwood - Fixed threading issues in RCTImageDownloader | Nick Lockwood - [iOS][Assets]: Cleaning up more 1x png from Libraries | Radu Marin - [ReactNative][docs] LinkingIOS | Tadeu Zagallo - Fixing TouchableOpacity and TouchableHighlight documentation | Ben Alpert - [react-native] Add React.addons.createFragment | Ben Alpert --- .../src/DependencyResolver/haste/DependencyGraph/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 6f09c5e8..00c4a5c5 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -713,6 +713,6 @@ function NotFoundError() { this.status = 404; } -NotFoundError.__proto__ = Error.prototype; +util.inherits(NotFoundError, Error); module.exports = DependecyGraph; From 2c4e9aba802f6fcef5ef65c70b5a1e6233aac039 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 31 Mar 2015 16:04:02 -0700 Subject: [PATCH 104/936] [ReactNative] Add few hints in the UI --- debugger.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debugger.html b/debugger.html index 38e9e6cd..d0d4aba5 100644 --- a/debugger.html +++ b/debugger.html @@ -66,7 +66,7 @@ ws.onopen = function() { setStatus('Debugger session #' + sessionID + ' active'); ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)})); } else { - setStatus('Waiting for simulator'); + setStatus('Waiting, press ⌘R in simulator to reload and connect'); } } From 28a0a2586d9a9bd88103ec0aa7fc8d227e24dae5 Mon Sep 17 00:00:00 2001 From: daviskoh Date: Tue, 31 Mar 2015 17:54:38 -0700 Subject: [PATCH 105/936] Bugfix/require module regexp Summary: Resolves https://github.com/facebook/react-native/issues/316. Also updated the spec for the Haste Dependency Resolver. Not sure if these changes are the ones desired so feedback would be welcome! Closes https://github.com/facebook/react-native/pull/368 Github Author: daviskoh Test Plan: ./runJestTests --- .../__tests__/DependencyGraph-test.js | 1 + .../haste/DependencyGraph/index.js | 4 ++-- .../__tests__/HasteDependencyResolver-test.js | 11 ++++++++--- .../src/DependencyResolver/haste/index.js | 7 +++---- .../src/DependencyResolver/haste/requirePattern.js | 14 ++++++++++++++ 5 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 react-packager/src/DependencyResolver/haste/requirePattern.js diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 69ed11e6..3202274c 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -14,6 +14,7 @@ jest .dontMock('path') .dontMock('absolute-path') .dontMock('../docblock') + .dontMock('../../requirePattern') .setMock('../../../ModuleDescriptor', function(data) {return data;}); describe('DependencyGraph', function() { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 00c4a5c5..a23f6d50 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -12,6 +12,7 @@ var ModuleDescriptor = require('../../ModuleDescriptor'); var q = require('q'); var fs = require('fs'); var docblock = require('./docblock'); +var requirePattern = require('../requirePattern'); var path = require('path'); var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); @@ -600,7 +601,6 @@ DependecyGraph.prototype._processAssetChange = function(eventType, file) { /** * Extract all required modules from a `code` string. */ -var requireRe = /\brequire\s*\(\s*[\'"]([^"\']+)["\']\s*\)/g; var blockCommentRe = /\/\*(.|\n)*?\*\//g; var lineCommentRe = /\/\/.+(\n|$)/g; function extractRequires(code) { @@ -609,7 +609,7 @@ function extractRequires(code) { code .replace(blockCommentRe, '') .replace(lineCommentRe, '') - .replace(requireRe, function(match, dep) { + .replace(requirePattern, function(match, _, dep) { deps.push(dep); }); diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 69354590..c81944d8 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -10,6 +10,7 @@ jest.dontMock('../') .dontMock('q') + .dontMock('../requirePattern') .setMock('../../ModuleDescriptor', function(data) {return data;}); var q = require('q'); @@ -226,11 +227,13 @@ describe('HasteDependencyResolver', function() { }); var depGraph = depResolver._depGraph; - var dependencies = ['x', 'y', 'z']; + var dependencies = ['x', 'y', 'z', 'a', 'b']; var code = [ 'require("x")', 'require("y")', - 'require("z")', + 'require( "z" )', + 'require( "a")', + 'require("b" )', ].join('\n'); depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { @@ -255,7 +258,9 @@ describe('HasteDependencyResolver', function() { ' require, requireDynamic, requireLazy, module, exports) {' + ' require(\'changed\')', 'require(\'y\')', - 'require("z")});', + 'require("z")', + 'require("a")', + 'require("b")});', ].join('\n')); }); }); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 0e46d5e8..941a687e 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -9,8 +9,8 @@ 'use strict'; var path = require('path'); -var FileWatcher = require('../../FileWatcher'); var DependencyGraph = require('./DependencyGraph'); +var requirePattern = require('./requirePattern'); var ModuleDescriptor = require('../ModuleDescriptor'); var declareOpts = require('../../lib/declareOpts'); @@ -25,7 +25,6 @@ var DEFINE_MODULE_CODE = [ ].join(''); var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; -var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; var validateOpts = declareOpts({ projectRoots: { @@ -146,12 +145,12 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { } var relativizedCode = - code.replace(REL_REQUIRE_STMT, function(codeMatch, depName) { + code.replace(requirePattern, function(codeMatch, _, depName) { var depId = resolvedDeps[depName]; if (depId != null) { return 'require(\'' + depId + '\')'; } else { - return codeMatch; + return codeMatch.replace(/\s+/g, ''); } }); diff --git a/react-packager/src/DependencyResolver/haste/requirePattern.js b/react-packager/src/DependencyResolver/haste/requirePattern.js new file mode 100644 index 00000000..26d807ff --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/requirePattern.js @@ -0,0 +1,14 @@ +/** + * 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_RE = /\brequire\s*?\(\s*?([\'"])([^"\']+)\1\s*?\)/g; + +module.exports = REQUIRE_RE; From 6846220139e52d94e035fdab0789677fce683511 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Tue, 31 Mar 2015 19:01:48 -0700 Subject: [PATCH 106/936] Updates from Tue 31 Mar - Bugfix/require module regexp | Amjad Masad - [ReactNative] RCTView's shadowOffset is of float type, not CGFloat | Kevin Gozali - Fix WebView automaticallyAdjustContentInsets error | Spencer Ahrens - [react-native] map view - add onTouch** props | Jiajie Zhu - [react-native] Fix documentation extraction for View | Ben Alpert - [ReactNative] Add few hints in the UI | Alex Kotliarskyi - Adding `scrollWithoutAnimationTo` method for ScrollViews | Felix Oghina - [ScrollView] Add "bounces" property to ScrollView propTypes | Spencer Ahrens - Fix a crash in RCTAsyncLocalStorage when the value is not a string. | Spencer Ahrens - [ReactNative] Remove global MutationObserver to fix Bluebird feature detection | Christopher Chedeau - [catalyst] fix typo | Jiajie Zhu - [react-packager] check-in bluebird | Amjad Masad - [react-native] v0.3.1 | Amjad Masad - [Pod] Preserve header directory structure | Alex Akers - [react-native] Bring React.render behavior in line with web | Ben Alpert - Expose html prop on WebView | Spencer Ahrens - missing '.' in ListView.DataSource example | Christopher Chedeau - [react-native] Support returning null from a component | Ben Alpert - [react-native] Fix race condition in removeSubviewsFromContainerWithID: | Ben Alpert --- debugger.html | 2 +- .../__tests__/DependencyGraph-test.js | 1 + .../haste/DependencyGraph/index.js | 4 ++-- .../__tests__/HasteDependencyResolver-test.js | 11 ++++++++--- .../src/DependencyResolver/haste/index.js | 7 +++---- .../src/DependencyResolver/haste/requirePattern.js | 14 ++++++++++++++ 6 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 react-packager/src/DependencyResolver/haste/requirePattern.js diff --git a/debugger.html b/debugger.html index 38e9e6cd..d0d4aba5 100644 --- a/debugger.html +++ b/debugger.html @@ -66,7 +66,7 @@ ws.onopen = function() { setStatus('Debugger session #' + sessionID + ' active'); ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)})); } else { - setStatus('Waiting for simulator'); + setStatus('Waiting, press ⌘R in simulator to reload and connect'); } } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 69ed11e6..3202274c 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -14,6 +14,7 @@ jest .dontMock('path') .dontMock('absolute-path') .dontMock('../docblock') + .dontMock('../../requirePattern') .setMock('../../../ModuleDescriptor', function(data) {return data;}); describe('DependencyGraph', function() { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 00c4a5c5..a23f6d50 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -12,6 +12,7 @@ var ModuleDescriptor = require('../../ModuleDescriptor'); var q = require('q'); var fs = require('fs'); var docblock = require('./docblock'); +var requirePattern = require('../requirePattern'); var path = require('path'); var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); @@ -600,7 +601,6 @@ DependecyGraph.prototype._processAssetChange = function(eventType, file) { /** * Extract all required modules from a `code` string. */ -var requireRe = /\brequire\s*\(\s*[\'"]([^"\']+)["\']\s*\)/g; var blockCommentRe = /\/\*(.|\n)*?\*\//g; var lineCommentRe = /\/\/.+(\n|$)/g; function extractRequires(code) { @@ -609,7 +609,7 @@ function extractRequires(code) { code .replace(blockCommentRe, '') .replace(lineCommentRe, '') - .replace(requireRe, function(match, dep) { + .replace(requirePattern, function(match, _, dep) { deps.push(dep); }); diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 69354590..c81944d8 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -10,6 +10,7 @@ jest.dontMock('../') .dontMock('q') + .dontMock('../requirePattern') .setMock('../../ModuleDescriptor', function(data) {return data;}); var q = require('q'); @@ -226,11 +227,13 @@ describe('HasteDependencyResolver', function() { }); var depGraph = depResolver._depGraph; - var dependencies = ['x', 'y', 'z']; + var dependencies = ['x', 'y', 'z', 'a', 'b']; var code = [ 'require("x")', 'require("y")', - 'require("z")', + 'require( "z" )', + 'require( "a")', + 'require("b" )', ].join('\n'); depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { @@ -255,7 +258,9 @@ describe('HasteDependencyResolver', function() { ' require, requireDynamic, requireLazy, module, exports) {' + ' require(\'changed\')', 'require(\'y\')', - 'require("z")});', + 'require("z")', + 'require("a")', + 'require("b")});', ].join('\n')); }); }); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 0e46d5e8..941a687e 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -9,8 +9,8 @@ 'use strict'; var path = require('path'); -var FileWatcher = require('../../FileWatcher'); var DependencyGraph = require('./DependencyGraph'); +var requirePattern = require('./requirePattern'); var ModuleDescriptor = require('../ModuleDescriptor'); var declareOpts = require('../../lib/declareOpts'); @@ -25,7 +25,6 @@ var DEFINE_MODULE_CODE = [ ].join(''); var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; -var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; var validateOpts = declareOpts({ projectRoots: { @@ -146,12 +145,12 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { } var relativizedCode = - code.replace(REL_REQUIRE_STMT, function(codeMatch, depName) { + code.replace(requirePattern, function(codeMatch, _, depName) { var depId = resolvedDeps[depName]; if (depId != null) { return 'require(\'' + depId + '\')'; } else { - return codeMatch; + return codeMatch.replace(/\s+/g, ''); } }); diff --git a/react-packager/src/DependencyResolver/haste/requirePattern.js b/react-packager/src/DependencyResolver/haste/requirePattern.js new file mode 100644 index 00000000..26d807ff --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/requirePattern.js @@ -0,0 +1,14 @@ +/** + * 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_RE = /\brequire\s*?\(\s*?([\'"])([^"\']+)\1\s*?\)/g; + +module.exports = REQUIRE_RE; From 69ae1bb3b7a569bea951d709624ee7ab4d047bee Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 31 Mar 2015 20:06:11 -0700 Subject: [PATCH 107/936] [react-packager] Fix EISDIR error --- .../FileWatcher/__tests__/FileWatcher-test.js | 17 +++++++++++++++++ react-packager/src/FileWatcher/index.js | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index e24618dc..fc45205a 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -9,6 +9,8 @@ 'use strict'; jest + .dontMock('util') + .dontMock('events') .dontMock('../') .dontMock('q') .setMock( @@ -38,6 +40,21 @@ describe('FileWatcher', function() { }); }); + pit('should emit events', function() { + var cb; + Watcher.prototype.on.mockImplementation(function(type, callback) { + cb = callback; + }); + var fileWatcher = new FileWatcher(['rootDir']); + var handler = jest.genMockFn(); + fileWatcher.on('all', handler); + return fileWatcher._loading.then(function(){ + cb(1, 2, 3, 4); + jest.runAllTimers(); + expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]); + }); + }); + pit('it should end the watcher', function() { var fileWatcher = new FileWatcher(['rootDir']); Watcher.prototype.close.mockImplementation(function(callback) { diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index 86ec962b..af8e4ffd 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -45,8 +45,8 @@ function FileWatcher(rootConfigs) { rootConfigs.map(createWatcher) ).then(function(watchers) { watchers.forEach(function(watcher) { - watcher.on('all', function(type, filepath, root) { - fileWatcher.emit('all', type, filepath, root); + watcher.on('all', function(type, filepath, root, stat) { + fileWatcher.emit('all', type, filepath, root, stat); }); }); return watchers; From 7f6255b16f19f6955d6dfb90536d013a39532d57 Mon Sep 17 00:00:00 2001 From: Pilwon Huh Date: Tue, 31 Mar 2015 20:23:33 -0700 Subject: [PATCH 108/936] [react-packager] Switch from Q to Bluebird as promises library Summary: This PR improves performance of `react-packager` by switching the promises library from the [Q](https://github.com/kriskowal/q) to [Bluebird](https://github.com/petkaantonov/bluebird). [Here is the test result](https://github.com/facebook/react-native/issues/361#issuecomment-87829808) showing a noticeable difference. (2x speed improvement) Please refer to [this issue](https://github.com/facebook/react-native/issues/361) for more details. Closes https://github.com/facebook/react-native/pull/516 Github Author: Pilwon Huh Test Plan: ./runJestTests start app and click around --- react-packager/__mocks__/bluebird.js | 5 +++ .../__tests__/DependencyGraph-test.js | 1 - .../haste/DependencyGraph/index.js | 34 +++++++++---------- .../__tests__/HasteDependencyResolver-test.js | 8 ++--- .../src/DependencyResolver/node/index.js | 2 +- react-packager/src/FileWatcher/index.js | 10 +++--- react-packager/src/JSTransformer/Cache.js | 10 +++--- .../src/JSTransformer/__tests__/Cache-test.js | 16 ++++----- react-packager/src/JSTransformer/index.js | 13 +++---- .../src/Packager/__tests__/Packager-test.js | 7 ++-- react-packager/src/Packager/index.js | 5 ++- .../src/Server/__tests__/Server-test.js | 32 ++++++++--------- react-packager/src/Server/index.js | 8 ++--- 13 files changed, 75 insertions(+), 76 deletions(-) create mode 100644 react-packager/__mocks__/bluebird.js diff --git a/react-packager/__mocks__/bluebird.js b/react-packager/__mocks__/bluebird.js new file mode 100644 index 00000000..9ac6e14b --- /dev/null +++ b/react-packager/__mocks__/bluebird.js @@ -0,0 +1,5 @@ +'use strict'; + +jest.autoMockOff(); +module.exports = require.requireActual('bluebird'); +jest.autoMockOn(); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 3202274c..5f6ec882 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,7 +10,6 @@ jest .dontMock('../index') - .dontMock('q') .dontMock('path') .dontMock('absolute-path') .dontMock('../docblock') diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index a23f6d50..9ca430c4 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -9,7 +9,7 @@ 'use strict'; var ModuleDescriptor = require('../../ModuleDescriptor'); -var q = require('q'); +var Promise = require('bluebird'); var fs = require('fs'); var docblock = require('./docblock'); var requirePattern = require('../requirePattern'); @@ -19,10 +19,10 @@ var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); -var readFile = q.nfbind(fs.readFile); -var readDir = q.nfbind(fs.readdir); -var lstat = q.nfbind(fs.lstat); -var realpath = q.nfbind(fs.realpath); +var readFile = Promise.promisify(fs.readFile); +var readDir = Promise.promisify(fs.readdir); +var lstat = Promise.promisify(fs.lstat); +var realpath = Promise.promisify(fs.realpath); var validateOpts = declareOpts({ roots: { @@ -73,7 +73,7 @@ DependecyGraph.prototype.load = function() { return this._loading; } - this._loading = q.all([ + this._loading = Promise.all([ this._search(), this._buildAssetMap(), ]); @@ -263,7 +263,7 @@ DependecyGraph.prototype._search = function() { var dir = this._queue.shift(); if (dir == null) { - return q.Promise.resolve(this._graph); + return Promise.resolve(this._graph); } // Steps: @@ -292,10 +292,10 @@ DependecyGraph.prototype._search = function() { var processing = self._findAndProcessPackage(files, dir) .then(function() { - return q.all(modulePaths.map(self._processModule.bind(self))); + return Promise.all(modulePaths.map(self._processModule.bind(self))); }); - return q.all([ + return Promise.all([ processing, self._search() ]); @@ -324,7 +324,7 @@ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { if (packagePath != null) { return this._processPackage(packagePath); } else { - return q(); + return Promise.resolve(); } }; @@ -338,7 +338,7 @@ DependecyGraph.prototype._processPackage = function(packagePath) { packageJson = JSON.parse(content); } catch (e) { debug('WARNING: malformed package.json: ', packagePath); - return q(); + return Promise.resolve(); } if (packageJson.name == null) { @@ -346,7 +346,7 @@ DependecyGraph.prototype._processPackage = function(packagePath) { 'WARNING: package.json `%s` is missing a name field', packagePath ); - return q(); + return Promise.resolve(); } packageJson._root = packageRoot; @@ -556,7 +556,7 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) { DependecyGraph.prototype._buildAssetMap = function() { if (this._assetRoots == null || this._assetRoots.length === 0) { - return q(); + return Promise.resolve(); } this._assetMap = Object.create(null); @@ -640,13 +640,13 @@ function withExtJs(file) { function handleBrokenLink(e) { debug('WARNING: error stating, possibly broken symlink', e.message); - return q(); + return Promise.resolve(); } function readAndStatDir(dir) { return readDir(dir) .then(function(files){ - return q.all(files.map(function(filePath) { + return Promise.all(files.map(function(filePath) { return realpath(path.join(dir, filePath)).catch(handleBrokenLink); })); }).then(function(files) { @@ -660,7 +660,7 @@ function readAndStatDir(dir) { return [ files, - q.all(stats), + Promise.all(stats), ]; }); } @@ -676,7 +676,7 @@ function buildAssetMap(roots, processAsset) { var root = queue.shift(); if (root == null) { - return q(); + return Promise.resolve(); } return readAndStatDir(root).spread(function(files, stats) { diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index c81944d8..2a24f2d1 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -13,7 +13,7 @@ jest.dontMock('../') .dontMock('../requirePattern') .setMock('../../ModuleDescriptor', function(data) {return data;}); -var q = require('q'); +var Promise = require('bluebird'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; @@ -41,7 +41,7 @@ describe('HasteDependencyResolver', function() { return deps; }); depGraph.load.mockImpl(function() { - return q(); + return Promise.resolve(); }); return depResolver.getDependencies('/root/index.js', { dev: false }) @@ -101,7 +101,7 @@ describe('HasteDependencyResolver', function() { return deps; }); depGraph.load.mockImpl(function() { - return q(); + return Promise.resolve(); }); return depResolver.getDependencies('/root/index.js', { dev: true }) @@ -162,7 +162,7 @@ describe('HasteDependencyResolver', function() { return deps; }); depGraph.load.mockImpl(function() { - return q(); + return Promise.resolve(); }); return depResolver.getDependencies('/root/index.js', { dev: false }) diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js index 46c93c2f..57388564 100644 --- a/react-packager/src/DependencyResolver/node/index.js +++ b/react-packager/src/DependencyResolver/node/index.js @@ -8,7 +8,7 @@ */ 'use strict'; -var Promise = require('q').Promise; +var Promise = require('bluebird'); var ModuleDescriptor = require('../ModuleDescriptor'); var mdeps = require('module-deps'); diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index af8e4ffd..6f451b48 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -10,12 +10,10 @@ var EventEmitter = require('events').EventEmitter; var sane = require('sane'); -var q = require('q'); +var Promise = require('bluebird'); var util = require('util'); var exec = require('child_process').exec; -var Promise = q.Promise; - var detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { @@ -41,7 +39,7 @@ function FileWatcher(rootConfigs) { fileWatcher = this; - this._loading = q.all( + this._loading = Promise.all( rootConfigs.map(createWatcher) ).then(function(watchers) { watchers.forEach(function(watcher) { @@ -59,7 +57,7 @@ util.inherits(FileWatcher, EventEmitter); FileWatcher.prototype.end = function() { return this._loading.then(function(watchers) { watchers.forEach(function(watcher) { - return q.ninvoke(watcher, 'close'); + return Promise.promisify(watcher.close, watcher)(); }); }); }; @@ -88,7 +86,7 @@ function createWatcher(rootConfig) { FileWatcher.createDummyWatcher = function() { var ev = new EventEmitter(); ev.end = function() { - return q(); + return Promise.resolve(); }; return ev; }; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index 363be7b9..4761d15e 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -14,12 +14,10 @@ var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); var isAbsolutePath = require('absolute-path'); var path = require('path'); -var q = require('q'); +var Promise = require('bluebird'); var tmpdir = require('os').tmpDir(); var version = require('../../../../package.json').version; -var Promise = q.Promise; - var validateOpts = declareOpts({ resetCache: { type: 'boolean', @@ -74,7 +72,7 @@ Cache.prototype._set = function(filepath, loaderPromise) { this._data[filepath] = loaderPromise.then(function(data) { return [ data, - q.nfbind(fs.stat)(filepath) + Promise.promisify(fs.stat)(filepath) ]; }).spread(function(data, stat) { this._persistEventually(); @@ -105,13 +103,13 @@ Cache.prototype._persistCache = function() { var data = this._data; var cacheFilepath = this._cacheFilePath; - this._persisting = q.all(_.values(data)) + this._persisting = Promise.all(_.values(data)) .then(function(values) { var json = Object.create(null); Object.keys(data).forEach(function(key, i) { json[key] = values[i]; }); - return q.nfbind(fs.writeFile)(cacheFilepath, JSON.stringify(json)); + return Promise.promisify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); }) .then(function() { this._persisting = null; diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 6af1ff94..51f7ec05 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -15,7 +15,7 @@ jest .dontMock('crypto') .dontMock('../Cache'); -var q = require('q'); +var Promise = require('bluebird'); describe('JSTransformer Cache', function() { var Cache; @@ -32,7 +32,7 @@ describe('JSTransformer Cache', function() { it('calls loader callback for uncached file', function() { var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { - return q(); + return Promise.resolve(); }); cache.get('/rootDir/someFile', loaderCb); expect(loaderCb).toBeCalledWith('/rootDir/someFile'); @@ -48,7 +48,7 @@ describe('JSTransformer Cache', function() { }); var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { - return q('lol'); + return Promise.resolve('lol'); }); return cache.get('/rootDir/someFile', loaderCb).then(function(value) { expect(value).toBe('lol'); @@ -65,7 +65,7 @@ describe('JSTransformer Cache', function() { }); var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { - return q('lol'); + return Promise.resolve('lol'); }); return cache.get('/rootDir/someFile', loaderCb).then(function() { var shouldNotBeCalled = jest.genMockFn(); @@ -152,7 +152,7 @@ describe('JSTransformer Cache', function() { var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { - return q('new value'); + return Promise.resolve('new value'); }); return cache.get('/rootDir/someFile', loaderCb).then(function(value) { @@ -193,13 +193,13 @@ describe('JSTransformer Cache', function() { var cache = new Cache({projectRoots: ['/rootDir']}); cache.get('/rootDir/bar', function() { - return q('bar value'); + return Promise.resolve('bar value'); }); cache.get('/rootDir/foo', function() { - return q('foo value'); + return Promise.resolve('foo value'); }); cache.get('/rootDir/baz', function() { - return q('baz value'); + return Promise.resolve('baz value'); }); jest.runAllTicks(); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index c7f7bb7f..fde8336e 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -9,14 +9,13 @@ 'use strict'; var fs = require('fs'); -var q = require('q'); +var Promise = require('bluebird'); var Cache = require('./Cache'); -var _ = require('underscore'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); var util = require('util'); -var readFile = q.nfbind(fs.readFile); +var readFile = Promise.promisify(fs.readFile); module.exports = Transformer; Transformer.TransformError = TransformError; @@ -63,12 +62,14 @@ function Transformer(options) { }); if (options.transformModulePath == null) { - this._failedToStart = q.Promise.reject(new Error('No transfrom module')); + this._failedToStart = Promise.reject(new Error('No transfrom module')); } else { this._workers = workerFarm( {autoStart: true, maxConcurrentCallsPerWorker: 1}, options.transformModulePath ); + + this._transform = Promise.promisify(this._workers); } } @@ -86,13 +87,13 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { return this._failedToStart; } - var workers = this._workers; + var transform = this._transform; return this._cache.get(filePath, function() { return readFile(filePath) .then(function(buffer) { var sourceCode = buffer.toString(); - return q.nfbind(workers)({ + return transform({ sourceCode: sourceCode, filename: filePath, }).then( diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index bed6fac3..8f61df97 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -11,13 +11,12 @@ jest .setMock('worker-farm', function() { return function() {};}) .dontMock('path') - .dontMock('q') .dontMock('os') .dontMock('underscore') .setMock('uglify-js') .dontMock('../'); -var q = require('q'); +var Promise = require('bluebird'); describe('Packager', function() { var getDependencies; @@ -56,7 +55,7 @@ describe('Packager', function() { ]; getDependencies.mockImpl(function() { - return q({ + return Promise.resolve({ mainModuleId: 'foo', dependencies: modules }); @@ -64,7 +63,7 @@ describe('Packager', function() { require('../../JSTransformer').prototype.loadFileAndTransform .mockImpl(function(path) { - return q({ + return Promise.resolve({ code: 'transformed ' + path, sourceCode: 'source ' + path, sourcePath: path diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 843efe75..bf5a635d 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -11,8 +11,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); -var q = require('q'); -var Promise = require('q').Promise; +var Promise = require('bluebird'); var Transformer = require('../JSTransformer'); var DependencyResolver = require('../DependencyResolver'); var _ = require('underscore'); @@ -140,7 +139,7 @@ Packager.prototype._transformModule = function(module) { var transform; if (module.isAsset) { - transform = q(generateAssetModule(module)); + transform = Promise.resolve(generateAssetModule(module)); } else { transform = this._transformer.loadFileAndTransform( path.resolve(module.path) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index aa7ca349..a7b65021 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -9,7 +9,6 @@ 'use strict'; jest.setMock('worker-farm', function() { return function() {}; }) - .dontMock('q') .dontMock('os') .dontMock('path') .dontMock('url') @@ -21,7 +20,7 @@ jest.setMock('worker-farm', function() { return function() {}; }) .setMock('uglify-js') .dontMock('../'); -var q = require('q'); +var Promise = require('bluebird'); describe('processRequest', function() { var server; @@ -36,18 +35,19 @@ describe('processRequest', function() { }; var makeRequest = function(requestHandler, requrl) { - var deferred = q.defer(); - requestHandler({ - url: requrl - },{ - end: function(res) { - deferred.resolve(res); + return new Promise(function(resolve) { + requestHandler( + { url: requrl }, + { + end: function(res) { + resolve(res); + } + }, + { + next: function() {} } - },{ - next: function() {} - } - ); - return deferred.promise; + ); + }); }; var invalidatorFunc = jest.genMockFunction(); @@ -60,7 +60,7 @@ describe('processRequest', function() { FileWatcher = require('../../FileWatcher'); Packager.prototype.package = jest.genMockFunction().mockImpl(function() { - return q({ + return Promise.resolve({ getSource: function() { return 'this is the source'; }, @@ -156,7 +156,7 @@ describe('processRequest', function() { var packageFunc = jest.genMockFunction(); packageFunc .mockReturnValueOnce( - q({ + Promise.resolve({ getSource: function() { return 'this is the first source'; }, @@ -164,7 +164,7 @@ describe('processRequest', function() { }) ) .mockReturnValue( - q({ + Promise.resolve({ getSource: function() { return 'this is the rebuilt source'; }, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 23af55db..65507581 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -14,7 +14,7 @@ var declareOpts = require('../lib/declareOpts'); var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); -var q = require('q'); +var Promise = require('bluebird'); var _ = require('underscore'); module.exports = Server; @@ -123,7 +123,7 @@ Server.prototype._rebuildPackages = function() { Object.keys(packages).forEach(function(key) { var options = getOptionsFromUrl(key); // Wait for a previous build (if exists) to finish. - packages[key] = (packages[key] || q()).finally(function() { + packages[key] = (packages[key] || 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[key] = buildPackage(options).then(function(p) { @@ -154,7 +154,7 @@ Server.prototype._informChangeWatchers = function() { }; Server.prototype.end = function() { - q.all([ + Promise.all([ this._fileWatcher.end(), this._packager.kill(), ]); @@ -188,7 +188,7 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { res.end(ret); } else if (parts[1] === 'packages') { ret += '

Cached Packages

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

' + url + '

'; ret += p.getDebugInfo(); From cde9ab236f95a36c3a6d0d33df950f23a461346c Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Tue, 31 Mar 2015 22:48:18 -0700 Subject: [PATCH 109/936] - [react-packager] Switch from Q to Bluebird as promises library | Pilwon Huh - [Touchable] Change default `activeOpacity` to 0.2 to match iOS | James Ide - [ReactNative] Normalize name for examples | Christopher Chedeau - [ReactNative] Added support for 3 digit hex colors | Arthur Lee - [react-packager] Fix EISDIR error | Amjad Masad - make renderError and renderLoading props optional for WebView | Don Yu --- react-packager/__mocks__/bluebird.js | 5 +++ .../__tests__/DependencyGraph-test.js | 1 - .../haste/DependencyGraph/index.js | 34 +++++++++---------- .../__tests__/HasteDependencyResolver-test.js | 8 ++--- .../src/DependencyResolver/node/index.js | 2 +- .../FileWatcher/__tests__/FileWatcher-test.js | 17 ++++++++++ react-packager/src/FileWatcher/index.js | 14 ++++---- react-packager/src/JSTransformer/Cache.js | 10 +++--- .../src/JSTransformer/__tests__/Cache-test.js | 16 ++++----- react-packager/src/JSTransformer/index.js | 13 +++---- .../src/Packager/__tests__/Packager-test.js | 7 ++-- react-packager/src/Packager/index.js | 5 ++- .../src/Server/__tests__/Server-test.js | 32 ++++++++--------- react-packager/src/Server/index.js | 8 ++--- 14 files changed, 94 insertions(+), 78 deletions(-) create mode 100644 react-packager/__mocks__/bluebird.js diff --git a/react-packager/__mocks__/bluebird.js b/react-packager/__mocks__/bluebird.js new file mode 100644 index 00000000..9ac6e14b --- /dev/null +++ b/react-packager/__mocks__/bluebird.js @@ -0,0 +1,5 @@ +'use strict'; + +jest.autoMockOff(); +module.exports = require.requireActual('bluebird'); +jest.autoMockOn(); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 3202274c..5f6ec882 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,7 +10,6 @@ jest .dontMock('../index') - .dontMock('q') .dontMock('path') .dontMock('absolute-path') .dontMock('../docblock') diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index a23f6d50..9ca430c4 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -9,7 +9,7 @@ 'use strict'; var ModuleDescriptor = require('../../ModuleDescriptor'); -var q = require('q'); +var Promise = require('bluebird'); var fs = require('fs'); var docblock = require('./docblock'); var requirePattern = require('../requirePattern'); @@ -19,10 +19,10 @@ var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); -var readFile = q.nfbind(fs.readFile); -var readDir = q.nfbind(fs.readdir); -var lstat = q.nfbind(fs.lstat); -var realpath = q.nfbind(fs.realpath); +var readFile = Promise.promisify(fs.readFile); +var readDir = Promise.promisify(fs.readdir); +var lstat = Promise.promisify(fs.lstat); +var realpath = Promise.promisify(fs.realpath); var validateOpts = declareOpts({ roots: { @@ -73,7 +73,7 @@ DependecyGraph.prototype.load = function() { return this._loading; } - this._loading = q.all([ + this._loading = Promise.all([ this._search(), this._buildAssetMap(), ]); @@ -263,7 +263,7 @@ DependecyGraph.prototype._search = function() { var dir = this._queue.shift(); if (dir == null) { - return q.Promise.resolve(this._graph); + return Promise.resolve(this._graph); } // Steps: @@ -292,10 +292,10 @@ DependecyGraph.prototype._search = function() { var processing = self._findAndProcessPackage(files, dir) .then(function() { - return q.all(modulePaths.map(self._processModule.bind(self))); + return Promise.all(modulePaths.map(self._processModule.bind(self))); }); - return q.all([ + return Promise.all([ processing, self._search() ]); @@ -324,7 +324,7 @@ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { if (packagePath != null) { return this._processPackage(packagePath); } else { - return q(); + return Promise.resolve(); } }; @@ -338,7 +338,7 @@ DependecyGraph.prototype._processPackage = function(packagePath) { packageJson = JSON.parse(content); } catch (e) { debug('WARNING: malformed package.json: ', packagePath); - return q(); + return Promise.resolve(); } if (packageJson.name == null) { @@ -346,7 +346,7 @@ DependecyGraph.prototype._processPackage = function(packagePath) { 'WARNING: package.json `%s` is missing a name field', packagePath ); - return q(); + return Promise.resolve(); } packageJson._root = packageRoot; @@ -556,7 +556,7 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) { DependecyGraph.prototype._buildAssetMap = function() { if (this._assetRoots == null || this._assetRoots.length === 0) { - return q(); + return Promise.resolve(); } this._assetMap = Object.create(null); @@ -640,13 +640,13 @@ function withExtJs(file) { function handleBrokenLink(e) { debug('WARNING: error stating, possibly broken symlink', e.message); - return q(); + return Promise.resolve(); } function readAndStatDir(dir) { return readDir(dir) .then(function(files){ - return q.all(files.map(function(filePath) { + return Promise.all(files.map(function(filePath) { return realpath(path.join(dir, filePath)).catch(handleBrokenLink); })); }).then(function(files) { @@ -660,7 +660,7 @@ function readAndStatDir(dir) { return [ files, - q.all(stats), + Promise.all(stats), ]; }); } @@ -676,7 +676,7 @@ function buildAssetMap(roots, processAsset) { var root = queue.shift(); if (root == null) { - return q(); + return Promise.resolve(); } return readAndStatDir(root).spread(function(files, stats) { diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index c81944d8..2a24f2d1 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -13,7 +13,7 @@ jest.dontMock('../') .dontMock('../requirePattern') .setMock('../../ModuleDescriptor', function(data) {return data;}); -var q = require('q'); +var Promise = require('bluebird'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; @@ -41,7 +41,7 @@ describe('HasteDependencyResolver', function() { return deps; }); depGraph.load.mockImpl(function() { - return q(); + return Promise.resolve(); }); return depResolver.getDependencies('/root/index.js', { dev: false }) @@ -101,7 +101,7 @@ describe('HasteDependencyResolver', function() { return deps; }); depGraph.load.mockImpl(function() { - return q(); + return Promise.resolve(); }); return depResolver.getDependencies('/root/index.js', { dev: true }) @@ -162,7 +162,7 @@ describe('HasteDependencyResolver', function() { return deps; }); depGraph.load.mockImpl(function() { - return q(); + return Promise.resolve(); }); return depResolver.getDependencies('/root/index.js', { dev: false }) diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js index 46c93c2f..57388564 100644 --- a/react-packager/src/DependencyResolver/node/index.js +++ b/react-packager/src/DependencyResolver/node/index.js @@ -8,7 +8,7 @@ */ 'use strict'; -var Promise = require('q').Promise; +var Promise = require('bluebird'); var ModuleDescriptor = require('../ModuleDescriptor'); var mdeps = require('module-deps'); diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index e24618dc..fc45205a 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -9,6 +9,8 @@ 'use strict'; jest + .dontMock('util') + .dontMock('events') .dontMock('../') .dontMock('q') .setMock( @@ -38,6 +40,21 @@ describe('FileWatcher', function() { }); }); + pit('should emit events', function() { + var cb; + Watcher.prototype.on.mockImplementation(function(type, callback) { + cb = callback; + }); + var fileWatcher = new FileWatcher(['rootDir']); + var handler = jest.genMockFn(); + fileWatcher.on('all', handler); + return fileWatcher._loading.then(function(){ + cb(1, 2, 3, 4); + jest.runAllTimers(); + expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]); + }); + }); + pit('it should end the watcher', function() { var fileWatcher = new FileWatcher(['rootDir']); Watcher.prototype.close.mockImplementation(function(callback) { diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index 86ec962b..6f451b48 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -10,12 +10,10 @@ var EventEmitter = require('events').EventEmitter; var sane = require('sane'); -var q = require('q'); +var Promise = require('bluebird'); var util = require('util'); var exec = require('child_process').exec; -var Promise = q.Promise; - var detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { @@ -41,12 +39,12 @@ function FileWatcher(rootConfigs) { fileWatcher = this; - this._loading = q.all( + this._loading = Promise.all( rootConfigs.map(createWatcher) ).then(function(watchers) { watchers.forEach(function(watcher) { - watcher.on('all', function(type, filepath, root) { - fileWatcher.emit('all', type, filepath, root); + watcher.on('all', function(type, filepath, root, stat) { + fileWatcher.emit('all', type, filepath, root, stat); }); }); return watchers; @@ -59,7 +57,7 @@ util.inherits(FileWatcher, EventEmitter); FileWatcher.prototype.end = function() { return this._loading.then(function(watchers) { watchers.forEach(function(watcher) { - return q.ninvoke(watcher, 'close'); + return Promise.promisify(watcher.close, watcher)(); }); }); }; @@ -88,7 +86,7 @@ function createWatcher(rootConfig) { FileWatcher.createDummyWatcher = function() { var ev = new EventEmitter(); ev.end = function() { - return q(); + return Promise.resolve(); }; return ev; }; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index 363be7b9..4761d15e 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -14,12 +14,10 @@ var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); var isAbsolutePath = require('absolute-path'); var path = require('path'); -var q = require('q'); +var Promise = require('bluebird'); var tmpdir = require('os').tmpDir(); var version = require('../../../../package.json').version; -var Promise = q.Promise; - var validateOpts = declareOpts({ resetCache: { type: 'boolean', @@ -74,7 +72,7 @@ Cache.prototype._set = function(filepath, loaderPromise) { this._data[filepath] = loaderPromise.then(function(data) { return [ data, - q.nfbind(fs.stat)(filepath) + Promise.promisify(fs.stat)(filepath) ]; }).spread(function(data, stat) { this._persistEventually(); @@ -105,13 +103,13 @@ Cache.prototype._persistCache = function() { var data = this._data; var cacheFilepath = this._cacheFilePath; - this._persisting = q.all(_.values(data)) + this._persisting = Promise.all(_.values(data)) .then(function(values) { var json = Object.create(null); Object.keys(data).forEach(function(key, i) { json[key] = values[i]; }); - return q.nfbind(fs.writeFile)(cacheFilepath, JSON.stringify(json)); + return Promise.promisify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); }) .then(function() { this._persisting = null; diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 6af1ff94..51f7ec05 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -15,7 +15,7 @@ jest .dontMock('crypto') .dontMock('../Cache'); -var q = require('q'); +var Promise = require('bluebird'); describe('JSTransformer Cache', function() { var Cache; @@ -32,7 +32,7 @@ describe('JSTransformer Cache', function() { it('calls loader callback for uncached file', function() { var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { - return q(); + return Promise.resolve(); }); cache.get('/rootDir/someFile', loaderCb); expect(loaderCb).toBeCalledWith('/rootDir/someFile'); @@ -48,7 +48,7 @@ describe('JSTransformer Cache', function() { }); var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { - return q('lol'); + return Promise.resolve('lol'); }); return cache.get('/rootDir/someFile', loaderCb).then(function(value) { expect(value).toBe('lol'); @@ -65,7 +65,7 @@ describe('JSTransformer Cache', function() { }); var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { - return q('lol'); + return Promise.resolve('lol'); }); return cache.get('/rootDir/someFile', loaderCb).then(function() { var shouldNotBeCalled = jest.genMockFn(); @@ -152,7 +152,7 @@ describe('JSTransformer Cache', function() { var cache = new Cache({projectRoots: ['/rootDir']}); var loaderCb = jest.genMockFn().mockImpl(function() { - return q('new value'); + return Promise.resolve('new value'); }); return cache.get('/rootDir/someFile', loaderCb).then(function(value) { @@ -193,13 +193,13 @@ describe('JSTransformer Cache', function() { var cache = new Cache({projectRoots: ['/rootDir']}); cache.get('/rootDir/bar', function() { - return q('bar value'); + return Promise.resolve('bar value'); }); cache.get('/rootDir/foo', function() { - return q('foo value'); + return Promise.resolve('foo value'); }); cache.get('/rootDir/baz', function() { - return q('baz value'); + return Promise.resolve('baz value'); }); jest.runAllTicks(); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index c7f7bb7f..fde8336e 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -9,14 +9,13 @@ 'use strict'; var fs = require('fs'); -var q = require('q'); +var Promise = require('bluebird'); var Cache = require('./Cache'); -var _ = require('underscore'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); var util = require('util'); -var readFile = q.nfbind(fs.readFile); +var readFile = Promise.promisify(fs.readFile); module.exports = Transformer; Transformer.TransformError = TransformError; @@ -63,12 +62,14 @@ function Transformer(options) { }); if (options.transformModulePath == null) { - this._failedToStart = q.Promise.reject(new Error('No transfrom module')); + this._failedToStart = Promise.reject(new Error('No transfrom module')); } else { this._workers = workerFarm( {autoStart: true, maxConcurrentCallsPerWorker: 1}, options.transformModulePath ); + + this._transform = Promise.promisify(this._workers); } } @@ -86,13 +87,13 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { return this._failedToStart; } - var workers = this._workers; + var transform = this._transform; return this._cache.get(filePath, function() { return readFile(filePath) .then(function(buffer) { var sourceCode = buffer.toString(); - return q.nfbind(workers)({ + return transform({ sourceCode: sourceCode, filename: filePath, }).then( diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index bed6fac3..8f61df97 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -11,13 +11,12 @@ jest .setMock('worker-farm', function() { return function() {};}) .dontMock('path') - .dontMock('q') .dontMock('os') .dontMock('underscore') .setMock('uglify-js') .dontMock('../'); -var q = require('q'); +var Promise = require('bluebird'); describe('Packager', function() { var getDependencies; @@ -56,7 +55,7 @@ describe('Packager', function() { ]; getDependencies.mockImpl(function() { - return q({ + return Promise.resolve({ mainModuleId: 'foo', dependencies: modules }); @@ -64,7 +63,7 @@ describe('Packager', function() { require('../../JSTransformer').prototype.loadFileAndTransform .mockImpl(function(path) { - return q({ + return Promise.resolve({ code: 'transformed ' + path, sourceCode: 'source ' + path, sourcePath: path diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 843efe75..bf5a635d 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -11,8 +11,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); -var q = require('q'); -var Promise = require('q').Promise; +var Promise = require('bluebird'); var Transformer = require('../JSTransformer'); var DependencyResolver = require('../DependencyResolver'); var _ = require('underscore'); @@ -140,7 +139,7 @@ Packager.prototype._transformModule = function(module) { var transform; if (module.isAsset) { - transform = q(generateAssetModule(module)); + transform = Promise.resolve(generateAssetModule(module)); } else { transform = this._transformer.loadFileAndTransform( path.resolve(module.path) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index aa7ca349..a7b65021 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -9,7 +9,6 @@ 'use strict'; jest.setMock('worker-farm', function() { return function() {}; }) - .dontMock('q') .dontMock('os') .dontMock('path') .dontMock('url') @@ -21,7 +20,7 @@ jest.setMock('worker-farm', function() { return function() {}; }) .setMock('uglify-js') .dontMock('../'); -var q = require('q'); +var Promise = require('bluebird'); describe('processRequest', function() { var server; @@ -36,18 +35,19 @@ describe('processRequest', function() { }; var makeRequest = function(requestHandler, requrl) { - var deferred = q.defer(); - requestHandler({ - url: requrl - },{ - end: function(res) { - deferred.resolve(res); + return new Promise(function(resolve) { + requestHandler( + { url: requrl }, + { + end: function(res) { + resolve(res); + } + }, + { + next: function() {} } - },{ - next: function() {} - } - ); - return deferred.promise; + ); + }); }; var invalidatorFunc = jest.genMockFunction(); @@ -60,7 +60,7 @@ describe('processRequest', function() { FileWatcher = require('../../FileWatcher'); Packager.prototype.package = jest.genMockFunction().mockImpl(function() { - return q({ + return Promise.resolve({ getSource: function() { return 'this is the source'; }, @@ -156,7 +156,7 @@ describe('processRequest', function() { var packageFunc = jest.genMockFunction(); packageFunc .mockReturnValueOnce( - q({ + Promise.resolve({ getSource: function() { return 'this is the first source'; }, @@ -164,7 +164,7 @@ describe('processRequest', function() { }) ) .mockReturnValue( - q({ + Promise.resolve({ getSource: function() { return 'this is the rebuilt source'; }, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 23af55db..65507581 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -14,7 +14,7 @@ var declareOpts = require('../lib/declareOpts'); var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); -var q = require('q'); +var Promise = require('bluebird'); var _ = require('underscore'); module.exports = Server; @@ -123,7 +123,7 @@ Server.prototype._rebuildPackages = function() { Object.keys(packages).forEach(function(key) { var options = getOptionsFromUrl(key); // Wait for a previous build (if exists) to finish. - packages[key] = (packages[key] || q()).finally(function() { + packages[key] = (packages[key] || 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[key] = buildPackage(options).then(function(p) { @@ -154,7 +154,7 @@ Server.prototype._informChangeWatchers = function() { }; Server.prototype.end = function() { - q.all([ + Promise.all([ this._fileWatcher.end(), this._packager.kill(), ]); @@ -188,7 +188,7 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { res.end(ret); } else if (parts[1] === 'packages') { ret += '

Cached Packages

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

' + url + '

'; ret += p.getDebugInfo(); From ab9f78496f03e6cbaac9c160a9b902d61d099afe Mon Sep 17 00:00:00 2001 From: Jacob Gable Date: Tue, 31 Mar 2015 23:14:46 -0700 Subject: [PATCH 110/936] Do not expose define references in require polyfill Summary: See #406 Made sure the jest tests pass but didn't know a good unit test to add for this. Closes https://github.com/facebook/react-native/pull/427 Github Author: Jacob Gable Test Plan: * ./runJestTests * start app and click around --- .../src/DependencyResolver/haste/polyfills/require.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/haste/polyfills/require.js index 00955ba4..80705848 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/require.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -586,15 +586,11 @@ _register('module', 0); _register('exports', 0); - _register('define', define); _register('global', global); _register('require', require); _register('requireDynamic', require); _register('requireLazy', requireLazy); - define.amd = {}; - - global.define = define; global.require = require; global.requireDynamic = require; global.requireLazy = requireLazy; From ceeb06eb1aa72cfb77c5a46fd8fe110f978fe996 Mon Sep 17 00:00:00 2001 From: Justin Carmony Date: Tue, 31 Mar 2015 23:41:15 -0700 Subject: [PATCH 111/936] Packager status page & build validating against it. Summary: Creating a packager status page so React can validate a proper packager instance is running on 8081. See #257 for details on this bug. The biggest thing in this PR is I have it perform an exit 2 in the build script if the check fails. This will cause the build to fail, they can click on the error and see a nice message. Not sure if there is a way to throw a warning instead. Also, I broke the bash script into several lines, in the Xcode editor it looks fine but in the source code it looks less than ideal. We might want to break that out into it's own bash script that is called. Let me know if you want to do that. Closes https://github.com/facebook/react-native/pull/308 Github Author: Justin Carmony Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packager.js b/packager.js index 9503df6e..ad47d1d9 100644 --- a/packager.js +++ b/packager.js @@ -150,6 +150,17 @@ function getDevToolsLauncher(options) { }; } +// A status page so the React/project.pbxproj build script +// can verify that packager is running on 8081 and not +// another program / service. +function statusPageMiddleware(req, res, next) { + if (req.url === '/status') { + res.end('packager-status:running'); + } else { + next(); + } +} + function getAppMiddleware(options) { return ReactPackager.middleware({ projectRoots: options.projectRoots, @@ -168,6 +179,7 @@ function runServer( .use(loadRawBody) .use(openStackFrameInEditor) .use(getDevToolsLauncher(options)) + .use(statusPageMiddleware) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { From e0b19eff73fe6ba27a8ec0785d904ab699837558 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 1 Apr 2015 08:46:12 -0700 Subject: [PATCH 112/936] Updates from Wed 1 Apr - Packager status page & build validating against it. | Spencer Ahrens - Do not expose define references in require polyfill | Amjad Masad --- README.md | 28 +++++++++---------- packager.js | 12 ++++++++ .../haste/polyfills/require.js | 4 --- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 17c811c7..8f9f649b 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ React Native Packager -------------------- React Native Packager is a project similar in scope to browserify or -webpack; it provides a CommonJS-like module system, JavaScript +webpack, it provides a CommonJS-like module system, JavaScript compilation (ES6, Flow, JSX), bundling, and asset loading. The main difference is the Packager's focus on compilation and bundling speed. We aim for a sub-second edit-reload -cycle. Additionally, we don't want users -- with large code bases -- +cycles. Additionally, we don't want users -- with large code bases -- to wait more than a few seconds after starting the packager. The main deviation from the node module system is the support for our @@ -19,7 +19,7 @@ namely the node module format. We want to even go further, and let you choose your own packager and asset pipeline or even integrate into your existing infrastructure. -React Native users need not to understand how the packager works; +React Native users need not to understand how the packager work, however, this documentation might be useful for advanced users and people who want to fix bugs or add features to the packager (patches welcome!). @@ -45,10 +45,10 @@ Does the following in order: ### /path/to/moduleName.map * if the package has been previously generated via the `.bundle` - endpoint, then the source map will be generated from that package + endpoint then the source map will be generated from that package * if the package has not been previously asked for, this will go - through the same steps outlined in the `.bundle` endpoint, then - generate the source map + through the same steps outlined in the `.bundle` endpoint then + generate the source map. Note that source map generation currently assumes that the code has been compiled with jstransform, which preserves line and column @@ -66,15 +66,15 @@ Here are the current options the packager accepts: * `minify` boolean, defaults to false: whether to minify the bundle. * `runModule` boolean, defaults to true: whether to require your entry point module. So if you requested `moduleName`, this option will add - a `require('moduleName')` to the end of your bundle. + a `require('moduleName')` the end of your bundle. * `inlineSourceMap` boolean, defaults to false: whether to inline source maps. ### /debug -This is a page used for debugging; it has links to two pages: +This is a page used for debugging, it has links to two pages: -* Cached Packages: which shows you the packages that have already been +* Cached Packages: which shows you the packages that's been already generated and cached * Dependency Graph: is the in-memory graph of all the modules and their dependencies @@ -103,8 +103,8 @@ middleware. Takes the following options: packager * `polyfillModuleName` array: Paths to polyfills you want to be included at the start of the bundle -* `cacheVersion` string: Used in creating the cache file -* `resetCache` boolean, defaults to false: Whether to use the cache on +* `cacheVersion` string: used in creating the cache file +* `resetCache` boolean, defaults to false: whether to use the cache on disk * `transformModulePath` string: Path to the module used as a JavaScript transformer @@ -133,6 +133,6 @@ is informed by React Native needs. ### Why didn't you use webpack? -We love webpack; however, when we tried it on our codebase, it was slower -than our developers wanted it to be. You find can more discussion about -the subject [here](https://github.com/facebook/react-native/issues/5). +We love webpack, however, when we tried on our codebase it was slower +than our developers would like it to be. You find can more discussion about +the subject [here](https://github.com/facebook/react-native/issues/5) diff --git a/packager.js b/packager.js index 9503df6e..ad47d1d9 100644 --- a/packager.js +++ b/packager.js @@ -150,6 +150,17 @@ function getDevToolsLauncher(options) { }; } +// A status page so the React/project.pbxproj build script +// can verify that packager is running on 8081 and not +// another program / service. +function statusPageMiddleware(req, res, next) { + if (req.url === '/status') { + res.end('packager-status:running'); + } else { + next(); + } +} + function getAppMiddleware(options) { return ReactPackager.middleware({ projectRoots: options.projectRoots, @@ -168,6 +179,7 @@ function runServer( .use(loadRawBody) .use(openStackFrameInEditor) .use(getDevToolsLauncher(options)) + .use(statusPageMiddleware) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/haste/polyfills/require.js index 00955ba4..80705848 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/require.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -586,15 +586,11 @@ _register('module', 0); _register('exports', 0); - _register('define', define); _register('global', global); _register('require', require); _register('requireDynamic', require); _register('requireLazy', requireLazy); - define.amd = {}; - - global.define = define; global.require = require; global.requireDynamic = require; global.requireLazy = requireLazy; From bedd4029c86481af6f01162957ae62d8a15948e3 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 2 Apr 2015 06:32:34 -0700 Subject: [PATCH 113/936] [react-packager] Ignore dotfiles in file watching --- react-packager/src/FileWatcher/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index 6f451b48..9af96c14 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -64,7 +64,10 @@ FileWatcher.prototype.end = function() { function createWatcher(rootConfig) { return detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(rootConfig.dir, rootConfig.globs); + var watcher = new Watcher(rootConfig.dir, { + glob: rootConfig.globs, + dot: false, + }); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { From 461f169a9effef5f682920ff635ac0b528c0abba Mon Sep 17 00:00:00 2001 From: Steve Lacy Date: Thu, 2 Apr 2015 16:58:35 -0700 Subject: [PATCH 114/936] Update deps order - core modules first Summary: **packager/packager.js** - Update deps order - node core modules first The core deps do not get installed, and do not need to be after the dep check. Closes https://github.com/facebook/react-native/pull/224 Github Author: Steve Lacy Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packager.js b/packager.js index ad47d1d9..e2a32f2c 100644 --- a/packager.js +++ b/packager.js @@ -10,6 +10,8 @@ var fs = require('fs'); var path = require('path'); +var exec = require('child_process').exec; +var http = require('http'); if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { console.log( @@ -21,11 +23,9 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { process.exit(); } -var exec = require('child_process').exec; +var connect = require('connect'); var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); -var connect = require('connect'); -var http = require('http'); var launchEditor = require('./launchEditor.js'); var parseCommandLine = require('./parseCommandLine.js'); var webSocketProxy = require('./webSocketProxy.js'); From 98fc8336477fa43a17b5c201554ab18bb9aa66fd Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Fri, 3 Apr 2015 08:38:06 -0700 Subject: [PATCH 115/936] Updates from Fri 3 Apr - Update deps order - core modules first | Amjad Masad - [ReactNative] Workaround double cmd+r bug | Christopher Chedeau - [react-native] Nicer error for undefined or string tag names | Ben Alpert - [ReactNative] Fix script load from local files | Tadeu Zagallo - [react_native] JS files from D1961099: Format stack trace on native side | Alex Kotliarskyi - [ReactNative] Cleanup TabBar and its example | Christopher Chedeau - [ReactNative] Allow recover from debugger error | Tadeu Zagallo - [react-native] Update react to 0.13.1, jstransform alongside | Ben Alpert - Fixed tap-to-zoom in Groups photo viewer | Sumeet Vaidya - Fix hitTest for auto | Tadeu Zagallo - [ReactNative] Unfork RKRootView | Tadeu Zagallo - [react-packager] Ignore dotfiles in file watching | Amjad Masad --- packager.js | 6 +++--- react-packager/src/FileWatcher/index.js | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packager.js b/packager.js index ad47d1d9..e2a32f2c 100644 --- a/packager.js +++ b/packager.js @@ -10,6 +10,8 @@ var fs = require('fs'); var path = require('path'); +var exec = require('child_process').exec; +var http = require('http'); if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { console.log( @@ -21,11 +23,9 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { process.exit(); } -var exec = require('child_process').exec; +var connect = require('connect'); var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); -var connect = require('connect'); -var http = require('http'); var launchEditor = require('./launchEditor.js'); var parseCommandLine = require('./parseCommandLine.js'); var webSocketProxy = require('./webSocketProxy.js'); diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index 6f451b48..9af96c14 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -64,7 +64,10 @@ FileWatcher.prototype.end = function() { function createWatcher(rootConfig) { return detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(rootConfig.dir, rootConfig.globs); + var watcher = new Watcher(rootConfig.dir, { + glob: rootConfig.globs, + dot: false, + }); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { From d476461120b9bf5d98c1c4f8c2ded2cba3e66eaa Mon Sep 17 00:00:00 2001 From: Pilwon Huh Date: Fri, 3 Apr 2015 11:39:29 -0700 Subject: [PATCH 116/936] react-packager: Add ES6 import statement support to DependencyGraph. Summary: This PR teaches packager's `DependencyGraph` how to extract dependencies written with ES6 `import` statements. It fixes the issue where you are not able to write your app with ES6 `import` statements when your custom transformer (replacing the default [JSTransform](https://github.com/facebook/jstransform), for example, [babel](http://babeljs.io/)) already supports the ES6 `import` syntax. It will also be useful for [JSTransform](https://github.com/facebook/jstransform) later on once it implements `import` feature too. Closes https://github.com/facebook/react-native/pull/386 Github Author: Pilwon Huh Test Plan: runJestTests.sh --- .../__tests__/DependencyGraph-test.js | 2 +- .../haste/DependencyGraph/index.js | 8 +- .../__tests__/HasteDependencyResolver-test.js | 287 +++++++++++++++++- .../src/DependencyResolver/haste/index.js | 22 +- .../{requirePattern.js => replacePatterns.js} | 5 +- 5 files changed, 297 insertions(+), 27 deletions(-) rename react-packager/src/DependencyResolver/haste/{requirePattern.js => replacePatterns.js} (68%) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 5f6ec882..5bca3013 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -13,7 +13,7 @@ jest .dontMock('path') .dontMock('absolute-path') .dontMock('../docblock') - .dontMock('../../requirePattern') + .dontMock('../../replacePatterns') .setMock('../../../ModuleDescriptor', function(data) {return data;}); describe('DependencyGraph', function() { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 9ca430c4..554db134 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -12,7 +12,7 @@ var ModuleDescriptor = require('../../ModuleDescriptor'); var Promise = require('bluebird'); var fs = require('fs'); var docblock = require('./docblock'); -var requirePattern = require('../requirePattern'); +var replacePatterns = require('../replacePatterns'); var path = require('path'); var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); @@ -609,7 +609,11 @@ function extractRequires(code) { code .replace(blockCommentRe, '') .replace(lineCommentRe, '') - .replace(requirePattern, function(match, _, dep) { + .replace(replacePatterns.IMPORT_RE, function(match, pre, quot, dep, post) { + deps.push(dep); + return match; + }) + .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { deps.push(dep); }); diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 2a24f2d1..8620d488 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -10,7 +10,7 @@ jest.dontMock('../') .dontMock('q') - .dontMock('../requirePattern') + .dontMock('../replacePatterns') .setMock('../../ModuleDescriptor', function(data) {return data;}); var Promise = require('bluebird'); @@ -228,13 +228,148 @@ describe('HasteDependencyResolver', function() { var depGraph = depResolver._depGraph; var dependencies = ['x', 'y', 'z', 'a', 'b']; + + /*eslint-disable */ var code = [ + "import'x';", + "import 'x';", + "import 'x' ;", + "import Default from 'x';", + "import * as All from 'x';", + "import {} from 'x';", + "import { } from 'x';", + "import {Foo} from 'x';", + "import { Foo } from 'x';", + "import { Foo, } from 'x';", + "import {Foo as Bar} from 'x';", + "import { Foo as Bar } from 'x';", + "import { Foo as Bar, } from 'x';", + "import { Foo, Bar } from 'x';", + "import { Foo, Bar, } from 'x';", + "import { Foo as Bar, Baz } from 'x';", + "import { Foo as Bar, Baz, } from 'x';", + "import { Foo, Bar as Baz } from 'x';", + "import { Foo, Bar as Baz, } from 'x';", + "import { Foo as Bar, Baz as Qux } from 'x';", + "import { Foo as Bar, Baz as Qux, } from 'x';", + "import { Foo, Bar, Baz } from 'x';", + "import { Foo, Bar, Baz, } from 'x';", + "import { Foo as Bar, Baz, Qux } from 'x';", + "import { Foo as Bar, Baz, Qux, } from 'x';", + "import { Foo, Bar as Baz, Qux } from 'x';", + "import { Foo, Bar as Baz, Qux, } from 'x';", + "import { Foo, Bar, Baz as Qux } from 'x';", + "import { Foo, Bar, Baz as Qux, } from 'x';", + "import { Foo as Bar, Baz as Qux, Norf } from 'x';", + "import { Foo as Bar, Baz as Qux, Norf, } from 'x';", + "import { Foo as Bar, Baz, Qux as Norf } from 'x';", + "import { Foo as Bar, Baz, Qux as Norf, } from 'x';", + "import { Foo, Bar as Baz, Qux as Norf } from 'x';", + "import { Foo, Bar as Baz, Qux as Norf, } from 'x';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'x';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'x';", + "import Default, * as All from 'x';", + "import Default, { } from 'x';", + "import Default, { Foo } from 'x';", + "import Default, { Foo, } from 'x';", + "import Default, { Foo as Bar } from 'x';", + "import Default, { Foo as Bar, } from 'x';", + "import Default, { Foo, Bar } from 'x';", + "import Default, { Foo, Bar, } from 'x';", + "import Default, { Foo as Bar, Baz } from 'x';", + "import Default, { Foo as Bar, Baz, } from 'x';", + "import Default, { Foo, Bar as Baz } from 'x';", + "import Default, { Foo, Bar as Baz, } from 'x';", + "import Default, { Foo as Bar, Baz as Qux } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, } from 'x';", + "import Default, { Foo, Bar, Baz } from 'x';", + "import Default, { Foo, Bar, Baz, } from 'x';", + "import Default, { Foo as Bar, Baz, Qux } from 'x';", + "import Default, { Foo as Bar, Baz, Qux, } from 'x';", + "import Default, { Foo, Bar as Baz, Qux } from 'x';", + "import Default, { Foo, Bar as Baz, Qux, } from 'x';", + "import Default, { Foo, Bar, Baz as Qux } from 'x';", + "import Default, { Foo, Bar, Baz as Qux, } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, Norf } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'x';", + "import Default, { Foo as Bar, Baz, Qux as Norf } from 'x';", + "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'x';", + "import Default, { Foo, Bar as Baz, Qux as Norf } from 'x';", + "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'x';", + "import Default , { } from 'x';", + 'import "x";', + 'import Default from "x";', + 'import * as All from "x";', + 'import { } from "x";', + 'import { Foo } from "x";', + 'import { Foo, } from "x";', + 'import { Foo as Bar } from "x";', + 'import { Foo as Bar, } from "x";', + 'import { Foo, Bar } from "x";', + 'import { Foo, Bar, } from "x";', + 'import { Foo as Bar, Baz } from "x";', + 'import { Foo as Bar, Baz, } from "x";', + 'import { Foo, Bar as Baz } from "x";', + 'import { Foo, Bar as Baz, } from "x";', + 'import { Foo as Bar, Baz as Qux } from "x";', + 'import { Foo as Bar, Baz as Qux, } from "x";', + 'import { Foo, Bar, Baz } from "x";', + 'import { Foo, Bar, Baz, } from "x";', + 'import { Foo as Bar, Baz, Qux } from "x";', + 'import { Foo as Bar, Baz, Qux, } from "x";', + 'import { Foo, Bar as Baz, Qux } from "x";', + 'import { Foo, Bar as Baz, Qux, } from "x";', + 'import { Foo, Bar, Baz as Qux } from "x";', + 'import { Foo, Bar, Baz as Qux, } from "x";', + 'import { Foo as Bar, Baz as Qux, Norf } from "x";', + 'import { Foo as Bar, Baz as Qux, Norf, } from "x";', + 'import { Foo as Bar, Baz, Qux as Norf } from "x";', + 'import { Foo as Bar, Baz, Qux as Norf, } from "x";', + 'import { Foo, Bar as Baz, Qux as Norf } from "x";', + 'import { Foo, Bar as Baz, Qux as Norf, } from "x";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "x";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "x";', + 'import Default, * as All from "x";', + 'import Default, { } from "x";', + 'import Default, { Foo } from "x";', + 'import Default, { Foo, } from "x";', + 'import Default, { Foo as Bar } from "x";', + 'import Default, { Foo as Bar, } from "x";', + 'import Default, { Foo, Bar } from "x";', + 'import Default, { Foo, Bar, } from "x";', + 'import Default, { Foo as Bar, Baz } from "x";', + 'import Default, { Foo as Bar, Baz, } from "x";', + 'import Default, { Foo, Bar as Baz } from "x";', + 'import Default, { Foo, Bar as Baz, } from "x";', + 'import Default, { Foo as Bar, Baz as Qux } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, } from "x";', + 'import Default, { Foo, Bar, Baz } from "x";', + 'import Default, { Foo, Bar, Baz, } from "x";', + 'import Default, { Foo as Bar, Baz, Qux } from "x";', + 'import Default, { Foo as Bar, Baz, Qux, } from "x";', + 'import Default, { Foo, Bar as Baz, Qux } from "x";', + 'import Default, { Foo, Bar as Baz, Qux, } from "x";', + 'import Default, { Foo, Bar, Baz as Qux } from "x";', + 'import Default, { Foo, Bar, Baz as Qux, } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, Norf } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "x";', + 'import Default, { Foo as Bar, Baz, Qux as Norf } from "x";', + 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "x";', + 'import Default, { Foo, Bar as Baz, Qux as Norf } from "x";', + 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "x";', + 'import Default from "y";', + 'import * as All from \'z\';', 'require("x")', 'require("y")', - 'require( "z" )', + 'require( \'z\' )', 'require( "a")', 'require("b" )', ].join('\n'); + /*eslint-disable */ depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { if (toModuleName === 'x') { @@ -242,7 +377,7 @@ describe('HasteDependencyResolver', function() { id: 'changed' }; } else if (toModuleName === 'y') { - return { id: 'y' }; + return { id: 'Y' }; } return null; }); @@ -254,13 +389,145 @@ describe('HasteDependencyResolver', function() { }, code); expect(processedCode).toEqual([ - '__d(\'test module\',["changed","y"],function(global,' + - ' require, requireDynamic, requireLazy, module, exports) {' + - ' require(\'changed\')', - 'require(\'y\')', - 'require("z")', - 'require("a")', - 'require("b")});', + '__d(\'test module\',["changed","Y"],function(global,' + + ' require, requireDynamic, requireLazy, module, exports) { ' + + "import'x';", + "import 'changed';", + "import 'changed' ;", + "import Default from 'changed';", + "import * as All from 'changed';", + "import {} from 'changed';", + "import { } from 'changed';", + "import {Foo} from 'changed';", + "import { Foo } from 'changed';", + "import { Foo, } from 'changed';", + "import {Foo as Bar} from 'changed';", + "import { Foo as Bar } from 'changed';", + "import { Foo as Bar, } from 'changed';", + "import { Foo, Bar } from 'changed';", + "import { Foo, Bar, } from 'changed';", + "import { Foo as Bar, Baz } from 'changed';", + "import { Foo as Bar, Baz, } from 'changed';", + "import { Foo, Bar as Baz } from 'changed';", + "import { Foo, Bar as Baz, } from 'changed';", + "import { Foo as Bar, Baz as Qux } from 'changed';", + "import { Foo as Bar, Baz as Qux, } from 'changed';", + "import { Foo, Bar, Baz } from 'changed';", + "import { Foo, Bar, Baz, } from 'changed';", + "import { Foo as Bar, Baz, Qux } from 'changed';", + "import { Foo as Bar, Baz, Qux, } from 'changed';", + "import { Foo, Bar as Baz, Qux } from 'changed';", + "import { Foo, Bar as Baz, Qux, } from 'changed';", + "import { Foo, Bar, Baz as Qux } from 'changed';", + "import { Foo, Bar, Baz as Qux, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", + "import Default, * as All from 'changed';", + "import Default, { } from 'changed';", + "import Default, { Foo } from 'changed';", + "import Default, { Foo, } from 'changed';", + "import Default, { Foo as Bar } from 'changed';", + "import Default, { Foo as Bar, } from 'changed';", + "import Default, { Foo, Bar } from 'changed';", + "import Default, { Foo, Bar, } from 'changed';", + "import Default, { Foo as Bar, Baz } from 'changed';", + "import Default, { Foo as Bar, Baz, } from 'changed';", + "import Default, { Foo, Bar as Baz } from 'changed';", + "import Default, { Foo, Bar as Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz } from 'changed';", + "import Default, { Foo, Bar, Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", + "import Default , { } from 'changed';", + 'import "changed";', + 'import Default from "changed";', + 'import * as All from "changed";', + 'import { } from "changed";', + 'import { Foo } from "changed";', + 'import { Foo, } from "changed";', + 'import { Foo as Bar } from "changed";', + 'import { Foo as Bar, } from "changed";', + 'import { Foo, Bar } from "changed";', + 'import { Foo, Bar, } from "changed";', + 'import { Foo as Bar, Baz } from "changed";', + 'import { Foo as Bar, Baz, } from "changed";', + 'import { Foo, Bar as Baz } from "changed";', + 'import { Foo, Bar as Baz, } from "changed";', + 'import { Foo as Bar, Baz as Qux } from "changed";', + 'import { Foo as Bar, Baz as Qux, } from "changed";', + 'import { Foo, Bar, Baz } from "changed";', + 'import { Foo, Bar, Baz, } from "changed";', + 'import { Foo as Bar, Baz, Qux } from "changed";', + 'import { Foo as Bar, Baz, Qux, } from "changed";', + 'import { Foo, Bar as Baz, Qux } from "changed";', + 'import { Foo, Bar as Baz, Qux, } from "changed";', + 'import { Foo, Bar, Baz as Qux } from "changed";', + 'import { Foo, Bar, Baz as Qux, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', + 'import Default, * as All from "changed";', + 'import Default, { } from "changed";', + 'import Default, { Foo } from "changed";', + 'import Default, { Foo, } from "changed";', + 'import Default, { Foo as Bar } from "changed";', + 'import Default, { Foo as Bar, } from "changed";', + 'import Default, { Foo, Bar } from "changed";', + 'import Default, { Foo, Bar, } from "changed";', + 'import Default, { Foo as Bar, Baz } from "changed";', + 'import Default, { Foo as Bar, Baz, } from "changed";', + 'import Default, { Foo, Bar as Baz } from "changed";', + 'import Default, { Foo, Bar as Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz } from "changed";', + 'import Default, { Foo, Bar, Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', + 'import Default from "Y";', + 'import * as All from \'z\';', + 'require("changed")', + 'require("Y")', + 'require( \'z\' )', + 'require( "a")', + 'require("b" )});', ].join('\n')); }); }); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 941a687e..e5736623 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -10,7 +10,7 @@ var path = require('path'); var DependencyGraph = require('./DependencyGraph'); -var requirePattern = require('./requirePattern'); +var replacePatterns = require('./replacePatterns'); var ModuleDescriptor = require('../ModuleDescriptor'); var declareOpts = require('../../lib/declareOpts'); @@ -144,20 +144,20 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { } } - var relativizedCode = - code.replace(requirePattern, function(codeMatch, _, depName) { - var depId = resolvedDeps[depName]; - if (depId != null) { - return 'require(\'' + depId + '\')'; - } else { - return codeMatch.replace(/\s+/g, ''); - } - }); + var relativizeCode = function(codeMatch, pre, quot, depName, post) { + var depId = resolvedDeps[depName]; + if (depId) { + return pre + quot + depId + post; + } else { + return codeMatch; + } + }; return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) { return { '_moduleName_': module.id, - '_code_': relativizedCode, + '_code_': code.replace(replacePatterns.IMPORT_RE, relativizeCode) + .replace(replacePatterns.REQUIRE_RE, relativizeCode), '_deps_': JSON.stringify(resolvedDepsArr), }[key]; }); diff --git a/react-packager/src/DependencyResolver/haste/requirePattern.js b/react-packager/src/DependencyResolver/haste/replacePatterns.js similarity index 68% rename from react-packager/src/DependencyResolver/haste/requirePattern.js rename to react-packager/src/DependencyResolver/haste/replacePatterns.js index 26d807ff..cde2d873 100644 --- a/react-packager/src/DependencyResolver/haste/requirePattern.js +++ b/react-packager/src/DependencyResolver/haste/replacePatterns.js @@ -9,6 +9,5 @@ 'use strict'; -var REQUIRE_RE = /\brequire\s*?\(\s*?([\'"])([^"\']+)\1\s*?\)/g; - -module.exports = REQUIRE_RE; +exports.IMPORT_RE = /(\bimport\s+?(?:.+\s+?from\s+?)?)(['"])([^'"]+)(\2)/g; +exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; From e22b4e000b4365833833425c8ecf7861317aa1cd Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 3 Apr 2015 15:46:59 -0700 Subject: [PATCH 117/936] [react-packager] Don't cache rejected promise --- react-packager/src/JSTransformer/index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index fde8336e..abfae248 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -61,9 +61,7 @@ function Transformer(options) { projectRoots: options.projectRoots, }); - if (options.transformModulePath == null) { - this._failedToStart = Promise.reject(new Error('No transfrom module')); - } else { + if (options.transformModulePath != null) { this._workers = workerFarm( {autoStart: true, maxConcurrentCallsPerWorker: 1}, options.transformModulePath @@ -83,8 +81,8 @@ Transformer.prototype.invalidateFile = function(filePath) { }; Transformer.prototype.loadFileAndTransform = function(filePath) { - if (this._failedToStart) { - return this._failedToStart; + if (this._transform == null) { + return Promise.reject(new Error('No transfrom module')); } var transform = this._transform; From 31e0018c1c4305d01f1409c69becec1cadb653d0 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 3 Apr 2015 17:04:46 -0700 Subject: [PATCH 118/936] [react-packager] Deprecate global image namespace in favor of CommonJS resolution --- .../__tests__/DependencyGraph-test.js | 150 +++++++++++++++++- .../haste/DependencyGraph/index.js | 73 +++++---- .../src/DependencyResolver/haste/index.js | 2 +- 3 files changed, 193 insertions(+), 32 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 5bca3013..b6a978c6 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -64,7 +64,7 @@ describe('DependencyGraph', function() { }); }); - pit('should get dependencies', function() { + pit('should get dependencies with deprecated assets', function() { var root = '/root'; fs.__setMockFilesystem({ 'root': { @@ -83,7 +83,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], fileWatcher: fileWatcher, - assetRoots: ['/root/imgs'] + assetRoots_DEPRECATED: ['/root/imgs'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -98,6 +98,97 @@ describe('DependencyGraph', function() { }); }); + pit('should get dependencies with relative assets', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { + id: 'index', + altId: 'rootPackage/index', + path: '/root/index.js', + dependencies: ['./imgs/a.png'] + }, + { id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true + }, + ]); + }); + }); + + pit('Deprecated and relative assets can live together', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")', + 'require("image!a")', + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetRoots_DEPRECATED: ['/root/imgs'], + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { + id: 'index', + altId: 'rootPackage/index', + path: '/root/index.js', + dependencies: ['./imgs/a.png', 'image!a'] + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true + }, + ]); + }); + }); + pit('should get recursive dependencies', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -821,7 +912,7 @@ describe('DependencyGraph', function() { }); }); - pit('updates module dependencies on asset add', function() { + pit('updates module dependencies on deprecated asset add', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ 'root': { @@ -836,7 +927,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - assetRoots: [root], + assetRoots_DEPRECATED: [root], assetExts: ['png'], fileWatcher: fileWatcher }); @@ -870,6 +961,57 @@ describe('DependencyGraph', function() { }); }); + pit('updates module dependencies on relative asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./foo.png")' + ].join('\n'), + 'package.json': JSON.stringify({ + name: 'aPackage' + }), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetExts: ['png'], + fileWatcher: fileWatcher + }); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'] + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'] + }, + { id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + }, + ]); + }); + }); + }); + pit('runs changes through ignore filter', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 554db134..3348907f 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -37,7 +37,7 @@ var validateOpts = declareOpts({ type: 'object', required: true, }, - assetRoots: { + assetRoots_DEPRECATED: { type: 'array', default: [], }, @@ -51,7 +51,7 @@ function DependecyGraph(options) { var opts = validateOpts(options); this._roots = opts.roots; - this._assetRoots = opts.assetRoots; + this._assetRoots_DEPRECATED = opts.assetRoots_DEPRECATED; this._assetExts = opts.assetExts; this._ignoreFilePath = opts.ignoreFilePath; this._fileWatcher = options.fileWatcher; @@ -64,6 +64,10 @@ function DependecyGraph(options) { this._moduleById = Object.create(null); this._debugUpdateEvents = []; + this._moduleExtPattern = new RegExp( + '.' + ['js'].concat(this._assetExts).join('|') + '$' + ); + // Kick off the search process to precompute the dependency graph. this._init(); } @@ -75,7 +79,7 @@ DependecyGraph.prototype.load = function() { this._loading = Promise.all([ this._search(), - this._buildAssetMap(), + this._buildAssetMap_DEPRECATED(), ]); return this._loading; @@ -147,15 +151,15 @@ DependecyGraph.prototype.resolveDependency = function( fromModule, depModuleId ) { - if (this._assetMap != null) { - // Process asset requires. + if (this._assetMap_DEPRECATED != null) { var assetMatch = depModuleId.match(/^image!(.+)/); + // Process DEPRECATED global asset requires. if (assetMatch && assetMatch[1]) { - if (!this._assetMap[assetMatch[1]]) { + if (!this._assetMap_DEPRECATED[assetMatch[1]]) { debug('WARINING: Cannot find asset:', assetMatch[1]); return null; } - return this._assetMap[assetMatch[1]]; + return this._assetMap_DEPRECATED[assetMatch[1]]; } } @@ -218,7 +222,11 @@ DependecyGraph.prototype.resolveDependency = function( // fromModule.path: /x/y/z // modulePath: /x/y/a/b var dir = path.dirname(fromModule.path); - modulePath = withExtJs(path.join(dir, depModuleId)); + modulePath = path.join(dir, depModuleId); + + if (this._assetExts.indexOf(extname(modulePath)) === -1) { + modulePath = withExtJs(modulePath); + } dep = this._graph[modulePath]; @@ -287,7 +295,7 @@ DependecyGraph.prototype._search = function() { return false; } - return filePath.match(/\.js$/); + return filePath.match(self._moduleExtPattern); }); var processing = self._findAndProcessPackage(files, dir) @@ -370,11 +378,21 @@ DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { * Parse a module and update indices. */ DependecyGraph.prototype._processModule = function(modulePath) { + var moduleData = { path: path.resolve(modulePath) }; + var module; + + if (this._assetExts.indexOf(extname(modulePath)) > -1) { + moduleData.id = this._lookupName(modulePath); + moduleData.isAsset = true; + moduleData.dependencies = []; + module = Promise.resolve(new ModuleDescriptor(moduleData)); + this._updateGraphWithModule(module); + } + var self = this; return readFile(modulePath, 'utf8') .then(function(content) { var moduleDocBlock = docblock.parseAsObject(content); - var moduleData = { path: path.resolve(modulePath) }; if (moduleDocBlock.providesModule || moduleDocBlock.provides) { moduleData.id = moduleDocBlock.providesModule || moduleDocBlock.provides; @@ -387,7 +405,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { } moduleData.dependencies = extractRequires(content); - var module = new ModuleDescriptor(moduleData); + module = new ModuleDescriptor(moduleData); self._updateGraphWithModule(module); return module; }); @@ -497,8 +515,8 @@ DependecyGraph.prototype._processFileChange = function( this._debugUpdateEvents.push({event: eventType, path: filePath}); if (this._assetExts.indexOf(extname(filePath)) > -1) { - this._processAssetChange(eventType, absPath); - return; + this._processAssetChange_DEPRECATED(eventType, absPath); + // Fall through because new-style assets are actually modules. } var isPackage = path.basename(filePath) === 'package.json'; @@ -554,27 +572,28 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) { return null; }; -DependecyGraph.prototype._buildAssetMap = function() { - if (this._assetRoots == null || this._assetRoots.length === 0) { +DependecyGraph.prototype._buildAssetMap_DEPRECATED = function() { + if (this._assetRoots_DEPRECATED == null || + this._assetRoots_DEPRECATED.length === 0) { return Promise.resolve(); } - this._assetMap = Object.create(null); - return buildAssetMap( - this._assetRoots, - this._processAsset.bind(this) + this._assetMap_DEPRECATED = Object.create(null); + return buildAssetMap_DEPRECATED( + this._assetRoots_DEPRECATED, + this._processAsset_DEPRECATED.bind(this) ); }; -DependecyGraph.prototype._processAsset = function(file) { +DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { var ext = extname(file); if (this._assetExts.indexOf(ext) !== -1) { var name = assetName(file, ext); - if (this._assetMap[name] != null) { + if (this._assetMap_DEPRECATED[name] != null) { debug('Conflcting assets', name); } - this._assetMap[name] = new ModuleDescriptor({ + this._assetMap_DEPRECATED[name] = new ModuleDescriptor({ id: 'image!' + name, path: path.resolve(file), isAsset: true, @@ -583,18 +602,18 @@ DependecyGraph.prototype._processAsset = function(file) { } }; -DependecyGraph.prototype._processAssetChange = function(eventType, file) { - if (this._assetMap == null) { +DependecyGraph.prototype._processAssetChange_DEPRECATED = function(eventType, file) { + if (this._assetMap_DEPRECATED == null) { return; } var name = assetName(file, extname(file)); if (eventType === 'change' || eventType === 'delete') { - delete this._assetMap[name]; + delete this._assetMap_DEPRECATED[name]; } if (eventType === 'change' || eventType === 'add') { - this._processAsset(file); + this._processAsset_DEPRECATED(file); } }; @@ -673,7 +692,7 @@ function readAndStatDir(dir) { * Given a list of roots and list of extensions find all the files in * the directory with that extension and build a map of those assets. */ -function buildAssetMap(roots, processAsset) { +function buildAssetMap_DEPRECATED(roots, processAsset) { var queue = roots.slice(0); function search() { diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index e5736623..e700e5fd 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -61,7 +61,7 @@ function HasteDependencyResolver(options) { this._depGraph = new DependencyGraph({ roots: opts.projectRoots, - assetRoots: opts.assetRoots, + assetRoots_DEPRECATED: opts.assetRoots, ignoreFilePath: function(filepath) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); From 1364487d20e5c9a402b59630668439fc27e1fa7b Mon Sep 17 00:00:00 2001 From: Mike Wilcox Date: Sun, 5 Apr 2015 09:14:09 -0400 Subject: [PATCH 119/936] Fix Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f9f649b..f85d148d 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ middleware. Takes the following options: * `projectRoots` array (required): Is the roots where your JavaScript file will exist -* `blacklistRE` regexp: Is a patter to ignore certain paths from the +* `blacklistRE` regexp: Is a pattern to ignore certain paths from the packager * `polyfillModuleName` array: Paths to polyfills you want to be included at the start of the bundle From 2a25eec53e408df3f81f43fa19274f0f93aedee5 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 6 Apr 2015 08:38:56 -0700 Subject: [PATCH 120/936] Updates from Mon 6 Apr - [ReactNative] Revert D1965911 | Christopher Chedeau - [ReactNative] Remove experimental Portal only needed for android right now. | Spencer Ahrens - [ReactNative] rename Animation to AnimationExperimental with warning docs | Spencer Ahrens - navigator.getCurrentRoutes() | Eric Vicenti - Fixing jsdoc parsing of functions that are defined over multiple lines (Fixes #410) | Christopher Chedeau - Added constraint of child type to touchablewithoutfeedback | Christopher Chedeau - [react-packager] Deprecate global image namespace in favor of CommonJS resolution | Amjad Masad - [react-packager] Don't cache rejected promise | Amjad Masad - [ReactNative] Start Navigator gesture config, disable gesture in AdsManager | Eric Vicenti - [Flow] Clean react-native-github for Flow v0.8.0 | Gabe Levi - add maximumValue and minimumValue as valid attributes for native Slider | Christopher Chedeau - react-packager: Add ES6 import statement support to DependencyGraph. | Amjad Masad - Remove false annotation | Christopher Chedeau - [madman] prevent pulling the content down inconsistently when the keyboard shows up | Kevin Gozali - add @flow back to View.js | Basil Hosmer - [ReactNative] Turn of lint warning for constant conditions | Eric Vicenti - [UIExplorer] Fixed 'Push View Example' in NavigatorIOS example | Christopher Chedeau - SliderIOS.js comments - grammar correction | Christopher Chedeau --- README.md | 2 +- .../__tests__/DependencyGraph-test.js | 152 +++++++++- .../haste/DependencyGraph/index.js | 81 +++-- .../__tests__/HasteDependencyResolver-test.js | 287 +++++++++++++++++- .../src/DependencyResolver/haste/index.js | 24 +- .../{requirePattern.js => replacePatterns.js} | 5 +- react-packager/src/JSTransformer/index.js | 8 +- 7 files changed, 494 insertions(+), 65 deletions(-) rename react-packager/src/DependencyResolver/haste/{requirePattern.js => replacePatterns.js} (68%) diff --git a/README.md b/README.md index f85d148d..8f9f649b 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ middleware. Takes the following options: * `projectRoots` array (required): Is the roots where your JavaScript file will exist -* `blacklistRE` regexp: Is a pattern to ignore certain paths from the +* `blacklistRE` regexp: Is a patter to ignore certain paths from the packager * `polyfillModuleName` array: Paths to polyfills you want to be included at the start of the bundle diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 5f6ec882..b6a978c6 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -13,7 +13,7 @@ jest .dontMock('path') .dontMock('absolute-path') .dontMock('../docblock') - .dontMock('../../requirePattern') + .dontMock('../../replacePatterns') .setMock('../../../ModuleDescriptor', function(data) {return data;}); describe('DependencyGraph', function() { @@ -64,7 +64,7 @@ describe('DependencyGraph', function() { }); }); - pit('should get dependencies', function() { + pit('should get dependencies with deprecated assets', function() { var root = '/root'; fs.__setMockFilesystem({ 'root': { @@ -83,7 +83,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], fileWatcher: fileWatcher, - assetRoots: ['/root/imgs'] + assetRoots_DEPRECATED: ['/root/imgs'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -98,6 +98,97 @@ describe('DependencyGraph', function() { }); }); + pit('should get dependencies with relative assets', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { + id: 'index', + altId: 'rootPackage/index', + path: '/root/index.js', + dependencies: ['./imgs/a.png'] + }, + { id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true + }, + ]); + }); + }); + + pit('Deprecated and relative assets can live together', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")', + 'require("image!a")', + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetRoots_DEPRECATED: ['/root/imgs'], + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { + id: 'index', + altId: 'rootPackage/index', + path: '/root/index.js', + dependencies: ['./imgs/a.png', 'image!a'] + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true + }, + ]); + }); + }); + pit('should get recursive dependencies', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -821,7 +912,7 @@ describe('DependencyGraph', function() { }); }); - pit('updates module dependencies on asset add', function() { + pit('updates module dependencies on deprecated asset add', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ 'root': { @@ -836,7 +927,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - assetRoots: [root], + assetRoots_DEPRECATED: [root], assetExts: ['png'], fileWatcher: fileWatcher }); @@ -870,6 +961,57 @@ describe('DependencyGraph', function() { }); }); + pit('updates module dependencies on relative asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./foo.png")' + ].join('\n'), + 'package.json': JSON.stringify({ + name: 'aPackage' + }), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetExts: ['png'], + fileWatcher: fileWatcher + }); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'] + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'] + }, + { id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + }, + ]); + }); + }); + }); + pit('runs changes through ignore filter', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 9ca430c4..3348907f 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -12,7 +12,7 @@ var ModuleDescriptor = require('../../ModuleDescriptor'); var Promise = require('bluebird'); var fs = require('fs'); var docblock = require('./docblock'); -var requirePattern = require('../requirePattern'); +var replacePatterns = require('../replacePatterns'); var path = require('path'); var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); @@ -37,7 +37,7 @@ var validateOpts = declareOpts({ type: 'object', required: true, }, - assetRoots: { + assetRoots_DEPRECATED: { type: 'array', default: [], }, @@ -51,7 +51,7 @@ function DependecyGraph(options) { var opts = validateOpts(options); this._roots = opts.roots; - this._assetRoots = opts.assetRoots; + this._assetRoots_DEPRECATED = opts.assetRoots_DEPRECATED; this._assetExts = opts.assetExts; this._ignoreFilePath = opts.ignoreFilePath; this._fileWatcher = options.fileWatcher; @@ -64,6 +64,10 @@ function DependecyGraph(options) { this._moduleById = Object.create(null); this._debugUpdateEvents = []; + this._moduleExtPattern = new RegExp( + '.' + ['js'].concat(this._assetExts).join('|') + '$' + ); + // Kick off the search process to precompute the dependency graph. this._init(); } @@ -75,7 +79,7 @@ DependecyGraph.prototype.load = function() { this._loading = Promise.all([ this._search(), - this._buildAssetMap(), + this._buildAssetMap_DEPRECATED(), ]); return this._loading; @@ -147,15 +151,15 @@ DependecyGraph.prototype.resolveDependency = function( fromModule, depModuleId ) { - if (this._assetMap != null) { - // Process asset requires. + if (this._assetMap_DEPRECATED != null) { var assetMatch = depModuleId.match(/^image!(.+)/); + // Process DEPRECATED global asset requires. if (assetMatch && assetMatch[1]) { - if (!this._assetMap[assetMatch[1]]) { + if (!this._assetMap_DEPRECATED[assetMatch[1]]) { debug('WARINING: Cannot find asset:', assetMatch[1]); return null; } - return this._assetMap[assetMatch[1]]; + return this._assetMap_DEPRECATED[assetMatch[1]]; } } @@ -218,7 +222,11 @@ DependecyGraph.prototype.resolveDependency = function( // fromModule.path: /x/y/z // modulePath: /x/y/a/b var dir = path.dirname(fromModule.path); - modulePath = withExtJs(path.join(dir, depModuleId)); + modulePath = path.join(dir, depModuleId); + + if (this._assetExts.indexOf(extname(modulePath)) === -1) { + modulePath = withExtJs(modulePath); + } dep = this._graph[modulePath]; @@ -287,7 +295,7 @@ DependecyGraph.prototype._search = function() { return false; } - return filePath.match(/\.js$/); + return filePath.match(self._moduleExtPattern); }); var processing = self._findAndProcessPackage(files, dir) @@ -370,11 +378,21 @@ DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { * Parse a module and update indices. */ DependecyGraph.prototype._processModule = function(modulePath) { + var moduleData = { path: path.resolve(modulePath) }; + var module; + + if (this._assetExts.indexOf(extname(modulePath)) > -1) { + moduleData.id = this._lookupName(modulePath); + moduleData.isAsset = true; + moduleData.dependencies = []; + module = Promise.resolve(new ModuleDescriptor(moduleData)); + this._updateGraphWithModule(module); + } + var self = this; return readFile(modulePath, 'utf8') .then(function(content) { var moduleDocBlock = docblock.parseAsObject(content); - var moduleData = { path: path.resolve(modulePath) }; if (moduleDocBlock.providesModule || moduleDocBlock.provides) { moduleData.id = moduleDocBlock.providesModule || moduleDocBlock.provides; @@ -387,7 +405,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { } moduleData.dependencies = extractRequires(content); - var module = new ModuleDescriptor(moduleData); + module = new ModuleDescriptor(moduleData); self._updateGraphWithModule(module); return module; }); @@ -497,8 +515,8 @@ DependecyGraph.prototype._processFileChange = function( this._debugUpdateEvents.push({event: eventType, path: filePath}); if (this._assetExts.indexOf(extname(filePath)) > -1) { - this._processAssetChange(eventType, absPath); - return; + this._processAssetChange_DEPRECATED(eventType, absPath); + // Fall through because new-style assets are actually modules. } var isPackage = path.basename(filePath) === 'package.json'; @@ -554,27 +572,28 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) { return null; }; -DependecyGraph.prototype._buildAssetMap = function() { - if (this._assetRoots == null || this._assetRoots.length === 0) { +DependecyGraph.prototype._buildAssetMap_DEPRECATED = function() { + if (this._assetRoots_DEPRECATED == null || + this._assetRoots_DEPRECATED.length === 0) { return Promise.resolve(); } - this._assetMap = Object.create(null); - return buildAssetMap( - this._assetRoots, - this._processAsset.bind(this) + this._assetMap_DEPRECATED = Object.create(null); + return buildAssetMap_DEPRECATED( + this._assetRoots_DEPRECATED, + this._processAsset_DEPRECATED.bind(this) ); }; -DependecyGraph.prototype._processAsset = function(file) { +DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { var ext = extname(file); if (this._assetExts.indexOf(ext) !== -1) { var name = assetName(file, ext); - if (this._assetMap[name] != null) { + if (this._assetMap_DEPRECATED[name] != null) { debug('Conflcting assets', name); } - this._assetMap[name] = new ModuleDescriptor({ + this._assetMap_DEPRECATED[name] = new ModuleDescriptor({ id: 'image!' + name, path: path.resolve(file), isAsset: true, @@ -583,18 +602,18 @@ DependecyGraph.prototype._processAsset = function(file) { } }; -DependecyGraph.prototype._processAssetChange = function(eventType, file) { - if (this._assetMap == null) { +DependecyGraph.prototype._processAssetChange_DEPRECATED = function(eventType, file) { + if (this._assetMap_DEPRECATED == null) { return; } var name = assetName(file, extname(file)); if (eventType === 'change' || eventType === 'delete') { - delete this._assetMap[name]; + delete this._assetMap_DEPRECATED[name]; } if (eventType === 'change' || eventType === 'add') { - this._processAsset(file); + this._processAsset_DEPRECATED(file); } }; @@ -609,7 +628,11 @@ function extractRequires(code) { code .replace(blockCommentRe, '') .replace(lineCommentRe, '') - .replace(requirePattern, function(match, _, dep) { + .replace(replacePatterns.IMPORT_RE, function(match, pre, quot, dep, post) { + deps.push(dep); + return match; + }) + .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { deps.push(dep); }); @@ -669,7 +692,7 @@ function readAndStatDir(dir) { * Given a list of roots and list of extensions find all the files in * the directory with that extension and build a map of those assets. */ -function buildAssetMap(roots, processAsset) { +function buildAssetMap_DEPRECATED(roots, processAsset) { var queue = roots.slice(0); function search() { diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 2a24f2d1..8620d488 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -10,7 +10,7 @@ jest.dontMock('../') .dontMock('q') - .dontMock('../requirePattern') + .dontMock('../replacePatterns') .setMock('../../ModuleDescriptor', function(data) {return data;}); var Promise = require('bluebird'); @@ -228,13 +228,148 @@ describe('HasteDependencyResolver', function() { var depGraph = depResolver._depGraph; var dependencies = ['x', 'y', 'z', 'a', 'b']; + + /*eslint-disable */ var code = [ + "import'x';", + "import 'x';", + "import 'x' ;", + "import Default from 'x';", + "import * as All from 'x';", + "import {} from 'x';", + "import { } from 'x';", + "import {Foo} from 'x';", + "import { Foo } from 'x';", + "import { Foo, } from 'x';", + "import {Foo as Bar} from 'x';", + "import { Foo as Bar } from 'x';", + "import { Foo as Bar, } from 'x';", + "import { Foo, Bar } from 'x';", + "import { Foo, Bar, } from 'x';", + "import { Foo as Bar, Baz } from 'x';", + "import { Foo as Bar, Baz, } from 'x';", + "import { Foo, Bar as Baz } from 'x';", + "import { Foo, Bar as Baz, } from 'x';", + "import { Foo as Bar, Baz as Qux } from 'x';", + "import { Foo as Bar, Baz as Qux, } from 'x';", + "import { Foo, Bar, Baz } from 'x';", + "import { Foo, Bar, Baz, } from 'x';", + "import { Foo as Bar, Baz, Qux } from 'x';", + "import { Foo as Bar, Baz, Qux, } from 'x';", + "import { Foo, Bar as Baz, Qux } from 'x';", + "import { Foo, Bar as Baz, Qux, } from 'x';", + "import { Foo, Bar, Baz as Qux } from 'x';", + "import { Foo, Bar, Baz as Qux, } from 'x';", + "import { Foo as Bar, Baz as Qux, Norf } from 'x';", + "import { Foo as Bar, Baz as Qux, Norf, } from 'x';", + "import { Foo as Bar, Baz, Qux as Norf } from 'x';", + "import { Foo as Bar, Baz, Qux as Norf, } from 'x';", + "import { Foo, Bar as Baz, Qux as Norf } from 'x';", + "import { Foo, Bar as Baz, Qux as Norf, } from 'x';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'x';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'x';", + "import Default, * as All from 'x';", + "import Default, { } from 'x';", + "import Default, { Foo } from 'x';", + "import Default, { Foo, } from 'x';", + "import Default, { Foo as Bar } from 'x';", + "import Default, { Foo as Bar, } from 'x';", + "import Default, { Foo, Bar } from 'x';", + "import Default, { Foo, Bar, } from 'x';", + "import Default, { Foo as Bar, Baz } from 'x';", + "import Default, { Foo as Bar, Baz, } from 'x';", + "import Default, { Foo, Bar as Baz } from 'x';", + "import Default, { Foo, Bar as Baz, } from 'x';", + "import Default, { Foo as Bar, Baz as Qux } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, } from 'x';", + "import Default, { Foo, Bar, Baz } from 'x';", + "import Default, { Foo, Bar, Baz, } from 'x';", + "import Default, { Foo as Bar, Baz, Qux } from 'x';", + "import Default, { Foo as Bar, Baz, Qux, } from 'x';", + "import Default, { Foo, Bar as Baz, Qux } from 'x';", + "import Default, { Foo, Bar as Baz, Qux, } from 'x';", + "import Default, { Foo, Bar, Baz as Qux } from 'x';", + "import Default, { Foo, Bar, Baz as Qux, } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, Norf } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'x';", + "import Default, { Foo as Bar, Baz, Qux as Norf } from 'x';", + "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'x';", + "import Default, { Foo, Bar as Baz, Qux as Norf } from 'x';", + "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'x';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'x';", + "import Default , { } from 'x';", + 'import "x";', + 'import Default from "x";', + 'import * as All from "x";', + 'import { } from "x";', + 'import { Foo } from "x";', + 'import { Foo, } from "x";', + 'import { Foo as Bar } from "x";', + 'import { Foo as Bar, } from "x";', + 'import { Foo, Bar } from "x";', + 'import { Foo, Bar, } from "x";', + 'import { Foo as Bar, Baz } from "x";', + 'import { Foo as Bar, Baz, } from "x";', + 'import { Foo, Bar as Baz } from "x";', + 'import { Foo, Bar as Baz, } from "x";', + 'import { Foo as Bar, Baz as Qux } from "x";', + 'import { Foo as Bar, Baz as Qux, } from "x";', + 'import { Foo, Bar, Baz } from "x";', + 'import { Foo, Bar, Baz, } from "x";', + 'import { Foo as Bar, Baz, Qux } from "x";', + 'import { Foo as Bar, Baz, Qux, } from "x";', + 'import { Foo, Bar as Baz, Qux } from "x";', + 'import { Foo, Bar as Baz, Qux, } from "x";', + 'import { Foo, Bar, Baz as Qux } from "x";', + 'import { Foo, Bar, Baz as Qux, } from "x";', + 'import { Foo as Bar, Baz as Qux, Norf } from "x";', + 'import { Foo as Bar, Baz as Qux, Norf, } from "x";', + 'import { Foo as Bar, Baz, Qux as Norf } from "x";', + 'import { Foo as Bar, Baz, Qux as Norf, } from "x";', + 'import { Foo, Bar as Baz, Qux as Norf } from "x";', + 'import { Foo, Bar as Baz, Qux as Norf, } from "x";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "x";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "x";', + 'import Default, * as All from "x";', + 'import Default, { } from "x";', + 'import Default, { Foo } from "x";', + 'import Default, { Foo, } from "x";', + 'import Default, { Foo as Bar } from "x";', + 'import Default, { Foo as Bar, } from "x";', + 'import Default, { Foo, Bar } from "x";', + 'import Default, { Foo, Bar, } from "x";', + 'import Default, { Foo as Bar, Baz } from "x";', + 'import Default, { Foo as Bar, Baz, } from "x";', + 'import Default, { Foo, Bar as Baz } from "x";', + 'import Default, { Foo, Bar as Baz, } from "x";', + 'import Default, { Foo as Bar, Baz as Qux } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, } from "x";', + 'import Default, { Foo, Bar, Baz } from "x";', + 'import Default, { Foo, Bar, Baz, } from "x";', + 'import Default, { Foo as Bar, Baz, Qux } from "x";', + 'import Default, { Foo as Bar, Baz, Qux, } from "x";', + 'import Default, { Foo, Bar as Baz, Qux } from "x";', + 'import Default, { Foo, Bar as Baz, Qux, } from "x";', + 'import Default, { Foo, Bar, Baz as Qux } from "x";', + 'import Default, { Foo, Bar, Baz as Qux, } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, Norf } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "x";', + 'import Default, { Foo as Bar, Baz, Qux as Norf } from "x";', + 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "x";', + 'import Default, { Foo, Bar as Baz, Qux as Norf } from "x";', + 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "x";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "x";', + 'import Default from "y";', + 'import * as All from \'z\';', 'require("x")', 'require("y")', - 'require( "z" )', + 'require( \'z\' )', 'require( "a")', 'require("b" )', ].join('\n'); + /*eslint-disable */ depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { if (toModuleName === 'x') { @@ -242,7 +377,7 @@ describe('HasteDependencyResolver', function() { id: 'changed' }; } else if (toModuleName === 'y') { - return { id: 'y' }; + return { id: 'Y' }; } return null; }); @@ -254,13 +389,145 @@ describe('HasteDependencyResolver', function() { }, code); expect(processedCode).toEqual([ - '__d(\'test module\',["changed","y"],function(global,' + - ' require, requireDynamic, requireLazy, module, exports) {' + - ' require(\'changed\')', - 'require(\'y\')', - 'require("z")', - 'require("a")', - 'require("b")});', + '__d(\'test module\',["changed","Y"],function(global,' + + ' require, requireDynamic, requireLazy, module, exports) { ' + + "import'x';", + "import 'changed';", + "import 'changed' ;", + "import Default from 'changed';", + "import * as All from 'changed';", + "import {} from 'changed';", + "import { } from 'changed';", + "import {Foo} from 'changed';", + "import { Foo } from 'changed';", + "import { Foo, } from 'changed';", + "import {Foo as Bar} from 'changed';", + "import { Foo as Bar } from 'changed';", + "import { Foo as Bar, } from 'changed';", + "import { Foo, Bar } from 'changed';", + "import { Foo, Bar, } from 'changed';", + "import { Foo as Bar, Baz } from 'changed';", + "import { Foo as Bar, Baz, } from 'changed';", + "import { Foo, Bar as Baz } from 'changed';", + "import { Foo, Bar as Baz, } from 'changed';", + "import { Foo as Bar, Baz as Qux } from 'changed';", + "import { Foo as Bar, Baz as Qux, } from 'changed';", + "import { Foo, Bar, Baz } from 'changed';", + "import { Foo, Bar, Baz, } from 'changed';", + "import { Foo as Bar, Baz, Qux } from 'changed';", + "import { Foo as Bar, Baz, Qux, } from 'changed';", + "import { Foo, Bar as Baz, Qux } from 'changed';", + "import { Foo, Bar as Baz, Qux, } from 'changed';", + "import { Foo, Bar, Baz as Qux } from 'changed';", + "import { Foo, Bar, Baz as Qux, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", + "import Default, * as All from 'changed';", + "import Default, { } from 'changed';", + "import Default, { Foo } from 'changed';", + "import Default, { Foo, } from 'changed';", + "import Default, { Foo as Bar } from 'changed';", + "import Default, { Foo as Bar, } from 'changed';", + "import Default, { Foo, Bar } from 'changed';", + "import Default, { Foo, Bar, } from 'changed';", + "import Default, { Foo as Bar, Baz } from 'changed';", + "import Default, { Foo as Bar, Baz, } from 'changed';", + "import Default, { Foo, Bar as Baz } from 'changed';", + "import Default, { Foo, Bar as Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz } from 'changed';", + "import Default, { Foo, Bar, Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", + "import Default , { } from 'changed';", + 'import "changed";', + 'import Default from "changed";', + 'import * as All from "changed";', + 'import { } from "changed";', + 'import { Foo } from "changed";', + 'import { Foo, } from "changed";', + 'import { Foo as Bar } from "changed";', + 'import { Foo as Bar, } from "changed";', + 'import { Foo, Bar } from "changed";', + 'import { Foo, Bar, } from "changed";', + 'import { Foo as Bar, Baz } from "changed";', + 'import { Foo as Bar, Baz, } from "changed";', + 'import { Foo, Bar as Baz } from "changed";', + 'import { Foo, Bar as Baz, } from "changed";', + 'import { Foo as Bar, Baz as Qux } from "changed";', + 'import { Foo as Bar, Baz as Qux, } from "changed";', + 'import { Foo, Bar, Baz } from "changed";', + 'import { Foo, Bar, Baz, } from "changed";', + 'import { Foo as Bar, Baz, Qux } from "changed";', + 'import { Foo as Bar, Baz, Qux, } from "changed";', + 'import { Foo, Bar as Baz, Qux } from "changed";', + 'import { Foo, Bar as Baz, Qux, } from "changed";', + 'import { Foo, Bar, Baz as Qux } from "changed";', + 'import { Foo, Bar, Baz as Qux, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', + 'import Default, * as All from "changed";', + 'import Default, { } from "changed";', + 'import Default, { Foo } from "changed";', + 'import Default, { Foo, } from "changed";', + 'import Default, { Foo as Bar } from "changed";', + 'import Default, { Foo as Bar, } from "changed";', + 'import Default, { Foo, Bar } from "changed";', + 'import Default, { Foo, Bar, } from "changed";', + 'import Default, { Foo as Bar, Baz } from "changed";', + 'import Default, { Foo as Bar, Baz, } from "changed";', + 'import Default, { Foo, Bar as Baz } from "changed";', + 'import Default, { Foo, Bar as Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz } from "changed";', + 'import Default, { Foo, Bar, Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', + 'import Default from "Y";', + 'import * as All from \'z\';', + 'require("changed")', + 'require("Y")', + 'require( \'z\' )', + 'require( "a")', + 'require("b" )});', ].join('\n')); }); }); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 941a687e..e700e5fd 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -10,7 +10,7 @@ var path = require('path'); var DependencyGraph = require('./DependencyGraph'); -var requirePattern = require('./requirePattern'); +var replacePatterns = require('./replacePatterns'); var ModuleDescriptor = require('../ModuleDescriptor'); var declareOpts = require('../../lib/declareOpts'); @@ -61,7 +61,7 @@ function HasteDependencyResolver(options) { this._depGraph = new DependencyGraph({ roots: opts.projectRoots, - assetRoots: opts.assetRoots, + assetRoots_DEPRECATED: opts.assetRoots, ignoreFilePath: function(filepath) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); @@ -144,20 +144,20 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { } } - var relativizedCode = - code.replace(requirePattern, function(codeMatch, _, depName) { - var depId = resolvedDeps[depName]; - if (depId != null) { - return 'require(\'' + depId + '\')'; - } else { - return codeMatch.replace(/\s+/g, ''); - } - }); + var relativizeCode = function(codeMatch, pre, quot, depName, post) { + var depId = resolvedDeps[depName]; + if (depId) { + return pre + quot + depId + post; + } else { + return codeMatch; + } + }; return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) { return { '_moduleName_': module.id, - '_code_': relativizedCode, + '_code_': code.replace(replacePatterns.IMPORT_RE, relativizeCode) + .replace(replacePatterns.REQUIRE_RE, relativizeCode), '_deps_': JSON.stringify(resolvedDepsArr), }[key]; }); diff --git a/react-packager/src/DependencyResolver/haste/requirePattern.js b/react-packager/src/DependencyResolver/haste/replacePatterns.js similarity index 68% rename from react-packager/src/DependencyResolver/haste/requirePattern.js rename to react-packager/src/DependencyResolver/haste/replacePatterns.js index 26d807ff..cde2d873 100644 --- a/react-packager/src/DependencyResolver/haste/requirePattern.js +++ b/react-packager/src/DependencyResolver/haste/replacePatterns.js @@ -9,6 +9,5 @@ 'use strict'; -var REQUIRE_RE = /\brequire\s*?\(\s*?([\'"])([^"\']+)\1\s*?\)/g; - -module.exports = REQUIRE_RE; +exports.IMPORT_RE = /(\bimport\s+?(?:.+\s+?from\s+?)?)(['"])([^'"]+)(\2)/g; +exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index fde8336e..abfae248 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -61,9 +61,7 @@ function Transformer(options) { projectRoots: options.projectRoots, }); - if (options.transformModulePath == null) { - this._failedToStart = Promise.reject(new Error('No transfrom module')); - } else { + if (options.transformModulePath != null) { this._workers = workerFarm( {autoStart: true, maxConcurrentCallsPerWorker: 1}, options.transformModulePath @@ -83,8 +81,8 @@ Transformer.prototype.invalidateFile = function(filePath) { }; Transformer.prototype.loadFileAndTransform = function(filePath) { - if (this._failedToStart) { - return this._failedToStart; + if (this._transform == null) { + return Promise.reject(new Error('No transfrom module')); } var transform = this._transform; From 5cbbc10e4f47b82c4d5f580a1265cfe183ad35f0 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 7 Apr 2015 15:52:52 -0700 Subject: [PATCH 121/936] [ReactNative] Better error message for EADDRINUSE --- packager.js | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/packager.js b/packager.js index e2a32f2c..55004b7c 100644 --- a/packager.js +++ b/packager.js @@ -23,6 +23,7 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { process.exit(); } +var chalk = require('chalk'); var connect = require('connect'); var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); @@ -88,13 +89,35 @@ console.log('\n' + ' ===============================================================\n' ); -console.log('Looking for JS files in\n ', options.projectRoots.join('\n ')); +console.log( + 'Looking for JS files in\n ', + chalk.dim(options.projectRoots.join('\n ')), + '\n' +); process.on('uncaughtException', function(e) { - console.error(e); - console.error(e.stack); - console.error('\n >>> ERROR: could not create packager - please shut down ' + - 'any existing instances that are already running.\n\n'); + if (e.code === 'EADDRINUSE') { + console.log( + chalk.bgRed.bold(' ERROR '), + chalk.red('Packager can\'t listen on port', chalk.bold(options.port)) + ); + console.log('Most likely another process is already using this port'); + console.log('Run the following command to find out which process:'); + console.log('\n ', chalk.bold('lsof -n -i4TCP:' + options.port), '\n'); + console.log('You can either shut down the other process:'); + console.log('\n ', chalk.bold('kill -9 '), '\n'); + console.log('or run packager on different port.'); + } else { + console.log(chalk.bgRed.bold(' ERROR '), chalk.red(e.message)); + var errorAttributes = JSON.stringify(e); + if (errorAttributes !== '{}') { + console.error(chalk.red(errorAttributes)); + } + console.error(chalk.red(e.stack)); + } + console.log('\nSee', chalk.underline('http://facebook.github.io/react-native/docs/troubleshooting.html')); + console.log('for common problems and solutions.'); + process.exit(1); }); var server = runServer(options, function() { @@ -151,13 +174,13 @@ function getDevToolsLauncher(options) { } // A status page so the React/project.pbxproj build script -// can verify that packager is running on 8081 and not +// can verify that packager is running on 8081 and not // another program / service. function statusPageMiddleware(req, res, next) { if (req.url === '/status') { res.end('packager-status:running'); } else { - next(); + next(); } } From 0c6c300c0d75850d72bca4f8e2455dcdc26a0f9f Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Tue, 7 Apr 2015 18:26:09 -0700 Subject: [PATCH 122/936] Updates from Tue 7 Apr - [AdsManager] Correct back button functionality | Eric Vicenti - [ReactNative] Replace Backstack with BackAndroid | Eric Vicenti - [ReactNative] Better error message for EADDRINUSE | Alex Kotliarskyi - [ReactNative] npm install --save chalk | Alex Kotliarskyi - Removed redundant views and shadow views | Nick Lockwood - [ReactNative] Fix variable shadowing in RCTText | Tadeu Zagallo - [react-packager] check in image-size module | Amjad Masad - Refactored RCTLog and added facility to prepend extra data to the log message | Nick Lockwood - [ReactNative] Fix crash on image download | Tadeu Zagallo - [React Native] #WIP Modify RCTShadowText measure function to honor maxNumberOfLines property | Alex Akers - Add promise support to AsyncStorage | Spencer Ahrens - [ReactNative] Revert high-level Subscribable | Eric Vicenti - [ReactNative] wrong deprecated prop check in RCTConvert | Kevin Gozali - [ReactNative][MAdMan] Add type for image source, flowify AdsManagerObjectiveTypes | Philipp von Weitershausen --- packager.js | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/packager.js b/packager.js index e2a32f2c..55004b7c 100644 --- a/packager.js +++ b/packager.js @@ -23,6 +23,7 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { process.exit(); } +var chalk = require('chalk'); var connect = require('connect'); var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); @@ -88,13 +89,35 @@ console.log('\n' + ' ===============================================================\n' ); -console.log('Looking for JS files in\n ', options.projectRoots.join('\n ')); +console.log( + 'Looking for JS files in\n ', + chalk.dim(options.projectRoots.join('\n ')), + '\n' +); process.on('uncaughtException', function(e) { - console.error(e); - console.error(e.stack); - console.error('\n >>> ERROR: could not create packager - please shut down ' + - 'any existing instances that are already running.\n\n'); + if (e.code === 'EADDRINUSE') { + console.log( + chalk.bgRed.bold(' ERROR '), + chalk.red('Packager can\'t listen on port', chalk.bold(options.port)) + ); + console.log('Most likely another process is already using this port'); + console.log('Run the following command to find out which process:'); + console.log('\n ', chalk.bold('lsof -n -i4TCP:' + options.port), '\n'); + console.log('You can either shut down the other process:'); + console.log('\n ', chalk.bold('kill -9 '), '\n'); + console.log('or run packager on different port.'); + } else { + console.log(chalk.bgRed.bold(' ERROR '), chalk.red(e.message)); + var errorAttributes = JSON.stringify(e); + if (errorAttributes !== '{}') { + console.error(chalk.red(errorAttributes)); + } + console.error(chalk.red(e.stack)); + } + console.log('\nSee', chalk.underline('http://facebook.github.io/react-native/docs/troubleshooting.html')); + console.log('for common problems and solutions.'); + process.exit(1); }); var server = runServer(options, function() { @@ -151,13 +174,13 @@ function getDevToolsLauncher(options) { } // A status page so the React/project.pbxproj build script -// can verify that packager is running on 8081 and not +// can verify that packager is running on 8081 and not // another program / service. function statusPageMiddleware(req, res, next) { if (req.url === '/status') { res.end('packager-status:running'); } else { - next(); + next(); } } From 061de15c1c205d51acab3ff39f45de42b05116c7 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Tue, 7 Apr 2015 21:50:24 -0700 Subject: [PATCH 123/936] [ReactNative] Do flow check when running packager --- getFlowTypeCheckMiddleware.js | 86 ++++++++++++++++++++++++++++++ packager.js | 6 +++ react-packager/src/Server/index.js | 6 +++ 3 files changed, 98 insertions(+) create mode 100644 getFlowTypeCheckMiddleware.js diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js new file mode 100644 index 00000000..e0f98bde --- /dev/null +++ b/getFlowTypeCheckMiddleware.js @@ -0,0 +1,86 @@ +/** + * 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 exec = require('child_process').exec; + +function getFlowTypeCheckMiddleware(options) { + return function(req, res, next) { + if (options.skipflow) { + return next(); + } + if (options.flowroot || options.projectRoots.length === 1) { + var flowroot = options.flowroot || options.projectRoots[0]; + } else { + console.warn('flow: No suitable root'); + return next(); + } + exec('command -v flow >/dev/null 2>&1', function(error, stdout) { + if (error) { + console.warn('flow: Skipping because not installed. Install with ' + + '`brew install flow`.'); + return next(); + } else { + return doFlowTypecheck(res, flowroot, next); + } + }); + }; +} + +function doFlowTypecheck(res, flowroot, next) { + var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; + var start = Date.now(); + console.log('flow: Running static typechecks.'); + exec(flowCmd, function(flowError, stdout) { + if (!flowError) { + console.log('flow: Typechecks passed (' + (Date.now() - start) + 'ms).'); + return next(); + } else { + try { + var flowResponse = JSON.parse(stdout); + var errors = []; + var errorNum = 1; + flowResponse.errors.forEach(function(err) { + // flow errors are paired across callsites, so we indent and prefix to + // group them + var indent = ''; + err.message.forEach(function(msg) { + errors.push({ + description: indent + 'E' + errorNum + ': ' + msg.descr, + filename: msg.path, + lineNumber: msg.line, + column: msg.start, + }); + indent = ' '; + }); + errorNum++; + }); + var message = 'Flow found type errors. If you think these are wrong, ' + + 'make sure flow is up to date, or disable with --skipflow.'; + } catch (e) { + var message = + 'Flow failed to provide parseable output:\n\n`' + stdout + '`'; + console.error(message, '\nException: `', e, '`\n\n'); + } + var error = { + status: 500, + message: message, + type: 'FlowError', + errors: errors, + }; + console.error('flow: Error running command `' + flowCmd + '`:\n', error); + res.writeHead(error.status, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + res.end(JSON.stringify(error)); + } + }); +} + +module.exports = getFlowTypeCheckMiddleware; diff --git a/packager.js b/packager.js index 55004b7c..a67b723d 100644 --- a/packager.js +++ b/packager.js @@ -13,6 +13,8 @@ var path = require('path'); var exec = require('child_process').exec; var http = require('http'); +var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware'); + if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { console.log( '\n' + @@ -40,6 +42,9 @@ var options = parseCommandLine([{ }, { command: 'assetRoots', description: 'specify the root directories of app assets' +}, { + command: 'skipflow', + description: 'Disable flow checks' }]); if (options.projectRoots) { @@ -203,6 +208,7 @@ function runServer( .use(openStackFrameInEditor) .use(getDevToolsLauncher(options)) .use(statusPageMiddleware) + .use(getFlowTypeCheckMiddleware(options)) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 65507581..617359bf 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -320,6 +320,12 @@ function handleError(res, error) { }); if (error.type === 'TransformError' || error.type === 'NotFoundError') { + error.errors = [{ + description: error.description, + filename: error.filename, + lineNumber: error.lineNumber, + }]; + console.error(error); res.end(JSON.stringify(error)); } else { console.error(error.stack || error); From f88167157c0b0f1cce5647d5c68568a1d7c51685 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 8 Apr 2015 13:27:56 -0700 Subject: [PATCH 124/936] [react-packager] Implement new style asset packaging (with dimensions) --- .../DependencyResolver/ModuleDescriptor.js | 5 ++ .../__tests__/DependencyGraph-test.js | 6 +-- .../haste/DependencyGraph/index.js | 2 +- .../src/Packager/__tests__/Packager-test.js | 35 +++++++++++-- react-packager/src/Packager/index.js | 49 +++++++++++++++++-- 5 files changed, 87 insertions(+), 10 deletions(-) diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index c56593cf..c46a57e6 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -30,8 +30,13 @@ function ModuleDescriptor(fields) { this.isPolyfill = fields.isPolyfill || false; + this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; this.isAsset = fields.isAsset || false; + if (this.isAsset_DEPRECATED && this.isAsset) { + throw new Error('Cannot be an asset and a deprecated asset'); + } + this.altId = fields.altId; this._fields = fields; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index b6a978c6..f42f6f8a 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -92,7 +92,7 @@ describe('DependencyGraph', function() { { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset: true + isAsset_DEPRECATED: true }, ]); }); @@ -183,7 +183,7 @@ describe('DependencyGraph', function() { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset: true + isAsset_DEPRECATED: true }, ]); }); @@ -954,7 +954,7 @@ describe('DependencyGraph', function() { { id: 'image!foo', path: '/root/foo.png', dependencies: [], - isAsset: true, + isAsset_DEPRECATED: true, }, ]); }); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 3348907f..fbc7de71 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -596,7 +596,7 @@ DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { this._assetMap_DEPRECATED[name] = new ModuleDescriptor({ id: 'image!' + name, path: path.resolve(file), - isAsset: true, + isAsset_DEPRECATED: true, dependencies: [], }); } diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 8f61df97..f4675c47 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -43,14 +43,21 @@ describe('Packager', function() { }; }); - var packager = new Packager({projectRoots: []}); + var packager = new Packager({projectRoots: ['/root']}); var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, - { id: 'image!img', + { + id: 'image!img', path: '/root/img/img.png', - isAsset: true, + isAsset_DEPRECATED: true, dependencies: [], + }, + { + id: 'new_image.png', + path: '/root/img/new_image.png', + isAsset: true, + dependencies: [] } ]; @@ -74,6 +81,10 @@ describe('Packager', function() { return 'lol ' + code + ' lol'; }); + require('image-size').mockImpl(function(path, cb) { + cb(null, { width: 50, height: 100 }); + }); + return packager.package('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0]).toEqual([ @@ -96,6 +107,24 @@ describe('Packager', function() { '/root/img/img.png' ]); + var imgModule = { + isStatic: true, + path: '/root/img/new_image.png', + uri: 'img/new_image.png', + width: 50, + height: 100, + }; + + expect(p.addModule.mock.calls[3]).toEqual([ + 'lol module.exports = ' + + JSON.stringify(imgModule) + + '; lol', + 'module.exports = ' + + JSON.stringify(imgModule) + + ';', + '/root/img/new_image.png' + ]); + expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} ]); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index bf5a635d..cfdd842d 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -18,6 +18,7 @@ var _ = require('underscore'); var Package = require('./Package'); var Activity = require('../Activity'); var declareOpts = require('../lib/declareOpts'); +var imageSize = require('image-size'); var validateOpts = declareOpts({ projectRoots: { @@ -88,6 +89,8 @@ function Packager(options) { transformModulePath: opts.transformModulePath, nonPersistent: opts.nonPersistent, }); + + this._projectRoots = opts.projectRoots; } Packager.prototype.kill = function() { @@ -138,8 +141,13 @@ Packager.prototype.getDependencies = function(main, isDev) { Packager.prototype._transformModule = function(module) { var transform; - if (module.isAsset) { - transform = Promise.resolve(generateAssetModule(module)); + if (module.isAsset_DEPRECATED) { + transform = Promise.resolve(generateAssetModule_DEPRECATED(module)); + } else if (module.isAsset) { + transform = generateAssetModule( + module, + getPathRelativeToRoot(this._projectRoots, module.path) + ); } else { transform = this._transformer.loadFileAndTransform( path.resolve(module.path) @@ -166,7 +174,7 @@ Packager.prototype.getGraphDebugInfo = function() { return this._resolver.getDebugInfo(); }; -function generateAssetModule(module) { +function generateAssetModule_DEPRECATED(module) { var code = 'module.exports = ' + JSON.stringify({ uri: module.id.replace(/^[^!]+!/, ''), isStatic: true, @@ -179,4 +187,39 @@ function generateAssetModule(module) { }; } +var sizeOf = Promise.promisify(imageSize); + +function generateAssetModule(module, relPath) { + return sizeOf(module.path).then(function(dimensions) { + var img = { + isStatic: true, + path: module.path, //TODO(amasad): this should be path inside tar file. + uri: relPath, + width: dimensions.width, + height: dimensions.height, + }; + + var code = 'module.exports = ' + JSON.stringify(img) + ';'; + + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; + }); +} + +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' + ); +} + module.exports = Packager; From 408160de7d626d6242c95c01328658c3fd27c522 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 8 Apr 2015 14:28:44 -0700 Subject: [PATCH 125/936] [react-packager] Don't depend on error.stack being available --- react-packager/src/JSTransformer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index abfae248..962eb7fe 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -125,7 +125,7 @@ function formatError(err, filename, source) { function formatGenericError(err, filename) { var msg = 'TransformError: ' + filename + ': ' + err.message; var error = new TransformError(); - var stack = err.stack.split('\n').slice(0, -1); + var stack = (err.stack || '').split('\n').slice(0, -1); stack.push(msg); error.stack = stack.join('\n'); error.message = msg; From c24fc75a53b81fdd581e325008db44d19699e398 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 8 Apr 2015 14:24:00 -0700 Subject: [PATCH 126/936] [react-native] Listen on all IPv6 interfaces --- packager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager.js b/packager.js index a67b723d..b098f0a3 100644 --- a/packager.js +++ b/packager.js @@ -219,5 +219,5 @@ function runServer( .use(connect.compress()) .use(connect.errorHandler()); - return http.createServer(app).listen(options.port, readyCallback); + return http.createServer(app).listen(options.port, '::', readyCallback); } From caa89c36f03e502d35b3b28bd8ed055821b905ff Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 9 Apr 2015 08:46:53 -0700 Subject: [PATCH 127/936] Updates from Thu 9 Apr - [React Native] Fix RCTText crashes | Alex Akers - Ensure that NSLocationWhenInUseUsageDescription is set, throw error if not | Alex Kotliarskyi - [ReactNative] fix exception handler method name | Spencer Ahrens - [ReactNative] Re-configure horizontal swipe animations | Eric Vicenti - [ReactNative] : apply the fontWeight correctly if fontFamily style is also present | Kevin Gozali - [MAdMan] Dimensions.get('window') considered harmful | Philipp von Weitershausen - Navigator: Changed transitioner background color to 'transparent' | Eric Vicenti - [react-native] Listen on all IPv6 interfaces | Ben Alpert - [react-packager] Don't depend on error.stack being available | Amjad Masad - [ReactNative] fixup AnimationExperimental a bit | Spencer Ahrens - [react-packager] Implement new style asset packaging (with dimensions) | Amjad Masad - [React Native] RCT_EXPORT lvl.2 | Alex Akers - [react_native] Implement TextInput end editing | Andrei Coman - [react_native] Make TextInput focus, blur, dismiss and show keyboard work | Andrei Coman - Added non-class-scanning-based approach fror registering js methods | Nick Lockwood - [ReactNative] Update package.json | Christopher Chedeau - [ReactNative] Do flow check when running packager | Spencer Ahrens - [ReactNative] Fix typo/bug in Navigator._completeTransition | Eric Vicenti - [ReactNative] Fix Navigator exception when touching during transition | Eric Vicenti - [ReactNative] Remove bridge retaining cycles | Tadeu Zagallo - [ReactNative] Fix and re-add WebView executor | Tadeu Zagallo --- getFlowTypeCheckMiddleware.js | 86 +++++++++++++++++++ packager.js | 8 +- .../DependencyResolver/ModuleDescriptor.js | 5 ++ .../__tests__/DependencyGraph-test.js | 6 +- .../haste/DependencyGraph/index.js | 2 +- react-packager/src/JSTransformer/index.js | 2 +- .../src/Packager/__tests__/Packager-test.js | 35 +++++++- react-packager/src/Packager/index.js | 49 ++++++++++- react-packager/src/Server/index.js | 6 ++ 9 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 getFlowTypeCheckMiddleware.js diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js new file mode 100644 index 00000000..e0f98bde --- /dev/null +++ b/getFlowTypeCheckMiddleware.js @@ -0,0 +1,86 @@ +/** + * 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 exec = require('child_process').exec; + +function getFlowTypeCheckMiddleware(options) { + return function(req, res, next) { + if (options.skipflow) { + return next(); + } + if (options.flowroot || options.projectRoots.length === 1) { + var flowroot = options.flowroot || options.projectRoots[0]; + } else { + console.warn('flow: No suitable root'); + return next(); + } + exec('command -v flow >/dev/null 2>&1', function(error, stdout) { + if (error) { + console.warn('flow: Skipping because not installed. Install with ' + + '`brew install flow`.'); + return next(); + } else { + return doFlowTypecheck(res, flowroot, next); + } + }); + }; +} + +function doFlowTypecheck(res, flowroot, next) { + var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; + var start = Date.now(); + console.log('flow: Running static typechecks.'); + exec(flowCmd, function(flowError, stdout) { + if (!flowError) { + console.log('flow: Typechecks passed (' + (Date.now() - start) + 'ms).'); + return next(); + } else { + try { + var flowResponse = JSON.parse(stdout); + var errors = []; + var errorNum = 1; + flowResponse.errors.forEach(function(err) { + // flow errors are paired across callsites, so we indent and prefix to + // group them + var indent = ''; + err.message.forEach(function(msg) { + errors.push({ + description: indent + 'E' + errorNum + ': ' + msg.descr, + filename: msg.path, + lineNumber: msg.line, + column: msg.start, + }); + indent = ' '; + }); + errorNum++; + }); + var message = 'Flow found type errors. If you think these are wrong, ' + + 'make sure flow is up to date, or disable with --skipflow.'; + } catch (e) { + var message = + 'Flow failed to provide parseable output:\n\n`' + stdout + '`'; + console.error(message, '\nException: `', e, '`\n\n'); + } + var error = { + status: 500, + message: message, + type: 'FlowError', + errors: errors, + }; + console.error('flow: Error running command `' + flowCmd + '`:\n', error); + res.writeHead(error.status, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + res.end(JSON.stringify(error)); + } + }); +} + +module.exports = getFlowTypeCheckMiddleware; diff --git a/packager.js b/packager.js index 55004b7c..b098f0a3 100644 --- a/packager.js +++ b/packager.js @@ -13,6 +13,8 @@ var path = require('path'); var exec = require('child_process').exec; var http = require('http'); +var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware'); + if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { console.log( '\n' + @@ -40,6 +42,9 @@ var options = parseCommandLine([{ }, { command: 'assetRoots', description: 'specify the root directories of app assets' +}, { + command: 'skipflow', + description: 'Disable flow checks' }]); if (options.projectRoots) { @@ -203,6 +208,7 @@ function runServer( .use(openStackFrameInEditor) .use(getDevToolsLauncher(options)) .use(statusPageMiddleware) + .use(getFlowTypeCheckMiddleware(options)) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { @@ -213,5 +219,5 @@ function runServer( .use(connect.compress()) .use(connect.errorHandler()); - return http.createServer(app).listen(options.port, readyCallback); + return http.createServer(app).listen(options.port, '::', readyCallback); } diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index c56593cf..c46a57e6 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -30,8 +30,13 @@ function ModuleDescriptor(fields) { this.isPolyfill = fields.isPolyfill || false; + this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; this.isAsset = fields.isAsset || false; + if (this.isAsset_DEPRECATED && this.isAsset) { + throw new Error('Cannot be an asset and a deprecated asset'); + } + this.altId = fields.altId; this._fields = fields; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index b6a978c6..f42f6f8a 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -92,7 +92,7 @@ describe('DependencyGraph', function() { { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset: true + isAsset_DEPRECATED: true }, ]); }); @@ -183,7 +183,7 @@ describe('DependencyGraph', function() { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset: true + isAsset_DEPRECATED: true }, ]); }); @@ -954,7 +954,7 @@ describe('DependencyGraph', function() { { id: 'image!foo', path: '/root/foo.png', dependencies: [], - isAsset: true, + isAsset_DEPRECATED: true, }, ]); }); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 3348907f..fbc7de71 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -596,7 +596,7 @@ DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { this._assetMap_DEPRECATED[name] = new ModuleDescriptor({ id: 'image!' + name, path: path.resolve(file), - isAsset: true, + isAsset_DEPRECATED: true, dependencies: [], }); } diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index abfae248..962eb7fe 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -125,7 +125,7 @@ function formatError(err, filename, source) { function formatGenericError(err, filename) { var msg = 'TransformError: ' + filename + ': ' + err.message; var error = new TransformError(); - var stack = err.stack.split('\n').slice(0, -1); + var stack = (err.stack || '').split('\n').slice(0, -1); stack.push(msg); error.stack = stack.join('\n'); error.message = msg; diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 8f61df97..f4675c47 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -43,14 +43,21 @@ describe('Packager', function() { }; }); - var packager = new Packager({projectRoots: []}); + var packager = new Packager({projectRoots: ['/root']}); var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, - { id: 'image!img', + { + id: 'image!img', path: '/root/img/img.png', - isAsset: true, + isAsset_DEPRECATED: true, dependencies: [], + }, + { + id: 'new_image.png', + path: '/root/img/new_image.png', + isAsset: true, + dependencies: [] } ]; @@ -74,6 +81,10 @@ describe('Packager', function() { return 'lol ' + code + ' lol'; }); + require('image-size').mockImpl(function(path, cb) { + cb(null, { width: 50, height: 100 }); + }); + return packager.package('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0]).toEqual([ @@ -96,6 +107,24 @@ describe('Packager', function() { '/root/img/img.png' ]); + var imgModule = { + isStatic: true, + path: '/root/img/new_image.png', + uri: 'img/new_image.png', + width: 50, + height: 100, + }; + + expect(p.addModule.mock.calls[3]).toEqual([ + 'lol module.exports = ' + + JSON.stringify(imgModule) + + '; lol', + 'module.exports = ' + + JSON.stringify(imgModule) + + ';', + '/root/img/new_image.png' + ]); + expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} ]); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index bf5a635d..cfdd842d 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -18,6 +18,7 @@ var _ = require('underscore'); var Package = require('./Package'); var Activity = require('../Activity'); var declareOpts = require('../lib/declareOpts'); +var imageSize = require('image-size'); var validateOpts = declareOpts({ projectRoots: { @@ -88,6 +89,8 @@ function Packager(options) { transformModulePath: opts.transformModulePath, nonPersistent: opts.nonPersistent, }); + + this._projectRoots = opts.projectRoots; } Packager.prototype.kill = function() { @@ -138,8 +141,13 @@ Packager.prototype.getDependencies = function(main, isDev) { Packager.prototype._transformModule = function(module) { var transform; - if (module.isAsset) { - transform = Promise.resolve(generateAssetModule(module)); + if (module.isAsset_DEPRECATED) { + transform = Promise.resolve(generateAssetModule_DEPRECATED(module)); + } else if (module.isAsset) { + transform = generateAssetModule( + module, + getPathRelativeToRoot(this._projectRoots, module.path) + ); } else { transform = this._transformer.loadFileAndTransform( path.resolve(module.path) @@ -166,7 +174,7 @@ Packager.prototype.getGraphDebugInfo = function() { return this._resolver.getDebugInfo(); }; -function generateAssetModule(module) { +function generateAssetModule_DEPRECATED(module) { var code = 'module.exports = ' + JSON.stringify({ uri: module.id.replace(/^[^!]+!/, ''), isStatic: true, @@ -179,4 +187,39 @@ function generateAssetModule(module) { }; } +var sizeOf = Promise.promisify(imageSize); + +function generateAssetModule(module, relPath) { + return sizeOf(module.path).then(function(dimensions) { + var img = { + isStatic: true, + path: module.path, //TODO(amasad): this should be path inside tar file. + uri: relPath, + width: dimensions.width, + height: dimensions.height, + }; + + var code = 'module.exports = ' + JSON.stringify(img) + ';'; + + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; + }); +} + +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' + ); +} + module.exports = Packager; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 65507581..617359bf 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -320,6 +320,12 @@ function handleError(res, error) { }); if (error.type === 'TransformError' || error.type === 'NotFoundError') { + error.errors = [{ + description: error.description, + filename: error.filename, + lineNumber: error.lineNumber, + }]; + console.error(error); res.end(JSON.stringify(error)); } else { console.error(error.stack || error); From dddf121b64d09913b390df1a84d100372a8286f0 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 9 Apr 2015 09:32:07 -0700 Subject: [PATCH 128/936] Unbreak fatal --- getFlowTypeCheckMiddleware.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js index e0f98bde..b1f6f7dd 100644 --- a/getFlowTypeCheckMiddleware.js +++ b/getFlowTypeCheckMiddleware.js @@ -34,6 +34,10 @@ function getFlowTypeCheckMiddleware(options) { } function doFlowTypecheck(res, flowroot, next) { + // vjeux: big hack to make it work on the sample app because we don't generate a + // .flowconfig in the init script right now. + return next(); + var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; var start = Date.now(); console.log('flow: Running static typechecks.'); From 21f1497418cf6d2d1ee916195169799bda39293e Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 9 Apr 2015 12:05:27 -0700 Subject: [PATCH 129/936] [react-packager] Implement the browser field package.json spec --- .../__tests__/DependencyGraph-test.js | 290 ++++++++++++++++++ .../haste/DependencyGraph/index.js | 51 ++- 2 files changed, 333 insertions(+), 8 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index f42f6f8a..40c15979 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -674,6 +674,296 @@ describe('DependencyGraph', function() { ]); }); }); + + pit('should support simple browser field in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client.js', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should supportbrowser field in packages w/o .js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should support mapping main in browser field json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main.js': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should work do correct browser mapping w/o js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should support browser mapping of files', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + './node.js': './not-node.js', + './not-browser': './browser.js', + './dir/server.js': './dir/client', + }, + }), + 'main.js': 'some other code', + 'client.js': 'require("./node")\nrequire("./dir/server.js")', + 'not-node.js': 'require("./not-browser")', + 'not-browser.js': 'require("./dir/server")', + 'browser.js': 'some browser code', + 'dir': { + 'server.js': 'some node code', + 'client.js': 'some browser code', + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: ['./node', './dir/server.js'] + }, + { id: 'aPackage/not-node', + path: '/root/aPackage/not-node.js', + dependencies: ['./not-browser'] + }, + { id: 'aPackage/browser', + path: '/root/aPackage/browser.js', + dependencies: [] + }, + { id: 'aPackage/dir/client', + path: '/root/aPackage/dir/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should support browser mapping for packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + browser: { + 'node-package': 'browser-package', + } + }), + 'index.js': 'require("node-package")', + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', + }, + 'browser-package': { + 'package.json': JSON.stringify({ + 'name': 'browser-package', + }), + 'index.js': 'some browser code', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/index', + path: '/root/aPackage/index.js', + dependencies: ['node-package'] + }, + { id: 'browser-package/index', + path: '/root/aPackage/browser-package/index.js', + dependencies: [] + }, + ]); + }); + }); }); describe('file watch updating', function() { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index fbc7de71..d0ce699e 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -168,15 +168,22 @@ DependecyGraph.prototype.resolveDependency = function( // Package relative modules starts with '.' or '..'. if (depModuleId[0] !== '.') { - // 1. `depModuleId` is simply a top-level `providesModule`. - // 2. `depModuleId` is a package module but given the full path from the - // package, i.e. package_name/module_name + // Check if we need to map the dependency to something else via the + // `browser` field in package.json + var fromPackageJson = this._lookupPackage(fromModule.path); + if (fromPackageJson && fromPackageJson.browser && + fromPackageJson.browser[depModuleId]) { + depModuleId = fromPackageJson.browser[depModuleId]; + } + + // `depModuleId` is simply a top-level `providesModule`. + // `depModuleId` is a package module but given the full path from the + // package, i.e. package_name/module_name if (this._moduleById[sansExtJs(depModuleId)]) { return this._moduleById[sansExtJs(depModuleId)]; } - // 3. `depModuleId` is a package and it's depending on the "main" - // resolution. + // `depModuleId` is a package and it's depending on the "main" resolution. packageJson = this._packagesById[depModuleId]; // We are being forgiving here and raising an error because we could be @@ -190,7 +197,25 @@ DependecyGraph.prototype.resolveDependency = function( return null; } - var main = packageJson.main || 'index'; + var main; + + // We prioritize the `browser` field if it's a module path. + if (typeof packageJson.browser === 'string') { + main = packageJson.browser; + } else { + main = packageJson.main || 'index'; + } + + // If there is a mapping for main in the `browser` field. + if (packageJson.browser && typeof packageJson.browser === 'object') { + var tmpMain = packageJson.browser[main] || + packageJson.browser[withExtJs(main)] || + packageJson.browser[sansExtJs(main)]; + if (tmpMain) { + main = tmpMain; + } + } + modulePath = withExtJs(path.join(packageJson._root, main)); dep = this._graph[modulePath]; @@ -207,8 +232,7 @@ DependecyGraph.prototype.resolveDependency = function( return dep; } else { - // 4. `depModuleId` is a module defined in a package relative to - // `fromModule`. + // `depModuleId` is a module defined in a package relative to `fromModule`. packageJson = this._lookupPackage(fromModule.path); if (packageJson == null) { @@ -224,12 +248,23 @@ DependecyGraph.prototype.resolveDependency = function( var dir = path.dirname(fromModule.path); modulePath = path.join(dir, depModuleId); + if (packageJson.browser && typeof packageJson.browser === 'object') { + var relPath = './' + path.relative(packageJson._root, modulePath); + var tmpModulePath = packageJson.browser[withExtJs(relPath)] || + packageJson.browser[sansExtJs(relPath)]; + if (tmpModulePath) { + modulePath = path.join(packageJson._root, tmpModulePath); + } + } + + // JS modules can be required without extensios. if (this._assetExts.indexOf(extname(modulePath)) === -1) { modulePath = withExtJs(modulePath); } dep = this._graph[modulePath]; + // Maybe the dependency is a directory and there is an index.js inside it. if (dep == null) { modulePath = path.join(dir, depModuleId, 'index.js'); } From 4b9ec6c5f7991b015417648ff31860d851a7a645 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 9 Apr 2015 12:17:32 -0700 Subject: [PATCH 130/936] [react-packager] Correct module extension regexp --- .../__tests__/DependencyGraph-test.js | 36 +++++++++++++++++++ .../haste/DependencyGraph/index.js | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 40c15979..25343fdc 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -64,6 +64,42 @@ describe('DependencyGraph', function() { }); }); + pit('should get dependencies with the correct extensions', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + 'a.js.orig': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + ]); + }); + }); + pit('should get dependencies with deprecated assets', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index d0ce699e..26276f5a 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -65,7 +65,7 @@ function DependecyGraph(options) { this._debugUpdateEvents = []; this._moduleExtPattern = new RegExp( - '.' + ['js'].concat(this._assetExts).join('|') + '$' + '\.(' + ['js'].concat(this._assetExts).join('|') + ')$' ); // Kick off the search process to precompute the dependency graph. From 4d9f4301ec2f1559176aab18196ecda143a9280c Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Thu, 9 Apr 2015 17:49:20 -0700 Subject: [PATCH 131/936] [ReactNative] Don't redbox on flow config errors --- getFlowTypeCheckMiddleware.js | 76 +++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js index e0f98bde..312da45e 100644 --- a/getFlowTypeCheckMiddleware.js +++ b/getFlowTypeCheckMiddleware.js @@ -8,8 +8,11 @@ */ 'use strict'; +var chalk = require('chalk'); var exec = require('child_process').exec; +var hasWarned = {}; + function getFlowTypeCheckMiddleware(options) { return function(req, res, next) { if (options.skipflow) { @@ -18,13 +21,19 @@ function getFlowTypeCheckMiddleware(options) { if (options.flowroot || options.projectRoots.length === 1) { var flowroot = options.flowroot || options.projectRoots[0]; } else { - console.warn('flow: No suitable root'); + if (!hasWarned.noRoot) { + hasWarned.noRoot = true; + console.warn('flow: No suitable root'); + } return next(); } exec('command -v flow >/dev/null 2>&1', function(error, stdout) { if (error) { - console.warn('flow: Skipping because not installed. Install with ' + - '`brew install flow`.'); + if (!hasWarned.noFlow) { + hasWarned.noFlow = true; + console.warn(chalk.yellow('flow: Skipping because not installed. Install with ' + + '`brew install flow`.')); + } return next(); } else { return doFlowTypecheck(res, flowroot, next); @@ -36,10 +45,19 @@ function getFlowTypeCheckMiddleware(options) { function doFlowTypecheck(res, flowroot, next) { var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; var start = Date.now(); - console.log('flow: Running static typechecks.'); - exec(flowCmd, function(flowError, stdout) { + // Log start message if flow is slow to let user know something is happening. + var flowSlow = setTimeout( + function() { + console.log(chalk.gray('flow: Running static typechecks.')); + }, + 500 + ); + exec(flowCmd, function(flowError, stdout, stderr) { + clearTimeout(flowSlow); if (!flowError) { - console.log('flow: Typechecks passed (' + (Date.now() - start) + 'ms).'); + console.log(chalk.gray( + 'flow: Typechecks passed (' + (Date.now() - start) + 'ms).') + ); return next(); } else { try { @@ -61,24 +79,38 @@ function doFlowTypecheck(res, flowroot, next) { }); errorNum++; }); - var message = 'Flow found type errors. If you think these are wrong, ' + - 'make sure flow is up to date, or disable with --skipflow.'; + var error = { + status: 500, + 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.', + 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', + }); + res.end(JSON.stringify(error)); } catch (e) { - var message = - 'Flow failed to provide parseable output:\n\n`' + stdout + '`'; - console.error(message, '\nException: `', e, '`\n\n'); + if (stderr.match(/Could not find a \.flowconfig/)) { + if (!hasWarned.noConfig) { + hasWarned.noConfig = true; + console.warn(chalk.yellow('flow: ' + stderr)); + } + } else { + if (!hasWarned.brokenFlow) { + hasWarned.brokenFlow = true; + console.warn(chalk.yellow( + 'Flow failed to provide parseable output:\n\n`' + stdout + + '`.\n' + 'stderr: `' + stderr + '`' + )); + } + } + return next(); } - var error = { - status: 500, - message: message, - type: 'FlowError', - errors: errors, - }; - console.error('flow: Error running command `' + flowCmd + '`:\n', error); - res.writeHead(error.status, { - 'Content-Type': 'application/json; charset=UTF-8', - }); - res.end(JSON.stringify(error)); } }); } From 81077c8560c0f7c8112b08c11313c64226da5d53 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 10 Apr 2015 01:33:10 -0700 Subject: [PATCH 132/936] Updates from Fri 10 Apr - Implemented response headers when using `XMLHttpRequest` | Nick Lockwood - [ReactNative] Don't redbox on flow config errors | Spencer Ahrens - [ReactNative] Fix suggestions in TextInput when setting value prop | Spencer Ahrens - Link LinkingIOS in SampleApp | Nick Lockwood - unify use of password and secureTextEntry for TextInput | Nick Lockwood - Added random js queue+execution time sampling in react native | Bryce Redd - [react_native] JS files from D1980312: [react_native] Fix webview | Andrei Coman - [react-packager] Correct module extension regexp | Amjad Masad - [react-packager] Implement the browser field package.json spec | Amjad Masad - [ReactNative] Bring back crash reporting | Alex Kotliarskyi - Remove duplicate word | Nick Lockwood - NavigatorIOS navigationBarHidden property support | Nick Lockwood - [Scroll] Include content insets in scroll events | Nick Lockwood --- getFlowTypeCheckMiddleware.js | 80 +++-- .../__tests__/DependencyGraph-test.js | 326 ++++++++++++++++++ .../haste/DependencyGraph/index.js | 53 ++- 3 files changed, 424 insertions(+), 35 deletions(-) diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js index b1f6f7dd..312da45e 100644 --- a/getFlowTypeCheckMiddleware.js +++ b/getFlowTypeCheckMiddleware.js @@ -8,8 +8,11 @@ */ 'use strict'; +var chalk = require('chalk'); var exec = require('child_process').exec; +var hasWarned = {}; + function getFlowTypeCheckMiddleware(options) { return function(req, res, next) { if (options.skipflow) { @@ -18,13 +21,19 @@ function getFlowTypeCheckMiddleware(options) { if (options.flowroot || options.projectRoots.length === 1) { var flowroot = options.flowroot || options.projectRoots[0]; } else { - console.warn('flow: No suitable root'); + if (!hasWarned.noRoot) { + hasWarned.noRoot = true; + console.warn('flow: No suitable root'); + } return next(); } exec('command -v flow >/dev/null 2>&1', function(error, stdout) { if (error) { - console.warn('flow: Skipping because not installed. Install with ' + - '`brew install flow`.'); + if (!hasWarned.noFlow) { + hasWarned.noFlow = true; + console.warn(chalk.yellow('flow: Skipping because not installed. Install with ' + + '`brew install flow`.')); + } return next(); } else { return doFlowTypecheck(res, flowroot, next); @@ -34,16 +43,21 @@ function getFlowTypeCheckMiddleware(options) { } function doFlowTypecheck(res, flowroot, next) { - // vjeux: big hack to make it work on the sample app because we don't generate a - // .flowconfig in the init script right now. - return next(); - var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; var start = Date.now(); - console.log('flow: Running static typechecks.'); - exec(flowCmd, function(flowError, stdout) { + // Log start message if flow is slow to let user know something is happening. + var flowSlow = setTimeout( + function() { + console.log(chalk.gray('flow: Running static typechecks.')); + }, + 500 + ); + exec(flowCmd, function(flowError, stdout, stderr) { + clearTimeout(flowSlow); if (!flowError) { - console.log('flow: Typechecks passed (' + (Date.now() - start) + 'ms).'); + console.log(chalk.gray( + 'flow: Typechecks passed (' + (Date.now() - start) + 'ms).') + ); return next(); } else { try { @@ -65,24 +79,38 @@ function doFlowTypecheck(res, flowroot, next) { }); errorNum++; }); - var message = 'Flow found type errors. If you think these are wrong, ' + - 'make sure flow is up to date, or disable with --skipflow.'; + var error = { + status: 500, + 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.', + 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', + }); + res.end(JSON.stringify(error)); } catch (e) { - var message = - 'Flow failed to provide parseable output:\n\n`' + stdout + '`'; - console.error(message, '\nException: `', e, '`\n\n'); + if (stderr.match(/Could not find a \.flowconfig/)) { + if (!hasWarned.noConfig) { + hasWarned.noConfig = true; + console.warn(chalk.yellow('flow: ' + stderr)); + } + } else { + if (!hasWarned.brokenFlow) { + hasWarned.brokenFlow = true; + console.warn(chalk.yellow( + 'Flow failed to provide parseable output:\n\n`' + stdout + + '`.\n' + 'stderr: `' + stderr + '`' + )); + } + } + return next(); } - var error = { - status: 500, - message: message, - type: 'FlowError', - errors: errors, - }; - console.error('flow: Error running command `' + flowCmd + '`:\n', error); - res.writeHead(error.status, { - 'Content-Type': 'application/json; charset=UTF-8', - }); - res.end(JSON.stringify(error)); } }); } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index f42f6f8a..25343fdc 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -64,6 +64,42 @@ describe('DependencyGraph', function() { }); }); + pit('should get dependencies with the correct extensions', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + 'a.js.orig': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + ]); + }); + }); + pit('should get dependencies with deprecated assets', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -674,6 +710,296 @@ describe('DependencyGraph', function() { ]); }); }); + + pit('should support simple browser field in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client.js', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should supportbrowser field in packages w/o .js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should support mapping main in browser field json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main.js': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should work do correct browser mapping w/o js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should support browser mapping of files', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + './node.js': './not-node.js', + './not-browser': './browser.js', + './dir/server.js': './dir/client', + }, + }), + 'main.js': 'some other code', + 'client.js': 'require("./node")\nrequire("./dir/server.js")', + 'not-node.js': 'require("./not-browser")', + 'not-browser.js': 'require("./dir/server")', + 'browser.js': 'some browser code', + 'dir': { + 'server.js': 'some node code', + 'client.js': 'some browser code', + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/client', + path: '/root/aPackage/client.js', + dependencies: ['./node', './dir/server.js'] + }, + { id: 'aPackage/not-node', + path: '/root/aPackage/not-node.js', + dependencies: ['./not-browser'] + }, + { id: 'aPackage/browser', + path: '/root/aPackage/browser.js', + dependencies: [] + }, + { id: 'aPackage/dir/client', + path: '/root/aPackage/dir/client.js', + dependencies: [] + }, + ]); + }); + }); + + pit('should support browser mapping for packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + browser: { + 'node-package': 'browser-package', + } + }), + 'index.js': 'require("node-package")', + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', + }, + 'browser-package': { + 'package.json': JSON.stringify({ + 'name': 'browser-package', + }), + 'index.js': 'some browser code', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/index', + path: '/root/aPackage/index.js', + dependencies: ['node-package'] + }, + { id: 'browser-package/index', + path: '/root/aPackage/browser-package/index.js', + dependencies: [] + }, + ]); + }); + }); }); describe('file watch updating', function() { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index fbc7de71..26276f5a 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -65,7 +65,7 @@ function DependecyGraph(options) { this._debugUpdateEvents = []; this._moduleExtPattern = new RegExp( - '.' + ['js'].concat(this._assetExts).join('|') + '$' + '\.(' + ['js'].concat(this._assetExts).join('|') + ')$' ); // Kick off the search process to precompute the dependency graph. @@ -168,15 +168,22 @@ DependecyGraph.prototype.resolveDependency = function( // Package relative modules starts with '.' or '..'. if (depModuleId[0] !== '.') { - // 1. `depModuleId` is simply a top-level `providesModule`. - // 2. `depModuleId` is a package module but given the full path from the - // package, i.e. package_name/module_name + // Check if we need to map the dependency to something else via the + // `browser` field in package.json + var fromPackageJson = this._lookupPackage(fromModule.path); + if (fromPackageJson && fromPackageJson.browser && + fromPackageJson.browser[depModuleId]) { + depModuleId = fromPackageJson.browser[depModuleId]; + } + + // `depModuleId` is simply a top-level `providesModule`. + // `depModuleId` is a package module but given the full path from the + // package, i.e. package_name/module_name if (this._moduleById[sansExtJs(depModuleId)]) { return this._moduleById[sansExtJs(depModuleId)]; } - // 3. `depModuleId` is a package and it's depending on the "main" - // resolution. + // `depModuleId` is a package and it's depending on the "main" resolution. packageJson = this._packagesById[depModuleId]; // We are being forgiving here and raising an error because we could be @@ -190,7 +197,25 @@ DependecyGraph.prototype.resolveDependency = function( return null; } - var main = packageJson.main || 'index'; + var main; + + // We prioritize the `browser` field if it's a module path. + if (typeof packageJson.browser === 'string') { + main = packageJson.browser; + } else { + main = packageJson.main || 'index'; + } + + // If there is a mapping for main in the `browser` field. + if (packageJson.browser && typeof packageJson.browser === 'object') { + var tmpMain = packageJson.browser[main] || + packageJson.browser[withExtJs(main)] || + packageJson.browser[sansExtJs(main)]; + if (tmpMain) { + main = tmpMain; + } + } + modulePath = withExtJs(path.join(packageJson._root, main)); dep = this._graph[modulePath]; @@ -207,8 +232,7 @@ DependecyGraph.prototype.resolveDependency = function( return dep; } else { - // 4. `depModuleId` is a module defined in a package relative to - // `fromModule`. + // `depModuleId` is a module defined in a package relative to `fromModule`. packageJson = this._lookupPackage(fromModule.path); if (packageJson == null) { @@ -224,12 +248,23 @@ DependecyGraph.prototype.resolveDependency = function( var dir = path.dirname(fromModule.path); modulePath = path.join(dir, depModuleId); + if (packageJson.browser && typeof packageJson.browser === 'object') { + var relPath = './' + path.relative(packageJson._root, modulePath); + var tmpModulePath = packageJson.browser[withExtJs(relPath)] || + packageJson.browser[sansExtJs(relPath)]; + if (tmpModulePath) { + modulePath = path.join(packageJson._root, tmpModulePath); + } + } + + // JS modules can be required without extensios. if (this._assetExts.indexOf(extname(modulePath)) === -1) { modulePath = withExtJs(modulePath); } dep = this._graph[modulePath]; + // Maybe the dependency is a directory and there is an index.js inside it. if (dep == null) { modulePath = path.join(dir, depModuleId, 'index.js'); } From 39da8034ddebecbe030e3b751b08821b68341237 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Mon, 13 Apr 2015 12:51:31 -0700 Subject: [PATCH 133/936] React Native: Add String.prototyp.es6 Polyfill --- .../__tests__/HasteDependencyResolver-test.js | 34 ++++++++ .../src/DependencyResolver/haste/index.js | 1 + .../haste/polyfills/String.prototype.es6.js | 85 +++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 8620d488..d5c54b7e 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -82,6 +82,17 @@ describe('HasteDependencyResolver', function() { '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' + ], + }, module ]); }); @@ -142,6 +153,17 @@ describe('HasteDependencyResolver', function() { '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' + ], + }, module ]); }); @@ -203,6 +225,17 @@ describe('HasteDependencyResolver', function() { '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' + ], + }, { path: 'some module', id: 'some module', isPolyfill: true, @@ -212,6 +245,7 @@ describe('HasteDependencyResolver', function() { 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', + 'polyfills/String.prototype.es6.js' ] }, module diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index e700e5fd..c1863998 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -112,6 +112,7 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function( path.join(__dirname, 'polyfills/polyfills.js'), path.join(__dirname, 'polyfills/console.js'), path.join(__dirname, 'polyfills/error-guard.js'), + path.join(__dirname, 'polyfills/String.prototype.es6.js'), ].concat(this._polyfillModuleNames); var polyfillModules = polyfillModuleNames.map( diff --git a/react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js b/react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js new file mode 100644 index 00000000..afc68d76 --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js @@ -0,0 +1,85 @@ +/** + * @provides String.prototype.es6 + * @polyfill + */ + +/*eslint global-strict:0, no-extend-native:0, no-bitwise:0 */ +/*jshint bitwise:false*/ + +/* + * NOTE: We use (Number(x) || 0) to replace NaN values with zero. + */ + +if (!String.prototype.startsWith) { + String.prototype.startsWith = function(search) { + 'use strict'; + if (this == null) { + throw TypeError(); + } + var string = String(this); + var pos = arguments.length > 1 ? + (Number(arguments[1]) || 0) : 0; + var start = Math.min(Math.max(pos, 0), string.length); + return string.indexOf(String(search), pos) === start; + }; +} + +if (!String.prototype.endsWith) { + String.prototype.endsWith = function(search) { + 'use strict'; + if (this == null) { + throw TypeError(); + } + var string = String(this); + var stringLength = string.length; + var searchString = String(search); + var pos = arguments.length > 1 ? + (Number(arguments[1]) || 0) : stringLength; + var end = Math.min(Math.max(pos, 0), stringLength); + var start = end - searchString.length; + if (start < 0) { + return false; + } + return string.lastIndexOf(searchString, start) === start; + }; +} + +if (!String.prototype.contains) { + String.prototype.contains = function(search) { + 'use strict'; + if (this == null) { + throw TypeError(); + } + var string = String(this); + var pos = arguments.length > 1 ? + (Number(arguments[1]) || 0) : 0; + return string.indexOf(String(search), pos) !== -1; + }; +} + +if (!String.prototype.repeat) { + String.prototype.repeat = function(count) { + 'use strict'; + if (this == null) { + throw TypeError(); + } + var string = String(this); + count = Number(count) || 0; + if (count < 0 || count === Infinity) { + throw RangeError(); + } + if (count === 1) { + return string; + } + var result = ''; + while (count) { + if (count & 1) { + result += string; + } + if ((count >>= 1)) { + string += string; + } + } + return result; + }; +} From 241e263e5e89c58cdc4b3eb3636db922e5dc09df Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Tue, 14 Apr 2015 08:52:40 -0700 Subject: [PATCH 134/936] [ReactNative] allow running JS app server for Android from fbobjc --- blacklist.js | 35 ++++++++++++++++++++++------------- packager.js | 8 ++++++-- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/blacklist.js b/blacklist.js index b5ba4185..9bfeda50 100644 --- a/blacklist.js +++ b/blacklist.js @@ -18,26 +18,35 @@ var sharedBlacklist = [ 'node_modules/react-tools/src/event/EventPropagators.js' ]; -var webBlacklist = [ - '.ios.js' -]; - -var iosBlacklist = [ - 'node_modules/react-tools/src/browser/ui/React.js', - 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', - // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', - '.web.js', - '.android.js', -]; +var platformBlacklists = { + web: [ + '.ios.js' + ], + ios: [ + 'node_modules/react-tools/src/browser/ui/React.js', + 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', + // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + '.web.js', + '.android.js', + ], + android: [ + 'node_modules/react-tools/src/browser/ui/React.js', + 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', + 'node_modules/react-tools/src/browser/ReactTextComponent.js', + // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + '.web.js', + '.ios.js', + ], +}; function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); } -function blacklist(isWeb, additionalBlacklist) { +function blacklist(platform, additionalBlacklist) { return new RegExp('(' + (additionalBlacklist || []).concat(sharedBlacklist) - .concat(isWeb ? webBlacklist : iosBlacklist) + .concat(platformBlacklists[platform] || []) .map(escapeRegExp) .join('|') + ')$' diff --git a/packager.js b/packager.js index b098f0a3..23da3a78 100644 --- a/packager.js +++ b/packager.js @@ -42,6 +42,10 @@ var options = parseCommandLine([{ }, { command: 'assetRoots', description: 'specify the root directories of app assets' +}, { + command: 'platform', + default: 'ios', + description: 'Specify the platform-specific blacklist (ios, android, web).' }, { command: 'skipflow', description: 'Disable flow checks' @@ -192,7 +196,7 @@ function statusPageMiddleware(req, res, next) { function getAppMiddleware(options) { return ReactPackager.middleware({ projectRoots: options.projectRoots, - blacklistRE: blacklist(false), + blacklistRE: blacklist(options.platform), cacheVersion: '2', transformModulePath: require.resolve('./transformer.js'), assetRoots: options.assetRoots, @@ -200,7 +204,7 @@ function getAppMiddleware(options) { } function runServer( - options, /* {[]string projectRoot, bool web} */ + options, readyCallback ) { var app = connect() From 4a3e69ec74e53b8daf8bca7dbee987f721011848 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 14 Apr 2015 10:46:41 -0700 Subject: [PATCH 135/936] [react-packager] Support @nx resolution postfix for assets --- .../DependencyResolver/ModuleDescriptor.js | 6 ++ .../__tests__/DependencyGraph-test.js | 73 ++++++++++++++++++- .../haste/DependencyGraph/index.js | 42 +++++++++-- .../src/Packager/__tests__/Packager-test.js | 5 +- react-packager/src/Packager/index.js | 4 +- 5 files changed, 119 insertions(+), 11 deletions(-) diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index c46a57e6..3cdfa1c6 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -37,6 +37,12 @@ function ModuleDescriptor(fields) { throw new Error('Cannot be an asset and a deprecated asset'); } + this.resolution = fields.resolution; + + if (this.isAsset && isNaN(this.resolution)) { + throw new Error('Expected resolution to be a number for asset modules'); + } + this.altId = fields.altId; this._fields = fields; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 25343fdc..40705911 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -169,7 +169,74 @@ describe('DependencyGraph', function() { { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a.png', dependencies: [], - isAsset: true + isAsset: true, + resolution: 1, + }, + ]); + }); + }); + + pit('should get dependencies with assets and resolution', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png");', + 'require("./imgs/b.png");', + 'require("./imgs/c.png");', + ].join('\n'), + 'imgs': { + 'a@1.5x.png': '', + 'b@.7x.png': '', + 'c.png': '', + 'c@2x.png': '', + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { + id: 'index', + altId: 'rootPackage/index', + path: '/root/index.js', + dependencies: [ + './imgs/a.png', + './imgs/b.png', + './imgs/c.png', + ] + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a@1.5x.png', + resolution: 1.5, + dependencies: [], + isAsset: true, + }, + { + id: 'rootPackage/imgs/b.png', + path: '/root/imgs/b@.7x.png', + resolution: 0.7, + dependencies: [], + isAsset: true + }, + { + id: 'rootPackage/imgs/c.png', + path: '/root/imgs/c.png', + resolution: 1, + dependencies: [], + isAsset: true }, ]); }); @@ -213,7 +280,8 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a.png', dependencies: [], - isAsset: true + isAsset: true, + resolution: 1, }, { id: 'image!a', @@ -1332,6 +1400,7 @@ describe('DependencyGraph', function() { path: '/root/foo.png', dependencies: [], isAsset: true, + resolution: 1, }, ]); }); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 26276f5a..f92a3195 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -258,7 +258,7 @@ DependecyGraph.prototype.resolveDependency = function( } // JS modules can be required without extensios. - if (this._assetExts.indexOf(extname(modulePath)) === -1) { + if (!this._isFileAsset(modulePath)) { modulePath = withExtJs(modulePath); } @@ -266,10 +266,14 @@ DependecyGraph.prototype.resolveDependency = function( // Maybe the dependency is a directory and there is an index.js inside it. if (dep == null) { - modulePath = path.join(dir, depModuleId, 'index.js'); + dep = this._graph[path.join(dir, depModuleId, 'index.js')]; } - dep = this._graph[modulePath]; + // Maybe it's an asset with @n.nx resolution and the path doesn't map + // to the id + if (dep == null && this._isFileAsset(modulePath)) { + dep = this._moduleById[this._lookupName(modulePath)]; + } if (dep == null) { debug( @@ -417,11 +421,14 @@ DependecyGraph.prototype._processModule = function(modulePath) { var module; if (this._assetExts.indexOf(extname(modulePath)) > -1) { - moduleData.id = this._lookupName(modulePath); + var assetData = extractResolutionPostfix(this._lookupName(modulePath)); + moduleData.id = assetData.assetName; + moduleData.resolution = assetData.resolution; moduleData.isAsset = true; moduleData.dependencies = []; - module = Promise.resolve(new ModuleDescriptor(moduleData)); + module = new ModuleDescriptor(moduleData); this._updateGraphWithModule(module); + return Promise.resolve(module); } var self = this; @@ -652,6 +659,10 @@ DependecyGraph.prototype._processAssetChange_DEPRECATED = function(eventType, fi } }; +DependecyGraph.prototype._isFileAsset = function(file) { + return this._assetExts.indexOf(extname(file)) !== -1; +}; + /** * Extract all required modules from a `code` string. */ @@ -761,6 +772,27 @@ function extname(name) { return path.extname(name).replace(/^\./, ''); } +function extractResolutionPostfix(filename) { + var ext = extname(filename); + var re = new RegExp('@([\\d\\.]+)x\\.' + ext + '$'); + + var match = filename.match(re); + var resolution; + + if (!(match && match[1])) { + resolution = 1; + } else { + resolution = parseFloat(match[1], 10); + if (isNaN(resolution)) { + resolution = 1; + } + } + + return { + resolution: resolution, + assetName: match ? filename.replace(re, '.' + ext) : filename, + }; +} function NotFoundError() { Error.call(this); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index f4675c47..cc5c4471 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -57,6 +57,7 @@ describe('Packager', function() { id: 'new_image.png', path: '/root/img/new_image.png', isAsset: true, + resolution: 2, dependencies: [] } ]; @@ -111,8 +112,8 @@ describe('Packager', function() { isStatic: true, path: '/root/img/new_image.png', uri: 'img/new_image.png', - width: 50, - height: 100, + width: 25, + height: 50, }; expect(p.addModule.mock.calls[3]).toEqual([ diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index cfdd842d..9e94be21 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -195,8 +195,8 @@ function generateAssetModule(module, relPath) { isStatic: true, path: module.path, //TODO(amasad): this should be path inside tar file. uri: relPath, - width: dimensions.width, - height: dimensions.height, + width: dimensions.width / module.resolution, + height: dimensions.height / module.resolution, }; var code = 'module.exports = ' + JSON.stringify(img) + ';'; From a998049c081b00fc2a0eca399f74594874ba9d60 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 14 Apr 2015 15:26:59 -0700 Subject: [PATCH 136/936] [react-packager] Add Array.prototype.es6 polyfill --- .../__tests__/HasteDependencyResolver-test.js | 39 ++++++- .../src/DependencyResolver/haste/index.js | 1 + .../haste/polyfills/Array.prototype.es6.js | 106 ++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index d5c54b7e..b3063cb5 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -93,6 +93,18 @@ describe('HasteDependencyResolver', function() { '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', + ], + }, module ]); }); @@ -164,6 +176,18 @@ describe('HasteDependencyResolver', function() { '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 ]); }); @@ -236,6 +260,18 @@ describe('HasteDependencyResolver', function() { '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', + ], + }, { path: 'some module', id: 'some module', isPolyfill: true, @@ -245,7 +281,8 @@ describe('HasteDependencyResolver', function() { 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', - 'polyfills/String.prototype.es6.js' + 'polyfills/String.prototype.es6.js', + 'polyfills/Array.prototype.es6.js' ] }, module diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index c1863998..2583ac8f 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -113,6 +113,7 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function( path.join(__dirname, 'polyfills/console.js'), path.join(__dirname, 'polyfills/error-guard.js'), path.join(__dirname, 'polyfills/String.prototype.es6.js'), + path.join(__dirname, 'polyfills/Array.prototype.es6.js'), ].concat(this._polyfillModuleNames); var polyfillModules = polyfillModuleNames.map( diff --git a/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js b/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js new file mode 100644 index 00000000..78298a2f --- /dev/null +++ b/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js @@ -0,0 +1,106 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @provides Array.prototype.es6 + * @polyfill + * @requires __DEV__ + */ + +/*eslint-disable */ +/*jslint bitwise: true */ + +(function (undefined) { + if (__DEV__) { + // Define DEV-only setter that blows up when someone incorrectly + // iterates over arrays. + try { + Object.defineProperty && Object.defineProperty( + Array.prototype, + '__ARRAY_ENUMERATION_GUARD__', + { + configurable: true, + enumerable: true, + get: function() { + console.error( + 'Your code is broken! Do not iterate over arrays with ' + + 'for...in. See https://fburl.com/31944000 for more information.' + ); + } + } + ); + } catch (e) { + // Nothing + } + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex + function findIndex(predicate, context) { + /** + * Why am I seeing this "findIndex" method as a value in my array!? + * + * We polyfill the "findIndex" method -- called like + * `[1, 2, 3].findIndex(1)` -- for older browsers. A side effect of the way + * we do that is that the method is enumerable. If you were incorrectly + * iterating over your array using the object property iterator syntax + * `for (key in obj)` you will see the method name "findIndex" as a key. + * + * To fix your code please do one of the following: + * + * - Use a regular for loop with index. + * - Use one of the array methods: a.forEach, a.map, etc. + * - Guard your body of your loop with a `arr.hasOwnProperty(key)` check. + * + * More info: + * https://our.intern.facebook.com/intern/dex/qa/669736606441771/ + */ + if (this == null) { + throw new TypeError( + 'Array.prototype.findIndex called on null or undefined' + ); + } + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + var list = Object(this); + var length = list.length >>> 0; + for (var i = 0; i < length; i++) { + if (predicate.call(context, list[i], i, list)) { + return i; + } + } + return -1; + } + + if (!Array.prototype.findIndex) { + Array.prototype.findIndex = findIndex; + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find + if (!Array.prototype.find) { + Array.prototype.find = function(predicate, context) { + /** + * Why am I seeing this "find" method as a value in my array!? + * + * We polyfill the "find" method -- called like + * `[1, 2, 3].find(1)` -- for older browsers. A side effect of the way + * we do that is that the method is enumerable. If you were incorrectly + * iterating over your array using the object property iterator syntax + * `for (key in obj)` you will see the method name "find" as a key. + * + * To fix your code please do one of the following: + * + * - Use a regular for loop with index. + * - Use one of the array methods: a.forEach, a.map, etc. + * - Guard your body of your loop with a `arr.hasOwnProperty(key)` check. + * + * More info: + * https://our.intern.facebook.com/intern/dex/qa/669736606441771/ + */ + if (this == null) { + throw new TypeError('Array.prototype.find called on null or undefined'); + } + var index = findIndex.call(this, predicate, context); + return index === -1 ? undefined : this[index]; + }; + } +})(); From 852f8533a134cf174bf9f1e66f8dc79dd39cb7aa Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 16 Apr 2015 13:00:11 -0700 Subject: [PATCH 137/936] [react-packager] implement /assets endpoint to serve assets --- .../AssetServer/__tests__/AssetServer-test.js | 85 +++++++++++ react-packager/src/AssetServer/index.js | 132 ++++++++++++++++++ .../__tests__/DependencyGraph-test.js | 1 + .../haste/DependencyGraph/index.js | 25 +--- .../src/Packager/__tests__/Packager-test.js | 2 +- react-packager/src/Packager/index.js | 2 +- .../src/Server/__tests__/Server-test.js | 28 ++++ react-packager/src/Server/index.js | 25 ++++ .../DependencyGraph => }/__mocks__/fs.js | 5 + .../__tests__/extractAssetResolution-test.js | 42 ++++++ .../src/lib/extractAssetResolution.js | 28 ++++ 11 files changed, 350 insertions(+), 25 deletions(-) create mode 100644 react-packager/src/AssetServer/__tests__/AssetServer-test.js create mode 100644 react-packager/src/AssetServer/index.js rename react-packager/src/{DependencyResolver/haste/DependencyGraph => }/__mocks__/fs.js (96%) create mode 100644 react-packager/src/lib/__tests__/extractAssetResolution-test.js create mode 100644 react-packager/src/lib/extractAssetResolution.js diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js new file mode 100644 index 00000000..eede72c0 --- /dev/null +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -0,0 +1,85 @@ +'use strict'; + +jest + .autoMockOff() + .mock('../../lib/declareOpts') + .mock('fs'); + +var fs = require('fs'); +var AssetServer = require('../'); +var Promise = require('bluebird'); + +describe('AssetServer', function() { + pit('should work for the simple case', function() { + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); + + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b.png': 'b image', + 'b@2x.png': 'b2 image', + } + } + }); + + 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'); + }); + }); + }); + + pit.only('should pick the bigger one', function() { + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); + + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b@1x.png': 'b1 image', + 'b@2x.png': 'b2 image', + 'b@4x.png': 'b4 image', + 'b@4.5x.png': 'b4.5 image', + } + } + }); + + return server.get('imgs/b@3x.png').then(function(data) { + expect(data).toBe('b4 image'); + }); + }); + + pit('should support multiple project roots', function() { + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); + + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b.png': 'b image', + }, + 'root2': { + 'newImages': { + 'imgs': { + 'b@1x.png': 'b1 image', + }, + }, + }, + } + }); + + return server.get('newImages/imgs/b.png').then(function(data) { + expect(data).toBe('b1 image'); + }); + }); +}); diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js new file mode 100644 index 00000000..bdabafff --- /dev/null +++ b/react-packager/src/AssetServer/index.js @@ -0,0 +1,132 @@ +/** + * 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 declareOpts = require('../lib/declareOpts'); +var extractAssetResolution = require('../lib/extractAssetResolution'); +var path = require('path'); +var Promise = require('bluebird'); +var fs = require('fs'); + +var lstat = Promise.promisify(fs.lstat); +var readDir = Promise.promisify(fs.readdir); +var readFile = Promise.promisify(fs.readFile); + +module.exports = AssetServer; + +var validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + assetExts: { + type: 'array', + default: ['png'], + }, +}); + +function AssetServer(options) { + var 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 resolutions in this directory + * 4. Then pick the closest resolution (rounding up) to the requested one + */ + +AssetServer.prototype.get = function(assetPath) { + var filename = path.basename(assetPath); + + return findRoot( + this._roots, + path.dirname(assetPath) + ).then(function(dir) { + return [ + dir, + readDir(dir), + ]; + }).spread(function(dir, files) { + // Easy case. File exactly what the client requested. + var index = files.indexOf(filename); + if (index > -1) { + return readFile(path.join(dir, filename)); + } + + var assetData = extractAssetResolution(filename); + var map = buildAssetMap(dir, files); + var record = map[assetData.assetName]; + + if (!record) { + throw new Error('Asset not found'); + } + + for (var i = 0; i < record.resolutions.length; i++) { + if (record.resolutions[i] >= assetData.resolution) { + return readFile(record.files[i]); + } + } + + return readFile(record.files[record.files.length - 1]); + }); +}; + +function findRoot(roots, dir) { + return Promise.some( + roots.map(function(root) { + var absPath = path.join(root, dir); + return lstat(absPath).then(function(stat) { + if (!stat.isDirectory()) { + throw new Error('Looking for dirs'); + } + stat._path = absPath; + return stat; + }); + }), + 1 + ).spread( + function(stat) { + return stat._path; + } + ); +} + +function buildAssetMap(dir, files) { + var assets = files.map(extractAssetResolution); + 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] = { + resolutions: [], + files: [], + }; + } + + var insertIndex; + var length = record.resolutions.length; + for (insertIndex = 0; insertIndex < length; insertIndex++) { + if (asset.resolution < record.resolutions[insertIndex]) { + break; + } + } + record.resolutions.splice(insertIndex, 0, asset.resolution); + record.files.splice(insertIndex, 0, path.join(dir, file)); + }); + + return map; +} diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 40705911..98ae7eb7 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -14,6 +14,7 @@ jest .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') + .dontMock('../../../../lib/extractAssetResolution') .setMock('../../../ModuleDescriptor', function(data) {return data;}); describe('DependencyGraph', function() { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index f92a3195..9257d788 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -18,6 +18,7 @@ var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); +var extractAssetResolution = require('../../../lib/extractAssetResolution'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -421,7 +422,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { var module; if (this._assetExts.indexOf(extname(modulePath)) > -1) { - var assetData = extractResolutionPostfix(this._lookupName(modulePath)); + var assetData = extractAssetResolution(this._lookupName(modulePath)); moduleData.id = assetData.assetName; moduleData.resolution = assetData.resolution; moduleData.isAsset = true; @@ -772,28 +773,6 @@ function extname(name) { return path.extname(name).replace(/^\./, ''); } -function extractResolutionPostfix(filename) { - var ext = extname(filename); - var re = new RegExp('@([\\d\\.]+)x\\.' + ext + '$'); - - var match = filename.match(re); - var resolution; - - if (!(match && match[1])) { - resolution = 1; - } else { - resolution = parseFloat(match[1], 10); - if (isNaN(resolution)) { - resolution = 1; - } - } - - return { - resolution: resolution, - assetName: match ? filename.replace(re, '.' + ext) : filename, - }; -} - function NotFoundError() { Error.call(this); Error.captureStackTrace(this, this.constructor); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index cc5c4471..0c9d4a84 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -111,7 +111,7 @@ describe('Packager', function() { var imgModule = { isStatic: true, path: '/root/img/new_image.png', - uri: 'img/new_image.png', + uri: 'assets/img/new_image.png', width: 25, height: 50, }; diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 9e94be21..74e2ff4c 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -194,7 +194,7 @@ function generateAssetModule(module, relPath) { var img = { isStatic: true, path: module.path, //TODO(amasad): this should be path inside tar file. - uri: relPath, + uri: path.join('assets', relPath), width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, }; diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index a7b65021..58a1aca7 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -229,4 +229,32 @@ describe('processRequest', function() { expect(res.end).not.toBeCalled(); }); }); + + describe.only('/assets endpoint', function() { + var AssetServer; + beforeEach(function() { + AssetServer = require('../../AssetServer'); + }); + + it('should serve simple case', function() { + var req = { + url: '/assets/imgs/a.png', + }; + var res = { + end: jest.genMockFn(), + }; + + AssetServer.prototype.get.mockImpl(function() { + return Promise.resolve('i am image'); + }); + + server.processRequest(req, res); + jest.runAllTimers(); + expect(res.end).toBeCalledWith('i am image'); + }); + + it('should return 404', function() { + + }); + }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 617359bf..51d5569f 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -14,6 +14,7 @@ var declareOpts = require('../lib/declareOpts'); var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); +var AssetServer = require('../AssetServer'); var Promise = require('bluebird'); var _ = require('underscore'); @@ -99,6 +100,11 @@ function Server(options) { packagerOpts.fileWatcher = this._fileWatcher; this._packager = new Packager(packagerOpts); + this._assetServer = new AssetServer({ + projectRoots: opts.projectRoots, + assetExts: opts.assetExts, + }); + var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); @@ -230,6 +236,22 @@ Server.prototype._processOnChangeRequest = function(req, res) { }); }; +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(); +}; + Server.prototype.processRequest = function(req, res, next) { var urlObj = url.parse(req.url, true); var pathname = urlObj.pathname; @@ -245,6 +267,9 @@ Server.prototype.processRequest = function(req, res, next) { } else if (pathname.match(/^\/onchange\/?$/)) { this._processOnChangeRequest(req, res); return; + } else if (pathname.match(/^\/assets\//)) { + this._processAssetsRequest(req, res); + return; } else { next(); return; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js b/react-packager/src/__mocks__/fs.js similarity index 96% rename from react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js rename to react-packager/src/__mocks__/fs.js index 3ebee183..0ea13d15 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__mocks__/fs.js +++ b/react-packager/src/__mocks__/fs.js @@ -42,6 +42,11 @@ fs.readdir.mockImpl(function(filepath, callback) { }); fs.readFile.mockImpl(function(filepath, encoding, callback) { + if (arguments.length === 2) { + callback = encoding; + encoding = null; + } + try { var node = getToNode(filepath); // dir check diff --git a/react-packager/src/lib/__tests__/extractAssetResolution-test.js b/react-packager/src/lib/__tests__/extractAssetResolution-test.js new file mode 100644 index 00000000..ad5ac3fb --- /dev/null +++ b/react-packager/src/lib/__tests__/extractAssetResolution-test.js @@ -0,0 +1,42 @@ +'use strict'; + +jest.autoMockOff(); +var extractAssetResolution = require('../extractAssetResolution'); + +describe('extractAssetResolution', function() { + it('should extract resolution simple case', function() { + var data = extractAssetResolution('test@2x.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 2, + }); + }); + + it('should default resolution to 1', function() { + var data = extractAssetResolution('test.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 1, + }); + }); + + it('should support float', function() { + var data = extractAssetResolution('test@1.1x.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 1.1, + }); + + data = extractAssetResolution('test@.1x.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 0.1, + }); + + data = extractAssetResolution('test@0.2x.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 0.2, + }); + }); +}); diff --git a/react-packager/src/lib/extractAssetResolution.js b/react-packager/src/lib/extractAssetResolution.js new file mode 100644 index 00000000..8fb91afc --- /dev/null +++ b/react-packager/src/lib/extractAssetResolution.js @@ -0,0 +1,28 @@ +'use strict'; + +var path = require('path'); + +function extractAssetResolution(filename) { + var ext = path.extname(filename); + + var re = new RegExp('@([\\d\\.]+)x\\' + ext + '$'); + + var match = filename.match(re); + var resolution; + + if (!(match && match[1])) { + resolution = 1; + } else { + resolution = parseFloat(match[1], 10); + if (isNaN(resolution)) { + resolution = 1; + } + } + + return { + resolution: resolution, + assetName: match ? filename.replace(re, ext) : filename, + }; +} + +module.exports = extractAssetResolution; From f45d03265977ccd9bf6ea8275900ca3e32b5b975 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 16 Apr 2015 13:03:20 -0700 Subject: [PATCH 138/936] [react-packager] Remove links to internal wiki --- .../haste/polyfills/Array.prototype.es6.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js b/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js index 78298a2f..8df5bbcc 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js @@ -23,7 +23,7 @@ get: function() { console.error( 'Your code is broken! Do not iterate over arrays with ' + - 'for...in. See https://fburl.com/31944000 for more information.' + 'for...in.' ); } } @@ -49,9 +49,6 @@ * - Use a regular for loop with index. * - Use one of the array methods: a.forEach, a.map, etc. * - Guard your body of your loop with a `arr.hasOwnProperty(key)` check. - * - * More info: - * https://our.intern.facebook.com/intern/dex/qa/669736606441771/ */ if (this == null) { throw new TypeError( @@ -92,9 +89,6 @@ * - Use a regular for loop with index. * - Use one of the array methods: a.forEach, a.map, etc. * - Guard your body of your loop with a `arr.hasOwnProperty(key)` check. - * - * More info: - * https://our.intern.facebook.com/intern/dex/qa/669736606441771/ */ if (this == null) { throw new TypeError('Array.prototype.find called on null or undefined'); From 4b52f2eacc0b91f9036a563933ee1c1a2a0d51d3 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 17 Apr 2015 04:02:37 -0700 Subject: [PATCH 139/936] [ReactNative] Send batched calls from objc to js every frame + add bridge profiling --- react-packager/src/Server/index.js | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 51d5569f..3c7be043 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -17,6 +17,8 @@ var Activity = require('../Activity'); var AssetServer = require('../AssetServer'); var Promise = require('bluebird'); var _ = require('underscore'); +var exec = require('child_process').exec; +var fs = require('fs'); module.exports = Server; @@ -252,6 +254,42 @@ Server.prototype._processAssetsRequest = function(req, res) { ).done(); }; +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; + } else { + exec('rm ' + dumpName); + exec('open ' + dumpName.replace(/json$/, 'html'), function (error) { + if (error) { + console.error(error); + } + res.end(); + }); + } + }); +}; + Server.prototype.processRequest = function(req, res, next) { var urlObj = url.parse(req.url, true); var pathname = urlObj.pathname; @@ -270,6 +308,9 @@ Server.prototype.processRequest = function(req, res, next) { } else if (pathname.match(/^\/assets\//)) { this._processAssetsRequest(req, res); return; + } else if (pathname.match(/^\/profile\/?$/)) { + this._processProfile(req, res); + return; } else { next(); return; From 2fabfdae1141d40cf07393ec43617b62ca028556 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 17 Apr 2015 09:10:45 -0700 Subject: [PATCH 140/936] [react-packager] Add asset extensions to file watch glob in the project root --- react-packager/src/Server/index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 3c7be043..e0bfeb2f 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -71,13 +71,17 @@ function Server(options) { this._packages = 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', '**/package.json', - ] + ].concat(assetGlobs), }; }); @@ -86,9 +90,7 @@ function Server(options) { opts.assetRoots.map(function(dir) { return { dir: dir, - globs: opts.assetExts.map(function(ext) { - return '**/*.' + ext; - }), + globs: assetGlobs, }; }) ); From 83ee7ad9dd2d6eb4ca68a60f3bfd1179c873ebab Mon Sep 17 00:00:00 2001 From: Peter Cottle Date: Fri, 17 Apr 2015 09:10:20 -0700 Subject: [PATCH 141/936] [ReactNative|Easy] Change watchman too-long error message Summary: @wez Mentioned this in Issue #239 -- right now when watchman takes too long we recommend you run `watchman` from your terminal which actually expects some arguments, so it prints out the following: ``` [pcottle:~/Desktop/react-native:changeErrorMessage]$ watchman { "error": "invalid command (expected an array with some elements!)", "cli_validated": true, "version": "3.0.0" } ``` basically this ends up being more confusing since the command we recommend you run errors out, so lets change it to `watchman version` which at least exists cleanly. I kept the troubleshooting link as https://facebook.github.io/watchman/docs/troubleshooting.html since it sounds like we will update that with the issue people run into in #239 Closes https://github.com/facebook/react-native/pull/825 Github Author: Peter Cottle Test Plan: Imported from GitHub, without a `Test Plan:` line. --- react-packager/src/FileWatcher/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index 9af96c14..b629dfbf 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -73,7 +73,7 @@ function createWatcher(rootConfig) { var rejectTimeout = setTimeout(function() { reject(new Error([ 'Watcher took too long to load', - 'Try running `watchman` from your terminal', + 'Try running `watchman version` from your terminal', 'https://facebook.github.io/watchman/docs/troubleshooting.html', ].join('\n'))); }, MAX_WAIT_TIME); From a68cc06f0b448cb6c2dd7f2c553a3f27ab17e01c Mon Sep 17 00:00:00 2001 From: James Ide Date: Fri, 17 Apr 2015 09:52:57 -0700 Subject: [PATCH 142/936] [Errors] Fix Red Box by fixing providesModule parsing Summary: cc @amasad An error occurred while trying to display the Red Box since loadSourceMap was not included in the JS bundle. This is because node-haste was treating its docblock as a multiline directive which doesn't make sense for `@providesModule`. In loadSourceMap.js's case, the directive's value was parsed as "loadSourceMap -- disabled flow due to mysterious validation errors --". There are two fixes: add a newline under the `@providesModule` directive, and change the module ID code to look at only the first token of the directive. I opted for the latter so we avoid this class of bugs entirely and AFAIK it's nonsensical to have multiple `@providesModule` values anyway. Closes https://github.com/facebook/react-native/pull/866 Github Author: James Ide Test Plan: Run the packager, trigger an error in an app, see the red box now show up again. --- .../src/DependencyResolver/haste/DependencyGraph/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 9257d788..dba6265a 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -437,8 +437,9 @@ DependecyGraph.prototype._processModule = function(modulePath) { .then(function(content) { var moduleDocBlock = docblock.parseAsObject(content); if (moduleDocBlock.providesModule || moduleDocBlock.provides) { - moduleData.id = - moduleDocBlock.providesModule || moduleDocBlock.provides; + moduleData.id = /^(\S*)/.exec( + moduleDocBlock.providesModule || moduleDocBlock.provides + )[1]; // Incase someone wants to require this module via // packageName/path/to/module From ed3aaadc39bcb5b1981d0e379bb32b7f7e0eb960 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 17 Apr 2015 15:20:52 -0700 Subject: [PATCH 143/936] [react-packager] Add more information to deprecated asset requires --- .../__tests__/DependencyGraph-test.js | 7 ++-- .../haste/DependencyGraph/index.js | 1 + .../src/Packager/__tests__/Packager-test.js | 15 +++++++-- react-packager/src/Packager/index.js | 33 ++++++++++++------- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 98ae7eb7..d3f6ce28 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -129,7 +129,8 @@ describe('DependencyGraph', function() { { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset_DEPRECATED: true + isAsset_DEPRECATED: true, + resolution: 1, }, ]); }); @@ -288,7 +289,8 @@ describe('DependencyGraph', function() { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset_DEPRECATED: true + isAsset_DEPRECATED: true, + resolution: 1, }, ]); }); @@ -1350,6 +1352,7 @@ describe('DependencyGraph', function() { path: '/root/foo.png', dependencies: [], isAsset_DEPRECATED: true, + resolution: 1, }, ]); }); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index dba6265a..66534597 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -642,6 +642,7 @@ DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { path: path.resolve(file), isAsset_DEPRECATED: true, dependencies: [], + resolution: extractAssetResolution(file).resolution, }); } }; diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 0c9d4a84..333e0f56 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -52,6 +52,7 @@ describe('Packager', function() { path: '/root/img/img.png', isAsset_DEPRECATED: true, dependencies: [], + resolution: 2, }, { id: 'new_image.png', @@ -98,12 +99,22 @@ describe('Packager', function() { 'source /root/bar.js', '/root/bar.js' ]); + + var imgModule_DEPRECATED = { + isStatic: true, + path: '/root/img/img.png', + uri: 'img', + width: 25, + height: 50, + deprecated: true, + }; + expect(p.addModule.mock.calls[2]).toEqual([ 'lol module.exports = ' + - JSON.stringify({ uri: 'img', isStatic: true}) + + JSON.stringify(imgModule_DEPRECATED) + '; lol', 'module.exports = ' + - JSON.stringify({ uri: 'img', isStatic: true}) + + JSON.stringify(imgModule_DEPRECATED) + ';', '/root/img/img.png' ]); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 74e2ff4c..2b1eb6b1 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -20,6 +20,8 @@ var Activity = require('../Activity'); var declareOpts = require('../lib/declareOpts'); var imageSize = require('image-size'); +var sizeOf = Promise.promisify(imageSize); + var validateOpts = declareOpts({ projectRoots: { type: 'array', @@ -142,7 +144,7 @@ Packager.prototype._transformModule = function(module) { var transform; if (module.isAsset_DEPRECATED) { - transform = Promise.resolve(generateAssetModule_DEPRECATED(module)); + transform = generateAssetModule_DEPRECATED(module); } else if (module.isAsset) { transform = generateAssetModule( module, @@ -175,20 +177,27 @@ Packager.prototype.getGraphDebugInfo = function() { }; function generateAssetModule_DEPRECATED(module) { - var code = 'module.exports = ' + JSON.stringify({ - uri: module.id.replace(/^[^!]+!/, ''), - isStatic: true, - }) + ';'; + return sizeOf(module.path).then(function(dimensions) { + var img = { + isStatic: true, + path: module.path, + uri: module.id.replace(/^[^!]+!/, ''), + width: dimensions.width / module.resolution, + height: dimensions.height / module.resolution, + deprecated: true, + }; - return { - code: code, - sourceCode: code, - sourcePath: module.path, - }; + + var code = 'module.exports = ' + JSON.stringify(img) + ';'; + + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; + }); } -var sizeOf = Promise.promisify(imageSize); - function generateAssetModule(module, relPath) { return sizeOf(module.path).then(function(dimensions) { var img = { From f7e2e4114b1cdef50e1e24fc3671d7ca976f7463 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 17 Apr 2015 17:01:18 -0700 Subject: [PATCH 144/936] [ReactNative] Dim packager output --- getFlowTypeCheckMiddleware.js | 15 +++------------ react-packager/src/Activity/__mocks__/chalk.js | 13 +++++++++++++ react-packager/src/Activity/index.js | 12 +++++++----- 3 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 react-packager/src/Activity/__mocks__/chalk.js diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js index 312da45e..cd910054 100644 --- a/getFlowTypeCheckMiddleware.js +++ b/getFlowTypeCheckMiddleware.js @@ -10,6 +10,7 @@ var chalk = require('chalk'); var exec = require('child_process').exec; +var Activity = require('./react-packager/src/Activity'); var hasWarned = {}; @@ -44,20 +45,10 @@ function getFlowTypeCheckMiddleware(options) { function doFlowTypecheck(res, flowroot, next) { var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; - var start = Date.now(); - // Log start message if flow is slow to let user know something is happening. - var flowSlow = setTimeout( - function() { - console.log(chalk.gray('flow: Running static typechecks.')); - }, - 500 - ); + var eventId = Activity.startEvent('flow static typechecks'); exec(flowCmd, function(flowError, stdout, stderr) { - clearTimeout(flowSlow); + Activity.endEvent(eventId); if (!flowError) { - console.log(chalk.gray( - 'flow: Typechecks passed (' + (Date.now() - start) + 'ms).') - ); return next(); } else { try { diff --git a/react-packager/src/Activity/__mocks__/chalk.js b/react-packager/src/Activity/__mocks__/chalk.js new file mode 100644 index 00000000..2981f979 --- /dev/null +++ b/react-packager/src/Activity/__mocks__/chalk.js @@ -0,0 +1,13 @@ +/** + * 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: function(s) { return s; }, +}; diff --git a/react-packager/src/Activity/index.js b/react-packager/src/Activity/index.js index 05285d0f..8e593f9f 100644 --- a/react-packager/src/Activity/index.js +++ b/react-packager/src/Activity/index.js @@ -8,6 +8,8 @@ */ 'use strict'; +var chalk = require('chalk'); + var COLLECTION_PERIOD = 1000; var _endedEvents = Object.create(null); @@ -132,22 +134,22 @@ function _writeAction(action) { switch (action.action) { case 'startEvent': - console.log( + console.log(chalk.dim( '[' + fmtTime + '] ' + ' ' + action.eventName + data - ); + )); break; case 'endEvent': var startAction = _eventStarts[action.eventId]; var startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : ''; - console.log( + console.log(chalk.dim( '[' + fmtTime + '] ' + ' ' + startAction.eventName + - '(' + (action.tstamp - startAction.tstamp) + 'ms)' + + ' (' + (action.tstamp - startAction.tstamp) + 'ms)' + startData - ); + )); delete _eventStarts[action.eventId]; break; From 2d258f4fa0bd9fa3bd497430d89fefb47134f91b Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Sat, 18 Apr 2015 20:09:03 -0700 Subject: [PATCH 145/936] Print server logs from e2e tests --- packager.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packager.sh b/packager.sh index 93a017c3..f763b9ba 100755 --- a/packager.sh +++ b/packager.sh @@ -7,6 +7,12 @@ # 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. +if [ $REACT_PACKAGER_LOG ]; +then + echo "Logs will be redirected to $REACT_PACKAGER_LOG" + exec &> $REACT_PACKAGER_LOG +fi + ulimit -n 4096 THIS_DIR=$(dirname "$0") From 259894046c271cf70684342a79763d45734da349 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 20 Apr 2015 12:41:29 -0700 Subject: [PATCH 146/936] [ReactNative] Skip flow checks for URLs that are not bundles --- getFlowTypeCheckMiddleware.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js index cd910054..059f0e31 100644 --- a/getFlowTypeCheckMiddleware.js +++ b/getFlowTypeCheckMiddleware.js @@ -16,7 +16,8 @@ var hasWarned = {}; function getFlowTypeCheckMiddleware(options) { return function(req, res, next) { - if (options.skipflow) { + var isBundle = req.url.indexOf('.bundle') !== -1; + if (options.skipflow || !isBundle) { return next(); } if (options.flowroot || options.projectRoots.length === 1) { From d108061db3ae53444fddc485df4b4f8ad903edb9 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 20 Apr 2015 12:03:58 -0700 Subject: [PATCH 147/936] [ReactNative] Update method name on chrome debugger --- debugger.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debugger.html b/debugger.html index d0d4aba5..6e002a22 100644 --- a/debugger.html +++ b/debugger.html @@ -47,7 +47,7 @@ var messageHandlers = { } loadScript(message.url, sendReply.bind(null, null)); }, - 'executeJSCall:method:arguments:callback:': function(message, sendReply) { + 'executeJSCall:method:arguments:context:callback:': function(message, sendReply) { var returnValue = [[], [], [], [], []]; try { if (window && window.require) { From 10cd77b5330e45068a121552ec081ebd75a5b720 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 20 Apr 2015 14:35:45 -0700 Subject: [PATCH 148/936] [ReactNative] Fix Chrome debugger --- debugger.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debugger.html b/debugger.html index 6e002a22..d72e40ea 100644 --- a/debugger.html +++ b/debugger.html @@ -41,13 +41,13 @@ var messageHandlers = { window.localStorage.setItem('sessionID', message.id); window.location.reload(); }, - 'executeApplicationScript:sourceURL:onComplete:': function(message, sendReply) { + 'executeApplicationScript': function(message, sendReply) { for (var key in message.inject) { window[key] = JSON.parse(message.inject[key]); } loadScript(message.url, sendReply.bind(null, null)); }, - 'executeJSCall:method:arguments:context:callback:': function(message, sendReply) { + 'executeJSCall': function(message, sendReply) { var returnValue = [[], [], [], [], []]; try { if (window && window.require) { From 201d65dead480701d9d401bfc8bc7065843d5939 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 20 Apr 2015 15:54:24 -0700 Subject: [PATCH 149/936] [react-packager] Implement Packager::getAssets --- react-packager/src/Packager/Package.js | 11 +++++++ .../src/Packager/__tests__/Package-test.js | 12 +++++++ .../src/Packager/__tests__/Packager-test.js | 8 +++++ react-packager/src/Packager/index.js | 33 ++++++++++--------- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 0f55c8ed..67e31e47 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -17,6 +17,7 @@ module.exports = Package; function Package(sourceMapUrl) { this._finalized = false; this._modules = []; + this._assets = []; this._sourceMapUrl = sourceMapUrl; } @@ -36,6 +37,10 @@ Package.prototype.addModule = function( }); }; +Package.prototype.addAsset = function(asset) { + this._assets.push(asset); +}; + Package.prototype.finalize = function(options) { options = options || {}; if (options.runMainModule) { @@ -49,6 +54,8 @@ Package.prototype.finalize = function(options) { Object.freeze(this._modules); Object.seal(this._modules); + Object.freeze(this._assets); + Object.seal(this._assets); this._finalized = true; }; @@ -146,6 +153,10 @@ Package.prototype.getSourceMap = function(options) { return map; }; +Package.prototype.getAssets = function() { + return this._assets; +}; + Package.prototype._getMappings = function() { var modules = this._modules; diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index 5a7438d2..db596a7b 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -76,6 +76,18 @@ describe('Package', function() { expect(s).toEqual(genSourceMap(p._modules)); }); }); + + describe('getAssets()', function() { + it('should save and return asset objects', function() { + var p = new Package('test_url'); + var asset1 = {}; + var asset2 = {}; + p.addAsset(asset1); + p.addAsset(asset2); + p.finalize(); + expect(p.getAssets()).toEqual([asset1, asset2]); + }); + }); }); function genSourceMap(modules) { diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 333e0f56..c1773216 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -140,6 +140,14 @@ describe('Packager', function() { expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} ]); + + expect(p.addAsset.mock.calls[0]).toEqual([ + imgModule_DEPRECATED + ]); + + expect(p.addAsset.mock.calls[1]).toEqual([ + imgModule + ]); }); }); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 2b1eb6b1..aab55c08 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -100,9 +100,9 @@ Packager.prototype.kill = function() { }; Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { - var transformModule = this._transformModule.bind(this); var ppackage = new Package(sourceMapUrl); + var transformModule = this._transformModule.bind(this, ppackage); var findEventId = Activity.startEvent('find dependencies'); var transformEventId; @@ -140,16 +140,13 @@ Packager.prototype.getDependencies = function(main, isDev) { return this._resolver.getDependencies(main, { dev: isDev }); }; -Packager.prototype._transformModule = function(module) { +Packager.prototype._transformModule = function(ppackage, module) { var transform; if (module.isAsset_DEPRECATED) { - transform = generateAssetModule_DEPRECATED(module); + transform = this.generateAssetModule_DEPRECATED(ppackage, module); } else if (module.isAsset) { - transform = generateAssetModule( - module, - getPathRelativeToRoot(this._projectRoots, module.path) - ); + transform = this.generateAssetModule(ppackage, module); } else { transform = this._transformer.loadFileAndTransform( path.resolve(module.path) @@ -166,17 +163,11 @@ Packager.prototype._transformModule = function(module) { }); }; - -function verifyRootExists(root) { - // Verify that the root exists. - assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); -} - Packager.prototype.getGraphDebugInfo = function() { return this._resolver.getDebugInfo(); }; -function generateAssetModule_DEPRECATED(module) { +Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { return sizeOf(module.path).then(function(dimensions) { var img = { isStatic: true, @@ -187,6 +178,7 @@ function generateAssetModule_DEPRECATED(module) { deprecated: true, }; + ppackage.addAsset(img); var code = 'module.exports = ' + JSON.stringify(img) + ';'; @@ -196,9 +188,11 @@ function generateAssetModule_DEPRECATED(module) { sourcePath: module.path, }; }); -} +}; + +Packager.prototype.generateAssetModule = function(ppackage, module) { + var relPath = getPathRelativeToRoot(this._projectRoots, module.path); -function generateAssetModule(module, relPath) { return sizeOf(module.path).then(function(dimensions) { var img = { isStatic: true, @@ -208,6 +202,8 @@ function generateAssetModule(module, relPath) { height: dimensions.height / module.resolution, }; + ppackage.addAsset(img); + var code = 'module.exports = ' + JSON.stringify(img) + ';'; return { @@ -231,4 +227,9 @@ function getPathRelativeToRoot(roots, absPath) { ); } +function verifyRootExists(root) { + // Verify that the root exists. + assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); +} + module.exports = Packager; From f91f7084ec00823ab50fecc714d7025f200837e5 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 21 Apr 2015 10:57:53 -0700 Subject: [PATCH 150/936] [react-packager] Allow json files as modules --- .../DependencyResolver/ModuleDescriptor.js | 2 + .../__tests__/DependencyGraph-test.js | 40 +++++++++++++++++++ .../haste/DependencyGraph/index.js | 13 +++++- .../src/Packager/__tests__/Packager-test.js | 18 ++++++++- react-packager/src/Packager/index.js | 15 +++++++ react-packager/src/Server/index.js | 2 +- 6 files changed, 86 insertions(+), 4 deletions(-) diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index 3cdfa1c6..90db1c4a 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -45,6 +45,8 @@ function ModuleDescriptor(fields) { this.altId = fields.altId; + this.isJSON = fields.isJSON; + this._fields = fields; } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index d3f6ce28..cda717e8 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -101,6 +101,46 @@ describe('DependencyGraph', function() { }); }); + pit('should get json dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'package' + }), + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./a.json")' + ].join('\n'), + 'a.json': JSON.stringify({}), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { + id: 'index', + altId: 'package/index', + path: '/root/index.js', + dependencies: ['./a.json'] + }, + { + id: 'package/a.json', + isJSON: true, + path: '/root/a.json', + dependencies: [] + }, + ]); + }); + }); + pit('should get dependencies with deprecated assets', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 66534597..6afb5f25 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -66,7 +66,7 @@ function DependecyGraph(options) { this._debugUpdateEvents = []; this._moduleExtPattern = new RegExp( - '\.(' + ['js'].concat(this._assetExts).join('|') + ')$' + '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' ); // Kick off the search process to precompute the dependency graph. @@ -259,7 +259,7 @@ DependecyGraph.prototype.resolveDependency = function( } // JS modules can be required without extensios. - if (!this._isFileAsset(modulePath)) { + if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { modulePath = withExtJs(modulePath); } @@ -432,6 +432,15 @@ DependecyGraph.prototype._processModule = function(modulePath) { return Promise.resolve(module); } + if (extname(modulePath) === 'json') { + moduleData.id = this._lookupName(modulePath); + moduleData.isJSON = true; + moduleData.dependencies = []; + module = new ModuleDescriptor(moduleData); + this._updateGraphWithModule(module); + return Promise.resolve(module); + } + var self = this; return readFile(modulePath, 'utf8') .then(function(content) { diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index c1773216..763e6dd6 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -43,6 +43,10 @@ describe('Packager', function() { }; }); + require('fs').readFile.mockImpl(function(file, callback) { + callback(null, '{"json":true}'); + }); + var packager = new Packager({projectRoots: ['/root']}); var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, @@ -60,7 +64,13 @@ describe('Packager', function() { isAsset: true, resolution: 2, dependencies: [] - } + }, + { + id: 'package/file.json', + path: '/root/file.json', + isJSON: true, + dependencies: [], + }, ]; getDependencies.mockImpl(function() { @@ -137,6 +147,12 @@ describe('Packager', function() { '/root/img/new_image.png' ]); + expect(p.addModule.mock.calls[4]).toEqual([ + 'lol module.exports = {"json":true}; lol', + 'module.exports = {"json":true};', + '/root/file.json' + ]); + expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} ]); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index aab55c08..c651dde7 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -21,6 +21,7 @@ var declareOpts = require('../lib/declareOpts'); var imageSize = require('image-size'); var sizeOf = Promise.promisify(imageSize); +var readFile = Promise.promisify(fs.readFile); var validateOpts = declareOpts({ projectRoots: { @@ -147,6 +148,8 @@ Packager.prototype._transformModule = function(ppackage, module) { 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) @@ -206,6 +209,18 @@ Packager.prototype.generateAssetModule = function(ppackage, module) { var code = 'module.exports = ' + JSON.stringify(img) + ';'; + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; + }); +}; + +function generateJSONModule(module) { + return readFile(module.path).then(function(data) { + var code = 'module.exports = ' + data.toString('utf8') + ';'; + return { code: code, sourceCode: code, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index e0bfeb2f..525636cb 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -80,7 +80,7 @@ function Server(options) { dir: dir, globs: [ '**/*.js', - '**/package.json', + '**/*.json', ].concat(assetGlobs), }; }); From 2645ffb69a43c0b2848539c6c26a064c4e9362b7 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 21 Apr 2015 10:59:29 -0700 Subject: [PATCH 151/936] [react-packager] bump watchman watch timeout to 10 seconds --- react-packager/src/FileWatcher/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index b629dfbf..38ad19bf 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -26,7 +26,7 @@ var detectingWatcherClass = new Promise(function(resolve) { module.exports = FileWatcher; -var MAX_WAIT_TIME = 3000; +var MAX_WAIT_TIME = 10000; // Singleton var fileWatcher = null; From 749c81842c93347b3f97a915a3671e1750bac133 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 21 Apr 2015 10:58:20 -0700 Subject: [PATCH 152/936] [react-packager] Add jpe?g to asset extensions --- packager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packager.js b/packager.js index 23da3a78..212e17f7 100644 --- a/packager.js +++ b/packager.js @@ -200,6 +200,7 @@ function getAppMiddleware(options) { cacheVersion: '2', transformModulePath: require.resolve('./transformer.js'), assetRoots: options.assetRoots, + assetExts: ['png', 'jpeg', 'jpg'] }); } From d4b5f2e51428adc101a2a3e6a401e9171079c11b Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 22 Apr 2015 11:04:24 -0700 Subject: [PATCH 153/936] [react-packager] Additional data to asset modules --- .../AssetServer/__tests__/AssetServer-test.js | 145 ++++++++++++------ react-packager/src/AssetServer/index.js | 63 ++++++-- .../__tests__/DependencyGraph-test.js | 2 +- .../haste/DependencyGraph/index.js | 6 +- .../src/Packager/__tests__/Packager-test.js | 31 +++- react-packager/src/Packager/index.js | 21 ++- react-packager/src/Server/index.js | 9 +- react-packager/src/__mocks__/fs.js | 12 +- .../__tests__/extractAssetResolution-test.js | 24 ++- ...tResolution.js => getAssetDataFromName.js} | 9 +- 10 files changed, 227 insertions(+), 95 deletions(-) rename react-packager/src/lib/{extractAssetResolution.js => getAssetDataFromName.js} (63%) diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index eede72c0..94dba8cf 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -3,6 +3,7 @@ jest .autoMockOff() .mock('../../lib/declareOpts') + .mock('crypto') .mock('fs'); var fs = require('fs'); @@ -10,63 +11,65 @@ var AssetServer = require('../'); var Promise = require('bluebird'); describe('AssetServer', function() { - pit('should work for the simple case', function() { - var server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); + describe('assetServer.get', function() { + pit('should work for the simple case', function() { + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b.png': 'b image', - 'b@2x.png': 'b2 image', + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b.png': 'b image', + 'b@2x.png': 'b2 image', + } } - } - }); + }); - 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'); + 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'); + }); }); }); - }); - pit.only('should pick the bigger one', function() { - var server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); + pit('should pick the bigger one', function() { + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b@1x.png': 'b1 image', - 'b@2x.png': 'b2 image', - 'b@4x.png': 'b4 image', - 'b@4.5x.png': 'b4.5 image', + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b@1x.png': 'b1 image', + 'b@2x.png': 'b2 image', + 'b@4x.png': 'b4 image', + 'b@4.5x.png': 'b4.5 image', + } } - } + }); + + return server.get('imgs/b@3x.png').then(function(data) { + expect(data).toBe('b4 image'); + }); }); - return server.get('imgs/b@3x.png').then(function(data) { - expect(data).toBe('b4 image'); - }); - }); + pit('should support multiple project roots', function() { + var server = new AssetServer({ + projectRoots: ['/root', '/root2'], + assetExts: ['png'], + }); - pit('should support multiple project roots', function() { - var server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b.png': 'b image', + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b.png': 'b image', + }, }, 'root2': { 'newImages': { @@ -75,11 +78,53 @@ 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(function(data) { + expect(data).toBe('b1 image'); + }); + }); + }); + + describe('assetSerer.getAssetData', function() { + pit('should get assetData', function() { + var hash = { + update: jest.genMockFn(), + digest: jest.genMockFn(), + }; + + hash.digest.mockImpl(function() { + return 'wow such hash'; + }); + require('crypto').createHash.mockImpl(function() { + return hash; + }); + + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); + + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b@1x.png': 'b1 image', + 'b@2x.png': 'b2 image', + 'b@4x.png': 'b4 image', + 'b@4.5x.png': 'b4.5 image', + } + } + }); + + return server.getAssetData('imgs/b.png').then(function(data) { + expect(hash.update.mock.calls.length).toBe(4); + expect(data).toEqual({ + type: 'png', + name: 'b', + scales: [1, 2, 4, 4.5], + hash: 'wow such hash', + }); + }); }); }); }); diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index bdabafff..6f07dd01 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -9,10 +9,11 @@ 'use strict'; var declareOpts = require('../lib/declareOpts'); -var extractAssetResolution = require('../lib/extractAssetResolution'); +var getAssetDataFromName = require('../lib/getAssetDataFromName'); var path = require('path'); var Promise = require('bluebird'); var fs = require('fs'); +var crypto = require('crypto'); var lstat = Promise.promisify(fs.lstat); var readDir = Promise.promisify(fs.readdir); @@ -44,11 +45,11 @@ function AssetServer(options) { * * 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 resolutions in this directory + * 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.get = function(assetPath) { +AssetServer.prototype._getAssetRecord = function(assetPath) { var filename = path.basename(assetPath); return findRoot( @@ -60,13 +61,7 @@ AssetServer.prototype.get = function(assetPath) { readDir(dir), ]; }).spread(function(dir, files) { - // Easy case. File exactly what the client requested. - var index = files.indexOf(filename); - if (index > -1) { - return readFile(path.join(dir, filename)); - } - - var assetData = extractAssetResolution(filename); + var assetData = getAssetDataFromName(filename); var map = buildAssetMap(dir, files); var record = map[assetData.assetName]; @@ -74,8 +69,15 @@ AssetServer.prototype.get = function(assetPath) { throw new Error('Asset not found'); } - for (var i = 0; i < record.resolutions.length; i++) { - if (record.resolutions[i] >= assetData.resolution) { + 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]); } } @@ -84,6 +86,33 @@ AssetServer.prototype.get = function(assetPath) { }); }; +AssetServer.prototype.getAssetData = function(assetPath) { + var nameData = getAssetDataFromName(assetPath); + var data = { + name: nameData.name, + type: 'png', + }; + + return this._getAssetRecord(assetPath).then(function(record) { + data.scales = record.scales; + + return Promise.all( + record.files.map(function(file) { + return lstat(file); + }) + ); + }).then(function(stats) { + var hash = crypto.createHash('md5'); + + stats.forEach(function(stat) { + hash.update(stat.mtime.getTime().toString()); + }); + + data.hash = hash.digest('hex'); + return data; + }); +}; + function findRoot(roots, dir) { return Promise.some( roots.map(function(root) { @@ -105,26 +134,26 @@ function findRoot(roots, dir) { } function buildAssetMap(dir, files) { - var assets = files.map(extractAssetResolution); + 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] = { - resolutions: [], + scales: [], files: [], }; } var insertIndex; - var length = record.resolutions.length; + var length = record.scales.length; for (insertIndex = 0; insertIndex < length; insertIndex++) { - if (asset.resolution < record.resolutions[insertIndex]) { + if (asset.resolution < record.scales[insertIndex]) { break; } } - record.resolutions.splice(insertIndex, 0, asset.resolution); + record.scales.splice(insertIndex, 0, asset.resolution); record.files.splice(insertIndex, 0, path.join(dir, file)); }); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index cda717e8..9cb08122 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -14,7 +14,7 @@ jest .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') - .dontMock('../../../../lib/extractAssetResolution') + .dontMock('../../../../lib/getAssetDataFromName') .setMock('../../../ModuleDescriptor', function(data) {return data;}); describe('DependencyGraph', function() { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 6afb5f25..08a4b513 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -18,7 +18,7 @@ var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); -var extractAssetResolution = require('../../../lib/extractAssetResolution'); +var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -422,7 +422,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { var module; if (this._assetExts.indexOf(extname(modulePath)) > -1) { - var assetData = extractAssetResolution(this._lookupName(modulePath)); + var assetData = getAssetDataFromName(this._lookupName(modulePath)); moduleData.id = assetData.assetName; moduleData.resolution = assetData.resolution; moduleData.isAsset = true; @@ -651,7 +651,7 @@ DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { path: path.resolve(file), isAsset_DEPRECATED: true, dependencies: [], - resolution: extractAssetResolution(file).resolution, + resolution: getAssetDataFromName(file).resolution, }); } }; diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 763e6dd6..8e1420a3 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -43,11 +43,20 @@ describe('Packager', function() { }; }); + require('fs').readFile.mockImpl(function(file, callback) { callback(null, '{"json":true}'); }); - var packager = new Packager({projectRoots: ['/root']}); + var assetServer = { + getAssetData: jest.genMockFn(), + }; + + var packager = new Packager({ + projectRoots: ['/root'], + assetServer: assetServer, + }); + var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, @@ -97,6 +106,15 @@ describe('Packager', function() { cb(null, { width: 50, height: 100 }); }); + assetServer.getAssetData.mockImpl(function() { + return { + scales: [1,2,3], + hash: 'i am a hash', + name: 'img', + type: 'png', + }; + }); + return packager.package('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0]).toEqual([ @@ -111,6 +129,7 @@ describe('Packager', function() { ]); var imgModule_DEPRECATED = { + __packager_asset: true, isStatic: true, path: '/root/img/img.png', uri: 'img', @@ -130,11 +149,15 @@ describe('Packager', function() { ]); var imgModule = { - isStatic: true, - path: '/root/img/new_image.png', - uri: 'assets/img/new_image.png', + __packager_asset: true, + fileSystemLocation: '/root/img', + httpServerLocation: '/assets/img', width: 25, height: 50, + scales: [1, 2, 3], + hash: 'i am a hash', + name: 'img', + type: 'png', }; expect(p.addModule.mock.calls[3]).toEqual([ diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index c651dde7..8563e272 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -67,6 +67,10 @@ var validateOpts = declareOpts({ type: 'object', required: true, }, + assetServer: { + type: 'object', + required: true, + } }); function Packager(options) { @@ -94,6 +98,7 @@ function Packager(options) { }); this._projectRoots = opts.projectRoots; + this._assetServer = opts.assetServer; } Packager.prototype.kill = function() { @@ -173,6 +178,7 @@ Packager.prototype.getGraphDebugInfo = function() { 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(/^[^!]+!/, ''), @@ -196,13 +202,20 @@ Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { Packager.prototype.generateAssetModule = function(ppackage, module) { var relPath = getPathRelativeToRoot(this._projectRoots, module.path); - return sizeOf(module.path).then(function(dimensions) { + return Promise.all([ + sizeOf(module.path), + this._assetServer.getAssetData(relPath), + ]).spread(function(dimensions, assetData) { var img = { - isStatic: true, - path: module.path, //TODO(amasad): this should be path inside tar file. - uri: path.join('assets', relPath), + __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); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 525636cb..79022b21 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -100,15 +100,16 @@ function Server(options) { ? FileWatcher.createDummyWatcher() : new FileWatcher(watchRootConfigs); - var packagerOpts = Object.create(opts); - packagerOpts.fileWatcher = this._fileWatcher; - this._packager = new Packager(packagerOpts); - this._assetServer = new AssetServer({ projectRoots: opts.projectRoots, assetExts: opts.assetExts, }); + var packagerOpts = Object.create(opts); + packagerOpts.fileWatcher = this._fileWatcher; + packagerOpts.assetServer = this._assetServer; + this._packager = new Packager(packagerOpts); + var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); diff --git a/react-packager/src/__mocks__/fs.js b/react-packager/src/__mocks__/fs.js index 0ea13d15..d0e08a2f 100644 --- a/react-packager/src/__mocks__/fs.js +++ b/react-packager/src/__mocks__/fs.js @@ -67,6 +67,12 @@ fs.lstat.mockImpl(function(filepath, callback) { return callback(e); } + var mtime = { + getTime: function() { + return Math.ceil(Math.random() * 10000000); + } + }; + if (node && typeof node === 'object' && node.SYMLINK == null) { callback(null, { isDirectory: function() { @@ -74,7 +80,8 @@ fs.lstat.mockImpl(function(filepath, callback) { }, isSymbolicLink: function() { return false; - } + }, + mtime: mtime, }); } else { callback(null, { @@ -86,7 +93,8 @@ fs.lstat.mockImpl(function(filepath, callback) { return true; } return false; - } + }, + mtime: mtime, }); } }); diff --git a/react-packager/src/lib/__tests__/extractAssetResolution-test.js b/react-packager/src/lib/__tests__/extractAssetResolution-test.js index ad5ac3fb..d0309ca6 100644 --- a/react-packager/src/lib/__tests__/extractAssetResolution-test.js +++ b/react-packager/src/lib/__tests__/extractAssetResolution-test.js @@ -1,42 +1,52 @@ 'use strict'; jest.autoMockOff(); -var extractAssetResolution = require('../extractAssetResolution'); +var getAssetDataFromName = require('../getAssetDataFromName'); -describe('extractAssetResolution', function() { +describe('getAssetDataFromName', function() { it('should extract resolution simple case', function() { - var data = extractAssetResolution('test@2x.png'); + var data = getAssetDataFromName('test@2x.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 2, + type: 'png', + name: 'test', }); }); it('should default resolution to 1', function() { - var data = extractAssetResolution('test.png'); + var data = getAssetDataFromName('test.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 1, + type: 'png', + name: 'test', }); }); it('should support float', function() { - var data = extractAssetResolution('test@1.1x.png'); + var data = getAssetDataFromName('test@1.1x.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 1.1, + type: 'png', + name: 'test', }); - data = extractAssetResolution('test@.1x.png'); + data = getAssetDataFromName('test@.1x.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 0.1, + type: 'png', + name: 'test', }); - data = extractAssetResolution('test@0.2x.png'); + data = getAssetDataFromName('test@0.2x.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 0.2, + type: 'png', + name: 'test', }); }); }); diff --git a/react-packager/src/lib/extractAssetResolution.js b/react-packager/src/lib/getAssetDataFromName.js similarity index 63% rename from react-packager/src/lib/extractAssetResolution.js rename to react-packager/src/lib/getAssetDataFromName.js index 8fb91afc..c4848fd1 100644 --- a/react-packager/src/lib/extractAssetResolution.js +++ b/react-packager/src/lib/getAssetDataFromName.js @@ -2,7 +2,7 @@ var path = require('path'); -function extractAssetResolution(filename) { +function getAssetDataFromName(filename) { var ext = path.extname(filename); var re = new RegExp('@([\\d\\.]+)x\\' + ext + '$'); @@ -19,10 +19,13 @@ function extractAssetResolution(filename) { } } + var assetName = match ? filename.replace(re, ext) : filename; return { resolution: resolution, - assetName: match ? filename.replace(re, ext) : filename, + assetName: assetName, + type: ext.slice(1), + name: path.basename(assetName, ext) }; } -module.exports = extractAssetResolution; +module.exports = getAssetDataFromName; From 4e96edb8b1fb04fb9edc4084cd23e4aa12d73628 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 22 Apr 2015 15:43:11 -0700 Subject: [PATCH 154/936] [ReactNative] console.error shows RedBox with pretty stack trace --- .../haste/polyfills/console.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index 91fb970f..4f513f45 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -23,7 +23,7 @@ log: 1, info: 2, warn: 3, - error: 4 + error: 4, }; function setupConsole(global) { @@ -35,8 +35,10 @@ function getNativeLogFunction(level) { return function() { var str = Array.prototype.map.call(arguments, function(arg) { - if (arg == null) { - return arg === null ? 'null' : 'undefined'; + if (arg === undefined) { + return 'undefined'; + } else if (arg === null) { + return 'null'; } else if (typeof arg === 'string') { return '"' + arg + '"'; } else { @@ -48,14 +50,18 @@ if (typeof arg.toString === 'function') { try { return arg.toString(); - } catch (E) { - return 'unknown'; - } + } catch (E) {} } + return '["' + typeof arg + '" failed to stringify]'; } } }).join(', '); global.nativeLoggingHook(str, level); + if (global.reportException && level === LOG_LEVELS.error) { + var error = new Error(str); + error.framesToPop = 1; + global.reportException(error); + } }; } From 98c10d72d2395b208fad859b0d918c15845b6c5e Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 22 Apr 2015 16:30:27 -0700 Subject: [PATCH 155/936] [ReactNative] Backport packager logs redirect --- packager.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packager.sh b/packager.sh index 93a017c3..f763b9ba 100755 --- a/packager.sh +++ b/packager.sh @@ -7,6 +7,12 @@ # 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. +if [ $REACT_PACKAGER_LOG ]; +then + echo "Logs will be redirected to $REACT_PACKAGER_LOG" + exec &> $REACT_PACKAGER_LOG +fi + ulimit -n 4096 THIS_DIR=$(dirname "$0") From 240424714ecf10192fe2285a6418569da5bb83b1 Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Thu, 23 Apr 2015 10:42:00 -0700 Subject: [PATCH 156/936] [ReactNative] Disable console.error => redboxes to unwedge Android --- .../src/DependencyResolver/haste/polyfills/console.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index 4f513f45..11464cce 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -60,7 +60,11 @@ if (global.reportException && level === LOG_LEVELS.error) { var error = new Error(str); error.framesToPop = 1; - global.reportException(error); + // TODO(sahrens): re-enable this when we have a way to turn + // it off by default for MAdMan/Android, and/or all + // consumers of console.error() are fixed, including + // CatalystErrorHandlerModuleTestCase + // global.reportException(error); } }; } From 91b320cf35029931cb17cbab44fcab6254e1e1a4 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 23 Apr 2015 11:30:39 -0700 Subject: [PATCH 157/936] [react-packager] Fix jest tests --- .../AssetServer/__tests__/AssetServer-test.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 94dba8cf..ba804b5f 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -1,16 +1,23 @@ 'use strict'; jest - .autoMockOff() - .mock('../../lib/declareOpts') - .mock('crypto') - .mock('fs'); + .dontMock('path') + .dontMock('../../lib/getAssetDataFromName') + .dontMock('../'); -var fs = require('fs'); -var AssetServer = require('../'); var Promise = require('bluebird'); describe('AssetServer', function() { + var AssetServer; + var crypto; + var fs; + + beforeEach(function() { + AssetServer = require('../'); + crypto = require('crypto'); + fs = require('fs'); + }); + describe('assetServer.get', function() { pit('should work for the simple case', function() { var server = new AssetServer({ @@ -96,7 +103,7 @@ describe('AssetServer', function() { hash.digest.mockImpl(function() { return 'wow such hash'; }); - require('crypto').createHash.mockImpl(function() { + crypto.createHash.mockImpl(function() { return hash; }); From f635db3b6d86d9e139fe0a84368ce6af94478ab3 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 23 Apr 2015 11:44:02 -0700 Subject: [PATCH 158/936] [react-packager] Change uri to name --- react-packager/src/Packager/__tests__/Packager-test.js | 2 +- react-packager/src/Packager/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 8e1420a3..3040e098 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -132,7 +132,7 @@ describe('Packager', function() { __packager_asset: true, isStatic: true, path: '/root/img/img.png', - uri: 'img', + name: 'img', width: 25, height: 50, deprecated: true, diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 8563e272..79dd6f23 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -181,7 +181,7 @@ Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { __packager_asset: true, isStatic: true, path: module.path, - uri: module.id.replace(/^[^!]+!/, ''), + name: module.id.replace(/^[^!]+!/, ''), width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, deprecated: true, From 0b3ca1c53e0f05b7c2a3dd14b08be17c89a05c1f Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Thu, 23 Apr 2015 11:34:25 -0700 Subject: [PATCH 159/936] [ReactNative] Back out D2014163 entirely --- .../haste/polyfills/console.js | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index 11464cce..91fb970f 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -23,7 +23,7 @@ log: 1, info: 2, warn: 3, - error: 4, + error: 4 }; function setupConsole(global) { @@ -35,10 +35,8 @@ function getNativeLogFunction(level) { return function() { var str = Array.prototype.map.call(arguments, function(arg) { - if (arg === undefined) { - return 'undefined'; - } else if (arg === null) { - return 'null'; + if (arg == null) { + return arg === null ? 'null' : 'undefined'; } else if (typeof arg === 'string') { return '"' + arg + '"'; } else { @@ -50,22 +48,14 @@ if (typeof arg.toString === 'function') { try { return arg.toString(); - } catch (E) {} + } catch (E) { + return 'unknown'; + } } - return '["' + typeof arg + '" failed to stringify]'; } } }).join(', '); global.nativeLoggingHook(str, level); - if (global.reportException && level === LOG_LEVELS.error) { - var error = new Error(str); - error.framesToPop = 1; - // TODO(sahrens): re-enable this when we have a way to turn - // it off by default for MAdMan/Android, and/or all - // consumers of console.error() are fixed, including - // CatalystErrorHandlerModuleTestCase - // global.reportException(error); - } }; } From 2115b67bd90b9e4ce9c15a230b555832475e9440 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Thu, 23 Apr 2015 17:04:11 -0700 Subject: [PATCH 160/936] [ReactNative][madman] Reverted D2014357 --- react-packager/src/Packager/__tests__/Packager-test.js | 2 +- react-packager/src/Packager/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 3040e098..8e1420a3 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -132,7 +132,7 @@ describe('Packager', function() { __packager_asset: true, isStatic: true, path: '/root/img/img.png', - name: 'img', + uri: 'img', width: 25, height: 50, deprecated: true, diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 79dd6f23..8563e272 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -181,7 +181,7 @@ Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { __packager_asset: true, isStatic: true, path: module.path, - name: module.id.replace(/^[^!]+!/, ''), + uri: module.id.replace(/^[^!]+!/, ''), width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, deprecated: true, From 04da81d4d5ef6a26a72b4133467d0498739c751d Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 24 Apr 2015 10:31:28 -0700 Subject: [PATCH 161/936] [ReactNative] Fix launchEditor script --- launchEditor.js | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/launchEditor.js b/launchEditor.js index cf89ed4f..b572b5cb 100644 --- a/launchEditor.js +++ b/launchEditor.js @@ -8,23 +8,21 @@ */ 'use strict'; +var chalk = require('chalk'); var fs = require('fs'); -var spawn = require('child_process').spawn; +var exec = require('child_process').exec; -var firstLaunch = true; - -function guessEditor() { - if (firstLaunch) { - console.log('When you see Red Box with stack trace, you can click any ' + - 'stack frame to jump to the source file. The packager will launch your ' + - 'editor of choice. It will first look at REACT_EDITOR environment ' + - 'variable, then at EDITOR. To set it up, you can add something like ' + - 'REACT_EDITOR=atom to your .bashrc.'); - firstLaunch = false; - } - - var editor = process.env.REACT_EDITOR || process.env.EDITOR || 'subl'; - return editor; +function printInstructions(title) { + console.log([ + '', + chalk.bgBlue.white.bold(' ' + title + ' '), + ' When you see Red Box with stack trace, you can click any ', + ' stack frame to jump to the source file. The packager will launch your ', + ' editor of choice. It will first look at REACT_EDITOR environment ', + ' variable, then at EDITOR. To set it up, you can add something like ', + ' REACT_EDITOR=atom to your .bashrc.', + '' + ].join('\n')); } function launchEditor(fileName, lineNumber) { @@ -37,9 +35,18 @@ function launchEditor(fileName, lineNumber) { argument += ':' + lineNumber; } - var editor = guessEditor(); - console.log('Opening ' + fileName + ' with ' + editor); - spawn(editor, [argument], { stdio: ['pipe', 'pipe', process.stderr] }); + var editor = process.env.REACT_EDITOR || process.env.EDITOR; + if (editor) { + console.log('Opening ' + chalk.underline(fileName) + ' with ' + chalk.bold(editor)); + exec(editor + ' ' + argument, function(error) { + if (error) { + console.log(chalk.red(error.message)); + printInstructions('How to fix'); + } + }); + } else { + printInstructions('PRO TIP'); + } } module.exports = launchEditor; From 3a012f5d465609ccc5d1dbab6cd58f7d554eb988 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 24 Apr 2015 14:57:24 -0700 Subject: [PATCH 162/936] [ReactNative] Fix reloading in debug mode sometimes crashes packager --- webSocketProxy.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/webSocketProxy.js b/webSocketProxy.js index 8223bbf2..f8636213 100644 --- a/webSocketProxy.js +++ b/webSocketProxy.js @@ -34,7 +34,12 @@ function attachToServer(server, path) { ws.on('message', function(message) { allClientsExcept(ws).forEach(function(cn) { - cn.send(message); + try { + // Sometimes this call throws 'not opened' + cn.send(message); + } catch(e) { + console.warn('WARN: ' + e.message); + } }); }); }); From 09f6ad84fe093d461fd06a513b9865f83f085fcc Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 27 Apr 2015 16:13:47 -0700 Subject: [PATCH 163/936] temporarily disable flow check in packager while we figure out versioning issues. cc @gabelevi, @bhosmer --- getFlowTypeCheckMiddleware.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js index 059f0e31..c7f3e2b1 100644 --- a/getFlowTypeCheckMiddleware.js +++ b/getFlowTypeCheckMiddleware.js @@ -13,11 +13,12 @@ var exec = require('child_process').exec; 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 (options.skipflow || !isBundle) { + if (DISABLE_FLOW_CHECK || options.skipflow || !isBundle) { return next(); } if (options.flowroot || options.projectRoots.length === 1) { From 876f1084afda1f011c5751df9825fa2175e923f0 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 27 Apr 2015 19:55:24 -0700 Subject: [PATCH 164/936] [ReactNative] temp disable flow check in packager for OSS --- packager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 212e17f7..48552d93 100644 --- a/packager.js +++ b/packager.js @@ -213,7 +213,8 @@ function runServer( .use(openStackFrameInEditor) .use(getDevToolsLauncher(options)) .use(statusPageMiddleware) - .use(getFlowTypeCheckMiddleware(options)) + // Temporarily disable flow check until it's more stable + //.use(getFlowTypeCheckMiddleware(options)) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { From fca69ad9d5f7dd973801ca5526fab3f6e23514df Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 27 Apr 2015 19:33:01 -0700 Subject: [PATCH 165/936] [ReactNative] Bump watchman timeout to 25s --- react-packager/src/FileWatcher/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index 38ad19bf..cd1a28e5 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -26,7 +26,7 @@ var detectingWatcherClass = new Promise(function(resolve) { module.exports = FileWatcher; -var MAX_WAIT_TIME = 10000; +var MAX_WAIT_TIME = 25000; // Singleton var fileWatcher = null; From 0fde2c25838a32985161f9c662a5a0b3c65af550 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Tue, 28 Apr 2015 15:55:42 -0700 Subject: [PATCH 166/936] JS: Use Object.defineProperty for Array Polyfills --- .../haste/polyfills/Array.prototype.es6.js | 82 +++++-------------- 1 file changed, 20 insertions(+), 62 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js b/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js index 8df5bbcc..80e62f05 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js @@ -3,53 +3,14 @@ * * @provides Array.prototype.es6 * @polyfill - * @requires __DEV__ */ /*eslint-disable */ /*jslint bitwise: true */ -(function (undefined) { - if (__DEV__) { - // Define DEV-only setter that blows up when someone incorrectly - // iterates over arrays. - try { - Object.defineProperty && Object.defineProperty( - Array.prototype, - '__ARRAY_ENUMERATION_GUARD__', - { - configurable: true, - enumerable: true, - get: function() { - console.error( - 'Your code is broken! Do not iterate over arrays with ' + - 'for...in.' - ); - } - } - ); - } catch (e) { - // Nothing - } - } - +(function(undefined) { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex function findIndex(predicate, context) { - /** - * Why am I seeing this "findIndex" method as a value in my array!? - * - * We polyfill the "findIndex" method -- called like - * `[1, 2, 3].findIndex(1)` -- for older browsers. A side effect of the way - * we do that is that the method is enumerable. If you were incorrectly - * iterating over your array using the object property iterator syntax - * `for (key in obj)` you will see the method name "findIndex" as a key. - * - * To fix your code please do one of the following: - * - * - Use a regular for loop with index. - * - Use one of the array methods: a.forEach, a.map, etc. - * - Guard your body of your loop with a `arr.hasOwnProperty(key)` check. - */ if (this == null) { throw new TypeError( 'Array.prototype.findIndex called on null or undefined' @@ -69,32 +30,29 @@ } if (!Array.prototype.findIndex) { - Array.prototype.findIndex = findIndex; + Object.defineProperty(Array.prototype, 'findIndex', { + enumerable: false, + writable: true, + configurable: true, + value: findIndex + }); } // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find if (!Array.prototype.find) { - Array.prototype.find = function(predicate, context) { - /** - * Why am I seeing this "find" method as a value in my array!? - * - * We polyfill the "find" method -- called like - * `[1, 2, 3].find(1)` -- for older browsers. A side effect of the way - * we do that is that the method is enumerable. If you were incorrectly - * iterating over your array using the object property iterator syntax - * `for (key in obj)` you will see the method name "find" as a key. - * - * To fix your code please do one of the following: - * - * - Use a regular for loop with index. - * - Use one of the array methods: a.forEach, a.map, etc. - * - Guard your body of your loop with a `arr.hasOwnProperty(key)` check. - */ - if (this == null) { - throw new TypeError('Array.prototype.find called on null or undefined'); + Object.defineProperty(Array.prototype, 'find', { + enumerable: false, + writable: true, + configurable: true, + value: function(predicate, context) { + if (this == null) { + throw new TypeError( + 'Array.prototype.find called on null or undefined' + ); + } + var index = findIndex.call(this, predicate, context); + return index === -1 ? undefined : this[index]; } - var index = findIndex.call(this, predicate, context); - return index === -1 ? undefined : this[index]; - }; + }); } })(); From 830646529a41e41d9cd39b87e4531168482e5778 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 1 May 2015 17:05:24 -0700 Subject: [PATCH 167/936] [react-packager] Combine source maps coming from transformer Summary: @public Fixes [#393](https://github.com/facebook/react-native/issues/393). Currently the transformer assumes line-preserving compilers and defaults to a super-fast source map generation process. However, we need to support compilers that aren't preserving lines. I had feared this wuold slow down the server but I came about a little known peace of the spec that defines an "indexed source map" just for the purpose of concating files: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit Test Plan: 1. runJestTests.sh 2. run server and click around example apps 3. add a custom transporter like babel 4. add a custom file and a debugger statement 5. debug in chrome and make sure it works redbox still works --- .../__tests__/Transformer-test.js | 4 +- react-packager/src/JSTransformer/index.js | 8 +- react-packager/src/Packager/Package.js | 112 ++++++++++++--- .../src/Packager/__tests__/Package-test.js | 132 ++++++++++++++++-- .../src/Packager/__tests__/Packager-test.js | 56 ++++---- react-packager/src/Packager/index.js | 37 ++--- react-packager/src/lib/ModuleTransport.js | 38 +++++ 7 files changed, 308 insertions(+), 79 deletions(-) create mode 100644 react-packager/src/lib/ModuleTransport.js diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 72845a2e..eb307a02 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -11,6 +11,7 @@ jest .dontMock('worker-farm') .dontMock('os') + .dontMock('../../lib/ModuleTransport') .dontMock('../index'); var OPTIONS = { @@ -37,7 +38,7 @@ describe('Transformer', function() { pit('should loadFileAndTransform', function() { workers.mockImpl(function(data, callback) { - callback(null, { code: 'transformed' }); + callback(null, { code: 'transformed', map: 'sourceMap' }); }); require('fs').readFile.mockImpl(function(file, callback) { callback(null, 'content'); @@ -47,6 +48,7 @@ describe('Transformer', function() { .then(function(data) { expect(data).toEqual({ code: 'transformed', + map: 'sourceMap', sourcePath: 'file', sourceCode: 'content' }); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 962eb7fe..33e01703 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -14,6 +14,7 @@ var Cache = require('./Cache'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); var util = require('util'); +var ModuleTransport = require('../lib/ModuleTransport'); var readFile = Promise.promisify(fs.readFile); @@ -100,11 +101,12 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { throw formatError(res.error, filePath, sourceCode); } - return { + return new ModuleTransport({ code: res.code, + map: res.map, sourcePath: filePath, - sourceCode: sourceCode - }; + sourceCode: sourceCode, + }); } ); }); diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 67e31e47..c621f747 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -11,6 +11,7 @@ var _ = require('underscore'); var base64VLQ = require('./base64-vlq'); var UglifyJS = require('uglify-js'); +var ModuleTransport = require('../lib/ModuleTransport'); module.exports = Package; @@ -19,22 +20,25 @@ function Package(sourceMapUrl) { this._modules = []; this._assets = []; this._sourceMapUrl = sourceMapUrl; + this._shouldCombineSourceMaps = false; } Package.prototype.setMainModuleId = function(moduleId) { this._mainModuleId = moduleId; }; -Package.prototype.addModule = function( - transformedCode, - sourceCode, - sourcePath -) { - this._modules.push({ - transformedCode: transformedCode, - sourceCode: sourceCode, - sourcePath: sourcePath - }); +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) { @@ -45,11 +49,12 @@ Package.prototype.finalize = function(options) { options = options || {}; if (options.runMainModule) { var runCode = ';require("' + this._mainModuleId + '");'; - this.addModule( - runCode, - runCode, - 'RunMainModule.js' - ); + this.addModule(new ModuleTransport({ + code: runCode, + virtual: true, + sourceCode: runCode, + sourcePath: 'RunMainModule.js' + })); } Object.freeze(this._modules); @@ -67,7 +72,7 @@ Package.prototype._assertFinalized = function() { Package.prototype._getSource = function() { if (this._source == null) { - this._source = _.pluck(this._modules, 'transformedCode').join('\n'); + this._source = _.pluck(this._modules, 'code').join('\n'); } return this._source; }; @@ -136,10 +141,50 @@ Package.prototype.getMinifiedSourceAndMap = function() { } }; +/** + * 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', @@ -168,13 +213,14 @@ Package.prototype._getMappings = function() { // 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 transformedCode = module.transformedCode; + var code = module.code; var lastCharNewLine = false; - module.lines = 0; - for (var t = 0; t < transformedCode.length; t++) { + moduleLines[module.sourcePath] = 0; + for (var t = 0; t < code.length; t++) { if (t === 0 && i === 0) { mappings += firstLine; } else if (t === 0) { @@ -183,13 +229,15 @@ Package.prototype._getMappings = function() { // 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 - modules[i - 1].lines); + mappings += base64VLQ.encode( + 0 - moduleLines[modules[i - 1].sourcePath] + ); mappings += 'A'; } else if (lastCharNewLine) { - module.lines++; + moduleLines[module.sourcePath]++; mappings += line; } - lastCharNewLine = transformedCode[t] === '\n'; + lastCharNewLine = code[t] === '\n'; if (lastCharNewLine) { mappings += ';'; } @@ -218,7 +266,25 @@ Package.prototype.getDebugInfo = function() { this._modules.map(function(m) { return '

Path:

' + m.sourcePath + '

Source:

' + '
'; + _.escape(m.code) + '
'; }).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.code ], + }; +} diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index db596a7b..0aaa3971 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -13,11 +13,13 @@ jest.autoMockOff(); var SourceMapGenerator = require('source-map').SourceMapGenerator; describe('Package', function() { + var ModuleTransport; var Package; var ppackage; beforeEach(function() { Package = require('../Package'); + ModuleTransport = require('../../lib/ModuleTransport'); ppackage = new Package('test_url'); ppackage.getSourceMap = jest.genMockFn().mockImpl(function() { return 'test-source-map'; @@ -26,8 +28,17 @@ describe('Package', function() { describe('source package', function() { it('should create a package and get the source', function() { - ppackage.addModule('transformed foo;', 'source foo', 'foo path'); - ppackage.addModule('transformed bar;', 'source bar', 'bar path'); + ppackage.addModule(new ModuleTransport({ + code: 'transformed foo;', + sourceCode: 'source foo', + sourcePath: 'foo path', + })); + ppackage.addModule(new ModuleTransport({ + code: 'transformed bar;', + sourceCode: 'source bar', + sourcePath: 'bar path', + })); + ppackage.finalize({}); expect(ppackage.getSource()).toBe([ 'transformed foo;', @@ -37,8 +48,18 @@ describe('Package', function() { }); it('should create a package and add run module code', function() { - ppackage.addModule('transformed foo;', 'source foo', 'foo path'); - ppackage.addModule('transformed bar;', 'source bar', 'bar path'); + ppackage.addModule(new ModuleTransport({ + code: 'transformed foo;', + sourceCode: 'source foo', + sourcePath: 'foo path' + })); + + ppackage.addModule(new ModuleTransport({ + code: 'transformed bar;', + sourceCode: 'source bar', + sourcePath: 'bar path' + })); + ppackage.setMainModuleId('foo'); ppackage.finalize({runMainModule: true}); expect(ppackage.getSource()).toBe([ @@ -59,7 +80,11 @@ describe('Package', function() { return minified; }; - ppackage.addModule('transformed foo;', 'source foo', 'foo path'); + ppackage.addModule(new ModuleTransport({ + code: 'transformed foo;', + sourceCode: 'source foo', + sourcePath: 'foo path' + })); ppackage.finalize(); expect(ppackage.getMinifiedSourceAndMap()).toBe(minified); }); @@ -68,13 +93,104 @@ describe('Package', function() { describe('sourcemap package', function() { it('should create sourcemap', function() { var p = new Package('test_url'); - p.addModule('transformed foo;\n', 'source foo', 'foo path'); - p.addModule('transformed bar;\n', 'source bar', 'bar path'); + p.addModule(new ModuleTransport({ + code: [ + 'transformed foo', + 'transformed foo', + 'transformed foo', + ].join('\n'), + sourceCode: [ + 'source foo', + 'source foo', + 'source foo', + ].join('\n'), + sourcePath: 'foo path', + })); + p.addModule(new ModuleTransport({ + code: [ + 'transformed bar', + 'transformed bar', + 'transformed bar', + ].join('\n'), + sourceCode: [ + 'source bar', + 'source bar', + 'source bar', + ].join('\n'), + sourcePath: 'bar path', + })); + p.setMainModuleId('foo'); p.finalize({runMainModule: true}); var s = p.getSourceMap(); expect(s).toEqual(genSourceMap(p._modules)); }); + + it('should combine sourcemaps', function() { + var p = new Package('test_url'); + + p.addModule(new ModuleTransport({ + code: 'transformed foo;\n', + map: {name: 'sourcemap foo'}, + sourceCode: 'source foo', + sourcePath: 'foo path' + })); + + p.addModule(new ModuleTransport({ + code: 'transformed foo;\n', + map: {name: 'sourcemap bar'}, + sourceCode: 'source foo', + sourcePath: 'foo path' + })); + + p.addModule(new ModuleTransport({ + code: 'image module;\nimage module;', + virtual: true, + sourceCode: 'image module;\nimage module;', + sourcePath: 'image.png', + })); + + p.setMainModuleId('foo'); + p.finalize({runMainModule: true}); + + var s = p.getSourceMap(); + expect(s).toEqual({ + file: 'bundle.js', + version: 3, + sections: [ + { offset: { line: 0, column: 0 }, map: { name: 'sourcemap foo' } }, + { offset: { line: 2, column: 0 }, map: { name: 'sourcemap bar' } }, + { + offset: { + column: 0, + line: 4 + }, + map: { + file: 'image.png', + mappings: 'AAAA;AACA;', + names: {}, + sources: [ 'image.png' ], + sourcesContent: ['image module;\nimage module;'], + version: 3, + } + }, + { + offset: { + column: 0, + line: 6 + }, + map: { + file: 'RunMainModule.js', + mappings: 'AAAA;', + names: {}, + sources: [ 'RunMainModule.js' ], + sourcesContent: [';require("foo");'], + version: 3, + } + } + ], + }); + }); }); describe('getAssets()', function() { @@ -95,7 +211,7 @@ describe('Package', function() { var packageLineNo = 0; for (var i = 0; i < modules.length; i++) { var module = modules[i]; - var transformedCode = module.transformedCode; + var transformedCode = module.code; var sourcePath = module.sourcePath; var sourceCode = module.sourceCode; var transformedLineCount = 0; diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 8e1420a3..3f093446 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -13,6 +13,7 @@ jest .dontMock('path') .dontMock('os') .dontMock('underscore') + .dontMock('../../lib/ModuleTransport') .setMock('uglify-js') .dontMock('../'); @@ -93,6 +94,7 @@ describe('Packager', function() { .mockImpl(function(path) { return Promise.resolve({ code: 'transformed ' + path, + map: 'sourcemap ' + path, sourceCode: 'source ' + path, sourcePath: path }); @@ -117,16 +119,19 @@ describe('Packager', function() { return packager.package('/root/foo.js', true, 'source_map_url') .then(function(p) { - expect(p.addModule.mock.calls[0]).toEqual([ - 'lol transformed /root/foo.js lol', - 'source /root/foo.js', - '/root/foo.js' - ]); - expect(p.addModule.mock.calls[1]).toEqual([ - 'lol transformed /root/bar.js lol', - 'source /root/bar.js', - '/root/bar.js' - ]); + expect(p.addModule.mock.calls[0][0]).toEqual({ + code: 'lol transformed /root/foo.js lol', + map: 'sourcemap /root/foo.js', + sourceCode: 'source /root/foo.js', + sourcePath: '/root/foo.js', + }); + + expect(p.addModule.mock.calls[1][0]).toEqual({ + code: 'lol transformed /root/bar.js lol', + map: 'sourcemap /root/bar.js', + sourceCode: 'source /root/bar.js', + sourcePath: '/root/bar.js' + }); var imgModule_DEPRECATED = { __packager_asset: true, @@ -138,15 +143,15 @@ describe('Packager', function() { deprecated: true, }; - expect(p.addModule.mock.calls[2]).toEqual([ - 'lol module.exports = ' + + expect(p.addModule.mock.calls[2][0]).toEqual({ + code: 'lol module.exports = ' + JSON.stringify(imgModule_DEPRECATED) + '; lol', - 'module.exports = ' + + sourceCode: 'module.exports = ' + JSON.stringify(imgModule_DEPRECATED) + ';', - '/root/img/img.png' - ]); + sourcePath: '/root/img/img.png' + }); var imgModule = { __packager_asset: true, @@ -160,21 +165,21 @@ describe('Packager', function() { type: 'png', }; - expect(p.addModule.mock.calls[3]).toEqual([ - 'lol module.exports = ' + + expect(p.addModule.mock.calls[3][0]).toEqual({ + code: 'lol module.exports = ' + JSON.stringify(imgModule) + '; lol', - 'module.exports = ' + + sourceCode: 'module.exports = ' + JSON.stringify(imgModule) + ';', - '/root/img/new_image.png' - ]); + sourcePath: '/root/img/new_image.png' + }); - expect(p.addModule.mock.calls[4]).toEqual([ - 'lol module.exports = {"json":true}; lol', - 'module.exports = {"json":true};', - '/root/file.json' - ]); + expect(p.addModule.mock.calls[4][0]).toEqual({ + code: 'lol module.exports = {"json":true}; lol', + sourceCode: 'module.exports = {"json":true};', + sourcePath: '/root/file.json' + }); expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} @@ -189,5 +194,4 @@ describe('Packager', function() { ]); }); }); - }); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 8563e272..c03a0432 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -14,9 +14,9 @@ var path = require('path'); var Promise = require('bluebird'); var Transformer = require('../JSTransformer'); var DependencyResolver = require('../DependencyResolver'); -var _ = require('underscore'); var Package = require('./Package'); var Activity = require('../Activity'); +var ModuleTransport = require('../lib/ModuleTransport'); var declareOpts = require('../lib/declareOpts'); var imageSize = require('image-size'); @@ -125,12 +125,8 @@ Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { .then(function(transformedModules) { Activity.endEvent(transformEventId); - transformedModules.forEach(function(transformed) { - ppackage.addModule( - transformed.code, - transformed.sourceCode, - transformed.sourcePath - ); + transformedModules.forEach(function(moduleTransport) { + ppackage.addModule(moduleTransport); }); ppackage.finalize({ runMainModule: runModule }); @@ -163,11 +159,13 @@ Packager.prototype._transformModule = function(ppackage, module) { var resolver = this._resolver; return transform.then(function(transformed) { - return _.extend( - {}, - transformed, - {code: resolver.wrapModule(module, transformed.code)} - ); + var code = resolver.wrapModule(module, transformed.code); + return new ModuleTransport({ + code: code, + map: transformed.map, + sourceCode: transformed.sourceCode, + sourcePath: transformed.sourcePath, + }); }); }; @@ -191,11 +189,12 @@ Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { var code = 'module.exports = ' + JSON.stringify(img) + ';'; - return { + return new ModuleTransport({ code: code, sourceCode: code, sourcePath: module.path, - }; + virtual: true, + }); }); }; @@ -222,11 +221,12 @@ Packager.prototype.generateAssetModule = function(ppackage, module) { var code = 'module.exports = ' + JSON.stringify(img) + ';'; - return { + return new ModuleTransport({ code: code, sourceCode: code, sourcePath: module.path, - }; + virtual: true, + }); }); }; @@ -234,11 +234,12 @@ function generateJSONModule(module) { return readFile(module.path).then(function(data) { var code = 'module.exports = ' + data.toString('utf8') + ';'; - return { + return new ModuleTransport({ code: code, sourceCode: code, sourcePath: module.path, - }; + virtual: true, + }); }); } diff --git a/react-packager/src/lib/ModuleTransport.js b/react-packager/src/lib/ModuleTransport.js new file mode 100644 index 00000000..a5f1d568 --- /dev/null +++ b/react-packager/src/lib/ModuleTransport.js @@ -0,0 +1,38 @@ +/** + * 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'; + +function ModuleTransport(data) { + assertExists(data, 'code'); + this.code = data.code; + + assertExists(data, 'sourceCode'); + this.sourceCode = data.sourceCode; + + assertExists(data, 'sourcePath'); + this.sourcePath = data.sourcePath; + + this.virtual = data.virtual; + + if (this.virtual && data.map) { + throw new Error('Virtual modules cannot have source maps'); + } + + this.map = data.map; + + Object.freeze(this); +} + +module.exports = ModuleTransport; + +function assertExists(obj, field) { + if (obj[field] == null) { + throw new Error('Modules must have `' + field + '`'); + } +} From 95a120b66319062a27606a0655aadfc2f68f1c6f Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 4 May 2015 18:34:29 -0700 Subject: [PATCH 168/936] [ReactNative] improve console logging a little bit --- .../haste/polyfills/console.js | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/haste/polyfills/console.js index 91fb970f..57576961 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -35,25 +35,34 @@ function getNativeLogFunction(level) { return function() { var str = Array.prototype.map.call(arguments, function(arg) { - if (arg == null) { - return arg === null ? 'null' : 'undefined'; - } else if (typeof arg === 'string') { - return '"' + arg + '"'; + var ret; + var type = typeof arg; + if (arg === null) { + ret = 'null'; + } else if (arg === undefined) { + ret = 'undefined'; + } else if (type === 'string') { + ret = '"' + arg + '"'; + } else if (type === 'function') { + try { + ret = arg.toString(); + } catch (e) { + ret = '[function unknown]'; + } } else { // Perform a try catch, just in case the object has a circular // reference or stringify throws for some other reason. try { - return JSON.stringify(arg); + ret = JSON.stringify(arg); } catch (e) { if (typeof arg.toString === 'function') { try { - return arg.toString(); - } catch (E) { - return 'unknown'; - } + ret = arg.toString(); + } catch (E) {} } } } + return ret || '["' + type + '" failed to stringify]'; }).join(', '); global.nativeLoggingHook(str, level); }; From acd777a90659ff2116c09e188debbc824d36c3a2 Mon Sep 17 00:00:00 2001 From: Evgen Filatov Date: Tue, 5 May 2015 14:14:49 -0700 Subject: [PATCH 169/936] Fixed name of Chome window, Connects to #297 Summary: Hi! I have the same problem as described here https://github.com/facebook/react-native/issues/297 It could occurs after restarting `packager.sh` or `debuger-ui` page. I found simple solution that works for me, but I am not 100% sure it will works for any user with this problem. How could this be tested automatically? Closes https://github.com/facebook/react-native/pull/1101 Github Author: Evgen Filatov Test Plan: Imported from GitHub, without a `Test Plan:` line. --- launchChromeDevTools.applescript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchChromeDevTools.applescript b/launchChromeDevTools.applescript index 1fe6f4b0..4b718f5b 100755 --- a/launchChromeDevTools.applescript +++ b/launchChromeDevTools.applescript @@ -10,7 +10,7 @@ on run argv set theURL to item 1 of argv - tell application "Google Chrome" + tell application "Chrome" activate if (count every window) = 0 then From b05ca45244939f0a075a4535a9a1425111a3b724 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 5 May 2015 14:05:19 -0700 Subject: [PATCH 170/936] [react-packager] Use gracful-fs to avoid EMFILE errors Summary: @public Currently, every time we call into the packager we have to change the ulimit to make sure we don't hit the EMFILE error (the packager uses as much concurrency as possible). Using graceful-fs, the fs module -- with monkey patching -- becomes intelligent enough to recover from EMFILE errors. Test Plan: * set `ulimit -n 256* * start server * request from your browser: http://localhost:8081/RKJSModules/MainBundle/CatalystBundle.includeRequire.bundle * it works --- react-packager/index.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/react-packager/index.js b/react-packager/index.js index 3a70659c..a7b6264b 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -8,6 +8,8 @@ */ 'use strict'; +useGracefulFs(); + var Activity = require('./src/Activity'); var Server = require('./src/Server'); @@ -45,3 +47,16 @@ exports.getDependencies = function(options, main) { return r.dependencies; }); }; + +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]; + } + }); +} From 2f4dcae50579b37d4ea3c15014cf7be241f2871b Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Thu, 7 May 2015 17:30:41 -0700 Subject: [PATCH 171/936] [ReactNative] Register assets with AssetRegistry --- react-packager/src/Packager/__tests__/Packager-test.js | 8 ++++---- react-packager/src/Packager/index.js | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 3f093446..d4a1c0f3 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -166,12 +166,12 @@ describe('Packager', function() { }; expect(p.addModule.mock.calls[3][0]).toEqual({ - code: 'lol module.exports = ' + + code: 'lol module.exports = require("AssetRegistry").registerAsset(' + JSON.stringify(imgModule) + - '; lol', - sourceCode: 'module.exports = ' + + '); lol', + sourceCode: 'module.exports = require("AssetRegistry").registerAsset(' + JSON.stringify(imgModule) + - ';', + ');', sourcePath: '/root/img/new_image.png' }); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index c03a0432..e647f764 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -219,7 +219,8 @@ Packager.prototype.generateAssetModule = function(ppackage, module) { ppackage.addAsset(img); - var code = 'module.exports = ' + JSON.stringify(img) + ';'; + var ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);'; + var code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img)); return new ModuleTransport({ code: code, From 5663456373b15a42dff8921c89582b0b1ddc6ca2 Mon Sep 17 00:00:00 2001 From: Dmitry Soshnikov Date: Sun, 10 May 2015 22:18:55 -0700 Subject: [PATCH 172/936] [jest] Update to v0.4.2 --- .../src/AssetServer/__tests__/AssetServer-test.js | 5 ++++- .../haste/DependencyGraph/__tests__/DependencyGraph-test.js | 3 ++- .../haste/__tests__/HasteDependencyResolver-test.js | 2 ++ react-packager/src/JSTransformer/__tests__/Cache-test.js | 6 ++++-- .../src/JSTransformer/__tests__/Transformer-test.js | 3 ++- react-packager/src/Packager/__tests__/Packager-test.js | 4 ++-- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index ba804b5f..43f22345 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -1,10 +1,13 @@ 'use strict'; jest - .dontMock('path') .dontMock('../../lib/getAssetDataFromName') .dontMock('../'); +jest + .mock('crypto') + .mock('fs'); + var Promise = require('bluebird'); describe('AssetServer', function() { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 9cb08122..f75445d0 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,13 +10,14 @@ jest .dontMock('../index') - .dontMock('path') .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') .dontMock('../../../../lib/getAssetDataFromName') .setMock('../../../ModuleDescriptor', function(data) {return data;}); +jest.mock('fs'); + describe('DependencyGraph', function() { var DependencyGraph; var fileWatcher; diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index b3063cb5..9bc8b8b9 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -13,6 +13,8 @@ jest.dontMock('../') .dontMock('../replacePatterns') .setMock('../../ModuleDescriptor', function(data) {return data;}); +jest.mock('path'); + var Promise = require('bluebird'); describe('HasteDependencyResolver', function() { diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 51f7ec05..7ad65818 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -10,11 +10,13 @@ jest .dontMock('underscore') - .dontMock('path') .dontMock('absolute-path') - .dontMock('crypto') .dontMock('../Cache'); +jest + .mock('os') + .mock('fs'); + var Promise = require('bluebird'); describe('JSTransformer Cache', function() { diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index eb307a02..5948916a 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -10,10 +10,11 @@ jest .dontMock('worker-farm') - .dontMock('os') .dontMock('../../lib/ModuleTransport') .dontMock('../index'); +jest.mock('fs'); + var OPTIONS = { transformModulePath: '/foo/bar' }; diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index d4a1c0f3..9babda54 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -10,13 +10,13 @@ jest .setMock('worker-farm', function() { return function() {};}) - .dontMock('path') - .dontMock('os') .dontMock('underscore') .dontMock('../../lib/ModuleTransport') .setMock('uglify-js') .dontMock('../'); +jest.mock('fs'); + var Promise = require('bluebird'); describe('Packager', function() { From a91b293682f612d6edd90c725db69c53d88a713b Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 11 May 2015 12:54:07 -0700 Subject: [PATCH 173/936] decode pathName when extracting from url --- react-packager/src/Server/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 79022b21..d2c15717 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -349,7 +349,7 @@ function getOptionsFromUrl(reqUrl) { // node v0.11.14 bug see https://github.com/facebook/react-native/issues/218 urlObj.query = urlObj.query || {}; - var pathname = urlObj.pathname; + 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. From ac49f8ca99dfdc41f21a13b4361a4f3f2fe24c95 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 13 May 2015 10:56:09 -0700 Subject: [PATCH 174/936] [ReactNative] differentiate fatal and soft exceptions --- .../src/DependencyResolver/haste/polyfills/error-guard.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js index 3816617f..d4aa4122 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js @@ -22,14 +22,17 @@ ErrorUtils._globalHandler = fun; }, reportError: function(error) { - Error._globalHandler && ErrorUtils._globalHandler(error); + ErrorUtils._globalHandler && ErrorUtils._globalHandler(error); + }, + reportFatalError: function(error) { + ErrorUtils._globalHandler && ErrorUtils._globalHandler(error, true); }, applyWithGuard: function(fun, context, args) { try { ErrorUtils._inGuard++; return fun.apply(context, args); } catch (e) { - ErrorUtils._globalHandler && ErrorUtils._globalHandler(e); + ErrorUtils.reportError(e); } finally { ErrorUtils._inGuard--; } From e5b939d6232eb559e9034c408c3d5ea319d6afbb Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 13 May 2015 14:44:16 -0700 Subject: [PATCH 175/936] [react-packager] Use transformer name in cache name Summary: @public Shouldn't confuse the cache from files transformed by different transformers. This takes into account the transformer in the cache hash name. Test Plan: * start server with --babel * generate bundle * start server with --jstransform * generate bundle * compare them and they're different --- react-packager/src/JSTransformer/Cache.js | 6 +++ .../src/JSTransformer/__tests__/Cache-test.js | 37 ++++++++++++++++--- react-packager/src/JSTransformer/index.js | 1 + 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index 4761d15e..584077b6 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -31,6 +31,10 @@ var validateOpts = declareOpts({ type: 'array', required: true, }, + transformModulePath: { + type:'string', + required: true, + }, }); module.exports = Cache; @@ -162,6 +166,8 @@ function cacheFilePath(options) { var cacheVersion = options.cacheVersion || '0'; hash.update(cacheVersion); + hash.update(options.transformModulePath); + var name = 'react-packager-cache-' + hash.digest('hex'); return path.join(tmpdir, name); } diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 7ad65818..f91490ba 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -32,10 +32,14 @@ describe('JSTransformer Cache', function() { describe('getting/setting', function() { it('calls loader callback for uncached file', function() { - var cache = new Cache({projectRoots: ['/rootDir']}); + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn().mockImpl(function() { return Promise.resolve(); }); + cache.get('/rootDir/someFile', loaderCb); expect(loaderCb).toBeCalledWith('/rootDir/someFile'); }); @@ -48,10 +52,15 @@ describe('JSTransformer Cache', function() { } }); }); - var cache = new Cache({projectRoots: ['/rootDir']}); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn().mockImpl(function() { return Promise.resolve('lol'); }); + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { expect(value).toBe('lol'); }); @@ -65,10 +74,15 @@ describe('JSTransformer Cache', function() { } }); }); - var cache = new Cache({projectRoots: ['/rootDir']}); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn().mockImpl(function() { return Promise.resolve('lol'); }); + return cache.get('/rootDir/someFile', loaderCb).then(function() { var shouldNotBeCalled = jest.genMockFn(); return cache.get('/rootDir/someFile', shouldNotBeCalled) @@ -126,8 +140,12 @@ describe('JSTransformer Cache', function() { }); pit('should load cache from disk', function() { - var cache = new Cache({projectRoots: ['/rootDir']}); + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn(); + return cache.get('/rootDir/someFile', loaderCb).then(function(value) { expect(loaderCb).not.toBeCalled(); expect(value).toBe('oh hai'); @@ -152,7 +170,10 @@ describe('JSTransformer Cache', function() { return 123; }; - var cache = new Cache({projectRoots: ['/rootDir']}); + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); var loaderCb = jest.genMockFn().mockImpl(function() { return Promise.resolve('new value'); }); @@ -193,7 +214,11 @@ describe('JSTransformer Cache', function() { }); }); - var cache = new Cache({projectRoots: ['/rootDir']}); + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + cache.get('/rootDir/bar', function() { return Promise.resolve('bar value'); }); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 33e01703..2dc5e20b 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -60,6 +60,7 @@ function Transformer(options) { resetCache: options.resetCache, cacheVersion: options.cacheVersion, projectRoots: options.projectRoots, + transformModulePath: options.transformModulePath, }); if (options.transformModulePath != null) { From 936f7867652035546014815b3180001dc00a228a Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 13 May 2015 17:45:36 -0700 Subject: [PATCH 176/936] [react-native] Make document.js into a polyfill. Fixes #1149 Summary: @public document shimming must run before anything else. However, we don't currently guarantee that. This moves the document shimming into `document.js` which is used as a polyfill. Test Plan: * start server * go to playground app * require `NativeModules` as the first thing * open chrome debugger * no error --- packager.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 48552d93..eb90b04e 100644 --- a/packager.js +++ b/packager.js @@ -200,7 +200,12 @@ function getAppMiddleware(options) { cacheVersion: '2', transformModulePath: require.resolve('./transformer.js'), assetRoots: options.assetRoots, - assetExts: ['png', 'jpeg', 'jpg'] + assetExts: ['png', 'jpeg', 'jpg'], + polyfillModuleNames: [ + require.resolve( + '../Libraries/JavaScriptAppEngine/polyfills/document.js' + ), + ], }); } From 6266a4d0d561c03a43d73c6900cb50154e25176a Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 14 May 2015 10:15:05 -0700 Subject: [PATCH 177/936] [react native] Bump jest-cli version to 0.4.3 in RN packages --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ffaf9e7..3eaf2f3b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "dependencies": {}, "devDependencies": { - "jest-cli": "0.2.1", + "jest-cli": "0.4.3", "eslint": "0.9.2" } } From cc9ab9ee79c6ea24eb32408dc0853c90942b4d78 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 15 May 2015 15:11:49 -0700 Subject: [PATCH 178/936] [react-native] Use trailing commas transform Summary: @public Apparently trailing commas transform isn't exported by react-tools. We need to pull it out manually. This is not so clean but we're swtching to babel very shortly. Test Plan: * npm start * write `foo(a,b,c,)` in some file * request that file in the browser and make sure that trailing comma is gone --- transformer.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/transformer.js b/transformer.js index 31cb8d31..00beeadd 100644 --- a/transformer.js +++ b/transformer.js @@ -16,10 +16,12 @@ var reactVisitors = require('react-tools/vendor/fbtransform/visitors').getAllVisitors(); var staticTypeSyntax = require('jstransform/visitors/type-syntax').visitorList; +var trailingCommaVisitors = + require('jstransform/visitors/es7-trailing-comma-visitors.js').visitorList; + // Note that reactVisitors now handles ES6 classes, rest parameters, arrow // functions, template strings, and object short notation. -var visitorList = reactVisitors; - +var visitorList = reactVisitors.concat(trailingCommaVisitors); function transform(srcTxt, filename) { var options = { From 46e1404a76f8368f176db0526efcbdd333913236 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 15 May 2015 15:47:44 -0700 Subject: [PATCH 179/936] [react-native] Fix source map issue with virtual modules --- react-packager/src/Packager/Package.js | 2 +- .../src/Packager/__tests__/Packager-test.js | 12 +++++++++--- react-packager/src/Packager/index.js | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index c621f747..eca709d5 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -285,6 +285,6 @@ function generateSourceMapForVirtualModule(module) { names: [], mappings: mappings, file: module.sourcePath, - sourcesContent: [ module.code ], + sourcesContent: [ module.sourceCode ], }; } diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 9babda54..901c467b 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -150,7 +150,9 @@ describe('Packager', function() { sourceCode: 'module.exports = ' + JSON.stringify(imgModule_DEPRECATED) + ';', - sourcePath: '/root/img/img.png' + sourcePath: '/root/img/img.png', + virtual: true, + map: undefined, }); var imgModule = { @@ -172,13 +174,17 @@ describe('Packager', function() { sourceCode: 'module.exports = require("AssetRegistry").registerAsset(' + JSON.stringify(imgModule) + ');', - sourcePath: '/root/img/new_image.png' + sourcePath: '/root/img/new_image.png', + virtual: true, + map: undefined, }); expect(p.addModule.mock.calls[4][0]).toEqual({ code: 'lol module.exports = {"json":true}; lol', sourceCode: 'module.exports = {"json":true};', - sourcePath: '/root/file.json' + sourcePath: '/root/file.json', + map: undefined, + virtual: true, }); expect(p.finalize.mock.calls[0]).toEqual([ diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index e647f764..e5afefe7 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -165,6 +165,7 @@ Packager.prototype._transformModule = function(ppackage, module) { map: transformed.map, sourceCode: transformed.sourceCode, sourcePath: transformed.sourcePath, + virtual: transformed.virtual, }); }); }; From a7233772846336fa3f32240a91c7a7e5854038e6 Mon Sep 17 00:00:00 2001 From: Dmitry Soshnikov Date: Sun, 17 May 2015 00:15:21 -0700 Subject: [PATCH 180/936] [jest] Update to v0.4.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3eaf2f3b..521de63c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "dependencies": {}, "devDependencies": { - "jest-cli": "0.4.3", + "jest-cli": "0.4.4", "eslint": "0.9.2" } } From 05b6cdf97a7f1689306c1aee9f33458c657a7ee9 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sun, 17 May 2015 02:45:42 -0700 Subject: [PATCH 181/936] [react-native] Update jest to get perf bugfix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 521de63c..f3af007f 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "dependencies": {}, "devDependencies": { - "jest-cli": "0.4.4", + "jest-cli": "0.4.5", "eslint": "0.9.2" } } From 74e7e70e768cc9d8d113f96f6f10539d9a5112c7 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 20 May 2015 10:29:56 -0700 Subject: [PATCH 182/936] [ReactNative] Cleanup _build_bundle script --- parseCommandLine.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/parseCommandLine.js b/parseCommandLine.js index 65061e3f..17a2d8af 100644 --- a/parseCommandLine.js +++ b/parseCommandLine.js @@ -24,12 +24,21 @@ function parseCommandLine(config) { // optimist default API requires you to write the command name three time // This is a small wrapper to accept an object instead for (var i = 0; i < config.length; ++i) { + if (config[i].type === 'string') { + optimist.string(config[i].command); + } else { + optimist.boolean(config[i].command); + } + optimist - .boolean(config[i].command) .default(config[i].command, config[i].default) .describe(config[i].command, config[i].description); + + if (config[i].required) { + optimist.demand(config[i].command); + } } - var argv = optimist.argv; + var argv = optimist.parse(process.argv); // optimist doesn't have support for --dev=false, instead it returns 'false' for (var i = 0; i < config.length; ++i) { From c9c61222cf873aa0a2fb2947d3124c4f46e60bc7 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 20 May 2015 13:38:02 -0700 Subject: [PATCH 183/936] [react-packager] Implement getJSModulePaths API --- react-packager/src/Packager/Package.js | 9 +++++++++ .../src/Packager/__tests__/Package-test.js | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index eca709d5..70f57c4a 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -249,6 +249,15 @@ Package.prototype._getMappings = function() { 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 + '
', diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index 0aaa3971..1d8c3dd1 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -204,8 +204,28 @@ describe('Package', function() { expect(p.getAssets()).toEqual([asset1, asset2]); }); }); + + describe('getJSModulePaths()', function() { + it('should return module paths', function() { + var p = new Package('test_url'); + p.addModule(new ModuleTransport({ + code: 'transformed foo;\n', + sourceCode: 'source foo', + sourcePath: 'foo path' + })); + p.addModule(new ModuleTransport({ + code: 'image module;\nimage module;', + virtual: true, + sourceCode: 'image module;\nimage module;', + sourcePath: 'image.png', + })); + + expect(p.getJSModulePaths()).toEqual(['foo path']); + }); + }); }); + function genSourceMap(modules) { var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3}); var packageLineNo = 0; From 2271166a83b36ef110034f4d4fc053aad79686fd Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 20 May 2015 15:28:59 -0700 Subject: [PATCH 184/936] [react-packager] Add first class support to popular image formats Summary: @public 1. Default to first class support of popular image formats 2. Add tests to make sure we support other than png Test Plan: 1. ./runJestTests.sh 2. Add test.png and test.jpg images in the Playground app dir 3. require both images and render then in the playground app 4. they render --- .../AssetServer/__tests__/AssetServer-test.js | 66 ++++++++++++++ react-packager/src/AssetServer/index.js | 4 +- .../__tests__/DependencyGraph-test.js | 90 +++++++++++++------ .../haste/DependencyGraph/index.js | 2 +- .../src/DependencyResolver/haste/index.js | 5 ++ react-packager/src/Packager/index.js | 1 + react-packager/src/Server/index.js | 2 +- 7 files changed, 137 insertions(+), 33 deletions(-) diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 43f22345..c6acc6a8 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -47,6 +47,32 @@ describe('AssetServer', function() { }); }); + pit('should work for the simple case with jpg', function() { + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png', 'jpg'], + }); + + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b.png': 'png image', + 'b.jpg': 'jpeg image', + } + } + }); + + return Promise.all([ + server.get('imgs/b.jpg'), + server.get('imgs/b.png'), + ]).then(function(data) { + expect(data).toEqual([ + 'jpeg image', + 'png image', + ]); + }); + }); + pit('should pick the bigger one', function() { var server = new AssetServer({ projectRoots: ['/root'], @@ -136,5 +162,45 @@ describe('AssetServer', function() { }); }); }); + + pit('should get assetData for non-png images', function() { + var hash = { + update: jest.genMockFn(), + digest: jest.genMockFn(), + }; + + hash.digest.mockImpl(function() { + return 'wow such hash'; + }); + crypto.createHash.mockImpl(function() { + return hash; + }); + + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png', 'jpeg'], + }); + + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b@1x.jpg': 'b1 image', + 'b@2x.jpg': 'b2 image', + 'b@4x.jpg': 'b4 image', + 'b@4.5x.jpg': 'b4.5 image', + } + } + }); + + return server.getAssetData('imgs/b.jpg').then(function(data) { + expect(hash.update.mock.calls.length).toBe(4); + expect(data).toEqual({ + type: 'jpg', + name: 'b', + scales: [1, 2, 4, 4.5], + hash: 'wow such hash', + }); + }); + }); }); }); diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 6f07dd01..9bae2682 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -28,7 +28,7 @@ var validateOpts = declareOpts({ }, assetExts: { type: 'array', - default: ['png'], + required: true, }, }); @@ -90,7 +90,7 @@ AssetServer.prototype.getAssetData = function(assetPath) { var nameData = getAssetDataFromName(assetPath); var data = { name: nameData.name, - type: 'png', + type: nameData.type, }; return this._getAssetRecord(assetPath).then(function(record) { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index f75445d0..c247e59d 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -55,7 +55,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -91,7 +92,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -121,7 +123,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -161,6 +164,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); return dgraph.load().then(function() { @@ -199,6 +203,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -246,6 +251,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -308,6 +314,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); return dgraph.load().then(function() { @@ -358,7 +365,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -391,7 +399,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -421,7 +430,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -455,7 +465,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -489,7 +500,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -519,7 +531,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -552,7 +565,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -595,7 +609,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) @@ -641,7 +656,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -674,7 +690,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -712,7 +729,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -755,7 +773,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -798,7 +817,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -847,7 +867,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -888,7 +909,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -931,7 +953,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -974,7 +997,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -1027,7 +1051,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -1092,7 +1117,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { expect(dgraph.getOrderedDependencies('/root/index.js')) @@ -1158,7 +1184,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { filesystem.root['index.js'] = @@ -1209,7 +1236,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { filesystem.root['index.js'] = @@ -1260,7 +1288,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { delete filesystem.root.foo; @@ -1310,7 +1339,8 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { filesystem.root['bar.js'] = [ @@ -1367,7 +1397,7 @@ describe('DependencyGraph', function() { roots: [root], assetRoots_DEPRECATED: [root], assetExts: ['png'], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, }); return dgraph.load().then(function() { @@ -1419,7 +1449,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], assetExts: ['png'], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, }); return dgraph.load().then(function() { @@ -1482,6 +1512,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ roots: [root], fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], ignoreFilePath: function(filePath) { if (filePath === '/root/bar.js') { return true; @@ -1550,7 +1581,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ roots: [root], - fileWatcher: fileWatcher + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], }); return dgraph.load().then(function() { triggerFileChange('change', 'aPackage', '/root', { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 08a4b513..0881e5dc 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -44,7 +44,7 @@ var validateOpts = declareOpts({ }, assetExts: { type: 'array', - default: ['png'], + required: true, } }); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 2583ac8f..da68785e 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -54,6 +54,10 @@ var validateOpts = declareOpts({ type: 'object', required: true, }, + assetExts: { + type: 'array', + required: true, + } }); function HasteDependencyResolver(options) { @@ -62,6 +66,7 @@ function HasteDependencyResolver(options) { this._depGraph = new DependencyGraph({ roots: opts.projectRoots, assetRoots_DEPRECATED: opts.assetRoots, + assetExts: opts.assetExts, ignoreFilePath: function(filepath) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index e5afefe7..a85281d2 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -86,6 +86,7 @@ function Packager(options) { moduleFormat: opts.moduleFormat, assetRoots: opts.assetRoots, fileWatcher: opts.fileWatcher, + assetExts: opts.assetExts, }); this._transformer = new Transformer({ diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index d2c15717..d4da5ae1 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -60,7 +60,7 @@ var validateOpts = declareOpts({ }, assetExts: { type: 'array', - default: ['png'], + default: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'], }, }); From 39af67ece3d4ce72d8fbe3ec352db8b9f7290b23 Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Wed, 20 May 2015 18:38:55 -0700 Subject: [PATCH 185/936] Unbreak RN JS server for Android. --- parseCommandLine.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/parseCommandLine.js b/parseCommandLine.js index 17a2d8af..36b8ab11 100644 --- a/parseCommandLine.js +++ b/parseCommandLine.js @@ -52,6 +52,15 @@ function parseCommandLine(config) { if (argv[command] === 'false') { argv[command] = false; } + if (config[i].type === 'string') { + // According to https://github.com/substack/node-optimist#numbers, + // every argument that looks like a number should be converted to one. + var strValue = argv[command]; + var numValue = strValue ? Number(strValue) : undefined; + if (typeof numValue === 'number' && !isNaN(numValue)) { + argv[command] = numValue; + } + } } // Show --help From 61efe8a34b5d5d2bb255f0dd60a35362fc426373 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 22 May 2015 10:17:43 -0700 Subject: [PATCH 186/936] [react-packager] Use actual error types Summary: @public Previously, we had to use errors as a property on the result object because there was no way to pass custom props between the child worker and the parent. This has been fixed in node-worker-farm (D2092153) and now we can use regular errors. This also adapts the transformer to babel-specific errors. Generic errors, however, should still work and render readable info. Additionally, I deprecated, but maintained backwards compatiblity for people in OSS that are using custom transformers. Test Plan: 1. `./runJestTests.sh` 2. `./runJestTests.sh PackagerIntegration` 3. open the playground app 4. Add a syntax error. Say `1=x` somewhere in the file 5. Reload and see error message 'SyntaxError description (line:col)' 6. Make sure that the stack trace is clickable and it attempts to open the editor to the location --- .../__tests__/Transformer-test.js | 27 +++++++++----- react-packager/src/JSTransformer/index.js | 37 +++++++++---------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 5948916a..22ca53e6 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -57,25 +57,34 @@ describe('Transformer', function() { }); pit('should add file info to parse errors', function() { + var message = 'message'; + var snippet = 'snippet'; + require('fs').readFile.mockImpl(function(file, callback) { callback(null, 'var x;\nvar answer = 1 = x;'); }); workers.mockImpl(function(data, callback) { - var esprimaError = new Error('Error: Line 2: Invalid left-hand side in assignment'); - esprimaError.description = 'Invalid left-hand side in assignment'; - esprimaError.lineNumber = 2; - esprimaError.column = 15; - callback(null, {error: esprimaError}); + var babelError = new SyntaxError(message); + babelError.type = 'SyntaxError'; + babelError.description = message; + babelError.loc = { + line: 2, + column: 15, + }; + babelError.codeFrame = snippet; + callback(babelError); }); return new Transformer(OPTIONS).loadFileAndTransform('foo-file.js') .catch(function(error) { expect(error.type).toEqual('TransformError'); - expect(error.snippet).toEqual([ - 'var answer = 1 = x;', - ' ^', - ].join('\n')); + expect(error.message).toBe('SyntaxError ' + message); + expect(error.lineNumber).toBe(2); + expect(error.column).toBe(15); + expect(error.filename).toBe('foo-file.js'); + expect(error.description).toBe(message); + expect(error.snippet).toBe(snippet); }); }); }); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 2dc5e20b..513d4394 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -99,7 +99,12 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { }).then( function(res) { if (res.error) { - throw formatError(res.error, filePath, sourceCode); + console.warn( + 'Error property on the result value form the transformer', + 'module is deprecated and will be removed in future versions.', + 'Please pass an error object as the first argument to the callback' + ); + throw formatError(res.error, filePath); } return new ModuleTransport({ @@ -110,6 +115,8 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { }); } ); + }).catch(function(err) { + throw formatError(err, filePath); }); }); }; @@ -118,8 +125,8 @@ function TransformError() {} util.inherits(TransformError, SyntaxError); function formatError(err, filename, source) { - if (err.lineNumber && err.column) { - return formatEsprimaError(err, filename, source); + if (err.loc) { + return formatBabelError(err, filename, source); } else { return formatGenericError(err, filename, source); } @@ -136,26 +143,16 @@ function formatGenericError(err, filename) { return error; } -function formatEsprimaError(err, filename, source) { - var stack = err.stack.split('\n'); - stack.shift(); - - var msg = 'TransformError: ' + err.description + ' ' + filename + ':' + - err.lineNumber + ':' + err.column; - var sourceLine = source.split('\n')[err.lineNumber - 1]; - var snippet = sourceLine + '\n' + new Array(err.column - 1).join(' ') + '^'; - - stack.unshift(msg); - +function formatBabelError(err, filename) { var error = new TransformError(); - error.message = msg; error.type = 'TransformError'; - error.stack = stack.join('\n'); - error.snippet = snippet; + error.message = (err.type || error.type) + ' ' + err.message; + error.stack = err.stack; + error.snippet = err.codeFrame; + error.lineNumber = err.loc.line; + error.column = err.loc.column; error.filename = filename; - error.lineNumber = err.lineNumber; - error.column = err.column; - error.description = err.description; + error.description = err.message; return error; } From b1e6bc6c739977718f4ebf3c8db17da462681b3b Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 22 May 2015 12:16:15 -0700 Subject: [PATCH 187/936] [react-native] Replace jstransform with Babel in the OSS repo Summary: @public Replaces jstransform with Babel. Additionally, stops (using the deprecated) passing an error property back from the transformer, and instead passes an error in the first argument. This is because we were able to update node-worker-farm to handle custom properties on errors. Test Plan: 1. Export the oss project 2. npm install 3. Start the movies app 4. Make sure it works 5. Add a syntax error 6. Make sure the message is correct --- transformer.js | 70 ++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/transformer.js b/transformer.js index 00beeadd..a2efcea3 100644 --- a/transformer.js +++ b/transformer.js @@ -10,40 +10,37 @@ */ 'use strict'; -var jstransform = require('jstransform').transform; +var babel = require('babel'); -var reactVisitors = - require('react-tools/vendor/fbtransform/visitors').getAllVisitors(); -var staticTypeSyntax = - require('jstransform/visitors/type-syntax').visitorList; -var trailingCommaVisitors = - require('jstransform/visitors/es7-trailing-comma-visitors.js').visitorList; - -// Note that reactVisitors now handles ES6 classes, rest parameters, arrow -// functions, template strings, and object short notation. -var visitorList = reactVisitors.concat(trailingCommaVisitors); - -function transform(srcTxt, filename) { - var options = { - es3: true, - sourceType: 'nonStrictModule', +function transform(srcTxt, filename, options) { + var result = babel.transform(srcTxt, { + retainLines: true, + compact: true, + comments: false, filename: filename, + whitelist: [ + 'es6.arrowFunctions', + 'es6.blockScoping', + 'es6.classes', + 'es6.destructuring', + 'es6.parameters.rest', + 'es6.properties.computed', + 'es6.properties.shorthand', + 'es6.spread', + 'es6.templateLiterals', + 'es7.trailingFunctionCommas', + 'es7.objectRestSpread', + 'flow', + 'react', + ], + sourceFileName: filename, + sourceMaps: false, + extra: options || {}, + }); + + return { + code: result.code, }; - - // These tranforms mostly just erase type annotations and static typing - // related statements, but they were conflicting with other tranforms. - // Running them first solves that problem - var staticTypeSyntaxResult = jstransform( - staticTypeSyntax, - srcTxt, - options - ); - - return jstransform( - visitorList, - staticTypeSyntaxResult.code, - options - ); } module.exports = function(data, callback) { @@ -54,15 +51,8 @@ module.exports = function(data, callback) { data.filename ); } catch (e) { - return callback(null, { - error: { - lineNumber: e.lineNumber, - column: e.column, - message: e.message, - stack: e.stack, - description: e.description - } - }); + callback(e); + return; } callback(null, result); From a6a8432100125544c3f976b6d6781e8c30db7be3 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 22 May 2015 17:12:10 -0700 Subject: [PATCH 188/936] [react-packager] Introduce buildPackage API --- react-packager/index.js | 35 ++++++++------ react-packager/src/Packager/Package.js | 9 ++-- .../src/Packager/__tests__/Package-test.js | 20 ++++++++ .../src/Server/__tests__/Server-test.js | 28 ++++++++++- react-packager/src/Server/index.js | 47 +++++++++++++++---- 5 files changed, 111 insertions(+), 28 deletions(-) diff --git a/react-packager/index.js b/react-packager/index.js index a7b6264b..6be11199 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -18,14 +18,17 @@ exports.middleware = function(options) { return server.processRequest.bind(server); }; -exports.buildPackageFromUrl = function(options, reqUrl) { - Activity.disable(); - // Don't start the filewatcher or the cache. - if (options.nonPersistent == null) { - options.nonPersistent = true; - } +exports.buildPackage = function(options, packageOptions) { + var server = createServer(options); + return server.buildPackage(packageOptions) + .then(function(p) { + server.end(); + return p; + }); +}; - var server = new Server(options); +exports.buildPackageFromUrl = function(options, reqUrl) { + var server = createServer(options); return server.buildPackageFromUrl(reqUrl) .then(function(p) { server.end(); @@ -34,13 +37,7 @@ exports.buildPackageFromUrl = function(options, reqUrl) { }; exports.getDependencies = function(options, main) { - Activity.disable(); - // Don't start the filewatcher or the cache. - if (options.nonPersistent == null) { - options.nonPersistent = true; - } - - var server = new Server(options); + var server = createServer(options); return server.getDependencies(main) .then(function(r) { server.end(); @@ -60,3 +57,13 @@ function useGracefulFs() { } }); } + +function createServer(options) { + Activity.disable(); + // Don't start the filewatcher or the cache. + if (options.nonPersistent == null) { + options.nonPersistent = true; + } + + return new Server(options); +} diff --git a/react-packager/src/Packager/Package.js b/react-packager/src/Packager/Package.js index 70f57c4a..6b538946 100644 --- a/react-packager/src/Packager/Package.js +++ b/react-packager/src/Packager/Package.js @@ -15,6 +15,8 @@ var ModuleTransport = require('../lib/ModuleTransport'); module.exports = Package; +var SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL='; + function Package(sourceMapUrl) { this._finalized = false; this._modules = []; @@ -96,12 +98,11 @@ Package.prototype.getSource = function(options) { } var source = this._getSource(); - source += '\n\/\/@ sourceMappingURL='; if (options.inlineSourceMap) { - source += this._getInlineSourceMap(); - } else { - source += this._sourceMapUrl; + source += SOURCEMAPPING_URL + this._getInlineSourceMap(); + } else if (this._sourceMapUrl) { + source += SOURCEMAPPING_URL + this._sourceMapUrl; } return source; diff --git a/react-packager/src/Packager/__tests__/Package-test.js b/react-packager/src/Packager/__tests__/Package-test.js index 1d8c3dd1..d43c65c0 100644 --- a/react-packager/src/Packager/__tests__/Package-test.js +++ b/react-packager/src/Packager/__tests__/Package-test.js @@ -47,6 +47,26 @@ describe('Package', function() { ].join('\n')); }); + it('should be ok to leave out the source map url', function() { + var p = new Package(); + p.addModule(new ModuleTransport({ + code: 'transformed foo;', + sourceCode: 'source foo', + sourcePath: 'foo path', + })); + p.addModule(new ModuleTransport({ + code: 'transformed bar;', + sourceCode: 'source bar', + sourcePath: 'bar path', + })); + + p.finalize({}); + expect(p.getSource()).toBe([ + 'transformed foo;', + 'transformed bar;', + ].join('\n')); + }); + it('should create a package and add run module code', function() { ppackage.addModule(new ModuleTransport({ code: 'transformed foo;', diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 58a1aca7..e4e7b508 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -230,7 +230,7 @@ describe('processRequest', function() { }); }); - describe.only('/assets endpoint', function() { + describe('/assets endpoint', function() { var AssetServer; beforeEach(function() { AssetServer = require('../../AssetServer'); @@ -257,4 +257,30 @@ describe('processRequest', function() { }); }); + + describe('buildPackage(options)', function() { + it('Calls the packager with the correct args', function() { + server.buildPackage({ + entryFile: 'foo file' + }); + expect(Packager.prototype.package).toBeCalledWith( + 'foo file', + true, + undefined, + true + ); + }); + }); + + 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( + 'path/to/foo.js', + false, + '/path/to/foo.map', + false + ); + }); + }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index d4da5ae1..0ce5c584 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -129,7 +129,7 @@ Server.prototype._onFileChange = function(type, filepath, root) { }; Server.prototype._rebuildPackages = function() { - var buildPackage = this._buildPackage.bind(this); + var buildPackage = this.buildPackage.bind(this); var packages = this._packages; Object.keys(packages).forEach(function(key) { var options = getOptionsFromUrl(key); @@ -171,18 +171,47 @@ Server.prototype.end = function() { ]); }; -Server.prototype._buildPackage = function(options) { +var packageOpts = declareOpts({ + sourceMapUrl: { + type: 'string', + required: false, + }, + entryFile: { + type: 'string', + required: true, + }, + dev: { + type: 'boolean', + default: true, + }, + minify: { + type: 'boolean', + default: false, + }, + runModule: { + type: 'boolean', + default: true, + }, + inlineSourceMap: { + type: 'boolean', + default: false, + }, +}); + +Server.prototype.buildPackage = function(options) { + var opts = packageOpts(options); + return this._packager.package( - options.main, - options.runModule, - options.sourceMapUrl, - options.dev + opts.entryFile, + opts.runModule, + opts.sourceMapUrl, + opts.dev ); }; Server.prototype.buildPackageFromUrl = function(reqUrl) { var options = getOptionsFromUrl(reqUrl); - return this._buildPackage(options); + return this.buildPackage(options); }; Server.prototype.getDependencies = function(main) { @@ -321,7 +350,7 @@ Server.prototype.processRequest = function(req, res, next) { var startReqEventId = Activity.startEvent('request:' + req.url); var options = getOptionsFromUrl(req.url); - var building = this._packages[req.url] || this._buildPackage(options); + var building = this._packages[req.url] || this.buildPackage(options); this._packages[req.url] = building; building.then( @@ -363,7 +392,7 @@ function getOptionsFromUrl(reqUrl) { return { sourceMapUrl: pathname.replace(/\.bundle$/, '.map'), - main: entryFile, + entryFile: entryFile, dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), minify: getBoolOptionFromQuery(urlObj.query, 'minify'), runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true), From ea9ca4637c20d91e7b68e77e4b54e2519584b4d1 Mon Sep 17 00:00:00 2001 From: James Ide Date: Tue, 26 May 2015 14:16:47 -0700 Subject: [PATCH 189/936] [Packager] Fix the --root, --assetRoots, and --platform options Summary: Looks like these options were handled as booleans when they should be handled as strings. Explicitly specify them as strings. Closes https://github.com/facebook/react-native/pull/1377 Github Author: James Ide Test Plan: `packager/packager.sh --root A --root B` works. Also tested `packager/packager.sh --root A,B`. --- packager.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packager.js b/packager.js index eb90b04e..d630d06e 100644 --- a/packager.js +++ b/packager.js @@ -36,14 +36,18 @@ var webSocketProxy = require('./webSocketProxy.js'); var options = parseCommandLine([{ command: 'port', default: 8081, + type: 'string', }, { command: 'root', + type: 'string', description: 'add another root(s) to be used by the packager in this project', }, { command: 'assetRoots', + type: 'string', description: 'specify the root directories of app assets' }, { command: 'platform', + type: 'string', default: 'ios', description: 'Specify the platform-specific blacklist (ios, android, web).' }, { @@ -65,13 +69,13 @@ if (options.projectRoots) { } if (options.root) { - if (typeof options.root === 'string') { - options.projectRoots.push(path.resolve(options.root)); - } else { - options.root.forEach(function(root) { - options.projectRoots.push(path.resolve(root)); - }); + if (!Array.isArray(options.root)) { + options.root = options.root.split(','); } + + options.root.forEach(function(root) { + options.projectRoots.push(path.resolve(root)); + }); } if (options.assetRoots) { From fddc50b98bcec0a23d902ee7ab91d57469606539 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 28 May 2015 00:47:37 -0700 Subject: [PATCH 190/936] [ReactNative] Add option to file watcher to ignore node_modules --- react-packager/src/FileWatcher/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index cd1a28e5..d26d19f9 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -67,6 +67,7 @@ function createWatcher(rootConfig) { var watcher = new Watcher(rootConfig.dir, { glob: rootConfig.globs, dot: false, + ignore: 'node_modules', }); return new Promise(function(resolve, reject) { From 65103e0c6306d6416574526566968e7526fe1694 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 28 May 2015 13:29:08 -0100 Subject: [PATCH 191/936] [ReactNative] Update sane fork + FileWatcher config --- react-packager/src/FileWatcher/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index d26d19f9..d90de452 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -67,7 +67,7 @@ function createWatcher(rootConfig) { var watcher = new Watcher(rootConfig.dir, { glob: rootConfig.globs, dot: false, - ignore: 'node_modules', + ignore: '**/node_modules/**/*', }); return new Promise(function(resolve, reject) { From 91dcbe09055b264378910049b1c73ae761bdd967 Mon Sep 17 00:00:00 2001 From: rickyc Date: Tue, 2 Jun 2015 15:14:25 -0700 Subject: [PATCH 192/936] Debugger won't start due to spaces in directory path Summary: Similar issue to #214. When I attempt to do command + D in the simulator, I get the following issue. ``` Launching Dev Tools... Failed to run launchChromeDevTools.applescript { [Error: Command failed: /bin/sh -c /Users/ricky/Dropbox (Personal)/Sites/AwesomeProject/node_modules/react-native/packager/launchChromeDevTools.applescript http://localhost:8081/debugger-ui /bin/sh: -c: line 0: syntax error near unexpected token `Personal' /bin/sh: -c: line 0: `/Users/ricky/Dropbox (Personal)/Sites/AwesomeProject/node_modules/react-native/packager/launchChromeDevTools.applescript http://localhost:8081/debugger-ui' ] killed: false, code: 2, signal: null, cmd: '/bin/sh -c /Users/ricky/Dropbox (Personal)/Sites/AwesomeProject/node_modules/react-native/packager/launchChromeDevTools.applescript http://localhost:8081/debugger-ui' } /bin/sh: -c: line 0: syntax error near unexpected token `Personal' /bin/sh: -c: line 0: `/Users/ricky/Dropbox (Personal)/Sites/AwesomeProject/node_modules/react-native/packa Closes https://github.com/facebook/react-native/pull/348 Github Author: rickyc Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packager.js b/packager.js index d630d06e..7c4be0a7 100644 --- a/packager.js +++ b/packager.js @@ -10,7 +10,7 @@ var fs = require('fs'); var path = require('path'); -var exec = require('child_process').exec; +var execFile = require('child_process').execFile; var http = require('http'); var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware'); @@ -172,7 +172,7 @@ function getDevToolsLauncher(options) { var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui'; var script = 'launchChromeDevTools.applescript'; console.log('Launching Dev Tools...'); - exec(path.join(__dirname, script) + ' ' + debuggerURL, function(err, stdout, stderr) { + execFile(path.join(__dirname, script), [debuggerURL], function(err, stdout, stderr) { if (err) { console.log('Failed to run ' + script, err); } From 22a205487a86231f92db04a6856cca2d78870efe Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Wed, 3 Jun 2015 11:35:32 -0700 Subject: [PATCH 193/936] [Cosmetic] Fix typo in packager README Summary: Closes https://github.com/facebook/react-native/pull/693 Github Author: Tyler McGinnis Test Plan: Imported from GitHub, without a `Test Plan:` line. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f9f649b..c3fae480 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ if the option is boolean `1/0` or `true/false` is accepted. Here are the current options the packager accepts: * `dev` boolean, defaults to true: sets a global `__DEV__` variable - which will effect how the React Nativeg core libraries behave. + which will effect how the React Native core libraries behave. * `minify` boolean, defaults to false: whether to minify the bundle. * `runModule` boolean, defaults to true: whether to require your entry point module. So if you requested `moduleName`, this option will add From e4da97c24aac9265619ffb73e26f061249edf739 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 3 Jun 2015 11:39:39 -0700 Subject: [PATCH 194/936] [react-packager] Add support for nested node_modules Summary: @public The packager's resolver started out imitating node-haste, which meant that we didn't support nested modules. Now this is a problem. Bigger projects are bound to have versions of different versions of the same package at different levels of the dependency tree. This makes loading dependencies lazy for node_modules and implements the node resolution algorithm. However, it also mantains that some modules are still "haste" format, which currently defaults to "react-native" and "react-tools". Finally, this means ~5 seconds speed up on every server start. This should also have a big impact on open source users with projects with big node_modules. Test Plan: 1- test the app with --reset-cache 2- click around test and production apps 3- update the OSS library 4- create a new project 5- npm install multiple modules 6- create some version conflict in your project 7- make sure we do the "right" thing 8- test file changes to make sure it works --- .../DependencyResolver/ModuleDescriptor.js | 41 +- .../__tests__/ModuleDescriptor-test.js | 109 + .../__tests__/DependencyGraph-test.js | 1959 +++++++++++++++-- .../haste/DependencyGraph/index.js | 382 +++- .../__tests__/HasteDependencyResolver-test.js | 6 +- .../src/DependencyResolver/haste/index.js | 5 +- 6 files changed, 2125 insertions(+), 377 deletions(-) create mode 100644 react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index 90db1c4a..3c1a1d26 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -8,6 +8,9 @@ */ 'use strict'; +var Promise = require('bluebird'); +var isAbsolutePath = require('absolute-path'); + function ModuleDescriptor(fields) { if (!fields.id) { throw new Error('Missing required fields id'); @@ -17,17 +20,13 @@ function ModuleDescriptor(fields) { if (!fields.path) { throw new Error('Missing required fields path'); } + if (!isAbsolutePath(fields.path)) { + throw new Error('Expected absolute path but found: ' + fields.path); + } this.path = fields.path; - if (!fields.dependencies) { - throw new Error('Missing required fields dependencies'); - } this.dependencies = fields.dependencies; - this.resolveDependency = fields.resolveDependency; - - this.entry = fields.entry || false; - this.isPolyfill = fields.isPolyfill || false; this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; @@ -50,12 +49,30 @@ function ModuleDescriptor(fields) { this._fields = fields; } +ModuleDescriptor.prototype.loadDependencies = function(loader) { + if (!this.dependencies) { + if (this._loadingDependencies) { + return this._loadingDependencies; + } + + var self = this; + this._loadingDependencies = loader(this).then(function(dependencies) { + self.dependencies = dependencies; + }); + return this._loadingDependencies; + } + + return Promise.resolve(this.dependencies); +}; + ModuleDescriptor.prototype.toJSON = function() { - return { - id: this.id, - path: this.path, - dependencies: this.dependencies - }; + var ret = {}; + Object.keys(this).forEach(function(prop) { + if (prop[0] !== '_' && typeof this[prop] !== 'function') { + ret[prop] = this[prop]; + } + }, this); + return ret; }; module.exports = ModuleDescriptor; diff --git a/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js b/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js new file mode 100644 index 00000000..99bed5df --- /dev/null +++ b/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js @@ -0,0 +1,109 @@ +/** + * 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('../ModuleDescriptor'); + + +describe('ModuleDescriptor', function() { + var ModuleDescriptor; + var Promise; + + beforeEach(function() { + ModuleDescriptor = require('../ModuleDescriptor'); + Promise = require('bluebird'); + }); + + describe('constructor', function() { + it('should validate fields', function() { + /* eslint no-new:0*/ + expect(function() { + new ModuleDescriptor({}); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + }); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + path: 'foo', + }); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + }); + }).toThrow(); + + var m = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + expect(m.toJSON()).toEqual({ + altId:undefined, + dependencies: undefined, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + }); + }); + + describe('loadDependencies', function() { + pit('should load dependencies', function() { + var mod = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + return mod.loadDependencies(function() { + return Promise.resolve([1, 2]); + }).then(function() { + expect(mod.dependencies).toEqual([1, 2]); + }); + }); + + pit('should load cached dependencies', function() { + var mod = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + return mod.loadDependencies(function() { + return Promise.resolve([1, 2]); + }).then(function() { + return mod.loadDependencies(function() { + throw new Error('no!'); + }); + }).then(function() { + expect(mod.dependencies).toEqual([1, 2]); + }); + }); + }); +}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index c247e59d..6c0fd05f 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,11 +10,12 @@ jest .dontMock('../index') + .dontMock('crypto') .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') .dontMock('../../../../lib/getAssetDataFromName') - .setMock('../../../ModuleDescriptor', function(data) {return data;}); + .dontMock('../../../ModuleDescriptor'); jest.mock('fs'); @@ -34,6 +35,14 @@ describe('DependencyGraph', function() { }; }); + // There are a lot of crap in ModuleDescriptors, this maps an array + // to get the relevant data. + function getDataFromModules(modules) { + return modules.map(function(module) { + return module.toJSON(); + }); + } + describe('getOrderedDependencies', function() { pit('should get dependencies', function() { var root = '/root'; @@ -58,11 +67,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, ]); }); }); @@ -95,11 +126,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, ]); }); }); @@ -126,20 +179,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json'] + dependencies: ['./a.json'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'package/a.json', isJSON: true, path: '/root/a.json', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -167,15 +231,31 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, - { id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -205,20 +285,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png'] + dependencies: ['./imgs/a.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -253,8 +344,8 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', @@ -264,7 +355,13 @@ describe('DependencyGraph', function() { './imgs/a.png', './imgs/b.png', './imgs/c.png', - ] + ], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/a.png', @@ -272,20 +369,32 @@ describe('DependencyGraph', function() { resolution: 1.5, dependencies: [], isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.png', resolution: 0.7, dependencies: [], - isAsset: true + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.png', resolution: 1, dependencies: [], - isAsset: true + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -317,14 +426,20 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'] + dependencies: ['./imgs/a.png', 'image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/a.png', @@ -332,6 +447,10 @@ describe('DependencyGraph', function() { dependencies: [], isAsset: true, resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'image!a', @@ -339,6 +458,10 @@ describe('DependencyGraph', function() { dependencies: [], isAsset_DEPRECATED: true, resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -368,11 +491,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: ['index'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -402,13 +547,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/main', + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -433,13 +596,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/index', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -468,14 +648,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'EpicModule', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'EpicModule', altId: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -503,13 +700,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/lib/index', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/lib/index', path: '/root/aPackage/lib/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -534,13 +748,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, - { id: 'test/lib/index', + { + id: 'test/index', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'test/lib/index', path: '/root/lib/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -568,10 +799,21 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -612,18 +854,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) + return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', + { + id: 'index', altId: '/root/somedir/somefile.js', path: '/root/somedir/somefile.js', - dependencies: ['c'] + dependencies: ['c'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'c', + { + id: 'c', altId: '/root/c.js', path: '/root/c.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -659,17 +915,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage', + { + id: 'aPackage', altId: '/root/b.js', path: '/root/b.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -693,12 +963,19 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['lolomg'] + dependencies: ['lolomg'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); }); @@ -732,16 +1009,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -776,16 +1066,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/subdir/lolynot', + { + id: 'aPackage/subdir/lolynot', path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -820,24 +1124,56 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', + altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', + altId: undefined, path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'] + dependencies: ['./subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/subdir/lolynot', + { + id: 'aPackage/subdir/lolynot', + altId: undefined, path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'] + dependencies: ['../other'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/other', + { + id: 'aPackage/other', + altId: undefined, path: '/root/aPackage/other.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -870,16 +1206,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', + altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/client', + { + id: 'aPackage/client', + altId: undefined, path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -912,16 +1264,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', + altId: undefined, path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -956,16 +1322,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1000,16 +1379,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1054,28 +1446,58 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'] + dependencies: ['./node', './dir/server.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/not-node', path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'] + dependencies: ['./not-browser'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/browser', path: '/root/aPackage/browser.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/dir/client', path: '/root/aPackage/dir/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1120,20 +1542,576 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: ['node-package'] + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'browser-package/index', path: '/root/aPackage/browser-package/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + describe('node_modules', function() { + pit('should work with nested node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('nested node_modules with specific paths', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar/lol");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + 'lol.js': '', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/lol', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('nested node_modules with browser field', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar/lol");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + browser: { + './lol': './wow' + } + }), + 'main.js': 'bar 1 module', + 'lol.js': '', + 'wow.js': '', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + browser: './main2', + }), + 'main2.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/wow', + path: '/root/node_modules/foo/node_modules/bar/wow.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main2', + path: '/root/node_modules/bar/main2.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('node_modules should support multi level', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': '', + }, + }, + 'path': { + 'to': { + 'bar.js': [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")', + ].join('\n'), + }, + 'node_modules': {}, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar', + path: '/root/path/to/bar.js', + altId: '/root/path/to/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should selectively ignore providesModule in node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("shouldWork");', + 'require("dontWork");', + 'require("wontWork");', + ].join('\n'), + 'node_modules': { + 'react-tools': { + 'package.json': JSON.stringify({ + name: 'react-tools', + main: 'main.js', + }), + 'main.js': [ + '/**', + ' * @providesModule shouldWork', + ' */', + 'require("submodule");', + ].join('\n'), + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule dontWork', + ' */', + 'hi();', + ].join('\n'), + }, + 'submodule': { + 'package.json': JSON.stringify({ + name: 'submodule', + main: 'main.js', + }), + 'main.js': 'log()', + }, + } + }, + 'ember': { + 'package.json': JSON.stringify({ + name: 'ember', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule wontWork', + ' */', + 'hi();', + ].join('\n'), + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['shouldWork', 'dontWork', 'wontWork'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'shouldWork', + path: '/root/node_modules/react-tools/main.js', + altId:'react-tools/main', + dependencies: ['submodule'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'submodule/main', + path: '/root/node_modules/react-tools/node_modules/submodule/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should ignore modules it cant find (assumes own require system)', function() { + // For example SourceMap.js implements it's own require system. + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo/lol");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1187,22 +2165,36 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1239,22 +2231,36 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1291,19 +2297,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1342,7 +2360,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -1354,27 +2372,54 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'] - }, - { id: 'bar', - altId: '/root/bar.js', - path: '/root/bar.js', - dependencies: ['foo'] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar', + altId: '/root/bar.js', + path: '/root/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + altId: '/root/foo.js', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -1400,32 +2445,50 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['image!foo'] + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'] - }, - { id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1452,30 +2515,49 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'aPackage/index', path: '/root/index.js', - dependencies: ['./foo.png'] + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) .toEqual([ - { id: 'index', altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'] - }, - { id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, + { + id: 'index', + altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -1520,7 +2602,7 @@ describe('DependencyGraph', function() { return false; } }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -1532,21 +2614,42 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['bar'] + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'foo', + { + id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1584,25 +2687,407 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'foo', + { + id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates package.json', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); + triggerFileChange('change', 'index.js', root); + + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['bPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('changes to browser field', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'browser.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/browser', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('removes old package from cache', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + + filesystem.root.node_modules.foo['main.js'] = 'lol'; + triggerFileChange('change', 'main.js', '/root/node_modules/foo'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package main changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + 'browser.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ + name: 'foo', + main: 'main.js', + browser: 'browser.js', + }); + triggerFileChange('change', 'package.json', '/root/node_modules/foo'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/browser', + altId: undefined, + path: '/root/node_modules/foo/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 0881e5dc..fc899cb2 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -1,3 +1,6 @@ +// TODO +// Fix it to work with tests + /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -19,6 +22,7 @@ var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); +var crypto = require('crypto'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -45,7 +49,11 @@ var validateOpts = declareOpts({ assetExts: { type: 'array', required: true, - } + }, + _providesModuleNodeModules: { + type: 'array', + default: ['react-tools', 'react-native'], + }, }); function DependecyGraph(options) { @@ -69,6 +77,8 @@ function DependecyGraph(options) { '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' ); + this._providesModuleNodeModules = opts._providesModuleNodeModules; + // Kick off the search process to precompute the dependency graph. this._init(); } @@ -90,59 +100,101 @@ DependecyGraph.prototype.load = function() { * Given an entry file return an array of all the dependent module descriptors. */ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { - var absolutePath = this._getAbsolutePath(entryPath); - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._roots - ); - } + return this.load().then(function() { + var absolutePath = this._getAbsolutePath(entryPath); + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); + } - var module = this._graph[absolutePath]; - if (module == null) { - throw new Error('Module with path "' + entryPath + '" is not in graph'); - } + var module = this._graph[absolutePath]; + if (module == null) { + throw new Error('Module with path "' + entryPath + '" is not in graph'); + } - var self = this; - var deps = []; - var visited = Object.create(null); + var self = this; + var deps = []; + var visited = Object.create(null); - // Node haste sucks. Id's aren't unique. So to make sure our entry point - // is the thing that ends up in our dependency list. - var graphMap = Object.create(this._moduleById); - graphMap[module.id] = module; + // Node haste sucks. Id's aren't unique. So to make sure our entry point + // is the thing that ends up in our dependency list. + var graphMap = Object.create(this._moduleById); + graphMap[module.id] = module; - // Recursively collect the dependency list. - function collect(module) { - deps.push(module); + // Recursively collect the dependency list. + function collect(mod) { + deps.push(mod); - module.dependencies.forEach(function(name) { - var id = sansExtJs(name); - var dep = self.resolveDependency(module, id); - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - name, - module.id - ); - return; + if (mod.dependencies == null) { + return mod.loadDependencies(function() { + return readFile(mod.path, 'utf8').then(function(content) { + return extractRequires(content); + }); + }).then(function() { + return iter(mod); + }); } - if (!visited[dep.id]) { - visited[dep.id] = true; - collect(dep); - } + return iter(mod); + } + + function iter(mod) { + var p = Promise.resolve(); + mod.dependencies.forEach(function(name) { + var id = sansExtJs(name); + var dep = self.resolveDependency(mod, id); + + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + name, + mod.id + ); + return; + } + + p = p.then(function() { + if (!visited[realId(dep)]) { + visited[realId(dep)] = true; + return collect(dep); + } + return null; + }); + }); + return p; + } + + visited[realId(module)] = true; + return collect(module).then(function() { + return deps; }); - } - - visited[module.id] = true; - collect(module); - - return deps; + }.bind(this)); }; +function browserFieldRedirect(packageJson, modulePath, isMain) { + if (packageJson.browser && typeof packageJson.browser === 'object') { + if (isMain) { + var tmpMain = packageJson.browser[modulePath] || + packageJson.browser[sansExtJs(modulePath)] || + packageJson.browser[withExtJs(modulePath)]; + if (tmpMain) { + return tmpMain; + } + } else { + var relPath = './' + path.relative(packageJson._root, modulePath); + var tmpModulePath = packageJson.browser[withExtJs(relPath)] || + packageJson.browser[sansExtJs(relPath)]; + if (tmpModulePath) { + return path.join(packageJson._root, tmpModulePath); + } + } + } + return modulePath; +} + /** * Given a module descriptor `fromModule` return the module descriptor for * the required module `depModuleId`. It could be top-level or relative, @@ -157,7 +209,7 @@ DependecyGraph.prototype.resolveDependency = function( // Process DEPRECATED global asset requires. if (assetMatch && assetMatch[1]) { if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARINING: Cannot find asset:', assetMatch[1]); + debug('WARNING: Cannot find asset:', assetMatch[1]); return null; } return this._assetMap_DEPRECATED[assetMatch[1]]; @@ -177,19 +229,39 @@ DependecyGraph.prototype.resolveDependency = function( depModuleId = fromPackageJson.browser[depModuleId]; } + + var packageName = depModuleId.replace(/\/.+/, ''); + packageJson = this._lookupNodePackage(fromModule.path, packageName); + + if (packageJson != null && packageName !== depModuleId) { + modulePath = path.join( + packageJson._root, + path.relative(packageName, depModuleId) + ); + + modulePath = browserFieldRedirect(packageJson, modulePath); + + dep = this._graph[withExtJs(modulePath)]; + if (dep != null) { + return dep; + } + } + // `depModuleId` is simply a top-level `providesModule`. // `depModuleId` is a package module but given the full path from the // package, i.e. package_name/module_name - if (this._moduleById[sansExtJs(depModuleId)]) { + if (packageJson == null && this._moduleById[sansExtJs(depModuleId)]) { return this._moduleById[sansExtJs(depModuleId)]; } - // `depModuleId` is a package and it's depending on the "main" resolution. - packageJson = this._packagesById[depModuleId]; - - // We are being forgiving here and raising an error because we could be - // processing a file that uses it's own require system. if (packageJson == null) { + // `depModuleId` is a package and it's depending on the "main" resolution. + packageJson = this._packagesById[depModuleId]; + } + + // We are being forgiving here and not raising an error because we could be + // processing a file that uses it's own require system. + if (packageJson == null || packageName !== depModuleId) { debug( 'WARNING: Cannot find required module `%s` from module `%s`.', depModuleId, @@ -198,33 +270,8 @@ DependecyGraph.prototype.resolveDependency = function( return null; } - var main; - - // We prioritize the `browser` field if it's a module path. - if (typeof packageJson.browser === 'string') { - main = packageJson.browser; - } else { - main = packageJson.main || 'index'; - } - - // If there is a mapping for main in the `browser` field. - if (packageJson.browser && typeof packageJson.browser === 'object') { - var tmpMain = packageJson.browser[main] || - packageJson.browser[withExtJs(main)] || - packageJson.browser[sansExtJs(main)]; - if (tmpMain) { - main = tmpMain; - } - } - - modulePath = withExtJs(path.join(packageJson._root, main)); - dep = this._graph[modulePath]; - - // Some packages use just a dir and rely on an index.js inside that dir. - if (dep == null) { - dep = this._graph[path.join(packageJson._root, main, 'index.js')]; - } - + // We are requiring node or a haste package via it's main file. + dep = this._resolvePackageMain(packageJson); if (dep == null) { throw new Error( 'Cannot find package main file for package: ' + packageJson._root @@ -248,15 +295,7 @@ DependecyGraph.prototype.resolveDependency = function( // modulePath: /x/y/a/b var dir = path.dirname(fromModule.path); modulePath = path.join(dir, depModuleId); - - if (packageJson.browser && typeof packageJson.browser === 'object') { - var relPath = './' + path.relative(packageJson._root, modulePath); - var tmpModulePath = packageJson.browser[withExtJs(relPath)] || - packageJson.browser[sansExtJs(relPath)]; - if (tmpModulePath) { - modulePath = path.join(packageJson._root, tmpModulePath); - } - } + modulePath = browserFieldRedirect(packageJson, modulePath); // JS modules can be required without extensios. if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { @@ -291,6 +330,25 @@ DependecyGraph.prototype.resolveDependency = function( } }; +DependecyGraph.prototype._resolvePackageMain = function(packageJson) { + var main; + // We prioritize the `browser` field if it's a module path. + if (typeof packageJson.browser === 'string') { + main = packageJson.browser; + } else { + main = packageJson.main || 'index'; + } + + // If there is a mapping for main in the `browser` field. + main = browserFieldRedirect(packageJson, main, true); + + var modulePath = withExtJs(path.join(packageJson._root, main)); + + return this._graph[modulePath] || + // Some packages use just a dir and rely on an index.js inside that dir. + this._graph[path.join(packageJson._root, main, 'index.js')]; +}; + /** * Intiates the filewatcher and kicks off the search process. */ @@ -339,9 +397,9 @@ DependecyGraph.prototype._search = function() { }); var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); + .then(function() { + return Promise.all(modulePaths.map(self._processModule.bind(self))); + }); return Promise.all([ processing, @@ -358,10 +416,8 @@ DependecyGraph.prototype._search = function() { * and update indices. */ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { - var self = this; - var packagePath; - for (var i = 0; i < files.length ; i++) { + for (var i = 0; i < files.length; i++) { var file = files[i]; if (path.basename(file) === 'package.json') { packagePath = file; @@ -406,12 +462,16 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - this._packagesById[packageJson.name] = packageJson; + if (!this._isInNodeModules(packageJson._root)) { + this._packagesById[packageJson.name] = packageJson; + } }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - delete this._packagesById[packageJson.name]; + if (!this._isInNodeModules(packageJson._root)) { + delete this._packagesById[packageJson.name]; + } }; /** @@ -441,6 +501,14 @@ DependecyGraph.prototype._processModule = function(modulePath) { return Promise.resolve(module); } + if (this._isInNodeModules(modulePath)) { + moduleData.id = this._lookupName(modulePath); + moduleData.dependencies = null; + module = new ModuleDescriptor(moduleData); + this._updateGraphWithModule(module); + return Promise.resolve(module); + } + var self = this; return readFile(modulePath, 'utf8') .then(function(content) { @@ -484,6 +552,10 @@ DependecyGraph.prototype._deleteModule = function(module) { // Others may keep a reference so we mark it as deleted. module.deleted = true; + if (this._isInNodeModules(module.path)) { + return; + } + // Haste allows different module to have the same id. if (this._moduleById[module.id] === module) { delete this._moduleById[module.id]; @@ -504,6 +576,10 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { this._graph[module.path] = module; + if (this._isInNodeModules(module.path)) { + return; + } + if (this._moduleById[module.id]) { debug( 'WARNING: Top-level module name conflict `%s`.\n' + @@ -527,28 +603,14 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { * Find the nearest package to a module. */ DependecyGraph.prototype._lookupPackage = function(modulePath) { - var packageByRoot = this._packageByRoot; + return lookupPackage(path.dirname(modulePath), this._packageByRoot); +}; - /** - * Auxiliary function to recursively lookup a package. - */ - function lookupPackage(currDir) { - // ideally we stop once we're outside root and this can be a simple child - // dir check. However, we have to support modules that was symlinked inside - // our project root. - if (currDir === '/') { - return null; - } else { - var packageJson = packageByRoot[currDir]; - if (packageJson) { - return packageJson; - } else { - return lookupPackage(path.dirname(currDir)); - } - } - } - - return lookupPackage(path.dirname(modulePath)); +/** + * Find the nearest node package to a module. + */ +DependecyGraph.prototype._lookupNodePackage = function(startPath, packageName) { + return lookupNodePackage(path.dirname(startPath), this._packageByRoot, packageName); }; /** @@ -573,12 +635,14 @@ DependecyGraph.prototype._processFileChange = function( } var isPackage = path.basename(filePath) === 'package.json'; + var packageJson; + if (isPackage) { + packageJson = this._packageByRoot[path.dirname(absPath)]; + } + if (eventType === 'delete') { - if (isPackage) { - var packageJson = this._packageByRoot[path.dirname(absPath)]; - if (packageJson) { - this._removePackageFromIndices(packageJson); - } + if (isPackage && packageJson) { + this._removePackageFromIndices(packageJson); } else { var module = this._graph[absPath]; if (module == null) { @@ -591,7 +655,14 @@ DependecyGraph.prototype._processFileChange = function( var self = this; this._loading = this._loading.then(function() { if (isPackage) { - return self._processPackage(absPath); + self._removePackageFromIndices(packageJson); + return self._processPackage(absPath) + .then(function(p) { + return self._resolvePackageMain(p); + }) + .then(function(mainModule) { + return self._processModule(mainModule.path); + }); } return self._processModule(absPath); }); @@ -675,6 +746,25 @@ DependecyGraph.prototype._isFileAsset = function(file) { return this._assetExts.indexOf(extname(file)) !== -1; }; +DependecyGraph.prototype._isInNodeModules = function(file) { + var inNodeModules = file.indexOf('/node_modules/') !== -1; + + if (!inNodeModules) { + return false; + } + + var dirs = this._providesModuleNodeModules; + + for (var i = 0; i < dirs.length; i++) { + var index = file.indexOf(dirs[i]); + if (index !== -1) { + return file.slice(index).indexOf('/node_modules/') !== -1; + } + } + + return true; +}; + /** * Extract all required modules from a `code` string. */ @@ -784,6 +874,54 @@ function extname(name) { return path.extname(name).replace(/^\./, ''); } +function realId(module) { + if (module._realId) { + return module._realId; + } + + var hash = crypto.createHash('md5'); + hash.update(module.id); + hash.update(module.path); + Object.defineProperty(module, '_realId', { value: hash.digest('hex') }); + return module._realId; +} + +/** + * Auxiliary function to recursively lookup a package. + */ +function lookupPackage(currDir, packageByRoot) { + // ideally we stop once we're outside root and this can be a simple child + // dir check. However, we have to support modules that was symlinked inside + // our project root. + if (currDir === '/') { + return null; + } else { + var packageJson = packageByRoot[currDir]; + if (packageJson) { + return packageJson; + } else { + return lookupPackage(path.dirname(currDir), packageByRoot); + } + } +} + +/** + * Auxiliary function to recursively lookup a package. + */ +function lookupNodePackage(currDir, packageByRoot, packageName) { + if (currDir === '/') { + return null; + } + var packageRoot = path.join(currDir, 'node_modules', packageName); + + var packageJson = packageByRoot[packageRoot]; + if (packageJson) { + return packageJson; + } else { + return lookupNodePackage(path.dirname(currDir), packageByRoot, packageName); + } +} + function NotFoundError() { Error.call(this); Error.captureStackTrace(this, this.constructor); diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 9bc8b8b9..1f8c9547 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -40,7 +40,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -123,7 +123,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -207,7 +207,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index da68785e..d7a8c0eb 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -91,9 +91,8 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph.load() - .then(function() { - var dependencies = depGraph.getOrderedDependencies(main); + return depGraph.getOrderedDependencies(main) + .then(function(dependencies) { var mainModuleId = dependencies[0].id; self._prependPolyfillDependencies(dependencies, opts.dev); From 581673ff3a0617abdfb895dd9999b4507462c024 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 3 Jun 2015 14:12:03 -0700 Subject: [PATCH 195/936] [react-packager] Support packages with '.' in the name Summary: @public Fixes issue #1055 For some historical reason we used to strip the extension of the module name before passing it to `resolveDependency` which is completly capable of handling all kinds of names. The fix is one line, but added a few tests for this. Test Plan: * ./runJestTests.sh * ./runJestTests.sh PacakgerIntegration * Open app and click around --- .../__tests__/DependencyGraph-test.js | 131 ++++++++++++++++++ .../haste/DependencyGraph/index.js | 3 +- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 6c0fd05f..4dce3b8a 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -577,6 +577,80 @@ describe('DependencyGraph', function() { }); }); + pit('should work with packages with a dot in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + 'require("x.y.z")', + ].join('\n'), + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + }, + 'x.y.z': { + 'package.json': JSON.stringify({ + name: 'x.y.z', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['sha.js', 'x.y.z'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'sha.js/main', + path: '/root/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'x.y.z/main', + path: '/root/x.y.z/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + pit('should default main package to index.js', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -2116,6 +2190,63 @@ describe('DependencyGraph', function() { ]); }); }); + + pit('should work with node packages with a .js in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + ].join('\n'), + 'node_modules': { + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['sha.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'sha.js/main', + path: '/root/node_modules/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); }); describe('file watch updating', function() { diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index fc899cb2..74c13127 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -144,8 +144,7 @@ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { function iter(mod) { var p = Promise.resolve(); mod.dependencies.forEach(function(name) { - var id = sansExtJs(name); - var dep = self.resolveDependency(mod, id); + var dep = self.resolveDependency(mod, name); if (dep == null) { debug( From db842e2c236bd8b7b51f80279da5396d10592248 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 4 Jun 2015 15:09:13 -0700 Subject: [PATCH 196/936] [react-packager] Fix more node_modules resolution rules Summary: @public Fixes #773 This fixes `.json` name resolution. And also reads `package.json` when doing a directory module resolution. The algorithm can be found here: https://nodejs.org/api/modules.html I'll probably start including the node (or browserify) modules test in later diffs to make sure we're fully compliant. Test Plan: * ./runJestTests.sh * ./runJestTests.sh PackagerIntegration * open playground and require a json file * test redbox --- .../__tests__/DependencyGraph-test.js | 69 ++++++++++++++++++- .../haste/DependencyGraph/index.js | 35 ++++------ 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 4dce3b8a..935c4e30 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -168,9 +168,11 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("./a.json")' + 'require("./a.json")', + 'require("./b")' ].join('\n'), 'a.json': JSON.stringify({}), + 'b.json': JSON.stringify({}), } }); @@ -186,7 +188,7 @@ describe('DependencyGraph', function() { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json'], + dependencies: ['./a.json', './b'], isAsset: false, isAsset_DEPRECATED: false, isJSON: undefined, @@ -205,6 +207,17 @@ describe('DependencyGraph', function() { resolution: undefined, resolveDependency: undefined, }, + { + id: 'package/b.json', + isJSON: true, + path: '/root/b.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -851,6 +864,58 @@ describe('DependencyGraph', function() { }); }); + pit('should resolve require to main if it is a dir w/ a package.json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'package.json': JSON.stringify({ + 'main': 'main.js', + }), + 'index.js': 'lol', + 'main.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'test/index', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: '/root/lib/main.js', + path: '/root/lib/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + pit('should ignore malformed packages', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 74c13127..1aa8b129 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -1,6 +1,3 @@ -// TODO -// Fix it to work with tests - /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -296,16 +293,18 @@ DependecyGraph.prototype.resolveDependency = function( modulePath = path.join(dir, depModuleId); modulePath = browserFieldRedirect(packageJson, modulePath); - // JS modules can be required without extensios. - if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { - modulePath = withExtJs(modulePath); - } + dep = this._graph[modulePath] || + this._graph[modulePath + '.js'] || + this._graph[modulePath + '.json']; - dep = this._graph[modulePath]; - - // Maybe the dependency is a directory and there is an index.js inside it. + // Maybe the dependency is a directory and there is a packageJson and/or index.js inside it. if (dep == null) { - dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + var dirPackageJson = this._packageByRoot[path.join(dir, depModuleId).replace(/\/$/, '')]; + if (dirPackageJson) { + dep = this._resolvePackageMain(dirPackageJson); + } else { + dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + } } // Maybe it's an asset with @n.nx resolution and the path doesn't map @@ -444,14 +443,6 @@ DependecyGraph.prototype._processPackage = function(packagePath) { return Promise.resolve(); } - if (packageJson.name == null) { - debug( - 'WARNING: package.json `%s` is missing a name field', - packagePath - ); - return Promise.resolve(); - } - packageJson._root = packageRoot; self._addPackageToIndices(packageJson); @@ -461,14 +452,14 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - if (!this._isInNodeModules(packageJson._root)) { + if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { this._packagesById[packageJson.name] = packageJson; } }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - if (!this._isInNodeModules(packageJson._root)) { + if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { delete this._packagesById[packageJson.name]; } }; @@ -536,7 +527,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { */ DependecyGraph.prototype._lookupName = function(modulePath) { var packageJson = this._lookupPackage(modulePath); - if (packageJson == null) { + if (packageJson == null || packageJson.name == null) { return path.resolve(modulePath); } else { var relativePath = From a2396a6446ab3e82332d28a515bc77d41911131e Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 4 Jun 2015 16:57:33 -0700 Subject: [PATCH 197/936] Revert [react-packager] Add support for nested node_modules --- .../DependencyResolver/ModuleDescriptor.js | 41 +- .../__tests__/ModuleDescriptor-test.js | 109 - .../__tests__/DependencyGraph-test.js | 2157 ++--------------- .../haste/DependencyGraph/index.js | 422 ++-- .../__tests__/HasteDependencyResolver-test.js | 6 +- .../src/DependencyResolver/haste/index.js | 5 +- 6 files changed, 403 insertions(+), 2337 deletions(-) delete mode 100644 react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index 3c1a1d26..90db1c4a 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -8,9 +8,6 @@ */ 'use strict'; -var Promise = require('bluebird'); -var isAbsolutePath = require('absolute-path'); - function ModuleDescriptor(fields) { if (!fields.id) { throw new Error('Missing required fields id'); @@ -20,13 +17,17 @@ function ModuleDescriptor(fields) { if (!fields.path) { throw new Error('Missing required fields path'); } - if (!isAbsolutePath(fields.path)) { - throw new Error('Expected absolute path but found: ' + fields.path); - } this.path = fields.path; + if (!fields.dependencies) { + throw new Error('Missing required fields dependencies'); + } this.dependencies = fields.dependencies; + this.resolveDependency = fields.resolveDependency; + + this.entry = fields.entry || false; + this.isPolyfill = fields.isPolyfill || false; this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; @@ -49,30 +50,12 @@ function ModuleDescriptor(fields) { this._fields = fields; } -ModuleDescriptor.prototype.loadDependencies = function(loader) { - if (!this.dependencies) { - if (this._loadingDependencies) { - return this._loadingDependencies; - } - - var self = this; - this._loadingDependencies = loader(this).then(function(dependencies) { - self.dependencies = dependencies; - }); - return this._loadingDependencies; - } - - return Promise.resolve(this.dependencies); -}; - ModuleDescriptor.prototype.toJSON = function() { - var ret = {}; - Object.keys(this).forEach(function(prop) { - if (prop[0] !== '_' && typeof this[prop] !== 'function') { - ret[prop] = this[prop]; - } - }, this); - return ret; + return { + id: this.id, + path: this.path, + dependencies: this.dependencies + }; }; module.exports = ModuleDescriptor; diff --git a/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js b/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js deleted file mode 100644 index 99bed5df..00000000 --- a/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js +++ /dev/null @@ -1,109 +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'; - -jest - .dontMock('absolute-path') - .dontMock('../ModuleDescriptor'); - - -describe('ModuleDescriptor', function() { - var ModuleDescriptor; - var Promise; - - beforeEach(function() { - ModuleDescriptor = require('../ModuleDescriptor'); - Promise = require('bluebird'); - }); - - describe('constructor', function() { - it('should validate fields', function() { - /* eslint no-new:0*/ - expect(function() { - new ModuleDescriptor({}); - }).toThrow(); - - expect(function() { - new ModuleDescriptor({ - id: 'foo', - }); - }).toThrow(); - - expect(function() { - new ModuleDescriptor({ - id: 'foo', - path: 'foo', - }); - }).toThrow(); - - expect(function() { - new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - }); - }).toThrow(); - - var m = new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - - expect(m.toJSON()).toEqual({ - altId:undefined, - dependencies: undefined, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - }); - }); - - describe('loadDependencies', function() { - pit('should load dependencies', function() { - var mod = new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - - return mod.loadDependencies(function() { - return Promise.resolve([1, 2]); - }).then(function() { - expect(mod.dependencies).toEqual([1, 2]); - }); - }); - - pit('should load cached dependencies', function() { - var mod = new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - - return mod.loadDependencies(function() { - return Promise.resolve([1, 2]); - }).then(function() { - return mod.loadDependencies(function() { - throw new Error('no!'); - }); - }).then(function() { - expect(mod.dependencies).toEqual([1, 2]); - }); - }); - }); -}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 935c4e30..c247e59d 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,12 +10,11 @@ jest .dontMock('../index') - .dontMock('crypto') .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') .dontMock('../../../../lib/getAssetDataFromName') - .dontMock('../../../ModuleDescriptor'); + .setMock('../../../ModuleDescriptor', function(data) {return data;}); jest.mock('fs'); @@ -35,14 +34,6 @@ describe('DependencyGraph', function() { }; }); - // There are a lot of crap in ModuleDescriptors, this maps an array - // to get the relevant data. - function getDataFromModules(modules) { - return modules.map(function(module) { - return module.toJSON(); - }); - } - describe('getOrderedDependencies', function() { pit('should get dependencies', function() { var root = '/root'; @@ -67,33 +58,11 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, - { - id: 'a', - altId: '/root/a.js', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, ]); }); }); @@ -126,33 +95,11 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, - { - id: 'a', - altId: '/root/a.js', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, ]); }); }); @@ -168,11 +115,9 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("./a.json")', - 'require("./b")' + 'require("./a.json")' ].join('\n'), 'a.json': JSON.stringify({}), - 'b.json': JSON.stringify({}), } }); @@ -181,42 +126,20 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json', './b'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./a.json'] }, { id: 'package/a.json', isJSON: true, path: '/root/a.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'package/b.json', - isJSON: true, - path: '/root/b.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -244,31 +167,15 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - isAsset: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, + { id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, }, ]); }); @@ -298,31 +205,20 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./imgs/a.png'] }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + { id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, }, ]); }); @@ -357,8 +253,8 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', @@ -368,13 +264,7 @@ describe('DependencyGraph', function() { './imgs/a.png', './imgs/b.png', './imgs/c.png', - ], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + ] }, { id: 'rootPackage/imgs/a.png', @@ -382,32 +272,20 @@ describe('DependencyGraph', function() { resolution: 1.5, dependencies: [], isAsset: true, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, }, { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.png', resolution: 0.7, dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + isAsset: true }, { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.png', resolution: 1, dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + isAsset: true }, ]); }); @@ -439,20 +317,14 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./imgs/a.png', 'image!a'] }, { id: 'rootPackage/imgs/a.png', @@ -460,10 +332,6 @@ describe('DependencyGraph', function() { dependencies: [], isAsset: true, resolution: 1, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, }, { id: 'image!a', @@ -471,10 +339,6 @@ describe('DependencyGraph', function() { dependencies: [], isAsset_DEPRECATED: true, resolution: 1, - isAsset: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, }, ]); }); @@ -504,33 +368,11 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'a', - altId: '/root/a.js', - path: '/root/a.js', - dependencies: ['index'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, ]); }); }); @@ -560,105 +402,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should work with packages with a dot in the name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("sha.js")', - 'require("x.y.z")', - ].join('\n'), - 'sha.js': { - 'package.json': JSON.stringify({ - name: 'sha.js', - main: 'main.js' - }), - 'main.js': 'lol' - }, - 'x.y.z': { - 'package.json': JSON.stringify({ - name: 'x.y.z', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['sha.js', 'x.y.z'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'sha.js/main', - path: '/root/sha.js/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'x.y.z/main', - path: '/root/x.y.z/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -683,30 +433,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/index', + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -735,31 +468,14 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'EpicModule', + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'EpicModule', altId: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -787,30 +503,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/lib/index', + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/lib/index', path: '/root/aPackage/lib/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -835,82 +534,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'test/index', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'test/lib/index', + {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, + { id: 'test/lib/index', path: '/root/lib/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should resolve require to main if it is a dir w/ a package.json', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'test', - }), - 'index.js': 'require("./lib/")', - lib: { - 'package.json': JSON.stringify({ - 'main': 'main.js', - }), - 'index.js': 'lol', - 'main.js': 'lol', - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'test/index', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: '/root/lib/main.js', - path: '/root/lib/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -938,21 +568,10 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, ]); }); }); @@ -993,32 +612,18 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) .toEqual([ - { - id: 'index', + { id: 'index', altId: '/root/somedir/somefile.js', path: '/root/somedir/somefile.js', - dependencies: ['c'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['c'] }, - { - id: 'c', + { id: 'c', altId: '/root/c.js', path: '/root/c.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1054,31 +659,17 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, - { - id: 'aPackage', + { id: 'aPackage', altId: '/root/b.js', path: '/root/b.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1102,19 +693,12 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['lolomg'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['lolomg'] } ]); }); @@ -1148,29 +732,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage/subdir/lolynot'] }, { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1205,30 +776,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage/subdir/lolynot'] }, - { - id: 'aPackage/subdir/lolynot', + { id: 'aPackage/subdir/lolynot', path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1263,56 +820,24 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, - { - id: 'aPackage/main', - altId: undefined, + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./subdir/lolynot'] }, - { - id: 'aPackage/subdir/lolynot', - altId: undefined, + { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['../other'] }, - { - id: 'aPackage/other', - altId: undefined, + { id: 'aPackage/other', path: '/root/aPackage/other.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1345,32 +870,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, - { - id: 'aPackage/client', - altId: undefined, + { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1403,30 +912,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', - altId: undefined, path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1461,29 +956,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1518,29 +1000,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1585,58 +1054,28 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./node', './dir/server.js'] }, { id: 'aPackage/not-node', path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./not-browser'] }, { id: 'aPackage/browser', path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, { id: 'aPackage/dir/client', path: '/root/aPackage/dir/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1681,633 +1120,20 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: ['node-package'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['node-package'] }, { id: 'browser-package/index', path: '/root/aPackage/browser-package/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - describe('node_modules', function() { - pit('should work with nested node_modules', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 1 module', - }, - } - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 2 module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('nested node_modules with specific paths', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 1 module', - 'lol.js': '', - }, - } - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 2 module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/lol', - altId: undefined, - path: '/root/node_modules/foo/node_modules/bar/lol.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('nested node_modules with browser field', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - browser: { - './lol': './wow' - } - }), - 'main.js': 'bar 1 module', - 'lol.js': '', - 'wow.js': '', - }, - } - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - browser: './main2', - }), - 'main2.js': 'bar 2 module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/wow', - path: '/root/node_modules/foo/node_modules/bar/wow.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main2', - path: '/root/node_modules/bar/main2.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('node_modules should support multi level', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': '', - }, - }, - 'path': { - 'to': { - 'bar.js': [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")', - ].join('\n'), - }, - 'node_modules': {}, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar', - path: '/root/path/to/bar.js', - altId: '/root/path/to/bar.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should selectively ignore providesModule in node_modules', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("shouldWork");', - 'require("dontWork");', - 'require("wontWork");', - ].join('\n'), - 'node_modules': { - 'react-tools': { - 'package.json': JSON.stringify({ - name: 'react-tools', - main: 'main.js', - }), - 'main.js': [ - '/**', - ' * @providesModule shouldWork', - ' */', - 'require("submodule");', - ].join('\n'), - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js':[ - '/**', - ' * @providesModule dontWork', - ' */', - 'hi();', - ].join('\n'), - }, - 'submodule': { - 'package.json': JSON.stringify({ - name: 'submodule', - main: 'main.js', - }), - 'main.js': 'log()', - }, - } - }, - 'ember': { - 'package.json': JSON.stringify({ - name: 'ember', - main: 'main.js', - }), - 'main.js':[ - '/**', - ' * @providesModule wontWork', - ' */', - 'hi();', - ].join('\n'), - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['shouldWork', 'dontWork', 'wontWork'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'shouldWork', - path: '/root/node_modules/react-tools/main.js', - altId:'react-tools/main', - dependencies: ['submodule'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'submodule/main', - path: '/root/node_modules/react-tools/node_modules/submodule/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should ignore modules it cant find (assumes own require system)', function() { - // For example SourceMap.js implements it's own require system. - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo/lol");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'foo module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should work with node packages with a .js in the name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("sha.js")', - ].join('\n'), - 'node_modules': { - 'sha.js': { - 'package.json': JSON.stringify({ - name: 'sha.js', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['sha.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'sha.js/main', - path: '/root/node_modules/sha.js/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -2361,36 +1187,22 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); }); }); }); @@ -2427,36 +1239,22 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); }); }); }); @@ -2493,31 +1291,19 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -2556,7 +1342,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -2568,54 +1354,27 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar', - altId: '/root/bar.js', - path: '/root/bar.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'] + }, + { id: 'bar', + altId: '/root/bar.js', + path: '/root/bar.js', + dependencies: ['foo'] + }, + { id: 'foo', + altId: '/root/foo.js', + path: '/root/foo.js', + dependencies: ['aPackage'] + }, ]); }); }); @@ -2641,50 +1400,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['image!foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['image!foo'] } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - isAsset: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, - }, - ]); + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + }, + { id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + }, + ]); }); }); }); @@ -2711,49 +1452,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'aPackage/index', path: '/root/index.js', - dependencies: ['./foo.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./foo.png'] } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + { id: 'index', altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'] + }, + { id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, }, ]); }); @@ -2798,7 +1520,7 @@ describe('DependencyGraph', function() { return false; } }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -2810,42 +1532,21 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage', 'foo'] }, - { - id: 'aPackage/main', + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['bar'] }, - { - id: 'foo', + { id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, ]); }); @@ -2883,407 +1584,25 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage', 'foo'] }, - { - id: 'aPackage/main', + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, - { - id: 'foo', + { id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('updates package.json', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { - filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); - triggerFileChange('change', 'index.js', root); - - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'bPackage', - main: 'main.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['bPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bPackage/main', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('changes to browser field', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - 'browser.js': 'browser', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'browser.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/browser', - path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('removes old package from cache', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - 'browser.js': 'browser', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'bPackage', - main: 'main.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('should update node package changes', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 1 module', - }, - } - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - - filesystem.root.node_modules.foo['main.js'] = 'lol'; - triggerFileChange('change', 'main.js', '/root/node_modules/foo'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('should update node package main changes', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'foo module', - 'browser.js': 'foo module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ - name: 'foo', - main: 'main.js', - browser: 'browser.js', - }); - triggerFileChange('change', 'package.json', '/root/node_modules/foo'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/browser', - altId: undefined, - path: '/root/node_modules/foo/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, ]); }); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 1aa8b129..0881e5dc 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -19,7 +19,6 @@ var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); -var crypto = require('crypto'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -46,11 +45,7 @@ var validateOpts = declareOpts({ assetExts: { type: 'array', required: true, - }, - _providesModuleNodeModules: { - type: 'array', - default: ['react-tools', 'react-native'], - }, + } }); function DependecyGraph(options) { @@ -74,8 +69,6 @@ function DependecyGraph(options) { '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' ); - this._providesModuleNodeModules = opts._providesModuleNodeModules; - // Kick off the search process to precompute the dependency graph. this._init(); } @@ -97,99 +90,58 @@ DependecyGraph.prototype.load = function() { * Given an entry file return an array of all the dependent module descriptors. */ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { - return this.load().then(function() { - var absolutePath = this._getAbsolutePath(entryPath); - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._roots - ); - } - - var module = this._graph[absolutePath]; - if (module == null) { - throw new Error('Module with path "' + entryPath + '" is not in graph'); - } - - var self = this; - var deps = []; - var visited = Object.create(null); - - // Node haste sucks. Id's aren't unique. So to make sure our entry point - // is the thing that ends up in our dependency list. - var graphMap = Object.create(this._moduleById); - graphMap[module.id] = module; - - // Recursively collect the dependency list. - function collect(mod) { - deps.push(mod); - - if (mod.dependencies == null) { - return mod.loadDependencies(function() { - return readFile(mod.path, 'utf8').then(function(content) { - return extractRequires(content); - }); - }).then(function() { - return iter(mod); - }); - } - - return iter(mod); - } - - function iter(mod) { - var p = Promise.resolve(); - mod.dependencies.forEach(function(name) { - var dep = self.resolveDependency(mod, name); - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - name, - mod.id - ); - return; - } - - p = p.then(function() { - if (!visited[realId(dep)]) { - visited[realId(dep)] = true; - return collect(dep); - } - return null; - }); - }); - return p; - } - - visited[realId(module)] = true; - return collect(module).then(function() { - return deps; - }); - }.bind(this)); -}; - -function browserFieldRedirect(packageJson, modulePath, isMain) { - if (packageJson.browser && typeof packageJson.browser === 'object') { - if (isMain) { - var tmpMain = packageJson.browser[modulePath] || - packageJson.browser[sansExtJs(modulePath)] || - packageJson.browser[withExtJs(modulePath)]; - if (tmpMain) { - return tmpMain; - } - } else { - var relPath = './' + path.relative(packageJson._root, modulePath); - var tmpModulePath = packageJson.browser[withExtJs(relPath)] || - packageJson.browser[sansExtJs(relPath)]; - if (tmpModulePath) { - return path.join(packageJson._root, tmpModulePath); - } - } + var absolutePath = this._getAbsolutePath(entryPath); + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); } - return modulePath; -} + + var module = this._graph[absolutePath]; + if (module == null) { + throw new Error('Module with path "' + entryPath + '" is not in graph'); + } + + var self = this; + var deps = []; + var visited = Object.create(null); + + // Node haste sucks. Id's aren't unique. So to make sure our entry point + // is the thing that ends up in our dependency list. + var graphMap = Object.create(this._moduleById); + graphMap[module.id] = module; + + // Recursively collect the dependency list. + function collect(module) { + deps.push(module); + + module.dependencies.forEach(function(name) { + var id = sansExtJs(name); + var dep = self.resolveDependency(module, id); + + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + name, + module.id + ); + return; + } + + if (!visited[dep.id]) { + visited[dep.id] = true; + collect(dep); + } + }); + } + + visited[module.id] = true; + collect(module); + + return deps; +}; /** * Given a module descriptor `fromModule` return the module descriptor for @@ -205,7 +157,7 @@ DependecyGraph.prototype.resolveDependency = function( // Process DEPRECATED global asset requires. if (assetMatch && assetMatch[1]) { if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARNING: Cannot find asset:', assetMatch[1]); + debug('WARINING: Cannot find asset:', assetMatch[1]); return null; } return this._assetMap_DEPRECATED[assetMatch[1]]; @@ -225,39 +177,19 @@ DependecyGraph.prototype.resolveDependency = function( depModuleId = fromPackageJson.browser[depModuleId]; } - - var packageName = depModuleId.replace(/\/.+/, ''); - packageJson = this._lookupNodePackage(fromModule.path, packageName); - - if (packageJson != null && packageName !== depModuleId) { - modulePath = path.join( - packageJson._root, - path.relative(packageName, depModuleId) - ); - - modulePath = browserFieldRedirect(packageJson, modulePath); - - dep = this._graph[withExtJs(modulePath)]; - if (dep != null) { - return dep; - } - } - // `depModuleId` is simply a top-level `providesModule`. // `depModuleId` is a package module but given the full path from the // package, i.e. package_name/module_name - if (packageJson == null && this._moduleById[sansExtJs(depModuleId)]) { + if (this._moduleById[sansExtJs(depModuleId)]) { return this._moduleById[sansExtJs(depModuleId)]; } - if (packageJson == null) { - // `depModuleId` is a package and it's depending on the "main" resolution. - packageJson = this._packagesById[depModuleId]; - } + // `depModuleId` is a package and it's depending on the "main" resolution. + packageJson = this._packagesById[depModuleId]; - // We are being forgiving here and not raising an error because we could be + // We are being forgiving here and raising an error because we could be // processing a file that uses it's own require system. - if (packageJson == null || packageName !== depModuleId) { + if (packageJson == null) { debug( 'WARNING: Cannot find required module `%s` from module `%s`.', depModuleId, @@ -266,8 +198,33 @@ DependecyGraph.prototype.resolveDependency = function( return null; } - // We are requiring node or a haste package via it's main file. - dep = this._resolvePackageMain(packageJson); + var main; + + // We prioritize the `browser` field if it's a module path. + if (typeof packageJson.browser === 'string') { + main = packageJson.browser; + } else { + main = packageJson.main || 'index'; + } + + // If there is a mapping for main in the `browser` field. + if (packageJson.browser && typeof packageJson.browser === 'object') { + var tmpMain = packageJson.browser[main] || + packageJson.browser[withExtJs(main)] || + packageJson.browser[sansExtJs(main)]; + if (tmpMain) { + main = tmpMain; + } + } + + modulePath = withExtJs(path.join(packageJson._root, main)); + dep = this._graph[modulePath]; + + // Some packages use just a dir and rely on an index.js inside that dir. + if (dep == null) { + dep = this._graph[path.join(packageJson._root, main, 'index.js')]; + } + if (dep == null) { throw new Error( 'Cannot find package main file for package: ' + packageJson._root @@ -291,22 +248,28 @@ DependecyGraph.prototype.resolveDependency = function( // modulePath: /x/y/a/b var dir = path.dirname(fromModule.path); modulePath = path.join(dir, depModuleId); - modulePath = browserFieldRedirect(packageJson, modulePath); - dep = this._graph[modulePath] || - this._graph[modulePath + '.js'] || - this._graph[modulePath + '.json']; - - // Maybe the dependency is a directory and there is a packageJson and/or index.js inside it. - if (dep == null) { - var dirPackageJson = this._packageByRoot[path.join(dir, depModuleId).replace(/\/$/, '')]; - if (dirPackageJson) { - dep = this._resolvePackageMain(dirPackageJson); - } else { - dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + if (packageJson.browser && typeof packageJson.browser === 'object') { + var relPath = './' + path.relative(packageJson._root, modulePath); + var tmpModulePath = packageJson.browser[withExtJs(relPath)] || + packageJson.browser[sansExtJs(relPath)]; + if (tmpModulePath) { + modulePath = path.join(packageJson._root, tmpModulePath); } } + // JS modules can be required without extensios. + if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { + modulePath = withExtJs(modulePath); + } + + dep = this._graph[modulePath]; + + // Maybe the dependency is a directory and there is an index.js inside it. + if (dep == null) { + dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + } + // Maybe it's an asset with @n.nx resolution and the path doesn't map // to the id if (dep == null && this._isFileAsset(modulePath)) { @@ -328,25 +291,6 @@ DependecyGraph.prototype.resolveDependency = function( } }; -DependecyGraph.prototype._resolvePackageMain = function(packageJson) { - var main; - // We prioritize the `browser` field if it's a module path. - if (typeof packageJson.browser === 'string') { - main = packageJson.browser; - } else { - main = packageJson.main || 'index'; - } - - // If there is a mapping for main in the `browser` field. - main = browserFieldRedirect(packageJson, main, true); - - var modulePath = withExtJs(path.join(packageJson._root, main)); - - return this._graph[modulePath] || - // Some packages use just a dir and rely on an index.js inside that dir. - this._graph[path.join(packageJson._root, main, 'index.js')]; -}; - /** * Intiates the filewatcher and kicks off the search process. */ @@ -395,9 +339,9 @@ DependecyGraph.prototype._search = function() { }); var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); + .then(function() { + return Promise.all(modulePaths.map(self._processModule.bind(self))); + }); return Promise.all([ processing, @@ -414,8 +358,10 @@ DependecyGraph.prototype._search = function() { * and update indices. */ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { + var self = this; + var packagePath; - for (var i = 0; i < files.length; i++) { + for (var i = 0; i < files.length ; i++) { var file = files[i]; if (path.basename(file) === 'package.json') { packagePath = file; @@ -443,6 +389,14 @@ DependecyGraph.prototype._processPackage = function(packagePath) { return Promise.resolve(); } + if (packageJson.name == null) { + debug( + 'WARNING: package.json `%s` is missing a name field', + packagePath + ); + return Promise.resolve(); + } + packageJson._root = packageRoot; self._addPackageToIndices(packageJson); @@ -452,16 +406,12 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { - this._packagesById[packageJson.name] = packageJson; - } + this._packagesById[packageJson.name] = packageJson; }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { - delete this._packagesById[packageJson.name]; - } + delete this._packagesById[packageJson.name]; }; /** @@ -491,14 +441,6 @@ DependecyGraph.prototype._processModule = function(modulePath) { return Promise.resolve(module); } - if (this._isInNodeModules(modulePath)) { - moduleData.id = this._lookupName(modulePath); - moduleData.dependencies = null; - module = new ModuleDescriptor(moduleData); - this._updateGraphWithModule(module); - return Promise.resolve(module); - } - var self = this; return readFile(modulePath, 'utf8') .then(function(content) { @@ -527,7 +469,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { */ DependecyGraph.prototype._lookupName = function(modulePath) { var packageJson = this._lookupPackage(modulePath); - if (packageJson == null || packageJson.name == null) { + if (packageJson == null) { return path.resolve(modulePath); } else { var relativePath = @@ -542,10 +484,6 @@ DependecyGraph.prototype._deleteModule = function(module) { // Others may keep a reference so we mark it as deleted. module.deleted = true; - if (this._isInNodeModules(module.path)) { - return; - } - // Haste allows different module to have the same id. if (this._moduleById[module.id] === module) { delete this._moduleById[module.id]; @@ -566,10 +504,6 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { this._graph[module.path] = module; - if (this._isInNodeModules(module.path)) { - return; - } - if (this._moduleById[module.id]) { debug( 'WARNING: Top-level module name conflict `%s`.\n' + @@ -593,14 +527,28 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { * Find the nearest package to a module. */ DependecyGraph.prototype._lookupPackage = function(modulePath) { - return lookupPackage(path.dirname(modulePath), this._packageByRoot); -}; + var packageByRoot = this._packageByRoot; -/** - * Find the nearest node package to a module. - */ -DependecyGraph.prototype._lookupNodePackage = function(startPath, packageName) { - return lookupNodePackage(path.dirname(startPath), this._packageByRoot, packageName); + /** + * Auxiliary function to recursively lookup a package. + */ + function lookupPackage(currDir) { + // ideally we stop once we're outside root and this can be a simple child + // dir check. However, we have to support modules that was symlinked inside + // our project root. + if (currDir === '/') { + return null; + } else { + var packageJson = packageByRoot[currDir]; + if (packageJson) { + return packageJson; + } else { + return lookupPackage(path.dirname(currDir)); + } + } + } + + return lookupPackage(path.dirname(modulePath)); }; /** @@ -625,14 +573,12 @@ DependecyGraph.prototype._processFileChange = function( } var isPackage = path.basename(filePath) === 'package.json'; - var packageJson; - if (isPackage) { - packageJson = this._packageByRoot[path.dirname(absPath)]; - } - if (eventType === 'delete') { - if (isPackage && packageJson) { - this._removePackageFromIndices(packageJson); + if (isPackage) { + var packageJson = this._packageByRoot[path.dirname(absPath)]; + if (packageJson) { + this._removePackageFromIndices(packageJson); + } } else { var module = this._graph[absPath]; if (module == null) { @@ -645,14 +591,7 @@ DependecyGraph.prototype._processFileChange = function( var self = this; this._loading = this._loading.then(function() { if (isPackage) { - self._removePackageFromIndices(packageJson); - return self._processPackage(absPath) - .then(function(p) { - return self._resolvePackageMain(p); - }) - .then(function(mainModule) { - return self._processModule(mainModule.path); - }); + return self._processPackage(absPath); } return self._processModule(absPath); }); @@ -736,25 +675,6 @@ DependecyGraph.prototype._isFileAsset = function(file) { return this._assetExts.indexOf(extname(file)) !== -1; }; -DependecyGraph.prototype._isInNodeModules = function(file) { - var inNodeModules = file.indexOf('/node_modules/') !== -1; - - if (!inNodeModules) { - return false; - } - - var dirs = this._providesModuleNodeModules; - - for (var i = 0; i < dirs.length; i++) { - var index = file.indexOf(dirs[i]); - if (index !== -1) { - return file.slice(index).indexOf('/node_modules/') !== -1; - } - } - - return true; -}; - /** * Extract all required modules from a `code` string. */ @@ -864,54 +784,6 @@ function extname(name) { return path.extname(name).replace(/^\./, ''); } -function realId(module) { - if (module._realId) { - return module._realId; - } - - var hash = crypto.createHash('md5'); - hash.update(module.id); - hash.update(module.path); - Object.defineProperty(module, '_realId', { value: hash.digest('hex') }); - return module._realId; -} - -/** - * Auxiliary function to recursively lookup a package. - */ -function lookupPackage(currDir, packageByRoot) { - // ideally we stop once we're outside root and this can be a simple child - // dir check. However, we have to support modules that was symlinked inside - // our project root. - if (currDir === '/') { - return null; - } else { - var packageJson = packageByRoot[currDir]; - if (packageJson) { - return packageJson; - } else { - return lookupPackage(path.dirname(currDir), packageByRoot); - } - } -} - -/** - * Auxiliary function to recursively lookup a package. - */ -function lookupNodePackage(currDir, packageByRoot, packageName) { - if (currDir === '/') { - return null; - } - var packageRoot = path.join(currDir, 'node_modules', packageName); - - var packageJson = packageByRoot[packageRoot]; - if (packageJson) { - return packageJson; - } else { - return lookupNodePackage(path.dirname(currDir), packageByRoot, packageName); - } -} - function NotFoundError() { Error.call(this); Error.captureStackTrace(this, this.constructor); diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 1f8c9547..9bc8b8b9 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -40,7 +40,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return Promise.resolve(deps); + return deps; }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -123,7 +123,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return Promise.resolve(deps); + return deps; }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -207,7 +207,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return Promise.resolve(deps); + return deps; }); depGraph.load.mockImpl(function() { return Promise.resolve(); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index d7a8c0eb..da68785e 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -91,8 +91,9 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph.getOrderedDependencies(main) - .then(function(dependencies) { + return depGraph.load() + .then(function() { + var dependencies = depGraph.getOrderedDependencies(main); var mainModuleId = dependencies[0].id; self._prependPolyfillDependencies(dependencies, opts.dev); From 712c52609b184621c6863afd7d08598c67cf10ce Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Tue, 9 Jun 2015 15:43:52 -0700 Subject: [PATCH 198/936] [ReactNative] Fix require-time redboxes --- .../DependencyResolver/haste/polyfills/require.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/haste/polyfills/require.js index 80705848..04a0bff7 100644 --- a/react-packager/src/DependencyResolver/haste/polyfills/require.js +++ b/react-packager/src/DependencyResolver/haste/polyfills/require.js @@ -117,11 +117,12 @@ } var _now = _performance ? - _performance.now.bind(_performance) : function(){return 0;}; + _performance.now.bind(_performance) : function(){ return 0; }; var _factoryStackCount = 0; var _factoryTime = 0; var _totalFactories = 0; + var _inGuard = false; /** * The require function conforming to CommonJS spec: @@ -188,9 +189,15 @@ } return module.exports; } - - if (global.ErrorUtils && !global.ErrorUtils.inGuard()) { - return ErrorUtils.applyWithGuard(require, this, arguments); + if (global.ErrorUtils && !_inGuard) { + _inGuard = true; + try { + var ret = require.apply(this, arguments); + } catch(e) { + global.ErrorUtils.reportFatalError(e); + } + _inGuard = false; + return ret; } if (!module) { From 24026b6c25d34f99afe4b6786191ef24c13c2e0b Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 11 Jun 2015 10:43:15 -0700 Subject: [PATCH 199/936] [ReactNative] Revert packager ignoring node_modules --- packager.js | 4 ++++ react-packager/src/FileWatcher/index.js | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 7c4be0a7..7dd013c6 100644 --- a/packager.js +++ b/packager.js @@ -53,6 +53,9 @@ var options = parseCommandLine([{ }, { command: 'skipflow', description: 'Disable flow checks' +}, { + command: 'nonPersistent', + description: 'Disable file watcher' }]); if (options.projectRoots) { @@ -199,6 +202,7 @@ function statusPageMiddleware(req, res, next) { function getAppMiddleware(options) { return ReactPackager.middleware({ + nonPersistent: options.nonPersistent, projectRoots: options.projectRoots, blacklistRE: blacklist(options.platform), cacheVersion: '2', diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index d90de452..cd1a28e5 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -67,7 +67,6 @@ function createWatcher(rootConfig) { var watcher = new Watcher(rootConfig.dir, { glob: rootConfig.globs, dot: false, - ignore: '**/node_modules/**/*', }); return new Promise(function(resolve, reject) { From 1af1e8bd288ed1420ff6840a3f4cbf6ae7b20f01 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 11 Jun 2015 10:43:15 -0700 Subject: [PATCH 200/936] [ReactNative] Revert packager ignoring node_modules --- packager.js | 4 ++++ react-packager/src/FileWatcher/index.js | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index 7c4be0a7..7dd013c6 100644 --- a/packager.js +++ b/packager.js @@ -53,6 +53,9 @@ var options = parseCommandLine([{ }, { command: 'skipflow', description: 'Disable flow checks' +}, { + command: 'nonPersistent', + description: 'Disable file watcher' }]); if (options.projectRoots) { @@ -199,6 +202,7 @@ function statusPageMiddleware(req, res, next) { function getAppMiddleware(options) { return ReactPackager.middleware({ + nonPersistent: options.nonPersistent, projectRoots: options.projectRoots, blacklistRE: blacklist(options.platform), cacheVersion: '2', diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index d90de452..cd1a28e5 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -67,7 +67,6 @@ function createWatcher(rootConfig) { var watcher = new Watcher(rootConfig.dir, { glob: rootConfig.globs, dot: false, - ignore: '**/node_modules/**/*', }); return new Promise(function(resolve, reject) { From 253544cb45035f3e0a3b9003eace5e09a51c5613 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 16 Jun 2015 12:04:22 -0700 Subject: [PATCH 201/936] [react-packager] Make it safe to include files without a newline at the end Summary: @public Fixes #1431 Fixes #1005 Files with no newlines and a comment at the end of the file would've caused a syntax error in the bundle: ```js __d('module', function() { hi(); // wow }) ``` This fixes the issue by inserting a new lines before `})`. Test Plan: * ./runJestTests.sh * ./runJestTests.sh PackagerIntegration * open app to the playground app * add an error * observe that the redbox has the correct lines --- .../haste/__tests__/HasteDependencyResolver-test.js | 3 ++- react-packager/src/DependencyResolver/haste/index.js | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 9bc8b8b9..0d1296ba 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -600,7 +600,8 @@ describe('HasteDependencyResolver', function() { 'require("Y")', 'require( \'z\' )', 'require( "a")', - 'require("b" )});', + 'require("b" )', + '});', ].join('\n')); }); }); diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index da68785e..aaa79c95 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -20,8 +20,7 @@ var DEFINE_MODULE_CODE = [ '_deps_,', 'function(global, require, requireDynamic, requireLazy, module, exports) {', ' _code_', - '}', - ');', + '\n});', ].join(''); var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; From 69e4217fbebe067d9dff45aefba219f866707f22 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 17 Jun 2015 07:51:48 -0700 Subject: [PATCH 202/936] [ReactNative] Refactor BatchedBridge and MessageQueue Summary: @public The current implementation of `MessageQueue` is huge, over-complicated and spread across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory` Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge` is only an instance of it. Test Plan: I had to make some updates to the tests, but no real update to the native side. There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer. Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable. Manually testing: Create a big hierarchy, like `` example. Use the `TimerMixin` example to generate multiple calls. Test the failure callback on the `Geolocation` example. All the calls go through this entry point, so it's hard to miss if it's broken. --- debugger.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/debugger.html b/debugger.html index d72e40ea..0d008455 100644 --- a/debugger.html +++ b/debugger.html @@ -48,10 +48,11 @@ var messageHandlers = { loadScript(message.url, sendReply.bind(null, null)); }, 'executeJSCall': function(message, sendReply) { - var returnValue = [[], [], [], [], []]; + var returnValue = null; try { if (window && window.require) { - returnValue = window.require(message.moduleName)[message.moduleMethod].apply(null, message.arguments); + var module = window.require(message.moduleName); + returnValue = module[message.moduleMethod].apply(module, message.arguments); } } finally { sendReply(JSON.stringify(returnValue)); From 4b9d5f8ad053fa6c8ee347c17454039f54192525 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 18 Jun 2015 21:24:21 -0700 Subject: [PATCH 203/936] [react-packager] Add tests to ensure we return all dependency types --- .../src/Packager/__tests__/Packager-test.js | 54 ++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 901c467b..89184c95 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -23,6 +23,9 @@ describe('Packager', function() { var getDependencies; var wrapModule; var Packager; + var packager; + var assetServer; + var modules; beforeEach(function() { getDependencies = jest.genMockFn(); @@ -35,30 +38,27 @@ describe('Packager', function() { }); Packager = require('../'); - }); - pit('create a package', function() { require('fs').statSync.mockImpl(function() { return { - isDirectory: function() {return true;} + isDirectory: () => true }; }); - require('fs').readFile.mockImpl(function(file, callback) { callback(null, '{"json":true}'); }); - var assetServer = { + assetServer = { getAssetData: jest.genMockFn(), }; - var packager = new Packager({ + packager = new Packager({ projectRoots: ['/root'], assetServer: assetServer, }); - var modules = [ + modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, { @@ -116,7 +116,9 @@ describe('Packager', function() { type: 'png', }; }); + }); + pit('create a package', function() { return packager.package('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0][0]).toEqual({ @@ -200,4 +202,42 @@ describe('Packager', function() { ]); }); }); + + pit('gets the list of dependencies', function() { + return packager.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', + }, + ]); + }); + }); }); From 85ac2356916610cd30fdd48967fb6cb729cf001f Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 19 Jun 2015 14:19:17 -0700 Subject: [PATCH 204/936] [ReactNative] Don't activate Chrome when debugger is already open Summary: Before this diff every time you reload in debug mode Chrome window is actiavated. Looks like that behaviour is pretty annoying. Fixes #689 @public Test Plan: ``` $ ./packager/launchChromeDevTools.applescript 'https://www.facebook.com/' ``` First time it opens a new tab and activates Chrome, running this again does not activate Chrome if the tab already exists. --- launchChromeDevTools.applescript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchChromeDevTools.applescript b/launchChromeDevTools.applescript index 4b718f5b..a8390790 100755 --- a/launchChromeDevTools.applescript +++ b/launchChromeDevTools.applescript @@ -11,7 +11,6 @@ on run argv set theURL to item 1 of argv tell application "Chrome" - activate if (count every window) = 0 then make new window @@ -40,6 +39,7 @@ on run argv set theWindow's active tab index to theTabIndex else tell window 1 + activate make new tab with properties {URL:theURL} end tell end if From 0405c827989d7a7ec7dc169e6709feb8e4a4de22 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 19 Jun 2015 15:14:39 -0700 Subject: [PATCH 205/936] [react-packager] Cache based on options, not url Summary: @public We cached based on url, which wasn't unique becuase some options would be defaulted. This was obvious when starting the server via fbrnios which tries to warmup the bundle. And then when the device woke up it will send a request (that is identical in reality) but would miss the cache. This changes the cache key into a JSON stringification of the options. Test Plan: * ./runJestTests.sh * ./fbrnios.sh run --- react-packager/src/Server/index.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 0ce5c584..3e8ca9e8 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -131,13 +131,14 @@ Server.prototype._onFileChange = function(type, filepath, root) { Server.prototype._rebuildPackages = function() { var buildPackage = this.buildPackage.bind(this); var packages = this._packages; - Object.keys(packages).forEach(function(key) { - var options = getOptionsFromUrl(key); + + Object.keys(packages).forEach(function(optionsJson) { + var options = JSON.parse(optionsJson); // Wait for a previous build (if exists) to finish. - packages[key] = (packages[key] || Promise.resolve()).finally(function() { + packages[optionsJson] = (packages[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[key] = buildPackage(options).then(function(p) { + packages[optionsJson] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({ inlineSourceMap: options.inlineSourceMap, @@ -146,7 +147,7 @@ Server.prototype._rebuildPackages = function() { return p; }); }); - return packages[key]; + return packages[optionsJson]; }); }; @@ -228,9 +229,9 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { res.end(ret); } else if (parts[1] === 'packages') { ret += '

Cached Packages

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

' + url + '

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

' + optionsJson + '

'; ret += p.getDebugInfo(); }); }, this)).then( @@ -350,10 +351,11 @@ Server.prototype.processRequest = function(req, res, next) { var startReqEventId = Activity.startEvent('request:' + req.url); var options = getOptionsFromUrl(req.url); - var building = this._packages[req.url] || this.buildPackage(options); + var optionsJson = JSON.stringify(options); + var building = this._packages[optionsJson] || this.buildPackage(options); - this._packages[req.url] = building; - building.then( + this._packages[optionsJson] = building; + building.then( function(p) { if (requestType === 'bundle') { res.end(p.getSource({ From 8faa406e9622daf6ae2e24d12e23233e037dde81 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 19 Jun 2015 18:01:21 -0700 Subject: [PATCH 206/936] [react-packager] Rewrite dependency graph (support node_modules, speed, fix bugs etc) Summary: @public Fixes #773, #1055 The resolver was getting a bit unwieldy because a lot has changed since the initial writing (porting node-haste). This also splits up a large complex file into the following: * Makes use of classes: Module, AssetModule, Package, and AssetModule_DEPRECATED (`image!` modules) * DependencyGraph is lazy for everything that isn't haste modules and packages (need to read ahead of time) * Lazy makes it fast, easier to reason about, and easier to add new loaders * Has a centralized filesystem wrapper: fast-fs (ffs) * ffs is async and lazy for any read operation and sync for directory/file lookup which makes it fast * we can easily drop in different adapters for ffs to be able to build up the tree: watchman, git ls-files, etc * use es6 for classes and easier to read promise-based code Follow up diffs will include: * Using new types (Module, AssetModule etc) in the rest of the codebase (currently we convert to plain object which is a bit of a hack) * using watchman to build up the fs * some caching at the object creation level (we are recreating Modules and Packages many times, we can cache them) * A plugin system for loaders (e.g. @tadeuzagallo wants to add a native module loader) Test Plan: * ./runJestTests.sh react-packager * ./runJestTests.sh PackagerIntegration * Export open source and run the e2e test * reset cache * ./fbrnios.sh run and click around --- blacklist.js | 1 - react-packager/.babelrc | 24 + react-packager/index.js | 4 + react-packager/src/AssetServer/index.js | 20 +- .../src/DependencyResolver/AssetModule.js | 46 + .../AssetModule_DEPRECATED.js | 40 + .../__tests__/DependencyGraph-test.js | 3241 +++++++++++++++++ .../{haste => }/DependencyGraph/docblock.js | 0 .../DependencyGraph/index.js | 556 +++ .../src/DependencyResolver/Module.js | 133 + .../src/DependencyResolver/ModuleCache.js | 72 + .../DependencyResolver/ModuleDescriptor.js | 61 - .../src/DependencyResolver/Package.js | 84 + .../__tests__/HasteDependencyResolver-test.js | 334 +- .../src/DependencyResolver/fastfs.js | 302 ++ .../__tests__/DependencyGraph-test.js | 1612 -------- .../haste/DependencyGraph/index.js | 798 ---- .../src/DependencyResolver/haste/index.js | 176 - .../src/DependencyResolver/index.js | 177 +- .../src/DependencyResolver/node/index.js | 51 - .../polyfills/Array.prototype.es6.js | 0 .../polyfills/String.prototype.es6.js | 0 .../{haste => }/polyfills/console.js | 0 .../{haste => }/polyfills/error-guard.js | 0 .../{haste => }/polyfills/polyfills.js | 0 .../{haste => }/polyfills/prelude.js | 0 .../{haste => }/polyfills/prelude_dev.js | 0 .../{haste => }/polyfills/require.js | 0 .../{haste => }/replacePatterns.js | 0 .../src/Packager/__tests__/Packager-test.js | 2 +- react-packager/src/Packager/index.js | 21 +- react-packager/src/__mocks__/fs.js | 20 +- 32 files changed, 4881 insertions(+), 2894 deletions(-) create mode 100644 react-packager/.babelrc create mode 100644 react-packager/src/DependencyResolver/AssetModule.js create mode 100644 react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js create mode 100644 react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js rename react-packager/src/DependencyResolver/{haste => }/DependencyGraph/docblock.js (100%) create mode 100644 react-packager/src/DependencyResolver/DependencyGraph/index.js create mode 100644 react-packager/src/DependencyResolver/Module.js create mode 100644 react-packager/src/DependencyResolver/ModuleCache.js delete mode 100644 react-packager/src/DependencyResolver/ModuleDescriptor.js create mode 100644 react-packager/src/DependencyResolver/Package.js rename react-packager/src/DependencyResolver/{haste => }/__tests__/HasteDependencyResolver-test.js (65%) create mode 100644 react-packager/src/DependencyResolver/fastfs.js delete mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js delete mode 100644 react-packager/src/DependencyResolver/haste/DependencyGraph/index.js delete mode 100644 react-packager/src/DependencyResolver/haste/index.js delete mode 100644 react-packager/src/DependencyResolver/node/index.js rename react-packager/src/DependencyResolver/{haste => }/polyfills/Array.prototype.es6.js (100%) rename react-packager/src/DependencyResolver/{haste => }/polyfills/String.prototype.es6.js (100%) rename react-packager/src/DependencyResolver/{haste => }/polyfills/console.js (100%) rename react-packager/src/DependencyResolver/{haste => }/polyfills/error-guard.js (100%) rename react-packager/src/DependencyResolver/{haste => }/polyfills/polyfills.js (100%) rename react-packager/src/DependencyResolver/{haste => }/polyfills/prelude.js (100%) rename react-packager/src/DependencyResolver/{haste => }/polyfills/prelude_dev.js (100%) rename react-packager/src/DependencyResolver/{haste => }/polyfills/require.js (100%) rename react-packager/src/DependencyResolver/{haste => }/replacePatterns.js (100%) diff --git a/blacklist.js b/blacklist.js index 9bfeda50..a2ba7167 100644 --- a/blacklist.js +++ b/blacklist.js @@ -11,7 +11,6 @@ // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ - __dirname, 'website', 'node_modules/react-tools/src/utils/ImmutableObject.js', 'node_modules/react-tools/src/core/ReactInstanceHandles.js', diff --git a/react-packager/.babelrc b/react-packager/.babelrc new file mode 100644 index 00000000..c1b12d81 --- /dev/null +++ b/react-packager/.babelrc @@ -0,0 +1,24 @@ +// Keep in sync with packager/transformer.js +{ + "retainLines": true, + "compact": true, + "comments": false, + "whitelist": [ + "es6.arrowFunctions", + "es6.blockScoping", + // This is the only place where we differ from transformer.js + "es6.constants", + "es6.classes", + "es6.destructuring", + "es6.parameters.rest", + "es6.properties.computed", + "es6.properties.shorthand", + "es6.spread", + "es6.templateLiterals", + "es7.trailingFunctionCommas", + "es7.objectRestSpread", + "flow", + "react" + ], + "sourceMaps": false +} diff --git a/react-packager/index.js b/react-packager/index.js index 6be11199..d4ea0dd3 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -8,6 +8,10 @@ */ 'use strict'; +require('babel/register')({ + only: /react-packager\/src/ +}); + useGracefulFs(); var Activity = require('./src/Activity'); diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 9bae2682..0479afc2 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -15,7 +15,7 @@ var Promise = require('bluebird'); var fs = require('fs'); var crypto = require('crypto'); -var lstat = Promise.promisify(fs.lstat); +var stat = Promise.promisify(fs.stat); var readDir = Promise.promisify(fs.readdir); var readFile = Promise.promisify(fs.readFile); @@ -98,14 +98,14 @@ AssetServer.prototype.getAssetData = function(assetPath) { return Promise.all( record.files.map(function(file) { - return lstat(file); + return stat(file); }) ); }).then(function(stats) { var hash = crypto.createHash('md5'); - stats.forEach(function(stat) { - hash.update(stat.mtime.getTime().toString()); + stats.forEach(function(fstat) { + hash.update(fstat.mtime.getTime().toString()); }); data.hash = hash.digest('hex'); @@ -117,18 +117,18 @@ function findRoot(roots, dir) { return Promise.some( roots.map(function(root) { var absPath = path.join(root, dir); - return lstat(absPath).then(function(stat) { - if (!stat.isDirectory()) { + return stat(absPath).then(function(fstat) { + if (!fstat.isDirectory()) { throw new Error('Looking for dirs'); } - stat._path = absPath; - return stat; + fstat._path = absPath; + return fstat; }); }), 1 ).spread( - function(stat) { - return stat._path; + function(fstat) { + return fstat._path; } ); } diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js new file mode 100644 index 00000000..431e6d01 --- /dev/null +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -0,0 +1,46 @@ +'use strict'; + +const Module = require('./Module'); +const Promise = require('bluebird'); +const getAssetDataFromName = require('../lib/getAssetDataFromName'); + +class AssetModule extends Module { + + isHaste() { + return Promise.resolve(false); + } + + getDependencies() { + return Promise.resolve([]); + } + + _read() { + return Promise.resolve({}); + } + + 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: [], + })); + } + + hash() { + return `AssetModule : ${this.path}`; + } +} + +module.exports = AssetModule; diff --git a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js new file mode 100644 index 00000000..7a25c590 --- /dev/null +++ b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -0,0 +1,40 @@ +'use strict'; + +const Module = require('./Module'); +const Promise = require('bluebird'); +const getAssetDataFromName = require('../lib/getAssetDataFromName'); + +class AssetModule_DEPRECATED extends Module { + isHaste() { + return Promise.resolve(false); + } + + getName() { + return Promise.resolve(this.name); + } + + getDependencies() { + 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}`; + } +} + +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 new file mode 100644 index 00000000..dbe7dc3a --- /dev/null +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -0,0 +1,3241 @@ +/** + * 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') + .dontMock('crypto') + .dontMock('absolute-path') + .dontMock('../docblock') + .dontMock('../../replacePatterns') + .dontMock('../../../lib/getAssetDataFromName') + .dontMock('../../fastfs') + .dontMock('../../AssetModule_DEPRECATED') + .dontMock('../../AssetModule') + .dontMock('../../Module') + .dontMock('../../Package') + .dontMock('../../ModuleCache'); + +jest.mock('fs'); + +describe('DependencyGraph', function() { + var DependencyGraph; + var fileWatcher; + var fs; + + beforeEach(function() { + fs = require('fs'); + DependencyGraph = require('../index'); + + fileWatcher = { + on: function() { + return this; + } + }; + }); + + describe('getOrderedDependencies', function() { + pit('should get dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + ]); + }); + }); + + pit('should get dependencies with the correct extensions', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + 'a.js.orig': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'a', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should get json dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'package' + }), + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./a.json")', + 'require("./b")' + ].join('\n'), + 'a.json': JSON.stringify({}), + 'b.json': JSON.stringify({}), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./a.json', './b'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'package/a.json', + isJSON: true, + path: '/root/a.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'package/b.json', + isJSON: true, + path: '/root/b.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should get dependencies with deprecated assets', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!a")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + assetRoots_DEPRECATED: ['/root/imgs'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('should get dependencies with relative assets', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./imgs/a.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('should get dependencies with assets and resolution', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png");', + 'require("./imgs/b.png");', + 'require("./imgs/c.png");', + ].join('\n'), + 'imgs': { + 'a@1.5x.png': '', + 'b@.7x.png': '', + 'c.png': '', + 'c@2x.png': '', + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: [ + './imgs/a.png', + './imgs/b.png', + './imgs/c.png', + ], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a@1.5x.png', + resolution: 1.5, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'rootPackage/imgs/b.png', + path: '/root/imgs/b@.7x.png', + resolution: 0.7, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'rootPackage/imgs/c.png', + path: '/root/imgs/c.png', + resolution: 1, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('Deprecated and relative assets can live together', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")', + 'require("image!a")', + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + assetRoots_DEPRECATED: ['/root/imgs'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./imgs/a.png', 'image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('should get recursive dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")', + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + 'require("index")', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'a', + path: '/root/a.js', + dependencies: ['index'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with packages with a dot in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + 'require("x.y.z")', + ].join('\n'), + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + }, + 'x.y.z': { + 'package.json': JSON.stringify({ + name: 'x.y.z', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['sha.js', 'x.y.z'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'sha.js/main.js', + path: '/root/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'x.y.z/main.js', + path: '/root/x.y.z/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should default main package to index.js', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': 'lol', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should resolve using alternative ids', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': [ + '/**', + ' * @providesModule EpicModule', + ' */', + ].join('\n'), + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'EpicModule', + path: '/root/aPackage/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should default use index.js if main is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'lib', + }), + lib: { + 'index.js': 'lol', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/lib/index.js', + path: '/root/aPackage/lib/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should resolve require to index if it is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'index.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'test/index.js', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'test/lib/index.js', + path: '/root/lib/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should resolve require to main if it is a dir w/ a package.json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'package.json': JSON.stringify({ + 'main': 'main.js', + }), + 'index.js': 'lol', + 'main.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'test/index.js', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: '/root/lib/main.js', + path: '/root/lib/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should ignore malformed packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + ].join('\n'), + 'aPackage': { + 'package.json': 'lol', + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('can have multiple modules with the same name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("b")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', + ].join('\n'), + 'c.js': [ + '/**', + ' * @providesModule c', + ' */', + ].join('\n'), + 'somedir': { + 'somefile.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("c")', + ].join('\n') + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/somedir/somefile.js', + dependencies: ['c'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'c', + path: '/root/c.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('providesModule wins when conflict with package', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule aPackage', + ' */', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage', + path: '/root/b.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should be forgiving with missing requires', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("lolomg")', + ].join('\n') + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['lolomg'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + } + ]); + }); + }); + + pit('should work with packages with subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/subdir/lolynot.js', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work with packages with symlinked subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'symlinkedPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + }, + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { SYMLINK: '/symlinkedPackage' }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/subdir/lolynot.js', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work with relative modules in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'require("./subdir/lolynot")', + 'subdir': { + 'lolynot.js': 'require("../other")' + }, + 'other.js': 'some code' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: ['./subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/subdir/lolynot.js', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: ['../other'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/other.js', + path: '/root/aPackage/other.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support simple browser field in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client.js', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support browser field in packages w/o .js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should support mapping main in browser field json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main.js': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work do correct browser mapping w/o js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support browser mapping of files', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + './node.js': './not-node.js', + './not-browser': './browser.js', + './dir/server.js': './dir/client', + './hello.js': './bye.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'require("./node")\nrequire("./dir/server.js")', + 'not-node.js': 'require("./not-browser")', + 'not-browser.js': 'require("./dir/server")', + 'browser.js': 'some browser code', + 'dir': { + 'server.js': 'some node code', + 'client.js': 'require("../hello")', + }, + 'hello.js': 'hello', + 'bye.js': 'bye', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: ['./node', './dir/server.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/not-node.js', + path: '/root/aPackage/not-node.js', + dependencies: ['./not-browser'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/browser.js', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/dir/client.js', + path: '/root/aPackage/dir/client.js', + dependencies: ['../hello'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/bye.js', + path: '/root/aPackage/bye.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should support browser mapping for packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + browser: { + 'node-package': 'browser-package', + } + }), + 'index.js': 'require("node-package")', + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', + }, + 'browser-package': { + 'package.json': JSON.stringify({ + 'name': 'browser-package', + }), + 'index.js': 'some browser code', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'browser-package/index.js', + path: '/root/aPackage/browser-package/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + describe('node_modules', function() { + pit('should work with nested node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('nested node_modules with specific paths', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar/");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar/lol");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + 'lol.js': '', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo', 'bar/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/lol.js', + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('nested node_modules with browser field', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar/lol");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + browser: { + './lol': './wow' + } + }), + 'main.js': 'bar 1 module', + 'lol.js': '', + 'wow.js': '', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + browser: './main2', + }), + 'main2.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/lol.js', + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main2.js', + path: '/root/node_modules/bar/main2.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('node_modules should support multi level', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': '', + }, + }, + 'path': { + 'to': { + 'bar.js': [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")', + ].join('\n'), + }, + 'node_modules': {}, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar', + path: '/root/path/to/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should selectively ignore providesModule in node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("shouldWork");', + 'require("dontWork");', + 'require("wontWork");', + ].join('\n'), + 'node_modules': { + 'react-tools': { + 'package.json': JSON.stringify({ + name: 'react-tools', + main: 'main.js', + }), + 'main.js': [ + '/**', + ' * @providesModule shouldWork', + ' */', + 'require("submodule");', + ].join('\n'), + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule dontWork', + ' */', + 'hi();', + ].join('\n'), + }, + 'submodule': { + 'package.json': JSON.stringify({ + name: 'submodule', + main: 'main.js', + }), + 'main.js': 'log()', + }, + } + }, + 'ember': { + 'package.json': JSON.stringify({ + name: 'ember', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule wontWork', + ' */', + 'hi();', + ].join('\n'), + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['shouldWork', 'dontWork', 'wontWork'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'shouldWork', + path: '/root/node_modules/react-tools/main.js', + dependencies: ['submodule'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'submodule/main.js', + path: '/root/node_modules/react-tools/node_modules/submodule/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should ignore modules it cant find (assumes own require system)', function() { + // For example SourceMap.js implements it's own require system. + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo/lol");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with node packages with a .js in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + ].join('\n'), + 'node_modules': { + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['sha.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'sha.js/main.js', + path: '/root/node_modules/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + describe('file watch updating', function() { + var triggerFileChange; + var mockStat = { + isDirectory: () => false + }; + + beforeEach(function() { + var callbacks = []; + triggerFileChange = (...args) => + callbacks.map(callback => callback(...args)); + + fileWatcher = { + on: function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + callbacks.push(callback); + return this; + } + }; + }); + + pit('updates module dependencies', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/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) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file change', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/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) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file delete', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + delete filesystem.root.foo; + triggerFileChange('delete', 'foo.js', root); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js', root, mockStat); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar', + path: '/root/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on deprecated asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!foo")' + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetRoots_DEPRECATED: [root], + assetExts: ['png'], + fileWatcher: fileWatcher, + }); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: false, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on relative asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./foo.png")' + ].join('\n'), + 'package.json': JSON.stringify({ + name: 'aPackage' + }), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetExts: ['png'], + fileWatcher: fileWatcher, + }); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('runs changes through ignore filter', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + ignoreFilePath: function(filePath) { + if (filePath === '/root/bar.js') { + return true; + } + return false; + } + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js', root, mockStat); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should ignore directory updates', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + triggerFileChange('change', 'aPackage', '/root', { + isDirectory: function(){ return true; } + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates package.json', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); + triggerFileChange('change', 'index.js', root, mockStat); + + 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) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['bPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('changes to browser field', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'browser.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/browser.js', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('removes old package from cache', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/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) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + + 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) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package main changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + 'browser.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ + name: 'foo', + main: 'main.js', + browser: 'browser.js', + }); + triggerFileChange('change', 'package.json', '/root/node_modules/foo', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/browser.js', + path: '/root/node_modules/foo/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + }); +}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/DependencyGraph/docblock.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js rename to react-packager/src/DependencyResolver/DependencyGraph/docblock.js diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js new file mode 100644 index 00000000..5bb63084 --- /dev/null +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -0,0 +1,556 @@ +/** + * 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 path = require('path'); +const Fastfs = require('../fastfs'); +const ModuleCache = require('../ModuleCache'); +const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); +const declareOpts = require('../../lib/declareOpts'); +const isAbsolutePath = require('absolute-path'); +const debug = require('debug')('DependencyGraph'); +const getAssetDataFromName = require('../../lib/getAssetDataFromName'); +const util = require('util'); +const Promise = require('bluebird'); +const _ = require('underscore'); + +const validateOpts = declareOpts({ + roots: { + type: 'array', + required: true, + }, + ignoreFilePath: { + type: 'function', + default: function(){} + }, + fileWatcher: { + type: 'object', + required: true, + }, + assetRoots_DEPRECATED: { + type: 'array', + default: [], + }, + assetExts: { + type: 'array', + required: true, + }, + providesModuleNodeModules: { + type: 'array', + default: [ + 'react-tools', + 'react-native', + // Parse requires AsyncStorage. They will + // change that to require('react-native') which + // should work after this release and we can + // remove it from here. + 'parse', + ], + }, +}); + +class DependencyGraph { + constructor(options) { + this._opts = validateOpts(options); + this._hasteMap = Object.create(null); + this._immediateResolutionCache = Object.create(null); + this.load(); + } + + load() { + if (this._loading) { + return this._loading; + } + + const modulePattern = new RegExp( + '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' + ); + + this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, { + pattern: modulePattern, + ignore: this._opts.ignoreFilePath, + }); + + this._fastfs.on('change', this._processFileChange.bind(this)); + + this._moduleCache = new ModuleCache(this._fastfs); + + this._loading = Promise.all([ + this._fastfs.build().then(() => this._buildHasteMap()), + this._buildAssetMap_DEPRECATED(), + ]); + + return this._loading; + } + + resolveDependency(fromModule, toModuleName) { + if (fromModule._ref) { + fromModule = fromModule._ref; + } + + const resHash = resolutionHash(fromModule.path, toModuleName); + + if (this._immediateResolutionCache[resHash]) { + return Promise.resolve(this._immediateResolutionCache[resHash]); + } + + const asset_DEPRECATED = this._resolveAsset_DEPRECATED( + fromModule, + toModuleName + ); + if (asset_DEPRECATED) { + return Promise.resolve(asset_DEPRECATED); + } + + const cacheResult = (result) => { + this._immediateResolutionCache[resHash] = result; + return result; + }; + + const forgive = () => { + console.warn( + 'Unable to resolve module %s from %s', + toModuleName, + fromModule.path + ); + return null; + }; + + if (!this._isNodeModulesDir(fromModule.path) + && toModuleName[0] !== '.' && + toModuleName[0] !== '/') { + return this._resolveHasteDependency(fromModule, toModuleName).catch( + () => this._resolveNodeDependency(fromModule, toModuleName) + ).then( + cacheResult, + forgive + ); + } + + return this._resolveNodeDependency(fromModule, toModuleName) + .then( + cacheResult, + forgive + ); + } + + getOrderedDependencies(entryPath) { + return this.load().then(() => { + const absolutePath = path.resolve(this._getAbsolutePath(entryPath)); + + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._opts.roots + ); + } + + const entry = this._moduleCache.getModule(absolutePath); + const deps = []; + const visited = Object.create(null); + visited[entry.hash()] = true; + + const collect = (mod) => { + deps.push(mod); + return mod.getDependencies().then( + depNames => Promise.all( + depNames.map(name => this.resolveDependency(mod, name)) + ).then((dependencies) => [depNames, dependencies]) + ).then(([depNames, dependencies]) => { + let p = Promise.resolve(); + dependencies.forEach((modDep, i) => { + if (modDep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`', + depNames[i], + mod.path + ); + return; + } + + p = p.then(() => { + if (!visited[modDep.hash()]) { + visited[modDep.hash()] = true; + return collect(modDep); + } + return null; + }); + }); + + return p; + }); + }; + + return collect(entry) + .then(() => Promise.all(deps.map(dep => dep.getPlainObject()))); + }); + } + + _getAbsolutePath(filePath) { + if (isAbsolutePath(filePath)) { + return filePath; + } + + for (let i = 0; i < this._opts.roots.length; i++) { + const root = this._opts.roots[i]; + const absPath = path.join(root, filePath); + if (this._fastfs.fileExists(absPath)) { + return absPath; + } + } + + return null; + } + + _resolveHasteDependency(fromModule, toModuleName) { + toModuleName = normalizePath(toModuleName); + + let p = fromModule.getPackage(); + if (p) { + p = p.redirectRequire(toModuleName); + } else { + p = Promise.resolve(toModuleName); + } + + return p.then((realModuleName) => { + let dep = this._hasteMap[realModuleName]; + + if (dep && dep.type === 'Module') { + return dep; + } + + let packageName = realModuleName; + + while (packageName && packageName !== '.') { + dep = this._hasteMap[packageName]; + if (dep && dep.type === 'Package') { + break; + } + packageName = path.dirname(packageName); + } + + if (dep && dep.type === 'Package') { + const potentialModulePath = path.join( + dep.root, + path.relative(packageName, realModuleName) + ); + return this._loadAsFile(potentialModulePath) + .catch(() => this._loadAsDir(potentialModulePath)); + } + + throw new Error('Unable to resolve dependency'); + }); + } + + _redirectRequire(fromModule, modulePath) { + return Promise.resolve(fromModule.getPackage()).then(p => { + if (p) { + return p.redirectRequire(modulePath); + } + return modulePath; + }); + } + + _resolveNodeDependency(fromModule, toModuleName) { + if (toModuleName[0] === '.' || toModuleName[1] === '/') { + const potentialModulePath = isAbsolutePath(toModuleName) ? + toModuleName : + path.join(path.dirname(fromModule.path), toModuleName); + return this._redirectRequire(fromModule, potentialModulePath).then( + realModuleName => this._loadAsFile(realModuleName) + .catch(() => this._loadAsDir(realModuleName)) + ); + } else { + return this._redirectRequire(fromModule, toModuleName).then( + realModuleName => { + const searchQueue = []; + for (let currDir = path.dirname(fromModule.path); + currDir !== '/'; + currDir = path.dirname(currDir)) { + searchQueue.push( + path.join(currDir, 'node_modules', realModuleName) + ); + } + + let p = Promise.reject(new Error('Node module not found')); + searchQueue.forEach(potentialModulePath => { + p = p.catch( + () => this._loadAsFile(potentialModulePath) + ).catch( + () => this._loadAsDir(potentialModulePath) + ); + }); + + return p; + }); + } + } + + _resolveAsset_DEPRECATED(fromModule, toModuleName) { + if (this._assetMap_DEPRECATED != null) { + const assetMatch = toModuleName.match(/^image!(.+)/); + // Process DEPRECATED global asset requires. + if (assetMatch && assetMatch[1]) { + if (!this._assetMap_DEPRECATED[assetMatch[1]]) { + debug('WARINING: Cannot find asset:', assetMatch[1]); + return null; + } + return this._assetMap_DEPRECATED[assetMatch[1]]; + } + } + return null; + } + + _isAssetFile(file) { + return this._opts.assetExts.indexOf(extname(file)) !== -1; + } + + _loadAsFile(potentialModulePath) { + return Promise.resolve().then(() => { + if (this._isAssetFile(potentialModulePath)) { + const {name, type} = getAssetDataFromName(potentialModulePath); + const pattern = new RegExp('^' + name + '(@[\\d\\.]+x)?\\.' + type); + // We arbitrarly grab the first one, because scale selection + // will happen somewhere + const [assetFile] = this._fastfs.matches( + path.dirname(potentialModulePath), + pattern + ); + + if (assetFile) { + return this._moduleCache.getAssetModule(assetFile); + } + } + + let file; + if (this._fastfs.fileExists(potentialModulePath)) { + file = potentialModulePath; + } else if (this._fastfs.fileExists(potentialModulePath + '.js')) { + file = potentialModulePath + '.js'; + } else if (this._fastfs.fileExists(potentialModulePath + '.json')) { + file = potentialModulePath + '.json'; + } else { + throw new Error(`File ${potentialModulePath} doesnt exist`); + } + + return this._moduleCache.getModule(file); + }); + } + + _loadAsDir(potentialDirPath) { + return Promise.resolve().then(() => { + if (!this._fastfs.dirExists(potentialDirPath)) { + throw new Error(`Invalid directory ${potentialDirPath}`); + } + + const packageJsonPath = path.join(potentialDirPath, 'package.json'); + if (this._fastfs.fileExists(packageJsonPath)) { + return this._moduleCache.getPackage(packageJsonPath) + .getMain().then( + (main) => this._loadAsFile(main).catch( + () => this._loadAsDir(main) + ) + ); + } + + return this._loadAsFile(path.join(potentialDirPath, 'index')); + }); + } + + _buildHasteMap() { + let promises = this._fastfs.findFilesByExt('js', { + ignore: (file) => this._isNodeModulesDir(file) + }).map(file => this._processHasteModule(file)); + + promises = promises.concat( + this._fastfs.findFilesByName('package.json', { + ignore: (file) => this._isNodeModulesDir(file) + }).map(file => this._processHastePackage(file)) + ); + + return Promise.all(promises); + } + + _processHasteModule(file) { + const module = this._moduleCache.getModule(file); + return module.isHaste().then( + isHaste => isHaste && module.getName() + .then(name => this._updateHasteMap(name, module)) + ); + } + + _processHastePackage(file) { + file = path.resolve(file); + const p = this._moduleCache.getPackage(file, this._fastfs); + return p.isHaste() + .then(isHaste => isHaste && p.getName() + .then(name => this._updateHasteMap(name, p))) + .catch(e => { + if (e instanceof SyntaxError) { + // Malformed package.json. + return; + } + throw e; + }); + } + + _updateHasteMap(name, mod) { + if (this._hasteMap[name]) { + debug('WARNING: conflicting haste modules: ' + name); + if (mod.type === 'Package' && + this._hasteMap[name].type === 'Module') { + // Modules takes precendence over packages. + return; + } + } + this._hasteMap[name] = mod; + } + + _isNodeModulesDir(file) { + const inNodeModules = file.indexOf('/node_modules/') !== -1; + + if (!inNodeModules) { + return false; + } + + const dirs = this._opts.providesModuleNodeModules; + + for (let i = 0; i < dirs.length; i++) { + const index = file.indexOf(dirs[i]); + if (index !== -1) { + return file.slice(index).indexOf('/node_modules/') !== -1; + } + } + + return true; + } + + _processAsset_DEPRECATED(file) { + let ext = extname(file); + if (this._opts.assetExts.indexOf(ext) !== -1) { + let name = assetName(file, ext); + if (this._assetMap_DEPRECATED[name] != null) { + debug('Conflcting assets', name); + } + + this._assetMap_DEPRECATED[name] = new AssetModule_DEPRECATED(file); + } + } + + _buildAssetMap_DEPRECATED() { + if (this._opts.assetRoots_DEPRECATED == null || + this._opts.assetRoots_DEPRECATED.length === 0) { + return Promise.resolve(); + } + + this._assetMap_DEPRECATED = Object.create(null); + + const pattern = new RegExp( + '\.(' + this._opts.assetExts.join('|') + ')$' + ); + + const fastfs = new Fastfs( + this._opts.assetRoots_DEPRECATED, + this._opts.fileWatcher, + { pattern, ignore: this._opts.ignoreFilePath } + ); + + fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this)); + + return fastfs.build().then( + () => fastfs.findFilesByExts(this._opts.assetExts).map( + file => this._processAsset_DEPRECATED(file) + ) + ); + } + + _processAssetChange_DEPRECATED(type, filePath, root, fstat) { + const name = assetName(filePath); + if (type === 'change' || type === 'delete') { + delete this._assetMap_DEPRECATED[name]; + } + + if (type === 'change' || type === 'add') { + this._loading = this._loading.then( + () => this._processAsset_DEPRECATED(path.join(root, filePath)) + ); + } + } + + _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); + + const absPath = path.join(root, filePath); + if ((fstat && fstat.isDirectory()) || + this._opts.ignoreFilePath(absPath) || + this._isNodeModulesDir(absPath)) { + return; + } + + if (type === 'delete' || type === 'change') { + _.each(this._hasteMap, (mod, name) => { + if (mod.path === absPath) { + delete this._hasteMap[name]; + } + }); + + if (type === 'delete') { + return; + } + } + + if (extname(absPath) === 'js' || extname(absPath) === 'json') { + this._loading = this._loading.then(() => { + if (path.basename(filePath) === 'package.json') { + return this._processHastePackage(absPath); + } else { + return this._processHasteModule(absPath); + } + }); + } + } +} + +function assetName(file, ext) { + return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); +} + +function extname(name) { + return path.extname(name).replace(/^\./, ''); +} + +function resolutionHash(modulePath, depName) { + return `${path.resolve(modulePath)}:${depName}`; +} + +function NotFoundError() { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + var msg = util.format.apply(util, arguments); + this.message = msg; + this.type = this.name = 'NotFoundError'; + this.status = 404; +} + +function normalizePath(modulePath) { + if (path.sep === '/') { + modulePath = path.normalize(modulePath); + } else if (path.posix) { + modulePath = path.posix.normalize(modulePath); + } + + return modulePath.replace(/\/$/, ''); +} + +util.inherits(NotFoundError, Error); + +module.exports = DependencyGraph; diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js new file mode 100644 index 00000000..d0be012b --- /dev/null +++ b/react-packager/src/DependencyResolver/Module.js @@ -0,0 +1,133 @@ +'use strict'; + +const Promise = require('bluebird'); +const docblock = require('./DependencyGraph/docblock'); +const isAbsolutePath = require('absolute-path'); +const path = require('path'); +const replacePatterns = require('./replacePatterns'); + +class Module { + + constructor(file, fastfs, moduleCache) { + if (!isAbsolutePath(file)) { + throw new Error('Expected file to be absolute path but got ' + file); + } + + this.path = path.resolve(file); + this.type = 'Module'; + + this._fastfs = fastfs; + this._moduleCache = moduleCache; + } + + isHaste() { + return this._read().then(data => !!data.id); + } + + getName() { + return this._read().then(data => { + if (data.id) { + return data.id; + } + + const p = this.getPackage(); + + if (!p) { + // Name is full path + return this.path; + } + + return p.getName() + .then(name => { + if (!name) { + return this.path; + } + + return path.join(name, path.relative(p.root, this.path)); + }); + }); + } + + getPackage() { + return this._moduleCache.getPackageForModule(this); + } + + getDependencies() { + return this._read().then(data => data.dependencies); + } + + _read() { + if (!this._reading) { + this._reading = this._fastfs.readFile(this.path).then(content => { + const data = {}; + const moduleDocBlock = docblock.parseAsObject(content); + if (moduleDocBlock.providesModule || moduleDocBlock.provides) { + data.id = /^(\S*)/.exec( + moduleDocBlock.providesModule || moduleDocBlock.provides + )[1]; + } + + // Ignore requires in generated code. An example of this is prebuilt + // files like the SourceMap library. + if ('extern' in moduleDocBlock) { + data.dependencies = []; + } else { + data.dependencies = extractRequires(content); + } + + return data; + }); + } + + return this._reading; + } + + getPlainObject() { + return Promise.all([ + this.getName(), + this.getDependencies(), + ]).then(([name, dependencies]) => this.addReference({ + path: this.path, + isJSON: path.extname(this.path) === '.json', + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + id: name, + dependencies + })); + } + + hash() { + return `Module : ${this.path}`; + } + + addReference(obj) { + Object.defineProperty(obj, '_ref', { value: this }); + return obj; + } +} + +/** + * Extract all required modules from a `code` string. + */ +var blockCommentRe = /\/\*(.|\n)*?\*\//g; +var lineCommentRe = /\/\/.+(\n|$)/g; +function extractRequires(code /*: string*/) /*: Array*/ { + var deps = []; + + code + .replace(blockCommentRe, '') + .replace(lineCommentRe, '') + .replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => { + deps.push(dep); + return match; + }) + .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { + deps.push(dep); + }); + + return deps; +} + +module.exports = Module; diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js new file mode 100644 index 00000000..3fa02cc1 --- /dev/null +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -0,0 +1,72 @@ +'use strict'; + +const AssetModule = require('./AssetModule'); +const Package = require('./Package'); +const Module = require('./Module'); +const path = require('path'); + +class ModuleCache { + + constructor(fastfs) { + this._moduleCache = Object.create(null); + this._packageCache = Object.create(null); + this._fastfs = fastfs; + fastfs.on('change', this._processFileChange.bind(this)); + } + + getModule(filePath) { + filePath = path.resolve(filePath); + if (!this._moduleCache[filePath]) { + this._moduleCache[filePath] = new Module(filePath, this._fastfs, this); + } + return this._moduleCache[filePath]; + } + + getAssetModule(filePath) { + filePath = path.resolve(filePath); + if (!this._moduleCache[filePath]) { + this._moduleCache[filePath] = new AssetModule( + filePath, + this._fastfs, + this + ); + } + return this._moduleCache[filePath]; + } + + getPackage(filePath) { + filePath = path.resolve(filePath); + if (!this._packageCache[filePath]){ + this._packageCache[filePath] = new Package(filePath, this._fastfs); + } + return this._packageCache[filePath]; + } + + getPackageForModule(module) { + // TODO(amasad): use ES6 Map. + if (module.__package) { + if (this._packageCache[module.__package]) { + return this._packageCache[module.__package]; + } else { + delete module.__package; + } + } + + const packagePath = this._fastfs.closest(module.path, 'package.json'); + + if (!packagePath) { + return null; + } + + module.__package = packagePath; + return this.getPackage(packagePath); + } + + _processFileChange(type, filePath, root) { + const absPath = path.join(root, filePath); + delete this._moduleCache[absPath]; + delete this._packageCache[absPath]; + } +} + +module.exports = ModuleCache; diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js deleted file mode 100644 index 90db1c4a..00000000 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ /dev/null @@ -1,61 +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'; - -function ModuleDescriptor(fields) { - if (!fields.id) { - throw new Error('Missing required fields id'); - } - this.id = fields.id; - - if (!fields.path) { - throw new Error('Missing required fields path'); - } - this.path = fields.path; - - if (!fields.dependencies) { - throw new Error('Missing required fields dependencies'); - } - this.dependencies = fields.dependencies; - - this.resolveDependency = fields.resolveDependency; - - this.entry = fields.entry || false; - - this.isPolyfill = fields.isPolyfill || false; - - this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; - this.isAsset = fields.isAsset || false; - - if (this.isAsset_DEPRECATED && this.isAsset) { - throw new Error('Cannot be an asset and a deprecated asset'); - } - - this.resolution = fields.resolution; - - if (this.isAsset && isNaN(this.resolution)) { - throw new Error('Expected resolution to be a number for asset modules'); - } - - this.altId = fields.altId; - - this.isJSON = fields.isJSON; - - this._fields = fields; -} - -ModuleDescriptor.prototype.toJSON = function() { - return { - id: this.id, - path: this.path, - dependencies: this.dependencies - }; -}; - -module.exports = ModuleDescriptor; diff --git a/react-packager/src/DependencyResolver/Package.js b/react-packager/src/DependencyResolver/Package.js new file mode 100644 index 00000000..f52ff42b --- /dev/null +++ b/react-packager/src/DependencyResolver/Package.js @@ -0,0 +1,84 @@ +'use strict'; + +const isAbsolutePath = require('absolute-path'); +const path = require('path'); + +class Package { + + constructor(file, fastfs) { + this.path = path.resolve(file); + this.root = path.dirname(this.path); + this._fastfs = fastfs; + this.type = 'Package'; + } + + getMain() { + return this._read().then(json => { + if (typeof json.browser === 'string') { + return path.join(this.root, json.browser); + } + + let main = json.main || 'index'; + + if (json.browser && typeof json.browser === 'object') { + main = json.browser[main] || + json.browser[main + '.js'] || + json.browser[main + '.json'] || + json.browser[main.replace(/(\.js|\.json)$/, '')] || + main; + } + + return path.join(this.root, main); + }); + } + + isHaste() { + return this._read().then(json => !!json.name); + } + + getName() { + return this._read().then(json => json.name); + } + + redirectRequire(name) { + return this._read().then(json => { + const {browser} = json; + + if (!browser || typeof browser !== 'object') { + return name; + } + + if (name[0] !== '/') { + return browser[name] || name; + } + + if (!isAbsolutePath(name)) { + throw new Error(`Expected ${name} to be absolute path`); + } + + const relPath = './' + path.relative(this.root, name); + const redirect = browser[relPath] || + browser[relPath + '.js'] || + browser[relPath + '.json']; + if (redirect) { + return path.join( + this.root, + redirect + ); + } + + return name; + }); + } + + _read() { + if (!this._reading) { + this._reading = this._fastfs.readFile(this.path) + .then(jsonStr => JSON.parse(jsonStr)); + } + + return this._reading; + } +} + +module.exports = Package; diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js similarity index 65% rename from react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js rename to react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index 0d1296ba..f9782886 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -9,9 +9,8 @@ 'use strict'; jest.dontMock('../') - .dontMock('q') - .dontMock('../replacePatterns') - .setMock('../../ModuleDescriptor', function(data) {return data;}); + .dontMock('q') + .dontMock('../replacePatterns'); jest.mock('path'); @@ -20,6 +19,11 @@ var Promise = require('bluebird'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; + function createModule(o) { + o.getPlainObject = () => Promise.resolve(o); + return o; + } + beforeEach(function() { // For the polyfillDeps require('path').join.mockImpl(function(a, b) { @@ -30,7 +34,10 @@ describe('HasteDependencyResolver', function() { describe('getDependencies', function() { pit('should get dependencies with polyfills', function() { - var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var module = createModule({ + id: 'index', + path: '/root/index.js', dependencies: ['a'] + }); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -40,7 +47,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -113,7 +120,12 @@ describe('HasteDependencyResolver', function() { }); pit('should get dependencies with polyfills', function() { - var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var module = createModule({ + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + }); + var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -123,7 +135,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -196,7 +208,11 @@ describe('HasteDependencyResolver', function() { }); pit('should pass in more polyfills', function() { - var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var module = createModule({ + id: 'index', + path: '/root/index.js', + dependencies: ['a'] + }); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -207,7 +223,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -294,7 +310,7 @@ describe('HasteDependencyResolver', function() { }); describe('wrapModule', function() { - it('should resolve modules', function() { + pit('should resolve modules', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', }); @@ -446,163 +462,165 @@ describe('HasteDependencyResolver', function() { depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { if (toModuleName === 'x') { - return { + return Promise.resolve(createModule({ id: 'changed' - }; + })); } else if (toModuleName === 'y') { - return { id: 'Y' }; + return Promise.resolve(createModule({ id: 'Y' })); } - return null; + + return Promise.resolve(null); }); - var processedCode = depResolver.wrapModule({ + return depResolver.wrapModule({ id: 'test module', path: '/root/test.js', dependencies: dependencies - }, code); + }, code).then(processedCode => { - expect(processedCode).toEqual([ - '__d(\'test module\',["changed","Y"],function(global,' + - ' require, requireDynamic, requireLazy, module, exports) { ' + - "import'x';", - "import 'changed';", - "import 'changed' ;", - "import Default from 'changed';", - "import * as All from 'changed';", - "import {} from 'changed';", - "import { } from 'changed';", - "import {Foo} from 'changed';", - "import { Foo } from 'changed';", - "import { Foo, } from 'changed';", - "import {Foo as Bar} from 'changed';", - "import { Foo as Bar } from 'changed';", - "import { Foo as Bar, } from 'changed';", - "import { Foo, Bar } from 'changed';", - "import { Foo, Bar, } from 'changed';", - "import { Foo as Bar, Baz } from 'changed';", - "import { Foo as Bar, Baz, } from 'changed';", - "import { Foo, Bar as Baz } from 'changed';", - "import { Foo, Bar as Baz, } from 'changed';", - "import { Foo as Bar, Baz as Qux } from 'changed';", - "import { Foo as Bar, Baz as Qux, } from 'changed';", - "import { Foo, Bar, Baz } from 'changed';", - "import { Foo, Bar, Baz, } from 'changed';", - "import { Foo as Bar, Baz, Qux } from 'changed';", - "import { Foo as Bar, Baz, Qux, } from 'changed';", - "import { Foo, Bar as Baz, Qux } from 'changed';", - "import { Foo, Bar as Baz, Qux, } from 'changed';", - "import { Foo, Bar, Baz as Qux } from 'changed';", - "import { Foo, Bar, Baz as Qux, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", - "import Default, * as All from 'changed';", - "import Default, { } from 'changed';", - "import Default, { Foo } from 'changed';", - "import Default, { Foo, } from 'changed';", - "import Default, { Foo as Bar } from 'changed';", - "import Default, { Foo as Bar, } from 'changed';", - "import Default, { Foo, Bar } from 'changed';", - "import Default, { Foo, Bar, } from 'changed';", - "import Default, { Foo as Bar, Baz } from 'changed';", - "import Default, { Foo as Bar, Baz, } from 'changed';", - "import Default, { Foo, Bar as Baz } from 'changed';", - "import Default, { Foo, Bar as Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz } from 'changed';", - "import Default, { Foo, Bar, Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", - "import Default , { } from 'changed';", - 'import "changed";', - 'import Default from "changed";', - 'import * as All from "changed";', - 'import { } from "changed";', - 'import { Foo } from "changed";', - 'import { Foo, } from "changed";', - 'import { Foo as Bar } from "changed";', - 'import { Foo as Bar, } from "changed";', - 'import { Foo, Bar } from "changed";', - 'import { Foo, Bar, } from "changed";', - 'import { Foo as Bar, Baz } from "changed";', - 'import { Foo as Bar, Baz, } from "changed";', - 'import { Foo, Bar as Baz } from "changed";', - 'import { Foo, Bar as Baz, } from "changed";', - 'import { Foo as Bar, Baz as Qux } from "changed";', - 'import { Foo as Bar, Baz as Qux, } from "changed";', - 'import { Foo, Bar, Baz } from "changed";', - 'import { Foo, Bar, Baz, } from "changed";', - 'import { Foo as Bar, Baz, Qux } from "changed";', - 'import { Foo as Bar, Baz, Qux, } from "changed";', - 'import { Foo, Bar as Baz, Qux } from "changed";', - 'import { Foo, Bar as Baz, Qux, } from "changed";', - 'import { Foo, Bar, Baz as Qux } from "changed";', - 'import { Foo, Bar, Baz as Qux, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', - 'import Default, * as All from "changed";', - 'import Default, { } from "changed";', - 'import Default, { Foo } from "changed";', - 'import Default, { Foo, } from "changed";', - 'import Default, { Foo as Bar } from "changed";', - 'import Default, { Foo as Bar, } from "changed";', - 'import Default, { Foo, Bar } from "changed";', - 'import Default, { Foo, Bar, } from "changed";', - 'import Default, { Foo as Bar, Baz } from "changed";', - 'import Default, { Foo as Bar, Baz, } from "changed";', - 'import Default, { Foo, Bar as Baz } from "changed";', - 'import Default, { Foo, Bar as Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz } from "changed";', - 'import Default, { Foo, Bar, Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', - 'import Default from "Y";', - 'import * as All from \'z\';', - 'require("changed")', - 'require("Y")', - 'require( \'z\' )', - 'require( "a")', - 'require("b" )', - '});', - ].join('\n')); + expect(processedCode).toEqual([ + '__d(\'test module\',["changed","Y"],function(global,' + + ' require, requireDynamic, requireLazy, module, exports) { ' + + "import'x';", + "import 'changed';", + "import 'changed' ;", + "import Default from 'changed';", + "import * as All from 'changed';", + "import {} from 'changed';", + "import { } from 'changed';", + "import {Foo} from 'changed';", + "import { Foo } from 'changed';", + "import { Foo, } from 'changed';", + "import {Foo as Bar} from 'changed';", + "import { Foo as Bar } from 'changed';", + "import { Foo as Bar, } from 'changed';", + "import { Foo, Bar } from 'changed';", + "import { Foo, Bar, } from 'changed';", + "import { Foo as Bar, Baz } from 'changed';", + "import { Foo as Bar, Baz, } from 'changed';", + "import { Foo, Bar as Baz } from 'changed';", + "import { Foo, Bar as Baz, } from 'changed';", + "import { Foo as Bar, Baz as Qux } from 'changed';", + "import { Foo as Bar, Baz as Qux, } from 'changed';", + "import { Foo, Bar, Baz } from 'changed';", + "import { Foo, Bar, Baz, } from 'changed';", + "import { Foo as Bar, Baz, Qux } from 'changed';", + "import { Foo as Bar, Baz, Qux, } from 'changed';", + "import { Foo, Bar as Baz, Qux } from 'changed';", + "import { Foo, Bar as Baz, Qux, } from 'changed';", + "import { Foo, Bar, Baz as Qux } from 'changed';", + "import { Foo, Bar, Baz as Qux, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", + "import Default, * as All from 'changed';", + "import Default, { } from 'changed';", + "import Default, { Foo } from 'changed';", + "import Default, { Foo, } from 'changed';", + "import Default, { Foo as Bar } from 'changed';", + "import Default, { Foo as Bar, } from 'changed';", + "import Default, { Foo, Bar } from 'changed';", + "import Default, { Foo, Bar, } from 'changed';", + "import Default, { Foo as Bar, Baz } from 'changed';", + "import Default, { Foo as Bar, Baz, } from 'changed';", + "import Default, { Foo, Bar as Baz } from 'changed';", + "import Default, { Foo, Bar as Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz } from 'changed';", + "import Default, { Foo, Bar, Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", + "import Default , { } from 'changed';", + 'import "changed";', + 'import Default from "changed";', + 'import * as All from "changed";', + 'import { } from "changed";', + 'import { Foo } from "changed";', + 'import { Foo, } from "changed";', + 'import { Foo as Bar } from "changed";', + 'import { Foo as Bar, } from "changed";', + 'import { Foo, Bar } from "changed";', + 'import { Foo, Bar, } from "changed";', + 'import { Foo as Bar, Baz } from "changed";', + 'import { Foo as Bar, Baz, } from "changed";', + 'import { Foo, Bar as Baz } from "changed";', + 'import { Foo, Bar as Baz, } from "changed";', + 'import { Foo as Bar, Baz as Qux } from "changed";', + 'import { Foo as Bar, Baz as Qux, } from "changed";', + 'import { Foo, Bar, Baz } from "changed";', + 'import { Foo, Bar, Baz, } from "changed";', + 'import { Foo as Bar, Baz, Qux } from "changed";', + 'import { Foo as Bar, Baz, Qux, } from "changed";', + 'import { Foo, Bar as Baz, Qux } from "changed";', + 'import { Foo, Bar as Baz, Qux, } from "changed";', + 'import { Foo, Bar, Baz as Qux } from "changed";', + 'import { Foo, Bar, Baz as Qux, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', + 'import Default, * as All from "changed";', + 'import Default, { } from "changed";', + 'import Default, { Foo } from "changed";', + 'import Default, { Foo, } from "changed";', + 'import Default, { Foo as Bar } from "changed";', + 'import Default, { Foo as Bar, } from "changed";', + 'import Default, { Foo, Bar } from "changed";', + 'import Default, { Foo, Bar, } from "changed";', + 'import Default, { Foo as Bar, Baz } from "changed";', + 'import Default, { Foo as Bar, Baz, } from "changed";', + 'import Default, { Foo, Bar as Baz } from "changed";', + 'import Default, { Foo, Bar as Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz } from "changed";', + 'import Default, { Foo, Bar, Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', + 'import Default from "Y";', + 'import * as All from \'z\';', + 'require("changed")', + 'require("Y")', + 'require( \'z\' )', + 'require( "a")', + 'require("b" )', + '});', + ].join('\n')); + }); }); }); }); diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js new file mode 100644 index 00000000..56ffe166 --- /dev/null +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -0,0 +1,302 @@ +'use strict'; + +const Promise = require('bluebird'); +const {EventEmitter} = require('events'); + +const _ = require('underscore'); +const debug = require('debug')('DependencyGraph'); +const fs = require('fs'); +const path = require('path'); + +const readDir = Promise.promisify(fs.readdir); +const readFile = Promise.promisify(fs.readFile); +const stat = Promise.promisify(fs.stat); + +class Fastfs extends EventEmitter { + constructor(roots, fileWatcher, {ignore, pattern}) { + super(); + this._fileWatcher = fileWatcher; + this._ignore = ignore; + this._pattern = pattern; + this._roots = roots.map(root => new File(root, { isDir: true })); + } + + build() { + const queue = this._roots.slice(); + return this._search(queue).then(() => { + this._fileWatcher.on('all', this._processFileChange.bind(this)); + }); + } + + stat(filePath) { + return Promise.resolve().then(() => { + const file = this._getFile(filePath); + return file.stat(); + }); + } + + getAllFiles() { + return _.chain(this._roots) + .map(root => root.getFiles()) + .flatten() + .value(); + } + + findFilesByExt(ext, { ignore }) { + return this.getAllFiles() + .filter( + file => file.ext() === ext && (!ignore || !ignore(file.path)) + ) + .map(file => file.path); + } + + findFilesByExts(exts) { + return this.getAllFiles() + .filter(file => exts.indexOf(file.ext()) !== -1) + .map(file => file.path); + } + + findFilesByName(name, { ignore }) { + return this.getAllFiles() + .filter( + file => path.basename(file.path) === name && + (!ignore || !ignore(file.path)) + ) + .map(file => file.path); + } + + readFile(filePath) { + return this._getFile(filePath).read(); + } + + closest(filePath, name) { + for (let file = this._getFile(filePath).parent; + file; + file = file.parent) { + if (file.children[name]) { + return file.children[name].path; + } + } + return null; + } + + fileExists(filePath) { + const file = this._getFile(filePath); + return file && !file.isDir; + } + + dirExists(filePath) { + const file = this._getFile(filePath); + return file && file.isDir; + } + + matches(dir, pattern) { + let dirFile = this._getFile(dir); + if (!dirFile.isDir) { + throw new Error(`Expected file ${dirFile.path} to be a directory`); + } + + return Object.keys(dirFile.children) + .filter(name => name.match(pattern)) + .map(name => path.join(dirFile.path, name)); + } + + _getRoot(filePath) { + for (let i = 0; i < this._roots.length; i++) { + let possibleRoot = this._roots[i]; + if (isDescendant(possibleRoot.path, filePath)) { + return possibleRoot; + } + } + return null; + } + + _getAndAssertRoot(filePath) { + const root = this._getRoot(filePath); + if (!root) { + throw new Error(`File ${filePath} not found in any of the roots`); + } + return root; + } + + _getFile(filePath) { + return this._getAndAssertRoot(filePath).getFileFromPath(filePath); + } + + _add(file) { + this._getAndAssertRoot(file.path).addChild(file); + } + + _search(queue) { + const dir = queue.shift(); + if (!dir) { + return Promise.resolve(); + } + + return readAndStatDir(dir.path).then(([filePaths, stats]) => { + filePaths.forEach((filePath, i) => { + if (this._ignore(filePath)) { + return; + } + + if (stats[i].isDirectory()) { + queue.push( + new File(filePath, { isDir: true, fstat: stats[i] }) + ); + return; + } + + if (filePath.match(this._pattern)) { + this._add(new File(filePath, { fstat: stats[i] })); + } + }); + return this._search(queue); + }); + } + + _processFileChange(type, filePath, root, fstat) { + const absPath = path.join(root, filePath); + if (this._ignore(absPath) || (fstat && fstat.isDirectory())) { + return; + } + + // Make sure this event belongs to one of our roots. + + if (!this._getRoot(absPath)) { + return; + } + + if (type === 'delete' || type === 'change') { + const file = this._getFile(absPath); + if (file) { + file.remove(); + } + } + + if (type !== 'delete') { + this._add(new File(absPath, { + isDir: false, + fstat + })); + } + + this.emit('change', type, filePath, root, fstat); + } +} + +class File { + constructor(filePath, {isDir, fstat}) { + this.path = filePath; + this.isDir = Boolean(isDir); + if (this.isDir) { + this.children = Object.create(null); + } + + if (fstat) { + this._stat = Promise.resolve(fstat); + } + } + + read() { + if (!this._read) { + this._read = readFile(this.path, 'utf8'); + } + return this._read; + } + + stat() { + if (!this._stat) { + this._stat = stat(this.path); + } + + return this._stat; + } + + addChild(file) { + const parts = path.relative(this.path, file.path).split(path.sep); + + if (parts.length === 0) { + return; + } + + if (parts.length === 1) { + this.children[parts[0]] = file; + file.parent = this; + } else if (this.children[parts[0]]) { + this.children[parts[0]].addChild(file); + } else { + const dir = new File(path.join(this.path, parts[0]), { isDir: true }); + dir.parent = this; + this.children[parts[0]] = dir; + dir.addChild(file); + } + } + + getFileFromPath(filePath) { + const parts = path.relative(this.path, filePath) + .split(path.sep); + + /*eslint consistent-this:0*/ + let file = this; + for (let i = 0; i < parts.length; i++) { + let fileName = parts[i]; + if (!fileName) { + continue; + } + + if (!file || !file.isDir) { + // File not found. + return null; + } + + file = file.children[fileName]; + } + + return file; + } + + getFiles() { + return _.flatten(_.values(this.children).map(file => { + if (file.isDir) { + return file.getFiles(); + } else { + return file; + } + })); + } + + ext() { + return path.extname(this.path).replace(/^\./, ''); + } + + remove() { + if (!this.parent) { + throw new Error(`No parent to delete ${this.path} from`); + } + + delete this.parent.children[path.basename(this.path)]; + } +} + +function isDescendant(root, child) { + return path.relative(root, child).indexOf('..') !== 0; +} + +function readAndStatDir(dir) { + return readDir(dir) + .then(files => Promise.all(files.map(f => path.join(dir, f)))) + .then(files => Promise.all( + files.map(f => stat(f).catch(handleBrokenLink)) + ).then(stats => [ + // Remove broken links. + files.filter((file, i ) => !!stats[i]), + stats.filter(Boolean), + ])); +} + +function handleBrokenLink(e) { + debug('WARNING: error stating, possibly broken symlink', e.message); + return Promise.resolve(); +} + +module.exports = Fastfs; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js deleted file mode 100644 index c247e59d..00000000 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ /dev/null @@ -1,1612 +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'; - -jest - .dontMock('../index') - .dontMock('absolute-path') - .dontMock('../docblock') - .dontMock('../../replacePatterns') - .dontMock('../../../../lib/getAssetDataFromName') - .setMock('../../../ModuleDescriptor', function(data) {return data;}); - -jest.mock('fs'); - -describe('DependencyGraph', function() { - var DependencyGraph; - var fileWatcher; - var fs; - - beforeEach(function() { - fs = require('fs'); - DependencyGraph = require('../index'); - - fileWatcher = { - on: function() { - return this; - } - }; - }); - - describe('getOrderedDependencies', function() { - pit('should get dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")' - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, - ]); - }); - }); - - pit('should get dependencies with the correct extensions', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")' - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - 'a.js.orig': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, - ]); - }); - }); - - pit('should get json dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'package' - }), - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./a.json")' - ].join('\n'), - 'a.json': JSON.stringify({}), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { - id: 'index', - altId: 'package/index', - path: '/root/index.js', - dependencies: ['./a.json'] - }, - { - id: 'package/a.json', - isJSON: true, - path: '/root/a.json', - dependencies: [] - }, - ]); - }); - }); - - pit('should get dependencies with deprecated assets', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("image!a")' - ].join('\n'), - 'imgs': { - 'a.png': '' - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - assetRoots_DEPRECATED: ['/root/imgs'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, - { id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); - }); - }); - - pit('should get dependencies with relative assets', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png")' - ].join('\n'), - 'imgs': { - 'a.png': '' - }, - 'package.json': JSON.stringify({ - name: 'rootPackage' - }), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { - id: 'index', - altId: 'rootPackage/index', - path: '/root/index.js', - dependencies: ['./imgs/a.png'] - }, - { id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - }, - ]); - }); - }); - - pit('should get dependencies with assets and resolution', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png");', - 'require("./imgs/b.png");', - 'require("./imgs/c.png");', - ].join('\n'), - 'imgs': { - 'a@1.5x.png': '', - 'b@.7x.png': '', - 'c.png': '', - 'c@2x.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage' - }), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { - id: 'index', - altId: 'rootPackage/index', - path: '/root/index.js', - dependencies: [ - './imgs/a.png', - './imgs/b.png', - './imgs/c.png', - ] - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a@1.5x.png', - resolution: 1.5, - dependencies: [], - isAsset: true, - }, - { - id: 'rootPackage/imgs/b.png', - path: '/root/imgs/b@.7x.png', - resolution: 0.7, - dependencies: [], - isAsset: true - }, - { - id: 'rootPackage/imgs/c.png', - path: '/root/imgs/c.png', - resolution: 1, - dependencies: [], - isAsset: true - }, - ]); - }); - }); - - pit('Deprecated and relative assets can live together', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png")', - 'require("image!a")', - ].join('\n'), - 'imgs': { - 'a.png': '' - }, - 'package.json': JSON.stringify({ - name: 'rootPackage' - }), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - assetRoots_DEPRECATED: ['/root/imgs'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { - id: 'index', - altId: 'rootPackage/index', - path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'] - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - }, - { - id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); - }); - }); - - pit('should get recursive dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - 'require("index")', - ].join('\n'), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, - ]); - }); - }); - - pit('should work with packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should default main package to index.js', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': 'lol', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/index', - path: '/root/aPackage/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should have altId for a package with providesModule', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': [ - '/**', - ' * @providesModule EpicModule', - ' */', - ].join('\n'), - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'EpicModule', - altId: 'aPackage/index', - path: '/root/aPackage/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should default use index.js if main is a dir', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'lib', - }), - lib: { - 'index.js': 'lol', - }, - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/lib/index', - path: '/root/aPackage/lib/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should resolve require to index if it is a dir', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'test', - }), - 'index.js': 'require("./lib/")', - lib: { - 'index.js': 'lol', - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, - { id: 'test/lib/index', - path: '/root/lib/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should ignore malformed packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': 'lol', - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - ]); - }); - }); - - pit('can have multiple modules with the same name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("b")', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule b', - ' */', - ].join('\n'), - 'c.js': [ - '/**', - ' * @providesModule c', - ' */', - ].join('\n'), - 'somedir': { - 'somefile.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("c")', - ].join('\n') - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) - .toEqual([ - { id: 'index', - altId: '/root/somedir/somefile.js', - path: '/root/somedir/somefile.js', - dependencies: ['c'] - }, - { id: 'c', - altId: '/root/c.js', - path: '/root/c.js', - dependencies: [] - }, - ]); - }); - }); - - pit('providesModule wins when conflict with package', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule aPackage', - ' */', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage', - altId: '/root/b.js', - path: '/root/b.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should be forgiving with missing requires', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("lolomg")', - ].join('\n') - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['lolomg'] - } - ]); - }); - }); - - pit('should work with packages with subdirs', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/subdir/lolynot")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol', - 'subdir': { - 'lolynot.js': 'lolynot' - } - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] - }, - { id: 'aPackage/subdir/lolynot', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should work with packages with symlinked subdirs', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'symlinkedPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol', - 'subdir': { - 'lolynot.js': 'lolynot' - } - }, - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/subdir/lolynot")', - ].join('\n'), - 'aPackage': { SYMLINK: '/symlinkedPackage' }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] - }, - { id: 'aPackage/subdir/lolynot', - path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should work with relative modules in packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'require("./subdir/lolynot")', - 'subdir': { - 'lolynot.js': 'require("../other")' - }, - 'other.js': 'some code' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'] - }, - { id: 'aPackage/subdir/lolynot', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'] - }, - { id: 'aPackage/other', - path: '/root/aPackage/other.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support simple browser field in packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client.js', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should supportbrowser field in packages w/o .js ext', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support mapping main in browser field json', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main.js': './client.js', - }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should work do correct browser mapping w/o js ext', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support browser mapping of files', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - './node.js': './not-node.js', - './not-browser': './browser.js', - './dir/server.js': './dir/client', - }, - }), - 'main.js': 'some other code', - 'client.js': 'require("./node")\nrequire("./dir/server.js")', - 'not-node.js': 'require("./not-browser")', - 'not-browser.js': 'require("./dir/server")', - 'browser.js': 'some browser code', - 'dir': { - 'server.js': 'some node code', - 'client.js': 'some browser code', - } - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'] - }, - { id: 'aPackage/not-node', - path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'] - }, - { id: 'aPackage/browser', - path: '/root/aPackage/browser.js', - dependencies: [] - }, - { id: 'aPackage/dir/client', - path: '/root/aPackage/dir/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support browser mapping for packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - browser: { - 'node-package': 'browser-package', - } - }), - 'index.js': 'require("node-package")', - 'node-package': { - 'package.json': JSON.stringify({ - 'name': 'node-package', - }), - 'index.js': 'some node code', - }, - 'browser-package': { - 'package.json': JSON.stringify({ - 'name': 'browser-package', - }), - 'index.js': 'some browser code', - }, - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/index', - path: '/root/aPackage/index.js', - dependencies: ['node-package'] - }, - { id: 'browser-package/index', - path: '/root/aPackage/browser-package/index.js', - dependencies: [] - }, - ]); - }); - }); - }); - - describe('file watch updating', function() { - var triggerFileChange; - - beforeEach(function() { - fileWatcher = { - on: function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - triggerFileChange = callback; - return this; - } - }; - }); - - pit('updates module dependencies', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - filesystem.root['index.js'] = - filesystem.root['index.js'].replace('require("foo")', ''); - triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file change', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - filesystem.root['index.js'] = - filesystem.root['index.js'].replace('require("foo")', ''); - triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file delete', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - delete filesystem.root.foo; - triggerFileChange('delete', 'foo.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - filesystem.root['bar.js'] = [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")' - ].join('\n'); - triggerFileChange('add', 'bar.js', root); - - filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js', root); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'] - }, - { id: 'bar', - altId: '/root/bar.js', - path: '/root/bar.js', - dependencies: ['foo'] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on deprecated asset add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("image!foo")' - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - roots: [root], - assetRoots_DEPRECATED: [root], - assetExts: ['png'], - fileWatcher: fileWatcher, - }); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'] - } - ]); - - filesystem.root['foo.png'] = ''; - triggerFileChange('add', 'foo.png', root); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'] - }, - { id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); - }); - }); - }); - - pit('updates module dependencies on relative asset add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./foo.png")' - ].join('\n'), - 'package.json': JSON.stringify({ - name: 'aPackage' - }), - }, - }); - - var dgraph = new DependencyGraph({ - roots: [root], - assetExts: ['png'], - fileWatcher: fileWatcher, - }); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'] - } - ]); - - filesystem.root['foo.png'] = ''; - triggerFileChange('add', 'foo.png', root); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'] - }, - { id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, - }, - ]); - }); - }); - }); - - pit('runs changes through ignore filter', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - ignoreFilePath: function(filePath) { - if (filePath === '/root/bar.js') { - return true; - } - return false; - } - }); - return dgraph.load().then(function() { - filesystem.root['bar.js'] = [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")' - ].join('\n'); - triggerFileChange('add', 'bar.js', root); - - filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js', root); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, - ]); - }); - }); - }); - - pit('should ignore directory updates', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - triggerFileChange('change', 'aPackage', '/root', { - isDirectory: function(){ return true; } - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, - ]); - }); - }); - }); - }); -}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js deleted file mode 100644 index 0881e5dc..00000000 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ /dev/null @@ -1,798 +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 ModuleDescriptor = require('../../ModuleDescriptor'); -var Promise = require('bluebird'); -var fs = require('fs'); -var docblock = require('./docblock'); -var replacePatterns = require('../replacePatterns'); -var path = require('path'); -var isAbsolutePath = require('absolute-path'); -var debug = require('debug')('DependecyGraph'); -var util = require('util'); -var declareOpts = require('../../../lib/declareOpts'); -var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); - -var readFile = Promise.promisify(fs.readFile); -var readDir = Promise.promisify(fs.readdir); -var lstat = Promise.promisify(fs.lstat); -var realpath = Promise.promisify(fs.realpath); - -var validateOpts = declareOpts({ - roots: { - type: 'array', - required: true, - }, - ignoreFilePath: { - type: 'function', - default: function(){} - }, - fileWatcher: { - type: 'object', - required: true, - }, - assetRoots_DEPRECATED: { - type: 'array', - default: [], - }, - assetExts: { - type: 'array', - required: true, - } -}); - -function DependecyGraph(options) { - var opts = validateOpts(options); - - this._roots = opts.roots; - this._assetRoots_DEPRECATED = opts.assetRoots_DEPRECATED; - this._assetExts = opts.assetExts; - this._ignoreFilePath = opts.ignoreFilePath; - this._fileWatcher = options.fileWatcher; - - this._loaded = false; - this._queue = this._roots.slice(); - this._graph = Object.create(null); - this._packageByRoot = Object.create(null); - this._packagesById = Object.create(null); - this._moduleById = Object.create(null); - this._debugUpdateEvents = []; - - this._moduleExtPattern = new RegExp( - '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' - ); - - // Kick off the search process to precompute the dependency graph. - this._init(); -} - -DependecyGraph.prototype.load = function() { - if (this._loading != null) { - return this._loading; - } - - this._loading = Promise.all([ - this._search(), - this._buildAssetMap_DEPRECATED(), - ]); - - return this._loading; -}; - -/** - * Given an entry file return an array of all the dependent module descriptors. - */ -DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { - var absolutePath = this._getAbsolutePath(entryPath); - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._roots - ); - } - - var module = this._graph[absolutePath]; - if (module == null) { - throw new Error('Module with path "' + entryPath + '" is not in graph'); - } - - var self = this; - var deps = []; - var visited = Object.create(null); - - // Node haste sucks. Id's aren't unique. So to make sure our entry point - // is the thing that ends up in our dependency list. - var graphMap = Object.create(this._moduleById); - graphMap[module.id] = module; - - // Recursively collect the dependency list. - function collect(module) { - deps.push(module); - - module.dependencies.forEach(function(name) { - var id = sansExtJs(name); - var dep = self.resolveDependency(module, id); - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - name, - module.id - ); - return; - } - - if (!visited[dep.id]) { - visited[dep.id] = true; - collect(dep); - } - }); - } - - visited[module.id] = true; - collect(module); - - return deps; -}; - -/** - * Given a module descriptor `fromModule` return the module descriptor for - * the required module `depModuleId`. It could be top-level or relative, - * or both. - */ -DependecyGraph.prototype.resolveDependency = function( - fromModule, - depModuleId -) { - if (this._assetMap_DEPRECATED != null) { - var assetMatch = depModuleId.match(/^image!(.+)/); - // Process DEPRECATED global asset requires. - if (assetMatch && assetMatch[1]) { - if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARINING: Cannot find asset:', assetMatch[1]); - return null; - } - return this._assetMap_DEPRECATED[assetMatch[1]]; - } - } - - var packageJson, modulePath, dep; - - // Package relative modules starts with '.' or '..'. - if (depModuleId[0] !== '.') { - - // Check if we need to map the dependency to something else via the - // `browser` field in package.json - var fromPackageJson = this._lookupPackage(fromModule.path); - if (fromPackageJson && fromPackageJson.browser && - fromPackageJson.browser[depModuleId]) { - depModuleId = fromPackageJson.browser[depModuleId]; - } - - // `depModuleId` is simply a top-level `providesModule`. - // `depModuleId` is a package module but given the full path from the - // package, i.e. package_name/module_name - if (this._moduleById[sansExtJs(depModuleId)]) { - return this._moduleById[sansExtJs(depModuleId)]; - } - - // `depModuleId` is a package and it's depending on the "main" resolution. - packageJson = this._packagesById[depModuleId]; - - // We are being forgiving here and raising an error because we could be - // processing a file that uses it's own require system. - if (packageJson == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - depModuleId, - fromModule.id - ); - return null; - } - - var main; - - // We prioritize the `browser` field if it's a module path. - if (typeof packageJson.browser === 'string') { - main = packageJson.browser; - } else { - main = packageJson.main || 'index'; - } - - // If there is a mapping for main in the `browser` field. - if (packageJson.browser && typeof packageJson.browser === 'object') { - var tmpMain = packageJson.browser[main] || - packageJson.browser[withExtJs(main)] || - packageJson.browser[sansExtJs(main)]; - if (tmpMain) { - main = tmpMain; - } - } - - modulePath = withExtJs(path.join(packageJson._root, main)); - dep = this._graph[modulePath]; - - // Some packages use just a dir and rely on an index.js inside that dir. - if (dep == null) { - dep = this._graph[path.join(packageJson._root, main, 'index.js')]; - } - - if (dep == null) { - throw new Error( - 'Cannot find package main file for package: ' + packageJson._root - ); - } - return dep; - } else { - - // `depModuleId` is a module defined in a package relative to `fromModule`. - packageJson = this._lookupPackage(fromModule.path); - - if (packageJson == null) { - throw new Error( - 'Expected relative module lookup from ' + fromModule.id + ' to ' + - depModuleId + ' to be within a package but no package.json found.' - ); - } - - // Example: depModuleId: ../a/b - // fromModule.path: /x/y/z - // modulePath: /x/y/a/b - var dir = path.dirname(fromModule.path); - modulePath = path.join(dir, depModuleId); - - if (packageJson.browser && typeof packageJson.browser === 'object') { - var relPath = './' + path.relative(packageJson._root, modulePath); - var tmpModulePath = packageJson.browser[withExtJs(relPath)] || - packageJson.browser[sansExtJs(relPath)]; - if (tmpModulePath) { - modulePath = path.join(packageJson._root, tmpModulePath); - } - } - - // JS modules can be required without extensios. - if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { - modulePath = withExtJs(modulePath); - } - - dep = this._graph[modulePath]; - - // Maybe the dependency is a directory and there is an index.js inside it. - if (dep == null) { - dep = this._graph[path.join(dir, depModuleId, 'index.js')]; - } - - // Maybe it's an asset with @n.nx resolution and the path doesn't map - // to the id - if (dep == null && this._isFileAsset(modulePath)) { - dep = this._moduleById[this._lookupName(modulePath)]; - } - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.' + - ' Inferred required module path is %s', - depModuleId, - fromModule.id, - modulePath - ); - return null; - } - - return dep; - } -}; - -/** - * Intiates the filewatcher and kicks off the search process. - */ -DependecyGraph.prototype._init = function() { - var processChange = this._processFileChange.bind(this); - var watcher = this._fileWatcher; - - this._loading = this.load().then(function() { - watcher.on('all', processChange); - }); -}; - -/** - * Implements a DFS over the file system looking for modules and packages. - */ -DependecyGraph.prototype._search = function() { - var self = this; - var dir = this._queue.shift(); - - if (dir == null) { - return Promise.resolve(this._graph); - } - - // Steps: - // 1. Read a dir and stat all the entries. - // 2. Filter the files and queue up the directories. - // 3. Process any package.json in the files - // 4. recur. - return readAndStatDir(dir) - .spread(function(files, stats) { - var modulePaths = files.filter(function(filePath, i) { - if (self._ignoreFilePath(filePath)) { - return false; - } - - if (stats[i].isDirectory()) { - self._queue.push(filePath); - return false; - } - - if (stats[i].isSymbolicLink()) { - return false; - } - - return filePath.match(self._moduleExtPattern); - }); - - var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); - - return Promise.all([ - processing, - self._search() - ]); - }) - .then(function() { - return self; - }); -}; - -/** - * Given a list of files find a `package.json` file, and if found parse it - * and update indices. - */ -DependecyGraph.prototype._findAndProcessPackage = function(files, root) { - var self = this; - - var packagePath; - for (var i = 0; i < files.length ; i++) { - var file = files[i]; - if (path.basename(file) === 'package.json') { - packagePath = file; - break; - } - } - - if (packagePath != null) { - return this._processPackage(packagePath); - } else { - return Promise.resolve(); - } -}; - -DependecyGraph.prototype._processPackage = function(packagePath) { - var packageRoot = path.dirname(packagePath); - var self = this; - return readFile(packagePath, 'utf8') - .then(function(content) { - var packageJson; - try { - packageJson = JSON.parse(content); - } catch (e) { - debug('WARNING: malformed package.json: ', packagePath); - return Promise.resolve(); - } - - if (packageJson.name == null) { - debug( - 'WARNING: package.json `%s` is missing a name field', - packagePath - ); - return Promise.resolve(); - } - - packageJson._root = packageRoot; - self._addPackageToIndices(packageJson); - - return packageJson; - }); -}; - -DependecyGraph.prototype._addPackageToIndices = function(packageJson) { - this._packageByRoot[packageJson._root] = packageJson; - this._packagesById[packageJson.name] = packageJson; -}; - -DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { - delete this._packageByRoot[packageJson._root]; - delete this._packagesById[packageJson.name]; -}; - -/** - * Parse a module and update indices. - */ -DependecyGraph.prototype._processModule = function(modulePath) { - var moduleData = { path: path.resolve(modulePath) }; - var module; - - if (this._assetExts.indexOf(extname(modulePath)) > -1) { - var assetData = getAssetDataFromName(this._lookupName(modulePath)); - moduleData.id = assetData.assetName; - moduleData.resolution = assetData.resolution; - moduleData.isAsset = true; - moduleData.dependencies = []; - module = new ModuleDescriptor(moduleData); - this._updateGraphWithModule(module); - return Promise.resolve(module); - } - - if (extname(modulePath) === 'json') { - moduleData.id = this._lookupName(modulePath); - moduleData.isJSON = true; - moduleData.dependencies = []; - module = new ModuleDescriptor(moduleData); - this._updateGraphWithModule(module); - return Promise.resolve(module); - } - - var self = this; - return readFile(modulePath, 'utf8') - .then(function(content) { - var moduleDocBlock = docblock.parseAsObject(content); - if (moduleDocBlock.providesModule || moduleDocBlock.provides) { - moduleData.id = /^(\S*)/.exec( - moduleDocBlock.providesModule || moduleDocBlock.provides - )[1]; - - // Incase someone wants to require this module via - // packageName/path/to/module - moduleData.altId = self._lookupName(modulePath); - } else { - moduleData.id = self._lookupName(modulePath); - } - moduleData.dependencies = extractRequires(content); - - module = new ModuleDescriptor(moduleData); - self._updateGraphWithModule(module); - return module; - }); -}; - -/** - * Compute the name of module relative to a package it may belong to. - */ -DependecyGraph.prototype._lookupName = function(modulePath) { - var packageJson = this._lookupPackage(modulePath); - if (packageJson == null) { - return path.resolve(modulePath); - } else { - var relativePath = - sansExtJs(path.relative(packageJson._root, modulePath)); - return path.join(packageJson.name, relativePath); - } -}; - -DependecyGraph.prototype._deleteModule = function(module) { - delete this._graph[module.path]; - - // Others may keep a reference so we mark it as deleted. - module.deleted = true; - - // Haste allows different module to have the same id. - if (this._moduleById[module.id] === module) { - delete this._moduleById[module.id]; - } - - if (module.altId && this._moduleById[module.altId] === module) { - delete this._moduleById[module.altId]; - } -}; - -/** - * Update the graph and indices with the module. - */ -DependecyGraph.prototype._updateGraphWithModule = function(module) { - if (this._graph[module.path]) { - this._deleteModule(this._graph[module.path]); - } - - this._graph[module.path] = module; - - if (this._moduleById[module.id]) { - debug( - 'WARNING: Top-level module name conflict `%s`.\n' + - 'module with path `%s` will replace `%s`', - module.id, - module.path, - this._moduleById[module.id].path - ); - } - - this._moduleById[module.id] = module; - - // Some module maybe refrenced by both @providesModule and - // require(package/moduleName). - if (module.altId != null && this._moduleById[module.altId] == null) { - this._moduleById[module.altId] = module; - } -}; - -/** - * Find the nearest package to a module. - */ -DependecyGraph.prototype._lookupPackage = function(modulePath) { - var packageByRoot = this._packageByRoot; - - /** - * Auxiliary function to recursively lookup a package. - */ - function lookupPackage(currDir) { - // ideally we stop once we're outside root and this can be a simple child - // dir check. However, we have to support modules that was symlinked inside - // our project root. - if (currDir === '/') { - return null; - } else { - var packageJson = packageByRoot[currDir]; - if (packageJson) { - return packageJson; - } else { - return lookupPackage(path.dirname(currDir)); - } - } - } - - return lookupPackage(path.dirname(modulePath)); -}; - -/** - * Process a filewatcher change event. - */ -DependecyGraph.prototype._processFileChange = function( - eventType, - filePath, - root, - stat -) { - var absPath = path.join(root, filePath); - if (this._ignoreFilePath(absPath)) { - return; - } - - this._debugUpdateEvents.push({event: eventType, path: filePath}); - - if (this._assetExts.indexOf(extname(filePath)) > -1) { - this._processAssetChange_DEPRECATED(eventType, absPath); - // Fall through because new-style assets are actually modules. - } - - var isPackage = path.basename(filePath) === 'package.json'; - if (eventType === 'delete') { - if (isPackage) { - var packageJson = this._packageByRoot[path.dirname(absPath)]; - if (packageJson) { - this._removePackageFromIndices(packageJson); - } - } else { - var module = this._graph[absPath]; - if (module == null) { - return; - } - - this._deleteModule(module); - } - } else if (!(stat && stat.isDirectory())) { - var self = this; - this._loading = this._loading.then(function() { - if (isPackage) { - return self._processPackage(absPath); - } - return self._processModule(absPath); - }); - } -}; - -DependecyGraph.prototype.getDebugInfo = function() { - return '

FileWatcher Update Events

' + - '
' + util.inspect(this._debugUpdateEvents) + '
' + - '

Graph dump

' + - '
' + util.inspect(this._graph) + '
'; -}; - -/** - * Searches all roots for the file and returns the first one that has file of - * the same path. - */ -DependecyGraph.prototype._getAbsolutePath = function(filePath) { - if (isAbsolutePath(filePath)) { - return filePath; - } - - for (var i = 0; i < this._roots.length; i++) { - var root = this._roots[i]; - var absPath = path.join(root, filePath); - if (this._graph[absPath]) { - return absPath; - } - } - - return null; -}; - -DependecyGraph.prototype._buildAssetMap_DEPRECATED = function() { - if (this._assetRoots_DEPRECATED == null || - this._assetRoots_DEPRECATED.length === 0) { - return Promise.resolve(); - } - - this._assetMap_DEPRECATED = Object.create(null); - return buildAssetMap_DEPRECATED( - this._assetRoots_DEPRECATED, - this._processAsset_DEPRECATED.bind(this) - ); -}; - -DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { - var ext = extname(file); - if (this._assetExts.indexOf(ext) !== -1) { - var name = assetName(file, ext); - if (this._assetMap_DEPRECATED[name] != null) { - debug('Conflcting assets', name); - } - - this._assetMap_DEPRECATED[name] = new ModuleDescriptor({ - id: 'image!' + name, - path: path.resolve(file), - isAsset_DEPRECATED: true, - dependencies: [], - resolution: getAssetDataFromName(file).resolution, - }); - } -}; - -DependecyGraph.prototype._processAssetChange_DEPRECATED = function(eventType, file) { - if (this._assetMap_DEPRECATED == null) { - return; - } - - var name = assetName(file, extname(file)); - if (eventType === 'change' || eventType === 'delete') { - delete this._assetMap_DEPRECATED[name]; - } - - if (eventType === 'change' || eventType === 'add') { - this._processAsset_DEPRECATED(file); - } -}; - -DependecyGraph.prototype._isFileAsset = function(file) { - return this._assetExts.indexOf(extname(file)) !== -1; -}; - -/** - * Extract all required modules from a `code` string. - */ -var blockCommentRe = /\/\*(.|\n)*?\*\//g; -var lineCommentRe = /\/\/.+(\n|$)/g; -function extractRequires(code) { - var deps = []; - - code - .replace(blockCommentRe, '') - .replace(lineCommentRe, '') - .replace(replacePatterns.IMPORT_RE, function(match, pre, quot, dep, post) { - deps.push(dep); - return match; - }) - .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { - deps.push(dep); - }); - - return deps; -} - -/** - * `file` without the .js extension. - */ -function sansExtJs(file) { - if (file.match(/\.js$/)) { - return file.slice(0, -3); - } else { - return file; - } -} - -/** - * `file` with the .js extension. - */ -function withExtJs(file) { - if (file.match(/\.js$/)) { - return file; - } else { - return file + '.js'; - } -} - -function handleBrokenLink(e) { - debug('WARNING: error stating, possibly broken symlink', e.message); - return Promise.resolve(); -} - -function readAndStatDir(dir) { - return readDir(dir) - .then(function(files){ - return Promise.all(files.map(function(filePath) { - return realpath(path.join(dir, filePath)).catch(handleBrokenLink); - })); - }).then(function(files) { - files = files.filter(function(f) { - return !!f; - }); - - var stats = files.map(function(filePath) { - return lstat(filePath).catch(handleBrokenLink); - }); - - return [ - files, - Promise.all(stats), - ]; - }); -} - -/** - * Given a list of roots and list of extensions find all the files in - * the directory with that extension and build a map of those assets. - */ -function buildAssetMap_DEPRECATED(roots, processAsset) { - var queue = roots.slice(0); - - function search() { - var root = queue.shift(); - - if (root == null) { - return Promise.resolve(); - } - - return readAndStatDir(root).spread(function(files, stats) { - files.forEach(function(file, i) { - if (stats[i].isDirectory()) { - queue.push(file); - } else { - processAsset(file); - } - }); - - return search(); - }); - } - - return search(); -} - -function assetName(file, ext) { - return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); -} - -function extname(name) { - return path.extname(name).replace(/^\./, ''); -} - -function NotFoundError() { - Error.call(this); - Error.captureStackTrace(this, this.constructor); - var msg = util.format.apply(util, arguments); - this.message = msg; - this.type = this.name = 'NotFoundError'; - this.status = 404; -} - -util.inherits(NotFoundError, Error); - -module.exports = DependecyGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js deleted file mode 100644 index aaa79c95..00000000 --- a/react-packager/src/DependencyResolver/haste/index.js +++ /dev/null @@ -1,176 +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 path = require('path'); -var DependencyGraph = require('./DependencyGraph'); -var replacePatterns = require('./replacePatterns'); -var ModuleDescriptor = require('../ModuleDescriptor'); -var declareOpts = require('../../lib/declareOpts'); - -var DEFINE_MODULE_CODE = [ - '__d(', - '\'_moduleName_\',', - '_deps_,', - 'function(global, require, requireDynamic, requireLazy, module, exports) {', - ' _code_', - '\n});', -].join(''); - -var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; - -var validateOpts = declareOpts({ - projectRoots: { - type: 'array', - required: true, - }, - blacklistRE: { - type: 'object', // typeof regex is object - }, - polyfillModuleNames: { - type: 'array', - default: [], - }, - nonPersistent: { - type: 'boolean', - default: false, - }, - moduleFormat: { - type: 'string', - default: 'haste', - }, - assetRoots: { - type: 'array', - default: [], - }, - fileWatcher: { - type: 'object', - required: true, - }, - assetExts: { - type: 'array', - required: true, - } -}); - -function HasteDependencyResolver(options) { - var opts = validateOpts(options); - - this._depGraph = new DependencyGraph({ - roots: opts.projectRoots, - assetRoots_DEPRECATED: opts.assetRoots, - assetExts: opts.assetExts, - ignoreFilePath: function(filepath) { - return filepath.indexOf('__tests__') !== -1 || - (opts.blacklistRE && opts.blacklistRE.test(filepath)); - }, - fileWatcher: opts.fileWatcher, - }); - - - this._polyfillModuleNames = opts.polyfillModuleNames || []; -} - -var getDependenciesValidateOpts = declareOpts({ - dev: { - type: 'boolean', - default: true, - }, -}); - -HasteDependencyResolver.prototype.getDependencies = function(main, options) { - var opts = getDependenciesValidateOpts(options); - - var depGraph = this._depGraph; - var self = this; - - return depGraph.load() - .then(function() { - var dependencies = depGraph.getOrderedDependencies(main); - var mainModuleId = dependencies[0].id; - - self._prependPolyfillDependencies(dependencies, opts.dev); - - return { - mainModuleId: mainModuleId, - dependencies: dependencies - }; - }); -}; - -HasteDependencyResolver.prototype._prependPolyfillDependencies = function( - dependencies, - isDev -) { - var polyfillModuleNames = [ - isDev - ? path.join(__dirname, 'polyfills/prelude_dev.js') - : path.join(__dirname, 'polyfills/prelude.js'), - path.join(__dirname, 'polyfills/require.js'), - path.join(__dirname, 'polyfills/polyfills.js'), - path.join(__dirname, 'polyfills/console.js'), - path.join(__dirname, 'polyfills/error-guard.js'), - path.join(__dirname, 'polyfills/String.prototype.es6.js'), - path.join(__dirname, 'polyfills/Array.prototype.es6.js'), - ].concat(this._polyfillModuleNames); - - var polyfillModules = polyfillModuleNames.map( - function(polyfillModuleName, idx) { - return new ModuleDescriptor({ - path: polyfillModuleName, - id: polyfillModuleName, - dependencies: polyfillModuleNames.slice(0, idx), - isPolyfill: true - }); - } - ); - dependencies.unshift.apply(dependencies, polyfillModules); -}; - -HasteDependencyResolver.prototype.wrapModule = function(module, code) { - if (module.isPolyfill) { - return code; - } - - var resolvedDeps = Object.create(null); - var resolvedDepsArr = []; - - for (var i = 0; i < module.dependencies.length; i++) { - var depName = module.dependencies[i]; - var dep = this._depGraph.resolveDependency(module, depName); - if (dep) { - resolvedDeps[depName] = dep.id; - resolvedDepsArr.push(dep.id); - } - } - - var relativizeCode = function(codeMatch, pre, quot, depName, post) { - var depId = resolvedDeps[depName]; - if (depId) { - return pre + quot + depId + post; - } else { - return codeMatch; - } - }; - - return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) { - return { - '_moduleName_': module.id, - '_code_': code.replace(replacePatterns.IMPORT_RE, relativizeCode) - .replace(replacePatterns.REQUIRE_RE, relativizeCode), - '_deps_': JSON.stringify(resolvedDepsArr), - }[key]; - }); -}; - -HasteDependencyResolver.prototype.getDebugInfo = function() { - return this._depGraph.getDebugInfo(); -}; - -module.exports = HasteDependencyResolver; diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index ca80ab0b..bfc4e943 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -8,15 +8,174 @@ */ 'use strict'; -var HasteDependencyResolver = require('./haste'); -var NodeDependencyResolver = require('./node'); +var path = require('path'); +var DependencyGraph = require('./DependencyGraph'); +var replacePatterns = require('./replacePatterns'); +var declareOpts = require('../lib/declareOpts'); +var Promise = require('bluebird'); -module.exports = function createDependencyResolver(options) { - if (options.moduleFormat === 'haste') { - return new HasteDependencyResolver(options); - } else if (options.moduleFormat === 'node') { - return new NodeDependencyResolver(options); - } else { - throw new Error('unsupported'); +var validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + blacklistRE: { + type: 'object', // typeof regex is object + }, + polyfillModuleNames: { + type: 'array', + default: [], + }, + nonPersistent: { + type: 'boolean', + default: false, + }, + moduleFormat: { + type: 'string', + default: 'haste', + }, + assetRoots: { + type: 'array', + default: [], + }, + fileWatcher: { + type: 'object', + required: true, + }, + assetExts: { + type: 'array', + required: true, } +}); + +function HasteDependencyResolver(options) { + var opts = validateOpts(options); + + this._depGraph = new DependencyGraph({ + roots: opts.projectRoots, + assetRoots_DEPRECATED: opts.assetRoots, + assetExts: opts.assetExts, + ignoreFilePath: function(filepath) { + return filepath.indexOf('__tests__') !== -1 || + (opts.blacklistRE && opts.blacklistRE.test(filepath)); + }, + fileWatcher: opts.fileWatcher, + }); + + + this._polyfillModuleNames = opts.polyfillModuleNames || []; +} + +var getDependenciesValidateOpts = declareOpts({ + dev: { + type: 'boolean', + default: true, + }, +}); + +HasteDependencyResolver.prototype.getDependencies = function(main, options) { + var opts = getDependenciesValidateOpts(options); + + var depGraph = this._depGraph; + var self = this; + return depGraph.load().then( + () => depGraph.getOrderedDependencies(main).then( + dependencies => { + const mainModuleId = dependencies[0].id; + self._prependPolyfillDependencies( + dependencies, + opts.dev + ); + + return { + mainModuleId: mainModuleId, + dependencies: dependencies + }; + } + ) + ); }; + +HasteDependencyResolver.prototype._prependPolyfillDependencies = function( + dependencies, + isDev +) { + var polyfillModuleNames = [ + isDev + ? path.join(__dirname, 'polyfills/prelude_dev.js') + : path.join(__dirname, 'polyfills/prelude.js'), + path.join(__dirname, 'polyfills/require.js'), + path.join(__dirname, 'polyfills/polyfills.js'), + path.join(__dirname, 'polyfills/console.js'), + path.join(__dirname, 'polyfills/error-guard.js'), + path.join(__dirname, 'polyfills/String.prototype.es6.js'), + path.join(__dirname, 'polyfills/Array.prototype.es6.js'), + ].concat(this._polyfillModuleNames); + + var polyfillModules = polyfillModuleNames.map( + (polyfillModuleName, idx) => ({ + path: polyfillModuleName, + id: polyfillModuleName, + dependencies: polyfillModuleNames.slice(0, idx), + isPolyfill: true, + }) + ); + + dependencies.unshift.apply(dependencies, polyfillModules); +}; + +HasteDependencyResolver.prototype.wrapModule = function(module, code) { + 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); + } + })); + }) + ).then(() => { + const relativizeCode = (codeMatch, pre, quot, depName, post) => { + const depId = resolvedDeps[depName]; + if (depId) { + return pre + quot + depId + post; + } else { + return codeMatch; + } + }; + + return defineModuleCode({ + code: code + .replace(replacePatterns.IMPORT_RE, relativizeCode) + .replace(replacePatterns.REQUIRE_RE, relativizeCode), + deps: JSON.stringify(resolvedDepsArr), + moduleName: module.id, + }); + }); +}; + +HasteDependencyResolver.prototype.getDebugInfo = function() { + return this._depGraph.getDebugInfo(); +}; + +function defineModuleCode({moduleName, code, deps}) { + return [ + `__d(`, + `'${moduleName}',`, + `${deps},`, + 'function(global, require, ', + 'requireDynamic, requireLazy, module, exports) {', + ` ${code}`, + '\n});', + ].join(''); +} + +module.exports = HasteDependencyResolver; diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js deleted file mode 100644 index 57388564..00000000 --- a/react-packager/src/DependencyResolver/node/index.js +++ /dev/null @@ -1,51 +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 Promise = require('bluebird'); -var ModuleDescriptor = require('../ModuleDescriptor'); - -var mdeps = require('module-deps'); -var path = require('path'); - -exports.getRuntimeCode = function() {}; - -exports.wrapModule = function(id, source) { - return Promise.resolve( - 'define(' + JSON.stringify(id) + ',' + ' function(exports, module) {\n' - + source + '\n});' - ); -}; - -exports.getDependencies = function(root, fileEntryPath) { - return new Promise(function(resolve) { - fileEntryPath = path.join(process.cwd(), root, fileEntryPath); - - var md = mdeps(); - - md.end({file: fileEntryPath}); - - var deps = []; - - md.on('data', function(data) { - deps.push( - new ModuleDescriptor({ - id: data.id, - deps: data.deps, - path: data.file, - entry: data.entry - }) - ); - }); - - md.on('end', function() { - resolve(deps); - }); - }); -}; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js b/react-packager/src/DependencyResolver/polyfills/Array.prototype.es6.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js rename to react-packager/src/DependencyResolver/polyfills/Array.prototype.es6.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js b/react-packager/src/DependencyResolver/polyfills/String.prototype.es6.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js rename to react-packager/src/DependencyResolver/polyfills/String.prototype.es6.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/polyfills/console.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/console.js rename to react-packager/src/DependencyResolver/polyfills/console.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/react-packager/src/DependencyResolver/polyfills/error-guard.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/error-guard.js rename to react-packager/src/DependencyResolver/polyfills/error-guard.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js b/react-packager/src/DependencyResolver/polyfills/polyfills.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/polyfills.js rename to react-packager/src/DependencyResolver/polyfills/polyfills.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude.js b/react-packager/src/DependencyResolver/polyfills/prelude.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/prelude.js rename to react-packager/src/DependencyResolver/polyfills/prelude.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js b/react-packager/src/DependencyResolver/polyfills/prelude_dev.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js rename to react-packager/src/DependencyResolver/polyfills/prelude_dev.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/polyfills/require.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/require.js rename to react-packager/src/DependencyResolver/polyfills/require.js diff --git a/react-packager/src/DependencyResolver/haste/replacePatterns.js b/react-packager/src/DependencyResolver/replacePatterns.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/replacePatterns.js rename to react-packager/src/DependencyResolver/replacePatterns.js diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 89184c95..aaa8963c 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -101,7 +101,7 @@ describe('Packager', function() { }); wrapModule.mockImpl(function(module, code) { - return 'lol ' + code + ' lol'; + return Promise.resolve('lol ' + code + ' lol'); }); require('image-size').mockImpl(function(path, cb) { diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index a85281d2..04404ff1 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -159,16 +159,17 @@ Packager.prototype._transformModule = function(ppackage, module) { } var resolver = this._resolver; - return transform.then(function(transformed) { - var code = resolver.wrapModule(module, transformed.code); - return new ModuleTransport({ - code: code, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }); - }); + 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() { diff --git a/react-packager/src/__mocks__/fs.js b/react-packager/src/__mocks__/fs.js index d0e08a2f..b5251a44 100644 --- a/react-packager/src/__mocks__/fs.js +++ b/react-packager/src/__mocks__/fs.js @@ -51,7 +51,7 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) { var node = getToNode(filepath); // dir check if (node && typeof node === 'object' && node.SYMLINK == null) { - callback(new Error('Trying to read a dir, ESIDR, or whatever')); + callback(new Error('Error readFile a dir: ' + filepath)); } return callback(null, node); } catch (e) { @@ -59,12 +59,13 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) { } }); -fs.lstat.mockImpl(function(filepath, callback) { +fs.stat.mockImpl(function(filepath, callback) { var node; try { node = getToNode(filepath); } catch (e) { - return callback(e); + callback(e); + return; } var mtime = { @@ -73,7 +74,12 @@ fs.lstat.mockImpl(function(filepath, callback) { } }; - if (node && typeof node === 'object' && node.SYMLINK == null) { + if (node.SYMLINK) { + fs.stat(node.SYMLINK, callback); + return; + } + + if (node && typeof node === 'object') { callback(null, { isDirectory: function() { return true; @@ -89,9 +95,6 @@ fs.lstat.mockImpl(function(filepath, callback) { return false; }, isSymbolicLink: function() { - if (typeof node === 'object' && node.SYMLINK) { - return true; - } return false; }, mtime: mtime, @@ -113,6 +116,9 @@ function getToNode(filepath) { } var node = filesystem; parts.slice(1).forEach(function(part) { + if (node && node.SYMLINK) { + node = getToNode(node.SYMLINK); + } node = node[part]; }); From 695a5c2fbc452f73b39d699fa38dd5eb820d81ee Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 19 Jun 2015 22:48:36 -0700 Subject: [PATCH 207/936] [react-packager] Cache in-memory file lookups (~200ms win on file change) --- react-packager/src/DependencyResolver/fastfs.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 56ffe166..fc09ea8b 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -11,6 +11,7 @@ const path = require('path'); const readDir = Promise.promisify(fs.readdir); const readFile = Promise.promisify(fs.readFile); const stat = Promise.promisify(fs.stat); +const hasOwn = Object.prototype.hasOwnProperty; class Fastfs extends EventEmitter { constructor(roots, fileWatcher, {ignore, pattern}) { @@ -19,6 +20,7 @@ class Fastfs extends EventEmitter { this._ignore = ignore; this._pattern = pattern; this._roots = roots.map(root => new File(root, { isDir: true })); + this._fastPaths = Object.create(null); } build() { @@ -120,7 +122,12 @@ class Fastfs extends EventEmitter { } _getFile(filePath) { - return this._getAndAssertRoot(filePath).getFileFromPath(filePath); + filePath = path.normalize(filePath); + if (!hasOwn.call(this._fastPaths, filePath)) { + this._fastPaths[filePath] = this._getAndAssertRoot(filePath).getFileFromPath(filePath); + } + + return this._fastPaths[filePath]; } _add(file) { @@ -161,7 +168,6 @@ class Fastfs extends EventEmitter { } // Make sure this event belongs to one of our roots. - if (!this._getRoot(absPath)) { return; } @@ -173,6 +179,8 @@ class Fastfs extends EventEmitter { } } + delete this._fastPaths[path.normalize(absPath)]; + if (type !== 'delete') { this._add(new File(absPath, { isDir: false, From 050f0b355816ddc41681d70b92dc489aca906885 Mon Sep 17 00:00:00 2001 From: Bill Fisher Date: Sat, 20 Jun 2015 16:29:38 -0700 Subject: [PATCH 208/936] [ReactNative][easy] fix server 500 response typo Summary: @public corrected small typo in the 500 response from the packager server Test Plan: add throw to promise function prior to error handler, run packager, cache a bundle with bundle extension URI, open /debug/packages, see clean 500 error --- react-packager/src/Server/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 3e8ca9e8..914075fe 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -237,7 +237,7 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { }, this)).then( function() { res.end(ret); }, function(e) { - res.wrteHead(500); + res.writeHead(500); res.end('Internal Error'); console.log(e.stack); } From 23ac061d83d4d5235a68953c0b3f7b11c9e10982 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Mon, 22 Jun 2015 03:12:35 -0700 Subject: [PATCH 209/936] Added support for React installed in the application via Cocoapods Summary: Similarly to npm-installed react, this change makes changes to the packager so that it understands that it's been installed via Cocoapods and determines the project and asset roots properly (from the main application directory). Closes https://github.com/facebook/react-native/pull/1568 Github Author: Jarek Potiuk Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packager.js b/packager.js index 7dd013c6..c719b860 100644 --- a/packager.js +++ b/packager.js @@ -66,6 +66,9 @@ if (options.projectRoots) { if (__dirname.match(/node_modules\/react-native\/packager$/)) { // packager is running from node_modules of another project options.projectRoots = [path.resolve(__dirname, '../../..')]; + } else if (__dirname.match(/Pods\/React\/packager$/)) { + // packager is running from node_modules of another project + options.projectRoots = [path.resolve(__dirname, '../../..')]; } else { options.projectRoots = [path.resolve(__dirname, '..')]; } @@ -88,6 +91,8 @@ if (options.assetRoots) { } else { if (__dirname.match(/node_modules\/react-native\/packager$/)) { options.assetRoots = [path.resolve(__dirname, '../../..')]; + } else if (__dirname.match(/Pods\/React\/packager$/)) { + options.assetRoots = [path.resolve(__dirname, '../../..')]; } else { options.assetRoots = [path.resolve(__dirname, '..')]; } From bc1f94b7b20e0db3cd3ce81b3a2b97332f619ce3 Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Mon, 22 Jun 2015 08:42:02 -0700 Subject: [PATCH 210/936] Replace bluebird with promise --- react-packager/__mocks__/bluebird.js | 5 --- .../AssetServer/__tests__/AssetServer-test.js | 2 +- react-packager/src/AssetServer/index.js | 41 +++++++++++-------- .../src/DependencyResolver/AssetModule.js | 2 +- .../AssetModule_DEPRECATED.js | 2 +- .../DependencyGraph/index.js | 2 +- .../src/DependencyResolver/Module.js | 2 +- .../__tests__/HasteDependencyResolver-test.js | 2 +- .../src/DependencyResolver/fastfs.js | 8 ++-- .../src/DependencyResolver/index.js | 2 +- react-packager/src/FileWatcher/index.js | 4 +- react-packager/src/JSTransformer/Cache.js | 14 ++++--- .../src/JSTransformer/__tests__/Cache-test.js | 2 +- react-packager/src/JSTransformer/index.js | 6 +-- .../src/Packager/__tests__/Packager-test.js | 2 +- react-packager/src/Packager/index.js | 10 +++-- .../src/Server/__tests__/Server-test.js | 2 +- react-packager/src/Server/index.js | 2 +- 18 files changed, 57 insertions(+), 53 deletions(-) delete mode 100644 react-packager/__mocks__/bluebird.js diff --git a/react-packager/__mocks__/bluebird.js b/react-packager/__mocks__/bluebird.js deleted file mode 100644 index 9ac6e14b..00000000 --- a/react-packager/__mocks__/bluebird.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -jest.autoMockOff(); -module.exports = require.requireActual('bluebird'); -jest.autoMockOn(); diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index c6acc6a8..95916c9e 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -8,7 +8,7 @@ jest .mock('crypto') .mock('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('AssetServer', function() { var AssetServer; diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 0479afc2..2cd365fd 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -11,13 +11,13 @@ var declareOpts = require('../lib/declareOpts'); var getAssetDataFromName = require('../lib/getAssetDataFromName'); var path = require('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); var fs = require('fs'); var crypto = require('crypto'); -var stat = Promise.promisify(fs.stat); -var readDir = Promise.promisify(fs.readdir); -var readFile = Promise.promisify(fs.readFile); +var stat = Promise.denodeify(fs.stat); +var readDir = Promise.denodeify(fs.readdir); +var readFile = Promise.denodeify(fs.readFile); module.exports = AssetServer; @@ -56,12 +56,15 @@ AssetServer.prototype._getAssetRecord = function(assetPath) { this._roots, path.dirname(assetPath) ).then(function(dir) { - return [ + return Promise.all([ dir, readDir(dir), - ]; - }).spread(function(dir, files) { + ]); + }).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]; @@ -114,21 +117,23 @@ AssetServer.prototype.getAssetData = function(assetPath) { }; function findRoot(roots, dir) { - return Promise.some( + return Promise.all( roots.map(function(root) { var absPath = path.join(root, dir); return stat(absPath).then(function(fstat) { - if (!fstat.isDirectory()) { - throw new Error('Looking for dirs'); - } - fstat._path = absPath; - return fstat; + return {path: absPath, isDirectory: fstat.isDirectory()}; + }, function (err) { + return {path: absPath, isDirectory: false}; }); - }), - 1 - ).spread( - function(fstat) { - return fstat._path; + }) + ).then( + function(stats) { + for (var i = 0; i < stats.length; i++) { + if (stats[i].isDirectory) { + return stats[i].path; + } + } + throw new Error('Could not find any directories'); } ); } diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js index 431e6d01..bfe4b6f8 100644 --- a/react-packager/src/DependencyResolver/AssetModule.js +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -1,7 +1,7 @@ 'use strict'; const Module = require('./Module'); -const Promise = require('bluebird'); +const Promise = require('promise'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); class AssetModule extends Module { diff --git a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js index 7a25c590..fd4cb708 100644 --- a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js +++ b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -1,7 +1,7 @@ 'use strict'; const Module = require('./Module'); -const Promise = require('bluebird'); +const Promise = require('promise'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); class AssetModule_DEPRECATED extends Module { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 5bb63084..63ba7875 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -17,7 +17,7 @@ const isAbsolutePath = require('absolute-path'); const debug = require('debug')('DependencyGraph'); const getAssetDataFromName = require('../../lib/getAssetDataFromName'); const util = require('util'); -const Promise = require('bluebird'); +const Promise = require('promise'); const _ = require('underscore'); const validateOpts = declareOpts({ diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index d0be012b..3ae9354d 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -1,6 +1,6 @@ 'use strict'; -const Promise = require('bluebird'); +const Promise = require('promise'); const docblock = require('./DependencyGraph/docblock'); const isAbsolutePath = require('absolute-path'); const path = require('path'); diff --git a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index f9782886..da159b5e 100644 --- a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -14,7 +14,7 @@ jest.dontMock('../') jest.mock('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index fc09ea8b..0053b14e 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -1,6 +1,6 @@ 'use strict'; -const Promise = require('bluebird'); +const Promise = require('promise'); const {EventEmitter} = require('events'); const _ = require('underscore'); @@ -8,9 +8,9 @@ const debug = require('debug')('DependencyGraph'); const fs = require('fs'); const path = require('path'); -const readDir = Promise.promisify(fs.readdir); -const readFile = Promise.promisify(fs.readFile); -const stat = Promise.promisify(fs.stat); +const readDir = Promise.denodeify(fs.readdir); +const readFile = Promise.denodeify(fs.readFile); +const stat = Promise.denodeify(fs.stat); const hasOwn = Object.prototype.hasOwnProperty; class Fastfs extends EventEmitter { diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index bfc4e943..0ddf5c3c 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -12,7 +12,7 @@ var path = require('path'); var DependencyGraph = require('./DependencyGraph'); var replacePatterns = require('./replacePatterns'); var declareOpts = require('../lib/declareOpts'); -var Promise = require('bluebird'); +var Promise = require('promise'); var validateOpts = declareOpts({ projectRoots: { diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index cd1a28e5..aac211ad 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -10,7 +10,7 @@ var EventEmitter = require('events').EventEmitter; var sane = require('sane'); -var Promise = require('bluebird'); +var Promise = require('promise'); var util = require('util'); var exec = require('child_process').exec; @@ -57,7 +57,7 @@ util.inherits(FileWatcher, EventEmitter); FileWatcher.prototype.end = function() { return this._loading.then(function(watchers) { watchers.forEach(function(watcher) { - return Promise.promisify(watcher.close, watcher)(); + return Promise.denodeify(watcher.close).call(watcher); }); }); }; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index 584077b6..aee8d4f2 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -14,7 +14,7 @@ var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); var isAbsolutePath = require('absolute-path'); var path = require('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); var tmpdir = require('os').tmpDir(); var version = require('../../../../package.json').version; @@ -74,11 +74,13 @@ Cache.prototype.get = function(filepath, loaderCb) { Cache.prototype._set = function(filepath, loaderPromise) { this._data[filepath] = loaderPromise.then(function(data) { - return [ + return Promise.all([ data, - Promise.promisify(fs.stat)(filepath) - ]; - }).spread(function(data, stat) { + Promise.denodeify(fs.stat)(filepath) + ]); + }).then(function(ref) { + var data = ref[0]; + var stat = ref[1]; this._persistEventually(); return { data: data, @@ -113,7 +115,7 @@ Cache.prototype._persistCache = function() { Object.keys(data).forEach(function(key, i) { json[key] = values[i]; }); - return Promise.promisify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); + return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); }) .then(function() { this._persisting = null; diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index f91490ba..3877b3dd 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -17,7 +17,7 @@ jest .mock('os') .mock('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('JSTransformer Cache', function() { var Cache; diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 513d4394..7c20e10e 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -9,14 +9,14 @@ 'use strict'; var fs = require('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); var Cache = require('./Cache'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); var util = require('util'); var ModuleTransport = require('../lib/ModuleTransport'); -var readFile = Promise.promisify(fs.readFile); +var readFile = Promise.denodeify(fs.readFile); module.exports = Transformer; Transformer.TransformError = TransformError; @@ -69,7 +69,7 @@ function Transformer(options) { options.transformModulePath ); - this._transform = Promise.promisify(this._workers); + this._transform = Promise.denodeify(this._workers); } } diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index aaa8963c..216e9009 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -17,7 +17,7 @@ jest jest.mock('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('Packager', function() { var getDependencies; diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 04404ff1..3c6d1a2f 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -11,7 +11,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); var Transformer = require('../JSTransformer'); var DependencyResolver = require('../DependencyResolver'); var Package = require('./Package'); @@ -20,8 +20,8 @@ var ModuleTransport = require('../lib/ModuleTransport'); var declareOpts = require('../lib/declareOpts'); var imageSize = require('image-size'); -var sizeOf = Promise.promisify(imageSize); -var readFile = Promise.promisify(fs.readFile); +var sizeOf = Promise.denodeify(imageSize); +var readFile = Promise.denodeify(fs.readFile); var validateOpts = declareOpts({ projectRoots: { @@ -207,7 +207,9 @@ Packager.prototype.generateAssetModule = function(ppackage, module) { return Promise.all([ sizeOf(module.path), this._assetServer.getAssetData(relPath), - ]).spread(function(dimensions, assetData) { + ]).then(function(res) { + var dimensions = res[0]; + var assetData = res[1]; var img = { __packager_asset: true, fileSystemLocation: path.dirname(module.path), diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index e4e7b508..32c9060a 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -20,7 +20,7 @@ jest.setMock('worker-farm', function() { return function() {}; }) .setMock('uglify-js') .dontMock('../'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('processRequest', function() { var server; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 914075fe..1d2140ef 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -15,7 +15,7 @@ var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); var AssetServer = require('../AssetServer'); -var Promise = require('bluebird'); +var Promise = require('promise'); var _ = require('underscore'); var exec = require('child_process').exec; var fs = require('fs'); From fca872fac2b175c941c14f70b611791cf5068ac6 Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Mon, 22 Jun 2015 12:58:12 -0700 Subject: [PATCH 211/936] [React Native][Packager] allow --assetRoots to be relative paths --- packager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index c719b860..f2d526e0 100644 --- a/packager.js +++ b/packager.js @@ -86,7 +86,9 @@ if (options.root) { if (options.assetRoots) { if (!Array.isArray(options.assetRoots)) { - options.assetRoots = options.assetRoots.split(','); + options.assetRoots = options.assetRoots.split(',').map(function (dir) { + return path.resolve(process.cwd(), dir); + }); } } else { if (__dirname.match(/node_modules\/react-native\/packager$/)) { From 55b4b7a6affb34bcfdd86256efa24f4d1d26a6db Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 22 Jun 2015 16:38:52 -0700 Subject: [PATCH 212/936] Enable react.displayName transform Fixes #1715 --- transformer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/transformer.js b/transformer.js index a2efcea3..9096cc87 100644 --- a/transformer.js +++ b/transformer.js @@ -32,6 +32,7 @@ function transform(srcTxt, filename, options) { 'es7.objectRestSpread', 'flow', 'react', + 'react.displayName', ], sourceFileName: filename, sourceMaps: false, From fef3c7294ddc7f1ebb1190eedf81371f3d371b4a Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 24 Jun 2015 13:43:31 -0700 Subject: [PATCH 213/936] [ReactNative] Bring in node console.log formatting Summary: @public The current output of console.log is extremely bad. If you pass NaN, it shows up as null (super confusing I know -_-), if you pass a cyclical object, it just says that it is cyclic and that's it. It doesn't print up the first few levels which are NOT cyclical and would be really helpful. It turns out that nodejs console.log pretty printer is really awesome and can be easily extracted as a few hundred lines. This is going to be such a productivity boost that I think it's the right tradeoff to embed it like this Test Plan: ``` var a = {kikoo: {lol: 1}} a.kikoo.nice = a; console.log(a); > { kikoo: { lol: 1, nice: [Circular] } } console.log(NaN) > NaN ``` --- .../DependencyResolver/polyfills/console.js | 372 ++++++++++++++++-- 1 file changed, 342 insertions(+), 30 deletions(-) diff --git a/react-packager/src/DependencyResolver/polyfills/console.js b/react-packager/src/DependencyResolver/polyfills/console.js index 57576961..ff2ff39f 100644 --- a/react-packager/src/DependencyResolver/polyfills/console.js +++ b/react-packager/src/DependencyResolver/polyfills/console.js @@ -11,12 +11,352 @@ * * @provides console * @polyfill + * @nolint */ -/*eslint global-strict:0*/ (function(global) { 'use strict'; + var inspect = (function() { + // Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + // + // https://github.com/joyent/node/blob/master/lib/util.js + + function inspect(obj, opts) { + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + return formatValue(ctx, obj, opts.depth); + } + + function stylizeNoColor(str, styleType) { + return str; + } + + function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; + } + + + function formatValue(ctx, value, recurseTimes) { + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); + } + + + function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); + } + + + function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; + } + + + function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; + } + + + function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; + } + + + function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; + } + + + // NOTE: These type checking functions intentionally don't use `instanceof` + // because it is fragile and can be easily faked with `Object.create()`. + function isArray(ar) { + return Array.isArray(ar); + } + + function isBoolean(arg) { + return typeof arg === 'boolean'; + } + + function isNull(arg) { + return arg === null; + } + + function isNullOrUndefined(arg) { + return arg == null; + } + + function isNumber(arg) { + return typeof arg === 'number'; + } + + function isString(arg) { + return typeof arg === 'string'; + } + + function isSymbol(arg) { + return typeof arg === 'symbol'; + } + + function isUndefined(arg) { + return arg === void 0; + } + + function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; + } + + function isObject(arg) { + return typeof arg === 'object' && arg !== null; + } + + function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; + } + + function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); + } + + function isFunction(arg) { + return typeof arg === 'function'; + } + + function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; + } + + function objectToString(o) { + return Object.prototype.toString.call(o); + } + + function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); + } + + return inspect; + })(); + + var OBJECT_COLUMN_NAME = '(index)'; var LOG_LEVELS = { trace: 0, @@ -27,7 +367,6 @@ }; function setupConsole(global) { - if (!global.nativeLoggingHook) { return; } @@ -35,34 +374,7 @@ function getNativeLogFunction(level) { return function() { var str = Array.prototype.map.call(arguments, function(arg) { - var ret; - var type = typeof arg; - if (arg === null) { - ret = 'null'; - } else if (arg === undefined) { - ret = 'undefined'; - } else if (type === 'string') { - ret = '"' + arg + '"'; - } else if (type === 'function') { - try { - ret = arg.toString(); - } catch (e) { - ret = '[function unknown]'; - } - } else { - // Perform a try catch, just in case the object has a circular - // reference or stringify throws for some other reason. - try { - ret = JSON.stringify(arg); - } catch (e) { - if (typeof arg.toString === 'function') { - try { - ret = arg.toString(); - } catch (E) {} - } - } - } - return ret || '["' + type + '" failed to stringify]'; + return inspect(arg, {depth: 10}); }).join(', '); global.nativeLoggingHook(str, level); }; From 95800d156426d6f458384c53751f55e8191da8e7 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 24 Jun 2015 14:50:00 -0700 Subject: [PATCH 214/936] [react-packager] Fix the confused node_modules detection function Summary: @public We have a function that detects whether a give file is to be treated as a node_modules. If so it doesn't have access to the haste module map. There is an exception to this rule which is a few modules that are allowed to do that. Currently thats react-native, react-tools, and parse. The current implementation had a bug where if you had `react-native` (or react-tools etc) in the name before the actual package root then the detection will be off. This fixes the problem by starting from the `lastIndexOf('node_modules')` directory, that way nothing confuses us. Test Plan: ./runJestTests.sh export OSS, patch, run e2e test --- .../__tests__/DependencyGraph-test.js | 59 +++++++++++++++++++ .../DependencyGraph/index.js | 12 ++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index dbe7dc3a..09861b52 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -2171,6 +2171,65 @@ describe('DependencyGraph', function() { }); }); + pit('should not be confused by prev occuring whitelisted names', function() { + var root = '/react-tools'; + fs.__setMockFilesystem({ + 'react-tools': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("shouldWork");', + ].join('\n'), + 'node_modules': { + 'react-tools': { + 'package.json': JSON.stringify({ + name: 'react-tools', + main: 'main.js', + }), + 'main.js': [ + '/**', + ' * @providesModule shouldWork', + ' */', + ].join('\n'), + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/react-tools/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/react-tools/index.js', + dependencies: ['shouldWork'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'shouldWork', + path: '/react-tools/node_modules/react-tools/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should ignore modules it cant find (assumes own require system)', function() { // For example SourceMap.js implements it's own require system. var root = '/root'; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 63ba7875..9390a10e 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -414,18 +414,20 @@ class DependencyGraph { } _isNodeModulesDir(file) { - const inNodeModules = file.indexOf('/node_modules/') !== -1; + let parts = path.normalize(file).split(path.sep); + const indexOfNodeModules = parts.lastIndexOf('node_modules'); - if (!inNodeModules) { + if (indexOfNodeModules === -1) { return false; } + parts = parts.slice(indexOfNodeModules + 1); + const dirs = this._opts.providesModuleNodeModules; for (let i = 0; i < dirs.length; i++) { - const index = file.indexOf(dirs[i]); - if (index !== -1) { - return file.slice(index).indexOf('/node_modules/') !== -1; + if (parts.indexOf(dirs[i]) > -1) { + return false; } } From 693fd0bbe5b75863e83079732e62d2eb9ccf2b58 Mon Sep 17 00:00:00 2001 From: Jing Chen Date: Wed, 24 Jun 2015 16:15:49 -0700 Subject: [PATCH 215/936] [events] Add JS require time and fix up some existing logs --- react-packager/src/DependencyResolver/polyfills/prelude.js | 3 +++ react-packager/src/DependencyResolver/polyfills/prelude_dev.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/react-packager/src/DependencyResolver/polyfills/prelude.js b/react-packager/src/DependencyResolver/polyfills/prelude.js index 9f4db44e..a1004b7d 100644 --- a/react-packager/src/DependencyResolver/polyfills/prelude.js +++ b/react-packager/src/DependencyResolver/polyfills/prelude.js @@ -1,2 +1,5 @@ /* eslint global-strict:0 */ __DEV__ = false; + +/* global __BUNDLE_START_TIME__:true */ +__BUNDLE_START_TIME__ = Date.now(); diff --git a/react-packager/src/DependencyResolver/polyfills/prelude_dev.js b/react-packager/src/DependencyResolver/polyfills/prelude_dev.js index 26b26a07..14b97faa 100644 --- a/react-packager/src/DependencyResolver/polyfills/prelude_dev.js +++ b/react-packager/src/DependencyResolver/polyfills/prelude_dev.js @@ -1,2 +1,5 @@ /* eslint global-strict:0 */ __DEV__ = true; + +/* global __BUNDLE_START_TIME__:true */ +__BUNDLE_START_TIME__ = Date.now(); From 737a32417f296dc0f870fb528ad0fc7921b4ede9 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 24 Jun 2015 17:40:29 -0700 Subject: [PATCH 216/936] [react-packager] Injectible file crawlers (2x crawl speedup) --- .../__tests__/DependencyGraph-test.js | 7 +- .../DependencyGraph/index.js | 35 +++--- .../src/DependencyResolver/crawlers/index.js | 36 ++++++ .../src/DependencyResolver/crawlers/node.js | 61 ++++++++++ .../DependencyResolver/crawlers/watchman.js | 70 ++++++++++++ .../src/DependencyResolver/fastfs.js | 84 +++++--------- .../FileWatcher/__tests__/FileWatcher-test.js | 4 +- react-packager/src/FileWatcher/index.js | 108 ++++++++++-------- 8 files changed, 285 insertions(+), 120 deletions(-) create mode 100644 react-packager/src/DependencyResolver/crawlers/index.js create mode 100644 react-packager/src/DependencyResolver/crawlers/node.js create mode 100644 react-packager/src/DependencyResolver/crawlers/watchman.js diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 09861b52..673d9c58 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -13,6 +13,8 @@ jest .dontMock('crypto') .dontMock('absolute-path') .dontMock('../docblock') + .dontMock('../../crawlers') + .dontMock('../../crawlers/node') .dontMock('../../replacePatterns') .dontMock('../../../lib/getAssetDataFromName') .dontMock('../../fastfs') @@ -22,6 +24,8 @@ jest .dontMock('../../Package') .dontMock('../../ModuleCache'); +const Promise = require('promise'); + jest.mock('fs'); describe('DependencyGraph', function() { @@ -36,7 +40,8 @@ describe('DependencyGraph', function() { fileWatcher = { on: function() { return this; - } + }, + isWatchman: () => Promise.resolve(false) }; }); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 9390a10e..8145bfa0 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -8,17 +8,19 @@ */ 'use strict'; -const path = require('path'); +const Activity = require('../../Activity'); +const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); const Fastfs = require('../fastfs'); const ModuleCache = require('../ModuleCache'); -const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); -const declareOpts = require('../../lib/declareOpts'); -const isAbsolutePath = require('absolute-path'); -const debug = require('debug')('DependencyGraph'); -const getAssetDataFromName = require('../../lib/getAssetDataFromName'); -const util = require('util'); const Promise = require('promise'); const _ = require('underscore'); +const crawl = require('../crawlers'); +const debug = require('debug')('DependencyGraph'); +const declareOpts = require('../../lib/declareOpts'); +const getAssetDataFromName = require('../../lib/getAssetDataFromName'); +const isAbsolutePath = require('absolute-path'); +const path = require('path'); +const util = require('util'); const validateOpts = declareOpts({ roots: { @@ -68,13 +70,18 @@ class DependencyGraph { return this._loading; } - const modulePattern = new RegExp( - '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' - ); + const crawlActivity = Activity.startEvent('fs crawl'); + const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED); + this._crawling = crawl(allRoots, { + ignore: this._opts.ignoreFilePath, + exts: ['js', 'json'].concat(this._opts.assetExts), + fileWatcher: this._opts.fileWatcher, + }); + this._crawling.then((files) => Activity.endEvent(crawlActivity)); this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, { - pattern: modulePattern, ignore: this._opts.ignoreFilePath, + crawling: this._crawling, }); this._fastfs.on('change', this._processFileChange.bind(this)); @@ -454,14 +461,10 @@ class DependencyGraph { this._assetMap_DEPRECATED = Object.create(null); - const pattern = new RegExp( - '\.(' + this._opts.assetExts.join('|') + ')$' - ); - const fastfs = new Fastfs( this._opts.assetRoots_DEPRECATED, this._opts.fileWatcher, - { pattern, ignore: this._opts.ignoreFilePath } + { ignore: this._opts.ignoreFilePath, crawling: this._crawling } ); fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this)); diff --git a/react-packager/src/DependencyResolver/crawlers/index.js b/react-packager/src/DependencyResolver/crawlers/index.js new file mode 100644 index 00000000..71290af4 --- /dev/null +++ b/react-packager/src/DependencyResolver/crawlers/index.js @@ -0,0 +1,36 @@ +'use strict'; + +const nodeCrawl = require('./node'); +//const watchmanCrawl = require('./watchman'); + +function crawl(roots, options) { + return nodeCrawl(roots, options); + + // Although, in theory, watchman should be much faster; + // there is currently a bottleneck somewhere in the + // encoding/decoding that is causing it to be slower + // than node crawling. However, this should be fixed soon. + // https://github.com/facebook/watchman/issues/113 + /* + const {fileWatcher} = options; + return fileWatcher.isWatchman().then(isWatchman => { + + console.log(isWatchman); + if (!isWatchman) { + return false; + } + + // Make sure we're dealing with a version of watchman + // that's using `watch-project` + // TODO(amasad): properly expose (and document) used sane internals. + return fileWatcher.getWatchers().then(([watcher]) => !!watcher.watchProjectInfo.root); + }).then(isWatchman => { + if (isWatchman) { + return watchmanCrawl(roots, options); + } + + return nodeCrawl(roots, options); + });*/ +} + +module.exports = crawl; diff --git a/react-packager/src/DependencyResolver/crawlers/node.js b/react-packager/src/DependencyResolver/crawlers/node.js new file mode 100644 index 00000000..72030905 --- /dev/null +++ b/react-packager/src/DependencyResolver/crawlers/node.js @@ -0,0 +1,61 @@ +'use strict'; + +const Promise = require('promise'); +const debug = require('debug')('DependencyGraph'); +const fs = require('fs'); +const path = require('path'); + +const readDir = Promise.denodeify(fs.readdir); +const stat = Promise.denodeify(fs.stat); + +function nodeRecReadDir(roots, {ignore, exts}) { + const queue = roots.slice(); + const retFiles = []; + const extPattern = new RegExp( + '\.(' + exts.join('|') + ')$' + ); + + function search() { + const currDir = queue.shift(); + if (!currDir) { + return Promise.resolve(); + } + + return readDir(currDir) + .then(files => files.map(f => path.join(currDir, f))) + .then(files => Promise.all( + files.map(f => stat(f).catch(handleBrokenLink)) + ).then(stats => [ + // Remove broken links. + files.filter((file, i) => !!stats[i]), + stats.filter(Boolean), + ])) + .then(([files, stats]) => { + files.forEach((filePath, i) => { + if (ignore(filePath)) { + return; + } + + if (stats[i].isDirectory()) { + queue.push(filePath); + return; + } + + if (filePath.match(extPattern)) { + retFiles.push(filePath); + } + }); + + return search(); + }); + } + + return search().then(() => retFiles); +} + +function handleBrokenLink(e) { + debug('WARNING: error stating, possibly broken symlink', e.message); + return Promise.resolve(); +} + +module.exports = nodeRecReadDir; diff --git a/react-packager/src/DependencyResolver/crawlers/watchman.js b/react-packager/src/DependencyResolver/crawlers/watchman.js new file mode 100644 index 00000000..d6479a51 --- /dev/null +++ b/react-packager/src/DependencyResolver/crawlers/watchman.js @@ -0,0 +1,70 @@ +'use strict'; + +const Promise = require('promise'); +const path = require('path'); + +function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) { + const files = []; + return Promise.all( + roots.map( + root => fileWatcher.getWatcherForRoot(root) + ) + ).then( + watchers => { + // All watchman roots for all watches we have. + const watchmanRoots = watchers.map( + watcher => watcher.watchProjectInfo.root + ); + + // Actual unique watchers (because we use watch-project we may end up with + // duplicate "real" watches, and that's by design). + // TODO(amasad): push this functionality into the `FileWatcher`. + const uniqueWatchers = watchers.filter( + (watcher, i) => watchmanRoots.indexOf(watcher.watchProjectInfo.root) === i + ); + + return Promise.all( + uniqueWatchers.map(watcher => { + const watchedRoot = watcher.watchProjectInfo.root; + + // Build up an expression to filter the output by the relevant roots. + const dirExpr = ['anyof']; + for (let i = 0; i < roots.length; i++) { + const root = roots[i]; + if (isDescendant(watchedRoot, root)) { + dirExpr.push(['dirname', path.relative(watchedRoot, root)]); + } + } + + const cmd = Promise.promisify(watcher.client.command.bind(watcher.client)); + return cmd(['query', watchedRoot, { + 'suffix': exts, + 'expression': ['allof', ['type', 'f'], 'exists', dirExpr], + 'fields': ['name'], + }]).then(resp => { + if ('warning' in resp) { + console.warn('watchman warning: ', resp.warning); + } + + resp.files.forEach(filePath => { + filePath = path.join( + watchedRoot, + filePath + ); + + if (!ignore(filePath)) { + files.push(filePath); + } + return false; + }); + }); + }) + ); + }).then(() => files); +} + +function isDescendant(root, child) { + return path.relative(root, child).indexOf('..') !== 0; +} + +module.exports = watchmanRecReadDir; diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 0053b14e..67f7076e 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -4,28 +4,46 @@ const Promise = require('promise'); const {EventEmitter} = require('events'); const _ = require('underscore'); -const debug = require('debug')('DependencyGraph'); const fs = require('fs'); const path = require('path'); -const readDir = Promise.denodeify(fs.readdir); const readFile = Promise.denodeify(fs.readFile); const stat = Promise.denodeify(fs.stat); + const hasOwn = Object.prototype.hasOwnProperty; class Fastfs extends EventEmitter { - constructor(roots, fileWatcher, {ignore, pattern}) { + constructor(roots, fileWatcher, {ignore, crawling}) { super(); this._fileWatcher = fileWatcher; this._ignore = ignore; - this._pattern = pattern; this._roots = roots.map(root => new File(root, { isDir: true })); this._fastPaths = Object.create(null); + this._crawling = crawling; } build() { - const queue = this._roots.slice(); - return this._search(queue).then(() => { + const rootsPattern = new RegExp( + '^(' + this._roots.map(root => escapeRegExp(root.path)).join('|') + ')' + ); + + return this._crawling.then(files => { + files.forEach(filePath => { + if (filePath.match(rootsPattern)) { + const newFile = new File(filePath, { isDir: false }); + const parent = this._fastPaths[path.dirname(filePath)]; + if (parent) { + parent.addChild(newFile); + } else { + this._add(newFile); + for (let file = newFile; file; file = file.parent) { + if (!this._fastPaths[file.path]) { + this._fastPaths[file.path] = file; + } + } + } + } + }); this._fileWatcher.on('all', this._processFileChange.bind(this)); }); } @@ -134,32 +152,6 @@ class Fastfs extends EventEmitter { this._getAndAssertRoot(file.path).addChild(file); } - _search(queue) { - const dir = queue.shift(); - if (!dir) { - return Promise.resolve(); - } - - return readAndStatDir(dir.path).then(([filePaths, stats]) => { - filePaths.forEach((filePath, i) => { - if (this._ignore(filePath)) { - return; - } - - if (stats[i].isDirectory()) { - queue.push( - new File(filePath, { isDir: true, fstat: stats[i] }) - ); - return; - } - - if (filePath.match(this._pattern)) { - this._add(new File(filePath, { fstat: stats[i] })); - } - }); - return this._search(queue); - }); - } _processFileChange(type, filePath, root, fstat) { const absPath = path.join(root, filePath); @@ -182,10 +174,7 @@ class Fastfs extends EventEmitter { delete this._fastPaths[path.normalize(absPath)]; if (type !== 'delete') { - this._add(new File(absPath, { - isDir: false, - fstat - })); + this._add(new File(absPath, { isDir: false })); } this.emit('change', type, filePath, root, fstat); @@ -193,16 +182,12 @@ class Fastfs extends EventEmitter { } class File { - constructor(filePath, {isDir, fstat}) { + constructor(filePath, { isDir }) { this.path = filePath; this.isDir = Boolean(isDir); if (this.isDir) { this.children = Object.create(null); } - - if (fstat) { - this._stat = Promise.resolve(fstat); - } } read() { @@ -290,21 +275,8 @@ function isDescendant(root, child) { return path.relative(root, child).indexOf('..') !== 0; } -function readAndStatDir(dir) { - return readDir(dir) - .then(files => Promise.all(files.map(f => path.join(dir, f)))) - .then(files => Promise.all( - files.map(f => stat(f).catch(handleBrokenLink)) - ).then(stats => [ - // Remove broken links. - files.filter((file, i ) => !!stats[i]), - stats.filter(Boolean), - ])); -} - -function handleBrokenLink(e) { - debug('WARNING: error stating, possibly broken symlink', e.message); - return Promise.resolve(); +function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); } module.exports = Fastfs; diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index fc45205a..a5eeda3d 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -33,7 +33,7 @@ describe('FileWatcher', function() { pit('it should get the watcher instance when ready', function() { var fileWatcher = new FileWatcher(['rootDir']); - return fileWatcher._loading.then(function(watchers) { + return fileWatcher.getWatchers().then(function(watchers) { watchers.forEach(function(watcher) { expect(watcher instanceof Watcher).toBe(true); }); @@ -48,7 +48,7 @@ describe('FileWatcher', function() { var fileWatcher = new FileWatcher(['rootDir']); var handler = jest.genMockFn(); fileWatcher.on('all', handler); - return fileWatcher._loading.then(function(){ + return fileWatcher.getWatchers().then(function(){ cb(1, 2, 3, 4); jest.runAllTimers(); expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]); diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index aac211ad..46b4667e 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -8,13 +8,14 @@ */ 'use strict'; -var EventEmitter = require('events').EventEmitter; -var sane = require('sane'); -var Promise = require('promise'); -var util = require('util'); -var exec = require('child_process').exec; +const EventEmitter = require('events').EventEmitter; +const sane = require('sane'); +const Promise = require('promise'); +const exec = require('child_process').exec; -var detectingWatcherClass = new Promise(function(resolve) { +const MAX_WAIT_TIME = 25000; + +const detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { resolve(sane.NodeWatcher); @@ -24,53 +25,76 @@ var detectingWatcherClass = new Promise(function(resolve) { }); }); -module.exports = FileWatcher; +let inited = false; -var MAX_WAIT_TIME = 25000; +class FileWatcher extends EventEmitter { -// Singleton -var fileWatcher = null; + constructor(rootConfigs) { + if (inited) { + throw new Error('FileWatcher can only be instantiated once'); + } + inited = true; -function FileWatcher(rootConfigs) { - if (fileWatcher) { - // This allows us to optimize watching in the future by merging roots etc. - throw new Error('FileWatcher can only be instantiated once'); + super(); + this._watcherByRoot = Object.create(null); + + this._loading = Promise.all( + rootConfigs.map(createWatcher) + ).then(watchers => { + watchers.forEach((watcher, i) => { + this._watcherByRoot[rootConfigs[i].dir] = watcher; + watcher.on( + 'all', + // args = (type, filePath, root, stat) + (...args) => this.emit('all', ...args) + ); + }); + return watchers; + }); + + this._loading.done(); } - fileWatcher = this; + getWatchers() { + return this._loading; + } - this._loading = Promise.all( - rootConfigs.map(createWatcher) - ).then(function(watchers) { - watchers.forEach(function(watcher) { - watcher.on('all', function(type, filepath, root, stat) { - fileWatcher.emit('all', type, filepath, root, stat); - }); - }); - return watchers; - }); - this._loading.done(); + getWatcherForRoot(root) { + return this._loading.then(() => this._watcherByRoot[root]); + } + + isWatchman() { + return detectingWatcherClass.then( + Watcher => Watcher === sane.WatchmanWatcher + ); + } + + end() { + return this._loading.then( + (watchers) => watchers.map( + watcher => Promise.denodeify(watcher.close).call(watcher) + ) + ); + } + + static createDummyWatcher() { + const ev = new EventEmitter(); + ev.end = function() { + return Promise.resolve(); + }; + return ev; + } } -util.inherits(FileWatcher, EventEmitter); - -FileWatcher.prototype.end = function() { - return this._loading.then(function(watchers) { - watchers.forEach(function(watcher) { - return Promise.denodeify(watcher.close).call(watcher); - }); - }); -}; - function createWatcher(rootConfig) { return detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(rootConfig.dir, { + const watcher = new Watcher(rootConfig.dir, { glob: rootConfig.globs, dot: false, }); return new Promise(function(resolve, reject) { - var rejectTimeout = setTimeout(function() { + const rejectTimeout = setTimeout(function() { reject(new Error([ 'Watcher took too long to load', 'Try running `watchman version` from your terminal', @@ -86,10 +110,4 @@ function createWatcher(rootConfig) { }); } -FileWatcher.createDummyWatcher = function() { - var ev = new EventEmitter(); - ev.end = function() { - return Promise.resolve(); - }; - return ev; -}; +module.exports = FileWatcher; From d70ff047461fdbd0adfea9a200165175598b2037 Mon Sep 17 00:00:00 2001 From: Johannes Lumpe Date: Fri, 26 Jun 2015 15:56:22 -0700 Subject: [PATCH 217/936] [Packager] Allow user to specify a custom transformer file Summary: This is an edited re-submission of #1458 because I'm stupid. Closes https://github.com/facebook/react-native/pull/1497 Github Author: Johannes Lumpe Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packager.js b/packager.js index f2d526e0..d179617c 100644 --- a/packager.js +++ b/packager.js @@ -12,6 +12,7 @@ var fs = require('fs'); var path = require('path'); var execFile = require('child_process').execFile; var http = require('http'); +var isAbsolutePath = require('absolute-path'); var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware'); @@ -56,6 +57,11 @@ var options = parseCommandLine([{ }, { command: 'nonPersistent', description: 'Disable file watcher' +}, { + command: 'transformer', + type: 'string', + default: require.resolve('./transformer.js'), + description: 'Specify a custom transformer to be used (absolute path)' }]); if (options.projectRoots) { @@ -208,12 +214,17 @@ function statusPageMiddleware(req, res, next) { } function getAppMiddleware(options) { + var transformerPath = options.transformer; + if (!isAbsolutePath(transformerPath)) { + transformerPath = path.resolve(process.cwd(), transformerPath); + } + return ReactPackager.middleware({ nonPersistent: options.nonPersistent, projectRoots: options.projectRoots, blacklistRE: blacklist(options.platform), cacheVersion: '2', - transformModulePath: require.resolve('./transformer.js'), + transformModulePath: transformerPath, assetRoots: options.assetRoots, assetExts: ['png', 'jpeg', 'jpg'], polyfillModuleNames: [ From 15a0f07c5f2e3febc0ef4a38934cc15044d75942 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 26 Jun 2015 16:17:16 -0700 Subject: [PATCH 218/936] [react-packager] Enable watchman fs crawl Summary: @public Now that watchman perf issue was fixed we can enable watchman-based fs crawling which is faster than node. This showed an existing issue with some files missing from the blacklist which I addressed. Test Plan: ./fbrnios.sh run click around and scroll all the apps --- blacklist.js | 4 ++-- .../__tests__/DependencyGraph-test.js | 3 ++- .../src/DependencyResolver/crawlers/index.js | 14 ++------------ .../src/DependencyResolver/crawlers/watchman.js | 2 +- react-packager/src/FileWatcher/index.js | 10 +++++++--- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/blacklist.js b/blacklist.js index a2ba7167..af2e2879 100644 --- a/blacklist.js +++ b/blacklist.js @@ -24,7 +24,7 @@ var platformBlacklists = { ios: [ 'node_modules/react-tools/src/browser/ui/React.js', 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', - // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.android.js', ], @@ -32,7 +32,7 @@ var platformBlacklists = { 'node_modules/react-tools/src/browser/ui/React.js', 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', 'node_modules/react-tools/src/browser/ReactTextComponent.js', - // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.ios.js', ], diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 673d9c58..471e6069 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -2353,7 +2353,8 @@ describe('DependencyGraph', function() { } callbacks.push(callback); return this; - } + }, + isWatchman: () => Promise.resolve(false), }; }); diff --git a/react-packager/src/DependencyResolver/crawlers/index.js b/react-packager/src/DependencyResolver/crawlers/index.js index 71290af4..fe755bcb 100644 --- a/react-packager/src/DependencyResolver/crawlers/index.js +++ b/react-packager/src/DependencyResolver/crawlers/index.js @@ -1,21 +1,11 @@ 'use strict'; const nodeCrawl = require('./node'); -//const watchmanCrawl = require('./watchman'); +const watchmanCrawl = require('./watchman'); function crawl(roots, options) { - return nodeCrawl(roots, options); - - // Although, in theory, watchman should be much faster; - // there is currently a bottleneck somewhere in the - // encoding/decoding that is causing it to be slower - // than node crawling. However, this should be fixed soon. - // https://github.com/facebook/watchman/issues/113 - /* const {fileWatcher} = options; return fileWatcher.isWatchman().then(isWatchman => { - - console.log(isWatchman); if (!isWatchman) { return false; } @@ -30,7 +20,7 @@ function crawl(roots, options) { } return nodeCrawl(roots, options); - });*/ + }); } module.exports = crawl; diff --git a/react-packager/src/DependencyResolver/crawlers/watchman.js b/react-packager/src/DependencyResolver/crawlers/watchman.js index d6479a51..1871e3ea 100644 --- a/react-packager/src/DependencyResolver/crawlers/watchman.js +++ b/react-packager/src/DependencyResolver/crawlers/watchman.js @@ -36,7 +36,7 @@ function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) { } } - const cmd = Promise.promisify(watcher.client.command.bind(watcher.client)); + const cmd = Promise.denodeify(watcher.client.command.bind(watcher.client)); return cmd(['query', watchedRoot, { 'suffix': exts, 'expression': ['allof', ['type', 'f'], 'exists', dirExpr], diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index 46b4667e..d9ed7f32 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -12,9 +12,11 @@ const EventEmitter = require('events').EventEmitter; const sane = require('sane'); const Promise = require('promise'); const exec = require('child_process').exec; +const _ = require('underscore'); const MAX_WAIT_TIME = 25000; +// TODO(amasad): can we use watchman version command instead?r const detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { @@ -79,9 +81,11 @@ class FileWatcher extends EventEmitter { static createDummyWatcher() { const ev = new EventEmitter(); - ev.end = function() { - return Promise.resolve(); - }; + _.extend(ev, { + isWatchman: () => Promise.resolve(false), + end: () => Promise.resolve(), + }); + return ev; } } From 157a77e48dde7a44b9d24fc4f38b4147a6017df3 Mon Sep 17 00:00:00 2001 From: Joe Wood Date: Fri, 26 Jun 2015 16:18:49 -0700 Subject: [PATCH 219/936] [Packager] Windows support for Packager - Blacklist changes Summary: Another Pull Request implementing the changes in issue #468 - Enabled Packager to run on Windows This change relates to the blacklist fixes. It includes the path conversion for blacklist and changes to the default watched directory. It has no impact on Mac OSX. Closes https://github.com/facebook/react-native/pull/893 Github Author: Joe Wood Test Plan: Imported from GitHub, without a `Test Plan:` line. --- blacklist.js | 6 +++++- packager.js | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/blacklist.js b/blacklist.js index af2e2879..237691a8 100644 --- a/blacklist.js +++ b/blacklist.js @@ -8,6 +8,8 @@ */ 'use strict'; +var path = require('path'); + // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ @@ -39,7 +41,9 @@ var platformBlacklists = { }; function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + var escaped = str.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + // convert the '/' into an escaped local file separator + return escaped.replace(/\//g,'\\' + path.sep); } function blacklist(platform, additionalBlacklist) { diff --git a/packager.js b/packager.js index d179617c..ff0faa31 100644 --- a/packager.js +++ b/packager.js @@ -69,8 +69,9 @@ if (options.projectRoots) { options.projectRoots = options.projectRoots.split(','); } } else { - if (__dirname.match(/node_modules\/react-native\/packager$/)) { - // packager is running from node_modules of another project + // match on either path separator + if (__dirname.match(/node_modules[\/\\]react-native[\/\\]packager$/)) { + // packager is running from node_modules of another project options.projectRoots = [path.resolve(__dirname, '../../..')]; } else if (__dirname.match(/Pods\/React\/packager$/)) { // packager is running from node_modules of another project @@ -97,7 +98,8 @@ if (options.assetRoots) { }); } } else { - if (__dirname.match(/node_modules\/react-native\/packager$/)) { + // match on either path separator + if (__dirname.match(/node_modules[\/\\]react-native[\/\\]packager$/)) { options.assetRoots = [path.resolve(__dirname, '../../..')]; } else if (__dirname.match(/Pods\/React\/packager$/)) { options.assetRoots = [path.resolve(__dirname, '../../..')]; From 6b2c12e3007de7654fe02329816b25d0fdf037f5 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 26 Jun 2015 17:45:34 -0700 Subject: [PATCH 220/936] [react-packager] Update sane to get a new version of fb-watchman (perf) --- react-packager/src/DependencyResolver/DependencyGraph/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 8145bfa0..42c1a485 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -29,6 +29,7 @@ const validateOpts = declareOpts({ }, ignoreFilePath: { type: 'function', + default: function(){} }, fileWatcher: { From b3d9bf6909a64e51b9e78e84694e869ffb43513d Mon Sep 17 00:00:00 2001 From: Dmitry Soshnikov Date: Mon, 29 Jun 2015 17:32:44 -0700 Subject: [PATCH 221/936] [react-native][jest] Sync to 0.5.x and update to io.js --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3af007f..cc3f4fc6 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "dependencies": {}, "devDependencies": { - "jest-cli": "0.4.5", + "jest-cli": "git://github.com/facebook/jest#0.5.x", "eslint": "0.9.2" } } From 70dde863ae6cba3138b02b0173f0805c5353e246 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 30 Jun 2015 03:53:42 -0700 Subject: [PATCH 222/936] [react-packager] Use latest babel-core in place of babel (40% perf improvement) --- react-packager/index.js | 2 +- transformer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/index.js b/react-packager/index.js index d4ea0dd3..c47d762a 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -8,7 +8,7 @@ */ 'use strict'; -require('babel/register')({ +require('babel-core/register')({ only: /react-packager\/src/ }); diff --git a/transformer.js b/transformer.js index a2efcea3..c5b235da 100644 --- a/transformer.js +++ b/transformer.js @@ -10,7 +10,7 @@ */ 'use strict'; -var babel = require('babel'); +var babel = require('babel-core'); function transform(srcTxt, filename, options) { var result = babel.transform(srcTxt, { From 5a0723cfbabb2aa7e5b90d22ea298d5863223130 Mon Sep 17 00:00:00 2001 From: James Ide Date: Fri, 22 May 2015 23:58:02 -0700 Subject: [PATCH 223/936] [Tests] Update tests to run on io.js with the latest version of jest Updates the tests in small ways so they run on io.js with some updates: - The Cache test which relies on Promises uses `runAllImmediates` for modern versions of Node because bluebird uses `setImmediate` instead of `process.nextTick` for Node >0.10. Test Plan: Run `npm test` with the latest version of jest. --- react-packager/src/JSTransformer/__tests__/Cache-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 3877b3dd..df3ccfd7 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -229,7 +229,7 @@ describe('JSTransformer Cache', function() { return Promise.resolve('baz value'); }); - jest.runAllTicks(); + jest.runAllImmediates(); expect(fs.writeFile).toBeCalled(); }); }); From 62431c7f6295dd53a7eef6710922a92d316e3b25 Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 1 Jul 2015 12:52:10 -0700 Subject: [PATCH 224/936] [Tests] Update tests to run on io.js with the latest version of jest Summary: [This is a preview diff for getting RN's tests to pass with a future version of jest that supports io.js and other future versions of Node. This can be merged once the diff to update jest is merged upstream and published.] Updates the tests in small ways so they run on io.js with two updates: - The Cache test which relies on Promises uses `runAllImmediates` for modern versions of Node because bluebird uses `setImmediate` instead of `process.nextTick` for Node >0.10. Closes https://github.com/facebook/react-native/pull/1382 Github Author: James Ide Test Plan: Run `npm test` with the latest version of jest. --- react-packager/src/JSTransformer/__tests__/Cache-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index 3877b3dd..df3ccfd7 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -229,7 +229,7 @@ describe('JSTransformer Cache', function() { return Promise.resolve('baz value'); }); - jest.runAllTicks(); + jest.runAllImmediates(); expect(fs.writeFile).toBeCalled(); }); }); From 802ad3feb30ba17f07d22789f593f0aad77475ab Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 1 Jul 2015 16:44:30 -0700 Subject: [PATCH 225/936] [react-packager] fix test --- react-packager/src/JSTransformer/__tests__/Cache-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index df3ccfd7..3877b3dd 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -229,7 +229,7 @@ describe('JSTransformer Cache', function() { return Promise.resolve('baz value'); }); - jest.runAllImmediates(); + jest.runAllTicks(); expect(fs.writeFile).toBeCalled(); }); }); From 01d07a3eb61e12897c7fd1d74e27d8bfb2d1fa66 Mon Sep 17 00:00:00 2001 From: James Ide Date: Fri, 10 Jul 2015 00:23:58 -0700 Subject: [PATCH 226/936] [io.js] Print a warning message if the user is not on io.js 2.x Summary: Detects if the user is on Node or io.js 1.x and prints a banner explaining how to upgrade. We probably should link to more detailed upgrade docs so this is just a start. I also added a function to format banners that is kind of useful. Addresses part of #1737 ![packager-banner](https://cloud.githubusercontent.com/assets/379606/8447050/ad615402-1f67-11e5-8c02-ece5f7488135.png) Closes https://github.com/facebook/react-native/pull/1824 Github Author: James Ide --- checkNodeVersion.js | 40 ++++++++++++++++ formatBanner.js | 108 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- packager.js | 25 ++++++---- 4 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 checkNodeVersion.js create mode 100644 formatBanner.js diff --git a/checkNodeVersion.js b/checkNodeVersion.js new file mode 100644 index 00000000..f1a07c03 --- /dev/null +++ b/checkNodeVersion.js @@ -0,0 +1,40 @@ +/** + * 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 chalk = require('chalk'); +var semver = require('semver'); + +var formatBanner = require('./formatBanner'); + +function checkNodeVersion() { + if (!semver.satisfies(process.version, '>=2.0.0')) { + var engine = semver.lt(process.version, '1.0.0') ? 'Node' : 'io.js'; + var message = 'You are currently running ' + engine + ' ' + + process.version + '.\n' + + '\n' + + 'React Native is moving to io.js 2.x. There are several ways to upgrade' + + 'to io.js depending on your preference.\n' + + '\n' + + 'nvm: nvm install iojs && nvm alias default iojs\n' + + 'Homebrew: brew unlink node; brew install iojs && brew ln iojs --force\n' + + 'Installer: download the Mac .pkg from https://iojs.org/\n' + + '\n' + + 'About io.js: https://iojs.org\n' + + 'Follow along at: https://github.com/facebook/react-native/issues/1737'; + console.log(formatBanner(message, { + chalkFunction: chalk.green, + marginLeft: 1, + marginRight: 1, + paddingBottom: 1, + })); + } +} + +module.exports = checkNodeVersion; diff --git a/formatBanner.js b/formatBanner.js new file mode 100644 index 00000000..f54095b0 --- /dev/null +++ b/formatBanner.js @@ -0,0 +1,108 @@ +/** + * 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 wordwrap = require('wordwrap'); + +var HORIZONTAL_LINE = '\u2500'; +var VERTICAL_LINE = '\u2502'; +var TOP_LEFT = '\u250c'; +var TOP_RIGHT = '\u2510'; +var BOTTOM_LEFT = '\u2514'; +var BOTTOM_RIGHT = '\u2518'; + +/** + * Prints a banner with a border around it containing the given message. The + * following options are supported: + * + * type Options = { + * // A function to apply to each line of text to decorate it + * chalkFunction: (string: message) => string; + * // The total width (max line length) of the banner, including margin and + * // padding (default = 80) + * width: number; + * // How much leading space to prepend to each line (default = 0) + * marginLeft: number; + * // How much trailing space to append to each line (default = 0) + * marginRight: number; + * // Space between the top banner border and the text (default = 0) + * paddingTop: number; + * // Space between the bottom banner border and the text (default = 0) + * paddingBottom: number; + * // Space between the left banner border and the text (default = 2) + * paddingLeft: number; + * // Space between the right banner border and the text (default = 2) + * paddingRight: number; + * }; + */ +function formatBanner(message, options) { + options = options || {}; + _.defaults(options, { + chalkFunction: _.identity, + width: 80, + marginLeft: 0, + marginRight: 0, + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 2, + paddingRight: 2, + }); + + var width = options.width; + var marginLeft = options.marginLeft; + var marginRight = options.marginRight; + var paddingTop = options.paddingTop; + var paddingBottom = options.paddingBottom; + var paddingLeft = options.paddingLeft; + var paddingRight = options.paddingRight; + + var horizSpacing = marginLeft + paddingLeft + paddingRight + marginRight; + // 2 for the banner borders + var maxLineWidth = width - horizSpacing - 2; + var wrap = wordwrap(maxLineWidth); + var body = wrap(message); + + var left = spaces(marginLeft) + VERTICAL_LINE + spaces(paddingLeft); + var right = spaces(paddingRight) + VERTICAL_LINE + spaces(marginRight); + var bodyLines = _.flatten([ + arrayOf('', paddingTop), + body.split('\n'), + arrayOf('', paddingBottom), + ]).map(function(line) { + var padding = spaces(Math.max(0, maxLineWidth - line.length)); + return left + options.chalkFunction(line) + padding + right; + }); + + var horizontalBorderLine = repeatString( + HORIZONTAL_LINE, + width - marginLeft - marginRight - 2 + ); + var top = spaces(marginLeft) + TOP_LEFT + horizontalBorderLine + TOP_RIGHT + + spaces(marginRight); + var bottom = spaces(marginLeft) + BOTTOM_LEFT + horizontalBorderLine + + BOTTOM_RIGHT + spaces(marginRight); + return _.flatten([top, bodyLines, bottom]).join('\n'); +} + +function spaces(number) { + return repeatString(' ', number); +} + +function repeatString(string, number) { + return new Array(number + 1).join(string); +} + +function arrayOf(value, number) { + return _.range(number).map(function() { + return value; + }); +} + +module.exports = formatBanner; diff --git a/package.json b/package.json index cc3f4fc6..f9927a87 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "lint": "node linter.js Examples/", "start": "./packager/packager.sh" }, - "dependencies": {}, + "dependencies": { + "wordwrap": "^1.0.0" + }, "devDependencies": { "jest-cli": "git://github.com/facebook/jest#0.5.x", "eslint": "0.9.2" diff --git a/packager.js b/packager.js index ff0faa31..fcff7e58 100644 --- a/packager.js +++ b/packager.js @@ -30,6 +30,8 @@ var chalk = require('chalk'); var connect = require('connect'); var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); +var checkNodeVersion = require('./checkNodeVersion'); +var formatBanner = require('./formatBanner'); var launchEditor = require('./launchEditor.js'); var parseCommandLine = require('./parseCommandLine.js'); var webSocketProxy = require('./webSocketProxy.js'); @@ -108,16 +110,19 @@ if (options.assetRoots) { } } -console.log('\n' + -' ===============================================================\n' + -' | Running packager on port ' + options.port + '. \n' + -' | Keep this packager running while developing on any JS \n' + -' | projects. Feel free to close this tab and run your own \n' + -' | packager instance if you prefer. \n' + -' | \n' + -' | https://github.com/facebook/react-native \n' + -' | \n' + -' ===============================================================\n' +checkNodeVersion(); + +console.log(formatBanner( + 'Running packager on port ' + options.port + '.\n'+ + '\n' + + 'Keep this packager running while developing on any JS projects. Feel free ' + + 'to close this tab and run your own packager instance if you prefer.\n' + + '\n' + + 'https://github.com/facebook/react-native', { + marginLeft: 1, + marginRight: 1, + paddingBottom: 1, + }) ); console.log( From f7d51c750f1405a2aa0a979f34f9120b6ff7d946 Mon Sep 17 00:00:00 2001 From: Dave Sibiski Date: Wed, 15 Jul 2015 08:24:01 -0700 Subject: [PATCH 227/936] [Packager] Fix when loading a path that can't be handled Summary: [Packager] Adds `NotFoundError` when loading a path that can't be handled Resolves https://github.com/facebook/react-native/issues/1944 Closes https://github.com/facebook/react-native/pull/1948 Github Author: Dave Sibiski --- .../src/DependencyResolver/DependencyGraph/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 42c1a485..e05575db 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -150,7 +150,16 @@ class DependencyGraph { getOrderedDependencies(entryPath) { return this.load().then(() => { - const absolutePath = path.resolve(this._getAbsolutePath(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( From 0cc159ff66fbcd0590f8cbcf8425555894090374 Mon Sep 17 00:00:00 2001 From: Sean Powell Date: Thu, 16 Jul 2015 14:16:50 -0700 Subject: [PATCH 228/936] Support debugger reconnection when the packager goes down. Summary: This should resolve the issue highlighted in #1709 Closes https://github.com/facebook/react-native/pull/1992 Github Author: Sean Powell --- debugger.html | 67 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/debugger.html b/debugger.html index 0d008455..c9a81602 100644 --- a/debugger.html +++ b/debugger.html @@ -27,6 +27,11 @@ window.onbeforeunload = function() { } }; +// Alias native implementations needed by the debugger before platform-specific +// implementations are loaded into the global namespace +var debuggerSetTimeout = window.setTimeout; +var DebuggerWebSocket = window.WebSocket; + function setStatus(status) { document.getElementById('status').innerHTML = status; } @@ -58,35 +63,43 @@ var messageHandlers = { sendReply(JSON.stringify(returnValue)); } } +}; + +function connectToDebuggerProxy() { + var ws = new DebuggerWebSocket('ws://' + window.location.host + '/debugger-proxy'); + + ws.onopen = function() { + if (sessionID) { + setStatus('Debugger session #' + sessionID + ' active'); + ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)})); + } else { + setStatus('Waiting, press ⌘R in simulator to reload and connect'); + } + }; + + ws.onmessage = function(message) { + var object = JSON.parse(message.data); + var sendReply = function(result) { + ws.send(JSON.stringify({replyID: object.id, result: result})); + }; + var handler = messageHandlers[object.method]; + if (handler) { + handler(object, sendReply); + } else { + console.warn('Unknown method: ' + object.method); + } + }; + + ws.onclose = function() { + setStatus('Disconnected from proxy. Attempting reconnection. Is node server running?'); + + sessionID = null; + window.localStorage.removeItem('sessionID'); + debuggerSetTimeout(connectToDebuggerProxy, 100); + }; } -var ws = new WebSocket('ws://' + window.location.host + '/debugger-proxy'); - -ws.onopen = function() { - if (sessionID) { - setStatus('Debugger session #' + sessionID + ' active'); - ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)})); - } else { - setStatus('Waiting, press ⌘R in simulator to reload and connect'); - } -} - -ws.onmessage = function(message) { - var object = JSON.parse(message.data); - var sendReply = function(result) { - ws.send(JSON.stringify({replyID: object.id, result: result})); - } - var handler = messageHandlers[object.method]; - if (handler) { - handler(object, sendReply); - } else { - console.warn('Unknown method: ' + object.method); - } -} - -ws.onclose = function() { - setStatus('Disconnected from proxy. Is node server running?'); -} +connectToDebuggerProxy(); function loadScript(src, callback) { var script = document.createElement('script'); From fa6c4dde655eacb6357d461170a7a0791de9e64e Mon Sep 17 00:00:00 2001 From: Daniel Brockman Date: Thu, 23 Jul 2015 12:05:40 -0700 Subject: [PATCH 229/936] #!/bin/bash => #!/usr/bin/env bash Summary: This change makes `npm start` work correctly on e.g. NixOS. Closes https://github.com/facebook/react-native/pull/2006 Github Author: Daniel Brockman --- launchPackager.command | 2 +- packager.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launchPackager.command b/launchPackager.command index eb777493..0537f7c8 100755 --- a/launchPackager.command +++ b/launchPackager.command @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Copyright (c) 2015-present, Facebook, Inc. # All rights reserved. diff --git a/packager.sh b/packager.sh index f763b9ba..95cd8ce1 100755 --- a/packager.sh +++ b/packager.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Copyright (c) 2015-present, Facebook, Inc. # All rights reserved. From c90d79fe01d7a4f1ebf0ae3e8645045bedb19d82 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Thu, 23 Jul 2015 16:56:23 -0700 Subject: [PATCH 230/936] [react-native] enable react devtools from JavascriptCore --- webSocketProxy.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/webSocketProxy.js b/webSocketProxy.js index f8636213..22151c4e 100644 --- a/webSocketProxy.js +++ b/webSocketProxy.js @@ -17,7 +17,19 @@ function attachToServer(server, path) { }); var clients = []; + function sendSpecial(message) { + clients.forEach(function (cn) { + try { + cn.send(JSON.stringify(message)); + } catch(e) { + console.warn('WARN: ' + e.message); + } + }); + } + wss.on('connection', function(ws) { + var id = Math.random().toString(15).slice(10, 20); + sendSpecial({$open: id}); clients.push(ws); var allClientsExcept = function(ws) { @@ -26,10 +38,12 @@ function attachToServer(server, path) { ws.onerror = function() { clients = allClientsExcept(ws); + sendSpecial({$error: id}); }; ws.onclose = function() { clients = allClientsExcept(ws); + sendSpecial({$close: id}); }; ws.on('message', function(message) { From 4b7f9c60c4163d89195b288849f135bdccf200d1 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Thu, 23 Jul 2015 17:50:16 -0700 Subject: [PATCH 231/936] [ReactNative] Update core RN modules to work with React 0.14-beta1 --- blacklist.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/blacklist.js b/blacklist.js index 237691a8..a1b9c946 100644 --- a/blacklist.js +++ b/blacklist.js @@ -14,9 +14,13 @@ var path = require('path'); // modulePathIgnorePatterns. var sharedBlacklist = [ 'website', - 'node_modules/react-tools/src/utils/ImmutableObject.js', - 'node_modules/react-tools/src/core/ReactInstanceHandles.js', - 'node_modules/react-tools/src/event/EventPropagators.js' + 'node_modules/react-tools/src/React.js', + 'node_modules/react-tools/src/renderers/shared/event/EventPropagators.js', + 'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js', + 'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderSyntheticEvent.js', + 'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderTouchHistoryStore.js', + 'node_modules/react-tools/src/renderers/shared/reconciler/ReactInstanceHandles.js', + 'node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js', ]; var platformBlacklists = { @@ -24,17 +28,10 @@ var platformBlacklists = { '.ios.js' ], ios: [ - 'node_modules/react-tools/src/browser/ui/React.js', - 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', - 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.android.js', ], android: [ - 'node_modules/react-tools/src/browser/ui/React.js', - 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', - 'node_modules/react-tools/src/browser/ReactTextComponent.js', - 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.ios.js', ], From 03b4050c6e4b6c3775c4c9fd209895a93fab8c61 Mon Sep 17 00:00:00 2001 From: James Ide Date: Fri, 24 Jul 2015 18:31:41 -0700 Subject: [PATCH 232/936] [Packager] Include Content-Type headers with bundle and source maps Summary: The packager did not send back the Content-Type headers. Adding these. Closes https://github.com/facebook/react-native/pull/2029 Github Author: James Ide --- react-packager/src/Server/__tests__/Server-test.js | 1 + react-packager/src/Server/index.js | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 32c9060a..7d399cb2 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -39,6 +39,7 @@ describe('processRequest', function() { requestHandler( { url: requrl }, { + setHeader: jest.genMockFunction(), end: function(res) { resolve(res); } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 1d2140ef..a4ee53ef 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -358,13 +358,17 @@ Server.prototype.processRequest = function(req, res, next) { building.then( function(p) { if (requestType === 'bundle') { - res.end(p.getSource({ + 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') { - res.end(JSON.stringify(p.getSourceMap())); + var sourceMap = JSON.stringify(p.getSourceMap()); + res.setHeader('Content-Type', 'application/json'); + res.end(sourceMap); Activity.endEvent(startReqEventId); } }, From 0c72825ef81c7d060d2b1c2a63d3ff9c5182266a Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 29 Jul 2015 02:30:26 -0700 Subject: [PATCH 233/936] [ReactNative] Fix ResponderEventPlugin after React upgrade --- blacklist.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/blacklist.js b/blacklist.js index a1b9c946..b79f2899 100644 --- a/blacklist.js +++ b/blacklist.js @@ -17,9 +17,6 @@ var sharedBlacklist = [ 'node_modules/react-tools/src/React.js', 'node_modules/react-tools/src/renderers/shared/event/EventPropagators.js', 'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js', - 'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderSyntheticEvent.js', - 'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderTouchHistoryStore.js', - 'node_modules/react-tools/src/renderers/shared/reconciler/ReactInstanceHandles.js', 'node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js', ]; From ab23c251c37269e94bee4ff99efe8638b6cc1d67 Mon Sep 17 00:00:00 2001 From: Felix Oghina Date: Thu, 30 Jul 2015 04:24:37 -0700 Subject: [PATCH 234/936] [reactnative] add launchAndroidPackager.command --- launchAndroidPackager.command | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 launchAndroidPackager.command diff --git a/launchAndroidPackager.command b/launchAndroidPackager.command new file mode 100755 index 00000000..10951f3f --- /dev/null +++ b/launchAndroidPackager.command @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# 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. + +# Set terminal title +echo -en "\033]0;React Packager\a" +clear + +THIS_DIR=$(dirname "$0") +$THIS_DIR/packager.sh --platform android --port 8082 +echo "Process terminated. Press to close the window" +read From 63a96af6c6837e09e345c0263b409ba6407fb7d9 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 3 Aug 2015 18:16:18 -0700 Subject: [PATCH 235/936] [react-packager] Add support for platform in the resolver Summary: Teach the resolver about platform-based resolution. The platform extension is inferred from the entry point. It works for haste modules, as well as node-based resolution. --- .../__tests__/DependencyGraph-test.js | 234 ++++++++++++++++++ .../DependencyGraph/index.js | 75 ++++-- 2 files changed, 293 insertions(+), 16 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 471e6069..e2f0fd98 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -1809,6 +1809,77 @@ describe('DependencyGraph', function() { }); }); + pit('platform should work with node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.ios.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + }), + 'index.ios.js': '', + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main' + }), + 'main.ios.js': '', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.ios.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/index.ios.js', + path: '/root/node_modules/foo/index.ios.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.ios.js', + path: '/root/node_modules/bar/main.ios.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + pit('nested node_modules with specific paths', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -2333,6 +2404,169 @@ describe('DependencyGraph', function() { ]); }); }); + + pit('should work with multiple platforms (haste)', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.ios.js': ` + /** + * @providesModule index + */ + require('a'); + `, + 'a.ios.js': ` + /** + * @providesModule a + */ + `, + 'a.android.js': ` + /** + * @providesModule a + */ + `, + 'a.js': ` + /** + * @providesModule a + */ + `, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.ios.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'a', + path: '/root/a.ios.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should pick the generic file', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.ios.js': ` + /** + * @providesModule index + */ + require('a'); + `, + 'a.android.js': ` + /** + * @providesModule a + */ + `, + 'a.js': ` + /** + * @providesModule a + */ + `, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.ios.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'a', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with multiple platforms (node)', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.ios.js': ` + /** + * @providesModule index + */ + require('./a'); + `, + 'a.ios.js': '', + 'a.android.js': '', + 'a.js': '', + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.ios.js', + dependencies: ['./a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: '/root/a.ios.js', + path: '/root/a.ios.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); }); describe('file watch updating', function() { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index e05575db..0a8c0076 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -56,6 +56,10 @@ const validateOpts = declareOpts({ 'parse', ], }, + platforms: { + type: 'array', + default: ['ios', 'android'], + } }); class DependencyGraph { @@ -169,6 +173,13 @@ class DependencyGraph { ); } + 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 deps = []; const visited = Object.create(null); @@ -237,16 +248,14 @@ class DependencyGraph { } return p.then((realModuleName) => { - let dep = this._hasteMap[realModuleName]; - + let dep = this._getHasteModule(realModuleName); if (dep && dep.type === 'Module') { return dep; } let packageName = realModuleName; - while (packageName && packageName !== '.') { - dep = this._hasteMap[packageName]; + dep = this._getHasteModule(packageName); if (dep && dep.type === 'Package') { break; } @@ -349,6 +358,9 @@ class DependencyGraph { let file; if (this._fastfs.fileExists(potentialModulePath)) { file = potentialModulePath; + } else if (this._platformExt != null && + this._fastfs.fileExists(potentialModulePath + '.' + this._platformExt + '.js')) { + file = potentialModulePath + '.' + this._platformExt + '.js'; } else if (this._fastfs.fileExists(potentialModulePath + '.js')) { file = potentialModulePath + '.js'; } else if (this._fastfs.fileExists(potentialModulePath + '.json')) { @@ -419,15 +431,32 @@ class DependencyGraph { } _updateHasteMap(name, mod) { - if (this._hasteMap[name]) { - debug('WARNING: conflicting haste modules: ' + name); - if (mod.type === 'Package' && - this._hasteMap[name].type === 'Module') { - // Modules takes precendence over packages. - return; - } + if (this._hasteMap[name] == null) { + this._hasteMap[name] = []; } - this._hasteMap[name] = mod; + + if (mod.type === 'Module') { + // Modules takes precendence over packages. + this._hasteMap[name].unshift(mod); + } else { + this._hasteMap[name].push(mod); + } + } + + _getHasteModule(name) { + if (this._hasteMap[name]) { + const modules = this._hasteMap[name]; + if (this._platformExt != null) { + for (let i = 0; i < modules.length; i++) { + if (getPlatformExt(modules[i].path) === this._platformExt) { + return modules[i]; + } + } + } + + return modules[0]; + } + return null; } _isNodeModulesDir(file) { @@ -511,12 +540,17 @@ class DependencyGraph { return; } + /*eslint no-labels: 0 */ if (type === 'delete' || type === 'change') { - _.each(this._hasteMap, (mod, name) => { - if (mod.path === absPath) { - delete this._hasteMap[name]; + loop: for (let name in this._hasteMap) { + let modules = this._hasteMap[name]; + for (var i = 0; i < modules.length; i++) { + if (modules[i].path === absPath) { + modules.splice(i, 1); + break loop; + } } - }); + } if (type === 'delete') { return; @@ -566,6 +600,15 @@ function normalizePath(modulePath) { return modulePath.replace(/\/$/, ''); } +// Extract platform extension: index.ios.js -> ios +function getPlatformExt(file) { + const parts = path.basename(file).split('.'); + if (parts.length < 3) { + return null; + } + return parts[parts.length - 2]; +} + util.inherits(NotFoundError, Error); module.exports = DependencyGraph; From 7b1ffed4e727e713fec92356ad8ada562485731b Mon Sep 17 00:00:00 2001 From: James Ide Date: Tue, 4 Aug 2015 05:23:31 -0700 Subject: [PATCH 236/936] [Async] Enable async/await and update UIExplorer and tests Summary: - Enables async/await in .babelrc and transformer.js - Adds regenerator to package.json. Users still need to explicitly require the regenerator runtime -- this is so that you only pay for what you use. - Update AsyncStorage examples in UIExplorer to use async/await - Update promise tests in UIExplorer to use async/await in addition to the promise API Closes https://github.com/facebook/react-native/pull/1765 Github Author: James Ide --- react-packager/.babelrc | 4 +++- transformer.js | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/react-packager/.babelrc b/react-packager/.babelrc index c1b12d81..06b8d825 100644 --- a/react-packager/.babelrc +++ b/react-packager/.babelrc @@ -15,10 +15,12 @@ "es6.properties.shorthand", "es6.spread", "es6.templateLiterals", + "es7.asyncFunctions", "es7.trailingFunctionCommas", "es7.objectRestSpread", "flow", - "react" + "react", + "regenerator" ], "sourceMaps": false } diff --git a/transformer.js b/transformer.js index c5b235da..a1da1a02 100644 --- a/transformer.js +++ b/transformer.js @@ -28,10 +28,12 @@ function transform(srcTxt, filename, options) { 'es6.properties.shorthand', 'es6.spread', 'es6.templateLiterals', + 'es7.asyncFunctions', 'es7.trailingFunctionCommas', 'es7.objectRestSpread', 'flow', 'react', + 'regenerator', ], sourceFileName: filename, sourceMaps: false, From f47a29f922bdeca8748abdc4c9b5dc6f60a98784 Mon Sep 17 00:00:00 2001 From: Thomas Aylott Date: Wed, 5 Aug 2015 12:31:51 -0700 Subject: [PATCH 237/936] [react-packager] Fixes stack traces Summary: TransformErrors weren't showing stack traces, making it hard to debug problems in transformer code. --- react-packager/src/JSTransformer/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 7c20e10e..0cc7f2c6 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -121,7 +121,9 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { }); }; -function TransformError() {} +function TransformError() { + Error.captureStackTrace && Error.captureStackTrace(this, TransformError); +} util.inherits(TransformError, SyntaxError); function formatError(err, filename, source) { From 06f508a55a82e8290864db31f0f1bdd9695d397b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 6 Aug 2015 11:33:46 -0700 Subject: [PATCH 238/936] [JSAppServer] Don't keep track of not found packages --- react-packager/src/Server/index.js | 53 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index a4ee53ef..fdeffc65 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -372,12 +372,36 @@ Server.prototype.processRequest = function(req, res, next) { Activity.endEvent(startReqEventId); } }, - function(error) { - handleError(res, error); - } + this._handleError.bind(this, res, optionsJson) ).done(); }; +Server.prototype._handleError = function(res, packageID, 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._packages[packageID]; + } + } 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); @@ -417,26 +441,3 @@ function getBoolOptionFromQuery(query, opt, defaultVal) { return query[opt] === 'true' || query[opt] === '1'; } - -function handleError(res, 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, - }]; - console.error(error); - res.end(JSON.stringify(error)); - } 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', - })); - } -} From cc87e5173fee6ccceb0ab1bff74fb5e5b77fdd86 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 7 Aug 2015 15:01:40 -0700 Subject: [PATCH 239/936] [ReactNative] Show banner promoting DevTools Summary: When React DevTools is not installed, React prints a tiny warning to the console. However, in debugger.html we have a lot of free space we could use to promote React DevTools more actively. --- debugger.html | 63 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/debugger.html b/debugger.html index c9a81602..8d377f64 100644 --- a/debugger.html +++ b/debugger.html @@ -27,6 +27,12 @@ window.onbeforeunload = function() { } }; +window.addEventListener('load', function () { + if (typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { + document.getElementById('devtools-banner').style.display = 'block'; + } +}); + // Alias native implementations needed by the debugger before platform-specific // implementations are loaded into the global namespace var debuggerSetTimeout = window.setTimeout; @@ -112,6 +118,13 @@ function loadScript(src, callback) { })(); -

- 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

+
+

Install React DevTools

+

+ React Developer Tools is an extension that allows you to inspect the + React component hierarchies in the Chrome Developer Tools. +

+ + Install + +
+
+

+ 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

+
From 1d6f7c9cc76c66599b116f51ab687c2ec08c8df1 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Mon, 10 Aug 2015 14:34:27 +0100 Subject: [PATCH 240/936] Grammar --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c3fae480..0b6762ba 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ namely the node module format. We want to even go further, and let you choose your own packager and asset pipeline or even integrate into your existing infrastructure. -React Native users need not to understand how the packager work, -however, this documentation might be useful for advanced users and +React Native users need not understand how the packager works. +However, this documentation might be useful for advanced users and people who want to fix bugs or add features to the packager (patches welcome!). From 49fde903b8956c7ba734b9d07587bcea58fb98d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 10 Aug 2015 16:00:16 -0700 Subject: [PATCH 241/936] [react-packager] Promote Cache to top level Summary: The cache is only used for JSTransformer at the moment. We're doing IO and some computation to get each module's name, whether is a haste or node module and it's dependencies. This work happens on startup so by caching this value we shouldbe able to reduce the start up time. Lets promote the Cache to the Packager level to be able to use it by any of the components of the packager. For now, on this diff we'll start using it to cache the mentioned fields. Also we had to introduce the concept of fields in the cache as manually merging the date we had for each path is not possible as we're using promisses all around. With the new API, each field is a promise. @amasad and I did some manual testing to measure the impact of this change and looks like it's saves 1 second when building the haste map (which represents 50% of the time). Overall this reduces 1 second of start up time which was currently about 8s on my mac book pro. --- react-packager/src/Cache/__mocks__/index.js | 20 ++ .../src/Cache/__tests__/Cache-test.js | 288 ++++++++++++++++++ react-packager/src/Cache/index.js | 222 ++++++++++++++ .../__tests__/DependencyGraph-test.js | 61 +++- .../DependencyGraph/index.js | 19 +- .../src/DependencyResolver/Module.js | 53 ++-- .../src/DependencyResolver/ModuleCache.js | 30 +- .../src/DependencyResolver/Package.js | 15 +- .../src/DependencyResolver/index.js | 7 +- react-packager/src/JSTransformer/Cache.js | 175 ----------- .../src/JSTransformer/__tests__/Cache-test.js | 236 -------------- .../__tests__/Transformer-test.js | 8 +- react-packager/src/JSTransformer/index.js | 35 +-- react-packager/src/Packager/index.js | 27 +- 14 files changed, 712 insertions(+), 484 deletions(-) create mode 100644 react-packager/src/Cache/__mocks__/index.js create mode 100644 react-packager/src/Cache/__tests__/Cache-test.js create mode 100644 react-packager/src/Cache/index.js delete mode 100644 react-packager/src/JSTransformer/Cache.js delete mode 100644 react-packager/src/JSTransformer/__tests__/Cache-test.js diff --git a/react-packager/src/Cache/__mocks__/index.js b/react-packager/src/Cache/__mocks__/index.js new file mode 100644 index 00000000..6f7632f6 --- /dev/null +++ b/react-packager/src/Cache/__mocks__/index.js @@ -0,0 +1,20 @@ +/** + * 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'; + +class Cache { + get(filepath, field, cb) { + return cb(filepath); + } + + invalidate(filepath) { } + end() { } +} + +module.exports = Cache; diff --git a/react-packager/src/Cache/__tests__/Cache-test.js b/react-packager/src/Cache/__tests__/Cache-test.js new file mode 100644 index 00000000..8172a243 --- /dev/null +++ b/react-packager/src/Cache/__tests__/Cache-test.js @@ -0,0 +1,288 @@ +/** + * 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('underscore') + .dontMock('absolute-path') + .dontMock('../'); + +jest + .mock('os') + .mock('fs'); + +var Promise = require('promise'); + +describe('JSTransformer Cache', () => { + var Cache; + + beforeEach(() => { + require('os').tmpDir.mockImpl(() => 'tmpDir'); + + Cache = require('../'); + }); + + describe('getting/setting', () => { + pit('calls loader callback for uncached file', () => { + require('fs').stat.mockImpl((file, callback) => { + callback(null, { + mtime: { + getTime: () => {} + } + }); + }); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve()); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then($ => + expect(loaderCb).toBeCalledWith('/rootDir/someFile') + ); + }); + + pit('supports storing multiple fields', () => { + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var index = 0; + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve(index++) + ); + + return cache + .get('/rootDir/someFile', 'field1', loaderCb) + .then(value => { + expect(value).toBe(0); + return cache + .get('/rootDir/someFile', 'field2', loaderCb) + .then(value2 => expect(value2).toBe(1)); + }); + }); + + pit('gets the value from the loader callback', () => { + require('fs').stat.mockImpl((file, callback) => + callback(null, { + mtime: { + getTime: () => {} + } + }) + ); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('lol') + ); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then(value => expect(value).toBe('lol')); + }); + + pit('caches the value after the first call', () => { + require('fs').stat.mockImpl((file, callback) => { + callback(null, { + mtime: { + getTime: () => {} + } + }); + }); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('lol') + ); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then(() => { + var shouldNotBeCalled = jest.genMockFn(); + return cache.get('/rootDir/someFile', 'field', shouldNotBeCalled) + .then(value => { + expect(shouldNotBeCalled).not.toBeCalled(); + expect(value).toBe('lol'); + }); + }); + }); + + pit('clears old field when getting new field and mtime changed', () => { + var mtime = 0; + require('fs').stat.mockImpl((file, callback) => { + callback(null, { + mtime: { + getTime: () => mtime++ + } + }); + }); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('lol' + mtime) + ); + + return cache + .get('/rootDir/someFile', 'field1', loaderCb) + .then(value => cache + .get('/rootDir/someFile', 'field2', loaderCb) + .then(value2 => cache + .get('/rootDir/someFile', 'field1', loaderCb) + .then(value3 => expect(value3).toBe('lol2')) + ) + ); + }); + }); + + describe('loading cache from disk', () => { + var fileStats; + + beforeEach(() => { + fileStats = { + '/rootDir/someFile': { + mtime: { + getTime: () => 22 + } + }, + '/rootDir/foo': { + mtime: { + getTime: () => 11 + } + } + }; + + var fs = require('fs'); + + fs.existsSync.mockImpl(() => true); + + fs.statSync.mockImpl(filePath => fileStats[filePath]); + + fs.readFileSync.mockImpl(() => JSON.stringify({ + '/rootDir/someFile': { + metadata: {mtime: 22}, + data: {field: 'oh hai'}, + }, + '/rootDir/foo': { + metadata: {mtime: 11}, + data: {field: 'lol wat'}, + } + })); + }); + + pit('should load cache from disk', () => { + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn(); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then(value => { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('oh hai'); + + return cache + .get('/rootDir/foo', 'field', loaderCb) + .then(val => { + expect(loaderCb).not.toBeCalled(); + expect(val).toBe('lol wat'); + }); + }); + }); + + pit('should not load outdated cache', () => { + require('fs').stat.mockImpl((file, callback) => + callback(null, { + mtime: { + getTime: () => {} + } + }) + ); + + fileStats['/rootDir/foo'].mtime.getTime = () => 123; + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('new value') + ); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then(value => { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('oh hai'); + + return cache + .get('/rootDir/foo', 'field', loaderCb) + .then(val => { + expect(loaderCb).toBeCalled(); + expect(val).toBe('new value'); + }); + }); + }); + }); + + describe('writing cache to disk', () => { + it('should write cache to disk', () => { + var index = 0; + var mtimes = [10, 20, 30]; + var debounceIndex = 0; + require('underscore').debounce = callback => { + return () => { + if (++debounceIndex === 3) { + callback(); + } + }; + }; + + var fs = require('fs'); + fs.stat.mockImpl((file, callback) => + callback(null, { + mtime: { + getTime: () => mtimes[index++] + } + }) + ); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + + cache.get('/rootDir/bar', 'field', () => + Promise.resolve('bar value') + ); + cache.get('/rootDir/foo', 'field', () => + Promise.resolve('foo value') + ); + cache.get('/rootDir/baz', 'field', () => + Promise.resolve('baz value') + ); + + jest.runAllTicks(); + expect(fs.writeFile).toBeCalled(); + }); + }); +}); diff --git a/react-packager/src/Cache/index.js b/react-packager/src/Cache/index.js new file mode 100644 index 00000000..2ed3575e --- /dev/null +++ b/react-packager/src/Cache/index.js @@ -0,0 +1,222 @@ +/** + * 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 crypto = require('crypto'); +var declareOpts = require('../lib/declareOpts'); +var fs = require('fs'); +var isAbsolutePath = require('absolute-path'); +var path = require('path'); +var Promise = require('promise'); +var tmpdir = require('os').tmpDir(); +var version = require('../../../../package.json').version; + +var validateOpts = declareOpts({ + resetCache: { + type: 'boolean', + default: false, + }, + cacheVersion: { + type: 'string', + default: '1.0', + }, + projectRoots: { + type: 'array', + required: true, + }, + transformModulePath: { + type:'string', + required: true, + }, +}); + +// TODO: move to Packager directory +class Cache { + constructor(options) { + var opts = validateOpts(options); + + this._cacheFilePath = this._getCacheFilePath(opts); + + var data; + if (!opts.resetCache) { + data = this._loadCacheSync(this._cacheFilePath); + } else { + data = Object.create(null); + } + this._data = data; + + this._persistEventually = _.debounce( + this._persistCache.bind(this), + 2000, + ); + } + + get(filepath, field, loaderCb) { + if (!isAbsolutePath(filepath)) { + throw new Error('Use absolute paths'); + } + + var recordP = this._has(filepath, field) + ? this._data[filepath].data[field] + : this._set(filepath, field, loaderCb(filepath)); + + return recordP.then(record => record); + } + + invalidate(filepath) { + if (this._has(filepath)) { + delete this._data[filepath]; + } + } + + end() { + return this._persistCache(); + } + + _has(filepath, field) { + return Object.prototype.hasOwnProperty.call(this._data, filepath) && + (!field || Object.prototype.hasOwnProperty.call(this._data[filepath].data, field)); + } + + _set(filepath, field, loaderPromise) { + let record = this._data[filepath]; + if (!record) { + record = Object.create(null); + this._data[filepath] = record; + this._data[filepath].data = Object.create(null); + this._data[filepath].metadata = Object.create(null); + } + + record.data[field] = loaderPromise + .then(data => Promise.all([ + data, + Promise.denodeify(fs.stat)(filepath), + ])) + .then(([data, stat]) => { + this._persistEventually(); + + // Evict all existing field data from the cache if we're putting new + // more up to date data + var mtime = stat.mtime.getTime(); + if (record.metadata.mtime !== mtime) { + record.data = Object.create(null); + } + record.metadata.mtime = mtime; + + return data; + }); + + return record.data[field]; + } + + _persistCache() { + if (this._persisting != null) { + return this._persisting; + } + + var data = this._data; + var cacheFilepath = this._cacheFilePath; + + var allPromises = _.values(data) + .map(record => { + var fieldNames = Object.keys(record.data); + var fieldValues = _.values(record.data); + + return Promise + .all(fieldValues) + .then(ref => { + var ret = Object.create(null); + ret.metadata = record.metadata; + ret.data = Object.create(null); + fieldNames.forEach((field, index) => + ret.data[field] = ref[index] + ); + + return ret; + }); + } + ); + + this._persisting = Promise.all(allPromises) + .then(values => { + var json = Object.create(null); + Object.keys(data).forEach((key, i) => { + json[key] = Object.create(null); + json[key].metadata = data[key].metadata; + json[key].data = values[i].data; + }); + return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); + }) + .then(() => { + this._persisting = null; + return true; + }); + + return this._persisting; + } + + _loadCacheSync(cachePath) { + var ret = Object.create(null); + if (!fs.existsSync(cachePath)) { + return ret; + } + + var cacheOnDisk; + try { + cacheOnDisk = JSON.parse(fs.readFileSync(cachePath)); + } catch (e) { + if (e instanceof SyntaxError) { + console.warn('Unable to parse cache file. Will clear and continue.'); + fs.unlinkSync(cachePath); + return ret; + } + throw e; + } + + // Filter outdated cache and convert to promises. + Object.keys(cacheOnDisk).forEach(key => { + if (!fs.existsSync(key)) { + return; + } + var record = cacheOnDisk[key]; + var stat = fs.statSync(key); + if (stat.mtime.getTime() === record.metadata.mtime) { + ret[key] = Object.create(null); + ret[key].metadata = Object.create(null); + ret[key].data = Object.create(null); + ret[key].metadata.mtime = record.metadata.mtime; + + Object.keys(record.data).forEach(field => { + ret[key].data[field] = Promise.resolve(record.data[field]); + }); + } + }); + + return ret; + } + + _getCacheFilePath(options) { + var hash = crypto.createHash('md5'); + hash.update(version); + + var roots = options.projectRoots.join(',').split(path.sep).join('-'); + hash.update(roots); + + var cacheVersion = options.cacheVersion || '0'; + hash.update(cacheVersion); + + hash.update(options.transformModulePath); + + var name = 'react-packager-cache-' + hash.digest('hex'); + return path.join(tmpdir, name); + } +} + +module.exports = Cache; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index e2f0fd98..595b1f7c 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -29,12 +29,15 @@ const Promise = require('promise'); jest.mock('fs'); describe('DependencyGraph', function() { + var cache; + var Cache; var DependencyGraph; var fileWatcher; var fs; beforeEach(function() { fs = require('fs'); + Cache = require('../../../Cache'); DependencyGraph = require('../index'); fileWatcher = { @@ -43,6 +46,8 @@ describe('DependencyGraph', function() { }, isWatchman: () => Promise.resolve(false) }; + + cache = new Cache({}); }); describe('getOrderedDependencies', function() { @@ -68,6 +73,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -125,6 +131,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -176,6 +183,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -235,6 +243,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -286,6 +295,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -342,6 +352,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -419,6 +430,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -480,6 +492,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -532,6 +545,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -584,6 +598,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -644,6 +659,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -700,6 +716,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -750,6 +767,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -799,6 +817,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -845,6 +864,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -895,6 +915,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -943,6 +964,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -996,6 +1018,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { expect(deps) @@ -1053,6 +1076,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1100,6 +1124,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1146,6 +1171,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1204,6 +1230,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1262,6 +1289,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1340,6 +1368,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1396,6 +1425,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1452,6 +1482,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1508,6 +1539,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1579,6 +1611,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1681,6 +1714,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1761,6 +1795,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1842,6 +1877,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { expect(deps) @@ -1924,6 +1960,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2020,6 +2057,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2105,6 +2143,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2209,6 +2248,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2277,6 +2317,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/react-tools/index.js').then(function(deps) { expect(deps) @@ -2333,6 +2374,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2377,6 +2419,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2437,6 +2480,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { expect(deps) @@ -2492,6 +2536,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { expect(deps) @@ -2540,6 +2585,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { expect(deps) @@ -2623,6 +2669,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = @@ -2687,6 +2734,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = @@ -2751,6 +2799,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { delete filesystem.root.foo; @@ -2814,6 +2863,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ @@ -2895,6 +2945,7 @@ describe('DependencyGraph', function() { assetRoots_DEPRECATED: [root], assetExts: ['png'], fileWatcher: fileWatcher, + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { @@ -2966,6 +3017,7 @@ describe('DependencyGraph', function() { roots: [root], assetExts: ['png'], fileWatcher: fileWatcher, + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { @@ -3052,7 +3104,8 @@ describe('DependencyGraph', function() { return true; } return false; - } + }, + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ @@ -3137,6 +3190,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { @@ -3207,6 +3261,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); @@ -3273,6 +3328,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ @@ -3337,6 +3393,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ @@ -3399,6 +3456,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -3498,6 +3556,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 0a8c0076..e66191e4 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -59,7 +59,11 @@ const validateOpts = declareOpts({ platforms: { type: 'array', default: ['ios', 'android'], - } + }, + cache: { + type: 'object', + required: true, + }, }); class DependencyGraph { @@ -67,6 +71,7 @@ class DependencyGraph { this._opts = validateOpts(options); this._hasteMap = Object.create(null); this._immediateResolutionCache = Object.create(null); + this._cache = this._opts.cache; this.load(); } @@ -84,17 +89,21 @@ class DependencyGraph { }); this._crawling.then((files) => Activity.endEvent(crawlActivity)); - this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, { + this._fastfs = new Fastfs(this._opts.roots, this._opts.fileWatcher, { ignore: this._opts.ignoreFilePath, crawling: this._crawling, }); this._fastfs.on('change', this._processFileChange.bind(this)); - this._moduleCache = new ModuleCache(this._fastfs); + this._moduleCache = new ModuleCache(this._fastfs, this._cache); this._loading = Promise.all([ - this._fastfs.build().then(() => this._buildHasteMap()), + this._fastfs.build() + .then(() => { + const hasteActivity = Activity.startEvent('haste map'); + this._buildHasteMap().then(() => Activity.endEvent(hasteActivity)); + }), this._buildAssetMap_DEPRECATED(), ]); @@ -141,7 +150,7 @@ class DependencyGraph { () => this._resolveNodeDependency(fromModule, toModuleName) ).then( cacheResult, - forgive + forgive, ); } diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 3ae9354d..3f1b13ef 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -8,7 +8,7 @@ const replacePatterns = require('./replacePatterns'); class Module { - constructor(file, fastfs, moduleCache) { + constructor(file, fastfs, moduleCache, cache) { if (!isAbsolutePath(file)) { throw new Error('Expected file to be absolute path but got ' + file); } @@ -18,34 +18,41 @@ class Module { this._fastfs = fastfs; this._moduleCache = moduleCache; + this._cache = cache; } isHaste() { - return this._read().then(data => !!data.id); + return this._cache.get(this.path, 'haste', () => + this._read().then(data => !!data.id) + ); } getName() { - return this._read().then(data => { - if (data.id) { - return data.id; - } + return this._cache.get( + this.path, + 'name', + () => this._read().then(data => { + if (data.id) { + return data.id; + } - const p = this.getPackage(); + const p = this.getPackage(); - if (!p) { - // Name is full path - return this.path; - } + if (!p) { + // Name is full path + return this.path; + } - return p.getName() - .then(name => { - if (!name) { - return this.path; - } + return p.getName() + .then(name => { + if (!name) { + return this.path; + } - return path.join(name, path.relative(p.root, this.path)); - }); - }); + return path.join(name, path.relative(p.root, this.path)); + }); + }) + ); } getPackage() { @@ -53,7 +60,13 @@ class Module { } getDependencies() { - return this._read().then(data => data.dependencies); + return this._cache.get(this.path, 'dependencies', () => + this._read().then(data => data.dependencies) + ); + } + + invalidate() { + this._cache.invalidate(this.path); } _read() { diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index 3fa02cc1..a7e29322 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -7,17 +7,23 @@ const path = require('path'); class ModuleCache { - constructor(fastfs) { + constructor(fastfs, cache) { this._moduleCache = Object.create(null); this._packageCache = Object.create(null); this._fastfs = fastfs; + this._cache = cache; fastfs.on('change', this._processFileChange.bind(this)); } getModule(filePath) { filePath = path.resolve(filePath); if (!this._moduleCache[filePath]) { - this._moduleCache[filePath] = new Module(filePath, this._fastfs, this); + this._moduleCache[filePath] = new Module( + filePath, + this._fastfs, + this, + this._cache, + ); } return this._moduleCache[filePath]; } @@ -28,7 +34,8 @@ class ModuleCache { this._moduleCache[filePath] = new AssetModule( filePath, this._fastfs, - this + this, + this._cache, ); } return this._moduleCache[filePath]; @@ -37,7 +44,11 @@ class ModuleCache { getPackage(filePath) { filePath = path.resolve(filePath); if (!this._packageCache[filePath]){ - this._packageCache[filePath] = new Package(filePath, this._fastfs); + this._packageCache[filePath] = new Package( + filePath, + this._fastfs, + this._cache, + ); } return this._packageCache[filePath]; } @@ -64,8 +75,15 @@ class ModuleCache { _processFileChange(type, filePath, root) { const absPath = path.join(root, filePath); - delete this._moduleCache[absPath]; - delete this._packageCache[absPath]; + + if (this._moduleCache[absPath]) { + this._moduleCache[absPath].invalidate(); + delete this._moduleCache[absPath]; + } + if (this._packageCache[absPath]) { + this._packageCache[absPath].invalidate(); + delete this._packageCache[absPath]; + } } } diff --git a/react-packager/src/DependencyResolver/Package.js b/react-packager/src/DependencyResolver/Package.js index f52ff42b..5354e588 100644 --- a/react-packager/src/DependencyResolver/Package.js +++ b/react-packager/src/DependencyResolver/Package.js @@ -5,11 +5,12 @@ const path = require('path'); class Package { - constructor(file, fastfs) { + constructor(file, fastfs, cache) { this.path = path.resolve(file); this.root = path.dirname(this.path); this._fastfs = fastfs; this.type = 'Package'; + this._cache = cache; } getMain() { @@ -33,11 +34,19 @@ class Package { } isHaste() { - return this._read().then(json => !!json.name); + return this._cache.get(this.path, 'haste', () => + this._read().then(json => !!json.name) + ); } getName() { - return this._read().then(json => json.name); + return this._cache.get(this.path, 'name', () => + this._read().then(json => json.name) + ); + } + + invalidate() { + this._cache.invalidate(this.path); } redirectRequire(name) { diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index 0ddf5c3c..eae2e3da 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -45,7 +45,11 @@ var validateOpts = declareOpts({ assetExts: { type: 'array', required: true, - } + }, + cache: { + type: 'object', + required: true, + }, }); function HasteDependencyResolver(options) { @@ -60,6 +64,7 @@ function HasteDependencyResolver(options) { (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, fileWatcher: opts.fileWatcher, + cache: opts.cache, }); diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js deleted file mode 100644 index aee8d4f2..00000000 --- a/react-packager/src/JSTransformer/Cache.js +++ /dev/null @@ -1,175 +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 crypto = require('crypto'); -var declareOpts = require('../lib/declareOpts'); -var fs = require('fs'); -var isAbsolutePath = require('absolute-path'); -var path = require('path'); -var Promise = require('promise'); -var tmpdir = require('os').tmpDir(); -var version = require('../../../../package.json').version; - -var validateOpts = declareOpts({ - resetCache: { - type: 'boolean', - default: false, - }, - cacheVersion: { - type: 'string', - default: '1.0', - }, - projectRoots: { - type: 'array', - required: true, - }, - transformModulePath: { - type:'string', - required: true, - }, -}); -module.exports = Cache; - -function Cache(options) { - var opts = validateOpts(options); - - this._cacheFilePath = cacheFilePath(opts); - - var data; - if (!opts.resetCache) { - data = loadCacheSync(this._cacheFilePath); - } else { - data = Object.create(null); - } - this._data = data; - - this._has = Object.prototype.hasOwnProperty.bind(data); - this._persistEventually = _.debounce( - this._persistCache.bind(this), - 2000 - ); -} - -Cache.prototype.get = function(filepath, loaderCb) { - if (!isAbsolutePath(filepath)) { - throw new Error('Use absolute paths'); - } - - var recordP = this._has(filepath) - ? this._data[filepath] - : this._set(filepath, loaderCb(filepath)); - - return recordP.then(function(record) { - return record.data; - }); -}; - -Cache.prototype._set = function(filepath, loaderPromise) { - this._data[filepath] = loaderPromise.then(function(data) { - return Promise.all([ - data, - Promise.denodeify(fs.stat)(filepath) - ]); - }).then(function(ref) { - var data = ref[0]; - var stat = ref[1]; - this._persistEventually(); - return { - data: data, - mtime: stat.mtime.getTime(), - }; - }.bind(this)); - - return this._data[filepath]; -}; - -Cache.prototype.invalidate = function(filepath){ - if (this._has(filepath)) { - delete this._data[filepath]; - } -}; - -Cache.prototype.end = function() { - return this._persistCache(); -}; - -Cache.prototype._persistCache = function() { - if (this._persisting != null) { - return this._persisting; - } - - var data = this._data; - var cacheFilepath = this._cacheFilePath; - - this._persisting = Promise.all(_.values(data)) - .then(function(values) { - var json = Object.create(null); - Object.keys(data).forEach(function(key, i) { - json[key] = values[i]; - }); - return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); - }) - .then(function() { - this._persisting = null; - return true; - }.bind(this)); - - return this._persisting; -}; - -function loadCacheSync(cachePath) { - var ret = Object.create(null); - if (!fs.existsSync(cachePath)) { - return ret; - } - - var cacheOnDisk; - try { - cacheOnDisk = JSON.parse(fs.readFileSync(cachePath)); - } catch (e) { - if (e instanceof SyntaxError) { - console.warn('Unable to parse cache file. Will clear and continue.'); - fs.unlinkSync(cachePath); - return ret; - } - throw e; - } - - // Filter outdated cache and convert to promises. - Object.keys(cacheOnDisk).forEach(function(key) { - if (!fs.existsSync(key)) { - return; - } - var value = cacheOnDisk[key]; - var stat = fs.statSync(key); - if (stat.mtime.getTime() === value.mtime) { - ret[key] = Promise.resolve(value); - } - }); - - return ret; -} - -function cacheFilePath(options) { - var hash = crypto.createHash('md5'); - hash.update(version); - - var roots = options.projectRoots.join(',').split(path.sep).join('-'); - hash.update(roots); - - var cacheVersion = options.cacheVersion || '0'; - hash.update(cacheVersion); - - hash.update(options.transformModulePath); - - var name = 'react-packager-cache-' + hash.digest('hex'); - return path.join(tmpdir, name); -} diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js deleted file mode 100644 index 3877b3dd..00000000 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ /dev/null @@ -1,236 +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'; - -jest - .dontMock('underscore') - .dontMock('absolute-path') - .dontMock('../Cache'); - -jest - .mock('os') - .mock('fs'); - -var Promise = require('promise'); - -describe('JSTransformer Cache', function() { - var Cache; - - beforeEach(function() { - require('os').tmpDir.mockImpl(function() { - return 'tmpDir'; - }); - - Cache = require('../Cache'); - }); - - describe('getting/setting', function() { - it('calls loader callback for uncached file', function() { - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn().mockImpl(function() { - return Promise.resolve(); - }); - - cache.get('/rootDir/someFile', loaderCb); - expect(loaderCb).toBeCalledWith('/rootDir/someFile'); - }); - - pit('gets the value from the loader callback', function() { - require('fs').stat.mockImpl(function(file, callback) { - callback(null, { - mtime: { - getTime: function() {} - } - }); - }); - - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn().mockImpl(function() { - return Promise.resolve('lol'); - }); - - return cache.get('/rootDir/someFile', loaderCb).then(function(value) { - expect(value).toBe('lol'); - }); - }); - - pit('caches the value after the first call', function() { - require('fs').stat.mockImpl(function(file, callback) { - callback(null, { - mtime: { - getTime: function() {} - } - }); - }); - - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn().mockImpl(function() { - return Promise.resolve('lol'); - }); - - return cache.get('/rootDir/someFile', loaderCb).then(function() { - var shouldNotBeCalled = jest.genMockFn(); - return cache.get('/rootDir/someFile', shouldNotBeCalled) - .then(function(value) { - expect(shouldNotBeCalled).not.toBeCalled(); - expect(value).toBe('lol'); - }); - }); - }); - }); - - describe('loading cache from disk', function() { - var fileStats; - - beforeEach(function() { - fileStats = { - '/rootDir/someFile': { - mtime: { - getTime: function() { - return 22; - } - } - }, - '/rootDir/foo': { - mtime: { - getTime: function() { - return 11; - } - } - } - }; - - var fs = require('fs'); - - fs.existsSync.mockImpl(function() { - return true; - }); - - fs.statSync.mockImpl(function(filePath) { - return fileStats[filePath]; - }); - - fs.readFileSync.mockImpl(function() { - return JSON.stringify({ - '/rootDir/someFile': { - mtime: 22, - data: 'oh hai' - }, - '/rootDir/foo': { - mtime: 11, - data: 'lol wat' - } - }); - }); - }); - - pit('should load cache from disk', function() { - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn(); - - return cache.get('/rootDir/someFile', loaderCb).then(function(value) { - expect(loaderCb).not.toBeCalled(); - expect(value).toBe('oh hai'); - - return cache.get('/rootDir/foo', loaderCb).then(function(value) { - expect(loaderCb).not.toBeCalled(); - expect(value).toBe('lol wat'); - }); - }); - }); - - pit('should not load outdated cache', function() { - require('fs').stat.mockImpl(function(file, callback) { - callback(null, { - mtime: { - getTime: function() {} - } - }); - }); - - fileStats['/rootDir/foo'].mtime.getTime = function() { - return 123; - }; - - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn().mockImpl(function() { - return Promise.resolve('new value'); - }); - - return cache.get('/rootDir/someFile', loaderCb).then(function(value) { - expect(loaderCb).not.toBeCalled(); - expect(value).toBe('oh hai'); - - return cache.get('/rootDir/foo', loaderCb).then(function(value) { - expect(loaderCb).toBeCalled(); - expect(value).toBe('new value'); - }); - }); - }); - }); - - describe('writing cache to disk', function() { - it('should write cache to disk', function() { - var index = 0; - var mtimes = [10, 20, 30]; - var debounceIndex = 0; - require('underscore').debounce = function(callback) { - return function () { - if (++debounceIndex === 3) { - callback(); - } - }; - }; - - var fs = require('fs'); - fs.stat.mockImpl(function(file, callback) { - callback(null, { - mtime: { - getTime: function() { - return mtimes[index++]; - } - } - }); - }); - - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - - cache.get('/rootDir/bar', function() { - return Promise.resolve('bar value'); - }); - cache.get('/rootDir/foo', function() { - return Promise.resolve('foo value'); - }); - cache.get('/rootDir/baz', function() { - return Promise.resolve('baz value'); - }); - - jest.runAllTicks(); - expect(fs.writeFile).toBeCalled(); - }); - }); -}); diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 22ca53e6..fdcd17eb 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -15,8 +15,11 @@ jest jest.mock('fs'); +var Cache = require('../../Cache'); + var OPTIONS = { - transformModulePath: '/foo/bar' + transformModulePath: '/foo/bar', + cache: new Cache({}), }; describe('Transformer', function() { @@ -28,9 +31,6 @@ describe('Transformer', function() { jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() { return workers; })); - require('../Cache').prototype.get.mockImpl(function(filePath, callback) { - return callback(); - }); require('fs').readFile.mockImpl(function(file, callback) { callback(null, 'content'); }); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 0cc7f2c6..f7884016 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -10,7 +10,6 @@ var fs = require('fs'); var Promise = require('promise'); -var Cache = require('./Cache'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); var util = require('util'); @@ -33,35 +32,20 @@ var validateOpts = declareOpts({ type: 'array', default: [], }, - cacheVersion: { - type: 'string', - default: '1.0', - }, - resetCache: { - type: 'boolean', - default: false, - }, transformModulePath: { type:'string', required: false, }, - nonPersistent: { - type: 'boolean', - default: false, + cache: { + type: 'object', + required: true, }, }); function Transformer(options) { var opts = validateOpts(options); - this._cache = opts.nonPersistent - ? new DummyCache() - : new Cache({ - resetCache: options.resetCache, - cacheVersion: options.cacheVersion, - projectRoots: options.projectRoots, - transformModulePath: options.transformModulePath, - }); + this._cache = opts.cache; if (options.transformModulePath != null) { this._workers = workerFarm( @@ -75,7 +59,6 @@ function Transformer(options) { Transformer.prototype.kill = function() { this._workers && workerFarm.end(this._workers); - return this._cache.end(); }; Transformer.prototype.invalidateFile = function(filePath) { @@ -88,7 +71,8 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { } var transform = this._transform; - return this._cache.get(filePath, function() { + 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(); @@ -157,10 +141,3 @@ function formatBabelError(err, filename) { error.description = err.message; return error; } - -function DummyCache() {} -DummyCache.prototype.get = function(filePath, loaderCb) { - return loaderCb(); -}; -DummyCache.prototype.end = -DummyCache.prototype.invalidate = function(){}; diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 3c6d1a2f..a718bd26 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -12,6 +12,7 @@ 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'); @@ -78,6 +79,15 @@ function Packager(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, @@ -87,15 +97,14 @@ function Packager(options) { assetRoots: opts.assetRoots, fileWatcher: opts.fileWatcher, assetExts: opts.assetExts, + cache: this._cache, }); this._transformer = new Transformer({ projectRoots: opts.projectRoots, blacklistRE: opts.blacklistRE, - cacheVersion: opts.cacheVersion, - resetCache: opts.resetCache, + cache: this._cache, transformModulePath: opts.transformModulePath, - nonPersistent: opts.nonPersistent, }); this._projectRoots = opts.projectRoots; @@ -103,7 +112,8 @@ function Packager(options) { } Packager.prototype.kill = function() { - return this._transformer.kill(); + this._transformer.kill(); + return this._cache.end(); }; Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { @@ -267,4 +277,13 @@ function verifyRootExists(root) { 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; From 8c521f9749cee10abe67da89e7c61bb337ecdf12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 10 Aug 2015 16:00:20 -0700 Subject: [PATCH 242/936] [react-packager] Add more granular Activity logging --- .../DependencyResolver/DependencyGraph/index.js | 17 +++++++++++++---- react-packager/src/DependencyResolver/fastfs.js | 3 +++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index e66191e4..7e718582 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -101,7 +101,7 @@ class DependencyGraph { this._loading = Promise.all([ this._fastfs.build() .then(() => { - const hasteActivity = Activity.startEvent('haste map'); + const hasteActivity = Activity.startEvent('Building Haste Map'); this._buildHasteMap().then(() => Activity.endEvent(hasteActivity)); }), this._buildAssetMap_DEPRECATED(), @@ -518,9 +518,18 @@ class DependencyGraph { fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this)); return fastfs.build().then( - () => fastfs.findFilesByExts(this._opts.assetExts).map( - file => this._processAsset_DEPRECATED(file) - ) + () => { + const processAsset_DEPRECATEDActivity = Activity.startEvent( + 'Building (deprecated) Asset Map', + ); + + const assets = fastfs.findFilesByExts(this._opts.assetExts).map( + file => this._processAsset_DEPRECATED(file) + ); + + Activity.endEvent(processAsset_DEPRECATEDActivity); + return assets; + } ); } diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 67f7076e..88429602 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -1,5 +1,6 @@ 'use strict'; +const Activity = require('../Activity'); const Promise = require('promise'); const {EventEmitter} = require('events'); @@ -28,6 +29,7 @@ class Fastfs extends EventEmitter { ); return this._crawling.then(files => { + const fastfsActivity = Activity.startEvent('Building in-memory fs'); files.forEach(filePath => { if (filePath.match(rootsPattern)) { const newFile = new File(filePath, { isDir: false }); @@ -44,6 +46,7 @@ class Fastfs extends EventEmitter { } } }); + Activity.endEvent(fastfsActivity); this._fileWatcher.on('all', this._processFileChange.bind(this)); }); } From d1dc802e20dc661e70332789599c80158fbbef3d Mon Sep 17 00:00:00 2001 From: Evan Solomon Date: Mon, 10 Aug 2015 21:37:39 -0700 Subject: [PATCH 243/936] [Babel] Upgrade babel and regenerator to the latest version Summary: See https://github.com/babel/babel/commit/c0fd4c1f9e0b18231f585c4fa793e4cb0e01aed1 Closes https://github.com/facebook/react-native/pull/1753 Github Author: Evan Solomon @allow-crlf-text --- react-packager/.babelrc | 2 +- transformer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/.babelrc b/react-packager/.babelrc index 06b8d825..b8372585 100644 --- a/react-packager/.babelrc +++ b/react-packager/.babelrc @@ -10,7 +10,7 @@ "es6.constants", "es6.classes", "es6.destructuring", - "es6.parameters.rest", + "es6.parameters", "es6.properties.computed", "es6.properties.shorthand", "es6.spread", diff --git a/transformer.js b/transformer.js index a1da1a02..a7711931 100644 --- a/transformer.js +++ b/transformer.js @@ -23,7 +23,7 @@ function transform(srcTxt, filename, options) { 'es6.blockScoping', 'es6.classes', 'es6.destructuring', - 'es6.parameters.rest', + 'es6.parameters', 'es6.properties.computed', 'es6.properties.shorthand', 'es6.spread', From cf1a313d5c2b2d39eadb2096e5e646040062981c Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Tue, 11 Aug 2015 10:25:39 -0700 Subject: [PATCH 244/936] Bust jest caching and fix tests --- react-packager/src/Cache/__mocks__/Cache.js | 20 +++++++++++++++++++ .../src/Cache/__tests__/Cache-test.js | 8 ++++++++ 2 files changed, 28 insertions(+) create mode 100644 react-packager/src/Cache/__mocks__/Cache.js diff --git a/react-packager/src/Cache/__mocks__/Cache.js b/react-packager/src/Cache/__mocks__/Cache.js new file mode 100644 index 00000000..6f7632f6 --- /dev/null +++ b/react-packager/src/Cache/__mocks__/Cache.js @@ -0,0 +1,20 @@ +/** + * 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'; + +class Cache { + get(filepath, field, cb) { + return cb(filepath); + } + + invalidate(filepath) { } + end() { } +} + +module.exports = Cache; 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 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 245/936] [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 6ed3ba3e41edb9270e0bf55246ef69634a4763ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Oghin=C4=83?= Date: Tue, 4 Aug 2015 12:50:22 +0100 Subject: [PATCH 246/936] [cli] convert project generation to use yeoman --- react-packager/src/Activity/__mocks__/chalk.js | 13 ------------- .../src/Activity/__tests__/Activity-test.js | 3 +++ .../__tests__/DependencyGraph-test.js | 3 ++- .../__tests__/HasteDependencyResolver-test.js | 3 ++- .../src/Packager/__tests__/Packager-test.js | 3 ++- react-packager/src/Server/__tests__/Server-test.js | 3 ++- 6 files changed, 11 insertions(+), 17 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 2981f979..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: function(s) { return s; }, -}; diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index c854aa33..e8fda3dc 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -9,6 +9,9 @@ 'use strict'; jest.autoMockOff(); +jest.setMock('chalk', { + dim: function(s) { return s; }, +}); describe('Activity', function() { var Activity; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 471e6069..c7b5bb7a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -22,7 +22,8 @@ jest .dontMock('../../AssetModule') .dontMock('../../Module') .dontMock('../../Package') - .dontMock('../../ModuleCache'); + .dontMock('../../ModuleCache') + .setMock('chalk', { dim: function(s) { return s; } }); const Promise = require('promise'); diff --git a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index da159b5e..2ac854e4 100644 --- a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -10,7 +10,8 @@ jest.dontMock('../') .dontMock('q') - .dontMock('../replacePatterns'); + .dontMock('../replacePatterns') + .setMock('chalk', { dim: function(s) { return s; } }); jest.mock('path'); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 216e9009..00c76870 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -13,7 +13,8 @@ jest .dontMock('underscore') .dontMock('../../lib/ModuleTransport') .setMock('uglify-js') - .dontMock('../'); + .dontMock('../') + .setMock('chalk', { dim: function(s) { return s; } }); jest.mock('fs'); diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 7d399cb2..f0761d31 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -18,7 +18,8 @@ jest.setMock('worker-farm', function() { return function() {}; }) } }) .setMock('uglify-js') - .dontMock('../'); + .dontMock('../') + .setMock('chalk', { dim: function(s) { return s; } }); var Promise = require('promise'); From 3f1619b158f2b2abb45a9ff3c7fe63d258159c75 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 12 Aug 2015 11:47:02 -0700 Subject: [PATCH 247/936] [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 += ''; + ret += ''; ret += ''; 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 248/936] [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 249/936] [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 250/936] [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 251/936] [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 252/936] [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 253/936] [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 254/936] [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 255/936] [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 256/936] [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 257/936] [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 258/936] [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 259/936] [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 260/936] 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 261/936] [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 262/936] [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 263/936] [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 264/936] [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 265/936] [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 266/936] 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 267/936] [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 268/936] [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 269/936] [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 270/936] [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 271/936] [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 272/936] [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 273/936] [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 274/936] [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 275/936] [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 276/936] 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 - + + + - - - - - -
-

Install React DevTools

-

- React Developer Tools is an extension that allows you to inspect the - React component hierarchies in the Chrome Developer Tools. -

- - Install - -
-
-

- 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...

-
- - diff --git a/debuggerWorker.js b/debuggerWorker.js deleted file mode 100644 index 3c3f3136..00000000 --- a/debuggerWorker.js +++ /dev/null @@ -1,46 +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. - */ -/* global self, importScripts, postMessage, onmessage: true */ -/* eslint no-unused-vars: 0 */ -'use strict'; - -var messageHandlers = { - 'executeApplicationScript': function(message, sendReply) { - for (var key in message.inject) { - self[key] = JSON.parse(message.inject[key]); - } - importScripts(message.url); - sendReply(); - }, - 'executeJSCall': function(message, sendReply) { - var returnValue = [[], [], [], [], []]; - try { - if (require) { - returnValue = require(message.moduleName)[message.moduleMethod].apply(null, message.arguments); - } - } finally { - sendReply(JSON.stringify(returnValue)); - } - } -}; - -onmessage = function(message) { - var object = message.data; - - var sendReply = function(result) { - postMessage({replyID: object.id, result: result}); - }; - - var handler = messageHandlers[object.method]; - if (handler) { - handler(object, sendReply); - } else { - console.warn('Unknown method: ' + object.method); - } -}; diff --git a/getDevToolsMiddleware.js b/getDevToolsMiddleware.js deleted file mode 100644 index 966e47c4..00000000 --- a/getDevToolsMiddleware.js +++ /dev/null @@ -1,61 +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 execFile = require('child_process').execFile; -var fs = require('fs'); -var opn = require('opn'); -var path = require('path'); - -function getChromeAppName() { - switch (process.platform) { - case 'darwin': - return 'google chrome'; - case 'win32': - return 'chrome'; - default: - return 'google-chrome'; - } -} - -module.exports = function(options, isDebuggerConnected) { - return function(req, res, next) { - if (req.url === '/debugger-ui') { - var debuggerPath = path.join(__dirname, 'debugger.html'); - res.writeHead(200, {'Content-Type': 'text/html'}); - fs.createReadStream(debuggerPath).pipe(res); - } else if (req.url === '/debuggerWorker.js') { - var workerPath = path.join(__dirname, 'debuggerWorker.js'); - res.writeHead(200, {'Content-Type': 'application/javascript'}); - fs.createReadStream(workerPath).pipe(res); - } else if (req.url === '/launch-safari-devtools') { - // TODO: remove `console.log` and dev tools binary - console.log( - 'We removed support for Safari dev-tools. ' + - 'If you still need this, please let us know.' - ); - } else if (req.url === '/launch-chrome-devtools') { - if (isDebuggerConnected()) { - // Dev tools are already open; no need to open another session - res.end('OK'); - return; - } - var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui'; - console.log('Launching Dev Tools...'); - opn(debuggerURL, {app: [getChromeAppName()]}, function(err) { - if (err) { - console.error('Google Chrome exited with error:', err); - } - }); - res.end('OK'); - } else { - next(); - } - }; -}; diff --git a/getFlowTypeCheckMiddleware.js b/getFlowTypeCheckMiddleware.js deleted file mode 100644 index f3481551..00000000 --- a/getFlowTypeCheckMiddleware.js +++ /dev/null @@ -1,150 +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 chalk = require('chalk'); -var exec = require('child_process').exec; -var url = require('url'); -var Activity = require('./react-packager').Activity; - -var hasWarned = {}; - -function getFlowTypeCheckMiddleware(options) { - return function(req, res, next) { - 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 { - if (!hasWarned.noRoot) { - hasWarned.noRoot = true; - console.warn('flow: No suitable root'); - } - _endFlowBad(res); - return; - } - exec('command -v flow >/dev/null 2>&1', function(error, stdout) { - if (error) { - if (!hasWarned.noFlow) { - hasWarned.noFlow = true; - console.warn(chalk.yellow('flow: Skipping because not installed. Install with ' + - '`brew install flow`.')); - } - _endFlowBad(res); - return; - } else { - return doFlowTypecheck(res, flowroot, next); - } - }); - }; -} - -function doFlowTypecheck(res, flowroot, next) { - var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; - var eventId = Activity.startEvent('flow static typechecks'); - exec(flowCmd, function(flowError, stdout, stderr) { - Activity.endEvent(eventId); - if (!flowError) { - _endFlowOk(res); - return; - } else { - try { - var flowResponse = JSON.parse(stdout); - var errors = []; - var errorNum = 1; - flowResponse.errors.forEach(function(err) { - // flow errors are paired across callsites, so we indent and prefix to - // group them - var indent = ''; - err.message.forEach(function(msg) { - errors.push({ - description: indent + 'E' + errorNum + ': ' + msg.descr, - filename: msg.path, - lineNumber: msg.line, - column: msg.start, - }); - indent = ' '; - }); - errorNum++; - }); - var error = { - 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.', - type: 'FlowError', - errors: errors, - }; - res.writeHead(error.status, { - 'Content-Type': 'application/json; charset=UTF-8', - }); - res.end(JSON.stringify(error)); - } catch (e) { - if (stderr.match(/Could not find a \.flowconfig/)) { - if (!hasWarned.noConfig) { - hasWarned.noConfig = true; - console.warn(chalk.yellow('flow: ' + stderr)); - } - _endFlowBad(res); - } else if (flowError.code === 3) { - if (!hasWarned.timeout) { - hasWarned.timeout = true; - console.warn(chalk.yellow('flow: ' + stdout)); - } - _endSkipFlow(res); - } else { - if (!hasWarned.brokenFlow) { - hasWarned.brokenFlow = true; - console.warn(chalk.yellow( - 'Flow failed to provide parseable output:\n\n`' + stdout + - '`.\n' + 'stderr: `' + stderr + '`' - )); - } - _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; diff --git a/launchEditor.js b/launchEditor.js deleted file mode 100644 index 0bb47004..00000000 --- a/launchEditor.js +++ /dev/null @@ -1,103 +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 chalk = require('chalk'); -var fs = require('fs'); -var spawn = require('child_process').spawn; - -function isTerminalEditor(editor) { - switch (editor) { - case 'vim': - case 'emacs': - case 'nano': - return true; - } - return false; -} - -function getArgumentsForLineNumber(editor, fileName, lineNumber) { - switch (editor) { - case 'vim': - case 'mvim': - return [fileName, '+' + lineNumber]; - case 'atom': - case 'subl': - case 'sublime': - return [fileName + ':' + lineNumber]; - case 'joe': - case 'emacs': - case 'emacsclient': - return ['+' + lineNumber, fileName]; - case 'rmate': - case 'mate': - return ['--line', lineNumber, fileName]; - } - - // For all others, drop the lineNumber until we have - // a mapping above, since providing the lineNumber incorrectly - // can result in errors or confusing behavior. - return [fileName]; -} - -function printInstructions(title) { - console.log([ - '', - chalk.bgBlue.white.bold(' ' + title + ' '), - ' When you see Red Box with stack trace, you can click any ', - ' stack frame to jump to the source file. The packager will launch your ', - ' editor of choice. It will first look at REACT_EDITOR environment ', - ' variable, then at EDITOR. To set it up, you can add something like ', - ' REACT_EDITOR=atom to your .bashrc.', - '' - ].join('\n')); -} - -var _childProcess = null; -function launchEditor(fileName, lineNumber) { - if (!fs.existsSync(fileName)) { - return; - } - - var editor = process.env.REACT_EDITOR || process.env.EDITOR; - if (!editor) { - printInstructions('PRO TIP'); - return; - } - - var args = [fileName]; - if (lineNumber) { - args = getArgumentsForLineNumber(editor, fileName, lineNumber); - } - console.log('Opening ' + chalk.underline(fileName) + ' with ' + chalk.bold(editor)); - - if (_childProcess && isTerminalEditor(editor)) { - // There's an existing editor process already and it's attached - // to the terminal, so go kill it. Otherwise two separate editor - // instances attach to the stdin/stdout which gets confusing. - _childProcess.kill('SIGKILL'); - } - - _childProcess = spawn(editor, args, {stdio: 'inherit'}); - _childProcess.on('exit', function(errorCode) { - _childProcess = null; - - if (errorCode) { - console.log(chalk.red('Your editor exited with an error!')); - printInstructions('Keep these instructions in mind:'); - } - }); - - _childProcess.on('error', function(error) { - console.log(chalk.red(error.message)); - printInstructions('How to fix:'); - }) -} - -module.exports = launchEditor; diff --git a/loadRawBodyMiddleware.js b/loadRawBodyMiddleware.js deleted file mode 100644 index 2b59195f..00000000 --- a/loadRawBodyMiddleware.js +++ /dev/null @@ -1,22 +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 = function(req, res, next) { - req.rawBody = ''; - req.setEncoding('utf8'); - - req.on('data', function(chunk) { - req.rawBody += chunk; - }); - - req.on('end', function() { - next(); - }); -}; diff --git a/openStackFrameInEditorMiddleware.js b/openStackFrameInEditorMiddleware.js deleted file mode 100644 index 78baa105..00000000 --- a/openStackFrameInEditorMiddleware.js +++ /dev/null @@ -1,21 +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'; - -const launchEditor = require('./launchEditor'); - -module.exports = function(req, res, next) { - if (req.url === '/open-stack-frame') { - var frame = JSON.parse(req.rawBody); - launchEditor(frame.file, frame.lineNumber); - res.end('OK'); - } else { - next(); - } -}; diff --git a/parseCommandLine.js b/parseCommandLine.js deleted file mode 100644 index 4293b79b..00000000 --- a/parseCommandLine.js +++ /dev/null @@ -1,76 +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. - * - * Wrapper on-top of `optimist` in order to properly support boolean flags - * and have a slightly less akward API. - * - * Usage example: - * var argv = parseCommandLine([{ - * command: 'web', - * description: 'Run in a web browser instead of iOS', - * default: true - * }]) - */ -'use strict'; - -var optimist = require('optimist'); - -function parseCommandLine(config, args) { - args = args || process.argv; - // optimist default API requires you to write the command name three time - // This is a small wrapper to accept an object instead - for (var i = 0; i < config.length; ++i) { - if (config[i].type === 'string') { - optimist.string(config[i].command); - } else { - optimist.boolean(config[i].command); - } - - optimist - .default(config[i].command, config[i].default) - .describe(config[i].command, config[i].description); - - if (config[i].required) { - optimist.demand(config[i].command); - } - } - var argv = optimist.parse(args); - - // optimist doesn't have support for --dev=false, instead it returns 'false' - for (var i = 0; i < config.length; ++i) { - var command = config[i].command; - if (argv[command] === undefined) { - argv[command] = config[i].default; - } - if (argv[command] === 'true') { - argv[command] = true; - } - if (argv[command] === 'false') { - argv[command] = false; - } - if (config[i].type === 'string') { - // According to https://github.com/substack/node-optimist#numbers, - // every argument that looks like a number should be converted to one. - var strValue = argv[command]; - var numValue = strValue ? Number(strValue) : undefined; - if (typeof numValue === 'number' && !isNaN(numValue)) { - argv[command] = numValue; - } - } - } - - // Show --help - if (argv.help || argv.h) { - optimist.showHelp(); - process.exit(); - } - - return argv; -} - -module.exports = parseCommandLine; diff --git a/statusPageMiddleware.js b/statusPageMiddleware.js deleted file mode 100644 index fb793468..00000000 --- a/statusPageMiddleware.js +++ /dev/null @@ -1,21 +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'; - -/** - * Status page so that anyone who needs to can verify that the packager is - * running on 8081 and not another program / service. - */ -module.exports = function(req, res, next) { - if (req.url === '/status') { - res.end('packager-status:running'); - } else { - next(); - } -}; diff --git a/systraceProfileMiddleware.js b/systraceProfileMiddleware.js deleted file mode 100644 index 02b274c9..00000000 --- a/systraceProfileMiddleware.js +++ /dev/null @@ -1,52 +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'; - -const exec = require('child_process').exec; -const fs = require('fs'); -const path = require('path'); - -module.exports = function(req, res, next) { - if (req.url !== '/systrace') { - next(); - return; - } - - 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) { - var response = '\n** Failed executing `' + cmd + '` **\n\n' + - 'Google trace-viewer is required to visualize the data, ' + - 'You can install it with `brew install trace2html`\n\n' + - 'NOTE: Your profile data was kept at:\n' + dumpName; - console.log(response); - res.end(response); - } else { - console.error(error); - res.end('Unknown error: ' + error.message); - } - return; - } else { - exec('rm ' + dumpName); - exec('open ' + dumpName.replace(/json$/, 'html'), function(err) { - if (err) { - console.error(err); - res.end(err.message); - } else { - res.end(); - } - }); - } - }); -}; diff --git a/webSocketProxy.js b/webSocketProxy.js deleted file mode 100644 index f5ff7ea0..00000000 --- a/webSocketProxy.js +++ /dev/null @@ -1,73 +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'; - - -function attachToServer(server, path) { - var WebSocketServer = require('ws').Server; - var wss = new WebSocketServer({ - server: server, - path: path - }); - var clients = []; - - function sendSpecial(message) { - clients.forEach(function (cn) { - try { - cn.send(JSON.stringify(message)); - } catch(e) { - // Sometimes this call throws 'not opened' - } - }); - } - - wss.on('connection', function(ws) { - var id = Math.random().toString(15).slice(10, 20); - sendSpecial({$open: id}); - clients.push(ws); - - var allClientsExcept = function(ws) { - return clients.filter(function(cn) { return cn !== ws; }); - }; - - ws.onerror = function() { - clients = allClientsExcept(ws); - sendSpecial({$error: id}); - }; - - ws.onclose = function() { - clients = allClientsExcept(ws); - sendSpecial({$close: id}); - }; - - ws.on('message', function(message) { - allClientsExcept(ws).forEach(function(cn) { - try { - cn.send(message); - } catch(e) { - // Sometimes this call throws 'not opened' - } - }); - }); - }); - - return { - server: wss, - isChromeConnected: function() { - return clients - .map(function(ws) { return ws.upgradeReq.headers['user-agent']; }) - .filter(Boolean) - .some(function(userAgent) { return userAgent.includes('Chrome'); }) - } - }; -} - -module.exports = { - attachToServer: attachToServer -}; From abeaa5bc9b14adce3c7059ad8c513d583bd5768a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 27 Oct 2015 10:32:00 -0700 Subject: [PATCH 414/936] Improve error logging when rebuild fails Summary: public We're seeing intermittent errors on the packager when rebasing. Since this is very hard to repro lets add more logging to the warning we show just before starting to rebuild the entire haste map again Reviewed By: frantic Differential Revision: D2585427 fb-gh-sync-id: fbfa953f6c8ae78cbee2f3ab19ad494b084165c8 --- .../src/DependencyResolver/DependencyGraph/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 1baef52f..4c0a2168 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -211,9 +211,13 @@ class DependencyGraph { // we are in an error state and we should decide to do a full rebuild. this._loading = this._loading.finally(() => { if (this._hasteMapError) { + console.warn( + 'Rebuilding haste map to recover from error:\n' + + this._hasteMapError.stack + ); this._hasteMapError = null; + // Rebuild the entire map if last change resulted in an error. - console.warn('Rebuilding haste map to recover from error'); this._loading = this._hasteMap.build(); } else { this._loading = this._hasteMap.processFileChange(type, absPath); From c5c4dc94b10d5f8a96a4d59596d82706f190a9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 27 Oct 2015 17:44:59 -0700 Subject: [PATCH 415/936] Infer platform from extension Summary: public Aparently this used to work on 0.11, lets fix it :) Reviewed By: foghina Differential Revision: D2557719 fb-gh-sync-id: dcbca077431c1356c89dfc55b71eecff64c7ad3d --- react-packager/src/Server/index.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index c18fe04a..2b589d52 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -11,6 +11,7 @@ const Activity = require('../Activity'); const AssetServer = require('../AssetServer'); const FileWatcher = require('../FileWatcher'); +const getPlatformExtension = require('../lib/getPlatformExtension'); const Bundler = require('../Bundler'); const Promise = require('promise'); @@ -173,6 +174,10 @@ class Server { buildBundle(options) { return Promise.resolve().then(() => { + if (!options.platform) { + options.platform = getPlatformExtension(options.entryFile); + } + const opts = bundleOpts(options); return this._bundler.bundle( opts.entryFile, @@ -191,6 +196,10 @@ class Server { getDependencies(options) { return Promise.resolve().then(() => { + if (!options.platform) { + options.platform = getPlatformExtension(options.entryFile); + } + const opts = dependencyOpts(options); return this._bundler.getDependencies( opts.entryFile, @@ -432,6 +441,10 @@ class Server { const sourceMapUrlObj = _.clone(urlObj); sourceMapUrlObj.pathname = pathname.replace(/\.bundle$/, '.map'); + // try to get the platform from the url + const platform = urlObj.query.platform || + getPlatformExtension(pathname); + return { sourceMapUrl: url.format(sourceMapUrlObj), entryFile: entryFile, @@ -443,7 +456,7 @@ class Server { 'inlineSourceMap', false ), - platform: urlObj.query.platform, + platform: platform, }; } From 02c3c465f875d776122300f1f08e335fcf582b40 Mon Sep 17 00:00:00 2001 From: Bhuwan Khattar Date: Wed, 28 Oct 2015 21:05:15 -0700 Subject: [PATCH 416/936] require InitializeJavaScriptAppEngine in bundle Reviewed By: martinbigio Differential Revision: D2582794 fb-gh-sync-id: d6cfdda81f556ef0cefb2e9dc58f5978b651bd5b --- react-packager/src/Bundler/Bundle.js | 21 ++++-- .../src/Bundler/__tests__/Bundle-test.js | 36 ++++++++-- .../src/Bundler/__tests__/Bundler-test.js | 10 ++- react-packager/src/Bundler/index.js | 13 +++- .../src/Server/__tests__/Server-test.js | 67 +++++++++++-------- react-packager/src/Server/index.js | 17 ++--- 6 files changed, 109 insertions(+), 55 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 6f20b49d..a441aa3c 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -53,13 +53,8 @@ class Bundle { 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' - })); + options.runBeforeMainModule.forEach(this._addRequireCall, this); + this._addRequireCall(this._mainModuleId); } Object.freeze(this._modules); @@ -69,6 +64,18 @@ class Bundle { this._finalized = true; } + _addRequireCall(moduleId) { + const code = ';require("' + moduleId + '");'; + const name = 'require-' + moduleId; + this.addModule(new ModuleTransport({ + name, + code, + virtual: true, + sourceCode: code, + sourcePath: name + '.js', + })); + } + _assertFinalized() { if (!this._finalized) { throw new Error('Bundle needs to be finalized before getting any source'); diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 80a23335..62ad59d3 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -81,10 +81,14 @@ describe('Bundle', function() { })); bundle.setMainModuleId('foo'); - bundle.finalize({runMainModule: true}); + bundle.finalize({ + runBeforeMainModule: ['bar'], + runMainModule: true, + }); expect(bundle.getSource()).toBe([ 'transformed foo;', 'transformed bar;', + ';require("bar");', ';require("foo");', '\/\/@ sourceMappingURL=test_url', ].join('\n')); @@ -141,7 +145,10 @@ describe('Bundle', function() { })); p.setMainModuleId('foo'); - p.finalize({runMainModule: true}); + p.finalize({ + runBeforeMainModule: [], + runMainModule: true, + }); var s = p.getSourceMap(); expect(s).toEqual(genSourceMap(p.getModules())); }); @@ -171,7 +178,10 @@ describe('Bundle', function() { })); p.setMainModuleId('foo'); - p.finalize({runMainModule: true}); + p.finalize({ + runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + runMainModule: true, + }); var s = p.getSourceMap(); expect(s).toEqual({ @@ -200,14 +210,28 @@ describe('Bundle', function() { line: 6 }, map: { - file: 'RunMainModule.js', + file: 'require-InitializeJavaScriptAppEngine.js', mappings: 'AAAA;', names: [], - sources: [ 'RunMainModule.js' ], + sources: [ 'require-InitializeJavaScriptAppEngine.js' ], + sourcesContent: [';require("InitializeJavaScriptAppEngine");'], + version: 3, + } + }, + { + offset: { + column: 0, + line: 7 + }, + map: { + file: 'require-foo.js', + mappings: 'AAAA;', + names: [], + sources: [ 'require-foo.js' ], sourcesContent: [';require("foo");'], version: 3, } - } + }, ], }); }); diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index 4b0526ad..233df3c8 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -146,8 +146,12 @@ describe('Bundler', function() { }; }); - return bundler.bundle('/root/foo.js', true, 'source_map_url') - .then(function(p) { + return bundler.bundle({ + entryFile: '/root/foo.js', + runBeforeMainModule: [], + runModule: true, + sourceMapUrl: 'source_map_url', + }).then(function(p) { expect(p.addModule.mock.calls[0][0]).toEqual({ code: 'lol transformed /root/foo.js lol', map: 'sourcemap /root/foo.js', @@ -221,7 +225,7 @@ describe('Bundler', function() { }); expect(p.finalize.mock.calls[0]).toEqual([ - {runMainModule: true} + {runMainModule: true, runBeforeMainModule: []} ]); expect(p.addAsset.mock.calls).toContain([ diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 1bad8ec6..54a843f2 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -132,12 +132,19 @@ class Bundler { return this._bundlesLayout.generateLayout(main, isDev); } - bundle(main, runModule, sourceMapUrl, isDev, platform) { + bundle({ + entryFile, + runModule: runMainModule, + runBeforeMainModule, + sourceMapUrl, + dev: isDev, + platform, + }) { const bundle = new Bundle(sourceMapUrl); const findEventId = Activity.startEvent('find dependencies'); let transformEventId; - return this.getDependencies(main, isDev, platform).then((response) => { + return this.getDependencies(entryFile, isDev, platform).then((response) => { Activity.endEvent(findEventId); transformEventId = Activity.startEvent('transform'); @@ -174,7 +181,7 @@ class Bundler { bundle.addModule(moduleTransport); }); - bundle.finalize({ runMainModule: runModule }); + bundle.finalize({runBeforeMainModule, runMainModule}); return bundle; }); } diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 5220c0c1..d68e0410 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -106,13 +106,16 @@ describe('processRequest', () => { 'index.ios.includeRequire.bundle' ).then(response => { expect(response).toEqual('this is the source'); - expect(Bundler.prototype.bundle).toBeCalledWith( - 'index.ios.js', - true, - 'index.ios.includeRequire.map', - true, - undefined - ); + expect(Bundler.prototype.bundle).toBeCalledWith({ + entryFile: 'index.ios.js', + inlineSourceMap: false, + minify: false, + runModule: true, + sourceMapUrl: 'index.ios.includeRequire.map', + dev: true, + platform: undefined, + runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + }); }); }); @@ -122,13 +125,16 @@ describe('processRequest', () => { 'index.bundle?platform=ios' ).then(function(response) { expect(response).toEqual('this is the source'); - expect(Bundler.prototype.bundle).toBeCalledWith( - 'index.js', - true, - 'index.map?platform=ios', - true, - 'ios', - ); + expect(Bundler.prototype.bundle).toBeCalledWith({ + entryFile: 'index.js', + inlineSourceMap: false, + minify: false, + runModule: true, + sourceMapUrl: 'index.map?platform=ios', + dev: true, + platform: 'ios', + runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + }); }); }); @@ -260,13 +266,15 @@ describe('processRequest', () => { return server.buildBundle({ entryFile: 'foo file' }).then(() => - expect(Bundler.prototype.bundle).toBeCalledWith( - 'foo file', - true, - undefined, - true, - undefined - ) + expect(Bundler.prototype.bundle).toBeCalledWith({ + entryFile: 'foo file', + inlineSourceMap: false, + minify: false, + runModule: true, + dev: true, + platform: undefined, + runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + }) ); }); }); @@ -275,13 +283,16 @@ describe('processRequest', () => { pit('Calls the bundler with the correct args', () => { return server.buildBundleFromUrl('/path/to/foo.bundle?dev=false&runModule=false') .then(() => - expect(Bundler.prototype.bundle).toBeCalledWith( - 'path/to/foo.js', - false, - '/path/to/foo.map?dev=false&runModule=false', - false, - undefined - ) + expect(Bundler.prototype.bundle).toBeCalledWith({ + entryFile: 'path/to/foo.js', + inlineSourceMap: false, + minify: false, + runModule: false, + sourceMapUrl: '/path/to/foo.map?dev=false&runModule=false', + dev: false, + platform: undefined, + runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + }) ); }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 2b589d52..49704f12 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -94,7 +94,14 @@ const bundleOpts = declareOpts({ platform: { type: 'string', required: true, - } + }, + runBeforeMainModule: { + type: 'array', + default: [ + // Ensures essential globals are available and are patched correctly. + 'InitializeJavaScriptAppEngine' + ], + }, }); const dependencyOpts = declareOpts({ @@ -179,13 +186,7 @@ class Server { } const opts = bundleOpts(options); - return this._bundler.bundle( - opts.entryFile, - opts.runModule, - opts.sourceMapUrl, - opts.dev, - opts.platform - ); + return this._bundler.bundle(opts); }); } From a0dd08e26c21e8758abbee0970d7da742acffa82 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 3 Nov 2015 01:19:15 -0800 Subject: [PATCH 417/936] Log minification time Summary: public Add an activity to log minification time. Reviewed By: vjeux Differential Revision: D2604863 fb-gh-sync-id: cbdf0297f0611c87b589b87928af235f9ba81172 --- react-packager/src/Bundler/Bundle.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index a441aa3c..bf4edc32 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -12,6 +12,7 @@ const _ = require('underscore'); const base64VLQ = require('./base64-vlq'); const UglifyJS = require('uglify-js'); const ModuleTransport = require('../lib/ModuleTransport'); +const Activity = require('../Activity'); const SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL='; @@ -128,11 +129,13 @@ class Bundle { const source = this._getSource(); try { + const minifyActivity = Activity.startEvent('minify'); this._minifiedSourceAndMap = UglifyJS.minify(source, { fromString: true, outSourceMap: 'bundle.js', inSourceMap: this.getSourceMap(), }); + Activity.endEvent(minifyActivity); return this._minifiedSourceAndMap; } catch(e) { // Sometimes, when somebody is using a new syntax feature that we From e93b1f8411678d5aaaf05e1e03fa13069f374aee Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 4 Nov 2015 08:52:03 -0800 Subject: [PATCH 418/936] Convert System.import transform to babel 6 Reviewed By: martinbigio Differential Revision: D2615286 fb-gh-sync-id: 4f0e8e3359413d972ab4d0da291870768a77e361 --- .../babel-plugin-system-import/6/index.js | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 react-packager/src/transforms/babel-plugin-system-import/6/index.js diff --git a/react-packager/src/transforms/babel-plugin-system-import/6/index.js b/react-packager/src/transforms/babel-plugin-system-import/6/index.js new file mode 100644 index 00000000..8a6cbcf0 --- /dev/null +++ b/react-packager/src/transforms/babel-plugin-system-import/6/index.js @@ -0,0 +1,63 @@ + /** + * 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. + * + */ +/*jslint node: true */ +'use strict'; + +const t = require('babel-types'); + +/** + * Transforms asynchronous module importing into a function call + * that includes which bundle needs to be loaded + * + * Transforms: + * + * System.import('moduleA') + * + * to: + * + * loadBundles('bundleA') + */ +module.exports = function() { + return { + visitor: { + CallExpression: function (path, state) { + if (!isAppropriateSystemImportCall(path.node)) { + return; + } + + var bundlesLayout = state.opts.bundlesLayout; + var bundleID = bundlesLayout.getBundleIDForModule( + path.node.arguments[0].value + ); + + var bundles = bundleID.split('.'); + bundles.splice(0, 1); + bundles = bundles.map(function(id) { + return t.stringLiteral('bundle.' + id); + }); + + path.replaceWith(t.callExpression( + t.identifier('loadBundles'), + [t.arrayExpression(bundles)] + )); + }, + }, + }; +}; + +function isAppropriateSystemImportCall(node) { + return ( + node.callee.type === 'MemberExpression' && + node.callee.object.name === 'System' && + node.callee.property.name === 'import' && + node.arguments.length === 1 && + node.arguments[0].type === 'StringLiteral' + ); +} From 29befc8d1c383a2e422a7a7a76eda5484b9a3751 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Wed, 4 Nov 2015 14:33:48 -0800 Subject: [PATCH 419/936] Update jest to 0.7.1 Reviewed By: frantic Differential Revision: D2614935 fb-gh-sync-id: 64cc4fb439f0c53f0eb0588c22e0291813a7e334 --- .../src/SocketInterface/__tests__/SocketServer-test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js b/react-packager/src/SocketInterface/__tests__/SocketServer-test.js index e006d612..39c28d96 100644 --- a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js +++ b/react-packager/src/SocketInterface/__tests__/SocketServer-test.js @@ -22,6 +22,7 @@ var net = require('net'); describe('SocketServer', () => { let netServer; let bunser; + let processOn; beforeEach(() => { const {EventEmitter} = require.requireActual('events'); @@ -32,6 +33,14 @@ describe('SocketServer', () => { bunser = new EventEmitter(); bser.BunserBuf.mockImpl(() => bunser); bser.dumpToBuffer.mockImpl((a) => a); + + // Don't attach `process.on('exit')` handlers directly from SocketServer + processOn = process.on; + process.on = jest.genMockFn(); + }); + + afterEach(() => { + process.on = processOn; }); pit('create a server', () => { From c73100186443560ef78edfe98ad49bc150b71ef1 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Wed, 4 Nov 2015 16:04:44 -0800 Subject: [PATCH 420/936] Start pulling DependencyResolver apart Summary: I'm planning to split up `DependencyResolver` into the react-native specific implementation of it and a new, generic resolver that is going to replace `node-haste` for jest and other places where we might need JS dependency resolution. The plan is to split the two folders up so that: * `Resolver` is the folder for all the react-native specific resolver code * `DependencyResolver` will become a standalone library, eventually moving into a package called `node-haste`. There is still a lot to be figured out. This is just the first diff of likely many. The current goal is to make `DependencyResolver` standalone to be able to create an instance of a resolver and resolve all dependencies for a file. This is when I can start integrating it more seriously with jest. This diff simply moves a bunch of things around and turns `HasteModuleResolver` into an ES2015 class ( :) ). bypass-lint public Reviewed By: davidaurelio Differential Revision: D2614151 fb-gh-sync-id: ff4e434c4747d2fb032d34dc19fb85e0b0c553ac --- .../src/Bundler/__tests__/Bundler-test.js | 4 +- react-packager/src/Bundler/index.js | 4 +- .../__tests__/BundlesLayout-test.js | 16 +- .../BundlesLayoutIntegration-test.js | 6 +- .../src/DependencyResolver/index.js | 175 ----------------- .../__tests__/Resolver-test.js} | 32 ++-- react-packager/src/Resolver/index.js | 178 ++++++++++++++++++ .../polyfills/Array.prototype.es6.js | 0 .../polyfills/String.prototype.es6.js | 0 .../polyfills/console.js | 0 .../polyfills/error-guard.js | 0 .../polyfills/polyfills.js | 0 .../polyfills/prelude.js | 0 .../polyfills/prelude_dev.js | 0 .../polyfills/require.js | 2 +- 15 files changed, 208 insertions(+), 209 deletions(-) delete mode 100644 react-packager/src/DependencyResolver/index.js rename react-packager/src/{DependencyResolver/__tests__/HasteDependencyResolver-test.js => Resolver/__tests__/Resolver-test.js} (98%) create mode 100644 react-packager/src/Resolver/index.js rename react-packager/src/{DependencyResolver => Resolver}/polyfills/Array.prototype.es6.js (100%) rename react-packager/src/{DependencyResolver => Resolver}/polyfills/String.prototype.es6.js (100%) rename react-packager/src/{DependencyResolver => Resolver}/polyfills/console.js (100%) rename react-packager/src/{DependencyResolver => Resolver}/polyfills/error-guard.js (100%) rename react-packager/src/{DependencyResolver => Resolver}/polyfills/polyfills.js (100%) rename react-packager/src/{DependencyResolver => Resolver}/polyfills/prelude.js (100%) rename react-packager/src/{DependencyResolver => Resolver}/polyfills/prelude_dev.js (100%) rename react-packager/src/{DependencyResolver => Resolver}/polyfills/require.js (96%) diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index 233df3c8..ac8494be 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -19,7 +19,7 @@ jest.mock('fs'); var Bundler = require('../'); var JSTransformer = require('../../JSTransformer'); -var DependencyResolver = require('../../DependencyResolver'); +var Resolver = require('../../Resolver'); var sizeOf = require('image-size'); var fs = require('fs'); @@ -54,7 +54,7 @@ describe('Bundler', function() { beforeEach(function() { getDependencies = jest.genMockFn(); wrapModule = jest.genMockFn(); - DependencyResolver.mockImpl(function() { + Resolver.mockImpl(function() { return { getDependencies: getDependencies, wrapModule: wrapModule, diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 54a843f2..2b0c5ff2 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -16,7 +16,7 @@ const ProgressBar = require('progress'); const BundlesLayout = require('../BundlesLayout'); const Cache = require('../Cache'); const Transformer = require('../JSTransformer'); -const DependencyResolver = require('../DependencyResolver'); +const Resolver = require('../Resolver'); const Bundle = require('./Bundle'); const Activity = require('../Activity'); const ModuleTransport = require('../lib/ModuleTransport'); @@ -94,7 +94,7 @@ class Bundler { transformModulePath: opts.transformModulePath, }); - this._resolver = new DependencyResolver({ + this._resolver = new Resolver({ projectRoots: opts.projectRoots, blacklistRE: opts.blacklistRE, polyfillModuleNames: opts.polyfillModuleNames, diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js index 497709d1..bc4fac8a 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -13,14 +13,14 @@ jest.dontMock('../index') var Promise = require('promise'); var BundlesLayout = require('../index'); -var DependencyResolver = require('../../DependencyResolver'); +var Resolver = require('../../Resolver'); var loadCacheSync = require('../../lib/loadCacheSync'); describe('BundlesLayout', () => { function newBundlesLayout(options) { return new BundlesLayout(Object.assign({ projectRoots: ['/root'], - dependencyResolver: new DependencyResolver(), + dependencyResolver: new Resolver(), }, options)); } @@ -38,7 +38,7 @@ describe('BundlesLayout', () => { } pit('should bundle sync dependencies', () => { - DependencyResolver.prototype.getDependencies.mockImpl((path) => { + Resolver.prototype.getDependencies.mockImpl((path) => { switch (path) { case '/root/index.js': return Promise.resolve({ @@ -67,7 +67,7 @@ describe('BundlesLayout', () => { }); pit('should separate async dependencies into different bundle', () => { - DependencyResolver.prototype.getDependencies.mockImpl((path) => { + Resolver.prototype.getDependencies.mockImpl((path) => { switch (path) { case '/root/index.js': return Promise.resolve({ @@ -100,7 +100,7 @@ describe('BundlesLayout', () => { }); pit('separate async dependencies of async dependencies', () => { - DependencyResolver.prototype.getDependencies.mockImpl((path) => { + Resolver.prototype.getDependencies.mockImpl((path) => { switch (path) { case '/root/index.js': return Promise.resolve({ @@ -142,7 +142,7 @@ describe('BundlesLayout', () => { }); pit('separate bundle sync dependencies of async ones on same bundle', () => { - DependencyResolver.prototype.getDependencies.mockImpl((path) => { + Resolver.prototype.getDependencies.mockImpl((path) => { switch (path) { case '/root/index.js': return Promise.resolve({ @@ -180,7 +180,7 @@ describe('BundlesLayout', () => { }); pit('separate cache in which bundle is each dependency', () => { - DependencyResolver.prototype.getDependencies.mockImpl((path) => { + Resolver.prototype.getDependencies.mockImpl((path) => { switch (path) { case '/root/index.js': return Promise.resolve({ @@ -218,7 +218,7 @@ describe('BundlesLayout', () => { }); pit('separate cache in which bundle is each dependency', () => { - DependencyResolver.prototype.getDependencies.mockImpl((path) => { + Resolver.prototype.getDependencies.mockImpl((path) => { switch (path) { case '/root/index.js': return Promise.resolve({ diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 23be64a7..6fab15da 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -20,7 +20,7 @@ jest.mock('fs'); var BundlesLayout = require('../index'); var Cache = require('../../Cache'); -var DependencyResolver = require('../../DependencyResolver'); +var Resolver = require('../../Resolver'); var fs = require('fs'); describe('BundlesLayout', () => { @@ -47,7 +47,7 @@ describe('BundlesLayout', () => { describe('generate', () => { function newBundlesLayout() { - const resolver = new DependencyResolver({ + const resolver = new Resolver({ projectRoots: ['/root', '/' + __dirname.split('/')[1]], fileWatcher: fileWatcher, cache: new Cache(), @@ -577,7 +577,7 @@ describe('BundlesLayout', () => { }); function getBaseFs() { - const p = path.join(__dirname, '../../../DependencyResolver/polyfills').substring(1); + const p = path.join(__dirname, '../../../Resolver/polyfills').substring(1); const root = {}; let currentPath = root; diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js deleted file mode 100644 index 366455f0..00000000 --- a/react-packager/src/DependencyResolver/index.js +++ /dev/null @@ -1,175 +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 path = require('path'); -var DependencyGraph = require('./DependencyGraph'); -var replacePatterns = require('./replacePatterns'); -var Polyfill = require('./Polyfill'); -var declareOpts = require('../lib/declareOpts'); -var Promise = require('promise'); - -var validateOpts = declareOpts({ - projectRoots: { - type: 'array', - required: true, - }, - blacklistRE: { - type: 'object', // typeof regex is object - }, - polyfillModuleNames: { - type: 'array', - default: [], - }, - moduleFormat: { - type: 'string', - default: 'haste', - }, - assetRoots: { - type: 'array', - default: [], - }, - fileWatcher: { - type: 'object', - required: true, - }, - assetExts: { - type: 'array', - required: true, - }, - cache: { - type: 'object', - required: true, - }, -}); - -function HasteDependencyResolver(options) { - var opts = validateOpts(options); - - this._depGraph = new DependencyGraph({ - roots: opts.projectRoots, - assetRoots_DEPRECATED: opts.assetRoots, - assetExts: opts.assetExts, - ignoreFilePath: function(filepath) { - return filepath.indexOf('__tests__') !== -1 || - (opts.blacklistRE && opts.blacklistRE.test(filepath)); - }, - fileWatcher: opts.fileWatcher, - cache: opts.cache, - }); - - - this._polyfillModuleNames = opts.polyfillModuleNames || []; -} - -var getDependenciesValidateOpts = declareOpts({ - dev: { - type: 'boolean', - default: true, - }, - platform: { - type: 'string', - required: false, - }, -}); - -HasteDependencyResolver.prototype.getDependencies = function(main, options) { - var opts = getDependenciesValidateOpts(options); - - return this._depGraph.getDependencies(main, opts.platform).then( - resolutionResponse => { - this._getPolyfillDependencies(opts.dev).reverse().forEach( - polyfill => resolutionResponse.prependDependency(polyfill) - ); - - return resolutionResponse.finalize(); - } - ); -}; - -HasteDependencyResolver.prototype._getPolyfillDependencies = function(isDev) { - var polyfillModuleNames = [ - isDev - ? path.join(__dirname, 'polyfills/prelude_dev.js') - : path.join(__dirname, 'polyfills/prelude.js'), - path.join(__dirname, 'polyfills/require.js'), - path.join(__dirname, 'polyfills/polyfills.js'), - path.join(__dirname, 'polyfills/console.js'), - path.join(__dirname, 'polyfills/error-guard.js'), - path.join(__dirname, 'polyfills/String.prototype.es6.js'), - path.join(__dirname, 'polyfills/Array.prototype.es6.js'), - ].concat(this._polyfillModuleNames); - - return polyfillModuleNames.map( - (polyfillModuleName, idx) => new Polyfill({ - path: polyfillModuleName, - id: polyfillModuleName, - dependencies: polyfillModuleNames.slice(0, idx), - isPolyfill: true, - }) - ); -}; - -HasteDependencyResolver.prototype.wrapModule = function(resolutionResponse, module, code) { - return Promise.resolve().then(() => { - if (module.isPolyfill()) { - return Promise.resolve(code); - } - - const resolvedDeps = Object.create(null); - const resolvedDepsArr = []; - - return Promise.all( - resolutionResponse.getResolvedDependencyPairs(module).map( - ([depName, 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]; - if (depId) { - return pre + quot + depId + post; - } else { - return codeMatch; - } - }; - - return module.getName().then( - name => defineModuleCode({ - code: code.replace(replacePatterns.IMPORT_RE, relativizeCode) - .replace(replacePatterns.EXPORT_RE, relativizeCode) - .replace(replacePatterns.REQUIRE_RE, relativizeCode), - moduleName: name, - }) - ); - }); - }); -}; - -HasteDependencyResolver.prototype.getDebugInfo = function() { - return this._depGraph.getDebugInfo(); -}; - -function defineModuleCode({moduleName, code}) { - return [ - `__d(`, - `'${moduleName}',`, - 'function(global, require, module, exports) {', - ` ${code}`, - '\n});', - ].join(''); -} - -module.exports = HasteDependencyResolver; diff --git a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js similarity index 98% rename from react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js rename to react-packager/src/Resolver/__tests__/Resolver-test.js index be6ac2a9..715c0459 100644 --- a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -10,19 +10,20 @@ jest.dontMock('../') .dontMock('underscore') - .dontMock('../replacePatterns'); + .dontMock('../../DependencyResolver/replacePatterns'); jest.mock('path'); var Promise = require('promise'); -var HasteDependencyResolver = require('../'); -var Module = require('../Module'); -var Polyfill = require('../Polyfill'); +var Resolver = require('../'); +var Module = require('../../DependencyResolver/Module'); +var Polyfill = require('../../DependencyResolver/Polyfill'); +var DependencyGraph = require('../../DependencyResolver/DependencyGraph'); var path = require('path'); var _ = require('underscore'); -describe('HasteDependencyResolver', function() { +describe('Resolver', function() { beforeEach(function() { Polyfill.mockClear(); @@ -60,13 +61,11 @@ describe('HasteDependencyResolver', function() { var module = createModule('index'); var deps = [module]; - var depResolver = new HasteDependencyResolver({ + var depResolver = new Resolver({ projectRoot: '/root', }); - // Is there a better way? How can I mock the prototype instead? - var depGraph = depResolver._depGraph; - depGraph.getDependencies.mockImpl(function() { + DependencyGraph.prototype.getDependencies.mockImpl(function() { return Promise.resolve(new ResolutionResponseMock({ dependencies: deps, mainModuleId: 'index', @@ -144,12 +143,11 @@ describe('HasteDependencyResolver', function() { var module = createModule('index'); var deps = [module]; - var depResolver = new HasteDependencyResolver({ + var depResolver = new Resolver({ projectRoot: '/root', }); - var depGraph = depResolver._depGraph; - depGraph.getDependencies.mockImpl(function() { + DependencyGraph.prototype.getDependencies.mockImpl(function() { return Promise.resolve(new ResolutionResponseMock({ dependencies: deps, mainModuleId: 'index', @@ -160,7 +158,7 @@ describe('HasteDependencyResolver', function() { return depResolver.getDependencies('/root/index.js', { dev: true }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); - expect(depGraph.getDependencies).toBeCalledWith('/root/index.js', undefined); + expect(DependencyGraph.mock.instances[0].getDependencies).toBeCalledWith('/root/index.js', undefined); expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]); expect(result.dependencies[result.dependencies.length - 1]) .toBe(module); @@ -171,13 +169,12 @@ describe('HasteDependencyResolver', function() { var module = createModule('index'); var deps = [module]; - var depResolver = new HasteDependencyResolver({ + var depResolver = new Resolver({ projectRoot: '/root', polyfillModuleNames: ['some module'], }); - var depGraph = depResolver._depGraph; - depGraph.getDependencies.mockImpl(function() { + DependencyGraph.prototype.getDependencies.mockImpl(function() { return Promise.resolve(new ResolutionResponseMock({ dependencies: deps, mainModuleId: 'index', @@ -209,11 +206,10 @@ describe('HasteDependencyResolver', function() { describe('wrapModule', function() { pit('should resolve modules', function() { - var depResolver = new HasteDependencyResolver({ + var depResolver = new Resolver({ projectRoot: '/root', }); - var depGraph = depResolver._depGraph; var dependencies = ['x', 'y', 'z', 'a', 'b']; /*eslint-disable */ diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js new file mode 100644 index 00000000..13c2ee46 --- /dev/null +++ b/react-packager/src/Resolver/index.js @@ -0,0 +1,178 @@ +/** + * 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 path = require('path'); +var DependencyGraph = require('../DependencyResolver/DependencyGraph'); +var replacePatterns = require('../DependencyResolver/replacePatterns'); +var Polyfill = require('../DependencyResolver/Polyfill'); +var declareOpts = require('../lib/declareOpts'); +var Promise = require('promise'); + +var validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + blacklistRE: { + type: 'object', // typeof regex is object + }, + polyfillModuleNames: { + type: 'array', + default: [], + }, + moduleFormat: { + type: 'string', + default: 'haste', + }, + assetRoots: { + type: 'array', + default: [], + }, + fileWatcher: { + type: 'object', + required: true, + }, + assetExts: { + type: 'array', + required: true, + }, + cache: { + type: 'object', + required: true, + }, +}); + +var getDependenciesValidateOpts = declareOpts({ + dev: { + type: 'boolean', + default: true, + }, + platform: { + type: 'string', + required: false, + }, +}); + +class Resolver { + + constructor(options) { + var opts = validateOpts(options); + + this._depGraph = new DependencyGraph({ + roots: opts.projectRoots, + assetRoots_DEPRECATED: opts.assetRoots, + assetExts: opts.assetExts, + ignoreFilePath: function(filepath) { + return filepath.indexOf('__tests__') !== -1 || + (opts.blacklistRE && opts.blacklistRE.test(filepath)); + }, + fileWatcher: opts.fileWatcher, + cache: opts.cache, + }); + + this._polyfillModuleNames = opts.polyfillModuleNames || []; + } + + getDependencies(main, options) { + var opts = getDependenciesValidateOpts(options); + + return this._depGraph.getDependencies(main, opts.platform).then( + resolutionResponse => { + this._getPolyfillDependencies(opts.dev).reverse().forEach( + polyfill => resolutionResponse.prependDependency(polyfill) + ); + + return resolutionResponse.finalize(); + } + ); + } + + _getPolyfillDependencies(isDev) { + var polyfillModuleNames = [ + isDev + ? path.join(__dirname, 'polyfills/prelude_dev.js') + : path.join(__dirname, 'polyfills/prelude.js'), + path.join(__dirname, 'polyfills/require.js'), + path.join(__dirname, 'polyfills/polyfills.js'), + path.join(__dirname, 'polyfills/console.js'), + path.join(__dirname, 'polyfills/error-guard.js'), + path.join(__dirname, 'polyfills/String.prototype.es6.js'), + path.join(__dirname, 'polyfills/Array.prototype.es6.js'), + ].concat(this._polyfillModuleNames); + + return polyfillModuleNames.map( + (polyfillModuleName, idx) => new Polyfill({ + path: polyfillModuleName, + id: polyfillModuleName, + dependencies: polyfillModuleNames.slice(0, idx), + isPolyfill: true, + }) + ); + } + + wrapModule(resolutionResponse, module, code) { + return Promise.resolve().then(() => { + if (module.isPolyfill()) { + return Promise.resolve(code); + } + + const resolvedDeps = Object.create(null); + const resolvedDepsArr = []; + + return Promise.all( + resolutionResponse.getResolvedDependencyPairs(module).map( + ([depName, 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]; + if (depId) { + return pre + quot + depId + post; + } else { + return codeMatch; + } + }; + + return module.getName().then( + name => defineModuleCode({ + code: code.replace(replacePatterns.IMPORT_RE, relativizeCode) + .replace(replacePatterns.EXPORT_RE, relativizeCode) + .replace(replacePatterns.REQUIRE_RE, relativizeCode), + moduleName: name, + }) + ); + }); + }); + } + + getDebugInfo() { + return this._depGraph.getDebugInfo(); + } + +} + +function defineModuleCode({moduleName, code}) { + return [ + `__d(`, + `'${moduleName}',`, + 'function(global, require, module, exports) {', + ` ${code}`, + '\n});', + ].join(''); +} + +module.exports = Resolver; diff --git a/react-packager/src/DependencyResolver/polyfills/Array.prototype.es6.js b/react-packager/src/Resolver/polyfills/Array.prototype.es6.js similarity index 100% rename from react-packager/src/DependencyResolver/polyfills/Array.prototype.es6.js rename to react-packager/src/Resolver/polyfills/Array.prototype.es6.js diff --git a/react-packager/src/DependencyResolver/polyfills/String.prototype.es6.js b/react-packager/src/Resolver/polyfills/String.prototype.es6.js similarity index 100% rename from react-packager/src/DependencyResolver/polyfills/String.prototype.es6.js rename to react-packager/src/Resolver/polyfills/String.prototype.es6.js diff --git a/react-packager/src/DependencyResolver/polyfills/console.js b/react-packager/src/Resolver/polyfills/console.js similarity index 100% rename from react-packager/src/DependencyResolver/polyfills/console.js rename to react-packager/src/Resolver/polyfills/console.js diff --git a/react-packager/src/DependencyResolver/polyfills/error-guard.js b/react-packager/src/Resolver/polyfills/error-guard.js similarity index 100% rename from react-packager/src/DependencyResolver/polyfills/error-guard.js rename to react-packager/src/Resolver/polyfills/error-guard.js diff --git a/react-packager/src/DependencyResolver/polyfills/polyfills.js b/react-packager/src/Resolver/polyfills/polyfills.js similarity index 100% rename from react-packager/src/DependencyResolver/polyfills/polyfills.js rename to react-packager/src/Resolver/polyfills/polyfills.js diff --git a/react-packager/src/DependencyResolver/polyfills/prelude.js b/react-packager/src/Resolver/polyfills/prelude.js similarity index 100% rename from react-packager/src/DependencyResolver/polyfills/prelude.js rename to react-packager/src/Resolver/polyfills/prelude.js diff --git a/react-packager/src/DependencyResolver/polyfills/prelude_dev.js b/react-packager/src/Resolver/polyfills/prelude_dev.js similarity index 100% rename from react-packager/src/DependencyResolver/polyfills/prelude_dev.js rename to react-packager/src/Resolver/polyfills/prelude_dev.js diff --git a/react-packager/src/DependencyResolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js similarity index 96% rename from react-packager/src/DependencyResolver/polyfills/require.js rename to react-packager/src/Resolver/polyfills/require.js index b674ba4e..6ebfee90 100644 --- a/react-packager/src/DependencyResolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -55,7 +55,7 @@ mod.isInitialized = true; // keep args in sync with with defineModuleCode in - // packager/react-packager/src/DependencyResolver/index.js + // packager/react-packager/src/Resolver/index.js mod.factory.call(global, global, require, mod.module, mod.module.exports); } catch (e) { mod.hasError = true; From bc9a3eb15965946bb56a9bc89f9ad115f9ab14aa Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Wed, 4 Nov 2015 18:58:25 -0800 Subject: [PATCH 421/936] Remove dependency on underscore Reviewed By: davidaurelio Differential Revision: D2614853 fb-gh-sync-id: 706fb0db6852f599a1b3dd022dbc22aabb76539a --- .../src/DependencyResolver/fastfs.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 623df232..7864049b 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -4,7 +4,6 @@ const Activity = require('../Activity'); const Promise = require('promise'); const {EventEmitter} = require('events'); -const _ = require('underscore'); const fs = require('fs'); const path = require('path'); @@ -62,10 +61,8 @@ class Fastfs extends EventEmitter { } getAllFiles() { - return _.chain(this._roots) - .map(root => root.getFiles()) - .flatten() - .value(); + // one-level-deep flatten of files + return [].concat(...this._roots.map(root => root.getFiles())); } findFilesByExt(ext, { ignore }) { @@ -278,13 +275,16 @@ class File { } getFiles() { - return _.flatten(_.values(this.children).map(file => { + const files = []; + Object.keys(this.children).forEach(key => { + const file = this.children[key]; if (file.isDir) { - return file.getFiles(); + files.push(...file.getFiles()); } else { - return file; + files.push(file); } - })); + }); + return files; } ext() { From 8ecac28100de732fe491e7d20361af3e9136befc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Sagnes?= Date: Thu, 5 Nov 2015 07:18:54 -0800 Subject: [PATCH 422/936] Set ASCII as the default bundle encoding Reviewed By: davidaurelio Differential Revision: D2596387 fb-gh-sync-id: deadb8e9a44bbe7b12ce9e6bb5e3c96d4f646074 --- react-packager/src/Bundler/Bundle.js | 1 + 1 file changed, 1 insertion(+) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index bf4edc32..66b5e704 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -134,6 +134,7 @@ class Bundle { fromString: true, outSourceMap: 'bundle.js', inSourceMap: this.getSourceMap(), + output: {ascii_only: true}, }); Activity.endEvent(minifyActivity); return this._minifiedSourceAndMap; From 48a2a5ccc73096f2c529e753f4f04e2b10ac2adc Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 5 Nov 2015 12:20:08 -0800 Subject: [PATCH 423/936] Remove log level 'log' from JS Summary: Log level 'log' from JS should be equivalent to 'info'. Also added knowledge of 'trace' log level in RCTLog. public Reviewed By: vjeux Differential Revision: D2615500 fb-gh-sync-id: 7a02f49bf7953c1a075741c21e984470c44b5551 --- react-packager/src/Resolver/polyfills/console.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/console.js b/react-packager/src/Resolver/polyfills/console.js index b6f20c76..e08e8ef7 100644 --- a/react-packager/src/Resolver/polyfills/console.js +++ b/react-packager/src/Resolver/polyfills/console.js @@ -360,10 +360,9 @@ var OBJECT_COLUMN_NAME = '(index)'; var LOG_LEVELS = { trace: 0, - log: 1, - info: 2, - warn: 3, - error: 4 + info: 1, + warn: 2, + error: 3 }; function setupConsole(global) { @@ -407,7 +406,7 @@ } } if (rows.length === 0) { - global.nativeLoggingHook('', LOG_LEVELS.log); + global.nativeLoggingHook('', LOG_LEVELS.info); return; } @@ -453,13 +452,13 @@ // Native logging hook adds "RCTLog >" at the front of every // logged string, which would shift the header and screw up // the table - global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.log); + global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.info); } global.console = { error: getNativeLogFunction(LOG_LEVELS.error), info: getNativeLogFunction(LOG_LEVELS.info), - log: getNativeLogFunction(LOG_LEVELS.log), + log: getNativeLogFunction(LOG_LEVELS.info), warn: getNativeLogFunction(LOG_LEVELS.warn), trace: getNativeLogFunction(LOG_LEVELS.trace), table: consoleTablePolyfill From 9373eb73ac11a70cec3bf01102070de4d1c2cce1 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Fri, 6 Nov 2015 14:13:36 -0800 Subject: [PATCH 424/936] Polyfill Array.from Reviewed By: cpojer Differential Revision: D2627437 fb-gh-sync-id: 6a7c94e13db772d96cbd8ebab37eccb0826963bc --- .../Resolver/polyfills/Array.prototype.es6.js | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/react-packager/src/Resolver/polyfills/Array.prototype.es6.js b/react-packager/src/Resolver/polyfills/Array.prototype.es6.js index 80e62f05..34a38645 100644 --- a/react-packager/src/Resolver/polyfills/Array.prototype.es6.js +++ b/react-packager/src/Resolver/polyfills/Array.prototype.es6.js @@ -55,4 +55,78 @@ } }); } + + /** + * Creates an array from array like objects. + * + * https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from + */ + if (!Array.from) { + Array.from = function(arrayLike /*, mapFn, thisArg */) { + if (arrayLike == null) { + throw new TypeError('Object is null or undefined'); + } + + // Optional args. + var mapFn = arguments[1]; + var thisArg = arguments[2]; + + var C = this; + var items = Object(arrayLike); + var symbolIterator = typeof Symbol === 'function' + ? Symbol.iterator + : '@@iterator'; + var mapping = typeof mapFn === 'function'; + var usingIterator = typeof items[symbolIterator] === 'function'; + var key = 0; + var ret; + var value; + + if (usingIterator) { + ret = typeof C === 'function' + ? new C() + : []; + var it = items[symbolIterator](); + var next; + + while (!(next = it.next()).done) { + value = next.value; + + if (mapping) { + value = mapFn.call(thisArg, value, key); + } + + ret[key] = value; + key += 1; + } + + ret.length = key; + return ret; + } + + var len = items.length; + if (isNaN(len) || len < 0) { + len = 0; + } + + ret = typeof C === 'function' + ? new C(len) + : new Array(len); + + while (key < len) { + value = items[key]; + + if (mapping) { + value = mapFn.call(thisArg, value, key); + } + + ret[key] = value; + + key += 1; + } + + ret.length = key; + return ret; + }; + } })(); From ef0b3e9338f39f2db260c3f019818a9c53abd80a Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Fri, 6 Nov 2015 17:19:55 -0800 Subject: [PATCH 425/936] Add polyfills to jest setup scripts Reviewed By: cpojer Differential Revision: D2627866 fb-gh-sync-id: 6d2c95ebc1fe05da3a90f8f6df3287bdbac870bd --- .../BundlesLayoutIntegration-test.js | 1 + .../src/Resolver/__tests__/Resolver-test.js | 16 +++- react-packager/src/Resolver/index.js | 1 + .../src/Resolver/polyfills/Array.es6.js | 81 +++++++++++++++++++ .../Resolver/polyfills/Array.prototype.es6.js | 74 ----------------- 5 files changed, 98 insertions(+), 75 deletions(-) create mode 100644 react-packager/src/Resolver/polyfills/Array.es6.js diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 6fab15da..93164ed1 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -35,6 +35,7 @@ describe('BundlesLayout', () => { 'polyfills/error-guard.js', 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', + 'polyfills/Array.es6.js', ]; const baseFs = getBaseFs(); diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 715c0459..9687fb04 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -135,6 +135,19 @@ describe('Resolver', function() { 'polyfills/String.prototype.es6.js', ], }, + { id: 'polyfills/Array.es6.js', + isPolyfill: true, + path: 'polyfills/Array.es6.js', + dependencies: [ + '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', + ], + } ]); }); }); @@ -196,7 +209,8 @@ describe('Resolver', function() { 'polyfills/console.js', 'polyfills/error-guard.js', 'polyfills/String.prototype.es6.js', - 'polyfills/Array.prototype.es6.js' + 'polyfills/Array.prototype.es6.js', + 'polyfills/Array.es6.js', ] }, ]); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 13c2ee46..d2c25e09 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -105,6 +105,7 @@ class Resolver { path.join(__dirname, 'polyfills/error-guard.js'), path.join(__dirname, 'polyfills/String.prototype.es6.js'), path.join(__dirname, 'polyfills/Array.prototype.es6.js'), + path.join(__dirname, 'polyfills/Array.es6.js'), ].concat(this._polyfillModuleNames); return polyfillModuleNames.map( diff --git a/react-packager/src/Resolver/polyfills/Array.es6.js b/react-packager/src/Resolver/polyfills/Array.es6.js new file mode 100644 index 00000000..ae3ac25e --- /dev/null +++ b/react-packager/src/Resolver/polyfills/Array.es6.js @@ -0,0 +1,81 @@ +/** + * Copyright 2013-2014 Facebook, Inc. + * @provides Array.es6 + * @polyfill + */ + +/*eslint-disable */ + +/** + * Creates an array from array like objects. + * + * https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from + */ +if (!Array.from) { + Array.from = function(arrayLike /*, mapFn, thisArg */) { + if (arrayLike == null) { + throw new TypeError('Object is null or undefined'); + } + + // Optional args. + var mapFn = arguments[1]; + var thisArg = arguments[2]; + + var C = this; + var items = Object(arrayLike); + var symbolIterator = typeof Symbol === 'function' + ? Symbol.iterator + : '@@iterator'; + var mapping = typeof mapFn === 'function'; + var usingIterator = typeof items[symbolIterator] === 'function'; + var key = 0; + var ret; + var value; + + if (usingIterator) { + ret = typeof C === 'function' + ? new C() + : []; + var it = items[symbolIterator](); + var next; + + while (!(next = it.next()).done) { + value = next.value; + + if (mapping) { + value = mapFn.call(thisArg, value, key); + } + + ret[key] = value; + key += 1; + } + + ret.length = key; + return ret; + } + + var len = items.length; + if (isNaN(len) || len < 0) { + len = 0; + } + + ret = typeof C === 'function' + ? new C(len) + : new Array(len); + + while (key < len) { + value = items[key]; + + if (mapping) { + value = mapFn.call(thisArg, value, key); + } + + ret[key] = value; + + key += 1; + } + + ret.length = key; + return ret; + }; +} diff --git a/react-packager/src/Resolver/polyfills/Array.prototype.es6.js b/react-packager/src/Resolver/polyfills/Array.prototype.es6.js index 34a38645..80e62f05 100644 --- a/react-packager/src/Resolver/polyfills/Array.prototype.es6.js +++ b/react-packager/src/Resolver/polyfills/Array.prototype.es6.js @@ -55,78 +55,4 @@ } }); } - - /** - * Creates an array from array like objects. - * - * https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from - */ - if (!Array.from) { - Array.from = function(arrayLike /*, mapFn, thisArg */) { - if (arrayLike == null) { - throw new TypeError('Object is null or undefined'); - } - - // Optional args. - var mapFn = arguments[1]; - var thisArg = arguments[2]; - - var C = this; - var items = Object(arrayLike); - var symbolIterator = typeof Symbol === 'function' - ? Symbol.iterator - : '@@iterator'; - var mapping = typeof mapFn === 'function'; - var usingIterator = typeof items[symbolIterator] === 'function'; - var key = 0; - var ret; - var value; - - if (usingIterator) { - ret = typeof C === 'function' - ? new C() - : []; - var it = items[symbolIterator](); - var next; - - while (!(next = it.next()).done) { - value = next.value; - - if (mapping) { - value = mapFn.call(thisArg, value, key); - } - - ret[key] = value; - key += 1; - } - - ret.length = key; - return ret; - } - - var len = items.length; - if (isNaN(len) || len < 0) { - len = 0; - } - - ret = typeof C === 'function' - ? new C(len) - : new Array(len); - - while (key < len) { - value = items[key]; - - if (mapping) { - value = mapFn.call(thisArg, value, key); - } - - ret[key] = value; - - key += 1; - } - - ret.length = key; - return ret; - }; - } })(); From 1d1006449e3e0c4be69819a695cd7e339e3328d8 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Fri, 6 Nov 2015 19:50:10 -0800 Subject: [PATCH 426/936] Update core libraries for React 0.14 final Summary: All minor changes since we were already on the beta: most notable is that destructors are required in pooling to help prevent memory leaks. public Reviewed By: sebmarkbage Differential Revision: D2608692 fb-gh-sync-id: acdad38768f7f48c0f0e7e44cbff6f0db316f4ca --- blacklist.js | 71 ++++++++++++------- .../__tests__/DependencyGraph-test.js | 22 +++--- .../DependencyGraph/index.js | 3 +- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/blacklist.js b/blacklist.js index cefb0d1e..36be002b 100644 --- a/blacklist.js +++ b/blacklist.js @@ -13,37 +13,58 @@ var path = require('path'); // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ - 'node_modules/react-tools/src/React.js', - 'node_modules/react-tools/src/renderers/shared/event/EventPropagators.js', - 'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js', - 'node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js', - 'node_modules/react-tools/docs/js/react.js', - 'node_modules/react-tools/src/package.json', + 'node_modules/react-haste/renderers/shared/event/eventPlugins/ResponderEventPlugin.js', + 'node_modules/react-haste/React.js', - // Those conflicts with the ones in react-tools/. We need to blacklist the + // For each of these fbjs files (especially the non-forks/stubs), we should + // consider deleting the conflicting copy and just using the fbjs version. + 'node_modules/fbjs-haste/__forks__/Map.js', + 'node_modules/fbjs-haste/__forks__/Promise.js', + 'node_modules/fbjs-haste/__forks__/fetch.js', + 'node_modules/fbjs-haste/core/Deferred.js', + 'node_modules/fbjs-haste/core/PromiseMap.js', + 'node_modules/fbjs-haste/core/areEqual.js', + 'node_modules/fbjs-haste/core/flattenArray.js', + 'node_modules/fbjs-haste/core/isEmpty.js', + 'node_modules/fbjs-haste/core/removeFromArray.js', + 'node_modules/fbjs-haste/core/resolveImmediate.js', + 'node_modules/fbjs-haste/core/sprintf.js', + 'node_modules/fbjs-haste/crypto/crc32.js', + 'node_modules/fbjs-haste/fetch/fetchWithRetries.js', + 'node_modules/fbjs-haste/functional/everyObject.js', + 'node_modules/fbjs-haste/functional/filterObject.js', + 'node_modules/fbjs-haste/functional/forEachObject.js', + 'node_modules/fbjs-haste/functional/someObject.js', + 'node_modules/fbjs-haste/request/xhrSimpleDataSerializer.js', + 'node_modules/fbjs-haste/stubs/ErrorUtils.js', + 'node_modules/fbjs-haste/stubs/URI.js', + 'node_modules/fbjs-haste/useragent/UserAgent.js', + 'node_modules/fbjs-haste/utils/nullthrows.js', + + // Those conflicts with the ones in fbjs-haste/. We need to blacklist the // internal version otherwise they won't work in open source. - 'downstream/core/invariant.js', - 'downstream/key-mirror/keyMirror.js', + 'downstream/core/CSSCore.js', + 'downstream/core/TouchEventUtils.js', + 'downstream/core/camelize.js', + 'downstream/core/createArrayFromMixed.js', + 'downstream/core/createNodesFromMarkup.js', + 'downstream/core/dom/containsNode.js', + 'downstream/core/dom/focusNode.js', + 'downstream/core/dom/getActiveElement.js', + 'downstream/core/dom/getUnboundedScrollPosition.js', + 'downstream/core/dom/isNode.js', + 'downstream/core/dom/isTextNode.js', 'downstream/core/emptyFunction.js', 'downstream/core/emptyObject.js', - 'downstream/key-mirror/keyOf.js', - 'downstream/core/dom/isNode.js', - 'downstream/core/TouchEventUtils.js', - 'downstream/core/nativeRequestAnimationFrame.js', - 'downstream/core/dom/containsNode.js', - 'downstream/core/dom/isTextNode.js', - 'downstream/functional/mapObject.js', - 'downstream/core/camelize.js', - 'downstream/core/hyphenate.js', - 'downstream/core/createArrayFromMixed.js', - 'downstream/core/toArray.js', - 'downstream/core/dom/getActiveElement.js', - 'downstream/core/dom/focusNode.js', - 'downstream/core/dom/getUnboundedScrollPosition.js', - 'downstream/core/createNodesFromMarkup.js', - 'downstream/core/CSSCore.js', 'downstream/core/getMarkupWrap.js', + 'downstream/core/hyphenate.js', 'downstream/core/hyphenateStyleName.js', + 'downstream/core/invariant.js', + 'downstream/core/nativeRequestAnimationFrame.js', + 'downstream/core/toArray.js', + 'downstream/functional/mapObject.js', + 'downstream/key-mirror/keyMirror.js', + 'downstream/key-mirror/keyOf.js', ]; // Raw unescaped patterns in case you need to use wildcards diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 1794a206..2c321820 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -2245,9 +2245,9 @@ describe('DependencyGraph', function() { 'require("wontWork");', ].join('\n'), 'node_modules': { - 'react-tools': { + 'react-haste': { 'package.json': JSON.stringify({ - name: 'react-tools', + name: 'react-haste', main: 'main.js', }), 'main.js': [ @@ -2315,7 +2315,7 @@ describe('DependencyGraph', function() { }, { id: 'shouldWork', - path: '/root/node_modules/react-tools/main.js', + path: '/root/node_modules/react-haste/main.js', dependencies: ['submodule'], isAsset: false, isAsset_DEPRECATED: false, @@ -2325,7 +2325,7 @@ describe('DependencyGraph', function() { }, { id: 'submodule/main.js', - path: '/root/node_modules/react-tools/node_modules/submodule/main.js', + path: '/root/node_modules/react-haste/node_modules/submodule/main.js', dependencies: [], isAsset: false, isAsset_DEPRECATED: false, @@ -2338,9 +2338,9 @@ describe('DependencyGraph', function() { }); pit('should not be confused by prev occuring whitelisted names', function() { - var root = '/react-tools'; + var root = '/react-haste'; fs.__setMockFilesystem({ - 'react-tools': { + 'react-haste': { 'index.js': [ '/**', ' * @providesModule index', @@ -2348,9 +2348,9 @@ describe('DependencyGraph', function() { 'require("shouldWork");', ].join('\n'), 'node_modules': { - 'react-tools': { + 'react-haste': { 'package.json': JSON.stringify({ - name: 'react-tools', + name: 'react-haste', main: 'main.js', }), 'main.js': [ @@ -2369,12 +2369,12 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return getOrderedDependenciesAsJSON(dgraph, '/react-tools/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/react-haste/index.js').then(function(deps) { expect(deps) .toEqual([ { id: 'index', - path: '/react-tools/index.js', + path: '/react-haste/index.js', dependencies: ['shouldWork'], isAsset: false, isAsset_DEPRECATED: false, @@ -2384,7 +2384,7 @@ describe('DependencyGraph', function() { }, { id: 'shouldWork', - path: '/react-tools/node_modules/react-tools/main.js', + path: '/react-haste/node_modules/react-haste/main.js', dependencies: [], isAsset: false, isAsset_DEPRECATED: false, diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 4c0a2168..cceac086 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -49,7 +49,8 @@ const validateOpts = declareOpts({ providesModuleNodeModules: { type: 'array', default: [ - 'react-tools', + 'fbjs-haste', + 'react-haste', 'react-native', // Parse requires AsyncStorage. They will // change that to require('react-native') which From 24bac5ee054ef024ce0a4fa3c3b1385a081b37fe Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Sat, 7 Nov 2015 11:39:11 -0800 Subject: [PATCH 427/936] Fix open source Summary: Unfortunately ReactTestUtils requires ReactDOM (for renderIntoDocument), and ReactTestUtils is required by ReactNative. This worked internally because we already shimmed ReactDOM for Relay; now we shim it externally too, at least until we figure out a better solution for this in React. public Reviewed By: vjeux Differential Revision: D2629811 fb-gh-sync-id: 94aac2c4eda39d039fdcedd88295e7afb0f5c5b2 --- blacklist.js | 1 + 1 file changed, 1 insertion(+) diff --git a/blacklist.js b/blacklist.js index 36be002b..bb64f752 100644 --- a/blacklist.js +++ b/blacklist.js @@ -15,6 +15,7 @@ var path = require('path'); var sharedBlacklist = [ 'node_modules/react-haste/renderers/shared/event/eventPlugins/ResponderEventPlugin.js', 'node_modules/react-haste/React.js', + 'node_modules/react-haste/renderers/dom/ReactDOM.js', // For each of these fbjs files (especially the non-forks/stubs), we should // consider deleting the conflicting copy and just using the fbjs version. From 0d1a442cff39a6198d06a7c07ec048819220679e Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 9 Nov 2015 13:32:42 -0800 Subject: [PATCH 428/936] Don't require Activity inside of DependencyResolver Reviewed By: martinbigio Differential Revision: D2619364 fb-gh-sync-id: 7947388c8041e5d6cbc1e20c8eb9fc7325dc46e0 --- .../DependencyGraph/DeprecatedAssetMap.js | 28 ++++++++++++++----- .../DependencyGraph/index.js | 26 +++++++++++------ .../src/DependencyResolver/fastfs.js | 14 +++++++--- react-packager/src/Resolver/index.js | 25 +++++++++-------- 4 files changed, 62 insertions(+), 31 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js index 7292a85c..fb0f6f16 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js @@ -8,7 +8,6 @@ */ 'use strict'; -const Activity = require('../../Activity'); const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); const Fastfs = require('../fastfs'); const debug = require('debug')('ReactNativePackager:DependencyGraph'); @@ -16,7 +15,15 @@ const path = require('path'); const Promise = require('promise'); class DeprecatedAssetMap { - constructor({ fsCrawl, roots, assetExts, fileWatcher, ignoreFilePath, helpers }) { + constructor({ + fsCrawl, + roots, + assetExts, + fileWatcher, + ignoreFilePath, + helpers, + activity + }) { if (roots == null || roots.length === 0) { this._disabled = true; return; @@ -29,8 +36,9 @@ class DeprecatedAssetMap { 'Assets', roots, fileWatcher, - { ignore: ignoreFilePath, crawling: fsCrawl } + { ignore: ignoreFilePath, crawling: fsCrawl, activity } ); + this._activity = activity; this._fastfs.on('change', this._processFileChange.bind(this)); } @@ -42,15 +50,21 @@ class DeprecatedAssetMap { return this._fastfs.build().then( () => { - const processAsset_DEPRECATEDActivity = Activity.startEvent( - 'Building (deprecated) Asset Map', - ); + const activity = this._activity; + let processAsset_DEPRECATEDActivity; + if (activity) { + processAsset_DEPRECATEDActivity = activity.startEvent( + 'Building (deprecated) Asset Map', + ); + } this._fastfs.findFilesByExts(this._assetExts).forEach( file => this._processAsset(file) ); - Activity.endEvent(processAsset_DEPRECATEDActivity); + if (activity) { + activity.endEvent(processAsset_DEPRECATEDActivity); + } } ); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index cceac086..3f1045b1 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -8,7 +8,6 @@ */ 'use strict'; -const Activity = require('../../Activity'); const Fastfs = require('../fastfs'); const ModuleCache = require('../ModuleCache'); const Promise = require('promise'); @@ -25,14 +24,20 @@ const HasteMap = require('./HasteMap'); const DeprecatedAssetMap = require('./DeprecatedAssetMap'); const validateOpts = declareOpts({ + activity: { + type: 'object', + default: { + startEvent: () => {}, + endEvent: () => {}, + }, + }, roots: { type: 'array', required: true, }, ignoreFilePath: { type: 'function', - - default: function(){} + default: () => {} }, fileWatcher: { type: 'object', @@ -86,15 +91,16 @@ class DependencyGraph { return this._loading; } - const depGraphActivity = Activity.startEvent('Building Dependency Graph'); - const crawlActivity = Activity.startEvent('Crawling File System'); + const {activity} = this._opts; + 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, exts: ['js', 'json'].concat(this._opts.assetExts), fileWatcher: this._opts.fileWatcher, }); - this._crawling.then((files) => Activity.endEvent(crawlActivity)); + this._crawling.then((files) => activity.endEvent(crawlActivity)); this._fastfs = new Fastfs( 'JavaScript', @@ -103,6 +109,7 @@ class DependencyGraph { { ignore: this._opts.ignoreFilePath, crawling: this._crawling, + activity: activity, } ); @@ -124,17 +131,18 @@ class DependencyGraph { fileWatcher: this._opts.fileWatcher, ignoreFilePath: this._opts.ignoreFilePath, assetExts: this._opts.assetExts, + activity: this._opts.activity, }); this._loading = Promise.all([ this._fastfs.build() .then(() => { - const hasteActivity = Activity.startEvent('Building Haste Map'); - return this._hasteMap.build().then(() => Activity.endEvent(hasteActivity)); + const hasteActivity = activity.startEvent('Building Haste Map'); + return this._hasteMap.build().then(() => activity.endEvent(hasteActivity)); }), this._deprecatedAssetMap.build(), ]).then(() => - Activity.endEvent(depGraphActivity) + activity.endEvent(depGraphActivity) ); return this._loading; diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 7864049b..9347cf40 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -1,6 +1,5 @@ 'use strict'; -const Activity = require('../Activity'); const Promise = require('promise'); const {EventEmitter} = require('events'); @@ -15,7 +14,7 @@ const hasOwn = Object.prototype.hasOwnProperty; const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError'; class Fastfs extends EventEmitter { - constructor(name, roots, fileWatcher, {ignore, crawling}) { + constructor(name, roots, fileWatcher, {ignore, crawling, activity}) { super(); this._name = name; this._fileWatcher = fileWatcher; @@ -23,6 +22,7 @@ class Fastfs extends EventEmitter { this._roots = roots.map(root => new File(root, { isDir: true })); this._fastPaths = Object.create(null); this._crawling = crawling; + this._activity = activity; } build() { @@ -31,7 +31,11 @@ class Fastfs extends EventEmitter { ); return this._crawling.then(files => { - const fastfsActivity = Activity.startEvent('Building in-memory fs for ' + this._name); + let fastfsActivity; + const activity = this._activity; + if (activity) { + fastfsActivity = activity.startEvent('Building in-memory fs for ' + this._name); + } files.forEach(filePath => { if (filePath.match(rootsPattern)) { const newFile = new File(filePath, { isDir: false }); @@ -48,7 +52,9 @@ class Fastfs extends EventEmitter { } } }); - Activity.endEvent(fastfsActivity); + if (activity) { + activity.endEvent(fastfsActivity); + } this._fileWatcher.on('all', this._processFileChange.bind(this)); }); } diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index d2c25e09..9992ef94 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -8,14 +8,16 @@ */ 'use strict'; -var path = require('path'); -var DependencyGraph = require('../DependencyResolver/DependencyGraph'); -var replacePatterns = require('../DependencyResolver/replacePatterns'); -var Polyfill = require('../DependencyResolver/Polyfill'); -var declareOpts = require('../lib/declareOpts'); -var Promise = require('promise'); -var validateOpts = declareOpts({ +const path = require('path'); +const Activity = require('../Activity'); +const DependencyGraph = require('../DependencyResolver/DependencyGraph'); +const replacePatterns = require('../DependencyResolver/replacePatterns'); +const Polyfill = require('../DependencyResolver/Polyfill'); +const declareOpts = require('../lib/declareOpts'); +const Promise = require('promise'); + +const validateOpts = declareOpts({ projectRoots: { type: 'array', required: true, @@ -49,7 +51,7 @@ var validateOpts = declareOpts({ }, }); -var getDependenciesValidateOpts = declareOpts({ +const getDependenciesValidateOpts = declareOpts({ dev: { type: 'boolean', default: true, @@ -63,9 +65,10 @@ var getDependenciesValidateOpts = declareOpts({ class Resolver { constructor(options) { - var opts = validateOpts(options); + const opts = validateOpts(options); this._depGraph = new DependencyGraph({ + activity: Activity, roots: opts.projectRoots, assetRoots_DEPRECATED: opts.assetRoots, assetExts: opts.assetExts, @@ -81,7 +84,7 @@ class Resolver { } getDependencies(main, options) { - var opts = getDependenciesValidateOpts(options); + const opts = getDependenciesValidateOpts(options); return this._depGraph.getDependencies(main, opts.platform).then( resolutionResponse => { @@ -95,7 +98,7 @@ class Resolver { } _getPolyfillDependencies(isDev) { - var polyfillModuleNames = [ + const polyfillModuleNames = [ isDev ? path.join(__dirname, 'polyfills/prelude_dev.js') : path.join(__dirname, 'polyfills/prelude.js'), From 57fed218a86a0859409f698c366f023f96ab4d48 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 9 Nov 2015 13:32:45 -0800 Subject: [PATCH 429/936] Move `getPlatformExtension` and `getAssetDataFromName` into `DependencyResolver` Reviewed By: martinbigio Differential Revision: D2619690 fb-gh-sync-id: 04ca68a1fa2dc7b77a49c38ea1515d7afbdee603 --- react-packager/src/AssetServer/__tests__/AssetServer-test.js | 4 ++-- react-packager/src/AssetServer/index.js | 2 +- react-packager/src/DependencyResolver/AssetModule.js | 2 +- .../src/DependencyResolver/AssetModule_DEPRECATED.js | 2 +- .../src/DependencyResolver/DependencyGraph/HasteMap.js | 2 +- .../DependencyResolver/DependencyGraph/ResolutionRequest.js | 2 +- .../src/DependencyResolver/DependencyGraph/index.js | 4 ++-- .../lib/__tests__/getAssetDataFromName-test.js | 0 .../lib/__tests__/getPlatformExtension-test.js} | 0 .../src/{ => DependencyResolver}/lib/getAssetDataFromName.js | 0 .../src/{ => DependencyResolver}/lib/getPlatformExtension.js | 0 react-packager/src/Server/index.js | 2 +- 12 files changed, 10 insertions(+), 10 deletions(-) rename react-packager/src/{ => DependencyResolver}/lib/__tests__/getAssetDataFromName-test.js (100%) rename react-packager/src/{lib/__tests__/getPotentialPlatformExt-test.js => DependencyResolver/lib/__tests__/getPlatformExtension-test.js} (100%) rename react-packager/src/{ => DependencyResolver}/lib/getAssetDataFromName.js (100%) rename react-packager/src/{ => DependencyResolver}/lib/getPlatformExtension.js (100%) diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 17c752cf..7a544a27 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -1,8 +1,8 @@ 'use strict'; jest - .dontMock('../../lib/getPlatformExtension') - .dontMock('../../lib/getAssetDataFromName') + .dontMock('../../DependencyResolver/lib/getPlatformExtension') + .dontMock('../../DependencyResolver/lib/getAssetDataFromName') .dontMock('../'); jest diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 41864486..bb411ce6 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -13,7 +13,7 @@ const Promise = require('promise'); const crypto = require('crypto'); const declareOpts = require('../lib/declareOpts'); const fs = require('fs'); -const getAssetDataFromName = require('../lib/getAssetDataFromName'); +const getAssetDataFromName = require('../DependencyResolver/lib/getAssetDataFromName'); const path = require('path'); const stat = Promise.denodeify(fs.stat); diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js index 2e103159..1e5ff7d4 100644 --- a/react-packager/src/DependencyResolver/AssetModule.js +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -2,7 +2,7 @@ const Module = require('./Module'); const Promise = require('promise'); -const getAssetDataFromName = require('../lib/getAssetDataFromName'); +const getAssetDataFromName = require('./lib/getAssetDataFromName'); class AssetModule extends Module { constructor(...args) { diff --git a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js index 19817d4b..a77d0aef 100644 --- a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js +++ b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -2,7 +2,7 @@ const Module = require('./Module'); const Promise = require('promise'); -const getAssetDataFromName = require('../lib/getAssetDataFromName'); +const getAssetDataFromName = require('./lib/getAssetDataFromName'); class AssetModule_DEPRECATED extends Module { constructor(...args) { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js index 6c8bdc0a..75cd43f9 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -9,7 +9,7 @@ 'use strict'; const path = require('path'); -const getPlatformExtension = require('../../lib/getPlatformExtension'); +const getPlatformExtension = require('../lib/getPlatformExtension'); const Promise = require('promise'); const GENERIC_PLATFORM = 'generic'; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index e8396de6..951beaef 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -12,7 +12,7 @@ const debug = require('debug')('ReactNativePackager:DependencyGraph'); const util = require('util'); const path = require('path'); const isAbsolutePath = require('absolute-path'); -const getAssetDataFromName = require('../../lib/getAssetDataFromName'); +const getAssetDataFromName = require('../lib/getAssetDataFromName'); const Promise = require('promise'); class ResolutionRequest { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 3f1045b1..16ce50dd 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -13,7 +13,7 @@ const ModuleCache = require('../ModuleCache'); const Promise = require('promise'); const crawl = require('../crawlers'); const declareOpts = require('../../lib/declareOpts'); -const getPontentialPlatformExt = require('../../lib/getPlatformExtension'); +const getPlatformExtension = require('../lib/getPlatformExtension'); const isAbsolutePath = require('absolute-path'); const path = require('path'); const util = require('util'); @@ -173,7 +173,7 @@ class DependencyGraph { _getRequestPlatform(entryPath, platform) { if (platform == null) { - platform = getPontentialPlatformExt(entryPath); + platform = getPlatformExtension(entryPath); if (platform == null || this._opts.platforms.indexOf(platform) === -1) { platform = null; } diff --git a/react-packager/src/lib/__tests__/getAssetDataFromName-test.js b/react-packager/src/DependencyResolver/lib/__tests__/getAssetDataFromName-test.js similarity index 100% rename from react-packager/src/lib/__tests__/getAssetDataFromName-test.js rename to react-packager/src/DependencyResolver/lib/__tests__/getAssetDataFromName-test.js diff --git a/react-packager/src/lib/__tests__/getPotentialPlatformExt-test.js b/react-packager/src/DependencyResolver/lib/__tests__/getPlatformExtension-test.js similarity index 100% rename from react-packager/src/lib/__tests__/getPotentialPlatformExt-test.js rename to react-packager/src/DependencyResolver/lib/__tests__/getPlatformExtension-test.js diff --git a/react-packager/src/lib/getAssetDataFromName.js b/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js similarity index 100% rename from react-packager/src/lib/getAssetDataFromName.js rename to react-packager/src/DependencyResolver/lib/getAssetDataFromName.js diff --git a/react-packager/src/lib/getPlatformExtension.js b/react-packager/src/DependencyResolver/lib/getPlatformExtension.js similarity index 100% rename from react-packager/src/lib/getPlatformExtension.js rename to react-packager/src/DependencyResolver/lib/getPlatformExtension.js diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 49704f12..e163e261 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -11,7 +11,7 @@ const Activity = require('../Activity'); const AssetServer = require('../AssetServer'); const FileWatcher = require('../FileWatcher'); -const getPlatformExtension = require('../lib/getPlatformExtension'); +const getPlatformExtension = require('../DependencyResolver/lib/getPlatformExtension'); const Bundler = require('../Bundler'); const Promise = require('promise'); From 120bdae3f663ed69d52cb153b59e7bd45f812a21 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 9 Nov 2015 13:32:46 -0800 Subject: [PATCH 430/936] Remove usage of declareOpts Reviewed By: martinbigio Differential Revision: D2619798 fb-gh-sync-id: ddc58233c06899017dbb98b3d8258eb0fe61cda3 --- .../__tests__/DependencyGraph-test.js | 250 ++++++------------ .../DependencyGraph/index.js | 79 ++---- react-packager/src/Resolver/index.js | 11 + 3 files changed, 113 insertions(+), 227 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 2c321820..b3a57c65 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -22,8 +22,7 @@ var DependencyGraph = require('../index'); var fs = require('fs'); describe('DependencyGraph', function() { - var cache; - var fileWatcher; + let defaults; function getOrderedDependenciesAsJSON(dgraph, entry, platform) { return dgraph.getDependencies(entry, platform) @@ -45,14 +44,29 @@ describe('DependencyGraph', function() { } beforeEach(function() { - fileWatcher = { + const fileWatcher = { on: function() { return this; }, isWatchman: () => Promise.resolve(false) }; - cache = new Cache(); + defaults = { + assetExts: ['png', 'jpg'], + cache: new Cache(), + fileWatcher, + providesModuleNodeModules: [ + 'haste-fbjs', + 'react-haste', + 'react-native', + // Parse requires AsyncStorage. They will + // change that to require('react-native') which + // should work after this release and we can + // remove it from here. + 'parse', + ], + platforms: ['ios', 'android'], + }; }); describe('get sync dependencies', function() { @@ -75,10 +89,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -133,10 +145,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -185,10 +195,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -244,10 +252,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(deps => { expect(deps) @@ -293,11 +299,9 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -346,10 +350,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -403,10 +405,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -484,10 +484,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js', 'ios').then(function(deps) { @@ -562,11 +560,9 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -625,10 +621,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -678,10 +672,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -731,10 +723,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -792,10 +782,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -849,10 +837,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -900,10 +886,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -950,10 +934,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -997,10 +979,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1048,10 +1028,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1097,10 +1075,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1143,10 +1119,8 @@ describe('DependencyGraph', function() { console.error = jest.genMockFn(); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return dgraph.load().catch(() => { @@ -1171,10 +1145,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1218,10 +1190,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1277,10 +1247,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1336,10 +1304,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1415,10 +1381,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1472,10 +1436,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1529,10 +1491,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1586,10 +1546,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1658,10 +1616,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1761,10 +1717,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1842,10 +1796,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -1924,10 +1876,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) @@ -2007,10 +1957,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -2104,10 +2052,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -2190,10 +2136,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -2295,10 +2239,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -2364,10 +2306,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/react-haste/index.js').then(function(deps) { expect(deps) @@ -2421,10 +2361,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -2466,10 +2404,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -2527,10 +2463,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) @@ -2588,10 +2522,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) @@ -2637,10 +2569,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) @@ -2699,10 +2629,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(deps => { expect(deps) @@ -2764,7 +2692,7 @@ describe('DependencyGraph', function() { triggerFileChange = (...args) => callbacks.map(callback => callback(...args)); - fileWatcher = { + defaults.fileWatcher = { on: function(eventType, callback) { if (eventType !== 'all') { throw new Error('Can only handle "all" event in watcher.'); @@ -2804,10 +2732,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['index.js'] = @@ -2869,10 +2795,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['index.js'] = @@ -2934,10 +2858,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { delete filesystem.root.foo; @@ -2998,10 +2920,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['bar.js'] = [ @@ -3079,11 +2999,10 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], assetRoots_DEPRECATED: [root], assetExts: ['png'], - fileWatcher: fileWatcher, - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { @@ -3152,10 +3071,9 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], assetExts: ['png'], - fileWatcher: fileWatcher, - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { @@ -3234,16 +3152,14 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], ignoreFilePath: function(filePath) { if (filePath === '/root/bar.js') { return true; } return false; }, - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['bar.js'] = [ @@ -3325,10 +3241,8 @@ describe('DependencyGraph', function() { } }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { @@ -3396,10 +3310,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); @@ -3463,10 +3375,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ @@ -3528,10 +3438,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ @@ -3591,10 +3499,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -3691,10 +3597,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ @@ -3755,10 +3659,8 @@ describe('DependencyGraph', function() { }); var dgraph = new DependencyGraph({ + ...defaults, roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, }); return dgraph.getDependencies('/root/index.js') diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 16ce50dd..b35a155a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -12,7 +12,6 @@ const Fastfs = require('../fastfs'); const ModuleCache = require('../ModuleCache'); const Promise = require('promise'); const crawl = require('../crawlers'); -const declareOpts = require('../../lib/declareOpts'); const getPlatformExtension = require('../lib/getPlatformExtension'); const isAbsolutePath = require('absolute-path'); const path = require('path'); @@ -23,60 +22,34 @@ const ResolutionResponse = require('./ResolutionResponse'); const HasteMap = require('./HasteMap'); const DeprecatedAssetMap = require('./DeprecatedAssetMap'); -const validateOpts = declareOpts({ - activity: { - type: 'object', - default: { - startEvent: () => {}, - endEvent: () => {}, - }, - }, - roots: { - type: 'array', - required: true, - }, - ignoreFilePath: { - type: 'function', - default: () => {} - }, - fileWatcher: { - type: 'object', - required: true, - }, - assetRoots_DEPRECATED: { - type: 'array', - default: [], - }, - assetExts: { - type: 'array', - required: true, - }, - providesModuleNodeModules: { - type: 'array', - default: [ - 'fbjs-haste', - 'react-haste', - 'react-native', - // Parse requires AsyncStorage. They will - // change that to require('react-native') which - // should work after this release and we can - // remove it from here. - 'parse', - ], - }, - platforms: { - type: 'array', - default: ['ios', 'android'], - }, - cache: { - type: 'object', - required: true, - }, -}); +const defaultActivity = { + startEvent: () => {}, + endEvent: () => {}, +}; class DependencyGraph { - constructor(options) { - this._opts = validateOpts(options); + constructor({ + activity, + roots, + ignoreFilePath, + fileWatcher, + assetRoots_DEPRECATED, + assetExts, + providesModuleNodeModules, + platforms, + cache, + }) { + this._opts = { + activity: activity || defaultActivity, + roots, + ignoreFilePath: ignoreFilePath || () => {}, + fileWatcher, + assetRoots_DEPRECATED: assetRoots_DEPRECATED || [], + assetExts, + providesModuleNodeModules, + platforms, + cache, + }; this._cache = this._opts.cache; this._helpers = new Helpers(this._opts); this.load().catch((err) => { diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 9992ef94..52ca39b1 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -76,6 +76,17 @@ class Resolver { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, + providesModuleNodeModules: [ + 'fbjs-haste', + 'react-haste', + 'react-native', + // Parse requires AsyncStorage. They will + // change that to require('react-native') which + // should work after this release and we can + // remove it from here. + 'parse', + ], + platforms: ['ios', 'android'], fileWatcher: opts.fileWatcher, cache: opts.cache, }); From 8ff93e65d23c811995634dd03984ac0c3c75dc25 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 9 Nov 2015 13:32:47 -0800 Subject: [PATCH 431/936] Fix lint errors Reviewed By: davidaurelio Differential Revision: D2628366 fb-gh-sync-id: 8d849e2deb038842475e66ef641fa1d4f7b78d8f --- .../DependencyGraph/DeprecatedAssetMap.js | 6 +- .../DependencyGraph/HasteMap.js | 10 +- .../DependencyGraph/ResolutionResponse.js | 2 +- .../__tests__/DependencyGraph-test.js | 344 +++++++++--------- .../src/DependencyResolver/Module.js | 2 +- .../src/DependencyResolver/ModuleCache.js | 2 +- .../__tests__/Module-test.js | 8 +- .../src/DependencyResolver/fastfs.js | 6 +- .../lib/getPlatformExtension.js | 2 - 9 files changed, 190 insertions(+), 192 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js index fb0f6f16..aedfa4ce 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js @@ -22,7 +22,7 @@ class DeprecatedAssetMap { fileWatcher, ignoreFilePath, helpers, - activity + activity, }) { if (roots == null || roots.length === 0) { this._disabled = true; @@ -85,9 +85,9 @@ class DeprecatedAssetMap { } _processAsset(file) { - let ext = this._helpers.extname(file); + const ext = this._helpers.extname(file); if (this._assetExts.indexOf(ext) !== -1) { - let name = assetName(file, ext); + const name = assetName(file, ext); if (this._map[name] != null) { debug('Conflcting assets', name); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js index 75cd43f9..c2101591 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -25,12 +25,12 @@ class HasteMap { this._map = Object.create(null); let promises = this._fastfs.findFilesByExt('js', { - ignore: (file) => this._helpers.isNodeModulesDir(file) + ignore: (file) => this._helpers.isNodeModulesDir(file), }).map(file => this._processHasteModule(file)); promises = promises.concat( this._fastfs.findFilesByName('package.json', { - ignore: (file) => this._helpers.isNodeModulesDir(file) + ignore: (file) => this._helpers.isNodeModulesDir(file), }).map(file => this._processHastePackage(file)) ); @@ -41,9 +41,9 @@ class HasteMap { return Promise.resolve().then(() => { /*eslint no-labels: 0 */ if (type === 'delete' || type === 'change') { - loop: for (let name in this._map) { + loop: for (const name in this._map) { const modulesMap = this._map[name]; - for (let platform in modulesMap) { + for (const platform in modulesMap) { const module = modulesMap[platform]; if (module.path === absPath) { delete modulesMap[platform]; @@ -53,7 +53,7 @@ class HasteMap { } if (type === 'delete') { - return; + return null; } } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js index d29ff225..323e2e8d 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js @@ -51,7 +51,7 @@ class ResolutionResponse { this.dependencies.unshift(module); } - pushAsyncDependency(dependency){ + pushAsyncDependency(dependency) { this._assertNotFinalized(); this.asyncDependencies.push(dependency); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index b3a57c65..f43765e4 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -48,7 +48,7 @@ describe('DependencyGraph', function() { on: function() { return this; }, - isWatchman: () => Promise.resolve(false) + isWatchman: () => Promise.resolve(false), }; defaults = { @@ -78,14 +78,14 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("a")' + 'require("a")', ].join('\n'), 'a.js': [ '/**', ' * @providesModule a', ' */', ].join('\n'), - } + }, }); var dgraph = new DependencyGraph({ @@ -104,7 +104,7 @@ describe('DependencyGraph', function() { isJSON: false, isPolyfill: false, resolution: undefined, - resolveDependency: undefined + resolveDependency: undefined, }, { id: 'a', @@ -115,7 +115,7 @@ describe('DependencyGraph', function() { isJSON: false, isPolyfill: false, resolution: undefined, - resolveDependency: undefined + resolveDependency: undefined, }, ]); }); @@ -129,7 +129,7 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("a")' + 'require("a")', ].join('\n'), 'a.js': [ '/**', @@ -141,7 +141,7 @@ describe('DependencyGraph', function() { ' * @providesModule a', ' */', ].join('\n'), - } + }, }); var dgraph = new DependencyGraph({ @@ -180,18 +180,18 @@ describe('DependencyGraph', function() { fs.__setMockFilesystem({ 'root': { 'package.json': JSON.stringify({ - name: 'package' + name: 'package', }), 'index.js': [ '/**', ' * @providesModule index', ' */', 'require("./a.json")', - 'require("./b")' + 'require("./b")', ].join('\n'), 'a.json': JSON.stringify({}), 'b.json': JSON.stringify({}), - } + }, }); var dgraph = new DependencyGraph({ @@ -240,7 +240,7 @@ describe('DependencyGraph', function() { fs.__setMockFilesystem({ 'root': { 'package.json': JSON.stringify({ - name: 'package' + name: 'package', }), 'index.js': [ '/**', @@ -248,7 +248,7 @@ describe('DependencyGraph', function() { ' */', 'require("./package.json")', ].join('\n'), - } + }, }); var dgraph = new DependencyGraph({ @@ -290,12 +290,12 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("image!a")' + 'require("image!a")', ].join('\n'), 'imgs': { - 'a.png': '' + 'a.png': '', }, - } + }, }); var dgraph = new DependencyGraph({ @@ -338,15 +338,15 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("./imgs/a.png")' + 'require("./imgs/a.png")', ].join('\n'), 'imgs': { - 'a.png': '' + 'a.png': '', }, 'package.json': JSON.stringify({ - name: 'rootPackage' + name: 'rootPackage', }), - } + }, }); var dgraph = new DependencyGraph({ @@ -399,9 +399,9 @@ describe('DependencyGraph', function() { 'c@2x.png': '', }, 'package.json': JSON.stringify({ - name: 'rootPackage' + name: 'rootPackage', }), - } + }, }); var dgraph = new DependencyGraph({ @@ -478,9 +478,9 @@ describe('DependencyGraph', function() { 'c@2x.ios.png': '', }, 'package.json': JSON.stringify({ - name: 'rootPackage' + name: 'rootPackage', }), - } + }, }); var dgraph = new DependencyGraph({ @@ -551,12 +551,12 @@ describe('DependencyGraph', function() { 'require("image!a")', ].join('\n'), 'imgs': { - 'a.png': '' + 'a.png': '', }, 'package.json': JSON.stringify({ - name: 'rootPackage' + name: 'rootPackage', }), - } + }, }); var dgraph = new DependencyGraph({ @@ -617,7 +617,7 @@ describe('DependencyGraph', function() { ' */', 'require("index")', ].join('\n'), - } + }, }); var dgraph = new DependencyGraph({ @@ -664,11 +664,11 @@ describe('DependencyGraph', function() { 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), - 'main.js': 'lol' - } - } + 'main.js': 'lol', + }, + }, }); var dgraph = new DependencyGraph({ @@ -715,11 +715,11 @@ describe('DependencyGraph', function() { 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), - 'main.js': 'lol' - } - } + 'main.js': 'lol', + }, + }, }); var dgraph = new DependencyGraph({ @@ -767,18 +767,18 @@ describe('DependencyGraph', function() { 'sha.js': { 'package.json': JSON.stringify({ name: 'sha.js', - main: 'main.js' + main: 'main.js', }), - 'main.js': 'lol' + 'main.js': 'lol', }, 'x.y.z': { 'package.json': JSON.stringify({ name: 'x.y.z', - main: 'main.js' + main: 'main.js', }), - 'main.js': 'lol' - } - } + 'main.js': 'lol', + }, + }, }); var dgraph = new DependencyGraph({ @@ -832,8 +832,8 @@ describe('DependencyGraph', function() { name: 'aPackage', }), 'index.js': 'lol', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -881,8 +881,8 @@ describe('DependencyGraph', function() { ' * @providesModule EpicModule', ' */', ].join('\n'), - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -929,8 +929,8 @@ describe('DependencyGraph', function() { lib: { 'index.js': 'lol', }, - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -975,7 +975,7 @@ describe('DependencyGraph', function() { lib: { 'index.js': 'lol', }, - } + }, }); var dgraph = new DependencyGraph({ @@ -1024,7 +1024,7 @@ describe('DependencyGraph', function() { 'index.js': 'lol', 'main.js': 'lol', }, - } + }, }); var dgraph = new DependencyGraph({ @@ -1069,9 +1069,9 @@ describe('DependencyGraph', function() { ].join('\n'), 'aPackage': { 'package.json': 'lol', - 'main.js': 'lol' - } - } + 'main.js': 'lol', + }, + }, }); var dgraph = new DependencyGraph({ @@ -1109,7 +1109,7 @@ describe('DependencyGraph', function() { ' * @providesModule index', ' */', ].join('\n'), - } + }, }); const _exit = process.exit; @@ -1140,8 +1140,8 @@ describe('DependencyGraph', function() { ' * @providesModule index', ' */', 'require("lolomg")', - ].join('\n') - } + ].join('\n'), + }, }); var dgraph = new DependencyGraph({ @@ -1161,7 +1161,7 @@ describe('DependencyGraph', function() { isPolyfill: false, resolution: undefined, resolveDependency: undefined, - } + }, ]); }); }); @@ -1179,14 +1179,14 @@ describe('DependencyGraph', function() { 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'lol', 'subdir': { - 'lolynot.js': 'lolynot' - } - } - } + 'lolynot.js': 'lolynot', + }, + }, + }, }); var dgraph = new DependencyGraph({ @@ -1228,12 +1228,12 @@ describe('DependencyGraph', function() { 'symlinkedPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'lol', 'subdir': { - 'lolynot.js': 'lolynot' - } + 'lolynot.js': 'lolynot', + }, }, 'root': { 'index.js': [ @@ -1243,7 +1243,7 @@ describe('DependencyGraph', function() { 'require("aPackage/subdir/lolynot")', ].join('\n'), 'aPackage': { SYMLINK: '/symlinkedPackage' }, - } + }, }); var dgraph = new DependencyGraph({ @@ -1292,15 +1292,15 @@ describe('DependencyGraph', function() { 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'require("./subdir/lolynot")', 'subdir': { - 'lolynot.js': 'require("../other")' + 'lolynot.js': 'require("../other")', }, - 'other.js': 'some code' - } - } + 'other.js': 'some code', + }, + }, }); var dgraph = new DependencyGraph({ @@ -1376,8 +1376,8 @@ describe('DependencyGraph', function() { }), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1431,8 +1431,8 @@ describe('DependencyGraph', function() { }), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1486,8 +1486,8 @@ describe('DependencyGraph', function() { }), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1541,8 +1541,8 @@ describe('DependencyGraph', function() { }), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1611,8 +1611,8 @@ describe('DependencyGraph', function() { }, 'hello.js': 'hello', 'bye.js': 'bye', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1697,7 +1697,7 @@ describe('DependencyGraph', function() { name: 'aPackage', browser: { 'node-package': 'browser-package', - } + }, }), 'index.js': 'require("node-package")', 'node-package': { @@ -1712,8 +1712,8 @@ describe('DependencyGraph', function() { }), 'index.js': 'some browser code', }, - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1782,7 +1782,7 @@ describe('DependencyGraph', function() { }), 'main.js': 'bar 1 module', }, - } + }, }, 'bar': { 'package.json': JSON.stringify({ @@ -1792,7 +1792,7 @@ describe('DependencyGraph', function() { 'main.js': 'bar 2 module', }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -1867,12 +1867,12 @@ describe('DependencyGraph', function() { 'bar': { 'package.json': JSON.stringify({ name: 'bar', - main: 'main' + main: 'main', }), 'main.ios.js': '', }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -1912,7 +1912,7 @@ describe('DependencyGraph', function() { isPolyfill: false, resolution: undefined, }, - ]); + ]); }); }); @@ -1943,7 +1943,7 @@ describe('DependencyGraph', function() { 'main.js': 'bar 1 module', 'lol.js': '', }, - } + }, }, 'bar': { 'package.json': JSON.stringify({ @@ -1953,7 +1953,7 @@ describe('DependencyGraph', function() { 'main.js': 'bar 2 module', }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -2031,14 +2031,14 @@ describe('DependencyGraph', function() { name: 'bar', main: 'main.js', browser: { - './lol': './wow' - } + './lol': './wow', + }, }), 'main.js': 'bar 1 module', 'lol.js': '', 'wow.js': '', }, - } + }, }, 'bar': { 'package.json': JSON.stringify({ @@ -2048,7 +2048,7 @@ describe('DependencyGraph', function() { 'main2.js': 'bar 2 module', }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -2132,7 +2132,7 @@ describe('DependencyGraph', function() { }, 'node_modules': {}, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -2220,7 +2220,7 @@ describe('DependencyGraph', function() { }), 'main.js': 'log()', }, - } + }, }, 'ember': { 'package.json': JSON.stringify({ @@ -2235,7 +2235,7 @@ describe('DependencyGraph', function() { ].join('\n'), }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -2302,7 +2302,7 @@ describe('DependencyGraph', function() { ].join('\n'), }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -2357,7 +2357,7 @@ describe('DependencyGraph', function() { 'main.js': 'foo module', }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -2395,12 +2395,12 @@ describe('DependencyGraph', function() { 'sha.js': { 'package.json': JSON.stringify({ name: 'sha.js', - main: 'main.js' + main: 'main.js', }), - 'main.js': 'lol' - } - } - } + 'main.js': 'lol', + }, + }, + }, }); var dgraph = new DependencyGraph({ @@ -2459,7 +2459,7 @@ describe('DependencyGraph', function() { * @providesModule a */ `, - } + }, }); var dgraph = new DependencyGraph({ @@ -2518,7 +2518,7 @@ describe('DependencyGraph', function() { * @providesModule a */ `, - } + }, }); var dgraph = new DependencyGraph({ @@ -2565,7 +2565,7 @@ describe('DependencyGraph', function() { 'a.ios.js': '', 'a.android.js': '', 'a.js': '', - } + }, }); var dgraph = new DependencyGraph({ @@ -2625,7 +2625,7 @@ describe('DependencyGraph', function() { 'main.js': 'require("./package.json")', }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -2684,7 +2684,7 @@ describe('DependencyGraph', function() { describe('file watch updating', function() { var triggerFileChange; var mockStat = { - isDirectory: () => false + isDirectory: () => false, }; beforeEach(function() { @@ -2713,22 +2713,22 @@ describe('DependencyGraph', function() { ' * @providesModule index', ' */', 'require("aPackage")', - 'require("foo")' + 'require("foo")', ].join('\n'), 'foo': [ '/**', ' * @providesModule foo', ' */', - 'require("aPackage")' + 'require("aPackage")', ].join('\n'), 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'main', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -2776,22 +2776,22 @@ describe('DependencyGraph', function() { ' * @providesModule index', ' */', 'require("aPackage")', - 'require("foo")' + 'require("foo")', ].join('\n'), 'foo.js': [ '/**', ' * @providesModule foo', ' */', - 'require("aPackage")' + 'require("aPackage")', ].join('\n'), 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'main', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -2839,22 +2839,22 @@ describe('DependencyGraph', function() { ' * @providesModule index', ' */', 'require("aPackage")', - 'require("foo")' + 'require("foo")', ].join('\n'), 'foo.js': [ '/**', ' * @providesModule foo', ' */', - 'require("aPackage")' + 'require("aPackage")', ].join('\n'), 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'main', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -2887,7 +2887,7 @@ describe('DependencyGraph', function() { isPolyfill: false, resolution: undefined, }, - ]); + ]); }); }); }); @@ -2901,22 +2901,22 @@ describe('DependencyGraph', function() { ' * @providesModule index', ' */', 'require("aPackage")', - 'require("foo")' + 'require("foo")', ].join('\n'), 'foo.js': [ '/**', ' * @providesModule foo', ' */', - 'require("aPackage")' + 'require("aPackage")', ].join('\n'), 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'main', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -2928,7 +2928,7 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule bar', ' */', - 'require("foo")' + 'require("foo")', ].join('\n'); triggerFileChange('add', 'bar.js', root, mockStat); @@ -2980,7 +2980,7 @@ describe('DependencyGraph', function() { resolution: undefined, resolveDependency: undefined, }, - ]); + ]); }); }); }); @@ -2993,7 +2993,7 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("image!foo")' + 'require("image!foo")', ].join('\n'), }, }); @@ -3018,7 +3018,7 @@ describe('DependencyGraph', function() { isPolyfill: false, resolution: undefined, resolveDependency: undefined, - } + }, ]); filesystem.root['foo.png'] = ''; @@ -3062,10 +3062,10 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("./foo.png")' + 'require("./foo.png")', ].join('\n'), 'package.json': JSON.stringify({ - name: 'aPackage' + name: 'aPackage', }), }, }); @@ -3088,7 +3088,7 @@ describe('DependencyGraph', function() { isPolyfill: false, resolution: undefined, resolveDependency: undefined, - } + }, ]); filesystem.root['foo.png'] = ''; @@ -3118,8 +3118,8 @@ describe('DependencyGraph', function() { isJSON: false, isPolyfill: false, resolveDependency: undefined, - }, - ]); + }, + ]); }); }); }); @@ -3133,22 +3133,22 @@ describe('DependencyGraph', function() { ' * @providesModule index', ' */', 'require("aPackage")', - 'require("foo")' + 'require("foo")', ].join('\n'), 'foo.js': [ '/**', ' * @providesModule foo', ' */', - 'require("aPackage")' + 'require("aPackage")', ].join('\n'), 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'main', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -3166,7 +3166,7 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule bar', ' */', - 'require("foo")' + 'require("foo")', ].join('\n'); triggerFileChange('add', 'bar.js', root, mockStat); @@ -3223,22 +3223,22 @@ describe('DependencyGraph', function() { ' * @providesModule index', ' */', 'require("aPackage")', - 'require("foo")' + 'require("foo")', ].join('\n'), 'foo.js': [ '/**', ' * @providesModule foo', ' */', - 'require("aPackage")' + 'require("aPackage")', ].join('\n'), 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'main', - } - } + }, + }, }); var dgraph = new DependencyGraph({ ...defaults, @@ -3246,7 +3246,7 @@ describe('DependencyGraph', function() { }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { - isDirectory: function(){ return true; } + isDirectory: () => true, }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -3302,11 +3302,11 @@ describe('DependencyGraph', function() { 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'main', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -3366,12 +3366,12 @@ describe('DependencyGraph', function() { 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'main', 'browser.js': 'browser', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -3429,12 +3429,12 @@ describe('DependencyGraph', function() { 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: 'main.js' + main: 'main.js', }), 'main.js': 'main', 'browser.js': 'browser', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -3492,10 +3492,10 @@ describe('DependencyGraph', function() { }), 'main.js': 'bar 1 module', }, - } + }, }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -3593,7 +3593,7 @@ describe('DependencyGraph', function() { 'browser.js': 'foo module', }, }, - } + }, }); var dgraph = new DependencyGraph({ @@ -3648,14 +3648,14 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'System.import("a")' + 'System.import("a")', ].join('\n'), 'a.js': [ '/**', ' * @providesModule a', ' */', ].join('\n'), - } + }, }); var dgraph = new DependencyGraph({ @@ -3663,13 +3663,13 @@ describe('DependencyGraph', function() { roots: [root], }); - return dgraph.getDependencies('/root/index.js') - .then(response => response.finalize()) - .then(({ asyncDependencies }) => { - expect(asyncDependencies).toEqual([ - ['/root/a.js'] - ]); - }); + return dgraph.getDependencies('/root/index.js') + .then(response => response.finalize()) + .then(({ asyncDependencies }) => { + expect(asyncDependencies).toEqual([ + ['/root/a.js'], + ]); + }); }); }); }); diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 7d4b6246..79a88f16 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -145,7 +145,7 @@ class Module { */ const blockCommentRe = /\/\*(.|\n)*?\*\//g; const lineCommentRe = /\/\/.+(\n|$)/g; -function extractRequires(code /*: string*/) /*: Array*/ { +function extractRequires(code) { var deps = { sync: [], async: [], diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index a7e29322..8ea52c00 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -43,7 +43,7 @@ class ModuleCache { getPackage(filePath) { filePath = path.resolve(filePath); - if (!this._packageCache[filePath]){ + if (!this._packageCache[filePath]) { this._packageCache[filePath] = new Package( filePath, this._fastfs, diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 21ca6288..5b16b8e4 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -51,7 +51,7 @@ describe('Module', () => { fs.__setMockFilesystem({ 'root': { 'index.js': 'System.import("dep1")', - } + }, }); return expectAsyncDependenciesToEqual([['dep1']]); @@ -61,7 +61,7 @@ describe('Module', () => { fs.__setMockFilesystem({ 'root': { 'index.js': 'System.import(\'dep1\')', - } + }, }); return expectAsyncDependenciesToEqual([['dep1']]); @@ -74,7 +74,7 @@ describe('Module', () => { 'System.import("dep1")', 'System.import("dep2")', ].join('\n'), - } + }, }); return expectAsyncDependenciesToEqual([ @@ -87,7 +87,7 @@ describe('Module', () => { fs.__setMockFilesystem({ 'root': { 'index.js': 'System.import(\n"dep1"\n)', - } + }, }); return expectAsyncDependenciesToEqual([['dep1']]); diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 9347cf40..363090bf 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -142,7 +142,7 @@ class Fastfs extends EventEmitter { } matches(dir, pattern) { - let dirFile = this._getFile(dir); + const dirFile = this._getFile(dir); if (!dirFile.isDir) { throw new Error(`Expected file ${dirFile.path} to be a directory`); } @@ -154,7 +154,7 @@ class Fastfs extends EventEmitter { _getRoot(filePath) { for (let i = 0; i < this._roots.length; i++) { - let possibleRoot = this._roots[i]; + const possibleRoot = this._roots[i]; if (isDescendant(possibleRoot.path, filePath)) { return possibleRoot; } @@ -264,7 +264,7 @@ class File { /*eslint consistent-this:0*/ let file = this; for (let i = 0; i < parts.length; i++) { - let fileName = parts[i]; + const fileName = parts[i]; if (!fileName) { continue; } diff --git a/react-packager/src/DependencyResolver/lib/getPlatformExtension.js b/react-packager/src/DependencyResolver/lib/getPlatformExtension.js index 417cd866..cfcb6501 100644 --- a/react-packager/src/DependencyResolver/lib/getPlatformExtension.js +++ b/react-packager/src/DependencyResolver/lib/getPlatformExtension.js @@ -8,8 +8,6 @@ */ 'use strict'; -const path = require('path'); - const SUPPORTED_PLATFORM_EXTS = ['android', 'ios', 'web']; const re = new RegExp( From de981ecccd586a475da0a4a1887df1fd20b016fd Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 9 Nov 2015 13:32:48 -0800 Subject: [PATCH 432/936] Use a mock for FileWatcher Reviewed By: davidaurelio Differential Revision: D2628885 fb-gh-sync-id: f8b96c483eb7deb52a7ca4897801e2d0ef9e4f4c --- .../src/DependencyResolver/__tests__/Module-test.js | 7 ++++--- react-packager/src/DependencyResolver/fastfs.js | 8 ++++++++ react-packager/src/__mocks__/fs.js | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 5b16b8e4..237bab5d 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -13,7 +13,6 @@ jest .dontMock('../fastfs') .dontMock('../replacePatterns') .dontMock('../DependencyGraph/docblock') - .dontMock('../../FileWatcher') .dontMock('../Module'); jest @@ -24,10 +23,12 @@ var Module = require('../Module'); var ModuleCache = require('../ModuleCache'); var Promise = require('promise'); var fs = require('fs'); -var FileWatcher = require('../../FileWatcher'); describe('Module', () => { - const fileWatcher = new FileWatcher(['/root']); + const fileWatcher = { + on: () => this, + isWatchman: () => Promise.resolve(false), + }; describe('Async Dependencies', () => { function expectAsyncDependenciesToEqual(expected) { diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 363090bf..d34892dd 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.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'); diff --git a/react-packager/src/__mocks__/fs.js b/react-packager/src/__mocks__/fs.js index ced46a98..1aca9250 100644 --- a/react-packager/src/__mocks__/fs.js +++ b/react-packager/src/__mocks__/fs.js @@ -81,7 +81,7 @@ fs.stat.mockImpl(function(filepath, callback) { var mtime = { getTime: function() { return Math.ceil(Math.random() * 10000000); - } + }, }; if (node.SYMLINK) { From 391e4c8236ba114acf87fecb1ccbe4bb080edd40 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 9 Nov 2015 13:32:49 -0800 Subject: [PATCH 433/936] Inline cache mock for DependencyGraph-test Reviewed By: davidaurelio Differential Revision: D2629211 fb-gh-sync-id: ade0bf4e486b58ecbc9a29eb3bdb61286eebb3d5 --- react-packager/__mocks__/debug.js | 4 +--- .../__tests__/DependencyGraph-test.js | 12 ++++++++---- .../src/DependencyResolver/DependencyGraph/index.js | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/react-packager/__mocks__/debug.js b/react-packager/__mocks__/debug.js index 41955326..87390917 100644 --- a/react-packager/__mocks__/debug.js +++ b/react-packager/__mocks__/debug.js @@ -8,6 +8,4 @@ */ 'use strict'; -module.exports = function() { - return function() {}; -}; +module.exports = () => () => {}; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index f43765e4..07f47ad7 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -13,11 +13,8 @@ jest.autoMockOff(); const Promise = require('promise'); jest - .mock('fs') - .mock('../../../Cache') - .mock('../../../Activity'); + .mock('fs'); -var Cache = require('../../../Cache'); var DependencyGraph = require('../index'); var fs = require('fs'); @@ -51,6 +48,13 @@ describe('DependencyGraph', function() { isWatchman: () => Promise.resolve(false), }; + const Cache = jest.genMockFn(); + Cache.prototype.get = jest.genMockFn().mockImplementation( + (filepath, field, cb) => cb(filepath) + ); + Cache.prototype.invalidate = jest.genMockFn(); + Cache.prototype.end = jest.genMockFn(); + defaults = { assetExts: ['png', 'jpg'], cache: new Cache(), diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index b35a155a..cbd7e192 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -42,7 +42,7 @@ class DependencyGraph { this._opts = { activity: activity || defaultActivity, roots, - ignoreFilePath: ignoreFilePath || () => {}, + ignoreFilePath: ignoreFilePath || (() => {}), fileWatcher, assetRoots_DEPRECATED: assetRoots_DEPRECATED || [], assetExts, From 9d883035867d3739066d3f91d6f97916a64b5813 Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 9 Nov 2015 16:41:23 -0800 Subject: [PATCH 434/936] Source nvm.sh (best effort) when running react-native-xcode.sh Summary: When Node is installed with nvm, scripts invoked from Xcode need to set up nvm. This can be done either by sourcing .profile/.bash_profile/.bashrc/etc... or by sourcing nvm.sh directly -- this diff does the latter and handles the case where nvm may have been installed the official way (under ~/.nvm/nvm.sh AFAIK) or via homebrew. Closes https://github.com/facebook/react-native/pull/4015 Reviewed By: svcscm Differential Revision: D2633301 Pulled By: frantic fb-gh-sync-id: 3c2b9b0d21887ba21d6f85f5d279314d50c1db28 --- react-native-xcode.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index 844788f1..dfa413b8 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -33,6 +33,15 @@ cd .. set -x DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH +# Define NVM_DIR and source the nvm.sh setup script +[ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" + +if [[ -s "$HOME/.nvm/nvm.sh" ]]; then + . "$HOME/.nvm/nvm.sh" +elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then + . "$(brew --prefix nvm)/nvm.sh" +fi + react-native bundle \ --entry-file index.ios.js \ --platform ios \ From 839de43d3a2512cdb3f17c4bcb701266fd810ab7 Mon Sep 17 00:00:00 2001 From: Bhuwan Khattar Date: Mon, 9 Nov 2015 18:30:31 -0800 Subject: [PATCH 435/936] Don't special case performanceNow Summary: The previous version of `performanceNow` used to reassign to a `require(...)` alias, which our inline requires transform guarded against. Before the update to React 0.14, we were pulling `performanceNow` from a hardcoded `react-tools` version (https://github.com/facebook/react/blob/b4e74e38e43ac53af8acd62c78c9213be0194245/src/shared/vendor/performance/performanceNow.js), so we couldn't just fix the file and had to special case it. Now that we've updated to React 0.14, `performanceNow` is pulled from fbjs and no longer needs to be special cased. public Reviewed By: cpojer Differential Revision: D2634940 fb-gh-sync-id: 7085cde3179c04f9ecfd87bdd472b19e370ee73c --- transformer.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/transformer.js b/transformer.js index 7109574e..b07e7b5e 100644 --- a/transformer.js +++ b/transformer.js @@ -17,11 +17,7 @@ function transform(src, filename, options) { options = options || {}; const plugins = []; - if ( - options.inlineRequires && - // (TODO: balpert, cpojer): Remove this once react is updated to 0.14 - !filename.endsWith('performanceNow.js') - ) { + if (options.inlineRequires) { plugins.push({ position: 'after', transformer: inlineRequires, From a724cfde2defded4f74c602e8869c6325fc7b706 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 9 Nov 2015 19:47:56 -0800 Subject: [PATCH 436/936] Defualts for platforms and assetExts Reviewed By: voideanvalue Differential Revision: D2635446 fb-gh-sync-id: 57e628cf5f917c7e2511d0c8bde696310ab0fc7a --- .../src/DependencyResolver/DependencyGraph/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index cbd7e192..21208e1b 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -45,9 +45,9 @@ class DependencyGraph { ignoreFilePath: ignoreFilePath || (() => {}), fileWatcher, assetRoots_DEPRECATED: assetRoots_DEPRECATED || [], - assetExts, + assetExts: assetExts || [], providesModuleNodeModules, - platforms, + platforms: platforms || [], cache, }; this._cache = this._opts.cache; From 7e89bb9920b509954f7c39f21aa4846fb796fccd Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Tue, 10 Nov 2015 02:30:12 -0800 Subject: [PATCH 437/936] Use `debug` instead of `console.warn` Reviewed By: voideanvalue Differential Revision: D2636111 fb-gh-sync-id: d21ec56045461353317348c4da14c92be8015bd3 --- .../src/DependencyResolver/DependencyGraph/ResolutionRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 951beaef..9a1d0dfc 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -70,7 +70,7 @@ class ResolutionRequest { throw error; } - console.warn( + debug( 'Unable to resolve module %s from %s', toModuleName, fromModule.path From a88f69e4827ac1c8600295a857a74dad8e8a428c Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 10 Nov 2015 10:51:39 -0800 Subject: [PATCH 438/936] Upgrade transformers to be compatible babel 6 Reviewed By: vjeux Differential Revision: D2626155 fb-gh-sync-id: e919c8fb8bbf139bcd979ee2738c7ec4fc9eba74 --- babelRegisterOnly.js | 15 +++++++++++- react-packager/.babelrc | 42 +++++++++++++++++---------------- transformer.js | 51 ++++++++++++++++++++--------------------- 3 files changed, 61 insertions(+), 47 deletions(-) diff --git a/babelRegisterOnly.js b/babelRegisterOnly.js index 349ba638..bf328610 100644 --- a/babelRegisterOnly.js +++ b/babelRegisterOnly.js @@ -8,9 +8,22 @@ */ 'use strict'; +require('babel-polyfill'); + +var fs = require('fs'); +var path = require('path'); + var _only = []; +function readBabelRC() { + var rcpath = path.join(__dirname, 'react-packager', '.babelrc'); + var source = fs.readFileSync(rcpath).toString(); + return JSON.parse(source); +} + module.exports = function(onlyList) { _only = _only.concat(onlyList); - require('babel-core/register')({only: _only}); + var config = readBabelRC(); + config.only = _only; + require('babel-core/register')(config); }; diff --git a/react-packager/.babelrc b/react-packager/.babelrc index 602c9fe6..3882b9be 100644 --- a/react-packager/.babelrc +++ b/react-packager/.babelrc @@ -1,27 +1,29 @@ -// Keep in sync with packager/transformer.js { "retainLines": true, "compact": true, "comments": false, - "whitelist": [ - "es6.arrowFunctions", - "es6.blockScoping", - "es6.classes", - "es6.constants", - "es6.destructuring", - "es6.modules", - "es6.parameters", - "es6.properties.computed", - "es6.properties.shorthand", - "es6.spread", - "es6.templateLiterals", - "es7.asyncFunctions", - "es7.trailingFunctionCommas", - "es7.objectRestSpread", - "flow", - "react", - "react.displayName", - "regenerator" + "plugins": [ + "syntax-async-functions", + "syntax-class-properties", + "syntax-trailing-function-commas", + "transform-es2015-arrow-functions", + "transform-es2015-block-scoping", + "transform-es2015-classes", + "transform-es2015-computed-properties", + "transform-es2015-constants", + "transform-es2015-destructuring", + ["transform-es2015-modules-commonjs", {"strict": false, "allowTopLevelThis": true}], + "transform-es2015-parameters", + "transform-es2015-shorthand-properties", + "transform-es2015-spread", + "transform-es2015-template-literals", + "transform-class-properties", + "transform-flow-strip-types", + "transform-object-assign", + "transform-object-rest-spread", + "transform-react-display-name", + "transform-react-jsx", + "transform-regenerator" ], "sourceMaps": false } diff --git a/transformer.js b/transformer.js index b07e7b5e..e7368cc6 100644 --- a/transformer.js +++ b/transformer.js @@ -18,10 +18,7 @@ function transform(src, filename, options) { const plugins = []; if (options.inlineRequires) { - plugins.push({ - position: 'after', - transformer: inlineRequires, - }); + plugins.push([inlineRequires]); } const result = babel.transform(src, { @@ -29,31 +26,33 @@ function transform(src, filename, options) { compact: true, comments: false, filename, - whitelist: [ + plugins: plugins.concat([ // Keep in sync with packager/react-packager/.babelrc - 'es6.arrowFunctions', - 'es6.blockScoping', - 'es6.classes', - 'es6.constants', - 'es6.destructuring', - 'es6.modules', - 'es6.parameters', - 'es6.properties.computed', - 'es6.properties.shorthand', - 'es6.spread', - 'es6.templateLiterals', - 'es7.asyncFunctions', - 'es7.trailingFunctionCommas', - 'es7.objectRestSpread', - 'flow', - 'react', - 'react.displayName', - 'regenerator', - ], - plugins, + 'syntax-async-functions', + 'syntax-class-properties', + 'syntax-jsx', + 'syntax-trailing-function-commas', + 'transform-class-properties', + 'transform-es2015-arrow-functions', + 'transform-es2015-block-scoping', + 'transform-es2015-classes', + 'transform-es2015-computed-properties', + 'transform-es2015-constants', + 'transform-es2015-destructuring', + ['transform-es2015-modules-commonjs', {strict: false, allowTopLevelThis: true}], + 'transform-es2015-parameters', + 'transform-es2015-shorthand-properties', + 'transform-es2015-spread', + 'transform-es2015-template-literals', + 'transform-flow-strip-types', + 'transform-object-assign', + 'transform-object-rest-spread', + 'transform-react-display-name', + 'transform-react-jsx', + 'transform-regenerator', + ]), sourceFileName: filename, sourceMaps: false, - extra: options || {}, }); return { From 7ea13f835c01277988bc3cad9fc7d8a6a772d27a Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 10 Nov 2015 10:51:41 -0800 Subject: [PATCH 439/936] Use the previously upgraded transforms Summary: public Move all the transforms that had been previously upgraded to be the default now. Reviewed By: vjeux Differential Revision: D2631308 fb-gh-sync-id: a2120a9850c98ce65784b77598baa123121246ab --- .../babel-plugin-system-import/6/index.js | 63 ------------------- .../babel-plugin-system-import/index.js | 50 +++++++-------- 2 files changed, 24 insertions(+), 89 deletions(-) delete mode 100644 react-packager/src/transforms/babel-plugin-system-import/6/index.js diff --git a/react-packager/src/transforms/babel-plugin-system-import/6/index.js b/react-packager/src/transforms/babel-plugin-system-import/6/index.js deleted file mode 100644 index 8a6cbcf0..00000000 --- a/react-packager/src/transforms/babel-plugin-system-import/6/index.js +++ /dev/null @@ -1,63 +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. - * - */ -/*jslint node: true */ -'use strict'; - -const t = require('babel-types'); - -/** - * Transforms asynchronous module importing into a function call - * that includes which bundle needs to be loaded - * - * Transforms: - * - * System.import('moduleA') - * - * to: - * - * loadBundles('bundleA') - */ -module.exports = function() { - return { - visitor: { - CallExpression: function (path, state) { - if (!isAppropriateSystemImportCall(path.node)) { - return; - } - - var bundlesLayout = state.opts.bundlesLayout; - var bundleID = bundlesLayout.getBundleIDForModule( - path.node.arguments[0].value - ); - - var bundles = bundleID.split('.'); - bundles.splice(0, 1); - bundles = bundles.map(function(id) { - return t.stringLiteral('bundle.' + id); - }); - - path.replaceWith(t.callExpression( - t.identifier('loadBundles'), - [t.arrayExpression(bundles)] - )); - }, - }, - }; -}; - -function isAppropriateSystemImportCall(node) { - return ( - node.callee.type === 'MemberExpression' && - node.callee.object.name === 'System' && - node.callee.property.name === 'import' && - node.arguments.length === 1 && - node.arguments[0].type === 'StringLiteral' - ); -} diff --git a/react-packager/src/transforms/babel-plugin-system-import/index.js b/react-packager/src/transforms/babel-plugin-system-import/index.js index 21ef64c0..8a6cbcf0 100644 --- a/react-packager/src/transforms/babel-plugin-system-import/index.js +++ b/react-packager/src/transforms/babel-plugin-system-import/index.js @@ -10,7 +10,7 @@ /*jslint node: true */ 'use strict'; -var t = require('babel-core').types; +const t = require('babel-types'); /** * Transforms asynchronous module importing into a function call @@ -24,34 +24,32 @@ var t = require('babel-core').types; * * loadBundles('bundleA') */ -module.exports = function systemImportTransform(babel) { - return new babel.Transformer('system-import', { - CallExpression: function(node, parent, scope, state) { - if (!isAppropriateSystemImportCall(node, parent)) { - return node; - } +module.exports = function() { + return { + visitor: { + CallExpression: function (path, state) { + if (!isAppropriateSystemImportCall(path.node)) { + return; + } - var bundlesLayout = state.opts.extra.bundlesLayout; - var bundleID = bundlesLayout.getBundleIDForModule( - node.arguments[0].value - ); + var bundlesLayout = state.opts.bundlesLayout; + var bundleID = bundlesLayout.getBundleIDForModule( + path.node.arguments[0].value + ); - var bundles = bundleID.split('.'); - bundles.splice(0, 1); - bundles = bundles.map(function(id) { - return t.literal('bundle.' + id); - }); + var bundles = bundleID.split('.'); + bundles.splice(0, 1); + bundles = bundles.map(function(id) { + return t.stringLiteral('bundle.' + id); + }); - return t.callExpression( - t.identifier('loadBundles'), - [t.arrayExpression(bundles)] - ); + path.replaceWith(t.callExpression( + t.identifier('loadBundles'), + [t.arrayExpression(bundles)] + )); + }, }, - - metadata: { - group: 'fb' - } - }); + }; }; function isAppropriateSystemImportCall(node) { @@ -60,6 +58,6 @@ function isAppropriateSystemImportCall(node) { node.callee.object.name === 'System' && node.callee.property.name === 'import' && node.arguments.length === 1 && - node.arguments[0].type === 'Literal' + node.arguments[0].type === 'StringLiteral' ); } From a7338495f85e3b2288a762d99cb156d1bf35941b Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 10 Nov 2015 10:51:51 -0800 Subject: [PATCH 440/936] Fix breakages when upgrading to babel 6 Reviewed By: vjeux Differential Revision: D2628092 fb-gh-sync-id: 077a3572fe8b261d390be2bdc32d9d06c2b80a01 --- react-packager/src/Bundler/index.js | 13 +++++++------ react-packager/src/Cache/__tests__/Cache-test.js | 11 ++++------- .../__tests__/babel-plugin-system-import-test.js | 6 +++--- transformer.js | 1 + 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 2b0c5ff2..82a0282b 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -140,7 +140,8 @@ class Bundler { dev: isDev, platform, }) { - const bundle = new Bundle(sourceMapUrl); + // Const cannot have the same name as the method (babel/babel#2834) + const bbundle = new Bundle(sourceMapUrl); const findEventId = Activity.startEvent('find dependencies'); let transformEventId; @@ -158,11 +159,11 @@ class Bundler { }); } - bundle.setMainModuleId(response.mainModuleId); + bbundle.setMainModuleId(response.mainModuleId); return Promise.all( response.dependencies.map( module => this._transformModule( - bundle, + bbundle, response, module, platform @@ -178,11 +179,11 @@ class Bundler { Activity.endEvent(transformEventId); transformedModules.forEach(function(moduleTransport) { - bundle.addModule(moduleTransport); + bbundle.addModule(moduleTransport); }); - bundle.finalize({runBeforeMainModule, runMainModule}); - return bundle; + bbundle.finalize({runBeforeMainModule, runMainModule}); + return bbundle; }); } diff --git a/react-packager/src/Cache/__tests__/Cache-test.js b/react-packager/src/Cache/__tests__/Cache-test.js index 89c3196e..ee06dd22 100644 --- a/react-packager/src/Cache/__tests__/Cache-test.js +++ b/react-packager/src/Cache/__tests__/Cache-test.js @@ -16,21 +16,18 @@ jest .dontMock('../../lib/getCacheFilePath'); jest - .mock('os') - .mock('fs'); + .mock('fs') + .setMock('os', { + tmpDir() { return 'tmpDir'; } + }); var Promise = require('promise'); var fs = require('fs'); -var os = require('os'); var _ = require('underscore'); var Cache = require('../'); describe('JSTransformer Cache', () => { - beforeEach(() => { - os.tmpDir.mockImpl(() => 'tmpDir'); - }); - describe('getting/setting', () => { pit('calls loader callback for uncached file', () => { fs.stat.mockImpl((file, callback) => { diff --git a/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js b/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js index ddf8f240..43aa4b56 100644 --- a/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js +++ b/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js @@ -46,9 +46,9 @@ describe('System.import', () => { function transform(source) { return babel.transform(source, { - plugins: [require('../')], - blacklist: ['strict'], - extra: { bundlesLayout: layout }, + plugins: [ + [require('../'), { bundlesLayout: layout }] + ], }).code; } diff --git a/transformer.js b/transformer.js index e7368cc6..f194c440 100644 --- a/transformer.js +++ b/transformer.js @@ -47,6 +47,7 @@ function transform(src, filename, options) { 'transform-flow-strip-types', 'transform-object-assign', 'transform-object-rest-spread', + 'transform-object-assign', 'transform-react-display-name', 'transform-react-jsx', 'transform-regenerator', From 70ad5943fa70471a0db2bc9cb04742fdaa345570 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 11 Nov 2015 04:36:30 -0800 Subject: [PATCH 441/936] Fix launchCommand Summary: public Fixes #4055 Babel is failing to find the plugins when running scripts with the full path, cd into the folder before starting the packager as a workaround. Reviewed By: vjeux Differential Revision: D2638529 fb-gh-sync-id: b57151d3091d56a2c070692fb6296184a8fc62de --- launchPackager.command | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launchPackager.command b/launchPackager.command index ec0f417a..0d08a298 100755 --- a/launchPackager.command +++ b/launchPackager.command @@ -12,7 +12,9 @@ echo -en "\033]0;React Packager\a" clear THIS_DIR=$(dirname "$0") -$THIS_DIR/packager.sh +pushd $THIS_DIR +source packager.sh +popd echo "Process terminated. Press to close the window" read From 17706d6ce43a141c2c95f36edfc10e3f334ea457 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 11 Nov 2015 22:53:04 -0800 Subject: [PATCH 442/936] Use external babel helpers Reviewed By: vjeux Differential Revision: D2643810 fb-gh-sync-id: a415e2449a98b6070c688c1c250eb8a908b40409 --- .../BundlesLayoutIntegration-test.js | 1 + .../src/Resolver/__tests__/Resolver-test.js | 17 +- react-packager/src/Resolver/index.js | 1 + .../src/Resolver/polyfills/babelHelpers.js | 195 ++++++++++++++++++ transformer.js | 1 + 5 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 react-packager/src/Resolver/polyfills/babelHelpers.js diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 93164ed1..1cade30c 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -36,6 +36,7 @@ describe('BundlesLayout', () => { 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', 'polyfills/Array.es6.js', + 'polyfills/babelHelpers.js', ]; const baseFs = getBaseFs(); diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 9687fb04..583060e8 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -147,7 +147,21 @@ describe('Resolver', function() { 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', ], - } + }, + { id: 'polyfills/babelHelpers.js', + isPolyfill: true, + path: 'polyfills/babelHelpers.js', + dependencies: [ + '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', + 'polyfills/Array.es6.js', + ], + }, ]); }); }); @@ -211,6 +225,7 @@ describe('Resolver', function() { 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', 'polyfills/Array.es6.js', + 'polyfills/babelHelpers.js', ] }, ]); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 52ca39b1..726d6a75 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -120,6 +120,7 @@ class Resolver { path.join(__dirname, 'polyfills/String.prototype.es6.js'), path.join(__dirname, 'polyfills/Array.prototype.es6.js'), path.join(__dirname, 'polyfills/Array.es6.js'), + path.join(__dirname, 'polyfills/babelHelpers.js'), ].concat(this._polyfillModuleNames); return polyfillModuleNames.map( diff --git a/react-packager/src/Resolver/polyfills/babelHelpers.js b/react-packager/src/Resolver/polyfills/babelHelpers.js new file mode 100644 index 00000000..5eca9e30 --- /dev/null +++ b/react-packager/src/Resolver/polyfills/babelHelpers.js @@ -0,0 +1,195 @@ +/** + * 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. + */ + +/* global self:true */ +/* eslint-disable strict */ + +// Created by running: +// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits objectWithoutProperties possibleConstructorReturn slicedToArray toConsumableArray'.split(' ')) +// then replacing the `global` reference in the last line to also use `this`. + +(function (global) { + var babelHelpers = global.babelHelpers = {}; + + babelHelpers.createRawReactElement = (function () { + var REACT_ELEMENT_TYPE = typeof Symbol === "function" && Symbol.for && Symbol.for("react.element") || 0xeac7; + return function createRawReactElement(type, key, props) { + return { + $$typeof: REACT_ELEMENT_TYPE, + type: type, + key: key, + ref: null, + props: props, + _owner: null + }; + }; + })(); + + babelHelpers.classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + + babelHelpers.createClass = (function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + })(); + + babelHelpers.defineProperty = function (obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + }; + + babelHelpers._extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; + }; + + babelHelpers.get = function get(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent, property, receiver); + } + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; + } + + return getter.call(receiver); + } + }; + + babelHelpers.inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + }; + + babelHelpers.objectWithoutProperties = function (obj, keys) { + var target = {}; + + for (var i in obj) { + if (keys.indexOf(i) >= 0) continue; + if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; + target[i] = obj[i]; + } + + return target; + }; + + babelHelpers.possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + }; + + babelHelpers.slicedToArray = (function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; + } + } + + return _arr; + } + + return function (arr, i) { + if (Array.isArray(arr)) { + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); + } else { + throw new TypeError("Invalid attempt to destructure non-iterable instance"); + } + }; + })(); + + babelHelpers.toConsumableArray = function (arr) { + if (Array.isArray(arr)) { + for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; + + return arr2; + } else { + return Array.from(arr); + } + }; +})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); diff --git a/transformer.js b/transformer.js index f194c440..6e0a9f15 100644 --- a/transformer.js +++ b/transformer.js @@ -28,6 +28,7 @@ function transform(src, filename, options) { filename, plugins: plugins.concat([ // Keep in sync with packager/react-packager/.babelrc + 'external-helpers-2', 'syntax-async-functions', 'syntax-class-properties', 'syntax-jsx', From a0cc7d3ee04bfb88381117a6171fd28090708057 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 12 Nov 2015 03:11:44 -0800 Subject: [PATCH 443/936] Update oss transformer Summary: public The inline-requires transform (from `fbjs-scripts`) is only used for oss tests, and wasn't updated, fix it. Reviewed By: mkonicek Differential Revision: D2641711 fb-gh-sync-id: 90a084082b9c55c5bcb776346f4655451a8a61f7 --- transformer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transformer.js b/transformer.js index 6e0a9f15..01bb8e07 100644 --- a/transformer.js +++ b/transformer.js @@ -11,7 +11,7 @@ 'use strict'; const babel = require('babel-core'); -const inlineRequires = require('fbjs-scripts/babel/inline-requires'); +const inlineRequires = require('fbjs-scripts/babel-6/inline-requires'); function transform(src, filename, options) { options = options || {}; From 48ffe711f820050cc548db5dfc04e08a4b136ce4 Mon Sep 17 00:00:00 2001 From: Giampaolo Bellavite Date: Fri, 13 Nov 2015 11:00:05 -0800 Subject: [PATCH 444/936] Fix typo in console.error Summary: Closes https://github.com/facebook/react-native/pull/4116 Reviewed By: svcscm Differential Revision: D2652595 Pulled By: cpojer fb-gh-sync-id: a46a19dcbb119b4e14df2e4418787ea232c32606 --- 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 21208e1b..3bb57cff 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -54,7 +54,7 @@ class DependencyGraph { this._helpers = new Helpers(this._opts); this.load().catch((err) => { // This only happens at initialization. Live errors are easier to recover from. - console.error('Error building DepdendencyGraph:\n', err.stack); + console.error('Error building DependencyGraph:\n', err.stack); process.exit(1); }); } From b7df3ca666122edd6813dd1a336818b3f5f8a0fc Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Sat, 14 Nov 2015 03:23:46 -0800 Subject: [PATCH 445/936] Unbreak jest tests (and travis e2e tests) Reviewed By: mkonicek Differential Revision: D2652002 fb-gh-sync-id: 8aab8da47dd737acc4ee9acddc484bd3bbdf1184 --- react-packager/.babelrc | 2 +- transformer.js | 56 ++++++++++++++--------------------------- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/react-packager/.babelrc b/react-packager/.babelrc index 3882b9be..c72259da 100644 --- a/react-packager/.babelrc +++ b/react-packager/.babelrc @@ -6,6 +6,7 @@ "syntax-async-functions", "syntax-class-properties", "syntax-trailing-function-commas", + "transform-class-properties", "transform-es2015-arrow-functions", "transform-es2015-block-scoping", "transform-es2015-classes", @@ -17,7 +18,6 @@ "transform-es2015-shorthand-properties", "transform-es2015-spread", "transform-es2015-template-literals", - "transform-class-properties", "transform-flow-strip-types", "transform-object-assign", "transform-object-rest-spread", diff --git a/transformer.js b/transformer.js index 01bb8e07..ce984be9 100644 --- a/transformer.js +++ b/transformer.js @@ -11,51 +11,33 @@ 'use strict'; const babel = require('babel-core'); +const fs = require('fs'); const inlineRequires = require('fbjs-scripts/babel-6/inline-requires'); +const json5 = require('json5'); +const path = require('path'); + +const babelRC = + json5.parse( + fs.readFileSync( + path.resolve(__dirname, 'react-packager', '.babelrc'))); function transform(src, filename, options) { options = options || {}; - const plugins = []; + + const extraPlugins = ['external-helpers-2']; + const extraConfig = { + filename, + sourceFileName: filename, + }; + + const config = Object.assign({}, babelRC, extraConfig); if (options.inlineRequires) { - plugins.push([inlineRequires]); + extraPlugins.push(inlineRequires); } + config.plugins = extraPlugins.concat(config.plugins); - const result = babel.transform(src, { - retainLines: true, - compact: true, - comments: false, - filename, - plugins: plugins.concat([ - // Keep in sync with packager/react-packager/.babelrc - 'external-helpers-2', - 'syntax-async-functions', - 'syntax-class-properties', - 'syntax-jsx', - 'syntax-trailing-function-commas', - 'transform-class-properties', - 'transform-es2015-arrow-functions', - 'transform-es2015-block-scoping', - 'transform-es2015-classes', - 'transform-es2015-computed-properties', - 'transform-es2015-constants', - 'transform-es2015-destructuring', - ['transform-es2015-modules-commonjs', {strict: false, allowTopLevelThis: true}], - 'transform-es2015-parameters', - 'transform-es2015-shorthand-properties', - 'transform-es2015-spread', - 'transform-es2015-template-literals', - 'transform-flow-strip-types', - 'transform-object-assign', - 'transform-object-rest-spread', - 'transform-object-assign', - 'transform-react-display-name', - 'transform-react-jsx', - 'transform-regenerator', - ]), - sourceFileName: filename, - sourceMaps: false, - }); + const result = babel.transform(src, Object.assign({}, babelRC, config)); return { code: result.code From ccea74fc878863767fc962024f401d5ce02bc38d Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Sat, 14 Nov 2015 13:48:09 -0800 Subject: [PATCH 446/936] Fix babelHelpers.extend Summary: public The recent change to move babelhelpers out of the module broke the extends method. Reviewed By: spicyj Differential Revision: D2656448 fb-gh-sync-id: 633433deea00ecb140afbb732bff0599e67b4d41 --- react-packager/src/Resolver/polyfills/babelHelpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/polyfills/babelHelpers.js b/react-packager/src/Resolver/polyfills/babelHelpers.js index 5eca9e30..b1254a68 100644 --- a/react-packager/src/Resolver/polyfills/babelHelpers.js +++ b/react-packager/src/Resolver/polyfills/babelHelpers.js @@ -70,7 +70,7 @@ return obj; }; - babelHelpers._extends = Object.assign || function (target) { + babelHelpers._extends = babelHelpers.extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; From a8ded758d0cf837a4e1af3e5dc79e286eedc37f1 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 16 Nov 2015 22:48:28 -0800 Subject: [PATCH 447/936] Sync new haste features from upstream Reviewed By: amasad Differential Revision: D2644383 fb-gh-sync-id: 5225e6afb8e5b835865473b2880a77235e6bd6eb --- .../DependencyGraph/HasteMap.js | 8 +- .../DependencyGraph/ResolutionRequest.js | 35 +++- .../DependencyGraph/ResolutionResponse.js | 5 + .../__tests__/DependencyGraph-test.js | 180 ++++++++++++++++++ .../DependencyGraph/index.js | 22 ++- .../src/DependencyResolver/Module.js | 67 +------ .../src/DependencyResolver/ModuleCache.js | 4 +- .../__tests__/Module-test.js | 70 ++++++- .../src/DependencyResolver/fastfs.js | 26 +-- .../DependencyResolver/lib/extractRequires.js | 57 ++++++ .../DependencyResolver/lib/replacePatterns.js | 15 ++ .../src/Resolver/__tests__/Resolver-test.js | 3 +- react-packager/src/Resolver/index.js | 2 +- 13 files changed, 405 insertions(+), 89 deletions(-) create mode 100644 react-packager/src/DependencyResolver/lib/extractRequires.js create mode 100644 react-packager/src/DependencyResolver/lib/replacePatterns.js diff --git a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js index c2101591..e1626fef 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -15,7 +15,8 @@ const Promise = require('promise'); const GENERIC_PLATFORM = 'generic'; class HasteMap { - constructor({ fastfs, moduleCache, helpers }) { + constructor({ extensions, fastfs, moduleCache, helpers }) { + this._extensions = extensions; this._fastfs = fastfs; this._moduleCache = moduleCache; this._helpers = helpers; @@ -24,7 +25,7 @@ class HasteMap { build() { this._map = Object.create(null); - let promises = this._fastfs.findFilesByExt('js', { + let promises = this._fastfs.findFilesByExts(this._extensions, { ignore: (file) => this._helpers.isNodeModulesDir(file), }).map(file => this._processHasteModule(file)); @@ -57,8 +58,7 @@ class HasteMap { } } - if (this._helpers.extname(absPath) === 'js' || - this._helpers.extname(absPath) === 'json') { + if (this._extensions.indexOf(this._helpers.extname(absPath)) !== -1) { if (path.basename(absPath) === 'package.json') { return this._processHastePackage(absPath); } else { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 9a1d0dfc..2125b97f 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -97,8 +97,10 @@ class ResolutionRequest { ); } - getOrderedDependencies(response) { - return Promise.resolve().then(() => { + getOrderedDependencies(response, mocksPattern) { + return this._getAllMocks(mocksPattern).then(mocks => { + response.setMocks(mocks); + const entry = this._moduleCache.getModule(this._entryPath); const visited = Object.create(null); visited[entry.hash()] = true; @@ -110,8 +112,20 @@ class ResolutionRequest { depNames.map(name => this.resolveDependency(mod, name)) ).then((dependencies) => [depNames, dependencies]) ).then(([depNames, dependencies]) => { + if (mocks) { + return mod.getName().then(name => { + if (mocks[name]) { + const mockModule = + this._moduleCache.getModule(mocks[name]); + depNames.push(name); + dependencies.push(mockModule); + } + return [depNames, dependencies]; + }); + } + return Promise.resolve([depNames, dependencies]); + }).then(([depNames, dependencies]) => { let p = Promise.resolve(); - const filteredPairs = []; dependencies.forEach((modDep, i) => { @@ -163,6 +177,20 @@ class ResolutionRequest { )); } + _getAllMocks(pattern) { + // Take all mocks in all the roots into account. This is necessary + // because currently mocks are global: any module can be mocked by + // any mock in the system. + let mocks = null; + if (pattern) { + mocks = Object.create(null); + this._fastfs.matchFilesByPattern(pattern).forEach(file => + mocks[path.basename(file, path.extname(file))] = file + ); + } + return Promise.resolve(mocks); + } + _resolveHasteDependency(fromModule, toModuleName) { toModuleName = normalizePath(toModuleName); @@ -349,6 +377,7 @@ class ResolutionRequest { _resetResolutionCache() { this._immediateResolutionCache = Object.create(null); } + } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js index 323e2e8d..aa454a9f 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js @@ -13,6 +13,7 @@ class ResolutionResponse { this.dependencies = []; this.asyncDependencies = []; this.mainModuleId = null; + this.mocks = null; this._mappings = Object.create(null); this._finalized = false; } @@ -64,6 +65,10 @@ class ResolutionResponse { } } + setMocks(mocks) { + this.mocks = mocks; + } + getResolvedDependencyPairs(module) { this._assertFinalized(); return this._mappings[module.hash()]; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 07f47ad7..b8caec76 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -3676,4 +3676,184 @@ describe('DependencyGraph', function() { }); }); }); + + describe('Extensions', () => { + pit('supports custom file extensions', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.jsx': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")', + ].join('\n'), + 'a.coffee': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + 'X.js': '', + }, + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + extensions: ['jsx', 'coffee'], + }); + + return dgraph.matchFilesByPattern('.*') + .then(files => { + expect(files).toEqual([ + '/root/index.jsx', '/root/a.coffee', + ]); + }) + .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.jsx')) + .then(deps => { + expect(deps).toEqual([ + { + dependencies: ['a'], + id: 'index', + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + path: '/root/index.jsx', + resolution: undefined, + }, + { + dependencies: [], + id: 'a', + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + path: '/root/a.coffee', + resolution: undefined, + }, + ]); + }); + }); + }); + + describe('Mocks', () => { + pit('resolves to null if mocksPattern is not specified', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + '__mocks': { + 'A.js': '', + }, + 'index.js': '', + }, + }); + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + + return dgraph.getDependencies('/root/index.js') + .then(response => response.finalize()) + .then(response => { + expect(response.mocks).toBe(null); + }); + }); + + pit('retrieves a list of all mocks in the system', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + '__mocks__': { + 'A.js': '', + 'b.js': '', + }, + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + mocksPattern: /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/, + }); + + return dgraph.getDependencies('/root/b.js') + .then(response => response.finalize()) + .then(response => { + expect(response.mocks).toEqual({ + A: '/root/__mocks__/A.js', + b: '/root/__mocks__/b.js', + }); + }); + }); + + pit('adds mocks as a dependency of their actual module', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + '__mocks__': { + 'A.js': [ + 'require("b");', + ].join('\n'), + 'b.js': '', + }, + 'A.js': [ + '/**', + ' * @providesModule A', + ' */', + 'require("foo");', + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + mocksPattern: /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/, + }); + + return getOrderedDependenciesAsJSON(dgraph, '/root/A.js') + .then(deps => { + expect(deps).toEqual([ + { + path: '/root/A.js', + isJSON: false, + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + id: 'A', + dependencies: ['foo', 'A'], + }, + { + path: '/root/foo.js', + isJSON: false, + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + id: 'foo', + dependencies: [], + }, + { + path: '/root/__mocks__/A.js', + isJSON: false, + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + id: '/root/__mocks__/A.js', + dependencies: ['b'], + }, + ]); + }); + }); + }); }); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 3bb57cff..9f87dcbc 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -38,6 +38,9 @@ class DependencyGraph { providesModuleNodeModules, platforms, cache, + extensions, + mocksPattern, + extractRequires, }) { this._opts = { activity: activity || defaultActivity, @@ -49,6 +52,9 @@ class DependencyGraph { providesModuleNodeModules, platforms: platforms || [], cache, + extensions: extensions || ['js', 'json'], + mocksPattern, + extractRequires, }; this._cache = this._opts.cache; this._helpers = new Helpers(this._opts); @@ -70,7 +76,7 @@ class DependencyGraph { const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED); this._crawling = crawl(allRoots, { ignore: this._opts.ignoreFilePath, - exts: ['js', 'json'].concat(this._opts.assetExts), + exts: this._opts.extensions.concat(this._opts.assetExts), fileWatcher: this._opts.fileWatcher, }); this._crawling.then((files) => activity.endEvent(crawlActivity)); @@ -88,10 +94,15 @@ class DependencyGraph { this._fastfs.on('change', this._processFileChange.bind(this)); - this._moduleCache = new ModuleCache(this._fastfs, this._cache); + this._moduleCache = new ModuleCache( + this._fastfs, + this._cache, + this._opts.extractRequires + ); this._hasteMap = new HasteMap({ fastfs: this._fastfs, + extensions: this._opts.extensions, moduleCache: this._moduleCache, assetExts: this._opts.exts, helpers: this._helpers, @@ -138,12 +149,16 @@ class DependencyGraph { const response = new ResolutionResponse(); return Promise.all([ - req.getOrderedDependencies(response), + req.getOrderedDependencies(response, this._opts.mocksPattern), req.getAsyncDependencies(response), ]).then(() => response); }); } + matchFilesByPattern(pattern) { + return this.load().then(() => this._fastfs.matchFilesByPattern(pattern)); + } + _getRequestPlatform(entryPath, platform) { if (platform == null) { platform = getPlatformExtension(entryPath); @@ -208,6 +223,7 @@ class DependencyGraph { return this._loading; }); } + } function NotFoundError() { diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 79a88f16..a6112082 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -11,11 +11,11 @@ const docblock = require('./DependencyGraph/docblock'); const isAbsolutePath = require('absolute-path'); const path = require('path'); -const replacePatterns = require('./replacePatterns'); +const extractRequires = require('./lib/extractRequires'); class Module { - constructor(file, fastfs, moduleCache, cache) { + constructor(file, fastfs, moduleCache, cache, extractor) { if (!isAbsolutePath(file)) { throw new Error('Expected file to be absolute path but got ' + file); } @@ -26,12 +26,11 @@ class Module { this._fastfs = fastfs; this._moduleCache = moduleCache; this._cache = cache; + this._extractor = extractor; } isHaste() { - return this._cache.get(this.path, 'haste', () => - this._read().then(data => !!data.id) - ); + return this._read().then(data => !!data.id); } getName() { @@ -67,19 +66,17 @@ class Module { } getDependencies() { - return this._cache.get(this.path, 'dependencies', () => - this._read().then(data => data.dependencies) - ); - } - - invalidate() { - this._cache.invalidate(this.path); + return this._read().then(data => data.dependencies); } getAsyncDependencies() { return this._read().then(data => data.asyncDependencies); } + invalidate() { + this._cache.invalidate(this.path); + } + _read() { if (!this._reading) { this._reading = this._fastfs.readFile(this.path).then(content => { @@ -96,7 +93,7 @@ class Module { if ('extern' in moduleDocBlock) { data.dependencies = []; } else { - var dependencies = extractRequires(content); + var dependencies = (this._extractor || extractRequires)(content).deps; data.dependencies = dependencies.sync; data.asyncDependencies = dependencies.async; } @@ -140,48 +137,4 @@ class Module { } } -/** - * Extract all required modules from a `code` string. - */ -const blockCommentRe = /\/\*(.|\n)*?\*\//g; -const lineCommentRe = /\/\/.+(\n|$)/g; -function extractRequires(code) { - var deps = { - sync: [], - async: [], - }; - - code - .replace(blockCommentRe, '') - .replace(lineCommentRe, '') - // Parse sync dependencies. See comment below for further detils. - .replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => { - deps.sync.push(dep); - return match; - }) - .replace(replacePatterns.EXPORT_RE, (match, pre, quot, dep, post) => { - deps.sync.push(dep); - return match; - }) - // Parse the sync dependencies this module has. When the module is - // required, all it's sync dependencies will be loaded into memory. - // Sync dependencies can be defined either using `require` or the ES6 - // `import` or `export` syntaxes: - // 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 the import statement: - // System.import('dep1') - .replace(replacePatterns.SYSTEM_IMPORT_RE, (match, pre, quot, dep, post) => { - deps.async.push([dep]); - return match; - }); - - return deps; -} - module.exports = Module; diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index 8ea52c00..a8891439 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -7,11 +7,12 @@ const path = require('path'); class ModuleCache { - constructor(fastfs, cache) { + constructor(fastfs, cache, extractRequires) { this._moduleCache = Object.create(null); this._packageCache = Object.create(null); this._fastfs = fastfs; this._cache = cache; + this._extractRequires = extractRequires; fastfs.on('change', this._processFileChange.bind(this)); } @@ -23,6 +24,7 @@ class ModuleCache { this._fastfs, this, this._cache, + this._extractRequires ); } return this._moduleCache[filePath]; diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 237bab5d..656a1b00 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -11,18 +11,19 @@ jest .dontMock('absolute-path') .dontMock('../fastfs') - .dontMock('../replacePatterns') + .dontMock('../lib/extractRequires') + .dontMock('../lib/replacePatterns') .dontMock('../DependencyGraph/docblock') .dontMock('../Module'); jest .mock('fs'); -var Fastfs = require('../fastfs'); -var Module = require('../Module'); -var ModuleCache = require('../ModuleCache'); -var Promise = require('promise'); -var fs = require('fs'); +const Fastfs = require('../fastfs'); +const Module = require('../Module'); +const ModuleCache = require('../ModuleCache'); +const Promise = require('promise'); +const fs = require('fs'); describe('Module', () => { const fileWatcher = { @@ -30,17 +31,31 @@ describe('Module', () => { isWatchman: () => Promise.resolve(false), }; + const Cache = jest.genMockFn(); + Cache.prototype.get = jest.genMockFn().mockImplementation( + (filepath, field, cb) => cb(filepath) + ); + Cache.prototype.invalidate = jest.genMockFn(); + Cache.prototype.end = jest.genMockFn(); + + describe('Async Dependencies', () => { function expectAsyncDependenciesToEqual(expected) { - var fastfs = new Fastfs( + const fastfs = new Fastfs( 'test', ['/root'], fileWatcher, {crawling: Promise.resolve(['/root/index.js']), ignore: []}, ); + const cache = new Cache(); return fastfs.build().then(() => { - var module = new Module('/root/index.js', fastfs, new ModuleCache(fastfs)); + const module = new Module( + '/root/index.js', + fastfs, + new ModuleCache(fastfs, cache), + cache + ); return module.getAsyncDependencies().then(actual => expect(actual).toEqual(expected) @@ -94,4 +109,43 @@ describe('Module', () => { return expectAsyncDependenciesToEqual([['dep1']]); }); }); + + describe('Extrators', () => { + + function createModuleWithExtractor(extractor) { + const fastfs = new Fastfs( + 'test', + ['/root'], + fileWatcher, + {crawling: Promise.resolve(['/root/index.js']), ignore: []}, + ); + const cache = new Cache(); + + return fastfs.build().then(() => { + return new Module( + '/root/index.js', + fastfs, + new ModuleCache(fastfs, cache), + cache, + extractor + ); + }); + } + + pit('uses custom require extractors if specified', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': '', + }, + }); + + return createModuleWithExtractor( + code => ({deps: {sync: ['foo', 'bar']}}) + ).then(module => + module.getDependencies().then(actual => + expect(actual).toEqual(['foo', 'bar']) + ) + ); + }); + }); }); diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index d34892dd..17738744 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -79,21 +79,19 @@ class Fastfs extends EventEmitter { return [].concat(...this._roots.map(root => root.getFiles())); } - findFilesByExt(ext, { ignore }) { + findFilesByExt(ext, { ignore } = {}) { + return this.findFilesByExts([ext], {ignore}); + } + + findFilesByExts(exts, { ignore } = {}) { return this.getAllFiles() - .filter( - file => file.ext() === ext && (!ignore || !ignore(file.path)) - ) + .filter(file => ( + exts.indexOf(file.ext()) !== -1 && (!ignore || !ignore(file.path)) + )) .map(file => file.path); } - findFilesByExts(exts) { - return this.getAllFiles() - .filter(file => exts.indexOf(file.ext()) !== -1) - .map(file => file.path); - } - - findFilesByName(name, { ignore }) { + findFilesByName(name, { ignore } = {}) { return this.getAllFiles() .filter( file => path.basename(file.path) === name && @@ -102,6 +100,12 @@ class Fastfs extends EventEmitter { .map(file => file.path); } + matchFilesByPattern(pattern) { + return this.getAllFiles() + .filter(file => file.path.match(pattern)) + .map(file => file.path); + } + readFile(filePath) { const file = this._getFile(filePath); if (!file) { diff --git a/react-packager/src/DependencyResolver/lib/extractRequires.js b/react-packager/src/DependencyResolver/lib/extractRequires.js new file mode 100644 index 00000000..107cc6ba --- /dev/null +++ b/react-packager/src/DependencyResolver/lib/extractRequires.js @@ -0,0 +1,57 @@ +/** + * 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 replacePatterns = require('./replacePatterns'); + +/** + * Extract all required modules from a `code` string. + */ +const blockCommentRe = /\/\*(.|\n)*?\*\//g; +const lineCommentRe = /\/\/.+(\n|$)/g; +function extractRequires(code) { + var deps = { + sync: [], + async: [], + }; + + code = code + .replace(blockCommentRe, '') + .replace(lineCommentRe, '') + // 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` or `export` syntaxes: + // var dep1 = require('dep1'); + .replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => { + deps.sync.push(dep); + return match; + }) + .replace(replacePatterns.EXPORT_RE, (match, pre, quot, dep, post) => { + deps.sync.push(dep); + return match; + }) + .replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => { + deps.sync.push(dep); + return match; + }) + // 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 the import statement: + // System.import('dep1') + .replace(replacePatterns.SYSTEM_IMPORT_RE, (match, pre, quot, dep, post) => { + deps.async.push([dep]); + return match; + }); + + return {code, deps}; +} + +module.exports = extractRequires; diff --git a/react-packager/src/DependencyResolver/lib/replacePatterns.js b/react-packager/src/DependencyResolver/lib/replacePatterns.js new file mode 100644 index 00000000..a4e563d2 --- /dev/null +++ b/react-packager/src/DependencyResolver/lib/replacePatterns.js @@ -0,0 +1,15 @@ +/** + * 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'; + +exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; +exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; +exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; +exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 583060e8..f5858921 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -10,7 +10,8 @@ jest.dontMock('../') .dontMock('underscore') - .dontMock('../../DependencyResolver/replacePatterns'); + .dontMock('../../DependencyResolver/lib/extractRequires') + .dontMock('../../DependencyResolver/lib/replacePatterns'); jest.mock('path'); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 726d6a75..60820faa 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -12,7 +12,7 @@ const path = require('path'); const Activity = require('../Activity'); const DependencyGraph = require('../DependencyResolver/DependencyGraph'); -const replacePatterns = require('../DependencyResolver/replacePatterns'); +const replacePatterns = require('../DependencyResolver/lib/replacePatterns'); const Polyfill = require('../DependencyResolver/Polyfill'); const declareOpts = require('../lib/declareOpts'); const Promise = require('promise'); From 844282c37bc8b13b9d199637c2031b1ddf11cd5b Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 17 Nov 2015 03:36:50 -0800 Subject: [PATCH 448/936] Add a naive WPO implementation Summary: public RFC: The minifier haven't been stripping dead-code, and it also can't kill unused modules, so as a temporary solution this inlines `__DEV__`, kill dead branches and kill dead modules. For now I'm just white-listing the dev variable, but we could definitely do better than that, but as a temporary fix this should be helpful. I also intend to kill some dead variables, so we can kill unused requires, although inline-requires can also fix it. Reviewed By: vjeux Differential Revision: D2605454 fb-gh-sync-id: 50acb9dcbded07a43080b93ac826a5ceda695936 --- react-packager/src/Bundler/Bundle.js | 44 ++++-- .../src/Bundler/__tests__/Bundle-test.js | 12 +- react-packager/src/Server/index.js | 3 + .../__tests__/dead-module-elimination-test.js | 106 ++++++++++++++ .../dead-module-elimination.js | 130 ++++++++++++++++++ .../whole-program-optimisations/index.js | 14 ++ 6 files changed, 291 insertions(+), 18 deletions(-) create mode 100644 react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js create mode 100644 react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js create mode 100644 react-packager/src/transforms/whole-program-optimisations/index.js diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 66b5e704..b6635be9 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -21,6 +21,7 @@ class Bundle { this._finalized = false; this._modules = []; this._assets = []; + this._sourceMap = false; this._sourceMapUrl = sourceMapUrl; this._shouldCombineSourceMaps = false; } @@ -83,16 +84,36 @@ class Bundle { } } - _getSource() { - if (this._source == null) { - this._source = _.pluck(this._modules, 'code').join('\n'); + _getSource(dev) { + if (this._source) { + return this._source; } + + this._source = _.pluck(this._modules, 'code').join('\n'); + + if (dev) { + return this._source; + } + + const wpoActivity = Activity.startEvent('Whole Program Optimisations'); + const result = require('babel-core').transform(this._source, { + retainLines: true, + compact: true, + plugins: require('../transforms/whole-program-optimisations'), + inputSourceMap: this.getSourceMap(), + }); + + this._source = result.code; + this._sourceMap = result.map; + + Activity.endEvent(wpoActivity); + return this._source; } - _getInlineSourceMap() { + _getInlineSourceMap(dev) { if (this._inlineSourceMap == null) { - const sourceMap = this.getSourceMap({excludeSource: true}); + const sourceMap = this.getSourceMap({excludeSource: true, dev}); /*eslint-env node*/ const encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64'); this._inlineSourceMap = 'data:application/json;base64,' + encoded; @@ -106,13 +127,13 @@ class Bundle { options = options || {}; if (options.minify) { - return this.getMinifiedSourceAndMap().code; + return this.getMinifiedSourceAndMap(options.dev).code; } - let source = this._getSource(); + let source = this._getSource(options.dev); if (options.inlineSourceMap) { - source += SOURCEMAPPING_URL + this._getInlineSourceMap(); + source += SOURCEMAPPING_URL + this._getInlineSourceMap(options.dev); } else if (this._sourceMapUrl) { source += SOURCEMAPPING_URL + this._sourceMapUrl; } @@ -120,14 +141,14 @@ class Bundle { return source; } - getMinifiedSourceAndMap() { + getMinifiedSourceAndMap(dev) { this._assertFinalized(); if (this._minifiedSourceAndMap) { return this._minifiedSourceAndMap; } - const source = this._getSource(); + const source = this._getSource(dev); try { const minifyActivity = Activity.startEvent('minify'); this._minifiedSourceAndMap = UglifyJS.minify(source, { @@ -203,7 +224,7 @@ class Bundle { options = options || {}; if (options.minify) { - return this.getMinifiedSourceAndMap().map; + return this.getMinifiedSourceAndMap(options.dev).map; } if (this._shouldCombineSourceMaps) { @@ -314,7 +335,6 @@ class Bundle { modules: this._modules, assets: this._assets, sourceMapUrl: this._sourceMapUrl, - shouldCombineSourceMaps: this._shouldCombineSourceMaps, mainModuleId: this._mainModuleId, }; } diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 62ad59d3..46b57741 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -40,7 +40,7 @@ describe('Bundle', function() { })); bundle.finalize({}); - expect(bundle.getSource()).toBe([ + expect(bundle.getSource({dev: true})).toBe([ 'transformed foo;', 'transformed bar;', '\/\/@ sourceMappingURL=test_url' @@ -61,7 +61,7 @@ describe('Bundle', function() { })); p.finalize({}); - expect(p.getSource()).toBe([ + expect(p.getSource({dev: true})).toBe([ 'transformed foo;', 'transformed bar;', ].join('\n')); @@ -85,7 +85,7 @@ describe('Bundle', function() { runBeforeMainModule: ['bar'], runMainModule: true, }); - expect(bundle.getSource()).toBe([ + expect(bundle.getSource({dev: true})).toBe([ 'transformed foo;', 'transformed bar;', ';require("bar");', @@ -110,7 +110,7 @@ describe('Bundle', function() { sourcePath: 'foo path' })); bundle.finalize(); - expect(bundle.getMinifiedSourceAndMap()).toBe(minified); + expect(bundle.getMinifiedSourceAndMap({dev: true})).toBe(minified); }); }); @@ -149,7 +149,7 @@ describe('Bundle', function() { runBeforeMainModule: [], runMainModule: true, }); - var s = p.getSourceMap(); + var s = p.getSourceMap({dev: true}); expect(s).toEqual(genSourceMap(p.getModules())); }); @@ -183,7 +183,7 @@ describe('Bundle', function() { runMainModule: true, }); - var s = p.getSourceMap(); + var s = p.getSourceMap({dev: true}); expect(s).toEqual({ file: 'bundle.js', version: 3, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index e163e261..1abb78cf 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -240,6 +240,7 @@ class Server { p.getSource({ inlineSourceMap: options.inlineSourceMap, minify: options.minify, + dev: options.dev, }); return p; }); @@ -366,6 +367,7 @@ class Server { var bundleSource = p.getSource({ inlineSourceMap: options.inlineSourceMap, minify: options.minify, + dev: options.dev, }); res.setHeader('Content-Type', 'application/javascript'); res.end(bundleSource); @@ -373,6 +375,7 @@ class Server { } else if (requestType === 'map') { var sourceMap = p.getSourceMap({ minify: options.minify, + dev: options.dev, }); if (typeof sourceMap !== 'string') { diff --git a/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js b/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js new file mode 100644 index 00000000..089f8695 --- /dev/null +++ b/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js @@ -0,0 +1,106 @@ +/** + * 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.autoMockOff(); + +var deadModuleElimintation = require('../dead-module-elimination'); +var babel = require('babel-core'); + +const compile = (code) => + babel.transform(code, { + plugins: [deadModuleElimintation], + }).code; + +const compare = (source, output) => { + const out = trim(compile(source)) + // workaround babel/source map bug + .replace(/^false;/, ''); + + expect(out).toEqual(trim(output)); +}; + + +const trim = (str) => + str.replace(/\s/g, ''); + +describe('dead-module-elimination', () => { + it('should inline __DEV__', () => { + compare( + `__DEV__ = false; + var foo = __DEV__;`, + `var foo = false;` + ); + }); + + it('should accept unary operators with literals', () => { + compare( + `__DEV__ = !1; + var foo = __DEV__;`, + `var foo = false;` + ); + }); + + it('should kill dead branches', () => { + compare( + `__DEV__ = false; + if (__DEV__) { + doSomething(); + }`, + `` + ); + }); + + it('should kill unreferenced modules', () => { + compare( + `__d('foo', function() {})`, + `` + ); + }); + + it('should kill unreferenced modules at multiple levels', () => { + compare( + `__d('bar', function() {}); + __d('foo', function() { require('bar'); });`, + `` + ); + }); + + it('should kill modules referenced only from dead branches', () => { + compare( + `__DEV__ = false; + __d('bar', function() {}); + if (__DEV__) { require('bar'); }`, + `` + ); + }); + + it('should replace logical expressions with the result', () => { + compare( + `__DEV__ = false; + __d('bar', function() {}); + __DEV__ && require('bar');`, + `false;` + ); + }); + + it('should keep if result branch', () => { + compare( + `__DEV__ = false; + __d('bar', function() {}); + if (__DEV__) { + killWithFire(); + } else { + require('bar'); + }`, + `__d('bar', function() {}); + require('bar');` + ); + }); +}); diff --git a/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js b/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js new file mode 100644 index 00000000..82e9b5da --- /dev/null +++ b/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js @@ -0,0 +1,130 @@ +/** + * 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 t = require('babel-types'); + +var globals = Object.create(null); +var requires = Object.create(null); +var _requires; + +const hasDeadModules = modules => + Object.keys(modules).some(key => modules[key] === 0); + +function CallExpression(path) { + const { node } = path; + const fnName = node.callee.name; + + if (fnName === 'require' || fnName === '__d') { + var moduleName = node.arguments[0].value; + if (fnName === '__d' && _requires && !_requires[moduleName]) { + path.remove(); + } else if (fnName === '__d'){ + requires[moduleName] = requires[moduleName] || 0; + } else { + requires[moduleName] = (requires[moduleName] || 0) + 1; + } + } +} + +module.exports = function () { + var firstPass = { + AssignmentExpression(path) { + const { node } = path; + + if (node.left.type === 'Identifier' && node.left.name === '__DEV__') { + var value; + if (node.right.type === 'BooleanLiteral') { + value = node.right.value; + } else if ( + node.right.type === 'UnaryExpression' && + node.right.operator === '!' && + node.right.argument.type === 'NumericLiteral' + ) { + value = !node.right.argument.value; + } else { + return; + } + globals[node.left.name] = value; + + // workaround babel/source map bug - the minifier should strip it + path.replaceWith(t.booleanLiteral(value)); + + //path.remove(); + //scope.removeBinding(node.left.name); + } + }, + IfStatement(path) { + const { node } = path; + + if (node.test.type === 'Identifier' && node.test.name in globals) { + if (globals[node.test.name]) { + path.replaceWithMultiple(node.consequent.body); + } else if (node.alternate) { + path.replaceWithMultiple(node.alternate.body); + } else { + path.remove(); + } + } + }, + Identifier(path) { + const { node } = path; + + var parent = path.parent; + if (parent.type === 'AssignmentExpression' && parent.left === node) { + return; + } + + if (node.name in globals) { + path.replaceWith(t.booleanLiteral(globals[node.name])); + } + }, + + CallExpression, + + LogicalExpression(path) { + const { node } = path; + + if (node.left.type === 'Identifier' && node.left.name in globals) { + const value = globals[node.left.name]; + + if (node.operator === '&&') { + if (value) { + path.replaceWith(node.right); + } else { + path.replaceWith(t.booleanLiteral(value)); + } + } else if (node.operator === '||') { + if (value) { + path.replaceWith(t.booleanLiteral(value)); + } else { + path.replaceWith(node.right); + } + } + } + } + }; + + var secondPass = { + CallExpression, + }; + + return { + visitor: { + Program(path) { + path.traverse(firstPass); + while (hasDeadModules(requires)) { + _requires = requires; + requires = {}; + path.traverse(secondPass); + } + } + } + }; +}; diff --git a/react-packager/src/transforms/whole-program-optimisations/index.js b/react-packager/src/transforms/whole-program-optimisations/index.js new file mode 100644 index 00000000..f802c0f7 --- /dev/null +++ b/react-packager/src/transforms/whole-program-optimisations/index.js @@ -0,0 +1,14 @@ +/** + * 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'; + +// Return the list of plugins use for Whole Program Optimisations +module.exports = [ + require('./dead-module-elimination'), +]; From 4ddb158c727db980fe1aedcbea7103679dbaf733 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 17 Nov 2015 11:10:35 -0800 Subject: [PATCH 449/936] Add ternary elimination support to the WPO pass Summary: public Replaces ``` __DEV__ ? foo() : bar() ``` with ``` bar() ``` for non-dev builds. Reviewed By: davidaurelio Differential Revision: D2663838 fb-gh-sync-id: 30fdfa72dda89fc0d340da359e0f43db2917fcf5 --- .../__tests__/dead-module-elimination-test.js | 9 +++++ .../dead-module-elimination.js | 37 ++++++++++++------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js b/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js index 089f8695..7525fd0f 100644 --- a/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js +++ b/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js @@ -103,4 +103,13 @@ describe('dead-module-elimination', () => { require('bar');` ); }); + + it('should replace falsy ternaries with alternate expression', () => { + compare( + `__DEV__ = false; + __DEV__ ? foo() : bar(); + `, + `bar();` + ); + }); }); diff --git a/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js b/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js index 82e9b5da..39eeb0a6 100644 --- a/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js +++ b/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js @@ -33,6 +33,28 @@ function CallExpression(path) { } } +function IfStatement(path) { + const { node } = path; + + if (node.test.type === 'Identifier' && node.test.name in globals) { + if (globals[node.test.name]) { + if (node.consequent.type === 'BlockStatement') { + path.replaceWithMultiple(node.consequent.body); + } else { + path.replaceWith(node.consequent); + } + } else if (node.alternate) { + if (node.alternate.type === 'BlockStatement') { + path.replaceWithMultiple(node.alternate.body); + } else { + path.replaceWith(node.alternate); + } + } else { + path.remove(); + } + } + } + module.exports = function () { var firstPass = { AssignmentExpression(path) { @@ -60,19 +82,8 @@ module.exports = function () { //scope.removeBinding(node.left.name); } }, - IfStatement(path) { - const { node } = path; - - if (node.test.type === 'Identifier' && node.test.name in globals) { - if (globals[node.test.name]) { - path.replaceWithMultiple(node.consequent.body); - } else if (node.alternate) { - path.replaceWithMultiple(node.alternate.body); - } else { - path.remove(); - } - } - }, + IfStatement, + ConditionalExpression: IfStatement, Identifier(path) { const { node } = path; From b289600a7aaae51b1fcef9bd0c51d3c00dc06729 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 20 Nov 2015 03:33:07 -0800 Subject: [PATCH 450/936] Port CatalystLoggingTestCase to iOS Reviewed By: nicklockwood Differential Revision: D2658485 fb-gh-sync-id: e6803aefee69ee058651fc4c8c202619543f0cd2 --- .../src/Resolver/polyfills/console.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/console.js b/react-packager/src/Resolver/polyfills/console.js index e08e8ef7..5461602b 100644 --- a/react-packager/src/Resolver/polyfills/console.js +++ b/react-packager/src/Resolver/polyfills/console.js @@ -366,7 +366,6 @@ }; function setupConsole(global) { - var originalConsole = global.console; if (!global.nativeLoggingHook) { @@ -375,16 +374,23 @@ function getNativeLogFunction(level) { return function() { - 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) { + var str; + if (arguments.length === 1 && typeof arguments[0] === 'string') { + str = arguments[0]; + } else { + str = Array.prototype.map.call(arguments, function(arg) { + return inspect(arg, {depth: 10}); + }).join(', '); + } + + var logLevel = level; + if (str.slice(0, 9) === 'Warning: ' && logLevel >= 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; + logLevel = LOG_LEVELS.warn; } - global.nativeLoggingHook(str, level); + global.nativeLoggingHook(str, logLevel); }; } From b0b2a27b751572bb2fcf56f58524a45def1da59c Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Fri, 20 Nov 2015 07:52:36 -0800 Subject: [PATCH 451/936] Ensure correct Babel plugin locations in packager transform Summary: Manually resolve all default Babel plugins. `babel.transform` will attempt to resolve all base plugins relative to the file it's compiling. This makes sure that we're using the plugins installed in the react-native package. **NOTE:** Haven't tested this. Please don't merge until Travis has done CI. Closes https://github.com/facebook/react-native/pull/4248 Reviewed By: svcscm Differential Revision: D2679651 Pulled By: davidaurelio fb-gh-sync-id: 0cdef1e738ba28c09c811432a71047f80540ed7b --- transformer.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/transformer.js b/transformer.js index ce984be9..4700407c 100644 --- a/transformer.js +++ b/transformer.js @@ -37,6 +37,21 @@ function transform(src, filename, options) { } config.plugins = extraPlugins.concat(config.plugins); + // Manually resolve all default Babel plugins. babel.transform will attempt to resolve + // all base plugins relative to the file it's compiling. This makes sure that we're + // using the plugins installed in the react-native package. + config.plugins = config.plugins.map(function(plugin) { + // Normalise plugin to an array. + if (!Array.isArray(plugin)) { + plugin = [plugin]; + } + // Only resolve the plugin if it's a string reference. + if (typeof plugin[0] === 'string') { + plugin[0] = require(`babel-plugin-${plugin[0]}`); + } + return plugin; + }); + const result = babel.transform(src, Object.assign({}, babelRC, config)); return { From f02d6c184867895ebb86ae23ff8a5b75e4256682 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 20 Nov 2015 20:17:17 -0800 Subject: [PATCH 452/936] Add infra for Prepack build option Summary: This adds a build option for using Prepack (an experimental packager) to build a bundle. It doesn't actually take on the npm package dependency because it's not published/open source (yet). This will be used while we experiment and should be maintained as the build system changes so that we can continue getting fresh builds. I found that saveBundleAndMap and processBundle were over abstracted and got in my way so I inlined it and removed the unit tests because the unit test was testing trivial code that is likely to change interface. I went with a separate build phase and a separate Bundle class even though there are a lot of commonalities. I imagine that the requirements for Prepack will continue to diverge. Especially for source maps but a larger refactor could try to unify these a bit more. The fact that modules are wrapped before the write phase seems to be an unfortunate architecture that makes this difficult. Closes https://github.com/facebook/react-native/pull/4226 Reviewed By: amasad Differential Revision: D2673760 Pulled By: sebmarkbage fb-gh-sync-id: 299ccc42e4be1d9dee19ade443ea3388db2e39a8 --- react-packager/index.js | 9 ++ react-packager/src/Bundler/PrepackBundle.js | 149 ++++++++++++++++++ react-packager/src/Bundler/index.js | 104 +++++++++--- react-packager/src/Server/index.js | 11 ++ .../src/SocketInterface/SocketClient.js | 8 + .../src/SocketInterface/SocketServer.js | 8 + 6 files changed, 267 insertions(+), 22 deletions(-) create mode 100644 react-packager/src/Bundler/PrepackBundle.js diff --git a/react-packager/index.js b/react-packager/index.js index e0a29684..e1a294f4 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -35,6 +35,15 @@ exports.buildBundle = function(options, bundleOptions) { }); }; +exports.buildPrepackBundle = function(options, bundleOptions) { + var server = createNonPersistentServer(options); + return server.buildPrepackBundle(bundleOptions) + .then(function(p) { + server.end(); + return p; + }); +}; + exports.buildPackageFromUrl = exports.buildBundleFromUrl = function(options, reqUrl) { var server = createNonPersistentServer(options); diff --git a/react-packager/src/Bundler/PrepackBundle.js b/react-packager/src/Bundler/PrepackBundle.js new file mode 100644 index 00000000..123d3297 --- /dev/null +++ b/react-packager/src/Bundler/PrepackBundle.js @@ -0,0 +1,149 @@ +/** + * 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 fs = require('fs'); + +class PrepackBundle { + constructor(sourceMapUrl) { + this._finalized = false; + this._moduleIds = Object.create(null); + this._modules = Object.create(null); + this._eagerModules = []; + this._mainModule = null; + this._assets = []; + this._sourceMapUrl = sourceMapUrl; + } + + addModule(id, module, deps, isPolyfill) { + this._modules[module.sourcePath] = { module, deps }; + this._moduleIds[id] = module.sourcePath; + if (isPolyfill) { + this._eagerModules.push(id); + } + } + + addAsset(asset) { + this._assets.push(asset); + } + + // Synchronously load a file path. + _loadFilename(path) { + const module = this._modules[path]; + if (!module) { + throw new Error('Could not find file "' + path + '" in preloaded files.'); + } + return module.module.code; + } + + // Synchronously resolve a relative require from a parent module. + _resolveFilename(parentPath, relativePath) { + if (!parentPath) { + const resolvedPath = this._moduleIds[relativePath]; + if (!resolvedPath) { + throw new Error('Could not resolve "' + relativePath + '".'); + } + return resolvedPath; + } + const deps = this._modules[parentPath].deps; + const resolvedPath = deps[relativePath]; + if (!resolvedPath) { + throw new Error( + 'Could not resolve "' + relativePath + '" from "' + parentPath + '".' + ); + } + return resolvedPath; + } + + build(options) { + var prepack = require('prepack'); + + var batchedBridgeConfig = (options && options.batchedBridgeConfig) || null; + if (typeof batchedBridgeConfig === 'string') { + batchedBridgeConfig = JSON.parse( + fs.readFileSync(batchedBridgeConfig, 'utf-8') + ); + } + + var options = { + batchedBridgeConfig: batchedBridgeConfig, + environment: 'react-native', + resolveFilename: this._resolveFilename.bind(this), + loadFilename: this._loadFilename.bind(this), + eagerModules: this._eagerModules + }; + + return prepack.compileModule(this._mainModule, options); + } + + finalize(options) { + options = options || {}; + if (options.runMainModule) { + options.runBeforeMainModule.forEach(this._addRequireCall, this); + this._mainModule = options.mainModuleId; + } + + Object.freeze(this._moduleIds); + Object.freeze(this._modules); + Object.freeze(this._assets); + Object.freeze(this._eagerModules); + this._finalized = true; + } + + _addRequireCall(moduleId) { + this._eagerModules.push(moduleId); + } + + _assertFinalized() { + if (!this._finalized) { + throw new Error('Bundle needs to be finalized before getting any source'); + } + } + + getAssets() { + return this._assets; + } + + toJSON() { + if (!this._finalized) { + throw new Error('Cannot serialize bundle unless finalized'); + } + + return { + modules: this._modules, + moduleIds: this._moduleIds, + assets: this._assets, + sourceMapUrl: this._sourceMapUrl, + mainModule: this._mainModule, + eagerModules: this._eagerModules, + }; + } + + static fromJSON(json) { + const bundle = new PrepackBundle(json.sourceMapUrl); + bundle._assets = json.assets; + bundle._moduleIds = json.moduleIds; + bundle._modules = json.modules; + bundle._sourceMapUrl = json.sourceMapUrl; + + bundle._eagerModules = json.eagerModules; + bundle._mainModule = json.mainModule; + + Object.freeze(bundle._moduleIds); + Object.freeze(bundle._modules); + Object.freeze(bundle._assets); + Object.freeze(bundle._eagerModules); + + bundle._finalized = true; + + return bundle; + } +} + +module.exports = PrepackBundle; diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 82a0282b..5f5d7959 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -18,6 +18,7 @@ const Cache = require('../Cache'); const Transformer = require('../JSTransformer'); const Resolver = require('../Resolver'); const Bundle = require('./Bundle'); +const PrepackBundle = require('./PrepackBundle'); const Activity = require('../Activity'); const ModuleTransport = require('../lib/ModuleTransport'); const declareOpts = require('../lib/declareOpts'); @@ -171,7 +172,7 @@ class Bundler { if (bar) { bar.tick(); } - return transformed; + return this._wrapTransformedModule(response, module, transformed); }) ) ); @@ -187,6 +188,68 @@ class Bundler { }); } + prepackBundle({ + entryFile, + runModule: runMainModule, + runBeforeMainModule, + sourceMapUrl, + dev: isDev, + platform, + }) { + const bundle = new PrepackBundle(sourceMapUrl); + const findEventId = Activity.startEvent('find dependencies'); + let transformEventId; + let mainModuleId; + + return this.getDependencies(entryFile, isDev, platform).then((response) => { + Activity.endEvent(findEventId); + transformEventId = Activity.startEvent('transform'); + + let bar; + if (process.stdout.isTTY) { + bar = new ProgressBar('transforming [:bar] :percent :current/:total', { + complete: '=', + incomplete: ' ', + width: 40, + total: response.dependencies.length, + }); + } + + mainModuleId = response.mainModuleId; + + return Promise.all( + response.dependencies.map( + module => this._transformModule( + bundle, + response, + module, + platform + ).then(transformed => { + if (bar) { + bar.tick(); + } + + var deps = Object.create(null); + var pairs = response.getResolvedDependencyPairs(module); + if (pairs) { + pairs.forEach(pair => { + deps[pair[0]] = pair[1].path; + }); + } + + return module.getName().then(name => { + bundle.addModule(name, transformed, deps, module.isPolyfill()); + }); + }) + ) + ); + }).then(() => { + Activity.endEvent(transformEventId); + bundle.finalize({runBeforeMainModule, runMainModule, mainModuleId }); + return bundle; + }); + } + invalidateFile(filePath) { this._transformer.invalidateFile(filePath); } @@ -228,35 +291,32 @@ class Bundler { } _transformModule(bundle, response, module, platform = null) { - let transform; - if (module.isAsset_DEPRECATED()) { - transform = this.generateAssetModule_DEPRECATED(bundle, module); + return this.generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { - transform = this.generateAssetModule(bundle, module, platform); + return this.generateAssetModule(bundle, module, platform); } else if (module.isJSON()) { - transform = generateJSONModule(module); + return generateJSONModule(module); } else { - transform = this._transformer.loadFileAndTransform( + return this._transformer.loadFileAndTransform( path.resolve(module.path) ); } + } - const resolver = this._resolver; - return transform.then( - transformed => resolver.wrapModule( - response, - module, - transformed.code - ).then( - code => new ModuleTransport({ - code: code, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }) - ) + _wrapTransformedModule(response, module, transformed) { + return this._resolver.wrapModule( + response, + module, + transformed.code + ).then( + code => new ModuleTransport({ + code: code, + map: transformed.map, + sourceCode: transformed.sourceCode, + sourcePath: transformed.sourcePath, + virtual: transformed.virtual, + }) ); } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 1abb78cf..28c5b4d1 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -190,6 +190,17 @@ class Server { }); } + buildPrepackBundle(options) { + return Promise.resolve().then(() => { + if (!options.platform) { + options.platform = getPlatformExtension(options.entryFile); + } + + const opts = bundleOpts(options); + return this._bundler.prepackBundle(opts); + }); + } + buildBundleFromUrl(reqUrl) { const options = this._getOptionsFromUrl(reqUrl); return this.buildBundle(options); diff --git a/react-packager/src/SocketInterface/SocketClient.js b/react-packager/src/SocketInterface/SocketClient.js index 41ce0df1..a344f5d9 100644 --- a/react-packager/src/SocketInterface/SocketClient.js +++ b/react-packager/src/SocketInterface/SocketClient.js @@ -9,6 +9,7 @@ 'use strict'; const Bundle = require('../Bundler/Bundle'); +const PrepackBundle = require('../Bundler/PrepackBundle'); const Promise = require('promise'); const bser = require('bser'); const debug = require('debug')('ReactNativePackager:SocketClient'); @@ -100,6 +101,13 @@ class SocketClient { }).then(json => Bundle.fromJSON(json)); } + buildPrepackBundle(options) { + return this._send({ + type: 'buildPrepackBundle', + data: options, + }).then(json => PrepackBundle.fromJSON(json)); + } + _send(message) { message.id = uid(); this._sock.write(bser.dumpToBuffer(message)); diff --git a/react-packager/src/SocketInterface/SocketServer.js b/react-packager/src/SocketInterface/SocketServer.js index 8258873a..56562e27 100644 --- a/react-packager/src/SocketInterface/SocketServer.js +++ b/react-packager/src/SocketInterface/SocketServer.js @@ -121,6 +121,14 @@ class SocketServer { ); break; + case 'buildPrepackBundle': + this._jobs++; + this._packagerServer.buildPrepackBundle(m.data).then( + (result) => this._reply(sock, m.id, 'result', result), + handleError, + ); + break; + case 'getOrderedDependencyPaths': this._jobs++; this._packagerServer.getOrderedDependencyPaths(m.data).then( From 97f0432de51974cbd45a2f512d26ad85447b20b3 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Sat, 21 Nov 2015 11:08:31 -0800 Subject: [PATCH 453/936] Add systrace markers to module require Summary: public Show modules' dependencies and time to load. Reviewed By: davidaurelio Differential Revision: D2603245 fb-gh-sync-id: a1d5067a8522b908b87fdfdd51ff4c4fdbc2edfc --- react-packager/src/Resolver/polyfills/require.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 6ebfee90..d7550ba1 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -54,9 +54,13 @@ // require cycles inside the factory from causing an infinite require loop. mod.isInitialized = true; + __DEV__ && BridgeProfiling().profile(id); + // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js mod.factory.call(global, global, require, mod.module, mod.module.exports); + + __DEV__ && BridgeProfiling().profileEnd(); } catch (e) { mod.hasError = true; mod.isInitialized = false; @@ -66,6 +70,16 @@ return mod.module.exports; } + const BridgeProfiling = __DEV__ && (() => { + var _BridgeProfiling; + try { + _BridgeProfiling = require('BridgeProfiling'); + } catch(e) {} + + return _BridgeProfiling && _BridgeProfiling.profile ? + _BridgeProfiling : { profile: () => {}, profileEnd: () => {} }; + }); + global.__d = define; global.require = require; })(this); From 8638b666fabaa4771bc341547612082ad6d4a40b Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 24 Nov 2015 09:14:44 -0800 Subject: [PATCH 454/936] Add babel helpers necessary for es2015 imports Summary: public Adding the babel helpers that are necessary to support ES2015 imports. Fixes: https://gist.github.com/ehd/49cb2465df9da6b39710 Reviewed By: mkonicek Differential Revision: D2690772 fb-gh-sync-id: b1b6c0c048bad809a5c58cdd0a2cbeaa11c72ea7 --- .../src/Resolver/polyfills/babelHelpers.js | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/polyfills/babelHelpers.js b/react-packager/src/Resolver/polyfills/babelHelpers.js index b1254a68..a7fac6bc 100644 --- a/react-packager/src/Resolver/polyfills/babelHelpers.js +++ b/react-packager/src/Resolver/polyfills/babelHelpers.js @@ -11,8 +11,10 @@ /* eslint-disable strict */ // Created by running: -// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits objectWithoutProperties possibleConstructorReturn slicedToArray toConsumableArray'.split(' ')) +// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray toConsumableArray'.split(' ')) // then replacing the `global` reference in the last line to also use `this`. +// +// actually, that's a lie, because babel6 omits _extends and createRawReactElement (function (global) { var babelHelpers = global.babelHelpers = {}; @@ -125,6 +127,29 @@ if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }; + babelHelpers.interopRequireDefault = function (obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; + }; + + babelHelpers.interopRequireWildcard = function (obj) { + if (obj && obj.__esModule) { + return obj; + } else { + var newObj = {}; + + if (obj != null) { + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; + } + } + + newObj.default = obj; + return newObj; + } + }; + babelHelpers.objectWithoutProperties = function (obj, keys) { var target = {}; From e88322d3a87029a36585884aa828881239f785f0 Mon Sep 17 00:00:00 2001 From: Mark Vayngrib Date: Tue, 24 Nov 2015 11:03:57 -0800 Subject: [PATCH 455/936] support react-native field with fallback to browser field Summary: React Native is a lot more powerful an environment than the browser, so we need an alternate mapping, as specified [here](https://github.com/defunctzombie/node-browser-resolve#browser-field) An example: ```js { "browser": { "./lib/server": false }, "react-native": { "dgram": "react-native-udp", "fs": "react-native-level-fs" }, "chromeapp": { "dgram": "chrome-dgram", "fs": "level-filesystem" } } ``` on the other hand, if "react-native" is not present in package.json, you should fall back to "browser" other than the one (nesting) test added, the tests are unchanged, just done for both "react-native" and "browser" (I've implemented [react-native-udp](https://npmjs.org/package/react-native-udp) and [react-native-level-fs](https://npmjs.org/package/react-native-level-fs), but they obviously don't belong in the traditional "browser" field as they won't run anywhere except in React Native.) Closes https://github.com/facebook/react-native/pull/2208 Reviewed By: svcscm Differential Revision: D2691236 Pulled By: vjeux fb-gh-sync-id: 34041ed50bda4ec07f31d1dc50dcdfa428af2512 --- .../__tests__/DependencyGraph-test.js | 840 +++++++++++------- .../src/DependencyResolver/Package.js | 33 +- 2 files changed, 530 insertions(+), 343 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index b8caec76..a89ce97a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -1362,170 +1362,491 @@ describe('DependencyGraph', function() { }); }); - pit('should support simple browser field in packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client.js', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, - }); + testBrowserField('browser') + testBrowserField('react-native') - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); + function replaceBrowserField (json, fieldName) { + if (fieldName !== 'browser') { + json[fieldName] = json.browser + delete json.browser + } - pit('should support browser field in packages w/o .js ext', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, - }); + return json + } - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); + function testBrowserField (fieldName) { + pit('should support simple browser field in packages ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: 'main.js', + browser: 'client.js', + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); - pit('should support mapping main in browser field json', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main.js': './client.js', + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); }); + pit('should support browser field in packages w/o .js ext ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: 'main.js', + browser: 'client', + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should support mapping main in browser field json ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: './main.js', + browser: { + './main.js': './client.js', + }, + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + assetExts: ['png', 'jpg'], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work do correct browser mapping w/o js ext ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + }, + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + assetExts: ['png', 'jpg'], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support browser mapping of files ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + './node.js': './not-node.js', + './not-browser': './browser.js', + './dir/server.js': './dir/client', + './hello.js': './bye.js', + }, + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'require("./node")\nrequire("./dir/server.js")', + 'not-node.js': 'require("./not-browser")', + 'not-browser.js': 'require("./dir/server")', + 'browser.js': 'some browser code', + 'dir': { + 'server.js': 'some node code', + 'client.js': 'require("../hello")', + }, + 'hello.js': 'hello', + 'bye.js': 'bye', + } + } + }); + var dgraph = new DependencyGraph({ ...defaults, roots: [root], }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: ['./node', './dir/server.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/not-node.js', + path: '/root/aPackage/not-node.js', + dependencies: ['./not-browser'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/browser.js', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/dir/client.js', + path: '/root/aPackage/dir/client.js', + dependencies: ['../hello'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/bye.js', + path: '/root/aPackage/bye.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); }); - }); - pit('should work do correct browser mapping w/o js ext', function() { + pit('should support browser mapping for packages ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + browser: { + 'node-package': 'browser-package', + } + }, fieldName)), + 'index.js': 'require("node-package")', + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', + }, + 'browser-package': { + 'package.json': JSON.stringify({ + 'name': 'browser-package', + }), + 'index.js': 'some browser code', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'browser-package/index.js', + path: '/root/aPackage/browser-package/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should support browser mapping for packages ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + browser: { + 'node-package': 'browser-package', + } + }, fieldName)), + 'index.js': 'require("node-package")', + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', + }, + 'browser-package': { + 'package.json': JSON.stringify({ + 'name': 'browser-package', + }), + 'index.js': 'some browser code', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'browser-package/index.js', + path: '/root/aPackage/browser-package/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + } + + pit('should fall back to browser mapping from react-native mapping', function() { var root = '/root'; fs.__setMockFilesystem({ 'root': { @@ -1538,186 +1859,36 @@ describe('DependencyGraph', function() { 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should support browser mapping of files', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - './node.js': './not-node.js', - './not-browser': './browser.js', - './dir/server.js': './dir/client', - './hello.js': './bye.js', - }, - }), - 'main.js': 'some other code', - 'client.js': 'require("./node")\nrequire("./dir/server.js")', - 'not-node.js': 'require("./not-browser")', - 'not-browser.js': 'require("./dir/server")', - 'browser.js': 'some browser code', - 'dir': { - 'server.js': 'some node code', - 'client.js': 'require("../hello")', - }, - 'hello.js': 'hello', - 'bye.js': 'bye', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/not-node.js', - path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/browser.js', - path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/dir/client.js', - path: '/root/aPackage/dir/client.js', - dependencies: ['../hello'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/bye.js', - path: '/root/aPackage/bye.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should support browser mapping for packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - browser: { - 'node-package': 'browser-package', - }, + 'react-native': { + 'node-package': 'rn-package', + } }), 'index.js': 'require("node-package")', - 'node-package': { - 'package.json': JSON.stringify({ - 'name': 'node-package', - }), - 'index.js': 'some node code', - }, - 'browser-package': { - 'package.json': JSON.stringify({ - 'name': 'browser-package', - }), - 'index.js': 'some browser code', - }, - }, - }, + 'node_modules': { + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package' + }), + 'index.js': 'some node code', + }, + 'rn-package': { + 'package.json': JSON.stringify({ + 'name': 'rn-package', + browser: { + 'nested-package': 'nested-browser-package' + } + }), + 'index.js': 'require("nested-package")', + }, + 'nested-browser-package': { + 'package.json': JSON.stringify({ + 'name': 'nested-browser-package', + }), + 'index.js': 'some code' + } + } + } + } }); var dgraph = new DependencyGraph({ @@ -1745,8 +1916,17 @@ describe('DependencyGraph', function() { isPolyfill: false, resolution: undefined, }, - { id: 'browser-package/index.js', - path: '/root/aPackage/browser-package/index.js', + { id: 'rn-package/index.js', + path: '/root/aPackage/node_modules/rn-package/index.js', + dependencies: ['nested-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'nested-browser-package/index.js', + path: '/root/aPackage/node_modules/nested-browser-package/index.js', dependencies: [], isAsset: false, isAsset_DEPRECATED: false, diff --git a/react-packager/src/DependencyResolver/Package.js b/react-packager/src/DependencyResolver/Package.js index d45a8923..2117e3f1 100644 --- a/react-packager/src/DependencyResolver/Package.js +++ b/react-packager/src/DependencyResolver/Package.js @@ -15,17 +15,18 @@ class Package { getMain() { return this._read().then(json => { - if (typeof json.browser === 'string') { - return path.join(this.root, json.browser); + var replacements = getReplacements(json) + if (typeof replacements === 'string') { + return path.join(this.root, replacements); } let main = json.main || 'index'; - if (json.browser && typeof json.browser === 'object') { - main = json.browser[main] || - json.browser[main + '.js'] || - json.browser[main + '.json'] || - json.browser[main.replace(/(\.js|\.json)$/, '')] || + if (replacements && typeof replacements === 'object') { + main = replacements[main] || + replacements[main + '.js'] || + replacements[main + '.json'] || + replacements[main.replace(/(\.js|\.json)$/, '')] || main; } @@ -51,14 +52,14 @@ class Package { redirectRequire(name) { return this._read().then(json => { - const {browser} = json; + var replacements = getReplacements(json); - if (!browser || typeof browser !== 'object') { + if (!replacements || typeof replacements !== 'object') { return name; } if (name[0] !== '/') { - return browser[name] || name; + return replacements[name] || name; } if (!isAbsolutePath(name)) { @@ -66,9 +67,9 @@ class Package { } const relPath = './' + path.relative(this.root, name); - const redirect = browser[relPath] || - browser[relPath + '.js'] || - browser[relPath + '.json']; + const redirect = replacements[relPath] || + replacements[relPath + '.js'] || + replacements[relPath + '.json']; if (redirect) { return path.join( this.root, @@ -90,4 +91,10 @@ class Package { } } +function getReplacements(pkg) { + return pkg['react-native'] == null + ? pkg.browser + : pkg['react-native']; +} + module.exports = Package; From df5d178115cdeaaf813e5629f7ea97e9ae7308bb Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 25 Nov 2015 17:34:55 -0800 Subject: [PATCH 456/936] Extract the Require polyfill from the default list of polyfills Summary: Extracts the module system from the list of dependencies and adds it back to the bundling process. Unbundling and Prepack has their own module systems. jest does as well. This is not normally part of the resolver, nor polyfills. In fact, I think we'll eventually start treating polyfills as normal modules just like core-js. The prelude is the weird part right now but I think that we'll eventually move the DEV flag to be module level instead of global and we can just get rid of this prelude. public Reviewed By: davidaurelio Differential Revision: D2693701 fb-gh-sync-id: a59ccda0fa15fcfcda52897e8290805eed1b92b3 --- .../src/Bundler/__tests__/Bundler-test.js | 7 +++++ react-packager/src/Bundler/index.js | 11 ++++++-- .../src/Resolver/__tests__/Resolver-test.js | 26 +---------------- react-packager/src/Resolver/index.js | 28 +++++++++++++++---- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index ac8494be..9ce3f7c4 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -46,6 +46,7 @@ describe('Bundler', function() { } var getDependencies; + var getModuleSystemDependencies; var wrapModule; var bundler; var assetServer; @@ -53,10 +54,12 @@ describe('Bundler', function() { beforeEach(function() { getDependencies = jest.genMockFn(); + getModuleSystemDependencies = jest.genMockFn(); wrapModule = jest.genMockFn(); Resolver.mockImpl(function() { return { getDependencies: getDependencies, + getModuleSystemDependencies: getModuleSystemDependencies, wrapModule: wrapModule, }; }); @@ -112,6 +115,10 @@ describe('Bundler', function() { }); }); + getModuleSystemDependencies.mockImpl(function() { + return []; + }); + JSTransformer.prototype.loadFileAndTransform .mockImpl(function(path) { return Promise.resolve({ diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 5f5d7959..e097667f 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -146,23 +146,30 @@ class Bundler { const findEventId = Activity.startEvent('find dependencies'); let transformEventId; + const moduleSystem = this._resolver.getModuleSystemDependencies( + { dev: isDev, platform } + ); + return this.getDependencies(entryFile, isDev, platform).then((response) => { Activity.endEvent(findEventId); transformEventId = Activity.startEvent('transform'); + // Prepend the module system polyfill to the top of dependencies + var dependencies = moduleSystem.concat(response.dependencies); + let bar; if (process.stdout.isTTY) { bar = new ProgressBar('transforming [:bar] :percent :current/:total', { complete: '=', incomplete: ' ', width: 40, - total: response.dependencies.length, + total: dependencies.length, }); } bbundle.setMainModuleId(response.mainModuleId); return Promise.all( - response.dependencies.map( + dependencies.map( module => this._transformModule( bbundle, response, diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index f5858921..bf6363c5 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -79,27 +79,15 @@ describe('Resolver', function() { expect(result.mainModuleId).toEqual('index'); 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, - 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'] + dependencies: [] }, { id: 'polyfills/console.js', isPolyfill: true, path: 'polyfills/console.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js' ], }, @@ -107,8 +95,6 @@ describe('Resolver', function() { isPolyfill: true, path: 'polyfills/error-guard.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js' ], @@ -117,8 +103,6 @@ describe('Resolver', function() { 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' @@ -128,8 +112,6 @@ describe('Resolver', function() { 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', @@ -140,8 +122,6 @@ describe('Resolver', function() { isPolyfill: true, path: 'polyfills/Array.es6.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', @@ -153,8 +133,6 @@ describe('Resolver', function() { isPolyfill: true, path: 'polyfills/babelHelpers.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', @@ -218,8 +196,6 @@ describe('Resolver', function() { id: 'some module', isPolyfill: true, dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 60820faa..a4e546e0 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -99,7 +99,7 @@ class Resolver { return this._depGraph.getDependencies(main, opts.platform).then( resolutionResponse => { - this._getPolyfillDependencies(opts.dev).reverse().forEach( + this._getPolyfillDependencies().reverse().forEach( polyfill => resolutionResponse.prependDependency(polyfill) ); @@ -108,12 +108,28 @@ class Resolver { ); } - _getPolyfillDependencies(isDev) { - const polyfillModuleNames = [ - isDev + getModuleSystemDependencies(options) { + const opts = getDependenciesValidateOpts(options); + + const prelude = opts.dev ? path.join(__dirname, 'polyfills/prelude_dev.js') - : path.join(__dirname, 'polyfills/prelude.js'), - path.join(__dirname, 'polyfills/require.js'), + : path.join(__dirname, 'polyfills/prelude.js'); + + const moduleSystem = path.join(__dirname, 'polyfills/require.js'); + + return [ + prelude, + moduleSystem + ].map(moduleName => new Polyfill({ + path: moduleName, + id: moduleName, + dependencies: [], + isPolyfill: true, + })); + } + + _getPolyfillDependencies() { + const polyfillModuleNames = [ path.join(__dirname, 'polyfills/polyfills.js'), path.join(__dirname, 'polyfills/console.js'), path.join(__dirname, 'polyfills/error-guard.js'), From 882707ed13d91332dc100ebbdb290d65dbe89e57 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 26 Nov 2015 05:33:38 -0800 Subject: [PATCH 457/936] =?UTF-8?q?Don=E2=80=99t=20error=20for=20known=20f?= =?UTF-8?q?iles=20that=20are=20reported=20as=20=E2=80=9Cadded=E2=80=9D=20b?= =?UTF-8?q?y=20watchman?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed By: tadeuzagallo Differential Revision: D2696636 fb-gh-sync-id: 89f90aba2a22d9c3e93632df4c4bc01791525833 --- .../DependencyGraph/HasteMap.js | 6 ++-- .../__tests__/DependencyGraph-test.js | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js index e1626fef..38d27e3e 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -7,7 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; - const path = require('path'); const getPlatformExtension = require('../lib/getPlatformExtension'); const Promise = require('promise'); @@ -118,11 +117,12 @@ class HasteMap { const moduleMap = this._map[name]; const modulePlatform = getPlatformExtension(mod.path) || GENERIC_PLATFORM; + const existingModule = moduleMap[modulePlatform]; - if (moduleMap[modulePlatform]) { + if (existingModule && existingModule.path !== mod.path) { throw new Error( `Naming collision detected: ${mod.path} ` + - `collides with ${moduleMap[modulePlatform].path}` + `collides with ${existingModule.path}` ); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index a89ce97a..d830cc82 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -3821,6 +3821,36 @@ describe('DependencyGraph', function() { }); }); }); + + pit('should not error when the watcher reports a known file as added', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'var b = require("b");', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', + 'module.exports = function() {};', + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { + triggerFileChange('add', 'index.js', root, mockStat); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js'); + }); + }); }); describe('getAsyncDependencies', () => { From efdc03785924a232f777d8cd485aa39430a4bc62 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 26 Nov 2015 10:07:12 -0800 Subject: [PATCH 458/936] =?UTF-8?q?Don=E2=80=99t=20do=20manual=20connectio?= =?UTF-8?q?n=20counting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed By: tadeuzagallo Differential Revision: D2696017 fb-gh-sync-id: 039abf9b7ed1570590e96e7077bb48a5493e8471 --- .../src/SocketInterface/SocketServer.js | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/react-packager/src/SocketInterface/SocketServer.js b/react-packager/src/SocketInterface/SocketServer.js index 56562e27..2e533527 100644 --- a/react-packager/src/SocketInterface/SocketServer.js +++ b/react-packager/src/SocketInterface/SocketServer.js @@ -52,13 +52,11 @@ class SocketServer { setImmediate(() => process.exit(1)); }); - this._numConnections = 0; this._server.on('connection', (sock) => this._handleConnection(sock)); // Disable the file watcher. options.nonPersistent = true; this._packagerServer = new Server(options); - this._jobs = 0; this._dieEventually(MAX_STARTUP_TIME); } @@ -68,8 +66,6 @@ class SocketServer { _handleConnection(sock) { debug('connection to server', process.pid); - this._numConnections++; - sock.on('close', () => this._numConnections--); const bunser = new bser.BunserBuf(); sock.on('data', (buf) => bunser.append(buf)); @@ -95,7 +91,6 @@ class SocketServer { const handleError = (error) => { debug('request error', error); - this._jobs--; this._reply(sock, m.id, 'error', error.stack); // Fatal error from JSTransformer transform workers. @@ -106,7 +101,6 @@ class SocketServer { switch (m.type) { case 'getDependencies': - this._jobs++; this._packagerServer.getDependencies(m.data).then( ({ dependencies }) => this._reply(sock, m.id, 'result', dependencies), handleError, @@ -114,7 +108,6 @@ class SocketServer { break; case 'buildBundle': - this._jobs++; this._packagerServer.buildBundle(m.data).then( (result) => this._reply(sock, m.id, 'result', result), handleError, @@ -122,7 +115,6 @@ class SocketServer { break; case 'buildPrepackBundle': - this._jobs++; this._packagerServer.buildPrepackBundle(m.data).then( (result) => this._reply(sock, m.id, 'result', result), handleError, @@ -130,7 +122,6 @@ class SocketServer { break; case 'getOrderedDependencyPaths': - this._jobs++; this._packagerServer.getOrderedDependencyPaths(m.data).then( (dependencies) => this._reply(sock, m.id, 'result', dependencies), handleError, @@ -156,17 +147,19 @@ class SocketServer { // Debounce the kill timer to make sure all the bytes are sent through // the socket and the client has time to fully finish and disconnect. this._dieEventually(); - this._jobs--; } _dieEventually(delay = MAX_IDLE_TIME) { clearTimeout(this._deathTimer); this._deathTimer = setTimeout(() => { - if (this._jobs <= 0 && this._numConnections <= 0) { - debug('server dying', process.pid); - process.exit(); - } - this._dieEventually(); + this._server.getConnections((error, numConnections) => { + // error is passed when connection count is below 0 + if (error || numConnections <= 0) { + debug('server dying', process.pid); + process.exit(); + } + this._dieEventually(); + }); }, delay); } From df687d84427b602aac43e90c1b74711673f6e627 Mon Sep 17 00:00:00 2001 From: Bram Date: Fri, 27 Nov 2015 07:15:28 -0800 Subject: [PATCH 459/936] bugfix #3997: react-native bundle not working on windows 10 Summary: fixes https://github.com/facebook/react-native/issues/3997 the root cause is in Mon, 09 Nov 2015 13:22:47 GMT ReactNativePackager:SocketServer uncaught error Error: listen EACCES C:\Users\donald\AppData\Local\Temp\react-packager-9248a9803ac72b509b389b456696850d This means that the socket server cannot create the socket. cfr https://gist.github.com/domenic/2790533 the socket name is not a valid windows socket name. Closes https://github.com/facebook/react-native/pull/4071 Reviewed By: mkonicek Differential Revision: D2699546 Pulled By: davidaurelio fb-gh-sync-id: 6c6494c14c42bb17506b8559001419c9f85e91e3 --- react-packager/src/SocketInterface/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/react-packager/src/SocketInterface/index.js b/react-packager/src/SocketInterface/index.js index c56026ea..32e3688c 100644 --- a/react-packager/src/SocketInterface/index.js +++ b/react-packager/src/SocketInterface/index.js @@ -35,10 +35,17 @@ const SocketInterface = { } }); - const sockPath = path.join( + let sockPath = path.join( tmpdir, 'react-packager-' + hash.digest('hex') ); + if (process.platform === 'win32'){ + // on Windows, use a named pipe, convert sockPath into a valid pipe name + // based on https://gist.github.com/domenic/2790533 + sockPath = sockPath.replace(/^\//, '') + sockPath = sockPath.replace(/\//g, '-') + sockPath = '\\\\.\\pipe\\' + sockPath + } if (fs.existsSync(sockPath)) { var sock = net.connect(sockPath); From b428bebf569cb46c1d9ea6a0566d0caad022f60d Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 1 Dec 2015 07:42:44 -0800 Subject: [PATCH 460/936] Add unbundling to packager Reviewed By: tadeuzagallo Differential Revision: D2707409 fb-gh-sync-id: 30216c36066dae68d83622dba2d598e9dc0a29db --- react-packager/src/Bundler/Bundle.js | 39 ++++++++++ .../src/Bundler/__tests__/Bundler-test.js | 10 ++- react-packager/src/Bundler/index.js | 10 ++- .../DependencyGraph/ResolutionResponse.js | 2 + .../src/Resolver/__tests__/Resolver-test.js | 3 +- react-packager/src/Resolver/index.js | 27 ++++--- .../Resolver/polyfills/require-unbundle.js | 73 +++++++++++++++++++ .../src/Server/__tests__/Server-test.js | 4 + react-packager/src/Server/index.js | 4 + react-packager/src/lib/ModuleTransport.js | 2 + 10 files changed, 158 insertions(+), 16 deletions(-) create mode 100644 react-packager/src/Resolver/polyfills/require-unbundle.js diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index b6635be9..f94c8ff5 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -16,6 +16,14 @@ const Activity = require('../Activity'); const SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL='; +const minifyCode = code => + UglifyJS.minify(code, {fromString: true, ascii_only: true}).code; +const getCode = x => x.code; +const getMinifiedCode = x => minifyCode(x.code); +const getNameAndCode = ({name, code}) => ({name, code}); +const getNameAndMinifiedCode = + ({name, code}) => ({name, code: minifyCode(code)}); + class Bundle { constructor(sourceMapUrl) { this._finalized = false; @@ -24,6 +32,8 @@ class Bundle { this._sourceMap = false; this._sourceMapUrl = sourceMapUrl; this._shouldCombineSourceMaps = false; + this._numPrependedModules = 0; + this._numRequireCalls = 0; } setMainModuleId(moduleId) { @@ -48,6 +58,10 @@ class Bundle { return this._modules; } + setNumPrependedModules(n) { + this._numPrependedModules = n; + } + addAsset(asset) { this._assets.push(asset); } @@ -76,6 +90,7 @@ class Bundle { sourceCode: code, sourcePath: name + '.js', })); + this._numRequireCalls += 1; } _assertFinalized() { @@ -141,6 +156,26 @@ class Bundle { return source; } + getUnbundle({minify}) { + const allModules = this._modules.slice(); + const prependedModules = this._numPrependedModules; + const requireCalls = this._numRequireCalls; + + const modules = + allModules + .splice(prependedModules, allModules.length - requireCalls - prependedModules); + const startupCode = + allModules + .map(minify ? getMinifiedCode : getCode) + .join('\n'); + + return { + startupCode, + modules: + modules.map(minify ? getNameAndMinifiedCode : getNameAndCode) + }; + } + getMinifiedSourceAndMap(dev) { this._assertFinalized(); @@ -336,6 +371,8 @@ class Bundle { assets: this._assets, sourceMapUrl: this._sourceMapUrl, mainModuleId: this._mainModuleId, + numPrependedModules: this._numPrependedModules, + numRequireCalls: this._numRequireCalls, }; } @@ -345,6 +382,8 @@ class Bundle { bundle._assets = json.assets; bundle._modules = json.modules; bundle._sourceMapUrl = json.sourceMapUrl; + bundle._numPrependedModules = json.numPrependedModules; + bundle._numRequireCalls = json.numRequireCalls; Object.freeze(bundle._modules); Object.seal(bundle._modules); diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index 9ce3f7c4..ccdc8496 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -130,7 +130,10 @@ describe('Bundler', function() { }); wrapModule.mockImpl(function(response, module, code) { - return Promise.resolve('lol ' + code + ' lol'); + return module.getName().then(name => ({ + name, + code: 'lol ' + code + ' lol' + })); }); sizeOf.mockImpl(function(path, cb) { @@ -160,6 +163,7 @@ describe('Bundler', function() { sourceMapUrl: 'source_map_url', }).then(function(p) { expect(p.addModule.mock.calls[0][0]).toEqual({ + name: 'foo', code: 'lol transformed /root/foo.js lol', map: 'sourcemap /root/foo.js', sourceCode: 'source /root/foo.js', @@ -167,6 +171,7 @@ describe('Bundler', function() { }); expect(p.addModule.mock.calls[1][0]).toEqual({ + name: 'bar', code: 'lol transformed /root/bar.js lol', map: 'sourcemap /root/bar.js', sourceCode: 'source /root/bar.js', @@ -183,6 +188,7 @@ describe('Bundler', function() { }; expect(p.addModule.mock.calls[2][0]).toEqual({ + name: 'image!img', code: 'lol module.exports = ' + JSON.stringify(imgModule_DEPRECATED) + '; lol', @@ -212,6 +218,7 @@ describe('Bundler', function() { }; expect(p.addModule.mock.calls[3][0]).toEqual({ + name: 'new_image.png', code: 'lol module.exports = require("AssetRegistry").registerAsset(' + JSON.stringify(imgModule) + '); lol', @@ -224,6 +231,7 @@ describe('Bundler', function() { }); expect(p.addModule.mock.calls[4][0]).toEqual({ + name: 'package/file.json', code: 'lol module.exports = {"json":true}; lol', sourceCode: 'module.exports = {"json":true};', sourcePath: '/root/file.json', diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index e097667f..ffe17735 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -140,6 +140,7 @@ class Bundler { sourceMapUrl, dev: isDev, platform, + unbundle: isUnbundle, }) { // Const cannot have the same name as the method (babel/babel#2834) const bbundle = new Bundle(sourceMapUrl); @@ -147,7 +148,7 @@ class Bundler { let transformEventId; const moduleSystem = this._resolver.getModuleSystemDependencies( - { dev: isDev, platform } + { dev: isDev, platform, isUnbundle } ); return this.getDependencies(entryFile, isDev, platform).then((response) => { @@ -168,6 +169,8 @@ class Bundler { } bbundle.setMainModuleId(response.mainModuleId); + bbundle.setNumPrependedModules( + response.numPrependedDependencies + moduleSystem.length); return Promise.all( dependencies.map( module => this._transformModule( @@ -317,8 +320,9 @@ class Bundler { module, transformed.code ).then( - code => new ModuleTransport({ - code: code, + ({code, name}) => new ModuleTransport({ + code, + name, map: transformed.map, sourceCode: transformed.sourceCode, sourcePath: transformed.sourcePath, diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js index aa454a9f..7fbafde8 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js @@ -14,6 +14,7 @@ class ResolutionResponse { this.asyncDependencies = []; this.mainModuleId = null; this.mocks = null; + this.numPrependedDependencies = 0; this._mappings = Object.create(null); this._finalized = false; } @@ -50,6 +51,7 @@ class ResolutionResponse { prependDependency(module) { this._assertNotFinalized(); this.dependencies.unshift(module); + this.numPrependedDependencies += 1; } pushAsyncDependency(dependency) { diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index bf6363c5..1de9905b 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -627,7 +627,8 @@ describe('Resolver', function() { createModule('test module', ['x', 'y']), code ).then(processedCode => { - expect(processedCode).toEqual([ + expect(processedCode.name).toEqual('test module'); + expect(processedCode.code).toEqual([ '__d(\'test module\',function(global, require,' + ' module, exports) { ' + // single line import diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index a4e546e0..8e8dd315 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -60,6 +60,10 @@ const getDependenciesValidateOpts = declareOpts({ type: 'string', required: false, }, + isUnbundle: { + type: 'boolean', + default: false + }, }); class Resolver { @@ -115,7 +119,9 @@ class Resolver { ? path.join(__dirname, 'polyfills/prelude_dev.js') : path.join(__dirname, 'polyfills/prelude.js'); - const moduleSystem = path.join(__dirname, 'polyfills/require.js'); + const moduleSystem = opts.isUnbundle + ? path.join(__dirname, 'polyfills/require-unbundle.js') + : path.join(__dirname, 'polyfills/require.js'); return [ prelude, @@ -152,7 +158,7 @@ class Resolver { wrapModule(resolutionResponse, module, code) { return Promise.resolve().then(() => { if (module.isPolyfill()) { - return Promise.resolve(code); + return Promise.resolve({code}); } const resolvedDeps = Object.create(null); @@ -179,14 +185,13 @@ class Resolver { } }; - return module.getName().then( - name => defineModuleCode({ - code: code.replace(replacePatterns.IMPORT_RE, relativizeCode) - .replace(replacePatterns.EXPORT_RE, relativizeCode) - .replace(replacePatterns.REQUIRE_RE, relativizeCode), - moduleName: name, - }) - ); + code = code + .replace(replacePatterns.IMPORT_RE, relativizeCode) + .replace(replacePatterns.EXPORT_RE, relativizeCode) + .replace(replacePatterns.REQUIRE_RE, relativizeCode); + + return module.getName().then(name => + ({name, code: defineModuleCode(name, code)})); }); }); } @@ -197,7 +202,7 @@ class Resolver { } -function defineModuleCode({moduleName, code}) { +function defineModuleCode(moduleName, code) { return [ `__d(`, `'${moduleName}',`, diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js new file mode 100644 index 00000000..1ea71705 --- /dev/null +++ b/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -0,0 +1,73 @@ +'use strict'; + +((global) => { + const {ErrorUtils, __nativeRequire} = global; + global.require = require; + global.__d = define; + + const modules = Object.create(null); + + const loadModule = ErrorUtils ? + guardedLoadModule : loadModuleImplementation; + + function define(moduleId, factory) { + modules[moduleId] = { + factory, + hasError: false, + exports: undefined, + }; + } + + function require(moduleId) { + const module = modules[moduleId]; + return module && module.exports || loadModule(moduleId, module); + } + + function guardedLoadModule(moduleId, module) { + try { + return loadModuleImplementation(moduleId, module); + } catch (e) { + ErrorUtils.reportFatalError(e); + } + } + + function loadModuleImplementation(moduleId, module) { + if (!module) { + __nativeRequire(moduleId); + module = modules[moduleId]; + } + + if (!module) { + throw unknownModuleError(moduleId); + } + + if (module.hasError) { + throw moduleThrewError(moduleId); + } + + const exports = module.exports = {}; + const {factory} = module; + try { + const moduleObject = {exports}; + factory(global, require, moduleObject, exports); + return (module.exports = moduleObject.exports); + } catch(e) { + module.hasError = true; + module.exports = undefined; + } + } + + function unknownModuleError(id) { + let message = 'Requiring unknown module "' + id + '".'; + if (__DEV__) { + message += + 'If you are sure the module is there, try restarting the packager.'; + } + return Error(message); + } + + function moduleThrewError(id) { + return Error('Requiring module "' + id + '", which threw an exception.'); + } + +})(this); diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index d68e0410..b8fee5fa 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -115,6 +115,7 @@ describe('processRequest', () => { dev: true, platform: undefined, runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + unbundle: false, }); }); }); @@ -134,6 +135,7 @@ describe('processRequest', () => { dev: true, platform: 'ios', runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + unbundle: false, }); }); }); @@ -274,6 +276,7 @@ describe('processRequest', () => { dev: true, platform: undefined, runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + unbundle: false, }) ); }); @@ -292,6 +295,7 @@ describe('processRequest', () => { dev: false, platform: undefined, runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + unbundle: false, }) ); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 28c5b4d1..b2ca6d73 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -102,6 +102,10 @@ const bundleOpts = declareOpts({ 'InitializeJavaScriptAppEngine' ], }, + unbundle: { + type: 'boolean', + default: false, + } }); const dependencyOpts = declareOpts({ diff --git a/react-packager/src/lib/ModuleTransport.js b/react-packager/src/lib/ModuleTransport.js index a5f1d568..afb660d3 100644 --- a/react-packager/src/lib/ModuleTransport.js +++ b/react-packager/src/lib/ModuleTransport.js @@ -9,6 +9,8 @@ 'use strict'; function ModuleTransport(data) { + this.name = data.name; + assertExists(data, 'code'); this.code = data.code; From d6dc12e551c39cf38bbadbb08cdbe58c3c90f307 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 2 Dec 2015 03:10:42 -0800 Subject: [PATCH 461/936] Add for-of transform to babel configurations Summary: Adds babel-plugin-transform-es2015-for-of to babel configuration. public Reviewed By: tadeuzagallo Differential Revision: D2712220 fb-gh-sync-id: cc6dd9e6e70946607ef9bb9f659eb628cf2eb555 --- react-packager/.babelrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/react-packager/.babelrc b/react-packager/.babelrc index c72259da..e1c61b11 100644 --- a/react-packager/.babelrc +++ b/react-packager/.babelrc @@ -23,7 +23,8 @@ "transform-object-rest-spread", "transform-react-display-name", "transform-react-jsx", - "transform-regenerator" + "transform-regenerator", + "transform-es2015-for-of" ], "sourceMaps": false } From c68fa4b4d89936692779e9c76ca70fab675af2f0 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 2 Dec 2015 18:57:20 -0800 Subject: [PATCH 462/936] RN: Support Multiple Packager Servers Summary: Adds support for multiple packager servers, which could be used to hot-reload dependencies in response to a change in the `cacheVersion`. Currently, there is no way to: - Create multiple `ReactPackager` servers. - Instantiate more than one `FileWatcher` constructor (due to the "single instance" invariant). public Reviewed By: martinbigio Differential Revision: D2713455 fb-gh-sync-id: 9be0f0cb2b846baf088d0cf14650cc8b9e950815 --- react-packager/index.js | 1 + react-packager/src/FileWatcher/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/react-packager/index.js b/react-packager/index.js index e1a294f4..5b28e774 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -16,6 +16,7 @@ var debug = require('debug'); var omit = require('underscore').omit; var Activity = require('./src/Activity'); +exports.createServer = createServer; exports.middleware = function(options) { var server = createServer(options); return server.processRequest.bind(server); diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index d9ed7f32..e32c3696 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -72,6 +72,7 @@ class FileWatcher extends EventEmitter { } end() { + inited = false; return this._loading.then( (watchers) => watchers.map( watcher => Promise.denodeify(watcher.close).call(watcher) From 5ea0b333e71c1403e0f78b1cdd5e02e396fb5b16 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Thu, 3 Dec 2015 07:23:58 -0800 Subject: [PATCH 463/936] Append JS_require_ to require profile names Reviewed By: tadeuzagallo Differential Revision: D2717939 fb-gh-sync-id: 4648e240eebfb3a7bc1c5041d4f1fba8761264a9 --- react-packager/src/Resolver/polyfills/require.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index d7550ba1..ebaa8c40 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -54,7 +54,7 @@ // require cycles inside the factory from causing an infinite require loop. mod.isInitialized = true; - __DEV__ && BridgeProfiling().profile(id); + __DEV__ && BridgeProfiling().profile('JS_require_' + id); // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js From b025a8d9460edf2297a9f69495a68a90d1a2e2b5 Mon Sep 17 00:00:00 2001 From: James Ide Date: Fri, 4 Dec 2015 06:43:03 -0800 Subject: [PATCH 464/936] Support plugins that conform to ES6 modules Summary: ES6 modules export an object with a property called `default`. See Babel itself for how this is handled: https://github.com/babel/babel/commit/b5b7e346a04c99da8793e2c65cc3b3c7c720253d Closes https://github.com/facebook/react-native/pull/4513 Reviewed By: svcscm Differential Revision: D2715512 Pulled By: mkonicek fb-gh-sync-id: 40e5ea35adcdb66806a4895578d637cd72538619 --- transformer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/transformer.js b/transformer.js index 4700407c..70dde770 100644 --- a/transformer.js +++ b/transformer.js @@ -48,6 +48,7 @@ function transform(src, filename, options) { // Only resolve the plugin if it's a string reference. if (typeof plugin[0] === 'string') { plugin[0] = require(`babel-plugin-${plugin[0]}`); + plugin[0] = plugin[0].__esModule ? plugin[0].default : plugin[0]; } return plugin; }); From 1e8e4dc2235401d95f8c84e71cb3c4737034dee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 8 Dec 2015 12:23:41 -0800 Subject: [PATCH 465/936] Introduce `loadBundles` Summary: public Introduce a new Polyfill module to load bundles. This polyfill contains the function to which System.import gets transformed into. It works similarly to require in the way it's registered. It keeps track of the bundles that have ever been requested using promises, either fulfilled or not. The use of promises makes the implementation quite easy as we don't need to differenciate whether the request has been started or not. There're a couple of follow up steps that still need to get done: - Included this polyfill on the ones we automatically include on the bundle. - Modify the transform to include the modules the user is actually requesting and pipe that through loadBundles so that the promise, once resolved, has the ordered list of modules the user requested. - Implement the actual native code that loads a bundle. This shouldn't be that taught as native will be able to assume it will never receive the same request twice. Reviewed By: davidaurelio Differential Revision: D2727241 fb-gh-sync-id: 317d80754783caf43f10c71a34a4558a4d298d45 --- .../polyfills/__tests__/loadBundles-test.js | 64 +++++++++++++++++++ .../src/Resolver/polyfills/loadBundles.js | 50 +++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js create mode 100644 react-packager/src/Resolver/polyfills/loadBundles.js diff --git a/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js b/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js new file mode 100644 index 00000000..ad0dcf27 --- /dev/null +++ b/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js @@ -0,0 +1,64 @@ +/** + * 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('../loadBundles'); +jest.mock('NativeModules'); + +let loadBundles; +let loadBundlesCalls; + +describe('loadBundles', () => { + beforeEach(() => { + loadBundles = jest.genMockFunction(); + loadBundlesCalls = loadBundles.mock.calls; + require('NativeModules').RCTBundlesLoader = {loadBundles}; + + require('../loadBundles'); + }); + + it('should set `global.__loadBundles` function when polyfill is initialized', () => { + expect(typeof global.__loadBundles).toBe('function'); + }); + + it('should return a promise', () => { + loadBundles.mockImpl((bundles, callback) => callback()); + expect(global.__loadBundles(['bundle.0']) instanceof Promise).toBeTruthy(); + }); + + pit('shouldn\'t request already loaded bundles', () => { + loadBundles.mockImpl((bundles, callback) => callback()); + return global + .__loadBundles(['bundle.0']) + .then(() => global.__loadBundles(['bundle.0'])) + .then(() => expect(loadBundlesCalls.length).toBe(1)); + }); + + pit('shouldn\'n request inflight bundles', () => { + loadBundles.mockImpl((bundles, callback) => { + if (bundles.length === 1 && bundles[0] === 'bundle.0') { + setTimeout(callback, 1000); + } else if (bundles.length === 1 && bundles[0] === 'bundle.1') { + setTimeout(callback, 500); + } + }); + + const promises = Promise.all([ + global.__loadBundles(['bundle.0']), + global.__loadBundles(['bundle.0', 'bundle.1']), + ]).then(() => { + expect(loadBundlesCalls.length).toBe(2); + expect(loadBundlesCalls[0][0][0]).toBe('bundle.0'); + expect(loadBundlesCalls[1][0][0]).toBe('bundle.1'); + }); + + jest.runAllTimers(); + return promises; + }); +}); diff --git a/react-packager/src/Resolver/polyfills/loadBundles.js b/react-packager/src/Resolver/polyfills/loadBundles.js new file mode 100644 index 00000000..551d4cac --- /dev/null +++ b/react-packager/src/Resolver/polyfills/loadBundles.js @@ -0,0 +1,50 @@ +/* eslint global-strict:0 */ +(global => { + let loadBundlesOnNative = (bundles) => + new Promise((_, resolve) => + require('NativeModules').RCTBundlesLoader.loadBundles(bundles, resolve)); + + let requestedBundles = Object.create(null); + + /** + * Returns a promise that is fulfilled once all the indicated bundles are + * loaded into memory and injected into the JS engine. + * This invokation might need to go through the bridge + * and run native code to load some, if not all, the requested bundles. + * If all the bundles have already been loaded, the promise is resolved + * immediately. Otherwise, we'll determine which bundles still need to get + * loaded considering both, the ones already loaded, and the ones being + * currently asynchronously loaded by other invocations to `__loadBundles`, + * and return a promise that will get fulfilled once all these are finally + * loaded. + * + * Note this function should only be invoked by generated code. + */ + global.__loadBundles = function(bundles) { + // split bundles by whether they've already been requested or not + const bundlesToRequest = bundles.filter(b => !requestedBundles[b]); + const bundlesAlreadyRequested = bundles.filter(b => !!requestedBundles[b]); + + // keep a reference to the promise loading each bundle + if (bundlesToRequest.length > 0) { + const nativePromise = loadBundlesOnNative(bundlesToRequest); + bundlesToRequest.forEach(b => requestedBundles[b] = nativePromise); + } + + return Promise.all(bundles.map(bundle => requestedBundles[bundle])); + }; +})(global); + + +// turns a callback async based function into a promised based one +function promisify(fn) { + return function() { + var self = this; + var args = Array.prototype.slice.call(arguments); + + return new Promise((resolve, reject) => { + args.push(resolve); + fn.apply(self, args); + }); + }; +} From da0ad0ce4c12b7a670f1bf29fa34f088d8d90947 Mon Sep 17 00:00:00 2001 From: Brian Leonard Date: Wed, 9 Dec 2015 04:39:36 -0800 Subject: [PATCH 466/936] Allow other Xcode configurations with DEV=false Summary: Updates build script to fix https://github.com/facebook/react-native/issues/4362 Closes https://github.com/facebook/react-native/pull/4520 Reviewed By: svcscm Differential Revision: D2730607 Pulled By: mkonicek fb-gh-sync-id: 2fc4a9815f19ab867a6a3dc77cf1b6eac8a25616 --- react-native-xcode.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index dfa413b8..5c88fdad 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -14,16 +14,12 @@ case "$CONFIGURATION" in Debug) DEV=true ;; - Release) - DEV=false - ;; "") echo "$0 must be invoked by Xcode" exit 1 ;; *) - echo "Unsupported value of \$CONFIGURATION=$CONFIGURATION" - exit 1 + DEV=false ;; esac From 8766f727b372fac88c5d1a1b82290d81c5862d1a Mon Sep 17 00:00:00 2001 From: Thomas Parslow Date: Wed, 9 Dec 2015 12:58:52 -0800 Subject: [PATCH 467/936] Add taggedTemplateLiteral to babelHelpers Summary: Needed to support tagged template literals (which are already enabled in babel). Not having this helper means we get a runtime crash. Closes https://github.com/facebook/react-native/pull/4680 Reviewed By: svcscm Differential Revision: D2740233 Pulled By: spicyj fb-gh-sync-id: 12729f670b7942ad7a04bd50ae1eca35d2b1e410 --- react-packager/src/Resolver/polyfills/babelHelpers.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/polyfills/babelHelpers.js b/react-packager/src/Resolver/polyfills/babelHelpers.js index a7fac6bc..e028a782 100644 --- a/react-packager/src/Resolver/polyfills/babelHelpers.js +++ b/react-packager/src/Resolver/polyfills/babelHelpers.js @@ -11,7 +11,7 @@ /* eslint-disable strict */ // Created by running: -// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray toConsumableArray'.split(' ')) +// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray taggedTemplateLiteral toConsumableArray '.split(' ')) // then replacing the `global` reference in the last line to also use `this`. // // actually, that's a lie, because babel6 omits _extends and createRawReactElement @@ -207,6 +207,14 @@ } }; })(); + + babelHelpers.taggedTemplateLiteral = function (strings, raw) { + return Object.freeze(Object.defineProperties(strings, { + raw: { + value: Object.freeze(raw) + } + })); + }; babelHelpers.toConsumableArray = function (arr) { if (Array.isArray(arr)) { From 8bc99d9a53b6027ac9f90ec501f45be29f139b23 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 9 Dec 2015 14:06:16 -0800 Subject: [PATCH 468/936] Fix asset httpServerLocation on Windows. Summary: Functions from the path module return paths with the native file system path separator. generateAssetModule is using path.join and path.dirname to generate httpServerLocation. That means that on Windows systems, the generated URL path has backslashes rather than forward slashes which breaks the app's ability to retrieve image assets using require('./image.png'). This change fixes this by checking path.sep and replacing backslashes with slashes on systems that require it. Closes https://github.com/facebook/react-native/pull/4416 Reviewed By: svcscm Differential Revision: D2740529 Pulled By: mkonicek fb-gh-sync-id: edae0f6762c7dc1db7af078209e38a2feab1e0a1 --- react-packager/src/Bundler/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index ffe17735..470c6821 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -364,6 +364,12 @@ class Bundler { generateAssetModule(bundle, module, platform = null) { const relPath = getPathRelativeToRoot(this._projectRoots, module.path); + var assetUrlPath = path.join('/assets', path.dirname(relPath)); + + // On Windows, change backslashes to slashes to get proper URL path from file path. + if (path.sep === '\\') { + assetUrlPath = assetUrlPath.replace(/\\/g, '/'); + } return Promise.all([ sizeOf(module.path), @@ -374,7 +380,7 @@ class Bundler { const img = { __packager_asset: true, fileSystemLocation: path.dirname(module.path), - httpServerLocation: path.join('/assets', path.dirname(relPath)), + httpServerLocation: assetUrlPath, width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, scales: assetData.scales, From ff3cac5436d4da94e81b55a23157f0373753bb37 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Thu, 10 Dec 2015 17:58:30 -0800 Subject: [PATCH 469/936] Update node-haste from upstream Reviewed By: zpao Differential Revision: D2747183 fb-gh-sync-id: f1b963b19cb6ea16945b16370d1bee26110bb329 --- .../__tests__/DependencyGraph-test.js | 74 +++++++++---------- .../DependencyGraph/docblock.js | 2 +- .../src/DependencyResolver/Package.js | 2 +- .../src/DependencyResolver/replacePatterns.js | 15 ---- 4 files changed, 39 insertions(+), 54 deletions(-) delete mode 100644 react-packager/src/DependencyResolver/replacePatterns.js diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index d830cc82..8b50c8b4 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -1362,19 +1362,19 @@ describe('DependencyGraph', function() { }); }); - testBrowserField('browser') - testBrowserField('react-native') + testBrowserField('browser'); + testBrowserField('react-native'); - function replaceBrowserField (json, fieldName) { + function replaceBrowserField(json, fieldName) { if (fieldName !== 'browser') { - json[fieldName] = json.browser - delete json.browser + json[fieldName] = json.browser; + delete json.browser; } - return json + return json; } - function testBrowserField (fieldName) { + function testBrowserField(fieldName) { pit('should support simple browser field in packages ("' + fieldName + '")', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -1393,8 +1393,8 @@ describe('DependencyGraph', function() { }, fieldName)), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1448,8 +1448,8 @@ describe('DependencyGraph', function() { }, fieldName)), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1503,8 +1503,8 @@ describe('DependencyGraph', function() { }, fieldName)), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1559,8 +1559,8 @@ describe('DependencyGraph', function() { }, fieldName)), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1630,15 +1630,15 @@ describe('DependencyGraph', function() { }, 'hello.js': 'hello', 'bye.js': 'bye', - } - } + }, + }, }); - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + const dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { id: 'index', @@ -1716,7 +1716,7 @@ describe('DependencyGraph', function() { name: 'aPackage', browser: { 'node-package': 'browser-package', - } + }, }, fieldName)), 'index.js': 'require("node-package")', 'node-package': { @@ -1731,8 +1731,8 @@ describe('DependencyGraph', function() { }), 'index.js': 'some browser code', }, - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1788,7 +1788,7 @@ describe('DependencyGraph', function() { name: 'aPackage', browser: { 'node-package': 'browser-package', - } + }, }, fieldName)), 'index.js': 'require("node-package")', 'node-package': { @@ -1803,8 +1803,8 @@ describe('DependencyGraph', function() { }), 'index.js': 'some browser code', }, - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1861,13 +1861,13 @@ describe('DependencyGraph', function() { name: 'aPackage', 'react-native': { 'node-package': 'rn-package', - } + }, }), 'index.js': 'require("node-package")', 'node_modules': { 'node-package': { 'package.json': JSON.stringify({ - 'name': 'node-package' + 'name': 'node-package', }), 'index.js': 'some node code', }, @@ -1875,8 +1875,8 @@ describe('DependencyGraph', function() { 'package.json': JSON.stringify({ 'name': 'rn-package', browser: { - 'nested-package': 'nested-browser-package' - } + 'nested-package': 'nested-browser-package', + }, }), 'index.js': 'require("nested-package")', }, @@ -1884,11 +1884,11 @@ describe('DependencyGraph', function() { 'package.json': JSON.stringify({ 'name': 'nested-browser-package', }), - 'index.js': 'some code' - } - } - } - } + 'index.js': 'some code', + }, + }, + }, + }, }); var dgraph = new DependencyGraph({ diff --git a/react-packager/src/DependencyResolver/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/DependencyGraph/docblock.js index 131f09d1..d710112a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/docblock.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/docblock.js @@ -25,7 +25,7 @@ function extract(contents) { } -var commentStartRe = /^\/\*\*?/; +var commentStartRe = /^\/\*\*/; var commentEndRe = /\*\/$/; var wsRe = /[\t ]+/g; var stringStartRe = /(\r?\n|^) *\*/g; diff --git a/react-packager/src/DependencyResolver/Package.js b/react-packager/src/DependencyResolver/Package.js index 2117e3f1..ac7ee88c 100644 --- a/react-packager/src/DependencyResolver/Package.js +++ b/react-packager/src/DependencyResolver/Package.js @@ -15,7 +15,7 @@ class Package { getMain() { return this._read().then(json => { - var replacements = getReplacements(json) + var replacements = getReplacements(json); if (typeof replacements === 'string') { return path.join(this.root, replacements); } diff --git a/react-packager/src/DependencyResolver/replacePatterns.js b/react-packager/src/DependencyResolver/replacePatterns.js deleted file mode 100644 index a4e563d2..00000000 --- a/react-packager/src/DependencyResolver/replacePatterns.js +++ /dev/null @@ -1,15 +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'; - -exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; -exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; -exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; -exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; From 2927b769935b224d8a78dd8fb92f4bd29ff7f736 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 11 Dec 2015 03:49:15 -0800 Subject: [PATCH 470/936] Rename BridgeProfiling to Systrace for consistency Summary: public Rename the `BridgeProfiling` JS module to `Systrace`, since it's actually just an API to Systrace markers. This should make it clearer as we add more perf tooling. Reviewed By: jspahrsummers Differential Revision: D2734001 fb-gh-sync-id: 642848fa7340c545067f2a7cf5cef8af1c8a69a2 --- react-packager/src/Resolver/polyfills/require.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index ebaa8c40..506d38e0 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -54,13 +54,13 @@ // require cycles inside the factory from causing an infinite require loop. mod.isInitialized = true; - __DEV__ && BridgeProfiling().profile('JS_require_' + id); + __DEV__ && Systrace().beginEvent('JS_require_' + id); // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js mod.factory.call(global, global, require, mod.module, mod.module.exports); - __DEV__ && BridgeProfiling().profileEnd(); + __DEV__ && Systrace().endEvent(); } catch (e) { mod.hasError = true; mod.isInitialized = false; @@ -70,14 +70,14 @@ return mod.module.exports; } - const BridgeProfiling = __DEV__ && (() => { - var _BridgeProfiling; + const Systrace = __DEV__ && (() => { + var _Systrace; try { - _BridgeProfiling = require('BridgeProfiling'); + _Systrace = require('Systrace'); } catch(e) {} - return _BridgeProfiling && _BridgeProfiling.profile ? - _BridgeProfiling : { profile: () => {}, profileEnd: () => {} }; + return _Systrace && _Systrace.beginEvent ? + _Systrace : { beginEvent: () => {}, endEvent: () => {} }; }); global.__d = define; From ab092485400a0ebce642aba442758cd3be632a44 Mon Sep 17 00:00:00 2001 From: Billy Lamberta Date: Sat, 12 Dec 2015 13:06:14 -0800 Subject: [PATCH 471/936] Add the npm global prefix to PATH. Summary: Related to issue https://github.com/facebook/react-native/issues/4034#issuecomment-164134543. The npm global install path may be set to a non-standard location. `react-native` is assumed to be in `PATH` but doesn't take into account the user's environment so building in Xcode fails: ~~~ react-native bundle --entry-file index.ios.js --platform ios --dev true --bundle-output [...] ../node_modules/react-native/packager/react-native-xcode.sh: line 48: react-native: command not found Command /bin/sh failed with exit code 127 ~~~ This fix uses `npm prefix -g` to get the bin location and adds it to `PATH`. Closes https://github.com/facebook/react-native/pull/4749 Reviewed By: svcscm Differential Revision: D2753215 Pulled By: androidtrunkagent fb-gh-sync-id: 964d1a71ac1bf204545a594a9fa433a7bc367f35 --- react-native-xcode.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index 5c88fdad..b0206be7 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -29,6 +29,9 @@ cd .. set -x DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH +# npm global install path may be a non-standard location +PATH="$(npm prefix -g)/bin:$PATH" + # Define NVM_DIR and source the nvm.sh setup script [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" From 26102c39341d27a22e8a57ecc078b7ac81c32839 Mon Sep 17 00:00:00 2001 From: WanderWang Date: Sat, 12 Dec 2015 17:26:22 -0800 Subject: [PATCH 472/936] improve exception message when we can't find a file Summary: in ```fastfs.js ``` when ```getFile()``` got a exception it will print ``` Unable to find file with path: null ``` in terminal . It's confused Closes https://github.com/facebook/react-native/pull/4737 Reviewed By: svcscm Differential Revision: D2752888 Pulled By: androidtrunkagent fb-gh-sync-id: a366da1eea27c691248dcb17019f4462a639ea70 --- react-packager/src/DependencyResolver/fastfs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 17738744..3a6c3ee1 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -109,7 +109,7 @@ class Fastfs extends EventEmitter { readFile(filePath) { const file = this._getFile(filePath); if (!file) { - throw new Error(`Unable to find file with path: ${file}`); + throw new Error(`Unable to find file with path: ${filePath}`); } return file.read(); } From ae4ea67fa6ff96140de88f922f0c9754109c8921 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 14 Dec 2015 11:12:54 -0800 Subject: [PATCH 473/936] Move FileWatcher into node-haste Reviewed By: davidaurelio Differential Revision: D2752711 fb-gh-sync-id: e656a3019b95c7677d5b27e74dc921ef62ba5c83 --- .../FileWatcher/__mocks__/sane.js | 2 +- .../FileWatcher/__tests__/FileWatcher-test.js | 23 +++++++++++++------ .../FileWatcher/index.js | 8 ++----- .../src/Server/__tests__/Server-test.js | 2 +- react-packager/src/Server/index.js | 2 +- 5 files changed, 21 insertions(+), 16 deletions(-) rename react-packager/src/{ => DependencyResolver}/FileWatcher/__mocks__/sane.js (98%) rename react-packager/src/{ => DependencyResolver}/FileWatcher/__tests__/FileWatcher-test.js (80%) rename react-packager/src/{ => DependencyResolver}/FileWatcher/index.js (94%) diff --git a/react-packager/src/FileWatcher/__mocks__/sane.js b/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js similarity index 98% rename from react-packager/src/FileWatcher/__mocks__/sane.js rename to react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js index 9823a930..2a36bb39 100644 --- a/react-packager/src/FileWatcher/__mocks__/sane.js +++ b/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js @@ -9,5 +9,5 @@ 'use strict'; module.exports = { - WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher') + WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher'), }; diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js similarity index 80% rename from react-packager/src/FileWatcher/__tests__/FileWatcher-test.js rename to react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js index 973c4f78..b0d2fd09 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js @@ -12,28 +12,37 @@ jest .dontMock('util') .dontMock('events') .dontMock('../') - .dontMock('q') .setMock('child_process', { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman'); - } + }, }); -var FileWatcher = require('../'); var sane = require('sane'); describe('FileWatcher', function() { var Watcher; + var FileWatcher; + var config; beforeEach(function() { Watcher = sane.WatchmanWatcher; Watcher.prototype.once.mockImplementation(function(type, callback) { callback(); }); + FileWatcher = require('../'); + + config = [{ + dir: 'rootDir', + globs: [ + '**/*.js', + '**/*.json', + ], + }]; }); pit('it should get the watcher instance when ready', function() { - var fileWatcher = new FileWatcher(['rootDir']); + var fileWatcher = new FileWatcher(config); return fileWatcher.getWatchers().then(function(watchers) { watchers.forEach(function(watcher) { expect(watcher instanceof Watcher).toBe(true); @@ -46,10 +55,10 @@ describe('FileWatcher', function() { Watcher.prototype.on.mockImplementation(function(type, callback) { cb = callback; }); - var fileWatcher = new FileWatcher(['rootDir']); + var fileWatcher = new FileWatcher(config); var handler = jest.genMockFn(); fileWatcher.on('all', handler); - return fileWatcher.getWatchers().then(function(){ + return fileWatcher.getWatchers().then(function() { cb(1, 2, 3, 4); jest.runAllTimers(); expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]); @@ -57,7 +66,7 @@ describe('FileWatcher', function() { }); pit('it should end the watcher', function() { - var fileWatcher = new FileWatcher(['rootDir']); + var fileWatcher = new FileWatcher(config); Watcher.prototype.close.mockImplementation(function(callback) { callback(); }); diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/DependencyResolver/FileWatcher/index.js similarity index 94% rename from react-packager/src/FileWatcher/index.js rename to react-packager/src/DependencyResolver/FileWatcher/index.js index e32c3696..227687e1 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/DependencyResolver/FileWatcher/index.js @@ -12,11 +12,10 @@ const EventEmitter = require('events').EventEmitter; const sane = require('sane'); const Promise = require('promise'); const exec = require('child_process').exec; -const _ = require('underscore'); const MAX_WAIT_TIME = 25000; -// TODO(amasad): can we use watchman version command instead?r +// TODO(amasad): can we use watchman version command instead? const detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { @@ -81,13 +80,10 @@ class FileWatcher extends EventEmitter { } static createDummyWatcher() { - const ev = new EventEmitter(); - _.extend(ev, { + return Object.assign(new EventEmitter(), { isWatchman: () => Promise.resolve(false), end: () => Promise.resolve(), }); - - return ev; } } diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index b8fee5fa..2fdb5e73 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -19,7 +19,7 @@ jest.setMock('worker-farm', function() { return () => {}; }) const Promise = require('promise'); var Bundler = require('../../Bundler'); -var FileWatcher = require('../../FileWatcher'); +var FileWatcher = require('../../DependencyResolver/FileWatcher'); var Server = require('../'); var Server = require('../../Server'); var AssetServer = require('../../AssetServer'); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index b2ca6d73..121dcddb 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -10,7 +10,7 @@ const Activity = require('../Activity'); const AssetServer = require('../AssetServer'); -const FileWatcher = require('../FileWatcher'); +const FileWatcher = require('../DependencyResolver/FileWatcher'); const getPlatformExtension = require('../DependencyResolver/lib/getPlatformExtension'); const Bundler = require('../Bundler'); const Promise = require('promise'); From 174c573c86eb88507f42727ce1ca01070aa3af84 Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 14 Dec 2015 12:28:34 -0800 Subject: [PATCH 474/936] Keep the original console as `global.originalConsole` Summary: If a console exists, keep the original as `global.originalConsole` before overwriting `global.console` with a polyfill. This matches what we do for XHR, fetch, and some other libraries. Closes https://github.com/facebook/react-native/pull/3322 Reviewed By: svcscm Differential Revision: D2755873 Pulled By: androidtrunkagent fb-gh-sync-id: 4c23f807b73b79cfa9fbbd4e2814d76eecabd596 --- .../src/Resolver/polyfills/console.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/console.js b/react-packager/src/Resolver/polyfills/console.js index 5461602b..6aa48550 100644 --- a/react-packager/src/Resolver/polyfills/console.js +++ b/react-packager/src/Resolver/polyfills/console.js @@ -366,8 +366,6 @@ }; function setupConsole(global) { - var originalConsole = global.console; - if (!global.nativeLoggingHook) { return; } @@ -461,7 +459,14 @@ global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.info); } - global.console = { + // Preserve the original `console` as `originalConsole` + var originalConsole = global.console; + var descriptor = Object.getOwnPropertyDescriptor(global, 'console'); + if (descriptor) { + Object.defineProperty(global, 'originalConsole', descriptor); + } + + var console = { error: getNativeLogFunction(LOG_LEVELS.error), info: getNativeLogFunction(LOG_LEVELS.info), log: getNativeLogFunction(LOG_LEVELS.info), @@ -469,17 +474,19 @@ trace: getNativeLogFunction(LOG_LEVELS.trace), table: consoleTablePolyfill }; + descriptor.value = console; + Object.defineProperty(global, 'console', descriptor); // If available, also call the original `console` method since that is // sometimes useful. Ex: on OS X, this will let you see rich output in // the Safari Web Inspector console. if (__DEV__ && originalConsole) { - Object.keys(global.console).forEach(methodName => { - var reactNativeMethod = global.console[methodName]; + Object.keys(console).forEach(methodName => { + var reactNativeMethod = console[methodName]; if (originalConsole[methodName]) { - global.console[methodName] = function() { + console[methodName] = function() { originalConsole[methodName](...arguments); - reactNativeMethod.apply(global.console, arguments); + reactNativeMethod.apply(console, arguments); }; } }); From 280cc2e80723898150d4897aa8753a49be8bd527 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 15 Dec 2015 07:45:41 -0800 Subject: [PATCH 475/936] Unbreak console reassignment on iOS7 Summary: public Unbreaks console assignment on iOS7 introduced in #3322 Reviewed By: alexeylang Differential Revision: D2759689 fb-gh-sync-id: 28cccfdf1123245732fa5ba0337ee8d7bb43c822 --- react-packager/src/Resolver/polyfills/console.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/console.js b/react-packager/src/Resolver/polyfills/console.js index 6aa48550..a8e5a72f 100644 --- a/react-packager/src/Resolver/polyfills/console.js +++ b/react-packager/src/Resolver/polyfills/console.js @@ -474,8 +474,14 @@ trace: getNativeLogFunction(LOG_LEVELS.trace), table: consoleTablePolyfill }; - descriptor.value = console; - Object.defineProperty(global, 'console', descriptor); + + // don't reassign to the original descriptor. breaks on ios7 + Object.defineProperty(global, 'console', { + value: console, + configurable: descriptor ? descriptor.configurable : true, + enumerable: descriptor ? descriptor.enumerable : true, + writable: descriptor ? descriptor.writable : true, + }); // If available, also call the original `console` method since that is // sometimes useful. Ex: on OS X, this will let you see rich output in From 3a5b8b5abe713298dfd943811c54c866b0f6fe4a Mon Sep 17 00:00:00 2001 From: Baris Sencan Date: Tue, 15 Dec 2015 18:34:19 -0800 Subject: [PATCH 476/936] [README] "find can more" -> "can find more" --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67bab1d9..83683939 100644 --- a/README.md +++ b/README.md @@ -144,5 +144,5 @@ is informed by React Native needs. ### Why didn't you use webpack? We love webpack, however, when we tried on our codebase it was slower -than our developers would like it to be. You find can more discussion about -the subject [here](https://github.com/facebook/react-native/issues/5) +than our developers would like it to be. You can find more discussion about +the subject [here](https://github.com/facebook/react-native/issues/5). From f14863dd9bb8fff8989c3f2248edfbdc86bb3d57 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 16 Dec 2015 01:47:47 -0800 Subject: [PATCH 477/936] RN: Install Updated `fbjs` Downstream Reviewed By: cpojer Differential Revision: D2754195 fb-gh-sync-id: 67b628fdfa34da379ab5fdad03b1baaf767c7f21 --- blacklist.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/blacklist.js b/blacklist.js index bb64f752..863e2a9a 100644 --- a/blacklist.js +++ b/blacklist.js @@ -63,9 +63,6 @@ var sharedBlacklist = [ 'downstream/core/invariant.js', 'downstream/core/nativeRequestAnimationFrame.js', 'downstream/core/toArray.js', - 'downstream/functional/mapObject.js', - 'downstream/key-mirror/keyMirror.js', - 'downstream/key-mirror/keyOf.js', ]; // Raw unescaped patterns in case you need to use wildcards From a065510ec8c88e108f0ca05da05c3f847b0ab68f Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Thu, 17 Dec 2015 14:09:40 -0800 Subject: [PATCH 478/936] Add support for nodenv with xcode Summary: Hello! I'm a fan of the *env family of version managers over the *vm family as I find them simpler and less obtrusive, but it seems that they don't currently work so well with react-native on OSX. See #4645 I've made a small addition to the xcode script that resolves the issue for me. Hopefully others will find this useful too :) If I've not made the right move here, what is the correct way to get react-native projects building in xcode when the binary is not in the $PATH used by the script? Cheers, Louis Closes https://github.com/facebook/react-native/pull/4801 Reviewed By: svcscm Differential Revision: D2770726 Pulled By: androidtrunkagent fb-gh-sync-id: 3b0f9e466549b3012a68c94738d2556173f4c21e --- react-native-xcode.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index b0206be7..b655222f 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -41,6 +41,11 @@ elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then . "$(brew --prefix nvm)/nvm.sh" fi +# Set up the nodenv node version manager if present +if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then + eval "$($HOME/.nodenv/bin/nodenv init -)" +fi + react-native bundle \ --entry-file index.ios.js \ --platform ios \ From 3f81ea58445e17586398d83ea22d09da3539285a Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 18 Dec 2015 11:00:25 -0800 Subject: [PATCH 479/936] Fix the sourceMap url on minified bundles Summary: public The minified bundle was always being generated with the `bundle.js` as the source map url. Reviewed By: martinbigio Differential Revision: D2773725 fb-gh-sync-id: 02cda95eb172fc373ce4e605418cbfcec22f433d --- react-packager/src/Bundler/Bundle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index f94c8ff5..b945dd42 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -188,7 +188,7 @@ class Bundle { const minifyActivity = Activity.startEvent('minify'); this._minifiedSourceAndMap = UglifyJS.minify(source, { fromString: true, - outSourceMap: 'bundle.js', + outSourceMap: this._sourceMapUrl, inSourceMap: this.getSourceMap(), output: {ascii_only: true}, }); From 90d1b6ee62df799a148526ec8a15faf3a1dbf0e6 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Fri, 18 Dec 2015 16:13:25 -0800 Subject: [PATCH 480/936] Move Cache into DependencyGraph Reviewed By: martinbigio Differential Revision: D2758776 fb-gh-sync-id: 15fb232e00267698e386d5422cb70e2091dabcdd --- react-packager/src/Bundler/index.js | 23 +++-- .../__tests__/BundlesLayout-test.js | 2 +- .../BundlesLayoutIntegration-test.js | 4 +- react-packager/src/BundlesLayout/index.js | 6 +- .../Cache/__mocks__/index.js | 0 .../Cache/__tests__/Cache-test.js | 87 ++++++++----------- .../{ => DependencyResolver}/Cache/index.js | 82 ++++++----------- .../Cache}/lib/getCacheFilePath.js | 11 +-- .../Cache}/lib/loadCacheSync.js | 0 .../__tests__/Transformer-test.js | 2 +- 10 files changed, 90 insertions(+), 127 deletions(-) rename react-packager/src/{ => DependencyResolver}/Cache/__mocks__/index.js (100%) rename react-packager/src/{ => DependencyResolver}/Cache/__tests__/Cache-test.js (80%) rename react-packager/src/{ => DependencyResolver}/Cache/index.js (73%) rename react-packager/src/{ => DependencyResolver/Cache}/lib/getCacheFilePath.js (65%) rename react-packager/src/{ => DependencyResolver/Cache}/lib/loadCacheSync.js (100%) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 470c6821..31794a9b 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -14,7 +14,7 @@ const path = require('path'); const Promise = require('promise'); const ProgressBar = require('progress'); const BundlesLayout = require('../BundlesLayout'); -const Cache = require('../Cache'); +const Cache = require('../DependencyResolver/Cache'); const Transformer = require('../JSTransformer'); const Resolver = require('../Resolver'); const Bundle = require('./Bundle'); @@ -23,6 +23,7 @@ const Activity = require('../Activity'); const ModuleTransport = require('../lib/ModuleTransport'); const declareOpts = require('../lib/declareOpts'); const imageSize = require('image-size'); +const version = require('../../../../package.json').version; const sizeOf = Promise.denodeify(imageSize); const readFile = Promise.denodeify(fs.readFile); @@ -88,11 +89,23 @@ class Bundler { opts.projectRoots.forEach(verifyRootExists); + let mtime; + try { + ({mtime} = fs.statSync(opts.transformModulePath)); + mtime = String(mtime.getTime()); + } catch (error) { + mtime = ''; + } + this._cache = new Cache({ resetCache: opts.resetCache, - cacheVersion: opts.cacheVersion, - projectRoots: opts.projectRoots, - transformModulePath: opts.transformModulePath, + cacheKey: [ + 'react-packager-cache', + version, + opts.cacheVersion, + opts.projectRoots.join(',').split(path.sep).join('-'), + mtime + ].join('$'), }); this._resolver = new Resolver({ @@ -365,7 +378,7 @@ class Bundler { generateAssetModule(bundle, module, platform = null) { const relPath = getPathRelativeToRoot(this._projectRoots, module.path); var assetUrlPath = path.join('/assets', path.dirname(relPath)); - + // On Windows, change backslashes to slashes to get proper URL path from file path. if (path.sep === '\\') { assetUrlPath = assetUrlPath.replace(/\\/g, '/'); diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js index bc4fac8a..dafdb368 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -14,7 +14,7 @@ jest.dontMock('../index') var Promise = require('promise'); var BundlesLayout = require('../index'); var Resolver = require('../../Resolver'); -var loadCacheSync = require('../../lib/loadCacheSync'); +var loadCacheSync = require('../../DependencyResolver/Cache/lib/loadCacheSync'); describe('BundlesLayout', () => { function newBundlesLayout(options) { diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 1cade30c..37701392 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -10,7 +10,7 @@ jest .autoMockOff() - .mock('../../Cache') + .mock('../../DependencyResolver/Cache') .mock('../../Activity'); const Promise = require('promise'); @@ -19,7 +19,7 @@ const path = require('path'); jest.mock('fs'); var BundlesLayout = require('../index'); -var Cache = require('../../Cache'); +var Cache = require('../../DependencyResolver/Cache'); var Resolver = require('../../Resolver'); var fs = require('fs'); diff --git a/react-packager/src/BundlesLayout/index.js b/react-packager/src/BundlesLayout/index.js index d946cefe..2fd90623 100644 --- a/react-packager/src/BundlesLayout/index.js +++ b/react-packager/src/BundlesLayout/index.js @@ -13,10 +13,11 @@ const Activity = require('../Activity'); const _ = require('underscore'); const declareOpts = require('../lib/declareOpts'); const fs = require('fs'); -const getCacheFilePath = require('../lib/getCacheFilePath'); -const loadCacheSync = require('../lib/loadCacheSync'); +const getCacheFilePath = require('../DependencyResolver/Cache/lib/getCacheFilePath'); +const loadCacheSync = require('../DependencyResolver/Cache/lib/loadCacheSync'); const version = require('../../../../package.json').version; const path = require('path'); +const tmpdir = require('os').tmpDir(); const validateOpts = declareOpts({ dependencyResolver: { @@ -189,6 +190,7 @@ class BundlesLayout { _getCacheFilePath(options) { return getCacheFilePath( + tmpdir, 'react-packager-bundles-cache-', version, options.projectRoots.join(',').split(path.sep).join('-'), diff --git a/react-packager/src/Cache/__mocks__/index.js b/react-packager/src/DependencyResolver/Cache/__mocks__/index.js similarity index 100% rename from react-packager/src/Cache/__mocks__/index.js rename to react-packager/src/DependencyResolver/Cache/__mocks__/index.js diff --git a/react-packager/src/Cache/__tests__/Cache-test.js b/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js similarity index 80% rename from react-packager/src/Cache/__tests__/Cache-test.js rename to react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js index ee06dd22..62220f6b 100644 --- a/react-packager/src/Cache/__tests__/Cache-test.js +++ b/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js @@ -9,38 +9,35 @@ 'use strict'; jest - .dontMock('underscore') .dontMock('absolute-path') .dontMock('../') - .dontMock('../../lib/loadCacheSync') - .dontMock('../../lib/getCacheFilePath'); + .dontMock('../lib/loadCacheSync') + .dontMock('../lib/getCacheFilePath'); jest .mock('fs') .setMock('os', { - tmpDir() { return 'tmpDir'; } + tmpDir() { return 'tmpDir'; }, }); var Promise = require('promise'); var fs = require('fs'); -var _ = require('underscore'); var Cache = require('../'); -describe('JSTransformer Cache', () => { +describe('Cache', () => { describe('getting/setting', () => { pit('calls loader callback for uncached file', () => { fs.stat.mockImpl((file, callback) => { callback(null, { mtime: { - getTime: () => {} - } + getTime: () => {}, + }, }); }); var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', + cacheKey: 'cache', }); var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve()); @@ -55,14 +52,13 @@ describe('JSTransformer Cache', () => { fs.stat.mockImpl((file, callback) => { callback(null, { mtime: { - getTime: () => {} - } + getTime: () => {}, + }, }); }); var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', + cacheKey: 'cache', }); var index = 0; var loaderCb = jest.genMockFn().mockImpl(() => @@ -83,14 +79,13 @@ describe('JSTransformer Cache', () => { fs.stat.mockImpl((file, callback) => callback(null, { mtime: { - getTime: () => {} - } + getTime: () => {}, + }, }) ); var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', + cacheKey: 'cache', }); var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve('lol') @@ -105,14 +100,13 @@ describe('JSTransformer Cache', () => { fs.stat.mockImpl((file, callback) => { callback(null, { mtime: { - getTime: () => {} - } + getTime: () => {}, + }, }); }); var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', + cacheKey: 'cache', }); var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve('lol') @@ -135,14 +129,13 @@ describe('JSTransformer Cache', () => { fs.stat.mockImpl((file, callback) => { callback(null, { mtime: { - getTime: () => mtime++ - } + getTime: () => mtime++, + }, }); }); var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', + cacheKey: 'cache', }); var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve('lol' + mtime) @@ -167,14 +160,14 @@ describe('JSTransformer Cache', () => { fileStats = { '/rootDir/someFile': { mtime: { - getTime: () => 22 - } + getTime: () => 22, + }, }, '/rootDir/foo': { mtime: { - getTime: () => 11 - } - } + getTime: () => 11, + }, + }, }; fs.existsSync.mockImpl(() => true); @@ -189,14 +182,13 @@ describe('JSTransformer Cache', () => { '/rootDir/foo': { metadata: {mtime: 11}, data: {field: 'lol wat'}, - } + }, })); }); pit('should load cache from disk', () => { var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', + cacheKey: 'cache', }); var loaderCb = jest.genMockFn(); @@ -219,16 +211,15 @@ describe('JSTransformer Cache', () => { fs.stat.mockImpl((file, callback) => callback(null, { mtime: { - getTime: () => {} - } + getTime: () => {}, + }, }) ); fileStats['/rootDir/foo'].mtime.getTime = () => 123; var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', + cacheKey: 'cache', }); var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve('new value') @@ -254,26 +245,17 @@ describe('JSTransformer Cache', () => { it('should write cache to disk', () => { var index = 0; var mtimes = [10, 20, 30]; - var debounceIndex = 0; - _.debounce = callback => { - return () => { - if (++debounceIndex === 3) { - callback(); - } - }; - }; fs.stat.mockImpl((file, callback) => callback(null, { mtime: { - getTime: () => mtimes[index++] - } + getTime: () => mtimes[index++], + }, }) ); var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', + cacheKey: 'cache', }); cache.get('/rootDir/bar', 'field', () => @@ -286,7 +268,10 @@ describe('JSTransformer Cache', () => { Promise.resolve('baz value') ); - jest.runAllTicks(); + // jest has some trouble with promises and timeouts within promises :( + jest.runAllTimers(); + jest.runAllTimers(); + expect(fs.writeFile).toBeCalled(); }); }); diff --git a/react-packager/src/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js similarity index 73% rename from react-packager/src/Cache/index.js rename to react-packager/src/DependencyResolver/Cache/index.js index cbd9e215..8552e0dc 100644 --- a/react-packager/src/Cache/index.js +++ b/react-packager/src/DependencyResolver/Cache/index.js @@ -9,50 +9,37 @@ 'use strict'; const Promise = require('promise'); -const _ = require('underscore'); -const declareOpts = require('../lib/declareOpts'); const fs = require('fs'); -const getCacheFilePath = require('../lib/getCacheFilePath'); +const getCacheFilePath = require('./lib/getCacheFilePath'); const isAbsolutePath = require('absolute-path'); -const loadCacheSync = require('../lib/loadCacheSync'); -const path = require('path'); -const version = require('../../../../package.json').version; +const loadCacheSync = require('./lib/loadCacheSync'); +const tmpdir = require('os').tmpDir(); -const validateOpts = declareOpts({ - resetCache: { - type: 'boolean', - default: false, - }, - cacheVersion: { - type: 'string', - default: '1.0', - }, - projectRoots: { - type: 'array', - required: true, - }, - transformModulePath: { - type:'string', - required: true, - }, -}); +function getObjectValues(object) { + return Object.keys(object).map(key => object[key]); +} + +function debounce(fn, delay) { + var timeout; + return () => { + clearTimeout(timeout); + timeout = setTimeout(fn, delay); + }; +} -// TODO: move to Packager directory class Cache { - constructor(options) { - var opts = validateOpts(options); - - this._cacheFilePath = this._getCacheFilePath(opts); - - var data; - if (!opts.resetCache) { - data = this._loadCacheSync(this._cacheFilePath); + constructor({ + resetCache, + cacheKey, + }) { + this._cacheFilePath = getCacheFilePath(tmpdir, cacheKey); + if (!resetCache) { + this._data = this._loadCacheSync(this._cacheFilePath); } else { - data = Object.create(null); + this._data = Object.create(null); } - this._data = data; - this._persistEventually = _.debounce( + this._persistEventually = debounce( this._persistCache.bind(this), 2000, ); @@ -124,10 +111,10 @@ class Cache { var data = this._data; var cacheFilepath = this._cacheFilePath; - var allPromises = _.values(data) + var allPromises = getObjectValues(data) .map(record => { var fieldNames = Object.keys(record.data); - var fieldValues = _.values(record.data); + var fieldValues = getObjectValues(record.data); return Promise .all(fieldValues) @@ -187,25 +174,6 @@ class Cache { return ret; } - - _getCacheFilePath(options) { - let mtime; - try { - ({mtime} = fs.statSync(options.transformModulePath)); - mtime = String(mtime.getTime()); - } catch (error) { - mtime = ''; - } - - return getCacheFilePath( - 'react-packager-cache-', - version, - options.projectRoots.join(',').split(path.sep).join('-'), - options.cacheVersion || '0', - options.transformModulePath, - mtime - ); - } } module.exports = Cache; diff --git a/react-packager/src/lib/getCacheFilePath.js b/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js similarity index 65% rename from react-packager/src/lib/getCacheFilePath.js rename to react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js index 1d858529..3975b65a 100644 --- a/react-packager/src/lib/getCacheFilePath.js +++ b/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js @@ -10,16 +10,11 @@ const crypto = require('crypto'); const path = require('path'); -const tmpdir = require('os').tmpDir(); -function getCacheFilePath(...args) { - args = Array.prototype.slice.call(args); - const prefix = args.shift(); - - let hash = crypto.createHash('md5'); +function getCacheFilePath(tmpdir, ...args) { + const hash = crypto.createHash('md5'); args.forEach(arg => hash.update(arg)); - - return path.join(tmpdir, prefix + hash.digest('hex')); + return path.join(tmpdir, hash.digest('hex')); } module.exports = getCacheFilePath; diff --git a/react-packager/src/lib/loadCacheSync.js b/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js similarity index 100% rename from react-packager/src/lib/loadCacheSync.js rename to react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index b182e1eb..83275f14 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -14,7 +14,7 @@ jest jest.mock('fs'); -var Cache = require('../../Cache'); +var Cache = require('../../DependencyResolver/Cache'); var Transformer = require('../'); var fs = require('fs'); From da445450901068792a9ebec6824f8d2bbe63a69a Mon Sep 17 00:00:00 2001 From: Bhuwan Khattar Date: Mon, 21 Dec 2015 12:14:33 -0800 Subject: [PATCH 481/936] getTransformOptions based on bundle and module Summary: Currently, the app server accepts `transformModulePath` which allows us to use different transformation variants. However, these options persist through the lifetime of the server. So we cannot conditionally transform a module differently for two bundles without restarting the server with different options. `getTransformOptions` basically allows us to change the options to the transformer at runtime based on the bundle and module being transformed. These options are also used as a cache key for the transformedSource to ensure that if a file is transformed with different options, caching doesn't cause any inconsistencies. public Reviewed By: martinbigio Differential Revision: D2776399 fb-gh-sync-id: 1e0f34d8fa7f0377fcf81f23eb6f3236bd397d56 --- README.md | 2 ++ react-packager/src/Bundler/Bundle.js | 4 ++++ react-packager/src/Bundler/__tests__/Bundler-test.js | 1 + react-packager/src/Bundler/index.js | 5 ++++- react-packager/src/JSTransformer/index.js | 7 +++++-- react-packager/src/Server/index.js | 4 ++++ 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 83683939..d14ae032 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,8 @@ middleware. Takes the following options: should be used as a persistent deamon to watch files and update itself * `assetRoots` array: Where should the packager look for assets +* `getTransformOptions` function: Middleware to get custom options for the + transformer based on the bundle and module being transformed. ### ReactPackager.buildPackageFromUrl(options, url) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index b945dd42..007dca29 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -58,6 +58,10 @@ class Bundle { return this._modules; } + getMainModuleId() { + return this._mainModuleId; + } + setNumPrependedModules(n) { this._numPrependedModules = n; } diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index ccdc8496..37d200f2 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -81,6 +81,7 @@ describe('Bundler', function() { bundler = new Bundler({ projectRoots: ['/root'], assetServer: assetServer, + getTransformOptions: () => ({}), }); modules = [ diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 31794a9b..19174129 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -135,6 +135,8 @@ class Bundler { this._projectRoots = opts.projectRoots; this._assetServer = opts.assetServer; + + this._getTransformOptions = opts.getTransformOptions; } kill() { @@ -322,7 +324,8 @@ class Bundler { return generateJSONModule(module); } else { return this._transformer.loadFileAndTransform( - path.resolve(module.path) + path.resolve(module.path), + this._getTransformOptions({bundle, module, platform}) ); } } diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 5c819381..07225252 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -82,16 +82,18 @@ class Transformer { this._cache.invalidate(filePath); } - loadFileAndTransform(filePath) { + loadFileAndTransform(filePath, options) { if (this._transform == null) { return Promise.reject(new Error('No transfrom module')); } debug('transforming file', filePath); + const optionsJSON = JSON.stringify(options); + return this._cache.get( filePath, - 'transformedSource', + 'transformedSource-' + optionsJSON, // TODO: use fastfs to avoid reading file from disk again () => readFile(filePath).then( buffer => { @@ -100,6 +102,7 @@ class Transformer { return this._transform({ sourceCode, filename: filePath, + options, }).then(res => { if (res.error) { console.warn( diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 121dcddb..a1fb415a 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -64,6 +64,10 @@ const validateOpts = declareOpts({ type: 'number', required: false, }, + getTransformOptions: { + type: 'function', + default: () => ({}), + } }); const bundleOpts = declareOpts({ From 869c0543d08b104aebe3ce674c1a851db2a3b3c1 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Tue, 22 Dec 2015 11:40:16 -0800 Subject: [PATCH 482/936] Update all tests to use Jasmine 2 Reviewed By: vjeux Differential Revision: D2782581 fb-gh-sync-id: 1d938a2bbdd8670c917c1793234dfdcb29fd4511 --- .../src/Resolver/polyfills/__tests__/loadBundles-test.js | 3 +-- react-packager/src/Resolver/polyfills/loadBundles.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js b/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js index ad0dcf27..39d68788 100644 --- a/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js +++ b/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js @@ -34,8 +34,7 @@ describe('loadBundles', () => { pit('shouldn\'t request already loaded bundles', () => { loadBundles.mockImpl((bundles, callback) => callback()); - return global - .__loadBundles(['bundle.0']) + return global.__loadBundles(['bundle.0']) .then(() => global.__loadBundles(['bundle.0'])) .then(() => expect(loadBundlesCalls.length).toBe(1)); }); diff --git a/react-packager/src/Resolver/polyfills/loadBundles.js b/react-packager/src/Resolver/polyfills/loadBundles.js index 551d4cac..4e893eda 100644 --- a/react-packager/src/Resolver/polyfills/loadBundles.js +++ b/react-packager/src/Resolver/polyfills/loadBundles.js @@ -1,7 +1,7 @@ /* eslint global-strict:0 */ (global => { let loadBundlesOnNative = (bundles) => - new Promise((_, resolve) => + new Promise((resolve) => require('NativeModules').RCTBundlesLoader.loadBundles(bundles, resolve)); let requestedBundles = Object.create(null); From f2124b55446dcd96dea9701bff8191d1fed1062e Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Tue, 22 Dec 2015 15:16:46 -0800 Subject: [PATCH 483/936] Improvements to the packager for very large projects Summary: Here are some small fixes for issues we've encountered with very large RN projects (mostly huge dependency trees in `node_modules`). cc amasad martinbigio Closes https://github.com/facebook/react-native/pull/4880 Reviewed By: svcscm Differential Revision: D2782834 Pulled By: mkonicek fb-gh-sync-id: e316a62b84ba796b80ac819431414ebf27f7b566 --- react-packager/src/DependencyResolver/FileWatcher/index.js | 2 +- react-packager/src/DependencyResolver/fastfs.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/FileWatcher/index.js b/react-packager/src/DependencyResolver/FileWatcher/index.js index 227687e1..6c8a0709 100644 --- a/react-packager/src/DependencyResolver/FileWatcher/index.js +++ b/react-packager/src/DependencyResolver/FileWatcher/index.js @@ -13,7 +13,7 @@ const sane = require('sane'); const Promise = require('promise'); const exec = require('child_process').exec; -const MAX_WAIT_TIME = 25000; +const MAX_WAIT_TIME = 120000; // TODO(amasad): can we use watchman version command instead? const detectingWatcherClass = new Promise(function(resolve) { diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 3a6c3ee1..5bff2437 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -293,11 +293,11 @@ class File { } getFiles() { - const files = []; + let files = []; Object.keys(this.children).forEach(key => { const file = this.children[key]; if (file.isDir) { - files.push(...file.getFiles()); + files = files.concat(file.getFiles()); } else { files.push(file); } From 61c745b8e6a54fb31ec1c5f96d781c85f5636dad Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Wed, 23 Dec 2015 11:38:02 -0800 Subject: [PATCH 484/936] Use //# instead of //@ for sourceMappingURL comment Summary: I had an issue debugging in chrome 48 on windows where source maps didn't work. I tried changing the sourceMappingURL comment to use the //# syntax instead and it fixed the problem. According to https://developers.google.com/web/updates/2013/06/sourceMappingURL-and-sourceURL-syntax-changed?hl=en the //@ syntax was deprecated for //#. Closes https://github.com/facebook/react-native/pull/4892 Reviewed By: martinbigio Differential Revision: D2781046 Pulled By: davidaurelio fb-gh-sync-id: 3b85f6153692a2e23509029ecf18dc51d35ba921 --- react-packager/src/Bundler/Bundle.js | 2 +- react-packager/src/Bundler/__tests__/Bundle-test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 007dca29..11a892f7 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -14,7 +14,7 @@ const UglifyJS = require('uglify-js'); const ModuleTransport = require('../lib/ModuleTransport'); const Activity = require('../Activity'); -const SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL='; +const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL='; const minifyCode = code => UglifyJS.minify(code, {fromString: true, ascii_only: true}).code; diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 46b57741..6dd3d3b7 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -43,7 +43,7 @@ describe('Bundle', function() { expect(bundle.getSource({dev: true})).toBe([ 'transformed foo;', 'transformed bar;', - '\/\/@ sourceMappingURL=test_url' + '\/\/# sourceMappingURL=test_url' ].join('\n')); }); @@ -90,7 +90,7 @@ describe('Bundle', function() { 'transformed bar;', ';require("bar");', ';require("foo");', - '\/\/@ sourceMappingURL=test_url', + '\/\/# sourceMappingURL=test_url', ].join('\n')); }); From 19e7bf23224183899b633cbf438809236e36d95a Mon Sep 17 00:00:00 2001 From: Bhuwan Khattar Date: Thu, 24 Dec 2015 01:01:18 -0800 Subject: [PATCH 485/936] getTransformOptionsModulePath Summary: Passing around a `getTransformOptions` function doesn't really work with the CLI utils, so I'm changing this to `getTransformOptionsModulePath` instead, which can easily be injected in through `rn-cli.config.js`. public Reviewed By: martinbigio Differential Revision: D2785789 fb-gh-sync-id: c9fdc358cb5d0db27e0d02496e44c013c77f3d5f --- README.md | 5 +++-- react-packager/src/Bundler/__tests__/Bundler-test.js | 1 - react-packager/src/Bundler/index.js | 7 +++++-- react-packager/src/Server/index.js | 6 +++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d14ae032..039865ba 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,9 @@ middleware. Takes the following options: should be used as a persistent deamon to watch files and update itself * `assetRoots` array: Where should the packager look for assets -* `getTransformOptions` function: Middleware to get custom options for the - transformer based on the bundle and module being transformed. +* `getTransformOptionsModulePath` string: Path to module that exports a function + that acts as a middleware for generating options to pass to the transformer + based on the bundle and module being transformed. ### ReactPackager.buildPackageFromUrl(options, url) diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index 37d200f2..ccdc8496 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -81,7 +81,6 @@ describe('Bundler', function() { bundler = new Bundler({ projectRoots: ['/root'], assetServer: assetServer, - getTransformOptions: () => ({}), }); modules = [ diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 19174129..824331c4 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -136,7 +136,9 @@ class Bundler { this._projectRoots = opts.projectRoots; this._assetServer = opts.assetServer; - this._getTransformOptions = opts.getTransformOptions; + if (opts.getTransformOptionsModulePath) { + this._getTransformOptions = require(opts.getTransformOptionsModulePath); + } } kill() { @@ -325,7 +327,8 @@ class Bundler { } else { return this._transformer.loadFileAndTransform( path.resolve(module.path), - this._getTransformOptions({bundle, module, platform}) + this._getTransformOptions ? + this._getTransformOptions({bundle, module, platform}) : {} ); } } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index a1fb415a..c1bc5ce1 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -64,9 +64,9 @@ const validateOpts = declareOpts({ type: 'number', required: false, }, - getTransformOptions: { - type: 'function', - default: () => ({}), + getTransformOptionsModulePath: { + type: 'string', + required: false, } }); From 30e8f9fd22499309a3713594579312b1ce77e4dd Mon Sep 17 00:00:00 2001 From: Adam Miskiewicz Date: Thu, 24 Dec 2015 08:31:17 -0800 Subject: [PATCH 486/936] Fix @providesModule not being ignored properly Summary: There's quite a bit of code scattered around the packager regarding ignoring the `providesModule` Haste pragma in any file that isn't in `react-native`, `react-tools` or `parse`. There is even a (passing) test case. However, there's an edge case. Take, for example, `fbjs`. It has a module inside of it called `ErrorUtils`. `react-relay` requires this file normally, in Common.JS style, by doing `require('fbjs/libs/ErrorUtils')`. But when `react-native` attempts to require `ErrorUtils` using the HasteModule format (in it's JavaScript initialization), it resolves the `fbjs` `ErrorUtils` module, instead of RN's `ErrorUtils`. This happens, it turns out, because when a module is read (in `Module._read`), it's not caring about whether or not it should pay attention to `providesModule`, and is just assigning the `providesModule` value as the id of the module no matter what. Then when `Module.getName` is called, it will always use that `data.id` that was set, thus creating the wrong dependency tree. This Closes https://github.com/facebook/react-native/pull/3625 Reviewed By: svcscm Differential Revision: D2632317 Pulled By: vjeux fb-gh-sync-id: efd8066eaf6f18fcf79698beab36cab90bf5cd6d --- .../src/DependencyResolver/AssetModule.js | 2 +- .../{Helpers.js => DependencyGraphHelpers.js} | 4 +- .../DependencyGraph/DeprecatedAssetMap.js | 2 +- .../__tests__/DependencyGraph-test.js | 77 ++++++++++++++++++- .../DependencyGraph/index.js | 7 +- .../src/DependencyResolver/Module.js | 23 ++++-- .../src/DependencyResolver/ModuleCache.js | 40 +++++----- .../src/DependencyResolver/Package.js | 12 +-- .../src/DependencyResolver/Polyfill.js | 2 +- .../__tests__/Module-test.js | 23 +++--- .../src/Resolver/__tests__/Resolver-test.js | 2 +- 11 files changed, 140 insertions(+), 54 deletions(-) rename react-packager/src/DependencyResolver/DependencyGraph/{Helpers.js => DependencyGraphHelpers.js} (94%) diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js index 1e5ff7d4..f5555fac 100644 --- a/react-packager/src/DependencyResolver/AssetModule.js +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -25,7 +25,7 @@ class AssetModule extends Module { return Promise.resolve([]); } - _read() { + read() { return Promise.resolve({}); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/Helpers.js b/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js similarity index 94% rename from react-packager/src/DependencyResolver/DependencyGraph/Helpers.js rename to react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js index fda56007..d7d9f20e 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/Helpers.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js @@ -10,7 +10,7 @@ const path = require('path'); -class Helpers { +class DependencyGraphHelpers { constructor({ providesModuleNodeModules, assetExts }) { this._providesModuleNodeModules = providesModuleNodeModules; this._assetExts = assetExts; @@ -46,4 +46,4 @@ class Helpers { } } -module.exports = Helpers; +module.exports = DependencyGraphHelpers; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js index aedfa4ce..5f18875a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js @@ -92,7 +92,7 @@ class DeprecatedAssetMap { debug('Conflcting assets', name); } - this._map[name] = new AssetModule_DEPRECATED(file); + this._map[name] = new AssetModule_DEPRECATED({ file }); } } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 8b50c8b4..b99d6065 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -2362,6 +2362,7 @@ describe('DependencyGraph', function() { pit('should selectively ignore providesModule in node_modules', function() { var root = '/root'; + var otherRoot = '/anotherRoot'; fs.__setMockFilesystem({ 'root': { 'index.js': [ @@ -2371,6 +2372,9 @@ describe('DependencyGraph', function() { 'require("shouldWork");', 'require("dontWork");', 'require("wontWork");', + 'require("ember");', + 'require("internalVendoredPackage");', + 'require("anotherIndex");', ].join('\n'), 'node_modules': { 'react-haste': { @@ -2378,6 +2382,7 @@ describe('DependencyGraph', function() { name: 'react-haste', main: 'main.js', }), + // @providesModule should not be ignored here, because react-haste is whitelisted 'main.js': [ '/**', ' * @providesModule shouldWork', @@ -2390,6 +2395,7 @@ describe('DependencyGraph', function() { name: 'bar', main: 'main.js', }), + // @providesModule should be ignored here, because it's not whitelisted 'main.js':[ '/**', ' * @providesModule dontWork', @@ -2411,20 +2417,48 @@ describe('DependencyGraph', function() { name: 'ember', main: 'main.js', }), + // @providesModule should be ignored here, because it's not whitelisted, + // and also, the modules "id" should be ember/main.js, not it's haste name 'main.js':[ '/**', ' * @providesModule wontWork', ' */', 'hi();', - ].join('\n'), + ].join('\n') }, }, + // This part of the dep graph is meant to emulate internal facebook infra. + // By whitelisting `vendored_modules`, haste should still work. + 'vendored_modules': { + 'a-vendored-package': { + 'package.json': JSON.stringify({ + name: 'a-vendored-package', + main: 'main.js', + }), + // @providesModule should _not_ be ignored here, because it's whitelisted. + 'main.js':[ + '/**', + ' * @providesModule internalVendoredPackage', + ' */', + 'hiFromInternalPackage();', + ].join('\n'), + } + }, }, + // we need to support multiple roots and using haste between them + 'anotherRoot': { + 'index.js': [ + '/**', + ' * @providesModule anotherIndex', + ' */', + 'wazup()', + ].join('\n'), + } }); var dgraph = new DependencyGraph({ ...defaults, - roots: [root], + roots: [root, otherRoot], }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) @@ -2432,7 +2466,14 @@ describe('DependencyGraph', function() { { id: 'index', path: '/root/index.js', - dependencies: ['shouldWork', 'dontWork', 'wontWork'], + dependencies: [ + 'shouldWork', + 'dontWork', + 'wontWork', + 'ember', + 'internalVendoredPackage', + 'anotherIndex' + ], isAsset: false, isAsset_DEPRECATED: false, isJSON: false, @@ -2459,6 +2500,36 @@ describe('DependencyGraph', function() { isPolyfill: false, resolution: undefined, }, + { + id: 'ember/main.js', + path: '/root/node_modules/ember/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'internalVendoredPackage', + path: '/root/vendored_modules/a-vendored-package/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'anotherIndex', + path: '/anotherRoot/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, ]); }); }); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 9f87dcbc..69072339 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -16,7 +16,7 @@ const getPlatformExtension = require('../lib/getPlatformExtension'); const isAbsolutePath = require('absolute-path'); const path = require('path'); const util = require('util'); -const Helpers = require('./Helpers'); +const DependencyGraphHelpers = require('./DependencyGraphHelpers'); const ResolutionRequest = require('./ResolutionRequest'); const ResolutionResponse = require('./ResolutionResponse'); const HasteMap = require('./HasteMap'); @@ -57,7 +57,7 @@ class DependencyGraph { extractRequires, }; this._cache = this._opts.cache; - this._helpers = new Helpers(this._opts); + this._helpers = new DependencyGraphHelpers(this._opts); this.load().catch((err) => { // This only happens at initialization. Live errors are easier to recover from. console.error('Error building DependencyGraph:\n', err.stack); @@ -97,7 +97,8 @@ class DependencyGraph { this._moduleCache = new ModuleCache( this._fastfs, this._cache, - this._opts.extractRequires + this._opts.extractRequires, + this._helpers ); this._hasteMap = new HasteMap({ diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index a6112082..3fcd63f0 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -15,7 +15,7 @@ const extractRequires = require('./lib/extractRequires'); class Module { - constructor(file, fastfs, moduleCache, cache, extractor) { + constructor({ file, fastfs, moduleCache, cache, extractor, depGraphHelpers }) { if (!isAbsolutePath(file)) { throw new Error('Expected file to be absolute path but got ' + file); } @@ -27,17 +27,18 @@ class Module { this._moduleCache = moduleCache; this._cache = cache; this._extractor = extractor; + this._depGraphHelpers = depGraphHelpers; } isHaste() { - return this._read().then(data => !!data.id); + return this.read().then(data => !!data.id); } getName() { return this._cache.get( this.path, 'name', - () => this._read().then(data => { + () => this.read().then(data => { if (data.id) { return data.id; } @@ -66,23 +67,31 @@ class Module { } getDependencies() { - return this._read().then(data => data.dependencies); + return this.read().then(data => data.dependencies); } getAsyncDependencies() { - return this._read().then(data => data.asyncDependencies); + return this.read().then(data => data.asyncDependencies); } invalidate() { this._cache.invalidate(this.path); } - _read() { + read() { if (!this._reading) { this._reading = this._fastfs.readFile(this.path).then(content => { const data = {}; + + // Set an id on the module if it's using @providesModule syntax + // and if it's NOT in node_modules (and not a whitelisted node_module). + // This handles the case where a project may have a dep that has @providesModule + // docblock comments, but doesn't want it to conflict with whitelisted @providesModule + // modules, such as react-haste, fbjs-haste, or react-native or with non-dependency, + // project-specific code that is using @providesModule. const moduleDocBlock = docblock.parseAsObject(content); - if (moduleDocBlock.providesModule || moduleDocBlock.provides) { + if (!this._depGraphHelpers.isNodeModulesDir(this.path) && + (moduleDocBlock.providesModule || moduleDocBlock.provides)) { data.id = /^(\S*)/.exec( moduleDocBlock.providesModule || moduleDocBlock.provides )[1]; diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index a8891439..0fba47a9 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -7,25 +7,27 @@ const path = require('path'); class ModuleCache { - constructor(fastfs, cache, extractRequires) { + constructor(fastfs, cache, extractRequires, depGraphHelpers) { this._moduleCache = Object.create(null); this._packageCache = Object.create(null); this._fastfs = fastfs; this._cache = cache; this._extractRequires = extractRequires; + this._depGraphHelpers = depGraphHelpers; fastfs.on('change', this._processFileChange.bind(this)); } getModule(filePath) { filePath = path.resolve(filePath); if (!this._moduleCache[filePath]) { - this._moduleCache[filePath] = new Module( - filePath, - this._fastfs, - this, - this._cache, - this._extractRequires - ); + this._moduleCache[filePath] = new Module({ + file: filePath, + fastfs: this._fastfs, + moduleCache: this, + cache: this._cache, + extractor: this._extractRequires, + depGraphHelpers: this._depGraphHelpers + }); } return this._moduleCache[filePath]; } @@ -33,12 +35,12 @@ class ModuleCache { getAssetModule(filePath) { filePath = path.resolve(filePath); if (!this._moduleCache[filePath]) { - this._moduleCache[filePath] = new AssetModule( - filePath, - this._fastfs, - this, - this._cache, - ); + this._moduleCache[filePath] = new AssetModule({ + file: filePath, + fastfs: this._fastfs, + moduleCache: this, + cache: this._cache, + }); } return this._moduleCache[filePath]; } @@ -46,11 +48,11 @@ class ModuleCache { getPackage(filePath) { filePath = path.resolve(filePath); if (!this._packageCache[filePath]) { - this._packageCache[filePath] = new Package( - filePath, - this._fastfs, - this._cache, - ); + this._packageCache[filePath] = new Package({ + file: filePath, + fastfs: this._fastfs, + cache: this._cache, + }); } return this._packageCache[filePath]; } diff --git a/react-packager/src/DependencyResolver/Package.js b/react-packager/src/DependencyResolver/Package.js index ac7ee88c..e88b0640 100644 --- a/react-packager/src/DependencyResolver/Package.js +++ b/react-packager/src/DependencyResolver/Package.js @@ -5,7 +5,7 @@ const path = require('path'); class Package { - constructor(file, fastfs, cache) { + constructor({ file, fastfs, cache }) { this.path = path.resolve(file); this.root = path.dirname(this.path); this._fastfs = fastfs; @@ -14,7 +14,7 @@ class Package { } getMain() { - return this._read().then(json => { + return this.read().then(json => { var replacements = getReplacements(json); if (typeof replacements === 'string') { return path.join(this.root, replacements); @@ -36,13 +36,13 @@ class Package { isHaste() { return this._cache.get(this.path, 'package-haste', () => - this._read().then(json => !!json.name) + this.read().then(json => !!json.name) ); } getName() { return this._cache.get(this.path, 'package-name', () => - this._read().then(json => json.name) + this.read().then(json => json.name) ); } @@ -51,7 +51,7 @@ class Package { } redirectRequire(name) { - return this._read().then(json => { + return this.read().then(json => { var replacements = getReplacements(json); if (!replacements || typeof replacements !== 'object') { @@ -81,7 +81,7 @@ class Package { }); } - _read() { + read() { if (!this._reading) { this._reading = this._fastfs.readFile(this.path) .then(jsonStr => JSON.parse(jsonStr)); diff --git a/react-packager/src/DependencyResolver/Polyfill.js b/react-packager/src/DependencyResolver/Polyfill.js index 752b864b..97c57adb 100644 --- a/react-packager/src/DependencyResolver/Polyfill.js +++ b/react-packager/src/DependencyResolver/Polyfill.js @@ -5,7 +5,7 @@ const Module = require('./Module'); class Polyfill extends Module { constructor({ path, id, dependencies }) { - super(path); + super({ file: path }); this._id = id; this._dependencies = dependencies; } diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 656a1b00..7938ca79 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -22,6 +22,7 @@ jest const Fastfs = require('../fastfs'); const Module = require('../Module'); const ModuleCache = require('../ModuleCache'); +const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers'); const Promise = require('promise'); const fs = require('fs'); @@ -50,12 +51,13 @@ describe('Module', () => { const cache = new Cache(); return fastfs.build().then(() => { - const module = new Module( - '/root/index.js', + const module = new Module({ + file: '/root/index.js', fastfs, - new ModuleCache(fastfs, cache), - cache - ); + moduleCache: new ModuleCache(fastfs, cache), + cache: cache, + depGraphHelpers: new DependencyGraphHelpers() + }); return module.getAsyncDependencies().then(actual => expect(actual).toEqual(expected) @@ -122,13 +124,14 @@ describe('Module', () => { const cache = new Cache(); return fastfs.build().then(() => { - return new Module( - '/root/index.js', + return new Module({ + file: '/root/index.js', fastfs, - new ModuleCache(fastfs, cache), + moduleCache: new ModuleCache(fastfs, cache), cache, - extractor - ); + extractor, + depGraphHelpers: new DependencyGraphHelpers() + }); }); } diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 1de9905b..6712f00b 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -51,7 +51,7 @@ describe('Resolver', function() { } function createModule(id, dependencies) { - var module = new Module(); + var module = new Module({}); module.getName.mockImpl(() => Promise.resolve(id)); module.getDependencies.mockImpl(() => Promise.resolve(dependencies)); return module; From ecec0ea842107d4f7c15c960b47fc84f8dc82dab Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Sun, 27 Dec 2015 06:00:39 -0800 Subject: [PATCH 487/936] Move WPO to the minify step Summary: public The Whole Program Optimisation (WPO) pass is quite slow, and can make it painful to iterate when having `dev=false`. Move it to happen only when both `dev=false` and `minify=true`. Reviewed By: davidaurelio Differential Revision: D2773941 fb-gh-sync-id: ac5ca4e1286e233c2d175eecdbf7494d5d3497b2 --- react-packager/src/Bundler/Bundle.js | 38 +++++++++++++--------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 11a892f7..b5e591c8 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -109,24 +109,6 @@ class Bundle { } this._source = _.pluck(this._modules, 'code').join('\n'); - - if (dev) { - return this._source; - } - - const wpoActivity = Activity.startEvent('Whole Program Optimisations'); - const result = require('babel-core').transform(this._source, { - retainLines: true, - compact: true, - plugins: require('../transforms/whole-program-optimisations'), - inputSourceMap: this.getSourceMap(), - }); - - this._source = result.code; - this._sourceMap = result.map; - - Activity.endEvent(wpoActivity); - return this._source; } @@ -187,13 +169,29 @@ class Bundle { return this._minifiedSourceAndMap; } - const source = this._getSource(dev); + let source = this._getSource(dev); + let map = this.getSourceMap(); + + if (!dev) { + const wpoActivity = Activity.startEvent('Whole Program Optimisations'); + const wpoResult = require('babel-core').transform(source, { + retainLines: true, + compact: true, + plugins: require('../transforms/whole-program-optimisations'), + inputSourceMap: map, + }); + Activity.endEvent(wpoActivity); + + source = wpoResult.code; + map = wpoResult.map; + } + try { const minifyActivity = Activity.startEvent('minify'); this._minifiedSourceAndMap = UglifyJS.minify(source, { fromString: true, outSourceMap: this._sourceMapUrl, - inSourceMap: this.getSourceMap(), + inSourceMap: map, output: {ascii_only: true}, }); Activity.endEvent(minifyActivity); From e6a807a8b7741b99c23bc5ca9d92f29fac3e2435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 28 Dec 2015 16:43:08 -0800 Subject: [PATCH 488/936] Introduce Packager and App HMR WebSocket connection Summary: public This diff adds infra to both the Packager and the running app to have a WebSocket based connection between them. This connection is toggled by a new dev menu item, namely `Enable/Disable Hot Loading`. Reviewed By: vjeux Differential Revision: D2787621 fb-gh-sync-id: d1dee769348e4830c28782e7b650d025f2b3a786 --- react-packager/src/Server/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index c1bc5ce1..c2acd305 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -134,6 +134,7 @@ class Server { this._projectRoots = opts.projectRoots; this._bundles = Object.create(null); this._changeWatchers = []; + this._fileChangeListeners = []; const assetGlobs = opts.assetExts.map(ext => '**/*.' + ext); @@ -175,6 +176,7 @@ class Server { this._fileWatcher.on('all', this._onFileChange.bind(this)); this._debouncedFileChangeHandler = _.debounce(filePath => { + this._fileChangeListeners.forEach(listener => listener(filePath)); this._rebuildBundles(filePath); this._informChangeWatchers(); }, 50); @@ -187,6 +189,10 @@ class Server { ]); } + addFileChangeListener(listener) { + this._fileChangeListeners.push(listener); + } + buildBundle(options) { return Promise.resolve().then(() => { if (!options.platform) { From afba6b48e7d830fb0d3aacfb497c0c9b4903331f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 28 Dec 2015 16:43:15 -0800 Subject: [PATCH 489/936] Make Packager send fresh modules through HMR interface Reviewed By: frantic Differential Revision: D2787951 fb-gh-sync-id: 63c04710b60d99b5161ef9a40b116ba2f72df745 --- react-packager/src/Bundler/index.js | 23 +++++++++++++++++++++++ react-packager/src/Server/index.js | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 824331c4..87e97d06 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -277,6 +277,29 @@ class Bundler { }); } + bundleForHMR({ + entryFile, + platform, + }) { + return this.getDependencies(entryFile, /*isDev*/true, platform).then(response => { + const module = response.dependencies.filter(module => module.path === entryFile)[0]; + + return Promise.all([ + module.getName(), + this._transformer.loadFileAndTransform(path.resolve(entryFile)), + ]).then(([moduleName, transformedSource]) => { + return (` + __accept( + '${moduleName}', + function(global, require, module, exports) { + ${transformedSource.code} + } + ); + `); + }); + }); + } + invalidateFile(filePath) { this._transformer.invalidateFile(filePath); } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index c2acd305..668d3878 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -112,6 +112,17 @@ const bundleOpts = declareOpts({ } }); +const hmrBundleOpts = declareOpts({ + entryFile: { + type: 'string', + required: true, + }, + platform: { + type: 'string', + required: true, + }, +}); + const dependencyOpts = declareOpts({ platform: { type: 'string', @@ -220,6 +231,17 @@ class Server { return this.buildBundle(options); } + buildBundleForHMR(options) { + return Promise.resolve().then(() => { + if (!options.platform) { + options.platform = getPlatformExtension(options.entryFile); + } + + const opts = hmrBundleOpts(options); + return this._bundler.bundleForHMR(opts); + }); + } + getDependencies(options) { return Promise.resolve().then(() => { if (!options.platform) { From 5bbd33051ab5122af98719a9577fee4c3662814e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 28 Dec 2015 16:43:16 -0800 Subject: [PATCH 490/936] Introduce HMR runtime Summary: public Add a very simple runtime for self-accepting modules. The API is the same one as Webpacks's one for now. The new infra will be end-to-end tested on a follow up diff. Reviewed By: vjeux Differential Revision: D2789428 fb-gh-sync-id: c39524814d53c6e4ec9d9d011081ea81089b00b6 --- .../src/Resolver/polyfills/require.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 506d38e0..73e54e59 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -10,6 +10,17 @@ isInitialized: false, hasError: false, }; + + if (__DEV__) { // HMR + Object.assign(modules[id].module, { + hot: { + acceptCallback: null, + accept: function(callback) { + this.acceptCallback = callback; + } + } + }); + } } function require(id) { @@ -82,4 +93,24 @@ global.__d = define; global.require = require; + + if (__DEV__) { // HMR + function accept(id, factory) { + var mod = modules[id]; + if (mod.module.hot.acceptCallback) { + mod.factory = factory; + mod.isInitialized = false; + require(id); + + mod.hot.acceptCallback(); + } else { + console.log( + '[HMR] Module `' + id + '` cannot be accepted. ' + + 'Please reload bundle to get the updates.' + ); + } + } + + global.__accept = accept; + } })(this); From 724fa73adfc18217f4b5c2dc2cc6611f09394d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 28 Dec 2015 16:43:21 -0800 Subject: [PATCH 491/936] Hot Loading E2E basic flow Summary: public Implement all the necessary glue code for several diffs submitted before to get Hot Loading work end to end: - Simplify `HMRClient`: we don't need to make it stateful allowing to enable and disable it because both when we enable and disable the interface we need to reload the bundle. - On the native side we introduced a singleton to process the bundle URL. This new class might alter the url to include the `hot` attribute. I'm not 100% sure this is the best way to implement this but we cannot use `CTLSettings` for this as it's are not available on oss and I didn't want to contaminate `RCTBridge` with something specific to hot loading. Also, we could potentially use this processor for other things in the future. Please let me know if you don't like this approach or you have a better idea :). - Use this processor to alter the default bundle URL and request a `hot` bundle when hot loading is enabled. Also make sure to enable the HMR interface when the client activates it on the dev menu. - Add packager `hot` option. - Include gaeron's `react-transform` on Facebook's JS transformer. The current implementation couples a bit React Native to this feature because `react-transform-hmr` is required on `InitializeJavaScriptAppEngine`. Ideally, the packager should accept an additional list of requires and include them on the bundle among all their dependencies. Note this is not the same as the option `runBeforeMainModule` as that one only adds a require to the provided module but doesn't include all the dependencies that module amy have that the entry point doesn't. I'll address this in a follow up task to enable asap hot loading (9536142) I had to remove 2 `.babelrc` files from `react-proxy` and `react-deep-force-update`. There's an internal task for fixing the underlaying issue to avoid doing this horrible hack (t9515889). Reviewed By: vjeux Differential Revision: D2790806 fb-gh-sync-id: d4b78a2acfa071d6b3accc2e6716ef5611ad4fda --- react-packager/src/Bundler/index.js | 32 ++++++++++++++----- react-packager/src/Resolver/index.js | 1 + .../src/Resolver/polyfills/require.js | 20 ++++++++++-- react-packager/src/Server/index.js | 7 +++- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 87e97d06..a2fd93be 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -137,7 +137,7 @@ class Bundler { this._assetServer = opts.assetServer; if (opts.getTransformOptionsModulePath) { - this._getTransformOptions = require(opts.getTransformOptionsModulePath); + this._getTransformOptionsModule = require(opts.getTransformOptionsModulePath); } } @@ -158,6 +158,7 @@ class Bundler { dev: isDev, platform, unbundle: isUnbundle, + hot: hot, }) { // Const cannot have the same name as the method (babel/babel#2834) const bbundle = new Bundle(sourceMapUrl); @@ -194,7 +195,8 @@ class Bundler { bbundle, response, module, - platform + platform, + hot, ).then(transformed => { if (bar) { bar.tick(); @@ -286,12 +288,16 @@ class Bundler { return Promise.all([ module.getName(), - this._transformer.loadFileAndTransform(path.resolve(entryFile)), + this._transformer.loadFileAndTransform( + path.resolve(entryFile), + // TODO(martinb): pass non null main (t9527509) + this._getTransformOptions({main: null}, {hot: true}), + ), ]).then(([moduleName, transformedSource]) => { return (` __accept( - '${moduleName}', - function(global, require, module, exports) { + '${moduleName}', + function(global, require, module, exports) { ${transformedSource.code} } ); @@ -340,7 +346,7 @@ class Bundler { ); } - _transformModule(bundle, response, module, platform = null) { + _transformModule(bundle, response, module, platform = null, hot = false) { if (module.isAsset_DEPRECATED()) { return this.generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { @@ -350,8 +356,10 @@ class Bundler { } else { return this._transformer.loadFileAndTransform( path.resolve(module.path), - this._getTransformOptions ? - this._getTransformOptions({bundle, module, platform}) : {} + this._getTransformOptions( + {bundleEntry: bundle.getMainModuleId(), modulePath: module.path}, + {hot: hot}, + ), ); } } @@ -445,6 +453,14 @@ class Bundler { }); }); } + + _getTransformOptions(config, options) { + const transformerOptions = this._getTransformOptionsModule + ? this._getTransformOptionsModule(config) + : null; + + return {...options, ...transformerOptions}; + } } function generateJSONModule(module) { diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 8e8dd315..be7e8a5e 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -89,6 +89,7 @@ class Resolver { // should work after this release and we can // remove it from here. 'parse', + 'react-transform-hmr', ], platforms: ['ios', 'android'], fileWatcher: opts.fileWatcher, diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 73e54e59..022a4831 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -16,7 +16,7 @@ hot: { acceptCallback: null, accept: function(callback) { - this.acceptCallback = callback; + modules[id].module.hot.acceptCallback = callback; } } }); @@ -97,12 +97,28 @@ if (__DEV__) { // HMR function accept(id, factory) { var mod = modules[id]; + + if (!mod) { + console.error( + 'Cannot accept unknown module `' + id + '`. Make sure you\'re not ' + + 'trying to modify something else other than a module ' + + '(i.e.: a polyfill).' + ); + } + + if (!mod.module.hot) { + console.error( + 'Cannot accept module because Hot Module Replacement ' + + 'API was not installed.' + ); + } + if (mod.module.hot.acceptCallback) { mod.factory = factory; mod.isInitialized = false; require(id); - mod.hot.acceptCallback(); + mod.module.hot.acceptCallback(); } else { console.log( '[HMR] Module `' + id + '` cannot be accepted. ' + diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 668d3878..91520395 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -109,7 +109,11 @@ const bundleOpts = declareOpts({ unbundle: { type: 'boolean', default: false, - } + }, + hot: { + type: 'boolean', + default: false, + }, }); const hmrBundleOpts = declareOpts({ @@ -501,6 +505,7 @@ class Server { entryFile: entryFile, dev: this._getBoolOptionFromQuery(urlObj.query, 'dev', true), minify: this._getBoolOptionFromQuery(urlObj.query, 'minify'), + hot: this._getBoolOptionFromQuery(urlObj.query, 'hot', false), runModule: this._getBoolOptionFromQuery(urlObj.query, 'runModule', true), inlineSourceMap: this._getBoolOptionFromQuery( urlObj.query, From e2ba64630d943911368c91e3ffcf50cd97c2e741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 28 Dec 2015 16:43:22 -0800 Subject: [PATCH 492/936] Gate Hot Loading Summary: public Until we support this fature on OSS, don't show the menu option. Reviewed By: vjeux Differential Revision: D2791198 fb-gh-sync-id: 11b66d467c1ab784bbf549b893d0a3abd69e2741 --- react-packager/src/Server/__tests__/Server-test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 2fdb5e73..107f2750 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -110,6 +110,7 @@ describe('processRequest', () => { entryFile: 'index.ios.js', inlineSourceMap: false, minify: false, + hot: false, runModule: true, sourceMapUrl: 'index.ios.includeRequire.map', dev: true, @@ -130,6 +131,7 @@ describe('processRequest', () => { entryFile: 'index.js', inlineSourceMap: false, minify: false, + hot: false, runModule: true, sourceMapUrl: 'index.map?platform=ios', dev: true, @@ -272,6 +274,7 @@ describe('processRequest', () => { entryFile: 'foo file', inlineSourceMap: false, minify: false, + hot: false, runModule: true, dev: true, platform: undefined, @@ -290,6 +293,7 @@ describe('processRequest', () => { entryFile: 'path/to/foo.js', inlineSourceMap: false, minify: false, + hot: false, runModule: false, sourceMapUrl: '/path/to/foo.map?dev=false&runModule=false', dev: false, From d59f716a224ce556e13742f55b15cdd560c76754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 29 Dec 2015 11:35:48 -0800 Subject: [PATCH 493/936] Improve error wording Reviewed By: frantic Differential Revision: D2793300 fb-gh-sync-id: f3f9403ee332d193b8a6c4a6dfb89d636dc48875 --- react-packager/src/Resolver/polyfills/require.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 022a4831..15f8cf55 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -121,8 +121,8 @@ mod.module.hot.acceptCallback(); } else { console.log( - '[HMR] Module `' + id + '` cannot be accepted. ' + - 'Please reload bundle to get the updates.' + '[HMR] Module `' + id + '` can\'t be hot reloaded because it ' + + 'doesn\'t provide accept callback hook. Reload the app to get the updates.' ); } } From dcbc6708e35dc99a560df6192b80e59db770a335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 29 Dec 2015 18:24:08 -0800 Subject: [PATCH 494/936] Send HMR updates only for files on the bundle Summary: public Compute the dependencies of the bundle entry file just before sending HMR updates. In case the file that was changed doesn't belong to the bundle bail. Reviewed By: vjeux Differential Revision: D2793736 fb-gh-sync-id: f858e71b0dd5fe4f5b2307a22c6cef627eb66a22 --- react-packager/src/Bundler/index.js | 4 ++++ .../src/DependencyResolver/DependencyGraph/index.js | 8 ++++++++ react-packager/src/Resolver/index.js | 4 ++++ react-packager/src/Server/index.js | 4 ++++ 4 files changed, 20 insertions(+) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index a2fd93be..dc76a267 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -310,6 +310,10 @@ class Bundler { this._transformer.invalidateFile(filePath); } + getShallowDependencies(entryFile) { + return this._resolver.getShallowDependencies(entryFile); + } + getDependencies(main, isDev, platform) { return this._resolver.getDependencies(main, { dev: isDev, platform }); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 69072339..ac88a2e3 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -133,6 +133,14 @@ class DependencyGraph { return this._loading; } + /** + * Returns a promise with the direct dependencies the module associated to + * the given entryPath has. + */ + getShallowDependencies(entryPath) { + return this._moduleCache.getModule(entryPath).getDependencies(); + } + getDependencies(entryPath, platform) { return this.load().then(() => { platform = this._getRequestPlatform(entryPath, platform); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index be7e8a5e..d0f92285 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -99,6 +99,10 @@ class Resolver { this._polyfillModuleNames = opts.polyfillModuleNames || []; } + getShallowDependencies(entryFile) { + return this._depGraph.getShallowDependencies(entryFile); + } + getDependencies(main, options) { const opts = getDependenciesValidateOpts(options); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 91520395..a6bb6b34 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -246,6 +246,10 @@ class Server { }); } + getShallowDependencies(entryFile) { + return this._bundler.getShallowDependencies(entryFile); + } + getDependencies(options) { return Promise.resolve().then(() => { if (!options.platform) { From d7a0233e2deef2089e502d55b2e73b665352e1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 29 Dec 2015 18:24:10 -0800 Subject: [PATCH 495/936] Make Hot Loading faster Summary: public Before this this when a file was changed besides sending the HMR update we rebuild every single bundle that the packager had build (to serve it faster when the user hit cmd+r). Since when hot loading is enabled we don't do cmd+r all this work was pointless (except for when you're developing multiple apps using the same packager instance at the same time, which we can assume is very uncommon). As a consequence, the HMR update was competing with the rebundling job making HMR quite slow (i.e.: on one huge internal app it took up to 6s for the HMR changes to get applied). So, this diff tweaks the file change listener so that we don't rebundle nor invoke the fileWatchers (use for live reload which is also useless when hot load is enabled) when hot loading is enabled. Also, it makes the HMR listener more high pri than the other listeners so that the HMR dev experience is as good as it can get. Reviewed By: vjeux Differential Revision: D2793827 fb-gh-sync-id: 724930db9f44974c15ad3f562910b0885e44efde --- react-packager/src/Server/index.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index a6bb6b34..717ef9e1 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -191,6 +191,16 @@ class Server { this._fileWatcher.on('all', this._onFileChange.bind(this)); this._debouncedFileChangeHandler = _.debounce(filePath => { + // if Hot Loading is enabled avoid rebuilding bundles and sending live + // updates. Instead, send the HMR updates right away and once that + // finishes, invoke any other file change listener. + if (this._hmrFileChangeListener) { + this._hmrFileChangeListener(filePath).then(() => { + this._fileChangeListeners.forEach(listener => listener(filePath)); + }).done(); + return; + } + this._fileChangeListeners.forEach(listener => listener(filePath)); this._rebuildBundles(filePath); this._informChangeWatchers(); @@ -208,6 +218,10 @@ class Server { this._fileChangeListeners.push(listener); } + setHMRFileChangeListener(listener) { + this._hmrFileChangeListener = listener; + } + buildBundle(options) { return Promise.resolve().then(() => { if (!options.platform) { From ac0b2df846d2421e03d5461e2da9431c89188ec3 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Tue, 29 Dec 2015 23:08:53 -0800 Subject: [PATCH 496/936] Update Object.assign non-object error message Summary: These are the sources, not the target. Copy pasta from above. Closes https://github.com/facebook/react-native/pull/4989 Reviewed By: svcscm Differential Revision: D2795198 Pulled By: spicyj fb-gh-sync-id: 61c52add02cb877284fbf62a4344361b5bf44515 --- react-packager/src/Resolver/polyfills/polyfills.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/polyfills.js b/react-packager/src/Resolver/polyfills/polyfills.js index 84bf2d3d..9698b491 100644 --- a/react-packager/src/Resolver/polyfills/polyfills.js +++ b/react-packager/src/Resolver/polyfills/polyfills.js @@ -39,7 +39,7 @@ Object.assign = function(target, sources) { if (typeof nextSource !== 'object' && typeof nextSource !== 'function') { throw new TypeError( - 'In this environment the target of assign MUST be an object.' + + 'In this environment the sources for assign MUST be an object.' + 'This error is a performance optimization and not spec compliant.' ); } @@ -54,7 +54,7 @@ Object.assign = function(target, sources) { var hasOwnProperty = Object.prototype.hasOwnProperty; if (!hasOwnProperty.call(nextSource, key)) { throw new TypeError( - 'One of the sources to assign has an enumerable key on the ' + + 'One of the sources for assign has an enumerable key on the ' + 'prototype chain. This is an edge case that we do not support. ' + 'This error is a performance optimization and not spec compliant.' ); From 2ab3fe921b4061e311f74528a146e6c9dc8d6a93 Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 30 Dec 2015 08:18:45 -0800 Subject: [PATCH 497/936] Improve watcher timeout error message (fixes #5005) Summary: Closes https://github.com/facebook/react-native/pull/5022 Reviewed By: svcscm Differential Revision: D2793185 Pulled By: milend fb-gh-sync-id: ec49d27d6e405924269a48f32cce401b1ce380f6 --- .../DependencyResolver/FileWatcher/index.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/react-packager/src/DependencyResolver/FileWatcher/index.js b/react-packager/src/DependencyResolver/FileWatcher/index.js index 6c8a0709..d75e0211 100644 --- a/react-packager/src/DependencyResolver/FileWatcher/index.js +++ b/react-packager/src/DependencyResolver/FileWatcher/index.js @@ -96,11 +96,7 @@ function createWatcher(rootConfig) { return new Promise(function(resolve, reject) { const rejectTimeout = setTimeout(function() { - reject(new Error([ - 'Watcher took too long to load', - 'Try running `watchman version` from your terminal', - 'https://facebook.github.io/watchman/docs/troubleshooting.html', - ].join('\n'))); + reject(new Error(timeoutMessage(Watcher))); }, MAX_WAIT_TIME); watcher.once('ready', function() { @@ -111,4 +107,17 @@ function createWatcher(rootConfig) { }); } +function timeoutMessage(Watcher) { + const lines = [ + 'Watcher took too long to load (' + Watcher.name + ')', + ]; + if (Watcher === sane.WatchmanWatcher) { + lines.push( + 'Try running `watchman version` from your terminal', + 'https://facebook.github.io/watchman/docs/troubleshooting.html', + ); + } + return lines.join('\n'); +} + module.exports = FileWatcher; From eed6dde1d41b48ce95778f6f346513163710ccb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 30 Dec 2015 10:07:55 -0800 Subject: [PATCH 498/936] Yellow boxes for HMR errors Reviewed By: vjeux Differential Revision: D2795143 fb-gh-sync-id: fd5c92af511258bb1252d991e994a8c37657644e --- react-packager/src/Resolver/polyfills/require.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 15f8cf55..afcda4ca 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -99,18 +99,20 @@ var mod = modules[id]; if (!mod) { - console.error( + console.warn( 'Cannot accept unknown module `' + id + '`. Make sure you\'re not ' + 'trying to modify something else other than a module ' + '(i.e.: a polyfill).' ); + return; } if (!mod.module.hot) { - console.error( + console.warn( 'Cannot accept module because Hot Module Replacement ' + 'API was not installed.' ); + return; } if (mod.module.hot.acceptCallback) { @@ -120,7 +122,7 @@ mod.module.hot.acceptCallback(); } else { - console.log( + console.warn( '[HMR] Module `' + id + '` can\'t be hot reloaded because it ' + 'doesn\'t provide accept callback hook. Reload the app to get the updates.' ); From e41008f208382fbf0173c4ab627e7b35e4065fbd Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 30 Dec 2015 11:38:44 -0800 Subject: [PATCH 499/936] Consume react, fbjs from npm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: We don't (yet) treat these the same as any other modules because we still have special resolution rules for them in the packager allowing the use of `providesModule`, but I believe this allows people to use npm react in their RN projects and not have duplicate copies of React. Fixes facebook/react-native#2985. This relies on fbjs 0.6, which includes `.flow` files alongside the `.js` files to allow them to be typechecked without additional configuration. This also uses react 0.14.5, which shims a couple of files (as `.native.js`) to avoid DOM-specific bits. Once we fix these in React, we will use the same code on web and native. Hopefully we can also remove the packager support I'm adding here for `.native.js`. This diff is not the desired end state for us – ideally the packager would know nothing of react or fbjs, and we'll get there eventually by not relying on `providesModule` in order to load react and fbjs modules. (fbjs change posted here but not merged yet: https://github.com/facebook/fbjs/pull/84.) This should also allow relay to work seamlessly with RN, but I haven't verified this. public Reviewed By: sebmarkbage Differential Revision: D2786197 fb-gh-sync-id: ff50f28445e949edc9501f4b599df7970813870d --- blacklist.js | 77 ++++++++++--------- .../DependencyGraph/HasteMap.js | 33 +++++--- .../DependencyGraph/ResolutionRequest.js | 5 ++ .../DependencyGraph/index.js | 5 +- react-packager/src/Resolver/index.js | 5 +- 5 files changed, 75 insertions(+), 50 deletions(-) diff --git a/blacklist.js b/blacklist.js index 863e2a9a..05973c28 100644 --- a/blacklist.js +++ b/blacklist.js @@ -13,36 +13,41 @@ var path = require('path'); // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ - 'node_modules/react-haste/renderers/shared/event/eventPlugins/ResponderEventPlugin.js', - 'node_modules/react-haste/React.js', - 'node_modules/react-haste/renderers/dom/ReactDOM.js', + /node_modules[/\\]react[/\\]dist[/\\].*/, + 'node_modules/react/lib/React.js', + 'node_modules/react/lib/ReactDOM.js', // For each of these fbjs files (especially the non-forks/stubs), we should // consider deleting the conflicting copy and just using the fbjs version. - 'node_modules/fbjs-haste/__forks__/Map.js', - 'node_modules/fbjs-haste/__forks__/Promise.js', - 'node_modules/fbjs-haste/__forks__/fetch.js', - 'node_modules/fbjs-haste/core/Deferred.js', - 'node_modules/fbjs-haste/core/PromiseMap.js', - 'node_modules/fbjs-haste/core/areEqual.js', - 'node_modules/fbjs-haste/core/flattenArray.js', - 'node_modules/fbjs-haste/core/isEmpty.js', - 'node_modules/fbjs-haste/core/removeFromArray.js', - 'node_modules/fbjs-haste/core/resolveImmediate.js', - 'node_modules/fbjs-haste/core/sprintf.js', - 'node_modules/fbjs-haste/crypto/crc32.js', - 'node_modules/fbjs-haste/fetch/fetchWithRetries.js', - 'node_modules/fbjs-haste/functional/everyObject.js', - 'node_modules/fbjs-haste/functional/filterObject.js', - 'node_modules/fbjs-haste/functional/forEachObject.js', - 'node_modules/fbjs-haste/functional/someObject.js', - 'node_modules/fbjs-haste/request/xhrSimpleDataSerializer.js', - 'node_modules/fbjs-haste/stubs/ErrorUtils.js', - 'node_modules/fbjs-haste/stubs/URI.js', - 'node_modules/fbjs-haste/useragent/UserAgent.js', - 'node_modules/fbjs-haste/utils/nullthrows.js', + // + // fbjs forks: + 'node_modules/fbjs/lib/Map.js', + 'node_modules/fbjs/lib/Promise.js', + 'node_modules/fbjs/lib/fetch.js', + // fbjs stubs: + 'node_modules/fbjs/lib/ErrorUtils.js', + 'node_modules/fbjs/lib/URI.js', + // fbjs modules: + 'node_modules/fbjs/lib/Deferred.js', + 'node_modules/fbjs/lib/PromiseMap.js', + 'node_modules/fbjs/lib/UserAgent.js', + 'node_modules/fbjs/lib/areEqual.js', + 'node_modules/fbjs/lib/base62.js', + 'node_modules/fbjs/lib/crc32.js', + 'node_modules/fbjs/lib/everyObject.js', + 'node_modules/fbjs/lib/fetchWithRetries.js', + 'node_modules/fbjs/lib/filterObject.js', + 'node_modules/fbjs/lib/flattenArray.js', + 'node_modules/fbjs/lib/forEachObject.js', + 'node_modules/fbjs/lib/isEmpty.js', + 'node_modules/fbjs/lib/nullthrows.js', + 'node_modules/fbjs/lib/removeFromArray.js', + 'node_modules/fbjs/lib/resolveImmediate.js', + 'node_modules/fbjs/lib/someObject.js', + 'node_modules/fbjs/lib/sprintf.js', + 'node_modules/fbjs/lib/xhrSimpleDataSerializer.js', - // Those conflicts with the ones in fbjs-haste/. We need to blacklist the + // Those conflicts with the ones in fbjs/. We need to blacklist the // internal version otherwise they won't work in open source. 'downstream/core/CSSCore.js', 'downstream/core/TouchEventUtils.js', @@ -63,11 +68,8 @@ var sharedBlacklist = [ 'downstream/core/invariant.js', 'downstream/core/nativeRequestAnimationFrame.js', 'downstream/core/toArray.js', -]; -// Raw unescaped patterns in case you need to use wildcards -var sharedBlacklistWildcards = [ - 'website\/node_modules\/.*', + /website\/node_modules\/.*/, ]; var platformBlacklists = { @@ -85,10 +87,16 @@ var platformBlacklists = { ], }; -function escapeRegExp(str) { - var escaped = str.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); - // convert the '/' into an escaped local file separator - return escaped.replace(/\//g,'\\' + path.sep); +function escapeRegExp(pattern) { + if (Object.prototype.toString.call(pattern) === '[object RegExp]') { + return pattern.source; + } else if (typeof pattern === 'string') { + var escaped = pattern.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + // convert the '/' into an escaped local file separator + return escaped.replace(/\//g,'\\' + path.sep); + } else { + throw new Error('Unexpected packager blacklist pattern: ' + pattern); + } } function blacklist(platform, additionalBlacklist) { @@ -96,7 +104,6 @@ function blacklist(platform, additionalBlacklist) { (additionalBlacklist || []).concat(sharedBlacklist) .concat(platformBlacklists[platform] || []) .map(escapeRegExp) - .concat(sharedBlacklistWildcards) .join('|') + ')$' ); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js index 38d27e3e..2f0b26af 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -12,12 +12,20 @@ const getPlatformExtension = require('../lib/getPlatformExtension'); const Promise = require('promise'); const GENERIC_PLATFORM = 'generic'; +const NATIVE_PLATFORM = 'native'; class HasteMap { - constructor({ extensions, fastfs, moduleCache, helpers }) { + constructor({ + extensions, + fastfs, + moduleCache, + preferNativePlatform, + helpers, + }) { this._extensions = extensions; this._fastfs = fastfs; this._moduleCache = moduleCache; + this._preferNativePlatform = preferNativePlatform; this._helpers = helpers; } @@ -73,18 +81,19 @@ class HasteMap { return null; } - // If no platform is given we choose the generic platform module list. - // If a platform is given and no modules exist we fallback - // to the generic platform module list. - if (platform == null) { - return modulesMap[GENERIC_PLATFORM]; - } else { - let module = modulesMap[platform]; - if (module == null) { - module = modulesMap[GENERIC_PLATFORM]; - } - return module; + // If platform is 'ios', we prefer .ios.js to .native.js which we prefer to + // a plain .js file. + let module = undefined; + if (module == null && platform != null) { + module = modulesMap[platform]; } + if (module == null && this._preferNativePlatform) { + module = modulesMap[NATIVE_PLATFORM]; + } + if (module == null) { + module = modulesMap[GENERIC_PLATFORM]; + } + return module; } _processHasteModule(file) { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 2125b97f..f896de27 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -18,6 +18,7 @@ const Promise = require('promise'); class ResolutionRequest { constructor({ platform, + preferNativePlatform, entryPath, hasteMap, deprecatedAssetMap, @@ -26,6 +27,7 @@ class ResolutionRequest { fastfs, }) { this._platform = platform; + this._preferNativePlatform = preferNativePlatform; this._entryPath = entryPath; this._hasteMap = hasteMap; this._deprecatedAssetMap = deprecatedAssetMap; @@ -329,6 +331,9 @@ class ResolutionRequest { } else if (this._platform != null && this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) { file = potentialModulePath + '.' + this._platform + '.js'; + } else if (this._preferNativePlatform && + this._fastfs.fileExists(potentialModulePath + '.native.js')) { + file = potentialModulePath + '.native.js'; } else if (this._fastfs.fileExists(potentialModulePath + '.js')) { file = potentialModulePath + '.js'; } else if (this._fastfs.fileExists(potentialModulePath + '.json')) { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index ac88a2e3..c4e983ad 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -37,6 +37,7 @@ class DependencyGraph { assetExts, providesModuleNodeModules, platforms, + preferNativePlatform, cache, extensions, mocksPattern, @@ -51,6 +52,7 @@ class DependencyGraph { assetExts: assetExts || [], providesModuleNodeModules, platforms: platforms || [], + preferNativePlatform: preferNativePlatform || false, cache, extensions: extensions || ['js', 'json'], mocksPattern, @@ -105,7 +107,7 @@ class DependencyGraph { fastfs: this._fastfs, extensions: this._opts.extensions, moduleCache: this._moduleCache, - assetExts: this._opts.exts, + preferNativePlatform: this._opts.preferNativePlatform, helpers: this._helpers, }); @@ -147,6 +149,7 @@ class DependencyGraph { const absPath = this._getAbsolutePath(entryPath); const req = new ResolutionRequest({ platform, + preferNativePlatform: this._opts.preferNativePlatform, entryPath: absPath, deprecatedAssetMap: this._deprecatedAssetMap, hasteMap: this._hasteMap, diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index d0f92285..ecc9eeeb 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -81,8 +81,8 @@ class Resolver { (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, providesModuleNodeModules: [ - 'fbjs-haste', - 'react-haste', + 'fbjs', + 'react', 'react-native', // Parse requires AsyncStorage. They will // change that to require('react-native') which @@ -92,6 +92,7 @@ class Resolver { 'react-transform-hmr', ], platforms: ['ios', 'android'], + preferNativePlatform: true, fileWatcher: opts.fileWatcher, cache: opts.cache, }); From b174b2fcd9dff9b49a44a111cb60ca7b1c15fa36 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Sat, 2 Jan 2016 03:56:32 -0500 Subject: [PATCH 500/936] Fix issues running the website locally on windows with npm3 --- blacklist.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blacklist.js b/blacklist.js index 05973c28..4fdca23c 100644 --- a/blacklist.js +++ b/blacklist.js @@ -89,7 +89,7 @@ var platformBlacklists = { function escapeRegExp(pattern) { if (Object.prototype.toString.call(pattern) === '[object RegExp]') { - return pattern.source; + return pattern.source.replace(/\//g, path.sep); } else if (typeof pattern === 'string') { var escaped = pattern.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); // convert the '/' into an escaped local file separator From e94c9d042b92fb456553ab3a4b08f6fefae94c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 4 Jan 2016 11:31:48 -0800 Subject: [PATCH 501/936] Introduce transforms pipeline Summary: public This diff introduces an internal transforms pipeline that integrates with the external one. This has been a feature we've been looking to implement for a long time to use babel instead of `replace` with regexps on many parts of the packager. Also, to split the bundle we'll need to run one transform. Internally for Facebook we can run the system-import transform altogether withe the other ones. For OSS we offer `transformer.js` which people can use out of the box if they're writing ES6 code. For those people, `transformer.js` will also run the internal transforms`. However they might want to tune the transforms, or even write the code on another language that compiles to Javascript and use a complete different transformer. On those cases we'll need to run the external transforms first and pipe the output through the internal transforms. Note that the order it's important as the internal transforms assume the code is written in JS, though the original code could be on other scripting languages (CoffeeScript, TypeScript, etc). Reviewed By: davidaurelio Differential Revision: D2725109 fb-gh-sync-id: d764e209c78743419c4cb97068495c771372ab90 --- react-packager/index.js | 2 + react-packager/src/Bundler/index.js | 5 + react-packager/src/JSTransformer/index.js | 16 ++- react-packager/src/JSTransformer/worker.js | 71 +++++++++++--- react-packager/src/Server/index.js | 4 + react-packager/src/SocketInterface/index.js | 4 +- .../babel-plugin-system-import/index.js | 98 +++++++++---------- react-packager/src/transforms/index.js | 16 +++ transformer.js | 6 +- 9 files changed, 152 insertions(+), 70 deletions(-) create mode 100644 react-packager/src/transforms/index.js diff --git a/react-packager/index.js b/react-packager/index.js index 5b28e774..367f23f3 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -15,6 +15,7 @@ useGracefulFs(); var debug = require('debug'); var omit = require('underscore').omit; var Activity = require('./src/Activity'); +var Transforms = require('./src/transforms'); exports.createServer = createServer; exports.middleware = function(options) { @@ -23,6 +24,7 @@ exports.middleware = function(options) { }; exports.Activity = Activity; +exports.getTransforms = Transforms.getAll; // Renamed "package" to "bundle". But maintain backwards // compat. diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index dc76a267..7fdbe911 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -56,6 +56,10 @@ const validateOpts = declareOpts({ type:'string', required: false, }, + enableInternalTransforms: { + type: 'boolean', + default: false, + }, nonPersistent: { type: 'boolean', default: false, @@ -131,6 +135,7 @@ class Bundler { blacklistRE: opts.blacklistRE, cache: this._cache, transformModulePath: opts.transformModulePath, + enableInternalTransforms: opts.enableInternalTransforms, }); this._projectRoots = opts.projectRoots; diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 07225252..89f31162 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -52,7 +52,11 @@ const validateOpts = declareOpts({ transformTimeoutInterval: { type: 'number', default: DEFAULT_MAX_CALL_TIME, - } + }, + enableInternalTransforms: { + type: 'boolean', + default: false, + }, }); class Transformer { @@ -60,6 +64,8 @@ class Transformer { const opts = this._opts = validateOpts(options); this._cache = opts.cache; + this._transformModulePath = opts.transformModulePath; + this._enableInternalTransforms = opts.enableInternalTransforms; if (opts.transformModulePath != null) { this._workers = workerFarm({ @@ -68,7 +74,7 @@ class Transformer { maxCallsPerWorker: MAX_CALLS_PER_WORKER, maxCallTime: opts.transformTimeoutInterval, maxRetries: MAX_RETRIES, - }, opts.transformModulePath); + }, require.resolve('./worker')); this._transform = Promise.denodeify(this._workers); } @@ -102,7 +108,11 @@ class Transformer { return this._transform({ sourceCode, filename: filePath, - options, + options: { + ...options, + externalTransformModulePath: this._transformModulePath, + enableInternalTransforms: this._enableInternalTransforms, + }, }).then(res => { if (res.error) { console.warn( diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js index b6b4f363..77a24938 100644 --- a/react-packager/src/JSTransformer/worker.js +++ b/react-packager/src/JSTransformer/worker.js @@ -8,27 +8,66 @@ */ 'use strict'; -var transformer = require('./transformer'); +var babel = require('babel-core'); +var Transforms = require('../transforms'); -module.exports = function (data, callback) { +// Runs internal transforms on the given sourceCode. Note that internal +// transforms should be run after the external ones to ensure that they run on +// Javascript code +function internalTransforms(sourceCode, filename, options) { + var result = babel.transform(sourceCode, { + retainLines: true, + compact: true, + comments: false, + filename: filename, + sourceFileName: filename, + sourceMaps: false, + plugins: Transforms.getAll() + }); + + return { + code: result.code, + filename: filename, + }; +} + +function onExternalTransformDone(data, callback, error, externalOutput) { var result; - try { - result = transformer.transform( - data.transformSets, - data.sourceCode, + if (data.options.enableInternalTransforms) { + result = internalTransforms( + externalOutput.code, + externalOutput.filename, data.options ); - } catch (e) { - return callback(null, { - error: { - lineNumber: e.lineNumber, - column: e.column, - message: e.message, - stack: e.stack, - description: e.description - } - }); + } else { + result = externalOutput; } callback(null, result); +} + +module.exports = function(data, callback) { + try { + if (data.options.externalTransformModulePath) { + var externalTransformModule = require( + data.options.externalTransformModulePath + ); + externalTransformModule( + data, + onExternalTransformDone.bind(null, data, callback) + ); + } else { + onExternalTransformDone( + data, + callback, + null, + { + code: data.sourceCode, + filename: data.filename + } + ); + } + } catch (e) { + callback(e); + } }; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 717ef9e1..3e18a23c 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -52,6 +52,10 @@ const validateOpts = declareOpts({ type: 'boolean', default: false, }, + enableInternalTransforms: { + type: 'boolean', + default: false, + }, assetRoots: { type: 'array', required: false, diff --git a/react-packager/src/SocketInterface/index.js b/react-packager/src/SocketInterface/index.js index 32e3688c..e2d70bbf 100644 --- a/react-packager/src/SocketInterface/index.js +++ b/react-packager/src/SocketInterface/index.js @@ -30,7 +30,9 @@ const SocketInterface = { const value = options[key]; if (value) { hash.update( - typeof value === 'string' ? value : JSON.stringify(value) + options[key] != null && typeof value === 'string' + ? value + : JSON.stringify(value) ); } }); diff --git a/react-packager/src/transforms/babel-plugin-system-import/index.js b/react-packager/src/transforms/babel-plugin-system-import/index.js index 8a6cbcf0..7cf0f0a0 100644 --- a/react-packager/src/transforms/babel-plugin-system-import/index.js +++ b/react-packager/src/transforms/babel-plugin-system-import/index.js @@ -1,63 +1,63 @@ - /** - * 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. - * - */ +/** + * 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. + * + */ /*jslint node: true */ 'use strict'; const t = require('babel-types'); /** - * Transforms asynchronous module importing into a function call - * that includes which bundle needs to be loaded - * - * Transforms: - * - * System.import('moduleA') - * - * to: - * - * loadBundles('bundleA') - */ +* Transforms asynchronous module importing into a function call +* that includes which bundle needs to be loaded +* +* Transforms: +* +* System.import('moduleA') +* +* to: +* +* loadBundles('bundleA') +*/ module.exports = function() { - return { - visitor: { - CallExpression: function (path, state) { - if (!isAppropriateSystemImportCall(path.node)) { - return; - } + return { + visitor: { + CallExpression: function (path, state) { + if (!isAppropriateSystemImportCall(path.node)) { + return; + } - var bundlesLayout = state.opts.bundlesLayout; - var bundleID = bundlesLayout.getBundleIDForModule( - path.node.arguments[0].value - ); + var bundlesLayout = state.opts.bundlesLayout; + var bundleID = bundlesLayout.getBundleIDForModule( + path.node.arguments[0].value + ); - var bundles = bundleID.split('.'); - bundles.splice(0, 1); - bundles = bundles.map(function(id) { - return t.stringLiteral('bundle.' + id); - }); + var bundles = bundleID.split('.'); + bundles.splice(0, 1); + bundles = bundles.map(function(id) { + return t.stringLiteral('bundle.' + id); + }); - path.replaceWith(t.callExpression( - t.identifier('loadBundles'), - [t.arrayExpression(bundles)] - )); - }, - }, - }; + path.replaceWith(t.callExpression( + t.identifier('loadBundles'), + [t.arrayExpression(bundles)] + )); + }, + }, + }; }; function isAppropriateSystemImportCall(node) { - return ( - node.callee.type === 'MemberExpression' && - node.callee.object.name === 'System' && - node.callee.property.name === 'import' && - node.arguments.length === 1 && - node.arguments[0].type === 'StringLiteral' - ); + return ( + node.callee.type === 'MemberExpression' && + node.callee.object.name === 'System' && + node.callee.property.name === 'import' && + node.arguments.length === 1 && + node.arguments[0].type === 'StringLiteral' + ); } diff --git a/react-packager/src/transforms/index.js b/react-packager/src/transforms/index.js new file mode 100644 index 00000000..1e55a34b --- /dev/null +++ b/react-packager/src/transforms/index.js @@ -0,0 +1,16 @@ +/** + * 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'; + +exports.getAll = function() { + return [ + require('./babel-plugin-system-import'), + ]; +}; + diff --git a/transformer.js b/transformer.js index 70dde770..51268606 100644 --- a/transformer.js +++ b/transformer.js @@ -15,6 +15,7 @@ const fs = require('fs'); const inlineRequires = require('fbjs-scripts/babel-6/inline-requires'); const json5 = require('json5'); const path = require('path'); +const ReactPackager = require('./react-packager'); const babelRC = json5.parse( @@ -53,10 +54,13 @@ function transform(src, filename, options) { return plugin; }); + config.plugins = config.plugins.concat(ReactPackager.getTransforms()); + const result = babel.transform(src, Object.assign({}, babelRC, config)); return { - code: result.code + code: result.code, + filename: filename, }; } From 3ee52f04ce2c7b8135616fcf4e473b8754308993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 4 Jan 2016 13:01:28 -0800 Subject: [PATCH 502/936] Make HMR server send full list modules that changed Summary: public Before this diff we were only accepting the module that was modified but the user. This works fine as long as the user doesn't modify the dependencies a module has but once he starts doing so the HMR runtime may fail when updating modules' code because they might might a few dependencies. For instance, if the user changes the `src` a `Image` has to reference an image (using the new asset system) that wasn't on the original bundle the user will get a red box. This diff addresses this by diffing the modules the app currently has with the new ones it should have and including all of them on the HMR update. Note this diffing is only done when the we realize the module that was modified changed it's dependencies so there's no additional overhead on this change. Reviewed By: vjeux Differential Revision: D2796325 fb-gh-sync-id: cac95f2e995310634c221bbbb09d9f3e7bc03e8d --- react-packager/src/Bundler/index.js | 52 ++++++++++--------- .../DependencyGraph/index.js | 7 +++ react-packager/src/Resolver/index.js | 4 ++ .../src/Resolver/polyfills/require.js | 8 +-- react-packager/src/Server/index.js | 35 +++---------- 5 files changed, 47 insertions(+), 59 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 7fdbe911..bbb4d3bb 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -284,31 +284,29 @@ class Bundler { }); } - bundleForHMR({ - entryFile, - platform, - }) { - return this.getDependencies(entryFile, /*isDev*/true, platform).then(response => { - const module = response.dependencies.filter(module => module.path === entryFile)[0]; - - return Promise.all([ - module.getName(), - this._transformer.loadFileAndTransform( - path.resolve(entryFile), - // TODO(martinb): pass non null main (t9527509) - this._getTransformOptions({main: null}, {hot: true}), - ), - ]).then(([moduleName, transformedSource]) => { - return (` - __accept( - '${moduleName}', - function(global, require, module, exports) { - ${transformedSource.code} - } - ); - `); - }); - }); + bundleForHMR(modules) { + return Promise.all( + modules.map(module => { + return Promise.all([ + module.getName(), + this._transformer.loadFileAndTransform( + module.path, + // TODO(martinb): pass non null main (t9527509) + this._getTransformOptions({main: null}, {hot: true}), + ), + ]).then(([moduleName, transformedSource]) => { + return (` + __accept( + '${moduleName}', + function(global, require, module, exports) { + ${transformedSource.code} + } + ); + `); + }); + }) + ) + .then(code => code.join('\n')); } invalidateFile(filePath) { @@ -319,6 +317,10 @@ class Bundler { return this._resolver.getShallowDependencies(entryFile); } + getModuleForPath(entryFile) { + return this._resolver.getModuleForPath(entryFile); + } + getDependencies(main, isDev, platform) { return this._resolver.getDependencies(main, { dev: isDev, platform }); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index c4e983ad..ec0d964e 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -143,6 +143,13 @@ class DependencyGraph { return this._moduleCache.getModule(entryPath).getDependencies(); } + /** + * Returns the module object for the given path. + */ + getModuleForPath(entryFile) { + return this._moduleCache.getModule(entryFile); + } + getDependencies(entryPath, platform) { return this.load().then(() => { platform = this._getRequestPlatform(entryPath, platform); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index ecc9eeeb..efa61d57 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -104,6 +104,10 @@ class Resolver { return this._depGraph.getShallowDependencies(entryFile); } + getModuleForPath(entryFile) { + return this._depGraph.getModuleForPath(entryFile); + } + getDependencies(main, options) { const opts = getDependenciesValidateOpts(options); diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index afcda4ca..36c2b6b9 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -99,12 +99,8 @@ var mod = modules[id]; if (!mod) { - console.warn( - 'Cannot accept unknown module `' + id + '`. Make sure you\'re not ' + - 'trying to modify something else other than a module ' + - '(i.e.: a polyfill).' - ); - return; + define(id, factory); + return; // new modules don't need to be accepted } if (!mod.module.hot) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 3e18a23c..858bc0e4 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -120,17 +120,6 @@ const bundleOpts = declareOpts({ }, }); -const hmrBundleOpts = declareOpts({ - entryFile: { - type: 'string', - required: true, - }, - platform: { - type: 'string', - required: true, - }, -}); - const dependencyOpts = declareOpts({ platform: { type: 'string', @@ -199,13 +188,10 @@ class Server { // updates. Instead, send the HMR updates right away and once that // finishes, invoke any other file change listener. if (this._hmrFileChangeListener) { - this._hmrFileChangeListener(filePath).then(() => { - this._fileChangeListeners.forEach(listener => listener(filePath)); - }).done(); + this._hmrFileChangeListener(filePath); return; } - this._fileChangeListeners.forEach(listener => listener(filePath)); this._rebuildBundles(filePath); this._informChangeWatchers(); }, 50); @@ -218,10 +204,6 @@ class Server { ]); } - addFileChangeListener(listener) { - this._fileChangeListeners.push(listener); - } - setHMRFileChangeListener(listener) { this._hmrFileChangeListener = listener; } @@ -253,21 +235,18 @@ class Server { return this.buildBundle(options); } - buildBundleForHMR(options) { - return Promise.resolve().then(() => { - if (!options.platform) { - options.platform = getPlatformExtension(options.entryFile); - } - - const opts = hmrBundleOpts(options); - return this._bundler.bundleForHMR(opts); - }); + buildBundleForHMR(modules) { + return this._bundler.bundleForHMR(modules); } getShallowDependencies(entryFile) { return this._bundler.getShallowDependencies(entryFile); } + getModuleForPath(entryFile) { + return this._bundler.getModuleForPath(entryFile); + } + getDependencies(options) { return Promise.resolve().then(() => { if (!options.platform) { From b34845e4b0051a577d6f145d3306fdbcc325403b Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Tue, 5 Jan 2016 10:45:00 -0800 Subject: [PATCH 503/936] Fix lint warnings in the resolver Reviewed By: martinbigio Differential Revision: D2803192 fb-gh-sync-id: 721821ed9ce8dd846f90ec09e7e1f36abf322b5a --- .../DependencyGraph/__tests__/DependencyGraph-test.js | 8 ++++---- react-packager/src/DependencyResolver/ModuleCache.js | 2 +- .../src/DependencyResolver/__tests__/Module-test.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index b99d6065..cd434a1d 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -2424,7 +2424,7 @@ describe('DependencyGraph', function() { ' * @providesModule wontWork', ' */', 'hi();', - ].join('\n') + ].join('\n'), }, }, // This part of the dep graph is meant to emulate internal facebook infra. @@ -2442,7 +2442,7 @@ describe('DependencyGraph', function() { ' */', 'hiFromInternalPackage();', ].join('\n'), - } + }, }, }, // we need to support multiple roots and using haste between them @@ -2453,7 +2453,7 @@ describe('DependencyGraph', function() { ' */', 'wazup()', ].join('\n'), - } + }, }); var dgraph = new DependencyGraph({ @@ -2472,7 +2472,7 @@ describe('DependencyGraph', function() { 'wontWork', 'ember', 'internalVendoredPackage', - 'anotherIndex' + 'anotherIndex', ], isAsset: false, isAsset_DEPRECATED: false, diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index 0fba47a9..a4836d0a 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -26,7 +26,7 @@ class ModuleCache { moduleCache: this, cache: this._cache, extractor: this._extractRequires, - depGraphHelpers: this._depGraphHelpers + depGraphHelpers: this._depGraphHelpers, }); } return this._moduleCache[filePath]; diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 7938ca79..ea95c43a 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -56,7 +56,7 @@ describe('Module', () => { fastfs, moduleCache: new ModuleCache(fastfs, cache), cache: cache, - depGraphHelpers: new DependencyGraphHelpers() + depGraphHelpers: new DependencyGraphHelpers(), }); return module.getAsyncDependencies().then(actual => @@ -130,7 +130,7 @@ describe('Module', () => { moduleCache: new ModuleCache(fastfs, cache), cache, extractor, - depGraphHelpers: new DependencyGraphHelpers() + depGraphHelpers: new DependencyGraphHelpers(), }); }); } From 18f1fb20466fec4b7f080c193641fce1ef51927c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 5 Jan 2016 21:56:41 -0800 Subject: [PATCH 504/936] Pipe transforms errors through transformation pipelines Summary: public Fixes an issue on the transforms pipeline which caused not to pipe errors that occured on the external transformer to JSTransformer. Reviewed By: yungsters Differential Revision: D2806498 fb-gh-sync-id: c9347d1957a3a9320b3f177ff9b19bf3802087a0 --- react-packager/src/JSTransformer/worker.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js index 77a24938..ef4a529c 100644 --- a/react-packager/src/JSTransformer/worker.js +++ b/react-packager/src/JSTransformer/worker.js @@ -32,6 +32,11 @@ function internalTransforms(sourceCode, filename, options) { } function onExternalTransformDone(data, callback, error, externalOutput) { + if (error) { + callback(error); + return; + } + var result; if (data.options.enableInternalTransforms) { result = internalTransforms( From 3ba3503cd8979cedf6e766c6ece1716d1d1cacbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 5 Jan 2016 22:02:14 -0800 Subject: [PATCH 505/936] Fix cache errors Reviewed By: yungsters Differential Revision: D2806495 fb-gh-sync-id: 4c1088cde8f0b88070f31b3a130b66f20a2a07cb --- react-packager/src/DependencyResolver/Cache/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/react-packager/src/DependencyResolver/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js index 8552e0dc..84b7524d 100644 --- a/react-packager/src/DependencyResolver/Cache/index.js +++ b/react-packager/src/DependencyResolver/Cache/index.js @@ -135,6 +135,10 @@ class Cache { .then(values => { var json = Object.create(null); Object.keys(data).forEach((key, i) => { + if (!values[i]) { + return; + } + json[key] = Object.create(null); json[key].metadata = data[key].metadata; json[key].data = values[i].data; From d30da1ec019d9187a900e852a41aed83b53ca4e0 Mon Sep 17 00:00:00 2001 From: Martin Bigio Date: Wed, 6 Jan 2016 07:14:20 -0800 Subject: [PATCH 506/936] Increase worker-farm timeout Reviewed By: davidaurelio Differential Revision: D2806738 fb-gh-sync-id: a165578002415388af830f77bd96f4888143028f --- 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 89f31162..1bb845d7 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -24,10 +24,10 @@ 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 = 120000; +const DEFAULT_MAX_CALL_TIME = 300000; // How may times can we tolerate failures from the worker. -const MAX_RETRIES = 3; +const MAX_RETRIES = 2; const validateOpts = declareOpts({ projectRoots: { From e02756ca213b2d2afd8866710e422cdbca2015f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 6 Jan 2016 09:46:56 -0800 Subject: [PATCH 507/936] Resolve requires on HMR Summary: public Requires are transformed when building the bundle but we forgot doing so when building the HMR one. Reviewed By: vjeux Differential Revision: D2801319 fb-gh-sync-id: ae70612945ab81a05154b14d6b756ef390770542 --- react-packager/src/Bundler/index.js | 52 ++++++++++++++++------------ react-packager/src/Resolver/index.js | 18 ++++++++-- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index bbb4d3bb..23cf71a9 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -284,29 +284,37 @@ class Bundler { }); } - bundleForHMR(modules) { - return Promise.all( - modules.map(module => { - return Promise.all([ - module.getName(), - this._transformer.loadFileAndTransform( - module.path, - // TODO(martinb): pass non null main (t9527509) - this._getTransformOptions({main: null}, {hot: true}), - ), - ]).then(([moduleName, transformedSource]) => { - return (` - __accept( - '${moduleName}', - function(global, require, module, exports) { - ${transformedSource.code} - } - ); - `); - }); + bundleForHMR({entryFile, platform, modules}) { + return this.getDependencies(entryFile, /*isDev*/true, platform) + .then(response => { + return Promise.all( + modules.map(module => { + return Promise.all([ + module.getName(), + this._transformer.loadFileAndTransform( + module.path, + // TODO(martinb): pass non null main (t9527509) + this._getTransformOptions({main: null}, {hot: true}), + ), + ]).then(([moduleName, transformed]) => { + return this._resolver.resolveRequires(response, + module, + transformed.code, + ).then(({name, code}) => { + return (` + __accept( + '${moduleName}', + function(global, require, module, exports) { + ${code} + } + ); + `); + }); + }); + }) + ); }) - ) - .then(code => code.join('\n')); + .then(modules => modules.join('\n')); } invalidateFile(filePath) { diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index efa61d57..8726233e 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -165,7 +165,7 @@ class Resolver { ); } - wrapModule(resolutionResponse, module, code) { + resolveRequires(resolutionResponse, module, code) { return Promise.resolve().then(() => { if (module.isPolyfill()) { return Promise.resolve({code}); @@ -200,12 +200,24 @@ class Resolver { .replace(replacePatterns.EXPORT_RE, relativizeCode) .replace(replacePatterns.REQUIRE_RE, relativizeCode); - return module.getName().then(name => - ({name, code: defineModuleCode(name, code)})); + return module.getName().then(name => { + return {name, code}; + }); }); }); } + wrapModule(resolutionResponse, module, code) { + if (module.isPolyfill()) { + return Promise.resolve({code}); + } + + return this.resolveRequires(resolutionResponse, module, code).then( + ({name, code}) => { + return {name, code: defineModuleCode(name, code)}; + }); + } + getDebugInfo() { return this._depGraph.getDebugInfo(); } From 9e5dfbddbc2f21d49faea11dc26468ec7dd9ed36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 6 Jan 2016 09:46:59 -0800 Subject: [PATCH 508/936] Move HMR to internal transform Summary: public We want to support Hot Loading on the packager itself instead of on the transformer. This will allow us to enable it on OSS (and for any scripting language, yay!). For now to enable Hot Loading the packager's internals transforms need to be manually enabled (start packager with `--enable-internal-transforms`). I think the internal pipeline should always be enabled as it doesn't affect performance if there're no transforms and the user can disable Hot Loading through the setting on the app though. I'll tweak this on a follow up commit. Reviewed By: vjeux Differential Revision: D2801343 fb-gh-sync-id: 563984d77b10c3925fda6fd5616b814cdbea2c66 --- react-packager/src/Bundler/index.js | 5 ----- react-packager/src/JSTransformer/index.js | 6 ------ react-packager/src/JSTransformer/worker.js | 17 ++++++--------- react-packager/src/Server/index.js | 4 ---- react-packager/src/transforms/index.js | 25 ++++++++++++++++++---- transformer.js | 2 -- 6 files changed, 27 insertions(+), 32 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 23cf71a9..26de3130 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -56,10 +56,6 @@ const validateOpts = declareOpts({ type:'string', required: false, }, - enableInternalTransforms: { - type: 'boolean', - default: false, - }, nonPersistent: { type: 'boolean', default: false, @@ -135,7 +131,6 @@ class Bundler { blacklistRE: opts.blacklistRE, cache: this._cache, transformModulePath: opts.transformModulePath, - enableInternalTransforms: opts.enableInternalTransforms, }); this._projectRoots = opts.projectRoots; diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 1bb845d7..2688e14c 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -53,10 +53,6 @@ const validateOpts = declareOpts({ type: 'number', default: DEFAULT_MAX_CALL_TIME, }, - enableInternalTransforms: { - type: 'boolean', - default: false, - }, }); class Transformer { @@ -65,7 +61,6 @@ class Transformer { this._cache = opts.cache; this._transformModulePath = opts.transformModulePath; - this._enableInternalTransforms = opts.enableInternalTransforms; if (opts.transformModulePath != null) { this._workers = workerFarm({ @@ -111,7 +106,6 @@ class Transformer { options: { ...options, externalTransformModulePath: this._transformModulePath, - enableInternalTransforms: this._enableInternalTransforms, }, }).then(res => { if (res.error) { diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js index ef4a529c..5e6e953b 100644 --- a/react-packager/src/JSTransformer/worker.js +++ b/react-packager/src/JSTransformer/worker.js @@ -22,7 +22,7 @@ function internalTransforms(sourceCode, filename, options) { filename: filename, sourceFileName: filename, sourceMaps: false, - plugins: Transforms.getAll() + plugins: Transforms.getAll(options) }); return { @@ -37,16 +37,11 @@ function onExternalTransformDone(data, callback, error, externalOutput) { return; } - var result; - if (data.options.enableInternalTransforms) { - result = internalTransforms( - externalOutput.code, - externalOutput.filename, - data.options - ); - } else { - result = externalOutput; - } + var result = internalTransforms( + externalOutput.code, + externalOutput.filename, + data.options + ); callback(null, result); } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 858bc0e4..f5c5b34e 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -52,10 +52,6 @@ const validateOpts = declareOpts({ type: 'boolean', default: false, }, - enableInternalTransforms: { - type: 'boolean', - default: false, - }, assetRoots: { type: 'array', required: false, diff --git a/react-packager/src/transforms/index.js b/react-packager/src/transforms/index.js index 1e55a34b..33cb2462 100644 --- a/react-packager/src/transforms/index.js +++ b/react-packager/src/transforms/index.js @@ -8,9 +8,26 @@ */ 'use strict'; -exports.getAll = function() { - return [ - require('./babel-plugin-system-import'), - ]; +exports.getAll = function(options) { + var plugins = []; + if (options.hot) { + plugins = plugins.concat([ + [ + 'react-transform', + { + transforms: [{ + transform: 'react-transform-hmr/lib/index.js', + imports: ['React'], + locals: ['module'], + }] + }, + ], + 'transform-es2015-block-scoping', + 'transform-es2015-constants', + ['transform-es2015-modules-commonjs', {strict: false, allowTopLevelThis: true}], + ]); + } + + return plugins; }; diff --git a/transformer.js b/transformer.js index 51268606..8a178c86 100644 --- a/transformer.js +++ b/transformer.js @@ -54,8 +54,6 @@ function transform(src, filename, options) { return plugin; }); - config.plugins = config.plugins.concat(ReactPackager.getTransforms()); - const result = babel.transform(src, Object.assign({}, babelRC, config)); return { From 85c016df312aa75512a9c45841d5d84d99ccffb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Kok=20J=C3=B8rgensen?= Date: Wed, 6 Jan 2016 11:02:35 -0800 Subject: [PATCH 509/936] Dont call npm before node is available Summary: This fixes an error introduced in `0.18.0-rc` `node` and `npm` isn't available if the developer is using `nvm` or `nodeenv`. XCode throws an error because of the call to `npm`. Simple move the line to after `node` and `npm` has been setup. Closes https://github.com/facebook/react-native/pull/5156 Reviewed By: svcscm Differential Revision: D2807589 Pulled By: androidtrunkagent fb-gh-sync-id: 30c33145b2cb6f30ff67f6648153d5aa67fb74ed --- react-native-xcode.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index b655222f..89886261 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -29,9 +29,6 @@ cd .. set -x DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH -# npm global install path may be a non-standard location -PATH="$(npm prefix -g)/bin:$PATH" - # Define NVM_DIR and source the nvm.sh setup script [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" @@ -46,6 +43,9 @@ if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then eval "$($HOME/.nodenv/bin/nodenv init -)" fi +# npm global install path may be a non-standard location +PATH="$(npm prefix -g)/bin:$PATH" + react-native bundle \ --entry-file index.ios.js \ --platform ios \ From db91ff81fd9596523c2eec6d94bbb80d90cddb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 6 Jan 2016 20:03:30 -0800 Subject: [PATCH 510/936] Bail internal transforms if there're no transforms to run Reviewed By: sam-swarr Differential Revision: D2810474 fb-gh-sync-id: d5ba48922c91c3dbacfccb0d087cc1deac14fa1c --- react-packager/src/JSTransformer/worker.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js index 5e6e953b..27542274 100644 --- a/react-packager/src/JSTransformer/worker.js +++ b/react-packager/src/JSTransformer/worker.js @@ -15,6 +15,14 @@ var Transforms = require('../transforms'); // transforms should be run after the external ones to ensure that they run on // Javascript code function internalTransforms(sourceCode, filename, options) { + var plugins = Transforms.getAll(options); + if (plugins.length === 0) { + return { + code: sourceCode, + filename: filename, + }; + } + var result = babel.transform(sourceCode, { retainLines: true, compact: true, @@ -22,7 +30,7 @@ function internalTransforms(sourceCode, filename, options) { filename: filename, sourceFileName: filename, sourceMaps: false, - plugins: Transforms.getAll(options) + plugins: plugins, }); return { From a62f260680bb7c2104a3f59b3f1fd14f6631b5ed Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Thu, 7 Jan 2016 06:13:14 -0800 Subject: [PATCH 511/936] Add option to throw/not throw when module can't resolve Reviewed By: martinbigio Differential Revision: D2808973 fb-gh-sync-id: 2e06355d1e0dd3c1ea297273c44858ec4ed574ee --- .../DependencyGraph/ResolutionRequest.js | 8 ++++++-- .../DependencyGraph/__tests__/DependencyGraph-test.js | 1 + .../src/DependencyResolver/DependencyGraph/index.js | 3 +++ react-packager/src/Resolver/index.js | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index f896de27..f4b20000 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -25,6 +25,7 @@ class ResolutionRequest { helpers, moduleCache, fastfs, + shouldThrowOnUnresolvedErrors, }) { this._platform = platform; this._preferNativePlatform = preferNativePlatform; @@ -34,6 +35,7 @@ class ResolutionRequest { this._helpers = helpers; this._moduleCache = moduleCache; this._fastfs = fastfs; + this._shouldThrowOnUnresolvedErrors = shouldThrowOnUnresolvedErrors; this._resetResolutionCache(); } @@ -67,8 +69,10 @@ class ResolutionRequest { }; const forgive = (error) => { - if (error.type !== 'UnableToResolveError' || - this._platform === 'ios') { + if ( + error.type !== 'UnableToResolveError' || + this._shouldThrowOnUnresolvedErrors(this._entryPath, this._platform) + ) { throw error; } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index cd434a1d..184ea71f 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -70,6 +70,7 @@ describe('DependencyGraph', function() { 'parse', ], platforms: ['ios', 'android'], + shouldThrowOnUnresolvedErrors: () => false, }; }); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index ec0d964e..634cf5da 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -42,6 +42,7 @@ class DependencyGraph { extensions, mocksPattern, extractRequires, + shouldThrowOnUnresolvedErrors = () => true, }) { this._opts = { activity: activity || defaultActivity, @@ -57,6 +58,7 @@ class DependencyGraph { extensions: extensions || ['js', 'json'], mocksPattern, extractRequires, + shouldThrowOnUnresolvedErrors, }; this._cache = this._opts.cache; this._helpers = new DependencyGraphHelpers(this._opts); @@ -163,6 +165,7 @@ class DependencyGraph { helpers: this._helpers, moduleCache: this._moduleCache, fastfs: this._fastfs, + shouldThrowOnUnresolvedErrors: this._opts.shouldThrowOnUnresolvedErrors, }); const response = new ResolutionResponse(); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 8726233e..b2466dcc 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -95,6 +95,7 @@ class Resolver { preferNativePlatform: true, fileWatcher: opts.fileWatcher, cache: opts.cache, + shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios', }); this._polyfillModuleNames = opts.polyfillModuleNames || []; From 0680259ad476dec850f9c3169b0fae70c7266664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 7 Jan 2016 08:48:39 -0800 Subject: [PATCH 512/936] Skip file removal on HMR interface Summary: public We're not planning to accept file removals in the short term on the HMR interface so lets bail when a file is removed (before this this we were throwing when trying to get the shallow dependencies). Reviewed By: yungsters Differential Revision: D2810534 fb-gh-sync-id: f2733382f4a2619e22bdf1163aa4180694fff9f8 --- react-packager/src/Bundler/index.js | 4 ++++ .../src/DependencyResolver/DependencyGraph/index.js | 4 ++++ react-packager/src/Resolver/index.js | 4 ++++ react-packager/src/Server/index.js | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 26de3130..e7cb033d 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -320,6 +320,10 @@ class Bundler { return this._resolver.getShallowDependencies(entryFile); } + stat(filePath) { + return this._resolver.stat(filePath); + } + getModuleForPath(entryFile) { return this._resolver.getModuleForPath(entryFile); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 634cf5da..1192159b 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -145,6 +145,10 @@ class DependencyGraph { return this._moduleCache.getModule(entryPath).getDependencies(); } + stat(filePath) { + return this._fastfs.stat(filePath); + } + /** * Returns the module object for the given path. */ diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index b2466dcc..cf021e16 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -105,6 +105,10 @@ class Resolver { return this._depGraph.getShallowDependencies(entryFile); } + stat(filePath) { + return this._depGraph.stat(filePath); + } + getModuleForPath(entryFile) { return this._depGraph.getModuleForPath(entryFile); } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index f5c5b34e..a8d363e7 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -184,7 +184,7 @@ class Server { // updates. Instead, send the HMR updates right away and once that // finishes, invoke any other file change listener. if (this._hmrFileChangeListener) { - this._hmrFileChangeListener(filePath); + this._hmrFileChangeListener(filePath, this._bundler.stat(filePath)); return; } From 28b3bbefafdfab6f7910752ca339a86b5605308a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 7 Jan 2016 10:39:13 -0800 Subject: [PATCH 513/936] Fixes Hot Loading re-loading bug Summary: public Fixes a terrible bug due to which when Hot Loading enabled when the user reloads we'll serve them the first `hot` bundle he requested. This happened because when HMR enabled we bailed out after sending the HMR updates and didn't rebuild any of the bundles the user requested before. As a consequence, when they reload we'd sent him the first and only one we ever built. The fix is to tweak the hmr listener to return a promise. This way we can run the remaining code on the file change listener just after the HMR stuff finishes. We need to do it this way to avoid the remaining stuff to compete for CPU with the HMR one and give the best possible experience when HMR is enabled. Reviewed By: davidaurelio Differential Revision: D2811382 fb-gh-sync-id: 906932d71f35467485cf8a865a8d59f4d2ff41a0 --- react-packager/src/Server/index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index a8d363e7..f5d284f2 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -180,16 +180,23 @@ class Server { this._fileWatcher.on('all', this._onFileChange.bind(this)); this._debouncedFileChangeHandler = _.debounce(filePath => { + const onFileChange = () => { + this._rebuildBundles(filePath); + this._informChangeWatchers(); + }; + // if Hot Loading is enabled avoid rebuilding bundles and sending live // updates. Instead, send the HMR updates right away and once that // finishes, invoke any other file change listener. if (this._hmrFileChangeListener) { - this._hmrFileChangeListener(filePath, this._bundler.stat(filePath)); + this._hmrFileChangeListener( + filePath, + this._bundler.stat(filePath), + ).then(onFileChange).done(); return; } - this._rebuildBundles(filePath); - this._informChangeWatchers(); + onFileChange(); }, 50); } From c2dcb04d296d16ec4dc4b661c8f023b5dc8bac50 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Fri, 8 Jan 2016 06:51:45 -0800 Subject: [PATCH 514/936] Updates for haste2 inside of jest Summary: I'm working on deploying haste2 with jest. This updates all the files that require changes for this to work and they are backwards compatible with the current version of jest. * package.json was just outdated. I think haste1's liberal handling with collisions made this a "non-issue" * env.js didn't properly set up ErrorUtils, also unsure why that isn't a problem in jest right now already? * some things were mocking things they shouldn't * Because of the regex that matches against providesModule and System.import, it isn't possible to list module names more than once. We have multiple tests reusing the same providesModule ids and using System.import with modules that only exist virtually within that test. Splitting up the strings makes the regexes work (we do the same kind of splitting on www sometimes if we need to) and using different providesModule names in different test files fixes the problem. I think the BundlesLayoutIntegration-test is going to be deleted, so this doesn't even matter. public Reviewed By: voideanvalue Differential Revision: D2809681 fb-gh-sync-id: 8fe6ed8b5a1be28ba141e9001de143e502693281 --- blacklist.js | 2 +- .../BundlesLayoutIntegration-test.js | 112 +++++++++--------- .../__tests__/DependencyGraph-test.js | 2 +- .../__tests__/Module-test.js | 10 +- .../babel-plugin-system-import-test.js | 6 +- 5 files changed, 66 insertions(+), 66 deletions(-) diff --git a/blacklist.js b/blacklist.js index 4fdca23c..f3c65203 100644 --- a/blacklist.js +++ b/blacklist.js @@ -10,7 +10,7 @@ var path = require('path'); -// Don't forget to everything listed here to `testConfig.json` +// Don't forget to everything listed here to `package.json` // modulePathIgnorePatterns. var sharedBlacklist = [ /node_modules[/\\]react[/\\]dist[/\\].*/, diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 37701392..fe4481a0 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -95,7 +95,7 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */`, } }); @@ -116,12 +116,12 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - require("a");`, + require("xa");`, 'a.js': ` /** - * @providesModule a + * @providesModule xa */`, } }); @@ -142,12 +142,12 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("a");`, + ${'System.import'}("xa");`, 'a.js': ` /**, - * @providesModule a + * @providesModule xa */`, } }); @@ -172,17 +172,17 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("a"); - System.import("b");`, + ${'System.import'}("xa"); + ${'System.import'}("xb");`, 'a.js': ` /**, - * @providesModule a + * @providesModule xa */`, 'b.js': ` /** - * @providesModule b + * @providesModule xb */`, } }); @@ -213,17 +213,17 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - require("a"); - System.import("b");`, + require("xa"); + ${'System.import'}("xb");`, 'a.js': ` /**, - * @providesModule a + * @providesModule xa */`, 'b.js': ` /** - * @providesModule b + * @providesModule xb */`, } }); @@ -248,22 +248,22 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("a");`, + ${'System.import'}("xa");`, 'a.js': ` /**, - * @providesModule a + * @providesModule xa */, - require("b");`, + require("xb");`, 'b.js': ` /** - * @providesModule b + * @providesModule xb */ - require("c");`, + require("xc");`, 'c.js': ` /** - * @providesModule c + * @providesModule xc */`, } }); @@ -288,23 +288,23 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("a"); - System.import("b");`, + ${'System.import'}("xa"); + ${'System.import'}("xb");`, 'a.js': ` /**, - * @providesModule a + * @providesModule xa */, - require("c");`, + require("xc");`, 'b.js': ` /** - * @providesModule b + * @providesModule xb */ - require("c");`, + require("xc");`, 'c.js': ` /** - * @providesModule c + * @providesModule xc */`, } }); @@ -336,22 +336,22 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("a");`, + ${'System.import'}("xa");`, 'a.js': ` /**, - * @providesModule a + * @providesModule xa */, - System.import("b");`, + ${'System.import'}("xb");`, 'b.js': ` /** - * @providesModule b + * @providesModule xb */ - require("c");`, + require("xc");`, 'c.js': ` /** - * @providesModule c + * @providesModule xc */`, } }); @@ -382,12 +382,12 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("a");`, + ${'System.import'}("xa");`, 'a.js':` /**, - * @providesModule a + * @providesModule xa */, require("./img.png");`, 'img.png': '', @@ -414,18 +414,18 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("a"); - System.import("b");`, + ${'System.import'}("xa"); + ${'System.import'}("xb");`, 'a.js':` /**, - * @providesModule a + * @providesModule xa */, require("./img.png");`, 'b.js':` /**, - * @providesModule b + * @providesModule xb */, require("./img.png");`, 'img.png': '', @@ -459,9 +459,9 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("./img.png");`, + ${'System.import'}("./img.png");`, 'img.png': '', } }); @@ -486,12 +486,12 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("a");`, + ${'System.import'}("xa");`, 'a.js':` /**, - * @providesModule a + * @providesModule xa */, require("image!img");`, 'img.png': '', @@ -518,9 +518,9 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("image!img");`, + ${'System.import'}("image!img");`, 'img.png': '', } }); @@ -545,9 +545,9 @@ describe('BundlesLayout', () => { 'root': { 'index.js': ` /** - * @providesModule index + * @providesModule xindex */ - System.import("aPackage");`, + ${'System.import'}("aPackage");`, 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 184ea71f..80cafd55 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -3934,7 +3934,7 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'System.import("a")', + 'System.' + 'import("a")', ].join('\n'), 'a.js': [ '/**', diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index ea95c43a..29a3e842 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -68,7 +68,7 @@ describe('Module', () => { pit('should recognize single dependency', () => { fs.__setMockFilesystem({ 'root': { - 'index.js': 'System.import("dep1")', + 'index.js': 'System.' + 'import("dep1")', }, }); @@ -78,7 +78,7 @@ describe('Module', () => { pit('should parse single quoted dependencies', () => { fs.__setMockFilesystem({ 'root': { - 'index.js': 'System.import(\'dep1\')', + 'index.js': 'System.' + 'import(\'dep1\')', }, }); @@ -89,8 +89,8 @@ describe('Module', () => { fs.__setMockFilesystem({ 'root': { 'index.js': [ - 'System.import("dep1")', - 'System.import("dep2")', + 'System.' + 'import("dep1")', + 'System.' + 'import("dep2")', ].join('\n'), }, }); @@ -104,7 +104,7 @@ describe('Module', () => { pit('parse fine new lines', () => { fs.__setMockFilesystem({ 'root': { - 'index.js': 'System.import(\n"dep1"\n)', + 'index.js': 'System.' + 'import(\n"dep1"\n)', }, }); diff --git a/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js b/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js index 43aa4b56..b29f1bd5 100644 --- a/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js +++ b/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js @@ -14,17 +14,17 @@ const BundlesLayout = require('../../../BundlesLayout'); const testData = { isolated: { - input: 'System.import("moduleA");', + input: 'System.' + 'import("moduleA");', output: 'loadBundles(["bundle.0"]);' }, single: { - input: 'System.import("moduleA").then(function (bundleA) {});', + input: 'System.' + 'import("moduleA").then(function (bundleA) {});', output: 'loadBundles(["bundle.0"]).then(function (bundleA) {});' }, multiple: { input: [ 'Promise.all([', - 'System.import("moduleA"), System.import("moduleB"),', + 'System.' + 'import("moduleA"), System.' + 'import("moduleB"),', ']).then(function (bundlesA, bundlesB) {});', ].join('\n'), output: [ From c75610febbc38f84afae852afa8706e66e9eb6a6 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Fri, 8 Jan 2016 06:55:38 -0800 Subject: [PATCH 515/936] Make Cache dir configurable Reviewed By: davidaurelio Differential Revision: D2811278 fb-gh-sync-id: 77c03a8f806135fff56914bac60c156d10b05ea4 --- react-packager/src/DependencyResolver/Cache/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/react-packager/src/DependencyResolver/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js index 84b7524d..89925fe2 100644 --- a/react-packager/src/DependencyResolver/Cache/index.js +++ b/react-packager/src/DependencyResolver/Cache/index.js @@ -13,7 +13,7 @@ const fs = require('fs'); const getCacheFilePath = require('./lib/getCacheFilePath'); const isAbsolutePath = require('absolute-path'); const loadCacheSync = require('./lib/loadCacheSync'); -const tmpdir = require('os').tmpDir(); +const tmpDir = require('os').tmpDir(); function getObjectValues(object) { return Object.keys(object).map(key => object[key]); @@ -31,8 +31,9 @@ class Cache { constructor({ resetCache, cacheKey, + cacheDirectory = tmpDir, }) { - this._cacheFilePath = getCacheFilePath(tmpdir, cacheKey); + this._cacheFilePath = getCacheFilePath(cacheDirectory, cacheKey); if (!resetCache) { this._data = this._loadCacheSync(this._cacheFilePath); } else { From 87e808693724cd5ffe04a0875ef3f1913d1c7d4a Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Fri, 8 Jan 2016 08:34:52 -0800 Subject: [PATCH 516/936] Require transform file eagerly in transform worker Summary: One consequence we didn't predict after introducing the Internal Transform Pipeline, was that when the workers would get started, we won't require the external transformer the user specified up until the first time each worker received a job. There're 2 visible consequences of this: (1) the transform progress bar seems to get stuck for about 5 seconds the first time the packager receives a request and (2) the first N (# of cores) HMR requests take way longer (about 4 seconds with FB's transformer instead of << 1 second) as we need to require lots of modules. This diff creates a temporary file for the js transformer workers that requires the user-specified transform file eagerly. That makes sure workers have imported babel and the transforms before receiving the first request. There are better ways to do this, like adding an `init()` method to the workers and call that eagerly. I will follow with another diff doing that. public Reviewed By: javache Differential Revision: D2812153 fb-gh-sync-id: 15be316b792d1acd878ed9303bea398aa0b52e1d --- .../JSTransformer/__tests__/Transformer-test.js | 1 + react-packager/src/JSTransformer/index.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 83275f14..7e129768 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -13,6 +13,7 @@ jest .dontMock('../'); jest.mock('fs'); +jest.setMock('temp', {path: () => '/arbitrary/path'}); var Cache = require('../../DependencyResolver/Cache'); var Transformer = require('../'); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 2688e14c..6cba12e5 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -12,6 +12,7 @@ const ModuleTransport = require('../lib/ModuleTransport'); const Promise = require('promise'); const declareOpts = require('../lib/declareOpts'); const fs = require('fs'); +const temp = require('temp'); const util = require('util'); const workerFarm = require('worker-farm'); const debug = require('debug')('ReactNativePackager:JStransformer'); @@ -63,13 +64,22 @@ class Transformer { this._transformModulePath = opts.transformModulePath; if (opts.transformModulePath != null) { + this._workerWrapperPath = temp.path(); + fs.writeFileSync( + this._workerWrapperPath, + ` + module.exports = require(${JSON.stringify(require.resolve('./worker'))}); + require(${JSON.stringify(String(opts.transformModulePath))}); + ` + ); + this._workers = workerFarm({ autoStart: true, maxConcurrentCallsPerWorker: 1, maxCallsPerWorker: MAX_CALLS_PER_WORKER, maxCallTime: opts.transformTimeoutInterval, maxRetries: MAX_RETRIES, - }, require.resolve('./worker')); + }, this._workerWrapperPath); this._transform = Promise.denodeify(this._workers); } @@ -77,6 +87,9 @@ class Transformer { kill() { this._workers && workerFarm.end(this._workers); + if (typeof this._workerWrapperPath === 'string') { + fs.unlink(this._workerWrapperPath, () => {}); // we don't care about potential errors here + } } invalidateFile(filePath) { From 4501181cc903de7d9ed0e55ba43eb899da23442b Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Fri, 8 Jan 2016 08:36:14 -0800 Subject: [PATCH 517/936] Use graceful-fs directly Reviewed By: davidaurelio Differential Revision: D2811784 fb-gh-sync-id: 95e4fd1538f4cd468288dc65e83f1d6ca98ce791 --- .../DependencyResolver/Cache/__tests__/Cache-test.js | 2 +- react-packager/src/DependencyResolver/Cache/index.js | 2 +- .../src/DependencyResolver/Cache/lib/loadCacheSync.js | 2 +- .../DependencyGraph/__tests__/DependencyGraph-test.js | 2 +- .../src/DependencyResolver/__tests__/Module-test.js | 2 +- .../src/DependencyResolver/crawlers/node.js | 2 +- react-packager/src/DependencyResolver/fastfs.js | 2 +- react-packager/src/__mocks__/graceful-fs.js | 11 +++++++++++ 8 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 react-packager/src/__mocks__/graceful-fs.js diff --git a/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js b/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js index 62220f6b..f4c0f430 100644 --- a/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js +++ b/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js @@ -21,7 +21,7 @@ jest }); var Promise = require('promise'); -var fs = require('fs'); +var fs = require('graceful-fs'); var Cache = require('../'); diff --git a/react-packager/src/DependencyResolver/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js index 89925fe2..3f601bd4 100644 --- a/react-packager/src/DependencyResolver/Cache/index.js +++ b/react-packager/src/DependencyResolver/Cache/index.js @@ -9,7 +9,7 @@ 'use strict'; const Promise = require('promise'); -const fs = require('fs'); +const fs = require('graceful-fs'); const getCacheFilePath = require('./lib/getCacheFilePath'); const isAbsolutePath = require('absolute-path'); const loadCacheSync = require('./lib/loadCacheSync'); diff --git a/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js b/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js index d04ec093..87d6944c 100644 --- a/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js +++ b/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js @@ -8,7 +8,7 @@ */ 'use strict'; -const fs = require('fs'); +const fs = require('graceful-fs'); function loadCacheSync(cachePath) { if (!fs.existsSync(cachePath)) { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 80cafd55..8f41d88c 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -16,7 +16,7 @@ jest .mock('fs'); var DependencyGraph = require('../index'); -var fs = require('fs'); +var fs = require('graceful-fs'); describe('DependencyGraph', function() { let defaults; diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 29a3e842..f0c9cbd2 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -24,7 +24,7 @@ const Module = require('../Module'); const ModuleCache = require('../ModuleCache'); const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers'); const Promise = require('promise'); -const fs = require('fs'); +const fs = require('graceful-fs'); describe('Module', () => { const fileWatcher = { diff --git a/react-packager/src/DependencyResolver/crawlers/node.js b/react-packager/src/DependencyResolver/crawlers/node.js index 528cd5e7..88e32d88 100644 --- a/react-packager/src/DependencyResolver/crawlers/node.js +++ b/react-packager/src/DependencyResolver/crawlers/node.js @@ -2,7 +2,7 @@ const Promise = require('promise'); const debug = require('debug')('ReactNativePackager:DependencyGraph'); -const fs = require('fs'); +const fs = require('graceful-fs'); const path = require('path'); const readDir = Promise.denodeify(fs.readdir); diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 5bff2437..ca01b86c 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -11,7 +11,7 @@ const Promise = require('promise'); const {EventEmitter} = require('events'); -const fs = require('fs'); +const fs = require('graceful-fs'); const path = require('path'); const readFile = Promise.denodeify(fs.readFile); diff --git a/react-packager/src/__mocks__/graceful-fs.js b/react-packager/src/__mocks__/graceful-fs.js new file mode 100644 index 00000000..c6b9a53e --- /dev/null +++ b/react-packager/src/__mocks__/graceful-fs.js @@ -0,0 +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'; + +module.exports = require('fs'); From 0cc1132b8c0f18801681c553a0d180726225f982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 8 Jan 2016 11:09:17 -0800 Subject: [PATCH 518/936] Add option to disable internal transforms Reviewed By: davidaurelio Differential Revision: D2815307 fb-gh-sync-id: 17ed8f13de7b4c41111efa4a5f2af5283e6ef3e0 --- react-packager/src/Bundler/index.js | 5 ++++ react-packager/src/JSTransformer/index.js | 31 +++++++++++++++-------- react-packager/src/Server/index.js | 6 ++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index e7cb033d..3380a3ce 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -80,6 +80,10 @@ const validateOpts = declareOpts({ type: 'number', required: false, }, + disableInternalTransforms: { + type: 'boolean', + default: false, + }, }); class Bundler { @@ -131,6 +135,7 @@ class Bundler { blacklistRE: opts.blacklistRE, cache: this._cache, transformModulePath: opts.transformModulePath, + disableInternalTransforms: opts.disableInternalTransforms, }); this._projectRoots = opts.projectRoots; diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 6cba12e5..24de2fa2 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -54,6 +54,10 @@ const validateOpts = declareOpts({ type: 'number', default: DEFAULT_MAX_CALL_TIME, }, + disableInternalTransforms: { + type: 'boolean', + default: false, + }, }); class Transformer { @@ -64,14 +68,20 @@ class Transformer { this._transformModulePath = opts.transformModulePath; if (opts.transformModulePath != null) { - this._workerWrapperPath = temp.path(); - fs.writeFileSync( - this._workerWrapperPath, - ` - module.exports = require(${JSON.stringify(require.resolve('./worker'))}); - require(${JSON.stringify(String(opts.transformModulePath))}); - ` - ); + let transformer; + + if (opts.disableInternalTransforms) { + transformer = opts.transformModulePath; + } else { + transformer = this._workerWrapperPath = temp.path(); + fs.writeFileSync( + this._workerWrapperPath, + ` + module.exports = require(${JSON.stringify(require.resolve('./worker'))}); + require(${JSON.stringify(String(opts.transformModulePath))}); + ` + ); + } this._workers = workerFarm({ autoStart: true, @@ -79,7 +89,7 @@ class Transformer { maxCallsPerWorker: MAX_CALLS_PER_WORKER, maxCallTime: opts.transformTimeoutInterval, maxRetries: MAX_RETRIES, - }, this._workerWrapperPath); + }, transformer); this._transform = Promise.denodeify(this._workers); } @@ -87,7 +97,8 @@ class Transformer { kill() { this._workers && workerFarm.end(this._workers); - if (typeof this._workerWrapperPath === 'string') { + if (this._workerWrapperPath && + typeof this._workerWrapperPath === 'string') { fs.unlink(this._workerWrapperPath, () => {}); // we don't care about potential errors here } } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index f5d284f2..fe7294bb 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -67,7 +67,11 @@ const validateOpts = declareOpts({ getTransformOptionsModulePath: { type: 'string', required: false, - } + }, + disableInternalTransforms: { + type: 'boolean', + default: false, + }, }); const bundleOpts = declareOpts({ From a401b1d166fed89dbf73cc43931cfdb087fd3517 Mon Sep 17 00:00:00 2001 From: Martin Bigio Date: Fri, 8 Jan 2016 13:38:30 -0800 Subject: [PATCH 519/936] Bump timeout to track intermittent error Reviewed By: sam-swarr Differential Revision: D2816605 fb-gh-sync-id: 3d05746493f0ff97c396c82eb30d9a49537a0473 --- react-packager/src/JSTransformer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 24de2fa2..14886d06 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -25,7 +25,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 = 300000; +const DEFAULT_MAX_CALL_TIME = 301000; // How may times can we tolerate failures from the worker. const MAX_RETRIES = 2; From 0001c2076058fbf6cc350537a3568fcf247963c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 11 Jan 2016 09:37:50 -0800 Subject: [PATCH 520/936] Include socket interface logs when timing out on transformer Reviewed By: davidaurelio Differential Revision: D2819820 fb-gh-sync-id: 066fd1191c459cf3434899de6326e25f798c4b07 --- react-packager/src/SocketInterface/SocketClient.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/react-packager/src/SocketInterface/SocketClient.js b/react-packager/src/SocketInterface/SocketClient.js index a344f5d9..c0016e17 100644 --- a/react-packager/src/SocketInterface/SocketClient.js +++ b/react-packager/src/SocketInterface/SocketClient.js @@ -136,9 +136,12 @@ class SocketClient { delete this._resolvers[message.id]; if (message.type === 'error') { - resolver.reject(new Error( - message.data + '\n' + 'See logs ' + LOG_PATH - )); + const errorLog = + message.data && message.data.indexOf('TimeoutError') === -1 + ? 'See logs ' + LOG_PATH + : getServerLogs(); + + resolver.reject(new Error(message.data + '\n' + errorLog)); } else { resolver.resolve(message.data); } From e35b8f172482f5806f0253028577ff89b07a15b6 Mon Sep 17 00:00:00 2001 From: Kelvin De Moya Date: Tue, 12 Jan 2016 08:24:42 -0800 Subject: [PATCH 521/936] Enable JSX files extension Summary: JSX extension is much used in React, this would be a natural addition for React Native. See: https://github.com/facebook/react-native/issues/2303 Closes https://github.com/facebook/react-native/pull/5233 Reviewed By: svcscm Differential Revision: D2818888 Pulled By: mkonicek fb-gh-sync-id: 856d1b8ba9ff88ba08a00923174e3284f359d774 --- .../src/DependencyResolver/DependencyGraph/ResolutionRequest.js | 2 ++ react-packager/src/DependencyResolver/DependencyGraph/index.js | 2 +- react-packager/src/Server/index.js | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index f4b20000..4414862f 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -340,6 +340,8 @@ class ResolutionRequest { file = potentialModulePath + '.native.js'; } else if (this._fastfs.fileExists(potentialModulePath + '.js')) { file = potentialModulePath + '.js'; + } else if (this._fastfs.fileExists(potentialModulePath + '.jsx')) { + file = potentialModulePath + '.jsx'; } else if (this._fastfs.fileExists(potentialModulePath + '.json')) { file = potentialModulePath + '.json'; } else { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 1192159b..1278d2ce 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -55,7 +55,7 @@ class DependencyGraph { platforms: platforms || [], preferNativePlatform: preferNativePlatform || false, cache, - extensions: extensions || ['js', 'json'], + extensions: extensions || ['js', 'jsx', 'json'], mocksPattern, extractRequires, shouldThrowOnUnresolvedErrors, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index fe7294bb..63432090 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -151,6 +151,7 @@ class Server { dir: dir, globs: [ '**/*.js', + '**/*.jsx', '**/*.json', ].concat(assetGlobs), }; From 91fb94b5cea7f0754bc8ebed5aea9077656610a0 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Tue, 12 Jan 2016 15:33:00 -0800 Subject: [PATCH 522/936] Support "free" mocks. Summary: It's possible that a mock doesn't have an associated real module that it maps to. This is actually very common in www, where we have JS mocks for dynamic PHP JS modules. The implementation I chose seems like the smartest one for now: if a module cannot be resolved, we look up whether we have a mock with the same id. If we do, we just resolve it. That's it! And it also only does the minimum amount of resolution necessary. public Reviewed By: martinbigio Differential Revision: D2822277 fb-gh-sync-id: 7c9fbb6f69a0c0c85157c0650f5719d94a02410e --- .../DependencyGraph/ResolutionRequest.js | 14 +++- .../__tests__/DependencyGraph-test.js | 84 +++++++++++++++++-- 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 4414862f..50a59025 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -135,15 +135,25 @@ class ResolutionRequest { const filteredPairs = []; dependencies.forEach((modDep, i) => { + const name = depNames[i]; if (modDep == null) { + // It is possible to require mocks that don't have a real + // module backing them. If a dependency cannot be found but there + // exists a mock with the desired ID, resolve it and add it as + // a dependency. + if (mocks && mocks[name]) { + const mockModule = this._moduleCache.getModule(mocks[name]); + return filteredPairs.push([name, mockModule]); + } + debug( 'WARNING: Cannot find required module `%s` from module `%s`', - depNames[i], + name, mod.path ); return false; } - return filteredPairs.push([depNames[i], modDep]); + return filteredPairs.push([name, modDep]); }); response.setResolvedDependencyPairs(mod, filteredPairs); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 8f41d88c..b1126a4c 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,13 +10,13 @@ jest.autoMockOff(); +jest.mock('fs'); + const Promise = require('promise'); +const DependencyGraph = require('../index'); +const fs = require('graceful-fs'); -jest - .mock('fs'); - -var DependencyGraph = require('../index'); -var fs = require('graceful-fs'); +const mocksPattern = /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/; describe('DependencyGraph', function() { let defaults; @@ -4024,7 +4024,7 @@ describe('DependencyGraph', function() { var root = '/root'; fs.__setMockFilesystem({ 'root': { - '__mocks': { + '__mocks__': { 'A.js': '', }, 'index.js': '', @@ -4061,7 +4061,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ ...defaults, roots: [root], - mocksPattern: /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/, + mocksPattern, }); return dgraph.getDependencies('/root/b.js') @@ -4101,7 +4101,7 @@ describe('DependencyGraph', function() { var dgraph = new DependencyGraph({ ...defaults, roots: [root], - mocksPattern: /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/, + mocksPattern, }); return getOrderedDependenciesAsJSON(dgraph, '/root/A.js') @@ -4134,6 +4134,74 @@ describe('DependencyGraph', function() { id: '/root/__mocks__/A.js', dependencies: ['b'], }, + { + path: '/root/__mocks__/b.js', + isJSON: false, + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + id: '/root/__mocks__/b.js', + dependencies: [], + }, + ]); + }); + }); + + pit('resolves mocks that do not have a real module associated with them', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + '__mocks__': { + 'foo.js': [ + 'require("b");', + ].join('\n'), + 'b.js': '', + }, + 'A.js': [ + '/**', + ' * @providesModule A', + ' */', + 'require("foo");', + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + mocksPattern, + }); + + return getOrderedDependenciesAsJSON(dgraph, '/root/A.js') + .then(deps => { + expect(deps).toEqual([ + { + path: '/root/A.js', + isJSON: false, + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + id: 'A', + dependencies: ['foo'], + }, + { + path: '/root/__mocks__/foo.js', + isJSON: false, + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + id: '/root/__mocks__/foo.js', + dependencies: ['b'], + }, + { + path: '/root/__mocks__/b.js', + isJSON: false, + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + id: '/root/__mocks__/b.js', + dependencies: [], + }, ]); }); }); From 1b33b523f4f5ccf37d6ac514ffa2335fe20d3f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 12 Jan 2016 16:44:18 -0800 Subject: [PATCH 523/936] Get rid of deprecated `fs.existSync` Reviewed By: mmahoney Differential Revision: D2824569 fb-gh-sync-id: efed6e88f566110b8286ea59563c2904b3dd8059 --- react-packager/src/SocketInterface/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/react-packager/src/SocketInterface/index.js b/react-packager/src/SocketInterface/index.js index e2d70bbf..69b52a4f 100644 --- a/react-packager/src/SocketInterface/index.js +++ b/react-packager/src/SocketInterface/index.js @@ -49,7 +49,7 @@ const SocketInterface = { sockPath = '\\\\.\\pipe\\' + sockPath } - if (fs.existsSync(sockPath)) { + if (existsSync(sockPath)) { var sock = net.connect(sockPath); sock.on('connect', () => { SocketClient.create(sockPath).then( @@ -135,4 +135,13 @@ function createServer(resolve, reject, options, sockPath) { }); } +function existsSync(filename) { + try { + fs.accessSync(filename); + return true; + } catch(ex) { + return false; + } +} + module.exports = SocketInterface; From da27090d830d1591829dd38ff6a71352333958ba Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Thu, 14 Jan 2016 13:47:31 -0800 Subject: [PATCH 524/936] Cache all module data Summary: We used to only cache the `dependencies` and `name` of a Module but we were actually accessing two more fields (async dependencies and the `isHaste` field) which meant we were always reading every single file from disk. In D2644383 I noticed that but realized that the Promise was cached, meaning we would read every file once *per instance* and I didn't think about cross-instance reads over time. My initial version added more caching (but also missed the `isHaste` field) but then I got rid of the cache call for `dependencies`. So my change from before didn't make anything worse but it also didn't make anything better. This change now caches everything until the contents of the file actually changes. public Reviewed By: martinbigio Differential Revision: D2831569 fb-gh-sync-id: 74081abc0ce3ca96b4e56c3c9b6d24aa84f7496c --- .../src/DependencyResolver/Module.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 3fcd63f0..f6eb72be 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -31,7 +31,11 @@ class Module { } isHaste() { - return this.read().then(data => !!data.id); + return this._cache.get( + this.path, + 'isHaste', + () => this.read().then(data => !!data.id) + ); } getName() { @@ -67,11 +71,19 @@ class Module { } getDependencies() { - return this.read().then(data => data.dependencies); + return this._cache.get( + this.path, + 'dependencies', + () => this.read().then(data => data.dependencies) + ); } getAsyncDependencies() { - return this.read().then(data => data.asyncDependencies); + return this._cache.get( + this.path, + 'asyncDependencies', + () => this.read().then(data => data.asyncDependencies) + ); } invalidate() { From 62a3d2c8434de423edb202dfe49d8414c3e152f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 14 Jan 2016 19:32:17 -0800 Subject: [PATCH 525/936] Make Hot Loading work on OSS Summary: public - Tweak OSS server to enable the HMR connection - Remove client gating code. - Resolve internal transforms plugins After this diff, Hot Loading should work on OSS. Reviewed By: javache Differential Revision: D2803620 fb-gh-sync-id: b678180c884d2bfaf454edf9e7abe6b3b3b32ebe --- .../src/JSTransformer/resolvePlugins.js | 32 +++++++++++++++++++ react-packager/src/JSTransformer/worker.js | 3 +- transformer.js | 19 ++--------- 3 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 react-packager/src/JSTransformer/resolvePlugins.js diff --git a/react-packager/src/JSTransformer/resolvePlugins.js b/react-packager/src/JSTransformer/resolvePlugins.js new file mode 100644 index 00000000..742ea0a7 --- /dev/null +++ b/react-packager/src/JSTransformer/resolvePlugins.js @@ -0,0 +1,32 @@ +/** + * 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'; + +/** + * Manually resolve all default Babel plugins. + * `babel.transform` will attempt to resolve all base plugins relative to + * the file it's compiling. This makes sure that we're using the plugins + * installed in the react-native package. + */ +function resolvePlugins(plugins) { + return plugins.map(function(plugin) { + // Normalise plugin to an array. + if (!Array.isArray(plugin)) { + plugin = [plugin]; + } + // Only resolve the plugin if it's a string reference. + if (typeof plugin[0] === 'string') { + plugin[0] = require('babel-plugin-' + plugin[0]); + plugin[0] = plugin[0].__esModule ? plugin[0].default : plugin[0]; + } + return plugin; + }); +} + +module.exports = resolvePlugins; diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js index 27542274..b33b4ae5 100644 --- a/react-packager/src/JSTransformer/worker.js +++ b/react-packager/src/JSTransformer/worker.js @@ -9,13 +9,14 @@ 'use strict'; var babel = require('babel-core'); +var resolvePlugins = require('./resolvePlugins'); var Transforms = require('../transforms'); // Runs internal transforms on the given sourceCode. Note that internal // transforms should be run after the external ones to ensure that they run on // Javascript code function internalTransforms(sourceCode, filename, options) { - var plugins = Transforms.getAll(options); + var plugins = resolvePlugins(Transforms.getAll(options)); if (plugins.length === 0) { return { code: sourceCode, diff --git a/transformer.js b/transformer.js index 8a178c86..df6009bd 100644 --- a/transformer.js +++ b/transformer.js @@ -16,6 +16,7 @@ const inlineRequires = require('fbjs-scripts/babel-6/inline-requires'); const json5 = require('json5'); const path = require('path'); const ReactPackager = require('./react-packager'); +const resolvePlugins = require('./react-packager/src/JSTransformer/resolvePlugins'); const babelRC = json5.parse( @@ -36,23 +37,7 @@ function transform(src, filename, options) { if (options.inlineRequires) { extraPlugins.push(inlineRequires); } - config.plugins = extraPlugins.concat(config.plugins); - - // Manually resolve all default Babel plugins. babel.transform will attempt to resolve - // all base plugins relative to the file it's compiling. This makes sure that we're - // using the plugins installed in the react-native package. - config.plugins = config.plugins.map(function(plugin) { - // Normalise plugin to an array. - if (!Array.isArray(plugin)) { - plugin = [plugin]; - } - // Only resolve the plugin if it's a string reference. - if (typeof plugin[0] === 'string') { - plugin[0] = require(`babel-plugin-${plugin[0]}`); - plugin[0] = plugin[0].__esModule ? plugin[0].default : plugin[0]; - } - return plugin; - }); + config.plugins = resolvePlugins(extraPlugins.concat(config.plugins)); const result = babel.transform(src, Object.assign({}, babelRC, config)); From fb94be43581798b17793bb39588dfab81c5edf0c Mon Sep 17 00:00:00 2001 From: Kyle Corbitt Date: Fri, 15 Jan 2016 05:14:27 -0800 Subject: [PATCH 526/936] PixelRatio.pixel() Summary: This implements #5073. It adds a static method `PixelRatio.pixel()` which returns the smallest drawable line width, primarily for use in styles. It also updates the example apps to use the new function. Closes https://github.com/facebook/react-native/pull/5076 Reviewed By: svcscm Differential Revision: D2799849 Pulled By: nicklockwood fb-gh-sync-id: b83a77790601fe882affbf65531114e7c5cf4bdf --- react-packager/src/Resolver/__tests__/Resolver-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 6712f00b..82184c61 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -10,6 +10,7 @@ jest.dontMock('../') .dontMock('underscore') + .dontMock('PixelRatio') .dontMock('../../DependencyResolver/lib/extractRequires') .dontMock('../../DependencyResolver/lib/replacePatterns'); From a258d5571688972b4d5fa150f36160bd26fbfaf8 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Fri, 15 Jan 2016 10:05:20 -0800 Subject: [PATCH 527/936] Don't extract dependencies from .json files Reviewed By: davidaurelio Differential Revision: D2832417 fb-gh-sync-id: 8bf3705bf620f3cc6d713ca08dde52464185797a --- react-packager/src/DependencyResolver/Module.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index f6eb72be..09fbe793 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -109,10 +109,11 @@ class Module { )[1]; } - // Ignore requires in generated code. An example of this is prebuilt - // files like the SourceMap library. - if ('extern' in moduleDocBlock) { + // Ignore requires in JSON files or generated code. An example of this + // is prebuilt files like the SourceMap library. + if (this.isJSON() || 'extern' in moduleDocBlock) { data.dependencies = []; + data.asyncDependencies = []; } else { var dependencies = (this._extractor || extractRequires)(content).deps; data.dependencies = dependencies.sync; From 85df6cf2cafe6e02ab101b6d6487679d9aa9fbfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 15 Jan 2016 10:31:22 -0800 Subject: [PATCH 528/936] Throw if there's an error persisting the cache Reviewed By: davidaurelio Differential Revision: D2824116 fb-gh-sync-id: 46b3c51a11d22e2da750f9c5a4b431a88004cd81 --- .../src/DependencyResolver/Cache/index.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/react-packager/src/DependencyResolver/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js index 3f601bd4..02384a20 100644 --- a/react-packager/src/DependencyResolver/Cache/index.js +++ b/react-packager/src/DependencyResolver/Cache/index.js @@ -109,18 +109,18 @@ class Cache { return this._persisting; } - var data = this._data; - var cacheFilepath = this._cacheFilePath; + const data = this._data; + const cacheFilepath = this._cacheFilePath; - var allPromises = getObjectValues(data) + const allPromises = getObjectValues(data) .map(record => { - var fieldNames = Object.keys(record.data); - var fieldValues = getObjectValues(record.data); + const fieldNames = Object.keys(record.data); + const fieldValues = getObjectValues(record.data); return Promise .all(fieldValues) .then(ref => { - var ret = Object.create(null); + const ret = Object.create(null); ret.metadata = record.metadata; ret.data = Object.create(null); fieldNames.forEach((field, index) => @@ -134,18 +134,22 @@ class Cache { this._persisting = Promise.all(allPromises) .then(values => { - var json = Object.create(null); + const json = Object.create(null); Object.keys(data).forEach((key, i) => { - if (!values[i]) { + // make sure the key wasn't added nor removed after we started + // persisting the cache + const value = values[i]; + if (!value) { return; } json[key] = Object.create(null); json[key].metadata = data[key].metadata; - json[key].data = values[i].data; + json[key].data = value.data; }); return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); }) + .catch(e => console.error('Error while persisting cache:', e.message)) .then(() => { this._persisting = null; return true; From 8a366256893162c40cd32afd111842918f1127e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 15 Jan 2016 10:51:56 -0800 Subject: [PATCH 529/936] Send assets updates through HRM interface Reviewed By: frantic Differential Revision: D2832693 fb-gh-sync-id: 816a01fa2f1f7cc8ca218de86b3e2e847ee005c9 --- react-packager/src/Bundler/index.js | 46 +++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 3380a3ce..ebac145c 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -291,11 +291,7 @@ class Bundler { modules.map(module => { return Promise.all([ module.getName(), - this._transformer.loadFileAndTransform( - module.path, - // TODO(martinb): pass non null main (t9527509) - this._getTransformOptions({main: null}, {hot: true}), - ), + this._transformModuleForHMR(module, platform), ]).then(([moduleName, transformed]) => { return this._resolver.resolveRequires(response, module, @@ -317,6 +313,24 @@ class Bundler { .then(modules => modules.join('\n')); } + _transformModuleForHMR(module, platform) { + if (module.isAsset()) { + return this._generateAssetObjAndCode(module, platform).then( + ({asset, code}) => { + return { + code, + }; + } + ); + } else { + return this._transformer.loadFileAndTransform( + module.path, + // TODO(martinb): pass non null main (t9527509) + this._getTransformOptions({main: null}, {hot: true}), + ); + } + } + invalidateFile(filePath) { this._transformer.invalidateFile(filePath); } @@ -371,9 +385,9 @@ class Bundler { _transformModule(bundle, response, module, platform = null, hot = false) { if (module.isAsset_DEPRECATED()) { - return this.generateAssetModule_DEPRECATED(bundle, module); + return this._generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { - return this.generateAssetModule(bundle, module, platform); + return this._generateAssetModule(bundle, module, platform); } else if (module.isJSON()) { return generateJSONModule(module); } else { @@ -408,7 +422,7 @@ class Bundler { return this._resolver.getDebugInfo(); } - generateAssetModule_DEPRECATED(bundle, module) { + _generateAssetModule_DEPRECATED(bundle, module) { return Promise.all([ sizeOf(module.path), module.getName(), @@ -435,7 +449,7 @@ class Bundler { }); } - generateAssetModule(bundle, module, platform = null) { + _generateAssetObjAndCode(module, platform = null) { const relPath = getPathRelativeToRoot(this._projectRoots, module.path); var assetUrlPath = path.join('/assets', path.dirname(relPath)); @@ -450,7 +464,7 @@ class Bundler { ]).then(function(res) { const dimensions = res[0]; const assetData = res[1]; - const img = { + const asset = { __packager_asset: true, fileSystemLocation: path.dirname(module.path), httpServerLocation: assetUrlPath, @@ -463,11 +477,17 @@ class Bundler { type: assetData.type, }; - bundle.addAsset(img); - const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);'; - const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img)); + const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(asset)); + return {asset, code}; + }); + } + + + _generateAssetModule(bundle, module, platform = null) { + return this._generateAssetObjAndCode(module, platform).then(({asset, code}) => { + bundle.addAsset(asset); return new ModuleTransport({ code: code, sourceCode: code, From b324dc6ae57f8003b225ac80b7c01341529764d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Sj=C3=B6lander?= Date: Mon, 18 Jan 2016 12:41:41 -0800 Subject: [PATCH 530/936] Escape directory path in packager.sh Summary: Previously could not handle directory paths with spaces. Closes https://github.com/facebook/react-native/pull/5381 Reviewed By: svcscm Differential Revision: D2839193 Pulled By: javache fb-gh-sync-id: e95825a602c053761826255ac5254b97fadbe090 --- launchPackager.command | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchPackager.command b/launchPackager.command index 0d08a298..56683667 100755 --- a/launchPackager.command +++ b/launchPackager.command @@ -12,7 +12,7 @@ echo -en "\033]0;React Packager\a" clear THIS_DIR=$(dirname "$0") -pushd $THIS_DIR +pushd "$THIS_DIR" source packager.sh popd From 95b69d548bb8536fd2548c3613bfa5f691905080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 20 Jan 2016 07:13:26 -0800 Subject: [PATCH 531/936] Simplify HMR codepath Reviewed By: davidaurelio Differential Revision: D2839590 fb-gh-sync-id: 9bb14cafc69eec7d7a8712b60435e29f2ba48d3c --- react-packager/src/Bundler/Bundle.js | 124 ++--- react-packager/src/Bundler/BundleBase.js | 94 ++++ react-packager/src/Bundler/HMRBundle.js | 47 ++ .../src/Bundler/__tests__/Bundle-test.js | 495 ++++++++++-------- .../src/Bundler/__tests__/Bundler-test.js | 81 +-- react-packager/src/Bundler/index.js | 143 +++-- 6 files changed, 537 insertions(+), 447 deletions(-) create mode 100644 react-packager/src/Bundler/BundleBase.js create mode 100644 react-packager/src/Bundler/HMRBundle.js diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index b5e591c8..04a2e1ae 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -10,6 +10,7 @@ const _ = require('underscore'); const base64VLQ = require('./base64-vlq'); +const BundleBase = require('./BundleBase'); const UglifyJS = require('uglify-js'); const ModuleTransport = require('../lib/ModuleTransport'); const Activity = require('../Activity'); @@ -24,11 +25,9 @@ const getNameAndCode = ({name, code}) => ({name, code}); const getNameAndMinifiedCode = ({name, code}) => ({name, code: minifyCode(code)}); -class Bundle { +class Bundle extends BundleBase { constructor(sourceMapUrl) { - this._finalized = false; - this._modules = []; - this._assets = []; + super(); this._sourceMap = false; this._sourceMapUrl = sourceMapUrl; this._shouldCombineSourceMaps = false; @@ -36,58 +35,49 @@ class Bundle { this._numRequireCalls = 0; } - setMainModuleId(moduleId) { - this._mainModuleId = moduleId; - } + addModule(resolver, response, module, transformed) { + return resolver.wrapModule( + response, + module, + transformed.code + ).then(({code, name}) => { + const moduleTransport = new ModuleTransport({ + code, + name, + map: transformed.map, + sourceCode: transformed.sourceCode, + sourcePath: transformed.sourcePath, + virtual: transformed.virtual, + }); - 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 && moduleTransport.map != null) { + this._shouldCombineSourceMaps = true; + } - // 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; - } - - getMainModuleId() { - return this._mainModuleId; + super.addModule(moduleTransport); + }); } setNumPrependedModules(n) { this._numPrependedModules = n; } - addAsset(asset) { - this._assets.push(asset); - } - finalize(options) { options = options || {}; if (options.runMainModule) { options.runBeforeMainModule.forEach(this._addRequireCall, this); - this._addRequireCall(this._mainModuleId); + this._addRequireCall(super.getMainModuleId()); } - Object.freeze(this._modules); - Object.seal(this._modules); - Object.freeze(this._assets); - Object.seal(this._assets); - this._finalized = true; + super.finalize(); } _addRequireCall(moduleId) { const code = ';require("' + moduleId + '");'; const name = 'require-' + moduleId; - this.addModule(new ModuleTransport({ + super.addModule(new ModuleTransport({ name, code, virtual: true, @@ -97,21 +87,6 @@ class Bundle { this._numRequireCalls += 1; } - _assertFinalized() { - if (!this._finalized) { - throw new Error('Bundle needs to be finalized before getting any source'); - } - } - - _getSource(dev) { - if (this._source) { - return this._source; - } - - this._source = _.pluck(this._modules, 'code').join('\n'); - return this._source; - } - _getInlineSourceMap(dev) { if (this._inlineSourceMap == null) { const sourceMap = this.getSourceMap({excludeSource: true, dev}); @@ -123,7 +98,7 @@ class Bundle { } getSource(options) { - this._assertFinalized(); + super.assertFinalized(); options = options || {}; @@ -131,7 +106,7 @@ class Bundle { return this.getMinifiedSourceAndMap(options.dev).code; } - let source = this._getSource(options.dev); + let source = super.getSource(); if (options.inlineSourceMap) { source += SOURCEMAPPING_URL + this._getInlineSourceMap(options.dev); @@ -143,7 +118,7 @@ class Bundle { } getUnbundle({minify}) { - const allModules = this._modules.slice(); + const allModules = super.getModules().slice(); const prependedModules = this._numPrependedModules; const requireCalls = this._numRequireCalls; @@ -163,13 +138,13 @@ class Bundle { } getMinifiedSourceAndMap(dev) { - this._assertFinalized(); + super.assertFinalized(); if (this._minifiedSourceAndMap) { return this._minifiedSourceAndMap; } - let source = this._getSource(dev); + let source = this.getSource(); let map = this.getSourceMap(); if (!dev) { @@ -235,7 +210,7 @@ class Bundle { }; let line = 0; - this._modules.forEach(function(module) { + super.getModules().forEach(function(module) { let map = module.map; if (module.virtual) { map = generateSourceMapForVirtualModule(module); @@ -256,7 +231,7 @@ class Bundle { } getSourceMap(options) { - this._assertFinalized(); + super.assertFinalized(); options = options || {}; @@ -271,22 +246,18 @@ class Bundle { const mappings = this._getMappings(); const map = { file: 'bundle.js', - sources: _.pluck(this._modules, 'sourcePath'), + sources: _.pluck(super.getModules(), 'sourcePath'), version: 3, names: [], mappings: mappings, sourcesContent: options.excludeSource - ? [] : _.pluck(this._modules, 'sourceCode') + ? [] : _.pluck(super.getModules(), 'sourceCode') }; return map; } - getAssets() { - return this._assets; - } - _getMappings() { - const modules = this._modules; + const modules = super.getModules(); // The first line mapping in our package is basically the base64vlq code for // zeros (A). @@ -333,7 +304,7 @@ class Bundle { } getJSModulePaths() { - return this._modules.filter(function(module) { + return super.getModules().filter(function(module) { // Filter out non-js files. Like images etc. return !module.virtual; }).map(function(module) { @@ -343,7 +314,7 @@ class Bundle { getDebugInfo() { return [ - '

Main Module:

' + this._mainModuleId + '
', + '

Main Module:

' + super.getMainModuleId() + '
', '', '

Module paths and transformed code:

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

Path:

' + m.sourcePath + '

Source:

' + '
'; @@ -369,10 +340,9 @@ class Bundle { } return { - modules: this._modules, - assets: this._assets, + ...super.toJSON(), sourceMapUrl: this._sourceMapUrl, - mainModuleId: this._mainModuleId, + mainModuleId: super.getMainModuleId(), numPrependedModules: this._numPrependedModules, numRequireCalls: this._numRequireCalls, }; @@ -380,18 +350,12 @@ class Bundle { static fromJSON(json) { const bundle = new Bundle(json.sourceMapUrl); - bundle._mainModuleId = json.mainModuleId; - bundle._assets = json.assets; - bundle._modules = json.modules; + bundle._sourceMapUrl = json.sourceMapUrl; bundle._numPrependedModules = json.numPrependedModules; bundle._numRequireCalls = json.numRequireCalls; - Object.freeze(bundle._modules); - Object.seal(bundle._modules); - Object.freeze(bundle._assets); - Object.seal(bundle._assets); - bundle._finalized = true; + BundleBase.fromJSON(bundle, json); return bundle; } diff --git a/react-packager/src/Bundler/BundleBase.js b/react-packager/src/Bundler/BundleBase.js new file mode 100644 index 00000000..635f87e7 --- /dev/null +++ b/react-packager/src/Bundler/BundleBase.js @@ -0,0 +1,94 @@ +/** + * 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 ModuleTransport = require('../lib/ModuleTransport'); + +class BundleBase { + constructor() { + this._finalized = false; + this._modules = []; + this._assets = []; + } + + getMainModuleId() { + return this._mainModuleId; + } + + setMainModuleId(moduleId) { + this._mainModuleId = moduleId; + } + + addModule(module) { + if (!module instanceof ModuleTransport) { + throw new Error('Expeceted a ModuleTransport object'); + } + + this._modules.push(module); + } + + getModules() { + return this._modules; + } + + getAssets() { + return this._assets; + } + + addAsset(asset) { + this._assets.push(asset); + } + + finalize(options) { + Object.freeze(this._modules); + Object.seal(this._modules); + Object.freeze(this._assets); + Object.seal(this._assets); + this._finalized = true; + } + + getSource(options) { + this.assertFinalized(); + + if (this._source) { + return this._source; + } + + this._source = _.pluck(this._modules, 'code').join('\n'); + return this._source; + } + + assertFinalized() { + if (!this._finalized) { + throw new Error('Bundle needs to be finalized before getting any source'); + } + } + + toJSON() { + return { + modules: this._modules, + assets: this._assets, + }; + } + + static fromJSON(bundle, json) { + bundle._assets = json.assets; + bundle._modules = json.modules; + bundle._mainModuleId = json.mainModuleId; + + Object.freeze(bundle._modules); + Object.seal(bundle._modules); + Object.freeze(bundle._assets); + Object.seal(bundle._assets); + bundle._finalized = true; + } +} + +module.exports = BundleBase; diff --git a/react-packager/src/Bundler/HMRBundle.js b/react-packager/src/Bundler/HMRBundle.js new file mode 100644 index 00000000..fa8c95db --- /dev/null +++ b/react-packager/src/Bundler/HMRBundle.js @@ -0,0 +1,47 @@ +/** + * 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 BundleBase = require('./BundleBase'); +const ModuleTransport = require('../lib/ModuleTransport'); + +class HMRBundle extends BundleBase { + constructor() { + super(); + } + + addModule(resolver, response, module, transformed) { + return resolver.resolveRequires(response, + module, + transformed.code, + ).then(({name, code}) => { + code = ` + __accept( + '${name}', + function(global, require, module, exports) { + ${code} + } + ); + `; + + const moduleTransport = new ModuleTransport({ + code, + name, + map: transformed.map, + sourceCode: transformed.sourceCode, + sourcePath: transformed.sourcePath, + virtual: transformed.virtual, + }); + + super.addModule(moduleTransport); + }); + } +} + +module.exports = HMRBundle; diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 6dd3d3b7..1180f0a2 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -10,92 +10,106 @@ jest.autoMockOff(); -var SourceMapGenerator = require('source-map').SourceMapGenerator; +const Bundle = require('../Bundle'); +const ModuleTransport = require('../../lib/ModuleTransport'); +const Promise = require('Promise'); +const SourceMapGenerator = require('source-map').SourceMapGenerator; +const UglifyJS = require('uglify-js'); -var Bundle = require('../Bundle'); -var ModuleTransport = require('../../lib/ModuleTransport'); -var UglifyJS = require('uglify-js'); - -describe('Bundle', function() { +describe('Bundle', () => { var bundle; - beforeEach(function() { + beforeEach(() => { bundle = new Bundle('test_url'); - bundle.getSourceMap = jest.genMockFn().mockImpl(function() { + bundle.getSourceMap = jest.genMockFn().mockImpl(() => { return 'test-source-map'; }); }); - 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', - })); - bundle.addModule(new ModuleTransport({ - code: 'transformed bar;', - sourceCode: 'source bar', - sourcePath: 'bar path', - })); - - bundle.finalize({}); - expect(bundle.getSource({dev: true})).toBe([ - 'transformed foo;', - 'transformed bar;', - '\/\/# sourceMappingURL=test_url' - ].join('\n')); - }); - - it('should be ok to leave out the source map url', function() { - var p = new Bundle(); - p.addModule(new ModuleTransport({ - code: 'transformed foo;', - sourceCode: 'source foo', - sourcePath: 'foo path', - })); - p.addModule(new ModuleTransport({ - code: 'transformed bar;', - sourceCode: 'source bar', - sourcePath: 'bar path', - })); - - p.finalize({}); - expect(p.getSource({dev: true})).toBe([ - 'transformed foo;', - 'transformed bar;', - ].join('\n')); - }); - - it('should create a bundle and add run module code', function() { - bundle.addModule(new ModuleTransport({ - code: 'transformed foo;', - sourceCode: 'source foo', - sourcePath: 'foo path' - })); - - bundle.addModule(new ModuleTransport({ - code: 'transformed bar;', - sourceCode: 'source bar', - sourcePath: 'bar path' - })); - - bundle.setMainModuleId('foo'); - bundle.finalize({ - runBeforeMainModule: ['bar'], - runMainModule: true, + describe('source bundle', () => { + pit('should create a bundle and get the source', () => { + return Promise.resolve().then(() => { + return addModule({ + bundle, + code: 'transformed foo;', + sourceCode: 'source foo', + sourcePath: 'foo path', + }); + }).then(() => { + return addModule({ + bundle, + code: 'transformed bar;', + sourceCode: 'source bar', + sourcePath: 'bar path', + }); + }).then(() => { + bundle.finalize({}); + expect(bundle.getSource({dev: true})).toBe([ + 'transformed foo;', + 'transformed bar;', + '\/\/# sourceMappingURL=test_url' + ].join('\n')); }); - expect(bundle.getSource({dev: true})).toBe([ - 'transformed foo;', - 'transformed bar;', - ';require("bar");', - ';require("foo");', - '\/\/# sourceMappingURL=test_url', - ].join('\n')); }); - it('should get minified source', function() { - var minified = { + pit('should be ok to leave out the source map url', () => { + const otherBundle = new Bundle(); + return Promise.resolve().then(() => { + return addModule({ + bundle: otherBundle, + code: 'transformed foo;', + sourceCode: 'source foo', + sourcePath: 'foo path', + }); + }).then(() => { + return addModule({ + bundle: otherBundle, + code: 'transformed bar;', + sourceCode: 'source bar', + sourcePath: 'bar path', + }); + }).then(() => { + otherBundle.finalize({}); + expect(otherBundle.getSource({dev: true})).toBe([ + 'transformed foo;', + 'transformed bar;', + ].join('\n')); + }); + }); + + pit('should create a bundle and add run module code', () => { + return Promise.resolve().then(() => { + return addModule({ + bundle, + code: 'transformed foo;', + sourceCode: 'source foo', + sourcePath: 'foo path', + }); + }).then(() => { + return addModule({ + bundle, + code: 'transformed bar;', + sourceCode: 'source bar', + sourcePath: 'bar path', + }); + }).then(() => { + bundle.setMainModuleId('foo'); + bundle.finalize({ + runBeforeMainModule: ['bar'], + runMainModule: true, + }); + expect(bundle.getSource({dev: true})).toBe([ + 'transformed foo;', + 'transformed bar;', + ';require("bar");', + ';require("foo");', + '\/\/# sourceMappingURL=test_url', + ].join('\n')); + }); + }); + + pit('should get minified source', () => { + const minified = { code: 'minified', map: 'map', }; @@ -104,141 +118,156 @@ describe('Bundle', function() { return minified; }; - bundle.addModule(new ModuleTransport({ - code: 'transformed foo;', - sourceCode: 'source foo', - sourcePath: 'foo path' - })); - bundle.finalize(); - expect(bundle.getMinifiedSourceAndMap({dev: true})).toBe(minified); - }); - }); - - describe('sourcemap bundle', function() { - it('should create sourcemap', function() { - var p = new Bundle('test_url'); - p.addModule(new ModuleTransport({ - code: [ - 'transformed foo', - 'transformed foo', - 'transformed foo', - ].join('\n'), - sourceCode: [ - 'source foo', - 'source foo', - 'source foo', - ].join('\n'), - sourcePath: 'foo path', - })); - p.addModule(new ModuleTransport({ - code: [ - 'transformed bar', - 'transformed bar', - 'transformed bar', - ].join('\n'), - sourceCode: [ - 'source bar', - 'source bar', - 'source bar', - ].join('\n'), - sourcePath: 'bar path', - })); - - p.setMainModuleId('foo'); - p.finalize({ - runBeforeMainModule: [], - runMainModule: true, - }); - var s = p.getSourceMap({dev: true}); - expect(s).toEqual(genSourceMap(p.getModules())); - }); - - it('should combine sourcemaps', function() { - var p = new Bundle('test_url'); - - p.addModule(new ModuleTransport({ - code: 'transformed foo;\n', - map: {name: 'sourcemap foo'}, - sourceCode: 'source foo', - sourcePath: 'foo path' - })); - - p.addModule(new ModuleTransport({ - code: 'transformed foo;\n', - map: {name: 'sourcemap bar'}, - sourceCode: 'source foo', - sourcePath: 'foo path' - })); - - p.addModule(new ModuleTransport({ - code: 'image module;\nimage module;', - virtual: true, - sourceCode: 'image module;\nimage module;', - sourcePath: 'image.png', - })); - - p.setMainModuleId('foo'); - p.finalize({ - runBeforeMainModule: ['InitializeJavaScriptAppEngine'], - runMainModule: true, - }); - - var s = p.getSourceMap({dev: true}); - expect(s).toEqual({ - file: 'bundle.js', - version: 3, - sections: [ - { offset: { line: 0, column: 0 }, map: { name: 'sourcemap foo' } }, - { offset: { line: 2, column: 0 }, map: { name: 'sourcemap bar' } }, - { - offset: { - column: 0, - line: 4 - }, - map: { - file: 'image.png', - mappings: 'AAAA;AACA;', - names: [], - sources: [ 'image.png' ], - sourcesContent: ['image module;\nimage module;'], - version: 3, - } - }, - { - offset: { - column: 0, - line: 6 - }, - map: { - file: 'require-InitializeJavaScriptAppEngine.js', - mappings: 'AAAA;', - names: [], - sources: [ 'require-InitializeJavaScriptAppEngine.js' ], - sourcesContent: [';require("InitializeJavaScriptAppEngine");'], - version: 3, - } - }, - { - offset: { - column: 0, - line: 7 - }, - map: { - file: 'require-foo.js', - mappings: 'AAAA;', - names: [], - sources: [ 'require-foo.js' ], - sourcesContent: [';require("foo");'], - version: 3, - } - }, - ], + return Promise.resolve().then(() => { + return addModule({ + bundle, + code: 'transformed foo;', + sourceCode: 'source foo', + sourcePath: 'foo path', + }); + }).then(() => { + bundle.finalize(); + expect(bundle.getMinifiedSourceAndMap({dev: true})).toBe(minified); }); }); }); - describe('getAssets()', function() { - it('should save and return asset objects', function() { + describe('sourcemap bundle', () => { + pit('should create sourcemap', () => { + const otherBundle = new Bundle('test_url'); + + return Promise.resolve().then(() => { + return addModule({ + bundle: otherBundle, + code: [ + 'transformed foo', + 'transformed foo', + 'transformed foo', + ].join('\n'), + sourceCode: [ + 'source foo', + 'source foo', + 'source foo', + ].join('\n'), + sourcePath: 'foo path', + }); + }).then(() => { + return addModule({ + bundle: otherBundle, + code: [ + 'transformed bar', + 'transformed bar', + 'transformed bar', + ].join('\n'), + sourceCode: [ + 'source bar', + 'source bar', + 'source bar', + ].join('\n'), + sourcePath: 'bar path', + }); + }).then(() => { + otherBundle.setMainModuleId('foo'); + otherBundle.finalize({ + runBeforeMainModule: [], + runMainModule: true, + }); + const sourceMap = otherBundle.getSourceMap({dev: true}); + expect(sourceMap).toEqual(genSourceMap(otherBundle.getModules())); + }); + }); + + pit('should combine sourcemaps', () => { + const otherBundle = new Bundle('test_url'); + + return Promise.resolve().then(() => { + return addModule({ + bundle: otherBundle, + code: 'transformed foo;\n', + sourceCode: 'source foo', + map: {name: 'sourcemap foo'}, + sourcePath: 'foo path', + }); + }).then(() => { + return addModule({ + bundle: otherBundle, + code: 'transformed bar;\n', + sourceCode: 'source bar', + map: {name: 'sourcemap bar'}, + sourcePath: 'bar path', + }); + }).then(() => { + return addModule({ + bundle: otherBundle, + code: 'image module;\nimage module;', + virtual: true, + sourceCode: 'image module;\nimage module;', + sourcePath: 'image.png', + }); + }).then(() => { + otherBundle.setMainModuleId('foo'); + otherBundle.finalize({ + runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + runMainModule: true, + }); + + const sourceMap = otherBundle.getSourceMap({dev: true}); + expect(sourceMap).toEqual({ + file: 'bundle.js', + version: 3, + sections: [ + { offset: { line: 0, column: 0 }, map: { name: 'sourcemap foo' } }, + { offset: { line: 2, column: 0 }, map: { name: 'sourcemap bar' } }, + { + offset: { + column: 0, + line: 4 + }, + map: { + file: 'image.png', + mappings: 'AAAA;AACA;', + names: [], + sources: [ 'image.png' ], + sourcesContent: ['image module;\nimage module;'], + version: 3, + } + }, + { + offset: { + column: 0, + line: 6 + }, + map: { + file: 'require-InitializeJavaScriptAppEngine.js', + mappings: 'AAAA;', + names: [], + sources: [ 'require-InitializeJavaScriptAppEngine.js' ], + sourcesContent: [';require("InitializeJavaScriptAppEngine");'], + version: 3, + } + }, + { + offset: { + column: 0, + line: 7 + }, + map: { + file: 'require-foo.js', + mappings: 'AAAA;', + names: [], + sources: [ 'require-foo.js' ], + sourcesContent: [';require("foo");'], + version: 3, + } + }, + ], + }); + }); + }); + }); + + describe('getAssets()', () => { + it('should save and return asset objects', () => { var p = new Bundle('test_url'); var asset1 = {}; var asset2 = {}; @@ -249,22 +278,27 @@ describe('Bundle', function() { }); }); - describe('getJSModulePaths()', function() { - it('should return module paths', function() { - var p = new Bundle('test_url'); - p.addModule(new ModuleTransport({ - code: 'transformed foo;\n', - sourceCode: 'source foo', - sourcePath: 'foo path' - })); - p.addModule(new ModuleTransport({ - code: 'image module;\nimage module;', - virtual: true, - sourceCode: 'image module;\nimage module;', - sourcePath: 'image.png', - })); - - expect(p.getJSModulePaths()).toEqual(['foo path']); + describe('getJSModulePaths()', () => { + pit('should return module paths', () => { + var otherBundle = new Bundle('test_url'); + return Promise.resolve().then(() => { + return addModule({ + bundle: otherBundle, + code: 'transformed foo;\n', + sourceCode: 'source foo', + sourcePath: 'foo path', + }); + }).then(() => { + return addModule({ + bundle: otherBundle, + code: 'image module;\nimage module;', + virtual: true, + sourceCode: 'image module;\nimage module;', + sourcePath: 'image.png', + }); + }).then(() => { + expect(otherBundle.getJSModulePaths()).toEqual(['foo path']); + }); }); }); }); @@ -302,3 +336,20 @@ function genSourceMap(modules) { } return sourceMapGen.toJSON(); } + +function resolverFor(code) { + return { + wrapModule: (response, module, sourceCode) => Promise.resolve( + {name: 'name', code} + ), + }; +} + +function addModule({bundle, code, sourceCode, sourcePath, map, virtual}) { + return bundle.addModule( + resolverFor(code), + null, + null, + {sourceCode, sourcePath, map, virtual} + ); +} diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index ccdc8496..467cf90d 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -161,46 +161,29 @@ describe('Bundler', function() { runBeforeMainModule: [], runModule: true, sourceMapUrl: 'source_map_url', - }).then(function(p) { - expect(p.addModule.mock.calls[0][0]).toEqual({ - name: 'foo', - code: 'lol transformed /root/foo.js lol', - map: 'sourcemap /root/foo.js', - sourceCode: 'source /root/foo.js', - sourcePath: '/root/foo.js', - }); + }).then(bundle => { + const ithAddedModule = (i) => bundle.addModule.mock.calls[i][2].path; - expect(p.addModule.mock.calls[1][0]).toEqual({ - name: 'bar', - code: 'lol transformed /root/bar.js lol', - map: 'sourcemap /root/bar.js', - sourceCode: 'source /root/bar.js', - sourcePath: '/root/bar.js' - }); + expect(ithAddedModule(0)).toEqual('/root/foo.js'); + expect(ithAddedModule(1)).toEqual('/root/bar.js'); + expect(ithAddedModule(2)).toEqual('/root/img/img.png'); + expect(ithAddedModule(3)).toEqual('/root/img/new_image.png'); + expect(ithAddedModule(4)).toEqual('/root/file.json'); - var imgModule_DEPRECATED = { + expect(bundle.finalize.mock.calls[0]).toEqual([ + {runMainModule: true, runBeforeMainModule: []} + ]); + + expect(bundle.addAsset.mock.calls).toContain([{ __packager_asset: true, path: '/root/img/img.png', uri: 'img', width: 25, height: 50, deprecated: true, - }; + }]); - expect(p.addModule.mock.calls[2][0]).toEqual({ - name: 'image!img', - code: 'lol module.exports = ' + - JSON.stringify(imgModule_DEPRECATED) + - '; lol', - sourceCode: 'module.exports = ' + - JSON.stringify(imgModule_DEPRECATED) + - ';', - sourcePath: '/root/img/img.png', - virtual: true, - map: undefined, - }); - - var imgModule = { + expect(bundle.addAsset.mock.calls).toContain([{ __packager_asset: true, fileSystemLocation: '/root/img', httpServerLocation: '/assets/img', @@ -215,41 +198,7 @@ describe('Bundler', function() { hash: 'i am a hash', name: 'img', type: 'png', - }; - - expect(p.addModule.mock.calls[3][0]).toEqual({ - name: 'new_image.png', - code: 'lol module.exports = require("AssetRegistry").registerAsset(' + - JSON.stringify(imgModule) + - '); lol', - sourceCode: 'module.exports = require("AssetRegistry").registerAsset(' + - JSON.stringify(imgModule) + - ');', - sourcePath: '/root/img/new_image.png', - virtual: true, - map: undefined, - }); - - expect(p.addModule.mock.calls[4][0]).toEqual({ - name: 'package/file.json', - code: 'lol module.exports = {"json":true}; lol', - sourceCode: 'module.exports = {"json":true};', - sourcePath: '/root/file.json', - map: undefined, - virtual: true, - }); - - expect(p.finalize.mock.calls[0]).toEqual([ - {runMainModule: true, runBeforeMainModule: []} - ]); - - expect(p.addAsset.mock.calls).toContain([ - imgModule_DEPRECATED - ]); - - expect(p.addAsset.mock.calls).toContain([ - imgModule - ]); + }]); // TODO(amasad) This fails with 0 != 5 in OSS //expect(ProgressBar.prototype.tick.mock.calls.length).toEqual(modules.length); diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index ebac145c..1cc28f23 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -18,6 +18,7 @@ const Cache = require('../DependencyResolver/Cache'); const Transformer = require('../JSTransformer'); const Resolver = require('../Resolver'); const Bundle = require('./Bundle'); +const HMRBundle = require('./HMRBundle'); const PrepackBundle = require('./PrepackBundle'); const Activity = require('../Activity'); const ModuleTransport = require('../lib/ModuleTransport'); @@ -155,31 +156,54 @@ class Bundler { return this._bundlesLayout.generateLayout(main, isDev); } - bundle({ + bundle(options) { + return this._bundle({ + bundle: new Bundle(options.sourceMapUrl), + includeSystemDependencies: true, + ...options, + }); + } + + bundleForHMR(options) { + return this._bundle({ + bundle: new HMRBundle(), + hot: true, + ...options, + }); + } + + _bundle({ + bundle, + modules, entryFile, runModule: runMainModule, runBeforeMainModule, - sourceMapUrl, dev: isDev, + includeSystemDependencies, platform, unbundle: isUnbundle, hot: hot, }) { - // Const cannot have the same name as the method (babel/babel#2834) - const bbundle = new Bundle(sourceMapUrl); const findEventId = Activity.startEvent('find dependencies'); let transformEventId; - const moduleSystem = this._resolver.getModuleSystemDependencies( - { dev: isDev, platform, isUnbundle } - ); - return this.getDependencies(entryFile, isDev, platform).then((response) => { Activity.endEvent(findEventId); + bundle.setMainModuleId(response.mainModuleId); transformEventId = Activity.startEvent('transform'); - // Prepend the module system polyfill to the top of dependencies - var dependencies = moduleSystem.concat(response.dependencies); + const moduleSystemDeps = includeSystemDependencies + ? this._resolver.getModuleSystemDependencies( + { dev: isDev, platform, isUnbundle } + ) + : []; + + const modulesToProcess = modules || response.dependencies; + const dependencies = moduleSystemDeps.concat(modulesToProcess); + + bundle.setNumPrependedModules && bundle.setNumPrependedModules( + response.numPrependedDependencies + moduleSystemDeps.length + ); let bar; if (process.stdout.isTTY) { @@ -191,34 +215,41 @@ class Bundler { }); } - bbundle.setMainModuleId(response.mainModuleId); - bbundle.setNumPrependedModules( - response.numPrependedDependencies + moduleSystem.length); return Promise.all( dependencies.map( - module => this._transformModule( - bbundle, + module => { + return this._transformModule( + bundle, + response, + module, + platform, + hot, + ).then(transformed => { + if (bar) { + bar.tick(); + } + + return { + module, + transformed, + }; + }); + } + ) + ).then(transformedModules => Promise.all( + transformedModules.map(({module, transformed}) => { + return bundle.addModule( + this._resolver, response, module, - platform, - hot, - ).then(transformed => { - if (bar) { - bar.tick(); - } - return this._wrapTransformedModule(response, module, transformed); - }) - ) - ); - }).then((transformedModules) => { + transformed, + ); + }) + )); + }).then(() => { Activity.endEvent(transformEventId); - - transformedModules.forEach(function(moduleTransport) { - bbundle.addModule(moduleTransport); - }); - - bbundle.finalize({runBeforeMainModule, runMainModule}); - return bbundle; + bundle.finalize({runBeforeMainModule, runMainModule}); + return bundle; }); } @@ -284,35 +315,6 @@ class Bundler { }); } - bundleForHMR({entryFile, platform, modules}) { - return this.getDependencies(entryFile, /*isDev*/true, platform) - .then(response => { - return Promise.all( - modules.map(module => { - return Promise.all([ - module.getName(), - this._transformModuleForHMR(module, platform), - ]).then(([moduleName, transformed]) => { - return this._resolver.resolveRequires(response, - module, - transformed.code, - ).then(({name, code}) => { - return (` - __accept( - '${moduleName}', - function(global, require, module, exports) { - ${code} - } - ); - `); - }); - }); - }) - ); - }) - .then(modules => modules.join('\n')); - } - _transformModuleForHMR(module, platform) { if (module.isAsset()) { return this._generateAssetObjAndCode(module, platform).then( @@ -401,23 +403,6 @@ class Bundler { } } - _wrapTransformedModule(response, module, transformed) { - return this._resolver.wrapModule( - response, - module, - transformed.code - ).then( - ({code, name}) => new ModuleTransport({ - code, - name, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }) - ); - } - getGraphDebugInfo() { return this._resolver.getDebugInfo(); } From f13ceab3152b8822e2dd880aa19799cedeeacb21 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Wed, 20 Jan 2016 10:25:23 -0800 Subject: [PATCH 532/936] add babelHelpers.toArray to polyfill Reviewed By: yungsters, davidaurelio Differential Revision: D2844203 fb-gh-sync-id: 6e76ed51265150108ef91ca64c5c5d930f14982e --- react-packager/src/Resolver/polyfills/babelHelpers.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/polyfills/babelHelpers.js b/react-packager/src/Resolver/polyfills/babelHelpers.js index e028a782..de54ebc2 100644 --- a/react-packager/src/Resolver/polyfills/babelHelpers.js +++ b/react-packager/src/Resolver/polyfills/babelHelpers.js @@ -11,7 +11,7 @@ /* eslint-disable strict */ // Created by running: -// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray taggedTemplateLiteral toConsumableArray '.split(' ')) +// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray taggedTemplateLiteral toArray toConsumableArray '.split(' ')) // then replacing the `global` reference in the last line to also use `this`. // // actually, that's a lie, because babel6 omits _extends and createRawReactElement @@ -216,6 +216,10 @@ })); }; + babelHelpers.toArray = function (arr) { + return Array.isArray(arr) ? arr : Array.from(arr); + }; + babelHelpers.toConsumableArray = function (arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; From d8888183ba80f313b65f3dd0059618c684f14674 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 20 Jan 2016 10:27:34 -0800 Subject: [PATCH 533/936] Fix default logger's loglevel, improve default error handler Reviewed By: majak Differential Revision: D2834267 fb-gh-sync-id: c89ce5160f03f57409497cc802690c8360d88a4d --- react-packager/src/Resolver/polyfills/error-guard.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/error-guard.js b/react-packager/src/Resolver/polyfills/error-guard.js index d4aa4122..ac99cadc 100644 --- a/react-packager/src/Resolver/polyfills/error-guard.js +++ b/react-packager/src/Resolver/polyfills/error-guard.js @@ -72,17 +72,12 @@ /** * This is the error handler that is called when we encounter an exception - * when loading a module. + * when loading a module. This will report any errors encountered before + * ExceptionsManager is configured. */ function setupErrorGuard() { var onError = function(e) { - global.console.error( - 'Error: ' + - '\n stack: ' + e.stack + - '\n line: ' + e.line + - '\n message: ' + e.message, - e - ); + global.console.error('Error: ' + e.message + ', stack:\n' + e.stack); }; global.ErrorUtils.setGlobalHandler(onError); } From f1fa67feafd3f399c1b7607690c5f842505b85bf Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Thu, 21 Jan 2016 07:22:20 -0800 Subject: [PATCH 534/936] Make the packager work with babel strict mode transform Summary: At the moment we have to disable strict mode for the transform-es2015-modules-commonjs because strict mode leaks to the global scope and breaks the bridge. It was due to the way the polyfills were bundled in the package. To fix it, I wrapped the polyfill modules in an IIFE. Then when strict mode was enabled some polyfills were broken due to strict mode errors so that was fixed too. Also removed the IIFE from the polyfills that included one. This diff doesn't enable the strict mode transform since some internal facebook modules depend on it not being enabled. When #5214 lands we could make the default babel config shipped with OSS react-native use strict mode modules and facebook could just modify the babel config to disable it if needed. This will allow removing `"strict": false` from https://github.com/facebook/react-native/blob/master/packager/react-packager/.babelrc#L16 Fixes #5316 Closes https://github.com/facebook/react-native/pull/5422 Reviewed By: svcscm Differential Revision: D2846422 Pulled By: davidaurelio fb-gh-sync-id: a3e2f8909aa87dabab2b872c61b887e80220fb56 --- .../src/Resolver/__tests__/Resolver-test.js | 29 + react-packager/src/Resolver/index.js | 12 +- .../Resolver/polyfills/Array.prototype.es6.js | 91 +- .../src/Resolver/polyfills/babelHelpers.js | 363 ++++--- .../src/Resolver/polyfills/console.js | 883 +++++++++--------- .../src/Resolver/polyfills/error-guard.js | 132 ++- .../src/Resolver/polyfills/loadBundles.js | 74 +- .../src/Resolver/polyfills/polyfills.js | 2 +- .../src/Resolver/polyfills/prelude.js | 7 +- .../src/Resolver/polyfills/prelude_dev.js | 7 +- .../Resolver/polyfills/require-unbundle.js | 113 ++- .../src/Resolver/polyfills/require.js | 218 +++-- .../__tests__/dead-module-elimination-test.js | 14 +- .../dead-module-elimination.js | 9 +- 14 files changed, 982 insertions(+), 972 deletions(-) diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 82184c61..825399b4 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -58,6 +58,14 @@ describe('Resolver', function() { return module; } + function createPolyfill(id, dependencies) { + var polyfill = new Polyfill({}); + polyfill.getName.mockImpl(() => Promise.resolve(id)); + polyfill.getDependencies.mockImpl(() => Promise.resolve(dependencies)); + polyfill.isPolyfill.mockReturnValue(true); + return polyfill; + } + describe('getDependencies', function() { pit('should get dependencies with polyfills', function() { var module = createModule('index'); @@ -1020,5 +1028,26 @@ describe('Resolver', function() { ].join('\n')); }); }); + + pit('should resolve polyfills', function () { + const depResolver = new Resolver({ + projectRoot: '/root', + }); + const polyfill = createPolyfill('test polyfill', []); + const code = [ + 'global.fetch = () => 1;', + ].join(''); + return depResolver.wrapModule( + null, + polyfill, + code + ).then(processedCode => { + expect(processedCode.code).toEqual([ + '(function(global) {', + 'global.fetch = () => 1;', + "\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);", + ].join('')); + }); + }); }); }); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index cf021e16..0166681f 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -214,7 +214,9 @@ class Resolver { wrapModule(resolutionResponse, module, code) { if (module.isPolyfill()) { - return Promise.resolve({code}); + return Promise.resolve({ + code: definePolyfillCode(code), + }); } return this.resolveRequires(resolutionResponse, module, code).then( @@ -239,4 +241,12 @@ function defineModuleCode(moduleName, code) { ].join(''); } +function definePolyfillCode(code) { + return [ + '(function(global) {', + code, + `\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);`, + ].join(''); +} + module.exports = Resolver; diff --git a/react-packager/src/Resolver/polyfills/Array.prototype.es6.js b/react-packager/src/Resolver/polyfills/Array.prototype.es6.js index 80e62f05..2752aab5 100644 --- a/react-packager/src/Resolver/polyfills/Array.prototype.es6.js +++ b/react-packager/src/Resolver/polyfills/Array.prototype.es6.js @@ -5,54 +5,51 @@ * @polyfill */ -/*eslint-disable */ -/*jslint bitwise: true */ +/* eslint-disable */ -(function(undefined) { - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex - function findIndex(predicate, context) { - if (this == null) { - throw new TypeError( - 'Array.prototype.findIndex called on null or undefined' - ); +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex +function findIndex(predicate, context) { + if (this == null) { + throw new TypeError( + 'Array.prototype.findIndex called on null or undefined' + ); + } + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + var list = Object(this); + var length = list.length >>> 0; + for (var i = 0; i < length; i++) { + if (predicate.call(context, list[i], i, list)) { + return i; } - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - var list = Object(this); - var length = list.length >>> 0; - for (var i = 0; i < length; i++) { - if (predicate.call(context, list[i], i, list)) { - return i; + } + return -1; +} + +if (!Array.prototype.findIndex) { + Object.defineProperty(Array.prototype, 'findIndex', { + enumerable: false, + writable: true, + configurable: true, + value: findIndex + }); +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find +if (!Array.prototype.find) { + Object.defineProperty(Array.prototype, 'find', { + enumerable: false, + writable: true, + configurable: true, + value: function(predicate, context) { + if (this == null) { + throw new TypeError( + 'Array.prototype.find called on null or undefined' + ); } + var index = findIndex.call(this, predicate, context); + return index === -1 ? undefined : this[index]; } - return -1; - } - - if (!Array.prototype.findIndex) { - Object.defineProperty(Array.prototype, 'findIndex', { - enumerable: false, - writable: true, - configurable: true, - value: findIndex - }); - } - - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find - if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, 'find', { - enumerable: false, - writable: true, - configurable: true, - value: function(predicate, context) { - if (this == null) { - throw new TypeError( - 'Array.prototype.find called on null or undefined' - ); - } - var index = findIndex.call(this, predicate, context); - return index === -1 ? undefined : this[index]; - } - }); - } -})(); + }); +} diff --git a/react-packager/src/Resolver/polyfills/babelHelpers.js b/react-packager/src/Resolver/polyfills/babelHelpers.js index de54ebc2..ff442257 100644 --- a/react-packager/src/Resolver/polyfills/babelHelpers.js +++ b/react-packager/src/Resolver/polyfills/babelHelpers.js @@ -7,8 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -/* global self:true */ -/* eslint-disable strict */ +/* eslint-disable */ // Created by running: // require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray taggedTemplateLiteral toArray toConsumableArray '.split(' ')) @@ -16,217 +15,215 @@ // // actually, that's a lie, because babel6 omits _extends and createRawReactElement -(function (global) { - var babelHelpers = global.babelHelpers = {}; +var babelHelpers = global.babelHelpers = {}; - babelHelpers.createRawReactElement = (function () { - var REACT_ELEMENT_TYPE = typeof Symbol === "function" && Symbol.for && Symbol.for("react.element") || 0xeac7; - return function createRawReactElement(type, key, props) { - return { - $$typeof: REACT_ELEMENT_TYPE, - type: type, - key: key, - ref: null, - props: props, - _owner: null - }; +babelHelpers.createRawReactElement = (function () { + var REACT_ELEMENT_TYPE = typeof Symbol === "function" && Symbol.for && Symbol.for("react.element") || 0xeac7; + return function createRawReactElement(type, key, props) { + return { + $$typeof: REACT_ELEMENT_TYPE, + type: type, + key: key, + ref: null, + props: props, + _owner: null }; - })(); - - babelHelpers.classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } }; +})(); - babelHelpers.createClass = (function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } +babelHelpers.classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; + +babelHelpers.createClass = (function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); } + } - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - })(); - - babelHelpers.defineProperty = function (obj, key, value) { - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } else { - obj[key] = value; - } - - return obj; + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; }; +})(); - babelHelpers._extends = babelHelpers.extends = Object.assign || function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; - }; - - babelHelpers.get = function get(object, property, receiver) { - if (object === null) object = Function.prototype; - var desc = Object.getOwnPropertyDescriptor(object, property); - - if (desc === undefined) { - var parent = Object.getPrototypeOf(object); - - if (parent === null) { - return undefined; - } else { - return get(parent, property, receiver); - } - } else if ("value" in desc) { - return desc.value; - } else { - var getter = desc.get; - - if (getter === undefined) { - return undefined; - } - - return getter.call(receiver); - } - }; - - babelHelpers.inherits = function (subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } +babelHelpers.defineProperty = function (obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; - }; + } else { + obj[key] = value; + } - babelHelpers.interopRequireDefault = function (obj) { - return obj && obj.__esModule ? obj : { - default: obj - }; - }; + return obj; +}; - babelHelpers.interopRequireWildcard = function (obj) { - if (obj && obj.__esModule) { - return obj; - } else { - var newObj = {}; +babelHelpers._extends = babelHelpers.extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; - if (obj != null) { - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; - } + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; } - - newObj.default = obj; - return newObj; } - }; + } - babelHelpers.objectWithoutProperties = function (obj, keys) { - var target = {}; + return target; +}; - for (var i in obj) { - if (keys.indexOf(i) >= 0) continue; - if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; - target[i] = obj[i]; +babelHelpers.get = function get(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent, property, receiver); + } + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; } - return target; - }; + return getter.call(receiver); + } +}; - babelHelpers.possibleConstructorReturn = function (self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); +babelHelpers.inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +}; + +babelHelpers.interopRequireDefault = function (obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; +}; + +babelHelpers.interopRequireWildcard = function (obj) { + if (obj && obj.__esModule) { + return obj; + } else { + var newObj = {}; + + if (obj != null) { + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; + } } - return call && (typeof call === "object" || typeof call === "function") ? call : self; - }; + newObj.default = obj; + return newObj; + } +}; - babelHelpers.slicedToArray = (function () { - function sliceIterator(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; +babelHelpers.objectWithoutProperties = function (obj, keys) { + var target = {}; + for (var i in obj) { + if (keys.indexOf(i) >= 0) continue; + if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; + target[i] = obj[i]; + } + + return target; +}; + +babelHelpers.possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; +}; + +babelHelpers.slicedToArray = (function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; + if (!_n && _i["return"]) _i["return"](); } finally { - try { - if (!_n && _i["return"]) _i["return"](); - } finally { - if (_d) throw _e; - } + if (_d) throw _e; } - - return _arr; } - return function (arr, i) { - if (Array.isArray(arr)) { - return arr; - } else if (Symbol.iterator in Object(arr)) { - return sliceIterator(arr, i); - } else { - throw new TypeError("Invalid attempt to destructure non-iterable instance"); - } - }; - })(); - - babelHelpers.taggedTemplateLiteral = function (strings, raw) { - return Object.freeze(Object.defineProperties(strings, { - raw: { - value: Object.freeze(raw) - } - })); - }; + return _arr; + } - babelHelpers.toArray = function (arr) { - return Array.isArray(arr) ? arr : Array.from(arr); - }; - - babelHelpers.toConsumableArray = function (arr) { + return function (arr, i) { if (Array.isArray(arr)) { - for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; - - return arr2; + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); } else { - return Array.from(arr); + throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); +})(); + +babelHelpers.taggedTemplateLiteral = function (strings, raw) { + return Object.freeze(Object.defineProperties(strings, { + raw: { + value: Object.freeze(raw) + } + })); +}; + +babelHelpers.toArray = function (arr) { + return Array.isArray(arr) ? arr : Array.from(arr); +}; + +babelHelpers.toConsumableArray = function (arr) { + if (Array.isArray(arr)) { + for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; + + return arr2; + } else { + return Array.from(arr); + } +}; diff --git a/react-packager/src/Resolver/polyfills/console.js b/react-packager/src/Resolver/polyfills/console.js index a8e5a72f..5406db73 100644 --- a/react-packager/src/Resolver/polyfills/console.js +++ b/react-packager/src/Resolver/polyfills/console.js @@ -14,495 +14,492 @@ * @nolint */ -(function(global) { - 'use strict'; +/* eslint-disable */ - var inspect = (function() { - // Copyright Joyent, Inc. and other Node contributors. - // - // Permission is hereby granted, free of charge, to any person obtaining a - // copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to permit - // persons to whom the Software is furnished to do so, subject to the - // following conditions: - // - // The above copyright notice and this permission notice shall be included - // in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE - // USE OR OTHER DEALINGS IN THE SOFTWARE. - // - // https://github.com/joyent/node/blob/master/lib/util.js +var inspect = (function() { + // Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + // + // https://github.com/joyent/node/blob/master/lib/util.js - function inspect(obj, opts) { - var ctx = { - seen: [], - stylize: stylizeNoColor - }; - return formatValue(ctx, obj, opts.depth); + function inspect(obj, opts) { + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + return formatValue(ctx, obj, opts.depth); + } + + function stylizeNoColor(str, styleType) { + return str; + } + + function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; + } + + + function formatValue(ctx, value, recurseTimes) { + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; } - function stylizeNoColor(str, styleType) { - return str; + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); } - function arrayToHash(array) { - var hash = {}; - - array.forEach(function(val, idx) { - hash[val] = true; - }); - - return hash; - } - - - function formatValue(ctx, value, recurseTimes) { - // Primitive types cannot have properties - var primitive = formatPrimitive(ctx, value); - if (primitive) { - return primitive; + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); } - - // Look up the keys of the object. - var keys = Object.keys(value); - var visibleKeys = arrayToHash(keys); - - // IE doesn't make error fields non-enumerable - // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx - if (isError(value) - && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { return formatError(value); } + } - // Some type of object without properties can be shortcutted. - if (keys.length === 0) { - if (isFunction(value)) { - var name = value.name ? ': ' + value.name : ''; - return ctx.stylize('[Function' + name + ']', 'special'); - } - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } - if (isDate(value)) { - return ctx.stylize(Date.prototype.toString.call(value), 'date'); - } - if (isError(value)) { - return formatError(value); - } - } + var base = '', array = false, braces = ['{', '}']; - var base = '', array = false, braces = ['{', '}']; + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } - // Make Array say that they are Array - if (isArray(value)) { - array = true; - braces = ['[', ']']; - } + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } - // Make functions say that they are functions - if (isFunction(value)) { - var n = value.name ? ': ' + value.name : ''; - base = ' [Function' + n + ']'; - } + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } - // Make RegExps say that they are RegExps + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { if (isRegExp(value)) { - base = ' ' + RegExp.prototype.toString.call(value); - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + Date.prototype.toUTCString.call(value); - } - - // Make error with message first say the error - if (isError(value)) { - base = ' ' + formatError(value); - } - - if (keys.length === 0 && (!array || value.length == 0)) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } else { - return ctx.stylize('[Object]', 'special'); - } - } - - ctx.seen.push(value); - - var output; - if (array) { - output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); } else { - output = keys.map(function(key) { - return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); - }); + return ctx.stylize('[Object]', 'special'); } - - ctx.seen.pop(); - - return reduceToSingleString(output, base, braces); } + ctx.seen.push(value); - function formatPrimitive(ctx, value) { - if (isUndefined(value)) - return ctx.stylize('undefined', 'undefined'); - if (isString(value)) { - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return ctx.stylize(simple, 'string'); - } - if (isNumber(value)) - return ctx.stylize('' + value, 'number'); - if (isBoolean(value)) - return ctx.stylize('' + value, 'boolean'); - // For some reason typeof null is "object", so special case here. - if (isNull(value)) - return ctx.stylize('null', 'null'); - } - - - function formatError(value) { - return '[' + Error.prototype.toString.call(value) + ']'; - } - - - function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { - var output = []; - for (var i = 0, l = value.length; i < l; ++i) { - if (hasOwnProperty(value, String(i))) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - String(i), true)); - } else { - output.push(''); - } - } - keys.forEach(function(key) { - if (!key.match(/^\d+$/)) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - key, true)); - } + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); }); - return output; } + ctx.seen.pop(); - function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { - var name, str, desc; - desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; - if (desc.get) { - if (desc.set) { - str = ctx.stylize('[Getter/Setter]', 'special'); + return reduceToSingleString(output, base, braces); + } + + + function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); + } + + + function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; + } + + + function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; + } + + + function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); } else { - str = ctx.stylize('[Getter]', 'special'); + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } } } else { - if (desc.set) { - str = ctx.stylize('[Setter]', 'special'); - } + str = ctx.stylize('[Circular]', 'special'); } - if (!hasOwnProperty(visibleKeys, key)) { - name = '[' + key + ']'; + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; } - if (!str) { - if (ctx.seen.indexOf(desc.value) < 0) { - if (isNull(recurseTimes)) { - str = formatValue(ctx, desc.value, null); - } else { - str = formatValue(ctx, desc.value, recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (array) { - str = str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = ctx.stylize('[Circular]', 'special'); - } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); } - if (isUndefined(name)) { - if (array && key.match(/^\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = ctx.stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = ctx.stylize(name, 'string'); - } + } + + return name + ': ' + str; + } + + + function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; + } + + + // NOTE: These type checking functions intentionally don't use `instanceof` + // because it is fragile and can be easily faked with `Object.create()`. + function isArray(ar) { + return Array.isArray(ar); + } + + function isBoolean(arg) { + return typeof arg === 'boolean'; + } + + function isNull(arg) { + return arg === null; + } + + function isNullOrUndefined(arg) { + return arg == null; + } + + function isNumber(arg) { + return typeof arg === 'number'; + } + + function isString(arg) { + return typeof arg === 'string'; + } + + function isSymbol(arg) { + return typeof arg === 'symbol'; + } + + function isUndefined(arg) { + return arg === void 0; + } + + function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; + } + + function isObject(arg) { + return typeof arg === 'object' && arg !== null; + } + + function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; + } + + function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); + } + + function isFunction(arg) { + return typeof arg === 'function'; + } + + function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; + } + + function objectToString(o) { + return Object.prototype.toString.call(o); + } + + function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); + } + + return inspect; +})(); + + +var OBJECT_COLUMN_NAME = '(index)'; +var LOG_LEVELS = { + trace: 0, + info: 1, + warn: 2, + error: 3 +}; + +function setupConsole(global) { + if (!global.nativeLoggingHook) { + return; + } + + function getNativeLogFunction(level) { + return function() { + var str; + if (arguments.length === 1 && typeof arguments[0] === 'string') { + str = arguments[0]; + } else { + str = Array.prototype.map.call(arguments, function(arg) { + return inspect(arg, {depth: 10}); + }).join(', '); } - return name + ': ' + str; - } - - - function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; - }, 0); - - if (length > 60) { - return braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; + var logLevel = level; + if (str.slice(0, 9) === 'Warning: ' && logLevel >= 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.) + logLevel = LOG_LEVELS.warn; } + global.nativeLoggingHook(str, logLevel); + }; + } - return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; - } - - - // NOTE: These type checking functions intentionally don't use `instanceof` - // because it is fragile and can be easily faked with `Object.create()`. - function isArray(ar) { - return Array.isArray(ar); - } - - function isBoolean(arg) { - return typeof arg === 'boolean'; - } - - function isNull(arg) { - return arg === null; - } - - function isNullOrUndefined(arg) { - return arg == null; - } - - function isNumber(arg) { - return typeof arg === 'number'; - } - - function isString(arg) { - return typeof arg === 'string'; - } - - function isSymbol(arg) { - return typeof arg === 'symbol'; - } - - function isUndefined(arg) { - return arg === void 0; - } - - function isRegExp(re) { - return isObject(re) && objectToString(re) === '[object RegExp]'; - } - - function isObject(arg) { - return typeof arg === 'object' && arg !== null; - } - - function isDate(d) { - return isObject(d) && objectToString(d) === '[object Date]'; - } - - function isError(e) { - return isObject(e) && - (objectToString(e) === '[object Error]' || e instanceof Error); - } - - function isFunction(arg) { - return typeof arg === 'function'; - } - - function isPrimitive(arg) { - return arg === null || - typeof arg === 'boolean' || - typeof arg === 'number' || - typeof arg === 'string' || - typeof arg === 'symbol' || // ES6 symbol - typeof arg === 'undefined'; - } - - function objectToString(o) { - return Object.prototype.toString.call(o); - } - - function hasOwnProperty(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); - } - - return inspect; - })(); - - - var OBJECT_COLUMN_NAME = '(index)'; - var LOG_LEVELS = { - trace: 0, - info: 1, - warn: 2, - error: 3 + var repeat = function(element, n) { + return Array.apply(null, Array(n)).map(function() { return element; }); }; - function setupConsole(global) { - if (!global.nativeLoggingHook) { + function consoleTablePolyfill(rows) { + // convert object -> array + if (!Array.isArray(rows)) { + var data = rows; + rows = []; + for (var key in data) { + if (data.hasOwnProperty(key)) { + var row = data[key]; + row[OBJECT_COLUMN_NAME] = key; + rows.push(row); + } + } + } + if (rows.length === 0) { + global.nativeLoggingHook('', LOG_LEVELS.info); return; } - function getNativeLogFunction(level) { - return function() { - var str; - if (arguments.length === 1 && typeof arguments[0] === 'string') { - str = arguments[0]; - } else { - str = Array.prototype.map.call(arguments, function(arg) { - return inspect(arg, {depth: 10}); - }).join(', '); - } + var columns = Object.keys(rows[0]).sort(); + var stringRows = []; + var columnWidths = []; - var logLevel = level; - if (str.slice(0, 9) === 'Warning: ' && logLevel >= 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.) - logLevel = LOG_LEVELS.warn; - } - global.nativeLoggingHook(str, logLevel); - }; - } - - var repeat = function(element, n) { - return Array.apply(null, Array(n)).map(function() { return element; }); - }; - - function consoleTablePolyfill(rows) { - // convert object -> array - if (!Array.isArray(rows)) { - var data = rows; - rows = []; - for (var key in data) { - if (data.hasOwnProperty(key)) { - var row = data[key]; - row[OBJECT_COLUMN_NAME] = key; - rows.push(row); - } - } + // Convert each cell to a string. Also + // figure out max cell width for each column + columns.forEach(function(k, i) { + columnWidths[i] = k.length; + for (var j = 0; j < rows.length; j++) { + var cellStr = rows[j][k].toString(); + stringRows[j] = stringRows[j] || []; + stringRows[j][i] = cellStr; + columnWidths[i] = Math.max(columnWidths[i], cellStr.length); } - if (rows.length === 0) { - global.nativeLoggingHook('', LOG_LEVELS.info); - return; - } - - var columns = Object.keys(rows[0]).sort(); - var stringRows = []; - var columnWidths = []; - - // Convert each cell to a string. Also - // figure out max cell width for each column - columns.forEach(function(k, i) { - columnWidths[i] = k.length; - for (var j = 0; j < rows.length; j++) { - var cellStr = rows[j][k].toString(); - stringRows[j] = stringRows[j] || []; - stringRows[j][i] = cellStr; - columnWidths[i] = Math.max(columnWidths[i], cellStr.length); - } - }); - - // Join all elements in the row into a single string with | separators - // (appends extra spaces to each cell to make separators | alligned) - var joinRow = function(row, space) { - var cells = row.map(function(cell, i) { - var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join(''); - return cell + extraSpaces; - }); - space = space || ' '; - return cells.join(space + '|' + space); - }; - - var separators = columnWidths.map(function(columnWidth) { - return repeat('-', columnWidth).join(''); - }); - var separatorRow = joinRow(separators, '-'); - var header = joinRow(columns); - var table = [header, separatorRow]; - - for (var i = 0; i < rows.length; i++) { - table.push(joinRow(stringRows[i])); - } - - // Notice extra empty line at the beginning. - // Native logging hook adds "RCTLog >" at the front of every - // logged string, which would shift the header and screw up - // the table - global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.info); - } - - // Preserve the original `console` as `originalConsole` - var originalConsole = global.console; - var descriptor = Object.getOwnPropertyDescriptor(global, 'console'); - if (descriptor) { - Object.defineProperty(global, 'originalConsole', descriptor); - } - - var console = { - error: getNativeLogFunction(LOG_LEVELS.error), - info: getNativeLogFunction(LOG_LEVELS.info), - log: getNativeLogFunction(LOG_LEVELS.info), - warn: getNativeLogFunction(LOG_LEVELS.warn), - trace: getNativeLogFunction(LOG_LEVELS.trace), - table: consoleTablePolyfill - }; - - // don't reassign to the original descriptor. breaks on ios7 - Object.defineProperty(global, 'console', { - value: console, - configurable: descriptor ? descriptor.configurable : true, - enumerable: descriptor ? descriptor.enumerable : true, - writable: descriptor ? descriptor.writable : true, }); - // If available, also call the original `console` method since that is - // sometimes useful. Ex: on OS X, this will let you see rich output in - // the Safari Web Inspector console. - if (__DEV__ && originalConsole) { - Object.keys(console).forEach(methodName => { - var reactNativeMethod = console[methodName]; - if (originalConsole[methodName]) { - console[methodName] = function() { - originalConsole[methodName](...arguments); - reactNativeMethod.apply(console, arguments); - }; - } + // Join all elements in the row into a single string with | separators + // (appends extra spaces to each cell to make separators | alligned) + var joinRow = function(row, space) { + var cells = row.map(function(cell, i) { + var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join(''); + return cell + extraSpaces; }); + space = space || ' '; + return cells.join(space + '|' + space); + }; + + var separators = columnWidths.map(function(columnWidth) { + return repeat('-', columnWidth).join(''); + }); + var separatorRow = joinRow(separators, '-'); + var header = joinRow(columns); + var table = [header, separatorRow]; + + for (var i = 0; i < rows.length; i++) { + table.push(joinRow(stringRows[i])); } + + // Notice extra empty line at the beginning. + // Native logging hook adds "RCTLog >" at the front of every + // logged string, which would shift the header and screw up + // the table + global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.info); } - if (typeof module !== 'undefined') { - module.exports = setupConsole; - } else { - setupConsole(global); + // Preserve the original `console` as `originalConsole` + var originalConsole = global.console; + var descriptor = Object.getOwnPropertyDescriptor(global, 'console'); + if (descriptor) { + Object.defineProperty(global, 'originalConsole', descriptor); } -})(this); + var console = { + error: getNativeLogFunction(LOG_LEVELS.error), + info: getNativeLogFunction(LOG_LEVELS.info), + log: getNativeLogFunction(LOG_LEVELS.info), + warn: getNativeLogFunction(LOG_LEVELS.warn), + trace: getNativeLogFunction(LOG_LEVELS.trace), + table: consoleTablePolyfill + }; + + // don't reassign to the original descriptor. breaks on ios7 + Object.defineProperty(global, 'console', { + value: console, + configurable: descriptor ? descriptor.configurable : true, + enumerable: descriptor ? descriptor.enumerable : true, + writable: descriptor ? descriptor.writable : true, + }); + + // If available, also call the original `console` method since that is + // sometimes useful. Ex: on OS X, this will let you see rich output in + // the Safari Web Inspector console. + if (__DEV__ && originalConsole) { + Object.keys(console).forEach(methodName => { + var reactNativeMethod = console[methodName]; + if (originalConsole[methodName]) { + console[methodName] = function() { + originalConsole[methodName](...arguments); + reactNativeMethod.apply(console, arguments); + }; + } + }); + } +} + +if (typeof module !== 'undefined') { + module.exports = setupConsole; +} else { + setupConsole(global); +} diff --git a/react-packager/src/Resolver/polyfills/error-guard.js b/react-packager/src/Resolver/polyfills/error-guard.js index ac99cadc..8810a4b1 100644 --- a/react-packager/src/Resolver/polyfills/error-guard.js +++ b/react-packager/src/Resolver/polyfills/error-guard.js @@ -13,74 +13,72 @@ * before any of the modules, this ErrorUtils must be defined (and the handler * set) globally before requiring anything. */ -/* eslint global-strict:0 */ -(function(global) { - var ErrorUtils = { - _inGuard: 0, - _globalHandler: null, - setGlobalHandler: function(fun) { - ErrorUtils._globalHandler = fun; - }, - reportError: function(error) { - ErrorUtils._globalHandler && ErrorUtils._globalHandler(error); - }, - reportFatalError: function(error) { - ErrorUtils._globalHandler && ErrorUtils._globalHandler(error, true); - }, - applyWithGuard: function(fun, context, args) { - try { - ErrorUtils._inGuard++; - return fun.apply(context, args); - } catch (e) { - ErrorUtils.reportError(e); - } finally { - ErrorUtils._inGuard--; - } - }, - applyWithGuardIfNeeded: function(fun, context, args) { - if (ErrorUtils.inGuard()) { - return fun.apply(context, args); - } else { - ErrorUtils.applyWithGuard(fun, context, args); - } - }, - inGuard: function() { - return ErrorUtils._inGuard; - }, - guard: function(fun, name, context) { - if (typeof fun !== 'function') { - console.warn('A function must be passed to ErrorUtils.guard, got ', fun); - return null; - } - name = name || fun.name || ''; - function guarded() { - return ( - ErrorUtils.applyWithGuard( - fun, - context || this, - arguments, - null, - name - ) - ); - } - - return guarded; +/* eslint strict:0 */ +var ErrorUtils = { + _inGuard: 0, + _globalHandler: null, + setGlobalHandler: function(fun) { + ErrorUtils._globalHandler = fun; + }, + reportError: function(error) { + ErrorUtils._globalHandler && ErrorUtils._globalHandler(error); + }, + reportFatalError: function(error) { + ErrorUtils._globalHandler && ErrorUtils._globalHandler(error, true); + }, + applyWithGuard: function(fun, context, args) { + try { + ErrorUtils._inGuard++; + return fun.apply(context, args); + } catch (e) { + ErrorUtils.reportError(e); + } finally { + ErrorUtils._inGuard--; + } + }, + applyWithGuardIfNeeded: function(fun, context, args) { + if (ErrorUtils.inGuard()) { + return fun.apply(context, args); + } else { + ErrorUtils.applyWithGuard(fun, context, args); + } + }, + inGuard: function() { + return ErrorUtils._inGuard; + }, + guard: function(fun, name, context) { + if (typeof fun !== 'function') { + console.warn('A function must be passed to ErrorUtils.guard, got ', fun); + return null; + } + name = name || fun.name || ''; + function guarded() { + return ( + ErrorUtils.applyWithGuard( + fun, + context || this, + arguments, + null, + name + ) + ); } - }; - global.ErrorUtils = ErrorUtils; - /** - * This is the error handler that is called when we encounter an exception - * when loading a module. This will report any errors encountered before - * ExceptionsManager is configured. - */ - function setupErrorGuard() { - var onError = function(e) { - global.console.error('Error: ' + e.message + ', stack:\n' + e.stack); - }; - global.ErrorUtils.setGlobalHandler(onError); + return guarded; } +}; +global.ErrorUtils = ErrorUtils; - setupErrorGuard(); -})(this); +/** + * This is the error handler that is called when we encounter an exception + * when loading a module. This will report any errors encountered before + * ExceptionsManager is configured. + */ +function setupErrorGuard() { + var onError = function(e) { + global.console.error('Error: ' + e.message + ', stack:\n' + e.stack); + }; + global.ErrorUtils.setGlobalHandler(onError); +} + +setupErrorGuard(); diff --git a/react-packager/src/Resolver/polyfills/loadBundles.js b/react-packager/src/Resolver/polyfills/loadBundles.js index 4e893eda..7bb9be10 100644 --- a/react-packager/src/Resolver/polyfills/loadBundles.js +++ b/react-packager/src/Resolver/polyfills/loadBundles.js @@ -1,50 +1,34 @@ -/* eslint global-strict:0 */ -(global => { - let loadBundlesOnNative = (bundles) => - new Promise((resolve) => - require('NativeModules').RCTBundlesLoader.loadBundles(bundles, resolve)); +/* eslint strict:0 */ - let requestedBundles = Object.create(null); +let loadBundlesOnNative = (bundles) => + new Promise((resolve) => + require('NativeModules').RCTBundlesLoader.loadBundles(bundles, resolve)); - /** - * Returns a promise that is fulfilled once all the indicated bundles are - * loaded into memory and injected into the JS engine. - * This invokation might need to go through the bridge - * and run native code to load some, if not all, the requested bundles. - * If all the bundles have already been loaded, the promise is resolved - * immediately. Otherwise, we'll determine which bundles still need to get - * loaded considering both, the ones already loaded, and the ones being - * currently asynchronously loaded by other invocations to `__loadBundles`, - * and return a promise that will get fulfilled once all these are finally - * loaded. - * - * Note this function should only be invoked by generated code. - */ - global.__loadBundles = function(bundles) { - // split bundles by whether they've already been requested or not - const bundlesToRequest = bundles.filter(b => !requestedBundles[b]); - const bundlesAlreadyRequested = bundles.filter(b => !!requestedBundles[b]); +let requestedBundles = Object.create(null); - // keep a reference to the promise loading each bundle - if (bundlesToRequest.length > 0) { - const nativePromise = loadBundlesOnNative(bundlesToRequest); - bundlesToRequest.forEach(b => requestedBundles[b] = nativePromise); - } +/** + * Returns a promise that is fulfilled once all the indicated bundles are + * loaded into memory and injected into the JS engine. + * This invokation might need to go through the bridge + * and run native code to load some, if not all, the requested bundles. + * If all the bundles have already been loaded, the promise is resolved + * immediately. Otherwise, we'll determine which bundles still need to get + * loaded considering both, the ones already loaded, and the ones being + * currently asynchronously loaded by other invocations to `__loadBundles`, + * and return a promise that will get fulfilled once all these are finally + * loaded. + * + * Note this function should only be invoked by generated code. + */ +global.__loadBundles = function(bundles) { + // split bundles by whether they've already been requested or not + const bundlesToRequest = bundles.filter(b => !requestedBundles[b]); - return Promise.all(bundles.map(bundle => requestedBundles[bundle])); - }; -})(global); + // keep a reference to the promise loading each bundle + if (bundlesToRequest.length > 0) { + const nativePromise = loadBundlesOnNative(bundlesToRequest); + bundlesToRequest.forEach(b => requestedBundles[b] = nativePromise); + } - -// turns a callback async based function into a promised based one -function promisify(fn) { - return function() { - var self = this; - var args = Array.prototype.slice.call(arguments); - - return new Promise((resolve, reject) => { - args.push(resolve); - fn.apply(self, args); - }); - }; -} + return Promise.all(bundles.map(bundle => requestedBundles[bundle])); +}; diff --git a/react-packager/src/Resolver/polyfills/polyfills.js b/react-packager/src/Resolver/polyfills/polyfills.js index 9698b491..56523342 100644 --- a/react-packager/src/Resolver/polyfills/polyfills.js +++ b/react-packager/src/Resolver/polyfills/polyfills.js @@ -15,7 +15,7 @@ // WARNING: This is an optimized version that fails on hasOwnProperty checks // and non objects. It's not spec-compliant. It's a perf optimization. -/* eslint global-strict:0 */ +/* eslint strict:0 */ Object.assign = function(target, sources) { if (__DEV__) { if (target == null) { diff --git a/react-packager/src/Resolver/polyfills/prelude.js b/react-packager/src/Resolver/polyfills/prelude.js index a1004b7d..02386ae1 100644 --- a/react-packager/src/Resolver/polyfills/prelude.js +++ b/react-packager/src/Resolver/polyfills/prelude.js @@ -1,5 +1,4 @@ -/* eslint global-strict:0 */ -__DEV__ = false; +/* eslint strict:0 */ +global.__DEV__ = false; -/* global __BUNDLE_START_TIME__:true */ -__BUNDLE_START_TIME__ = Date.now(); +global.__BUNDLE_START_TIME__ = Date.now(); diff --git a/react-packager/src/Resolver/polyfills/prelude_dev.js b/react-packager/src/Resolver/polyfills/prelude_dev.js index 14b97faa..99b9de2c 100644 --- a/react-packager/src/Resolver/polyfills/prelude_dev.js +++ b/react-packager/src/Resolver/polyfills/prelude_dev.js @@ -1,5 +1,4 @@ -/* eslint global-strict:0 */ -__DEV__ = true; +/* eslint strict:0 */ +global.__DEV__ = true; -/* global __BUNDLE_START_TIME__:true */ -__BUNDLE_START_TIME__ = Date.now(); +global.__BUNDLE_START_TIME__ = Date.now(); diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js index 1ea71705..c6c7cbc7 100644 --- a/react-packager/src/Resolver/polyfills/require-unbundle.js +++ b/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -1,73 +1,70 @@ 'use strict'; -((global) => { - const {ErrorUtils, __nativeRequire} = global; - global.require = require; - global.__d = define; +const {ErrorUtils, __nativeRequire} = global; +global.require = require; +global.__d = define; - const modules = Object.create(null); +const modules = Object.create(null); - const loadModule = ErrorUtils ? - guardedLoadModule : loadModuleImplementation; +const loadModule = ErrorUtils ? + guardedLoadModule : loadModuleImplementation; - function define(moduleId, factory) { - modules[moduleId] = { - factory, - hasError: false, - exports: undefined, - }; +function define(moduleId, factory) { + modules[moduleId] = { + factory, + hasError: false, + exports: undefined, + }; +} + +function require(moduleId) { + const module = modules[moduleId]; + return module && module.exports || loadModule(moduleId, module); +} + +function guardedLoadModule(moduleId, module) { + try { + return loadModuleImplementation(moduleId, module); + } catch (e) { + ErrorUtils.reportFatalError(e); + } +} + +function loadModuleImplementation(moduleId, module) { + if (!module) { + __nativeRequire(moduleId); + module = modules[moduleId]; } - function require(moduleId) { - const module = modules[moduleId]; - return module && module.exports || loadModule(moduleId, module); + if (!module) { + throw unknownModuleError(moduleId); } - function guardedLoadModule(moduleId, module) { - try { - return loadModuleImplementation(moduleId, module); - } catch (e) { - ErrorUtils.reportFatalError(e); - } + if (module.hasError) { + throw moduleThrewError(moduleId); } - function loadModuleImplementation(moduleId, module) { - if (!module) { - __nativeRequire(moduleId); - module = modules[moduleId]; - } - - if (!module) { - throw unknownModuleError(moduleId); - } - - if (module.hasError) { - throw moduleThrewError(moduleId); - } - - const exports = module.exports = {}; - const {factory} = module; - try { - const moduleObject = {exports}; - factory(global, require, moduleObject, exports); - return (module.exports = moduleObject.exports); - } catch(e) { - module.hasError = true; - module.exports = undefined; - } + const exports = module.exports = {}; + const {factory} = module; + try { + const moduleObject = {exports}; + factory(global, require, moduleObject, exports); + return (module.exports = moduleObject.exports); + } catch (e) { + module.hasError = true; + module.exports = undefined; } +} - function unknownModuleError(id) { - let message = 'Requiring unknown module "' + id + '".'; - if (__DEV__) { - message += - 'If you are sure the module is there, try restarting the packager.'; - } - return Error(message); +function unknownModuleError(id) { + let message = 'Requiring unknown module "' + id + '".'; + if (__DEV__) { + message += + 'If you are sure the module is there, try restarting the packager.'; } + return Error(message); +} - function moduleThrewError(id) { - return Error('Requiring module "' + id + '", which threw an exception.'); - } - -})(this); +function moduleThrewError(id) { + return Error('Requiring module "' + id + '", which threw an exception.'); +} diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 36c2b6b9..a5a7a55f 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -1,130 +1,128 @@ /* eslint strict:0 */ -(function(global) { - var modules = Object.create(null); - var inGuard = false; +var modules = Object.create(null); +var inGuard = false; - function define(id, factory) { - modules[id] = { - factory, - module: {exports: {}}, - isInitialized: false, - hasError: false, - }; +function define(id, factory) { + modules[id] = { + factory, + module: {exports: {}}, + isInitialized: false, + hasError: false, + }; - if (__DEV__) { // HMR - Object.assign(modules[id].module, { - hot: { - acceptCallback: null, - accept: function(callback) { - modules[id].module.hot.acceptCallback = callback; - } + if (__DEV__) { // HMR + Object.assign(modules[id].module, { + hot: { + acceptCallback: null, + accept: function(callback) { + modules[id].module.hot.acceptCallback = callback; } - }); - } - } - - function require(id) { - var mod = modules[id]; - if (mod && mod.isInitialized) { - return mod.module.exports; - } - - return requireImpl(id); - } - - function requireImpl(id) { - if (global.ErrorUtils && !inGuard) { - inGuard = true; - var returnValue; - try { - returnValue = requireImpl.apply(this, arguments); - } catch (e) { - global.ErrorUtils.reportFatalError(e); } - inGuard = false; - return returnValue; - } - - var mod = modules[id]; - if (!mod) { - var msg = 'Requiring unknown module "' + id + '"'; - if (__DEV__) { - msg += '. If you are sure the module is there, try restarting the packager.'; - } - throw new Error(msg); - } - - if (mod.hasError) { - throw new Error( - 'Requiring module "' + id + '" which threw an exception' - ); - } - - try { - // We must optimistically mark mod as initialized before running the factory to keep any - // require cycles inside the factory from causing an infinite require loop. - mod.isInitialized = true; - - __DEV__ && Systrace().beginEvent('JS_require_' + id); - - // keep args in sync with with defineModuleCode in - // packager/react-packager/src/Resolver/index.js - mod.factory.call(global, global, require, mod.module, mod.module.exports); - - __DEV__ && Systrace().endEvent(); - } catch (e) { - mod.hasError = true; - mod.isInitialized = false; - throw e; - } + }); + } +} +function require(id) { + var mod = modules[id]; + if (mod && mod.isInitialized) { return mod.module.exports; } - const Systrace = __DEV__ && (() => { - var _Systrace; + return requireImpl(id); +} + +function requireImpl(id) { + if (global.ErrorUtils && !inGuard) { + inGuard = true; + var returnValue; try { - _Systrace = require('Systrace'); - } catch(e) {} + returnValue = requireImpl.apply(this, arguments); + } catch (e) { + global.ErrorUtils.reportFatalError(e); + } + inGuard = false; + return returnValue; + } - return _Systrace && _Systrace.beginEvent ? - _Systrace : { beginEvent: () => {}, endEvent: () => {} }; - }); + var mod = modules[id]; + if (!mod) { + var msg = 'Requiring unknown module "' + id + '"'; + if (__DEV__) { + msg += '. If you are sure the module is there, try restarting the packager.'; + } + throw new Error(msg); + } - global.__d = define; - global.require = require; + if (mod.hasError) { + throw new Error( + 'Requiring module "' + id + '" which threw an exception' + ); + } - if (__DEV__) { // HMR - function accept(id, factory) { - var mod = modules[id]; + try { + // We must optimistically mark mod as initialized before running the factory to keep any + // require cycles inside the factory from causing an infinite require loop. + mod.isInitialized = true; - if (!mod) { - define(id, factory); - return; // new modules don't need to be accepted - } + __DEV__ && Systrace().beginEvent('JS_require_' + id); - if (!mod.module.hot) { - console.warn( - 'Cannot accept module because Hot Module Replacement ' + - 'API was not installed.' - ); - return; - } + // keep args in sync with with defineModuleCode in + // packager/react-packager/src/Resolver/index.js + mod.factory.call(global, global, require, mod.module, mod.module.exports); - if (mod.module.hot.acceptCallback) { - mod.factory = factory; - mod.isInitialized = false; - require(id); + __DEV__ && Systrace().endEvent(); + } catch (e) { + mod.hasError = true; + mod.isInitialized = false; + throw e; + } - mod.module.hot.acceptCallback(); - } else { - console.warn( - '[HMR] Module `' + id + '` can\'t be hot reloaded because it ' + - 'doesn\'t provide accept callback hook. Reload the app to get the updates.' - ); - } + return mod.module.exports; +} + +const Systrace = __DEV__ && (() => { + var _Systrace; + try { + _Systrace = require('Systrace'); + } catch (e) {} + + return _Systrace && _Systrace.beginEvent ? + _Systrace : { beginEvent: () => {}, endEvent: () => {} }; +}); + +global.__d = define; +global.require = require; + +if (__DEV__) { // HMR + function accept(id, factory) { + var mod = modules[id]; + + if (!mod) { + define(id, factory); + return; // new modules don't need to be accepted } - global.__accept = accept; + if (!mod.module.hot) { + console.warn( + 'Cannot accept module because Hot Module Replacement ' + + 'API was not installed.' + ); + return; + } + + if (mod.module.hot.acceptCallback) { + mod.factory = factory; + mod.isInitialized = false; + require(id); + + mod.module.hot.acceptCallback(); + } else { + console.warn( + '[HMR] Module `' + id + '` can\'t be hot reloaded because it ' + + 'doesn\'t provide accept callback hook. Reload the app to get the updates.' + ); + } } -})(this); + + global.__accept = accept; +} diff --git a/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js b/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js index 7525fd0f..4d7d142c 100644 --- a/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js +++ b/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js @@ -33,7 +33,7 @@ const trim = (str) => describe('dead-module-elimination', () => { it('should inline __DEV__', () => { compare( - `__DEV__ = false; + `global.__DEV__ = false; var foo = __DEV__;`, `var foo = false;` ); @@ -41,7 +41,7 @@ describe('dead-module-elimination', () => { it('should accept unary operators with literals', () => { compare( - `__DEV__ = !1; + `global.__DEV__ = !1; var foo = __DEV__;`, `var foo = false;` ); @@ -49,7 +49,7 @@ describe('dead-module-elimination', () => { it('should kill dead branches', () => { compare( - `__DEV__ = false; + `global.__DEV__ = false; if (__DEV__) { doSomething(); }`, @@ -74,7 +74,7 @@ describe('dead-module-elimination', () => { it('should kill modules referenced only from dead branches', () => { compare( - `__DEV__ = false; + `global.__DEV__ = false; __d('bar', function() {}); if (__DEV__) { require('bar'); }`, `` @@ -83,7 +83,7 @@ describe('dead-module-elimination', () => { it('should replace logical expressions with the result', () => { compare( - `__DEV__ = false; + `global.__DEV__ = false; __d('bar', function() {}); __DEV__ && require('bar');`, `false;` @@ -92,7 +92,7 @@ describe('dead-module-elimination', () => { it('should keep if result branch', () => { compare( - `__DEV__ = false; + `global.__DEV__ = false; __d('bar', function() {}); if (__DEV__) { killWithFire(); @@ -106,7 +106,7 @@ describe('dead-module-elimination', () => { it('should replace falsy ternaries with alternate expression', () => { compare( - `__DEV__ = false; + `global.__DEV__ = false; __DEV__ ? foo() : bar(); `, `bar();` diff --git a/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js b/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js index 39eeb0a6..1d6c8f34 100644 --- a/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js +++ b/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js @@ -60,7 +60,12 @@ module.exports = function () { AssignmentExpression(path) { const { node } = path; - if (node.left.type === 'Identifier' && node.left.name === '__DEV__') { + if ( + node.left.type === 'MemberExpression' && + node.left.object.name === 'global' && + node.left.property.type === 'Identifier' && + node.left.property.name === '__DEV__' + ) { var value; if (node.right.type === 'BooleanLiteral') { value = node.right.value; @@ -73,7 +78,7 @@ module.exports = function () { } else { return; } - globals[node.left.name] = value; + globals[node.left.property.name] = value; // workaround babel/source map bug - the minifier should strip it path.replaceWith(t.booleanLiteral(value)); From 6adf3a4f61e83b34ea1656f524d177c068dce777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 21 Jan 2016 08:15:19 -0800 Subject: [PATCH 535/936] Improve error message Summary: There's a long standing issue on open source (https://github.com/facebook/react-native/issues/4968). Until someone gets some free bandwidth to fix it lets at the very least improve the error message to guide users to the issue and suggest workarounds. public Reviewed By: mkonicek Differential Revision: D2849051 fb-gh-sync-id: ef7913442ceabcab2076141bd13ab1ceeb529759 --- .../DependencyGraph/ResolutionRequest.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 50a59025..65742f1a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -372,7 +372,13 @@ class ResolutionRequest { throw new UnableToResolveError( fromModule, toModule, - `Invalid directory ${potentialDirPath}`, +`Invalid directory ${potentialDirPath} + +This might related to https://github.com/facebook/react-native/issues/4968 +To resolve try the following: + 1. Clear watchman watches: \`watchman watch-del-all\`. + 2. Delete the \`node_modules\` folder: \`rm -rf node_modules && npm install\`. + 3. Reset packager cache: \`rm -fr $TMPDIR/react-*\` or \`npm start -- --reset-cache\`.`, ); } From da3838f6ca56d798d6ac2afc886148c26b358eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 21 Jan 2016 09:09:52 -0800 Subject: [PATCH 536/936] Unit test `worker.js` Summary: public We had a bug a few weeks ago which caused transform errors not to be shown properly. The problem was on this piece of code, so lets unit test it. Reviewed By: davidaurelio Differential Revision: D2849990 fb-gh-sync-id: 9f6bb8a8b7d59bbaa3577c29cee37fb23a50d66f --- .../JSTransformer/__tests__/worker-test.js | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 react-packager/src/JSTransformer/__tests__/worker-test.js diff --git a/react-packager/src/JSTransformer/__tests__/worker-test.js b/react-packager/src/JSTransformer/__tests__/worker-test.js new file mode 100644 index 00000000..fdb67d9d --- /dev/null +++ b/react-packager/src/JSTransformer/__tests__/worker-test.js @@ -0,0 +1,109 @@ +/** + * 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.autoMockOff(); + +jest.mock('babel-core'); + +const worker = require('../worker'); +const babel = require('babel-core'); + +const code = 'code'; + +describe('Resolver', function() { + beforeEach(() => { + babel.transform.mockImpl((source, options) => source); + }); + + describe('when no external transform is provided', () => { + it('should invoke internal transform if available', () => { + transform({ + sourceCode: 'code', + filename: 'test', + options: options({opts: {}, internalTransformsEnabled: true}), + }); + expect(babel.transform.mock.calls.length).toBe(1); + }); + + it('should not invoke internal transform if unavailable', () => { + transform({ + sourceCode: 'code', + filename: 'test', + options: options({opts: {}, internalTransformsEnabled: false}), + }); + expect(babel.transform.mock.calls.length).toBe(0); + }); + }); + + describe('when external transform is provided', () => { + it('should invoke both transformers if internal is available', () => { + transform({ + sourceCode: code, + filename: 'test', + options: options({ + opts: { + externalTransformModulePath: require.resolve( + '../../../../transformer.js' + ), + }, + internalTransformsEnabled: true, + }), + }); + expect(babel.transform.mock.calls.length).toBe(2); + }); + + it('should invoke only external transformer if internal is not available', () => { + transform({ + sourceCode: 'code', + filename: 'test', + options: options({ + opts: { + externalTransformModulePath: require.resolve( + '../../../../transformer.js' + ), + }, + internalTransformsEnabled: false, + }), + }); + expect(babel.transform.mock.calls.length).toBe(1); + }); + + it('should pipe errors through transform pipeline', () => { + const error = new Error('transform error'); + babel.transform.mockImpl((source, options) => { + throw error; + }); + + const callback = transform({ + sourceCode: 'code', + filename: 'test', + options: options({ + opts: { + externalTransformModulePath: require.resolve( + '../../../../transformer.js' + ), + }, + internalTransformsEnabled: true, + }), + }); + expect(callback.mock.calls[0][0]).toBe(error); + }); + }); +}); + +function transform(data) { + const callback = jest.genMockFunction(); + worker(data, callback); + return callback; +} + +function options({opts, internalTransformsEnabled}) { + return Object.assign(opts, {hot: internalTransformsEnabled}); +} From 18e5e50e117034d7e847faeddc7716d760c76233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 21 Jan 2016 09:51:18 -0800 Subject: [PATCH 537/936] Unit test reloading when hot loading is enabled Reviewed By: sahrens Differential Revision: D2824276 fb-gh-sync-id: a81acd20eb5b7d3da61becac00e4bdbea6b61a2f --- .../src/Server/__tests__/Server-test.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 107f2750..a21462ca 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -165,6 +165,18 @@ describe('processRequest', () => { }); it('rebuilds the bundles that contain a file when that file is changed', () => { + testChangingFileWith(() => new Server(options)); + }); + + it('rebuilds the bundles that contain a file when that file is changed, even when hot loading is enabled', () => { + testChangingFileWith(() => { + const server = new Server(options); + server.setHMRFileChangeListener(() => Promise.resolve()); + return server; + }); + }); + + function testChangingFileWith(createServer) { const bundleFunc = jest.genMockFunction(); bundleFunc .mockReturnValueOnce( @@ -182,7 +194,7 @@ describe('processRequest', () => { Bundler.prototype.bundle = bundleFunc; - server = new Server(options); + server = createServer(); requestHandler = server.processRequest.bind(server); @@ -205,7 +217,7 @@ describe('processRequest', () => { expect(response).toEqual('this is the rebuilt source') ); jest.runAllTicks(); - }); + } }); describe('/onchange endpoint', () => { From 6ec2225fc9783f6d511e0d7a40b3f383daf2e250 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 21 Jan 2016 10:36:50 -0800 Subject: [PATCH 538/936] Use numeric identifiers when building a bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: public Since the combination of node and haste modules (and modules that can be required as both node and haste module) can lead to situations where it’s impossible to decide an unambiguous module identifier, this diff switches all module ids to integers. Each integer maps to an absolute path to a JS file on disk. We also had a problem, where haste modules outside and inside node_modules could end up with the same module identifier. This problem has not manifested yet, because the last definition of a module wins. It becomes a problem when writing file-based unbundle modules to disk: the same file might be written to concurrently, leading to invalid code. Using indexed modules will also help indexed file unbundles, as we can encode module IDs as integers rather than scanning string IDs. Reviewed By: martinbigio Differential Revision: D2842418 fb-gh-sync-id: 97addd28e964ac5f2b5081dcd3f36124d2864df8 --- react-packager/src/Bundler/Bundle.js | 6 +- .../src/Bundler/__tests__/Bundler-test.js | 28 +- react-packager/src/Bundler/index.js | 50 +- .../BundlesLayoutIntegration-test.js | 1 + .../src/DependencyResolver/AssetModule.js | 8 +- .../DependencyGraph/ResolutionResponse.js | 5 + .../__tests__/DependencyGraph-test.js | 18 +- .../DependencyResolver/lib/replacePatterns.js | 8 +- .../src/Resolver/__tests__/Resolver-test.js | 442 ++---------------- react-packager/src/Resolver/index.js | 35 +- .../src/Resolver/polyfills/require.js | 27 +- 11 files changed, 172 insertions(+), 456 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 04a2e1ae..e457e203 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -40,10 +40,10 @@ class Bundle extends BundleBase { response, module, transformed.code - ).then(({code, name}) => { + ).then(({code, id}) => { const moduleTransport = new ModuleTransport({ code, - name, + name: id, map: transformed.map, sourceCode: transformed.sourceCode, sourcePath: transformed.sourcePath, @@ -75,7 +75,7 @@ class Bundle extends BundleBase { } _addRequireCall(moduleId) { - const code = ';require("' + moduleId + '");'; + const code = `;require(${JSON.stringify(moduleId)});`; const name = 'require-' + moduleId; super.addModule(new ModuleTransport({ name, diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index 467cf90d..a85695be 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -10,16 +10,23 @@ jest .setMock('worker-farm', () => () => undefined) + .dontMock('absolute-path') .dontMock('underscore') .dontMock('../../lib/ModuleTransport') + .dontMock('../../DependencyResolver/AssetModule') + .dontMock('../../DependencyResolver/Module') + .dontMock('../../DependencyResolver/lib/getAssetDataFromName') .setMock('uglify-js') .dontMock('../'); jest.mock('fs'); +var AssetModule = require('../../DependencyResolver/AssetModule'); var Bundler = require('../'); var JSTransformer = require('../../JSTransformer'); +var Module = require('../../DependencyResolver/Module'); var Resolver = require('../../Resolver'); + var sizeOf = require('image-size'); var fs = require('fs'); @@ -34,6 +41,14 @@ describe('Bundler', function() { isJSON, resolution, }) { + if (isAsset) { + const module = new AssetModule({ + file: path, + cache: {get: () => Promise.resolve(path)} + }); + module.getName = () => Promise.resolve(id); + return module; + } return { path, resolution, @@ -51,6 +66,8 @@ describe('Bundler', function() { var bundler; var assetServer; var modules; + const width = 50; + const height = 100; beforeEach(function() { getDependencies = jest.genMockFn(); @@ -108,10 +125,12 @@ describe('Bundler', function() { }), ]; + const mainModule = new Module({file: '/root/foo'}); getDependencies.mockImpl(function() { return Promise.resolve({ mainModuleId: 'foo', - dependencies: modules + dependencies: modules, + getMainModule: () => mainModule, }); }); @@ -132,12 +151,13 @@ describe('Bundler', function() { wrapModule.mockImpl(function(response, module, code) { return module.getName().then(name => ({ name, + id: name, code: 'lol ' + code + ' lol' })); }); sizeOf.mockImpl(function(path, cb) { - cb(null, { width: 50, height: 100 }); + cb(null, { width, height }); }); }); @@ -187,8 +207,8 @@ describe('Bundler', function() { __packager_asset: true, fileSystemLocation: '/root/img', httpServerLocation: '/assets/img', - width: 25, - height: 50, + width, + height, scales: [1, 2, 3], files: [ '/root/img/img.png', diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 1cc28f23..9b7bb1fa 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -102,6 +102,8 @@ class Bundler { mtime = ''; } + this._getModuleId = createModuleIdGetter(); + this._cache = new Cache({ resetCache: opts.resetCache, cacheKey: [ @@ -122,6 +124,7 @@ class Bundler { fileWatcher: opts.fileWatcher, assetExts: opts.assetExts, cache: this._cache, + getModuleId: this._getModuleId, }); this._bundlesLayout = new BundlesLayout({ @@ -187,9 +190,19 @@ class Bundler { const findEventId = Activity.startEvent('find dependencies'); let transformEventId; + if (isDev) { + // `require` calls int the require polyfill itself are not analyzed and + // replaced so that they use numeric module IDs. Therefore, we include + // the Systrace module before any other module, and it will set itself + // as property on the require function. + // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) + runBeforeMainModule = ['Systrace'].concat(runBeforeMainModule); + } + + const modulesByName = Object.create(null); return this.getDependencies(entryFile, isDev, platform).then((response) => { Activity.endEvent(findEventId); - bundle.setMainModuleId(response.mainModuleId); + bundle.setMainModuleId(this._getModuleId(response.getMainModule())); transformEventId = Activity.startEvent('transform'); const moduleSystemDeps = includeSystemDependencies @@ -225,6 +238,11 @@ class Bundler { platform, hot, ).then(transformed => { + return module.getName().then(name => { + modulesByName[name] = module; + return transformed; + }); + }).then(transformed => { if (bar) { bar.tick(); } @@ -248,7 +266,11 @@ class Bundler { )); }).then(() => { Activity.endEvent(transformEventId); - bundle.finalize({runBeforeMainModule, runMainModule}); + const runBeforeIds = runBeforeMainModule + .map(name => modulesByName[name]) + .filter(Boolean) + .map(this._getModuleId, this); + bundle.finalize({runBeforeMainModule: runBeforeIds, runMainModule}); return bundle; }); } @@ -462,8 +484,7 @@ class Bundler { type: assetData.type, }; - const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);'; - const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(asset)); + const code = module.getCode(asset); return {asset, code}; }); @@ -522,12 +543,21 @@ function verifyRootExists(root) { assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); } -class DummyCache { - get(filepath, field, loaderCb) { - return loaderCb(); - } - end(){} - invalidate(filepath){} +function createModuleIdGetter() { + const fileToIdMap = Object.create(null); + let nextId = 0; + return ( + ({path}) => { + if (!(path in fileToIdMap)) { + // can't be a number for now, since we also replace in import / export + // we can change that when we eventually change to analyzing dependencies + // on transformed modules + fileToIdMap[path] = String(nextId++); + } + return fileToIdMap[path]; + } + ); } + module.exports = Bundler; diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index fe4481a0..b18b424f 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -55,6 +55,7 @@ describe('BundlesLayout', () => { cache: new Cache(), assetExts: ['js', 'png'], assetRoots: ['/root'], + getModuleId: () => {}, }); return new BundlesLayout({ diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js index f5555fac..68a92ee2 100644 --- a/react-packager/src/DependencyResolver/AssetModule.js +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -18,13 +18,19 @@ class AssetModule extends Module { } getDependencies() { - return Promise.resolve([]); + return Promise.resolve(['AssetRegistry']); } getAsyncDependencies() { return Promise.resolve([]); } + getCode(assetData) { + return `module.exports = require('AssetRegistry').registerAsset(${ + JSON.stringify(assetData) + });`; + } + read() { return Promise.resolve({}); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js index 7fbafde8..3c1656c5 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js @@ -39,6 +39,11 @@ class ResolutionResponse { }); } + getMainModule() { + this._assertFinalized(); + return this._mainModule; + } + pushDependency(module) { this._assertNotFinalized(); if (this.dependencies.length === 0) { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index b1126a4c..a858f1d2 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -374,7 +374,7 @@ describe('DependencyGraph', function() { { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a.png', - dependencies: [], + dependencies: ['AssetRegistry'], isAsset: true, resolution: 1, isAsset_DEPRECATED: false, @@ -434,7 +434,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a@1.5x.png', resolution: 1.5, - dependencies: [], + dependencies: ['AssetRegistry'], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -444,7 +444,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.png', resolution: 0.7, - dependencies: [], + dependencies: ['AssetRegistry'], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -454,7 +454,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.png', resolution: 1, - dependencies: [], + dependencies: ['AssetRegistry'], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -514,7 +514,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a@1.5x.ios.png', resolution: 1.5, - dependencies: [], + dependencies: ['AssetRegistry'], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -524,7 +524,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.ios.png', resolution: 0.7, - dependencies: [], + dependencies: ['AssetRegistry'], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -534,7 +534,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.ios.png', resolution: 1, - dependencies: [], + dependencies: ['AssetRegistry'], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -585,7 +585,7 @@ describe('DependencyGraph', function() { { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a.png', - dependencies: [], + dependencies: ['AssetRegistry'], isAsset: true, resolution: 1, isAsset_DEPRECATED: false, @@ -3367,7 +3367,7 @@ describe('DependencyGraph', function() { { id: 'aPackage/foo.png', path: '/root/foo.png', - dependencies: [], + dependencies: ['AssetRegistry'], isAsset: true, resolution: 1, isAsset_DEPRECATED: false, diff --git a/react-packager/src/DependencyResolver/lib/replacePatterns.js b/react-packager/src/DependencyResolver/lib/replacePatterns.js index a4e563d2..3bd6ef45 100644 --- a/react-packager/src/DependencyResolver/lib/replacePatterns.js +++ b/react-packager/src/DependencyResolver/lib/replacePatterns.js @@ -9,7 +9,7 @@ 'use strict'; -exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; -exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; -exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; -exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; +exports.IMPORT_RE = /(\bimport\s*(?:[\s{][^'"]+[\s}]from\s*)??)(['"])([^'"]+)\2()/g; +exports.EXPORT_RE = /(\bexport\s*(?:[\s{][^'"]+[\s}]from\s*)??)(['"])([^'"]+)\2()/g; +exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)\2(\s*?\))/g; +exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)\2(\s*?\))/g; diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 825399b4..6093cf42 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -35,6 +35,12 @@ describe('Resolver', function() { }); }); + const modulesWithIds = []; + function getModuleId(module) { + const index = modulesWithIds.indexOf(module); + return String(index !== -1 ? index + 1 : modulesWithIds.push(module)); + } + class ResolutionResponseMock { constructor({dependencies, mainModuleId, asyncDependencies}) { this.dependencies = dependencies; @@ -73,6 +79,7 @@ describe('Resolver', function() { var depResolver = new Resolver({ projectRoot: '/root', + getModuleId, }); DependencyGraph.prototype.getDependencies.mockImpl(function() { @@ -160,6 +167,7 @@ describe('Resolver', function() { var depResolver = new Resolver({ projectRoot: '/root', + getModuleId, }); DependencyGraph.prototype.getDependencies.mockImpl(function() { @@ -187,6 +195,7 @@ describe('Resolver', function() { var depResolver = new Resolver({ projectRoot: '/root', polyfillModuleNames: ['some module'], + getModuleId, }); DependencyGraph.prototype.getDependencies.mockImpl(function() { @@ -223,12 +232,13 @@ describe('Resolver', function() { pit('should resolve modules', function() { var depResolver = new Resolver({ projectRoot: '/root', + getModuleId, }); - var dependencies = ['x', 'y', 'z', 'a', 'b']; + const magicJoiner = '\n\n\n'; /*eslint-disable */ - var code = [ + const testCases = [ // single line import "import'x';", "import 'x';", @@ -303,6 +313,7 @@ describe('Resolver', function() { 'import * as All from "x";', 'import { } from "x";', 'import { Foo } from "x";', + 'import{Foo}from"x";', 'import { Foo, } from "x";', 'import { Foo as Bar } from "x";', 'import { Foo as Bar, } from "x";', @@ -428,6 +439,7 @@ describe('Resolver', function() { "export { } from 'x';", "export {Foo} from 'x';", "export { Foo } from 'x';", + "export{Foo}from'x';", "export { Foo, } from 'x';", "export {Foo as Bar} from 'x';", "export { Foo as Bar } from 'x';", @@ -613,8 +625,9 @@ describe('Resolver', function() { 'require( \'z\' )', 'require( "a")', 'require("b" )', - ].join('\n'); + ] /*eslint-disable */ + const code = testCases.join(magicJoiner); const module = createModule('test module', ['x', 'y']); @@ -624,11 +637,21 @@ describe('Resolver', function() { asyncDependencies: [], }); - resolutionResponse.getResolvedDependencyPairs = (module) => { - return [ - ['x', createModule('changed')], - ['y', createModule('Y')], - ]; + const pairs = [ + ['x', createModule('changed')], + ['y', createModule('Y')], + ]; + resolutionResponse.getResolvedDependencyPairs = () => pairs; + + function makeExpected(code) { + return pairs + .reduce((code, [id, module]) => + code.replace( + RegExp(`(['"])${id}\\1`), + (_, quot) => `${quot}${getModuleId(module)}${quot} /* ${id} */` + ), + code + ); } return depResolver.wrapModule( @@ -637,395 +660,20 @@ describe('Resolver', function() { code ).then(processedCode => { expect(processedCode.name).toEqual('test module'); - expect(processedCode.code).toEqual([ - '__d(\'test module\',function(global, require,' + - ' module, exports) { ' + - // single line import - "import'x';", - "import 'changed';", - "import 'changed' ;", - "import Default from 'changed';", - "import * as All from 'changed';", - "import {} from 'changed';", - "import { } from 'changed';", - "import {Foo} from 'changed';", - "import { Foo } from 'changed';", - "import { Foo, } from 'changed';", - "import {Foo as Bar} from 'changed';", - "import { Foo as Bar } from 'changed';", - "import { Foo as Bar, } from 'changed';", - "import { Foo, Bar } from 'changed';", - "import { Foo, Bar, } from 'changed';", - "import { Foo as Bar, Baz } from 'changed';", - "import { Foo as Bar, Baz, } from 'changed';", - "import { Foo, Bar as Baz } from 'changed';", - "import { Foo, Bar as Baz, } from 'changed';", - "import { Foo as Bar, Baz as Qux } from 'changed';", - "import { Foo as Bar, Baz as Qux, } from 'changed';", - "import { Foo, Bar, Baz } from 'changed';", - "import { Foo, Bar, Baz, } from 'changed';", - "import { Foo as Bar, Baz, Qux } from 'changed';", - "import { Foo as Bar, Baz, Qux, } from 'changed';", - "import { Foo, Bar as Baz, Qux } from 'changed';", - "import { Foo, Bar as Baz, Qux, } from 'changed';", - "import { Foo, Bar, Baz as Qux } from 'changed';", - "import { Foo, Bar, Baz as Qux, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", - "import Default, * as All from 'changed';", - "import Default, { } from 'changed';", - "import Default, { Foo } from 'changed';", - "import Default, { Foo, } from 'changed';", - "import Default, { Foo as Bar } from 'changed';", - "import Default, { Foo as Bar, } from 'changed';", - "import Default, { Foo, Bar } from 'changed';", - "import Default, { Foo, Bar, } from 'changed';", - "import Default, { Foo as Bar, Baz } from 'changed';", - "import Default, { Foo as Bar, Baz, } from 'changed';", - "import Default, { Foo, Bar as Baz } from 'changed';", - "import Default, { Foo, Bar as Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz } from 'changed';", - "import Default, { Foo, Bar, Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", - "import Default , { } from 'changed';", - 'import "changed";', - 'import Default from "changed";', - 'import * as All from "changed";', - 'import { } from "changed";', - 'import { Foo } from "changed";', - 'import { Foo, } from "changed";', - 'import { Foo as Bar } from "changed";', - 'import { Foo as Bar, } from "changed";', - 'import { Foo, Bar } from "changed";', - 'import { Foo, Bar, } from "changed";', - 'import { Foo as Bar, Baz } from "changed";', - 'import { Foo as Bar, Baz, } from "changed";', - 'import { Foo, Bar as Baz } from "changed";', - 'import { Foo, Bar as Baz, } from "changed";', - 'import { Foo as Bar, Baz as Qux } from "changed";', - 'import { Foo as Bar, Baz as Qux, } from "changed";', - 'import { Foo, Bar, Baz } from "changed";', - 'import { Foo, Bar, Baz, } from "changed";', - 'import { Foo as Bar, Baz, Qux } from "changed";', - 'import { Foo as Bar, Baz, Qux, } from "changed";', - 'import { Foo, Bar as Baz, Qux } from "changed";', - 'import { Foo, Bar as Baz, Qux, } from "changed";', - 'import { Foo, Bar, Baz as Qux } from "changed";', - 'import { Foo, Bar, Baz as Qux, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', - 'import Default, * as All from "changed";', - 'import Default, { } from "changed";', - 'import Default, { Foo } from "changed";', - 'import Default, { Foo, } from "changed";', - 'import Default, { Foo as Bar } from "changed";', - 'import Default, { Foo as Bar, } from "changed";', - 'import Default, { Foo, Bar } from "changed";', - 'import Default, { Foo, Bar, } from "changed";', - 'import Default, { Foo as Bar, Baz } from "changed";', - 'import Default, { Foo as Bar, Baz, } from "changed";', - 'import Default, { Foo, Bar as Baz } from "changed";', - 'import Default, { Foo, Bar as Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz } from "changed";', - 'import Default, { Foo, Bar, Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', - 'import Default from "Y";', - 'import * as All from \'z\';', - // import with support for new lines - "import { Foo,\n Bar }\n from 'changed';", - "import { \nFoo,\nBar,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz\n }\n from 'changed';", - "import { \nFoo as Bar,\n Baz\n, }\n from 'changed';", - "import { Foo,\n Bar as Baz\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "import { Foo,\n Bar,\n Baz }\n from 'changed';", - "import { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';", - "import Default,\n * as All from 'changed';", - "import Default,\n { } from 'changed';", - "import Default,\n { Foo\n }\n from 'changed';", - "import Default,\n { Foo,\n }\n from 'changed';", - "import Default,\n { Foo as Bar\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar\n } from\n 'changed';", - "import Default,\n { Foo,\n Bar,\n } from\n 'changed';", - "import Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';", - "import Default\n , { } from 'changed';", - // single line export - "export'x';", - "export 'changed';", - "export 'changed' ;", - "export Default from 'changed';", - "export * as All from 'changed';", - "export {} from 'changed';", - "export { } from 'changed';", - "export {Foo} from 'changed';", - "export { Foo } from 'changed';", - "export { Foo, } from 'changed';", - "export {Foo as Bar} from 'changed';", - "export { Foo as Bar } from 'changed';", - "export { Foo as Bar, } from 'changed';", - "export { Foo, Bar } from 'changed';", - "export { Foo, Bar, } from 'changed';", - "export { Foo as Bar, Baz } from 'changed';", - "export { Foo as Bar, Baz, } from 'changed';", - "export { Foo, Bar as Baz } from 'changed';", - "export { Foo, Bar as Baz, } from 'changed';", - "export { Foo as Bar, Baz as Qux } from 'changed';", - "export { Foo as Bar, Baz as Qux, } from 'changed';", - "export { Foo, Bar, Baz } from 'changed';", - "export { Foo, Bar, Baz, } from 'changed';", - "export { Foo as Bar, Baz, Qux } from 'changed';", - "export { Foo as Bar, Baz, Qux, } from 'changed';", - "export { Foo, Bar as Baz, Qux } from 'changed';", - "export { Foo, Bar as Baz, Qux, } from 'changed';", - "export { Foo, Bar, Baz as Qux } from 'changed';", - "export { Foo, Bar, Baz as Qux, } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "export { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "export { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "export { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "export { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", - "export Default, * as All from 'changed';", - "export Default, { } from 'changed';", - "export Default, { Foo } from 'changed';", - "export Default, { Foo, } from 'changed';", - "export Default, { Foo as Bar } from 'changed';", - "export Default, { Foo as Bar, } from 'changed';", - "export Default, { Foo, Bar } from 'changed';", - "export Default, { Foo, Bar, } from 'changed';", - "export Default, { Foo as Bar, Baz } from 'changed';", - "export Default, { Foo as Bar, Baz, } from 'changed';", - "export Default, { Foo, Bar as Baz } from 'changed';", - "export Default, { Foo, Bar as Baz, } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, } from 'changed';", - "export Default, { Foo, Bar, Baz } from 'changed';", - "export Default, { Foo, Bar, Baz, } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux, } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux, } from 'changed';", - "export Default, { Foo, Bar, Baz as Qux } from 'changed';", - "export Default, { Foo, Bar, Baz as Qux, } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", - "export Default , { } from 'changed';", - 'export "changed";', - 'export Default from "changed";', - 'export * as All from "changed";', - 'export { } from "changed";', - 'export { Foo } from "changed";', - 'export { Foo, } from "changed";', - 'export { Foo as Bar } from "changed";', - 'export { Foo as Bar, } from "changed";', - 'export { Foo, Bar } from "changed";', - 'export { Foo, Bar, } from "changed";', - 'export { Foo as Bar, Baz } from "changed";', - 'export { Foo as Bar, Baz, } from "changed";', - 'export { Foo, Bar as Baz } from "changed";', - 'export { Foo, Bar as Baz, } from "changed";', - 'export { Foo as Bar, Baz as Qux } from "changed";', - 'export { Foo as Bar, Baz as Qux, } from "changed";', - 'export { Foo, Bar, Baz } from "changed";', - 'export { Foo, Bar, Baz, } from "changed";', - 'export { Foo as Bar, Baz, Qux } from "changed";', - 'export { Foo as Bar, Baz, Qux, } from "changed";', - 'export { Foo, Bar as Baz, Qux } from "changed";', - 'export { Foo, Bar as Baz, Qux, } from "changed";', - 'export { Foo, Bar, Baz as Qux } from "changed";', - 'export { Foo, Bar, Baz as Qux, } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'export { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'export { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'export { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'export { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', - 'export Default, * as All from "changed";', - 'export Default, { } from "changed";', - 'export Default, { Foo } from "changed";', - 'export Default, { Foo, } from "changed";', - 'export Default, { Foo as Bar } from "changed";', - 'export Default, { Foo as Bar, } from "changed";', - 'export Default, { Foo, Bar } from "changed";', - 'export Default, { Foo, Bar, } from "changed";', - 'export Default, { Foo as Bar, Baz } from "changed";', - 'export Default, { Foo as Bar, Baz, } from "changed";', - 'export Default, { Foo, Bar as Baz } from "changed";', - 'export Default, { Foo, Bar as Baz, } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, } from "changed";', - 'export Default, { Foo, Bar, Baz } from "changed";', - 'export Default, { Foo, Bar, Baz, } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux, } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux, } from "changed";', - 'export Default, { Foo, Bar, Baz as Qux } from "changed";', - 'export Default, { Foo, Bar, Baz as Qux, } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', - 'export Default from "Y";', - 'export * as All from \'z\';', - // export with support for new lines - "export { Foo,\n Bar }\n from 'changed';", - "export { \nFoo,\nBar,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz\n }\n from 'changed';", - "export { \nFoo as Bar,\n Baz\n, }\n from 'changed';", - "export { Foo,\n Bar as Baz\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "export { Foo,\n Bar,\n Baz }\n from 'changed';", - "export { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "export { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "export { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';", - "export Default,\n * as All from 'changed';", - "export Default,\n { } from 'changed';", - "export Default,\n { Foo\n }\n from 'changed';", - "export Default,\n { Foo,\n }\n from 'changed';", - "export Default,\n { Foo as Bar\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar\n } from\n 'changed';", - "export Default,\n { Foo,\n Bar,\n } from\n 'changed';", - "export Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';", - "export Default\n , { } from 'changed';", - // require - 'require("changed")', - 'require("Y")', - 'require( \'z\' )', - 'require( "a")', - 'require("b" )', - '});', - ].join('\n')); + + // extract the converted code from the module wrapper + const cases = + processedCode.code + .match(/__d\(.*?\{\s*([\s\S]*)\}/)[1] // match code in wrapper + .replace(/\s+$/, '') // remove trailing whitespace + .split(magicJoiner); // extract every tested case + + testCases.forEach((inputCode, i) => { + expect(cases[i]).toEqual(makeExpected(inputCode)); + if(cases[i]!==makeExpected(inputCode)) { + console.log('FAIL %s: input(%s) expected(%s) actual(%s)', i, inputCode, makeExpected(inputCode), cases[i]); + } + }); }); }); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 0166681f..c657aae5 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -49,6 +49,10 @@ const validateOpts = declareOpts({ type: 'object', required: true, }, + getModuleId: { + type: 'function', + required: true, + } }); const getDependenciesValidateOpts = declareOpts({ @@ -98,6 +102,7 @@ class Resolver { shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios', }); + this._getModuleId = options.getModuleId; this._polyfillModuleNames = opts.polyfillModuleNames || []; } @@ -177,36 +182,32 @@ class Resolver { } const resolvedDeps = Object.create(null); - const resolvedDepsArr = []; return Promise.all( resolutionResponse.getResolvedDependencyPairs(module).map( ([depName, depModule]) => { if (depModule) { - return depModule.getName().then(name => { - resolvedDeps[depName] = name; - resolvedDepsArr.push(name); - }); + resolvedDeps[depName] = this._getModuleId(depModule); } } ) ).then(() => { - const relativizeCode = (codeMatch, pre, quot, depName, post) => { - const depId = resolvedDeps[depName]; - if (depId) { - return pre + quot + depId + post; + const replaceModuleId = (codeMatch, pre, quot, depName, post = '') => { + if (depName in resolvedDeps) { + const replacement = `${quot}${resolvedDeps[depName]}${quot}`; + return `${pre}${replacement} /* ${depName} */${post}`; } else { return codeMatch; } }; code = code - .replace(replacePatterns.IMPORT_RE, relativizeCode) - .replace(replacePatterns.EXPORT_RE, relativizeCode) - .replace(replacePatterns.REQUIRE_RE, relativizeCode); + .replace(replacePatterns.IMPORT_RE, replaceModuleId) + .replace(replacePatterns.EXPORT_RE, replaceModuleId) + .replace(replacePatterns.REQUIRE_RE, replaceModuleId); return module.getName().then(name => { - return {name, code}; + return {name, code, id: this._getModuleId(module)}; }); }); }); @@ -220,8 +221,8 @@ class Resolver { } return this.resolveRequires(resolutionResponse, module, code).then( - ({name, code}) => { - return {name, code: defineModuleCode(name, code)}; + ({name, code, id}) => { + return {id, name, code: defineModuleCode(id, code, name)}; }); } @@ -231,10 +232,10 @@ class Resolver { } -function defineModuleCode(moduleName, code) { +function defineModuleCode(moduleId, code, verboseName = '') { return [ `__d(`, - `'${moduleName}',`, + `${JSON.stringify(moduleId)} /* ${verboseName} */ ,`, 'function(global, require, module, exports) {', ` ${code}`, '\n});', diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index a5a7a55f..bc14a6e8 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -59,18 +59,29 @@ function requireImpl(id) { ); } + + // `require` calls int the require polyfill itself are not analyzed and + // replaced so that they use numeric module IDs. + // The systrace module will expose itself on the require function so that + // it can be used here. + // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) + const {Systrace} = require; try { // We must optimistically mark mod as initialized before running the factory to keep any // require cycles inside the factory from causing an infinite require loop. mod.isInitialized = true; - __DEV__ && Systrace().beginEvent('JS_require_' + id); + if (__DEV__) { + Systrace.beginEvent('JS_require_' + id); + } // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js mod.factory.call(global, global, require, mod.module, mod.module.exports); - __DEV__ && Systrace().endEvent(); + if (__DEV__) { + Systrace.endEvent(); + } } catch (e) { mod.hasError = true; mod.isInitialized = false; @@ -80,15 +91,9 @@ function requireImpl(id) { return mod.module.exports; } -const Systrace = __DEV__ && (() => { - var _Systrace; - try { - _Systrace = require('Systrace'); - } catch (e) {} - - return _Systrace && _Systrace.beginEvent ? - _Systrace : { beginEvent: () => {}, endEvent: () => {} }; -}); +if (__DEV__) { + require.Systrace = { beginEvent: () => {}, endEvent: () => {} }; +} global.__d = define; global.require = require; From d1ae909bbb0c4cc31bbf642b8598f14da732e43a Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 21 Jan 2016 10:57:02 -0800 Subject: [PATCH 539/936] Fix a tiny typo in error message Reviewed By: davidaurelio Differential Revision: D2850195 fb-gh-sync-id: 8177ce2f8d7f799edc559b4f5a0da99d865874a7 --- .../src/DependencyResolver/DependencyGraph/ResolutionRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 65742f1a..2c18c95a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -374,7 +374,7 @@ class ResolutionRequest { toModule, `Invalid directory ${potentialDirPath} -This might related to https://github.com/facebook/react-native/issues/4968 +This might be related to https://github.com/facebook/react-native/issues/4968 To resolve try the following: 1. Clear watchman watches: \`watchman watch-del-all\`. 2. Delete the \`node_modules\` folder: \`rm -rf node_modules && npm install\`. From 0dbc239685bacd7bcc889d0f06b9dd3e6d177dc7 Mon Sep 17 00:00:00 2001 From: "tfallon@mail.depaul.edu" Date: Thu, 21 Jan 2016 12:59:41 -0800 Subject: [PATCH 540/936] Adding ETag handling to packager improves local development Summary: Closes https://github.com/facebook/react-native/pull/2473 Reviewed By: svcscm Differential Revision: D2669385 Pulled By: mkonicek fb-gh-sync-id: bb9ee4a309a05e0da0ccc7663a373f4a53b9a0a2 --- .../AssetServer/__tests__/AssetServer-test.js | 2 +- react-packager/src/Bundler/Bundle.js | 6 ++ .../src/Bundler/__tests__/Bundle-test.js | 10 ++++ .../src/Server/__tests__/Server-test.js | 55 ++++++++++++++----- react-packager/src/Server/index.js | 8 ++- 5 files changed, 66 insertions(+), 15 deletions(-) diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 7a544a27..1cb90b33 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -185,7 +185,7 @@ describe('AssetServer', () => { }); }); - describe('assetSerer.getAssetData', () => { + describe('assetServer.getAssetData', () => { pit('should get assetData', () => { const hash = { update: jest.genMockFn(), diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index e457e203..4f54e3be 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -14,6 +14,7 @@ const BundleBase = require('./BundleBase'); const UglifyJS = require('uglify-js'); const ModuleTransport = require('../lib/ModuleTransport'); const Activity = require('../Activity'); +const crypto = require('crypto'); const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL='; @@ -256,6 +257,11 @@ class Bundle extends BundleBase { return map; } + getEtag() { + var eTag = crypto.createHash('md5').update(this.getSource()).digest('hex'); + return eTag; + } + _getMappings() { const modules = super.getModules(); diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 1180f0a2..b82f4a6c 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -15,6 +15,7 @@ const ModuleTransport = require('../../lib/ModuleTransport'); const Promise = require('Promise'); const SourceMapGenerator = require('source-map').SourceMapGenerator; const UglifyJS = require('uglify-js'); +const crypto = require('crypto'); describe('Bundle', () => { var bundle; @@ -301,6 +302,15 @@ describe('Bundle', () => { }); }); }); + + describe('getEtag()', function() { + it('should return an etag', function() { + var bundle = new Bundle('test_url'); + bundle.finalize({}); + var eTag = crypto.createHash('md5').update(bundle.getSource()).digest('hex'); + expect(bundle.getEtag()).toEqual(eTag); + }); + }); }); diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index a21462ca..123134f8 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -14,7 +14,8 @@ jest.setMock('worker-farm', function() { return () => {}; }) .dontMock('url') .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) }) .setMock('uglify-js') - .dontMock('../'); + .dontMock('../') + .setMock('crypto'); const Promise = require('promise'); @@ -34,12 +35,17 @@ describe('processRequest', () => { polyfillModuleNames: null }; - const makeRequest = (reqHandler, requrl) => new Promise(resolve => + const makeRequest = (reqHandler, requrl, reqOptions) => new Promise(resolve => reqHandler( - { url: requrl }, + { url: requrl, headers:{}, ...reqOptions }, { - setHeader: jest.genMockFunction(), - end: res => resolve(res), + headers: {}, + getHeader(header) { return this.headers[header]; }, + setHeader(header, value) { this.headers[header] = value; }, + end(body) { + this.body = body; + resolve(this); + }, }, { next: () => {} }, ) @@ -55,6 +61,7 @@ describe('processRequest', () => { Promise.resolve({ getSource: () => 'this is the source', getSourceMap: () => 'this is the source map', + getEtag: () => 'this is an etag', }) ); @@ -76,9 +83,10 @@ describe('processRequest', () => { pit('returns JS bundle source on request of *.bundle', () => { return makeRequest( requestHandler, - 'mybundle.bundle?runModule=true' + 'mybundle.bundle?runModule=true', + null ).then(response => - expect(response).toEqual('this is the source') + expect(response.body).toEqual('this is the source') ); }); @@ -87,16 +95,35 @@ describe('processRequest', () => { requestHandler, 'mybundle.runModule.bundle' ).then(response => - expect(response).toEqual('this is the source') + expect(response.body).toEqual('this is the source') ); }); + pit('returns ETag header on request of *.bundle', () => { + return makeRequest( + requestHandler, + 'mybundle.bundle?runModule=true' + ).then(response => { + expect(response.getHeader('ETag')).toBeDefined() + }); + }); + + pit('returns 304 on request of *.bundle when if-none-match equals the ETag', () => { + return makeRequest( + requestHandler, + 'mybundle.bundle?runModule=true', + { headers : { 'if-none-match' : 'this is an etag' } } + ).then(response => { + expect(response.statusCode).toEqual(304) + }); + }); + pit('returns sourcemap on request of *.map', () => { return makeRequest( requestHandler, 'mybundle.map?runModule=true' ).then(response => - expect(response).toEqual('this is the source map') + expect(response.body).toEqual('this is the source map') ); }); @@ -105,7 +132,7 @@ describe('processRequest', () => { requestHandler, 'index.ios.includeRequire.bundle' ).then(response => { - expect(response).toEqual('this is the source'); + expect(response.body).toEqual('this is the source'); expect(Bundler.prototype.bundle).toBeCalledWith({ entryFile: 'index.ios.js', inlineSourceMap: false, @@ -126,7 +153,7 @@ describe('processRequest', () => { requestHandler, 'index.bundle?platform=ios' ).then(function(response) { - expect(response).toEqual('this is the source'); + expect(response.body).toEqual('this is the source'); expect(Bundler.prototype.bundle).toBeCalledWith({ entryFile: 'index.js', inlineSourceMap: false, @@ -183,12 +210,14 @@ describe('processRequest', () => { Promise.resolve({ getSource: () => 'this is the first source', getSourceMap: () => {}, + getEtag: () => () => 'this is an etag', }) ) .mockReturnValue( Promise.resolve({ getSource: () => 'this is the rebuilt source', getSourceMap: () => {}, + getEtag: () => () => 'this is an etag', }) ); @@ -200,7 +229,7 @@ describe('processRequest', () => { makeRequest(requestHandler, 'mybundle.bundle?runModule=true') .done(response => { - expect(response).toEqual('this is the first source'); + expect(response.body).toEqual('this is the first source'); expect(bundleFunc.mock.calls.length).toBe(1); }); @@ -214,7 +243,7 @@ describe('processRequest', () => { makeRequest(requestHandler, 'mybundle.bundle?runModule=true') .done(response => - expect(response).toEqual('this is the rebuilt source') + expect(response.body).toEqual('this is the rebuilt source') ); jest.runAllTicks(); } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 63432090..3bb8a9c0 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -430,7 +430,13 @@ class Server { dev: options.dev, }); res.setHeader('Content-Type', 'application/javascript'); - res.end(bundleSource); + res.setHeader('ETag', p.getEtag()); + if (req.headers['if-none-match'] === res.getHeader('ETag')){ + res.statusCode = 304; + res.end(); + } else { + res.end(bundleSource); + } Activity.endEvent(startReqEventId); } else if (requestType === 'map') { var sourceMap = p.getSourceMap({ From 176a6df94cb754a687e1f1c29069bf0e6c470b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 21 Jan 2016 13:56:23 -0800 Subject: [PATCH 541/936] Bump js/package.json version to clear cache Reviewed By: sam-swarr Differential Revision: D2851399 fb-gh-sync-id: 0fbeb7d4c6d669f981e3f5139387e506a3670931 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c5d4832e..117369a7 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.2", + "version": "0.1.3", "name": "react-native-packager", "description": "Build native apps with React!", "repository": { From 782d0838be66ec9b27f5014486e36fd93fdc07de Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 21 Jan 2016 13:57:32 -0800 Subject: [PATCH 542/936] =?UTF-8?q?Add=20ability=20to=20load=20=E2=80=9Cun?= =?UTF-8?q?bundles=E2=80=9D=20to=20android?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: public This adds the ability to load “unbundles” in RN android apps. Unbundles are created by invoking the packager with the `unbundle` command rather than `bundle`. The code detects usage of an “unbundle” by checking for the existence of a specific asset. Reviewed By: astreet Differential Revision: D2739596 fb-gh-sync-id: d0813c003fe0fa7b47798b970f56707079bfa5d7 --- react-packager/src/Resolver/polyfills/require-unbundle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js index c6c7cbc7..df75ae14 100644 --- a/react-packager/src/Resolver/polyfills/require-unbundle.js +++ b/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -1,6 +1,6 @@ 'use strict'; -const {ErrorUtils, __nativeRequire} = global; +const {ErrorUtils, nativeRequire} = global; global.require = require; global.__d = define; @@ -32,7 +32,7 @@ function guardedLoadModule(moduleId, module) { function loadModuleImplementation(moduleId, module) { if (!module) { - __nativeRequire(moduleId); + nativeRequire(moduleId); module = modules[moduleId]; } From 153989bcd30f24e6b3cb639f508d014311d5c16f Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 21 Jan 2016 14:10:49 -0800 Subject: [PATCH 543/936] `require` implementation for unbundles: Systrace and guard Summary: public Adds two feature to the require implementation for unbundled code: - Ports Systrace from `require.js` - Prevents already loaded modules from being overwritten by repeated calls to `nativeRequire` with the same ID Reviewed By: martinbigio Differential Revision: D2850345 fb-gh-sync-id: 122e2d5d4a64b2e868d4cf6e5079810f59b1db18 --- .../Resolver/polyfills/require-unbundle.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js index df75ae14..87e85601 100644 --- a/react-packager/src/Resolver/polyfills/require-unbundle.js +++ b/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -10,6 +10,11 @@ const loadModule = ErrorUtils ? guardedLoadModule : loadModuleImplementation; function define(moduleId, factory) { + if (moduleId in modules) { + // prevent repeated calls to `global.nativeRequire` to overwrite modules + // that are already loaded + return; + } modules[moduleId] = { factory, hasError: false, @@ -44,11 +49,26 @@ function loadModuleImplementation(moduleId, module) { throw moduleThrewError(moduleId); } + // `require` calls int the require polyfill itself are not analyzed and + // replaced so that they use numeric module IDs. + // The systrace module will expose itself on the require function so that + // it can be used here. + // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) + const {Systrace} = require; + const exports = module.exports = {}; const {factory} = module; try { + if (__DEV__) { + Systrace.beginEvent('JS_require_' + moduleId); + } + const moduleObject = {exports}; factory(global, require, moduleObject, exports); + + if (__DEV__) { + Systrace.endEvent(); + } return (module.exports = moduleObject.exports); } catch (e) { module.hasError = true; @@ -68,3 +88,7 @@ function unknownModuleError(id) { function moduleThrewError(id) { return Error('Requiring module "' + id + '", which threw an exception.'); } + +if (__DEV__) { + require.Systrace = { beginEvent: () => {}, endEvent: () => {} }; +} From 741cb48fa2589da1bae4a505d1ecee63074db950 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Fri, 22 Jan 2016 10:39:13 -0800 Subject: [PATCH 544/936] Distinguish between module ID and name Reviewed By: martinbigio Differential Revision: D2854975 fb-gh-sync-id: 8c75037d5d7f6155369d4ec581158ebabf445bac --- react-packager/src/Bundler/Bundle.js | 5 +--- react-packager/src/Bundler/BundleBase.js | 18 ++++++++++--- .../src/Bundler/__tests__/Bundle-test.js | 25 +++++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 4f54e3be..f02127f3 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -341,14 +341,11 @@ class Bundle extends BundleBase { } toJSON() { - if (!this._finalized) { - throw new Error('Cannot serialize bundle unless finalized'); - } + this.assertFinalized('Cannot serialize bundle unless finalized'); return { ...super.toJSON(), sourceMapUrl: this._sourceMapUrl, - mainModuleId: super.getMainModuleId(), numPrependedModules: this._numPrependedModules, numRequireCalls: this._numRequireCalls, }; diff --git a/react-packager/src/Bundler/BundleBase.js b/react-packager/src/Bundler/BundleBase.js index 635f87e7..b9e1cbe3 100644 --- a/react-packager/src/Bundler/BundleBase.js +++ b/react-packager/src/Bundler/BundleBase.js @@ -16,6 +16,7 @@ class BundleBase { this._finalized = false; this._modules = []; this._assets = []; + this._mainModuleId = this._mainModuleName = undefined; } getMainModuleId() { @@ -26,6 +27,14 @@ class BundleBase { this._mainModuleId = moduleId; } + getMainModuleName() { + return this._mainModuleName; + } + + setMainModuleName(moduleName) { + this._mainModuleName = moduleName; + } + addModule(module) { if (!module instanceof ModuleTransport) { throw new Error('Expeceted a ModuleTransport object'); @@ -65,9 +74,9 @@ class BundleBase { return this._source; } - assertFinalized() { + assertFinalized(message) { if (!this._finalized) { - throw new Error('Bundle needs to be finalized before getting any source'); + throw new Error(message || 'Bundle needs to be finalized before getting any source'); } } @@ -75,13 +84,16 @@ class BundleBase { return { modules: this._modules, assets: this._assets, + mainModuleId: this.getMainModuleId(), + mainModuleName: this.getMainModuleName(), }; } static fromJSON(bundle, json) { bundle._assets = json.assets; bundle._modules = json.modules; - bundle._mainModuleId = json.mainModuleId; + bundle.setMainModuleId(json.mainModuleId); + bundle.setMainModuleName(json.mainModuleName); Object.freeze(bundle._modules); Object.seal(bundle._modules); diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index b82f4a6c..5e5fca22 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -311,6 +311,31 @@ describe('Bundle', () => { expect(bundle.getEtag()).toEqual(eTag); }); }); + + describe('main module name and id:', function() { + it('keeps distinct module names and IDs', function() { + const id = 'arbitrary module ID'; + const name = 'arbitrary module name'; + bundle.setMainModuleId(id); + bundle.setMainModuleName(name); + + expect(bundle.getMainModuleId()).toEqual(id); + expect(bundle.getMainModuleName()).toEqual(name); + }); + + it('can serialize and deserialize module ID and name', function() { + const id = 'arbitrary module ID'; + const name = 'arbitrary module name'; + bundle.setMainModuleId(id); + bundle.setMainModuleName(name); + bundle.finalize({}); + + const deserialized = Bundle.fromJSON(bundle.toJSON()); + + expect(deserialized.getMainModuleId()).toEqual(id); + expect(deserialized.getMainModuleName()).toEqual(name); + }); + }); }); From baa6d7fb2447431572f2168416e5dcb513aa281c Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Fri, 22 Jan 2016 11:38:51 -0800 Subject: [PATCH 545/936] Backout "Use numeric identifiers when building a bundle" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: public The reverted change doesn’t play nice with inline requires, let’s revert it for now. I will bring it back after fixing it or adapting inline requires Reviewed By: martinbigio Differential Revision: D2854771 fb-gh-sync-id: 32fdbf8ad51240a9075b26502decb6328eed4b29 --- react-packager/src/Bundler/Bundle.js | 6 +- .../src/Bundler/__tests__/Bundler-test.js | 28 +- react-packager/src/Bundler/index.js | 50 +- .../BundlesLayoutIntegration-test.js | 1 - .../src/DependencyResolver/AssetModule.js | 8 +- .../DependencyGraph/ResolutionResponse.js | 5 - .../__tests__/DependencyGraph-test.js | 18 +- .../DependencyResolver/lib/replacePatterns.js | 8 +- .../src/Resolver/__tests__/Resolver-test.js | 442 ++++++++++++++++-- react-packager/src/Resolver/index.js | 35 +- .../src/Resolver/polyfills/require.js | 27 +- 11 files changed, 456 insertions(+), 172 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index f02127f3..2641550a 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -41,10 +41,10 @@ class Bundle extends BundleBase { response, module, transformed.code - ).then(({code, id}) => { + ).then(({code, name}) => { const moduleTransport = new ModuleTransport({ code, - name: id, + name, map: transformed.map, sourceCode: transformed.sourceCode, sourcePath: transformed.sourcePath, @@ -76,7 +76,7 @@ class Bundle extends BundleBase { } _addRequireCall(moduleId) { - const code = `;require(${JSON.stringify(moduleId)});`; + const code = ';require("' + moduleId + '");'; const name = 'require-' + moduleId; super.addModule(new ModuleTransport({ name, diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index a85695be..467cf90d 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -10,23 +10,16 @@ jest .setMock('worker-farm', () => () => undefined) - .dontMock('absolute-path') .dontMock('underscore') .dontMock('../../lib/ModuleTransport') - .dontMock('../../DependencyResolver/AssetModule') - .dontMock('../../DependencyResolver/Module') - .dontMock('../../DependencyResolver/lib/getAssetDataFromName') .setMock('uglify-js') .dontMock('../'); jest.mock('fs'); -var AssetModule = require('../../DependencyResolver/AssetModule'); var Bundler = require('../'); var JSTransformer = require('../../JSTransformer'); -var Module = require('../../DependencyResolver/Module'); var Resolver = require('../../Resolver'); - var sizeOf = require('image-size'); var fs = require('fs'); @@ -41,14 +34,6 @@ describe('Bundler', function() { isJSON, resolution, }) { - if (isAsset) { - const module = new AssetModule({ - file: path, - cache: {get: () => Promise.resolve(path)} - }); - module.getName = () => Promise.resolve(id); - return module; - } return { path, resolution, @@ -66,8 +51,6 @@ describe('Bundler', function() { var bundler; var assetServer; var modules; - const width = 50; - const height = 100; beforeEach(function() { getDependencies = jest.genMockFn(); @@ -125,12 +108,10 @@ describe('Bundler', function() { }), ]; - const mainModule = new Module({file: '/root/foo'}); getDependencies.mockImpl(function() { return Promise.resolve({ mainModuleId: 'foo', - dependencies: modules, - getMainModule: () => mainModule, + dependencies: modules }); }); @@ -151,13 +132,12 @@ describe('Bundler', function() { wrapModule.mockImpl(function(response, module, code) { return module.getName().then(name => ({ name, - id: name, code: 'lol ' + code + ' lol' })); }); sizeOf.mockImpl(function(path, cb) { - cb(null, { width, height }); + cb(null, { width: 50, height: 100 }); }); }); @@ -207,8 +187,8 @@ describe('Bundler', function() { __packager_asset: true, fileSystemLocation: '/root/img', httpServerLocation: '/assets/img', - width, - height, + width: 25, + height: 50, scales: [1, 2, 3], files: [ '/root/img/img.png', diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 9b7bb1fa..1cc28f23 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -102,8 +102,6 @@ class Bundler { mtime = ''; } - this._getModuleId = createModuleIdGetter(); - this._cache = new Cache({ resetCache: opts.resetCache, cacheKey: [ @@ -124,7 +122,6 @@ class Bundler { fileWatcher: opts.fileWatcher, assetExts: opts.assetExts, cache: this._cache, - getModuleId: this._getModuleId, }); this._bundlesLayout = new BundlesLayout({ @@ -190,19 +187,9 @@ class Bundler { const findEventId = Activity.startEvent('find dependencies'); let transformEventId; - if (isDev) { - // `require` calls int the require polyfill itself are not analyzed and - // replaced so that they use numeric module IDs. Therefore, we include - // the Systrace module before any other module, and it will set itself - // as property on the require function. - // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) - runBeforeMainModule = ['Systrace'].concat(runBeforeMainModule); - } - - const modulesByName = Object.create(null); return this.getDependencies(entryFile, isDev, platform).then((response) => { Activity.endEvent(findEventId); - bundle.setMainModuleId(this._getModuleId(response.getMainModule())); + bundle.setMainModuleId(response.mainModuleId); transformEventId = Activity.startEvent('transform'); const moduleSystemDeps = includeSystemDependencies @@ -238,11 +225,6 @@ class Bundler { platform, hot, ).then(transformed => { - return module.getName().then(name => { - modulesByName[name] = module; - return transformed; - }); - }).then(transformed => { if (bar) { bar.tick(); } @@ -266,11 +248,7 @@ class Bundler { )); }).then(() => { Activity.endEvent(transformEventId); - const runBeforeIds = runBeforeMainModule - .map(name => modulesByName[name]) - .filter(Boolean) - .map(this._getModuleId, this); - bundle.finalize({runBeforeMainModule: runBeforeIds, runMainModule}); + bundle.finalize({runBeforeMainModule, runMainModule}); return bundle; }); } @@ -484,7 +462,8 @@ class Bundler { type: assetData.type, }; - const code = module.getCode(asset); + const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);'; + const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(asset)); return {asset, code}; }); @@ -543,21 +522,12 @@ function verifyRootExists(root) { assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); } +class DummyCache { + get(filepath, field, loaderCb) { + return loaderCb(); + } -function createModuleIdGetter() { - const fileToIdMap = Object.create(null); - let nextId = 0; - return ( - ({path}) => { - if (!(path in fileToIdMap)) { - // can't be a number for now, since we also replace in import / export - // we can change that when we eventually change to analyzing dependencies - // on transformed modules - fileToIdMap[path] = String(nextId++); - } - return fileToIdMap[path]; - } - ); + end(){} + invalidate(filepath){} } - module.exports = Bundler; diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index b18b424f..fe4481a0 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -55,7 +55,6 @@ describe('BundlesLayout', () => { cache: new Cache(), assetExts: ['js', 'png'], assetRoots: ['/root'], - getModuleId: () => {}, }); return new BundlesLayout({ diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js index 68a92ee2..f5555fac 100644 --- a/react-packager/src/DependencyResolver/AssetModule.js +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -18,19 +18,13 @@ class AssetModule extends Module { } getDependencies() { - return Promise.resolve(['AssetRegistry']); + return Promise.resolve([]); } getAsyncDependencies() { return Promise.resolve([]); } - getCode(assetData) { - return `module.exports = require('AssetRegistry').registerAsset(${ - JSON.stringify(assetData) - });`; - } - read() { return Promise.resolve({}); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js index 3c1656c5..7fbafde8 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js @@ -39,11 +39,6 @@ class ResolutionResponse { }); } - getMainModule() { - this._assertFinalized(); - return this._mainModule; - } - pushDependency(module) { this._assertNotFinalized(); if (this.dependencies.length === 0) { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index a858f1d2..b1126a4c 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -374,7 +374,7 @@ describe('DependencyGraph', function() { { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a.png', - dependencies: ['AssetRegistry'], + dependencies: [], isAsset: true, resolution: 1, isAsset_DEPRECATED: false, @@ -434,7 +434,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a@1.5x.png', resolution: 1.5, - dependencies: ['AssetRegistry'], + dependencies: [], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -444,7 +444,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.png', resolution: 0.7, - dependencies: ['AssetRegistry'], + dependencies: [], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -454,7 +454,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.png', resolution: 1, - dependencies: ['AssetRegistry'], + dependencies: [], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -514,7 +514,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a@1.5x.ios.png', resolution: 1.5, - dependencies: ['AssetRegistry'], + dependencies: [], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -524,7 +524,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.ios.png', resolution: 0.7, - dependencies: ['AssetRegistry'], + dependencies: [], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -534,7 +534,7 @@ describe('DependencyGraph', function() { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.ios.png', resolution: 1, - dependencies: ['AssetRegistry'], + dependencies: [], isAsset: true, isAsset_DEPRECATED: false, isJSON: false, @@ -585,7 +585,7 @@ describe('DependencyGraph', function() { { id: 'rootPackage/imgs/a.png', path: '/root/imgs/a.png', - dependencies: ['AssetRegistry'], + dependencies: [], isAsset: true, resolution: 1, isAsset_DEPRECATED: false, @@ -3367,7 +3367,7 @@ describe('DependencyGraph', function() { { id: 'aPackage/foo.png', path: '/root/foo.png', - dependencies: ['AssetRegistry'], + dependencies: [], isAsset: true, resolution: 1, isAsset_DEPRECATED: false, diff --git a/react-packager/src/DependencyResolver/lib/replacePatterns.js b/react-packager/src/DependencyResolver/lib/replacePatterns.js index 3bd6ef45..a4e563d2 100644 --- a/react-packager/src/DependencyResolver/lib/replacePatterns.js +++ b/react-packager/src/DependencyResolver/lib/replacePatterns.js @@ -9,7 +9,7 @@ 'use strict'; -exports.IMPORT_RE = /(\bimport\s*(?:[\s{][^'"]+[\s}]from\s*)??)(['"])([^'"]+)\2()/g; -exports.EXPORT_RE = /(\bexport\s*(?:[\s{][^'"]+[\s}]from\s*)??)(['"])([^'"]+)\2()/g; -exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)\2(\s*?\))/g; -exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)\2(\s*?\))/g; +exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; +exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; +exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; +exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 6093cf42..825399b4 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -35,12 +35,6 @@ describe('Resolver', function() { }); }); - const modulesWithIds = []; - function getModuleId(module) { - const index = modulesWithIds.indexOf(module); - return String(index !== -1 ? index + 1 : modulesWithIds.push(module)); - } - class ResolutionResponseMock { constructor({dependencies, mainModuleId, asyncDependencies}) { this.dependencies = dependencies; @@ -79,7 +73,6 @@ describe('Resolver', function() { var depResolver = new Resolver({ projectRoot: '/root', - getModuleId, }); DependencyGraph.prototype.getDependencies.mockImpl(function() { @@ -167,7 +160,6 @@ describe('Resolver', function() { var depResolver = new Resolver({ projectRoot: '/root', - getModuleId, }); DependencyGraph.prototype.getDependencies.mockImpl(function() { @@ -195,7 +187,6 @@ describe('Resolver', function() { var depResolver = new Resolver({ projectRoot: '/root', polyfillModuleNames: ['some module'], - getModuleId, }); DependencyGraph.prototype.getDependencies.mockImpl(function() { @@ -232,13 +223,12 @@ describe('Resolver', function() { pit('should resolve modules', function() { var depResolver = new Resolver({ projectRoot: '/root', - getModuleId, }); - const magicJoiner = '\n\n\n'; + var dependencies = ['x', 'y', 'z', 'a', 'b']; /*eslint-disable */ - const testCases = [ + var code = [ // single line import "import'x';", "import 'x';", @@ -313,7 +303,6 @@ describe('Resolver', function() { 'import * as All from "x";', 'import { } from "x";', 'import { Foo } from "x";', - 'import{Foo}from"x";', 'import { Foo, } from "x";', 'import { Foo as Bar } from "x";', 'import { Foo as Bar, } from "x";', @@ -439,7 +428,6 @@ describe('Resolver', function() { "export { } from 'x';", "export {Foo} from 'x';", "export { Foo } from 'x';", - "export{Foo}from'x';", "export { Foo, } from 'x';", "export {Foo as Bar} from 'x';", "export { Foo as Bar } from 'x';", @@ -625,9 +613,8 @@ describe('Resolver', function() { 'require( \'z\' )', 'require( "a")', 'require("b" )', - ] + ].join('\n'); /*eslint-disable */ - const code = testCases.join(magicJoiner); const module = createModule('test module', ['x', 'y']); @@ -637,21 +624,11 @@ describe('Resolver', function() { asyncDependencies: [], }); - const pairs = [ - ['x', createModule('changed')], - ['y', createModule('Y')], - ]; - resolutionResponse.getResolvedDependencyPairs = () => pairs; - - function makeExpected(code) { - return pairs - .reduce((code, [id, module]) => - code.replace( - RegExp(`(['"])${id}\\1`), - (_, quot) => `${quot}${getModuleId(module)}${quot} /* ${id} */` - ), - code - ); + resolutionResponse.getResolvedDependencyPairs = (module) => { + return [ + ['x', createModule('changed')], + ['y', createModule('Y')], + ]; } return depResolver.wrapModule( @@ -660,20 +637,395 @@ describe('Resolver', function() { code ).then(processedCode => { expect(processedCode.name).toEqual('test module'); - - // extract the converted code from the module wrapper - const cases = - processedCode.code - .match(/__d\(.*?\{\s*([\s\S]*)\}/)[1] // match code in wrapper - .replace(/\s+$/, '') // remove trailing whitespace - .split(magicJoiner); // extract every tested case - - testCases.forEach((inputCode, i) => { - expect(cases[i]).toEqual(makeExpected(inputCode)); - if(cases[i]!==makeExpected(inputCode)) { - console.log('FAIL %s: input(%s) expected(%s) actual(%s)', i, inputCode, makeExpected(inputCode), cases[i]); - } - }); + expect(processedCode.code).toEqual([ + '__d(\'test module\',function(global, require,' + + ' module, exports) { ' + + // single line import + "import'x';", + "import 'changed';", + "import 'changed' ;", + "import Default from 'changed';", + "import * as All from 'changed';", + "import {} from 'changed';", + "import { } from 'changed';", + "import {Foo} from 'changed';", + "import { Foo } from 'changed';", + "import { Foo, } from 'changed';", + "import {Foo as Bar} from 'changed';", + "import { Foo as Bar } from 'changed';", + "import { Foo as Bar, } from 'changed';", + "import { Foo, Bar } from 'changed';", + "import { Foo, Bar, } from 'changed';", + "import { Foo as Bar, Baz } from 'changed';", + "import { Foo as Bar, Baz, } from 'changed';", + "import { Foo, Bar as Baz } from 'changed';", + "import { Foo, Bar as Baz, } from 'changed';", + "import { Foo as Bar, Baz as Qux } from 'changed';", + "import { Foo as Bar, Baz as Qux, } from 'changed';", + "import { Foo, Bar, Baz } from 'changed';", + "import { Foo, Bar, Baz, } from 'changed';", + "import { Foo as Bar, Baz, Qux } from 'changed';", + "import { Foo as Bar, Baz, Qux, } from 'changed';", + "import { Foo, Bar as Baz, Qux } from 'changed';", + "import { Foo, Bar as Baz, Qux, } from 'changed';", + "import { Foo, Bar, Baz as Qux } from 'changed';", + "import { Foo, Bar, Baz as Qux, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", + "import Default, * as All from 'changed';", + "import Default, { } from 'changed';", + "import Default, { Foo } from 'changed';", + "import Default, { Foo, } from 'changed';", + "import Default, { Foo as Bar } from 'changed';", + "import Default, { Foo as Bar, } from 'changed';", + "import Default, { Foo, Bar } from 'changed';", + "import Default, { Foo, Bar, } from 'changed';", + "import Default, { Foo as Bar, Baz } from 'changed';", + "import Default, { Foo as Bar, Baz, } from 'changed';", + "import Default, { Foo, Bar as Baz } from 'changed';", + "import Default, { Foo, Bar as Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz } from 'changed';", + "import Default, { Foo, Bar, Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", + "import Default , { } from 'changed';", + 'import "changed";', + 'import Default from "changed";', + 'import * as All from "changed";', + 'import { } from "changed";', + 'import { Foo } from "changed";', + 'import { Foo, } from "changed";', + 'import { Foo as Bar } from "changed";', + 'import { Foo as Bar, } from "changed";', + 'import { Foo, Bar } from "changed";', + 'import { Foo, Bar, } from "changed";', + 'import { Foo as Bar, Baz } from "changed";', + 'import { Foo as Bar, Baz, } from "changed";', + 'import { Foo, Bar as Baz } from "changed";', + 'import { Foo, Bar as Baz, } from "changed";', + 'import { Foo as Bar, Baz as Qux } from "changed";', + 'import { Foo as Bar, Baz as Qux, } from "changed";', + 'import { Foo, Bar, Baz } from "changed";', + 'import { Foo, Bar, Baz, } from "changed";', + 'import { Foo as Bar, Baz, Qux } from "changed";', + 'import { Foo as Bar, Baz, Qux, } from "changed";', + 'import { Foo, Bar as Baz, Qux } from "changed";', + 'import { Foo, Bar as Baz, Qux, } from "changed";', + 'import { Foo, Bar, Baz as Qux } from "changed";', + 'import { Foo, Bar, Baz as Qux, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', + 'import Default, * as All from "changed";', + 'import Default, { } from "changed";', + 'import Default, { Foo } from "changed";', + 'import Default, { Foo, } from "changed";', + 'import Default, { Foo as Bar } from "changed";', + 'import Default, { Foo as Bar, } from "changed";', + 'import Default, { Foo, Bar } from "changed";', + 'import Default, { Foo, Bar, } from "changed";', + 'import Default, { Foo as Bar, Baz } from "changed";', + 'import Default, { Foo as Bar, Baz, } from "changed";', + 'import Default, { Foo, Bar as Baz } from "changed";', + 'import Default, { Foo, Bar as Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz } from "changed";', + 'import Default, { Foo, Bar, Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', + 'import Default from "Y";', + 'import * as All from \'z\';', + // import with support for new lines + "import { Foo,\n Bar }\n from 'changed';", + "import { \nFoo,\nBar,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz\n }\n from 'changed';", + "import { \nFoo as Bar,\n Baz\n, }\n from 'changed';", + "import { Foo,\n Bar as Baz\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", + "import { Foo,\n Bar,\n Baz }\n from 'changed';", + "import { Foo,\n Bar,\n Baz,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", + "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", + "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", + "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';", + "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';", + "import Default,\n * as All from 'changed';", + "import Default,\n { } from 'changed';", + "import Default,\n { Foo\n }\n from 'changed';", + "import Default,\n { Foo,\n }\n from 'changed';", + "import Default,\n { Foo as Bar\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar\n } from\n 'changed';", + "import Default,\n { Foo,\n Bar,\n } from\n 'changed';", + "import Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';", + "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';", + "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';", + "import Default\n , { } from 'changed';", + // single line export + "export'x';", + "export 'changed';", + "export 'changed' ;", + "export Default from 'changed';", + "export * as All from 'changed';", + "export {} from 'changed';", + "export { } from 'changed';", + "export {Foo} from 'changed';", + "export { Foo } from 'changed';", + "export { Foo, } from 'changed';", + "export {Foo as Bar} from 'changed';", + "export { Foo as Bar } from 'changed';", + "export { Foo as Bar, } from 'changed';", + "export { Foo, Bar } from 'changed';", + "export { Foo, Bar, } from 'changed';", + "export { Foo as Bar, Baz } from 'changed';", + "export { Foo as Bar, Baz, } from 'changed';", + "export { Foo, Bar as Baz } from 'changed';", + "export { Foo, Bar as Baz, } from 'changed';", + "export { Foo as Bar, Baz as Qux } from 'changed';", + "export { Foo as Bar, Baz as Qux, } from 'changed';", + "export { Foo, Bar, Baz } from 'changed';", + "export { Foo, Bar, Baz, } from 'changed';", + "export { Foo as Bar, Baz, Qux } from 'changed';", + "export { Foo as Bar, Baz, Qux, } from 'changed';", + "export { Foo, Bar as Baz, Qux } from 'changed';", + "export { Foo, Bar as Baz, Qux, } from 'changed';", + "export { Foo, Bar, Baz as Qux } from 'changed';", + "export { Foo, Bar, Baz as Qux, } from 'changed';", + "export { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "export { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "export { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "export { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "export { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "export { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "export { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", + "export { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", + "export Default, * as All from 'changed';", + "export Default, { } from 'changed';", + "export Default, { Foo } from 'changed';", + "export Default, { Foo, } from 'changed';", + "export Default, { Foo as Bar } from 'changed';", + "export Default, { Foo as Bar, } from 'changed';", + "export Default, { Foo, Bar } from 'changed';", + "export Default, { Foo, Bar, } from 'changed';", + "export Default, { Foo as Bar, Baz } from 'changed';", + "export Default, { Foo as Bar, Baz, } from 'changed';", + "export Default, { Foo, Bar as Baz } from 'changed';", + "export Default, { Foo, Bar as Baz, } from 'changed';", + "export Default, { Foo as Bar, Baz as Qux } from 'changed';", + "export Default, { Foo as Bar, Baz as Qux, } from 'changed';", + "export Default, { Foo, Bar, Baz } from 'changed';", + "export Default, { Foo, Bar, Baz, } from 'changed';", + "export Default, { Foo as Bar, Baz, Qux } from 'changed';", + "export Default, { Foo as Bar, Baz, Qux, } from 'changed';", + "export Default, { Foo, Bar as Baz, Qux } from 'changed';", + "export Default, { Foo, Bar as Baz, Qux, } from 'changed';", + "export Default, { Foo, Bar, Baz as Qux } from 'changed';", + "export Default, { Foo, Bar, Baz as Qux, } from 'changed';", + "export Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "export Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "export Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "export Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "export Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "export Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", + "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", + "export Default , { } from 'changed';", + 'export "changed";', + 'export Default from "changed";', + 'export * as All from "changed";', + 'export { } from "changed";', + 'export { Foo } from "changed";', + 'export { Foo, } from "changed";', + 'export { Foo as Bar } from "changed";', + 'export { Foo as Bar, } from "changed";', + 'export { Foo, Bar } from "changed";', + 'export { Foo, Bar, } from "changed";', + 'export { Foo as Bar, Baz } from "changed";', + 'export { Foo as Bar, Baz, } from "changed";', + 'export { Foo, Bar as Baz } from "changed";', + 'export { Foo, Bar as Baz, } from "changed";', + 'export { Foo as Bar, Baz as Qux } from "changed";', + 'export { Foo as Bar, Baz as Qux, } from "changed";', + 'export { Foo, Bar, Baz } from "changed";', + 'export { Foo, Bar, Baz, } from "changed";', + 'export { Foo as Bar, Baz, Qux } from "changed";', + 'export { Foo as Bar, Baz, Qux, } from "changed";', + 'export { Foo, Bar as Baz, Qux } from "changed";', + 'export { Foo, Bar as Baz, Qux, } from "changed";', + 'export { Foo, Bar, Baz as Qux } from "changed";', + 'export { Foo, Bar, Baz as Qux, } from "changed";', + 'export { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'export { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'export { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'export { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'export { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'export { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'export { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', + 'export { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', + 'export Default, * as All from "changed";', + 'export Default, { } from "changed";', + 'export Default, { Foo } from "changed";', + 'export Default, { Foo, } from "changed";', + 'export Default, { Foo as Bar } from "changed";', + 'export Default, { Foo as Bar, } from "changed";', + 'export Default, { Foo, Bar } from "changed";', + 'export Default, { Foo, Bar, } from "changed";', + 'export Default, { Foo as Bar, Baz } from "changed";', + 'export Default, { Foo as Bar, Baz, } from "changed";', + 'export Default, { Foo, Bar as Baz } from "changed";', + 'export Default, { Foo, Bar as Baz, } from "changed";', + 'export Default, { Foo as Bar, Baz as Qux } from "changed";', + 'export Default, { Foo as Bar, Baz as Qux, } from "changed";', + 'export Default, { Foo, Bar, Baz } from "changed";', + 'export Default, { Foo, Bar, Baz, } from "changed";', + 'export Default, { Foo as Bar, Baz, Qux } from "changed";', + 'export Default, { Foo as Bar, Baz, Qux, } from "changed";', + 'export Default, { Foo, Bar as Baz, Qux } from "changed";', + 'export Default, { Foo, Bar as Baz, Qux, } from "changed";', + 'export Default, { Foo, Bar, Baz as Qux } from "changed";', + 'export Default, { Foo, Bar, Baz as Qux, } from "changed";', + 'export Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'export Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'export Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'export Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'export Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'export Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', + 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', + 'export Default from "Y";', + 'export * as All from \'z\';', + // export with support for new lines + "export { Foo,\n Bar }\n from 'changed';", + "export { \nFoo,\nBar,\n }\n from 'changed';", + "export { Foo as Bar,\n Baz\n }\n from 'changed';", + "export { \nFoo as Bar,\n Baz\n, }\n from 'changed';", + "export { Foo,\n Bar as Baz\n }\n from 'changed';", + "export { Foo,\n Bar as Baz,\n }\n from 'changed';", + "export { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", + "export { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", + "export { Foo,\n Bar,\n Baz }\n from 'changed';", + "export { Foo,\n Bar,\n Baz,\n }\n from 'changed';", + "export { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", + "export { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", + "export { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", + "export { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", + "export { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", + "export { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", + "export { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", + "export { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", + "export { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';", + "export { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';", + "export { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';", + "export { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';", + "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';", + "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';", + "export Default,\n * as All from 'changed';", + "export Default,\n { } from 'changed';", + "export Default,\n { Foo\n }\n from 'changed';", + "export Default,\n { Foo,\n }\n from 'changed';", + "export Default,\n { Foo as Bar\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n }\n from 'changed';", + "export Default,\n { Foo,\n Bar\n } from\n 'changed';", + "export Default,\n { Foo,\n Bar,\n } from\n 'changed';", + "export Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';", + "export Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';", + "export Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", + "export Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';", + "export Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", + "export Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", + "export Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", + "export Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", + "export Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';", + "export Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';", + "export Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';", + "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';", + "export Default\n , { } from 'changed';", + // require + 'require("changed")', + 'require("Y")', + 'require( \'z\' )', + 'require( "a")', + 'require("b" )', + '});', + ].join('\n')); }); }); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index c657aae5..0166681f 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -49,10 +49,6 @@ const validateOpts = declareOpts({ type: 'object', required: true, }, - getModuleId: { - type: 'function', - required: true, - } }); const getDependenciesValidateOpts = declareOpts({ @@ -102,7 +98,6 @@ class Resolver { shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios', }); - this._getModuleId = options.getModuleId; this._polyfillModuleNames = opts.polyfillModuleNames || []; } @@ -182,32 +177,36 @@ class Resolver { } const resolvedDeps = Object.create(null); + const resolvedDepsArr = []; return Promise.all( resolutionResponse.getResolvedDependencyPairs(module).map( ([depName, depModule]) => { if (depModule) { - resolvedDeps[depName] = this._getModuleId(depModule); + return depModule.getName().then(name => { + resolvedDeps[depName] = name; + resolvedDepsArr.push(name); + }); } } ) ).then(() => { - const replaceModuleId = (codeMatch, pre, quot, depName, post = '') => { - if (depName in resolvedDeps) { - const replacement = `${quot}${resolvedDeps[depName]}${quot}`; - return `${pre}${replacement} /* ${depName} */${post}`; + const relativizeCode = (codeMatch, pre, quot, depName, post) => { + const depId = resolvedDeps[depName]; + if (depId) { + return pre + quot + depId + post; } else { return codeMatch; } }; code = code - .replace(replacePatterns.IMPORT_RE, replaceModuleId) - .replace(replacePatterns.EXPORT_RE, replaceModuleId) - .replace(replacePatterns.REQUIRE_RE, replaceModuleId); + .replace(replacePatterns.IMPORT_RE, relativizeCode) + .replace(replacePatterns.EXPORT_RE, relativizeCode) + .replace(replacePatterns.REQUIRE_RE, relativizeCode); return module.getName().then(name => { - return {name, code, id: this._getModuleId(module)}; + return {name, code}; }); }); }); @@ -221,8 +220,8 @@ class Resolver { } return this.resolveRequires(resolutionResponse, module, code).then( - ({name, code, id}) => { - return {id, name, code: defineModuleCode(id, code, name)}; + ({name, code}) => { + return {name, code: defineModuleCode(name, code)}; }); } @@ -232,10 +231,10 @@ class Resolver { } -function defineModuleCode(moduleId, code, verboseName = '') { +function defineModuleCode(moduleName, code) { return [ `__d(`, - `${JSON.stringify(moduleId)} /* ${verboseName} */ ,`, + `'${moduleName}',`, 'function(global, require, module, exports) {', ` ${code}`, '\n});', diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index bc14a6e8..a5a7a55f 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -59,29 +59,18 @@ function requireImpl(id) { ); } - - // `require` calls int the require polyfill itself are not analyzed and - // replaced so that they use numeric module IDs. - // The systrace module will expose itself on the require function so that - // it can be used here. - // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) - const {Systrace} = require; try { // We must optimistically mark mod as initialized before running the factory to keep any // require cycles inside the factory from causing an infinite require loop. mod.isInitialized = true; - if (__DEV__) { - Systrace.beginEvent('JS_require_' + id); - } + __DEV__ && Systrace().beginEvent('JS_require_' + id); // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js mod.factory.call(global, global, require, mod.module, mod.module.exports); - if (__DEV__) { - Systrace.endEvent(); - } + __DEV__ && Systrace().endEvent(); } catch (e) { mod.hasError = true; mod.isInitialized = false; @@ -91,9 +80,15 @@ function requireImpl(id) { return mod.module.exports; } -if (__DEV__) { - require.Systrace = { beginEvent: () => {}, endEvent: () => {} }; -} +const Systrace = __DEV__ && (() => { + var _Systrace; + try { + _Systrace = require('Systrace'); + } catch (e) {} + + return _Systrace && _Systrace.beginEvent ? + _Systrace : { beginEvent: () => {}, endEvent: () => {} }; +}); global.__d = define; global.require = require; From 42e508efb80603fe9c484fed5d720664aec6df31 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Fri, 22 Jan 2016 13:54:24 -0800 Subject: [PATCH 546/936] Pass on the bundle name rather than the ID when getting transform options Summary: public This is needed to bring numerical module IDs back. The numerical ID of the main module will always be `0` and is therefore of no use when used as conditional. We have to pass on the name, which will be preserved, when requesting transform options. Reviewed By: martinbigio Differential Revision: D2855106 fb-gh-sync-id: f9bc40e753b1b283ce0b00068e3c9964a541ad9b --- react-packager/src/Bundler/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 1cc28f23..fda21ace 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -190,6 +190,7 @@ class Bundler { return this.getDependencies(entryFile, isDev, platform).then((response) => { Activity.endEvent(findEventId); bundle.setMainModuleId(response.mainModuleId); + bundle.setMainModuleName(response.mainModuleId); transformEventId = Activity.startEvent('transform'); const moduleSystemDeps = includeSystemDependencies @@ -396,7 +397,7 @@ class Bundler { return this._transformer.loadFileAndTransform( path.resolve(module.path), this._getTransformOptions( - {bundleEntry: bundle.getMainModuleId(), modulePath: module.path}, + {bundleEntry: bundle.getMainModuleName(), modulePath: module.path}, {hot: hot}, ), ); From c00871f08616ea95c81778802bed0b2c4b8f79bb Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 25 Jan 2016 05:32:49 -0800 Subject: [PATCH 547/936] Revert "Enable JSX files extension" Summary: This reverts commit 888749220dac26382412f2c38ac5f9205cc842e5. The original commit didn't handle cases like .(ios|android|native).js, and vjeux is in favor of standardizing on .js instead of custom extensions like .jsx or .es6 everywhere. > Unfortunately this pull request doesn't implement with .ios.jsx. But, when/if it does, it raises more questions like what happens if you have both ios.js and ios.jsx? > > Sorry to chime in super late but I actually think that this is harmful to support .jsx extension. We now live in a world where we have many transforms for es6, flow, jsx... Why would we add .jsx but not .flow or .es6. Supporting more extensions is only going to fragment tools even more. https://github.com/facebook/react-native/pull/5233#discussion_r49682279 Closes https://github.com/facebook/react-native/pull/5296 Reviewed By: svcscm Differential Revision: D2830784 Pulled By: bestander fb-gh-sync-id: 9a7a4745803acbcbc5dba176b971c629c904bacd --- .../src/DependencyResolver/DependencyGraph/ResolutionRequest.js | 2 -- react-packager/src/DependencyResolver/DependencyGraph/index.js | 2 +- react-packager/src/Server/index.js | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 2c18c95a..f6740439 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -350,8 +350,6 @@ class ResolutionRequest { file = potentialModulePath + '.native.js'; } else if (this._fastfs.fileExists(potentialModulePath + '.js')) { file = potentialModulePath + '.js'; - } else if (this._fastfs.fileExists(potentialModulePath + '.jsx')) { - file = potentialModulePath + '.jsx'; } else if (this._fastfs.fileExists(potentialModulePath + '.json')) { file = potentialModulePath + '.json'; } else { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 1278d2ce..1192159b 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -55,7 +55,7 @@ class DependencyGraph { platforms: platforms || [], preferNativePlatform: preferNativePlatform || false, cache, - extensions: extensions || ['js', 'jsx', 'json'], + extensions: extensions || ['js', 'json'], mocksPattern, extractRequires, shouldThrowOnUnresolvedErrors, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 3bb8a9c0..99a3472d 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -151,7 +151,6 @@ class Server { dir: dir, globs: [ '**/*.js', - '**/*.jsx', '**/*.json', ].concat(assetGlobs), }; From 2963af0befa255ef9d5edc0ab32d76e3e3173309 Mon Sep 17 00:00:00 2001 From: Mark Vayngrib Date: Mon, 25 Jan 2016 10:08:43 -0800 Subject: [PATCH 548/936] breaking test and fix for browser field mapping from package to file Summary: breaks on mappings like: ```json "browser": { "node-package": "./dir/browser.js" } ``` Closes https://github.com/facebook/react-native/pull/5505 Reviewed By: svcscm Differential Revision: D2860579 Pulled By: androidtrunkagent fb-gh-sync-id: 0d64c0999c47a6cbbf084cc8e0c8a6ea209b0880 --- .../DependencyGraph/ResolutionRequest.js | 31 +++++--- .../__tests__/DependencyGraph-test.js | 79 +++++++++++++++++++ 2 files changed, 101 insertions(+), 9 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index f6740439..87e00260 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -264,20 +264,33 @@ class ResolutionRequest { }); } + _resolveFileOrDir(fromModule, toModuleName) { + const potentialModulePath = isAbsolutePath(toModuleName) ? + toModuleName : + path.join(path.dirname(fromModule.path), toModuleName); + + return this._redirectRequire(fromModule, potentialModulePath).then( + realModuleName => this._tryResolve( + () => this._loadAsFile(realModuleName, fromModule, toModuleName), + () => this._loadAsDir(realModuleName, fromModule, toModuleName) + ) + ); + } + _resolveNodeDependency(fromModule, toModuleName) { if (toModuleName[0] === '.' || toModuleName[1] === '/') { - const potentialModulePath = isAbsolutePath(toModuleName) ? - toModuleName : - path.join(path.dirname(fromModule.path), toModuleName); - return this._redirectRequire(fromModule, potentialModulePath).then( - realModuleName => this._tryResolve( - () => this._loadAsFile(realModuleName, fromModule, toModuleName), - () => this._loadAsDir(realModuleName, fromModule, toModuleName) - ) - ); + return this._resolveFileOrDir(fromModule, toModuleName) } else { return this._redirectRequire(fromModule, toModuleName).then( realModuleName => { + if (realModuleName[0] === '.' || realModuleName[1] === '/') { + // derive absolute path /.../node_modules/fromModuleDir/realModuleName + let fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13 + let fromModuleDir = fromModule.path.slice(0, fromModule.path.indexOf('/', fromModuleParentIdx)) + let absPath = path.join(fromModuleDir, realModuleName) + return this._resolveFileOrDir(fromModule, absPath) + } + const searchQueue = []; for (let currDir = path.dirname(fromModule.path); currDir !== path.parse(fromModule.path).root; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index b1126a4c..6c88acb6 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -1774,6 +1774,85 @@ describe('DependencyGraph', function() { }); }); + pit('should support browser mapping of a package to a file ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + browser: { + 'node-package': './dir/browser.js', + }, + }, fieldName)), + 'index.js': 'require("./dir/ooga")', + 'dir': { + 'ooga.js': 'require("node-package")', + 'browser.js': 'some browser code', + }, + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', + }, + }, + }, + }); + + const dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: ['./dir/ooga'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/dir/ooga.js', + path: '/root/aPackage/dir/ooga.js', + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/dir/browser.js', + path: '/root/aPackage/dir/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + pit('should support browser mapping for packages ("' + fieldName + '")', function() { var root = '/root'; fs.__setMockFilesystem({ From e6916ec97202c938626196af49f9a608ddb0a554 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 25 Jan 2016 12:35:48 -0800 Subject: [PATCH 549/936] Improve react-native-xcode.sh integration Summary: Inspired by conversation in https://github.com/facebook/react-native/pull/5374, this PR improves `react-native-xcode.sh`: * No longer depends on global `react-native` binary * Gracefully handles missing `node` dependency and adds a new way to configure the path to `node` in non-standard installation environments This is how the error looks like: ![image](https://cloud.githubusercontent.com/assets/192222/12538882/3f9b5c3e-c29a-11e5-84fc-c7ccedf1c46a.png) Closes https://github.com/facebook/react-native/pull/5518 Reviewed By: svcscm Differential Revision: D2861116 Pulled By: frantic fb-gh-sync-id: 9a80eda6c844d066e34369b1cda503955171485b --- react-native-xcode.sh | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index 89886261..25533a85 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -23,12 +23,12 @@ case "$CONFIGURATION" in ;; esac +# Path to react-native folder inside node_modules +REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + # Xcode project file for React Native apps is located in ios/ subfolder cd .. -set -x -DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH - # Define NVM_DIR and source the nvm.sh setup script [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" @@ -43,10 +43,25 @@ if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then eval "$($HOME/.nodenv/bin/nodenv init -)" fi -# npm global install path may be a non-standard location -PATH="$(npm prefix -g)/bin:$PATH" +[ -z "$NODE_BINARY" ] && export NODE_BINARY="node" -react-native bundle \ +nodejs_not_found() +{ + echo "error: Can't find '$NODE_BINARY' binary to build React Native bundle" >&2 + echo "If you have non-standard nodejs installation, select your project in Xcode," >&2 + echo "find 'Build Phases' - 'Bundle React Native code and images'" >&2 + echo "and change NODE_BINARY to absolute path to your node executable" >&2 + echo "(you can find it by invoking 'which node' in the terminal)" >&2 + exit 2 +} + +type $NODE_BINARY >/dev/null 2>&1 || nodejs_not_found + +# Print commands before executing them (useful for troubleshooting) +set -x +DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH + +$NODE_BINARY $REACT_NATIVE_DIR/local-cli/cli.js bundle \ --entry-file index.ios.js \ --platform ios \ --dev $DEV \ From 7f5f8d10b53328e33a8eae22ccf691a86795a6e3 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 26 Jan 2016 08:20:18 -0800 Subject: [PATCH 550/936] Skip bundling for Simulator Summary: Following up on the conversation in https://www.prod.facebook.com/groups/reactnativeoss/permalink/1516993445263951/ I currently don't see any good reason to create an offline bundle for simulator builds. Closes https://github.com/facebook/react-native/pull/5519 Reviewed By: svcscm Differential Revision: D2859751 Pulled By: mkonicek fb-gh-sync-id: f70481e447e258f5531de773729fc31d9ebec6f7 --- react-native-xcode.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index 25533a85..c0ed7282 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -10,6 +10,13 @@ # This script is supposed to be invoked as part of Xcode build process # and relies on envoronment variables (including PWD) set by Xcode +# There is no point in creating an offline package for simulator builds +# because the packager is supposed to be running during development anyways +if [[ "$PLATFORM_NAME" = "iphonesimulator" ]]; then + echo "Skipping bundling for Simulator platform" + exit 0; +fi + case "$CONFIGURATION" in Debug) DEV=true From 2049eb99fd5bfcff3d441a1743534ba15e7744dd Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Tue, 26 Jan 2016 10:50:19 -0800 Subject: [PATCH 551/936] Expose `has` method on Cache. Reviewed By: davidaurelio Differential Revision: D2862315 fb-gh-sync-id: 03728a2593b477aef3bbfe01d42382893a05ea50 --- .../Cache/__tests__/Cache-test.js | 28 +++++++++++++++++++ .../src/DependencyResolver/Cache/index.js | 6 ++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js b/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js index f4c0f430..22d6825e 100644 --- a/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js +++ b/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js @@ -275,4 +275,32 @@ describe('Cache', () => { expect(fs.writeFile).toBeCalled(); }); }); + + describe('check for cache presence', () => { + it('synchronously resolves cache presence', () => { + fs.stat.mockImpl((file, callback) => + callback(null, { + mtime: { + getTime: () => {}, + }, + }) + ); + + var cache = new Cache({ + cacheKey: 'cache', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('banana') + ); + var file = '/rootDir/someFile'; + + return cache + .get(file, 'field', loaderCb) + .then(() => { + expect(cache.has(file)).toBe(true); + expect(cache.has(file, 'field')).toBe(true); + expect(cache.has(file, 'foo')).toBe(false); + }); + }); + }); }); diff --git a/react-packager/src/DependencyResolver/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js index 02384a20..40f16c77 100644 --- a/react-packager/src/DependencyResolver/Cache/index.js +++ b/react-packager/src/DependencyResolver/Cache/index.js @@ -51,7 +51,7 @@ class Cache { throw new Error('Use absolute paths'); } - var recordP = this._has(filepath, field) + var recordP = this.has(filepath, field) ? this._data[filepath].data[field] : this._set(filepath, field, loaderCb(filepath)); @@ -59,7 +59,7 @@ class Cache { } invalidate(filepath) { - if (this._has(filepath)) { + if (this.has(filepath)) { delete this._data[filepath]; } } @@ -68,7 +68,7 @@ class Cache { return this._persistCache(); } - _has(filepath, field) { + has(filepath, field) { return Object.prototype.hasOwnProperty.call(this._data, filepath) && (!field || Object.prototype.hasOwnProperty.call(this._data[filepath].data, field)); } From 4d834b3cb94de844a7428ebfdc2c33d0435094f4 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Tue, 26 Jan 2016 11:15:55 -0800 Subject: [PATCH 552/936] Fix some lint nits Reviewed By: voideanvalue Differential Revision: D2862332 fb-gh-sync-id: 19ab5e8a580137ffe6276b21a9018f7b322dd0cb --- .../DependencyGraph/ResolutionRequest.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 87e00260..ebe7c39a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -279,16 +279,16 @@ class ResolutionRequest { _resolveNodeDependency(fromModule, toModuleName) { if (toModuleName[0] === '.' || toModuleName[1] === '/') { - return this._resolveFileOrDir(fromModule, toModuleName) + return this._resolveFileOrDir(fromModule, toModuleName); } else { return this._redirectRequire(fromModule, toModuleName).then( realModuleName => { if (realModuleName[0] === '.' || realModuleName[1] === '/') { // derive absolute path /.../node_modules/fromModuleDir/realModuleName - let fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13 - let fromModuleDir = fromModule.path.slice(0, fromModule.path.indexOf('/', fromModuleParentIdx)) - let absPath = path.join(fromModuleDir, realModuleName) - return this._resolveFileOrDir(fromModule, absPath) + const fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13; + const fromModuleDir = fromModule.path.slice(0, fromModule.path.indexOf('/', fromModuleParentIdx)); + const absPath = path.join(fromModuleDir, realModuleName); + return this._resolveFileOrDir(fromModule, absPath); } const searchQueue = []; From d70519d5c54fa4e89e3ae3852f347a10d7d23899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 27 Jan 2016 07:40:55 -0800 Subject: [PATCH 553/936] Inline Require every module but the preloaded ones Reviewed By: javache Differential Revision: D2858983 fb-gh-sync-id: b73de54163d7b75891cb00be96ffb4556d3311a3 --- react-packager/src/Bundler/index.js | 65 ++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index fda21ace..b6ff9133 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -143,7 +143,9 @@ class Bundler { this._assetServer = opts.assetServer; if (opts.getTransformOptionsModulePath) { - this._getTransformOptionsModule = require(opts.getTransformOptionsModulePath); + this._transformOptionsModule = require( + opts.getTransformOptionsModulePath + ); } } @@ -224,6 +226,7 @@ class Bundler { response, module, platform, + isDev, hot, ).then(transformed => { if (bar) { @@ -289,7 +292,8 @@ class Bundler { bundle, response, module, - platform + platform, + isDev, ).then(transformed => { if (bar) { bar.tick(); @@ -326,15 +330,22 @@ class Bundler { } ); } else { - return this._transformer.loadFileAndTransform( - module.path, - // TODO(martinb): pass non null main (t9527509) - this._getTransformOptions({main: null}, {hot: true}), - ); + // TODO(martinb): pass non null main (t9527509) + return this._getTransformOptions( + {main: null, dev: true, platform: 'ios'}, // TODO(martinb): avoid hard-coding platform + {hot: true}, + ).then(options => { + return this._transformer.loadFileAndTransform(module.path, options); + }); } } invalidateFile(filePath) { + if (this._transformOptionsModule) { + this._transformOptionsModule.onFileChange && + this._transformOptionsModule.onFileChange(); + } + this._transformer.invalidateFile(filePath); } @@ -386,7 +397,7 @@ class Bundler { ); } - _transformModule(bundle, response, module, platform = null, hot = false) { + _transformModule(bundle, response, module, platform = null, dev = true, hot = false) { if (module.isAsset_DEPRECATED()) { return this._generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { @@ -394,13 +405,20 @@ class Bundler { } else if (module.isJSON()) { return generateJSONModule(module); } else { - return this._transformer.loadFileAndTransform( - path.resolve(module.path), - this._getTransformOptions( - {bundleEntry: bundle.getMainModuleName(), modulePath: module.path}, - {hot: hot}, - ), - ); + return this._getTransformOptions( + { + bundleEntry: bundle.getMainModuleName(), + platform: platform, + dev: dev, + modulePath: module.path, + }, + {hot: hot}, + ).then(options => { + return this._transformer.loadFileAndTransform( + path.resolve(module.path), + options, + ); + }); } } @@ -484,11 +502,20 @@ class Bundler { } _getTransformOptions(config, options) { - const transformerOptions = this._getTransformOptionsModule - ? this._getTransformOptionsModule(config) - : null; + const transformerOptions = this._transformOptionsModule + ? this._transformOptionsModule.get(Object.assign( + { + bundler: this, + platform: options.platform, + dev: options.dev, + }, + config, + )) + : Promise.resolve(null); - return {...options, ...transformerOptions}; + return transformerOptions.then(overrides => { + return {...options, ...overrides}; + }); } } From 1f2e16c0afee51e36e1eaccb384ee45253940ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 27 Jan 2016 14:55:02 -0800 Subject: [PATCH 554/936] Hot Loading Sourcemaps Summary: public To make sourcemaps work on Hot Loading work, we'll need to be able to serve them for each module that is dynamically replaced. To do so we introduced a new parameter to the bundler, namely `entryModuleOnly` to decide whether or not to process the full dependency tree or just the module associated to the entry file. Also we need to add `//sourceMappingURL` to the HMR updates so that in case of an error the runtime retrieves the sourcemaps for the file on which an error occurred from the server. Finally, we need to refactor a bit how we load the HMR updates into JSC. Unfortunately, if the code is eval'ed when an error is thrown, the line and column number are missing. This is a bug/missing feature in JSC. To walkaround the issue we need to eval the code on native. This adds a bit of complexity to HMR as for both platforms we'll have to have a thin module to inject code but I don't see any other alternative. when debugging this is not needed as Chrome supports sourceMappingURLs on eval'ed code Reviewed By: javache Differential Revision: D2841788 fb-gh-sync-id: ad9370d26894527a151cea722463e694c670227e --- react-packager/src/Bundler/Bundle.js | 10 ++- react-packager/src/Bundler/BundleBase.js | 4 + react-packager/src/Bundler/HMRBundle.js | 31 +++++--- .../src/Bundler/__tests__/Bundle-test.js | 4 +- react-packager/src/Bundler/index.js | 79 ++++++++++++++++--- .../src/Server/__tests__/Server-test.js | 4 + react-packager/src/Server/index.js | 9 +++ 7 files changed, 117 insertions(+), 24 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 2641550a..9700af08 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -206,7 +206,7 @@ class Bundle extends BundleBase { _getCombinedSourceMaps(options) { const result = { version: 3, - file: 'bundle.js', + file: this._getSourceMapFile(), sections: [], }; @@ -246,7 +246,7 @@ class Bundle extends BundleBase { const mappings = this._getMappings(); const map = { - file: 'bundle.js', + file: this._getSourceMapFile(), sources: _.pluck(super.getModules(), 'sourcePath'), version: 3, names: [], @@ -262,6 +262,12 @@ class Bundle extends BundleBase { return eTag; } + _getSourceMapFile() { + return this._sourceMapUrl + ? this._sourceMapUrl.replace('.map', '.bundle') + : 'bundle.js'; + } + _getMappings() { const modules = super.getModules(); diff --git a/react-packager/src/Bundler/BundleBase.js b/react-packager/src/Bundler/BundleBase.js index b9e1cbe3..79cbfefc 100644 --- a/react-packager/src/Bundler/BundleBase.js +++ b/react-packager/src/Bundler/BundleBase.js @@ -19,6 +19,10 @@ class BundleBase { this._mainModuleId = this._mainModuleName = undefined; } + isEmpty() { + return this._modules.length === 0 && this._assets.length === 0; + } + getMainModuleId() { return this._mainModuleId; } diff --git a/react-packager/src/Bundler/HMRBundle.js b/react-packager/src/Bundler/HMRBundle.js index fa8c95db..49a21dfd 100644 --- a/react-packager/src/Bundler/HMRBundle.js +++ b/react-packager/src/Bundler/HMRBundle.js @@ -8,12 +8,17 @@ */ 'use strict'; +const _ = require('underscore'); const BundleBase = require('./BundleBase'); const ModuleTransport = require('../lib/ModuleTransport'); class HMRBundle extends BundleBase { - constructor() { + constructor({sourceURLFn, sourceMappingURLFn}) { super(); + this._sourceURLFn = sourceURLFn + this._sourceMappingURLFn = sourceMappingURLFn; + this._sourceURLs = []; + this._sourceMappingURLs = []; } addModule(resolver, response, module, transformed) { @@ -21,14 +26,8 @@ class HMRBundle extends BundleBase { module, transformed.code, ).then(({name, code}) => { - code = ` - __accept( - '${name}', - function(global, require, module, exports) { - ${code} - } - ); - `; + // need to be in single line so that lines match on sourcemaps + code = `__accept(${JSON.stringify(name)}, function(global, require, module, exports) { ${code} });`; const moduleTransport = new ModuleTransport({ code, @@ -40,8 +39,22 @@ class HMRBundle extends BundleBase { }); super.addModule(moduleTransport); + this._sourceMappingURLs.push(this._sourceMappingURLFn(moduleTransport.sourcePath)); + this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath)); }); } + + getModulesCode() { + return this._modules.map(module => module.code); + } + + getSourceURLs() { + return this._sourceURLs; + } + + getSourceMappingURLs() { + return this._sourceMappingURLs; + } } module.exports = HMRBundle; diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 5e5fca22..b4152a7b 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -214,7 +214,7 @@ describe('Bundle', () => { const sourceMap = otherBundle.getSourceMap({dev: true}); expect(sourceMap).toEqual({ - file: 'bundle.js', + file: 'test_url', version: 3, sections: [ { offset: { line: 0, column: 0 }, map: { name: 'sourcemap foo' } }, @@ -340,7 +340,7 @@ describe('Bundle', () => { function genSourceMap(modules) { - var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3}); + var sourceMapGen = new SourceMapGenerator({file: 'test_url', version: 3}); var bundleLineNo = 0; for (var i = 0; i < modules.length; i++) { var module = modules[i]; diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index b6ff9133..d2852d44 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -166,9 +166,58 @@ class Bundler { }); } + _sourceHMRURL(platform, path) { + return this._hmrURL( + 'http://localhost:8081', // TODO: (martinb) avoid hardcoding + platform, + 'bundle', + path, + ); + } + + _sourceMappingHMRURL(platform, path) { + // Chrome expects `sourceURL` when eval'ing code + return this._hmrURL( + '\/\/# sourceURL=', + platform, + 'map', + path, + ); + } + + _hmrURL(prefix, platform, extensionOverride, path) { + const matchingRoot = this._projectRoots.find(root => path.startsWith(root)); + + if (!matchingRoot) { + throw new Error('No matching project root for ', path); + } + + const extensionStart = path.lastIndexOf('.'); + let resource = path.substring( + matchingRoot.length, + extensionStart !== -1 ? extensionStart : undefined, + ); + + const extension = extensionStart !== -1 + ? path.substring(extensionStart + 1) + : null; + + return ( + prefix + resource + + '.' + extensionOverride + '?' + + 'platform=' + platform + '&runModule=false&entryModuleOnly=true&hot=true' + ); + } + bundleForHMR(options) { return this._bundle({ - bundle: new HMRBundle(), + bundle: new HMRBundle({ + sourceURLFn: this._sourceHMRURL.bind(this, options.platform), + sourceMappingURLFn: this._sourceMappingHMRURL.bind( + this, + options.platform, + ), + }), hot: true, ...options, }); @@ -185,6 +234,7 @@ class Bundler { platform, unbundle: isUnbundle, hot: hot, + entryModuleOnly, }) { const findEventId = Activity.startEvent('find dependencies'); let transformEventId; @@ -195,18 +245,25 @@ class Bundler { bundle.setMainModuleName(response.mainModuleId); transformEventId = Activity.startEvent('transform'); - const moduleSystemDeps = includeSystemDependencies - ? this._resolver.getModuleSystemDependencies( - { dev: isDev, platform, isUnbundle } - ) - : []; + let dependencies; + if (entryModuleOnly) { + dependencies = response.dependencies.filter(module => + module.path.endsWith(entryFile) + ); + } else { + const moduleSystemDeps = includeSystemDependencies + ? this._resolver.getModuleSystemDependencies( + { dev: isDev, platform, isUnbundle } + ) + : []; - const modulesToProcess = modules || response.dependencies; - const dependencies = moduleSystemDeps.concat(modulesToProcess); + const modulesToProcess = modules || response.dependencies; + const dependencies = moduleSystemDeps.concat(modulesToProcess); - bundle.setNumPrependedModules && bundle.setNumPrependedModules( - response.numPrependedDependencies + moduleSystemDeps.length - ); + bundle.setNumPrependedModules && bundle.setNumPrependedModules( + response.numPrependedDependencies + moduleSystemDeps.length + ); + } let bar; if (process.stdout.isTTY) { diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 123134f8..668e162f 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -144,6 +144,7 @@ describe('processRequest', () => { platform: undefined, runBeforeMainModule: ['InitializeJavaScriptAppEngine'], unbundle: false, + entryModuleOnly: false, }); }); }); @@ -165,6 +166,7 @@ describe('processRequest', () => { platform: 'ios', runBeforeMainModule: ['InitializeJavaScriptAppEngine'], unbundle: false, + entryModuleOnly: false, }); }); }); @@ -321,6 +323,7 @@ describe('processRequest', () => { platform: undefined, runBeforeMainModule: ['InitializeJavaScriptAppEngine'], unbundle: false, + entryModuleOnly: false, }) ); }); @@ -341,6 +344,7 @@ describe('processRequest', () => { platform: undefined, runBeforeMainModule: ['InitializeJavaScriptAppEngine'], unbundle: false, + entryModuleOnly: false, }) ); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 99a3472d..559fb302 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -118,6 +118,10 @@ const bundleOpts = declareOpts({ type: 'boolean', default: false, }, + entryModuleOnly: { + type: 'boolean', + default: false, + }, }); const dependencyOpts = declareOpts({ @@ -527,6 +531,11 @@ class Server { false ), platform: platform, + entryModuleOnly: this._getBoolOptionFromQuery( + urlObj.query, + 'entryModuleOnly', + false, + ), }; } From 1de20b01355fba7b1763675dc357a8cc002678fa Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Wed, 27 Jan 2016 18:46:23 -0800 Subject: [PATCH 555/936] Limit mocks shared with a resolution response Reviewed By: davidaurelio Differential Revision: D2872340 fb-gh-sync-id: a76b580ff670115cd4bd0098e7b9f31110239369 --- .../DependencyGraph/ResolutionRequest.js | 19 ++++++++++--------- .../__tests__/DependencyGraph-test.js | 5 +++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index ebe7c39a..58dcc7d1 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -104,10 +104,9 @@ class ResolutionRequest { } getOrderedDependencies(response, mocksPattern) { - return this._getAllMocks(mocksPattern).then(mocks => { - response.setMocks(mocks); - + return this._getAllMocks(mocksPattern).then(allMocks => { const entry = this._moduleCache.getModule(this._entryPath); + const mocks = Object.create(null); const visited = Object.create(null); visited[entry.hash()] = true; @@ -118,13 +117,14 @@ class ResolutionRequest { depNames.map(name => this.resolveDependency(mod, name)) ).then((dependencies) => [depNames, dependencies]) ).then(([depNames, dependencies]) => { - if (mocks) { + if (allMocks) { return mod.getName().then(name => { - if (mocks[name]) { + if (allMocks[name]) { const mockModule = - this._moduleCache.getModule(mocks[name]); + this._moduleCache.getModule(allMocks[name]); depNames.push(name); dependencies.push(mockModule); + mocks[name] = allMocks[name]; } return [depNames, dependencies]; }); @@ -141,8 +141,9 @@ class ResolutionRequest { // module backing them. If a dependency cannot be found but there // exists a mock with the desired ID, resolve it and add it as // a dependency. - if (mocks && mocks[name]) { - const mockModule = this._moduleCache.getModule(mocks[name]); + if (allMocks && allMocks[name]) { + const mockModule = this._moduleCache.getModule(allMocks[name]); + mocks[name] = allMocks[name]; return filteredPairs.push([name, mockModule]); } @@ -172,7 +173,7 @@ class ResolutionRequest { }); }; - return collect(entry); + return collect(entry).then(() => response.setMocks(mocks)); }); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 6c88acb6..464985f6 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -4117,11 +4117,11 @@ describe('DependencyGraph', function() { return dgraph.getDependencies('/root/index.js') .then(response => response.finalize()) .then(response => { - expect(response.mocks).toBe(null); + expect(response.mocks).toEqual({}); }); }); - pit('retrieves a list of all mocks in the system', () => { + pit('retrieves a list of all required mocks', () => { var root = '/root'; fs.__setMockFilesystem({ 'root': { @@ -4133,6 +4133,7 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule b', ' */', + 'require("A");', ].join('\n'), }, }); From 370b7b25a34468b9cd10aafcb87e9a243cdaec40 Mon Sep 17 00:00:00 2001 From: Adam Miskiewicz Date: Thu, 28 Jan 2016 15:18:04 -0800 Subject: [PATCH 556/936] Fixing typo in Bundler Summary: With an upgraded Babel dependency, failing tests correctly found this typo. It does not currently cause a test failure in the current version of master, however, which is concerning. I found it when testing #5214. cc martinbigio just because it was your code :) Closes https://github.com/facebook/react-native/pull/5601 Reviewed By: svcscm Differential Revision: D2876386 Pulled By: androidtrunkagent fb-gh-sync-id: 378da39be79ac1b9a950ea9a7442ac24c0c3a87d --- react-packager/src/Bundler/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index d2852d44..b0ccc235 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -258,7 +258,7 @@ class Bundler { : []; const modulesToProcess = modules || response.dependencies; - const dependencies = moduleSystemDeps.concat(modulesToProcess); + dependencies = moduleSystemDeps.concat(modulesToProcess); bundle.setNumPrependedModules && bundle.setNumPrependedModules( response.numPrependedDependencies + moduleSystemDeps.length From 9fb17627844c031e84792a47e441d9b4fb319d3b Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 28 Jan 2016 16:28:37 -0800 Subject: [PATCH 557/936] Add transform option to module / module cache Summary: public This adds an option to `Module` (and its callers) that allows to transform code before extracting dependencies. The transform function has to return a promise that resolves to an object with `code`, and optionally `dependencies` and/or `asyncDependencies` properties, if standard dependency extraction cannot be applied Reviewed By: cpojer Differential Revision: D2870437 fb-gh-sync-id: 806d24ba16b1693d838a3fa747d82be9dc6ccf00 --- .../DependencyGraph/index.js | 18 +- .../src/DependencyResolver/Module.js | 34 ++- .../src/DependencyResolver/ModuleCache.js | 11 +- .../__tests__/Module-test.js | 234 ++++++++++++------ 4 files changed, 201 insertions(+), 96 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 1192159b..023fca50 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -42,6 +42,7 @@ class DependencyGraph { extensions, mocksPattern, extractRequires, + transformCode, shouldThrowOnUnresolvedErrors = () => true, }) { this._opts = { @@ -54,13 +55,13 @@ class DependencyGraph { providesModuleNodeModules, platforms: platforms || [], preferNativePlatform: preferNativePlatform || false, - cache, extensions: extensions || ['js', 'json'], mocksPattern, extractRequires, shouldThrowOnUnresolvedErrors, + transformCode }; - this._cache = this._opts.cache; + this._cache = cache; this._helpers = new DependencyGraphHelpers(this._opts); this.load().catch((err) => { // This only happens at initialization. Live errors are easier to recover from. @@ -98,12 +99,13 @@ class DependencyGraph { this._fastfs.on('change', this._processFileChange.bind(this)); - this._moduleCache = new ModuleCache( - this._fastfs, - this._cache, - this._opts.extractRequires, - this._helpers - ); + this._moduleCache = new ModuleCache({ + fastfs: this._fastfs, + cache: this._cache, + extractRequires: this._opts.extractRequires, + transformCode: this._opts.transformCode, + depGraphHelpers: this._helpers, + }); this._hasteMap = new HasteMap({ fastfs: this._fastfs, diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 09fbe793..df6ed710 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -15,7 +15,15 @@ const extractRequires = require('./lib/extractRequires'); class Module { - constructor({ file, fastfs, moduleCache, cache, extractor, depGraphHelpers }) { + constructor({ + file, + fastfs, + moduleCache, + cache, + extractor = extractRequires, + transformCode, + depGraphHelpers, + }) { if (!isAbsolutePath(file)) { throw new Error('Expected file to be absolute path but got ' + file); } @@ -27,6 +35,7 @@ class Module { this._moduleCache = moduleCache; this._cache = cache; this._extractor = extractor; + this._transformCode = transformCode; this._depGraphHelpers = depGraphHelpers; } @@ -38,6 +47,10 @@ class Module { ); } + getCode() { + return this.read().then(({code}) => code); + } + getName() { return this._cache.get( this.path, @@ -114,13 +127,22 @@ class Module { if (this.isJSON() || 'extern' in moduleDocBlock) { data.dependencies = []; data.asyncDependencies = []; + data.code = content; + return data; } else { - var dependencies = (this._extractor || extractRequires)(content).deps; - data.dependencies = dependencies.sync; - data.asyncDependencies = dependencies.async; - } + const transformCode = this._transformCode; + const codePromise = transformCode + ? transformCode(this, content) + : Promise.resolve({code: content}); - return data; + return codePromise.then(({code, dependencies, asyncDependencies}) => { + const {deps} = this._extractor(code); + data.dependencies = dependencies || deps.sync; + data.asyncDependencies = asyncDependencies || deps.async; + data.code = code; + return data; + }); + } }); } diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index a4836d0a..fac1820b 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -7,13 +7,21 @@ const path = require('path'); class ModuleCache { - constructor(fastfs, cache, extractRequires, depGraphHelpers) { + constructor({ + fastfs, + cache, + extractRequires, + transformCode, + depGraphHelpers, + }) { this._moduleCache = Object.create(null); this._packageCache = Object.create(null); this._fastfs = fastfs; this._cache = cache; this._extractRequires = extractRequires; + this._transformCode = transformCode; this._depGraphHelpers = depGraphHelpers; + fastfs.on('change', this._processFileChange.bind(this)); } @@ -26,6 +34,7 @@ class ModuleCache { moduleCache: this, cache: this._cache, extractor: this._extractRequires, + transformCode: this._transformCode, depGraphHelpers: this._depGraphHelpers, }); } diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index f0c9cbd2..947fa0b1 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -26,74 +26,74 @@ const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelper const Promise = require('promise'); const fs = require('graceful-fs'); +function mockIndexFile(indexJs) { + fs.__setMockFilesystem({'root': {'index.js': indexJs}}); +} + describe('Module', () => { const fileWatcher = { on: () => this, isWatchman: () => Promise.resolve(false), }; + const fileName = '/root/index.js'; - const Cache = jest.genMockFn(); - Cache.prototype.get = jest.genMockFn().mockImplementation( - (filepath, field, cb) => cb(filepath) - ); - Cache.prototype.invalidate = jest.genMockFn(); - Cache.prototype.end = jest.genMockFn(); + let cache, fastfs; + const createCache = () => ({ + get: jest.genMockFn().mockImplementation( + (filepath, field, cb) => cb(filepath) + ), + invalidate: jest.genMockFn(), + end: jest.genMockFn(), + }); + + const createModule = (options) => + new Module({ + cache, + fastfs, + file: fileName, + depGraphHelpers: new DependencyGraphHelpers(), + moduleCache: new ModuleCache({fastfs, cache}), + ...options, + }); + + beforeEach(function(done) { + cache = createCache(); + fastfs = new Fastfs( + 'test', + ['/root'], + fileWatcher, + {crawling: Promise.resolve([fileName]), ignore: []}, + ); + + fastfs.build().then(done); + }); describe('Async Dependencies', () => { function expectAsyncDependenciesToEqual(expected) { - const fastfs = new Fastfs( - 'test', - ['/root'], - fileWatcher, - {crawling: Promise.resolve(['/root/index.js']), ignore: []}, + const module = createModule(); + return module.getAsyncDependencies().then(actual => + expect(actual).toEqual(expected) ); - const cache = new Cache(); - - return fastfs.build().then(() => { - const module = new Module({ - file: '/root/index.js', - fastfs, - moduleCache: new ModuleCache(fastfs, cache), - cache: cache, - depGraphHelpers: new DependencyGraphHelpers(), - }); - - return module.getAsyncDependencies().then(actual => - expect(actual).toEqual(expected) - ); - }); } pit('should recognize single dependency', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'System.' + 'import("dep1")', - }, - }); + mockIndexFile('System.' + 'import("dep1")'); return expectAsyncDependenciesToEqual([['dep1']]); }); pit('should parse single quoted dependencies', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'System.' + 'import(\'dep1\')', - }, - }); + mockIndexFile('System.' + 'import(\'dep1\')'); return expectAsyncDependenciesToEqual([['dep1']]); }); pit('should parse multiple async dependencies on the same module', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - 'System.' + 'import("dep1")', - 'System.' + 'import("dep2")', - ].join('\n'), - }, - }); + mockIndexFile([ + 'System.' + 'import("dep1")', + 'System.' + 'import("dep2")', + ].join('\n')); return expectAsyncDependenciesToEqual([ ['dep1'], @@ -102,53 +102,125 @@ describe('Module', () => { }); pit('parse fine new lines', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'System.' + 'import(\n"dep1"\n)', - }, - }); + mockIndexFile('System.' + 'import(\n"dep1"\n)'); return expectAsyncDependenciesToEqual([['dep1']]); }); }); + describe('Code', () => { + const fileContents = 'arbitrary(code)'; + beforeEach(function() { + mockIndexFile(fileContents); + }); + + pit('exposes file contents as `code` property on the data exposed by `read()`', () => + createModule().read().then(({code}) => + expect(code).toBe(fileContents)) + ); + + pit('exposes file contes via the `getCode()` method', () => + createModule().getCode().then(code => + expect(code).toBe(fileContents)) + ); + + pit('does not save the code in the cache', () => + createModule().getCode().then(() => + expect(cache.get).not.toBeCalled() + ) + ); + }); + describe('Extrators', () => { - function createModuleWithExtractor(extractor) { - const fastfs = new Fastfs( - 'test', - ['/root'], - fileWatcher, - {crawling: Promise.resolve(['/root/index.js']), ignore: []}, - ); - const cache = new Cache(); - - return fastfs.build().then(() => { - return new Module({ - file: '/root/index.js', - fastfs, - moduleCache: new ModuleCache(fastfs, cache), - cache, - extractor, - depGraphHelpers: new DependencyGraphHelpers(), - }); - }); - } - pit('uses custom require extractors if specified', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': '', - }, + mockIndexFile(''); + const module = createModule({ + extractor: code => ({deps: {sync: ['foo', 'bar']}}), }); - return createModuleWithExtractor( - code => ({deps: {sync: ['foo', 'bar']}}) - ).then(module => - module.getDependencies().then(actual => - expect(actual).toEqual(['foo', 'bar']) - ) - ); + return module.getDependencies().then(actual => + expect(actual).toEqual(['foo', 'bar'])); + }); + }); + + describe('Custom Code Transform', () => { + let transformCode; + const fileContents = 'arbitrary(code);'; + const exampleCode = ` + require('a'); + System.import('b'); + require('c');`; + + beforeEach(function() { + transformCode = jest.genMockFn(); + mockIndexFile(fileContents); + transformCode.mockReturnValue(Promise.resolve({code: ''})); + }); + + pit('passes the module and file contents to the transform function when reading', () => { + const module = createModule({transformCode}); + return module.read() + .then(() => { + expect(transformCode).toBeCalledWith(module, fileContents); + }); + }); + + pit('uses the code that `transformCode` resolves to to extract dependencies', () => { + transformCode.mockReturnValue(Promise.resolve({code: exampleCode})); + const module = createModule({transformCode}); + + return Promise.all([ + module.getDependencies(), + module.getAsyncDependencies(), + ]).then(([dependencies, asyncDependencies]) => { + expect(dependencies).toEqual(['a', 'c']); + expect(asyncDependencies).toEqual([['b']]); + }); + }); + + pit('uses dependencies that `transformCode` resolves to, instead of extracting them', () => { + const mockedDependencies = ['foo', 'bar']; + transformCode.mockReturnValue(Promise.resolve({ + code: exampleCode, + dependencies: mockedDependencies, + })); + const module = createModule({transformCode}); + + return Promise.all([ + module.getDependencies(), + module.getAsyncDependencies(), + ]).then(([dependencies, asyncDependencies]) => { + expect(dependencies).toEqual(mockedDependencies); + expect(asyncDependencies).toEqual([['b']]); + }); + }); + + pit('uses async dependencies that `transformCode` resolves to, instead of extracting them', () => { + const mockedAsyncDependencies = [['foo', 'bar'], ['baz']]; + transformCode.mockReturnValue(Promise.resolve({ + code: exampleCode, + asyncDependencies: mockedAsyncDependencies, + })); + const module = createModule({transformCode}); + + return Promise.all([ + module.getDependencies(), + module.getAsyncDependencies(), + ]).then(([dependencies, asyncDependencies]) => { + expect(dependencies).toEqual(['a', 'c']); + expect(asyncDependencies).toEqual(mockedAsyncDependencies); + }); + }); + + pit('exposes the transformed code rather than the raw file contents', () => { + transformCode.mockReturnValue(Promise.resolve({code: exampleCode})); + const module = createModule({transformCode}); + return Promise.all([module.read(), module.getCode()]) + .then(([data, code]) => { + expect(data.code).toBe(exampleCode); + expect(code).toBe(exampleCode); + }); }); }); }); From 82d4cbe7dd8e25964fdc03f97db6e7a4de0ae261 Mon Sep 17 00:00:00 2001 From: Dave Miller Date: Thu, 28 Jan 2016 16:35:56 -0800 Subject: [PATCH 558/936] Fix bundler to not do as many passes in optimization Reviewed By: davidaurelio Differential Revision: D2876894 fb-gh-sync-id: b652ff77dd442a82c16f3b446c931fb985a2efcd --- .../whole-program-optimisations/dead-module-elimination.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js b/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js index 1d6c8f34..b5f33b4e 100644 --- a/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js +++ b/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js @@ -135,10 +135,12 @@ module.exports = function () { visitor: { Program(path) { path.traverse(firstPass); - while (hasDeadModules(requires)) { + var counter = 0; + while (hasDeadModules(requires) && counter < 3) { _requires = requires; requires = {}; path.traverse(secondPass); + counter++; } } } From e8472b44f4a71205d39096e338eeeb802c2ee713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 28 Jan 2016 17:02:02 -0800 Subject: [PATCH 559/936] Move HMR to external transformer Summary: public `babel-plugin-react-transform` doesn't support to run on transformed code (it doesn't detect some react components). The reason why HMR was originally on the internal transform was so that it worked with non JS code (TypeScript, Coffeescript, etc), but looks like this is not very popupar in the community, maybe because the JS ecosystem is getting better everyday. So long story short, for now lets move HMR to the external transformer. This should also give us a perf boost. Reviewed By: davidaurelio Differential Revision: D2870973 fb-gh-sync-id: 68ca8d0540b88b9516b9fef10643f93fc9b530d2 --- .../JSTransformer/__tests__/worker-test.js | 6 +++--- react-packager/src/transforms/index.js | 21 +------------------ transformer.js | 12 +++++++++++ 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/react-packager/src/JSTransformer/__tests__/worker-test.js b/react-packager/src/JSTransformer/__tests__/worker-test.js index fdb67d9d..f81907c2 100644 --- a/react-packager/src/JSTransformer/__tests__/worker-test.js +++ b/react-packager/src/JSTransformer/__tests__/worker-test.js @@ -23,7 +23,7 @@ describe('Resolver', function() { }); describe('when no external transform is provided', () => { - it('should invoke internal transform if available', () => { + xit('should invoke internal transform if available', () => { transform({ sourceCode: 'code', filename: 'test', @@ -43,7 +43,7 @@ describe('Resolver', function() { }); describe('when external transform is provided', () => { - it('should invoke both transformers if internal is available', () => { + xit('should invoke both transformers if internal is available', () => { transform({ sourceCode: code, filename: 'test', @@ -75,7 +75,7 @@ describe('Resolver', function() { expect(babel.transform.mock.calls.length).toBe(1); }); - it('should pipe errors through transform pipeline', () => { + xit('should pipe errors through transform pipeline', () => { const error = new Error('transform error'); babel.transform.mockImpl((source, options) => { throw error; diff --git a/react-packager/src/transforms/index.js b/react-packager/src/transforms/index.js index 33cb2462..37f17ccd 100644 --- a/react-packager/src/transforms/index.js +++ b/react-packager/src/transforms/index.js @@ -9,25 +9,6 @@ 'use strict'; exports.getAll = function(options) { - var plugins = []; - if (options.hot) { - plugins = plugins.concat([ - [ - 'react-transform', - { - transforms: [{ - transform: 'react-transform-hmr/lib/index.js', - imports: ['React'], - locals: ['module'], - }] - }, - ], - 'transform-es2015-block-scoping', - 'transform-es2015-constants', - ['transform-es2015-modules-commonjs', {strict: false, allowTopLevelThis: true}], - ]); - } - - return plugins; + return []; }; diff --git a/transformer.js b/transformer.js index df6009bd..a38fdb0d 100644 --- a/transformer.js +++ b/transformer.js @@ -33,6 +33,18 @@ function transform(src, filename, options) { }; const config = Object.assign({}, babelRC, extraConfig); + if (options.hot) { + extraPlugins.push([ + 'react-transform', + { + transforms: [{ + transform: 'react-transform-hmr/lib/index.js', + imports: ['React'], + locals: ['module'], + }] + }, + ]); + } if (options.inlineRequires) { extraPlugins.push(inlineRequires); From df48cd1163bb1b33252e994f41c3b35576f8125f Mon Sep 17 00:00:00 2001 From: Dmitry Soshnikov Date: Thu, 28 Jan 2016 17:20:39 -0800 Subject: [PATCH 560/936] Add Object.entries and Object.values polyfill Reviewed By: martinbigio Differential Revision: D2876430 fb-gh-sync-id: 25c7680a71c9187beb937f6faf030a83c9c81fc5 --- react-packager/src/Resolver/index.js | 1 + .../src/Resolver/polyfills/Object.es7.js | 56 ++++++++ .../polyfills/__tests__/Object.es7-test.js | 120 ++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 react-packager/src/Resolver/polyfills/Object.es7.js create mode 100644 react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 0166681f..4cb4707f 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -157,6 +157,7 @@ class Resolver { path.join(__dirname, 'polyfills/String.prototype.es6.js'), path.join(__dirname, 'polyfills/Array.prototype.es6.js'), path.join(__dirname, 'polyfills/Array.es6.js'), + path.join(__dirname, 'polyfills/Object.es7.js'), path.join(__dirname, 'polyfills/babelHelpers.js'), ].concat(this._polyfillModuleNames); diff --git a/react-packager/src/Resolver/polyfills/Object.es7.js b/react-packager/src/Resolver/polyfills/Object.es7.js new file mode 100644 index 00000000..597c49e7 --- /dev/null +++ b/react-packager/src/Resolver/polyfills/Object.es7.js @@ -0,0 +1,56 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @provides Object.es7 + * @polyfill + */ + +(function() { + + const hasOwnProperty = Object.prototype.hasOwnProperty; + + /** + * Returns an array of the given object's own enumerable entries. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries + * + */ + if (typeof Object.entries !== 'function') { + Object.entries = function(object) { + // `null` and `undefined` values are not allowed. + if (object == null) { + throw new TypeError('Object.entries called on non-object'); + } + + let entries = []; + for (let key in object) { + if (hasOwnProperty.call(object, key)) { + entries.push([key, object[key]]); + } + } + return entries; + }; + } + + /** + * Returns an array of the given object's own enumerable entries. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values + * + */ + if (typeof Object.values !== 'function') { + Object.values = function(object) { + // `null` and `undefined` values are not allowed. + if (object == null) { + throw new TypeError('Object.values called on non-object'); + } + + let values = []; + for (let key in object) { + if (hasOwnProperty.call(object, key)) { + values.push(object[key]); + } + } + return values; + }; + } + +})(); \ No newline at end of file diff --git a/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js b/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js new file mode 100644 index 00000000..ee7782eb --- /dev/null +++ b/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js @@ -0,0 +1,120 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @emails oncall+jsinfra + */ + + /* eslint-disable fb-www/object-create-only-one-param */ + +jest.autoMockOff(); + +describe('Object (ES7)', () => { + beforeEach(() => { + delete Object.entries; + delete Object.values; + jest.resetModuleRegistry(); + require('../Object.es7'); + }); + + describe('Object.entries', () => { + it('should have a length of 1', () => { + expect(Object.entries.length).toBe(1); + }); + + it('should check for type', () => { + expect(Object.entries.bind(null, null)).toThrow(TypeError( + 'Object.entries called on non-object' + )); + expect(Object.entries.bind(null, undefined)).toThrow(TypeError( + 'Object.entries called on non-object' + )); + expect(Object.entries.bind(null, [])).not.toThrow(); + expect(Object.entries.bind(null, () => {})).not.toThrow(); + expect(Object.entries.bind(null, {})).not.toThrow(); + expect(Object.entries.bind(null, 'abc')).not.toThrow(); + }); + + it('should return enumerable entries', () => { + let foo = Object.defineProperties({}, { + x: {value: 10, enumerable: true}, + y: {value: 20}, + }); + + expect(Object.entries(foo)).toEqual([['x', 10]]); + + let bar = {x: 10, y: 20}; + expect(Object.entries(bar)).toEqual([['x', 10], ['y', 20]]); + }); + + it('should work with proto-less objects', () => { + let foo = Object.create(null, { + x: {value: 10, enumerable: true}, + y: {value: 20}, + }); + + expect(Object.entries(foo)).toEqual([['x', 10]]); + }); + + it('should return only own entries', () => { + let foo = Object.create({z: 30}, { + x: {value: 10, enumerable: true}, + y: {value: 20}, + }); + + expect(Object.entries(foo)).toEqual([['x', 10]]); + }); + + it('should convert to object primitive string', () => { + expect(Object.entries('ab')).toEqual([['0', 'a'], ['1', 'b']]); + }); + }); + + describe('Object.values', () => { + it('should have a length of 1', () => { + expect(Object.values.length).toBe(1); + }); + + it('should check for type', () => { + expect(Object.values.bind(null, null)).toThrow(TypeError( + 'Object.values called on non-object' + )); + expect(Object.values.bind(null, [])).not.toThrow(); + expect(Object.values.bind(null, () => {})).not.toThrow(); + expect(Object.values.bind(null, {})).not.toThrow(); + }); + + it('should return enumerable values', () => { + let foo = Object.defineProperties({}, { + x: {value: 10, enumerable: true}, + y: {value: 20}, + }); + + expect(Object.values(foo)).toEqual([10]); + + let bar = {x: 10, y: 20}; + expect(Object.values(bar)).toEqual([10, 20]); + }); + + it('should work with proto-less objects', () => { + let foo = Object.create(null, { + x: {value: 10, enumerable: true}, + y: {value: 20}, + }); + + expect(Object.values(foo)).toEqual([10]); + }); + + it('should return only own values', () => { + let foo = Object.create({z: 30}, { + x: {value: 10, enumerable: true}, + y: {value: 20}, + }); + + expect(Object.values(foo)).toEqual([10]); + }); + + it('should convert to object primitive string', () => { + expect(Object.values('ab')).toEqual(['a', 'b']); + }); + }); +}); \ No newline at end of file From fb09783e8184d86ab27572ec271ec9c4fe7c2659 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 28 Jan 2016 21:18:10 -0800 Subject: [PATCH 561/936] Add partial reading of files to fastfs Summary: public Adds the ability to read files partially with `readWhile` to `Fastfs` This feature will be used by for building the haste map, where we only need to read the doc block (if present) to extract the provided module name. Reviewed By: martinbigio Differential Revision: D2878093 fb-gh-sync-id: 219cf6d5962b89eeb42c728e176bf9fcf6a67e9c --- .../DependencyResolver/__tests__/fastfs-data | 39 +++++++++ .../__tests__/fastfs-integrated-test.js | 86 +++++++++++++++++++ .../src/DependencyResolver/fastfs.js | 47 ++++++++++ 3 files changed, 172 insertions(+) create mode 100644 react-packager/src/DependencyResolver/__tests__/fastfs-data create mode 100644 react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js diff --git a/react-packager/src/DependencyResolver/__tests__/fastfs-data b/react-packager/src/DependencyResolver/__tests__/fastfs-data new file mode 100644 index 00000000..fe2c6388 --- /dev/null +++ b/react-packager/src/DependencyResolver/__tests__/fastfs-data @@ -0,0 +1,39 @@ +/** + * 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. + * An arbitrary module header + * @providesModule + */ + + const some: string = 'arbitrary code'; + const containing: string = 'ūñïčødę'; + +/** + * An arbitrary class that extends some thing + * It exposes a random number, which may be reset at the callers discretion + */ +class Arbitrary extends Something { + constructor() { + this.reset(); + } + + /** + * Returns the random number + * @returns number + */ + get random(): number { + return this._random; + } + + /** + * Re-creates the internal random number + * @returns void + */ + reset(): void { + this._random = Math.random(); + } +} diff --git a/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js b/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js new file mode 100644 index 00000000..f7fb3558 --- /dev/null +++ b/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js @@ -0,0 +1,86 @@ +/** + * 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.autoMockOff(); + +const Fastfs = require('../fastfs'); + +const {EventEmitter} = require('events'); +const fs = require('fs'); +const path = require('path'); + +const fileName = path.resolve(__dirname, 'fastfs-data'); +const contents = fs.readFileSync(fileName, 'utf-8'); + +describe('fastfs:', function() { + let fastfs; + const crawling = Promise.resolve([fileName]); + const roots = [__dirname]; + const watcher = new EventEmitter(); + + beforeEach(function(done) { + fastfs = new Fastfs('arbitrary', roots, watcher, {crawling}); + fastfs.build().then(done); + }); + + describe('partial reading', () => { + // these are integrated tests that read real files from disk + + pit('reads a file while a predicate returns true', function() { + return fastfs.readWhile(fileName, () => true).then(readContent => + expect(readContent).toEqual(contents) + ); + }); + + pit('invokes the predicate with the new chunk, the invocation index, and the result collected so far', () => { + const predicate = jest.genMockFn().mockReturnValue(true); + return fastfs.readWhile(fileName, predicate).then(() => { + let aggregated = ''; + const {calls} = predicate.mock; + expect(calls).not.toEqual([]); + + calls.forEach((call, i) => { + const [chunk] = call; + aggregated += chunk; + expect(chunk).not.toBe(''); + expect(call).toEqual([chunk, i, aggregated]); + }); + + expect(aggregated).toEqual(contents); + }); + }); + + pit('stops reading when the predicate returns false', () => { + const predicate = jest.genMockFn().mockImpl((_, i) => i !== 0); + return fastfs.readWhile(fileName, predicate).then((readContent) => { + const {calls} = predicate.mock; + expect(calls.length).toBe(1); + expect(readContent).toBe(calls[0][2]); + }); + }); + + pit('after reading the whole file with `readWhile`, `read()` still works', () => { + // this test allows to reuse the results of `readWhile` for `readFile` + return fastfs.readWhile(fileName, () => true).then(() => { + fastfs.readFile(fileName).then(readContent => + expect(readContent).toEqual(contents) + ); + }); + }); + + pit('after reading parts of the file with `readWhile`, `read()` still works', () => { + return fastfs.readWhile(fileName, () => false).then(() => { + fastfs.readFile(fileName).then(readContent => + expect(readContent).toEqual(contents) + ); + }); + }); + }); +}); diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index ca01b86c..f33b9927 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -14,6 +14,7 @@ const {EventEmitter} = require('events'); const fs = require('graceful-fs'); const path = require('path'); +const open = Promise.denodeify(fs.open); const readFile = Promise.denodeify(fs.readFile); const stat = Promise.denodeify(fs.stat); @@ -114,6 +115,14 @@ class Fastfs extends EventEmitter { return file.read(); } + readWhile(filePath, predicate) { + const file = this._getFile(filePath); + if (!file) { + throw new Error(`Unable to find file with path: ${filePath}`); + } + return file.readWhile(predicate); + } + closest(filePath, name) { for (let file = this._getFile(filePath).parent; file; @@ -241,6 +250,44 @@ class File { return this._read; } + readWhile(predicate) { + const CHUNK_SIZE = 512; + let result = ''; + + return open(this.path, 'r').then(fd => { + /* global Buffer: true */ + const buffer = new Buffer(CHUNK_SIZE); + const p = new Promise((resolve, reject) => { + let counter = 0; + const callback = (error, bytesRead) => { + if (error) { + reject(); + return; + } + + const chunk = buffer.toString('utf8', 0, bytesRead); + result += chunk; + if (bytesRead > 0 && predicate(chunk, counter++, result)) { + readChunk(fd, buffer, callback); + } else { + if (bytesRead === 0 && !this._read) { // reached EOF + this._read = Promise.resolve(result); + } + resolve(result); + } + }; + readChunk(fd, buffer, callback); + }); + + p.catch(() => fs.close(fd)); + return p; + }); + + function readChunk(fd, buffer, callback) { + fs.read(fd, buffer, 0, CHUNK_SIZE, null, callback); + } + } + stat() { if (!this._stat) { this._stat = stat(this.path); From d503cf243c9ba3f089bc29dda308610cdbcef0c8 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 28 Jan 2016 22:01:34 -0800 Subject: [PATCH 562/936] Adapt tests to new Object/es7 polyfill Summary: public Adds the new polyfill to the relevant tests (Resolver/BundlesLayout) Reviewed By: martinbigio Differential Revision: D2878697 fb-gh-sync-id: 9681f16dd19a0b337aac63101450c703bf387346 --- .../__tests__/BundlesLayoutIntegration-test.js | 1 + .../src/Resolver/__tests__/Resolver-test.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index fe4481a0..00868b22 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -36,6 +36,7 @@ describe('BundlesLayout', () => { 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', 'polyfills/Array.es6.js', + 'polyfills/Object.es7.js', 'polyfills/babelHelpers.js', ]; const baseFs = getBaseFs(); diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 825399b4..b542d43b 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -138,6 +138,18 @@ describe('Resolver', function() { 'polyfills/Array.prototype.es6.js', ], }, + { id: 'polyfills/Object.es7.js', + isPolyfill: true, + path: 'polyfills/Object.es7.js', + dependencies: [ + 'polyfills/polyfills.js', + 'polyfills/console.js', + 'polyfills/error-guard.js', + 'polyfills/String.prototype.es6.js', + 'polyfills/Array.prototype.es6.js', + 'polyfills/Array.es6.js', + ], + }, { id: 'polyfills/babelHelpers.js', isPolyfill: true, path: 'polyfills/babelHelpers.js', @@ -148,6 +160,7 @@ describe('Resolver', function() { 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', 'polyfills/Array.es6.js', + 'polyfills/Object.es7.js', ], }, ]); @@ -211,6 +224,7 @@ describe('Resolver', function() { 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', 'polyfills/Array.es6.js', + 'polyfills/Object.es7.js', 'polyfills/babelHelpers.js', ] }, From 5d2a4578809ee6d6a6198ab8a6e8a9e807fb23d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 29 Jan 2016 10:14:37 -0800 Subject: [PATCH 563/936] Make HMR faster Summary: public At the moment, when the user changes a file we end up pulling the dependencies of the entry point to build the bundle. This could take a long time if the bundle is big. To avoid it, lets introduce a new parameter to `getDependencies` to be able to avoid processing the modules recursively and reuse the resolution responseto build the bundle. Reviewed By: davidaurelio Differential Revision: D2862850 fb-gh-sync-id: b8ae2b811a8ae9aec5612f9655d1c762671ce730 --- .../src/Bundler/__tests__/Bundler-test.js | 11 ++-- react-packager/src/Bundler/index.js | 63 ++++++++++++++----- .../DependencyGraph/ResolutionRequest.js | 6 +- .../DependencyGraph/index.js | 8 ++- .../src/Resolver/__tests__/Resolver-test.js | 3 +- react-packager/src/Resolver/index.js | 22 ++++--- react-packager/src/Server/index.js | 5 ++ 7 files changed, 86 insertions(+), 32 deletions(-) diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index 467cf90d..5ed7d658 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -206,11 +206,12 @@ describe('Bundler', function() { }); pit('gets the list of dependencies from the resolver', function() { - return bundler.getDependencies('/root/foo.js', true) - .then( - () => expect(getDependencies) - .toBeCalledWith('/root/foo.js', { dev: true }) - ); + return bundler.getDependencies('/root/foo.js', true).then(() => + expect(getDependencies).toBeCalledWith( + '/root/foo.js', + { dev: true, recursive: true }, + ) + ); }); describe('getOrderedDependencyPaths', () => { diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index b0ccc235..d03fdb89 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -235,14 +235,48 @@ class Bundler { unbundle: isUnbundle, hot: hot, entryModuleOnly, + resolutionResponse, }) { - const findEventId = Activity.startEvent('find dependencies'); let transformEventId; + const moduleSystemDeps = includeSystemDependencies + ? this._resolver.getModuleSystemDependencies( + { dev: isDev, platform, isUnbundle } + ) + : []; + + const findModules = () => { + const findEventId = Activity.startEvent('find dependencies'); + return this.getDependencies(entryFile, isDev, platform).then(response => { + Activity.endEvent(findEventId); + bundle.setMainModuleId(response.mainModuleId); + bundle.setMainModuleName(response.mainModuleId); + if (!entryModuleOnly && bundle.setNumPrependedModules) { + bundle.setNumPrependedModules( + response.numPrependedDependencies + moduleSystemDeps.length + ); + } + + return { + response, + modulesToProcess: response.dependencies, + }; + }); + }; + + const useProvidedModules = () => { + const moduleId = this._resolver.getModuleForPath(entryFile); + bundle.setMainModuleId(moduleId); + bundle.setMainModuleName(moduleId); + return Promise.resolve({ + response: resolutionResponse, + modulesToProcess: modules + }); + }; + + return ( + modules ? useProvidedModules() : findModules() + ).then(({response, modulesToProcess}) => { - return this.getDependencies(entryFile, isDev, platform).then((response) => { - Activity.endEvent(findEventId); - bundle.setMainModuleId(response.mainModuleId); - bundle.setMainModuleName(response.mainModuleId); transformEventId = Activity.startEvent('transform'); let dependencies; @@ -259,10 +293,6 @@ class Bundler { const modulesToProcess = modules || response.dependencies; dependencies = moduleSystemDeps.concat(modulesToProcess); - - bundle.setNumPrependedModules && bundle.setNumPrependedModules( - response.numPrependedDependencies + moduleSystemDeps.length - ); } let bar; @@ -280,7 +310,6 @@ class Bundler { module => { return this._transformModule( bundle, - response, module, platform, isDev, @@ -347,7 +376,6 @@ class Bundler { response.dependencies.map( module => this._transformModule( bundle, - response, module, platform, isDev, @@ -418,8 +446,15 @@ class Bundler { return this._resolver.getModuleForPath(entryFile); } - getDependencies(main, isDev, platform) { - return this._resolver.getDependencies(main, { dev: isDev, platform }); + getDependencies(main, isDev, platform, recursive = true) { + return this._resolver.getDependencies( + main, + { + dev: isDev, + platform, + recursive, + }, + ); } getOrderedDependencyPaths({ entryFile, dev, platform }) { @@ -454,7 +489,7 @@ class Bundler { ); } - _transformModule(bundle, response, module, platform = null, dev = true, hot = false) { + _transformModule(bundle, module, platform = null, dev = true, hot = false) { if (module.isAsset_DEPRECATED()) { return this._generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 58dcc7d1..149cab01 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -103,7 +103,7 @@ class ResolutionRequest { ); } - getOrderedDependencies(response, mocksPattern) { + getOrderedDependencies(response, mocksPattern, recursive = true) { return this._getAllMocks(mocksPattern).then(allMocks => { const entry = this._moduleCache.getModule(this._entryPath); const mocks = Object.create(null); @@ -163,7 +163,9 @@ class ResolutionRequest { p = p.then(() => { if (!visited[modDep.hash()]) { visited[modDep.hash()] = true; - return collect(modDep); + if (recursive) { + return collect(modDep); + } } return null; }); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 023fca50..273b9b58 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -158,7 +158,7 @@ class DependencyGraph { return this._moduleCache.getModule(entryFile); } - getDependencies(entryPath, platform) { + getDependencies(entryPath, platform, recursive = true) { return this.load().then(() => { platform = this._getRequestPlatform(entryPath, platform); const absPath = this._getAbsolutePath(entryPath); @@ -177,7 +177,11 @@ class DependencyGraph { const response = new ResolutionResponse(); return Promise.all([ - req.getOrderedDependencies(response, this._opts.mocksPattern), + req.getOrderedDependencies( + response, + this._opts.mocksPattern, + recursive, + ), req.getAsyncDependencies(response), ]).then(() => response); }); diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index b542d43b..962bba96 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -186,7 +186,8 @@ describe('Resolver', function() { return depResolver.getDependencies('/root/index.js', { dev: true }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); - expect(DependencyGraph.mock.instances[0].getDependencies).toBeCalledWith('/root/index.js', undefined); + expect(DependencyGraph.mock.instances[0].getDependencies) + .toBeCalledWith('/root/index.js', undefined, true); expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]); expect(result.dependencies[result.dependencies.length - 1]) .toBe(module); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 4cb4707f..4f3444ec 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -64,6 +64,10 @@ const getDependenciesValidateOpts = declareOpts({ type: 'boolean', default: false }, + recursive: { + type: 'boolean', + default: true, + }, }); class Resolver { @@ -116,15 +120,17 @@ class Resolver { getDependencies(main, options) { const opts = getDependenciesValidateOpts(options); - return this._depGraph.getDependencies(main, opts.platform).then( - resolutionResponse => { - this._getPolyfillDependencies().reverse().forEach( - polyfill => resolutionResponse.prependDependency(polyfill) - ); + return this._depGraph.getDependencies( + main, + opts.platform, + opts.recursive, + ).then(resolutionResponse => { + this._getPolyfillDependencies().reverse().forEach( + polyfill => resolutionResponse.prependDependency(polyfill) + ); - return resolutionResponse.finalize(); - } - ); + return resolutionResponse.finalize(); + }); } getModuleSystemDependencies(options) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 559fb302..defbf5a5 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -137,6 +137,10 @@ const dependencyOpts = declareOpts({ type: 'string', required: true, }, + recursive: { + type: 'boolean', + default: true, + }, }); class Server { @@ -269,6 +273,7 @@ class Server { opts.entryFile, opts.dev, opts.platform, + opts.recursive, ); }); } From 72ddf1ef673787018a1863cfd78988d33070691b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 29 Jan 2016 10:14:43 -0800 Subject: [PATCH 564/936] Make HMR faster (2) Summary: public The HMR listener needs to be invoked on the non debounced callback to avoid loosing updates if 2 files are updated within the debounce configured time. Also, improve the time it takes to send HMR updates by avoiding rebuilding the bundle when the listener is defined. Instead just invalidate the bundles cache so that if the user reloads or disables Hot Loading the packager rebuilds the requested bundle. Reviewed By: davidaurelio Differential Revision: D2863141 fb-gh-sync-id: 3ab500eacbd4a2e4b63619755e5eabf8afdd1db9 --- .../src/Server/__tests__/Server-test.js | 64 +++++++++++++++---- react-packager/src/Server/index.js | 34 +++++----- 2 files changed, 67 insertions(+), 31 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 668e162f..b731a026 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -194,18 +194,6 @@ describe('processRequest', () => { }); it('rebuilds the bundles that contain a file when that file is changed', () => { - testChangingFileWith(() => new Server(options)); - }); - - it('rebuilds the bundles that contain a file when that file is changed, even when hot loading is enabled', () => { - testChangingFileWith(() => { - const server = new Server(options); - server.setHMRFileChangeListener(() => Promise.resolve()); - return server; - }); - }); - - function testChangingFileWith(createServer) { const bundleFunc = jest.genMockFunction(); bundleFunc .mockReturnValueOnce( @@ -225,7 +213,7 @@ describe('processRequest', () => { Bundler.prototype.bundle = bundleFunc; - server = createServer(); + server = new Server(options); requestHandler = server.processRequest.bind(server); @@ -248,7 +236,55 @@ describe('processRequest', () => { expect(response.body).toEqual('this is the rebuilt source') ); jest.runAllTicks(); - } + }); + + it('rebuilds the bundles that contain a file when that file is changed, even when hot loading is enabled', () => { + const bundleFunc = jest.genMockFunction(); + bundleFunc + .mockReturnValueOnce( + Promise.resolve({ + getSource: () => 'this is the first source', + getSourceMap: () => {}, + getEtag: () => () => 'this is an etag', + }) + ) + .mockReturnValue( + Promise.resolve({ + getSource: () => 'this is the rebuilt source', + getSourceMap: () => {}, + getEtag: () => () => 'this is an etag', + }) + ); + + Bundler.prototype.bundle = bundleFunc; + + const server = new Server(options); + server.setHMRFileChangeListener(() => {}); + + requestHandler = server.processRequest.bind(server); + + makeRequest(requestHandler, 'mybundle.bundle?runModule=true') + .done(response => { + expect(response.body).toEqual('this is the first source'); + expect(bundleFunc.mock.calls.length).toBe(1); + }); + + jest.runAllTicks(); + + triggerFileChange('all','path/file.js', options.projectRoots[0]); + jest.runAllTimers(); + jest.runAllTicks(); + + expect(bundleFunc.mock.calls.length).toBe(1); + server.setHMRFileChangeListener(null); + + makeRequest(requestHandler, 'mybundle.bundle?runModule=true') + .done(response => { + expect(response.body).toEqual('this is the rebuilt source'); + expect(bundleFunc.mock.calls.length).toBe(2); + }); + jest.runAllTicks(); + }); }); describe('/onchange endpoint', () => { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index defbf5a5..da8653f8 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -192,23 +192,8 @@ class Server { this._fileWatcher.on('all', this._onFileChange.bind(this)); this._debouncedFileChangeHandler = _.debounce(filePath => { - const onFileChange = () => { - this._rebuildBundles(filePath); - this._informChangeWatchers(); - }; - - // if Hot Loading is enabled avoid rebuilding bundles and sending live - // updates. Instead, send the HMR updates right away and once that - // finishes, invoke any other file change listener. - if (this._hmrFileChangeListener) { - this._hmrFileChangeListener( - filePath, - this._bundler.stat(filePath), - ).then(onFileChange).done(); - return; - } - - onFileChange(); + this._rebuildBundles(filePath); + this._informChangeWatchers(); }, 50); } @@ -288,11 +273,26 @@ class Server { _onFileChange(type, filepath, root) { const absPath = path.join(root, filepath); this._bundler.invalidateFile(absPath); + + // If Hot Loading is enabled avoid rebuilding bundles and sending live + // updates. Instead, send the HMR updates right away and clear the bundles + // cache so that if the user reloads we send them a fresh bundle + if (this._hmrFileChangeListener) { + // Clear cached bundles in case user reloads + this._clearBundles(); + this._hmrFileChangeListener(absPath, this._bundler.stat(absPath)); + return; + } + // Make sure the file watcher event runs through the system before // we rebuild the bundles. this._debouncedFileChangeHandler(absPath); } + _clearBundles() { + this._bundles = Object.create(null); + } + _rebuildBundles() { const buildBundle = this.buildBundle.bind(this); const bundles = this._bundles; From ef64bdf39233ec01b54ff09f992a33a446cca7ad Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Fri, 29 Jan 2016 18:17:16 -0800 Subject: [PATCH 565/936] remove ReactDOM from Jest blacklist Reviewed By: yungsters, spicyj Differential Revision: D2875010 fb-gh-sync-id: d316d5befd0d978bded99f70b814dc133c85b40f --- blacklist.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blacklist.js b/blacklist.js index f3c65203..0a705b9c 100644 --- a/blacklist.js +++ b/blacklist.js @@ -70,6 +70,9 @@ var sharedBlacklist = [ 'downstream/core/toArray.js', /website\/node_modules\/.*/, + + // TODO(jkassens, #9876132): Remove this rule when it's no longer needed. + 'downstream/relay/tools/relayUnstableBatchedUpdates.js', ]; var platformBlacklists = { From 6d58de1b7b8e4ac236acc997316721850218f5d3 Mon Sep 17 00:00:00 2001 From: Austin Kuo Date: Sun, 31 Jan 2016 06:44:10 -0800 Subject: [PATCH 566/936] Improve the error message when the packager can't find a module Summary: Closes https://github.com/facebook/react-native/pull/5647 Reviewed By: svcscm Differential Revision: D2884241 Pulled By: androidtrunkagent fb-gh-sync-id: 115491270c8f84d45f67a1a477543d9ace76f447 --- .../src/DependencyResolver/DependencyGraph/ResolutionRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 149cab01..26515617 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -386,7 +386,7 @@ class ResolutionRequest { throw new UnableToResolveError( fromModule, toModule, -`Invalid directory ${potentialDirPath} +`Unable to find this module in its module map or any of the node_modules directories under ${potentialDirPath} and its parent directories This might be related to https://github.com/facebook/react-native/issues/4968 To resolve try the following: From 6891e98578befe369abbd9bcfed7f702e916255c Mon Sep 17 00:00:00 2001 From: Sam Neubardt Date: Sun, 31 Jan 2016 15:50:58 -0800 Subject: [PATCH 567/936] Handle spaces in $REACT_NATIVE_DIR Summary: `react-native-xcode.sh` can fail to execute if `$REACT_NATIVE_DIR` contains spaces. E.g. if `$REACT_NATIVE_DIR` was `/Users/samn/src/Xcode Projects/AwesomeReact` then `react-native-xcode.sh` would fail with the following error: ``` node /Users/samn/src/Xcode Projects/ReactNativeBeaconTest/node_modules/react-native/local-cli/cli.js bundle .... (elided) module.js:341 throw err; ^ Error: Cannot find module '/Users/samn/src/Xcode' at Function.Module._resolveFilename (module.js:339:15) at Function.Module._load (module.js:290:25) at Function.Module.runMain (module.js:447:10) at startup (node.js:139:18) at node.js:999:3 Command /bin/sh failed with exit code 1 ``` This change properly handles paths with spaces in them by quoting `$REACT_NATIVE_DIR` Closes https://github.com/facebook/react-native/pull/5651 Reviewed By: svcscm Differential Revision: D2884600 Pulled By: androidtrunkagent fb-gh-sync-id: 3784d8f4e16c0c2cadac738c5f085a5023b5ecf7 --- react-native-xcode.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index c0ed7282..87251051 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -68,7 +68,7 @@ type $NODE_BINARY >/dev/null 2>&1 || nodejs_not_found set -x DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH -$NODE_BINARY $REACT_NATIVE_DIR/local-cli/cli.js bundle \ +$NODE_BINARY "$REACT_NATIVE_DIR/local-cli/cli.js" bundle \ --entry-file index.ios.js \ --platform ios \ --dev $DEV \ From f51d9718da8d99428a8d9592b251044123204ce2 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Sun, 31 Jan 2016 23:24:39 -0800 Subject: [PATCH 568/936] Add per-field cache invalidation Summary: Jest needs this for efficient caching of resolution responses. public Reviewed By: voideanvalue Differential Revision: D2873291 fb-gh-sync-id: fee27d2ffdfe64bd68fdb4d9e4259e721b33631f --- .../Cache/__tests__/Cache-test.js | 29 +++++++++++++++++++ .../src/DependencyResolver/Cache/index.js | 12 +++++--- .../DependencyGraph/index.js | 2 +- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js b/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js index 22d6825e..2de2318e 100644 --- a/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js +++ b/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js @@ -303,4 +303,33 @@ describe('Cache', () => { }); }); }); + + describe('invalidate', () => { + it('invalidates the cache per file or per-field', () => { + fs.stat.mockImpl((file, callback) => + callback(null, { + mtime: { + getTime: () => {}, + }, + }) + ); + + var cache = new Cache({ + cacheKey: 'cache', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('banana') + ); + var file = '/rootDir/someFile'; + + return cache.get(file, 'field', loaderCb).then(() => { + expect(cache.has(file)).toBe(true); + cache.invalidate(file, 'field'); + expect(cache.has(file)).toBe(true); + expect(cache.has(file, 'field')).toBe(false); + cache.invalidate(file); + expect(cache.has(file)).toBe(false); + }); + }); + }); }); diff --git a/react-packager/src/DependencyResolver/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js index 40f16c77..06d346d6 100644 --- a/react-packager/src/DependencyResolver/Cache/index.js +++ b/react-packager/src/DependencyResolver/Cache/index.js @@ -58,9 +58,13 @@ class Cache { return recordP.then(record => record); } - invalidate(filepath) { - if (this.has(filepath)) { - delete this._data[filepath]; + invalidate(filepath, field) { + if (this.has(filepath, field)) { + if (field == null) { + delete this._data[filepath]; + } else { + delete this._data[filepath].data[field]; + } } } @@ -70,7 +74,7 @@ class Cache { has(filepath, field) { return Object.prototype.hasOwnProperty.call(this._data, filepath) && - (!field || Object.prototype.hasOwnProperty.call(this._data[filepath].data, field)); + (field == null || Object.prototype.hasOwnProperty.call(this._data[filepath].data, field)); } _set(filepath, field, loaderPromise) { diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 273b9b58..6fda8d93 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -59,7 +59,7 @@ class DependencyGraph { mocksPattern, extractRequires, shouldThrowOnUnresolvedErrors, - transformCode + transformCode, }; this._cache = cache; this._helpers = new DependencyGraphHelpers(this._opts); From 5d21d04e5ab7d4fedf58e3598832924774959d22 Mon Sep 17 00:00:00 2001 From: Shane Rogers Date: Mon, 1 Feb 2016 07:42:33 -0800 Subject: [PATCH 569/936] Typo Summary: Tiny typo :) :bee: :beers: Closes https://github.com/facebook/react-native/pull/5665 Reviewed By: svcscm Differential Revision: D2885581 Pulled By: androidtrunkagent fb-gh-sync-id: 4f442a95ea05e2f58c47c664644210c99e256943 --- react-native-xcode.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index 87251051..66ecea85 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -8,7 +8,7 @@ # Bundle React Native app's code and image assets. # This script is supposed to be invoked as part of Xcode build process -# and relies on envoronment variables (including PWD) set by Xcode +# and relies on environment variables (including PWD) set by Xcode # There is no point in creating an offline package for simulator builds # because the packager is supposed to be running during development anyways From f0a42f703527bdf1fe13bec8be79ae8b77255198 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 1 Feb 2016 09:59:37 -0800 Subject: [PATCH 570/936] Fix problems with graceful-fs bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: public This fixes the problem that graceful-fs exposes an unpatched `close()` method, that will never trigger retries. This behavior deadlocks the packager when trying to use graceful-fs’s `open()`/`read()`/`close()` methods. See: isaacs/node-graceful-fs#56 Reviewed By: cpojer Differential Revision: D2885512 fb-gh-sync-id: 71112d2488929bf1775fe9f8566fa03fd66b6bea --- .../src/DependencyResolver/fastfs.js | 96 ++++++++++++------- 1 file changed, 61 insertions(+), 35 deletions(-) diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index f33b9927..325604ff 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -14,7 +14,10 @@ const {EventEmitter} = require('events'); const fs = require('graceful-fs'); const path = require('path'); -const open = Promise.denodeify(fs.open); +// workaround for https://github.com/isaacs/node-graceful-fs/issues/56 +// fs.close is patched, whereas graceful-fs.close is not. +const fsClose = require('fs').close; + const readFile = Promise.denodeify(fs.readFile); const stat = Promise.denodeify(fs.stat); @@ -251,41 +254,12 @@ class File { } readWhile(predicate) { - const CHUNK_SIZE = 512; - let result = ''; - - return open(this.path, 'r').then(fd => { - /* global Buffer: true */ - const buffer = new Buffer(CHUNK_SIZE); - const p = new Promise((resolve, reject) => { - let counter = 0; - const callback = (error, bytesRead) => { - if (error) { - reject(); - return; - } - - const chunk = buffer.toString('utf8', 0, bytesRead); - result += chunk; - if (bytesRead > 0 && predicate(chunk, counter++, result)) { - readChunk(fd, buffer, callback); - } else { - if (bytesRead === 0 && !this._read) { // reached EOF - this._read = Promise.resolve(result); - } - resolve(result); - } - }; - readChunk(fd, buffer, callback); - }); - - p.catch(() => fs.close(fd)); - return p; + return readWhile(this.path, predicate).then(({result, completed}) => { + if (completed && !this._read) { + this._read = Promise.resolve(result); + } + return result; }); - - function readChunk(fd, buffer, callback) { - fs.read(fd, buffer, 0, CHUNK_SIZE, null, callback); - } } stat() { @@ -365,6 +339,58 @@ class File { } } +function readWhile(filePath, predicate) { + return new Promise((resolve, reject) => { + fs.open(filePath, 'r', (openError, fd) => { + if (openError) { + reject(openError); + return; + } + + read( + fd, + /*global Buffer: true*/ + new Buffer(512), + makeReadCallback(fd, predicate, (readError, result, completed) => { + if (readError) { + reject(readError); + } else { + resolve({result, completed}); + } + }) + ); + }); + }); +} + +function read(fd, buffer, callback) { + fs.read(fd, buffer, 0, buffer.length, -1, callback); +} + +function close(fd, error, result, complete, callback) { + fsClose(fd, closeError => callback(error || closeError, result, complete)); +} + +function makeReadCallback(fd, predicate, callback) { + let result = ''; + let index = 0; + return function readCallback(error, bytesRead, buffer) { + if (error) { + close(fd, error, undefined, false, callback); + return; + } + + const completed = bytesRead === 0; + const chunk = completed ? '' : buffer.toString('utf8', 0, bytesRead); + result += chunk; + if (completed || !predicate(chunk, index++, result)) { + close(fd, null, result, completed, callback); + } else { + read(fd, buffer, readCallback); + } + }; +} + function isDescendant(root, child) { return path.relative(root, child).indexOf('..') !== 0; } From f4fc79ab683c1162e1f9ed396244e8f4bf1dcee1 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 1 Feb 2016 16:59:28 -0800 Subject: [PATCH 571/936] Expose fastfs on the DepencenyGraph instance. Summary: The FS as seen by an instance of a dependency graph is useful for clients of node-haste. This provides an accessor to fastfs so that the partial view of the filesystem can be queried. I was thinking of splitting out the fastfs creation to outside the constructor but the necessary refactoring doesn't seem worth it. The fastfs is intrinsically tied to the dependency graph and the `DependencyGraph` instance is meant to be a base-class for clients anyway. Both in react-packager and jest we are wrapping it with higher-level client specific containers, Resolver and HasteResolver respectively. public Reviewed By: davidaurelio Differential Revision: D2885217 fb-gh-sync-id: 47a408b2682516bee9d6a4e6c61b9063817aaf22 --- .../src/DependencyResolver/DependencyGraph/index.js | 4 ++-- react-packager/src/Resolver/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 6fda8d93..ed09f91a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -147,8 +147,8 @@ class DependencyGraph { return this._moduleCache.getModule(entryPath).getDependencies(); } - stat(filePath) { - return this._fastfs.stat(filePath); + getFS() { + return this._fastfs; } /** diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 4f3444ec..1822eb99 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -110,7 +110,7 @@ class Resolver { } stat(filePath) { - return this._depGraph.stat(filePath); + return this._depGraph.getFS().stat(filePath); } getModuleForPath(entryFile) { From 1a547cc2b123d73cd910f16b2dedb69fb91b1fce Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 1 Feb 2016 18:26:30 -0800 Subject: [PATCH 572/936] Updates from GitHub Summary: This syncs a couple of changes from GitHub that are necessary for jest: * Expose `set` on Cache * Add a function to `getAllModules` from the graph * Explicitly dontMock `graceful-fs` in the fastfs test. node-haste2 has this manual mock which is still used in automocked tests (ugh!). public Reviewed By: davidaurelio Differential Revision: D2885112 fb-gh-sync-id: b2060d5474ebee5aa13e6ba2bdf5879b46f3fd5f --- react-packager/src/DependencyResolver/Cache/index.js | 4 ++-- .../src/DependencyResolver/DependencyGraph/index.js | 4 ++++ react-packager/src/DependencyResolver/ModuleCache.js | 4 ++++ .../DependencyResolver/__tests__/fastfs-integrated-test.js | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js index 06d346d6..4e1698cb 100644 --- a/react-packager/src/DependencyResolver/Cache/index.js +++ b/react-packager/src/DependencyResolver/Cache/index.js @@ -53,7 +53,7 @@ class Cache { var recordP = this.has(filepath, field) ? this._data[filepath].data[field] - : this._set(filepath, field, loaderCb(filepath)); + : this.set(filepath, field, loaderCb(filepath)); return recordP.then(record => record); } @@ -77,7 +77,7 @@ class Cache { (field == null || Object.prototype.hasOwnProperty.call(this._data[filepath].data, field)); } - _set(filepath, field, loaderPromise) { + set(filepath, field, loaderPromise) { let record = this._data[filepath]; if (!record) { record = Object.create(null); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index ed09f91a..bc5d85d7 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -158,6 +158,10 @@ class DependencyGraph { return this._moduleCache.getModule(entryFile); } + getAllModules() { + return this.load().then(() => this._moduleCache.getAllModules()); + } + getDependencies(entryPath, platform, recursive = true) { return this.load().then(() => { platform = this._getRequestPlatform(entryPath, platform); diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index fac1820b..3fcfb173 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -41,6 +41,10 @@ class ModuleCache { return this._moduleCache[filePath]; } + getAllModules() { + return this._moduleCache; + } + getAssetModule(filePath) { filePath = path.resolve(filePath); if (!this._moduleCache[filePath]) { diff --git a/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js b/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js index f7fb3558..cbff314a 100644 --- a/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js +++ b/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js @@ -8,7 +8,8 @@ */ 'use strict'; -jest.autoMockOff(); +jest.autoMockOff() + .dontMock('graceful-fs'); const Fastfs = require('../fastfs'); From c620ddd71da32adeeddb4b9070f17dd9ba4cdf18 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 1 Feb 2016 19:08:09 -0800 Subject: [PATCH 573/936] Fix shallow dependency resolution Summary: The feature added in D2862850 only adds the module that requests shallow dependency resolution to the dependencies of a module. The reason why this is happens is because `collect` calls `response.addDependency` but if `recursive` is set to `false`, dependencies don't call `collect` and therefore don't get added to the resolution response. This fixes it by adding dependencies outside of the `collect` call. public Reviewed By: davidaurelio Differential Revision: D2885152 fb-gh-sync-id: 8ab3b6c6b7cd45d59615a99ac87984a41b5d7025 --- .../DependencyGraph/ResolutionRequest.js | 3 +- .../__tests__/DependencyGraph-test.js | 76 ++++++++++++++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 26515617..e95237e6 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -110,8 +110,8 @@ class ResolutionRequest { const visited = Object.create(null); visited[entry.hash()] = true; + response.pushDependency(entry); const collect = (mod) => { - response.pushDependency(mod); return mod.getDependencies().then( depNames => Promise.all( depNames.map(name => this.resolveDependency(mod, name)) @@ -163,6 +163,7 @@ class ResolutionRequest { p = p.then(() => { if (!visited[modDep.hash()]) { visited[modDep.hash()] = true; + response.pushDependency(modDep); if (recursive) { return collect(modDep); } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 464985f6..66d02729 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -21,8 +21,8 @@ const mocksPattern = /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/; describe('DependencyGraph', function() { let defaults; - function getOrderedDependenciesAsJSON(dgraph, entry, platform) { - return dgraph.getDependencies(entry, platform) + function getOrderedDependenciesAsJSON(dgraph, entry, platform, recursive = true) { + return dgraph.getDependencies(entry, platform, recursive) .then(response => response.finalize()) .then(({ dependencies }) => Promise.all(dependencies.map(dep => Promise.all([ dep.getName(), @@ -89,6 +89,12 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule a', ' */', + 'require("b")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', ].join('\n'), }, }); @@ -114,6 +120,17 @@ describe('DependencyGraph', function() { { id: 'a', path: '/root/a.js', + dependencies: ['b'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'b', + path: '/root/b.js', dependencies: [], isAsset: false, isAsset_DEPRECATED: false, @@ -126,6 +143,61 @@ describe('DependencyGraph', function() { }); }); + pit('should get shallow dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")', + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + 'require("b")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js', null, false).then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'a', + path: '/root/a.js', + dependencies: ['b'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + pit('should get dependencies with the correct extensions', function() { var root = '/root'; fs.__setMockFilesystem({ From 83e51b6b06f85588bd83176a765af31ac579f123 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Mon, 1 Feb 2016 20:10:12 -0800 Subject: [PATCH 574/936] move Relay sources to unsigned directory Reviewed By: yungsters Differential Revision: D2887448 fb-gh-sync-id: 53e71be7fa875b3495991f2bf941c5eeb6ae1534 --- blacklist.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blacklist.js b/blacklist.js index 0a705b9c..ee369e1a 100644 --- a/blacklist.js +++ b/blacklist.js @@ -72,7 +72,7 @@ var sharedBlacklist = [ /website\/node_modules\/.*/, // TODO(jkassens, #9876132): Remove this rule when it's no longer needed. - 'downstream/relay/tools/relayUnstableBatchedUpdates.js', + 'Libraries/Relay/relay/tools/relayUnstableBatchedUpdates.js', ]; var platformBlacklists = { From 042dc4349a1bc04374737d90967cae048bb1fbb3 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 2 Feb 2016 15:48:01 -0800 Subject: [PATCH 575/936] Use `fastfs.readWhile` to parse module docblocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Uses `fastfs.readWhile` to build the haste map - cuts the time needed to build the haste map in half - we don’t need to allocate memory for all JS files in the tree - we only read in as much as necessary during startup - we only read files completely that are part of the bundle - we will be able to move the transform before dependency extraction public Reviewed By: martinbigio Differential Revision: D2890933 fb-gh-sync-id: 5fef6b53458e8bc95d0251d0bcf16821581a3362 --- .../src/DependencyResolver/Module.js | 122 ++++++++++++------ .../__tests__/Module-test.js | 98 ++++++++++++++ react-packager/src/__mocks__/fs.js | 50 +++++++ 3 files changed, 227 insertions(+), 43 deletions(-) diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index df6ed710..f9142611 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -43,7 +43,7 @@ class Module { return this._cache.get( this.path, 'isHaste', - () => this.read().then(data => !!data.id) + () => this._readDocBlock().then(data => !!data.id) ); } @@ -55,9 +55,9 @@ class Module { return this._cache.get( this.path, 'name', - () => this.read().then(data => { - if (data.id) { - return data.id; + () => this._readDocBlock().then(({id}) => { + if (id) { + return id; } const p = this.getPackage(); @@ -103,48 +103,69 @@ class Module { this._cache.invalidate(this.path); } - read() { - if (!this._reading) { - this._reading = this._fastfs.readFile(this.path).then(content => { - const data = {}; + _parseDocBlock(docBlock) { + // Extract an id for the module if it's using @providesModule syntax + // and if it's NOT in node_modules (and not a whitelisted node_module). + // This handles the case where a project may have a dep that has @providesModule + // docblock comments, but doesn't want it to conflict with whitelisted @providesModule + // modules, such as react-haste, fbjs-haste, or react-native or with non-dependency, + // project-specific code that is using @providesModule. + const moduleDocBlock = docblock.parseAsObject(docBlock); + const provides = moduleDocBlock.providesModule || moduleDocBlock.provides; - // Set an id on the module if it's using @providesModule syntax - // and if it's NOT in node_modules (and not a whitelisted node_module). - // This handles the case where a project may have a dep that has @providesModule - // docblock comments, but doesn't want it to conflict with whitelisted @providesModule - // modules, such as react-haste, fbjs-haste, or react-native or with non-dependency, - // project-specific code that is using @providesModule. - const moduleDocBlock = docblock.parseAsObject(content); - if (!this._depGraphHelpers.isNodeModulesDir(this.path) && - (moduleDocBlock.providesModule || moduleDocBlock.provides)) { - data.id = /^(\S*)/.exec( - moduleDocBlock.providesModule || moduleDocBlock.provides - )[1]; - } + const id = provides && !this._depGraphHelpers.isNodeModulesDir(this.path) + ? /^\S+/.exec(provides)[0] + : undefined; + return [id, moduleDocBlock]; + } - // Ignore requires in JSON files or generated code. An example of this - // is prebuilt files like the SourceMap library. - if (this.isJSON() || 'extern' in moduleDocBlock) { - data.dependencies = []; - data.asyncDependencies = []; - data.code = content; - return data; - } else { - const transformCode = this._transformCode; - const codePromise = transformCode - ? transformCode(this, content) - : Promise.resolve({code: content}); - - return codePromise.then(({code, dependencies, asyncDependencies}) => { - const {deps} = this._extractor(code); - data.dependencies = dependencies || deps.sync; - data.asyncDependencies = asyncDependencies || deps.async; - data.code = code; - return data; - }); - } - }); + _readDocBlock() { + const reading = this._reading || this._docBlock; + if (reading) { + return reading; } + this._docBlock = this._fastfs.readWhile(this.path, whileInDocBlock) + .then(docBlock => { + const [id] = this._parseDocBlock(docBlock); + return {id}; + }); + return this._docBlock; + } + + read() { + if (this._reading) { + return this._reading; + } + + this._reading = this._fastfs.readFile(this.path).then(content => { + const [id, moduleDocBlock] = this._parseDocBlock(content); + + // Ignore requires in JSON files or generated code. An example of this + // is prebuilt files like the SourceMap library. + if (this.isJSON() || 'extern' in moduleDocBlock) { + return { + id, + dependencies: [], + asyncDependencies: [], + code: content, + }; + } else { + const transformCode = this._transformCode; + const codePromise = transformCode + ? transformCode(this, content) + : Promise.resolve({code: content}); + + return codePromise.then(({code, dependencies, asyncDependencies}) => { + const {deps} = this._extractor(code); + return { + id, + code, + dependencies: dependencies || deps.sync, + asyncDependencies: asyncDependencies || deps.async, + }; + }); + } + }); return this._reading; } @@ -181,4 +202,19 @@ class Module { } } +function whileInDocBlock(chunk, i, result) { + // consume leading whitespace + if (!/\S/.test(result)) { + return true; + } + + // check for start of doc block + if (!/^\s*\/(\*{2}|\*?$)/.test(result)) { + return false; + } + + // check for end of doc block + return !/\*\//.test(result); +} + module.exports = Module; diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 947fa0b1..4ed5868b 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -69,6 +69,104 @@ describe('Module', () => { fastfs.build().then(done); }); + describe('Module ID', () => { + const moduleId = 'arbitraryModule'; + const source = + `/** + * @providesModule ${moduleId} + */ + `; + + let module; + beforeEach(() => { + module = createModule(); + }); + + describe('@providesModule annotations', () => { + beforeEach(() => { + mockIndexFile(source); + }); + + pit('extracts the module name from the header', () => + module.getName().then(name => expect(name).toEqual(moduleId)) + ); + + pit('identifies the module as haste module', () => + module.isHaste().then(isHaste => expect(isHaste).toBe(true)) + ); + + pit('does not transform the file in order to access the name', () => { + const transformCode = + jest.genMockFn().mockReturnValue(Promise.resolve()); + return createModule({transformCode}).getName() + .then(() => expect(transformCode).not.toBeCalled()); + }); + + pit('does not transform the file in order to access the haste status', () => { + const transformCode = + jest.genMockFn().mockReturnValue(Promise.resolve()); + return createModule({transformCode}).isHaste() + .then(() => expect(transformCode).not.toBeCalled()); + }); + }); + + describe('@provides annotations', () => { + beforeEach(() => { + mockIndexFile(source.replace(/@providesModule/, '@provides')); + }); + + pit('extracts the module name from the header if it has a @provides annotation', () => + module.getName().then(name => expect(name).toEqual(moduleId)) + ); + + pit('identifies the module as haste module', () => + module.isHaste().then(isHaste => expect(isHaste).toBe(true)) + ); + + pit('does not transform the file in order to access the name', () => { + const transformCode = + jest.genMockFn().mockReturnValue(Promise.resolve()); + return createModule({transformCode}).getName() + .then(() => expect(transformCode).not.toBeCalled()); + }); + + pit('does not transform the file in order to access the haste status', () => { + const transformCode = + jest.genMockFn().mockReturnValue(Promise.resolve()); + return createModule({transformCode}).isHaste() + .then(() => expect(transformCode).not.toBeCalled()); + }); + }); + + describe('no annotation', () => { + beforeEach(() => { + mockIndexFile('arbitrary(code);'); + }); + + pit('uses the file name as module name', () => + module.getName().then(name => expect(name).toEqual(fileName)) + ); + + pit('does not identify the module as haste module', () => + module.isHaste().then(isHaste => expect(isHaste).toBe(false)) + ); + + pit('does not transform the file in order to access the name', () => { + const transformCode = + jest.genMockFn().mockReturnValue(Promise.resolve()); + return createModule({transformCode}).getName() + .then(() => expect(transformCode).not.toBeCalled()); + }); + + pit('does not transform the file in order to access the haste status', () => { + const transformCode = + jest.genMockFn().mockReturnValue(Promise.resolve()); + return createModule({transformCode}).isHaste() + .then(() => expect(transformCode).not.toBeCalled()); + }); + }); + }); + describe('Async Dependencies', () => { function expectAsyncDependenciesToEqual(expected) { const module = createModule(); diff --git a/react-packager/src/__mocks__/fs.js b/react-packager/src/__mocks__/fs.js index 1aca9250..64b0c9b1 100644 --- a/react-packager/src/__mocks__/fs.js +++ b/react-packager/src/__mocks__/fs.js @@ -112,6 +112,56 @@ fs.stat.mockImpl(function(filepath, callback) { } }); +const noop = () => {}; +fs.open.mockImpl(function(path) { + const callback = arguments[arguments.length - 1] || noop; + let data, error, fd; + try { + data = getToNode(path); + } catch (e) { + error = e; + } + + if (error || data == null) { + error = Error(`ENOENT: no such file or directory, open ${path}`); + } + if (data != null) { + /* global Buffer: true */ + fd = { + buffer: new Buffer(data, 'utf8'), + position: 0, + }; + } + + callback(error, fd); +}); + +fs.read.mockImpl((fd, buffer, writeOffset, length, position, callback = noop) => { + let bytesWritten; + try { + if (position == null || position < 0) { + ({position} = fd); + } + bytesWritten = + fd.buffer.copy(buffer, writeOffset, position, position + length); + fd.position = position + bytesWritten; + } catch (e) { + callback(Error('invalid argument')); + return; + } + callback(null, bytesWritten, buffer); +}); + +fs.close.mockImpl((fd, callback = noop) => { + try { + fd.buffer = fs.position = undefined; + } catch (e) { + callback(Error('invalid argument')); + return; + } + callback(null); +}); + var filesystem; fs.__setMockFilesystem = function(object) { From f2cb2355b23ff2d5ac5d9a467f2d611592291f68 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Tue, 2 Feb 2016 17:49:50 -0800 Subject: [PATCH 576/936] Add mocks for packages to resolution responses Summary: Right now, a mock called `debug.js` shadows a node module called `debug`. When I made mocking more strict I didn't realize that it didn't include those mocks any more because requiring `debug` would result in a module like `debug/node.js` which doesn't have a mock associated with it. This diff changes it so it looks at the associated `package.json` to match the name up to mocks. This is the least invasive non-breaking change I can make right now. Yes, mocking strategies are basically fucked but I don't want the haste2 integration to have even more breaking changes right now. Consider this code to be temporary, I'll fix this and make the mocking system more sane mid-term. public Reviewed By: davidaurelio Differential Revision: D2889198 fb-gh-sync-id: 58db852ed9acad830538245f7dc347365fe4de38 --- .../DependencyGraph/ResolutionRequest.js | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index e95237e6..879c53ef 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -118,14 +118,21 @@ class ResolutionRequest { ).then((dependencies) => [depNames, dependencies]) ).then(([depNames, dependencies]) => { if (allMocks) { - return mod.getName().then(name => { - if (allMocks[name]) { - const mockModule = - this._moduleCache.getModule(allMocks[name]); - depNames.push(name); - dependencies.push(mockModule); - mocks[name] = allMocks[name]; - } + const list = [mod.getName()]; + const pkg = mod.getPackage(); + if (pkg) { + list.push(pkg.getName()); + } + return Promise.all(list).then(names => { + names.forEach(name => { + if (allMocks[name] && !mocks[name]) { + const mockModule = + this._moduleCache.getModule(allMocks[name]); + depNames.push(name); + dependencies.push(mockModule); + mocks[name] = allMocks[name]; + } + }); return [depNames, dependencies]; }); } @@ -141,7 +148,7 @@ class ResolutionRequest { // module backing them. If a dependency cannot be found but there // exists a mock with the desired ID, resolve it and add it as // a dependency. - if (allMocks && allMocks[name]) { + if (allMocks && allMocks[name] && !mocks[name]) { const mockModule = this._moduleCache.getModule(allMocks[name]); mocks[name] = allMocks[name]; return filteredPairs.push([name, mockModule]); From 6d8ccc318bb2aad06ab8a5a044d2bc2d5f6ce637 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Tue, 2 Feb 2016 18:09:45 -0800 Subject: [PATCH 577/936] Move process.exit outside of DependencyResolver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: node-haste shouldn't ever call process.exit and should leave it up to clients to shut down properly. This change just moves it out into the `Resolver` class – I'll leave it up to David and Martin to improve error handling for the rn packager :) public Reviewed By: davidaurelio Differential Revision: D2889908 fb-gh-sync-id: 6f03162c44d89e268891ef71c8db784a6f2e081d --- .../__tests__/DependencyGraph-test.js | 14 +++----------- .../DependencyResolver/DependencyGraph/index.js | 17 +++++++++++------ .../src/Resolver/__tests__/Resolver-test.js | 3 ++- react-packager/src/Resolver/index.js | 5 +++++ 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 66d02729..09a44543 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -1189,22 +1189,14 @@ describe('DependencyGraph', function() { }, }); - const _exit = process.exit; - const _error = console.error; - - process.exit = jest.genMockFn(); - console.error = jest.genMockFn(); - var dgraph = new DependencyGraph({ ...defaults, roots: [root], }); - return dgraph.load().catch(() => { - expect(process.exit).toBeCalledWith(1); - expect(console.error).toBeCalled(); - process.exit = _exit; - console.error = _error; + return dgraph.load().catch(err => { + expect(err.message).toEqual('Failed to build DependencyGraph: Naming collision detected: /root/b.js collides with /root/index.js'); + expect(err.type).toEqual('DependencyGraphError'); }); }); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index bc5d85d7..3c5ade66 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -22,6 +22,8 @@ const ResolutionResponse = require('./ResolutionResponse'); const HasteMap = require('./HasteMap'); const DeprecatedAssetMap = require('./DeprecatedAssetMap'); +const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError'; + const defaultActivity = { startEvent: () => {}, endEvent: () => {}, @@ -63,11 +65,7 @@ class DependencyGraph { }; this._cache = cache; this._helpers = new DependencyGraphHelpers(this._opts); - this.load().catch((err) => { - // This only happens at initialization. Live errors are easier to recover from. - console.error('Error building DependencyGraph:\n', err.stack); - process.exit(1); - }); + this.load(); } load() { @@ -134,7 +132,14 @@ class DependencyGraph { this._deprecatedAssetMap.build(), ]).then(() => activity.endEvent(depGraphActivity) - ); + ).catch(err => { + const error = new Error( + `Failed to build DependencyGraph: ${err.message}` + ); + error.type = ERROR_BUILDING_DEP_GRAPH; + error.stack = err.stack; + throw error; + }); return this._loading; } diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 962bba96..7ef335d9 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -10,7 +10,6 @@ jest.dontMock('../') .dontMock('underscore') - .dontMock('PixelRatio') .dontMock('../../DependencyResolver/lib/extractRequires') .dontMock('../../DependencyResolver/lib/replacePatterns'); @@ -33,6 +32,8 @@ describe('Resolver', function() { path.join.mockImpl(function(a, b) { return b; }); + + DependencyGraph.prototype.load.mockImpl(() => Promise.resolve()); }); class ResolutionResponseMock { diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 1822eb99..4c6e3efb 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -103,6 +103,11 @@ class Resolver { }); this._polyfillModuleNames = opts.polyfillModuleNames || []; + + this._depGraph.load().catch(err => { + console.error(err.message + '\n' + err.stack); + process.exit(1); + }); } getShallowDependencies(entryFile) { From 38cea2edeb6f5920e41d389bb6658cf618ed6f6c Mon Sep 17 00:00:00 2001 From: Adam Miskiewicz Date: Wed, 3 Feb 2016 08:14:38 -0800 Subject: [PATCH 578/936] Use "babel-preset-react-native" Summary: Rather than specifying Babel plugins in the `.babelrc` packaged with react-native, leverage a Babel preset to define the plugins (https://github.com/exponentjs/babel-preset-react-native). This allows for a much better user experience for those who want (or need) to override options in their project's `.babelrc`. Prior to this PR, if a user wanted to use a custom babel-plugin (or a custom set of babel plugins), they'd have either 1) manually override the `.babelrc` in the react-packager directory (or fork RN), or 2) specify a custom transformer to use when running the packager that loaded their own `.babelrc`. Note - the custom transformer was necessary because without it, RN's `.babelrc` options would supersede the options defined in the project's `.babelrc`...potentially causing issues with plugin ordering. This PR makes the transformer check for the existence of a project-level `.babelrc`, and if it it's there, it _doesn't_ use the react-native `.babelrc`. This prevents any oddities with Babel plug Closes https://github.com/facebook/react-native/pull/5214 Reviewed By: davidaurelio Differential Revision: D2881814 Pulled By: martinbigio fb-gh-sync-id: 4168144b7a365fae62bbeed094d8a03a48b4798c --- babelRegisterOnly.js | 4 +- react-packager/.babelrc | 30 ------ react-packager/index.js | 2 - react-packager/rn-babelrc.json | 4 + react-packager/src/JSTransformer/index.js | 2 + .../src/JSTransformer/resolvePlugins.js | 32 ------ react-packager/src/JSTransformer/worker.js | 17 ++- react-packager/src/Resolver/index.js | 1 - react-packager/src/transforms/index.js | 14 --- transformer.js | 100 +++++++++++++----- 10 files changed, 88 insertions(+), 118 deletions(-) delete mode 100644 react-packager/.babelrc create mode 100644 react-packager/rn-babelrc.json delete mode 100644 react-packager/src/JSTransformer/resolvePlugins.js delete mode 100644 react-packager/src/transforms/index.js diff --git a/babelRegisterOnly.js b/babelRegisterOnly.js index bf328610..f862bb75 100644 --- a/babelRegisterOnly.js +++ b/babelRegisterOnly.js @@ -16,7 +16,7 @@ var path = require('path'); var _only = []; function readBabelRC() { - var rcpath = path.join(__dirname, 'react-packager', '.babelrc'); + var rcpath = path.join(__dirname, 'react-packager', 'rn-babelrc.json'); var source = fs.readFileSync(rcpath).toString(); return JSON.parse(source); } @@ -25,5 +25,5 @@ module.exports = function(onlyList) { _only = _only.concat(onlyList); var config = readBabelRC(); config.only = _only; - require('babel-core/register')(config); + require('babel-register')(config); }; diff --git a/react-packager/.babelrc b/react-packager/.babelrc deleted file mode 100644 index e1c61b11..00000000 --- a/react-packager/.babelrc +++ /dev/null @@ -1,30 +0,0 @@ -{ - "retainLines": true, - "compact": true, - "comments": false, - "plugins": [ - "syntax-async-functions", - "syntax-class-properties", - "syntax-trailing-function-commas", - "transform-class-properties", - "transform-es2015-arrow-functions", - "transform-es2015-block-scoping", - "transform-es2015-classes", - "transform-es2015-computed-properties", - "transform-es2015-constants", - "transform-es2015-destructuring", - ["transform-es2015-modules-commonjs", {"strict": false, "allowTopLevelThis": true}], - "transform-es2015-parameters", - "transform-es2015-shorthand-properties", - "transform-es2015-spread", - "transform-es2015-template-literals", - "transform-flow-strip-types", - "transform-object-assign", - "transform-object-rest-spread", - "transform-react-display-name", - "transform-react-jsx", - "transform-regenerator", - "transform-es2015-for-of" - ], - "sourceMaps": false -} diff --git a/react-packager/index.js b/react-packager/index.js index 367f23f3..5b28e774 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -15,7 +15,6 @@ useGracefulFs(); var debug = require('debug'); var omit = require('underscore').omit; var Activity = require('./src/Activity'); -var Transforms = require('./src/transforms'); exports.createServer = createServer; exports.middleware = function(options) { @@ -24,7 +23,6 @@ exports.middleware = function(options) { }; exports.Activity = Activity; -exports.getTransforms = Transforms.getAll; // Renamed "package" to "bundle". But maintain backwards // compat. diff --git a/react-packager/rn-babelrc.json b/react-packager/rn-babelrc.json new file mode 100644 index 00000000..bbc20ea9 --- /dev/null +++ b/react-packager/rn-babelrc.json @@ -0,0 +1,4 @@ +{ + "presets": [ "react-native" ], + "plugins": [] +} diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 14886d06..97679859 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -66,6 +66,7 @@ class Transformer { this._cache = opts.cache; this._transformModulePath = opts.transformModulePath; + this._projectRoots = opts.projectRoots; if (opts.transformModulePath != null) { let transformer; @@ -129,6 +130,7 @@ class Transformer { filename: filePath, options: { ...options, + projectRoots: this._projectRoots, externalTransformModulePath: this._transformModulePath, }, }).then(res => { diff --git a/react-packager/src/JSTransformer/resolvePlugins.js b/react-packager/src/JSTransformer/resolvePlugins.js deleted file mode 100644 index 742ea0a7..00000000 --- a/react-packager/src/JSTransformer/resolvePlugins.js +++ /dev/null @@ -1,32 +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'; - -/** - * Manually resolve all default Babel plugins. - * `babel.transform` will attempt to resolve all base plugins relative to - * the file it's compiling. This makes sure that we're using the plugins - * installed in the react-native package. - */ -function resolvePlugins(plugins) { - return plugins.map(function(plugin) { - // Normalise plugin to an array. - if (!Array.isArray(plugin)) { - plugin = [plugin]; - } - // Only resolve the plugin if it's a string reference. - if (typeof plugin[0] === 'string') { - plugin[0] = require('babel-plugin-' + plugin[0]); - plugin[0] = plugin[0].__esModule ? plugin[0].default : plugin[0]; - } - return plugin; - }); -} - -module.exports = resolvePlugins; diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js index b33b4ae5..c8d388a4 100644 --- a/react-packager/src/JSTransformer/worker.js +++ b/react-packager/src/JSTransformer/worker.js @@ -9,30 +9,25 @@ 'use strict'; var babel = require('babel-core'); -var resolvePlugins = require('./resolvePlugins'); -var Transforms = require('../transforms'); +var makeInternalConfig = require('babel-preset-react-native/configs/internal'); // Runs internal transforms on the given sourceCode. Note that internal // transforms should be run after the external ones to ensure that they run on // Javascript code function internalTransforms(sourceCode, filename, options) { - var plugins = resolvePlugins(Transforms.getAll(options)); - if (plugins.length === 0) { + var internalBabelConfig = makeInternalConfig(options); + + if (!internalBabelConfig) { return { code: sourceCode, filename: filename, }; } - var result = babel.transform(sourceCode, { - retainLines: true, - compact: true, - comments: false, + var result = babel.transform(sourceCode, Object.assign({ filename: filename, sourceFileName: filename, - sourceMaps: false, - plugins: plugins, - }); + }, internalBabelConfig)); return { code: result.code, diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 4c6e3efb..a677c219 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -93,7 +93,6 @@ class Resolver { // should work after this release and we can // remove it from here. 'parse', - 'react-transform-hmr', ], platforms: ['ios', 'android'], preferNativePlatform: true, diff --git a/react-packager/src/transforms/index.js b/react-packager/src/transforms/index.js deleted file mode 100644 index 37f17ccd..00000000 --- a/react-packager/src/transforms/index.js +++ /dev/null @@ -1,14 +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'; - -exports.getAll = function(options) { - return []; -}; - diff --git a/transformer.js b/transformer.js index a38fdb0d..071f4007 100644 --- a/transformer.js +++ b/transformer.js @@ -11,47 +11,95 @@ 'use strict'; const babel = require('babel-core'); +const externalHelpersPlugin = require('babel-plugin-external-helpers'); const fs = require('fs'); -const inlineRequires = require('fbjs-scripts/babel-6/inline-requires'); +const makeHMRConfig = require('babel-preset-react-native/configs/hmr'); +const resolvePlugins = require('babel-preset-react-native/lib/resolvePlugins'); +const inlineRequiresPlugin = require('fbjs-scripts/babel-6/inline-requires'); const json5 = require('json5'); const path = require('path'); -const ReactPackager = require('./react-packager'); -const resolvePlugins = require('./react-packager/src/JSTransformer/resolvePlugins'); -const babelRC = - json5.parse( - fs.readFileSync( - path.resolve(__dirname, 'react-packager', '.babelrc'))); +/** + * Return a memoized function that checks for the existence of a + * project level .babelrc file, and if it doesn't exist, reads the + * default RN babelrc file and uses that. + */ +const getBabelRC = (function() { + let babelRC = null; -function transform(src, filename, options) { - options = options || {}; + return function _getBabelRC(projectRoots) { + if (babelRC !== null) { + return babelRC; + } + + babelRC = { plugins: [] }; // empty babelrc + + // Let's look for the .babelrc in the first project root. + // In the future let's look into adding a command line option to specify + // this location. + // + // NOTE: we're not reading the project's .babelrc here. We leave it up to + // Babel to do that automatically and apply the transforms accordingly + // (which works because we pass in `filename` and `sourceFilename` to + // Babel when we transform). + let projectBabelRCPath; + if (projectRoots && projectRoots.length > 0) { + projectBabelRCPath = path.resolve(projectRoots[0], '.babelrc'); + } + + // If a .babelrc file doesn't exist in the project, + // use the Babel config provided with react-native. + if (!projectBabelRCPath || !fs.existsSync(projectBabelRCPath)) { + babelRC = json5.parse( + fs.readFileSync( + path.resolve(__dirname, 'react-packager', 'rn-babelrc.json')) + ); + + // Require the babel-preset's listed in the default babel config + babelRC.presets = babelRC.presets.map((preset) => require('babel-preset-' + preset)); + babelRC.plugins = resolvePlugins(babelRC.plugins); + } + + return babelRC; + } +})(); + +/** + * Given a filename and options, build a Babel + * config object with the appropriate plugins. + */ +function buildBabelConfig(filename, options) { + const babelRC = getBabelRC(options.projectRoots); - const extraPlugins = ['external-helpers-2']; const extraConfig = { filename, sourceFileName: filename, }; - const config = Object.assign({}, babelRC, extraConfig); - if (options.hot) { - extraPlugins.push([ - 'react-transform', - { - transforms: [{ - transform: 'react-transform-hmr/lib/index.js', - imports: ['React'], - locals: ['module'], - }] - }, - ]); - } + let config = Object.assign({}, babelRC, extraConfig); + + // Add extra plugins + const extraPlugins = [externalHelpersPlugin]; if (options.inlineRequires) { - extraPlugins.push(inlineRequires); + extraPlugins.push(inlineRequiresPlugin); } - config.plugins = resolvePlugins(extraPlugins.concat(config.plugins)); - const result = babel.transform(src, Object.assign({}, babelRC, config)); + config.plugins = extraPlugins.concat(config.plugins); + + if (options.hot) { + const hmrConfig = makeHMRConfig(options); + config = Object.assign({}, config, hmrConfig); + } + + return Object.assign({}, babelRC, config); +} + +function transform(src, filename, options) { + options = options || {}; + + const babelConfig = buildBabelConfig(filename, options); + const result = babel.transform(src, babelConfig); return { code: result.code, From e90d06dbed8adc63fe2a611b64d16a08fef056ee Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 3 Feb 2016 17:30:01 -0800 Subject: [PATCH 579/936] Support non-image assets in packager Summary: public The packager currently assumes that all assets that are not JSON or JS files must be images. Although it is possible to add other extension types, they crash the packager if you try to require them, because it attempts to get their dimensions, assuming that they are an image. This is a crude workaround for that problem, which skips the image-specific processing for non-image assets, but really it would be better if the packager was properly aware of different asset types and treated them differently (e.g. for sounds it could include the duration, for HTML pages it could parse and include linked CSS files, etc). I've also added an example of using `require('...')` to load a packager-managed HTML page in the UIExplorer WebView example. In future I anticipate that all static asset types (sounds, fonts, etc.) could be handled in this way, which allows them to be edited or added/removed on the fly instead of needing to restart the app. Reviewed By: martinbigio Differential Revision: D2895619 fb-gh-sync-id: cd93794ca66bad838621cd7df3ff3c62b5645e85 --- react-packager/src/Bundler/index.js | 12 +++++++++--- react-packager/src/Server/index.js | 7 ++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index d03fdb89..fc7be66d 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -554,8 +554,14 @@ class Bundler { assetUrlPath = assetUrlPath.replace(/\\/g, '/'); } + // Test extension against all types supported by image-size module. + // If it's not one of these, we won't treat it as an image. + let isImage = [ + 'png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff' + ].indexOf(path.extname(module.path).slice(1)) !== -1; + return Promise.all([ - sizeOf(module.path), + isImage ? sizeOf(module.path) : null, this._assetServer.getAssetData(relPath, platform), ]).then(function(res) { const dimensions = res[0]; @@ -564,8 +570,8 @@ class Bundler { __packager_asset: true, fileSystemLocation: path.dirname(module.path), httpServerLocation: assetUrlPath, - width: dimensions.width / module.resolution, - height: dimensions.height / module.resolution, + width: dimensions ? dimensions.width / module.resolution : undefined, + height: dimensions ? dimensions.height / module.resolution : undefined, scales: assetData.scales, files: assetData.files, hash: assetData.hash, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index da8653f8..f8c252a3 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -58,7 +58,12 @@ const validateOpts = declareOpts({ }, assetExts: { type: 'array', - default: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'], + default: [ + 'bmp', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'svg', 'webp', // Image formats + 'm4v', 'mov', 'mp4', 'mpeg', 'mpg', 'webm', // Video formats + 'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', // Audio formats + 'html', // Document formats + ], }, transformTimeoutInterval: { type: 'number', From 96b82f43c7bc7ed84255b59d345e42f77f586d3f Mon Sep 17 00:00:00 2001 From: Corentin Smith Date: Fri, 5 Feb 2016 12:08:49 -0800 Subject: [PATCH 580/936] Add String.prototype.includes polyfill Summary: Add a polyfill for `String.prototype.includes` taken from MDN (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/includes) See https://github.com/facebook/react-native/issues/5727 Closes https://github.com/facebook/react-native/pull/5735 Reviewed By: svcscm Differential Revision: D2906667 Pulled By: vjeux fb-gh-sync-id: e02f605bf57171062b29a98b98ba9fc898cedfc2 --- .../Resolver/polyfills/String.prototype.es6.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/react-packager/src/Resolver/polyfills/String.prototype.es6.js b/react-packager/src/Resolver/polyfills/String.prototype.es6.js index afc68d76..c455ef1e 100644 --- a/react-packager/src/Resolver/polyfills/String.prototype.es6.js +++ b/react-packager/src/Resolver/polyfills/String.prototype.es6.js @@ -83,3 +83,18 @@ if (!String.prototype.repeat) { return result; }; } + +if (!String.prototype.includes) { + String.prototype.includes = function(search, start) { + 'use strict'; + if (typeof start !== 'number') { + start = 0; + } + + if (start + search.length > this.length) { + return false; + } else { + return this.indexOf(search, start) !== -1; + } + }; +} From f6af247123ca99f78b989b19e41d9281d8b27741 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Sun, 7 Feb 2016 16:41:54 -0800 Subject: [PATCH 581/936] Remove String.prototype.contains polyfill Summary: Now that String.prototype.includes is there, we should remove the .contains one which has not been standardized. For fb reviewers, this needs to land after D2910339 which updates internal callsites. Closes https://github.com/facebook/react-native/pull/5794 Reviewed By: svcscm Differential Revision: D2910855 Pulled By: vjeux fb-gh-sync-id: 8fd216222385f038995d1ed10e8a2c4c34c7e928 --- .../src/Resolver/polyfills/String.prototype.es6.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/String.prototype.es6.js b/react-packager/src/Resolver/polyfills/String.prototype.es6.js index c455ef1e..59e5351d 100644 --- a/react-packager/src/Resolver/polyfills/String.prototype.es6.js +++ b/react-packager/src/Resolver/polyfills/String.prototype.es6.js @@ -44,19 +44,6 @@ if (!String.prototype.endsWith) { }; } -if (!String.prototype.contains) { - String.prototype.contains = function(search) { - 'use strict'; - if (this == null) { - throw TypeError(); - } - var string = String(this); - var pos = arguments.length > 1 ? - (Number(arguments[1]) || 0) : 0; - return string.indexOf(String(search), pos) !== -1; - }; -} - if (!String.prototype.repeat) { String.prototype.repeat = function(count) { 'use strict'; From 03a85ea8c2c045fb4d5e322fe84747f30971dcc4 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 8 Feb 2016 11:18:25 -0800 Subject: [PATCH 582/936] Clean up Bundler Summary: The bundler class had duplicated code and parts that were hard to follow, because functions accepted heterogenous arguments, leading to a lot of conditionals. This commit tries to remove duplication between build steps of different type of bundles, and by accepting less different types of arguments. These differences are now handled further up the call stack. public Reviewed By: sebmarkbage Differential Revision: D2905807 fb-gh-sync-id: ef85ea0d461a9a06a4a64480e014a5324c4ef532 --- react-packager/src/Bundler/BundleBase.js | 12 +- .../src/Bundler/__tests__/Bundle-test.js | 13 +- react-packager/src/Bundler/index.js | 291 ++++++++---------- .../DependencyGraph/ResolutionResponse.js | 15 + react-packager/src/Server/index.js | 2 +- 5 files changed, 140 insertions(+), 193 deletions(-) diff --git a/react-packager/src/Bundler/BundleBase.js b/react-packager/src/Bundler/BundleBase.js index 79cbfefc..d3794a43 100644 --- a/react-packager/src/Bundler/BundleBase.js +++ b/react-packager/src/Bundler/BundleBase.js @@ -16,7 +16,7 @@ class BundleBase { this._finalized = false; this._modules = []; this._assets = []; - this._mainModuleId = this._mainModuleName = undefined; + this._mainModuleId = undefined; } isEmpty() { @@ -31,14 +31,6 @@ class BundleBase { this._mainModuleId = moduleId; } - getMainModuleName() { - return this._mainModuleName; - } - - setMainModuleName(moduleName) { - this._mainModuleName = moduleName; - } - addModule(module) { if (!module instanceof ModuleTransport) { throw new Error('Expeceted a ModuleTransport object'); @@ -89,7 +81,6 @@ class BundleBase { modules: this._modules, assets: this._assets, mainModuleId: this.getMainModuleId(), - mainModuleName: this.getMainModuleName(), }; } @@ -97,7 +88,6 @@ class BundleBase { bundle._assets = json.assets; bundle._modules = json.modules; bundle.setMainModuleId(json.mainModuleId); - bundle.setMainModuleName(json.mainModuleName); Object.freeze(bundle._modules); Object.seal(bundle._modules); diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index b4152a7b..23fe5762 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -312,28 +312,21 @@ describe('Bundle', () => { }); }); - describe('main module name and id:', function() { - it('keeps distinct module names and IDs', function() { + describe('main module id:', function() { + it('can save a main module ID', function() { const id = 'arbitrary module ID'; - const name = 'arbitrary module name'; bundle.setMainModuleId(id); - bundle.setMainModuleName(name); - expect(bundle.getMainModuleId()).toEqual(id); - expect(bundle.getMainModuleName()).toEqual(name); }); - it('can serialize and deserialize module ID and name', function() { + it('can serialize and deserialize the module ID', function() { const id = 'arbitrary module ID'; - const name = 'arbitrary module name'; bundle.setMainModuleId(id); - bundle.setMainModuleName(name); bundle.finalize({}); const deserialized = Bundle.fromJSON(bundle.toJSON()); expect(deserialized.getMainModuleId()).toEqual(id); - expect(deserialized.getMainModuleName()).toEqual(name); }); }); }); diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index fc7be66d..a07d73f7 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -29,6 +29,8 @@ const version = require('../../../../package.json').version; const sizeOf = Promise.denodeify(imageSize); const readFile = Promise.denodeify(fs.readFile); +const noop = () => {}; + const validateOpts = declareOpts({ projectRoots: { type: 'array', @@ -159,9 +161,12 @@ class Bundler { } bundle(options) { + const {dev, isUnbundle, platform} = options; + const moduleSystemDeps = + this._resolver.getModuleSystemDependencies({dev, isUnbundle, platform}); return this._bundle({ bundle: new Bundle(options.sourceMapUrl), - includeSystemDependencies: true, + moduleSystemDeps, ...options, }); } @@ -209,7 +214,7 @@ class Bundler { ); } - bundleForHMR(options) { + hmrBundle(options) { return this._bundle({ bundle: new HMRBundle({ sourceURLFn: this._sourceHMRURL.bind(this, options.platform), @@ -225,121 +230,50 @@ class Bundler { _bundle({ bundle, - modules, entryFile, runModule: runMainModule, runBeforeMainModule, dev: isDev, - includeSystemDependencies, platform, - unbundle: isUnbundle, - hot: hot, + moduleSystemDeps = [], + hot, entryModuleOnly, - resolutionResponse, + resolutionResponse }) { - let transformEventId; - const moduleSystemDeps = includeSystemDependencies - ? this._resolver.getModuleSystemDependencies( - { dev: isDev, platform, isUnbundle } - ) - : []; - - const findModules = () => { - const findEventId = Activity.startEvent('find dependencies'); - return this.getDependencies(entryFile, isDev, platform).then(response => { - Activity.endEvent(findEventId); - bundle.setMainModuleId(response.mainModuleId); - bundle.setMainModuleName(response.mainModuleId); - if (!entryModuleOnly && bundle.setNumPrependedModules) { - bundle.setNumPrependedModules( - response.numPrependedDependencies + moduleSystemDeps.length - ); - } - - return { - response, - modulesToProcess: response.dependencies, - }; - }); - }; - - const useProvidedModules = () => { - const moduleId = this._resolver.getModuleForPath(entryFile); - bundle.setMainModuleId(moduleId); - bundle.setMainModuleName(moduleId); - return Promise.resolve({ - response: resolutionResponse, - modulesToProcess: modules - }); - }; - - return ( - modules ? useProvidedModules() : findModules() - ).then(({response, modulesToProcess}) => { - - transformEventId = Activity.startEvent('transform'); - - let dependencies; + const onResolutionResponse = response => { + bundle.setMainModuleId(response.mainModuleId); + if (bundle.setNumPrependedModules) { + bundle.setNumPrependedModules( + response.numPrependedDependencies + moduleSystemDeps.length + ); + } if (entryModuleOnly) { - dependencies = response.dependencies.filter(module => + response.dependencies = response.dependencies.filter(module => module.path.endsWith(entryFile) ); } else { - const moduleSystemDeps = includeSystemDependencies - ? this._resolver.getModuleSystemDependencies( - { dev: isDev, platform, isUnbundle } - ) - : []; - - const modulesToProcess = modules || response.dependencies; - dependencies = moduleSystemDeps.concat(modulesToProcess); + response.dependencies = moduleSystemDeps.concat(response.dependencies); } - - let bar; - if (process.stdout.isTTY) { - bar = new ProgressBar('transforming [:bar] :percent :current/:total', { - complete: '=', - incomplete: ' ', - width: 40, - total: dependencies.length, - }); - } - - return Promise.all( - dependencies.map( - module => { - return this._transformModule( - bundle, - module, - platform, - isDev, - hot, - ).then(transformed => { - if (bar) { - bar.tick(); - } - - return { - module, - transformed, - }; - }); - } + }; + const finalizeBundle = ({bundle, transformedModules, response}) => + Promise.all( + transformedModules.map(({module, transformed}) => + bundle.addModule(this._resolver, response, module, transformed) ) - ).then(transformedModules => Promise.all( - transformedModules.map(({module, transformed}) => { - return bundle.addModule( - this._resolver, - response, - module, - transformed, - ); - }) - )); - }).then(() => { - Activity.endEvent(transformEventId); - bundle.finalize({runBeforeMainModule, runMainModule}); - return bundle; + ).then(() => { + bundle.finalize({runBeforeMainModule, runMainModule}); + return bundle; + }); + + return this._buildBundle({ + entryFile, + isDev, + platform, + bundle, + hot, + resolutionResponse, + onResolutionResponse, + finalizeBundle, }); } @@ -351,78 +285,86 @@ class Bundler { dev: isDev, platform, }) { - const bundle = new PrepackBundle(sourceMapUrl); - const findEventId = Activity.startEvent('find dependencies'); - let transformEventId; - let mainModuleId; - - return this.getDependencies(entryFile, isDev, platform).then((response) => { - Activity.endEvent(findEventId); - transformEventId = Activity.startEvent('transform'); - - let bar; - if (process.stdout.isTTY) { - bar = new ProgressBar('transforming [:bar] :percent :current/:total', { - complete: '=', - incomplete: ' ', - width: 40, - total: response.dependencies.length, + const onModuleTransformed = ({module, transformed, response, bundle}) => { + const deps = Object.create(null); + const pairs = response.getResolvedDependencyPairs(module); + if (pairs) { + pairs.forEach(pair => { + deps[pair[0]] = pair[1].path; }); } - mainModuleId = response.mainModuleId; - - return Promise.all( - response.dependencies.map( - module => this._transformModule( - bundle, - module, - platform, - isDev, - ).then(transformed => { - if (bar) { - bar.tick(); - } - - var deps = Object.create(null); - var pairs = response.getResolvedDependencyPairs(module); - if (pairs) { - pairs.forEach(pair => { - deps[pair[0]] = pair[1].path; - }); - } - - return module.getName().then(name => { - bundle.addModule(name, transformed, deps, module.isPolyfill()); - }); - }) - ) - ); - }).then(() => { - Activity.endEvent(transformEventId); - bundle.finalize({runBeforeMainModule, runMainModule, mainModuleId }); + return module.getName().then(name => { + bundle.addModule(name, transformed, deps, module.isPolyfill()); + }); + }; + const finalizeBundle = ({bundle, response}) => { + const {mainModuleId} = response; + bundle.finalize({runBeforeMainModule, runMainModule, mainModuleId}); return bundle; + }; + + return this._buildBundle({ + entryFile, + isDev, + platform, + onModuleTransformed, + finalizeBundle, + bundle: new PrepackBundle(sourceMapUrl), }); } - _transformModuleForHMR(module, platform) { - if (module.isAsset()) { - return this._generateAssetObjAndCode(module, platform).then( - ({asset, code}) => { - return { - code, - }; - } - ); - } else { - // TODO(martinb): pass non null main (t9527509) - return this._getTransformOptions( - {main: null, dev: true, platform: 'ios'}, // TODO(martinb): avoid hard-coding platform - {hot: true}, - ).then(options => { - return this._transformer.loadFileAndTransform(module.path, options); - }); + _buildBundle({ + entryFile, + isDev, + platform, + bundle, + hot, + resolutionResponse, + onResolutionResponse = noop, + onModuleTransformed = noop, + finalizeBundle = noop, + }) { + const findEventId = Activity.startEvent('find dependencies'); + if (!resolutionResponse) { + resolutionResponse = this.getDependencies(entryFile, isDev, platform); } + + return Promise.resolve(resolutionResponse).then(response => { + Activity.endEvent(findEventId); + onResolutionResponse(response); + + const transformEventId = Activity.startEvent('transform'); + const bar = process.stdout.isTTY + ? new ProgressBar('transforming [:bar] :percent :current/:total', { + complete: '=', + incomplete: ' ', + width: 40, + total: response.dependencies.length, + }) + : {tick() {}}; + const transformPromises = + response.dependencies.map(module => + this._transformModule({ + mainModuleName: response.mainModuleId, + bundle, + module, + platform, + dev: isDev, + hot + }).then(transformed => { + bar.tick(); + onModuleTransformed({module, transformed, response, bundle}); + return {module, transformed}; + }) + ); + return Promise.all(transformPromises).then(transformedModules => { + Activity.endEvent(transformEventId); + return Promise + .resolve(finalizeBundle({bundle, transformedModules, response})) + .then(() => bundle); + }); + }); } invalidateFile(filePath) { @@ -489,7 +431,14 @@ class Bundler { ); } - _transformModule(bundle, module, platform = null, dev = true, hot = false) { + _transformModule({ + bundle, + module, + mainModuleName, + platform = null, + dev = true, + hot = false, + }) { if (module.isAsset_DEPRECATED()) { return this._generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { @@ -499,12 +448,12 @@ class Bundler { } else { return this._getTransformOptions( { - bundleEntry: bundle.getMainModuleName(), + bundleEntry: mainModuleName, platform: platform, dev: dev, modulePath: module.path, }, - {hot: hot}, + {hot}, ).then(options => { return this._transformer.loadFileAndTransform( path.resolve(module.path), diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js index 7fbafde8..c13895fb 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js @@ -19,6 +19,21 @@ class ResolutionResponse { this._finalized = false; } + copy(properties) { + const { + dependencies = this.dependencies, + asyncDependencies = this.asyncDependencies, + mainModuleId = this.mainModuleId, + mocks = this.mocks, + } = properties; + return Object.assign(new this.constructor(), this, { + dependencies, + asyncDependencies, + mainModuleId, + mocks, + }); + } + _assertNotFinalized() { if (this._finalized) { throw new Error('Attempted to mutate finalized response.'); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index f8c252a3..0146e7d6 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -241,7 +241,7 @@ class Server { } buildBundleForHMR(modules) { - return this._bundler.bundleForHMR(modules); + return this._bundler.hmrBundle(modules); } getShallowDependencies(entryFile) { From 54e65761994de37ca3e31155915e0af8637dae44 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 9 Feb 2016 17:15:22 -0800 Subject: [PATCH 583/936] / [node-haste] Remove support for asynchronous dependencies (`System.import`) Summary: public Remove the unused feature for async dependencies / bundle layouts. We can bring it back later, if needed. Reviewed By: cpojer Differential Revision: D2916543 fb-gh-sync-id: 3a3890f10d7d275a4cb9371a6e9cace601a82b2c shipit-source-id: 3a3890f10d7d275a4cb9371a6e9cace601a82b2c --- react-packager/src/Bundler/index.js | 12 - .../__tests__/BundlesLayout-test.js | 320 ---------- .../BundlesLayoutIntegration-test.js | 599 ------------------ react-packager/src/BundlesLayout/index.js | 219 ------- .../src/DependencyResolver/AssetModule.js | 4 - .../AssetModule_DEPRECATED.js | 4 - .../DependencyGraph/ResolutionRequest.js | 17 - .../DependencyGraph/ResolutionResponse.js | 8 - .../__tests__/DependencyGraph-test.js | 34 - .../DependencyGraph/index.js | 13 +- .../src/DependencyResolver/Module.js | 12 +- .../__tests__/Module-test.js | 70 +- .../DependencyResolver/lib/extractRequires.js | 10 - .../DependencyResolver/lib/replacePatterns.js | 1 - .../src/Resolver/__tests__/Resolver-test.js | 7 +- .../polyfills/__tests__/loadBundles-test.js | 63 -- .../src/Resolver/polyfills/loadBundles.js | 34 - .../babel-plugin-system-import-test.js | 71 --- .../babel-plugin-system-import/index.js | 63 -- 19 files changed, 10 insertions(+), 1551 deletions(-) delete mode 100644 react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js delete mode 100644 react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js delete mode 100644 react-packager/src/BundlesLayout/index.js delete mode 100644 react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js delete mode 100644 react-packager/src/Resolver/polyfills/loadBundles.js delete mode 100644 react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js delete mode 100644 react-packager/src/transforms/babel-plugin-system-import/index.js diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index a07d73f7..99923e65 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -13,7 +13,6 @@ const fs = require('fs'); const path = require('path'); const Promise = require('promise'); const ProgressBar = require('progress'); -const BundlesLayout = require('../BundlesLayout'); const Cache = require('../DependencyResolver/Cache'); const Transformer = require('../JSTransformer'); const Resolver = require('../Resolver'); @@ -126,13 +125,6 @@ class Bundler { cache: this._cache, }); - this._bundlesLayout = new BundlesLayout({ - dependencyResolver: this._resolver, - resetCache: opts.resetCache, - cacheVersion: opts.cacheVersion, - projectRoots: opts.projectRoots, - }); - this._transformer = new Transformer({ projectRoots: opts.projectRoots, blacklistRE: opts.blacklistRE, @@ -156,10 +148,6 @@ class Bundler { return this._cache.end(); } - getLayout(main, isDev) { - return this._bundlesLayout.generateLayout(main, isDev); - } - bundle(options) { const {dev, isUnbundle, platform} = options; const moduleSystemDeps = diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js deleted file mode 100644 index dafdb368..00000000 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js +++ /dev/null @@ -1,320 +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'; - -jest.dontMock('../index') - .mock('fs'); - -var Promise = require('promise'); -var BundlesLayout = require('../index'); -var Resolver = require('../../Resolver'); -var loadCacheSync = require('../../DependencyResolver/Cache/lib/loadCacheSync'); - -describe('BundlesLayout', () => { - function newBundlesLayout(options) { - return new BundlesLayout(Object.assign({ - projectRoots: ['/root'], - dependencyResolver: new Resolver(), - }, options)); - } - - describe('layout', () => { - function isPolyfill() { - return false; - } - - describe('getLayout', () => { - function dep(path) { - return { - path: path, - isPolyfill: isPolyfill, - }; - } - - pit('should bundle sync dependencies', () => { - Resolver.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({resetCache: true}) - .getLayout('/root/index.js') - .then(bundles => - expect(bundles).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js', '/root/a.js'], - children: [], - }) - ); - }); - - pit('should separate async dependencies into different bundle', () => { - Resolver.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({resetCache: true}) - .getLayout('/root/index.js') - .then(bundles => - expect(bundles).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js'], - children: [{ - id:'bundle.0.1', - modules: ['/root/a.js'], - children: [], - }], - }) - ); - }); - - pit('separate async dependencies of async dependencies', () => { - Resolver.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({resetCache: true}) - .getLayout('/root/index.js') - .then(bundles => - expect(bundles).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'], - children: [], - }], - }], - }) - ); - }); - - pit('separate bundle sync dependencies of async ones on same bundle', () => { - Resolver.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({resetCache: true}) - .getLayout('/root/index.js') - .then(bundles => - expect(bundles).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js'], - children: [{ - id: 'bundle.0.1', - modules: ['/root/a.js', '/root/b.js'], - children: [], - }], - }) - ); - }); - - pit('separate cache in which bundle is each dependency', () => { - Resolver.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({resetCache: true}) - .getLayout('/root/index.js') - .then(bundles => - expect(bundles).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js', '/root/a.js'], - children: [{ - id: 'bundle.0.1', - modules: ['/root/b.js'], - children: [], - }], - }) - ); - }); - - pit('separate cache in which bundle is each dependency', () => { - Resolver.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'], ['/root/c.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/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; - } - }); - - var layout = newBundlesLayout({resetCache: true}); - return layout.getLayout('/root/index.js').then(() => { - 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'); - }); - }); - }); - }); - - describe('cache', () => { - beforeEach(() => { - loadCacheSync.mockReturnValue({ - '/root/index.js': { - id: 'bundle.0', - modules: ['/root/index.js'], - children: [{ - id: 'bundle.0.1', - modules: ['/root/a.js'], - children: [], - }], - }, - '/root/b.js': { - id: 'bundle.2', - modules: ['/root/b.js'], - children: [], - }, - }); - }); - - pit('should load layouts', () => { - const layout = newBundlesLayout({ resetCache: false }); - - return Promise - .all([ - layout.getLayout('/root/index.js'), - layout.getLayout('/root/b.js'), - ]) - .then(([layoutIndex, layoutB]) => { - expect(layoutIndex).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js'], - children: [{ - id: 'bundle.0.1', - modules: ['/root/a.js'], - children: [], - }], - }); - - expect(layoutB).toEqual({ - id: 'bundle.2', - modules: ['/root/b.js'], - children: [], - }); - }); - }); - - it('should load moduleToBundle map', () => { - const layout = newBundlesLayout({ resetCache: false }); - - expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0'); - expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0.1'); - expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.2'); - }); - }); -}); diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js deleted file mode 100644 index 00868b22..00000000 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ /dev/null @@ -1,599 +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'; - -jest - .autoMockOff() - .mock('../../DependencyResolver/Cache') - .mock('../../Activity'); - -const Promise = require('promise'); -const path = require('path'); - -jest.mock('fs'); - -var BundlesLayout = require('../index'); -var Cache = require('../../DependencyResolver/Cache'); -var Resolver = require('../../Resolver'); -var fs = require('fs'); - -describe('BundlesLayout', () => { - var fileWatcher; - - 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', - 'polyfills/Array.es6.js', - 'polyfills/Object.es7.js', - 'polyfills/babelHelpers.js', - ]; - const baseFs = getBaseFs(); - - beforeEach(() => { - fileWatcher = { - on: () => this, - isWatchman: () => Promise.resolve(false) - }; - }); - - describe('generate', () => { - function newBundlesLayout() { - const resolver = new Resolver({ - projectRoots: ['/root', '/' + __dirname.split('/')[1]], - fileWatcher: fileWatcher, - cache: new Cache(), - assetExts: ['js', 'png'], - assetRoots: ['/root'], - }); - - return new BundlesLayout({ - dependencyResolver: resolver, - resetCache: true, - projectRoots: ['/root', '/' + __dirname.split('/')[1]], - }); - } - - function stripPolyfills(bundle) { - return Promise - .all(bundle.children.map(childModule => stripPolyfills(childModule))) - .then(children => { - const modules = bundle.modules - .filter(moduleName => { // filter polyfills - for (let p of polyfills) { - if (moduleName.indexOf(p) !== -1) { - return false; - } - } - return true; - }); - - return { - id: bundle.id, - modules: modules, - children: children, - }; - }); - } - - function setMockFilesystem(mockFs) { - fs.__setMockFilesystem(Object.assign(mockFs, baseFs)); - } - - pit('should bundle single-module app', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */`, - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - stripPolyfills(bundles).then(resolvedBundles => - expect(resolvedBundles).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js'], - children: [], - }) - ) - ); - }); - - pit('should bundle dependant modules', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - require("xa");`, - 'a.js': ` - /** - * @providesModule xa - */`, - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - stripPolyfills(bundles).then(resolvedBundles => - expect(resolvedBundles).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js', '/root/a.js'], - children: [], - }) - ) - ); - }); - - pit('should split bundles for async dependencies', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("xa");`, - 'a.js': ` - /**, - * @providesModule xa - */`, - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - stripPolyfills(bundles).then(resolvedBundles => - expect(resolvedBundles).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js'], - children: [{ - id: 'bundle.0.1', - modules: ['/root/a.js'], - children: [], - }], - }) - ) - ); - }); - - pit('should split into multiple bundles separate async dependencies', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("xa"); - ${'System.import'}("xb");`, - 'a.js': ` - /**, - * @providesModule xa - */`, - 'b.js': ` - /** - * @providesModule xb - */`, - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - 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: [], - }, - ], - }) - ) - ); - }); - - pit('should fully traverse sync dependencies', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - require("xa"); - ${'System.import'}("xb");`, - 'a.js': ` - /**, - * @providesModule xa - */`, - 'b.js': ` - /** - * @providesModule xb - */`, - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - 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: [], - }], - }) - ) - ); - }); - - pit('should include sync dependencies async dependencies might have', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("xa");`, - 'a.js': ` - /**, - * @providesModule xa - */, - require("xb");`, - 'b.js': ` - /** - * @providesModule xb - */ - require("xc");`, - 'c.js': ` - /** - * @providesModule xc - */`, - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - 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: [], - }], - }) - ) - ); - }); - - pit('should allow duplicated dependencies across bundles', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("xa"); - ${'System.import'}("xb");`, - 'a.js': ` - /**, - * @providesModule xa - */, - require("xc");`, - 'b.js': ` - /** - * @providesModule xb - */ - require("xc");`, - 'c.js': ` - /** - * @providesModule xc - */`, - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - 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: [], - }, - ], - }) - ) - ); - }); - - pit('should put in separate bundles async dependencies of async dependencies', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("xa");`, - 'a.js': ` - /**, - * @providesModule xa - */, - ${'System.import'}("xb");`, - 'b.js': ` - /** - * @providesModule xb - */ - require("xc");`, - 'c.js': ` - /** - * @providesModule xc - */`, - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - 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: [], - }], - }, - ], - }) - ) - ); - }); - - pit('should put image dependencies into separate bundles', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("xa");`, - 'a.js':` - /**, - * @providesModule xa - */, - require("./img.png");`, - 'img.png': '', - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - 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: [], - }], - }) - ) - ); - }); - - pit('should put image dependencies across bundles', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("xa"); - ${'System.import'}("xb");`, - 'a.js':` - /**, - * @providesModule xa - */, - require("./img.png");`, - 'b.js':` - /**, - * @providesModule xb - */, - require("./img.png");`, - 'img.png': '', - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - 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: [], - }, - ], - }) - ) - ); - }); - - pit('could async require asset', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("./img.png");`, - 'img.png': '', - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - stripPolyfills(bundles).then(resolvedBundles => - expect(resolvedBundles).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js'], - children: [{ - id: 'bundle.0.1', - modules: ['/root/img.png'], - children: [], - }], - }) - ) - ); - }); - - pit('should include deprecated assets into separate bundles', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("xa");`, - 'a.js':` - /**, - * @providesModule xa - */, - require("image!img");`, - 'img.png': '', - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - 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: [], - }], - }) - ) - ); - }); - - pit('could async require deprecated asset', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("image!img");`, - 'img.png': '', - } - }); - - return newBundlesLayout().getLayout('/root/index.js').then(bundles => - stripPolyfills(bundles).then(resolvedBundles => - expect(resolvedBundles).toEqual({ - id: 'bundle.0', - modules: ['/root/index.js'], - children: [{ - id: 'bundle.0.1', - modules: ['/root/img.png'], - children: [], - }], - }) - ) - ); - }); - - pit('should put packages into bundles', () => { - setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule xindex - */ - ${'System.import'}("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().getLayout('/root/index.js').then(bundles => - 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: [], - }], - }) - ) - ); - }); - }); - - function getBaseFs() { - const p = path.join(__dirname, '../../../Resolver/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 deleted file mode 100644 index 2fd90623..00000000 --- a/react-packager/src/BundlesLayout/index.js +++ /dev/null @@ -1,219 +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'; - -const Activity = require('../Activity'); - -const _ = require('underscore'); -const declareOpts = require('../lib/declareOpts'); -const fs = require('fs'); -const getCacheFilePath = require('../DependencyResolver/Cache/lib/getCacheFilePath'); -const loadCacheSync = require('../DependencyResolver/Cache/lib/loadCacheSync'); -const version = require('../../../../package.json').version; -const path = require('path'); -const tmpdir = require('os').tmpDir(); - -const validateOpts = declareOpts({ - dependencyResolver: { - type: 'object', - required: true, - }, - resetCache: { - type: 'boolean', - default: false, - }, - cacheVersion: { - type: 'string', - default: '1.0', - }, - projectRoots: { - type: 'array', - required: true, - }, -}); - -const BUNDLE_PREFIX = 'bundle'; - -/** - * 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; - - // Cache in which bundle is each module. - this._moduleToBundle = Object.create(null); - - // Cache the bundles layouts for each entry point. This entries - // are not evicted unless the user explicitly specifies so as - // computing them is pretty expensive - this._layouts = Object.create(null); - - // TODO: watch for file creations and removals to update this caches - - this._cacheFilePath = this._getCacheFilePath(opts); - if (!opts.resetCache) { - this._loadCacheSync(this._cacheFilePath); - } else { - this._persistCacheEventually(); - } - } - - getLayout(entryPath, isDev) { - if (this._layouts[entryPath]) { - return this._layouts[entryPath]; - } - var currentBundleID = 0; - const rootBundle = { - id: BUNDLE_PREFIX + '.' + currentBundleID++, - modules: [], - children: [], - }; - var pending = [{paths: [entryPath], bundle: rootBundle}]; - - this._layouts[entryPath] = promiseWhile( - () => pending.length > 0, - () => rootBundle, - () => { - const {paths, bundle} = pending.shift(); - - // pending sync dependencies we still need to explore for the current - // pending dependency - const pendingSyncDeps = paths; - - // 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 = Object.keys(syncDependencies); - if (dependencies.length > 0) { - bundle.modules = dependencies; - } - - // persist changes to layouts - this._persistCacheEventually(); - }, - index => { - 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] = true; - 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}); - }); - }); - }, - ); - }, - ); - - return this._layouts[entryPath]; - } - - getBundleIDForModule(path) { - return this._moduleToBundle[path]; - } - - _loadCacheSync(cachePath) { - const loadCacheId = Activity.startEvent('Loading bundles layout'); - const cacheOnDisk = loadCacheSync(cachePath); - - // TODO: create single-module bundles for unexistent modules - // TODO: remove modules that no longer exist - Object.keys(cacheOnDisk).forEach(entryPath => { - this._layouts[entryPath] = Promise.resolve(cacheOnDisk[entryPath]); - this._fillModuleToBundleMap(cacheOnDisk[entryPath]); - }); - - Activity.endEvent(loadCacheId); - } - - _fillModuleToBundleMap(bundle) { - bundle.modules.forEach(module => this._moduleToBundle[module] = bundle.id); - bundle.children.forEach(child => this._fillModuleToBundleMap(child)); - } - - _persistCacheEventually() { - _.debounce( - this._persistCache.bind(this), - 2000, - ); - } - - _persistCache() { - if (this._persisting !== null) { - return this._persisting; - } - - this._persisting = Promise - .all(_.values(this._layouts)) - .then(bundlesLayout => { - var json = Object.create(null); - Object.keys(this._layouts).forEach((p, i) => - json[p] = bundlesLayout[i] - ); - - return Promise.denodeify(fs.writeFile)( - this._cacheFilepath, - JSON.stringify(json), - ); - }) - .then(() => this._persisting = null); - - return this._persisting; - } - - _getCacheFilePath(options) { - return getCacheFilePath( - tmpdir, - 'react-packager-bundles-cache-', - version, - options.projectRoots.join(',').split(path.sep).join('-'), - options.cacheVersion || '0', - ); - } -} - -// 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) { - return _promiseWhile(condition, result, body, 0); -} - -function _promiseWhile(condition, result, body, index) { - if (!condition()) { - return Promise.resolve(result()); - } - - return body(index).then(() => - _promiseWhile(condition, result, body, index + 1) - ); -} - -module.exports = BundlesLayout; diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js index f5555fac..c2f51fbb 100644 --- a/react-packager/src/DependencyResolver/AssetModule.js +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -21,10 +21,6 @@ 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 a77d0aef..3c465946 100644 --- a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js +++ b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -24,10 +24,6 @@ class AssetModule_DEPRECATED extends Module { return Promise.resolve([]); } - getAsyncDependencies() { - return Promise.resolve([]); - } - hash() { return `AssetModule_DEPRECATED : ${this.path}`; } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 879c53ef..c4004ed7 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -187,23 +187,6 @@ class ResolutionRequest { }); } - getAsyncDependencies(response) { - return Promise.resolve().then(() => { - const mod = this._moduleCache.getModule(this._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))) - ); - }).then(asyncDependencies => asyncDependencies.forEach( - (dependency) => response.pushAsyncDependency(dependency) - )); - } - _getAllMocks(pattern) { // Take all mocks in all the roots into account. This is necessary // because currently mocks are global: any module can be mocked by diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js index c13895fb..56e1ed7a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js @@ -11,7 +11,6 @@ class ResolutionResponse { constructor() { this.dependencies = []; - this.asyncDependencies = []; this.mainModuleId = null; this.mocks = null; this.numPrependedDependencies = 0; @@ -22,13 +21,11 @@ class ResolutionResponse { copy(properties) { const { dependencies = this.dependencies, - asyncDependencies = this.asyncDependencies, mainModuleId = this.mainModuleId, mocks = this.mocks, } = properties; return Object.assign(new this.constructor(), this, { dependencies, - asyncDependencies, mainModuleId, mocks, }); @@ -69,11 +66,6 @@ class ResolutionResponse { this.numPrependedDependencies += 1; } - pushAsyncDependency(dependency) { - this._assertNotFinalized(); - this.asyncDependencies.push(dependency); - } - setResolvedDependencyPairs(module, pairs) { this._assertNotFinalized(); const hash = module.hash(); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 09a44543..990e234f 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -4068,40 +4068,6 @@ describe('DependencyGraph', function() { }); }); - describe('getAsyncDependencies', () => { - pit('should get dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'System.' + 'import("a")', - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - - return dgraph.getDependencies('/root/index.js') - .then(response => response.finalize()) - .then(({ asyncDependencies }) => { - expect(asyncDependencies).toEqual([ - ['/root/a.js'], - ]); - }); - }); - }); - describe('Extensions', () => { pit('supports custom file extensions', () => { var root = '/root'; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 3c5ade66..9241b18a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -185,14 +185,11 @@ class DependencyGraph { const response = new ResolutionResponse(); - return Promise.all([ - req.getOrderedDependencies( - response, - this._opts.mocksPattern, - recursive, - ), - req.getAsyncDependencies(response), - ]).then(() => response); + return req.getOrderedDependencies( + response, + this._opts.mocksPattern, + recursive, + ).then(() => response); }); } diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index f9142611..116a52e0 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -91,14 +91,6 @@ class Module { ); } - getAsyncDependencies() { - return this._cache.get( - this.path, - 'asyncDependencies', - () => this.read().then(data => data.asyncDependencies) - ); - } - invalidate() { this._cache.invalidate(this.path); } @@ -146,7 +138,6 @@ class Module { return { id, dependencies: [], - asyncDependencies: [], code: content, }; } else { @@ -155,13 +146,12 @@ class Module { ? transformCode(this, content) : Promise.resolve({code: content}); - return codePromise.then(({code, dependencies, asyncDependencies}) => { + return codePromise.then(({code, dependencies}) => { const {deps} = this._extractor(code); return { id, code, dependencies: dependencies || deps.sync, - asyncDependencies: asyncDependencies || deps.async, }; }); } diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 4ed5868b..71bc5522 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -167,45 +167,6 @@ describe('Module', () => { }); }); - describe('Async Dependencies', () => { - function expectAsyncDependenciesToEqual(expected) { - const module = createModule(); - return module.getAsyncDependencies().then(actual => - expect(actual).toEqual(expected) - ); - } - - pit('should recognize single dependency', () => { - mockIndexFile('System.' + 'import("dep1")'); - - return expectAsyncDependenciesToEqual([['dep1']]); - }); - - pit('should parse single quoted dependencies', () => { - mockIndexFile('System.' + 'import(\'dep1\')'); - - return expectAsyncDependenciesToEqual([['dep1']]); - }); - - pit('should parse multiple async dependencies on the same module', () => { - mockIndexFile([ - 'System.' + 'import("dep1")', - 'System.' + 'import("dep2")', - ].join('\n')); - - return expectAsyncDependenciesToEqual([ - ['dep1'], - ['dep2'], - ]); - }); - - pit('parse fine new lines', () => { - mockIndexFile('System.' + 'import(\n"dep1"\n)'); - - return expectAsyncDependenciesToEqual([['dep1']]); - }); - }); - describe('Code', () => { const fileContents = 'arbitrary(code)'; beforeEach(function() { @@ -247,7 +208,7 @@ describe('Module', () => { const fileContents = 'arbitrary(code);'; const exampleCode = ` require('a'); - System.import('b'); + arbitrary.code('b'); require('c');`; beforeEach(function() { @@ -268,12 +229,8 @@ describe('Module', () => { transformCode.mockReturnValue(Promise.resolve({code: exampleCode})); const module = createModule({transformCode}); - return Promise.all([ - module.getDependencies(), - module.getAsyncDependencies(), - ]).then(([dependencies, asyncDependencies]) => { + return module.getDependencies().then(dependencies => { expect(dependencies).toEqual(['a', 'c']); - expect(asyncDependencies).toEqual([['b']]); }); }); @@ -285,29 +242,8 @@ describe('Module', () => { })); const module = createModule({transformCode}); - return Promise.all([ - module.getDependencies(), - module.getAsyncDependencies(), - ]).then(([dependencies, asyncDependencies]) => { + return module.getDependencies().then(dependencies => { expect(dependencies).toEqual(mockedDependencies); - expect(asyncDependencies).toEqual([['b']]); - }); - }); - - pit('uses async dependencies that `transformCode` resolves to, instead of extracting them', () => { - const mockedAsyncDependencies = [['foo', 'bar'], ['baz']]; - transformCode.mockReturnValue(Promise.resolve({ - code: exampleCode, - asyncDependencies: mockedAsyncDependencies, - })); - const module = createModule({transformCode}); - - return Promise.all([ - module.getDependencies(), - module.getAsyncDependencies(), - ]).then(([dependencies, asyncDependencies]) => { - expect(dependencies).toEqual(['a', 'c']); - expect(asyncDependencies).toEqual(mockedAsyncDependencies); }); }); diff --git a/react-packager/src/DependencyResolver/lib/extractRequires.js b/react-packager/src/DependencyResolver/lib/extractRequires.js index 107cc6ba..5dbf63cc 100644 --- a/react-packager/src/DependencyResolver/lib/extractRequires.js +++ b/react-packager/src/DependencyResolver/lib/extractRequires.js @@ -18,7 +18,6 @@ const lineCommentRe = /\/\/.+(\n|$)/g; function extractRequires(code) { var deps = { sync: [], - async: [], }; code = code @@ -41,15 +40,6 @@ function extractRequires(code) { deps.sync.push(dep); return match; }) - // 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 the import statement: - // System.import('dep1') - .replace(replacePatterns.SYSTEM_IMPORT_RE, (match, pre, quot, dep, post) => { - deps.async.push([dep]); - return match; - }); return {code, deps}; } diff --git a/react-packager/src/DependencyResolver/lib/replacePatterns.js b/react-packager/src/DependencyResolver/lib/replacePatterns.js index a4e563d2..8c10710e 100644 --- a/react-packager/src/DependencyResolver/lib/replacePatterns.js +++ b/react-packager/src/DependencyResolver/lib/replacePatterns.js @@ -12,4 +12,3 @@ exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; -exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 7ef335d9..2f1f65de 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -37,10 +37,9 @@ describe('Resolver', function() { }); class ResolutionResponseMock { - constructor({dependencies, mainModuleId, asyncDependencies}) { + constructor({dependencies, mainModuleId}) { this.dependencies = dependencies; this.mainModuleId = mainModuleId; - this.asyncDependencies = asyncDependencies; } prependDependency(dependency) { @@ -80,7 +79,6 @@ describe('Resolver', function() { return Promise.resolve(new ResolutionResponseMock({ dependencies: deps, mainModuleId: 'index', - asyncDependencies: [], })); }); @@ -180,7 +178,6 @@ describe('Resolver', function() { return Promise.resolve(new ResolutionResponseMock({ dependencies: deps, mainModuleId: 'index', - asyncDependencies: [], })); }); @@ -208,7 +205,6 @@ describe('Resolver', function() { return Promise.resolve(new ResolutionResponseMock({ dependencies: deps, mainModuleId: 'index', - asyncDependencies: [], })); }); @@ -637,7 +633,6 @@ describe('Resolver', function() { const resolutionResponse = new ResolutionResponseMock({ dependencies: [module], mainModuleId: 'test module', - asyncDependencies: [], }); resolutionResponse.getResolvedDependencyPairs = (module) => { diff --git a/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js b/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js deleted file mode 100644 index 39d68788..00000000 --- a/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js +++ /dev/null @@ -1,63 +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'; - -jest.dontMock('../loadBundles'); -jest.mock('NativeModules'); - -let loadBundles; -let loadBundlesCalls; - -describe('loadBundles', () => { - beforeEach(() => { - loadBundles = jest.genMockFunction(); - loadBundlesCalls = loadBundles.mock.calls; - require('NativeModules').RCTBundlesLoader = {loadBundles}; - - require('../loadBundles'); - }); - - it('should set `global.__loadBundles` function when polyfill is initialized', () => { - expect(typeof global.__loadBundles).toBe('function'); - }); - - it('should return a promise', () => { - loadBundles.mockImpl((bundles, callback) => callback()); - expect(global.__loadBundles(['bundle.0']) instanceof Promise).toBeTruthy(); - }); - - pit('shouldn\'t request already loaded bundles', () => { - loadBundles.mockImpl((bundles, callback) => callback()); - return global.__loadBundles(['bundle.0']) - .then(() => global.__loadBundles(['bundle.0'])) - .then(() => expect(loadBundlesCalls.length).toBe(1)); - }); - - pit('shouldn\'n request inflight bundles', () => { - loadBundles.mockImpl((bundles, callback) => { - if (bundles.length === 1 && bundles[0] === 'bundle.0') { - setTimeout(callback, 1000); - } else if (bundles.length === 1 && bundles[0] === 'bundle.1') { - setTimeout(callback, 500); - } - }); - - const promises = Promise.all([ - global.__loadBundles(['bundle.0']), - global.__loadBundles(['bundle.0', 'bundle.1']), - ]).then(() => { - expect(loadBundlesCalls.length).toBe(2); - expect(loadBundlesCalls[0][0][0]).toBe('bundle.0'); - expect(loadBundlesCalls[1][0][0]).toBe('bundle.1'); - }); - - jest.runAllTimers(); - return promises; - }); -}); diff --git a/react-packager/src/Resolver/polyfills/loadBundles.js b/react-packager/src/Resolver/polyfills/loadBundles.js deleted file mode 100644 index 7bb9be10..00000000 --- a/react-packager/src/Resolver/polyfills/loadBundles.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint strict:0 */ - -let loadBundlesOnNative = (bundles) => - new Promise((resolve) => - require('NativeModules').RCTBundlesLoader.loadBundles(bundles, resolve)); - -let requestedBundles = Object.create(null); - -/** - * Returns a promise that is fulfilled once all the indicated bundles are - * loaded into memory and injected into the JS engine. - * This invokation might need to go through the bridge - * and run native code to load some, if not all, the requested bundles. - * If all the bundles have already been loaded, the promise is resolved - * immediately. Otherwise, we'll determine which bundles still need to get - * loaded considering both, the ones already loaded, and the ones being - * currently asynchronously loaded by other invocations to `__loadBundles`, - * and return a promise that will get fulfilled once all these are finally - * loaded. - * - * Note this function should only be invoked by generated code. - */ -global.__loadBundles = function(bundles) { - // split bundles by whether they've already been requested or not - const bundlesToRequest = bundles.filter(b => !requestedBundles[b]); - - // keep a reference to the promise loading each bundle - if (bundlesToRequest.length > 0) { - const nativePromise = loadBundlesOnNative(bundlesToRequest); - bundlesToRequest.forEach(b => requestedBundles[b] = nativePromise); - } - - return Promise.all(bundles.map(bundle => requestedBundles[bundle])); -}; diff --git a/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js b/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js deleted file mode 100644 index b29f1bd5..00000000 --- a/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @emails oncall+jsinfra - */ - -'use strict'; - -jest.autoMockOff(); -jest.mock('../../../BundlesLayout'); - -const babel = require('babel-core'); -const BundlesLayout = require('../../../BundlesLayout'); - -const testData = { - isolated: { - input: 'System.' + 'import("moduleA");', - output: 'loadBundles(["bundle.0"]);' - }, - single: { - input: 'System.' + 'import("moduleA").then(function (bundleA) {});', - output: 'loadBundles(["bundle.0"]).then(function (bundleA) {});' - }, - multiple: { - input: [ - 'Promise.all([', - 'System.' + 'import("moduleA"), System.' + 'import("moduleB"),', - ']).then(function (bundlesA, bundlesB) {});', - ].join('\n'), - output: [ - 'Promise.all([', - 'loadBundles(["bundle.0"]), loadBundles(["bundle.1"])', - ']).then(function (bundlesA, bundlesB) {});', - ].join(''), - }, -}; - -describe('System.import', () => { - let layout = new BundlesLayout(); - BundlesLayout.prototype.getBundleIDForModule.mockImpl(module => { - switch (module) { - case 'moduleA': return 'bundle.0'; - case 'moduleB': return 'bundle.1'; - } - }); - - function transform(source) { - return babel.transform(source, { - plugins: [ - [require('../'), { bundlesLayout: layout }] - ], - }).code; - } - - function test(data) { - // transform and remove new lines - expect(transform(data.input).replace(/(\r\n|\n|\r)/gm,'')).toEqual(data.output); - } - - it('should transform isolated `System.import`', () => { - test(testData.isolated); - }); - - it('should transform single `System.import`', () => { - test(testData.single); - }); - - it('should transform multiple `System.import`s', () => { - test(testData.multiple); - }); -}); diff --git a/react-packager/src/transforms/babel-plugin-system-import/index.js b/react-packager/src/transforms/babel-plugin-system-import/index.js deleted file mode 100644 index 7cf0f0a0..00000000 --- a/react-packager/src/transforms/babel-plugin-system-import/index.js +++ /dev/null @@ -1,63 +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. - * - */ -/*jslint node: true */ -'use strict'; - -const t = require('babel-types'); - -/** -* Transforms asynchronous module importing into a function call -* that includes which bundle needs to be loaded -* -* Transforms: -* -* System.import('moduleA') -* -* to: -* -* loadBundles('bundleA') -*/ -module.exports = function() { - return { - visitor: { - CallExpression: function (path, state) { - if (!isAppropriateSystemImportCall(path.node)) { - return; - } - - var bundlesLayout = state.opts.bundlesLayout; - var bundleID = bundlesLayout.getBundleIDForModule( - path.node.arguments[0].value - ); - - var bundles = bundleID.split('.'); - bundles.splice(0, 1); - bundles = bundles.map(function(id) { - return t.stringLiteral('bundle.' + id); - }); - - path.replaceWith(t.callExpression( - t.identifier('loadBundles'), - [t.arrayExpression(bundles)] - )); - }, - }, - }; -}; - -function isAppropriateSystemImportCall(node) { - return ( - node.callee.type === 'MemberExpression' && - node.callee.object.name === 'System' && - node.callee.property.name === 'import' && - node.arguments.length === 1 && - node.arguments[0].type === 'StringLiteral' - ); -} From fd7782849eca04fe74c9dbf5d8a836febb997ca1 Mon Sep 17 00:00:00 2001 From: Adam Miskiewicz Date: Thu, 11 Feb 2016 02:20:59 -0800 Subject: [PATCH 584/936] Remove knowledge of fbjs from the packager Summary: As spicyj mentioned in commit 6a838a4, the ideal state of affairs when it comes to consuming `react` and `fbjs` from NPM is for the packager not to have knowledge of either package. This PR addresses the `fbjs` part of that, and relies on https://github.com/facebook/fbjs/pull/95. **DO NOT MERGE** until #95 (or a variation) is in `fbjs` and is released to npm. This PR does several things: 1. Adds stub modules within RN that expose `fbjs` modules to be required using Haste. After discussing a few ideas with spicyj, this seemed like a good option to keep internal FB devs happy (and not make them change the way they write JS), but allow for removing packager complexity and fit in better with the NPM ecosystem. Note -- it skips stubbing `fetch`, `ExecutionEnvironment`, and `ErrorUtils`, due to the fact that these need to have Native specific implementations, and there's no reason for those implementations to exist in `fbjs`. 2. Removes the modules that were previously being used in lieu of their `fbjs` eq Closes https://github.com/facebook/react-native/pull/5084 Reviewed By: bestander Differential Revision: D2803288 Pulled By: davidaurelio fb-gh-sync-id: fd257958ee2f8696eebe9048c1e7628c168bf4a2 shipit-source-id: fd257958ee2f8696eebe9048c1e7628c168bf4a2 --- blacklist.js | 30 ---------------------------- react-packager/src/Resolver/index.js | 1 - 2 files changed, 31 deletions(-) diff --git a/blacklist.js b/blacklist.js index ee369e1a..318e09e8 100644 --- a/blacklist.js +++ b/blacklist.js @@ -17,36 +17,6 @@ var sharedBlacklist = [ 'node_modules/react/lib/React.js', 'node_modules/react/lib/ReactDOM.js', - // For each of these fbjs files (especially the non-forks/stubs), we should - // consider deleting the conflicting copy and just using the fbjs version. - // - // fbjs forks: - 'node_modules/fbjs/lib/Map.js', - 'node_modules/fbjs/lib/Promise.js', - 'node_modules/fbjs/lib/fetch.js', - // fbjs stubs: - 'node_modules/fbjs/lib/ErrorUtils.js', - 'node_modules/fbjs/lib/URI.js', - // fbjs modules: - 'node_modules/fbjs/lib/Deferred.js', - 'node_modules/fbjs/lib/PromiseMap.js', - 'node_modules/fbjs/lib/UserAgent.js', - 'node_modules/fbjs/lib/areEqual.js', - 'node_modules/fbjs/lib/base62.js', - 'node_modules/fbjs/lib/crc32.js', - 'node_modules/fbjs/lib/everyObject.js', - 'node_modules/fbjs/lib/fetchWithRetries.js', - 'node_modules/fbjs/lib/filterObject.js', - 'node_modules/fbjs/lib/flattenArray.js', - 'node_modules/fbjs/lib/forEachObject.js', - 'node_modules/fbjs/lib/isEmpty.js', - 'node_modules/fbjs/lib/nullthrows.js', - 'node_modules/fbjs/lib/removeFromArray.js', - 'node_modules/fbjs/lib/resolveImmediate.js', - 'node_modules/fbjs/lib/someObject.js', - 'node_modules/fbjs/lib/sprintf.js', - 'node_modules/fbjs/lib/xhrSimpleDataSerializer.js', - // Those conflicts with the ones in fbjs/. We need to blacklist the // internal version otherwise they won't work in open source. 'downstream/core/CSSCore.js', diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index a677c219..10e138c8 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -85,7 +85,6 @@ class Resolver { (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, providesModuleNodeModules: [ - 'fbjs', 'react', 'react-native', // Parse requires AsyncStorage. They will From 2dd2975fb914f91121d4f18ad94fa0b1b7b60fc1 Mon Sep 17 00:00:00 2001 From: Adam Miskiewicz Date: Thu, 11 Feb 2016 02:44:24 -0800 Subject: [PATCH 585/936] Reverted commit D2803288 Summary: As spicyj mentioned in commit 6a838a4, the ideal state of affairs when it comes to consuming `react` and `fbjs` from NPM is for the packager not to have knowledge of either package. This PR addresses the `fbjs` part of that, and relies on https://github.com/facebook/fbjs/pull/95. **DO NOT MERGE** until #95 (or a variation) is in `fbjs` and is released to npm. This PR does several things: 1. Adds stub modules within RN that expose `fbjs` modules to be required using Haste. After discussing a few ideas with spicyj, this seemed like a good option to keep internal FB devs happy (and not make them change the way they write JS), but allow for removing packager complexity and fit in better with the NPM ecosystem. Note -- it skips stubbing `fetch`, `ExecutionEnvironment`, and `ErrorUtils`, due to the fact that these need to have Native specific implementations, and there's no reason for those implementations to exist in `fbjs`. 2. Removes the modules that were previously being used in lieu of their `fbjs` eq Closes https://github.com/facebook/react-native/pull/5084 Reviewed By: bestander Differential Revision: D2803288 Pulled By: javache fb-gh-sync-id: 121ae811ce4cc30e6ea79246f85a1e4f65648ce1 shipit-source-id: 121ae811ce4cc30e6ea79246f85a1e4f65648ce1 --- blacklist.js | 30 ++++++++++++++++++++++++++++ react-packager/src/Resolver/index.js | 1 + 2 files changed, 31 insertions(+) diff --git a/blacklist.js b/blacklist.js index 318e09e8..ee369e1a 100644 --- a/blacklist.js +++ b/blacklist.js @@ -17,6 +17,36 @@ var sharedBlacklist = [ 'node_modules/react/lib/React.js', 'node_modules/react/lib/ReactDOM.js', + // For each of these fbjs files (especially the non-forks/stubs), we should + // consider deleting the conflicting copy and just using the fbjs version. + // + // fbjs forks: + 'node_modules/fbjs/lib/Map.js', + 'node_modules/fbjs/lib/Promise.js', + 'node_modules/fbjs/lib/fetch.js', + // fbjs stubs: + 'node_modules/fbjs/lib/ErrorUtils.js', + 'node_modules/fbjs/lib/URI.js', + // fbjs modules: + 'node_modules/fbjs/lib/Deferred.js', + 'node_modules/fbjs/lib/PromiseMap.js', + 'node_modules/fbjs/lib/UserAgent.js', + 'node_modules/fbjs/lib/areEqual.js', + 'node_modules/fbjs/lib/base62.js', + 'node_modules/fbjs/lib/crc32.js', + 'node_modules/fbjs/lib/everyObject.js', + 'node_modules/fbjs/lib/fetchWithRetries.js', + 'node_modules/fbjs/lib/filterObject.js', + 'node_modules/fbjs/lib/flattenArray.js', + 'node_modules/fbjs/lib/forEachObject.js', + 'node_modules/fbjs/lib/isEmpty.js', + 'node_modules/fbjs/lib/nullthrows.js', + 'node_modules/fbjs/lib/removeFromArray.js', + 'node_modules/fbjs/lib/resolveImmediate.js', + 'node_modules/fbjs/lib/someObject.js', + 'node_modules/fbjs/lib/sprintf.js', + 'node_modules/fbjs/lib/xhrSimpleDataSerializer.js', + // Those conflicts with the ones in fbjs/. We need to blacklist the // internal version otherwise they won't work in open source. 'downstream/core/CSSCore.js', diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 10e138c8..a677c219 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -85,6 +85,7 @@ class Resolver { (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, providesModuleNodeModules: [ + 'fbjs', 'react', 'react-native', // Parse requires AsyncStorage. They will From d066468cb265bf88ef3d19181ada28ceed1fc1e6 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Fri, 12 Feb 2016 06:13:19 -0800 Subject: [PATCH 586/936] Let the module cache depend on transform options Reviewed By: martinbigio Differential Revision: D2921693 fb-gh-sync-id: 6f95bdb03d59183a1b5f50db16c6aab2b16d3146 shipit-source-id: 6f95bdb03d59183a1b5f50db16c6aab2b16d3146 --- .../__tests__/DependencyGraph-test.js | 44 +++++- .../src/DependencyResolver/Module.js | 131 +++++++++++------- .../__tests__/Module-test.js | 92 ++++++++++-- 3 files changed, 202 insertions(+), 65 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 990e234f..c5b0b754 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -48,11 +48,41 @@ describe('DependencyGraph', function() { isWatchman: () => Promise.resolve(false), }; - const Cache = jest.genMockFn(); - Cache.prototype.get = jest.genMockFn().mockImplementation( - (filepath, field, cb) => cb(filepath) - ); - Cache.prototype.invalidate = jest.genMockFn(); + const Cache = jest.genMockFn().mockImplementation(function() { + this._maps = Object.create(null); + }); + Cache.prototype.has = jest.genMockFn() + .mockImplementation(function(filepath, field) { + if (!(filepath in this._maps)) { + return false; + } + return !field || field in this._maps[filepath]; + }); + Cache.prototype.get = jest.genMockFn() + .mockImplementation(function(filepath, field, factory) { + let cacheForPath = this._maps[filepath]; + if (this.has(filepath, field)) { + return field ? cacheForPath[field] : cacheForPath; + } + + if (!cacheForPath) { + cacheForPath = this._maps[filepath] = Object.create(null); + } + const value = cacheForPath[field] = factory(); + return value; + }); + Cache.prototype.invalidate = jest.genMockFn() + .mockImplementation(function(filepath, field) { + if (!this.has(filepath, field)) { + return; + } + + if (field) { + delete this._maps[filepath][field]; + } else { + delete this._maps[filepath]; + } + }); Cache.prototype.end = jest.genMockFn(); defaults = { @@ -3688,7 +3718,9 @@ describe('DependencyGraph', function() { }); }); - pit('updates package.json', function() { + //TODO(davidaurelio) Make this actually worked. The test only passed because + // the mocked cache didn't cache. In reality, it didn't work. I tried it. + xpit('updates package.json', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ 'root': { diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 116a52e0..c75473ac 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -8,6 +8,7 @@ */ 'use strict'; +const crypto = require('crypto'); const docblock = require('./DependencyGraph/docblock'); const isAbsolutePath = require('absolute-path'); const path = require('path'); @@ -43,12 +44,16 @@ class Module { return this._cache.get( this.path, 'isHaste', - () => this._readDocBlock().then(data => !!data.id) + () => this._readDocBlock().then(({id}) => !!id) ); } - getCode() { - return this.read().then(({code}) => code); + getCode(transformOptions) { + return this.read(transformOptions).then(({code}) => code); + } + + getMap(transformOptions) { + return this.read(transformOptions).then(({map}) => map); } getName() { @@ -83,12 +88,8 @@ class Module { return this._moduleCache.getPackageForModule(this); } - getDependencies() { - return this._cache.get( - this.path, - 'dependencies', - () => this.read().then(data => data.dependencies) - ); + getDependencies(transformOptions) { + return this.read(transformOptions).then(data => data.dependencies); } invalidate() { @@ -108,56 +109,50 @@ class Module { const id = provides && !this._depGraphHelpers.isNodeModulesDir(this.path) ? /^\S+/.exec(provides)[0] : undefined; - return [id, moduleDocBlock]; + return {id, moduleDocBlock}; } - _readDocBlock() { - const reading = this._reading || this._docBlock; - if (reading) { - return reading; + _readDocBlock(contentPromise) { + if (!this._docBlock) { + if (!contentPromise) { + contentPromise = this._fastfs.readWhile(this.path, whileInDocBlock); + } + this._docBlock = contentPromise + .then(docBlock => this._parseDocBlock(docBlock)); } - this._docBlock = this._fastfs.readWhile(this.path, whileInDocBlock) - .then(docBlock => { - const [id] = this._parseDocBlock(docBlock); - return {id}; - }); return this._docBlock; } - read() { - if (this._reading) { - return this._reading; - } + read(transformOptions) { + return this._cache.get( + this.path, + cacheKey('moduleData', transformOptions), + () => { + const fileContentPromise = this._fastfs.readFile(this.path); + return Promise.all([ + fileContentPromise, + this._readDocBlock(fileContentPromise) + ]).then(([code, {id, moduleDocBlock}]) => { + // Ignore requires in JSON files or generated code. An example of this + // is prebuilt files like the SourceMap library. + if (this.isJSON() || 'extern' in moduleDocBlock) { + return {id, code, dependencies: []}; + } else { + const transformCode = this._transformCode; + const codePromise = transformCode + ? transformCode(this, code, transformOptions) + : Promise.resolve({code}); - this._reading = this._fastfs.readFile(this.path).then(content => { - const [id, moduleDocBlock] = this._parseDocBlock(content); - - // Ignore requires in JSON files or generated code. An example of this - // is prebuilt files like the SourceMap library. - if (this.isJSON() || 'extern' in moduleDocBlock) { - return { - id, - dependencies: [], - code: content, - }; - } else { - const transformCode = this._transformCode; - const codePromise = transformCode - ? transformCode(this, content) - : Promise.resolve({code: content}); - - return codePromise.then(({code, dependencies}) => { - const {deps} = this._extractor(code); - return { - id, - code, - dependencies: dependencies || deps.sync, - }; - }); + return codePromise.then(({code, dependencies, map}) => { + if (!dependencies) { + dependencies = this._extractor(code).deps.sync; + } + return {id, code, dependencies, map}; + }); + } + }) } - }); - - return this._reading; + ); } hash() { @@ -207,4 +202,38 @@ function whileInDocBlock(chunk, i, result) { return !/\*\//.test(result); } +// use weak map to speed up hash creation of known objects +const knownHashes = new WeakMap(); +function stableObjectHash(object) { + let digest = knownHashes.get(object); + + if (!digest) { + const hash = crypto.createHash('md5'); + stableObjectHash.addTo(object, hash); + digest = hash.digest('base64'); + knownHashes.set(object, digest); + } + + return digest; +} +stableObjectHash.addTo = function addTo(value, hash) { + if (value === null || typeof value !== 'object') { + hash.update(JSON.stringify(value)); + } else { + Object.keys(value).sort().forEach(key => { + const valueForKey = value[key]; + if (valueForKey !== undefined) { + hash.update(key); + addTo(valueForKey, hash); + } + }); + } +}; + +function cacheKey(field, transformOptions) { + return transformOptions !== undefined + ? stableObjectHash(transformOptions) + '\0' + field + : field; +} + module.exports = Module; diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 71bc5522..71c3a43d 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -178,16 +178,10 @@ describe('Module', () => { expect(code).toBe(fileContents)) ); - pit('exposes file contes via the `getCode()` method', () => + pit('exposes file contents via the `getCode()` method', () => createModule().getCode().then(code => expect(code).toBe(fileContents)) ); - - pit('does not save the code in the cache', () => - createModule().getCode().then(() => - expect(cache.get).not.toBeCalled() - ) - ); }); describe('Extrators', () => { @@ -221,10 +215,19 @@ describe('Module', () => { const module = createModule({transformCode}); return module.read() .then(() => { - expect(transformCode).toBeCalledWith(module, fileContents); + expect(transformCode).toBeCalledWith(module, fileContents, undefined); }); }); + pit('passes any additional options to the transform function when reading', () => { + const module = createModule({transformCode}); + const transformOptions = {arbitrary: Object()}; + return module.read(transformOptions) + .then(() => + expect(transformCode.mock.calls[0][2]).toBe(transformOptions) + ); + }); + pit('uses the code that `transformCode` resolves to to extract dependencies', () => { transformCode.mockReturnValue(Promise.resolve({code: exampleCode})); const module = createModule({transformCode}); @@ -256,5 +259,78 @@ describe('Module', () => { expect(code).toBe(exampleCode); }); }); + + pit('exposes a source map returned by the transform', () => { + const map = {version: 3}; + transformCode.mockReturnValue(Promise.resolve({map, code: exampleCode})); + const module = createModule({transformCode}); + return Promise.all([module.read(), module.getMap()]) + .then(([data, sourceMap]) => { + expect(data.map).toBe(map); + expect(sourceMap).toBe(map); + }); + }); + + describe('Caching based on options', () => { + let module; + beforeEach(function() { + module = createModule({transformCode}); + }); + + const callsEqual = ([path1, key1], [path2, key2]) => { + expect(path1).toEqual(path2); + expect(key1).toEqual(key2); + } + + it('gets dependencies from the cache with the same cache key for the same transform options', () => { + const options = {some: 'options'}; + module.getDependencies(options); // first call + module.getDependencies(options); // second call + + const {calls} = cache.get.mock; + callsEqual(calls[0], calls[1]); + }); + + it('gets dependencies from the cache with the same cache key for the equivalent transform options', () => { + const options = {some: 'options'}; + module.getDependencies({a: 'b', c: 'd'}); // first call + module.getDependencies({c: 'd', a: 'b'}); // second call + + const {calls} = cache.get.mock; + callsEqual(calls[0], calls[1]); + }); + + it('gets dependencies from the cache with different cache keys for different transform options', () => { + module.getDependencies({some: 'options'}); + module.getDependencies({other: 'arbitrary options'}); + const {calls} = cache.get.mock; + expect(calls[0][1]).not.toEqual(calls[1][1]); + }); + + it('gets code from the cache with the same cache key for the same transform options', () => { + const options = {some: 'options'}; + module.getCode(options); // first call + module.getCode(options); // second call + + const {calls} = cache.get.mock; + callsEqual(calls[0], calls[1]); + }); + + it('gets code from the cache with the same cache key for the equivalent transform options', () => { + const options = {some: 'options'}; + module.getCode({a: 'b', c: 'd'}); // first call + module.getCode({c: 'd', a: 'b'}); // second call + + const {calls} = cache.get.mock; + callsEqual(calls[0], calls[1]); + }); + + it('gets code from the cache with different cache keys for different transform options', () => { + module.getCode({some: 'options'}); + module.getCode({other: 'arbitrary options'}); + const {calls} = cache.get.mock; + expect(calls[0][1]).not.toEqual(calls[1][1]); + }); + }); }); }); From c50043dd15a0876349d31ea9bb8b127c9de1312f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 12 Feb 2016 08:27:31 -0800 Subject: [PATCH 587/936] Fix RAM detection logic Reviewed By: davidaurelio Differential Revision: D2932012 fb-gh-sync-id: ae2340de09ec3a4fae9371c3b8e6148d4ef267b8 shipit-source-id: ae2340de09ec3a4fae9371c3b8e6148d4ef267b8 --- react-packager/src/Bundler/index.js | 4 ++-- react-packager/src/Resolver/index.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 99923e65..bc25ab1d 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -149,9 +149,9 @@ class Bundler { } bundle(options) { - const {dev, isUnbundle, platform} = options; + const {dev, unbundle, platform} = options; const moduleSystemDeps = - this._resolver.getModuleSystemDependencies({dev, isUnbundle, platform}); + this._resolver.getModuleSystemDependencies({dev, unbundle, platform}); return this._bundle({ bundle: new Bundle(options.sourceMapUrl), moduleSystemDeps, diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index a677c219..7e993a5d 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -60,7 +60,7 @@ const getDependenciesValidateOpts = declareOpts({ type: 'string', required: false, }, - isUnbundle: { + unbundle: { type: 'boolean', default: false }, @@ -144,7 +144,7 @@ class Resolver { ? path.join(__dirname, 'polyfills/prelude_dev.js') : path.join(__dirname, 'polyfills/prelude.js'); - const moduleSystem = opts.isUnbundle + const moduleSystem = opts.unbundle ? path.join(__dirname, 'polyfills/require-unbundle.js') : path.join(__dirname, 'polyfills/require.js'); From 012caf0e22d2f8ab6322d2e424af47ca14f89d20 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 15 Feb 2016 06:32:19 -0800 Subject: [PATCH 588/936] Remove test for missing feature Reviewed By: cpojer Differential Revision: D2937175 fb-gh-sync-id: 7c348018918a509e1cd681dfa8df9151b94ce765 shipit-source-id: 7c348018918a509e1cd681dfa8df9151b94ce765 --- .../__tests__/DependencyGraph-test.js | 66 ------------------- 1 file changed, 66 deletions(-) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index c5b0b754..fa823286 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -3718,72 +3718,6 @@ describe('DependencyGraph', function() { }); }); - //TODO(davidaurelio) Make this actually worked. The test only passed because - // the mocked cache didn't cache. In reality, it didn't work. I tried it. - xpit('updates package.json', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - 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); - - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'bPackage', - main: 'main.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['bPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - pit('changes to browser field', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ From 5357bd7d99b147ef036926d1bd3afeac67488914 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 15 Feb 2016 06:57:26 -0800 Subject: [PATCH 589/936] Replace custom rolled stable string hashing with library Reviewed By: cpojer Differential Revision: D2937202 fb-gh-sync-id: dc08547c71da2bc35cfad108e63fd5e87f0ba734 shipit-source-id: dc08547c71da2bc35cfad108e63fd5e87f0ba734 --- .../src/DependencyResolver/Module.js | 21 ++++--------------- .../__tests__/Module-test.js | 1 + 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index c75473ac..6b68a620 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -11,6 +11,7 @@ const crypto = require('crypto'); const docblock = require('./DependencyGraph/docblock'); const isAbsolutePath = require('absolute-path'); +const jsonStableStringify = require('json-stable-stringify'); const path = require('path'); const extractRequires = require('./lib/extractRequires'); @@ -206,29 +207,15 @@ function whileInDocBlock(chunk, i, result) { const knownHashes = new WeakMap(); function stableObjectHash(object) { let digest = knownHashes.get(object); - if (!digest) { - const hash = crypto.createHash('md5'); - stableObjectHash.addTo(object, hash); - digest = hash.digest('base64'); + digest = crypto.createHash('md5') + .update(jsonStableStringify(object)) + .digest('base64'); knownHashes.set(object, digest); } return digest; } -stableObjectHash.addTo = function addTo(value, hash) { - if (value === null || typeof value !== 'object') { - hash.update(JSON.stringify(value)); - } else { - Object.keys(value).sort().forEach(key => { - const valueForKey = value[key]; - if (valueForKey !== undefined) { - hash.update(key); - addTo(valueForKey, hash); - } - }); - } -}; function cacheKey(field, transformOptions) { return transformOptions !== undefined diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index 71c3a43d..bb4fa75e 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -10,6 +10,7 @@ jest .dontMock('absolute-path') + .dontMock('json-stable-stringify') .dontMock('../fastfs') .dontMock('../lib/extractRequires') .dontMock('../lib/replacePatterns') From 715c5b9b810b475b8f140f08edea777216abc572 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 15 Feb 2016 22:33:11 -0800 Subject: [PATCH 590/936] Improve performance of node-haste2 and react-packager Reviewed By: davidaurelio Differential Revision: D2911210 fb-gh-sync-id: 8ac2f213e8a9e089281bb065f9a7190d2e0f5b18 shipit-source-id: 8ac2f213e8a9e089281bb065f9a7190d2e0f5b18 --- react-packager/index.js | 1 + .../src/DependencyResolver/Cache/index.js | 4 +- .../Cache/lib/getCacheFilePath.js | 2 +- .../DependencyGraph/DependencyGraphHelpers.js | 14 +- .../DependencyGraph/DeprecatedAssetMap.js | 24 +-- .../DependencyGraph/HasteMap.js | 28 ++-- .../DependencyGraph/ResolutionRequest.js | 5 +- .../DependencyGraph/index.js | 36 +++-- .../FileWatcher/__mocks__/sane.js | 1 + .../FileWatcher/__tests__/FileWatcher-test.js | 73 +++++---- .../DependencyResolver/FileWatcher/index.js | 53 ++++--- .../src/DependencyResolver/Module.js | 8 +- .../src/DependencyResolver/ModuleCache.js | 5 +- .../src/DependencyResolver/Package.js | 4 +- .../__tests__/Module-test.js | 10 +- .../src/DependencyResolver/crawlers/node.js | 2 +- .../DependencyResolver/crawlers/watchman.js | 16 +- .../src/DependencyResolver/fastfs.js | 140 ++++++------------ .../DependencyResolver/lib/extractRequires.js | 2 +- .../lib/getAssetDataFromName.js | 2 +- .../lib/getPlatformExtension.js | 20 +-- react-packager/src/Server/index.js | 2 +- 22 files changed, 215 insertions(+), 237 deletions(-) diff --git a/react-packager/index.js b/react-packager/index.js index 5b28e774..a392bdf5 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -10,6 +10,7 @@ require('../babelRegisterOnly')([/react-packager\/src/]); +require('fast-path').replace(); useGracefulFs(); var debug = require('debug'); diff --git a/react-packager/src/DependencyResolver/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js index 4e1698cb..26ccb1e7 100644 --- a/react-packager/src/DependencyResolver/Cache/index.js +++ b/react-packager/src/DependencyResolver/Cache/index.js @@ -51,11 +51,9 @@ class Cache { throw new Error('Use absolute paths'); } - var recordP = this.has(filepath, field) + return this.has(filepath, field) ? this._data[filepath].data[field] : this.set(filepath, field, loaderCb(filepath)); - - return recordP.then(record => record); } invalidate(filepath, field) { diff --git a/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js b/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js index 3975b65a..a14faf9f 100644 --- a/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js +++ b/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js @@ -9,7 +9,7 @@ 'use strict'; const crypto = require('crypto'); -const path = require('path'); +const path = require('fast-path'); function getCacheFilePath(tmpdir, ...args) { const hash = crypto.createHash('md5'); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js b/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js index d7d9f20e..ed5a1a7b 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js @@ -8,7 +8,7 @@ */ 'use strict'; -const path = require('path'); +const path = require('fast-path'); class DependencyGraphHelpers { constructor({ providesModuleNodeModules, assetExts }) { @@ -17,17 +17,13 @@ class DependencyGraphHelpers { } isNodeModulesDir(file) { - let parts = path.normalize(file).split(path.sep); - const indexOfNodeModules = parts.lastIndexOf('node_modules'); - - if (indexOfNodeModules === -1) { + const index = file.lastIndexOf('/node_modules/'); + if (index === -1) { return false; } - parts = parts.slice(indexOfNodeModules + 1); - + const parts = file.substr(index + 14).split(path.sep); const dirs = this._providesModuleNodeModules; - for (let i = 0; i < dirs.length; i++) { if (parts.indexOf(dirs[i]) > -1) { return false; @@ -42,7 +38,7 @@ class DependencyGraphHelpers { } extname(name) { - return path.extname(name).replace(/^\./, ''); + return path.extname(name).substr(1); } } diff --git a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js index 5f18875a..531d54bc 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js @@ -11,7 +11,7 @@ const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); const Fastfs = require('../fastfs'); const debug = require('debug')('ReactNativePackager:DependencyGraph'); -const path = require('path'); +const path = require('fast-path'); const Promise = require('promise'); class DeprecatedAssetMap { @@ -23,8 +23,9 @@ class DeprecatedAssetMap { ignoreFilePath, helpers, activity, + enabled, }) { - if (roots == null || roots.length === 0) { + if (roots == null || roots.length === 0 || !enabled) { this._disabled = true; return; } @@ -32,15 +33,18 @@ class DeprecatedAssetMap { this._helpers = helpers; this._map = Object.create(null); this._assetExts = assetExts; - this._fastfs = new Fastfs( - 'Assets', - roots, - fileWatcher, - { ignore: ignoreFilePath, crawling: fsCrawl, activity } - ); this._activity = activity; - this._fastfs.on('change', this._processFileChange.bind(this)); + if (!this._disabled) { + this._fastfs = new Fastfs( + 'Assets', + roots, + fileWatcher, + { ignore: ignoreFilePath, crawling: fsCrawl, activity } + ); + + this._fastfs.on('change', this._processFileChange.bind(this)); + } } build() { @@ -89,7 +93,7 @@ class DeprecatedAssetMap { if (this._assetExts.indexOf(ext) !== -1) { const name = assetName(file, ext); if (this._map[name] != null) { - debug('Conflcting assets', name); + debug('Conflicting assets', name); } this._map[name] = new AssetModule_DEPRECATED({ file }); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js index 2f0b26af..47f93699 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -7,7 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; -const path = require('path'); +const path = require('fast-path'); const getPlatformExtension = require('../lib/getPlatformExtension'); const Promise = require('promise'); @@ -31,18 +31,18 @@ class HasteMap { build() { this._map = Object.create(null); - - let promises = this._fastfs.findFilesByExts(this._extensions, { - ignore: (file) => this._helpers.isNodeModulesDir(file), - }).map(file => this._processHasteModule(file)); - - promises = promises.concat( - this._fastfs.findFilesByName('package.json', { - ignore: (file) => this._helpers.isNodeModulesDir(file), - }).map(file => this._processHastePackage(file)) - ); - - return Promise.all(promises); + const promises = []; + this._fastfs.getAllFiles().forEach(filePath => { + if (!this._helpers.isNodeModulesDir(filePath)) { + if (this._extensions.indexOf(path.extname(filePath).substr(1)) !== -1) { + promises.push(this._processHasteModule(filePath)); + } + if (filePath.endsWith('/package.json')) { + promises.push(this._processHastePackage(filePath)); + } + } + }); + return Promise.all(promises).then(() => this._map); } processFileChange(type, absPath) { @@ -106,7 +106,7 @@ class HasteMap { _processHastePackage(file) { file = path.resolve(file); - const p = this._moduleCache.getPackage(file, this._fastfs); + const p = this._moduleCache.getPackage(file); return p.isHaste() .then(isHaste => isHaste && p.getName() .then(name => this._updateHasteMap(name, p))) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index c4004ed7..510b03fe 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -10,7 +10,8 @@ const debug = require('debug')('ReactNativePackager:DependencyGraph'); const util = require('util'); -const path = require('path'); +const path = require('fast-path'); +const realPath = require('path'); const isAbsolutePath = require('absolute-path'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); const Promise = require('promise'); @@ -287,7 +288,7 @@ class ResolutionRequest { const searchQueue = []; for (let currDir = path.dirname(fromModule.path); - currDir !== path.parse(fromModule.path).root; + currDir !== realPath.parse(fromModule.path).root; currDir = path.dirname(currDir)) { searchQueue.push( path.join(currDir, 'node_modules', realModuleName) diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 9241b18a..b3a987b1 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -14,7 +14,7 @@ const Promise = require('promise'); const crawl = require('../crawlers'); const getPlatformExtension = require('../lib/getPlatformExtension'); const isAbsolutePath = require('absolute-path'); -const path = require('path'); +const path = require('fast-path'); const util = require('util'); const DependencyGraphHelpers = require('./DependencyGraphHelpers'); const ResolutionRequest = require('./ResolutionRequest'); @@ -46,6 +46,7 @@ class DependencyGraph { extractRequires, transformCode, shouldThrowOnUnresolvedErrors = () => true, + enableAssetMap, }) { this._opts = { activity: activity || defaultActivity, @@ -60,8 +61,9 @@ class DependencyGraph { extensions: extensions || ['js', 'json'], mocksPattern, extractRequires, - shouldThrowOnUnresolvedErrors, transformCode, + shouldThrowOnUnresolvedErrors, + enableAssetMap: enableAssetMap || true, }; this._cache = cache; this._helpers = new DependencyGraphHelpers(this._opts); @@ -121,25 +123,33 @@ class DependencyGraph { ignoreFilePath: this._opts.ignoreFilePath, assetExts: this._opts.assetExts, activity: this._opts.activity, + enabled: this._opts.enableAssetMap, }); this._loading = Promise.all([ this._fastfs.build() .then(() => { const hasteActivity = activity.startEvent('Building Haste Map'); - return this._hasteMap.build().then(() => activity.endEvent(hasteActivity)); + return this._hasteMap.build().then(map => { + activity.endEvent(hasteActivity); + return map; + }); }), this._deprecatedAssetMap.build(), - ]).then(() => - activity.endEvent(depGraphActivity) - ).catch(err => { - const error = new Error( - `Failed to build DependencyGraph: ${err.message}` - ); - error.type = ERROR_BUILDING_DEP_GRAPH; - error.stack = err.stack; - throw error; - }); + ]).then( + response => { + activity.endEvent(depGraphActivity); + return response[0]; // Return the haste map + }, + err => { + const error = new Error( + `Failed to build DependencyGraph: ${err.message}` + ); + error.type = ERROR_BUILDING_DEP_GRAPH; + error.stack = err.stack; + throw error; + } + ); return this._loading; } diff --git a/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js b/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js index 2a36bb39..f59fa910 100644 --- a/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js +++ b/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js @@ -10,4 +10,5 @@ module.exports = { WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher'), + NodeWatcher: jest.genMockFromModule('sane/src/node_watcher'), }; diff --git a/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js index b0d2fd09..650e5100 100644 --- a/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js @@ -13,23 +13,27 @@ jest .dontMock('events') .dontMock('../') .setMock('child_process', { - exec: function(cmd, cb) { - cb(null, '/usr/bin/watchman'); - }, + exec: (cmd, cb) => cb(null, '/usr/bin/watchman'), }); -var sane = require('sane'); +const sane = require('sane'); -describe('FileWatcher', function() { - var Watcher; - var FileWatcher; - var config; +describe('FileWatcher', () => { + let WatchmanWatcher; + let NodeWatcher; + let FileWatcher; + let config; + + beforeEach(() => { + WatchmanWatcher = sane.WatchmanWatcher; + WatchmanWatcher.prototype.once.mockImplementation( + (type, callback) => callback() + ); + NodeWatcher = sane.NodeWatcher; + NodeWatcher.prototype.once.mockImplementation( + (type, callback) => callback() + ); - beforeEach(function() { - Watcher = sane.WatchmanWatcher; - Watcher.prototype.once.mockImplementation(function(type, callback) { - callback(); - }); FileWatcher = require('../'); config = [{ @@ -41,38 +45,45 @@ describe('FileWatcher', function() { }]; }); - pit('it should get the watcher instance when ready', function() { - var fileWatcher = new FileWatcher(config); - return fileWatcher.getWatchers().then(function(watchers) { - watchers.forEach(function(watcher) { - expect(watcher instanceof Watcher).toBe(true); + pit('gets the watcher instance when ready', () => { + const fileWatcher = new FileWatcher(config, {useWatchman: true}); + return fileWatcher.getWatchers().then(watchers => { + watchers.forEach(watcher => { + expect(watcher instanceof WatchmanWatcher).toBe(true); }); }); }); - pit('should emit events', function() { - var cb; - Watcher.prototype.on.mockImplementation(function(type, callback) { + pit('gets the node watcher if watchman is disabled', () => { + const fileWatcher = new FileWatcher(config, {useWatchman: false}); + return fileWatcher.getWatchers().then(watchers => { + watchers.forEach(watcher => { + expect(watcher instanceof NodeWatcher).toBe(true); + }); + }); + }); + + pit('emits events', () => { + let cb; + WatchmanWatcher.prototype.on.mockImplementation((type, callback) => { cb = callback; }); - var fileWatcher = new FileWatcher(config); - var handler = jest.genMockFn(); + const fileWatcher = new FileWatcher(config, {useWatchman: true}); + const handler = jest.genMockFn(); fileWatcher.on('all', handler); - return fileWatcher.getWatchers().then(function() { + return fileWatcher.getWatchers().then(watchers => { cb(1, 2, 3, 4); jest.runAllTimers(); expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]); }); }); - pit('it should end the watcher', function() { - var fileWatcher = new FileWatcher(config); - Watcher.prototype.close.mockImplementation(function(callback) { - callback(); - }); + pit('ends the watcher', () => { + const fileWatcher = new FileWatcher(config, {useWatchman: true}); + WatchmanWatcher.prototype.close.mockImplementation(callback => callback()); - return fileWatcher.end().then(function() { - expect(Watcher.prototype.close).toBeCalled(); + return fileWatcher.end().then(() => { + expect(WatchmanWatcher.prototype.close).toBeCalled(); }); }); }); diff --git a/react-packager/src/DependencyResolver/FileWatcher/index.js b/react-packager/src/DependencyResolver/FileWatcher/index.js index d75e0211..ab548366 100644 --- a/react-packager/src/DependencyResolver/FileWatcher/index.js +++ b/react-packager/src/DependencyResolver/FileWatcher/index.js @@ -30,17 +30,18 @@ let inited = false; class FileWatcher extends EventEmitter { - constructor(rootConfigs) { + constructor(rootConfigs, options) { if (inited) { throw new Error('FileWatcher can only be instantiated once'); } inited = true; super(); + this._useWatchman = options.useWatchman; this._watcherByRoot = Object.create(null); this._loading = Promise.all( - rootConfigs.map(createWatcher) + rootConfigs.map(rootConfig => this._createWatcher(rootConfig)) ).then(watchers => { watchers.forEach((watcher, i) => { this._watcherByRoot[rootConfigs[i].dir] = watcher; @@ -65,9 +66,9 @@ class FileWatcher extends EventEmitter { } isWatchman() { - return detectingWatcherClass.then( + return this._useWatchman ? detectingWatcherClass.then( Watcher => Watcher === sane.WatchmanWatcher - ); + ) : Promise.resolve(false); } end() { @@ -79,6 +80,30 @@ class FileWatcher extends EventEmitter { ); } + _createWatcher(rootConfig) { + return detectingWatcherClass.then(Watcher => { + if (!this._useWatchman) { + Watcher = sane.NodeWatcher; + } + const watcher = new Watcher(rootConfig.dir, { + glob: rootConfig.globs, + dot: false, + }); + + return new Promise((resolve, reject) => { + const rejectTimeout = setTimeout( + () => reject(new Error(timeoutMessage(Watcher))), + MAX_WAIT_TIME + ); + + watcher.once('ready', () => { + clearTimeout(rejectTimeout); + resolve(watcher); + }); + }); + }); + } + static createDummyWatcher() { return Object.assign(new EventEmitter(), { isWatchman: () => Promise.resolve(false), @@ -87,26 +112,6 @@ class FileWatcher extends EventEmitter { } } -function createWatcher(rootConfig) { - return detectingWatcherClass.then(function(Watcher) { - const watcher = new Watcher(rootConfig.dir, { - glob: rootConfig.globs, - dot: false, - }); - - return new Promise(function(resolve, reject) { - const rejectTimeout = setTimeout(function() { - reject(new Error(timeoutMessage(Watcher))); - }, MAX_WAIT_TIME); - - watcher.once('ready', function() { - clearTimeout(rejectTimeout); - resolve(watcher); - }); - }); - }); -} - function timeoutMessage(Watcher) { const lines = [ 'Watcher took too long to load (' + Watcher.name + ')', diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 6b68a620..6849c922 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -12,7 +12,7 @@ const crypto = require('crypto'); const docblock = require('./DependencyGraph/docblock'); const isAbsolutePath = require('absolute-path'); const jsonStableStringify = require('json-stable-stringify'); -const path = require('path'); +const path = require('fast-path'); const extractRequires = require('./lib/extractRequires'); class Module { @@ -30,7 +30,7 @@ class Module { throw new Error('Expected file to be absolute path but got ' + file); } - this.path = path.resolve(file); + this.path = file; this.type = 'Module'; this._fastfs = fastfs; @@ -132,7 +132,7 @@ class Module { const fileContentPromise = this._fastfs.readFile(this.path); return Promise.all([ fileContentPromise, - this._readDocBlock(fileContentPromise) + this._readDocBlock(fileContentPromise), ]).then(([code, {id, moduleDocBlock}]) => { // Ignore requires in JSON files or generated code. An example of this // is prebuilt files like the SourceMap library. @@ -151,7 +151,7 @@ class Module { return {id, code, dependencies, map}; }); } - }) + }); } ); } diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index 3fcfb173..11c56919 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -3,7 +3,7 @@ const AssetModule = require('./AssetModule'); const Package = require('./Package'); const Module = require('./Module'); -const path = require('path'); +const path = require('fast-path'); class ModuleCache { @@ -26,7 +26,6 @@ class ModuleCache { } getModule(filePath) { - filePath = path.resolve(filePath); if (!this._moduleCache[filePath]) { this._moduleCache[filePath] = new Module({ file: filePath, @@ -46,7 +45,6 @@ class ModuleCache { } getAssetModule(filePath) { - filePath = path.resolve(filePath); if (!this._moduleCache[filePath]) { this._moduleCache[filePath] = new AssetModule({ file: filePath, @@ -59,7 +57,6 @@ class ModuleCache { } getPackage(filePath) { - filePath = path.resolve(filePath); if (!this._packageCache[filePath]) { this._packageCache[filePath] = new Package({ file: filePath, diff --git a/react-packager/src/DependencyResolver/Package.js b/react-packager/src/DependencyResolver/Package.js index e88b0640..0d28b3b0 100644 --- a/react-packager/src/DependencyResolver/Package.js +++ b/react-packager/src/DependencyResolver/Package.js @@ -1,12 +1,12 @@ 'use strict'; const isAbsolutePath = require('absolute-path'); -const path = require('path'); +const path = require('fast-path'); class Package { constructor({ file, fastfs, cache }) { - this.path = path.resolve(file); + this.path = file; this.root = path.dirname(this.path); this._fastfs = fastfs; this.type = 'Package'; diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index bb4fa75e..ee2623a3 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -202,9 +202,9 @@ describe('Module', () => { let transformCode; const fileContents = 'arbitrary(code);'; const exampleCode = ` - require('a'); - arbitrary.code('b'); - require('c');`; + ${'require'}('a'); + ${'System.import'}('b'); + ${'require'}('c');`; beforeEach(function() { transformCode = jest.genMockFn(); @@ -281,7 +281,7 @@ describe('Module', () => { const callsEqual = ([path1, key1], [path2, key2]) => { expect(path1).toEqual(path2); expect(key1).toEqual(key2); - } + }; it('gets dependencies from the cache with the same cache key for the same transform options', () => { const options = {some: 'options'}; @@ -293,7 +293,6 @@ describe('Module', () => { }); it('gets dependencies from the cache with the same cache key for the equivalent transform options', () => { - const options = {some: 'options'}; module.getDependencies({a: 'b', c: 'd'}); // first call module.getDependencies({c: 'd', a: 'b'}); // second call @@ -318,7 +317,6 @@ describe('Module', () => { }); it('gets code from the cache with the same cache key for the equivalent transform options', () => { - const options = {some: 'options'}; module.getCode({a: 'b', c: 'd'}); // first call module.getCode({c: 'd', a: 'b'}); // second call diff --git a/react-packager/src/DependencyResolver/crawlers/node.js b/react-packager/src/DependencyResolver/crawlers/node.js index 88e32d88..871bbc1d 100644 --- a/react-packager/src/DependencyResolver/crawlers/node.js +++ b/react-packager/src/DependencyResolver/crawlers/node.js @@ -3,7 +3,7 @@ const Promise = require('promise'); const debug = require('debug')('ReactNativePackager:DependencyGraph'); const fs = require('graceful-fs'); -const path = require('path'); +const path = require('fast-path'); const readDir = Promise.denodeify(fs.readdir); const stat = Promise.denodeify(fs.stat); diff --git a/react-packager/src/DependencyResolver/crawlers/watchman.js b/react-packager/src/DependencyResolver/crawlers/watchman.js index 1871e3ea..6e3d6a6c 100644 --- a/react-packager/src/DependencyResolver/crawlers/watchman.js +++ b/react-packager/src/DependencyResolver/crawlers/watchman.js @@ -1,7 +1,7 @@ 'use strict'; const Promise = require('promise'); -const path = require('path'); +const path = require('fast-path'); function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) { const files = []; @@ -38,20 +38,16 @@ function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) { const cmd = Promise.denodeify(watcher.client.command.bind(watcher.client)); return cmd(['query', watchedRoot, { - 'suffix': exts, - 'expression': ['allof', ['type', 'f'], 'exists', dirExpr], - 'fields': ['name'], + suffix: exts, + expression: ['allof', ['type', 'f'], 'exists', dirExpr], + fields: ['name'], }]).then(resp => { if ('warning' in resp) { console.warn('watchman warning: ', resp.warning); } resp.files.forEach(filePath => { - filePath = path.join( - watchedRoot, - filePath - ); - + filePath = watchedRoot + path.sep + filePath; if (!ignore(filePath)) { files.push(filePath); } @@ -64,7 +60,7 @@ function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) { } function isDescendant(root, child) { - return path.relative(root, child).indexOf('..') !== 0; + return child.startsWith(root); } module.exports = watchmanRecReadDir; diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index 325604ff..af201008 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -12,7 +12,7 @@ const Promise = require('promise'); const {EventEmitter} = require('events'); const fs = require('graceful-fs'); -const path = require('path'); +const path = require('fast-path'); // workaround for https://github.com/isaacs/node-graceful-fs/issues/56 // fs.close is patched, whereas graceful-fs.close is not. @@ -21,8 +21,6 @@ const fsClose = require('fs').close; const readFile = Promise.denodeify(fs.readFile); const stat = Promise.denodeify(fs.stat); -const hasOwn = Object.prototype.hasOwnProperty; - const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError'; class Fastfs extends EventEmitter { @@ -31,17 +29,20 @@ class Fastfs extends EventEmitter { this._name = name; this._fileWatcher = fileWatcher; this._ignore = ignore; - this._roots = roots.map(root => new File(root, { isDir: true })); + this._roots = roots.map(root => { + // If the path ends in a separator ("/"), remove it to make string + // operations on paths safer. + if (root.endsWith(path.sep)) { + root = root.substr(0, root.length - 1); + } + return new File(root, true); + }); this._fastPaths = Object.create(null); this._crawling = crawling; this._activity = activity; } build() { - const rootsPattern = new RegExp( - '^(' + this._roots.map(root => escapeRegExp(root.path)).join('|') + ')' - ); - return this._crawling.then(files => { let fastfsActivity; const activity = this._activity; @@ -49,18 +50,16 @@ class Fastfs extends EventEmitter { fastfsActivity = activity.startEvent('Building in-memory fs for ' + this._name); } files.forEach(filePath => { - if (filePath.match(rootsPattern)) { - const newFile = new File(filePath, { isDir: false }); - const parent = this._fastPaths[path.dirname(filePath)]; + const root = this._getRoot(filePath); + if (root) { + const newFile = new File(filePath, false); + const dirname = filePath.substr(0, filePath.lastIndexOf(path.sep)); + const parent = this._fastPaths[dirname]; + this._fastPaths[filePath] = newFile; if (parent) { - parent.addChild(newFile); + parent.addChild(newFile, this._fastPaths); } else { - this._add(newFile); - for (let file = newFile; file; file = file.parent) { - if (!this._fastPaths[file.path]) { - this._fastPaths[file.path] = file; - } - } + root.addChild(newFile, this._fastPaths); } } }); @@ -72,42 +71,24 @@ class Fastfs extends EventEmitter { } stat(filePath) { - return Promise.resolve().then(() => { - const file = this._getFile(filePath); - return file.stat(); - }); + return Promise.resolve().then(() => this._getFile(filePath).stat()); } getAllFiles() { - // one-level-deep flatten of files - return [].concat(...this._roots.map(root => root.getFiles())); - } - - findFilesByExt(ext, { ignore } = {}) { - return this.findFilesByExts([ext], {ignore}); + return Object.keys(this._fastPaths) + .filter(filePath => !this._fastPaths[filePath].isDir); } findFilesByExts(exts, { ignore } = {}) { return this.getAllFiles() - .filter(file => ( - exts.indexOf(file.ext()) !== -1 && (!ignore || !ignore(file.path)) - )) - .map(file => file.path); - } - - findFilesByName(name, { ignore } = {}) { - return this.getAllFiles() - .filter( - file => path.basename(file.path) === name && - (!ignore || !ignore(file.path)) - ) - .map(file => file.path); + .filter(filePath => ( + exts.indexOf(path.extname(filePath).substr(1)) !== -1 && + (!ignore || !ignore(filePath)) + )); } matchFilesByPattern(pattern) { - return this.getAllFiles() - .filter(file => file.path.match(pattern)) - .map(file => file.path); + return this.getAllFiles().filter(file => file.match(pattern)); } readFile(filePath) { @@ -198,25 +179,25 @@ class Fastfs extends EventEmitter { _getFile(filePath) { filePath = path.normalize(filePath); - if (!hasOwn.call(this._fastPaths, filePath)) { - this._fastPaths[filePath] = this._getAndAssertRoot(filePath).getFileFromPath(filePath); + if (!this._fastPaths[filePath]) { + const file = this._getAndAssertRoot(filePath).getFileFromPath(filePath); + if (file) { + this._fastPaths[filePath] = file; + } } return this._fastPaths[filePath]; } - _add(file) { - this._getAndAssertRoot(file.path).addChild(file); - } - - _processFileChange(type, filePath, root, fstat) { - const absPath = path.join(root, filePath); + _processFileChange(type, filePath, rootPath, fstat) { + const absPath = path.join(rootPath, filePath); if (this._ignore(absPath) || (fstat && fstat.isDirectory())) { return; } // Make sure this event belongs to one of our roots. - if (!this._getRoot(absPath)) { + const root = this._getRoot(absPath); + if (!root) { return; } @@ -230,20 +211,19 @@ class Fastfs extends EventEmitter { delete this._fastPaths[path.normalize(absPath)]; if (type !== 'delete') { - this._add(new File(absPath, { isDir: false })); + const file = new File(absPath, false); + root.addChild(file, this._fastPaths); } - this.emit('change', type, filePath, root, fstat); + this.emit('change', type, filePath, rootPath, fstat); } } class File { - constructor(filePath, { isDir }) { + constructor(filePath, isDir) { this.path = filePath; - this.isDir = Boolean(isDir); - if (this.isDir) { - this.children = Object.create(null); - } + this.isDir = isDir; + this.children = this.isDir ? Object.create(null) : null; } read() { @@ -270,29 +250,24 @@ class File { return this._stat; } - addChild(file) { - const parts = path.relative(this.path, file.path).split(path.sep); - - if (parts.length === 0) { - return; - } - + addChild(file, fileMap) { + const parts = file.path.substr(this.path.length + 1).split(path.sep); if (parts.length === 1) { this.children[parts[0]] = file; file.parent = this; } else if (this.children[parts[0]]) { - this.children[parts[0]].addChild(file); + this.children[parts[0]].addChild(file, fileMap); } else { - const dir = new File(path.join(this.path, parts[0]), { isDir: true }); + const dir = new File(this.path + path.sep + parts[0], true); dir.parent = this; this.children[parts[0]] = dir; - dir.addChild(file); + fileMap[dir.path] = dir; + dir.addChild(file, fileMap); } } getFileFromPath(filePath) { - const parts = path.relative(this.path, filePath) - .split(path.sep); + const parts = path.relative(this.path, filePath).split(path.sep); /*eslint consistent-this:0*/ let file = this; @@ -313,21 +288,8 @@ class File { return file; } - getFiles() { - let files = []; - Object.keys(this.children).forEach(key => { - const file = this.children[key]; - if (file.isDir) { - files = files.concat(file.getFiles()); - } else { - files.push(file); - } - }); - return files; - } - ext() { - return path.extname(this.path).replace(/^\./, ''); + return path.extname(this.path).substr(1); } remove() { @@ -392,11 +354,7 @@ function makeReadCallback(fd, predicate, callback) { } function isDescendant(root, child) { - return path.relative(root, child).indexOf('..') !== 0; -} - -function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + return child.startsWith(root); } module.exports = Fastfs; diff --git a/react-packager/src/DependencyResolver/lib/extractRequires.js b/react-packager/src/DependencyResolver/lib/extractRequires.js index 5dbf63cc..547ffbbb 100644 --- a/react-packager/src/DependencyResolver/lib/extractRequires.js +++ b/react-packager/src/DependencyResolver/lib/extractRequires.js @@ -39,7 +39,7 @@ function extractRequires(code) { .replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => { deps.sync.push(dep); return match; - }) + }); return {code, deps}; } diff --git a/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js b/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js index 33fa13ce..a616d108 100644 --- a/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js +++ b/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js @@ -8,7 +8,7 @@ */ 'use strict'; -const path = require('path'); +const path = require('fast-path'); const getPlatformExtension = require('./getPlatformExtension'); function getAssetDataFromName(filename) { diff --git a/react-packager/src/DependencyResolver/lib/getPlatformExtension.js b/react-packager/src/DependencyResolver/lib/getPlatformExtension.js index cfcb6501..721470d1 100644 --- a/react-packager/src/DependencyResolver/lib/getPlatformExtension.js +++ b/react-packager/src/DependencyResolver/lib/getPlatformExtension.js @@ -8,19 +8,21 @@ */ 'use strict'; -const SUPPORTED_PLATFORM_EXTS = ['android', 'ios', 'web']; - -const re = new RegExp( - '[^\\.]+\\.(' + SUPPORTED_PLATFORM_EXTS.join('|') + ')\\.\\w+$' -); +const SUPPORTED_PLATFORM_EXTS = { + android: true, + ios: true, + web: true, +}; // Extract platform extension: index.ios.js -> ios function getPlatformExtension(file) { - const match = file.match(re); - if (match && match[1]) { - return match[1]; + const last = file.lastIndexOf('.'); + const secondToLast = file.lastIndexOf('.', last - 1); + if (secondToLast === -1) { + return null; } - return null; + const platform = file.substring(secondToLast + 1, last); + return SUPPORTED_PLATFORM_EXTS[platform] ? platform : null; } module.exports = getPlatformExtension; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 0146e7d6..aa32175a 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -182,7 +182,7 @@ class Server { this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() - : new FileWatcher(watchRootConfigs); + : new FileWatcher(watchRootConfigs, {useWatchman: true}); this._assetServer = new AssetServer({ projectRoots: opts.projectRoots, From 109532086ffb2d0e581cf836eee9c5ff2b8b6f81 Mon Sep 17 00:00:00 2001 From: Adam Ernst Date: Tue, 16 Feb 2016 11:50:39 -0800 Subject: [PATCH 591/936] Update README.md Reviewed By: svcscm Differential Revision: D2924282 fb-gh-sync-id: fe845cfa775470dc71dac8ab3bc62681e04f4f13 shipit-source-id: fe845cfa775470dc71dac8ab3bc62681e04f4f13 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 039865ba..8010cc38 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ to wait more than a few seconds after starting the packager. The main deviation from the node module system is the support for our proprietary module format known as `@providesModule`. However, we -discourage people to use this module format because going forward, we +discourage people from using this module format because going forward we want to completely separate our infrastructure from React Native and provide an experience most JavaScript developers are familiar with, namely the node module format. We want to even go further, and let you From ccbf9273fe463672d66a141854964b1e66afb3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 17 Feb 2016 10:00:35 -0800 Subject: [PATCH 592/936] Introduce transformer `cacheKey` Summary:public At the moment, the packager's cache can only be broken by changing packager's `package.json` version,by supplying a different `cacheKey` or by updating the `mtime` of the transformer. We need to add support for breaking the cache key when a plugin the transformer use gets updated. To do so, lets introduce a property on the transformer, namely `cacheKey`. Reviewed By: davidaurelio Differential Revision: D2940267 fb-gh-sync-id: 82c937d06c73abd32708bf97afe5f308c2a3b565 shipit-source-id: 82c937d06c73abd32708bf97afe5f308c2a3b565 --- react-packager/src/Bundler/index.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index bc25ab1d..6285fcc2 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -103,15 +103,24 @@ class Bundler { mtime = ''; } + const cacheKeyParts = [ + 'react-packager-cache', + version, + opts.cacheVersion, + opts.projectRoots.join(',').split(path.sep).join('-'), + mtime, + ]; + + if (opts.transformModulePath) { + const transformer = require(opts.transformModulePath); + if (typeof transformer.cacheKey !== 'undefined') { + cacheKeyParts.push(transformer.cacheKey); + } + } + this._cache = new Cache({ resetCache: opts.resetCache, - cacheKey: [ - 'react-packager-cache', - version, - opts.cacheVersion, - opts.projectRoots.join(',').split(path.sep).join('-'), - mtime - ].join('$'), + cacheKey: cacheKeyParts.join('$'), }); this._resolver = new Resolver({ From a558c4b59d8acde0806cde725a0849cadb9d1c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 17 Feb 2016 15:39:46 -0800 Subject: [PATCH 593/936] Tweak Hot Loading yellow box wording Summary: Users don't know what an accept callback is. Lets be more explicit on what type of modules we currently support hot loading. Reviewed By: weicool Differential Revision: D2945438 fb-gh-sync-id: d0fc228ab23833371f8fbbd86ed18e81c8ba0ebf shipit-source-id: d0fc228ab23833371f8fbbd86ed18e81c8ba0ebf --- react-packager/src/Resolver/polyfills/require.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index a5a7a55f..bc2824bb 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -118,8 +118,8 @@ if (__DEV__) { // HMR mod.module.hot.acceptCallback(); } else { console.warn( - '[HMR] Module `' + id + '` can\'t be hot reloaded because it ' + - 'doesn\'t provide accept callback hook. Reload the app to get the updates.' + '[HMR] Module `' + id + '` can\'t be hot reloaded because it\'s not a ' + + 'React component. To get the changes reload the JS bundle.' ); } } From b2a11aff51826c95d4d04bf336425f824d934b98 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Thu, 18 Feb 2016 00:12:42 -0800 Subject: [PATCH 594/936] Update + use node-haste2 Summary:This updates jest to 0.9 which will result in *much* faster startup time (1s vs. 10-15s) and better runtime overall (2-3x). The route gen and cli integration tests are failing locally, but also on master. javache is this expected right now or is this related to my changes? Reviewed By: javache Differential Revision: D2943137 fb-gh-sync-id: 8b39ba5f51e30fbc5bacb84d67013ab0a4061f6e shipit-source-id: 8b39ba5f51e30fbc5bacb84d67013ab0a4061f6e --- react-packager/src/Server/__tests__/Server-test.js | 1 + .../src/SocketInterface/__tests__/SocketInterface-test.js | 1 + react-packager/{ => src}/__mocks__/debug.js | 0 3 files changed, 2 insertions(+) rename react-packager/{ => src}/__mocks__/debug.js (100%) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index b731a026..e6a74aeb 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -10,6 +10,7 @@ jest.setMock('worker-farm', function() { return () => {}; }) .dontMock('os') + .dontMock('underscore') .dontMock('path') .dontMock('url') .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) }) diff --git a/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js b/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js index 729d2a1d..cf8345b3 100644 --- a/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js +++ b/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js @@ -11,6 +11,7 @@ jest.setMock('worker-farm', function() { return () => {}; }) .setMock('uglify-js') .mock('child_process') + .dontMock('underscore') .dontMock('../'); var SocketInterface = require('../'); diff --git a/react-packager/__mocks__/debug.js b/react-packager/src/__mocks__/debug.js similarity index 100% rename from react-packager/__mocks__/debug.js rename to react-packager/src/__mocks__/debug.js From 278b40ff69f3d3f156488b1cdf4360cfcb5aaac0 Mon Sep 17 00:00:00 2001 From: Qiao Liang Date: Thu, 18 Feb 2016 05:40:12 -0800 Subject: [PATCH 595/936] add getter as public api for ErrorUtils._globalHandler Summary:As discussed here #1194 . This add an getter the default handler, so that custom handler can be added (monkey patch?) without overwriting the default handler Closes https://github.com/facebook/react-native/pull/5575 Differential Revision: D2948994 fb-gh-sync-id: 2b6d1619cfe68f78b326c6d232b9bf57c489c45d shipit-source-id: 2b6d1619cfe68f78b326c6d232b9bf57c489c45d --- react-packager/src/Resolver/polyfills/error-guard.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/react-packager/src/Resolver/polyfills/error-guard.js b/react-packager/src/Resolver/polyfills/error-guard.js index 8810a4b1..d013ec11 100644 --- a/react-packager/src/Resolver/polyfills/error-guard.js +++ b/react-packager/src/Resolver/polyfills/error-guard.js @@ -20,6 +20,9 @@ var ErrorUtils = { setGlobalHandler: function(fun) { ErrorUtils._globalHandler = fun; }, + getGlobalHandler: function() { + return ErrorUtils._globalHandler; + }, reportError: function(error) { ErrorUtils._globalHandler && ErrorUtils._globalHandler(error); }, From c107132a36e096e8f284b0212a46ed5f3d9d081e Mon Sep 17 00:00:00 2001 From: Steve Kellock Date: Thu, 18 Feb 2016 07:35:18 -0800 Subject: [PATCH 596/936] Adds additional help when a module is missing. Summary:Hey. Long time fan, first time forker. You know when you're working on a project with someone and they bring in a new dependency? When you first pull their code and you haven't also installed that dependency, the error that is shown is this: ![image](https://cloud.githubusercontent.com/assets/68273/13145164/d8748b3e-d61c-11e5-9df9-3e47edf3fcfb.png) This PR simply adds `or running "npm install"` to the end of that message. Just adds a little clarity to newcomers. Hell knows I was lost for a while when I was starting. ![image](https://cloud.githubusercontent.com/assets/68273/13145253/65e8f31a-d61d-11e5-99ac-2d79d8e37123.png) Closes https://github.com/facebook/react-native/pull/6009 Differential Revision: D2949127 Pulled By: davidaurelio fb-gh-sync-id: b297d8c1570fec23cb179ddab4847e2438cc463b shipit-source-id: b297d8c1570fec23cb179ddab4847e2438cc463b --- react-packager/src/Resolver/polyfills/require-unbundle.js | 2 +- react-packager/src/Resolver/polyfills/require.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js index 87e85601..9c1e36e7 100644 --- a/react-packager/src/Resolver/polyfills/require-unbundle.js +++ b/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -80,7 +80,7 @@ function unknownModuleError(id) { let message = 'Requiring unknown module "' + id + '".'; if (__DEV__) { message += - 'If you are sure the module is there, try restarting the packager.'; + 'If you are sure the module is there, try restarting the packager or running "npm install".'; } return Error(message); } diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index bc2824bb..764e4dd1 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -48,7 +48,7 @@ function requireImpl(id) { if (!mod) { var msg = 'Requiring unknown module "' + id + '"'; if (__DEV__) { - msg += '. If you are sure the module is there, try restarting the packager.'; + msg += '. If you are sure the module is there, try restarting the packager or running "npm install".'; } throw new Error(msg); } From 60dacf3bc4d9695d64a4b1957338657afc042f9c Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Thu, 18 Feb 2016 18:02:21 -0800 Subject: [PATCH 597/936] Install node-haste2 and remove DependencyResolver Summary: This installs the 2.0 version of node-haste, removes the DependencyResolver and fixes up all the tests. Reviewed By: davidaurelio Differential Revision: D2943416 fb-gh-sync-id: aa83d436a33f910d12ed4cc6e2ad8d5742c123a5 shipit-source-id: aa83d436a33f910d12ed4cc6e2ad8d5742c123a5 --- .../AssetServer/__tests__/AssetServer-test.js | 8 +- react-packager/src/AssetServer/index.js | 2 +- react-packager/src/Bundler/index.js | 2 +- .../src/DependencyResolver/AssetModule.js | 47 - .../AssetModule_DEPRECATED.js | 45 - .../Cache/__mocks__/index.js | 20 - .../Cache/__tests__/Cache-test.js | 335 -- .../src/DependencyResolver/Cache/index.js | 190 - .../Cache/lib/getCacheFilePath.js | 20 - .../Cache/lib/loadCacheSync.js | 34 - .../DependencyGraph/DependencyGraphHelpers.js | 45 - .../DependencyGraph/DeprecatedAssetMap.js | 119 - .../DependencyGraph/HasteMap.js | 142 - .../DependencyGraph/ResolutionRequest.js | 446 -- .../DependencyGraph/ResolutionResponse.js | 87 - .../__tests__/DependencyGraph-test.js | 4285 ----------------- .../DependencyGraph/docblock.js | 83 - .../DependencyGraph/index.js | 287 -- .../FileWatcher/__mocks__/sane.js | 14 - .../FileWatcher/__tests__/FileWatcher-test.js | 89 - .../DependencyResolver/FileWatcher/index.js | 128 - .../src/DependencyResolver/Module.js | 226 - .../src/DependencyResolver/ModuleCache.js | 104 - .../src/DependencyResolver/Package.js | 100 - .../src/DependencyResolver/Polyfill.js | 38 - .../__tests__/Module-test.js | 335 -- .../DependencyResolver/__tests__/fastfs-data | 39 - .../__tests__/fastfs-integrated-test.js | 87 - .../src/DependencyResolver/crawlers/index.js | 26 - .../src/DependencyResolver/crawlers/node.js | 61 - .../DependencyResolver/crawlers/watchman.js | 66 - .../src/DependencyResolver/fastfs.js | 360 -- .../__tests__/getAssetDataFromName-test.js | 119 - .../__tests__/getPlatformExtension-test.js | 25 - .../DependencyResolver/lib/extractRequires.js | 47 - .../lib/getAssetDataFromName.js | 55 - .../lib/getPlatformExtension.js | 28 - .../DependencyResolver/lib/replacePatterns.js | 14 - .../__tests__/Transformer-test.js | 6 +- .../src/Resolver/__tests__/Resolver-test.js | 28 +- react-packager/src/Resolver/index.js | 6 +- .../src/Server/__tests__/Server-test.js | 4 +- react-packager/src/Server/index.js | 4 +- .../__tests__/SocketInterface-test.js | 3 +- .../__tests__/SocketServer-test.js | 3 +- react-packager/src/__mocks__/fs.js | 179 +- 46 files changed, 38 insertions(+), 8353 deletions(-) delete mode 100644 react-packager/src/DependencyResolver/AssetModule.js delete mode 100644 react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js delete mode 100644 react-packager/src/DependencyResolver/Cache/__mocks__/index.js delete mode 100644 react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js delete mode 100644 react-packager/src/DependencyResolver/Cache/index.js delete mode 100644 react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js delete mode 100644 react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js delete mode 100644 react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js delete mode 100644 react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js delete mode 100644 react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js delete mode 100644 react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js delete mode 100644 react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js delete mode 100644 react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js delete mode 100644 react-packager/src/DependencyResolver/DependencyGraph/docblock.js delete mode 100644 react-packager/src/DependencyResolver/DependencyGraph/index.js delete mode 100644 react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js delete mode 100644 react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js delete mode 100644 react-packager/src/DependencyResolver/FileWatcher/index.js delete mode 100644 react-packager/src/DependencyResolver/Module.js delete mode 100644 react-packager/src/DependencyResolver/ModuleCache.js delete mode 100644 react-packager/src/DependencyResolver/Package.js delete mode 100644 react-packager/src/DependencyResolver/Polyfill.js delete mode 100644 react-packager/src/DependencyResolver/__tests__/Module-test.js delete mode 100644 react-packager/src/DependencyResolver/__tests__/fastfs-data delete mode 100644 react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js delete mode 100644 react-packager/src/DependencyResolver/crawlers/index.js delete mode 100644 react-packager/src/DependencyResolver/crawlers/node.js delete mode 100644 react-packager/src/DependencyResolver/crawlers/watchman.js delete mode 100644 react-packager/src/DependencyResolver/fastfs.js delete mode 100644 react-packager/src/DependencyResolver/lib/__tests__/getAssetDataFromName-test.js delete mode 100644 react-packager/src/DependencyResolver/lib/__tests__/getPlatformExtension-test.js delete mode 100644 react-packager/src/DependencyResolver/lib/extractRequires.js delete mode 100644 react-packager/src/DependencyResolver/lib/getAssetDataFromName.js delete mode 100644 react-packager/src/DependencyResolver/lib/getPlatformExtension.js delete mode 100644 react-packager/src/DependencyResolver/lib/replacePatterns.js diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 1cb90b33..0172f39b 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -1,8 +1,7 @@ 'use strict'; jest - .dontMock('../../DependencyResolver/lib/getPlatformExtension') - .dontMock('../../DependencyResolver/lib/getAssetDataFromName') + .dontMock('node-haste/lib/lib/getPlatformExtension') .dontMock('../'); jest @@ -16,6 +15,11 @@ var crypto = require('crypto'); var fs = require('fs'); describe('AssetServer', () => { + beforeEach(() => { + const NodeHaste = require('node-haste'); + NodeHaste.getAssetDataFromName = require.requireActual('node-haste/lib/lib/getAssetDataFromName'); + }); + describe('assetServer.get', () => { pit('should work for the simple case', () => { const server = new AssetServer({ diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index bb411ce6..71356acf 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -13,7 +13,7 @@ const Promise = require('promise'); const crypto = require('crypto'); const declareOpts = require('../lib/declareOpts'); const fs = require('fs'); -const getAssetDataFromName = require('../DependencyResolver/lib/getAssetDataFromName'); +const getAssetDataFromName = require('node-haste').getAssetDataFromName; const path = require('path'); const stat = Promise.denodeify(fs.stat); diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 6285fcc2..22696cf5 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -13,7 +13,7 @@ const fs = require('fs'); const path = require('path'); const Promise = require('promise'); const ProgressBar = require('progress'); -const Cache = require('../DependencyResolver/Cache'); +const Cache = require('node-haste').Cache; const Transformer = require('../JSTransformer'); const Resolver = require('../Resolver'); const Bundle = require('./Bundle'); diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js deleted file mode 100644 index c2f51fbb..00000000 --- a/react-packager/src/DependencyResolver/AssetModule.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -const Module = require('./Module'); -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); - } - - getDependencies() { - return Promise.resolve([]); - } - - read() { - return Promise.resolve({}); - } - - getName() { - 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 deleted file mode 100644 index 3c465946..00000000 --- a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -const Module = require('./Module'); -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(`image!${this.name}`); - } - - getDependencies() { - return Promise.resolve([]); - } - - 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/Cache/__mocks__/index.js b/react-packager/src/DependencyResolver/Cache/__mocks__/index.js deleted file mode 100644 index 6f7632f6..00000000 --- a/react-packager/src/DependencyResolver/Cache/__mocks__/index.js +++ /dev/null @@ -1,20 +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'; - -class Cache { - get(filepath, field, cb) { - return cb(filepath); - } - - invalidate(filepath) { } - end() { } -} - -module.exports = Cache; diff --git a/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js b/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js deleted file mode 100644 index 2de2318e..00000000 --- a/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js +++ /dev/null @@ -1,335 +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'; - -jest - .dontMock('absolute-path') - .dontMock('../') - .dontMock('../lib/loadCacheSync') - .dontMock('../lib/getCacheFilePath'); - -jest - .mock('fs') - .setMock('os', { - tmpDir() { return 'tmpDir'; }, - }); - -var Promise = require('promise'); -var fs = require('graceful-fs'); - -var Cache = require('../'); - -describe('Cache', () => { - describe('getting/setting', () => { - pit('calls loader callback for uncached file', () => { - fs.stat.mockImpl((file, callback) => { - callback(null, { - mtime: { - getTime: () => {}, - }, - }); - }); - - var cache = new Cache({ - cacheKey: 'cache', - }); - var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve()); - - return cache - .get('/rootDir/someFile', 'field', loaderCb) - .then($ => - expect(loaderCb).toBeCalledWith('/rootDir/someFile') - ); - }); - - pit('supports storing multiple fields', () => { - fs.stat.mockImpl((file, callback) => { - callback(null, { - mtime: { - getTime: () => {}, - }, - }); - }); - - var cache = new Cache({ - cacheKey: 'cache', - }); - var index = 0; - var loaderCb = jest.genMockFn().mockImpl(() => - Promise.resolve(index++) - ); - - return cache - .get('/rootDir/someFile', 'field1', loaderCb) - .then(value => { - expect(value).toBe(0); - return cache - .get('/rootDir/someFile', 'field2', loaderCb) - .then(value2 => expect(value2).toBe(1)); - }); - }); - - pit('gets the value from the loader callback', () => { - fs.stat.mockImpl((file, callback) => - callback(null, { - mtime: { - getTime: () => {}, - }, - }) - ); - - var cache = new Cache({ - cacheKey: 'cache', - }); - var loaderCb = jest.genMockFn().mockImpl(() => - Promise.resolve('lol') - ); - - return cache - .get('/rootDir/someFile', 'field', loaderCb) - .then(value => expect(value).toBe('lol')); - }); - - pit('caches the value after the first call', () => { - fs.stat.mockImpl((file, callback) => { - callback(null, { - mtime: { - getTime: () => {}, - }, - }); - }); - - var cache = new Cache({ - cacheKey: 'cache', - }); - var loaderCb = jest.genMockFn().mockImpl(() => - Promise.resolve('lol') - ); - - return cache - .get('/rootDir/someFile', 'field', loaderCb) - .then(() => { - var shouldNotBeCalled = jest.genMockFn(); - return cache.get('/rootDir/someFile', 'field', shouldNotBeCalled) - .then(value => { - expect(shouldNotBeCalled).not.toBeCalled(); - expect(value).toBe('lol'); - }); - }); - }); - - pit('clears old field when getting new field and mtime changed', () => { - var mtime = 0; - fs.stat.mockImpl((file, callback) => { - callback(null, { - mtime: { - getTime: () => mtime++, - }, - }); - }); - - var cache = new Cache({ - cacheKey: 'cache', - }); - var loaderCb = jest.genMockFn().mockImpl(() => - Promise.resolve('lol' + mtime) - ); - - return cache - .get('/rootDir/someFile', 'field1', loaderCb) - .then(value => cache - .get('/rootDir/someFile', 'field2', loaderCb) - .then(value2 => cache - .get('/rootDir/someFile', 'field1', loaderCb) - .then(value3 => expect(value3).toBe('lol2')) - ) - ); - }); - }); - - describe('loading cache from disk', () => { - var fileStats; - - beforeEach(() => { - fileStats = { - '/rootDir/someFile': { - mtime: { - getTime: () => 22, - }, - }, - '/rootDir/foo': { - mtime: { - getTime: () => 11, - }, - }, - }; - - fs.existsSync.mockImpl(() => true); - - fs.statSync.mockImpl(filePath => fileStats[filePath]); - - fs.readFileSync.mockImpl(() => JSON.stringify({ - '/rootDir/someFile': { - metadata: {mtime: 22}, - data: {field: 'oh hai'}, - }, - '/rootDir/foo': { - metadata: {mtime: 11}, - data: {field: 'lol wat'}, - }, - })); - }); - - pit('should load cache from disk', () => { - var cache = new Cache({ - cacheKey: 'cache', - }); - var loaderCb = jest.genMockFn(); - - return cache - .get('/rootDir/someFile', 'field', loaderCb) - .then(value => { - expect(loaderCb).not.toBeCalled(); - expect(value).toBe('oh hai'); - - return cache - .get('/rootDir/foo', 'field', loaderCb) - .then(val => { - expect(loaderCb).not.toBeCalled(); - expect(val).toBe('lol wat'); - }); - }); - }); - - pit('should not load outdated cache', () => { - fs.stat.mockImpl((file, callback) => - callback(null, { - mtime: { - getTime: () => {}, - }, - }) - ); - - fileStats['/rootDir/foo'].mtime.getTime = () => 123; - - var cache = new Cache({ - cacheKey: 'cache', - }); - var loaderCb = jest.genMockFn().mockImpl(() => - Promise.resolve('new value') - ); - - return cache - .get('/rootDir/someFile', 'field', loaderCb) - .then(value => { - expect(loaderCb).not.toBeCalled(); - expect(value).toBe('oh hai'); - - return cache - .get('/rootDir/foo', 'field', loaderCb) - .then(val => { - expect(loaderCb).toBeCalled(); - expect(val).toBe('new value'); - }); - }); - }); - }); - - describe('writing cache to disk', () => { - it('should write cache to disk', () => { - var index = 0; - var mtimes = [10, 20, 30]; - - fs.stat.mockImpl((file, callback) => - callback(null, { - mtime: { - getTime: () => mtimes[index++], - }, - }) - ); - - var cache = new Cache({ - cacheKey: 'cache', - }); - - cache.get('/rootDir/bar', 'field', () => - Promise.resolve('bar value') - ); - cache.get('/rootDir/foo', 'field', () => - Promise.resolve('foo value') - ); - cache.get('/rootDir/baz', 'field', () => - Promise.resolve('baz value') - ); - - // jest has some trouble with promises and timeouts within promises :( - jest.runAllTimers(); - jest.runAllTimers(); - - expect(fs.writeFile).toBeCalled(); - }); - }); - - describe('check for cache presence', () => { - it('synchronously resolves cache presence', () => { - fs.stat.mockImpl((file, callback) => - callback(null, { - mtime: { - getTime: () => {}, - }, - }) - ); - - var cache = new Cache({ - cacheKey: 'cache', - }); - var loaderCb = jest.genMockFn().mockImpl(() => - Promise.resolve('banana') - ); - var file = '/rootDir/someFile'; - - return cache - .get(file, 'field', loaderCb) - .then(() => { - expect(cache.has(file)).toBe(true); - expect(cache.has(file, 'field')).toBe(true); - expect(cache.has(file, 'foo')).toBe(false); - }); - }); - }); - - describe('invalidate', () => { - it('invalidates the cache per file or per-field', () => { - fs.stat.mockImpl((file, callback) => - callback(null, { - mtime: { - getTime: () => {}, - }, - }) - ); - - var cache = new Cache({ - cacheKey: 'cache', - }); - var loaderCb = jest.genMockFn().mockImpl(() => - Promise.resolve('banana') - ); - var file = '/rootDir/someFile'; - - return cache.get(file, 'field', loaderCb).then(() => { - expect(cache.has(file)).toBe(true); - cache.invalidate(file, 'field'); - expect(cache.has(file)).toBe(true); - expect(cache.has(file, 'field')).toBe(false); - cache.invalidate(file); - expect(cache.has(file)).toBe(false); - }); - }); - }); -}); diff --git a/react-packager/src/DependencyResolver/Cache/index.js b/react-packager/src/DependencyResolver/Cache/index.js deleted file mode 100644 index 26ccb1e7..00000000 --- a/react-packager/src/DependencyResolver/Cache/index.js +++ /dev/null @@ -1,190 +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'; - -const Promise = require('promise'); -const fs = require('graceful-fs'); -const getCacheFilePath = require('./lib/getCacheFilePath'); -const isAbsolutePath = require('absolute-path'); -const loadCacheSync = require('./lib/loadCacheSync'); -const tmpDir = require('os').tmpDir(); - -function getObjectValues(object) { - return Object.keys(object).map(key => object[key]); -} - -function debounce(fn, delay) { - var timeout; - return () => { - clearTimeout(timeout); - timeout = setTimeout(fn, delay); - }; -} - -class Cache { - constructor({ - resetCache, - cacheKey, - cacheDirectory = tmpDir, - }) { - this._cacheFilePath = getCacheFilePath(cacheDirectory, cacheKey); - if (!resetCache) { - this._data = this._loadCacheSync(this._cacheFilePath); - } else { - this._data = Object.create(null); - } - - this._persistEventually = debounce( - this._persistCache.bind(this), - 2000, - ); - } - - get(filepath, field, loaderCb) { - if (!isAbsolutePath(filepath)) { - throw new Error('Use absolute paths'); - } - - return this.has(filepath, field) - ? this._data[filepath].data[field] - : this.set(filepath, field, loaderCb(filepath)); - } - - invalidate(filepath, field) { - if (this.has(filepath, field)) { - if (field == null) { - delete this._data[filepath]; - } else { - delete this._data[filepath].data[field]; - } - } - } - - end() { - return this._persistCache(); - } - - has(filepath, field) { - return Object.prototype.hasOwnProperty.call(this._data, filepath) && - (field == null || Object.prototype.hasOwnProperty.call(this._data[filepath].data, field)); - } - - set(filepath, field, loaderPromise) { - let record = this._data[filepath]; - if (!record) { - record = Object.create(null); - this._data[filepath] = record; - this._data[filepath].data = Object.create(null); - this._data[filepath].metadata = Object.create(null); - } - - record.data[field] = loaderPromise - .then(data => Promise.all([ - data, - Promise.denodeify(fs.stat)(filepath), - ])) - .then(([data, stat]) => { - this._persistEventually(); - - // Evict all existing field data from the cache if we're putting new - // more up to date data - var mtime = stat.mtime.getTime(); - if (record.metadata.mtime !== mtime) { - record.data = Object.create(null); - } - record.metadata.mtime = mtime; - - return data; - }); - - return record.data[field]; - } - - _persistCache() { - if (this._persisting != null) { - return this._persisting; - } - - const data = this._data; - const cacheFilepath = this._cacheFilePath; - - const allPromises = getObjectValues(data) - .map(record => { - const fieldNames = Object.keys(record.data); - const fieldValues = getObjectValues(record.data); - - return Promise - .all(fieldValues) - .then(ref => { - const ret = Object.create(null); - ret.metadata = record.metadata; - ret.data = Object.create(null); - fieldNames.forEach((field, index) => - ret.data[field] = ref[index] - ); - - return ret; - }); - } - ); - - this._persisting = Promise.all(allPromises) - .then(values => { - const json = Object.create(null); - Object.keys(data).forEach((key, i) => { - // make sure the key wasn't added nor removed after we started - // persisting the cache - const value = values[i]; - if (!value) { - return; - } - - json[key] = Object.create(null); - json[key].metadata = data[key].metadata; - json[key].data = value.data; - }); - return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); - }) - .catch(e => console.error('Error while persisting cache:', e.message)) - .then(() => { - this._persisting = null; - return true; - }); - - return this._persisting; - } - - _loadCacheSync(cachePath) { - var ret = Object.create(null); - var cacheOnDisk = loadCacheSync(cachePath); - - // Filter outdated cache and convert to promises. - Object.keys(cacheOnDisk).forEach(key => { - if (!fs.existsSync(key)) { - return; - } - var record = cacheOnDisk[key]; - var stat = fs.statSync(key); - if (stat.mtime.getTime() === record.metadata.mtime) { - ret[key] = Object.create(null); - ret[key].metadata = Object.create(null); - ret[key].data = Object.create(null); - ret[key].metadata.mtime = record.metadata.mtime; - - Object.keys(record.data).forEach(field => { - ret[key].data[field] = Promise.resolve(record.data[field]); - }); - } - }); - - return ret; - } -} - -module.exports = Cache; diff --git a/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js b/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js deleted file mode 100644 index a14faf9f..00000000 --- a/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js +++ /dev/null @@ -1,20 +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'; - -const crypto = require('crypto'); -const path = require('fast-path'); - -function getCacheFilePath(tmpdir, ...args) { - const hash = crypto.createHash('md5'); - args.forEach(arg => hash.update(arg)); - return path.join(tmpdir, hash.digest('hex')); -} - -module.exports = getCacheFilePath; diff --git a/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js b/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js deleted file mode 100644 index 87d6944c..00000000 --- a/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js +++ /dev/null @@ -1,34 +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'; - -const fs = require('graceful-fs'); - -function loadCacheSync(cachePath) { - if (!fs.existsSync(cachePath)) { - return Object.create(null); - } - - try { - return JSON.parse(fs.readFileSync(cachePath)); - } catch (e) { - if (e instanceof SyntaxError) { - console.warn('Unable to parse cache file. Will clear and continue.'); - try { - fs.unlinkSync(cachePath); - } catch (err) { - // Someone else might've deleted it. - } - return Object.create(null); - } - throw e; - } -} - -module.exports = loadCacheSync; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js b/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js deleted file mode 100644 index ed5a1a7b..00000000 --- a/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js +++ /dev/null @@ -1,45 +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'; - -const path = require('fast-path'); - -class DependencyGraphHelpers { - constructor({ providesModuleNodeModules, assetExts }) { - this._providesModuleNodeModules = providesModuleNodeModules; - this._assetExts = assetExts; - } - - isNodeModulesDir(file) { - const index = file.lastIndexOf('/node_modules/'); - if (index === -1) { - return false; - } - - const parts = file.substr(index + 14).split(path.sep); - const dirs = this._providesModuleNodeModules; - for (let i = 0; i < dirs.length; i++) { - if (parts.indexOf(dirs[i]) > -1) { - return false; - } - } - - return true; - } - - isAssetFile(file) { - return this._assetExts.indexOf(this.extname(file)) !== -1; - } - - extname(name) { - return path.extname(name).substr(1); - } -} - -module.exports = DependencyGraphHelpers; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js deleted file mode 100644 index 531d54bc..00000000 --- a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js +++ /dev/null @@ -1,119 +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'; - -const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); -const Fastfs = require('../fastfs'); -const debug = require('debug')('ReactNativePackager:DependencyGraph'); -const path = require('fast-path'); -const Promise = require('promise'); - -class DeprecatedAssetMap { - constructor({ - fsCrawl, - roots, - assetExts, - fileWatcher, - ignoreFilePath, - helpers, - activity, - enabled, - }) { - if (roots == null || roots.length === 0 || !enabled) { - this._disabled = true; - return; - } - - this._helpers = helpers; - this._map = Object.create(null); - this._assetExts = assetExts; - this._activity = activity; - - if (!this._disabled) { - this._fastfs = new Fastfs( - 'Assets', - roots, - fileWatcher, - { ignore: ignoreFilePath, crawling: fsCrawl, activity } - ); - - this._fastfs.on('change', this._processFileChange.bind(this)); - } - } - - build() { - if (this._disabled) { - return Promise.resolve(); - } - - return this._fastfs.build().then( - () => { - const activity = this._activity; - let processAsset_DEPRECATEDActivity; - if (activity) { - processAsset_DEPRECATEDActivity = activity.startEvent( - 'Building (deprecated) Asset Map', - ); - } - - this._fastfs.findFilesByExts(this._assetExts).forEach( - file => this._processAsset(file) - ); - - if (activity) { - activity.endEvent(processAsset_DEPRECATEDActivity); - } - } - ); - } - - resolve(fromModule, toModuleName) { - if (this._disabled) { - return null; - } - - const assetMatch = toModuleName.match(/^image!(.+)/); - if (assetMatch && assetMatch[1]) { - if (!this._map[assetMatch[1]]) { - debug('WARINING: Cannot find asset:', assetMatch[1]); - return null; - } - return this._map[assetMatch[1]]; - } - } - - _processAsset(file) { - const ext = this._helpers.extname(file); - if (this._assetExts.indexOf(ext) !== -1) { - const name = assetName(file, ext); - if (this._map[name] != null) { - debug('Conflicting assets', name); - } - - this._map[name] = new AssetModule_DEPRECATED({ file }); - } - } - - _processFileChange(type, filePath, root, fstat) { - const name = assetName(filePath); - if (type === 'change' || type === 'delete') { - delete this._map[name]; - } - - if (type === 'change' || type === 'add') { - this._processAsset(path.join(root, filePath)); - } - } -} - -function assetName(file, ext) { - return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); -} - -module.exports = DeprecatedAssetMap; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js deleted file mode 100644 index 47f93699..00000000 --- a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ /dev/null @@ -1,142 +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'; -const path = require('fast-path'); -const getPlatformExtension = require('../lib/getPlatformExtension'); -const Promise = require('promise'); - -const GENERIC_PLATFORM = 'generic'; -const NATIVE_PLATFORM = 'native'; - -class HasteMap { - constructor({ - extensions, - fastfs, - moduleCache, - preferNativePlatform, - helpers, - }) { - this._extensions = extensions; - this._fastfs = fastfs; - this._moduleCache = moduleCache; - this._preferNativePlatform = preferNativePlatform; - this._helpers = helpers; - } - - build() { - this._map = Object.create(null); - const promises = []; - this._fastfs.getAllFiles().forEach(filePath => { - if (!this._helpers.isNodeModulesDir(filePath)) { - if (this._extensions.indexOf(path.extname(filePath).substr(1)) !== -1) { - promises.push(this._processHasteModule(filePath)); - } - if (filePath.endsWith('/package.json')) { - promises.push(this._processHastePackage(filePath)); - } - } - }); - return Promise.all(promises).then(() => this._map); - } - - processFileChange(type, absPath) { - return Promise.resolve().then(() => { - /*eslint no-labels: 0 */ - if (type === 'delete' || type === 'change') { - loop: for (const name in this._map) { - const modulesMap = this._map[name]; - for (const platform in modulesMap) { - const module = modulesMap[platform]; - if (module.path === absPath) { - delete modulesMap[platform]; - break loop; - } - } - } - - if (type === 'delete') { - return null; - } - } - - if (this._extensions.indexOf(this._helpers.extname(absPath)) !== -1) { - if (path.basename(absPath) === 'package.json') { - return this._processHastePackage(absPath); - } else { - return this._processHasteModule(absPath); - } - } - }); - } - - getModule(name, platform = null) { - const modulesMap = this._map[name]; - if (modulesMap == null) { - return null; - } - - // If platform is 'ios', we prefer .ios.js to .native.js which we prefer to - // a plain .js file. - let module = undefined; - if (module == null && platform != null) { - module = modulesMap[platform]; - } - if (module == null && this._preferNativePlatform) { - module = modulesMap[NATIVE_PLATFORM]; - } - if (module == null) { - module = modulesMap[GENERIC_PLATFORM]; - } - return module; - } - - _processHasteModule(file) { - const module = this._moduleCache.getModule(file); - return module.isHaste().then( - isHaste => isHaste && module.getName() - .then(name => this._updateHasteMap(name, module)) - ); - } - - _processHastePackage(file) { - file = path.resolve(file); - const p = this._moduleCache.getPackage(file); - return p.isHaste() - .then(isHaste => isHaste && p.getName() - .then(name => this._updateHasteMap(name, p))) - .catch(e => { - if (e instanceof SyntaxError) { - // Malformed package.json. - return; - } - throw e; - }); - } - - _updateHasteMap(name, mod) { - if (this._map[name] == null) { - this._map[name] = Object.create(null); - } - - const moduleMap = this._map[name]; - const modulePlatform = getPlatformExtension(mod.path) || GENERIC_PLATFORM; - const existingModule = moduleMap[modulePlatform]; - - if (existingModule && existingModule.path !== mod.path) { - throw new Error( - `Naming collision detected: ${mod.path} ` + - `collides with ${existingModule.path}` - ); - } - - moduleMap[modulePlatform] = mod; - } -} - -module.exports = HasteMap; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js deleted file mode 100644 index 510b03fe..00000000 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ /dev/null @@ -1,446 +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'; - -const debug = require('debug')('ReactNativePackager:DependencyGraph'); -const util = require('util'); -const path = require('fast-path'); -const realPath = require('path'); -const isAbsolutePath = require('absolute-path'); -const getAssetDataFromName = require('../lib/getAssetDataFromName'); -const Promise = require('promise'); - -class ResolutionRequest { - constructor({ - platform, - preferNativePlatform, - entryPath, - hasteMap, - deprecatedAssetMap, - helpers, - moduleCache, - fastfs, - shouldThrowOnUnresolvedErrors, - }) { - this._platform = platform; - this._preferNativePlatform = preferNativePlatform; - this._entryPath = entryPath; - this._hasteMap = hasteMap; - this._deprecatedAssetMap = deprecatedAssetMap; - this._helpers = helpers; - this._moduleCache = moduleCache; - this._fastfs = fastfs; - this._shouldThrowOnUnresolvedErrors = shouldThrowOnUnresolvedErrors; - this._resetResolutionCache(); - } - - _tryResolve(action, secondaryAction) { - return action().catch((error) => { - if (error.type !== 'UnableToResolveError') { - throw error; - } - return secondaryAction(); - }); - } - - resolveDependency(fromModule, toModuleName) { - const resHash = resolutionHash(fromModule.path, toModuleName); - - if (this._immediateResolutionCache[resHash]) { - return Promise.resolve(this._immediateResolutionCache[resHash]); - } - - const asset_DEPRECATED = this._deprecatedAssetMap.resolve( - fromModule, - toModuleName - ); - if (asset_DEPRECATED) { - return Promise.resolve(asset_DEPRECATED); - } - - const cacheResult = (result) => { - this._immediateResolutionCache[resHash] = result; - return result; - }; - - const forgive = (error) => { - if ( - error.type !== 'UnableToResolveError' || - this._shouldThrowOnUnresolvedErrors(this._entryPath, this._platform) - ) { - throw error; - } - - debug( - 'Unable to resolve module %s from %s', - toModuleName, - fromModule.path - ); - return null; - }; - - if (!this._helpers.isNodeModulesDir(fromModule.path) - && toModuleName[0] !== '.' && - toModuleName[0] !== '/') { - return this._tryResolve( - () => this._resolveHasteDependency(fromModule, toModuleName), - () => this._resolveNodeDependency(fromModule, toModuleName) - ).then( - cacheResult, - forgive, - ); - } - - return this._resolveNodeDependency(fromModule, toModuleName) - .then( - cacheResult, - forgive, - ); - } - - getOrderedDependencies(response, mocksPattern, recursive = true) { - return this._getAllMocks(mocksPattern).then(allMocks => { - const entry = this._moduleCache.getModule(this._entryPath); - const mocks = Object.create(null); - const visited = Object.create(null); - visited[entry.hash()] = true; - - response.pushDependency(entry); - const collect = (mod) => { - return mod.getDependencies().then( - depNames => Promise.all( - depNames.map(name => this.resolveDependency(mod, name)) - ).then((dependencies) => [depNames, dependencies]) - ).then(([depNames, dependencies]) => { - if (allMocks) { - const list = [mod.getName()]; - const pkg = mod.getPackage(); - if (pkg) { - list.push(pkg.getName()); - } - return Promise.all(list).then(names => { - names.forEach(name => { - if (allMocks[name] && !mocks[name]) { - const mockModule = - this._moduleCache.getModule(allMocks[name]); - depNames.push(name); - dependencies.push(mockModule); - mocks[name] = allMocks[name]; - } - }); - return [depNames, dependencies]; - }); - } - return Promise.resolve([depNames, dependencies]); - }).then(([depNames, dependencies]) => { - let p = Promise.resolve(); - const filteredPairs = []; - - dependencies.forEach((modDep, i) => { - const name = depNames[i]; - if (modDep == null) { - // It is possible to require mocks that don't have a real - // module backing them. If a dependency cannot be found but there - // exists a mock with the desired ID, resolve it and add it as - // a dependency. - if (allMocks && allMocks[name] && !mocks[name]) { - const mockModule = this._moduleCache.getModule(allMocks[name]); - mocks[name] = allMocks[name]; - return filteredPairs.push([name, mockModule]); - } - - debug( - 'WARNING: Cannot find required module `%s` from module `%s`', - name, - mod.path - ); - return false; - } - return filteredPairs.push([name, modDep]); - }); - - response.setResolvedDependencyPairs(mod, filteredPairs); - - filteredPairs.forEach(([depName, modDep]) => { - p = p.then(() => { - if (!visited[modDep.hash()]) { - visited[modDep.hash()] = true; - response.pushDependency(modDep); - if (recursive) { - return collect(modDep); - } - } - return null; - }); - }); - - return p; - }); - }; - - return collect(entry).then(() => response.setMocks(mocks)); - }); - } - - _getAllMocks(pattern) { - // Take all mocks in all the roots into account. This is necessary - // because currently mocks are global: any module can be mocked by - // any mock in the system. - let mocks = null; - if (pattern) { - mocks = Object.create(null); - this._fastfs.matchFilesByPattern(pattern).forEach(file => - mocks[path.basename(file, path.extname(file))] = file - ); - } - return Promise.resolve(mocks); - } - - _resolveHasteDependency(fromModule, toModuleName) { - toModuleName = normalizePath(toModuleName); - - let p = fromModule.getPackage(); - if (p) { - p = p.redirectRequire(toModuleName); - } else { - p = Promise.resolve(toModuleName); - } - - return p.then((realModuleName) => { - let dep = this._hasteMap.getModule(realModuleName, this._platform); - if (dep && dep.type === 'Module') { - return dep; - } - - let packageName = realModuleName; - while (packageName && packageName !== '.') { - dep = this._hasteMap.getModule(packageName, this._platform); - if (dep && dep.type === 'Package') { - break; - } - packageName = path.dirname(packageName); - } - - if (dep && dep.type === 'Package') { - const potentialModulePath = path.join( - dep.root, - path.relative(packageName, realModuleName) - ); - return this._tryResolve( - () => this._loadAsFile( - potentialModulePath, - fromModule, - toModuleName, - ), - () => this._loadAsDir(potentialModulePath, fromModule, toModuleName), - ); - } - - throw new UnableToResolveError( - fromModule, - toModuleName, - 'Unable to resolve dependency', - ); - }); - } - - _redirectRequire(fromModule, modulePath) { - return Promise.resolve(fromModule.getPackage()).then(p => { - if (p) { - return p.redirectRequire(modulePath); - } - return modulePath; - }); - } - - _resolveFileOrDir(fromModule, toModuleName) { - const potentialModulePath = isAbsolutePath(toModuleName) ? - toModuleName : - path.join(path.dirname(fromModule.path), toModuleName); - - return this._redirectRequire(fromModule, potentialModulePath).then( - realModuleName => this._tryResolve( - () => this._loadAsFile(realModuleName, fromModule, toModuleName), - () => this._loadAsDir(realModuleName, fromModule, toModuleName) - ) - ); - } - - _resolveNodeDependency(fromModule, toModuleName) { - if (toModuleName[0] === '.' || toModuleName[1] === '/') { - return this._resolveFileOrDir(fromModule, toModuleName); - } else { - return this._redirectRequire(fromModule, toModuleName).then( - realModuleName => { - if (realModuleName[0] === '.' || realModuleName[1] === '/') { - // derive absolute path /.../node_modules/fromModuleDir/realModuleName - const fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13; - const fromModuleDir = fromModule.path.slice(0, fromModule.path.indexOf('/', fromModuleParentIdx)); - const absPath = path.join(fromModuleDir, realModuleName); - return this._resolveFileOrDir(fromModule, absPath); - } - - const searchQueue = []; - for (let currDir = path.dirname(fromModule.path); - currDir !== realPath.parse(fromModule.path).root; - currDir = path.dirname(currDir)) { - searchQueue.push( - path.join(currDir, 'node_modules', realModuleName) - ); - } - - let p = Promise.reject(new UnableToResolveError( - fromModule, - toModuleName, - 'Node module not found', - )); - searchQueue.forEach(potentialModulePath => { - p = this._tryResolve( - () => this._tryResolve( - () => p, - () => this._loadAsFile(potentialModulePath, fromModule, toModuleName), - ), - () => this._loadAsDir(potentialModulePath, fromModule, toModuleName) - ); - }); - - return p; - }); - } - } - - _loadAsFile(potentialModulePath, fromModule, toModule) { - return Promise.resolve().then(() => { - if (this._helpers.isAssetFile(potentialModulePath)) { - const dirname = path.dirname(potentialModulePath); - if (!this._fastfs.dirExists(dirname)) { - throw new UnableToResolveError( - fromModule, - toModule, - `Directory ${dirname} doesn't exist`, - ); - } - - const {name, type} = getAssetDataFromName(potentialModulePath); - - let pattern = '^' + name + '(@[\\d\\.]+x)?'; - if (this._platform != null) { - pattern += '(\\.' + this._platform + ')?'; - } - pattern += '\\.' + type; - - // We arbitrarly grab the first one, because scale selection - // will happen somewhere - const [assetFile] = this._fastfs.matches( - dirname, - new RegExp(pattern) - ); - - if (assetFile) { - return this._moduleCache.getAssetModule(assetFile); - } - } - - let file; - if (this._fastfs.fileExists(potentialModulePath)) { - file = potentialModulePath; - } else if (this._platform != null && - this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) { - file = potentialModulePath + '.' + this._platform + '.js'; - } else if (this._preferNativePlatform && - this._fastfs.fileExists(potentialModulePath + '.native.js')) { - file = potentialModulePath + '.native.js'; - } else if (this._fastfs.fileExists(potentialModulePath + '.js')) { - file = potentialModulePath + '.js'; - } else if (this._fastfs.fileExists(potentialModulePath + '.json')) { - file = potentialModulePath + '.json'; - } else { - throw new UnableToResolveError( - fromModule, - toModule, - `File ${potentialModulePath} doesnt exist`, - ); - } - - return this._moduleCache.getModule(file); - }); - } - - _loadAsDir(potentialDirPath, fromModule, toModule) { - return Promise.resolve().then(() => { - if (!this._fastfs.dirExists(potentialDirPath)) { - throw new UnableToResolveError( - fromModule, - toModule, -`Unable to find this module in its module map or any of the node_modules directories under ${potentialDirPath} and its parent directories - -This might be related to https://github.com/facebook/react-native/issues/4968 -To resolve try the following: - 1. Clear watchman watches: \`watchman watch-del-all\`. - 2. Delete the \`node_modules\` folder: \`rm -rf node_modules && npm install\`. - 3. Reset packager cache: \`rm -fr $TMPDIR/react-*\` or \`npm start -- --reset-cache\`.`, - ); - } - - const packageJsonPath = path.join(potentialDirPath, 'package.json'); - if (this._fastfs.fileExists(packageJsonPath)) { - return this._moduleCache.getPackage(packageJsonPath) - .getMain().then( - (main) => this._tryResolve( - () => this._loadAsFile(main, fromModule, toModule), - () => this._loadAsDir(main, fromModule, toModule) - ) - ); - } - - return this._loadAsFile( - path.join(potentialDirPath, 'index'), - fromModule, - toModule, - ); - }); - } - - _resetResolutionCache() { - this._immediateResolutionCache = Object.create(null); - } - -} - - -function resolutionHash(modulePath, depName) { - return `${path.resolve(modulePath)}:${depName}`; -} - - -function UnableToResolveError(fromModule, toModule, message) { - Error.call(this); - Error.captureStackTrace(this, this.constructor); - this.message = util.format( - 'Unable to resolve module %s from %s: %s', - toModule, - fromModule.path, - message, - ); - this.type = this.name = 'UnableToResolveError'; -} - -util.inherits(UnableToResolveError, Error); - -function normalizePath(modulePath) { - if (path.sep === '/') { - modulePath = path.normalize(modulePath); - } else if (path.posix) { - modulePath = path.posix.normalize(modulePath); - } - - return modulePath.replace(/\/$/, ''); -} - -module.exports = ResolutionRequest; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js deleted file mode 100644 index 56e1ed7a..00000000 --- a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js +++ /dev/null @@ -1,87 +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'; - -class ResolutionResponse { - constructor() { - this.dependencies = []; - this.mainModuleId = null; - this.mocks = null; - this.numPrependedDependencies = 0; - this._mappings = Object.create(null); - this._finalized = false; - } - - copy(properties) { - const { - dependencies = this.dependencies, - mainModuleId = this.mainModuleId, - mocks = this.mocks, - } = properties; - return Object.assign(new this.constructor(), this, { - dependencies, - mainModuleId, - mocks, - }); - } - - _assertNotFinalized() { - if (this._finalized) { - throw new Error('Attempted to mutate finalized response.'); - } - } - - _assertFinalized() { - if (!this._finalized) { - throw new Error('Attempted to access unfinalized response.'); - } - } - - finalize() { - return this._mainModule.getName().then(id => { - this.mainModuleId = id; - this._finalized = true; - return this; - }); - } - - pushDependency(module) { - this._assertNotFinalized(); - if (this.dependencies.length === 0) { - this._mainModule = module; - } - - this.dependencies.push(module); - } - - prependDependency(module) { - this._assertNotFinalized(); - this.dependencies.unshift(module); - this.numPrependedDependencies += 1; - } - - setResolvedDependencyPairs(module, pairs) { - this._assertNotFinalized(); - const hash = module.hash(); - if (this._mappings[hash] == null) { - this._mappings[hash] = pairs; - } - } - - setMocks(mocks) { - this.mocks = mocks; - } - - getResolvedDependencyPairs(module) { - this._assertFinalized(); - return this._mappings[module.hash()]; - } -} - -module.exports = ResolutionResponse; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js deleted file mode 100644 index fa823286..00000000 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ /dev/null @@ -1,4285 +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'; - -jest.autoMockOff(); - -jest.mock('fs'); - -const Promise = require('promise'); -const DependencyGraph = require('../index'); -const fs = require('graceful-fs'); - -const mocksPattern = /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/; - -describe('DependencyGraph', function() { - let defaults; - - function getOrderedDependenciesAsJSON(dgraph, entry, platform, recursive = true) { - return dgraph.getDependencies(entry, platform, recursive) - .then(response => response.finalize()) - .then(({ dependencies }) => Promise.all(dependencies.map(dep => Promise.all([ - dep.getName(), - dep.getDependencies(), - ]).then(([name, moduleDependencies]) => ({ - path: dep.path, - isJSON: dep.isJSON(), - isAsset: dep.isAsset(), - isAsset_DEPRECATED: dep.isAsset_DEPRECATED(), - isPolyfill: dep.isPolyfill(), - resolution: dep.resolution, - id: name, - dependencies: moduleDependencies, - }))) - )); - } - - beforeEach(function() { - const fileWatcher = { - on: function() { - return this; - }, - isWatchman: () => Promise.resolve(false), - }; - - const Cache = jest.genMockFn().mockImplementation(function() { - this._maps = Object.create(null); - }); - Cache.prototype.has = jest.genMockFn() - .mockImplementation(function(filepath, field) { - if (!(filepath in this._maps)) { - return false; - } - return !field || field in this._maps[filepath]; - }); - Cache.prototype.get = jest.genMockFn() - .mockImplementation(function(filepath, field, factory) { - let cacheForPath = this._maps[filepath]; - if (this.has(filepath, field)) { - return field ? cacheForPath[field] : cacheForPath; - } - - if (!cacheForPath) { - cacheForPath = this._maps[filepath] = Object.create(null); - } - const value = cacheForPath[field] = factory(); - return value; - }); - Cache.prototype.invalidate = jest.genMockFn() - .mockImplementation(function(filepath, field) { - if (!this.has(filepath, field)) { - return; - } - - if (field) { - delete this._maps[filepath][field]; - } else { - delete this._maps[filepath]; - } - }); - Cache.prototype.end = jest.genMockFn(); - - defaults = { - assetExts: ['png', 'jpg'], - cache: new Cache(), - fileWatcher, - providesModuleNodeModules: [ - 'haste-fbjs', - 'react-haste', - 'react-native', - // Parse requires AsyncStorage. They will - // change that to require('react-native') which - // should work after this release and we can - // remove it from here. - 'parse', - ], - platforms: ['ios', 'android'], - shouldThrowOnUnresolvedErrors: () => false, - }; - }); - - describe('get sync dependencies', function() { - pit('should get dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - 'require("b")', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule b', - ' */', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: ['b'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'b', - path: '/root/b.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should get shallow dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - 'require("b")', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule b', - ' */', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js', null, false).then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: ['b'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should get dependencies with the correct extensions', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - 'a.js.orig': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should get json dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'package', - }), - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./a.json")', - 'require("./b")', - ].join('\n'), - 'a.json': JSON.stringify({}), - 'b.json': JSON.stringify({}), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./a.json', './b'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'package/a.json', - isJSON: true, - path: '/root/a.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'package/b.json', - isJSON: true, - path: '/root/b.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should get package json as a dep', () => { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'package', - }), - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./package.json")', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(deps => { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./package.json'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'package/package.json', - isJSON: true, - path: '/root/package.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should get dependencies with deprecated assets', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("image!a")', - ].join('\n'), - 'imgs': { - 'a.png': '', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - assetRoots_DEPRECATED: ['/root/imgs'], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['image!a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - isAsset: false, - isJSON: false, - isPolyfill: false, - }, - ]); - }); - }); - - pit('should get dependencies with relative assets', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png")', - ].join('\n'), - 'imgs': { - 'a.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage', - }), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./imgs/a.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - }, - ]); - }); - }); - - pit('should get dependencies with assets and resolution', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png");', - 'require("./imgs/b.png");', - 'require("./imgs/c.png");', - ].join('\n'), - 'imgs': { - 'a@1.5x.png': '', - 'b@.7x.png': '', - 'c.png': '', - 'c@2x.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage', - }), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: [ - './imgs/a.png', - './imgs/b.png', - './imgs/c.png', - ], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a@1.5x.png', - resolution: 1.5, - dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/b.png', - path: '/root/imgs/b@.7x.png', - resolution: 0.7, - dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/c.png', - path: '/root/imgs/c.png', - resolution: 1, - dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - }, - ]); - }); - }); - - pit('should respect platform extension in assets', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png");', - 'require("./imgs/b.png");', - 'require("./imgs/c.png");', - ].join('\n'), - 'imgs': { - 'a@1.5x.ios.png': '', - 'b@.7x.ios.png': '', - 'c.ios.png': '', - 'c@2x.ios.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage', - }), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js', 'ios').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: [ - './imgs/a.png', - './imgs/b.png', - './imgs/c.png', - ], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a@1.5x.ios.png', - resolution: 1.5, - dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/b.png', - path: '/root/imgs/b@.7x.ios.png', - resolution: 0.7, - dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/c.png', - path: '/root/imgs/c.ios.png', - resolution: 1, - dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - }, - ]); - }); - }); - - pit('Deprecated and relative assets can live together', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png")', - 'require("image!a")', - ].join('\n'), - 'imgs': { - 'a.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage', - }), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - assetRoots_DEPRECATED: ['/root/imgs'], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - }, - { - id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - isAsset: false, - isJSON: false, - isPolyfill: false, - }, - ]); - }); - }); - - pit('should get recursive dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - 'require("index")', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: ['index'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should work with packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'lol', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should work with packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'lol', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should work with packages with a dot in the name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("sha.js")', - 'require("x.y.z")', - ].join('\n'), - 'sha.js': { - 'package.json': JSON.stringify({ - name: 'sha.js', - main: 'main.js', - }), - 'main.js': 'lol', - }, - 'x.y.z': { - 'package.json': JSON.stringify({ - name: 'x.y.z', - main: 'main.js', - }), - 'main.js': 'lol', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['sha.js', 'x.y.z'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'sha.js/main.js', - path: '/root/sha.js/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'x.y.z/main.js', - path: '/root/x.y.z/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should default main package to index.js', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': 'lol', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should resolve using alternative ids', () => { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': [ - '/**', - ' * @providesModule EpicModule', - ' */', - ].join('\n'), - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'EpicModule', - path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should default use index.js if main is a dir', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'lib', - }), - lib: { - 'index.js': 'lol', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/lib/index.js', - path: '/root/aPackage/lib/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should resolve require to index if it is a dir', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'test', - }), - 'index.js': 'require("./lib/")', - lib: { - 'index.js': 'lol', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'test/index.js', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'test/lib/index.js', - path: '/root/lib/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should resolve require to main if it is a dir w/ a package.json', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'test', - }), - 'index.js': 'require("./lib/")', - lib: { - 'package.json': JSON.stringify({ - 'main': 'main.js', - }), - 'index.js': 'lol', - 'main.js': 'lol', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'test/index.js', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: '/root/lib/main.js', - path: '/root/lib/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should ignore malformed packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - ].join('\n'), - 'aPackage': { - 'package.json': 'lol', - 'main.js': 'lol', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should fatal on multiple modules with the same name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule index', - ' */', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - - return dgraph.load().catch(err => { - expect(err.message).toEqual('Failed to build DependencyGraph: Naming collision detected: /root/b.js collides with /root/index.js'); - expect(err.type).toEqual('DependencyGraphError'); - }); - }); - - pit('should be forgiving with missing requires', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("lolomg")', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['lolomg'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should work with packages with subdirs', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/subdir/lolynot")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'lol', - 'subdir': { - 'lolynot.js': 'lolynot', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/subdir/lolynot.js', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should work with packages with symlinked subdirs', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'symlinkedPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'lol', - 'subdir': { - 'lolynot.js': 'lolynot', - }, - }, - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/subdir/lolynot")', - ].join('\n'), - 'aPackage': { SYMLINK: '/symlinkedPackage' }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/subdir/lolynot.js', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should work with relative modules in packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'require("./subdir/lolynot")', - 'subdir': { - 'lolynot.js': 'require("../other")', - }, - 'other.js': 'some code', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/subdir/lolynot.js', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/other.js', - path: '/root/aPackage/other.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - testBrowserField('browser'); - testBrowserField('react-native'); - - function replaceBrowserField(json, fieldName) { - if (fieldName !== 'browser') { - json[fieldName] = json.browser; - delete json.browser; - } - - return json; - } - - function testBrowserField(fieldName) { - pit('should support simple browser field in packages ("' + fieldName + '")', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify(replaceBrowserField({ - name: 'aPackage', - main: 'main.js', - browser: 'client.js', - }, fieldName)), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should support browser field in packages w/o .js ext ("' + fieldName + '")', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify(replaceBrowserField({ - name: 'aPackage', - main: 'main.js', - browser: 'client', - }, fieldName)), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should support mapping main in browser field json ("' + fieldName + '")', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify(replaceBrowserField({ - name: 'aPackage', - main: './main.js', - browser: { - './main.js': './client.js', - }, - }, fieldName)), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - assetExts: ['png', 'jpg'], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should work do correct browser mapping w/o js ext ("' + fieldName + '")', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify(replaceBrowserField({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - }, - }, fieldName)), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - assetExts: ['png', 'jpg'], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should support browser mapping of files ("' + fieldName + '")', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify(replaceBrowserField({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - './node.js': './not-node.js', - './not-browser': './browser.js', - './dir/server.js': './dir/client', - './hello.js': './bye.js', - }, - }, fieldName)), - 'main.js': 'some other code', - 'client.js': 'require("./node")\nrequire("./dir/server.js")', - 'not-node.js': 'require("./not-browser")', - 'not-browser.js': 'require("./dir/server")', - 'browser.js': 'some browser code', - 'dir': { - 'server.js': 'some node code', - 'client.js': 'require("../hello")', - }, - 'hello.js': 'hello', - 'bye.js': 'bye', - }, - }, - }); - - const dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/not-node.js', - path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/browser.js', - path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/dir/client.js', - path: '/root/aPackage/dir/client.js', - dependencies: ['../hello'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/bye.js', - path: '/root/aPackage/bye.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should support browser mapping for packages ("' + fieldName + '")', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify(replaceBrowserField({ - name: 'aPackage', - browser: { - 'node-package': 'browser-package', - }, - }, fieldName)), - 'index.js': 'require("node-package")', - 'node-package': { - 'package.json': JSON.stringify({ - 'name': 'node-package', - }), - 'index.js': 'some node code', - }, - 'browser-package': { - 'package.json': JSON.stringify({ - 'name': 'browser-package', - }), - 'index.js': 'some browser code', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['node-package'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'browser-package/index.js', - path: '/root/aPackage/browser-package/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should support browser mapping of a package to a file ("' + fieldName + '")', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify(replaceBrowserField({ - name: 'aPackage', - browser: { - 'node-package': './dir/browser.js', - }, - }, fieldName)), - 'index.js': 'require("./dir/ooga")', - 'dir': { - 'ooga.js': 'require("node-package")', - 'browser.js': 'some browser code', - }, - 'node-package': { - 'package.json': JSON.stringify({ - 'name': 'node-package', - }), - 'index.js': 'some node code', - }, - }, - }, - }); - - const dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['./dir/ooga'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/dir/ooga.js', - path: '/root/aPackage/dir/ooga.js', - dependencies: ['node-package'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/dir/browser.js', - path: '/root/aPackage/dir/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should support browser mapping for packages ("' + fieldName + '")', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify(replaceBrowserField({ - name: 'aPackage', - browser: { - 'node-package': 'browser-package', - }, - }, fieldName)), - 'index.js': 'require("node-package")', - 'node-package': { - 'package.json': JSON.stringify({ - 'name': 'node-package', - }), - 'index.js': 'some node code', - }, - 'browser-package': { - 'package.json': JSON.stringify({ - 'name': 'browser-package', - }), - 'index.js': 'some browser code', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['node-package'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'browser-package/index.js', - path: '/root/aPackage/browser-package/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - } - - pit('should fall back to browser mapping from react-native mapping', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - 'react-native': { - 'node-package': 'rn-package', - }, - }), - 'index.js': 'require("node-package")', - 'node_modules': { - 'node-package': { - 'package.json': JSON.stringify({ - 'name': 'node-package', - }), - 'index.js': 'some node code', - }, - 'rn-package': { - 'package.json': JSON.stringify({ - 'name': 'rn-package', - browser: { - 'nested-package': 'nested-browser-package', - }, - }), - 'index.js': 'require("nested-package")', - }, - 'nested-browser-package': { - 'package.json': JSON.stringify({ - 'name': 'nested-browser-package', - }), - 'index.js': 'some code', - }, - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['node-package'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'rn-package/index.js', - path: '/root/aPackage/node_modules/rn-package/index.js', - dependencies: ['nested-package'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'nested-browser-package/index.js', - path: '/root/aPackage/node_modules/nested-browser-package/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - describe('node_modules', function() { - pit('should work with nested node_modules', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 1 module', - }, - }, - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 2 module', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('platform should work with node_modules', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.ios.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - }), - 'index.ios.js': '', - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main', - }), - 'main.ios.js': '', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.ios.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/index.ios.js', - path: '/root/node_modules/foo/index.ios.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.ios.js', - path: '/root/node_modules/bar/main.ios.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('nested node_modules with specific paths', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar/");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 1 module', - 'lol.js': '', - }, - }, - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 2 module', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo', 'bar/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/lol.js', - path: '/root/node_modules/foo/node_modules/bar/lol.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('nested node_modules with browser field', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - browser: { - './lol': './wow', - }, - }), - 'main.js': 'bar 1 module', - 'lol.js': '', - 'wow.js': '', - }, - }, - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - browser: './main2', - }), - 'main2.js': 'bar 2 module', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/lol.js', - path: '/root/node_modules/foo/node_modules/bar/lol.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main2.js', - path: '/root/node_modules/bar/main2.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('node_modules should support multi level', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': '', - }, - }, - 'path': { - 'to': { - 'bar.js': [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")', - ].join('\n'), - }, - 'node_modules': {}, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar', - path: '/root/path/to/bar.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should selectively ignore providesModule in node_modules', function() { - var root = '/root'; - var otherRoot = '/anotherRoot'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("shouldWork");', - 'require("dontWork");', - 'require("wontWork");', - 'require("ember");', - 'require("internalVendoredPackage");', - 'require("anotherIndex");', - ].join('\n'), - 'node_modules': { - 'react-haste': { - 'package.json': JSON.stringify({ - name: 'react-haste', - main: 'main.js', - }), - // @providesModule should not be ignored here, because react-haste is whitelisted - 'main.js': [ - '/**', - ' * @providesModule shouldWork', - ' */', - 'require("submodule");', - ].join('\n'), - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - // @providesModule should be ignored here, because it's not whitelisted - 'main.js':[ - '/**', - ' * @providesModule dontWork', - ' */', - 'hi();', - ].join('\n'), - }, - 'submodule': { - 'package.json': JSON.stringify({ - name: 'submodule', - main: 'main.js', - }), - 'main.js': 'log()', - }, - }, - }, - 'ember': { - 'package.json': JSON.stringify({ - name: 'ember', - main: 'main.js', - }), - // @providesModule should be ignored here, because it's not whitelisted, - // and also, the modules "id" should be ember/main.js, not it's haste name - 'main.js':[ - '/**', - ' * @providesModule wontWork', - ' */', - 'hi();', - ].join('\n'), - }, - }, - // This part of the dep graph is meant to emulate internal facebook infra. - // By whitelisting `vendored_modules`, haste should still work. - 'vendored_modules': { - 'a-vendored-package': { - 'package.json': JSON.stringify({ - name: 'a-vendored-package', - main: 'main.js', - }), - // @providesModule should _not_ be ignored here, because it's whitelisted. - 'main.js':[ - '/**', - ' * @providesModule internalVendoredPackage', - ' */', - 'hiFromInternalPackage();', - ].join('\n'), - }, - }, - }, - // we need to support multiple roots and using haste between them - 'anotherRoot': { - 'index.js': [ - '/**', - ' * @providesModule anotherIndex', - ' */', - 'wazup()', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root, otherRoot], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: [ - 'shouldWork', - 'dontWork', - 'wontWork', - 'ember', - 'internalVendoredPackage', - 'anotherIndex', - ], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'shouldWork', - path: '/root/node_modules/react-haste/main.js', - dependencies: ['submodule'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'submodule/main.js', - path: '/root/node_modules/react-haste/node_modules/submodule/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'ember/main.js', - path: '/root/node_modules/ember/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'internalVendoredPackage', - path: '/root/vendored_modules/a-vendored-package/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'anotherIndex', - path: '/anotherRoot/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should not be confused by prev occuring whitelisted names', function() { - var root = '/react-haste'; - fs.__setMockFilesystem({ - 'react-haste': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("shouldWork");', - ].join('\n'), - 'node_modules': { - 'react-haste': { - 'package.json': JSON.stringify({ - name: 'react-haste', - main: 'main.js', - }), - 'main.js': [ - '/**', - ' * @providesModule shouldWork', - ' */', - ].join('\n'), - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/react-haste/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/react-haste/index.js', - dependencies: ['shouldWork'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'shouldWork', - path: '/react-haste/node_modules/react-haste/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - - pit('should ignore modules it cant find (assumes own require system)', function() { - // For example SourceMap.js implements it's own require system. - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo/lol");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'foo module', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should work with node packages with a .js in the name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("sha.js")', - ].join('\n'), - 'node_modules': { - 'sha.js': { - 'package.json': JSON.stringify({ - name: 'sha.js', - main: 'main.js', - }), - 'main.js': 'lol', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['sha.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'sha.js/main.js', - path: '/root/node_modules/sha.js/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should work with multiple platforms (haste)', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('a'); - `, - 'a.ios.js': ` - /** - * @providesModule a - */ - `, - 'a.android.js': ` - /** - * @providesModule a - */ - `, - 'a.js': ` - /** - * @providesModule a - */ - `, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.ios.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.ios.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should pick the generic file', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('a'); - `, - 'a.android.js': ` - /** - * @providesModule a - */ - `, - 'a.js': ` - /** - * @providesModule a - */ - `, - 'a.web.js': ` - /** - * @providesModule a - */ - `, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.ios.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should work with multiple platforms (node)', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('./a'); - `, - 'a.ios.js': '', - 'a.android.js': '', - 'a.js': '', - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.ios.js', - dependencies: ['./a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: '/root/a.ios.js', - path: '/root/a.ios.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - pit('should require package.json', () => { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo/package.json");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'require("./package.json")', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(deps => { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo/package.json', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/package.json', - path: '/root/node_modules/foo/package.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: true, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/bar/main.js', - dependencies: ['./package.json'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/package.json', - path: '/root/node_modules/bar/package.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: true, - isPolyfill: false, - resolution: undefined, - }, - - ]); - }); - }); - }); - - describe('file watch updating', function() { - var triggerFileChange; - var mockStat = { - isDirectory: () => false, - }; - - beforeEach(function() { - var callbacks = []; - triggerFileChange = (...args) => - callbacks.map(callback => callback(...args)); - - defaults.fileWatcher = { - on: function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - callbacks.push(callback); - return this; - }, - isWatchman: () => Promise.resolve(false), - }; - }); - - pit('updates module dependencies', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - 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 getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file change', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - 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 getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file delete', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { - delete filesystem.root.foo; - triggerFileChange('delete', 'foo.js', root); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { - filesystem.root['bar.js'] = [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")', - ].join('\n'); - triggerFileChange('add', 'bar.js', root, mockStat); - - filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js', root, mockStat); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar', - path: '/root/bar.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo', - path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('updates module dependencies on deprecated asset add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("image!foo")', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - assetRoots_DEPRECATED: [root], - assetExts: ['png'], - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['image!foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - - filesystem.root['foo.png'] = ''; - triggerFileChange('add', 'foo.png', root, mockStat); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { - expect(deps2) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['image!foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - isAsset: false, - isJSON: false, - isPolyfill: false, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('updates module dependencies on relative asset add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./foo.png")', - ].join('\n'), - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - assetExts: ['png'], - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { id: 'index', - path: '/root/index.js', - dependencies: ['./foo.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - - filesystem.root['foo.png'] = ''; - triggerFileChange('add', 'foo.png', root, mockStat); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { - expect(deps2) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./foo.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('runs changes through ignore filter', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - ignoreFilePath: function(filePath) { - if (filePath === '/root/bar.js') { - return true; - } - return false; - }, - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { - filesystem.root['bar.js'] = [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")', - ].join('\n'); - triggerFileChange('add', 'bar.js', root, mockStat); - - filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js', root, mockStat); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo', - path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('should ignore directory updates', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { - triggerFileChange('change', 'aPackage', '/root', { - isDirectory: () => true, - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo', - path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('changes to browser field', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - 'browser.js': 'browser', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'browser.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/browser.js', - path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('removes old package from cache', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - 'browser.js': 'browser', - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - 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 getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('should update node package changes', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 1 module', - }, - }, - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - - filesystem.root.node_modules.foo['main.js'] = 'lol'; - triggerFileChange('change', 'main.js', '/root/node_modules/foo', mockStat); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { - expect(deps2) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('should update node package main changes', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'foo module', - 'browser.js': 'foo module', - }, - }, - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ - name: 'foo', - main: 'main.js', - browser: 'browser.js', - }); - triggerFileChange('change', 'package.json', '/root/node_modules/foo', mockStat); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { - expect(deps2) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/browser.js', - path: '/root/node_modules/foo/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('should not error when the watcher reports a known file as added', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'var b = require("b");', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule b', - ' */', - 'module.exports = function() {};', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { - triggerFileChange('add', 'index.js', root, mockStat); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js'); - }); - }); - }); - - describe('Extensions', () => { - pit('supports custom file extensions', () => { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.jsx': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.coffee': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - 'X.js': '', - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - extensions: ['jsx', 'coffee'], - }); - - return dgraph.matchFilesByPattern('.*') - .then(files => { - expect(files).toEqual([ - '/root/index.jsx', '/root/a.coffee', - ]); - }) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.jsx')) - .then(deps => { - expect(deps).toEqual([ - { - dependencies: ['a'], - id: 'index', - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - path: '/root/index.jsx', - resolution: undefined, - }, - { - dependencies: [], - id: 'a', - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - path: '/root/a.coffee', - resolution: undefined, - }, - ]); - }); - }); - }); - - describe('Mocks', () => { - pit('resolves to null if mocksPattern is not specified', () => { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - '__mocks__': { - 'A.js': '', - }, - 'index.js': '', - }, - }); - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - - return dgraph.getDependencies('/root/index.js') - .then(response => response.finalize()) - .then(response => { - expect(response.mocks).toEqual({}); - }); - }); - - pit('retrieves a list of all required mocks', () => { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - '__mocks__': { - 'A.js': '', - 'b.js': '', - }, - 'b.js': [ - '/**', - ' * @providesModule b', - ' */', - 'require("A");', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - mocksPattern, - }); - - return dgraph.getDependencies('/root/b.js') - .then(response => response.finalize()) - .then(response => { - expect(response.mocks).toEqual({ - A: '/root/__mocks__/A.js', - b: '/root/__mocks__/b.js', - }); - }); - }); - - pit('adds mocks as a dependency of their actual module', () => { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - '__mocks__': { - 'A.js': [ - 'require("b");', - ].join('\n'), - 'b.js': '', - }, - 'A.js': [ - '/**', - ' * @providesModule A', - ' */', - 'require("foo");', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - mocksPattern, - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/A.js') - .then(deps => { - expect(deps).toEqual([ - { - path: '/root/A.js', - isJSON: false, - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - id: 'A', - dependencies: ['foo', 'A'], - }, - { - path: '/root/foo.js', - isJSON: false, - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - id: 'foo', - dependencies: [], - }, - { - path: '/root/__mocks__/A.js', - isJSON: false, - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - id: '/root/__mocks__/A.js', - dependencies: ['b'], - }, - { - path: '/root/__mocks__/b.js', - isJSON: false, - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - id: '/root/__mocks__/b.js', - dependencies: [], - }, - ]); - }); - }); - - pit('resolves mocks that do not have a real module associated with them', () => { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - '__mocks__': { - 'foo.js': [ - 'require("b");', - ].join('\n'), - 'b.js': '', - }, - 'A.js': [ - '/**', - ' * @providesModule A', - ' */', - 'require("foo");', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - mocksPattern, - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/A.js') - .then(deps => { - expect(deps).toEqual([ - { - path: '/root/A.js', - isJSON: false, - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - id: 'A', - dependencies: ['foo'], - }, - { - path: '/root/__mocks__/foo.js', - isJSON: false, - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - id: '/root/__mocks__/foo.js', - dependencies: ['b'], - }, - { - path: '/root/__mocks__/b.js', - isJSON: false, - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - id: '/root/__mocks__/b.js', - dependencies: [], - }, - ]); - }); - }); - }); -}); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/DependencyGraph/docblock.js deleted file mode 100644 index d710112a..00000000 --- a/react-packager/src/DependencyResolver/DependencyGraph/docblock.js +++ /dev/null @@ -1,83 +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 docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/; - -var ltrimRe = /^\s*/; -/** - * @param {String} contents - * @return {String} - */ -function extract(contents) { - var match = contents.match(docblockRe); - if (match) { - return match[0].replace(ltrimRe, '') || ''; - } - return ''; -} - - -var commentStartRe = /^\/\*\*/; -var commentEndRe = /\*\/$/; -var wsRe = /[\t ]+/g; -var stringStartRe = /(\r?\n|^) *\*/g; -var multilineRe = - /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g; -var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g; - -/** - * @param {String} contents - * @return {Array} - */ -function parse(docblock) { - docblock = docblock - .replace(commentStartRe, '') - .replace(commentEndRe, '') - .replace(wsRe, ' ') - .replace(stringStartRe, '$1'); - - // Normalize multi-line directives - var prev = ''; - while (prev !== docblock) { - prev = docblock; - docblock = docblock.replace(multilineRe, '\n$1 $2\n'); - } - docblock = docblock.trim(); - - var result = []; - var match; - while ((match = propertyRe.exec(docblock))) { - result.push([match[1], match[2]]); - } - - return result; -} - -/** - * Same as parse but returns an object of prop: value instead of array of paris - * If a property appers more than once the last one will be returned - * - * @param {String} contents - * @return {Object} - */ -function parseAsObject(docblock) { - var pairs = parse(docblock); - var result = {}; - for (var i = 0; i < pairs.length; i++) { - result[pairs[i][0]] = pairs[i][1]; - } - return result; -} - - -exports.extract = extract; -exports.parse = parse; -exports.parseAsObject = parseAsObject; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js deleted file mode 100644 index b3a987b1..00000000 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ /dev/null @@ -1,287 +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'; - -const Fastfs = require('../fastfs'); -const ModuleCache = require('../ModuleCache'); -const Promise = require('promise'); -const crawl = require('../crawlers'); -const getPlatformExtension = require('../lib/getPlatformExtension'); -const isAbsolutePath = require('absolute-path'); -const path = require('fast-path'); -const util = require('util'); -const DependencyGraphHelpers = require('./DependencyGraphHelpers'); -const ResolutionRequest = require('./ResolutionRequest'); -const ResolutionResponse = require('./ResolutionResponse'); -const HasteMap = require('./HasteMap'); -const DeprecatedAssetMap = require('./DeprecatedAssetMap'); - -const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError'; - -const defaultActivity = { - startEvent: () => {}, - endEvent: () => {}, -}; - -class DependencyGraph { - constructor({ - activity, - roots, - ignoreFilePath, - fileWatcher, - assetRoots_DEPRECATED, - assetExts, - providesModuleNodeModules, - platforms, - preferNativePlatform, - cache, - extensions, - mocksPattern, - extractRequires, - transformCode, - shouldThrowOnUnresolvedErrors = () => true, - enableAssetMap, - }) { - this._opts = { - activity: activity || defaultActivity, - roots, - ignoreFilePath: ignoreFilePath || (() => {}), - fileWatcher, - assetRoots_DEPRECATED: assetRoots_DEPRECATED || [], - assetExts: assetExts || [], - providesModuleNodeModules, - platforms: platforms || [], - preferNativePlatform: preferNativePlatform || false, - extensions: extensions || ['js', 'json'], - mocksPattern, - extractRequires, - transformCode, - shouldThrowOnUnresolvedErrors, - enableAssetMap: enableAssetMap || true, - }; - this._cache = cache; - this._helpers = new DependencyGraphHelpers(this._opts); - this.load(); - } - - load() { - if (this._loading) { - return this._loading; - } - - const {activity} = this._opts; - 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, - exts: this._opts.extensions.concat(this._opts.assetExts), - fileWatcher: this._opts.fileWatcher, - }); - this._crawling.then((files) => activity.endEvent(crawlActivity)); - - this._fastfs = new Fastfs( - 'JavaScript', - this._opts.roots, - this._opts.fileWatcher, - { - ignore: this._opts.ignoreFilePath, - crawling: this._crawling, - activity: activity, - } - ); - - this._fastfs.on('change', this._processFileChange.bind(this)); - - this._moduleCache = new ModuleCache({ - fastfs: this._fastfs, - cache: this._cache, - extractRequires: this._opts.extractRequires, - transformCode: this._opts.transformCode, - depGraphHelpers: this._helpers, - }); - - this._hasteMap = new HasteMap({ - fastfs: this._fastfs, - extensions: this._opts.extensions, - moduleCache: this._moduleCache, - preferNativePlatform: this._opts.preferNativePlatform, - helpers: this._helpers, - }); - - this._deprecatedAssetMap = new DeprecatedAssetMap({ - fsCrawl: this._crawling, - roots: this._opts.assetRoots_DEPRECATED, - helpers: this._helpers, - fileWatcher: this._opts.fileWatcher, - ignoreFilePath: this._opts.ignoreFilePath, - assetExts: this._opts.assetExts, - activity: this._opts.activity, - enabled: this._opts.enableAssetMap, - }); - - this._loading = Promise.all([ - this._fastfs.build() - .then(() => { - const hasteActivity = activity.startEvent('Building Haste Map'); - return this._hasteMap.build().then(map => { - activity.endEvent(hasteActivity); - return map; - }); - }), - this._deprecatedAssetMap.build(), - ]).then( - response => { - activity.endEvent(depGraphActivity); - return response[0]; // Return the haste map - }, - err => { - const error = new Error( - `Failed to build DependencyGraph: ${err.message}` - ); - error.type = ERROR_BUILDING_DEP_GRAPH; - error.stack = err.stack; - throw error; - } - ); - - return this._loading; - } - - /** - * Returns a promise with the direct dependencies the module associated to - * the given entryPath has. - */ - getShallowDependencies(entryPath) { - return this._moduleCache.getModule(entryPath).getDependencies(); - } - - getFS() { - return this._fastfs; - } - - /** - * Returns the module object for the given path. - */ - getModuleForPath(entryFile) { - return this._moduleCache.getModule(entryFile); - } - - getAllModules() { - return this.load().then(() => this._moduleCache.getAllModules()); - } - - getDependencies(entryPath, platform, recursive = true) { - return this.load().then(() => { - platform = this._getRequestPlatform(entryPath, platform); - const absPath = this._getAbsolutePath(entryPath); - const req = new ResolutionRequest({ - platform, - preferNativePlatform: this._opts.preferNativePlatform, - entryPath: absPath, - deprecatedAssetMap: this._deprecatedAssetMap, - hasteMap: this._hasteMap, - helpers: this._helpers, - moduleCache: this._moduleCache, - fastfs: this._fastfs, - shouldThrowOnUnresolvedErrors: this._opts.shouldThrowOnUnresolvedErrors, - }); - - const response = new ResolutionResponse(); - - return req.getOrderedDependencies( - response, - this._opts.mocksPattern, - recursive, - ).then(() => response); - }); - } - - matchFilesByPattern(pattern) { - return this.load().then(() => this._fastfs.matchFilesByPattern(pattern)); - } - - _getRequestPlatform(entryPath, platform) { - if (platform == null) { - platform = getPlatformExtension(entryPath); - if (platform == null || this._opts.platforms.indexOf(platform) === -1) { - platform = null; - } - } else if (this._opts.platforms.indexOf(platform) === -1) { - throw new Error('Unrecognized platform: ' + platform); - } - return platform; - } - - _getAbsolutePath(filePath) { - if (isAbsolutePath(filePath)) { - return path.resolve(filePath); - } - - for (let i = 0; i < this._opts.roots.length; i++) { - const root = this._opts.roots[i]; - const potentialAbsPath = path.join(root, filePath); - if (this._fastfs.fileExists(potentialAbsPath)) { - return path.resolve(potentialAbsPath); - } - } - - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - filePath, - this._opts.roots - ); - } - - _processFileChange(type, filePath, root, fstat) { - const absPath = path.join(root, filePath); - if (fstat && fstat.isDirectory() || - this._opts.ignoreFilePath(absPath) || - this._helpers.isNodeModulesDir(absPath)) { - return; - } - - // Ok, this is some tricky promise code. Our requirements are: - // * we need to report back failures - // * failures shouldn't block recovery - // * Errors can leave `hasteMap` in an incorrect state, and we need to rebuild - // After we process a file change we record any errors which will also be - // reported via the next request. On the next file change, we'll see that - // we are in an error state and we should decide to do a full rebuild. - this._loading = this._loading.finally(() => { - if (this._hasteMapError) { - console.warn( - 'Rebuilding haste map to recover from error:\n' + - this._hasteMapError.stack - ); - this._hasteMapError = null; - - // Rebuild the entire map if last change resulted in an error. - this._loading = this._hasteMap.build(); - } else { - this._loading = this._hasteMap.processFileChange(type, absPath); - this._loading.catch((e) => this._hasteMapError = e); - } - return this._loading; - }); - } - -} - -function NotFoundError() { - Error.call(this); - Error.captureStackTrace(this, this.constructor); - var msg = util.format.apply(util, arguments); - this.message = msg; - this.type = this.name = 'NotFoundError'; - this.status = 404; -} -util.inherits(NotFoundError, Error); - -module.exports = DependencyGraph; diff --git a/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js b/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js deleted file mode 100644 index f59fa910..00000000 --- a/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js +++ /dev/null @@ -1,14 +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 = { - WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher'), - NodeWatcher: jest.genMockFromModule('sane/src/node_watcher'), -}; diff --git a/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js deleted file mode 100644 index 650e5100..00000000 --- a/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js +++ /dev/null @@ -1,89 +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'; - -jest - .dontMock('util') - .dontMock('events') - .dontMock('../') - .setMock('child_process', { - exec: (cmd, cb) => cb(null, '/usr/bin/watchman'), - }); - -const sane = require('sane'); - -describe('FileWatcher', () => { - let WatchmanWatcher; - let NodeWatcher; - let FileWatcher; - let config; - - beforeEach(() => { - WatchmanWatcher = sane.WatchmanWatcher; - WatchmanWatcher.prototype.once.mockImplementation( - (type, callback) => callback() - ); - NodeWatcher = sane.NodeWatcher; - NodeWatcher.prototype.once.mockImplementation( - (type, callback) => callback() - ); - - FileWatcher = require('../'); - - config = [{ - dir: 'rootDir', - globs: [ - '**/*.js', - '**/*.json', - ], - }]; - }); - - pit('gets the watcher instance when ready', () => { - const fileWatcher = new FileWatcher(config, {useWatchman: true}); - return fileWatcher.getWatchers().then(watchers => { - watchers.forEach(watcher => { - expect(watcher instanceof WatchmanWatcher).toBe(true); - }); - }); - }); - - pit('gets the node watcher if watchman is disabled', () => { - const fileWatcher = new FileWatcher(config, {useWatchman: false}); - return fileWatcher.getWatchers().then(watchers => { - watchers.forEach(watcher => { - expect(watcher instanceof NodeWatcher).toBe(true); - }); - }); - }); - - pit('emits events', () => { - let cb; - WatchmanWatcher.prototype.on.mockImplementation((type, callback) => { - cb = callback; - }); - const fileWatcher = new FileWatcher(config, {useWatchman: true}); - const handler = jest.genMockFn(); - fileWatcher.on('all', handler); - return fileWatcher.getWatchers().then(watchers => { - cb(1, 2, 3, 4); - jest.runAllTimers(); - expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]); - }); - }); - - pit('ends the watcher', () => { - const fileWatcher = new FileWatcher(config, {useWatchman: true}); - WatchmanWatcher.prototype.close.mockImplementation(callback => callback()); - - return fileWatcher.end().then(() => { - expect(WatchmanWatcher.prototype.close).toBeCalled(); - }); - }); -}); diff --git a/react-packager/src/DependencyResolver/FileWatcher/index.js b/react-packager/src/DependencyResolver/FileWatcher/index.js deleted file mode 100644 index ab548366..00000000 --- a/react-packager/src/DependencyResolver/FileWatcher/index.js +++ /dev/null @@ -1,128 +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'; - -const EventEmitter = require('events').EventEmitter; -const sane = require('sane'); -const Promise = require('promise'); -const exec = require('child_process').exec; - -const MAX_WAIT_TIME = 120000; - -// TODO(amasad): can we use watchman version command instead? -const detectingWatcherClass = new Promise(function(resolve) { - exec('which watchman', function(err, out) { - if (err || out.length === 0) { - resolve(sane.NodeWatcher); - } else { - resolve(sane.WatchmanWatcher); - } - }); -}); - -let inited = false; - -class FileWatcher extends EventEmitter { - - constructor(rootConfigs, options) { - if (inited) { - throw new Error('FileWatcher can only be instantiated once'); - } - inited = true; - - super(); - this._useWatchman = options.useWatchman; - this._watcherByRoot = Object.create(null); - - this._loading = Promise.all( - rootConfigs.map(rootConfig => this._createWatcher(rootConfig)) - ).then(watchers => { - watchers.forEach((watcher, i) => { - this._watcherByRoot[rootConfigs[i].dir] = watcher; - watcher.on( - 'all', - // args = (type, filePath, root, stat) - (...args) => this.emit('all', ...args) - ); - }); - return watchers; - }); - - this._loading.done(); - } - - getWatchers() { - return this._loading; - } - - getWatcherForRoot(root) { - return this._loading.then(() => this._watcherByRoot[root]); - } - - isWatchman() { - return this._useWatchman ? detectingWatcherClass.then( - Watcher => Watcher === sane.WatchmanWatcher - ) : Promise.resolve(false); - } - - end() { - inited = false; - return this._loading.then( - (watchers) => watchers.map( - watcher => Promise.denodeify(watcher.close).call(watcher) - ) - ); - } - - _createWatcher(rootConfig) { - return detectingWatcherClass.then(Watcher => { - if (!this._useWatchman) { - Watcher = sane.NodeWatcher; - } - const watcher = new Watcher(rootConfig.dir, { - glob: rootConfig.globs, - dot: false, - }); - - return new Promise((resolve, reject) => { - const rejectTimeout = setTimeout( - () => reject(new Error(timeoutMessage(Watcher))), - MAX_WAIT_TIME - ); - - watcher.once('ready', () => { - clearTimeout(rejectTimeout); - resolve(watcher); - }); - }); - }); - } - - static createDummyWatcher() { - return Object.assign(new EventEmitter(), { - isWatchman: () => Promise.resolve(false), - end: () => Promise.resolve(), - }); - } -} - -function timeoutMessage(Watcher) { - const lines = [ - 'Watcher took too long to load (' + Watcher.name + ')', - ]; - if (Watcher === sane.WatchmanWatcher) { - lines.push( - 'Try running `watchman version` from your terminal', - 'https://facebook.github.io/watchman/docs/troubleshooting.html', - ); - } - return lines.join('\n'); -} - -module.exports = FileWatcher; diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js deleted file mode 100644 index 6849c922..00000000 --- a/react-packager/src/DependencyResolver/Module.js +++ /dev/null @@ -1,226 +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'; - -const crypto = require('crypto'); -const docblock = require('./DependencyGraph/docblock'); -const isAbsolutePath = require('absolute-path'); -const jsonStableStringify = require('json-stable-stringify'); -const path = require('fast-path'); -const extractRequires = require('./lib/extractRequires'); - -class Module { - - constructor({ - file, - fastfs, - moduleCache, - cache, - extractor = extractRequires, - transformCode, - depGraphHelpers, - }) { - if (!isAbsolutePath(file)) { - throw new Error('Expected file to be absolute path but got ' + file); - } - - this.path = file; - this.type = 'Module'; - - this._fastfs = fastfs; - this._moduleCache = moduleCache; - this._cache = cache; - this._extractor = extractor; - this._transformCode = transformCode; - this._depGraphHelpers = depGraphHelpers; - } - - isHaste() { - return this._cache.get( - this.path, - 'isHaste', - () => this._readDocBlock().then(({id}) => !!id) - ); - } - - getCode(transformOptions) { - return this.read(transformOptions).then(({code}) => code); - } - - getMap(transformOptions) { - return this.read(transformOptions).then(({map}) => map); - } - - getName() { - return this._cache.get( - this.path, - 'name', - () => this._readDocBlock().then(({id}) => { - if (id) { - return id; - } - - const p = this.getPackage(); - - if (!p) { - // Name is full path - return this.path; - } - - return p.getName() - .then(name => { - if (!name) { - return this.path; - } - - return path.join(name, path.relative(p.root, this.path)).replace(/\\/g, '/'); - }); - }) - ); - } - - getPackage() { - return this._moduleCache.getPackageForModule(this); - } - - getDependencies(transformOptions) { - return this.read(transformOptions).then(data => data.dependencies); - } - - invalidate() { - this._cache.invalidate(this.path); - } - - _parseDocBlock(docBlock) { - // Extract an id for the module if it's using @providesModule syntax - // and if it's NOT in node_modules (and not a whitelisted node_module). - // This handles the case where a project may have a dep that has @providesModule - // docblock comments, but doesn't want it to conflict with whitelisted @providesModule - // modules, such as react-haste, fbjs-haste, or react-native or with non-dependency, - // project-specific code that is using @providesModule. - const moduleDocBlock = docblock.parseAsObject(docBlock); - const provides = moduleDocBlock.providesModule || moduleDocBlock.provides; - - const id = provides && !this._depGraphHelpers.isNodeModulesDir(this.path) - ? /^\S+/.exec(provides)[0] - : undefined; - return {id, moduleDocBlock}; - } - - _readDocBlock(contentPromise) { - if (!this._docBlock) { - if (!contentPromise) { - contentPromise = this._fastfs.readWhile(this.path, whileInDocBlock); - } - this._docBlock = contentPromise - .then(docBlock => this._parseDocBlock(docBlock)); - } - return this._docBlock; - } - - read(transformOptions) { - return this._cache.get( - this.path, - cacheKey('moduleData', transformOptions), - () => { - const fileContentPromise = this._fastfs.readFile(this.path); - return Promise.all([ - fileContentPromise, - this._readDocBlock(fileContentPromise), - ]).then(([code, {id, moduleDocBlock}]) => { - // Ignore requires in JSON files or generated code. An example of this - // is prebuilt files like the SourceMap library. - if (this.isJSON() || 'extern' in moduleDocBlock) { - return {id, code, dependencies: []}; - } else { - const transformCode = this._transformCode; - const codePromise = transformCode - ? transformCode(this, code, transformOptions) - : Promise.resolve({code}); - - return codePromise.then(({code, dependencies, map}) => { - if (!dependencies) { - dependencies = this._extractor(code).deps.sync; - } - return {id, code, dependencies, map}; - }); - } - }); - } - ); - } - - hash() { - return `Module : ${this.path}`; - } - - isJSON() { - return path.extname(this.path) === '.json'; - } - - isAsset() { - return false; - } - - isPolyfill() { - return false; - } - - isAsset_DEPRECATED() { - return false; - } - - toJSON() { - return { - hash: this.hash(), - isJSON: this.isJSON(), - isAsset: this.isAsset(), - isAsset_DEPRECATED: this.isAsset_DEPRECATED(), - type: this.type, - path: this.path, - }; - } -} - -function whileInDocBlock(chunk, i, result) { - // consume leading whitespace - if (!/\S/.test(result)) { - return true; - } - - // check for start of doc block - if (!/^\s*\/(\*{2}|\*?$)/.test(result)) { - return false; - } - - // check for end of doc block - return !/\*\//.test(result); -} - -// use weak map to speed up hash creation of known objects -const knownHashes = new WeakMap(); -function stableObjectHash(object) { - let digest = knownHashes.get(object); - if (!digest) { - digest = crypto.createHash('md5') - .update(jsonStableStringify(object)) - .digest('base64'); - knownHashes.set(object, digest); - } - - return digest; -} - -function cacheKey(field, transformOptions) { - return transformOptions !== undefined - ? stableObjectHash(transformOptions) + '\0' + field - : field; -} - -module.exports = Module; diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js deleted file mode 100644 index 11c56919..00000000 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ /dev/null @@ -1,104 +0,0 @@ -'use strict'; - -const AssetModule = require('./AssetModule'); -const Package = require('./Package'); -const Module = require('./Module'); -const path = require('fast-path'); - -class ModuleCache { - - constructor({ - fastfs, - cache, - extractRequires, - transformCode, - depGraphHelpers, - }) { - this._moduleCache = Object.create(null); - this._packageCache = Object.create(null); - this._fastfs = fastfs; - this._cache = cache; - this._extractRequires = extractRequires; - this._transformCode = transformCode; - this._depGraphHelpers = depGraphHelpers; - - fastfs.on('change', this._processFileChange.bind(this)); - } - - getModule(filePath) { - if (!this._moduleCache[filePath]) { - this._moduleCache[filePath] = new Module({ - file: filePath, - fastfs: this._fastfs, - moduleCache: this, - cache: this._cache, - extractor: this._extractRequires, - transformCode: this._transformCode, - depGraphHelpers: this._depGraphHelpers, - }); - } - return this._moduleCache[filePath]; - } - - getAllModules() { - return this._moduleCache; - } - - getAssetModule(filePath) { - if (!this._moduleCache[filePath]) { - this._moduleCache[filePath] = new AssetModule({ - file: filePath, - fastfs: this._fastfs, - moduleCache: this, - cache: this._cache, - }); - } - return this._moduleCache[filePath]; - } - - getPackage(filePath) { - if (!this._packageCache[filePath]) { - this._packageCache[filePath] = new Package({ - file: filePath, - fastfs: this._fastfs, - cache: this._cache, - }); - } - return this._packageCache[filePath]; - } - - getPackageForModule(module) { - // TODO(amasad): use ES6 Map. - if (module.__package) { - if (this._packageCache[module.__package]) { - return this._packageCache[module.__package]; - } else { - delete module.__package; - } - } - - const packagePath = this._fastfs.closest(module.path, 'package.json'); - - if (!packagePath) { - return null; - } - - module.__package = packagePath; - return this.getPackage(packagePath); - } - - _processFileChange(type, filePath, root) { - const absPath = path.join(root, filePath); - - if (this._moduleCache[absPath]) { - this._moduleCache[absPath].invalidate(); - delete this._moduleCache[absPath]; - } - if (this._packageCache[absPath]) { - this._packageCache[absPath].invalidate(); - delete this._packageCache[absPath]; - } - } -} - -module.exports = ModuleCache; diff --git a/react-packager/src/DependencyResolver/Package.js b/react-packager/src/DependencyResolver/Package.js deleted file mode 100644 index 0d28b3b0..00000000 --- a/react-packager/src/DependencyResolver/Package.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict'; - -const isAbsolutePath = require('absolute-path'); -const path = require('fast-path'); - -class Package { - - constructor({ file, fastfs, cache }) { - this.path = file; - this.root = path.dirname(this.path); - this._fastfs = fastfs; - this.type = 'Package'; - this._cache = cache; - } - - getMain() { - return this.read().then(json => { - var replacements = getReplacements(json); - if (typeof replacements === 'string') { - return path.join(this.root, replacements); - } - - let main = json.main || 'index'; - - if (replacements && typeof replacements === 'object') { - main = replacements[main] || - replacements[main + '.js'] || - replacements[main + '.json'] || - replacements[main.replace(/(\.js|\.json)$/, '')] || - main; - } - - return path.join(this.root, main); - }); - } - - isHaste() { - return this._cache.get(this.path, 'package-haste', () => - this.read().then(json => !!json.name) - ); - } - - getName() { - return this._cache.get(this.path, 'package-name', () => - this.read().then(json => json.name) - ); - } - - invalidate() { - this._cache.invalidate(this.path); - } - - redirectRequire(name) { - return this.read().then(json => { - var replacements = getReplacements(json); - - if (!replacements || typeof replacements !== 'object') { - return name; - } - - if (name[0] !== '/') { - return replacements[name] || name; - } - - if (!isAbsolutePath(name)) { - throw new Error(`Expected ${name} to be absolute path`); - } - - const relPath = './' + path.relative(this.root, name); - const redirect = replacements[relPath] || - replacements[relPath + '.js'] || - replacements[relPath + '.json']; - if (redirect) { - return path.join( - this.root, - redirect - ); - } - - return name; - }); - } - - read() { - if (!this._reading) { - this._reading = this._fastfs.readFile(this.path) - .then(jsonStr => JSON.parse(jsonStr)); - } - - return this._reading; - } -} - -function getReplacements(pkg) { - return pkg['react-native'] == null - ? pkg.browser - : pkg['react-native']; -} - -module.exports = Package; diff --git a/react-packager/src/DependencyResolver/Polyfill.js b/react-packager/src/DependencyResolver/Polyfill.js deleted file mode 100644 index 97c57adb..00000000 --- a/react-packager/src/DependencyResolver/Polyfill.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -const Promise = require('promise'); -const Module = require('./Module'); - -class Polyfill extends Module { - constructor({ path, id, dependencies }) { - super({ file: 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__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js deleted file mode 100644 index ee2623a3..00000000 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ /dev/null @@ -1,335 +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'; - -jest - .dontMock('absolute-path') - .dontMock('json-stable-stringify') - .dontMock('../fastfs') - .dontMock('../lib/extractRequires') - .dontMock('../lib/replacePatterns') - .dontMock('../DependencyGraph/docblock') - .dontMock('../Module'); - -jest - .mock('fs'); - -const Fastfs = require('../fastfs'); -const Module = require('../Module'); -const ModuleCache = require('../ModuleCache'); -const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers'); -const Promise = require('promise'); -const fs = require('graceful-fs'); - -function mockIndexFile(indexJs) { - fs.__setMockFilesystem({'root': {'index.js': indexJs}}); -} - -describe('Module', () => { - const fileWatcher = { - on: () => this, - isWatchman: () => Promise.resolve(false), - }; - const fileName = '/root/index.js'; - - let cache, fastfs; - - const createCache = () => ({ - get: jest.genMockFn().mockImplementation( - (filepath, field, cb) => cb(filepath) - ), - invalidate: jest.genMockFn(), - end: jest.genMockFn(), - }); - - const createModule = (options) => - new Module({ - cache, - fastfs, - file: fileName, - depGraphHelpers: new DependencyGraphHelpers(), - moduleCache: new ModuleCache({fastfs, cache}), - ...options, - }); - - beforeEach(function(done) { - cache = createCache(); - fastfs = new Fastfs( - 'test', - ['/root'], - fileWatcher, - {crawling: Promise.resolve([fileName]), ignore: []}, - ); - - fastfs.build().then(done); - }); - - describe('Module ID', () => { - const moduleId = 'arbitraryModule'; - const source = - `/** - * @providesModule ${moduleId} - */ - `; - - let module; - beforeEach(() => { - module = createModule(); - }); - - describe('@providesModule annotations', () => { - beforeEach(() => { - mockIndexFile(source); - }); - - pit('extracts the module name from the header', () => - module.getName().then(name => expect(name).toEqual(moduleId)) - ); - - pit('identifies the module as haste module', () => - module.isHaste().then(isHaste => expect(isHaste).toBe(true)) - ); - - pit('does not transform the file in order to access the name', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - return createModule({transformCode}).getName() - .then(() => expect(transformCode).not.toBeCalled()); - }); - - pit('does not transform the file in order to access the haste status', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - return createModule({transformCode}).isHaste() - .then(() => expect(transformCode).not.toBeCalled()); - }); - }); - - describe('@provides annotations', () => { - beforeEach(() => { - mockIndexFile(source.replace(/@providesModule/, '@provides')); - }); - - pit('extracts the module name from the header if it has a @provides annotation', () => - module.getName().then(name => expect(name).toEqual(moduleId)) - ); - - pit('identifies the module as haste module', () => - module.isHaste().then(isHaste => expect(isHaste).toBe(true)) - ); - - pit('does not transform the file in order to access the name', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - return createModule({transformCode}).getName() - .then(() => expect(transformCode).not.toBeCalled()); - }); - - pit('does not transform the file in order to access the haste status', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - return createModule({transformCode}).isHaste() - .then(() => expect(transformCode).not.toBeCalled()); - }); - }); - - describe('no annotation', () => { - beforeEach(() => { - mockIndexFile('arbitrary(code);'); - }); - - pit('uses the file name as module name', () => - module.getName().then(name => expect(name).toEqual(fileName)) - ); - - pit('does not identify the module as haste module', () => - module.isHaste().then(isHaste => expect(isHaste).toBe(false)) - ); - - pit('does not transform the file in order to access the name', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - return createModule({transformCode}).getName() - .then(() => expect(transformCode).not.toBeCalled()); - }); - - pit('does not transform the file in order to access the haste status', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - return createModule({transformCode}).isHaste() - .then(() => expect(transformCode).not.toBeCalled()); - }); - }); - }); - - describe('Code', () => { - const fileContents = 'arbitrary(code)'; - beforeEach(function() { - mockIndexFile(fileContents); - }); - - pit('exposes file contents as `code` property on the data exposed by `read()`', () => - createModule().read().then(({code}) => - expect(code).toBe(fileContents)) - ); - - pit('exposes file contents via the `getCode()` method', () => - createModule().getCode().then(code => - expect(code).toBe(fileContents)) - ); - }); - - describe('Extrators', () => { - - pit('uses custom require extractors if specified', () => { - mockIndexFile(''); - const module = createModule({ - extractor: code => ({deps: {sync: ['foo', 'bar']}}), - }); - - return module.getDependencies().then(actual => - expect(actual).toEqual(['foo', 'bar'])); - }); - }); - - describe('Custom Code Transform', () => { - let transformCode; - const fileContents = 'arbitrary(code);'; - const exampleCode = ` - ${'require'}('a'); - ${'System.import'}('b'); - ${'require'}('c');`; - - beforeEach(function() { - transformCode = jest.genMockFn(); - mockIndexFile(fileContents); - transformCode.mockReturnValue(Promise.resolve({code: ''})); - }); - - pit('passes the module and file contents to the transform function when reading', () => { - const module = createModule({transformCode}); - return module.read() - .then(() => { - expect(transformCode).toBeCalledWith(module, fileContents, undefined); - }); - }); - - pit('passes any additional options to the transform function when reading', () => { - const module = createModule({transformCode}); - const transformOptions = {arbitrary: Object()}; - return module.read(transformOptions) - .then(() => - expect(transformCode.mock.calls[0][2]).toBe(transformOptions) - ); - }); - - pit('uses the code that `transformCode` resolves to to extract dependencies', () => { - transformCode.mockReturnValue(Promise.resolve({code: exampleCode})); - const module = createModule({transformCode}); - - return module.getDependencies().then(dependencies => { - expect(dependencies).toEqual(['a', 'c']); - }); - }); - - pit('uses dependencies that `transformCode` resolves to, instead of extracting them', () => { - const mockedDependencies = ['foo', 'bar']; - transformCode.mockReturnValue(Promise.resolve({ - code: exampleCode, - dependencies: mockedDependencies, - })); - const module = createModule({transformCode}); - - return module.getDependencies().then(dependencies => { - expect(dependencies).toEqual(mockedDependencies); - }); - }); - - pit('exposes the transformed code rather than the raw file contents', () => { - transformCode.mockReturnValue(Promise.resolve({code: exampleCode})); - const module = createModule({transformCode}); - return Promise.all([module.read(), module.getCode()]) - .then(([data, code]) => { - expect(data.code).toBe(exampleCode); - expect(code).toBe(exampleCode); - }); - }); - - pit('exposes a source map returned by the transform', () => { - const map = {version: 3}; - transformCode.mockReturnValue(Promise.resolve({map, code: exampleCode})); - const module = createModule({transformCode}); - return Promise.all([module.read(), module.getMap()]) - .then(([data, sourceMap]) => { - expect(data.map).toBe(map); - expect(sourceMap).toBe(map); - }); - }); - - describe('Caching based on options', () => { - let module; - beforeEach(function() { - module = createModule({transformCode}); - }); - - const callsEqual = ([path1, key1], [path2, key2]) => { - expect(path1).toEqual(path2); - expect(key1).toEqual(key2); - }; - - it('gets dependencies from the cache with the same cache key for the same transform options', () => { - const options = {some: 'options'}; - module.getDependencies(options); // first call - module.getDependencies(options); // second call - - const {calls} = cache.get.mock; - callsEqual(calls[0], calls[1]); - }); - - it('gets dependencies from the cache with the same cache key for the equivalent transform options', () => { - module.getDependencies({a: 'b', c: 'd'}); // first call - module.getDependencies({c: 'd', a: 'b'}); // second call - - const {calls} = cache.get.mock; - callsEqual(calls[0], calls[1]); - }); - - it('gets dependencies from the cache with different cache keys for different transform options', () => { - module.getDependencies({some: 'options'}); - module.getDependencies({other: 'arbitrary options'}); - const {calls} = cache.get.mock; - expect(calls[0][1]).not.toEqual(calls[1][1]); - }); - - it('gets code from the cache with the same cache key for the same transform options', () => { - const options = {some: 'options'}; - module.getCode(options); // first call - module.getCode(options); // second call - - const {calls} = cache.get.mock; - callsEqual(calls[0], calls[1]); - }); - - it('gets code from the cache with the same cache key for the equivalent transform options', () => { - module.getCode({a: 'b', c: 'd'}); // first call - module.getCode({c: 'd', a: 'b'}); // second call - - const {calls} = cache.get.mock; - callsEqual(calls[0], calls[1]); - }); - - it('gets code from the cache with different cache keys for different transform options', () => { - module.getCode({some: 'options'}); - module.getCode({other: 'arbitrary options'}); - const {calls} = cache.get.mock; - expect(calls[0][1]).not.toEqual(calls[1][1]); - }); - }); - }); -}); diff --git a/react-packager/src/DependencyResolver/__tests__/fastfs-data b/react-packager/src/DependencyResolver/__tests__/fastfs-data deleted file mode 100644 index fe2c6388..00000000 --- a/react-packager/src/DependencyResolver/__tests__/fastfs-data +++ /dev/null @@ -1,39 +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. - * An arbitrary module header - * @providesModule - */ - - const some: string = 'arbitrary code'; - const containing: string = 'ūñïčødę'; - -/** - * An arbitrary class that extends some thing - * It exposes a random number, which may be reset at the callers discretion - */ -class Arbitrary extends Something { - constructor() { - this.reset(); - } - - /** - * Returns the random number - * @returns number - */ - get random(): number { - return this._random; - } - - /** - * Re-creates the internal random number - * @returns void - */ - reset(): void { - this._random = Math.random(); - } -} diff --git a/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js b/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js deleted file mode 100644 index cbff314a..00000000 --- a/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js +++ /dev/null @@ -1,87 +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'; - -jest.autoMockOff() - .dontMock('graceful-fs'); - -const Fastfs = require('../fastfs'); - -const {EventEmitter} = require('events'); -const fs = require('fs'); -const path = require('path'); - -const fileName = path.resolve(__dirname, 'fastfs-data'); -const contents = fs.readFileSync(fileName, 'utf-8'); - -describe('fastfs:', function() { - let fastfs; - const crawling = Promise.resolve([fileName]); - const roots = [__dirname]; - const watcher = new EventEmitter(); - - beforeEach(function(done) { - fastfs = new Fastfs('arbitrary', roots, watcher, {crawling}); - fastfs.build().then(done); - }); - - describe('partial reading', () => { - // these are integrated tests that read real files from disk - - pit('reads a file while a predicate returns true', function() { - return fastfs.readWhile(fileName, () => true).then(readContent => - expect(readContent).toEqual(contents) - ); - }); - - pit('invokes the predicate with the new chunk, the invocation index, and the result collected so far', () => { - const predicate = jest.genMockFn().mockReturnValue(true); - return fastfs.readWhile(fileName, predicate).then(() => { - let aggregated = ''; - const {calls} = predicate.mock; - expect(calls).not.toEqual([]); - - calls.forEach((call, i) => { - const [chunk] = call; - aggregated += chunk; - expect(chunk).not.toBe(''); - expect(call).toEqual([chunk, i, aggregated]); - }); - - expect(aggregated).toEqual(contents); - }); - }); - - pit('stops reading when the predicate returns false', () => { - const predicate = jest.genMockFn().mockImpl((_, i) => i !== 0); - return fastfs.readWhile(fileName, predicate).then((readContent) => { - const {calls} = predicate.mock; - expect(calls.length).toBe(1); - expect(readContent).toBe(calls[0][2]); - }); - }); - - pit('after reading the whole file with `readWhile`, `read()` still works', () => { - // this test allows to reuse the results of `readWhile` for `readFile` - return fastfs.readWhile(fileName, () => true).then(() => { - fastfs.readFile(fileName).then(readContent => - expect(readContent).toEqual(contents) - ); - }); - }); - - pit('after reading parts of the file with `readWhile`, `read()` still works', () => { - return fastfs.readWhile(fileName, () => false).then(() => { - fastfs.readFile(fileName).then(readContent => - expect(readContent).toEqual(contents) - ); - }); - }); - }); -}); diff --git a/react-packager/src/DependencyResolver/crawlers/index.js b/react-packager/src/DependencyResolver/crawlers/index.js deleted file mode 100644 index fe755bcb..00000000 --- a/react-packager/src/DependencyResolver/crawlers/index.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -const nodeCrawl = require('./node'); -const watchmanCrawl = require('./watchman'); - -function crawl(roots, options) { - const {fileWatcher} = options; - return fileWatcher.isWatchman().then(isWatchman => { - if (!isWatchman) { - return false; - } - - // Make sure we're dealing with a version of watchman - // that's using `watch-project` - // TODO(amasad): properly expose (and document) used sane internals. - return fileWatcher.getWatchers().then(([watcher]) => !!watcher.watchProjectInfo.root); - }).then(isWatchman => { - if (isWatchman) { - return watchmanCrawl(roots, options); - } - - return nodeCrawl(roots, options); - }); -} - -module.exports = crawl; diff --git a/react-packager/src/DependencyResolver/crawlers/node.js b/react-packager/src/DependencyResolver/crawlers/node.js deleted file mode 100644 index 871bbc1d..00000000 --- a/react-packager/src/DependencyResolver/crawlers/node.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -const Promise = require('promise'); -const debug = require('debug')('ReactNativePackager:DependencyGraph'); -const fs = require('graceful-fs'); -const path = require('fast-path'); - -const readDir = Promise.denodeify(fs.readdir); -const stat = Promise.denodeify(fs.stat); - -function nodeRecReadDir(roots, {ignore, exts}) { - const queue = roots.slice(); - const retFiles = []; - const extPattern = new RegExp( - '\.(' + exts.join('|') + ')$' - ); - - function search() { - const currDir = queue.shift(); - if (!currDir) { - return Promise.resolve(); - } - - return readDir(currDir) - .then(files => files.map(f => path.join(currDir, f))) - .then(files => Promise.all( - files.map(f => stat(f).catch(handleBrokenLink)) - ).then(stats => [ - // Remove broken links. - files.filter((file, i) => !!stats[i]), - stats.filter(Boolean), - ])) - .then(([files, stats]) => { - files.forEach((filePath, i) => { - if (ignore(filePath)) { - return; - } - - if (stats[i].isDirectory()) { - queue.push(filePath); - return; - } - - if (filePath.match(extPattern)) { - retFiles.push(filePath); - } - }); - - return search(); - }); - } - - return search().then(() => retFiles); -} - -function handleBrokenLink(e) { - debug('WARNING: error stating, possibly broken symlink', e.message); - return Promise.resolve(); -} - -module.exports = nodeRecReadDir; diff --git a/react-packager/src/DependencyResolver/crawlers/watchman.js b/react-packager/src/DependencyResolver/crawlers/watchman.js deleted file mode 100644 index 6e3d6a6c..00000000 --- a/react-packager/src/DependencyResolver/crawlers/watchman.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -const Promise = require('promise'); -const path = require('fast-path'); - -function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) { - const files = []; - return Promise.all( - roots.map( - root => fileWatcher.getWatcherForRoot(root) - ) - ).then( - watchers => { - // All watchman roots for all watches we have. - const watchmanRoots = watchers.map( - watcher => watcher.watchProjectInfo.root - ); - - // Actual unique watchers (because we use watch-project we may end up with - // duplicate "real" watches, and that's by design). - // TODO(amasad): push this functionality into the `FileWatcher`. - const uniqueWatchers = watchers.filter( - (watcher, i) => watchmanRoots.indexOf(watcher.watchProjectInfo.root) === i - ); - - return Promise.all( - uniqueWatchers.map(watcher => { - const watchedRoot = watcher.watchProjectInfo.root; - - // Build up an expression to filter the output by the relevant roots. - const dirExpr = ['anyof']; - for (let i = 0; i < roots.length; i++) { - const root = roots[i]; - if (isDescendant(watchedRoot, root)) { - dirExpr.push(['dirname', path.relative(watchedRoot, root)]); - } - } - - const cmd = Promise.denodeify(watcher.client.command.bind(watcher.client)); - return cmd(['query', watchedRoot, { - suffix: exts, - expression: ['allof', ['type', 'f'], 'exists', dirExpr], - fields: ['name'], - }]).then(resp => { - if ('warning' in resp) { - console.warn('watchman warning: ', resp.warning); - } - - resp.files.forEach(filePath => { - filePath = watchedRoot + path.sep + filePath; - if (!ignore(filePath)) { - files.push(filePath); - } - return false; - }); - }); - }) - ); - }).then(() => files); -} - -function isDescendant(root, child) { - return child.startsWith(root); -} - -module.exports = watchmanRecReadDir; diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js deleted file mode 100644 index af201008..00000000 --- a/react-packager/src/DependencyResolver/fastfs.js +++ /dev/null @@ -1,360 +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'; - -const Promise = require('promise'); -const {EventEmitter} = require('events'); - -const fs = require('graceful-fs'); -const path = require('fast-path'); - -// workaround for https://github.com/isaacs/node-graceful-fs/issues/56 -// fs.close is patched, whereas graceful-fs.close is not. -const fsClose = require('fs').close; - -const readFile = Promise.denodeify(fs.readFile); -const stat = Promise.denodeify(fs.stat); - -const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError'; - -class Fastfs extends EventEmitter { - constructor(name, roots, fileWatcher, {ignore, crawling, activity}) { - super(); - this._name = name; - this._fileWatcher = fileWatcher; - this._ignore = ignore; - this._roots = roots.map(root => { - // If the path ends in a separator ("/"), remove it to make string - // operations on paths safer. - if (root.endsWith(path.sep)) { - root = root.substr(0, root.length - 1); - } - return new File(root, true); - }); - this._fastPaths = Object.create(null); - this._crawling = crawling; - this._activity = activity; - } - - build() { - return this._crawling.then(files => { - let fastfsActivity; - const activity = this._activity; - if (activity) { - fastfsActivity = activity.startEvent('Building in-memory fs for ' + this._name); - } - files.forEach(filePath => { - const root = this._getRoot(filePath); - if (root) { - const newFile = new File(filePath, false); - const dirname = filePath.substr(0, filePath.lastIndexOf(path.sep)); - const parent = this._fastPaths[dirname]; - this._fastPaths[filePath] = newFile; - if (parent) { - parent.addChild(newFile, this._fastPaths); - } else { - root.addChild(newFile, this._fastPaths); - } - } - }); - if (activity) { - activity.endEvent(fastfsActivity); - } - this._fileWatcher.on('all', this._processFileChange.bind(this)); - }); - } - - stat(filePath) { - return Promise.resolve().then(() => this._getFile(filePath).stat()); - } - - getAllFiles() { - return Object.keys(this._fastPaths) - .filter(filePath => !this._fastPaths[filePath].isDir); - } - - findFilesByExts(exts, { ignore } = {}) { - return this.getAllFiles() - .filter(filePath => ( - exts.indexOf(path.extname(filePath).substr(1)) !== -1 && - (!ignore || !ignore(filePath)) - )); - } - - matchFilesByPattern(pattern) { - return this.getAllFiles().filter(file => file.match(pattern)); - } - - readFile(filePath) { - const file = this._getFile(filePath); - if (!file) { - throw new Error(`Unable to find file with path: ${filePath}`); - } - return file.read(); - } - - readWhile(filePath, predicate) { - const file = this._getFile(filePath); - if (!file) { - throw new Error(`Unable to find file with path: ${filePath}`); - } - return file.readWhile(predicate); - } - - closest(filePath, name) { - for (let file = this._getFile(filePath).parent; - file; - file = file.parent) { - if (file.children[name]) { - return file.children[name].path; - } - } - return null; - } - - fileExists(filePath) { - let file; - try { - file = this._getFile(filePath); - } catch (e) { - if (e.type === NOT_FOUND_IN_ROOTS) { - return false; - } - throw e; - } - - return file && !file.isDir; - } - - dirExists(filePath) { - let file; - try { - file = this._getFile(filePath); - } catch (e) { - if (e.type === NOT_FOUND_IN_ROOTS) { - return false; - } - throw e; - } - - return file && file.isDir; - } - - matches(dir, pattern) { - const dirFile = this._getFile(dir); - if (!dirFile.isDir) { - throw new Error(`Expected file ${dirFile.path} to be a directory`); - } - - return Object.keys(dirFile.children) - .filter(name => name.match(pattern)) - .map(name => path.join(dirFile.path, name)); - } - - _getRoot(filePath) { - for (let i = 0; i < this._roots.length; i++) { - const possibleRoot = this._roots[i]; - if (isDescendant(possibleRoot.path, filePath)) { - return possibleRoot; - } - } - return null; - } - - _getAndAssertRoot(filePath) { - const root = this._getRoot(filePath); - if (!root) { - const error = new Error(`File ${filePath} not found in any of the roots`); - error.type = NOT_FOUND_IN_ROOTS; - throw error; - } - return root; - } - - _getFile(filePath) { - filePath = path.normalize(filePath); - if (!this._fastPaths[filePath]) { - const file = this._getAndAssertRoot(filePath).getFileFromPath(filePath); - if (file) { - this._fastPaths[filePath] = file; - } - } - - return this._fastPaths[filePath]; - } - - _processFileChange(type, filePath, rootPath, fstat) { - const absPath = path.join(rootPath, filePath); - if (this._ignore(absPath) || (fstat && fstat.isDirectory())) { - return; - } - - // Make sure this event belongs to one of our roots. - const root = this._getRoot(absPath); - if (!root) { - return; - } - - if (type === 'delete' || type === 'change') { - const file = this._getFile(absPath); - if (file) { - file.remove(); - } - } - - delete this._fastPaths[path.normalize(absPath)]; - - if (type !== 'delete') { - const file = new File(absPath, false); - root.addChild(file, this._fastPaths); - } - - this.emit('change', type, filePath, rootPath, fstat); - } -} - -class File { - constructor(filePath, isDir) { - this.path = filePath; - this.isDir = isDir; - this.children = this.isDir ? Object.create(null) : null; - } - - read() { - if (!this._read) { - this._read = readFile(this.path, 'utf8'); - } - return this._read; - } - - readWhile(predicate) { - return readWhile(this.path, predicate).then(({result, completed}) => { - if (completed && !this._read) { - this._read = Promise.resolve(result); - } - return result; - }); - } - - stat() { - if (!this._stat) { - this._stat = stat(this.path); - } - - return this._stat; - } - - addChild(file, fileMap) { - const parts = file.path.substr(this.path.length + 1).split(path.sep); - if (parts.length === 1) { - this.children[parts[0]] = file; - file.parent = this; - } else if (this.children[parts[0]]) { - this.children[parts[0]].addChild(file, fileMap); - } else { - const dir = new File(this.path + path.sep + parts[0], true); - dir.parent = this; - this.children[parts[0]] = dir; - fileMap[dir.path] = dir; - dir.addChild(file, fileMap); - } - } - - getFileFromPath(filePath) { - const parts = path.relative(this.path, filePath).split(path.sep); - - /*eslint consistent-this:0*/ - let file = this; - for (let i = 0; i < parts.length; i++) { - const fileName = parts[i]; - if (!fileName) { - continue; - } - - if (!file || !file.isDir) { - // File not found. - return null; - } - - file = file.children[fileName]; - } - - return file; - } - - ext() { - return path.extname(this.path).substr(1); - } - - remove() { - if (!this.parent) { - throw new Error(`No parent to delete ${this.path} from`); - } - - delete this.parent.children[path.basename(this.path)]; - } -} - -function readWhile(filePath, predicate) { - return new Promise((resolve, reject) => { - fs.open(filePath, 'r', (openError, fd) => { - if (openError) { - reject(openError); - return; - } - - read( - fd, - /*global Buffer: true*/ - new Buffer(512), - makeReadCallback(fd, predicate, (readError, result, completed) => { - if (readError) { - reject(readError); - } else { - resolve({result, completed}); - } - }) - ); - }); - }); -} - -function read(fd, buffer, callback) { - fs.read(fd, buffer, 0, buffer.length, -1, callback); -} - -function close(fd, error, result, complete, callback) { - fsClose(fd, closeError => callback(error || closeError, result, complete)); -} - -function makeReadCallback(fd, predicate, callback) { - let result = ''; - let index = 0; - return function readCallback(error, bytesRead, buffer) { - if (error) { - close(fd, error, undefined, false, callback); - return; - } - - const completed = bytesRead === 0; - const chunk = completed ? '' : buffer.toString('utf8', 0, bytesRead); - result += chunk; - if (completed || !predicate(chunk, index++, result)) { - close(fd, null, result, completed, callback); - } else { - read(fd, buffer, readCallback); - } - }; -} - -function isDescendant(root, child) { - return child.startsWith(root); -} - -module.exports = Fastfs; diff --git a/react-packager/src/DependencyResolver/lib/__tests__/getAssetDataFromName-test.js b/react-packager/src/DependencyResolver/lib/__tests__/getAssetDataFromName-test.js deleted file mode 100644 index ff61c405..00000000 --- a/react-packager/src/DependencyResolver/lib/__tests__/getAssetDataFromName-test.js +++ /dev/null @@ -1,119 +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'; - -jest.dontMock('../getPlatformExtension') - .dontMock('../getAssetDataFromName'); - -var getAssetDataFromName = require('../getAssetDataFromName'); - -describe('getAssetDataFromName', () => { - it('should get data from name', () => { - expect(getAssetDataFromName('a/b/c.png')).toEqual({ - resolution: 1, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: null, - }); - - expect(getAssetDataFromName('a/b/c@1x.png')).toEqual({ - resolution: 1, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: null, - }); - - expect(getAssetDataFromName('a/b/c@2.5x.png')).toEqual({ - resolution: 2.5, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: null, - }); - - expect(getAssetDataFromName('a/b/c.ios.png')).toEqual({ - resolution: 1, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: 'ios', - }); - - expect(getAssetDataFromName('a/b/c@1x.ios.png')).toEqual({ - resolution: 1, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: 'ios', - }); - - expect(getAssetDataFromName('a/b/c@2.5x.ios.png')).toEqual({ - resolution: 2.5, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: 'ios', - }); - }); - - describe('resolution extraction', () => { - it('should extract resolution simple case', () => { - var data = getAssetDataFromName('test@2x.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 2, - type: 'png', - name: 'test', - platform: null, - }); - }); - - it('should default resolution to 1', () => { - var data = getAssetDataFromName('test.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 1, - type: 'png', - name: 'test', - platform: null, - }); - }); - - it('should support float', () => { - var data = getAssetDataFromName('test@1.1x.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 1.1, - type: 'png', - name: 'test', - platform: null, - }); - - data = getAssetDataFromName('test@.1x.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 0.1, - type: 'png', - name: 'test', - platform: null, - }); - - data = getAssetDataFromName('test@0.2x.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 0.2, - type: 'png', - name: 'test', - platform: null, - }); - }); - }); -}); diff --git a/react-packager/src/DependencyResolver/lib/__tests__/getPlatformExtension-test.js b/react-packager/src/DependencyResolver/lib/__tests__/getPlatformExtension-test.js deleted file mode 100644 index f5ccdc3a..00000000 --- a/react-packager/src/DependencyResolver/lib/__tests__/getPlatformExtension-test.js +++ /dev/null @@ -1,25 +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'; - -jest.dontMock('../getPlatformExtension'); - -var getPlatformExtension = require('../getPlatformExtension'); - -describe('getPlatformExtension', function() { - it('should get platform ext', function() { - expect(getPlatformExtension('a.ios.js')).toBe('ios'); - expect(getPlatformExtension('a.android.js')).toBe('android'); - expect(getPlatformExtension('/b/c/a.ios.js')).toBe('ios'); - expect(getPlatformExtension('/b/c.android/a.ios.js')).toBe('ios'); - expect(getPlatformExtension('/b/c/a@1.5x.ios.png')).toBe('ios'); - expect(getPlatformExtension('/b/c/a@1.5x.lol.png')).toBe(null); - expect(getPlatformExtension('/b/c/a.lol.png')).toBe(null); - }); -}); diff --git a/react-packager/src/DependencyResolver/lib/extractRequires.js b/react-packager/src/DependencyResolver/lib/extractRequires.js deleted file mode 100644 index 547ffbbb..00000000 --- a/react-packager/src/DependencyResolver/lib/extractRequires.js +++ /dev/null @@ -1,47 +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'; - -const replacePatterns = require('./replacePatterns'); - -/** - * Extract all required modules from a `code` string. - */ -const blockCommentRe = /\/\*(.|\n)*?\*\//g; -const lineCommentRe = /\/\/.+(\n|$)/g; -function extractRequires(code) { - var deps = { - sync: [], - }; - - code = code - .replace(blockCommentRe, '') - .replace(lineCommentRe, '') - // 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` or `export` syntaxes: - // var dep1 = require('dep1'); - .replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => { - deps.sync.push(dep); - return match; - }) - .replace(replacePatterns.EXPORT_RE, (match, pre, quot, dep, post) => { - deps.sync.push(dep); - return match; - }) - .replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => { - deps.sync.push(dep); - return match; - }); - - return {code, deps}; -} - -module.exports = extractRequires; diff --git a/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js b/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js deleted file mode 100644 index a616d108..00000000 --- a/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js +++ /dev/null @@ -1,55 +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'; - -const path = require('fast-path'); -const getPlatformExtension = require('./getPlatformExtension'); - -function getAssetDataFromName(filename) { - const ext = path.extname(filename); - const platformExt = getPlatformExtension(filename); - - let pattern = '@([\\d\\.]+)x'; - if (platformExt != null) { - pattern += '(\\.' + platformExt + ')?'; - } - pattern += '\\' + ext + '$'; - const re = new RegExp(pattern); - - const match = filename.match(re); - let resolution; - - if (!(match && match[1])) { - resolution = 1; - } else { - resolution = parseFloat(match[1], 10); - if (isNaN(resolution)) { - resolution = 1; - } - } - - let assetName; - if (match) { - assetName = filename.replace(re, ext); - } else if (platformExt != null) { - assetName = filename.replace(new RegExp(`\\.${platformExt}\\${ext}`), ext); - } else { - assetName = filename; - } - - return { - resolution: resolution, - assetName: assetName, - type: ext.slice(1), - name: path.basename(assetName, ext), - platform: platformExt, - }; -} - -module.exports = getAssetDataFromName; diff --git a/react-packager/src/DependencyResolver/lib/getPlatformExtension.js b/react-packager/src/DependencyResolver/lib/getPlatformExtension.js deleted file mode 100644 index 721470d1..00000000 --- a/react-packager/src/DependencyResolver/lib/getPlatformExtension.js +++ /dev/null @@ -1,28 +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'; - -const SUPPORTED_PLATFORM_EXTS = { - android: true, - ios: true, - web: true, -}; - -// Extract platform extension: index.ios.js -> ios -function getPlatformExtension(file) { - const last = file.lastIndexOf('.'); - const secondToLast = file.lastIndexOf('.', last - 1); - if (secondToLast === -1) { - return null; - } - const platform = file.substring(secondToLast + 1, last); - return SUPPORTED_PLATFORM_EXTS[platform] ? platform : null; -} - -module.exports = getPlatformExtension; diff --git a/react-packager/src/DependencyResolver/lib/replacePatterns.js b/react-packager/src/DependencyResolver/lib/replacePatterns.js deleted file mode 100644 index 8c10710e..00000000 --- a/react-packager/src/DependencyResolver/lib/replacePatterns.js +++ /dev/null @@ -1,14 +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'; - -exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; -exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; -exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 7e129768..ad4dc4d4 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -15,16 +15,20 @@ jest jest.mock('fs'); jest.setMock('temp', {path: () => '/arbitrary/path'}); -var Cache = require('../../DependencyResolver/Cache'); var Transformer = require('../'); var fs = require('fs'); +var Cache; var options; describe('Transformer', function() { var workers; beforeEach(function() { + Cache = require('node-haste').Cache; + + Cache.prototype.get = jest.genMockFn().mockImpl((a, b, c) => c()); + workers = jest.genMockFn(); jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() { return workers; diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 2f1f65de..7b3a5616 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -9,29 +9,29 @@ 'use strict'; jest.dontMock('../') - .dontMock('underscore') - .dontMock('../../DependencyResolver/lib/extractRequires') - .dontMock('../../DependencyResolver/lib/replacePatterns'); + .dontMock('underscore'); jest.mock('path'); -var Promise = require('promise'); -var Resolver = require('../'); -var Module = require('../../DependencyResolver/Module'); -var Polyfill = require('../../DependencyResolver/Polyfill'); -var DependencyGraph = require('../../DependencyResolver/DependencyGraph'); +const Promise = require('promise'); +const Resolver = require('../'); +const DependencyGraph = require('node-haste'); -var path = require('path'); -var _ = require('underscore'); +const path = require('path'); +const _ = require('underscore'); + +let Module; +let Polyfill; describe('Resolver', function() { beforeEach(function() { - Polyfill.mockClear(); + Module = require('node-haste').Module; + Polyfill = require('node-haste').Polyfill; + + DependencyGraph.replacePatterns = require.requireActual('node-haste/lib/lib/replacePatterns'); // For the polyfillDeps - path.join.mockImpl(function(a, b) { - return b; - }); + path.join = jest.genMockFn().mockImpl((a, b) => b); DependencyGraph.prototype.load.mockImpl(() => Promise.resolve()); }); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 7e993a5d..5b9e8902 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -11,9 +11,9 @@ const path = require('path'); const Activity = require('../Activity'); -const DependencyGraph = require('../DependencyResolver/DependencyGraph'); -const replacePatterns = require('../DependencyResolver/lib/replacePatterns'); -const Polyfill = require('../DependencyResolver/Polyfill'); +const DependencyGraph = require('node-haste'); +const replacePatterns = require('node-haste').replacePatterns; +const Polyfill = require('node-haste').Polyfill; const declareOpts = require('../lib/declareOpts'); const Promise = require('promise'); diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index e6a74aeb..246397a2 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -21,11 +21,12 @@ jest.setMock('worker-farm', function() { return () => {}; }) const Promise = require('promise'); var Bundler = require('../../Bundler'); -var FileWatcher = require('../../DependencyResolver/FileWatcher'); var Server = require('../'); var Server = require('../../Server'); var AssetServer = require('../../AssetServer'); +var FileWatcher; + describe('processRequest', () => { var server; @@ -58,6 +59,7 @@ describe('processRequest', () => { var triggerFileChange; beforeEach(() => { + FileWatcher = require('node-haste').FileWatcher; Bundler.prototype.bundle = jest.genMockFunction().mockImpl(() => Promise.resolve({ getSource: () => 'this is the source', diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index aa32175a..7db64bf7 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -10,8 +10,8 @@ const Activity = require('../Activity'); const AssetServer = require('../AssetServer'); -const FileWatcher = require('../DependencyResolver/FileWatcher'); -const getPlatformExtension = require('../DependencyResolver/lib/getPlatformExtension'); +const FileWatcher = require('node-haste').FileWatcher; +const getPlatformExtension = require('node-haste').getPlatformExtension; const Bundler = require('../Bundler'); const Promise = require('promise'); diff --git a/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js b/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js index cf8345b3..b5a7b916 100644 --- a/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js +++ b/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js @@ -8,8 +8,7 @@ */ 'use strict'; -jest.setMock('worker-farm', function() { return () => {}; }) - .setMock('uglify-js') +jest.setMock('uglify-js') .mock('child_process') .dontMock('underscore') .dontMock('../'); diff --git a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js b/react-packager/src/SocketInterface/__tests__/SocketServer-test.js index 39c28d96..c269a0e5 100644 --- a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js +++ b/react-packager/src/SocketInterface/__tests__/SocketServer-test.js @@ -8,8 +8,7 @@ */ 'use strict'; -jest.setMock('worker-farm', function() { return () => {}; }) - .setMock('uglify-js') +jest.setMock('uglify-js') .mock('net') .mock('fs') .dontMock('../SocketServer'); diff --git a/react-packager/src/__mocks__/fs.js b/react-packager/src/__mocks__/fs.js index 64b0c9b1..adcbf11a 100644 --- a/react-packager/src/__mocks__/fs.js +++ b/react-packager/src/__mocks__/fs.js @@ -8,181 +8,4 @@ */ 'use strict'; -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); - } catch (e) { - return callback(e); - } - if (node && typeof node === 'object' && node.SYMLINK != null) { - return callback(null, node.SYMLINK); - } - callback(null, filepath); -}); - -fs.readdir.mockImpl(function(filepath, callback) { - callback = asyncCallback(callback); - var node; - try { - node = getToNode(filepath); - if (node && typeof node === 'object' && node.SYMLINK != null) { - node = getToNode(node.SYMLINK); - } - } catch (e) { - return callback(e); - } - - if (!(node && typeof node === 'object' && node.SYMLINK == null)) { - return callback(new Error(filepath + ' is not a directory.')); - } - - callback(null, Object.keys(node)); -}); - -fs.readFile.mockImpl(function(filepath, encoding, callback) { - callback = asyncCallback(callback); - if (arguments.length === 2) { - callback = encoding; - encoding = null; - } - - try { - var node = getToNode(filepath); - // dir check - if (node && typeof node === 'object' && node.SYMLINK == null) { - callback(new Error('Error readFile a dir: ' + filepath)); - } - return callback(null, node); - } catch (e) { - return callback(e); - } -}); - -fs.stat.mockImpl(function(filepath, callback) { - callback = asyncCallback(callback); - var node; - try { - node = getToNode(filepath); - } catch (e) { - callback(e); - return; - } - - var mtime = { - getTime: function() { - return Math.ceil(Math.random() * 10000000); - }, - }; - - if (node.SYMLINK) { - fs.stat(node.SYMLINK, callback); - return; - } - - if (node && typeof node === 'object') { - callback(null, { - isDirectory: function() { - return true; - }, - isSymbolicLink: function() { - return false; - }, - mtime: mtime, - }); - } else { - callback(null, { - isDirectory: function() { - return false; - }, - isSymbolicLink: function() { - return false; - }, - mtime: mtime, - }); - } -}); - -const noop = () => {}; -fs.open.mockImpl(function(path) { - const callback = arguments[arguments.length - 1] || noop; - let data, error, fd; - try { - data = getToNode(path); - } catch (e) { - error = e; - } - - if (error || data == null) { - error = Error(`ENOENT: no such file or directory, open ${path}`); - } - if (data != null) { - /* global Buffer: true */ - fd = { - buffer: new Buffer(data, 'utf8'), - position: 0, - }; - } - - callback(error, fd); -}); - -fs.read.mockImpl((fd, buffer, writeOffset, length, position, callback = noop) => { - let bytesWritten; - try { - if (position == null || position < 0) { - ({position} = fd); - } - bytesWritten = - fd.buffer.copy(buffer, writeOffset, position, position + length); - fd.position = position + bytesWritten; - } catch (e) { - callback(Error('invalid argument')); - return; - } - callback(null, bytesWritten, buffer); -}); - -fs.close.mockImpl((fd, callback = noop) => { - try { - fd.buffer = fs.position = undefined; - } catch (e) { - callback(Error('invalid argument')); - return; - } - callback(null); -}); - -var filesystem; - -fs.__setMockFilesystem = function(object) { - filesystem = object; - return filesystem; -}; - -function getToNode(filepath) { - var parts = filepath.split('/'); - if (parts[0] !== '') { - throw new Error('Make sure all paths are absolute.'); - } - var node = filesystem; - parts.slice(1).forEach(function(part) { - if (node && node.SYMLINK) { - node = getToNode(node.SYMLINK); - } - node = node[part]; - }); - - return node; -} - -module.exports = fs; +module.exports = require.requireActual('node-haste/mocks/graceful-fs'); From aafa9d684db14f703a96a66729b8dc46590cccdb Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 23 Feb 2016 06:09:38 -0800 Subject: [PATCH 598/936] Upgrade node-haste to v2.3.0 Summary: This updates to the latest published node-haste version, and also adapts code and tests to that version. Future upgrades should be easier. Reviewed By: bestander Differential Revision: D2963144 fb-gh-sync-id: 9fd2c84fc49643fb85ee5d9674a5e458d43d44ca shipit-source-id: 9fd2c84fc49643fb85ee5d9674a5e458d43d44ca --- .../AssetServer/__tests__/AssetServer-test.js | 1 + .../src/Bundler/__tests__/Bundler-test.js | 1 + .../__tests__/Transformer-test.js | 3 +- .../src/Resolver/__tests__/Resolver-test.js | 63 ++++++++++--------- react-packager/src/Resolver/index.js | 26 ++++---- .../src/Server/__tests__/Server-test.js | 1 + .../__tests__/SocketServer-test.js | 1 + 7 files changed, 50 insertions(+), 46 deletions(-) diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 0172f39b..15bfc0ce 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -2,6 +2,7 @@ jest .dontMock('node-haste/lib/lib/getPlatformExtension') + .dontMock('node-haste/node_modules/throat') .dontMock('../'); jest diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index 5ed7d658..1c72d0a4 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -10,6 +10,7 @@ jest .setMock('worker-farm', () => () => undefined) + .dontMock('node-haste/node_modules/throat') .dontMock('underscore') .dontMock('../../lib/ModuleTransport') .setMock('uglify-js') diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index ad4dc4d4..cd73549c 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -25,8 +25,7 @@ describe('Transformer', function() { var workers; beforeEach(function() { - Cache = require('node-haste').Cache; - + Cache = jest.genMockFn(); Cache.prototype.get = jest.genMockFn().mockImpl((a, b, c) => c()); workers = jest.genMockFn(); diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 7b3a5616..b12ebd48 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -15,25 +15,37 @@ jest.mock('path'); const Promise = require('promise'); const Resolver = require('../'); -const DependencyGraph = require('node-haste'); const path = require('path'); const _ = require('underscore'); +let DependencyGraph = jest.genMockFn(); +jest.setMock('node-haste', DependencyGraph); let Module; let Polyfill; describe('Resolver', function() { beforeEach(function() { - Module = require('node-haste').Module; - Polyfill = require('node-haste').Polyfill; + DependencyGraph.mockClear(); + Module = jest.genMockFn().mockImpl(function() { + this.getName = jest.genMockFn(); + this.getDependencies = jest.genMockFn(); + this.isPolyfill = jest.genMockFn().mockReturnValue(false); + }); + Polyfill = jest.genMockFn().mockImpl(function() { + var polyfill = new Module(); + polyfill.isPolyfill.mockReturnValue(true); + return polyfill; + }); DependencyGraph.replacePatterns = require.requireActual('node-haste/lib/lib/replacePatterns'); + DependencyGraph.prototype.createPolyfill = jest.genMockFn(); + DependencyGraph.prototype.getDependencies = jest.genMockFn(); // For the polyfillDeps path.join = jest.genMockFn().mockImpl((a, b) => b); - DependencyGraph.prototype.load.mockImpl(() => Promise.resolve()); + DependencyGraph.prototype.load = jest.genMockFn().mockImpl(() => Promise.resolve()); }); class ResolutionResponseMock { @@ -60,9 +72,9 @@ describe('Resolver', function() { function createPolyfill(id, dependencies) { var polyfill = new Polyfill({}); - polyfill.getName.mockImpl(() => Promise.resolve(id)); - polyfill.getDependencies.mockImpl(() => Promise.resolve(dependencies)); - polyfill.isPolyfill.mockReturnValue(true); + polyfill.getName = jest.genMockFn().mockImpl(() => Promise.resolve(id)); + polyfill.getDependencies = + jest.genMockFn().mockImpl(() => Promise.resolve(dependencies)); return polyfill; } @@ -86,30 +98,26 @@ describe('Resolver', function() { .then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies[result.dependencies.length - 1]).toBe(module); - expect(_.pluck(Polyfill.mock.calls, 0)).toEqual([ - { path: 'polyfills/polyfills.js', + expect(_.pluck(DependencyGraph.prototype.createPolyfill.mock.calls, 0)).toEqual([ + { file: 'polyfills/polyfills.js', id: 'polyfills/polyfills.js', - isPolyfill: true, dependencies: [] }, { id: 'polyfills/console.js', - isPolyfill: true, - path: 'polyfills/console.js', + file: 'polyfills/console.js', dependencies: [ 'polyfills/polyfills.js' ], }, { id: 'polyfills/error-guard.js', - isPolyfill: true, - path: 'polyfills/error-guard.js', + file: 'polyfills/error-guard.js', dependencies: [ 'polyfills/polyfills.js', 'polyfills/console.js' ], }, { id: 'polyfills/String.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/String.prototype.es6.js', + file: 'polyfills/String.prototype.es6.js', dependencies: [ 'polyfills/polyfills.js', 'polyfills/console.js', @@ -117,8 +125,7 @@ describe('Resolver', function() { ], }, { id: 'polyfills/Array.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/Array.prototype.es6.js', + file: 'polyfills/Array.prototype.es6.js', dependencies: [ 'polyfills/polyfills.js', 'polyfills/console.js', @@ -127,8 +134,7 @@ describe('Resolver', function() { ], }, { id: 'polyfills/Array.es6.js', - isPolyfill: true, - path: 'polyfills/Array.es6.js', + file: 'polyfills/Array.es6.js', dependencies: [ 'polyfills/polyfills.js', 'polyfills/console.js', @@ -138,8 +144,7 @@ describe('Resolver', function() { ], }, { id: 'polyfills/Object.es7.js', - isPolyfill: true, - path: 'polyfills/Object.es7.js', + file: 'polyfills/Object.es7.js', dependencies: [ 'polyfills/polyfills.js', 'polyfills/console.js', @@ -150,8 +155,7 @@ describe('Resolver', function() { ], }, { id: 'polyfills/babelHelpers.js', - isPolyfill: true, - path: 'polyfills/babelHelpers.js', + file: 'polyfills/babelHelpers.js', dependencies: [ 'polyfills/polyfills.js', 'polyfills/console.js', @@ -181,12 +185,14 @@ describe('Resolver', function() { })); }); + const polyfill = {}; + DependencyGraph.prototype.createPolyfill.mockReturnValueOnce(polyfill); return depResolver.getDependencies('/root/index.js', { dev: true }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(DependencyGraph.mock.instances[0].getDependencies) - .toBeCalledWith('/root/index.js', undefined, true); - expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]); + .toBeCalledWith({entryPath: '/root/index.js', recursive: true}); + expect(result.dependencies[0]).toBe(polyfill); expect(result.dependencies[result.dependencies.length - 1]) .toBe(module); }); @@ -211,10 +217,9 @@ describe('Resolver', function() { return depResolver.getDependencies('/root/index.js', { dev: false }) .then((result) => { expect(result.mainModuleId).toEqual('index'); - expect(Polyfill.mock.calls[result.dependencies.length - 2]).toEqual([ - { path: 'some module', + expect(DependencyGraph.prototype.createPolyfill.mock.calls[result.dependencies.length - 2]).toEqual([ + { file: 'some module', id: 'some module', - isPolyfill: true, dependencies: [ 'polyfills/polyfills.js', 'polyfills/console.js', diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 5b9e8902..76d8d5af 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -13,7 +13,6 @@ const path = require('path'); const Activity = require('../Activity'); const DependencyGraph = require('node-haste'); const replacePatterns = require('node-haste').replacePatterns; -const Polyfill = require('node-haste').Polyfill; const declareOpts = require('../lib/declareOpts'); const Promise = require('promise'); @@ -121,14 +120,13 @@ class Resolver { return this._depGraph.getModuleForPath(entryFile); } - getDependencies(main, options) { - const opts = getDependenciesValidateOpts(options); - - return this._depGraph.getDependencies( - main, - opts.platform, - opts.recursive, - ).then(resolutionResponse => { + getDependencies(entryPath, options) { + const {platform, recursive} = getDependenciesValidateOpts(options); + return this._depGraph.getDependencies({ + entryPath, + platform, + recursive, + }).then(resolutionResponse => { this._getPolyfillDependencies().reverse().forEach( polyfill => resolutionResponse.prependDependency(polyfill) ); @@ -151,11 +149,10 @@ class Resolver { return [ prelude, moduleSystem - ].map(moduleName => new Polyfill({ - path: moduleName, + ].map(moduleName => this._depGraph.createPolyfill({ + file: moduleName, id: moduleName, dependencies: [], - isPolyfill: true, })); } @@ -172,11 +169,10 @@ class Resolver { ].concat(this._polyfillModuleNames); return polyfillModuleNames.map( - (polyfillModuleName, idx) => new Polyfill({ - path: polyfillModuleName, + (polyfillModuleName, idx) => this._depGraph.createPolyfill({ + file: polyfillModuleName, id: polyfillModuleName, dependencies: polyfillModuleNames.slice(0, idx), - isPolyfill: true, }) ); } diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 246397a2..a2248842 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -9,6 +9,7 @@ 'use strict'; jest.setMock('worker-farm', function() { return () => {}; }) + .dontMock('node-haste/node_modules/throat') .dontMock('os') .dontMock('underscore') .dontMock('path') diff --git a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js b/react-packager/src/SocketInterface/__tests__/SocketServer-test.js index c269a0e5..12512eed 100644 --- a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js +++ b/react-packager/src/SocketInterface/__tests__/SocketServer-test.js @@ -11,6 +11,7 @@ jest.setMock('uglify-js') .mock('net') .mock('fs') + .dontMock('node-haste/node_modules/throat') .dontMock('../SocketServer'); var PackagerServer = require('../../Server'); From 02feea9a5ab55123c0cf643e54d6f5549d485273 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 24 Feb 2016 03:11:08 -0800 Subject: [PATCH 599/936] Create offline package if not running in "Debug" config Summary:**Problem**: As seen in https://github.com/facebook/react-native/issues/5820, many devs are confused by the fact that the offline bundle is not generated when running against the simulator, even when running in the "Release" configuration which is supposed to mimic "production" scenarios. This pull request is a small change that fixes https://github.com/facebook/react-native/issues/5820 by updating the `react-native-xcode.sh` shell script to still generate the ofline bundle during Release configuration. It also updates `AppDelegate.m` to better document this behaviour in the comments so as to avoid any surprises. **Test plan**: This is a simple change, the two tests done were 1. In a new React Native project, verify that an offline build is not generated when running against the simulator in "Debug" configuration as per normal. 2. Change to a "Release" configuration via Product > Scheme > Edit Scheme in XCode, then verify that the project runs with the offline build generated. ![screen shot 2016-02 Closes https://github.com/facebook/react-native/pull/6119 Differential Revision: D2970755 Pulled By: javache fb-gh-sync-id: 64f658512869c73aa19286ca1e3dc6e31b5ac617 shipit-source-id: 64f658512869c73aa19286ca1e3dc6e31b5ac617 --- react-native-xcode.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index 66ecea85..9e6ffbe4 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -10,15 +10,15 @@ # This script is supposed to be invoked as part of Xcode build process # and relies on environment variables (including PWD) set by Xcode -# There is no point in creating an offline package for simulator builds -# because the packager is supposed to be running during development anyways -if [[ "$PLATFORM_NAME" = "iphonesimulator" ]]; then - echo "Skipping bundling for Simulator platform" - exit 0; -fi - case "$CONFIGURATION" in Debug) + # Speed up build times by skipping the creation of the offline package for debug + # builds on the simulator since the packager is supposed to be running anyways. + if [[ "$PLATFORM_NAME" = "iphonesimulator" ]]; then + echo "Skipping bundling for Simulator platform" + exit 0; + fi + DEV=true ;; "") From 9de2ed3d488c5aae35313bda628c325e676e4286 Mon Sep 17 00:00:00 2001 From: Adam Miskiewicz Date: Fri, 26 Feb 2016 09:14:18 -0800 Subject: [PATCH 600/936] Don't hardcode 'localhost:8081' as the _hmrURL in the Bundler Summary:martinbigio this fixes your TODO. :rocket: :rocket: :rocket: Closes https://github.com/facebook/react-native/pull/5827 Differential Revision: D2932188 Pulled By: martinbigio fb-gh-sync-id: 8c8caf0782f05b51c90c8d09fdb743ddd3e6f97e shipit-source-id: 8c8caf0782f05b51c90c8d09fdb743ddd3e6f97e --- react-packager/src/Bundler/index.js | 8 ++++---- react-packager/src/Server/index.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 22696cf5..9da681d6 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -168,9 +168,9 @@ class Bundler { }); } - _sourceHMRURL(platform, path) { + _sourceHMRURL(platform, host, port, path) { return this._hmrURL( - 'http://localhost:8081', // TODO: (martinb) avoid hardcoding + `http://${host}:${port}`, platform, 'bundle', path, @@ -211,10 +211,10 @@ class Bundler { ); } - hmrBundle(options) { + hmrBundle(options, host, port) { return this._bundle({ bundle: new HMRBundle({ - sourceURLFn: this._sourceHMRURL.bind(this, options.platform), + sourceURLFn: this._sourceHMRURL.bind(this, options.platform, host, port), sourceMappingURLFn: this._sourceMappingHMRURL.bind( this, options.platform, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 7db64bf7..5949bd0c 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -240,8 +240,8 @@ class Server { return this.buildBundle(options); } - buildBundleForHMR(modules) { - return this._bundler.hmrBundle(modules); + buildBundleForHMR(modules, host, port) { + return this._bundler.hmrBundle(modules, host, port); } getShallowDependencies(entryFile) { From bf0806b8032e4d181874de695ea5807369cd7cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 26 Feb 2016 15:14:47 -0800 Subject: [PATCH 601/936] Allow parents to accept children modules Summary:In order to be able to Hot Load Redux stores and modules that export functions, we need to build infrastructure to bubble up the HMR updates similar to how webpack does: https://webpack.github.io/docs/hot-module-replacement-with-webpack.html. In here we introduce the minimum of this infrastructure we need to make this work. The Packager server needs to send the inverse dependencies to the HMR runtime that runs on the client so that it can bubble up the patches if they cannot be self accepted by the module that was changed. This diff relies on https://github.com/facebook/node-haste/pull/40/files which adds support for getting the inverse dependencies. Reviewed By: davidaurelio Differential Revision: D2950662 fb-gh-sync-id: 26dcd4aa15da76a727026a9d7ee06e7ae4d22eaa shipit-source-id: 26dcd4aa15da76a727026a9d7ee06e7ae4d22eaa --- react-packager/src/Bundler/HMRBundle.js | 12 +++-- .../src/Resolver/polyfills/require.js | 51 +++++++++++++++---- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/react-packager/src/Bundler/HMRBundle.js b/react-packager/src/Bundler/HMRBundle.js index 49a21dfd..72850778 100644 --- a/react-packager/src/Bundler/HMRBundle.js +++ b/react-packager/src/Bundler/HMRBundle.js @@ -26,9 +26,6 @@ class HMRBundle extends BundleBase { module, transformed.code, ).then(({name, code}) => { - // need to be in single line so that lines match on sourcemaps - code = `__accept(${JSON.stringify(name)}, function(global, require, module, exports) { ${code} });`; - const moduleTransport = new ModuleTransport({ code, name, @@ -44,8 +41,13 @@ class HMRBundle extends BundleBase { }); } - getModulesCode() { - return this._modules.map(module => module.code); + getModulesNamesAndCode() { + return this._modules.map(module => { + return { + name: JSON.stringify(module.name), + code: module.code, + }; + }); } getSourceURLs() { diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 764e4dd1..d5b0cd81 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -94,12 +94,12 @@ global.__d = define; global.require = require; if (__DEV__) { // HMR - function accept(id, factory) { + function accept(id, factory, inverseDependencies) { var mod = modules[id]; if (!mod) { define(id, factory); - return; // new modules don't need to be accepted + return true; // new modules don't need to be accepted } if (!mod.module.hot) { @@ -107,22 +107,51 @@ if (__DEV__) { // HMR 'Cannot accept module because Hot Module Replacement ' + 'API was not installed.' ); - return; + return false; } + // replace and initialize factory + if (factory) { + mod.factory = factory; + } + mod.isInitialized = false; + require(id); + if (mod.module.hot.acceptCallback) { - mod.factory = factory; - mod.isInitialized = false; - require(id); - mod.module.hot.acceptCallback(); + return true; } else { - console.warn( - '[HMR] Module `' + id + '` can\'t be hot reloaded because it\'s not a ' + - 'React component. To get the changes reload the JS bundle.' - ); + // need to have inverseDependencies to bubble up accept + if (!inverseDependencies) { + throw new Error('Undefined `inverseDependencies`'); + } + + // accept parent modules recursively up until all siblings are accepted + return acceptAll(inverseDependencies[id], inverseDependencies); } } + function acceptAll(modules, inverseDependencies) { + if (modules.length === 0) { + return true; + } + + var notAccepted = modules.filter(function(module) { + return !accept(module, /*factory*/ undefined, inverseDependencies); + }); + + var parents = []; + for (var i = 0; i < notAccepted.length; i++) { + // if this the module has no parents then the change cannot be hot loaded + if (inverseDependencies[notAccepted[i]].length === 0) { + return false; + } + + parents.pushAll(inverseDependencies[notAccepted[i]]); + } + + return acceptAll(parents, inverseDependencies); + } + global.__accept = accept; } From 5309991ba6836b4af40bb4d46c763a75cee654f0 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 1 Mar 2016 04:40:40 -0800 Subject: [PATCH 602/936] Add new worker for code transform, optimization, and dependency extraction Summary:This adds a new worker implementation that - uses the existing transforms to transform code - optionally inline `__DEV__`, `process.env.NODE_ENV`, and `Platform.OS` - optionally eliminate branches of conditionals with constant conditions - extracts dependencies - optionally minifies This will land as part of a multi-commit stack, not in isolation Reviewed By: martinbigio Differential Revision: D2976677 fb-gh-sync-id: 38e317f90b6948b28ef2e3fe8b66fc0b9c75aa38 shipit-source-id: 38e317f90b6948b28ef2e3fe8b66fc0b9c75aa38 --- .../worker/__tests__/constant-folding-test.js | 112 ++++++++ .../__tests__/extract-dependencies-test.js | 96 +++++++ .../worker/__tests__/inline-test.js | 133 ++++++++++ .../worker/__tests__/minify-test.js | 111 ++++++++ .../worker/__tests__/worker-test.js | 244 ++++++++++++++++++ .../JSTransformer/worker/constant-folding.js | 82 ++++++ .../worker/extract-dependencies.js | 126 +++++++++ .../src/JSTransformer/worker/index.js | 79 ++++++ .../src/JSTransformer/worker/inline.js | 102 ++++++++ .../src/JSTransformer/worker/minify.js | 88 +++++++ 10 files changed, 1173 insertions(+) create mode 100644 react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js create mode 100644 react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js create mode 100644 react-packager/src/JSTransformer/worker/__tests__/inline-test.js create mode 100644 react-packager/src/JSTransformer/worker/__tests__/minify-test.js create mode 100644 react-packager/src/JSTransformer/worker/__tests__/worker-test.js create mode 100644 react-packager/src/JSTransformer/worker/constant-folding.js create mode 100644 react-packager/src/JSTransformer/worker/extract-dependencies.js create mode 100644 react-packager/src/JSTransformer/worker/index.js create mode 100644 react-packager/src/JSTransformer/worker/inline.js create mode 100644 react-packager/src/JSTransformer/worker/minify.js diff --git a/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js b/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js new file mode 100644 index 00000000..a39a1e97 --- /dev/null +++ b/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js @@ -0,0 +1,112 @@ +/** + * 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.autoMockOff(); +const babel = require('babel-core'); +const constantFolding = require('../constant-folding'); + +function parse(code) { + return babel.transform(code, {code: false, babelrc: false, compact: true}); +} + +describe('constant expressions', () => { + it('can optimize conditional expressions with constant conditions', () => { + const code = ` + a( + 'production'=="production", + 'production'!=='development', + false && 1 || 0 || 2, + true || 3, + 'android'==='ios' ? null : {}, + 'android'==='android' ? {a:1} : {a:0}, + 'foo'==='bar' ? b : c, + f() ? g() : h() + );`; + expect(constantFolding('arbitrary.js', parse(code)).code) + .toEqual(`a(true,true,2,true,{},{a:1},c,f()?g():h());`); + }); + + it('can optimize ternary expressions with constant conditions', () => { + const code = + `var a = true ? 1 : 2; + var b = 'android' == 'android' + ? ('production' != 'production' ? 'a' : 'A') + : 'i';`; + expect(constantFolding('arbitrary.js', parse(code)).code) + .toEqual(`var a=1;var b='A';`); + }); + + it('can optimize logical operator expressions with constant conditions', () => { + const code = ` + var a = true || 1; + var b = 'android' == 'android' && + 'production' != 'production' || null || "A";`; + expect(constantFolding('arbitrary.js', parse(code)).code) + .toEqual(`var a=true;var b="A";`); + }); + + it('can optimize logical operators with partly constant operands', () => { + const code = ` + var a = "truthy" || z(); + var b = "truthy" && z(); + var c = null && z(); + var d = null || z(); + var e = !1 && z(); + `; + expect(constantFolding('arbitrary.js', parse(code)).code) + .toEqual(`var a="truthy";var b=z();var c=null;var d=z();var e=false;`); + }); + + it('can remode an if statement with a falsy constant test', () => { + const code = ` + if ('production' === 'development' || false) { + var a = 1; + } + `; + expect(constantFolding('arbitrary.js', parse(code)).code) + .toEqual(``); + }); + + it('can optimize if-else-branches with constant conditions', () => { + const code = ` + if ('production' == 'development') { + var a = 1; + var b = a + 2; + } else if ('development' == 'development') { + var a = 3; + var b = a + 4; + } else { + var a = 'b'; + } + `; + expect(constantFolding('arbitrary.js', parse(code)).code) + .toEqual(`{var a=3;var b=a+4;}`); + }); + + it('can optimize nested if-else constructs', () => { + const code = ` + if ('ios' === "android") { + if (true) { + require('a'); + } else { + require('b'); + } + } else if ('android' === 'android') { + if (true) { + require('c'); + } else { + require('d'); + } + } + `; + expect(constantFolding('arbitrary.js', parse(code)).code) + .toEqual(`{{require('c');}}`); + }); +}); diff --git a/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js b/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js new file mode 100644 index 00000000..a27def1d --- /dev/null +++ b/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js @@ -0,0 +1,96 @@ +/** + * 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.autoMockOff(); + +const extractDependencies = require('../extract-dependencies'); + +describe('Dependency extraction:', () => { + it('can extract calls to require', () => { + const code = `require('foo/bar'); + var React = require("React"); + var A = React.createClass({ + render: function() { + return require ( "Component" ); + } + }); + require + ('more');` + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies) + .toEqual(['foo/bar', 'React', 'Component', 'more']); + expect(dependencyOffsets).toEqual([8, 46, 147, 203]); + }); + + it('does not extract require method calls', () => { + const code = ` + require('a'); + foo.require('b'); + bar. + require ( 'c').require('d')require('e')`; + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual(['a', 'e']); + expect(dependencyOffsets).toEqual([15, 97]); + }); + + it('does not extract require calls from strings', () => { + const code = `require('foo'); + var React = '\\'require("React")'; + var a = ' // require("yadda")'; + var a = ' /* require("yadda") */'; + var A = React.createClass({ + render: function() { + return require ( "Component" ); + } + }); + " \\" require('more')";` + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual(['foo', 'Component']); + expect(dependencyOffsets).toEqual([8, 226]); + }); + + it('does not extract require calls in comments', () => { + const code = `require('foo')//require("not/this") + /* A comment here with a require('call') that should not be extracted */require('bar') + // ending comment without newline require("baz")`; + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual(['foo', 'bar']); + expect(dependencyOffsets).toEqual([8, 122]); + }); + + it('deduplicates dependencies', () => { + const code = `require('foo');require( "foo" ); + require("foo");`; + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual(['foo']); + expect(dependencyOffsets).toEqual([8, 24, 47]); + }); + + it('does not extract calls to function with names that start with "require"', () => { + const code = `arbitraryrequire('foo');`; + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual([]); + expect(dependencyOffsets).toEqual([]); + }); + + it('does not get confused by previous states', () => { + // yes, this was a bug + const code = `require("a");/* a comment */ var a = /[a]/.test('a');` + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual(['a']); + expect(dependencyOffsets).toEqual([8]); + }); +}); diff --git a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js new file mode 100644 index 00000000..53fe1ed8 --- /dev/null +++ b/react-packager/src/JSTransformer/worker/__tests__/inline-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.autoMockOff(); +const inline = require('../inline'); +const {transform, transformFromAst} = require('babel-core'); + +const babelOptions = { + babelrc: false, + compact: true, +}; + +function toString(ast) { + return normalize(transformFromAst(ast, babelOptions).code); +} + +function normalize(code) { + return transform(code, babelOptions).code; +} + +function toAst(code) { + return transform(code, {...babelOptions, code: false}).ast; +} + +describe('inline constants', () => { + it('replaces __DEV__ in the code', () => { + const code = `function a() { + var a = __DEV__ ? 1 : 2; + var b = a.__DEV__; + var c = function __DEV__(__DEV__) {}; + }` + const {ast} = inline('arbitrary.js', {code}, {dev: true}); + expect(toString(ast)).toEqual(normalize(code.replace(/__DEV__/, 'true'))); + }); + + it('replaces Platform.OS in the code if Platform is a global', () => { + const code = `function a() { + var a = Platform.OS; + var b = a.Platform.OS; + }` + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); + }); + + it('replaces Platform.OS in the code if Platform is a top level import', () => { + const code = ` + var Platform = require('Platform'); + function a() { + if (Platform.OS === 'android') a = function() {}; + var b = a.Platform.OS; + }` + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); + }); + + it('replaces require("Platform").OS in the code', () => { + const code = `function a() { + var a = require('Platform').OS; + var b = a.require('Platform').OS; + }` + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/require\('Platform'\)\.OS/, '"android"'))); + }); + + it('replaces React.Platform.OS in the code if React is a global', () => { + const code = `function a() { + var a = React.Platform.OS; + var b = a.React.Platform.OS; + }` + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.OS/, '"ios"'))); + }); + + it('replaces React.Platform.OS in the code if React is a top level import', () => { + const code = ` + var React = require('React'); + function a() { + if (React.Platform.OS === 'android') a = function() {}; + var b = a.React.Platform.OS; + }` + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/React.Platform\.OS/, '"ios"'))); + }); + + it('replaces require("React").Platform.OS in the code', () => { + const code = `function a() { + var a = require('React').Platform.OS; + var b = a.require('React').Platform.OS; + }` + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/require\('React'\)\.Platform\.OS/, '"android"'))); + }); + + it('replaces process.env.NODE_ENV in the code', () => { + const code = `function a() { + if (process.env.NODE_ENV === 'production') { + return require('Prod'); + } + return require('Dev'); + }` + const {ast} = inline('arbitrary.js', {code}, {dev: false}); + expect(toString(ast)).toEqual( + normalize(code.replace(/process\.env\.NODE_ENV/, '"production"'))); + }); + + it('replaces process.env.NODE_ENV in the code', () => { + const code = `function a() { + if (process.env.NODE_ENV === 'production') { + return require('Prod'); + } + return require('Dev'); + }` + const {ast} = inline('arbitrary.js', {code}, {dev: true}); + expect(toString(ast)).toEqual( + normalize(code.replace(/process\.env\.NODE_ENV/, '"development"'))); + }); + + it('accepts an AST as input', function() { + const code = `function ifDev(a,b){return __DEV__?a:b;}`; + const {ast} = inline('arbitrary.hs', {ast: toAst(code)}, {dev: false}); + expect(toString(ast)).toEqual(code.replace(/__DEV__/, 'false')) + }); +}); + diff --git a/react-packager/src/JSTransformer/worker/__tests__/minify-test.js b/react-packager/src/JSTransformer/worker/__tests__/minify-test.js new file mode 100644 index 00000000..e65594e7 --- /dev/null +++ b/react-packager/src/JSTransformer/worker/__tests__/minify-test.js @@ -0,0 +1,111 @@ +/** + * 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.autoMockOff(); + +const uglify = { + minify: jest.genMockFunction().mockImplementation(code => { + return { + code: code.replace(/(^|\W)\s+/g, '$1'), + map: {}, + }; + }), +}; +jest.setMock('uglify-js', uglify); + +const minify = require('../minify'); +const {any} = jasmine; + +describe('Minification:', () => { + const fileName = '/arbitrary/file.js'; + const DEPENDENCY_MARKER = '\u0002\ueffe\ue277\uead5'; + let map; + + beforeEach(() => { + uglify.minify.mockClear(); + map = {version: 3, sources: [fileName], mappings: ''}; + }); + + it('passes the transformed code to `uglify.minify`, wrapped in an immediately invoked function expression', () => { + const code = 'arbitrary(code)'; + minify('', code, {}, [], []); + expect(uglify.minify).toBeCalledWith( + `(function(){${code}}());`, any(Object)); + }); + + it('uses the passed module locals as parameters of the IIFE', () => { + const moduleLocals = ['arbitrary', 'parameters']; + minify('', '', {}, [], moduleLocals); + expect(uglify.minify).toBeCalledWith( + `(function(${moduleLocals}){}());`, any(Object)); + }); + + it('passes the transformed source map to `uglify.minify`', () => { + minify('', '', map, [], []); + const [, options] = uglify.minify.mock.calls[0]; + expect(options.inSourceMap).toEqual(map); + }); + + it('passes the file name as `outSourceMap` to `uglify.minify` (uglify uses it for the `file` field on the source map)', () => { + minify(fileName, '', {}, [], []); + const [, options] = uglify.minify.mock.calls[0]; + expect(options.outSourceMap).toEqual(fileName); + }); + + it('inserts a marker for every dependency offset before minifing', () => { + const code = ` + var React = require('React'); + var Immutable = require('Immutable');`; + const dependencyOffsets = [27, 67]; + const expectedCode = + code.replace(/require\('/g, '$&' + DEPENDENCY_MARKER); + + minify('', code, {}, dependencyOffsets, []); + expect(uglify.minify).toBeCalledWith( + `(function(){${expectedCode}}());`, any(Object)); + }); + + it('returns the code provided by uglify', () => { + const code = 'some(source) + code'; + uglify.minify.mockReturnValue({code: `!function(a,b,c){${code}}()`}); + + const result = minify('', '', {}, [], []); + expect(result.code).toBe(code); + }); + + it('extracts dependency offsets from the code provided by uglify', () => { + const code = ` + var a=r("${DEPENDENCY_MARKER}a-dependency"); + var b=r("\\x02\\ueffe\\ue277\\uead5b-dependency"); + var e=r(a()?'\\u0002\\ueffe\\ue277\\uead5c-dependency' + :'\x02\ueffe\ue277\uead5d-dependency');`; + uglify.minify.mockReturnValue({code: `!function(){${code}}());`}); + + const result = minify('', '', {}, [], []); + expect(result.dependencyOffsets).toEqual([15, 46, 81, 114]); + }); + + it('returns the source map object provided by uglify', () => { + uglify.minify.mockReturnValue({map, code: ''}); + const result = minify('', '', {}, [], []); + expect(result.map).toBe(map); + }); + + it('adds a `moduleLocals` object to the result that reflects the names of the minified module locals', () => { + const moduleLocals = ['arbitrary', 'parameters', 'here']; + uglify.minify.mockReturnValue({code: '(function(a,ll,d){}());'}); + const result = minify('', '', {}, [], moduleLocals); + expect(result.moduleLocals).toEqual({ + arbitrary: 'a', + parameters: 'll', + here: 'd', + }); + }); +}); diff --git a/react-packager/src/JSTransformer/worker/__tests__/worker-test.js b/react-packager/src/JSTransformer/worker/__tests__/worker-test.js new file mode 100644 index 00000000..a6acb668 --- /dev/null +++ b/react-packager/src/JSTransformer/worker/__tests__/worker-test.js @@ -0,0 +1,244 @@ +/** + * 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.autoMockOff(); +jest.mock('../constant-folding'); +jest.mock('../extract-dependencies'); +jest.mock('../inline'); +jest.mock('../minify'); + +const {transformCode} = require('..'); +const {any, objectContaining} = jasmine; + +describe('code transformation worker:', () => { + let extractDependencies, transform; + beforeEach(() => { + extractDependencies = + require('../extract-dependencies').mockReturnValue({}); + transform = jest.genMockFunction(); + }); + + it('calls the transform with file name, source code, and transform options', function() { + const filename = 'arbitrary/file.js'; + const sourceCode = 'arbitrary(code)'; + const transformOptions = {arbitrary: 'options'}; + transformCode(transform, filename, sourceCode, {transform: transformOptions}); + expect(transform).toBeCalledWith( + {filename, sourceCode, options: transformOptions}, any(Function)); + }); + + it('prefixes JSON files with an assignment to module.exports to make the code valid', function() { + const filename = 'arbitrary/file.json'; + const sourceCode = '{"arbitrary":"property"}'; + transformCode(transform, filename, sourceCode, {}); + expect(transform).toBeCalledWith( + {filename, sourceCode: `module.exports=${sourceCode}`}, any(Function)); + }); + + it('calls back with the result of the transform', done => { + const result = { + code: 'some.other(code)', + map: {} + }; + transform.mockImplementation((_, callback) => + callback(null, result)); + + transformCode(transform, 'filename', 'code', {}, (_, data) => { + expect(data).toEqual(objectContaining(result)); + done(); + }); + }); + + it('removes the leading assignment to `module.exports` before passing on the result if the file is a JSON file, even if minified', done => { + const result = { + code: 'p.exports={a:1,b:2}', + }; + transform.mockImplementation((_, callback) => + callback(null, result)); + + transformCode(transform, 'aribtrary/file.json', 'b', {}, (_, data) => { + expect(data.code).toBe('{a:1,b:2}'); + done(); + }); + }); + + it('calls back with any error yielded by the transform', done => { + const error = Error('arbitrary error'); + transform.mockImplementation((_, callback) => callback(error)); + transformCode(transform, 'filename', 'code', {}, e => { + expect(e).toBe(error); + done(); + }); + }); + + it('puts an empty `moduleLocals` object on the result', done => { + transform.mockImplementation( + (_, callback) => callback(null, {code: 'arbitrary'})); + transformCode(transform, 'filename', 'code', {}, (_, data) => { + expect(data.moduleLocals).toEqual({}); + done(); + }); + }); + + it('if a `moduleLocals` array is passed, the `moduleLocals` object is a key mirror of its items', done => { + transform.mockImplementation( + (_, callback) => callback(null, {code: 'arbitrary'})); + const moduleLocals = + ['arbitrary', 'list', 'containing', 'variable', 'names']; + + transformCode(transform, 'filename', 'code', {moduleLocals}, (_, data) => { + expect(data.moduleLocals).toEqual({ + arbitrary: 'arbitrary', + list: 'list', + containing: 'containing', + variable: 'variable', + names: 'names', + }); + done(); + }); + }); + + describe('dependency extraction:', () => { + let code; + + beforeEach(() => { + transform.mockImplementation( + (_, callback) => callback(null, {code})); + }); + + it('passes the transformed code the `extractDependencies`', done => { + code = 'arbitrary(code)'; + + transformCode(transform, 'filename', 'code', {}, (_, data) => { + expect(extractDependencies).toBeCalledWith(code); + done(); + }); + }); + + it('uses `dependencies` and `dependencyOffsets` provided by `extractDependencies` for the result', done => { + const dependencyData = { + dependencies: ['arbitrary', 'list', 'of', 'dependencies'], + dependencyOffsets: [12, 119, 185, 328, 471], + }; + extractDependencies.mockReturnValue(dependencyData); + + transformCode(transform, 'filename', 'code', {}, (_, data) => { + expect(data).toEqual(objectContaining(dependencyData)); + done(); + }); + }); + + it('does not extract requires if files are marked as "extern"', done => { + transformCode(transform, 'filename', 'code', {extern: true}, (_, {dependencies, dependencyOffsets}) => { + expect(extractDependencies).not.toBeCalled(); + expect(dependencies).toEqual([]); + expect(dependencyOffsets).toEqual([]); + done(); + }); + }); + + it('does not extract requires of JSON files', done => { + transformCode(transform, 'arbitrary.json', '{"arbitrary":"json"}', {}, (_, {dependencies, dependencyOffsets}) => { + expect(extractDependencies).not.toBeCalled(); + expect(dependencies).toEqual([]); + expect(dependencyOffsets).toEqual([]); + done(); + }); + }); + }); + + describe('Minifications:', () => { + let constantFolding, extractDependencies, inline, minify, options; + let transformResult, dependencyData, moduleLocals; + const filename = 'arbitrary/file.js'; + const foldedCode = 'arbitrary(folded(code));' + const foldedMap = {version: 3, sources: ['fold.js']} + + beforeEach(() => { + constantFolding = require('../constant-folding') + .mockReturnValue({code: foldedCode, map: foldedMap}); + extractDependencies = require('../extract-dependencies'); + inline = require('../inline'); + minify = require('../minify').mockReturnValue({}); + + moduleLocals = ['module', 'require', 'exports']; + options = {moduleLocals, minify: true}; + dependencyData = { + dependencies: ['a', 'b', 'c'], + dependencyOffsets: [100, 120, 140] + }; + + extractDependencies.mockImplementation( + code => code === foldedCode ? dependencyData : {}); + + transform.mockImplementation( + (_, callback) => callback(null, transformResult)); + }); + + it('passes the transform result to `inline` for constant inlining', done => { + transformResult = {map: {version: 3}, code: 'arbitrary(code)'}; + transformCode(transform, filename, 'code', options, () => { + expect(inline).toBeCalledWith(filename, transformResult, options); + done(); + }); + }); + + it('passes the result obtained from `inline` on to `constant-folding`', done => { + const inlineResult = {map: {version: 3, sources: []}, ast: {}}; + inline.mockReturnValue(inlineResult); + transformCode(transform, filename, 'code', options, () => { + expect(constantFolding).toBeCalledWith(filename, inlineResult); + done(); + }); + }); + + it('Uses the code obtained from `constant-folding` to extract dependencies', done => { + transformCode(transform, filename, 'code', options, () => { + expect(extractDependencies).toBeCalledWith(foldedCode); + done(); + }); + }); + + it('passes the code obtained from `constant-folding` to `minify`', done => { + transformCode(transform, filename, 'code', options, () => { + expect(minify).toBeCalledWith( + filename, + foldedCode, + foldedMap, + dependencyData.dependencyOffsets, + moduleLocals + ); + done(); + }); + }); + + it('uses the dependencies obtained from the optimized result', done => { + transformCode(transform, filename, 'code', options, (_, result) => { + expect(result.dependencies).toEqual(dependencyData.dependencies); + done(); + }); + }); + + it('uses data produced by `minify` for the result', done => { + const minifyResult = { + code: 'minified(code)', + dependencyOffsets: [10, 30, 60], + map: {version: 3, sources: ['minified.js']}, + moduleLocals: {module: 'x', require: 'y', exports: 'z'}, + }; + minify.mockReturnValue(minifyResult); + + transformCode(transform, 'filename', 'code', options, (_, result) => { + expect(result).toEqual(objectContaining(minifyResult)) + done(); + }); + }); + }); +}); diff --git a/react-packager/src/JSTransformer/worker/constant-folding.js b/react-packager/src/JSTransformer/worker/constant-folding.js new file mode 100644 index 00000000..acf14f68 --- /dev/null +++ b/react-packager/src/JSTransformer/worker/constant-folding.js @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2016-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 babel = require('babel-core'); +const t = babel.types; + +const isLiteral = binaryExpression => + t.isLiteral(binaryExpression.left) && t.isLiteral(binaryExpression.right); + +const Conditional = { + exit(path) { + const node = path.node; + const test = node.test; + if (t.isLiteral(test)) { + if (test.value || node.alternate) { + path.replaceWith(test.value ? node.consequent : node.alternate); + } else if (!test.value) { + path.remove(); + } + } + }, +}; + +const plugin = { + visitor: { + BinaryExpression: { + exit(path) { + const node = path.node; + if (t.isLiteral(node.left) && t.isLiteral(node.right)) { + const result = path.evaluate(); + if (result.confident) { + path.replaceWith(t.valueToNode(result.value)); + } + } + }, + }, + ConditionalExpression: Conditional, + IfStatement: Conditional, + LogicalExpression: { + exit(path) { + const node = path.node; + const left = node.left; + if (t.isLiteral(left)) { + const value = t.isNullLiteral(left) ? null : left.value; + if (node.operator === '||') { + path.replaceWith(value ? left : node.right); + } else { + path.replaceWith(value ? node.right : left); + } + } + } + }, + UnaryExpression: { + exit(path) { + const node = path.node; + if (node.operator === '!' && t.isLiteral(node.argument)) { + path.replaceWith(t.valueToNode(!node.argument.value)); + } + } + }, + }, +}; + +function constantFolding(filename, transformResult) { + return babel.transformFromAst(transformResult.ast, transformResult.code, { + filename, + plugins: [plugin], + inputSourceMap: transformResult.map, + babelrc: false, + compact: true, + }) +} + +module.exports = constantFolding; + diff --git a/react-packager/src/JSTransformer/worker/extract-dependencies.js b/react-packager/src/JSTransformer/worker/extract-dependencies.js new file mode 100644 index 00000000..3ca22164 --- /dev/null +++ b/react-packager/src/JSTransformer/worker/extract-dependencies.js @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2016-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 SINGLE_QUOTE = "'".charCodeAt(0); +const DOUBLE_QUOTE = '"'.charCodeAt(0); +const BACKSLASH = '\\'.charCodeAt(0); +const SLASH = '/'.charCodeAt(0); +const NEWLINE = '\n'.charCodeAt(0); +const ASTERISK = '*'.charCodeAt(0); + +// dollar is the only regex special character valid in identifiers +const escapeRegExp = identifier => identifier.replace(/[$]/g, '\\$'); + +function binarySearch(indexes, index) { + var low = 0; + var high = indexes.length - 1; + var i = 0; + + if (indexes[low] === index) { + return low; + } + while (high - low > 1) { + var current = low + ((high - low) >>> 1); // right shift divides by 2 and floors + if (index === indexes[current]) { + return current; + } + if (index > indexes[current]) { + low = current; + } else { + high = current; + } + } + return low; +} + +function indexOfCharCode(string, needle, i) { + for (var charCode; (charCode = string.charCodeAt(i)); i++) { + if (charCode === needle) { + return i; + } + } + return -1; +} + +const reRequire = /(?:^|[^.\s])\s*\brequire\s*\(\s*(['"])(.*?)\1/g; + +/** + * Extracts dependencies (module IDs imported with the `require` function) from + * a string containing code. + * The function is regular expression based for speed reasons. + * + * The code is traversed twice: + * 1. An array of ranges is built, where indexes 0-1, 2-3, 4-5, etc. are code, + * and indexes 1-2, 3-4, 5-6, etc. are string literals and comments. + * 2. require calls are extracted with a regular expression. + * + * The result of the dependency extraction is an de-duplicated array of + * dependencies, and an array of offsets to the string literals with module IDs. + * The index points to the opening quote. + */ +function extractDependencies(code) { + const ranges = [0]; + // are we currently in a quoted string? -> SINGLE_QUOTE or DOUBLE_QUOTE, else undefined + var currentQuote; + // scan the code for string literals and comments. + for (var i = 0, charCode; (charCode = code.charCodeAt(i)); i++) { + if (charCode === BACKSLASH) { + i += 1; + continue; + } + + if (charCode === SLASH && currentQuote === undefined) { + var next = code.charCodeAt(i + 1); + var end = undefined; + if (next === SLASH) { + end = indexOfCharCode(code, NEWLINE, i + 2); + } else if (next === ASTERISK) { + end = code.indexOf('*/', i + 2) + 1; // assume valid JS input here + } + if (end === -1) { + // if the comment does not end, it goes to the end of the file + end += code.length; + } + if (end !== undefined) { + ranges.push(i, end); + i = end; + continue; + } + } + + var isQuoteStart = currentQuote === undefined && + (charCode === SINGLE_QUOTE || charCode === DOUBLE_QUOTE); + if (isQuoteStart || currentQuote === charCode) { + ranges.push(i); + currentQuote = currentQuote === charCode ? undefined : charCode; + } + } + ranges.push(i); + + // extract dependencies + const dependencies = new Set(); + const dependencyOffsets = []; + for (var match; (match = reRequire.exec(code)); ) { + // check whether the match is in a code range, and not inside of a string + // literal or a comment + if (binarySearch(ranges, match.index) % 2 === 0) { + dependencies.add(match[2]); + dependencyOffsets.push( + match[0].length - match[2].length - 2 + match.index); + } + } + + return { + dependencyOffsets, + dependencies: Array.from(dependencies.values()), + }; +} + +module.exports = extractDependencies; diff --git a/react-packager/src/JSTransformer/worker/index.js b/react-packager/src/JSTransformer/worker/index.js new file mode 100644 index 00000000..bc6e9333 --- /dev/null +++ b/react-packager/src/JSTransformer/worker/index.js @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2016-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 constantFolding = require('./constant-folding'); +const extractDependencies = require('./extract-dependencies'); +const inline = require('./inline'); +const minify = require('./minify'); + +function keyMirrorFromArray(array) { + var keyMirror = {}; + array.forEach(key => keyMirror[key] = key); + return keyMirror; +} + +function makeTransformParams(filename, sourceCode, options) { + if (filename.endsWith('.json')) { + sourceCode = 'module.exports=' + sourceCode; + } + return {filename, sourceCode, options}; +} + +function transformCode(transform, filename, sourceCode, options, callback) { + const params = makeTransformParams(filename, sourceCode, options.transform); + const moduleLocals = options.moduleLocals || []; + const isJson = filename.endsWith('.json'); + + transform(params, (error, transformed) => { + if (error) { + callback(error); + return; + } + + var code, map; + if (options.minify) { + const optimized = + constantFolding(filename, inline(filename, transformed, options)); + code = optimized.code; + map = optimized.map; + } else { + code = transformed.code; + map = transformed.map; + } + + if (isJson) { + code = code.replace(/^\w+\.exports=/, ''); + } + + const moduleLocals = options.moduleLocals || []; + const dependencyData = isJson || options.extern + ? {dependencies: [], dependencyOffsets: []} + : extractDependencies(code); + + var result; + if (options.minify) { + result = minify( + filename, code, map, dependencyData.dependencyOffsets, moduleLocals); + result.dependencies = dependencyData.dependencies; + } else { + result = dependencyData; + result.code = code; + result.map = map; + result.moduleLocals = keyMirrorFromArray(moduleLocals); + } + + callback(null, result); + }); +} + +module.exports = function(transform, filename, sourceCode, options, callback) { + transformCode(require(transform), filename, sourceCode, options || {}, callback); +}; +module.exports.transformCode = transformCode; // for easier testing diff --git a/react-packager/src/JSTransformer/worker/inline.js b/react-packager/src/JSTransformer/worker/inline.js new file mode 100644 index 00000000..2a419933 --- /dev/null +++ b/react-packager/src/JSTransformer/worker/inline.js @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2016-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 babel = require('babel-core'); +const t = babel.types; + +const react = {name: 'React'}; +const platform = {name: 'Platform'}; +const os = {name: 'OS'}; +const requirePattern = {name: 'require'}; + +const env = {name: 'env'}; +const nodeEnv = {name: 'NODE_ENV'}; +const processId = {name: 'process'}; + +const dev = {name: '__DEV__'}; + +const isGlobal = (binding) => !binding; + +const isToplevelBinding = (binding) => isGlobal(binding) || !binding.scope.parent; + +const isRequireCall = (node, dependencyId, scope) => + t.isCallExpression(node) && + t.isIdentifier(node.callee, requirePattern) && + t.isStringLiteral(node.arguments[0], t.stringLiteral(dependencyId)); + +const isImport = (node, scope, pattern) => + t.isIdentifier(node, pattern) && + isToplevelBinding(scope.getBinding(pattern.name)) || + isRequireCall(node, pattern.name, scope); + +const isPlatformOS = (node, scope) => + t.isIdentifier(node.property, os) && + isImport(node.object, scope, platform); + +const isReactPlatformOS = (node, scope) => + t.isIdentifier(node.property, os) && + t.isMemberExpression(node.object) && + t.isIdentifier(node.object.property, platform) && + isImport(node.object.object, scope, react); + +const isProcessEnvNodeEnv = (node, scope) => + t.isIdentifier(node.property, nodeEnv) && + t.isMemberExpression(node.object) && + t.isIdentifier(node.object.property, env) && + t.isIdentifier(node.object.object, processId) && + isGlobal(scope.getBinding(processId.name)); + +const isDev = (node, parent, scope) => + t.isIdentifier(node, dev) && + isGlobal(scope.getBinding(dev.name)) && + !(t.isMemberExpression(parent)); + +const inlinePlugin = { + visitor: { + Identifier(path, state) { + if (isDev(path.node, path.parent, path.scope)) { + path.replaceWith(t.booleanLiteral(state.opts.dev)); + } + }, + MemberExpression(path, state) { + const node = path.node; + const scope = path.scope; + + if (isPlatformOS(node, scope) || isReactPlatformOS(node, scope)) { + path.replaceWith(t.stringLiteral(state.opts.platform)); + } + + if(isProcessEnvNodeEnv(node, scope)) { + path.replaceWith( + t.stringLiteral(state.opts.dev ? 'development' : 'production')); + } + }, + }, +}; + +const plugin = () => inlinePlugin; + +function inline(filename, transformResult, options) { + const code = transformResult.code; + const babelOptions = { + filename, + plugins: [[plugin, options]], + inputSourceMap: transformResult.map, + code: false, + babelrc: false, + compact: true, + }; + + return transformResult.ast + ? babel.transformFromAst(transformResult.ast, code, babelOptions) + : babel.transform(code, babelOptions); +} + +module.exports = inline; diff --git a/react-packager/src/JSTransformer/worker/minify.js b/react-packager/src/JSTransformer/worker/minify.js new file mode 100644 index 00000000..d3435918 --- /dev/null +++ b/react-packager/src/JSTransformer/worker/minify.js @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-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 uglify = require('uglify-js'); + +const MAGIC_MARKER = '\u0002\ueffe\ue277\uead5'; +const MAGIC_MARKER_SPLITTER = + /(?:\x02|\\u0002|\\x02)(?:\ueffe|\\ueffe)(?:\ue277|\\ue277)(?:\uead5|\\uead5)/; + +// IIFE = "immediately invoked function expression" +// we wrap modules in functions to allow the minifier to mangle local variables +function wrapCodeInIIFE(code, moduleLocals) { + return `(function(${moduleLocals.join(',')}){${code}}());`; +} + +function extractCodeFromIIFE(code) { + return code.substring(code.indexOf('{') + 1, code.lastIndexOf('}')); +} + +function extractModuleLocalsFromIIFE(code) { + return code.substring(code.indexOf('(', 1) + 1, code.indexOf(')')).split(','); +} + +function splitFirstElementAt(array, offset) { + const first = array.shift(); + array.unshift(first.slice(0, offset + 1), first.slice(offset + 1)); + return array; +} + +function insertMarkers(code, dependencyOffsets) { + return dependencyOffsets + .reduceRight(splitFirstElementAt, [code]) + .join(MAGIC_MARKER); +} + +function extractMarkers(codeWithMarkers) { + const dependencyOffsets = []; + const codeBits = codeWithMarkers.split(MAGIC_MARKER_SPLITTER); + var offset = 0; + for (var i = 0, max = codeBits.length - 1; i < max; i++) { + offset += codeBits[i].length; + dependencyOffsets.push(offset - 1); + } + + return {code: codeBits.join(''), dependencyOffsets}; +} + +function minify(filename, code, map, dependencyOffsets, moduleLocals) { + // before minifying, code is wrapped in an immediately invoked function + // expression, so that top level variables can be shortened safely + code = wrapCodeInIIFE( + // since we don't know where the strings specifying dependencies will be + // located in the minified code, we mark them with a special marker string + // and extract them afterwards. + // That way, post-processing code can use these positions + insertMarkers(code, dependencyOffsets), + moduleLocals + ); + + const minifyResult = uglify.minify(code, { + fromString: true, + inSourceMap: map, + outSourceMap: filename, + output: { + ascii_only: true, + screw_ie8: true, + }, + }); + + const minifiedModuleLocals = extractModuleLocalsFromIIFE(minifyResult.code); + const codeWithMarkers = extractCodeFromIIFE(minifyResult.code); + const result = extractMarkers(codeWithMarkers); + result.map = minifyResult.map; + result.moduleLocals = {}; + moduleLocals.forEach( + (key, i) => result.moduleLocals[key] = minifiedModuleLocals[i]); + + return result; +} + +module.exports = minify; From a8a501b5cf7acf39d0723b436b3d31f54fe26766 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 2 Mar 2016 04:27:13 -0800 Subject: [PATCH 603/936] Remove knowledge of fbjs from the packager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary:Follow-up to https://github.com/facebook/react-native/pull/5084 This… - changes all requires within RN to `require('fbjs/lib/…')` - updates `.flowconfig` - updates `packager/blacklist.js` - adapts tests - removes things from `Libraries/vendor/{core,emitter}` that are also in fbjs - removes knowledge of `fbjs` from the packager Closes https://github.com/facebook/react-native/pull/5084 Reviewed By: bestander Differential Revision: D2926835 fb-gh-sync-id: 2095e22b2f38e032599d1f2601722b3560e8b6e9 shipit-source-id: 2095e22b2f38e032599d1f2601722b3560e8b6e9 --- blacklist.js | 49 ---------------------------- package.json | 2 +- react-packager/src/Resolver/index.js | 1 - 3 files changed, 1 insertion(+), 51 deletions(-) diff --git a/blacklist.js b/blacklist.js index ee369e1a..27ec2899 100644 --- a/blacklist.js +++ b/blacklist.js @@ -17,57 +17,8 @@ var sharedBlacklist = [ 'node_modules/react/lib/React.js', 'node_modules/react/lib/ReactDOM.js', - // For each of these fbjs files (especially the non-forks/stubs), we should - // consider deleting the conflicting copy and just using the fbjs version. - // // fbjs forks: 'node_modules/fbjs/lib/Map.js', - 'node_modules/fbjs/lib/Promise.js', - 'node_modules/fbjs/lib/fetch.js', - // fbjs stubs: - 'node_modules/fbjs/lib/ErrorUtils.js', - 'node_modules/fbjs/lib/URI.js', - // fbjs modules: - 'node_modules/fbjs/lib/Deferred.js', - 'node_modules/fbjs/lib/PromiseMap.js', - 'node_modules/fbjs/lib/UserAgent.js', - 'node_modules/fbjs/lib/areEqual.js', - 'node_modules/fbjs/lib/base62.js', - 'node_modules/fbjs/lib/crc32.js', - 'node_modules/fbjs/lib/everyObject.js', - 'node_modules/fbjs/lib/fetchWithRetries.js', - 'node_modules/fbjs/lib/filterObject.js', - 'node_modules/fbjs/lib/flattenArray.js', - 'node_modules/fbjs/lib/forEachObject.js', - 'node_modules/fbjs/lib/isEmpty.js', - 'node_modules/fbjs/lib/nullthrows.js', - 'node_modules/fbjs/lib/removeFromArray.js', - 'node_modules/fbjs/lib/resolveImmediate.js', - 'node_modules/fbjs/lib/someObject.js', - 'node_modules/fbjs/lib/sprintf.js', - 'node_modules/fbjs/lib/xhrSimpleDataSerializer.js', - - // Those conflicts with the ones in fbjs/. We need to blacklist the - // internal version otherwise they won't work in open source. - 'downstream/core/CSSCore.js', - 'downstream/core/TouchEventUtils.js', - 'downstream/core/camelize.js', - 'downstream/core/createArrayFromMixed.js', - 'downstream/core/createNodesFromMarkup.js', - 'downstream/core/dom/containsNode.js', - 'downstream/core/dom/focusNode.js', - 'downstream/core/dom/getActiveElement.js', - 'downstream/core/dom/getUnboundedScrollPosition.js', - 'downstream/core/dom/isNode.js', - 'downstream/core/dom/isTextNode.js', - 'downstream/core/emptyFunction.js', - 'downstream/core/emptyObject.js', - 'downstream/core/getMarkupWrap.js', - 'downstream/core/hyphenate.js', - 'downstream/core/hyphenateStyleName.js', - 'downstream/core/invariant.js', - 'downstream/core/nativeRequestAnimationFrame.js', - 'downstream/core/toArray.js', /website\/node_modules\/.*/, diff --git a/package.json b/package.json index 117369a7..cf2306f0 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.3", + "version": "0.1.4", "name": "react-native-packager", "description": "Build native apps with React!", "repository": { diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 76d8d5af..d60e0fb2 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -84,7 +84,6 @@ class Resolver { (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, providesModuleNodeModules: [ - 'fbjs', 'react', 'react-native', // Parse requires AsyncStorage. They will From d13b75794f66efb7226da04c62ea8254a65168e2 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 2 Mar 2016 07:31:11 -0800 Subject: [PATCH 604/936] limit number of workers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary:This limits the number of spawned transformation workers depending on the number of available cores (`n`): ``` n < 3 → n n < 8 → floor(3/4 * n) n < 24 → floor(3/8 * n + 3) // factor going from 3/4 to 1/2 n >= 24 → floor(n / 2) ``` Reviewed By: bestander Differential Revision: D2999894 fb-gh-sync-id: 0163240b5f066432f9ba07161c0dfa2ec767ba58 shipit-source-id: 0163240b5f066432f9ba07161c0dfa2ec767ba58 --- react-packager/src/JSTransformer/index.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 97679859..a25cd57d 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -12,6 +12,7 @@ const ModuleTransport = require('../lib/ModuleTransport'); const Promise = require('promise'); const declareOpts = require('../lib/declareOpts'); const fs = require('fs'); +const os = require('os'); const temp = require('temp'); const util = require('util'); const workerFarm = require('worker-farm'); @@ -60,6 +61,23 @@ const validateOpts = declareOpts({ }, }); +const maxConcurrentWorkers = ((cores, override) => { + if (override) { + return Math.min(cores, override); + } + + if (cores < 3) { + return cores; + } + if (cores < 8) { + return Math.floor(cores * 0.75); + } + if (cores < 24) { + return Math.floor(3/8 * cores + 3); // between cores *.75 and cores / 2 + } + return cores / 2; +})(os.cpus().length, process.env.REACT_NATIVE_MAX_WORKERS); + class Transformer { constructor(options) { const opts = this._opts = validateOpts(options); @@ -87,6 +105,7 @@ class Transformer { this._workers = workerFarm({ autoStart: true, maxConcurrentCallsPerWorker: 1, + maxConcurrentWorkers: maxConcurrentWorkers, maxCallsPerWorker: MAX_CALLS_PER_WORKER, maxCallTime: opts.transformTimeoutInterval, maxRetries: MAX_RETRIES, From cbbe2a707e1b0ca127d256e196c773e03d0bae55 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 2 Mar 2016 08:26:11 -0800 Subject: [PATCH 605/936] Fix breakages caused by switch to fbjs Summary: This fixes a couple of breakages introduced by the switch to fbjs Reviewed By: bestander Differential Revision: D3000078 fb-gh-sync-id: 2971d049030f754d5001f6729716373a64078ddf shipit-source-id: 2971d049030f754d5001f6729716373a64078ddf --- blacklist.js | 1 + 1 file changed, 1 insertion(+) diff --git a/blacklist.js b/blacklist.js index 27ec2899..acca4c16 100644 --- a/blacklist.js +++ b/blacklist.js @@ -19,6 +19,7 @@ var sharedBlacklist = [ // fbjs forks: 'node_modules/fbjs/lib/Map.js', + 'node_modules/fbjs/lib/isEmpty.js', /website\/node_modules\/.*/, From 41aeaec16f4f99ee09c26a5912e0081bede7f0e7 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 2 Mar 2016 17:13:47 -0800 Subject: [PATCH 606/936] Use `invariant` from fbjs everywhere Reviewed By: yungsters Differential Revision: D3002422 fb-gh-sync-id: c7d308e6cfc717726dbbc43a2462fdabb052b2fe shipit-source-id: c7d308e6cfc717726dbbc43a2462fdabb052b2fe --- blacklist.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blacklist.js b/blacklist.js index acca4c16..b6476dc4 100644 --- a/blacklist.js +++ b/blacklist.js @@ -21,6 +21,8 @@ var sharedBlacklist = [ 'node_modules/fbjs/lib/Map.js', 'node_modules/fbjs/lib/isEmpty.js', + 'downstream/core/invariant.js', + /website\/node_modules\/.*/, // TODO(jkassens, #9876132): Remove this rule when it's no longer needed. From 0bdf452803cc3fbabc165cf1beb4901b679c7514 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Fri, 4 Mar 2016 17:04:27 -0800 Subject: [PATCH 607/936] Un-blacklist fbjs Summary: Blacklisting is no longer necessary, as fbjs is not used for haste module resolution any longer Reviewed By: yungsters Differential Revision: D3014257 fb-gh-sync-id: 39a0397e6b07bdff3dba9d48d58f4254c43eb6b6 shipit-source-id: 39a0397e6b07bdff3dba9d48d58f4254c43eb6b6 --- blacklist.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/blacklist.js b/blacklist.js index b6476dc4..823ab156 100644 --- a/blacklist.js +++ b/blacklist.js @@ -17,10 +17,6 @@ var sharedBlacklist = [ 'node_modules/react/lib/React.js', 'node_modules/react/lib/ReactDOM.js', - // fbjs forks: - 'node_modules/fbjs/lib/Map.js', - 'node_modules/fbjs/lib/isEmpty.js', - 'downstream/core/invariant.js', /website\/node_modules\/.*/, From 3e1708bcc37fcf1f7b98c473f163d537072299a5 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 8 Mar 2016 09:50:14 -0800 Subject: [PATCH 608/936] transform before extracting dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary:Make packager transform files before extracting their dependencies. This allows us to extract dependencies added by transforms (and to avoid including them manually). It also allows for better optimization and to get rid of the “whole program optimization” step: This diff utilizes the new worker introduced in D2976677 / d94a567 – that means that minified builds inline the following variables: - `__DEV__` → `false` - `process.env.NODE_ENV` → `'production'` - `Platform.OS` / `React.Platform.OS` → `'android'` / `'ios'` and eliminates branches of conditionals with constant conditions. Dependency extraction happens only after that step, which means that production bundles don’t include any modules that are not used. Fixes #4185 Reviewed By: martinbigio Differential Revision: D2977169 fb-gh-sync-id: e6ce8dd29d1b49aec49b309201141f5b2709da1d shipit-source-id: e6ce8dd29d1b49aec49b309201141f5b2709da1d --- package.json | 2 +- react-packager/src/Bundler/Bundle.js | 143 +-- react-packager/src/Bundler/HMRBundle.js | 21 +- .../src/Bundler/__tests__/Bundle-test.js | 47 +- .../src/Bundler/__tests__/Bundler-test.js | 53 +- react-packager/src/Bundler/index.js | 262 +++-- .../__tests__/Transformer-test.js | 79 +- .../JSTransformer/__tests__/worker-test.js | 109 --- react-packager/src/JSTransformer/index.js | 167 +--- react-packager/src/JSTransformer/worker.js | 77 -- .../worker/__tests__/constant-folding-test.js | 24 +- .../__tests__/extract-dependencies-test.js | 26 +- .../worker/__tests__/minify-test.js | 90 +- .../worker/__tests__/worker-test.js | 65 +- .../JSTransformer/worker/constant-folding.js | 5 +- .../worker/extract-dependencies.js | 111 +-- .../src/JSTransformer/worker/index.js | 44 +- .../src/JSTransformer/worker/inline.js | 2 + .../src/JSTransformer/worker/minify.js | 69 +- .../src/Resolver/__tests__/Resolver-test.js | 911 +++--------------- react-packager/src/Resolver/index.js | 91 +- react-packager/src/Server/index.js | 15 +- react-packager/src/lib/ModuleTransport.js | 6 +- .../__tests__/dead-module-elimination-test.js | 115 --- .../dead-module-elimination.js | 148 --- .../whole-program-optimisations/index.js | 14 - transformer.js | 10 +- 27 files changed, 616 insertions(+), 2090 deletions(-) delete mode 100644 react-packager/src/JSTransformer/__tests__/worker-test.js delete mode 100644 react-packager/src/JSTransformer/worker.js delete mode 100644 react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js delete mode 100644 react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js delete mode 100644 react-packager/src/transforms/whole-program-optimisations/index.js diff --git a/package.json b/package.json index cf2306f0..ab25c5cf 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.4", + "version": "0.2.0", "name": "react-native-packager", "description": "Build native apps with React!", "repository": { diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 9700af08..f4247027 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -11,53 +11,42 @@ const _ = require('underscore'); const base64VLQ = require('./base64-vlq'); const BundleBase = require('./BundleBase'); -const UglifyJS = require('uglify-js'); const ModuleTransport = require('../lib/ModuleTransport'); -const Activity = require('../Activity'); const crypto = require('crypto'); const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL='; -const minifyCode = code => - UglifyJS.minify(code, {fromString: true, ascii_only: true}).code; const getCode = x => x.code; -const getMinifiedCode = x => minifyCode(x.code); const getNameAndCode = ({name, code}) => ({name, code}); -const getNameAndMinifiedCode = - ({name, code}) => ({name, code: minifyCode(code)}); class Bundle extends BundleBase { - constructor(sourceMapUrl) { + constructor({sourceMapUrl, minify} = {}) { super(); this._sourceMap = false; this._sourceMapUrl = sourceMapUrl; this._shouldCombineSourceMaps = false; this._numPrependedModules = 0; this._numRequireCalls = 0; + this._minify = minify; } - addModule(resolver, response, module, transformed) { - return resolver.wrapModule( - response, + addModule(resolver, resolutionResponse, module, moduleTransport) { + return resolver.wrapModule({ + resolutionResponse, module, - transformed.code - ).then(({code, name}) => { - const moduleTransport = new ModuleTransport({ - code, - name, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }); - + name: moduleTransport.name, + code: moduleTransport.code, + map: moduleTransport.map, + meta: moduleTransport.meta, + minify: this._minify, + }).then(({code, map}) => { // 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 && moduleTransport.map != null) { + if (!this._shouldCombineSourceMaps && map != null) { this._shouldCombineSourceMaps = true; } - super.addModule(moduleTransport); + super.addModule(new ModuleTransport({...moduleTransport, code, map})); }); } @@ -103,10 +92,6 @@ class Bundle extends BundleBase { options = options || {}; - if (options.minify) { - return this.getMinifiedSourceAndMap(options.dev).code; - } - let source = super.getSource(); if (options.inlineSourceMap) { @@ -118,84 +103,22 @@ class Bundle extends BundleBase { return source; } - getUnbundle({minify}) { - const allModules = super.getModules().slice(); + getUnbundle() { + const allModules = this.getModules().slice(); const prependedModules = this._numPrependedModules; const requireCalls = this._numRequireCalls; const modules = allModules .splice(prependedModules, allModules.length - requireCalls - prependedModules); - const startupCode = - allModules - .map(minify ? getMinifiedCode : getCode) - .join('\n'); + const startupCode = allModules.map(getCode).join('\n'); return { startupCode, - modules: - modules.map(minify ? getNameAndMinifiedCode : getNameAndCode) + modules: modules.map(getNameAndCode) }; } - getMinifiedSourceAndMap(dev) { - super.assertFinalized(); - - if (this._minifiedSourceAndMap) { - return this._minifiedSourceAndMap; - } - - let source = this.getSource(); - let map = this.getSourceMap(); - - if (!dev) { - const wpoActivity = Activity.startEvent('Whole Program Optimisations'); - const wpoResult = require('babel-core').transform(source, { - retainLines: true, - compact: true, - plugins: require('../transforms/whole-program-optimisations'), - inputSourceMap: map, - }); - Activity.endEvent(wpoActivity); - - source = wpoResult.code; - map = wpoResult.map; - } - - try { - const minifyActivity = Activity.startEvent('minify'); - this._minifiedSourceAndMap = UglifyJS.minify(source, { - fromString: true, - outSourceMap: this._sourceMapUrl, - inSourceMap: map, - output: {ascii_only: true}, - }); - Activity.endEvent(minifyActivity); - return this._minifiedSourceAndMap; - } 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 @@ -211,14 +134,17 @@ class Bundle extends BundleBase { }; let line = 0; - super.getModules().forEach(function(module) { + this.getModules().forEach(module => { let map = module.map; + if (module.virtual) { map = generateSourceMapForVirtualModule(module); } if (options.excludeSource) { - map = _.extend({}, map, {sourcesContent: []}); + if (map.sourcesContent && map.sourcesContent.length) { + map = _.extend({}, map, {sourcesContent: []}); + } } result.sections.push({ @@ -234,25 +160,20 @@ class Bundle extends BundleBase { getSourceMap(options) { super.assertFinalized(); - options = options || {}; - - if (options.minify) { - return this.getMinifiedSourceAndMap(options.dev).map; - } - if (this._shouldCombineSourceMaps) { return this._getCombinedSourceMaps(options); } const mappings = this._getMappings(); + const modules = this.getModules(); const map = { file: this._getSourceMapFile(), - sources: _.pluck(super.getModules(), 'sourcePath'), + sources: modules.map(module => module.sourcePath), version: 3, names: [], mappings: mappings, sourcesContent: options.excludeSource - ? [] : _.pluck(super.getModules(), 'sourceCode') + ? [] : modules.map(module => module.sourceCode) }; return map; } @@ -316,12 +237,10 @@ class Bundle extends BundleBase { } getJSModulePaths() { - return super.getModules().filter(function(module) { + return this.getModules() // Filter out non-js files. Like images etc. - return !module.virtual; - }).map(function(module) { - return module.sourcePath; - }); + .filter(module => !module.virtual) + .map(module => module.sourcePath); } getDebugInfo() { @@ -338,7 +257,7 @@ class Bundle extends BundleBase { '}', '', '

Module paths and transformed code:

', - super.getModules().map(function(m) { + this.getModules().map(function(m) { return '

Path:

' + m.sourcePath + '

Source:

' + '
'; @@ -354,15 +273,17 @@ class Bundle extends BundleBase { sourceMapUrl: this._sourceMapUrl, numPrependedModules: this._numPrependedModules, numRequireCalls: this._numRequireCalls, + shouldCombineSourceMaps: this._shouldCombineSourceMaps, }; } static fromJSON(json) { - const bundle = new Bundle(json.sourceMapUrl); + const bundle = new Bundle({sourceMapUrl: json.sourceMapUrl}); bundle._sourceMapUrl = json.sourceMapUrl; bundle._numPrependedModules = json.numPrependedModules; bundle._numRequireCalls = json.numRequireCalls; + bundle._shouldCombineSourceMaps = json.shouldCombineSourceMaps; BundleBase.fromJSON(bundle, json); diff --git a/react-packager/src/Bundler/HMRBundle.js b/react-packager/src/Bundler/HMRBundle.js index 72850778..d9725eea 100644 --- a/react-packager/src/Bundler/HMRBundle.js +++ b/react-packager/src/Bundler/HMRBundle.js @@ -21,21 +21,14 @@ class HMRBundle extends BundleBase { this._sourceMappingURLs = []; } - addModule(resolver, response, module, transformed) { - return resolver.resolveRequires(response, + addModule(resolver, response, module, moduleTransport) { + return resolver.resolveRequires( + response, module, - transformed.code, - ).then(({name, code}) => { - const moduleTransport = new ModuleTransport({ - code, - name, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }); - - super.addModule(moduleTransport); + moduleTransport.code, + moduleTransport.meta.dependencyOffsets, + ).then(code => { + super.addModule(new ModuleTransport({...moduleTransport, code})); this._sourceMappingURLs.push(this._sourceMappingURLFn(moduleTransport.sourcePath)); this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath)); }); diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 23fe5762..87a5a432 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -11,17 +11,15 @@ jest.autoMockOff(); const Bundle = require('../Bundle'); -const ModuleTransport = require('../../lib/ModuleTransport'); const Promise = require('Promise'); const SourceMapGenerator = require('source-map').SourceMapGenerator; -const UglifyJS = require('uglify-js'); const crypto = require('crypto'); describe('Bundle', () => { var bundle; beforeEach(() => { - bundle = new Bundle('test_url'); + bundle = new Bundle({sourceMapUrl: 'test_url'}); bundle.getSourceMap = jest.genMockFn().mockImpl(() => { return 'test-source-map'; }); @@ -108,34 +106,11 @@ describe('Bundle', () => { ].join('\n')); }); }); - - pit('should get minified source', () => { - const minified = { - code: 'minified', - map: 'map', - }; - - UglifyJS.minify = function() { - return minified; - }; - - return Promise.resolve().then(() => { - return addModule({ - bundle, - code: 'transformed foo;', - sourceCode: 'source foo', - sourcePath: 'foo path', - }); - }).then(() => { - bundle.finalize(); - expect(bundle.getMinifiedSourceAndMap({dev: true})).toBe(minified); - }); - }); }); describe('sourcemap bundle', () => { pit('should create sourcemap', () => { - const otherBundle = new Bundle('test_url'); + const otherBundle = new Bundle({sourceMapUrl: 'test_url'}); return Promise.resolve().then(() => { return addModule({ @@ -179,7 +154,7 @@ describe('Bundle', () => { }); pit('should combine sourcemaps', () => { - const otherBundle = new Bundle('test_url'); + const otherBundle = new Bundle({sourceMapUrl: 'test_url'}); return Promise.resolve().then(() => { return addModule({ @@ -269,7 +244,7 @@ describe('Bundle', () => { describe('getAssets()', () => { it('should save and return asset objects', () => { - var p = new Bundle('test_url'); + var p = new Bundle({sourceMapUrl: 'test_url'}); var asset1 = {}; var asset2 = {}; p.addAsset(asset1); @@ -281,7 +256,7 @@ describe('Bundle', () => { describe('getJSModulePaths()', () => { pit('should return module paths', () => { - var otherBundle = new Bundle('test_url'); + var otherBundle = new Bundle({sourceMapUrl: 'test_url'}); return Promise.resolve().then(() => { return addModule({ bundle: otherBundle, @@ -305,7 +280,7 @@ describe('Bundle', () => { describe('getEtag()', function() { it('should return an etag', function() { - var bundle = new Bundle('test_url'); + var bundle = new Bundle({sourceMapUrl: 'test_url'}); bundle.finalize({}); var eTag = crypto.createHash('md5').update(bundle.getSource()).digest('hex'); expect(bundle.getEtag()).toEqual(eTag); @@ -365,19 +340,17 @@ function genSourceMap(modules) { return sourceMapGen.toJSON(); } -function resolverFor(code) { +function resolverFor(code, map) { return { - wrapModule: (response, module, sourceCode) => Promise.resolve( - {name: 'name', code} - ), + wrapModule: () => Promise.resolve({code, map}), }; } function addModule({bundle, code, sourceCode, sourcePath, map, virtual}) { return bundle.addModule( - resolverFor(code), + resolverFor(code, map), null, null, - {sourceCode, sourcePath, map, virtual} + {code, sourceCode, sourcePath, map, virtual} ); } diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index 1c72d0a4..e108177e 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -19,7 +19,6 @@ jest jest.mock('fs'); var Bundler = require('../'); -var JSTransformer = require('../../JSTransformer'); var Resolver = require('../../Resolver'); var sizeOf = require('image-size'); var fs = require('fs'); @@ -43,25 +42,29 @@ describe('Bundler', function() { isJSON() { return isJSON; }, isAsset() { return isAsset; }, isAsset_DEPRECATED() { return isAsset_DEPRECATED; }, + read: () => ({ + code: 'arbitrary', + source: 'arbitrary', + }), }; } var getDependencies; var getModuleSystemDependencies; - var wrapModule; var bundler; var assetServer; var modules; + var projectRoots; beforeEach(function() { getDependencies = jest.genMockFn(); getModuleSystemDependencies = jest.genMockFn(); - wrapModule = jest.genMockFn(); + projectRoots = ['/root']; + Resolver.mockImpl(function() { return { getDependencies: getDependencies, getModuleSystemDependencies: getModuleSystemDependencies, - wrapModule: wrapModule, }; }); @@ -80,7 +83,7 @@ describe('Bundler', function() { }; bundler = new Bundler({ - projectRoots: ['/root'], + projectRoots, assetServer: assetServer, }); @@ -109,34 +112,18 @@ describe('Bundler', function() { }), ]; - getDependencies.mockImpl(function() { - return Promise.resolve({ + getDependencies.mockImpl((main, options, transformOptions) => + Promise.resolve({ mainModuleId: 'foo', - dependencies: modules - }); - }); + dependencies: modules, + transformOptions, + }) + ); getModuleSystemDependencies.mockImpl(function() { return []; }); - JSTransformer.prototype.loadFileAndTransform - .mockImpl(function(path) { - return Promise.resolve({ - code: 'transformed ' + path, - map: 'sourcemap ' + path, - sourceCode: 'source ' + path, - sourcePath: path - }); - }); - - wrapModule.mockImpl(function(response, module, code) { - return module.getName().then(name => ({ - name, - code: 'lol ' + code + ' lol' - })); - }); - sizeOf.mockImpl(function(path, cb) { cb(null, { width: 50, height: 100 }); }); @@ -207,10 +194,20 @@ describe('Bundler', function() { }); pit('gets the list of dependencies from the resolver', function() { - return bundler.getDependencies('/root/foo.js', true).then(() => + const entryFile = '/root/foo.js'; + return bundler.getDependencies({entryFile, recursive: true}).then(() => expect(getDependencies).toBeCalledWith( '/root/foo.js', { dev: true, recursive: true }, + { minify: false, + dev: true, + transform: { + dev: true, + hot: false, + projectRoots, + } + }, + undefined, ) ); }); diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 9da681d6..3c1c426d 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -26,7 +26,6 @@ const imageSize = require('image-size'); const version = require('../../../../package.json').version; const sizeOf = Promise.denodeify(imageSize); -const readFile = Promise.denodeify(fs.readFile); const noop = () => {}; @@ -82,10 +81,6 @@ const validateOpts = declareOpts({ type: 'number', required: false, }, - disableInternalTransforms: { - type: 'boolean', - default: false, - }, }); class Bundler { @@ -123,6 +118,10 @@ class Bundler { cacheKey: cacheKeyParts.join('$'), }); + this._transformer = new Transformer({ + transformModulePath: opts.transformModulePath, + }); + this._resolver = new Resolver({ projectRoots: opts.projectRoots, blacklistRE: opts.blacklistRE, @@ -132,14 +131,10 @@ class Bundler { 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, - disableInternalTransforms: opts.disableInternalTransforms, + transformCode: + (module, code, options) => + this._transformer.transformFile(module.path, code, options), + minifyCode: this._transformer.minify, }); this._projectRoots = opts.projectRoots; @@ -158,11 +153,11 @@ class Bundler { } bundle(options) { - const {dev, unbundle, platform} = options; + const {dev, minify, unbundle} = options; const moduleSystemDeps = - this._resolver.getModuleSystemDependencies({dev, unbundle, platform}); + this._resolver.getModuleSystemDependencies({dev, unbundle}); return this._bundle({ - bundle: new Bundle(options.sourceMapUrl), + bundle: new Bundle({minify, sourceMapUrl: options.sourceMapUrl}), moduleSystemDeps, ...options, }); @@ -230,7 +225,8 @@ class Bundler { entryFile, runModule: runMainModule, runBeforeMainModule, - dev: isDev, + dev, + minify, platform, moduleSystemDeps = [], hot, @@ -264,7 +260,8 @@ class Bundler { return this._buildBundle({ entryFile, - isDev, + dev, + minify, platform, bundle, hot, @@ -279,7 +276,7 @@ class Bundler { runModule: runMainModule, runBeforeMainModule, sourceMapUrl, - dev: isDev, + dev, platform, }) { const onModuleTransformed = ({module, transformed, response, bundle}) => { @@ -303,17 +300,19 @@ class Bundler { return this._buildBundle({ entryFile, - isDev, + dev, platform, onModuleTransformed, finalizeBundle, + minify: false, bundle: new PrepackBundle(sourceMapUrl), }); } _buildBundle({ entryFile, - isDev, + dev, + minify, platform, bundle, hot, @@ -323,54 +322,54 @@ class Bundler { finalizeBundle = noop, }) { const findEventId = Activity.startEvent('find dependencies'); + if (!resolutionResponse) { - resolutionResponse = this.getDependencies(entryFile, isDev, platform); + let onProgess; + if (process.stdout.isTTY) { + const bar = new ProgressBar( + 'transformed :current/:total (:percent)', + {complete: '=', incomplete: ' ', width: 40, total: 1}, + ); + onProgess = (_, total) => { + bar.total = total; + bar.tick(); + }; + } + + resolutionResponse = this.getDependencies( + {entryFile, dev, platform, hot, onProgess, minify}); } return Promise.resolve(resolutionResponse).then(response => { Activity.endEvent(findEventId); onResolutionResponse(response); - const transformEventId = Activity.startEvent('transform'); - const bar = process.stdout.isTTY - ? new ProgressBar('transforming [:bar] :percent :current/:total', { - complete: '=', - incomplete: ' ', - width: 40, - total: response.dependencies.length, - }) - : {tick() {}}; - const transformPromises = - response.dependencies.map(module => - this._transformModule({ - mainModuleName: response.mainModuleId, - bundle, + const toModuleTransport = module => + this._toModuleTransport({ + module, + bundle, + transformOptions: response.transformOptions, + }).then(transformed => { + onModuleTransformed({ module, - platform, - dev: isDev, - hot - }).then(transformed => { - bar.tick(); - onModuleTransformed({module, transformed, response, bundle}); - return {module, transformed}; - }) + response, + bundle, + transformed, + }); + return {module, transformed}; + }); + + return Promise.all(response.dependencies.map(toModuleTransport)) + .then(transformedModules => + Promise + .resolve(finalizeBundle({bundle, transformedModules, response})) + .then(() => bundle) ); - return Promise.all(transformPromises).then(transformedModules => { - Activity.endEvent(transformEventId); - return Promise - .resolve(finalizeBundle({bundle, transformedModules, response})) - .then(() => bundle); - }); }); } invalidateFile(filePath) { - if (this._transformOptionsModule) { - this._transformOptionsModule.onFileChange && - this._transformOptionsModule.onFileChange(); - } - - this._transformer.invalidateFile(filePath); + this._cache.invalidate(filePath); } getShallowDependencies(entryFile) { @@ -385,19 +384,35 @@ class Bundler { return this._resolver.getModuleForPath(entryFile); } - getDependencies(main, isDev, platform, recursive = true) { - return this._resolver.getDependencies( - main, - { - dev: isDev, + getDependencies({ + entryFile, + platform, + dev = true, + minify = !dev, + hot = false, + recursive = true, + onProgess, + }) { + return this.getTransformOptions( + entryFile, {dev, platform, hot, projectRoots: this._projectRoots} + ).then(transformSpecificOptions => { + const transformOptions = { + minify, + dev, platform, - recursive, - }, - ); + transform: transformSpecificOptions, + }; + return this._resolver.getDependencies( + entryFile, + {dev, platform, recursive}, + transformOptions, + onProgess, + ); + }); } getOrderedDependencyPaths({ entryFile, dev, platform }) { - return this.getDependencies(entryFile, dev, platform).then( + return this.getDependencies({entryFile, dev, platform}).then( ({ dependencies }) => { const ret = []; const promises = []; @@ -428,36 +443,32 @@ class Bundler { ); } - _transformModule({ - bundle, - module, - mainModuleName, - platform = null, - dev = true, - hot = false, - }) { + _toModuleTransport({module, bundle, transformOptions}) { + let moduleTransport; if (module.isAsset_DEPRECATED()) { - return this._generateAssetModule_DEPRECATED(bundle, module); + moduleTransport = this._generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { - return this._generateAssetModule(bundle, module, platform); - } else if (module.isJSON()) { - return generateJSONModule(module); - } else { - return this._getTransformOptions( - { - bundleEntry: mainModuleName, - platform: platform, - dev: dev, - modulePath: module.path, - }, - {hot}, - ).then(options => { - return this._transformer.loadFileAndTransform( - path.resolve(module.path), - options, - ); - }); + moduleTransport = this._generateAssetModule( + bundle, module, transformOptions.platform); } + + if (moduleTransport) { + return Promise.resolve(moduleTransport); + } + + return Promise.all([ + module.getName(), + module.read(transformOptions), + ]).then(( + [name, {code, dependencies, dependencyOffsets, map, source}] + ) => new ModuleTransport({ + name, + code, + map, + meta: {dependencies, dependencyOffsets}, + sourceCode: source, + sourcePath: module.path + })); } getGraphDebugInfo() { @@ -480,9 +491,10 @@ class Bundler { bundle.addAsset(img); - const code = 'module.exports = ' + JSON.stringify(img) + ';'; + const code = 'module.exports=' + JSON.stringify(img) + ';'; return new ModuleTransport({ + name: id, code: code, sourceCode: code, sourcePath: module.path, @@ -525,19 +537,31 @@ class Bundler { type: assetData.type, }; - const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);'; - const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(asset)); + const json = JSON.stringify(asset); + const code = + `module.exports = require('AssetRegistry').registerAsset(${json});`; + const dependencies = ['AssetRegistry']; + const dependencyOffsets = [code.indexOf('AssetRegistry') - 1]; - return {asset, code}; + return { + asset, + code, + meta: {dependencies, dependencyOffsets} + }; }); } _generateAssetModule(bundle, module, platform = null) { - return this._generateAssetObjAndCode(module, platform).then(({asset, code}) => { + return Promise.all([ + module.getName(), + this._generateAssetObjAndCode(module, platform), + ]).then(([name, {asset, code, meta}]) => { bundle.addAsset(asset); return new ModuleTransport({ - code: code, + name, + code, + meta, sourceCode: code, sourcePath: module.path, virtual: true, @@ -545,37 +569,15 @@ class Bundler { }); } - _getTransformOptions(config, options) { - const transformerOptions = this._transformOptionsModule - ? this._transformOptionsModule.get(Object.assign( - { - bundler: this, - platform: options.platform, - dev: options.dev, - }, - config, - )) - : Promise.resolve(null); - - return transformerOptions.then(overrides => { - return {...options, ...overrides}; - }); + getTransformOptions(mainModuleName, options) { + const extraOptions = this._transformOptionsModule + ? this._transformOptionsModule(mainModuleName, options, this) + : null; + return Promise.resolve(extraOptions) + .then(extraOptions => Object.assign(options, extraOptions)); } } -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); @@ -594,12 +596,4 @@ function verifyRootExists(root) { 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/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index cd73549c..dc93a872 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -12,61 +12,66 @@ jest .dontMock('../../lib/ModuleTransport') .dontMock('../'); -jest.mock('fs'); -jest.setMock('temp', {path: () => '/arbitrary/path'}); +const fs = {writeFileSync: jest.genMockFn()}; +const temp = {path: () => '/arbitrary/path'}; +const workerFarm = jest.genMockFn(); +jest.setMock('fs', fs); +jest.setMock('temp', temp); +jest.setMock('worker-farm', workerFarm); var Transformer = require('../'); -var fs = require('fs'); -var Cache; -var options; +const {any} = jasmine; describe('Transformer', function() { - var workers; + let options, workers, Cache; + const fileName = '/an/arbitrary/file.js'; + const transformModulePath = __filename; beforeEach(function() { Cache = jest.genMockFn(); Cache.prototype.get = jest.genMockFn().mockImpl((a, b, c) => c()); - workers = jest.genMockFn(); - jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() { - return workers; - })); - - options = { - transformModulePath: '/foo/bar', - cache: new Cache({}), - }; + fs.writeFileSync.mockClear(); + options = {transformModulePath}; + workerFarm.mockClear(); + workerFarm.mockImpl((opts, path, methods) => { + const api = workers = {}; + methods.forEach(method => api[method] = jest.genMockFn()); + return api; + }); }); - pit('should loadFileAndTransform', function() { - workers.mockImpl(function(data, callback) { - callback(null, { code: 'transformed', map: 'sourceMap' }); - }); - fs.readFile.mockImpl(function(file, callback) { - callback(null, 'content'); + it('passes transform module path, file path, source code, and options to the worker farm when transforming', () => { + const transformOptions = {arbitrary: 'options'}; + const code = 'arbitrary(code)'; + new Transformer(options).transformFile(fileName, code, transformOptions); + expect(workers.transformAndExtractDependencies).toBeCalledWith( + transformModulePath, + fileName, + code, + transformOptions, + any(Function), + ); + }); + + pit('passes the data produced by the worker back', () => { + const transformer = new Transformer(options); + const result = { code: 'transformed', map: 'sourceMap' }; + workers.transformAndExtractDependencies.mockImpl(function(transformPath, filename, code, options, callback) { + callback(null, result); }); - return new Transformer(options).loadFileAndTransform('file') - .then(function(data) { - expect(data).toEqual({ - code: 'transformed', - map: 'sourceMap', - sourcePath: 'file', - sourceCode: 'content' - }); - }); + return transformer.transformFile(fileName, '', {}) + .then(data => expect(data).toBe(result)); }); pit('should add file info to parse errors', function() { + const transformer = new Transformer(options); var message = 'message'; var snippet = 'snippet'; - fs.readFile.mockImpl(function(file, callback) { - callback(null, 'var x;\nvar answer = 1 = x;'); - }); - - workers.mockImpl(function(data, callback) { + workers.transformAndExtractDependencies.mockImpl(function(transformPath, filename, code, options, callback) { var babelError = new SyntaxError(message); babelError.type = 'SyntaxError'; babelError.description = message; @@ -78,13 +83,13 @@ describe('Transformer', function() { callback(babelError); }); - return new Transformer(options).loadFileAndTransform('foo-file.js') + return transformer.transformFile(fileName, '', {}) .catch(function(error) { expect(error.type).toEqual('TransformError'); expect(error.message).toBe('SyntaxError ' + message); expect(error.lineNumber).toBe(2); expect(error.column).toBe(15); - expect(error.filename).toBe('foo-file.js'); + expect(error.filename).toBe(fileName); expect(error.description).toBe(message); expect(error.snippet).toBe(snippet); }); diff --git a/react-packager/src/JSTransformer/__tests__/worker-test.js b/react-packager/src/JSTransformer/__tests__/worker-test.js deleted file mode 100644 index f81907c2..00000000 --- a/react-packager/src/JSTransformer/__tests__/worker-test.js +++ /dev/null @@ -1,109 +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'; - -jest.autoMockOff(); - -jest.mock('babel-core'); - -const worker = require('../worker'); -const babel = require('babel-core'); - -const code = 'code'; - -describe('Resolver', function() { - beforeEach(() => { - babel.transform.mockImpl((source, options) => source); - }); - - describe('when no external transform is provided', () => { - xit('should invoke internal transform if available', () => { - transform({ - sourceCode: 'code', - filename: 'test', - options: options({opts: {}, internalTransformsEnabled: true}), - }); - expect(babel.transform.mock.calls.length).toBe(1); - }); - - it('should not invoke internal transform if unavailable', () => { - transform({ - sourceCode: 'code', - filename: 'test', - options: options({opts: {}, internalTransformsEnabled: false}), - }); - expect(babel.transform.mock.calls.length).toBe(0); - }); - }); - - describe('when external transform is provided', () => { - xit('should invoke both transformers if internal is available', () => { - transform({ - sourceCode: code, - filename: 'test', - options: options({ - opts: { - externalTransformModulePath: require.resolve( - '../../../../transformer.js' - ), - }, - internalTransformsEnabled: true, - }), - }); - expect(babel.transform.mock.calls.length).toBe(2); - }); - - it('should invoke only external transformer if internal is not available', () => { - transform({ - sourceCode: 'code', - filename: 'test', - options: options({ - opts: { - externalTransformModulePath: require.resolve( - '../../../../transformer.js' - ), - }, - internalTransformsEnabled: false, - }), - }); - expect(babel.transform.mock.calls.length).toBe(1); - }); - - xit('should pipe errors through transform pipeline', () => { - const error = new Error('transform error'); - babel.transform.mockImpl((source, options) => { - throw error; - }); - - const callback = transform({ - sourceCode: 'code', - filename: 'test', - options: options({ - opts: { - externalTransformModulePath: require.resolve( - '../../../../transformer.js' - ), - }, - internalTransformsEnabled: true, - }), - }); - expect(callback.mock.calls[0][0]).toBe(error); - }); - }); -}); - -function transform(data) { - const callback = jest.genMockFunction(); - worker(data, callback); - return callback; -} - -function options({opts, internalTransformsEnabled}) { - return Object.assign(opts, {hot: internalTransformsEnabled}); -} diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index a25cd57d..3ddd95bd 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -8,18 +8,13 @@ */ 'use strict'; -const ModuleTransport = require('../lib/ModuleTransport'); const Promise = require('promise'); const declareOpts = require('../lib/declareOpts'); -const fs = require('fs'); const os = require('os'); -const temp = require('temp'); const util = require('util'); const workerFarm = require('worker-farm'); const debug = require('debug')('ReactNativePackager:JStransformer'); -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. @@ -32,33 +27,14 @@ const DEFAULT_MAX_CALL_TIME = 301000; const MAX_RETRIES = 2; const validateOpts = declareOpts({ - projectRoots: { - type: 'array', - required: true, - }, - blacklistRE: { - type: 'object', // typeof regex is object - }, - polyfillModuleNames: { - type: 'array', - default: [], - }, transformModulePath: { type:'string', required: false, }, - cache: { - type: 'object', - required: true, - }, transformTimeoutInterval: { type: 'number', default: DEFAULT_MAX_CALL_TIME, }, - disableInternalTransforms: { - type: 'boolean', - default: false, - }, }); const maxConcurrentWorkers = ((cores, override) => { @@ -82,120 +58,67 @@ class Transformer { constructor(options) { const opts = this._opts = validateOpts(options); - this._cache = opts.cache; - this._transformModulePath = opts.transformModulePath; - this._projectRoots = opts.projectRoots; + const {transformModulePath} = opts; - if (opts.transformModulePath != null) { - let transformer; + if (transformModulePath) { + this._transformModulePath = require.resolve(transformModulePath); - if (opts.disableInternalTransforms) { - transformer = opts.transformModulePath; - } else { - transformer = this._workerWrapperPath = temp.path(); - fs.writeFileSync( - this._workerWrapperPath, - ` - module.exports = require(${JSON.stringify(require.resolve('./worker'))}); - require(${JSON.stringify(String(opts.transformModulePath))}); - ` - ); - } + this._workers = workerFarm( + { + autoStart: true, + maxConcurrentCallsPerWorker: 1, + maxConcurrentWorkers: maxConcurrentWorkers, + maxCallsPerWorker: MAX_CALLS_PER_WORKER, + maxCallTime: opts.transformTimeoutInterval, + maxRetries: MAX_RETRIES, + }, + require.resolve('./worker'), + ['minify', 'transformAndExtractDependencies'] + ); - this._workers = workerFarm({ - autoStart: true, - maxConcurrentCallsPerWorker: 1, - maxConcurrentWorkers: maxConcurrentWorkers, - maxCallsPerWorker: MAX_CALLS_PER_WORKER, - maxCallTime: opts.transformTimeoutInterval, - maxRetries: MAX_RETRIES, - }, transformer); - - this._transform = Promise.denodeify(this._workers); + this._transform = Promise.denodeify(this._workers.transformAndExtractDependencies); + this.minify = Promise.denodeify(this._workers.minify); } } kill() { this._workers && workerFarm.end(this._workers); - if (this._workerWrapperPath && - typeof this._workerWrapperPath === 'string') { - fs.unlink(this._workerWrapperPath, () => {}); // we don't care about potential errors here - } } - invalidateFile(filePath) { - this._cache.invalidate(filePath); - } - - loadFileAndTransform(filePath, options) { - if (this._transform == null) { + transformFile(fileName, code, options) { + if (!this._transform) { return Promise.reject(new Error('No transfrom module')); } + debug('transforming file', fileName); + return this + ._transform(this._transformModulePath, fileName, code, options) + .then(result => { + debug('done transforming file', fileName); + return result; + }) + .catch(error => { + if (error.type === 'TimeoutError') { + const timeoutErr = new Error( + `TimeoutError: transforming ${fileName} took longer than ` + + `${this._opts.transformTimeoutInterval / 1000} seconds.\n` + + `You can adjust timeout via the 'transformTimeoutInterval' option` + ); + timeoutErr.type = 'TimeoutError'; + throw timeoutErr; + } else if (error.type === 'ProcessTerminatedError') { + const uncaughtError = new Error( + 'Uncaught error in the transformer worker: ' + + this._opts.transformModulePath + ); + uncaughtError.type = 'ProcessTerminatedError'; + throw uncaughtError; + } - debug('transforming file', filePath); - - const optionsJSON = JSON.stringify(options); - - return this._cache.get( - filePath, - 'transformedSource-' + optionsJSON, - // TODO: use fastfs to avoid reading file from disk again - () => readFile(filePath).then( - buffer => { - const sourceCode = buffer.toString('utf8'); - - return this._transform({ - sourceCode, - filename: filePath, - options: { - ...options, - projectRoots: this._projectRoots, - externalTransformModulePath: this._transformModulePath, - }, - }).then(res => { - if (res.error) { - console.warn( - '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' - ); - throw formatError(res.error, filePath); - } - - debug('done transforming file', filePath); - - return new ModuleTransport({ - code: res.code, - map: res.map, - sourcePath: filePath, - sourceCode: sourceCode, - }); - }).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; - } else if (err.type === 'ProcessTerminatedError') { - const uncaughtError = new Error( - 'Uncaught error in the transformer worker: ' + - this._opts.transformModulePath - ); - uncaughtError.type = 'ProcessTerminatedError'; - throw uncaughtError; - } - - throw formatError(err, filePath); - }); - }) - ); + throw formatError(error, fileName); + }); } } - module.exports = Transformer; Transformer.TransformError = TransformError; diff --git a/react-packager/src/JSTransformer/worker.js b/react-packager/src/JSTransformer/worker.js deleted file mode 100644 index c8d388a4..00000000 --- a/react-packager/src/JSTransformer/worker.js +++ /dev/null @@ -1,77 +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 babel = require('babel-core'); -var makeInternalConfig = require('babel-preset-react-native/configs/internal'); - -// Runs internal transforms on the given sourceCode. Note that internal -// transforms should be run after the external ones to ensure that they run on -// Javascript code -function internalTransforms(sourceCode, filename, options) { - var internalBabelConfig = makeInternalConfig(options); - - if (!internalBabelConfig) { - return { - code: sourceCode, - filename: filename, - }; - } - - var result = babel.transform(sourceCode, Object.assign({ - filename: filename, - sourceFileName: filename, - }, internalBabelConfig)); - - return { - code: result.code, - filename: filename, - }; -} - -function onExternalTransformDone(data, callback, error, externalOutput) { - if (error) { - callback(error); - return; - } - - var result = internalTransforms( - externalOutput.code, - externalOutput.filename, - data.options - ); - - callback(null, result); -} - -module.exports = function(data, callback) { - try { - if (data.options.externalTransformModulePath) { - var externalTransformModule = require( - data.options.externalTransformModulePath - ); - externalTransformModule( - data, - onExternalTransformDone.bind(null, data, callback) - ); - } else { - onExternalTransformDone( - data, - callback, - null, - { - code: data.sourceCode, - filename: data.filename - } - ); - } - } catch (e) { - callback(e); - } -}; diff --git a/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js b/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js index a39a1e97..64ad751f 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js @@ -16,6 +16,16 @@ function parse(code) { return babel.transform(code, {code: false, babelrc: false, compact: true}); } +const babelOptions = { + babelrc: false, + compact: true, + retainLines: false, +}; + +function normalize({code}) { + return babel.transform(code, babelOptions).code; +} + describe('constant expressions', () => { it('can optimize conditional expressions with constant conditions', () => { const code = ` @@ -29,7 +39,7 @@ describe('constant expressions', () => { 'foo'==='bar' ? b : c, f() ? g() : h() );`; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`a(true,true,2,true,{},{a:1},c,f()?g():h());`); }); @@ -39,7 +49,7 @@ describe('constant expressions', () => { var b = 'android' == 'android' ? ('production' != 'production' ? 'a' : 'A') : 'i';`; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`var a=1;var b='A';`); }); @@ -48,7 +58,7 @@ describe('constant expressions', () => { var a = true || 1; var b = 'android' == 'android' && 'production' != 'production' || null || "A";`; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`var a=true;var b="A";`); }); @@ -60,7 +70,7 @@ describe('constant expressions', () => { var d = null || z(); var e = !1 && z(); `; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`var a="truthy";var b=z();var c=null;var d=z();var e=false;`); }); @@ -70,7 +80,7 @@ describe('constant expressions', () => { var a = 1; } `; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(``); }); @@ -86,7 +96,7 @@ describe('constant expressions', () => { var a = 'b'; } `; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`{var a=3;var b=a+4;}`); }); @@ -106,7 +116,7 @@ describe('constant expressions', () => { } } `; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`{{require('c');}}`); }); }); diff --git a/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js b/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js index a27def1d..fba330ee 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js @@ -22,7 +22,7 @@ describe('Dependency extraction:', () => { } }); require - ('more');` + ('more');`; const {dependencies, dependencyOffsets} = extractDependencies(code); expect(dependencies) .toEqual(['foo/bar', 'React', 'Component', 'more']); @@ -34,11 +34,11 @@ describe('Dependency extraction:', () => { require('a'); foo.require('b'); bar. - require ( 'c').require('d')require('e')`; + require ( 'c').require('d');require('e')`; const {dependencies, dependencyOffsets} = extractDependencies(code); expect(dependencies).toEqual(['a', 'e']); - expect(dependencyOffsets).toEqual([15, 97]); + expect(dependencyOffsets).toEqual([15, 98]); }); it('does not extract require calls from strings', () => { @@ -51,7 +51,7 @@ describe('Dependency extraction:', () => { return require ( "Component" ); } }); - " \\" require('more')";` + " \\" require('more')";`; const {dependencies, dependencyOffsets} = extractDependencies(code); expect(dependencies).toEqual(['foo', 'Component']); @@ -85,12 +85,28 @@ describe('Dependency extraction:', () => { expect(dependencyOffsets).toEqual([]); }); + it('does not extract calls to require with non-static arguments', () => { + const code = `require('foo/' + bar)`; + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual([]); + expect(dependencyOffsets).toEqual([]); + }); + it('does not get confused by previous states', () => { // yes, this was a bug - const code = `require("a");/* a comment */ var a = /[a]/.test('a');` + const code = `require("a");/* a comment */ var a = /[a]/.test('a');`; const {dependencies, dependencyOffsets} = extractDependencies(code); expect(dependencies).toEqual(['a']); expect(dependencyOffsets).toEqual([8]); }); + + it('can handle regular expressions', () => { + const code = `require('a'); /["']/.test('foo'); require("b");`; + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual(['a', 'b']); + expect(dependencyOffsets).toEqual([8, 42]); + }); }); diff --git a/react-packager/src/JSTransformer/worker/__tests__/minify-test.js b/react-packager/src/JSTransformer/worker/__tests__/minify-test.js index e65594e7..78450879 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/minify-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/minify-test.js @@ -21,91 +21,37 @@ const uglify = { jest.setMock('uglify-js', uglify); const minify = require('../minify'); -const {any} = jasmine; +const {objectContaining} = jasmine; describe('Minification:', () => { - const fileName = '/arbitrary/file.js'; - const DEPENDENCY_MARKER = '\u0002\ueffe\ue277\uead5'; + const filename = '/arbitrary/file.js'; + const code = 'arbitrary(code)'; let map; beforeEach(() => { uglify.minify.mockClear(); - map = {version: 3, sources: [fileName], mappings: ''}; + uglify.minify.mockReturnValue({code: '', map: '{}'}); + map = {version: 3, sources: ['?'], mappings: ''}; }); - it('passes the transformed code to `uglify.minify`, wrapped in an immediately invoked function expression', () => { - const code = 'arbitrary(code)'; - minify('', code, {}, [], []); - expect(uglify.minify).toBeCalledWith( - `(function(){${code}}());`, any(Object)); - }); - - it('uses the passed module locals as parameters of the IIFE', () => { - const moduleLocals = ['arbitrary', 'parameters']; - minify('', '', {}, [], moduleLocals); - expect(uglify.minify).toBeCalledWith( - `(function(${moduleLocals}){}());`, any(Object)); - }); - - it('passes the transformed source map to `uglify.minify`', () => { - minify('', '', map, [], []); - const [, options] = uglify.minify.mock.calls[0]; - expect(options.inSourceMap).toEqual(map); - }); - - it('passes the file name as `outSourceMap` to `uglify.minify` (uglify uses it for the `file` field on the source map)', () => { - minify(fileName, '', {}, [], []); - const [, options] = uglify.minify.mock.calls[0]; - expect(options.outSourceMap).toEqual(fileName); - }); - - it('inserts a marker for every dependency offset before minifing', () => { - const code = ` - var React = require('React'); - var Immutable = require('Immutable');`; - const dependencyOffsets = [27, 67]; - const expectedCode = - code.replace(/require\('/g, '$&' + DEPENDENCY_MARKER); - - minify('', code, {}, dependencyOffsets, []); - expect(uglify.minify).toBeCalledWith( - `(function(){${expectedCode}}());`, any(Object)); + it('passes file name, code, and source map to `uglify`', () => { + minify(filename, code, map); + expect(uglify.minify).toBeCalledWith(code, objectContaining({ + fromString: true, + inSourceMap: map, + outSourceMap: filename, + })); }); it('returns the code provided by uglify', () => { - const code = 'some(source) + code'; - uglify.minify.mockReturnValue({code: `!function(a,b,c){${code}}()`}); - - const result = minify('', '', {}, [], []); + uglify.minify.mockReturnValue({code, map: '{}'}); + const result = minify('', '', {}); expect(result.code).toBe(code); }); - it('extracts dependency offsets from the code provided by uglify', () => { - const code = ` - var a=r("${DEPENDENCY_MARKER}a-dependency"); - var b=r("\\x02\\ueffe\\ue277\\uead5b-dependency"); - var e=r(a()?'\\u0002\\ueffe\\ue277\\uead5c-dependency' - :'\x02\ueffe\ue277\uead5d-dependency');`; - uglify.minify.mockReturnValue({code: `!function(){${code}}());`}); - - const result = minify('', '', {}, [], []); - expect(result.dependencyOffsets).toEqual([15, 46, 81, 114]); - }); - - it('returns the source map object provided by uglify', () => { - uglify.minify.mockReturnValue({map, code: ''}); - const result = minify('', '', {}, [], []); - expect(result.map).toBe(map); - }); - - it('adds a `moduleLocals` object to the result that reflects the names of the minified module locals', () => { - const moduleLocals = ['arbitrary', 'parameters', 'here']; - uglify.minify.mockReturnValue({code: '(function(a,ll,d){}());'}); - const result = minify('', '', {}, [], moduleLocals); - expect(result.moduleLocals).toEqual({ - arbitrary: 'a', - parameters: 'll', - here: 'd', - }); + it('parses the source map object provided by uglify and sets the sources property', () => { + uglify.minify.mockReturnValue({map: JSON.stringify(map), code: ''}); + const result = minify(filename, '', {}); + expect(result.map).toEqual({...map, sources: [filename]}); }); }); diff --git a/react-packager/src/JSTransformer/worker/__tests__/worker-test.js b/react-packager/src/JSTransformer/worker/__tests__/worker-test.js index a6acb668..b0803509 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/worker-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/worker-test.js @@ -78,33 +78,6 @@ describe('code transformation worker:', () => { }); }); - it('puts an empty `moduleLocals` object on the result', done => { - transform.mockImplementation( - (_, callback) => callback(null, {code: 'arbitrary'})); - transformCode(transform, 'filename', 'code', {}, (_, data) => { - expect(data.moduleLocals).toEqual({}); - done(); - }); - }); - - it('if a `moduleLocals` array is passed, the `moduleLocals` object is a key mirror of its items', done => { - transform.mockImplementation( - (_, callback) => callback(null, {code: 'arbitrary'})); - const moduleLocals = - ['arbitrary', 'list', 'containing', 'variable', 'names']; - - transformCode(transform, 'filename', 'code', {moduleLocals}, (_, data) => { - expect(data.moduleLocals).toEqual({ - arbitrary: 'arbitrary', - list: 'list', - containing: 'containing', - variable: 'variable', - names: 'names', - }); - done(); - }); - }); - describe('dependency extraction:', () => { let code; @@ -155,21 +128,19 @@ describe('code transformation worker:', () => { }); describe('Minifications:', () => { - let constantFolding, extractDependencies, inline, minify, options; - let transformResult, dependencyData, moduleLocals; + let constantFolding, inline, options; + let transformResult, dependencyData; const filename = 'arbitrary/file.js'; - const foldedCode = 'arbitrary(folded(code));' - const foldedMap = {version: 3, sources: ['fold.js']} + const foldedCode = 'arbitrary(folded(code));'; + const foldedMap = {version: 3, sources: ['fold.js']}; beforeEach(() => { constantFolding = require('../constant-folding') .mockReturnValue({code: foldedCode, map: foldedMap}); extractDependencies = require('../extract-dependencies'); inline = require('../inline'); - minify = require('../minify').mockReturnValue({}); - moduleLocals = ['module', 'require', 'exports']; - options = {moduleLocals, minify: true}; + options = {minify: true}; dependencyData = { dependencies: ['a', 'b', 'c'], dependencyOffsets: [100, 120, 140] @@ -206,19 +177,6 @@ describe('code transformation worker:', () => { }); }); - it('passes the code obtained from `constant-folding` to `minify`', done => { - transformCode(transform, filename, 'code', options, () => { - expect(minify).toBeCalledWith( - filename, - foldedCode, - foldedMap, - dependencyData.dependencyOffsets, - moduleLocals - ); - done(); - }); - }); - it('uses the dependencies obtained from the optimized result', done => { transformCode(transform, filename, 'code', options, (_, result) => { expect(result.dependencies).toEqual(dependencyData.dependencies); @@ -226,17 +184,10 @@ describe('code transformation worker:', () => { }); }); - it('uses data produced by `minify` for the result', done => { - const minifyResult = { - code: 'minified(code)', - dependencyOffsets: [10, 30, 60], - map: {version: 3, sources: ['minified.js']}, - moduleLocals: {module: 'x', require: 'y', exports: 'z'}, - }; - minify.mockReturnValue(minifyResult); - + it('uses data produced by `constant-folding` for the result', done => { transformCode(transform, 'filename', 'code', options, (_, result) => { - expect(result).toEqual(objectContaining(minifyResult)) + expect(result) + .toEqual(objectContaining({code: foldedCode, map: foldedMap})); done(); }); }); diff --git a/react-packager/src/JSTransformer/worker/constant-folding.js b/react-packager/src/JSTransformer/worker/constant-folding.js index acf14f68..f896430f 100644 --- a/react-packager/src/JSTransformer/worker/constant-folding.js +++ b/react-packager/src/JSTransformer/worker/constant-folding.js @@ -73,9 +73,12 @@ function constantFolding(filename, transformResult) { filename, plugins: [plugin], inputSourceMap: transformResult.map, + sourceMaps: true, + sourceFileName: filename, babelrc: false, compact: true, - }) + retainLines: true, + }); } module.exports = constantFolding; diff --git a/react-packager/src/JSTransformer/worker/extract-dependencies.js b/react-packager/src/JSTransformer/worker/extract-dependencies.js index 3ca22164..4e1bfb39 100644 --- a/react-packager/src/JSTransformer/worker/extract-dependencies.js +++ b/react-packager/src/JSTransformer/worker/extract-dependencies.js @@ -8,48 +8,8 @@ */ 'use strict'; -const SINGLE_QUOTE = "'".charCodeAt(0); -const DOUBLE_QUOTE = '"'.charCodeAt(0); -const BACKSLASH = '\\'.charCodeAt(0); -const SLASH = '/'.charCodeAt(0); -const NEWLINE = '\n'.charCodeAt(0); -const ASTERISK = '*'.charCodeAt(0); - -// dollar is the only regex special character valid in identifiers -const escapeRegExp = identifier => identifier.replace(/[$]/g, '\\$'); - -function binarySearch(indexes, index) { - var low = 0; - var high = indexes.length - 1; - var i = 0; - - if (indexes[low] === index) { - return low; - } - while (high - low > 1) { - var current = low + ((high - low) >>> 1); // right shift divides by 2 and floors - if (index === indexes[current]) { - return current; - } - if (index > indexes[current]) { - low = current; - } else { - high = current; - } - } - return low; -} - -function indexOfCharCode(string, needle, i) { - for (var charCode; (charCode = string.charCodeAt(i)); i++) { - if (charCode === needle) { - return i; - } - } - return -1; -} - -const reRequire = /(?:^|[^.\s])\s*\brequire\s*\(\s*(['"])(.*?)\1/g; +const babel = require('babel-core'); +const babylon = require('babylon'); /** * Extracts dependencies (module IDs imported with the `require` function) from @@ -66,61 +26,24 @@ const reRequire = /(?:^|[^.\s])\s*\brequire\s*\(\s*(['"])(.*?)\1/g; * The index points to the opening quote. */ function extractDependencies(code) { - const ranges = [0]; - // are we currently in a quoted string? -> SINGLE_QUOTE or DOUBLE_QUOTE, else undefined - var currentQuote; - // scan the code for string literals and comments. - for (var i = 0, charCode; (charCode = code.charCodeAt(i)); i++) { - if (charCode === BACKSLASH) { - i += 1; - continue; - } - - if (charCode === SLASH && currentQuote === undefined) { - var next = code.charCodeAt(i + 1); - var end = undefined; - if (next === SLASH) { - end = indexOfCharCode(code, NEWLINE, i + 2); - } else if (next === ASTERISK) { - end = code.indexOf('*/', i + 2) + 1; // assume valid JS input here - } - if (end === -1) { - // if the comment does not end, it goes to the end of the file - end += code.length; - } - if (end !== undefined) { - ranges.push(i, end); - i = end; - continue; - } - } - - var isQuoteStart = currentQuote === undefined && - (charCode === SINGLE_QUOTE || charCode === DOUBLE_QUOTE); - if (isQuoteStart || currentQuote === charCode) { - ranges.push(i); - currentQuote = currentQuote === charCode ? undefined : charCode; - } - } - ranges.push(i); - - // extract dependencies + const ast = babylon.parse(code); const dependencies = new Set(); const dependencyOffsets = []; - for (var match; (match = reRequire.exec(code)); ) { - // check whether the match is in a code range, and not inside of a string - // literal or a comment - if (binarySearch(ranges, match.index) % 2 === 0) { - dependencies.add(match[2]); - dependencyOffsets.push( - match[0].length - match[2].length - 2 + match.index); - } - } - return { - dependencyOffsets, - dependencies: Array.from(dependencies.values()), - }; + babel.traverse(ast, { + CallExpression(path) { + const node = path.node; + const callee = node.callee; + const arg = node.arguments[0]; + if (callee.type !== 'Identifier' || callee.name !== 'require' || !arg || arg.type !== 'StringLiteral') { + return; + } + dependencyOffsets.push(arg.start); + dependencies.add(arg.value); + } + }); + + return {dependencyOffsets, dependencies: Array.from(dependencies)}; } module.exports = extractDependencies; diff --git a/react-packager/src/JSTransformer/worker/index.js b/react-packager/src/JSTransformer/worker/index.js index bc6e9333..1b68feae 100644 --- a/react-packager/src/JSTransformer/worker/index.js +++ b/react-packager/src/JSTransformer/worker/index.js @@ -13,12 +13,6 @@ const extractDependencies = require('./extract-dependencies'); const inline = require('./inline'); const minify = require('./minify'); -function keyMirrorFromArray(array) { - var keyMirror = {}; - array.forEach(key => keyMirror[key] = key); - return keyMirror; -} - function makeTransformParams(filename, sourceCode, options) { if (filename.endsWith('.json')) { sourceCode = 'module.exports=' + sourceCode; @@ -28,7 +22,6 @@ function makeTransformParams(filename, sourceCode, options) { function transformCode(transform, filename, sourceCode, options, callback) { const params = makeTransformParams(filename, sourceCode, options.transform); - const moduleLocals = options.moduleLocals || []; const isJson = filename.endsWith('.json'); transform(params, (error, transformed) => { @@ -52,28 +45,35 @@ function transformCode(transform, filename, sourceCode, options, callback) { code = code.replace(/^\w+\.exports=/, ''); } - const moduleLocals = options.moduleLocals || []; - const dependencyData = isJson || options.extern + const result = isJson || options.extern ? {dependencies: [], dependencyOffsets: []} : extractDependencies(code); - var result; - if (options.minify) { - result = minify( - filename, code, map, dependencyData.dependencyOffsets, moduleLocals); - result.dependencies = dependencyData.dependencies; - } else { - result = dependencyData; - result.code = code; - result.map = map; - result.moduleLocals = keyMirrorFromArray(moduleLocals); - } + result.code = code; + result.map = map; callback(null, result); }); } -module.exports = function(transform, filename, sourceCode, options, callback) { +exports.transformAndExtractDependencies = ( + transform, + filename, + sourceCode, + options, + callback +) => { transformCode(require(transform), filename, sourceCode, options || {}, callback); }; -module.exports.transformCode = transformCode; // for easier testing + +exports.minify = (filename, code, sourceMap, callback) => { + var result; + try { + result = minify(filename, code, sourceMap); + } catch (error) { + callback(error); + } + callback(null, result); +}; + +exports.transformCode = transformCode; // for easier testing diff --git a/react-packager/src/JSTransformer/worker/inline.js b/react-packager/src/JSTransformer/worker/inline.js index 2a419933..a294bcef 100644 --- a/react-packager/src/JSTransformer/worker/inline.js +++ b/react-packager/src/JSTransformer/worker/inline.js @@ -89,6 +89,8 @@ function inline(filename, transformResult, options) { filename, plugins: [[plugin, options]], inputSourceMap: transformResult.map, + sourceMaps: true, + sourceFileName: filename, code: false, babelrc: false, compact: true, diff --git a/react-packager/src/JSTransformer/worker/minify.js b/react-packager/src/JSTransformer/worker/minify.js index d3435918..089cfc78 100644 --- a/react-packager/src/JSTransformer/worker/minify.js +++ b/react-packager/src/JSTransformer/worker/minify.js @@ -10,63 +10,10 @@ const uglify = require('uglify-js'); -const MAGIC_MARKER = '\u0002\ueffe\ue277\uead5'; -const MAGIC_MARKER_SPLITTER = - /(?:\x02|\\u0002|\\x02)(?:\ueffe|\\ueffe)(?:\ue277|\\ue277)(?:\uead5|\\uead5)/; - -// IIFE = "immediately invoked function expression" -// we wrap modules in functions to allow the minifier to mangle local variables -function wrapCodeInIIFE(code, moduleLocals) { - return `(function(${moduleLocals.join(',')}){${code}}());`; -} - -function extractCodeFromIIFE(code) { - return code.substring(code.indexOf('{') + 1, code.lastIndexOf('}')); -} - -function extractModuleLocalsFromIIFE(code) { - return code.substring(code.indexOf('(', 1) + 1, code.indexOf(')')).split(','); -} - -function splitFirstElementAt(array, offset) { - const first = array.shift(); - array.unshift(first.slice(0, offset + 1), first.slice(offset + 1)); - return array; -} - -function insertMarkers(code, dependencyOffsets) { - return dependencyOffsets - .reduceRight(splitFirstElementAt, [code]) - .join(MAGIC_MARKER); -} - -function extractMarkers(codeWithMarkers) { - const dependencyOffsets = []; - const codeBits = codeWithMarkers.split(MAGIC_MARKER_SPLITTER); - var offset = 0; - for (var i = 0, max = codeBits.length - 1; i < max; i++) { - offset += codeBits[i].length; - dependencyOffsets.push(offset - 1); - } - - return {code: codeBits.join(''), dependencyOffsets}; -} - -function minify(filename, code, map, dependencyOffsets, moduleLocals) { - // before minifying, code is wrapped in an immediately invoked function - // expression, so that top level variables can be shortened safely - code = wrapCodeInIIFE( - // since we don't know where the strings specifying dependencies will be - // located in the minified code, we mark them with a special marker string - // and extract them afterwards. - // That way, post-processing code can use these positions - insertMarkers(code, dependencyOffsets), - moduleLocals - ); - +function minify(filename, code, sourceMap) { const minifyResult = uglify.minify(code, { fromString: true, - inSourceMap: map, + inSourceMap: sourceMap, outSourceMap: filename, output: { ascii_only: true, @@ -74,15 +21,9 @@ function minify(filename, code, map, dependencyOffsets, moduleLocals) { }, }); - const minifiedModuleLocals = extractModuleLocalsFromIIFE(minifyResult.code); - const codeWithMarkers = extractCodeFromIIFE(minifyResult.code); - const result = extractMarkers(codeWithMarkers); - result.map = minifyResult.map; - result.moduleLocals = {}; - moduleLocals.forEach( - (key, i) => result.moduleLocals[key] = minifiedModuleLocals[i]); - - return result; + minifyResult.map = JSON.parse(minifyResult.map); + minifyResult.map.sources = [filename]; + return minifyResult; } module.exports = minify; diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index b12ebd48..df6f609e 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -31,6 +31,7 @@ describe('Resolver', function() { this.getName = jest.genMockFn(); this.getDependencies = jest.genMockFn(); this.isPolyfill = jest.genMockFn().mockReturnValue(false); + this.isJSON = jest.genMockFn().mockReturnValue(false); }); Polyfill = jest.genMockFn().mockImpl(function() { var polyfill = new Module(); @@ -61,6 +62,10 @@ describe('Resolver', function() { finalize() { return Promise.resolve(this); } + + getResolvedDependencyPairs() { + return []; + } } function createModule(id, dependencies) { @@ -70,6 +75,12 @@ describe('Resolver', function() { return module; } + function createJsonModule(id) { + const module = createModule(id, []); + module.isJSON.mockReturnValue(true); + return module; + } + function createPolyfill(id, dependencies) { var polyfill = new Polyfill({}); polyfill.getName = jest.genMockFn().mockImpl(() => Promise.resolve(id)); @@ -79,6 +90,23 @@ describe('Resolver', function() { } describe('getDependencies', function() { + it('forwards transform options to the dependency graph', function() { + const transformOptions = {arbitrary: 'options'}; + const platform = 'ios'; + const entry = '/root/index.js'; + + DependencyGraph.prototype.getDependencies.mockImplementation( + () => Promise.reject()); + new Resolver({projectRoot: '/root', }) + .getDependencies(entry, {platform}, transformOptions); + expect(DependencyGraph.prototype.getDependencies).toBeCalledWith({ + entryPath: entry, + platform: platform, + transformOptions: transformOptions, + recursive: true, + }); + }); + pit('should get dependencies with polyfills', function() { var module = createModule('index'); var deps = [module]; @@ -242,388 +270,8 @@ describe('Resolver', function() { projectRoot: '/root', }); - var dependencies = ['x', 'y', 'z', 'a', 'b']; - /*eslint-disable */ var code = [ - // single line import - "import'x';", - "import 'x';", - "import 'x' ;", - "import Default from 'x';", - "import * as All from 'x';", - "import {} from 'x';", - "import { } from 'x';", - "import {Foo} from 'x';", - "import { Foo } from 'x';", - "import { Foo, } from 'x';", - "import {Foo as Bar} from 'x';", - "import { Foo as Bar } from 'x';", - "import { Foo as Bar, } from 'x';", - "import { Foo, Bar } from 'x';", - "import { Foo, Bar, } from 'x';", - "import { Foo as Bar, Baz } from 'x';", - "import { Foo as Bar, Baz, } from 'x';", - "import { Foo, Bar as Baz } from 'x';", - "import { Foo, Bar as Baz, } from 'x';", - "import { Foo as Bar, Baz as Qux } from 'x';", - "import { Foo as Bar, Baz as Qux, } from 'x';", - "import { Foo, Bar, Baz } from 'x';", - "import { Foo, Bar, Baz, } from 'x';", - "import { Foo as Bar, Baz, Qux } from 'x';", - "import { Foo as Bar, Baz, Qux, } from 'x';", - "import { Foo, Bar as Baz, Qux } from 'x';", - "import { Foo, Bar as Baz, Qux, } from 'x';", - "import { Foo, Bar, Baz as Qux } from 'x';", - "import { Foo, Bar, Baz as Qux, } from 'x';", - "import { Foo as Bar, Baz as Qux, Norf } from 'x';", - "import { Foo as Bar, Baz as Qux, Norf, } from 'x';", - "import { Foo as Bar, Baz, Qux as Norf } from 'x';", - "import { Foo as Bar, Baz, Qux as Norf, } from 'x';", - "import { Foo, Bar as Baz, Qux as Norf } from 'x';", - "import { Foo, Bar as Baz, Qux as Norf, } from 'x';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'x';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'x';", - "import Default, * as All from 'x';", - "import Default, { } from 'x';", - "import Default, { Foo } from 'x';", - "import Default, { Foo, } from 'x';", - "import Default, { Foo as Bar } from 'x';", - "import Default, { Foo as Bar, } from 'x';", - "import Default, { Foo, Bar } from 'x';", - "import Default, { Foo, Bar, } from 'x';", - "import Default, { Foo as Bar, Baz } from 'x';", - "import Default, { Foo as Bar, Baz, } from 'x';", - "import Default, { Foo, Bar as Baz } from 'x';", - "import Default, { Foo, Bar as Baz, } from 'x';", - "import Default, { Foo as Bar, Baz as Qux } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, } from 'x';", - "import Default, { Foo, Bar, Baz } from 'x';", - "import Default, { Foo, Bar, Baz, } from 'x';", - "import Default, { Foo as Bar, Baz, Qux } from 'x';", - "import Default, { Foo as Bar, Baz, Qux, } from 'x';", - "import Default, { Foo, Bar as Baz, Qux } from 'x';", - "import Default, { Foo, Bar as Baz, Qux, } from 'x';", - "import Default, { Foo, Bar, Baz as Qux } from 'x';", - "import Default, { Foo, Bar, Baz as Qux, } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, Norf } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'x';", - "import Default, { Foo as Bar, Baz, Qux as Norf } from 'x';", - "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'x';", - "import Default, { Foo, Bar as Baz, Qux as Norf } from 'x';", - "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'x';", - "import Default , { } from 'x';", - 'import "x";', - 'import Default from "x";', - 'import * as All from "x";', - 'import { } from "x";', - 'import { Foo } from "x";', - 'import { Foo, } from "x";', - 'import { Foo as Bar } from "x";', - 'import { Foo as Bar, } from "x";', - 'import { Foo, Bar } from "x";', - 'import { Foo, Bar, } from "x";', - 'import { Foo as Bar, Baz } from "x";', - 'import { Foo as Bar, Baz, } from "x";', - 'import { Foo, Bar as Baz } from "x";', - 'import { Foo, Bar as Baz, } from "x";', - 'import { Foo as Bar, Baz as Qux } from "x";', - 'import { Foo as Bar, Baz as Qux, } from "x";', - 'import { Foo, Bar, Baz } from "x";', - 'import { Foo, Bar, Baz, } from "x";', - 'import { Foo as Bar, Baz, Qux } from "x";', - 'import { Foo as Bar, Baz, Qux, } from "x";', - 'import { Foo, Bar as Baz, Qux } from "x";', - 'import { Foo, Bar as Baz, Qux, } from "x";', - 'import { Foo, Bar, Baz as Qux } from "x";', - 'import { Foo, Bar, Baz as Qux, } from "x";', - 'import { Foo as Bar, Baz as Qux, Norf } from "x";', - 'import { Foo as Bar, Baz as Qux, Norf, } from "x";', - 'import { Foo as Bar, Baz, Qux as Norf } from "x";', - 'import { Foo as Bar, Baz, Qux as Norf, } from "x";', - 'import { Foo, Bar as Baz, Qux as Norf } from "x";', - 'import { Foo, Bar as Baz, Qux as Norf, } from "x";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "x";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "x";', - 'import Default, * as All from "x";', - 'import Default, { } from "x";', - 'import Default, { Foo } from "x";', - 'import Default, { Foo, } from "x";', - 'import Default, { Foo as Bar } from "x";', - 'import Default, { Foo as Bar, } from "x";', - 'import Default, { Foo, Bar } from "x";', - 'import Default, { Foo, Bar, } from "x";', - 'import Default, { Foo as Bar, Baz } from "x";', - 'import Default, { Foo as Bar, Baz, } from "x";', - 'import Default, { Foo, Bar as Baz } from "x";', - 'import Default, { Foo, Bar as Baz, } from "x";', - 'import Default, { Foo as Bar, Baz as Qux } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, } from "x";', - 'import Default, { Foo, Bar, Baz } from "x";', - 'import Default, { Foo, Bar, Baz, } from "x";', - 'import Default, { Foo as Bar, Baz, Qux } from "x";', - 'import Default, { Foo as Bar, Baz, Qux, } from "x";', - 'import Default, { Foo, Bar as Baz, Qux } from "x";', - 'import Default, { Foo, Bar as Baz, Qux, } from "x";', - 'import Default, { Foo, Bar, Baz as Qux } from "x";', - 'import Default, { Foo, Bar, Baz as Qux, } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, Norf } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "x";', - 'import Default, { Foo as Bar, Baz, Qux as Norf } from "x";', - 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "x";', - 'import Default, { Foo, Bar as Baz, Qux as Norf } from "x";', - 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "x";', - 'import Default from "y";', - 'import * as All from \'z\';', - // import with support for new lines - "import { Foo,\n Bar }\n from 'x';", - "import { \nFoo,\nBar,\n }\n from 'x';", - "import { Foo as Bar,\n Baz\n }\n from 'x';", - "import { \nFoo as Bar,\n Baz\n, }\n from 'x';", - "import { Foo,\n Bar as Baz\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", - "import { Foo,\n Bar,\n Baz }\n from 'x';", - "import { Foo,\n Bar,\n Baz,\n }\n from 'x';", - "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", - "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", - "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", - "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", - "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'x';", - "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'x';", - "import Default,\n * as All from 'x';", - "import Default,\n { } from 'x';", - "import Default,\n { Foo\n }\n from 'x';", - "import Default,\n { Foo,\n }\n from 'x';", - "import Default,\n { Foo as Bar\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar\n } from\n 'x';", - "import Default,\n { Foo,\n Bar,\n } from\n 'x';", - "import Default,\n { Foo as Bar,\n Baz\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar as Baz\n }\n from 'x';", - "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'x';", - "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'x';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'x';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'x';", - "import Default\n , { } from 'x';", - // single line export - "export'x';", - "export 'x';", - "export 'x' ;", - "export Default from 'x';", - "export * as All from 'x';", - "export {} from 'x';", - "export { } from 'x';", - "export {Foo} from 'x';", - "export { Foo } from 'x';", - "export { Foo, } from 'x';", - "export {Foo as Bar} from 'x';", - "export { Foo as Bar } from 'x';", - "export { Foo as Bar, } from 'x';", - "export { Foo, Bar } from 'x';", - "export { Foo, Bar, } from 'x';", - "export { Foo as Bar, Baz } from 'x';", - "export { Foo as Bar, Baz, } from 'x';", - "export { Foo, Bar as Baz } from 'x';", - "export { Foo, Bar as Baz, } from 'x';", - "export { Foo as Bar, Baz as Qux } from 'x';", - "export { Foo as Bar, Baz as Qux, } from 'x';", - "export { Foo, Bar, Baz } from 'x';", - "export { Foo, Bar, Baz, } from 'x';", - "export { Foo as Bar, Baz, Qux } from 'x';", - "export { Foo as Bar, Baz, Qux, } from 'x';", - "export { Foo, Bar as Baz, Qux } from 'x';", - "export { Foo, Bar as Baz, Qux, } from 'x';", - "export { Foo, Bar, Baz as Qux } from 'x';", - "export { Foo, Bar, Baz as Qux, } from 'x';", - "export { Foo as Bar, Baz as Qux, Norf } from 'x';", - "export { Foo as Bar, Baz as Qux, Norf, } from 'x';", - "export { Foo as Bar, Baz, Qux as Norf } from 'x';", - "export { Foo as Bar, Baz, Qux as Norf, } from 'x';", - "export { Foo, Bar as Baz, Qux as Norf } from 'x';", - "export { Foo, Bar as Baz, Qux as Norf, } from 'x';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf } from 'x';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'x';", - "export Default, * as All from 'x';", - "export Default, { } from 'x';", - "export Default, { Foo } from 'x';", - "export Default, { Foo, } from 'x';", - "export Default, { Foo as Bar } from 'x';", - "export Default, { Foo as Bar, } from 'x';", - "export Default, { Foo, Bar } from 'x';", - "export Default, { Foo, Bar, } from 'x';", - "export Default, { Foo as Bar, Baz } from 'x';", - "export Default, { Foo as Bar, Baz, } from 'x';", - "export Default, { Foo, Bar as Baz } from 'x';", - "export Default, { Foo, Bar as Baz, } from 'x';", - "export Default, { Foo as Bar, Baz as Qux } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, } from 'x';", - "export Default, { Foo, Bar, Baz } from 'x';", - "export Default, { Foo, Bar, Baz, } from 'x';", - "export Default, { Foo as Bar, Baz, Qux } from 'x';", - "export Default, { Foo as Bar, Baz, Qux, } from 'x';", - "export Default, { Foo, Bar as Baz, Qux } from 'x';", - "export Default, { Foo, Bar as Baz, Qux, } from 'x';", - "export Default, { Foo, Bar, Baz as Qux } from 'x';", - "export Default, { Foo, Bar, Baz as Qux, } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, Norf } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, Norf, } from 'x';", - "export Default, { Foo as Bar, Baz, Qux as Norf } from 'x';", - "export Default, { Foo as Bar, Baz, Qux as Norf, } from 'x';", - "export Default, { Foo, Bar as Baz, Qux as Norf } from 'x';", - "export Default, { Foo, Bar as Baz, Qux as Norf, } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'x';", - "export Default , { } from 'x';", - 'export "x";', - 'export Default from "x";', - 'export * as All from "x";', - 'export { } from "x";', - 'export { Foo } from "x";', - 'export { Foo, } from "x";', - 'export { Foo as Bar } from "x";', - 'export { Foo as Bar, } from "x";', - 'export { Foo, Bar } from "x";', - 'export { Foo, Bar, } from "x";', - 'export { Foo as Bar, Baz } from "x";', - 'export { Foo as Bar, Baz, } from "x";', - 'export { Foo, Bar as Baz } from "x";', - 'export { Foo, Bar as Baz, } from "x";', - 'export { Foo as Bar, Baz as Qux } from "x";', - 'export { Foo as Bar, Baz as Qux, } from "x";', - 'export { Foo, Bar, Baz } from "x";', - 'export { Foo, Bar, Baz, } from "x";', - 'export { Foo as Bar, Baz, Qux } from "x";', - 'export { Foo as Bar, Baz, Qux, } from "x";', - 'export { Foo, Bar as Baz, Qux } from "x";', - 'export { Foo, Bar as Baz, Qux, } from "x";', - 'export { Foo, Bar, Baz as Qux } from "x";', - 'export { Foo, Bar, Baz as Qux, } from "x";', - 'export { Foo as Bar, Baz as Qux, Norf } from "x";', - 'export { Foo as Bar, Baz as Qux, Norf, } from "x";', - 'export { Foo as Bar, Baz, Qux as Norf } from "x";', - 'export { Foo as Bar, Baz, Qux as Norf, } from "x";', - 'export { Foo, Bar as Baz, Qux as Norf } from "x";', - 'export { Foo, Bar as Baz, Qux as Norf, } from "x";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore } from "x";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore, } from "x";', - 'export Default, * as All from "x";', - 'export Default, { } from "x";', - 'export Default, { Foo } from "x";', - 'export Default, { Foo, } from "x";', - 'export Default, { Foo as Bar } from "x";', - 'export Default, { Foo as Bar, } from "x";', - 'export Default, { Foo, Bar } from "x";', - 'export Default, { Foo, Bar, } from "x";', - 'export Default, { Foo as Bar, Baz } from "x";', - 'export Default, { Foo as Bar, Baz, } from "x";', - 'export Default, { Foo, Bar as Baz } from "x";', - 'export Default, { Foo, Bar as Baz, } from "x";', - 'export Default, { Foo as Bar, Baz as Qux } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, } from "x";', - 'export Default, { Foo, Bar, Baz } from "x";', - 'export Default, { Foo, Bar, Baz, } from "x";', - 'export Default, { Foo as Bar, Baz, Qux } from "x";', - 'export Default, { Foo as Bar, Baz, Qux, } from "x";', - 'export Default, { Foo, Bar as Baz, Qux } from "x";', - 'export Default, { Foo, Bar as Baz, Qux, } from "x";', - 'export Default, { Foo, Bar, Baz as Qux } from "x";', - 'export Default, { Foo, Bar, Baz as Qux, } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, Norf } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, Norf, } from "x";', - 'export Default, { Foo as Bar, Baz, Qux as Norf } from "x";', - 'export Default, { Foo as Bar, Baz, Qux as Norf, } from "x";', - 'export Default, { Foo, Bar as Baz, Qux as Norf } from "x";', - 'export Default, { Foo, Bar as Baz, Qux as Norf, } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "x";', - 'export Default from "y";', - 'export * as All from \'z\';', - // export with support for new lines - "export { Foo,\n Bar }\n from 'x';", - "export { \nFoo,\nBar,\n }\n from 'x';", - "export { Foo as Bar,\n Baz\n }\n from 'x';", - "export { \nFoo as Bar,\n Baz\n, }\n from 'x';", - "export { Foo,\n Bar as Baz\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", - "export { Foo,\n Bar,\n Baz }\n from 'x';", - "export { Foo,\n Bar,\n Baz,\n }\n from 'x';", - "export { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", - "export { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", - "export { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", - "export { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", - "export { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'x';", - "export { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'x';", - "export Default,\n * as All from 'x';", - "export Default,\n { } from 'x';", - "export Default,\n { Foo\n }\n from 'x';", - "export Default,\n { Foo,\n }\n from 'x';", - "export Default,\n { Foo as Bar\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar\n } from\n 'x';", - "export Default,\n { Foo,\n Bar,\n } from\n 'x';", - "export Default,\n { Foo as Bar,\n Baz\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar as Baz\n }\n from 'x';", - "export Default,\n { Foo,\n Bar as Baz,\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar,\n Baz\n }\n from 'x';", - "export Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'x';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'x';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'x';", - "export Default\n , { } from 'x';", // require 'require("x")', 'require("y")', @@ -633,8 +281,16 @@ describe('Resolver', function() { ].join('\n'); /*eslint-disable */ - const module = createModule('test module', ['x', 'y']); + function *findDependencyOffsets() { + const re = /(['"']).*?\1/g; + let match; + while ((match = re.exec(code))) { + yield match.index; + } + } + const dependencyOffsets = Array.from(findDependencyOffsets()); + const module = createModule('test module', ['x', 'y']); const resolutionResponse = new ResolutionResponseMock({ dependencies: [module], mainModuleId: 'test module', @@ -647,393 +303,15 @@ describe('Resolver', function() { ]; } - return depResolver.wrapModule( + return depResolver.wrapModule({ resolutionResponse, - createModule('test module', ['x', 'y']), - code - ).then(processedCode => { - expect(processedCode.name).toEqual('test module'); - expect(processedCode.code).toEqual([ - '__d(\'test module\',function(global, require,' + - ' module, exports) { ' + - // single line import - "import'x';", - "import 'changed';", - "import 'changed' ;", - "import Default from 'changed';", - "import * as All from 'changed';", - "import {} from 'changed';", - "import { } from 'changed';", - "import {Foo} from 'changed';", - "import { Foo } from 'changed';", - "import { Foo, } from 'changed';", - "import {Foo as Bar} from 'changed';", - "import { Foo as Bar } from 'changed';", - "import { Foo as Bar, } from 'changed';", - "import { Foo, Bar } from 'changed';", - "import { Foo, Bar, } from 'changed';", - "import { Foo as Bar, Baz } from 'changed';", - "import { Foo as Bar, Baz, } from 'changed';", - "import { Foo, Bar as Baz } from 'changed';", - "import { Foo, Bar as Baz, } from 'changed';", - "import { Foo as Bar, Baz as Qux } from 'changed';", - "import { Foo as Bar, Baz as Qux, } from 'changed';", - "import { Foo, Bar, Baz } from 'changed';", - "import { Foo, Bar, Baz, } from 'changed';", - "import { Foo as Bar, Baz, Qux } from 'changed';", - "import { Foo as Bar, Baz, Qux, } from 'changed';", - "import { Foo, Bar as Baz, Qux } from 'changed';", - "import { Foo, Bar as Baz, Qux, } from 'changed';", - "import { Foo, Bar, Baz as Qux } from 'changed';", - "import { Foo, Bar, Baz as Qux, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", - "import Default, * as All from 'changed';", - "import Default, { } from 'changed';", - "import Default, { Foo } from 'changed';", - "import Default, { Foo, } from 'changed';", - "import Default, { Foo as Bar } from 'changed';", - "import Default, { Foo as Bar, } from 'changed';", - "import Default, { Foo, Bar } from 'changed';", - "import Default, { Foo, Bar, } from 'changed';", - "import Default, { Foo as Bar, Baz } from 'changed';", - "import Default, { Foo as Bar, Baz, } from 'changed';", - "import Default, { Foo, Bar as Baz } from 'changed';", - "import Default, { Foo, Bar as Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz } from 'changed';", - "import Default, { Foo, Bar, Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", - "import Default , { } from 'changed';", - 'import "changed";', - 'import Default from "changed";', - 'import * as All from "changed";', - 'import { } from "changed";', - 'import { Foo } from "changed";', - 'import { Foo, } from "changed";', - 'import { Foo as Bar } from "changed";', - 'import { Foo as Bar, } from "changed";', - 'import { Foo, Bar } from "changed";', - 'import { Foo, Bar, } from "changed";', - 'import { Foo as Bar, Baz } from "changed";', - 'import { Foo as Bar, Baz, } from "changed";', - 'import { Foo, Bar as Baz } from "changed";', - 'import { Foo, Bar as Baz, } from "changed";', - 'import { Foo as Bar, Baz as Qux } from "changed";', - 'import { Foo as Bar, Baz as Qux, } from "changed";', - 'import { Foo, Bar, Baz } from "changed";', - 'import { Foo, Bar, Baz, } from "changed";', - 'import { Foo as Bar, Baz, Qux } from "changed";', - 'import { Foo as Bar, Baz, Qux, } from "changed";', - 'import { Foo, Bar as Baz, Qux } from "changed";', - 'import { Foo, Bar as Baz, Qux, } from "changed";', - 'import { Foo, Bar, Baz as Qux } from "changed";', - 'import { Foo, Bar, Baz as Qux, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', - 'import Default, * as All from "changed";', - 'import Default, { } from "changed";', - 'import Default, { Foo } from "changed";', - 'import Default, { Foo, } from "changed";', - 'import Default, { Foo as Bar } from "changed";', - 'import Default, { Foo as Bar, } from "changed";', - 'import Default, { Foo, Bar } from "changed";', - 'import Default, { Foo, Bar, } from "changed";', - 'import Default, { Foo as Bar, Baz } from "changed";', - 'import Default, { Foo as Bar, Baz, } from "changed";', - 'import Default, { Foo, Bar as Baz } from "changed";', - 'import Default, { Foo, Bar as Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz } from "changed";', - 'import Default, { Foo, Bar, Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', - 'import Default from "Y";', - 'import * as All from \'z\';', - // import with support for new lines - "import { Foo,\n Bar }\n from 'changed';", - "import { \nFoo,\nBar,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz\n }\n from 'changed';", - "import { \nFoo as Bar,\n Baz\n, }\n from 'changed';", - "import { Foo,\n Bar as Baz\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "import { Foo,\n Bar,\n Baz }\n from 'changed';", - "import { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';", - "import Default,\n * as All from 'changed';", - "import Default,\n { } from 'changed';", - "import Default,\n { Foo\n }\n from 'changed';", - "import Default,\n { Foo,\n }\n from 'changed';", - "import Default,\n { Foo as Bar\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar\n } from\n 'changed';", - "import Default,\n { Foo,\n Bar,\n } from\n 'changed';", - "import Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';", - "import Default\n , { } from 'changed';", - // single line export - "export'x';", - "export 'changed';", - "export 'changed' ;", - "export Default from 'changed';", - "export * as All from 'changed';", - "export {} from 'changed';", - "export { } from 'changed';", - "export {Foo} from 'changed';", - "export { Foo } from 'changed';", - "export { Foo, } from 'changed';", - "export {Foo as Bar} from 'changed';", - "export { Foo as Bar } from 'changed';", - "export { Foo as Bar, } from 'changed';", - "export { Foo, Bar } from 'changed';", - "export { Foo, Bar, } from 'changed';", - "export { Foo as Bar, Baz } from 'changed';", - "export { Foo as Bar, Baz, } from 'changed';", - "export { Foo, Bar as Baz } from 'changed';", - "export { Foo, Bar as Baz, } from 'changed';", - "export { Foo as Bar, Baz as Qux } from 'changed';", - "export { Foo as Bar, Baz as Qux, } from 'changed';", - "export { Foo, Bar, Baz } from 'changed';", - "export { Foo, Bar, Baz, } from 'changed';", - "export { Foo as Bar, Baz, Qux } from 'changed';", - "export { Foo as Bar, Baz, Qux, } from 'changed';", - "export { Foo, Bar as Baz, Qux } from 'changed';", - "export { Foo, Bar as Baz, Qux, } from 'changed';", - "export { Foo, Bar, Baz as Qux } from 'changed';", - "export { Foo, Bar, Baz as Qux, } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "export { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "export { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "export { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "export { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", - "export Default, * as All from 'changed';", - "export Default, { } from 'changed';", - "export Default, { Foo } from 'changed';", - "export Default, { Foo, } from 'changed';", - "export Default, { Foo as Bar } from 'changed';", - "export Default, { Foo as Bar, } from 'changed';", - "export Default, { Foo, Bar } from 'changed';", - "export Default, { Foo, Bar, } from 'changed';", - "export Default, { Foo as Bar, Baz } from 'changed';", - "export Default, { Foo as Bar, Baz, } from 'changed';", - "export Default, { Foo, Bar as Baz } from 'changed';", - "export Default, { Foo, Bar as Baz, } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, } from 'changed';", - "export Default, { Foo, Bar, Baz } from 'changed';", - "export Default, { Foo, Bar, Baz, } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux, } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux, } from 'changed';", - "export Default, { Foo, Bar, Baz as Qux } from 'changed';", - "export Default, { Foo, Bar, Baz as Qux, } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", - "export Default , { } from 'changed';", - 'export "changed";', - 'export Default from "changed";', - 'export * as All from "changed";', - 'export { } from "changed";', - 'export { Foo } from "changed";', - 'export { Foo, } from "changed";', - 'export { Foo as Bar } from "changed";', - 'export { Foo as Bar, } from "changed";', - 'export { Foo, Bar } from "changed";', - 'export { Foo, Bar, } from "changed";', - 'export { Foo as Bar, Baz } from "changed";', - 'export { Foo as Bar, Baz, } from "changed";', - 'export { Foo, Bar as Baz } from "changed";', - 'export { Foo, Bar as Baz, } from "changed";', - 'export { Foo as Bar, Baz as Qux } from "changed";', - 'export { Foo as Bar, Baz as Qux, } from "changed";', - 'export { Foo, Bar, Baz } from "changed";', - 'export { Foo, Bar, Baz, } from "changed";', - 'export { Foo as Bar, Baz, Qux } from "changed";', - 'export { Foo as Bar, Baz, Qux, } from "changed";', - 'export { Foo, Bar as Baz, Qux } from "changed";', - 'export { Foo, Bar as Baz, Qux, } from "changed";', - 'export { Foo, Bar, Baz as Qux } from "changed";', - 'export { Foo, Bar, Baz as Qux, } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'export { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'export { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'export { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'export { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', - 'export Default, * as All from "changed";', - 'export Default, { } from "changed";', - 'export Default, { Foo } from "changed";', - 'export Default, { Foo, } from "changed";', - 'export Default, { Foo as Bar } from "changed";', - 'export Default, { Foo as Bar, } from "changed";', - 'export Default, { Foo, Bar } from "changed";', - 'export Default, { Foo, Bar, } from "changed";', - 'export Default, { Foo as Bar, Baz } from "changed";', - 'export Default, { Foo as Bar, Baz, } from "changed";', - 'export Default, { Foo, Bar as Baz } from "changed";', - 'export Default, { Foo, Bar as Baz, } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, } from "changed";', - 'export Default, { Foo, Bar, Baz } from "changed";', - 'export Default, { Foo, Bar, Baz, } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux, } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux, } from "changed";', - 'export Default, { Foo, Bar, Baz as Qux } from "changed";', - 'export Default, { Foo, Bar, Baz as Qux, } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', - 'export Default from "Y";', - 'export * as All from \'z\';', - // export with support for new lines - "export { Foo,\n Bar }\n from 'changed';", - "export { \nFoo,\nBar,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz\n }\n from 'changed';", - "export { \nFoo as Bar,\n Baz\n, }\n from 'changed';", - "export { Foo,\n Bar as Baz\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "export { Foo,\n Bar,\n Baz }\n from 'changed';", - "export { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "export { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "export { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';", - "export Default,\n * as All from 'changed';", - "export Default,\n { } from 'changed';", - "export Default,\n { Foo\n }\n from 'changed';", - "export Default,\n { Foo,\n }\n from 'changed';", - "export Default,\n { Foo as Bar\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar\n } from\n 'changed';", - "export Default,\n { Foo,\n Bar,\n } from\n 'changed';", - "export Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';", - "export Default\n , { } from 'changed';", + module: createModule('test module', ['x', 'y']), + name: 'test module', + code, + meta: {dependencyOffsets} + }).then(({code: processedCode}) => { + expect(processedCode).toEqual([ + '__d("test module", function(global, require, module, exports) {' + // require 'require("changed")', 'require("Y")', @@ -1045,6 +323,22 @@ describe('Resolver', function() { }); }); + pit('should pass through passed-in source maps', () => { + const module = createModule('test module'); + const resolutionResponse = new ResolutionResponseMock({ + dependencies: [module], + mainModuleId: 'test module', + }); + const inputMap = {version: 3, mappings: 'ARBITRARY'}; + return new Resolver({projectRoot: '/root'}).wrapModule({ + resolutionResponse, + module, + name: 'test module', + code: 'arbitrary(code)', + map: inputMap, + }).then(({map}) => expect(map).toBe(inputMap)); + }); + pit('should resolve polyfills', function () { const depResolver = new Resolver({ projectRoot: '/root', @@ -1053,17 +347,90 @@ describe('Resolver', function() { const code = [ 'global.fetch = () => 1;', ].join(''); - return depResolver.wrapModule( - null, - polyfill, + return depResolver.wrapModule({ + module: polyfill, code - ).then(processedCode => { - expect(processedCode.code).toEqual([ + }).then(({code: processedCode}) => { + expect(processedCode).toEqual([ '(function(global) {', 'global.fetch = () => 1;', "\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);", ].join('')); }); }); + + describe('JSON files:', () => { + const code = JSON.stringify({arbitrary: "data"}); + const id = 'arbitrary.json'; + let depResolver, module, resolutionResponse; + + beforeEach(() => { + depResolver = new Resolver({projectRoot: '/root'}); + module = createJsonModule(id); + resolutionResponse = new ResolutionResponseMock({ + dependencies: [module], + mainModuleId: id, + }); + }); + + pit('should prefix JSON files with `module.exports=`', () => { + return depResolver + .wrapModule({resolutionResponse, module, name: id, code}) + .then(({code: processedCode}) => + expect(processedCode).toEqual([ + `__d(${JSON.stringify(id)}, function(global, require, module, exports) {`, + `module.exports = ${code}\n});`, + ].join(''))); + }); + }); + + describe('minification:', () => { + const code ='arbitrary(code)'; + const id = 'arbitrary.js'; + let depResolver, minifyCode, module, resolutionResponse, sourceMap; + + beforeEach(() => { + minifyCode = jest.genMockFn().mockImpl((filename, code, map) => + Promise.resolve({code, map})); + depResolver = new Resolver({ + projectRoot: '/root', + minifyCode + }); + module = createModule(id); + module.path = '/arbitrary/path.js'; + resolutionResponse = new ResolutionResponseMock({ + dependencies: [module], + mainModuleId: id, + }); + sourceMap = {version: 3, sources: ['input'], mappings: 'whatever'}; + }); + + pit('should invoke the minifier with the wrapped code', () => { + const wrappedCode = `__d("${id}", function(global, require, module, exports) {${code}\n});` + return depResolver + .wrapModule({ + resolutionResponse, + module, + name: id, + code, + map: sourceMap, + minify: true + }).then(() => { + expect(minifyCode).toBeCalledWith(module.path, wrappedCode, sourceMap); + }); + }); + + pit('should use minified code', () => { + const minifiedCode = 'minified(code)'; + const minifiedMap = {version: 3, file: ['minified']}; + minifyCode.mockReturnValue(Promise.resolve({code: minifiedCode, map: minifiedMap})); + return depResolver + .wrapModule({resolutionResponse, module, name: id, code, minify: true}) + .then(({code, map}) => { + expect(code).toEqual(minifiedCode); + expect(map).toEqual(minifiedMap); + }); + }); + }); }); }); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index d60e0fb2..707402fa 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -12,7 +12,6 @@ const path = require('path'); const Activity = require('../Activity'); const DependencyGraph = require('node-haste'); -const replacePatterns = require('node-haste').replacePatterns; const declareOpts = require('../lib/declareOpts'); const Promise = require('promise'); @@ -48,6 +47,12 @@ const validateOpts = declareOpts({ type: 'object', required: true, }, + transformCode: { + type: 'function', + }, + minifyCode: { + type: 'function', + }, }); const getDependenciesValidateOpts = declareOpts({ @@ -97,8 +102,10 @@ class Resolver { fileWatcher: opts.fileWatcher, cache: opts.cache, shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios', + transformCode: opts.transformCode, }); + this._minifyCode = opts.minifyCode; this._polyfillModuleNames = opts.polyfillModuleNames || []; this._depGraph.load().catch(err => { @@ -119,12 +126,14 @@ class Resolver { return this._depGraph.getModuleForPath(entryFile); } - getDependencies(entryPath, options) { + getDependencies(entryPath, options, transformOptions, onProgress) { const {platform, recursive} = getDependenciesValidateOpts(options); return this._depGraph.getDependencies({ entryPath, platform, + transformOptions, recursive, + onProgress, }).then(resolutionResponse => { this._getPolyfillDependencies().reverse().forEach( polyfill => resolutionResponse.prependDependency(polyfill) @@ -176,16 +185,14 @@ class Resolver { ); } - resolveRequires(resolutionResponse, module, code) { + resolveRequires(resolutionResponse, module, code, dependencyOffsets = []) { return Promise.resolve().then(() => { - if (module.isPolyfill()) { - return Promise.resolve({code}); - } - const resolvedDeps = Object.create(null); const resolvedDepsArr = []; return Promise.all( + // here, we build a map of all require strings (relative and absolute) + // to the canonical name of the module they reference resolutionResponse.getResolvedDependencyPairs(module).map( ([depName, depModule]) => { if (depModule) { @@ -197,59 +204,81 @@ class Resolver { } ) ).then(() => { - const relativizeCode = (codeMatch, pre, quot, depName, post) => { + const relativizeCode = (codeMatch, quot, depName) => { + // if we have a canonical name for the module imported here, + // we use it, so that require() is always called with the same + // id for every module. + // Example: + // -- in a/b.js: + // require('./c') => require('a/c'); + // -- in b/index.js: + // require('../a/c') => require('a/c'); const depId = resolvedDeps[depName]; if (depId) { - return pre + quot + depId + post; + return quot + depId + quot; } else { return codeMatch; } }; - code = code - .replace(replacePatterns.IMPORT_RE, relativizeCode) - .replace(replacePatterns.EXPORT_RE, relativizeCode) - .replace(replacePatterns.REQUIRE_RE, relativizeCode); + code = dependencyOffsets.reduceRight((codeBits, offset) => { + const first = codeBits.shift(); + codeBits.unshift( + first.slice(0, offset), + first.slice(offset).replace(/(['"])([^'"']*)\1/, relativizeCode), + ); + return codeBits; + }, [code]); - return module.getName().then(name => { - return {name, code}; - }); + return code.join(''); }); }); } - wrapModule(resolutionResponse, module, code) { - if (module.isPolyfill()) { - return Promise.resolve({ - code: definePolyfillCode(code), - }); + wrapModule({ + resolutionResponse, + module, + name, + map, + code, + meta = {}, + minify = false + }) { + if (module.isJSON()) { + code = `module.exports = ${code}`; } + const result = module.isPolyfill() + ? Promise.resolve({code: definePolyfillCode(code)}) + : this.resolveRequires( + resolutionResponse, + module, + code, + meta.dependencyOffsets + ).then(code => ({code: defineModuleCode(name, code), map})); - return this.resolveRequires(resolutionResponse, module, code).then( - ({name, code}) => { - return {name, code: defineModuleCode(name, code)}; - }); + return minify + ? result.then(({code, map}) => this._minifyCode(module.path, code, map)) + : result; } getDebugInfo() { return this._depGraph.getDebugInfo(); } - } function defineModuleCode(moduleName, code) { return [ `__d(`, - `'${moduleName}',`, - 'function(global, require, module, exports) {', - ` ${code}`, + `${JSON.stringify(moduleName)}, `, + `function(global, require, module, exports) {`, + `${code}`, '\n});', ].join(''); } -function definePolyfillCode(code) { +function definePolyfillCode(code,) { return [ - '(function(global) {', + `(function(global) {`, code, `\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);`, ].join(''); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 5949bd0c..c1f20606 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -73,10 +73,6 @@ const validateOpts = declareOpts({ type: 'string', required: false, }, - disableInternalTransforms: { - type: 'boolean', - default: false, - }, }); const bundleOpts = declareOpts({ @@ -146,6 +142,10 @@ const dependencyOpts = declareOpts({ type: 'boolean', default: true, }, + hot: { + type: 'boolean', + default: false, + }, }); class Server { @@ -259,12 +259,7 @@ class Server { } const opts = dependencyOpts(options); - return this._bundler.getDependencies( - opts.entryFile, - opts.dev, - opts.platform, - opts.recursive, - ); + return this._bundler.getDependencies(opts); }); } diff --git a/react-packager/src/lib/ModuleTransport.js b/react-packager/src/lib/ModuleTransport.js index afb660d3..8ba0aaea 100644 --- a/react-packager/src/lib/ModuleTransport.js +++ b/react-packager/src/lib/ModuleTransport.js @@ -21,11 +21,7 @@ function ModuleTransport(data) { this.sourcePath = data.sourcePath; this.virtual = data.virtual; - - if (this.virtual && data.map) { - throw new Error('Virtual modules cannot have source maps'); - } - + this.meta = data.meta; this.map = data.map; Object.freeze(this); diff --git a/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js b/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js deleted file mode 100644 index 4d7d142c..00000000 --- a/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js +++ /dev/null @@ -1,115 +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'; - -jest.autoMockOff(); - -var deadModuleElimintation = require('../dead-module-elimination'); -var babel = require('babel-core'); - -const compile = (code) => - babel.transform(code, { - plugins: [deadModuleElimintation], - }).code; - -const compare = (source, output) => { - const out = trim(compile(source)) - // workaround babel/source map bug - .replace(/^false;/, ''); - - expect(out).toEqual(trim(output)); -}; - - -const trim = (str) => - str.replace(/\s/g, ''); - -describe('dead-module-elimination', () => { - it('should inline __DEV__', () => { - compare( - `global.__DEV__ = false; - var foo = __DEV__;`, - `var foo = false;` - ); - }); - - it('should accept unary operators with literals', () => { - compare( - `global.__DEV__ = !1; - var foo = __DEV__;`, - `var foo = false;` - ); - }); - - it('should kill dead branches', () => { - compare( - `global.__DEV__ = false; - if (__DEV__) { - doSomething(); - }`, - `` - ); - }); - - it('should kill unreferenced modules', () => { - compare( - `__d('foo', function() {})`, - `` - ); - }); - - it('should kill unreferenced modules at multiple levels', () => { - compare( - `__d('bar', function() {}); - __d('foo', function() { require('bar'); });`, - `` - ); - }); - - it('should kill modules referenced only from dead branches', () => { - compare( - `global.__DEV__ = false; - __d('bar', function() {}); - if (__DEV__) { require('bar'); }`, - `` - ); - }); - - it('should replace logical expressions with the result', () => { - compare( - `global.__DEV__ = false; - __d('bar', function() {}); - __DEV__ && require('bar');`, - `false;` - ); - }); - - it('should keep if result branch', () => { - compare( - `global.__DEV__ = false; - __d('bar', function() {}); - if (__DEV__) { - killWithFire(); - } else { - require('bar'); - }`, - `__d('bar', function() {}); - require('bar');` - ); - }); - - it('should replace falsy ternaries with alternate expression', () => { - compare( - `global.__DEV__ = false; - __DEV__ ? foo() : bar(); - `, - `bar();` - ); - }); -}); diff --git a/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js b/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js deleted file mode 100644 index b5f33b4e..00000000 --- a/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js +++ /dev/null @@ -1,148 +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'; - -const t = require('babel-types'); - -var globals = Object.create(null); -var requires = Object.create(null); -var _requires; - -const hasDeadModules = modules => - Object.keys(modules).some(key => modules[key] === 0); - -function CallExpression(path) { - const { node } = path; - const fnName = node.callee.name; - - if (fnName === 'require' || fnName === '__d') { - var moduleName = node.arguments[0].value; - if (fnName === '__d' && _requires && !_requires[moduleName]) { - path.remove(); - } else if (fnName === '__d'){ - requires[moduleName] = requires[moduleName] || 0; - } else { - requires[moduleName] = (requires[moduleName] || 0) + 1; - } - } -} - -function IfStatement(path) { - const { node } = path; - - if (node.test.type === 'Identifier' && node.test.name in globals) { - if (globals[node.test.name]) { - if (node.consequent.type === 'BlockStatement') { - path.replaceWithMultiple(node.consequent.body); - } else { - path.replaceWith(node.consequent); - } - } else if (node.alternate) { - if (node.alternate.type === 'BlockStatement') { - path.replaceWithMultiple(node.alternate.body); - } else { - path.replaceWith(node.alternate); - } - } else { - path.remove(); - } - } - } - -module.exports = function () { - var firstPass = { - AssignmentExpression(path) { - const { node } = path; - - if ( - node.left.type === 'MemberExpression' && - node.left.object.name === 'global' && - node.left.property.type === 'Identifier' && - node.left.property.name === '__DEV__' - ) { - var value; - if (node.right.type === 'BooleanLiteral') { - value = node.right.value; - } else if ( - node.right.type === 'UnaryExpression' && - node.right.operator === '!' && - node.right.argument.type === 'NumericLiteral' - ) { - value = !node.right.argument.value; - } else { - return; - } - globals[node.left.property.name] = value; - - // workaround babel/source map bug - the minifier should strip it - path.replaceWith(t.booleanLiteral(value)); - - //path.remove(); - //scope.removeBinding(node.left.name); - } - }, - IfStatement, - ConditionalExpression: IfStatement, - Identifier(path) { - const { node } = path; - - var parent = path.parent; - if (parent.type === 'AssignmentExpression' && parent.left === node) { - return; - } - - if (node.name in globals) { - path.replaceWith(t.booleanLiteral(globals[node.name])); - } - }, - - CallExpression, - - LogicalExpression(path) { - const { node } = path; - - if (node.left.type === 'Identifier' && node.left.name in globals) { - const value = globals[node.left.name]; - - if (node.operator === '&&') { - if (value) { - path.replaceWith(node.right); - } else { - path.replaceWith(t.booleanLiteral(value)); - } - } else if (node.operator === '||') { - if (value) { - path.replaceWith(t.booleanLiteral(value)); - } else { - path.replaceWith(node.right); - } - } - } - } - }; - - var secondPass = { - CallExpression, - }; - - return { - visitor: { - Program(path) { - path.traverse(firstPass); - var counter = 0; - while (hasDeadModules(requires) && counter < 3) { - _requires = requires; - requires = {}; - path.traverse(secondPass); - counter++; - } - } - } - }; -}; diff --git a/react-packager/src/transforms/whole-program-optimisations/index.js b/react-packager/src/transforms/whole-program-optimisations/index.js deleted file mode 100644 index f802c0f7..00000000 --- a/react-packager/src/transforms/whole-program-optimisations/index.js +++ /dev/null @@ -1,14 +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'; - -// Return the list of plugins use for Whole Program Optimisations -module.exports = [ - require('./dead-module-elimination'), -]; diff --git a/transformer.js b/transformer.js index 071f4007..18da16f2 100644 --- a/transformer.js +++ b/transformer.js @@ -61,7 +61,7 @@ const getBabelRC = (function() { } return babelRC; - } + }; })(); /** @@ -81,14 +81,16 @@ function buildBabelConfig(filename, options) { // Add extra plugins const extraPlugins = [externalHelpersPlugin]; - if (options.inlineRequires) { + var inlineRequires = options.inlineRequires; + var blacklist = inlineRequires && inlineRequires.blacklist; + if (inlineRequires && !(blacklist && filename in blacklist)) { extraPlugins.push(inlineRequiresPlugin); } config.plugins = extraPlugins.concat(config.plugins); if (options.hot) { - const hmrConfig = makeHMRConfig(options); + const hmrConfig = makeHMRConfig(options, filename); config = Object.assign({}, config, hmrConfig); } @@ -102,7 +104,9 @@ function transform(src, filename, options) { const result = babel.transform(src, babelConfig); return { + ast: result.ast, code: result.code, + map: result.map, filename: filename, }; } From 3f072d22ea46b4794e01c0ee5de4fc04653e9374 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 8 Mar 2016 10:38:48 -0800 Subject: [PATCH 609/936] Don't rebuild bundles automatically on file changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Don’t rebuild bundles automatically after they have been requested once. This helps to not lock developer machines. Reviewed By: martinbigio Differential Revision: D3019751 fb-gh-sync-id: 98367b4fb89c5ae22c00444eabc1194ba6832dba shipit-source-id: 98367b4fb89c5ae22c00444eabc1194ba6832dba --- .../src/Server/__tests__/Server-test.js | 6 ++--- react-packager/src/Server/index.js | 26 +------------------ 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index a2248842..796f281a 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -197,7 +197,7 @@ describe('processRequest', () => { }); }); - it('rebuilds the bundles that contain a file when that file is changed', () => { + it('does not rebuild the bundles that contain a file when that file is changed', () => { const bundleFunc = jest.genMockFunction(); bundleFunc .mockReturnValueOnce( @@ -233,7 +233,7 @@ describe('processRequest', () => { jest.runAllTimers(); jest.runAllTicks(); - expect(bundleFunc.mock.calls.length).toBe(2); + expect(bundleFunc.mock.calls.length).toBe(1); makeRequest(requestHandler, 'mybundle.bundle?runModule=true') .done(response => @@ -242,7 +242,7 @@ describe('processRequest', () => { jest.runAllTicks(); }); - it('rebuilds the bundles that contain a file when that file is changed, even when hot loading is enabled', () => { + it('does not rebuild the bundles that contain a file when that file is changed, even when hot loading is enabled', () => { const bundleFunc = jest.genMockFunction(); bundleFunc .mockReturnValueOnce( diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index c1f20606..985516be 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -197,7 +197,7 @@ class Server { this._fileWatcher.on('all', this._onFileChange.bind(this)); this._debouncedFileChangeHandler = _.debounce(filePath => { - this._rebuildBundles(filePath); + this._clearBundles(); this._informChangeWatchers(); }, 50); } @@ -293,30 +293,6 @@ class Server { this._bundles = Object.create(null); } - _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, - dev: options.dev, - }); - return p; - }); - }); - return bundles[optionsJson]; - }); - } - _informChangeWatchers() { const watchers = this._changeWatchers; const headers = { From faaafd771e476cb21203fd39f0a07becb2299305 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 8 Mar 2016 12:21:25 -0800 Subject: [PATCH 610/936] Make uglify not append //# sourceMappingURL= Summary: We also need a more recent version of uglify that supports this Reviewed By: martinbigio Differential Revision: D3024959 fb-gh-sync-id: f9efdddceda4f726567c39884c844a8e74e6e09d shipit-source-id: f9efdddceda4f726567c39884c844a8e74e6e09d --- .../src/JSTransformer/worker/__tests__/minify-test.js | 2 +- react-packager/src/JSTransformer/worker/minify.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/JSTransformer/worker/__tests__/minify-test.js b/react-packager/src/JSTransformer/worker/__tests__/minify-test.js index 78450879..184a4aef 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/minify-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/minify-test.js @@ -39,7 +39,7 @@ describe('Minification:', () => { expect(uglify.minify).toBeCalledWith(code, objectContaining({ fromString: true, inSourceMap: map, - outSourceMap: filename, + outSourceMap: true, })); }); diff --git a/react-packager/src/JSTransformer/worker/minify.js b/react-packager/src/JSTransformer/worker/minify.js index 089cfc78..680cb885 100644 --- a/react-packager/src/JSTransformer/worker/minify.js +++ b/react-packager/src/JSTransformer/worker/minify.js @@ -14,7 +14,7 @@ function minify(filename, code, sourceMap) { const minifyResult = uglify.minify(code, { fromString: true, inSourceMap: sourceMap, - outSourceMap: filename, + outSourceMap: true, output: { ascii_only: true, screw_ie8: true, From 4a5cbbbec86e54ff1d7bdf849bef93a619abba21 Mon Sep 17 00:00:00 2001 From: Kureev Alexey Date: Wed, 9 Mar 2016 03:08:25 -0800 Subject: [PATCH 611/936] Replace underscore by lodash Summary:As far as we agreed to merge `rnpm` into react-native core, we need to align our dependencies to exclude duplications. One of the steps forward would be to use the same utilities library. According to the thread on fb, everybody is fine with replacing underscore by lodash (which we use internally for rnpm). So, here we go! cc mkonicek davidaurelio grabbou **Test plan** ``` $ npm test ``` ![image](https://cloud.githubusercontent.com/assets/2273613/13173972/ee34c922-d700-11e5-971b-68ff7322b6d6.png) **Code formatting** Changes are aligned with the current [style guide](https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#style-guide). Closes https://github.com/facebook/react-native/pull/6030 Differential Revision: D3016271 Pulled By: davidaurelio fb-gh-sync-id: c4f6776a7de7470283d3ca5a8b56e423247f5e45 shipit-source-id: c4f6776a7de7470283d3ca5a8b56e423247f5e45 --- react-packager/index.js | 11 ++++++++++- react-packager/src/Bundler/Bundle.js | 4 ++-- react-packager/src/Bundler/BundleBase.js | 3 +-- react-packager/src/Bundler/HMRBundle.js | 1 - react-packager/src/Bundler/__tests__/Bundler-test.js | 2 +- .../__mocks__/{underscore.js => lodash.js} | 2 +- .../src/Resolver/__tests__/Resolver-test.js | 11 ++++------- react-packager/src/Server/__tests__/Server-test.js | 2 +- react-packager/src/Server/index.js | 4 ++-- .../SocketInterface/__tests__/SocketInterface-test.js | 1 - react-packager/src/SocketInterface/index.js | 3 +-- 11 files changed, 23 insertions(+), 21 deletions(-) rename react-packager/src/JSTransformer/__mocks__/{underscore.js => lodash.js} (89%) diff --git a/react-packager/index.js b/react-packager/index.js index a392bdf5..dce805c8 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -14,7 +14,6 @@ require('fast-path').replace(); useGracefulFs(); var debug = require('debug'); -var omit = require('underscore').omit; var Activity = require('./src/Activity'); exports.createServer = createServer; @@ -118,6 +117,16 @@ function createNonPersistentServer(options) { return createServer(options); } +function omit(obj, blacklistedKeys) { + return Object.keys(obj).reduce((clone, key) => { + if (blacklistedKeys.indexOf(key) === -1) { + clone[key] = obj[key]; + } + + return clone; + }, {}); +} + // we need to listen on a socket as soon as a server is created, but only once. // This file also serves as entry point when spawning a socket server; in that // case we need to start the server immediately. diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index f4247027..e53d3c3a 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -8,7 +8,7 @@ */ 'use strict'; -const _ = require('underscore'); +const _ = require('lodash'); const base64VLQ = require('./base64-vlq'); const BundleBase = require('./BundleBase'); const ModuleTransport = require('../lib/ModuleTransport'); @@ -143,7 +143,7 @@ class Bundle extends BundleBase { if (options.excludeSource) { if (map.sourcesContent && map.sourcesContent.length) { - map = _.extend({}, map, {sourcesContent: []}); + map = Object.assign({}, map, {sourcesContent: []}); } } diff --git a/react-packager/src/Bundler/BundleBase.js b/react-packager/src/Bundler/BundleBase.js index d3794a43..b2f2d251 100644 --- a/react-packager/src/Bundler/BundleBase.js +++ b/react-packager/src/Bundler/BundleBase.js @@ -8,7 +8,6 @@ */ 'use strict'; -const _ = require('underscore'); const ModuleTransport = require('../lib/ModuleTransport'); class BundleBase { @@ -66,7 +65,7 @@ class BundleBase { return this._source; } - this._source = _.pluck(this._modules, 'code').join('\n'); + this._source = this._modules.map((module) => module.code).join('\n'); return this._source; } diff --git a/react-packager/src/Bundler/HMRBundle.js b/react-packager/src/Bundler/HMRBundle.js index d9725eea..90d6e3b3 100644 --- a/react-packager/src/Bundler/HMRBundle.js +++ b/react-packager/src/Bundler/HMRBundle.js @@ -8,7 +8,6 @@ */ 'use strict'; -const _ = require('underscore'); const BundleBase = require('./BundleBase'); const ModuleTransport = require('../lib/ModuleTransport'); diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index e108177e..ac37db89 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -11,7 +11,7 @@ jest .setMock('worker-farm', () => () => undefined) .dontMock('node-haste/node_modules/throat') - .dontMock('underscore') + .dontMock('lodash') .dontMock('../../lib/ModuleTransport') .setMock('uglify-js') .dontMock('../'); diff --git a/react-packager/src/JSTransformer/__mocks__/underscore.js b/react-packager/src/JSTransformer/__mocks__/lodash.js similarity index 89% rename from react-packager/src/JSTransformer/__mocks__/underscore.js rename to react-packager/src/JSTransformer/__mocks__/lodash.js index 45754e1a..ac8224e6 100644 --- a/react-packager/src/JSTransformer/__mocks__/underscore.js +++ b/react-packager/src/JSTransformer/__mocks__/lodash.js @@ -10,4 +10,4 @@ // Bug with Jest because we're going to the node_modules that is a sibling // of what jest thinks our root (the dir with the package.json) should be. -module.exports = require.requireActual('underscore'); +module.exports = require.requireActual('lodash'); diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index df6f609e..286dc9eb 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -8,16 +8,13 @@ */ 'use strict'; -jest.dontMock('../') - .dontMock('underscore'); - +jest.dontMock('../'); jest.mock('path'); const Promise = require('promise'); const Resolver = require('../'); const path = require('path'); -const _ = require('underscore'); let DependencyGraph = jest.genMockFn(); jest.setMock('node-haste', DependencyGraph); @@ -126,9 +123,9 @@ describe('Resolver', function() { .then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies[result.dependencies.length - 1]).toBe(module); - expect(_.pluck(DependencyGraph.prototype.createPolyfill.mock.calls, 0)).toEqual([ - { file: 'polyfills/polyfills.js', - id: 'polyfills/polyfills.js', + expect(DependencyGraph.prototype.createPolyfill.mock.calls.map((call) => call[0])).toEqual([ + { id: 'polyfills/polyfills.js', + file: 'polyfills/polyfills.js', dependencies: [] }, { id: 'polyfills/console.js', diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 796f281a..ce91842e 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -11,7 +11,7 @@ jest.setMock('worker-farm', function() { return () => {}; }) .dontMock('node-haste/node_modules/throat') .dontMock('os') - .dontMock('underscore') + .dontMock('lodash') .dontMock('path') .dontMock('url') .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) }) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 985516be..fe49ebbb 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -15,7 +15,7 @@ const getPlatformExtension = require('node-haste').getPlatformExtension; const Bundler = require('../Bundler'); const Promise = require('promise'); -const _ = require('underscore'); +const _ = require('lodash'); const declareOpts = require('../lib/declareOpts'); const path = require('path'); const url = require('url'); @@ -492,7 +492,7 @@ class Server { return true; }).join('.') + '.js'; - const sourceMapUrlObj = _.clone(urlObj); + const sourceMapUrlObj = Object.assign({}, urlObj); sourceMapUrlObj.pathname = pathname.replace(/\.bundle$/, '.map'); // try to get the platform from the url diff --git a/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js b/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js index b5a7b916..4c1aa812 100644 --- a/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js +++ b/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js @@ -10,7 +10,6 @@ jest.setMock('uglify-js') .mock('child_process') - .dontMock('underscore') .dontMock('../'); var SocketInterface = require('../'); diff --git a/react-packager/src/SocketInterface/index.js b/react-packager/src/SocketInterface/index.js index 69b52a4f..f9f3a77b 100644 --- a/react-packager/src/SocketInterface/index.js +++ b/react-packager/src/SocketInterface/index.js @@ -11,7 +11,6 @@ const Promise = require('promise'); const SocketClient = require('./SocketClient'); const SocketServer = require('./SocketServer'); -const _ = require('underscore'); const crypto = require('crypto'); const debug = require('debug')('ReactNativePackager:SocketInterface'); const fs = require('fs'); @@ -99,7 +98,7 @@ function createServer(resolve, reject, options, sockPath) { const log = fs.openSync(logPath, 'a'); // Enable server debugging by default since it's going to a log file. - const env = _.clone(process.env); + const env = Object.assign({}, process.env); env.DEBUG = 'ReactNativePackager:SocketServer'; // We have to go through the main entry point to make sure From c17a8e48cf185b5114598d2c9b1268c64252e378 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 9 Mar 2016 08:29:19 -0800 Subject: [PATCH 612/936] Make order of bundle transports deterministic Summary:After starting to minify off the main process, the order of module transport objects in `Bundle` instances became less deterministic. These changes guarantee that module transports appear in addition order, not in order of minification completion. Reviewed By: bestander Differential Revision: D3029588 fb-gh-sync-id: 80e83c05d7f78ed7e69583d7e3aa2831bd5ae4d0 shipit-source-id: 80e83c05d7f78ed7e69583d7e3aa2831bd5ae4d0 --- react-packager/src/Bundler/Bundle.js | 4 +- react-packager/src/Bundler/BundleBase.js | 12 +++++- .../src/Bundler/__tests__/Bundle-test.js | 41 ++++++++++++++++++- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index e53d3c3a..2a947f29 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -31,6 +31,7 @@ class Bundle extends BundleBase { } addModule(resolver, resolutionResponse, module, moduleTransport) { + const index = super.addModule(moduleTransport); return resolver.wrapModule({ resolutionResponse, module, @@ -46,7 +47,8 @@ class Bundle extends BundleBase { this._shouldCombineSourceMaps = true; } - super.addModule(new ModuleTransport({...moduleTransport, code, map})); + this.replaceModuleAt( + index, new ModuleTransport({...moduleTransport, code, map})); }); } diff --git a/react-packager/src/Bundler/BundleBase.js b/react-packager/src/Bundler/BundleBase.js index b2f2d251..472db21f 100644 --- a/react-packager/src/Bundler/BundleBase.js +++ b/react-packager/src/Bundler/BundleBase.js @@ -31,11 +31,19 @@ class BundleBase { } addModule(module) { - if (!module instanceof ModuleTransport) { + if (!(module instanceof ModuleTransport)) { throw new Error('Expeceted a ModuleTransport object'); } - this._modules.push(module); + return this._modules.push(module) - 1; + } + + replaceModuleAt(index, module) { + if (!(module instanceof ModuleTransport)) { + throw new Error('Expeceted a ModuleTransport object'); + } + + this._modules[index] = module; } getModules() { diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 87a5a432..173c9a32 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -11,6 +11,7 @@ jest.autoMockOff(); const Bundle = require('../Bundle'); +const ModuleTransport = require('../../lib/ModuleTransport'); const Promise = require('Promise'); const SourceMapGenerator = require('source-map').SourceMapGenerator; const crypto = require('crypto'); @@ -106,6 +107,35 @@ describe('Bundle', () => { ].join('\n')); }); }); + + fpit('should insert modules in a deterministic order, independent from timing of the wrapping process', () => { + const moduleTransports = [ + createModuleTransport({name: 'module1'}), + createModuleTransport({name: 'module2'}), + createModuleTransport({name: 'module3'}), + ]; + + const resolves = {}; + const resolver = { + wrapModule({name}) { + return new Promise(resolve => resolves[name] = resolve); + } + }; + + console.log(bundle.addModule+'') + const promise = Promise.all( + moduleTransports.map(m => bundle.addModule(resolver, null, null, m))) + .then(() => { + expect(bundle.getModules()) + .toEqual(moduleTransports); + }); + + resolves.module2({code: ''}); + resolves.module3({code: ''}); + resolves.module1({code: ''}); + + return promise; + }); }); describe('sourcemap bundle', () => { @@ -351,6 +381,15 @@ function addModule({bundle, code, sourceCode, sourcePath, map, virtual}) { resolverFor(code, map), null, null, - {code, sourceCode, sourcePath, map, virtual} + createModuleTransport({code, sourceCode, sourcePath, map, virtual}) ); } + +function createModuleTransport(data) { + return new ModuleTransport({ + code: '', + sourceCode: '', + sourcePath: '', + ...data, + }); +} From ec24c63e86a98cf8b117eedb6329980564d7b5f8 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Fri, 11 Mar 2016 05:59:47 -0800 Subject: [PATCH 613/936] Update node-haste and replace fast-path with node-haste's version to fix Windows compatibility Summary:This is the last bits needed to fix Windows compatibility on master, most of the work was done in node-haste. **Test plan** Run npm test Run the packager using Windows and Mac cc cpojer davidaurelio Closes https://github.com/facebook/react-native/pull/6260 Reviewed By: dmmiller, bestander Differential Revision: D3005397 Pulled By: davidaurelio fb-gh-sync-id: e16847808ebfa8b234315b2093dba204c9c1e869 shipit-source-id: e16847808ebfa8b234315b2093dba204c9c1e869 --- react-packager/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/index.js b/react-packager/index.js index dce805c8..49848ede 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -10,7 +10,7 @@ require('../babelRegisterOnly')([/react-packager\/src/]); -require('fast-path').replace(); +require('node-haste/lib/fastpath').replace(); useGracefulFs(); var debug = require('debug'); From 0fe44bd1ab385936eec38f0ca11cbf067b5bd697 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Fri, 11 Mar 2016 06:38:45 -0800 Subject: [PATCH 614/936] Remove debug code from tests + re-enable tests Reviewed By: bestander Differential Revision: D3040961 fb-gh-sync-id: 4e49334fb1d3f80f7a701b88088011f33cb9df6e shipit-source-id: 4e49334fb1d3f80f7a701b88088011f33cb9df6e --- react-packager/src/Bundler/__tests__/Bundle-test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 173c9a32..2235ba50 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -108,7 +108,7 @@ describe('Bundle', () => { }); }); - fpit('should insert modules in a deterministic order, independent from timing of the wrapping process', () => { + pit('should insert modules in a deterministic order, independent from timing of the wrapping process', () => { const moduleTransports = [ createModuleTransport({name: 'module1'}), createModuleTransport({name: 'module2'}), @@ -122,7 +122,6 @@ describe('Bundle', () => { } }; - console.log(bundle.addModule+'') const promise = Promise.all( moduleTransports.map(m => bundle.addModule(resolver, null, null, m))) .then(() => { From c46d6cc2393fb97703dd49ba641a861c694b0fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Sat, 12 Mar 2016 10:58:55 -0800 Subject: [PATCH 615/936] Avoid hardcoding platform on blacklist Reviewed By: davidaurelio Differential Revision: D3042906 fb-gh-sync-id: 4a424ef1012d75d06c830b284806aefd1556ff74 shipit-source-id: 4a424ef1012d75d06c830b284806aefd1556ff74 --- rn-cli.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rn-cli.config.js b/rn-cli.config.js index f0f85680..c278051e 100644 --- a/rn-cli.config.js +++ b/rn-cli.config.js @@ -17,8 +17,8 @@ module.exports = { return this._getRoots(); }, - getBlacklistRE() { - return blacklist(''); + getBlacklistRE(platform) { + return blacklist(platform); }, _getRoots() { From 44f7df4432d3c9c72808bcf64f5f7484706de69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Sun, 13 Mar 2016 11:13:40 -0700 Subject: [PATCH 616/936] Sourcemaps support for RAM Summary:This rev adds support for production sourcemaps on RAM. When we inject a module into JSC we use the original `sourceURL` and specify the `startingLineNumber` of the module relative to a "regular" bundle. By doing so, when an error is thrown, JSC will include the provided `sourceURL` as the filename and will use the indicated `startingLineNumber` to figure out on which line the error actually occurred. To make things a bit simpler and avoid having to deal with columns, we tweak the generated bundle so that each module starts on a new line. Since we cannot assure that each module's code will be on a single line as the minifier might break it on multiple (UglifyJS does so due to a bug on old versions of Chrome), we include on the index the line number that should be used when invoking `JSEvaluateScript`. Since the module length was not being used we replaced the placeholder we have there for the line number. Reviewed By: javache Differential Revision: D2997520 fb-gh-sync-id: 3243a489cbb5b48a963f4ccdd98ba63b30f53f3f shipit-source-id: 3243a489cbb5b48a963f4ccdd98ba63b30f53f3f --- react-packager/src/Bundler/Bundle.js | 19 +++++++------- .../src/Bundler/__tests__/Bundle-test.js | 15 ++++++++--- .../src/Bundler/__tests__/Bundler-test.js | 1 + react-packager/src/Bundler/index.js | 26 ++++++++++++++++--- react-packager/src/Resolver/index.js | 4 +++ react-packager/src/lib/ModuleTransport.js | 1 + 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 2a947f29..4771f678 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -16,9 +16,6 @@ const crypto = require('crypto'); const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL='; -const getCode = x => x.code; -const getNameAndCode = ({name, code}) => ({name, code}); - class Bundle extends BundleBase { constructor({sourceMapUrl, minify} = {}) { super(); @@ -40,6 +37,7 @@ class Bundle extends BundleBase { map: moduleTransport.map, meta: moduleTransport.meta, minify: this._minify, + polyfill: module.isPolyfill(), }).then(({code, map}) => { // If we get a map from the transformer we'll switch to a mode // were we're combining the source maps as opposed to @@ -113,20 +111,21 @@ class Bundle extends BundleBase { const modules = allModules .splice(prependedModules, allModules.length - requireCalls - prependedModules); - const startupCode = allModules.map(getCode).join('\n'); + const startupCode = allModules.map(({code}) => code).join('\n'); return { startupCode, - modules: modules.map(getNameAndCode) + modules: modules.map(({name, code, polyfill}) => + ({name, code, polyfill}) + ), }; } /** - * 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. + * Combine each of the sourcemaps multiple modules have into a single big + * one. This works well thanks to a neat trick defined on the sourcemap spec + * that makes use of of the `sections` field to combine sourcemaps by adding + * an offset. This is supported only by Chrome for now. */ _getCombinedSourceMaps(options) { const result = { diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index 2235ba50..d6fa569f 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -123,7 +123,7 @@ describe('Bundle', () => { }; const promise = Promise.all( - moduleTransports.map(m => bundle.addModule(resolver, null, null, m))) + moduleTransports.map(m => bundle.addModule(resolver, null, {isPolyfill: () => false}, m))) .then(() => { expect(bundle.getModules()) .toEqual(moduleTransports); @@ -375,12 +375,19 @@ function resolverFor(code, map) { }; } -function addModule({bundle, code, sourceCode, sourcePath, map, virtual}) { +function addModule({bundle, code, sourceCode, sourcePath, map, virtual, polyfill}) { return bundle.addModule( resolverFor(code, map), null, - null, - createModuleTransport({code, sourceCode, sourcePath, map, virtual}) + {isPolyfill: () => polyfill}, + createModuleTransport({ + code, + sourceCode, + sourcePath, + map, + virtual, + polyfill, + }), ); } diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index ac37db89..e1a67b2d 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -204,6 +204,7 @@ describe('Bundler', function() { transform: { dev: true, hot: false, + generateSourceMaps: false, projectRoots, } }, diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 3c1c426d..83296807 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -230,8 +230,9 @@ class Bundler { platform, moduleSystemDeps = [], hot, + unbundle, entryModuleOnly, - resolutionResponse + resolutionResponse, }) { const onResolutionResponse = response => { bundle.setMainModuleId(response.mainModuleId); @@ -265,6 +266,7 @@ class Bundler { platform, bundle, hot, + unbundle, resolutionResponse, onResolutionResponse, finalizeBundle, @@ -316,6 +318,7 @@ class Bundler { platform, bundle, hot, + unbundle, resolutionResponse, onResolutionResponse = noop, onModuleTransformed = noop, @@ -336,8 +339,15 @@ class Bundler { }; } - resolutionResponse = this.getDependencies( - {entryFile, dev, platform, hot, onProgess, minify}); + resolutionResponse = this.getDependencies({ + entryFile, + dev, + platform, + hot, + onProgess, + minify, + generateSourceMaps: unbundle, + }); } return Promise.resolve(resolutionResponse).then(response => { @@ -391,10 +401,18 @@ class Bundler { minify = !dev, hot = false, recursive = true, + generateSourceMaps = false, onProgess, }) { return this.getTransformOptions( - entryFile, {dev, platform, hot, projectRoots: this._projectRoots} + entryFile, + { + dev, + platform, + hot, + generateSourceMaps, + projectRoots: this._projectRoots, + }, ).then(transformSpecificOptions => { const transformOptions = { minify, diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 707402fa..6bd502c7 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -261,6 +261,10 @@ class Resolver { : result; } + minifyModule({path, code, map}) { + return this._minifyCode(path, code, map); + } + getDebugInfo() { return this._depGraph.getDebugInfo(); } diff --git a/react-packager/src/lib/ModuleTransport.js b/react-packager/src/lib/ModuleTransport.js index 8ba0aaea..d5e5032a 100644 --- a/react-packager/src/lib/ModuleTransport.js +++ b/react-packager/src/lib/ModuleTransport.js @@ -22,6 +22,7 @@ function ModuleTransport(data) { this.virtual = data.virtual; this.meta = data.meta; + this.polyfill = data.polyfill; this.map = data.map; Object.freeze(this); From 482e2fea0e1be64d166c4d4d8f777839d35ecdb7 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 14 Mar 2016 16:16:22 -0700 Subject: [PATCH 617/936] Bring back "Use numeric identifiers when building a bundle" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary:This brings back "Use numeric identifiers when building a bundle", previously backed out. This version passes on the correct entry module name to code that decides transform options. Original Description: Since the combination of node and haste modules (and modules that can be required as both node and haste module) can lead to situations where it’s impossible to decide an unambiguous module identifier, this diff switches all module ids to integers. Each integer maps to an absolute path to a JS file on disk. We also had a problem, where haste modules outside and inside node_modules could end up with the same module identifier. This problem has not manifested yet, because the last definition of a module wins. It becomes a problem when writing file-based unbundle modules to disk: the same file might be written to concurrently, leading to invalid code. Using indexed modules will also help indexed file unbundles, as we can encode module IDs as integers rather than scanning string IDs. Reviewed By: martinbigio Differential Revision: D2855202 fb-gh-sync-id: 9a011bc403690e1522b723e5742bef148a9efb52 shipit-source-id: 9a011bc403690e1522b723e5742bef148a9efb52 --- package.json | 2 +- react-packager/src/Bundler/Bundle.js | 5 +- react-packager/src/Bundler/HMRBundle.js | 13 ++- .../src/Bundler/__tests__/Bundle-test.js | 1 + react-packager/src/Bundler/index.js | 61 ++++++++-- .../src/Resolver/__tests__/Resolver-test.js | 48 ++++++-- react-packager/src/Resolver/index.js | 107 +++++++++--------- .../Resolver/polyfills/require-unbundle.js | 4 +- .../src/Resolver/polyfills/require.js | 29 +++-- react-packager/src/lib/ModuleTransport.js | 3 + 10 files changed, 181 insertions(+), 92 deletions(-) diff --git a/package.json b/package.json index ab25c5cf..b58ac91e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.2.0", + "version": "0.3.0", "name": "react-native-packager", "description": "Build native apps with React!", "repository": { diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 4771f678..af265ebb 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -37,7 +37,6 @@ class Bundle extends BundleBase { map: moduleTransport.map, meta: moduleTransport.meta, minify: this._minify, - polyfill: module.isPolyfill(), }).then(({code, map}) => { // If we get a map from the transformer we'll switch to a mode // were we're combining the source maps as opposed to @@ -65,10 +64,11 @@ class Bundle extends BundleBase { } _addRequireCall(moduleId) { - const code = ';require("' + moduleId + '");'; + const code = `;require(${JSON.stringify(moduleId)});`; const name = 'require-' + moduleId; super.addModule(new ModuleTransport({ name, + id: this._numRequireCalls - 1, code, virtual: true, sourceCode: code, @@ -118,6 +118,7 @@ class Bundle extends BundleBase { modules: modules.map(({name, code, polyfill}) => ({name, code, polyfill}) ), + modules, }; } diff --git a/react-packager/src/Bundler/HMRBundle.js b/react-packager/src/Bundler/HMRBundle.js index 90d6e3b3..caf3ec67 100644 --- a/react-packager/src/Bundler/HMRBundle.js +++ b/react-packager/src/Bundler/HMRBundle.js @@ -21,16 +21,17 @@ class HMRBundle extends BundleBase { } addModule(resolver, response, module, moduleTransport) { - return resolver.resolveRequires( + const code = resolver.resolveRequires( response, module, moduleTransport.code, moduleTransport.meta.dependencyOffsets, - ).then(code => { - super.addModule(new ModuleTransport({...moduleTransport, code})); - this._sourceMappingURLs.push(this._sourceMappingURLFn(moduleTransport.sourcePath)); - this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath)); - }); + ); + + super.addModule(new ModuleTransport({...moduleTransport, code})); + this._sourceMappingURLs.push(this._sourceMappingURLFn(moduleTransport.sourcePath)); + this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath)); + return Promise.resolve(); } getModulesNamesAndCode() { diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index d6fa569f..c560d25b 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -394,6 +394,7 @@ function addModule({bundle, code, sourceCode, sourcePath, map, virtual, polyfill function createModuleTransport(data) { return new ModuleTransport({ code: '', + id: '', sourceCode: '', sourcePath: '', ...data, diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 83296807..3ec53f34 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -106,6 +106,8 @@ class Bundler { mtime, ]; + this._getModuleId = createModuleIdFactory(); + if (opts.transformModulePath) { const transformer = require(opts.transformModulePath); if (typeof transformer.cacheKey !== 'undefined') { @@ -131,6 +133,7 @@ class Bundler { fileWatcher: opts.fileWatcher, assetExts: opts.assetExts, cache: this._cache, + getModuleId: this._getModuleId, transformCode: (module, code, options) => this._transformer.transformFile(module.path, code, options), @@ -208,6 +211,7 @@ class Bundler { hmrBundle(options, host, port) { return this._bundle({ + ...options, bundle: new HMRBundle({ sourceURLFn: this._sourceHMRURL.bind(this, options.platform, host, port), sourceMappingURLFn: this._sourceMappingHMRURL.bind( @@ -216,7 +220,7 @@ class Bundler { ), }), hot: true, - ...options, + dev: true, }); } @@ -234,8 +238,18 @@ class Bundler { entryModuleOnly, resolutionResponse, }) { + if (dev && runBeforeMainModule) { // no runBeforeMainModule for hmr bundles + // `require` calls in the require polyfill itself are not extracted and + // replaced with numeric module IDs, but the require polyfill + // needs Systrace. + // Therefore, we include the Systrace module before the main module, and + // it will set itself as property on the require function. + // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) + runBeforeMainModule = runBeforeMainModule.concat(['Systrace']); + } + const onResolutionResponse = response => { - bundle.setMainModuleId(response.mainModuleId); + bundle.setMainModuleId(this._getModuleId(getMainModule(response))); if (bundle.setNumPrependedModules) { bundle.setNumPrependedModules( response.numPrependedDependencies + moduleSystemDeps.length @@ -249,13 +263,23 @@ class Bundler { response.dependencies = moduleSystemDeps.concat(response.dependencies); } }; - const finalizeBundle = ({bundle, transformedModules, response}) => + const finalizeBundle = ({bundle, transformedModules, response, modulesByName}) => Promise.all( transformedModules.map(({module, transformed}) => bundle.addModule(this._resolver, response, module, transformed) ) ).then(() => { - bundle.finalize({runBeforeMainModule, runMainModule}); + const runBeforeMainModuleIds = Array.isArray(runBeforeMainModule) + ? runBeforeMainModule + .map(name => modulesByName[name]) + .filter(Boolean) + .map(this._getModuleId, this) + : undefined; + + bundle.finalize({ + runMainModule, + runBeforeMainModule: runBeforeMainModuleIds, + }); return bundle; }); @@ -325,6 +349,7 @@ class Bundler { finalizeBundle = noop, }) { const findEventId = Activity.startEvent('find dependencies'); + const modulesByName = Object.create(null); if (!resolutionResponse) { let onProgess; @@ -360,6 +385,7 @@ class Bundler { bundle, transformOptions: response.transformOptions, }).then(transformed => { + modulesByName[transformed.name] = module; onModuleTransformed({ module, response, @@ -371,9 +397,9 @@ class Bundler { return Promise.all(response.dependencies.map(toModuleTransport)) .then(transformedModules => - Promise - .resolve(finalizeBundle({bundle, transformedModules, response})) - .then(() => bundle) + Promise.resolve( + finalizeBundle({bundle, transformedModules, response, modulesByName}) + ).then(() => bundle) ); }); } @@ -481,6 +507,7 @@ class Bundler { [name, {code, dependencies, dependencyOffsets, map, source}] ) => new ModuleTransport({ name, + id: this._getModuleId(module), code, map, meta: {dependencies, dependencyOffsets}, @@ -513,6 +540,7 @@ class Bundler { return new ModuleTransport({ name: id, + id: this._getModuleId(module), code: code, sourceCode: code, sourcePath: module.path, @@ -578,8 +606,9 @@ class Bundler { bundle.addAsset(asset); return new ModuleTransport({ name, + id: this._getModuleId(module), code, - meta, + meta: meta, sourceCode: code, sourcePath: module.path, virtual: true, @@ -614,4 +643,20 @@ function verifyRootExists(root) { assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); } +function createModuleIdFactory() { + const fileToIdMap = Object.create(null); + let nextId = 0; + return ({path}) => { + if (!(path in fileToIdMap)) { + fileToIdMap[path] = nextId; + nextId += 1; + } + return fileToIdMap[path]; + }; +} + +function getMainModule({dependencies, numPrependedDependencies = 0}) { + return dependencies[numPrependedDependencies]; +} + module.exports = Bundler; diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 286dc9eb..971e94e6 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -67,6 +67,7 @@ describe('Resolver', function() { function createModule(id, dependencies) { var module = new Module({}); + module.path = id; module.getName.mockImpl(() => Promise.resolve(id)); module.getDependencies.mockImpl(() => Promise.resolve(dependencies)); return module; @@ -109,6 +110,7 @@ describe('Resolver', function() { var deps = [module]; var depResolver = new Resolver({ + getModuleId: createGetModuleId(), projectRoot: '/root', }); @@ -262,11 +264,17 @@ describe('Resolver', function() { }); describe('wrapModule', function() { - pit('should resolve modules', function() { - var depResolver = new Resolver({ + let depResolver, getModuleId; + beforeEach(() => { + getModuleId = createGetModuleId(); + depResolver = new Resolver({ + depResolver, + getModuleId, projectRoot: '/root', }); + }); + pit('should resolve modules', function() { /*eslint-disable */ var code = [ // require @@ -300,18 +308,24 @@ describe('Resolver', function() { ]; } + const moduleIds = new Map( + resolutionResponse + .getResolvedDependencyPairs() + .map(([importId, module]) => [importId, getModuleId(module)]) + ); + return depResolver.wrapModule({ resolutionResponse, - module: createModule('test module', ['x', 'y']), + module: module, name: 'test module', code, meta: {dependencyOffsets} }).then(({code: processedCode}) => { expect(processedCode).toEqual([ - '__d("test module", function(global, require, module, exports) {' + + `__d(${getModuleId(module)} /* test module */, function(global, require, module, exports) {` + // require - 'require("changed")', - 'require("Y")', + `require(${moduleIds.get('x')} /* x */)`, + `require(${moduleIds.get('y')} /* y */)`, 'require( \'z\' )', 'require( "a")', 'require("b" )', @@ -327,7 +341,7 @@ describe('Resolver', function() { mainModuleId: 'test module', }); const inputMap = {version: 3, mappings: 'ARBITRARY'}; - return new Resolver({projectRoot: '/root'}).wrapModule({ + return depResolver.wrapModule({ resolutionResponse, module, name: 'test module', @@ -362,7 +376,7 @@ describe('Resolver', function() { let depResolver, module, resolutionResponse; beforeEach(() => { - depResolver = new Resolver({projectRoot: '/root'}); + depResolver = new Resolver({getModuleId, projectRoot: '/root'}); module = createJsonModule(id); resolutionResponse = new ResolutionResponseMock({ dependencies: [module], @@ -375,7 +389,7 @@ describe('Resolver', function() { .wrapModule({resolutionResponse, module, name: id, code}) .then(({code: processedCode}) => expect(processedCode).toEqual([ - `__d(${JSON.stringify(id)}, function(global, require, module, exports) {`, + `__d(${getModuleId(module)} /* ${id} */, function(global, require, module, exports) {`, `module.exports = ${code}\n});`, ].join(''))); }); @@ -391,6 +405,7 @@ describe('Resolver', function() { Promise.resolve({code, map})); depResolver = new Resolver({ projectRoot: '/root', + getModuleId, minifyCode }); module = createModule(id); @@ -403,7 +418,7 @@ describe('Resolver', function() { }); pit('should invoke the minifier with the wrapped code', () => { - const wrappedCode = `__d("${id}", function(global, require, module, exports) {${code}\n});` + const wrappedCode = `__d(${getModuleId(module)} /* ${id} */, function(global, require, module, exports) {${code}\n});` return depResolver .wrapModule({ resolutionResponse, @@ -430,4 +445,17 @@ describe('Resolver', function() { }); }); }); + + function createGetModuleId() { + let nextId = 1; + const knownIds = new Map(); + function createId(path) { + const id = nextId; + nextId += 1; + knownIds.set(path, id); + return id; + } + + return ({path}) => knownIds.get(path) || createId(path); + } }); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 6bd502c7..11e696ff 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -47,6 +47,10 @@ const validateOpts = declareOpts({ type: 'object', required: true, }, + getModuleId: { + type: 'function', + required: true, + }, transformCode: { type: 'function', }, @@ -103,8 +107,10 @@ class Resolver { cache: opts.cache, shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios', transformCode: opts.transformCode, + assetDependencies: ['AssetRegistry'], }); + this._getModuleId = options.getModuleId; this._minifyCode = opts.minifyCode; this._polyfillModuleNames = opts.polyfillModuleNames || []; @@ -139,6 +145,8 @@ class Resolver { polyfill => resolutionResponse.prependDependency(polyfill) ); + // currently used by HMR + resolutionResponse.getModuleId = this._getModuleId; return resolutionResponse.finalize(); }); } @@ -186,53 +194,40 @@ class Resolver { } resolveRequires(resolutionResponse, module, code, dependencyOffsets = []) { - return Promise.resolve().then(() => { - const resolvedDeps = Object.create(null); - const resolvedDepsArr = []; + const resolvedDeps = Object.create(null); - return Promise.all( - // here, we build a map of all require strings (relative and absolute) - // to the canonical name of the module they reference - resolutionResponse.getResolvedDependencyPairs(module).map( - ([depName, depModule]) => { - if (depModule) { - return depModule.getName().then(name => { - resolvedDeps[depName] = name; - resolvedDepsArr.push(name); - }); - } - } - ) - ).then(() => { - const relativizeCode = (codeMatch, quot, depName) => { - // if we have a canonical name for the module imported here, - // we use it, so that require() is always called with the same - // id for every module. - // Example: - // -- in a/b.js: - // require('./c') => require('a/c'); - // -- in b/index.js: - // require('../a/c') => require('a/c'); - const depId = resolvedDeps[depName]; - if (depId) { - return quot + depId + quot; - } else { - return codeMatch; - } - }; - - code = dependencyOffsets.reduceRight((codeBits, offset) => { - const first = codeBits.shift(); - codeBits.unshift( - first.slice(0, offset), - first.slice(offset).replace(/(['"])([^'"']*)\1/, relativizeCode), - ); - return codeBits; - }, [code]); - - return code.join(''); + // here, we build a map of all require strings (relative and absolute) + // to the canonical ID of the module they reference + resolutionResponse.getResolvedDependencyPairs(module) + .forEach(([depName, depModule]) => { + if (depModule) { + resolvedDeps[depName] = this._getModuleId(depModule); + } }); - }); + + // if we have a canonical ID for the module imported here, + // we use it, so that require() is always called with the same + // id for every module. + // Example: + // -- in a/b.js: + // require('./c') => require(3); + // -- in b/index.js: + // require('../a/c') => require(3); + const replaceModuleId = (codeMatch, quote, depName) => + depName in resolvedDeps + ? `${JSON.stringify(resolvedDeps[depName])} /* ${depName} */` + : codeMatch; + + code = dependencyOffsets.reduceRight((codeBits, offset) => { + const first = codeBits.shift(); + codeBits.unshift( + first.slice(0, offset), + first.slice(offset).replace(/(['"])([^'"']*)\1/, replaceModuleId), + ); + return codeBits; + }, [code]); + + return code.join(''); } wrapModule({ @@ -247,18 +242,24 @@ class Resolver { if (module.isJSON()) { code = `module.exports = ${code}`; } - const result = module.isPolyfill() - ? Promise.resolve({code: definePolyfillCode(code)}) - : this.resolveRequires( + + if (module.isPolyfill()) { + code = definePolyfillCode(code); + } else { + const moduleId = this._getModuleId(module); + code = this.resolveRequires( resolutionResponse, module, code, meta.dependencyOffsets - ).then(code => ({code: defineModuleCode(name, code), map})); + ); + code = defineModuleCode(moduleId, code, name); + } + return minify - ? result.then(({code, map}) => this._minifyCode(module.path, code, map)) - : result; + ? this._minifyCode(module.path, code, map) + : Promise.resolve({code, map}); } minifyModule({path, code, map}) { @@ -270,10 +271,10 @@ class Resolver { } } -function defineModuleCode(moduleName, code) { +function defineModuleCode(moduleName, code, verboseName = '') { return [ `__d(`, - `${JSON.stringify(moduleName)}, `, + `${JSON.stringify(moduleName)} /* ${verboseName} */, `, `function(global, require, module, exports) {`, `${code}`, '\n});', diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js index 9c1e36e7..185e25a8 100644 --- a/react-packager/src/Resolver/polyfills/require-unbundle.js +++ b/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -54,7 +54,9 @@ function loadModuleImplementation(moduleId, module) { // The systrace module will expose itself on the require function so that // it can be used here. // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) - const {Systrace} = require; + if (__DEV__) { + var {Systrace} = require; + } const exports = module.exports = {}; const {factory} = module; diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index d5b0cd81..05fa02dc 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -59,18 +59,31 @@ function requireImpl(id) { ); } + // `require` calls int the require polyfill itself are not analyzed and + // replaced so that they use numeric module IDs. + // The systrace module will expose itself on the require function so that + // it can be used here. + // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) + if (__DEV__) { + var {Systrace} = require; + } + try { // We must optimistically mark mod as initialized before running the factory to keep any // require cycles inside the factory from causing an infinite require loop. mod.isInitialized = true; - __DEV__ && Systrace().beginEvent('JS_require_' + id); + if (__DEV__) { + Systrace.beginEvent('JS_require_' + id); + } // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js mod.factory.call(global, global, require, mod.module, mod.module.exports); - __DEV__ && Systrace().endEvent(); + if (__DEV__) { + Systrace.endEvent(); + } } catch (e) { mod.hasError = true; mod.isInitialized = false; @@ -80,15 +93,9 @@ function requireImpl(id) { return mod.module.exports; } -const Systrace = __DEV__ && (() => { - var _Systrace; - try { - _Systrace = require('Systrace'); - } catch (e) {} - - return _Systrace && _Systrace.beginEvent ? - _Systrace : { beginEvent: () => {}, endEvent: () => {} }; -}); +if (__DEV__) { + require.Systrace = { beginEvent: () => {}, endEvent: () => {} }; +} global.__d = define; global.require = require; diff --git a/react-packager/src/lib/ModuleTransport.js b/react-packager/src/lib/ModuleTransport.js index d5e5032a..e5718e2d 100644 --- a/react-packager/src/lib/ModuleTransport.js +++ b/react-packager/src/lib/ModuleTransport.js @@ -11,6 +11,9 @@ function ModuleTransport(data) { this.name = data.name; + assertExists(data, 'id'); + this.id = data.id; + assertExists(data, 'code'); this.code = data.code; From 054642231bdb8b9311bd63a6dad160dee9acccb4 Mon Sep 17 00:00:00 2001 From: Sam Swarr Date: Tue, 15 Mar 2016 12:08:32 -0700 Subject: [PATCH 618/936] Add ability to silence packager logs to stdout Summary:We use a few different modules to output logs to stdout when building a bundle with the packager: - ##js/react-native-github/packager/react-packager/src/Activity/index.js## - ##js/react-native-github/local-cli/util/log.js## - ##https://www.npmjs.com/package/progress## This diff also adds a ##silent## option to the packager ##Server##, which, when ##true##, will not create a ##progress## instance for the transformer. Reviewed By: martinbigio Differential Revision: D3048739 fb-gh-sync-id: a4c6caf36f5127946593f4a0a349fa145ad0d4e6 shipit-source-id: a4c6caf36f5127946593f4a0a349fa145ad0d4e6 --- react-packager/src/Bundler/index.js | 8 ++++++-- react-packager/src/Server/index.js | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 3ec53f34..75d89877 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -81,6 +81,10 @@ const validateOpts = declareOpts({ type: 'number', required: false, }, + silent: { + type: 'boolean', + default: false, + }, }); class Bundler { @@ -352,8 +356,8 @@ class Bundler { const modulesByName = Object.create(null); if (!resolutionResponse) { - let onProgess; - if (process.stdout.isTTY) { + let onProgess = noop; + if (process.stdout.isTTY && !this._opts.silent) { const bar = new ProgressBar( 'transformed :current/:total (:percent)', {complete: '=', incomplete: ' ', width: 40, total: 1}, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index fe49ebbb..c52762f6 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -73,6 +73,10 @@ const validateOpts = declareOpts({ type: 'string', required: false, }, + silent: { + type: 'boolean', + default: false, + }, }); const bundleOpts = declareOpts({ From d533323781857a6180743a05af435627815c4497 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 17 Mar 2016 13:11:17 -0700 Subject: [PATCH 619/936] Upgrade node-haste to v2.9.1 to make module order deterministic Summary:Changing the order of transformation and extraction of dependencies made the order of modules dependent on the time when the worker pool returns a result. node-haste v2.9.1 addresses this issue and makes the order of dependencies deterministic. This also bumps the packager versions to enforce cache invalidation. Reviewed By: martinbigio Differential Revision: D3065063 fb-gh-sync-id: 1d45b066e45c3f64092f779c3fce3becf6739409 shipit-source-id: 1d45b066e45c3f64092f779c3fce3becf6739409 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b58ac91e..2a88fc01 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.3.0", + "version": "0.3.1", "name": "react-native-packager", "description": "Build native apps with React!", "repository": { From 8a860a01c55bfc02898aa218ef4f056397068924 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 18 Mar 2016 08:09:20 -0700 Subject: [PATCH 620/936] Improved OSS flow and lint reporting Summary:- lint bot is now managed by Circle CI - checked that flow and lint errors are caught both by bot and CI - flow fix for npm 3 - Travis is now using npm 2 and Circle CI npm 3 - Refactored Travis script to be able to be able to fail on multiple lines Closes https://github.com/facebook/react-native/pull/6508 Differential Revision: D3069500 Pulled By: davidaurelio fb-gh-sync-id: 02772bf1eae5f2c44489c2e3a01899428a9640cb shipit-source-id: 02772bf1eae5f2c44489c2e3a01899428a9640cb --- .../AssetServer/__tests__/AssetServer-test.js | 14 ++++++++---- .../src/Bundler/__tests__/Bundler-test.js | 21 +++++++++++++----- .../src/Server/__tests__/Server-test.js | 22 +++++++++---------- .../__tests__/SocketServer-test.js | 5 +++-- 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 15bfc0ce..bf96ae8c 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -1,9 +1,15 @@ +/** + * Copyright (c) 2013-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('node-haste/lib/lib/getPlatformExtension') - .dontMock('node-haste/node_modules/throat') - .dontMock('../'); +jest.autoMockOff(); jest .mock('crypto') diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index e1a67b2d..4ee7f110 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -8,15 +8,24 @@ */ 'use strict'; +jest.autoMockOff(); + jest .setMock('worker-farm', () => () => undefined) - .dontMock('node-haste/node_modules/throat') - .dontMock('lodash') - .dontMock('../../lib/ModuleTransport') .setMock('uglify-js') - .dontMock('../'); - -jest.mock('fs'); + .mock('image-size') + .mock('fs') + .mock('assert') + .mock('progress') + .mock('node-haste') + .mock('../../JSTransformer') + .mock('../../lib/declareOpts') + .mock('../../Resolver') + .mock('../Bundle') + .mock('../PrepackBundle') + .mock('../HMRBundle') + .mock('../../Activity') + .mock('../../lib/declareOpts'); var Bundler = require('../'); var Resolver = require('../../Resolver'); diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index ce91842e..b54ef2b8 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -8,22 +8,22 @@ */ 'use strict'; +jest.autoMockOff(); + jest.setMock('worker-farm', function() { return () => {}; }) - .dontMock('node-haste/node_modules/throat') - .dontMock('os') - .dontMock('lodash') - .dontMock('path') - .dontMock('url') .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) }) .setMock('uglify-js') - .dontMock('../') - .setMock('crypto'); + .setMock('crypto') + .mock('../../Bundler') + .mock('../../AssetServer') + .mock('../../lib/declareOpts') + .mock('node-haste') + .mock('../../Activity'); const Promise = require('promise'); var Bundler = require('../../Bundler'); var Server = require('../'); -var Server = require('../../Server'); var AssetServer = require('../../AssetServer'); var FileWatcher; @@ -108,7 +108,7 @@ describe('processRequest', () => { requestHandler, 'mybundle.bundle?runModule=true' ).then(response => { - expect(response.getHeader('ETag')).toBeDefined() + expect(response.getHeader('ETag')).toBeDefined(); }); }); @@ -118,7 +118,7 @@ describe('processRequest', () => { 'mybundle.bundle?runModule=true', { headers : { 'if-none-match' : 'this is an etag' } } ).then(response => { - expect(response.statusCode).toEqual(304) + expect(response.statusCode).toEqual(304); }); }); @@ -262,7 +262,7 @@ describe('processRequest', () => { Bundler.prototype.bundle = bundleFunc; - const server = new Server(options); + server = new Server(options); server.setHMRFileChangeListener(() => {}); requestHandler = server.processRequest.bind(server); diff --git a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js b/react-packager/src/SocketInterface/__tests__/SocketServer-test.js index 12512eed..fb25fb91 100644 --- a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js +++ b/react-packager/src/SocketInterface/__tests__/SocketServer-test.js @@ -8,11 +8,12 @@ */ 'use strict'; +jest.autoMockOff(); jest.setMock('uglify-js') .mock('net') .mock('fs') - .dontMock('node-haste/node_modules/throat') - .dontMock('../SocketServer'); + .mock('bser') + .mock('../../Server'); var PackagerServer = require('../../Server'); var SocketServer = require('../SocketServer'); From f361f99f531d210019a4ee9b3efc5660b09cc2d9 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 21 Mar 2016 12:31:52 -0700 Subject: [PATCH 621/936] Add sourcemap support for asset-based random access bundles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary:This adds support for source maps that can be used for “random access modules” / “unbundles” - source maps contain an extra custom field: `x_facebook_offsets` - this field maps module IDs to line offsets - the source map is built as if all files were concatenated Decoding/symbolication works as follows: - when decoding a stack trace, and a stack frame comes from a filename that contains only numbers and ends with `.js`, look up the additionally needed line offset in the offset map and add it to the original line of the stack frame. - consume the source map as usual Reviewed By: martinbigio Differential Revision: D3072426 fb-gh-sync-id: 827e6dc13b1959f02903baafa7f9e4fc2e0d4bb9 shipit-source-id: 827e6dc13b1959f02903baafa7f9e4fc2e0d4bb9 --- react-packager/src/Bundler/Bundle.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index af265ebb..febac2db 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -115,9 +115,7 @@ class Bundle extends BundleBase { return { startupCode, - modules: modules.map(({name, code, polyfill}) => - ({name, code, polyfill}) - ), + startupModules: allModules, modules, }; } From abbc1a51574ef69dc96e095c140ad3df918fa50b Mon Sep 17 00:00:00 2001 From: Shayne Sweeney Date: Mon, 21 Mar 2016 12:57:24 -0700 Subject: [PATCH 622/936] Atomic install, remove locking, cache xz archives (for offline installs) Reviewed By: martinbigio Differential Revision: D3015398 fb-gh-sync-id: e0d987917b94ce541c8fa890930799aa8613737e shipit-source-id: e0d987917b94ce541c8fa890930799aa8613737e --- blacklist.js | 1 + 1 file changed, 1 insertion(+) diff --git a/blacklist.js b/blacklist.js index 823ab156..813358a5 100644 --- a/blacklist.js +++ b/blacklist.js @@ -13,6 +13,7 @@ var path = require('path'); // Don't forget to everything listed here to `package.json` // modulePathIgnorePatterns. var sharedBlacklist = [ + /js\/tmp\/.*/, /node_modules[/\\]react[/\\]dist[/\\].*/, 'node_modules/react/lib/React.js', 'node_modules/react/lib/ReactDOM.js', From ebfb4f738a6ef44c8a057ccf9dcce9d9330d9829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 21 Mar 2016 15:50:15 -0700 Subject: [PATCH 623/936] Make HMR compatible with numeric IDs Summary:We recently refactor the packager to transform the module names into numeric IDs but we forgot to update the HMR call site. As a consequence, HMR doesn't work the first time a file is saved but the second one. This is affecting master as of 3/20. If we don't land this before v0.23 is cut we'll have to cherry pick it. This rev does *not* need to be picked on v0.22. Reviewed By: bestander Differential Revision: D3075192 fb-gh-sync-id: 410e4bf8f937c0cdb8f2b462dd36f928a24e8aa8 shipit-source-id: 410e4bf8f937c0cdb8f2b462dd36f928a24e8aa8 --- react-packager/src/Bundler/HMRBundle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Bundler/HMRBundle.js b/react-packager/src/Bundler/HMRBundle.js index caf3ec67..1153b164 100644 --- a/react-packager/src/Bundler/HMRBundle.js +++ b/react-packager/src/Bundler/HMRBundle.js @@ -34,10 +34,10 @@ class HMRBundle extends BundleBase { return Promise.resolve(); } - getModulesNamesAndCode() { + getModulesIdsAndCode() { return this._modules.map(module => { return { - name: JSON.stringify(module.name), + id: JSON.stringify(module.id), code: module.code, }; }); From 18612273bebb692a9c653aca051d0f748bc19ac8 Mon Sep 17 00:00:00 2001 From: Shayne Sweeney Date: Mon, 21 Mar 2016 21:57:56 -0700 Subject: [PATCH 624/936] Fix asset path-traversal outside of roots Summary:`/assets/...` requests previously supported path-traversal potentially exposing and serving (private) files outside roots. **Test plan** Prior to patching perform the a path-traversal request to the server: ``` GET /assets/../../../../etc/hosts HTTP/1.1 Cache-Control: no-store Host: 127.0.0.1:8081 Connection: close Accept-Encoding: gzip User-Agent: okhttp/2.5.0 ``` Apply patch and verify a `404` response with body: `Asset not found` Test normal asset requests work. Closes https://github.com/facebook/react-native/pull/6398 Differential Revision: D3034857 Pulled By: shayne fb-gh-sync-id: f0e6714e4e3c5a63a3a402634a1eb5f3186d3561 shipit-source-id: f0e6714e4e3c5a63a3a402634a1eb5f3186d3561 --- react-packager/src/AssetServer/index.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 71356acf..57d1aea2 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -129,16 +129,23 @@ class AssetServer { _findRoot(roots, dir) { return Promise.all( roots.map(root => { - const absPath = path.join(root, dir); + // important: we want to resolve root + dir + // to ensure the requested path doesn't traverse beyond root + const absPath = path.resolve(root, dir); return stat(absPath).then(fstat => { - return {path: absPath, isDirectory: fstat.isDirectory()}; - }, err => { - return {path: absPath, isDirectory: false}; + // keep asset requests from traversing files + // up from the root (e.g. ../../../etc/hosts) + if (!absPath.startsWith(root)) { + return {path: absPath, isValid: false}; + } + return {path: absPath, isValid: fstat.isDirectory()}; + }, _ => { + return {path: absPath, isValid: false}; }); }) ).then(stats => { for (let i = 0; i < stats.length; i++) { - if (stats[i].isDirectory) { + if (stats[i].isValid) { return stats[i].path; } } From c0f446d7e2db5512c95d108e660cfc693346cd8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 23 Mar 2016 09:27:19 -0700 Subject: [PATCH 625/936] Move preloaded modules to startup code section Summary:We found that moving the preloaded modules to the startup section of the RAM Bundle improves TTI quite a bit by saving lots of through the bridge calls and injecting multiple modules at once on JSC. However, doing this on a non hacky way required a lot of work. The main changes this diff does are: - Add to `BundleBase` additional bundling options. This options are fetched based on the entry file we're building by invoking a module that exports a function (`getBundleOptionsModulePath`). - Implement `BundleOptions` module to include the `numPreloadedModules` attribute as a bundle additional option. This value is computed by getting the dependencies the entry file has and looking for the first module that exports a module we don't want to preload. The `numPreloadedModules` attribute is then used to decide where to splice the array of modules. - Additional kung fu to make sure sourcemaps work for both preloaded and non preloaded modules. Reviewed By: davidaurelio Differential Revision: D3046534 fb-gh-sync-id: 80b676222ca3bb8b9eecc912a7963be94d3dee1a shipit-source-id: 80b676222ca3bb8b9eecc912a7963be94d3dee1a --- README.md | 4 +- react-packager/src/Bundler/Bundle.js | 44 +++++++++++++++- react-packager/src/Bundler/BundleBase.js | 6 +-- react-packager/src/Bundler/index.js | 52 +++++++++++++------ .../src/SocketInterface/SocketServer.js | 6 ++- 5 files changed, 89 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8010cc38..9a89cd9d 100644 --- a/README.md +++ b/README.md @@ -132,9 +132,9 @@ is passed to `ReactPackager.middleware` To get verbose output when running the packager, define an environment variable: export DEBUG=ReactNativePackager:* - + You can combine this with other values, e.g. `DEBUG=babel,ReactNativePackager:*`. Under the hood this uses the [`debug`](https://www.npmjs.com/package/debug) package, see its documentation for all the available options. - + The `/debug` endpoint discussed above is also useful. ## FAQ diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index febac2db..a2edd080 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -25,6 +25,8 @@ class Bundle extends BundleBase { this._numPrependedModules = 0; this._numRequireCalls = 0; this._minify = minify; + + this._ramBundle = null; // cached RAM Bundle } addModule(resolver, resolutionResponse, module, moduleTransport) { @@ -73,6 +75,7 @@ class Bundle extends BundleBase { virtual: true, sourceCode: code, sourcePath: name + '.js', + meta: {preloaded: true}, })); this._numRequireCalls += 1; } @@ -103,7 +106,46 @@ class Bundle extends BundleBase { return source; } - getUnbundle() { + getUnbundle(type) { + if (this._ramBundle) { + return this._ramBundle; + } + + switch (type) { + case 'INDEX': + this._ramBundle = this._getAsIndexedFileUnbundle(); + break; + case 'ASSETS': + this._ramBundle = this._getAsAssetsUnbundle(); + break; + default: + throw new Error('Unkown RAM Bundle type:', type); + } + + return this._ramBundle; + } + + _getAsIndexedFileUnbundle() { + const modules = this.getModules(); + + // separate modules we need to preload from the ones we don't + const shouldPreload = (module) => module.meta && module.meta.preloaded; + const preloaded = modules.filter(module => shouldPreload(module)); + const notPreloaded = modules.filter(module => !shouldPreload(module)); + + // code that will be executed on bridge start up + const startupCode = preloaded.map(({code}) => code).join('\n'); + + return { + // include copy of all modules on the order they're writen on the bundle: + // polyfills, preloaded, additional requires, non preloaded + allModules: preloaded.concat(notPreloaded), + startupCode, // no entries on the index for these modules, only the code + modules: notPreloaded, // we include both the code and entries on the index + }; + } + + _getAsAssetsUnbundle() { const allModules = this.getModules().slice(); const prependedModules = this._numPrependedModules; const requireCalls = this._numRequireCalls; diff --git a/react-packager/src/Bundler/BundleBase.js b/react-packager/src/Bundler/BundleBase.js index 472db21f..53fa9c34 100644 --- a/react-packager/src/Bundler/BundleBase.js +++ b/react-packager/src/Bundler/BundleBase.js @@ -60,9 +60,8 @@ class BundleBase { finalize(options) { Object.freeze(this._modules); - Object.seal(this._modules); Object.freeze(this._assets); - Object.seal(this._assets); + this._finalized = true; } @@ -97,9 +96,8 @@ class BundleBase { bundle.setMainModuleId(json.mainModuleId); Object.freeze(bundle._modules); - Object.seal(bundle._modules); Object.freeze(bundle._assets); - Object.seal(bundle._assets); + bundle._finalized = true; } } diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 75d89877..f32150c2 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -356,13 +356,13 @@ class Bundler { const modulesByName = Object.create(null); if (!resolutionResponse) { - let onProgess = noop; + let onProgress = noop; if (process.stdout.isTTY && !this._opts.silent) { const bar = new ProgressBar( 'transformed :current/:total (:percent)', {complete: '=', incomplete: ' ', width: 40, total: 1}, ); - onProgess = (_, total) => { + onProgress = (_, total) => { bar.total = total; bar.tick(); }; @@ -373,7 +373,7 @@ class Bundler { dev, platform, hot, - onProgess, + onProgress, minify, generateSourceMaps: unbundle, }); @@ -383,10 +383,22 @@ class Bundler { Activity.endEvent(findEventId); onResolutionResponse(response); + // get entry file complete path (`entryFile` is relative to roots) + let entryFilePath; + if (response.dependencies.length > 0) { + const numModuleSystemDependencies = + this._resolver.getModuleSystemDependencies({dev, unbundle}).length; + entryFilePath = response.dependencies[ + response.numPrependedDependencies + + numModuleSystemDependencies + ].path; + } + const toModuleTransport = module => this._toModuleTransport({ module, bundle, + entryFilePath, transformOptions: response.transformOptions, }).then(transformed => { modulesByName[transformed.name] = module; @@ -432,7 +444,7 @@ class Bundler { hot = false, recursive = true, generateSourceMaps = false, - onProgess, + onProgress, }) { return this.getTransformOptions( entryFile, @@ -450,11 +462,12 @@ class Bundler { platform, transform: transformSpecificOptions, }; + return this._resolver.getDependencies( entryFile, {dev, platform, recursive}, transformOptions, - onProgess, + onProgress, ); }); } @@ -491,7 +504,7 @@ class Bundler { ); } - _toModuleTransport({module, bundle, transformOptions}) { + _toModuleTransport({module, bundle, entryFilePath, transformOptions}) { let moduleTransport; if (module.isAsset_DEPRECATED()) { moduleTransport = this._generateAssetModule_DEPRECATED(bundle, module); @@ -509,15 +522,24 @@ class Bundler { module.read(transformOptions), ]).then(( [name, {code, dependencies, dependencyOffsets, map, source}] - ) => new ModuleTransport({ - name, - id: this._getModuleId(module), - code, - map, - meta: {dependencies, dependencyOffsets}, - sourceCode: source, - sourcePath: module.path - })); + ) => { + const preloaded = + module.path === entryFilePath || + module.isPolyfill() || ( + transformOptions.transform.preloadedModules && + transformOptions.transform.preloadedModules.hasOwnProperty(module.path) + ); + + return new ModuleTransport({ + name, + id: this._getModuleId(module), + code, + map, + meta: {dependencies, dependencyOffsets, preloaded}, + sourceCode: source, + sourcePath: module.path + }) + }); } getGraphDebugInfo() { diff --git a/react-packager/src/SocketInterface/SocketServer.js b/react-packager/src/SocketInterface/SocketServer.js index 2e533527..3b107c47 100644 --- a/react-packager/src/SocketInterface/SocketServer.js +++ b/react-packager/src/SocketInterface/SocketServer.js @@ -136,7 +136,11 @@ class SocketServer { _reply(sock, id, type, data) { debug('request finished', type); - data = toJSON(data); + try { + data = toJSON(data); + } catch (e) { + console.error('SocketServer exception:', e); + } sock.write(bser.dumpToBuffer({ id, From ed654c022ee25c6c71491f366042f5e18058b257 Mon Sep 17 00:00:00 2001 From: Shayne Sweeney Date: Wed, 23 Mar 2016 12:35:08 -0700 Subject: [PATCH 626/936] Use different tmpdir Reviewed By: wez Differential Revision: D3086091 fb-gh-sync-id: b26a792b80b523205ceafaa0e9d2299527bc8176 shipit-source-id: b26a792b80b523205ceafaa0e9d2299527bc8176 --- blacklist.js | 1 - 1 file changed, 1 deletion(-) diff --git a/blacklist.js b/blacklist.js index 813358a5..823ab156 100644 --- a/blacklist.js +++ b/blacklist.js @@ -13,7 +13,6 @@ var path = require('path'); // Don't forget to everything listed here to `package.json` // modulePathIgnorePatterns. var sharedBlacklist = [ - /js\/tmp\/.*/, /node_modules[/\\]react[/\\]dist[/\\].*/, 'node_modules/react/lib/React.js', 'node_modules/react/lib/ReactDOM.js', From cccbb342a046ccc61ca778f1f1ac294db25edd6a Mon Sep 17 00:00:00 2001 From: Mart?n Bigio Date: Wed, 23 Mar 2016 15:16:46 -0700 Subject: [PATCH 627/936] Fix OSS tests Reviewed By: bestander Differential Revision: D3088301 fb-gh-sync-id: 310d9c7c1956f21f3c8e5da6a1455c2a9fad2d04 shipit-source-id: 310d9c7c1956f21f3c8e5da6a1455c2a9fad2d04 --- react-packager/src/Bundler/__tests__/Bundler-test.js | 12 +++++++----- react-packager/src/Bundler/index.js | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index 4ee7f110..ac5cc0bd 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -41,16 +41,18 @@ describe('Bundler', function() { isAsset, isAsset_DEPRECATED, isJSON, + isPolyfill, 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; }, + getDependencies: () => Promise.resolve(dependencies), + getName: () => Promise.resolve(id), + isJSON: () => isJSON, + isAsset: () => isAsset, + isAsset_DEPRECATED: () => isAsset_DEPRECATED, + isPolyfill: () => isPolyfill, read: () => ({ code: 'arbitrary', source: 'arbitrary', diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index f32150c2..8d630528 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -388,8 +388,9 @@ class Bundler { if (response.dependencies.length > 0) { const numModuleSystemDependencies = this._resolver.getModuleSystemDependencies({dev, unbundle}).length; + entryFilePath = response.dependencies[ - response.numPrependedDependencies + + (response.numPrependedDependencies || 0) + numModuleSystemDependencies ].path; } From 4ea783234b25838c39ca3c750212f20c344f399f Mon Sep 17 00:00:00 2001 From: Steven Chaitoff Date: Thu, 24 Mar 2016 08:07:48 -0700 Subject: [PATCH 628/936] file change listener for relay fragments in packager server Reviewed By: martinbigio Differential Revision: D3011797 fb-gh-sync-id: 9f7ddc7b3c0c0e2a78db829343d9fa93a46b4ad6 shipit-source-id: 9f7ddc7b3c0c0e2a78db829343d9fa93a46b4ad6 --- react-packager/src/Server/index.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index c52762f6..aa772685 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -217,6 +217,12 @@ class Server { this._hmrFileChangeListener = listener; } + addFileChangeListener(listener) { + if (this._fileChangeListeners.indexOf(listener) === -1) { + this._fileChangeListeners.push(listener); + } + } + buildBundle(options) { return Promise.resolve().then(() => { if (!options.platform) { @@ -288,6 +294,15 @@ class Server { return; } + Promise.all( + this._fileChangeListeners.map(listener => listener(absPath)) + ).then( + () => this._onFileChangeComplete(absPath), + () => this._onFileChangeComplete(absPath) + ); + } + + _onFileChangeComplete(absPath) { // Make sure the file watcher event runs through the system before // we rebuild the bundles. this._debouncedFileChangeHandler(absPath); From 7cf1c7f51fd2d3433a4792a2bc2fb0202618f7ce Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Thu, 24 Mar 2016 12:05:48 -0700 Subject: [PATCH 629/936] Fix fetching sourcemap in genymotion. Fixes #5338 Summary:Source maps are broken on Genymotion right now as they aren't being loaded from the correct URL. refer - https://github.com/facebook/react-native/issues/5338#issuecomment-188232402 **Test plan** Build and install UIExplorer from master branch in genymotion and enable hot reload. When you change a file and save it, you'll see a Yellow box due to source map fetching failed, as per the referenced comment. Doing the same for this branch doesn't produce any yellow boxes. Closes https://github.com/facebook/react-native/pull/6594 Differential Revision: D3088218 Pulled By: martinbigio fb-gh-sync-id: 0d1c19cc263de5c6c62061c399eef33fa4ac4a7b shipit-source-id: 0d1c19cc263de5c6c62061c399eef33fa4ac4a7b --- react-packager/src/Bundler/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 8d630528..52bb3ad2 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -170,9 +170,9 @@ class Bundler { }); } - _sourceHMRURL(platform, host, port, path) { + _sourceHMRURL(platform, path) { return this._hmrURL( - `http://${host}:${port}`, + '', platform, 'bundle', path, @@ -217,7 +217,7 @@ class Bundler { return this._bundle({ ...options, bundle: new HMRBundle({ - sourceURLFn: this._sourceHMRURL.bind(this, options.platform, host, port), + sourceURLFn: this._sourceHMRURL.bind(this, options.platform), sourceMappingURLFn: this._sourceMappingHMRURL.bind( this, options.platform, From 97e1db3e6c8da9b0e95a09a75ff1463c8d2043f4 Mon Sep 17 00:00:00 2001 From: Mart?n Bigio Date: Fri, 25 Mar 2016 08:44:38 -0700 Subject: [PATCH 630/936] Avoid getting `entryFilePath` on HMR codepath Summary: The HMR codepath is currently broken because of a recent change that tries to compute the absolute entryFile path (needed for RAM Bundling). HMR Bundler's bundles are special as they contains a single file (the file that was transformed). However, for performance reasons we recycle an existing resolution response which contains the polyfills and the module system modules. Reviewed By: sam-swarr Differential Revision: D3098069 fb-gh-sync-id: 23d61aa304cd6f59d4df4840965f5eedda05dc31 fbshipit-source-id: 23d61aa304cd6f59d4df4840965f5eedda05dc31 --- react-packager/src/Bundler/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 52bb3ad2..2fd484c0 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -385,7 +385,7 @@ class Bundler { // get entry file complete path (`entryFile` is relative to roots) let entryFilePath; - if (response.dependencies.length > 0) { + if (response.dependencies.length > 1) { // skip HMR requests const numModuleSystemDependencies = this._resolver.getModuleSystemDependencies({dev, unbundle}).length; From 30bdfdd370fa3c6691f5387ac453beba43363637 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 5 Apr 2016 01:12:13 -0700 Subject: [PATCH 631/936] Fix HMR on Windows Summary:Tested HMR on Windows and found 2 small issues related to paths that made it not work. Now it works nicely :) **Test plan (required)** Tested HMR in UIExplorer on Windows. Closes https://github.com/facebook/react-native/pull/6678 Differential Revision: D3138379 fb-gh-sync-id: f27cd2fa21f95954685c8c6916d820f41bc187be fbshipit-source-id: f27cd2fa21f95954685c8c6916d820f41bc187be --- react-packager/src/Bundler/index.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 2fd484c0..30da6417 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -189,23 +189,24 @@ class Bundler { ); } - _hmrURL(prefix, platform, extensionOverride, path) { - const matchingRoot = this._projectRoots.find(root => path.startsWith(root)); + _hmrURL(prefix, platform, extensionOverride, filePath) { + const matchingRoot = this._projectRoots.find(root => filePath.startsWith(root)); if (!matchingRoot) { - throw new Error('No matching project root for ', path); + throw new Error('No matching project root for ', filePath); } - const extensionStart = path.lastIndexOf('.'); - let resource = path.substring( + // Replaces '\' with '/' for Windows paths. + if (path.sep === '\\') { + filePath = filePath.replace(/\\/g, '/'); + } + + const extensionStart = filePath.lastIndexOf('.'); + let resource = filePath.substring( matchingRoot.length, extensionStart !== -1 ? extensionStart : undefined, ); - const extension = extensionStart !== -1 - ? path.substring(extensionStart + 1) - : null; - return ( prefix + resource + '.' + extensionOverride + '?' + From 6f9aad36410681fed1bbcbd4d0036eacec874e11 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Wed, 6 Apr 2016 07:58:36 -0700 Subject: [PATCH 632/936] Fixed images required from node_modules Summary:This fixes https://github.com/facebook/react-native/issues/6638 by resolving AssetRegistry relatively. Closes https://github.com/facebook/react-native/pull/6822 Reviewed By: davidaurelio Differential Revision: D3144463 Pulled By: bestander fb-gh-sync-id: d3eeb24ae9e08a32f742c50ae5f0314fd33d1b6b fbshipit-source-id: d3eeb24ae9e08a32f742c50ae5f0314fd33d1b6b --- react-packager/src/Bundler/index.js | 7 ++++--- react-packager/src/Resolver/index.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 30da6417..b25ce4ca 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -612,10 +612,11 @@ class Bundler { }; const json = JSON.stringify(asset); + const assetRegistryPath = 'react-native/Libraries/Image/AssetRegistry'; const code = - `module.exports = require('AssetRegistry').registerAsset(${json});`; - const dependencies = ['AssetRegistry']; - const dependencyOffsets = [code.indexOf('AssetRegistry') - 1]; + `module.exports = require(${JSON.stringify(assetRegistryPath)}).registerAsset(${json});`; + const dependencies = [assetRegistryPath]; + const dependencyOffsets = [code.indexOf(assetRegistryPath) - 1]; return { asset, diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 11e696ff..8de8d5f8 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -107,7 +107,7 @@ class Resolver { cache: opts.cache, shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios', transformCode: opts.transformCode, - assetDependencies: ['AssetRegistry'], + assetDependencies: ['react-native/Libraries/Image/AssetRegistry'], }); this._getModuleId = options.getModuleId; From 27e79ff0c39fc0154f8c34caf480a5e55a21678c Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 6 Apr 2016 09:20:39 -0700 Subject: [PATCH 633/936] CHORE - Remove Trailing Spaces Summary:Remove Trailing Spaces. Why: Sometimes there are conflicts with trailing spaces Saves space Those whose tools automatically delete them will have their pr watered down with trailing space removal Closes https://github.com/facebook/react-native/pull/6787 Differential Revision: D3144704 fb-gh-sync-id: d8a62f115a3f8a8a49d5b07f56c540a02af38cf8 fbshipit-source-id: d8a62f115a3f8a8a49d5b07f56c540a02af38cf8 --- react-native-xcode.sh | 8 ++++---- .../src/Resolver/polyfills/String.prototype.es6.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index 9e6ffbe4..57201bab 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -14,11 +14,11 @@ case "$CONFIGURATION" in Debug) # Speed up build times by skipping the creation of the offline package for debug # builds on the simulator since the packager is supposed to be running anyways. - if [[ "$PLATFORM_NAME" = "iphonesimulator" ]]; then - echo "Skipping bundling for Simulator platform" - exit 0; + if [[ "$PLATFORM_NAME" = "iphonesimulator" ]]; then + echo "Skipping bundling for Simulator platform" + exit 0; fi - + DEV=true ;; "") diff --git a/react-packager/src/Resolver/polyfills/String.prototype.es6.js b/react-packager/src/Resolver/polyfills/String.prototype.es6.js index 59e5351d..52bbc314 100644 --- a/react-packager/src/Resolver/polyfills/String.prototype.es6.js +++ b/react-packager/src/Resolver/polyfills/String.prototype.es6.js @@ -77,7 +77,7 @@ if (!String.prototype.includes) { if (typeof start !== 'number') { start = 0; } - + if (start + search.length > this.length) { return false; } else { From 65b0f7c868eb49d4fcddc2a03d00ab2c0e159fd7 Mon Sep 17 00:00:00 2001 From: Adam Miskiewicz Date: Wed, 6 Apr 2016 11:36:57 -0700 Subject: [PATCH 634/936] Pass transformOptions to getShallowDependencies. Summary:We weren't passing `transformOptions` to `getShallowDependencies`, and therefore, when this method was called on a module, it would bust the cache and cause a retransform of the file. This was resulting in a complete retransforming of all files when the HMR Client connected to the packager. Closes https://github.com/facebook/react-native/pull/6843 Differential Revision: D3145306 Pulled By: martinbigio fb-gh-sync-id: 3619c27801b2fc07b758fafed47fcc892bb8e6db fbshipit-source-id: 3619c27801b2fc07b758fafed47fcc892bb8e6db --- react-packager/src/Bundler/index.js | 29 ++++++++++++++++++++++++++-- react-packager/src/Resolver/index.js | 4 ++-- react-packager/src/Server/index.js | 11 +++++++++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index b25ce4ca..b8bfb6ba 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -426,8 +426,33 @@ class Bundler { this._cache.invalidate(filePath); } - getShallowDependencies(entryFile) { - return this._resolver.getShallowDependencies(entryFile); + getShallowDependencies({ + entryFile, + platform, + dev = true, + minify = !dev, + hot = false, + generateSourceMaps = false, + }) { + return this.getTransformOptions( + entryFile, + { + dev, + platform, + hot, + generateSourceMaps, + projectRoots: this._projectRoots, + }, + ).then(transformSpecificOptions => { + const transformOptions = { + minify, + dev, + platform, + transform: transformSpecificOptions, + }; + + return this._resolver.getShallowDependencies(entryFile, transformOptions); + }); } stat(filePath) { diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 8de8d5f8..fd67201f 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -120,8 +120,8 @@ class Resolver { }); } - getShallowDependencies(entryFile) { - return this._depGraph.getShallowDependencies(entryFile); + getShallowDependencies(entryFile, transformOptions) { + return this._depGraph.getShallowDependencies(entryFile, transformOptions); } stat(filePath) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index aa772685..f7b4b354 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -254,8 +254,15 @@ class Server { return this._bundler.hmrBundle(modules, host, port); } - getShallowDependencies(entryFile) { - return this._bundler.getShallowDependencies(entryFile); + getShallowDependencies(options) { + return Promise.resolve().then(() => { + if (!options.platform) { + options.platform = getPlatformExtension(options.entryFile); + } + + const opts = dependencyOpts(options); + return this._bundler.getShallowDependencies(opts); + }); } getModuleForPath(entryFile) { From f9a9cd6f25cf56956b078155e0805cd894f11a56 Mon Sep 17 00:00:00 2001 From: Thomas Aylott Date: Wed, 6 Apr 2016 12:13:36 -0700 Subject: [PATCH 635/936] Fixes hotcode reloading issue Reviewed By: martinbigio Differential Revision: D3136032 fb-gh-sync-id: 5666fd45ffa574d2156b03c7bfbda3fe97090f56 fbshipit-source-id: 5666fd45ffa574d2156b03c7bfbda3fe97090f56 --- react-packager/src/Bundler/index.js | 9 +++++---- react-packager/src/Resolver/polyfills/require.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index b8bfb6ba..a6041e40 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -390,10 +390,11 @@ class Bundler { const numModuleSystemDependencies = this._resolver.getModuleSystemDependencies({dev, unbundle}).length; - entryFilePath = response.dependencies[ - (response.numPrependedDependencies || 0) + - numModuleSystemDependencies - ].path; + + const dependencyIndex = (response.numPrependedDependencies || 0) + numModuleSystemDependencies; + if (dependencyIndex in response.dependencies) { + entryFilePath = response.dependencies[dependencyIndex].path; + } } const toModuleTransport = module => diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 05fa02dc..28a3c2ae 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -139,7 +139,7 @@ if (__DEV__) { // HMR } function acceptAll(modules, inverseDependencies) { - if (modules.length === 0) { + if (!modules || modules.length === 0) { return true; } From 1d9df442a4d68a9162e482061ad01d16a66855b9 Mon Sep 17 00:00:00 2001 From: Sam Swarr Date: Mon, 11 Apr 2016 09:23:06 -0700 Subject: [PATCH 636/936] Remove socket interface from dependencies command Reviewed By: davidaurelio Differential Revision: D3136438 fb-gh-sync-id: 51fce2caf60fdf32a8cba180c79b1996834cda6e fbshipit-source-id: 51fce2caf60fdf32a8cba180c79b1996834cda6e --- react-packager/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/react-packager/index.js b/react-packager/index.js index 49848ede..960342f6 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -64,6 +64,15 @@ exports.getDependencies = function(options, bundleOptions) { }); }; +exports.getOrderedDependencyPaths = function(options, bundleOptions) { + var server = createNonPersistentServer(options); + return server.getOrderedDependencyPaths(bundleOptions) + .then(function(paths) { + server.end(); + return paths; + }); +}; + exports.createClientFor = function(options) { if (options.verbose) { enableDebug(); From 43c5ed2be0220007f9dae28cdb893fc8af1a00a9 Mon Sep 17 00:00:00 2001 From: Sam Swarr Date: Mon, 11 Apr 2016 14:36:57 -0700 Subject: [PATCH 637/936] Delete SocketInterface code Reviewed By: davidaurelio Differential Revision: D3152105 fb-gh-sync-id: a6c13bb54c2164ebc063a1b14f00114738546c8c fbshipit-source-id: a6c13bb54c2164ebc063a1b14f00114738546c8c --- react-packager/index.js | 28 --- .../src/SocketInterface/SocketClient.js | 170 ------------- .../src/SocketInterface/SocketServer.js | 224 ------------------ .../__tests__/SocketClient-test.js | 113 --------- .../__tests__/SocketInterface-test.js | 81 ------- .../__tests__/SocketServer-test.js | 102 -------- react-packager/src/SocketInterface/index.js | 146 ------------ 7 files changed, 864 deletions(-) delete mode 100644 react-packager/src/SocketInterface/SocketClient.js delete mode 100644 react-packager/src/SocketInterface/SocketServer.js delete mode 100644 react-packager/src/SocketInterface/__tests__/SocketClient-test.js delete mode 100644 react-packager/src/SocketInterface/__tests__/SocketInterface-test.js delete mode 100644 react-packager/src/SocketInterface/__tests__/SocketServer-test.js delete mode 100644 react-packager/src/SocketInterface/index.js diff --git a/react-packager/index.js b/react-packager/index.js index 960342f6..ef143264 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -73,17 +73,6 @@ exports.getOrderedDependencyPaths = function(options, bundleOptions) { }); }; -exports.createClientFor = function(options) { - if (options.verbose) { - enableDebug(); - } - startSocketInterface(); - return ( - require('./src/SocketInterface') - .getOrCreateSocketFor(omit(options, ['verbose'])) - ); -}; - function useGracefulFs() { var fs = require('fs'); var gracefulFs = require('graceful-fs'); @@ -111,7 +100,6 @@ function createServer(options) { enableDebug(); } - startSocketInterface(); var Server = require('./src/Server'); return new Server(omit(options, ['verbose'])); } @@ -135,19 +123,3 @@ function omit(obj, blacklistedKeys) { return clone; }, {}); } - -// we need to listen on a socket as soon as a server is created, but only once. -// This file also serves as entry point when spawning a socket server; in that -// case we need to start the server immediately. -var didStartSocketInterface = false; -function startSocketInterface() { - if (didStartSocketInterface) { - return; - } - didStartSocketInterface = true; - require('./src/SocketInterface').listenOnServerMessages(); -} - -if (require.main === module) { // used as entry point - startSocketInterface(); -} diff --git a/react-packager/src/SocketInterface/SocketClient.js b/react-packager/src/SocketInterface/SocketClient.js deleted file mode 100644 index c0016e17..00000000 --- a/react-packager/src/SocketInterface/SocketClient.js +++ /dev/null @@ -1,170 +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'; - -const Bundle = require('../Bundler/Bundle'); -const PrepackBundle = require('../Bundler/PrepackBundle'); -const Promise = require('promise'); -const bser = require('bser'); -const debug = require('debug')('ReactNativePackager:SocketClient'); -const fs = require('fs'); -const net = require('net'); -const path = require('path'); -const tmpdir = require('os').tmpdir(); - -const LOG_PATH = path.join(tmpdir, 'react-packager.log'); - -class SocketClient { - static create(sockPath) { - return new SocketClient(sockPath).onReady(); - } - - constructor(sockPath) { - debug('connecting to', sockPath); - - this._sock = net.connect(sockPath); - this._ready = new Promise((resolve, reject) => { - this._sock.on('connect', () => { - this._sock.removeAllListeners('error'); - process.on('uncaughtException', (error) => { - console.error('uncaught error', error.stack); - setImmediate(() => process.exit(1)); - }); - resolve(this); - }); - this._sock.on('error', (e) => { - e.message = `Error connecting to server on ${sockPath} ` + - `with error: ${e.message}`; - e.message += getServerLogs(); - - reject(e); - }); - }); - - this._resolvers = Object.create(null); - const bunser = new bser.BunserBuf(); - this._sock.on('data', (buf) => bunser.append(buf)); - bunser.on('value', (message) => this._handleMessage(message)); - - this._sock.on('close', () => { - if (!this._closing) { - const terminate = (result) => { - const sockPathExists = fs.existsSync(sockPath); - throw new Error( - 'Server closed unexpectedly.\n' + - 'Server ping connection attempt result: ' + result + '\n' + - 'Socket path: `' + sockPath + '` ' + - (sockPathExists ? ' exists.' : 'doesn\'t exist') + '\n' + - getServerLogs() - ); - }; - - // before throwing ping the server to see if it's still alive - const socket = net.connect(sockPath); - socket.on('connect', () => { - socket.end(); - terminate('OK'); - }); - socket.on('error', error => terminate(error)); - } - }); - } - - onReady() { - return this._ready; - } - - getDependencies(main) { - return this._send({ - type: 'getDependencies', - data: main, - }); - } - - getOrderedDependencyPaths(main) { - return this._send({ - type: 'getOrderedDependencyPaths', - data: main, - }); - } - - buildBundle(options) { - return this._send({ - type: 'buildBundle', - data: options, - }).then(json => Bundle.fromJSON(json)); - } - - buildPrepackBundle(options) { - return this._send({ - type: 'buildPrepackBundle', - data: options, - }).then(json => PrepackBundle.fromJSON(json)); - } - - _send(message) { - message.id = uid(); - this._sock.write(bser.dumpToBuffer(message)); - return new Promise((resolve, reject) => { - this._resolvers[message.id] = {resolve, reject}; - }); - } - - _handleMessage(message) { - if (!(message && message.id && message.type)) { - throw new Error( - 'Malformed message from server ' + JSON.stringify(message) - ); - } - - debug('got message with type', message.type); - - const resolver = this._resolvers[message.id]; - if (!resolver) { - throw new Error( - 'Unrecognized message id (' + message.id + ') ' + - 'message already resolved or never existed.' - ); - } - - delete this._resolvers[message.id]; - - if (message.type === 'error') { - const errorLog = - message.data && message.data.indexOf('TimeoutError') === -1 - ? 'See logs ' + LOG_PATH - : getServerLogs(); - - resolver.reject(new Error(message.data + '\n' + errorLog)); - } else { - resolver.resolve(message.data); - } - } - - close() { - debug('closing connection'); - this._closing = true; - this._sock.end(); - } -} - -module.exports = SocketClient; - -function uid(len) { - len = len || 7; - return Math.random().toString(35).substr(2, len); -} - -function getServerLogs() { - if (fs.existsSync(LOG_PATH)) { - return '\nServer logs:\n' + fs.readFileSync(LOG_PATH, 'utf8'); - } - - return ''; -} diff --git a/react-packager/src/SocketInterface/SocketServer.js b/react-packager/src/SocketInterface/SocketServer.js deleted file mode 100644 index 3b107c47..00000000 --- a/react-packager/src/SocketInterface/SocketServer.js +++ /dev/null @@ -1,224 +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'; - -const Promise = require('promise'); -const Server = require('../Server'); -const bser = require('bser'); -const debug = require('debug')('ReactNativePackager:SocketServer'); -const fs = require('fs'); -const net = require('net'); - -const MAX_IDLE_TIME = 30 * 1000; -const MAX_STARTUP_TIME = 5 * 60 * 1000; - -class SocketServer { - constructor(sockPath, options) { - this._server = net.createServer(); - this._server.listen(sockPath); - this._ready = new Promise((resolve, reject) => { - this._server.once('error', (e) => reject(e)); - this._server.once('listening', () => { - // Remove error listener so we make sure errors propagate. - this._server.removeAllListeners('error'); - this._server.on( - 'close', - () => debug('server closed') - ); - - debug( - 'Process %d listening on socket path %s ' + - 'for server with options %j', - process.pid, - sockPath, - options - ); - resolve(this); - process.on('exit', code => { - debug('exit code:', code); - fs.unlinkSync(sockPath); - }); - }); - }); - - process.on('uncaughtException', (error) => { - debug('uncaught error', error.stack); - setImmediate(() => process.exit(1)); - }); - - this._server.on('connection', (sock) => this._handleConnection(sock)); - - // Disable the file watcher. - options.nonPersistent = true; - this._packagerServer = new Server(options); - this._dieEventually(MAX_STARTUP_TIME); - } - - onReady() { - return this._ready; - } - - _handleConnection(sock) { - debug('connection to server', process.pid); - - const bunser = new bser.BunserBuf(); - sock.on('data', (buf) => bunser.append(buf)); - bunser.on('value', (m) => this._handleMessage(sock, m)); - bunser.on('error', (e) => { - e.message = 'Unhandled error from the bser buffer. ' + - 'Either error on encoding or message handling: \n' + - e.message; - throw e; - }); - } - - _handleMessage(sock, m) { - if (!m || !m.id || !m.data) { - console.error('SocketServer recieved a malformed message: %j', m); - return; - } - - debug('got request', m); - - // Debounce the kill timer. - this._dieEventually(); - - const handleError = (error) => { - debug('request error', error); - this._reply(sock, m.id, 'error', error.stack); - - // Fatal error from JSTransformer transform workers. - if (error.type === 'ProcessTerminatedError') { - setImmediate(() => process.exit(1)); - } - }; - - switch (m.type) { - case 'getDependencies': - this._packagerServer.getDependencies(m.data).then( - ({ dependencies }) => this._reply(sock, m.id, 'result', dependencies), - handleError, - ); - break; - - case 'buildBundle': - this._packagerServer.buildBundle(m.data).then( - (result) => this._reply(sock, m.id, 'result', result), - handleError, - ); - break; - - case 'buildPrepackBundle': - this._packagerServer.buildPrepackBundle(m.data).then( - (result) => this._reply(sock, m.id, 'result', result), - handleError, - ); - break; - - case 'getOrderedDependencyPaths': - this._packagerServer.getOrderedDependencyPaths(m.data).then( - (dependencies) => this._reply(sock, m.id, 'result', dependencies), - handleError, - ); - break; - - default: - this._reply(sock, m.id, 'error', 'Unknown message type: ' + m.type); - } - } - - _reply(sock, id, type, data) { - debug('request finished', type); - - try { - data = toJSON(data); - } catch (e) { - console.error('SocketServer exception:', e); - } - - sock.write(bser.dumpToBuffer({ - id, - type, - data, - })); - - // Debounce the kill timer to make sure all the bytes are sent through - // the socket and the client has time to fully finish and disconnect. - this._dieEventually(); - } - - _dieEventually(delay = MAX_IDLE_TIME) { - clearTimeout(this._deathTimer); - this._deathTimer = setTimeout(() => { - this._server.getConnections((error, numConnections) => { - // error is passed when connection count is below 0 - if (error || numConnections <= 0) { - debug('server dying', process.pid); - process.exit(); - } - this._dieEventually(); - }); - }, delay); - } - - static listenOnServerIPCMessages() { - process.on('message', (message) => { - if (!(message && message.type && message.type === 'createSocketServer')) { - return; - } - - debug('server got ipc message', message); - - const {options, sockPath} = message.data; - // regexp doesn't naturally serialize to json. - options.blacklistRE = new RegExp(options.blacklistRE.source); - - new SocketServer(sockPath, options).onReady().then( - () => { - debug('succesfully created server'); - process.send({ type: 'createdServer' }); - }, - error => { - if (error.code === 'EADDRINUSE' || error.code === 'EEXIST') { - // Server already listening, this may happen if multiple - // clients where started in quick succussion (buck). - process.send({ type: 'createdServer' }); - - // Kill this server because some other server with the same - // config and socket already started. - debug('server already started'); - setImmediate(() => process.exit()); - } else { - debug('error creating server', error.code); - throw error; - } - } - ).done(); - }); - } -} - -// TODO move this to bser code. -function toJSON(object) { - if (!(object && typeof object === 'object')) { - return object; - } - - if (object.toJSON) { - return object.toJSON(); - } - - for (var p in object) { - object[p] = toJSON(object[p]); - } - - return object; -} - -module.exports = SocketServer; diff --git a/react-packager/src/SocketInterface/__tests__/SocketClient-test.js b/react-packager/src/SocketInterface/__tests__/SocketClient-test.js deleted file mode 100644 index df8d8f09..00000000 --- a/react-packager/src/SocketInterface/__tests__/SocketClient-test.js +++ /dev/null @@ -1,113 +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'; - -jest.setMock('worker-farm', function() { return () => {}; }) - .setMock('uglify-js') - .mock('net') - .dontMock('../SocketClient'); - -var Bundle = require('../../Bundler/Bundle'); -var SocketClient = require('../SocketClient'); -var bser = require('bser'); -var net = require('net'); - -describe('SocketClient', () => { - let sock; - let bunser; - - beforeEach(() => { - const {EventEmitter} = require.requireActual('events'); - sock = new EventEmitter(); - sock.write = jest.genMockFn(); - - net.connect.mockImpl(() => sock); - - bunser = new EventEmitter(); - bser.BunserBuf.mockImpl(() => bunser); - bser.dumpToBuffer.mockImpl((a) => a); - - Bundle.fromJSON.mockImpl((a) => a); - }); - - pit('create a connection', () => { - const client = new SocketClient('/sock'); - sock.emit('connect'); - return client.onReady().then(c => { - expect(c).toBe(client); - expect(net.connect).toBeCalledWith('/sock'); - }); - }); - - pit('buildBundle', () => { - const client = new SocketClient('/sock'); - sock.emit('connect'); - const options = { entryFile: '/main' }; - - const promise = client.buildBundle(options); - - expect(sock.write).toBeCalled(); - const message = sock.write.mock.calls[0][0]; - expect(message.type).toBe('buildBundle'); - expect(message.data).toEqual(options); - expect(typeof message.id).toBe('string'); - - bunser.emit('value', { - id: message.id, - type: 'result', - data: { bundle: 'foo' }, - }); - - return promise.then(bundle => expect(bundle).toEqual({ bundle: 'foo' })); - }); - - pit('getDependencies', () => { - const client = new SocketClient('/sock'); - sock.emit('connect'); - const main = '/main'; - - const promise = client.getDependencies(main); - - expect(sock.write).toBeCalled(); - const message = sock.write.mock.calls[0][0]; - expect(message.type).toBe('getDependencies'); - expect(message.data).toEqual(main); - expect(typeof message.id).toBe('string'); - - bunser.emit('value', { - id: message.id, - type: 'result', - data: ['a', 'b', 'c'], - }); - - return promise.then(result => expect(result).toEqual(['a', 'b', 'c'])); - }); - - pit('handle errors', () => { - const client = new SocketClient('/sock'); - sock.emit('connect'); - const main = '/main'; - - const promise = client.getDependencies(main); - - expect(sock.write).toBeCalled(); - const message = sock.write.mock.calls[0][0]; - expect(message.type).toBe('getDependencies'); - expect(message.data).toEqual(main); - expect(typeof message.id).toBe('string'); - - bunser.emit('value', { - id: message.id, - type: 'error', - data: 'some error' - }); - - return promise.catch(m => expect(m.message).toContain('some error')); - }); -}); diff --git a/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js b/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js deleted file mode 100644 index 4c1aa812..00000000 --- a/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js +++ /dev/null @@ -1,81 +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'; - -jest.setMock('uglify-js') - .mock('child_process') - .dontMock('../'); - -var SocketInterface = require('../'); -var SocketClient = require('../SocketClient'); -var childProcess = require('child_process'); -var fs = require('fs'); - -describe('SocketInterface', () => { - describe('getOrCreateSocketFor', () => { - pit('creates socket path by hashing options', () => { - fs.existsSync = jest.genMockFn().mockImpl(() => true); - fs.unlinkSync = jest.genMockFn(); - let callback; - - childProcess.spawn.mockImpl(() => ({ - on: (event, cb) => callback = cb, - send: (message) => { - setImmediate(() => callback({ type: 'createdServer' })); - }, - unref: () => undefined, - disconnect: () => undefined, - })); - - // Check that given two equivelant server options, we end up with the same - // socket path. - const options1 = { projectRoots: ['/root'], transformModulePath: '/root/foo' }; - const options2 = { transformModulePath: '/root/foo', projectRoots: ['/root'] }; - const options3 = { projectRoots: ['/root', '/root2'] }; - - return SocketInterface.getOrCreateSocketFor(options1).then(() => { - expect(SocketClient.create).toBeCalled(); - return SocketInterface.getOrCreateSocketFor(options2).then(() => { - expect(SocketClient.create.mock.calls.length).toBe(2); - expect(SocketClient.create.mock.calls[0]).toEqual(SocketClient.create.mock.calls[1]); - return SocketInterface.getOrCreateSocketFor(options3).then(() => { - expect(SocketClient.create.mock.calls.length).toBe(3); - expect(SocketClient.create.mock.calls[1]).not.toEqual(SocketClient.create.mock.calls[2]); - }); - }); - }); - }); - - pit('should fork a server', () => { - fs.existsSync = jest.genMockFn().mockImpl(() => false); - fs.unlinkSync = jest.genMockFn(); - let sockPath; - let callback; - - childProcess.spawn.mockImpl(() => ({ - on: (event, cb) => callback = cb, - send: (message) => { - expect(message.type).toBe('createSocketServer'); - expect(message.data.options).toEqual({ projectRoots: ['/root'] }); - expect(message.data.sockPath).toContain('react-packager'); - sockPath = message.data.sockPath; - - setImmediate(() => callback({ type: 'createdServer' })); - }, - unref: () => undefined, - disconnect: () => undefined, - })); - - return SocketInterface.getOrCreateSocketFor({ projectRoots: ['/root'] }) - .then(() => { - expect(SocketClient.create).toBeCalledWith(sockPath); - }); - }); - }); -}); diff --git a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js b/react-packager/src/SocketInterface/__tests__/SocketServer-test.js deleted file mode 100644 index fb25fb91..00000000 --- a/react-packager/src/SocketInterface/__tests__/SocketServer-test.js +++ /dev/null @@ -1,102 +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'; - -jest.autoMockOff(); -jest.setMock('uglify-js') - .mock('net') - .mock('fs') - .mock('bser') - .mock('../../Server'); - -var PackagerServer = require('../../Server'); -var SocketServer = require('../SocketServer'); -var bser = require('bser'); -var net = require('net'); - -describe('SocketServer', () => { - let netServer; - let bunser; - let processOn; - - beforeEach(() => { - const {EventEmitter} = require.requireActual('events'); - netServer = new EventEmitter(); - netServer.listen = jest.genMockFn(); - net.createServer.mockImpl(() => netServer); - - bunser = new EventEmitter(); - bser.BunserBuf.mockImpl(() => bunser); - bser.dumpToBuffer.mockImpl((a) => a); - - // Don't attach `process.on('exit')` handlers directly from SocketServer - processOn = process.on; - process.on = jest.genMockFn(); - }); - - afterEach(() => { - process.on = processOn; - }); - - pit('create a server', () => { - const server = new SocketServer('/sock', { projectRoots: ['/root'] }); - netServer.emit('listening'); - return server.onReady().then(s => { - expect(s).toBe(server); - expect(netServer.listen).toBeCalledWith('/sock'); - }); - }); - - pit('handles getDependencies message', () => { - const server = new SocketServer('/sock', { projectRoots: ['/root'] }); - netServer.emit('listening'); - return server.onReady().then(() => { - const sock = { on: jest.genMockFn(), write: jest.genMockFn() }; - netServer.emit('connection', sock); - PackagerServer.prototype.getDependencies.mockImpl( - () => Promise.resolve({ dependencies: ['a', 'b', 'c'] }) - ); - bunser.emit('value', { type: 'getDependencies', id: 1, data: '/main' }); - expect(PackagerServer.prototype.getDependencies).toBeCalledWith('/main'); - - // Run pending promises. - return Promise.resolve().then(() => { - expect(sock.write).toBeCalledWith( - { id: 1, type: 'result', data: ['a', 'b', 'c']} - ); - }); - }); - }); - - pit('handles buildBundle message', () => { - const server = new SocketServer('/sock', { projectRoots: ['/root'] }); - netServer.emit('listening'); - return server.onReady().then(() => { - const sock = { on: jest.genMockFn(), write: jest.genMockFn() }; - netServer.emit('connection', sock); - PackagerServer.prototype.buildBundle.mockImpl( - () => Promise.resolve({ bundle: 'foo' }) - ); - bunser.emit( - 'value', - { type: 'buildBundle', id: 1, data: { options: 'bar' } } - ); - expect(PackagerServer.prototype.buildBundle).toBeCalledWith( - { options: 'bar' } - ); - - // Run pending promises. - return Promise.resolve().then(() => { - expect(sock.write).toBeCalledWith( - { id: 1, type: 'result', data: { bundle: 'foo' }} - ); - }); - }); - }); -}); diff --git a/react-packager/src/SocketInterface/index.js b/react-packager/src/SocketInterface/index.js deleted file mode 100644 index f9f3a77b..00000000 --- a/react-packager/src/SocketInterface/index.js +++ /dev/null @@ -1,146 +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'; - -const Promise = require('promise'); -const SocketClient = require('./SocketClient'); -const SocketServer = require('./SocketServer'); -const crypto = require('crypto'); -const debug = require('debug')('ReactNativePackager:SocketInterface'); -const fs = require('fs'); -const net = require('net'); -const path = require('path'); -const tmpdir = require('os').tmpdir(); -const {spawn} = require('child_process'); - -const CREATE_SERVER_TIMEOUT = 5 * 60 * 1000; - -const SocketInterface = { - getOrCreateSocketFor(options) { - return new Promise((resolve, reject) => { - const hash = crypto.createHash('md5'); - Object.keys(options).sort().forEach(key => { - const value = options[key]; - if (value) { - hash.update( - options[key] != null && typeof value === 'string' - ? value - : JSON.stringify(value) - ); - } - }); - - let sockPath = path.join( - tmpdir, - 'react-packager-' + hash.digest('hex') - ); - if (process.platform === 'win32'){ - // on Windows, use a named pipe, convert sockPath into a valid pipe name - // based on https://gist.github.com/domenic/2790533 - sockPath = sockPath.replace(/^\//, '') - sockPath = sockPath.replace(/\//g, '-') - sockPath = '\\\\.\\pipe\\' + sockPath - } - - if (existsSync(sockPath)) { - var sock = net.connect(sockPath); - sock.on('connect', () => { - SocketClient.create(sockPath).then( - client => { - sock.end(); - resolve(client); - }, - error => { - sock.end(); - reject(error); - } - ); - }); - sock.on('error', (e) => { - try { - debug('deleting socket for not responding', sockPath); - fs.unlinkSync(sockPath); - } catch (err) { - // Another client might have deleted it first. - } - createServer(resolve, reject, options, sockPath); - }); - } else { - createServer(resolve, reject, options, sockPath); - } - }); - }, - - listenOnServerMessages() { - return SocketServer.listenOnServerIPCMessages(); - } -}; - -function createServer(resolve, reject, options, sockPath) { - const logPath = path.join(tmpdir, 'react-packager.log'); - - const timeout = setTimeout( - () => reject( - new Error( - 'Took too long to start server. Server logs: \n' + - fs.readFileSync(logPath, 'utf8') - ) - ), - CREATE_SERVER_TIMEOUT, - ); - - const log = fs.openSync(logPath, 'a'); - - // Enable server debugging by default since it's going to a log file. - const env = Object.assign({}, process.env); - env.DEBUG = 'ReactNativePackager:SocketServer'; - - // We have to go through the main entry point to make sure - // we go through the babel require hook. - const child = spawn( - process.execPath, - [path.join(__dirname, '..', '..', 'index.js')], - { - detached: true, - env: env, - stdio: ['ipc', log, log] - } - ); - - child.unref(); - - child.on('message', m => { - if (m && m.type && m.type === 'createdServer') { - clearTimeout(timeout); - child.disconnect(); - - resolve(SocketClient.create(sockPath)); - } - }); - - if (options.blacklistRE) { - options.blacklistRE = { source: options.blacklistRE.source }; - } - - child.send({ - type: 'createSocketServer', - data: { sockPath, options } - }); -} - -function existsSync(filename) { - try { - fs.accessSync(filename); - return true; - } catch(ex) { - return false; - } -} - -module.exports = SocketInterface; From c16cfc694b4b905df5ed61d4bcea4aa609d7da33 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 12 Apr 2016 08:28:22 -0700 Subject: [PATCH 638/936] Don't load 'babel-polyfill', only polyfill `Array#values`, `Object.values`, and `Object.entries` Summary:Instead of loading `'babel-polyfill'` into packager, we only polyfill es6 methods that are unavailable in node 4, and es7 stage 4 proposals. That makes sure that we are using native promises, and don't load unnecessary polyfills. Reviewed By: vjeux Differential Revision: D3168057 fb-gh-sync-id: 68b53795d9a1d7cfdc00fc31684da3ad21a7bb34 fbshipit-source-id: 68b53795d9a1d7cfdc00fc31684da3ad21a7bb34 --- babelRegisterOnly.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/babelRegisterOnly.js b/babelRegisterOnly.js index f862bb75..bb973ba4 100644 --- a/babelRegisterOnly.js +++ b/babelRegisterOnly.js @@ -8,7 +8,9 @@ */ 'use strict'; -require('babel-polyfill'); +Array.prototype.values || require('core-js/fn/array/values'); +Object.entries || require('core-js/fn/object/entries'); +Object.values || require('core-js/fn/object/values'); var fs = require('fs'); var path = require('path'); From b7b8861b757dc13a3854cbdb1d87d4d839444aa3 Mon Sep 17 00:00:00 2001 From: Leland Richardson Date: Tue, 19 Apr 2016 03:51:45 -0700 Subject: [PATCH 639/936] Whitelist the 'pdf' extension in the packager Summary:The WebView component in iOS currently does not support displaying PDFs without providing a remote URI or manually including the assets in the xcodeproj itself. This is because the packager has not whitelisted the 'pdf' extension. I've gone ahead and whitelisted the 'pdf extension according to the recommendation by nicklockwood GH comment: https://github.com/facebook/react-native/issues/1846#issuecomment-199302488 Closes https://github.com/facebook/react-native/pull/7004 Differential Revision: D3196019 Pulled By: nicklockwood fb-gh-sync-id: 10a86a9232095f98f277506141de0b8af5b21ab4 fbshipit-source-id: 10a86a9232095f98f277506141de0b8af5b21ab4 --- react-packager/src/Server/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index f7b4b354..1856fc96 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -62,7 +62,7 @@ const validateOpts = declareOpts({ 'bmp', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'svg', 'webp', // Image formats 'm4v', 'mov', 'mp4', 'mpeg', 'mpg', 'webm', // Video formats 'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', // Audio formats - 'html', // Document formats + 'html', 'pdf', // Document formats ], }, transformTimeoutInterval: { From f0e4826c46d41f7da51d666f26759198f0ca0dba Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 19 Apr 2016 08:12:36 -0700 Subject: [PATCH 640/936] Free modules' factories after caching module Summary:The require functions will keep the generate bytecode + lexical environment in memory unnecessarily, since we can be sure that it will be executed at most once Reviewed By: davidaurelio Differential Revision: D3168257 fb-gh-sync-id: 038e1bc08abea94ee52d0390b6aced5fb652f493 fbshipit-source-id: 038e1bc08abea94ee52d0390b6aced5fb652f493 --- .../src/Resolver/polyfills/require-unbundle.js | 10 ++++++++++ react-packager/src/Resolver/polyfills/require.js | 12 +++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js index 185e25a8..c2cddad6 100644 --- a/react-packager/src/Resolver/polyfills/require-unbundle.js +++ b/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -1,3 +1,12 @@ +/** + * Copyright (c) 2013-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 {ErrorUtils, nativeRequire} = global; @@ -67,6 +76,7 @@ function loadModuleImplementation(moduleId, module) { const moduleObject = {exports}; factory(global, require, moduleObject, exports); + module.factory = undefined; if (__DEV__) { Systrace.endEvent(); diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 28a3c2ae..bf5c3711 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -1,3 +1,12 @@ +/** + * Copyright (c) 2013-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. + */ + /* eslint strict:0 */ var modules = Object.create(null); var inGuard = false; @@ -36,7 +45,7 @@ function requireImpl(id) { inGuard = true; var returnValue; try { - returnValue = requireImpl.apply(this, arguments); + returnValue = requireImpl(id); } catch (e) { global.ErrorUtils.reportFatalError(e); } @@ -80,6 +89,7 @@ function requireImpl(id) { // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js mod.factory.call(global, global, require, mod.module, mod.module.exports); + mod.factory = undefined; if (__DEV__) { Systrace.endEvent(); From ae9f74e68d1bff128785cebf08472eac36d9df89 Mon Sep 17 00:00:00 2001 From: Mike Grabowski Date: Tue, 19 Apr 2016 18:08:59 -0700 Subject: [PATCH 641/936] Strip shebang when present in JS files Summary:Fixes #7034 Closes https://github.com/facebook/react-native/pull/7073 Differential Revision: D3199816 fb-gh-sync-id: 2099dd1f81b030933794be6a592a697cec3627d0 fbshipit-source-id: 2099dd1f81b030933794be6a592a697cec3627d0 --- .../JSTransformer/worker/__tests__/worker-test.js | 15 ++++++++++++++- react-packager/src/JSTransformer/worker/index.js | 3 +++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/worker/__tests__/worker-test.js b/react-packager/src/JSTransformer/worker/__tests__/worker-test.js index b0803509..b66dabe2 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/worker-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/worker-test.js @@ -68,7 +68,20 @@ describe('code transformation worker:', () => { done(); }); }); - + + it('removes shebang when present', done => { + const shebang = '#!/usr/bin/env node'; + const result = { + code: `${shebang} \n arbitrary(code)`, + }; + transform.mockImplementation((_, callback) => callback(null, result)); + transformCode(transform, 'arbitrary/file.js', 'b', {}, (_, data) => { + expect(data.code).not.toContain(shebang); + expect(data.code.split('\n').length).toEqual(result.code.split('\n').length); + done(); + }); + }); + it('calls back with any error yielded by the transform', done => { const error = Error('arbitrary error'); transform.mockImplementation((_, callback) => callback(error)); diff --git a/react-packager/src/JSTransformer/worker/index.js b/react-packager/src/JSTransformer/worker/index.js index 1b68feae..a767f999 100644 --- a/react-packager/src/JSTransformer/worker/index.js +++ b/react-packager/src/JSTransformer/worker/index.js @@ -43,6 +43,9 @@ function transformCode(transform, filename, sourceCode, options, callback) { if (isJson) { code = code.replace(/^\w+\.exports=/, ''); + } else { + // Remove shebang + code = code.replace(/^#!.*/, ''); } const result = isJson || options.extern From faa9adf2ab77e04f78568129e8811491cd1896a4 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 21 Apr 2016 06:48:27 -0700 Subject: [PATCH 642/936] Allow for falsy module exports Reviewed By: javache Differential Revision: D3207353 fb-gh-sync-id: 3e23eb9cc9facea7993ce684f87ff4b7b0003a1c fbshipit-source-id: 3e23eb9cc9facea7993ce684f87ff4b7b0003a1c --- react-packager/src/Resolver/polyfills/require-unbundle.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js index c2cddad6..70cd1139 100644 --- a/react-packager/src/Resolver/polyfills/require-unbundle.js +++ b/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -27,13 +27,16 @@ function define(moduleId, factory) { modules[moduleId] = { factory, hasError: false, + isInitialized: false, exports: undefined, }; } function require(moduleId) { const module = modules[moduleId]; - return module && module.exports || loadModule(moduleId, module); + return module && module.isInitialized + ? module.exports + : loadModule(moduleId, module); } function guardedLoadModule(moduleId, module) { @@ -68,6 +71,7 @@ function loadModuleImplementation(moduleId, module) { } const exports = module.exports = {}; + module.isInitialized = true; const {factory} = module; try { if (__DEV__) { @@ -83,6 +87,7 @@ function loadModuleImplementation(moduleId, module) { } return (module.exports = moduleObject.exports); } catch (e) { + module.isInitialized = false; module.hasError = true; module.exports = undefined; } From 071c17522221248adb696901f69df006a2a806c6 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 21 Apr 2016 09:27:50 -0700 Subject: [PATCH 643/936] Move React Core Integration to a Dependency Summary:Adding the react native renderer dependency and various fixes to support React 15. Don't use dispatchID for touchableHandleResponderGrant This callback argument was removed because "IDs" no longer exist. Instead, we'll use the tag from the event target. The corresponding PR on React Core is: https://github.com/facebook/react/pull/6338 Reviewed By: spicyj Differential Revision: D3159788 fb-gh-sync-id: 60e5cd2aa0af69d83fcdac3dfde0a85a748cb7b9 fbshipit-source-id: 60e5cd2aa0af69d83fcdac3dfde0a85a748cb7b9 --- blacklist.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/blacklist.js b/blacklist.js index 823ab156..2a1346f9 100644 --- a/blacklist.js +++ b/blacklist.js @@ -14,8 +14,6 @@ var path = require('path'); // modulePathIgnorePatterns. var sharedBlacklist = [ /node_modules[/\\]react[/\\]dist[/\\].*/, - 'node_modules/react/lib/React.js', - 'node_modules/react/lib/ReactDOM.js', 'downstream/core/invariant.js', From bdc9749ba0cdf2d92866bad647a106a2f5705739 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 22 Apr 2016 06:17:17 -0700 Subject: [PATCH 644/936] Improve error handling in require-unbundle Reviewed By: davidaurelio Differential Revision: D3207450 fb-gh-sync-id: 35247c265e35976dcee9fca4215403efa604479e fbshipit-source-id: 35247c265e35976dcee9fca4215403efa604479e --- .../Resolver/polyfills/require-unbundle.js | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js index 70cd1139..fa774787 100644 --- a/react-packager/src/Resolver/polyfills/require-unbundle.js +++ b/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -9,15 +9,11 @@ 'use strict'; -const {ErrorUtils, nativeRequire} = global; global.require = require; global.__d = define; const modules = Object.create(null); -const loadModule = ErrorUtils ? - guardedLoadModule : loadModuleImplementation; - function define(moduleId, factory) { if (moduleId in modules) { // prevent repeated calls to `global.nativeRequire` to overwrite modules @@ -36,20 +32,29 @@ function require(moduleId) { const module = modules[moduleId]; return module && module.isInitialized ? module.exports - : loadModule(moduleId, module); + : guardedLoadModule(moduleId, module); } +var inGuard = false; function guardedLoadModule(moduleId, module) { - try { + if (global.ErrorUtils && !inGuard) { + inGuard = true; + var returnValue; + try { + returnValue = loadModuleImplementation(moduleId, module); + } catch (e) { + global.ErrorUtils.reportFatalError(e); + } + inGuard = false; + return returnValue; + } else { return loadModuleImplementation(moduleId, module); - } catch (e) { - ErrorUtils.reportFatalError(e); } } function loadModuleImplementation(moduleId, module) { if (!module) { - nativeRequire(moduleId); + global.nativeRequire(moduleId); module = modules[moduleId]; } @@ -90,6 +95,7 @@ function loadModuleImplementation(moduleId, module) { module.isInitialized = false; module.hasError = true; module.exports = undefined; + throw e; } } From 05871df2a9f481bc32f523be3fb2586b8c33c42a Mon Sep 17 00:00:00 2001 From: Alexander Micklewright Date: Fri, 22 Apr 2016 16:05:46 -0700 Subject: [PATCH 645/936] Fix a bug in asset server when using relative project roots Summary:This PR fixes a bug where when using relative roots for the packager server, asset paths would be deemed invalid by the recently introduced security check. Resolving the root to an absolute path fixes that problem. I'd be happy to write a regression test for this but I had a hard time setting up a mock file system with relative paths. If it is required, some help would be appreciated... Closes https://github.com/facebook/react-native/pull/7161 Differential Revision: D3214840 fb-gh-sync-id: 08e13fb9f94a98206fd2d090f74a8b63ba2bf80f fbshipit-source-id: 08e13fb9f94a98206fd2d090f74a8b63ba2bf80f --- react-packager/src/AssetServer/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 57d1aea2..2b25218c 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -129,13 +129,14 @@ class AssetServer { _findRoot(roots, dir) { return Promise.all( roots.map(root => { + const absRoot = path.resolve(root); // important: we want to resolve root + dir // to ensure the requested path doesn't traverse beyond root const absPath = path.resolve(root, dir); return stat(absPath).then(fstat => { // keep asset requests from traversing files // up from the root (e.g. ../../../etc/hosts) - if (!absPath.startsWith(root)) { + if (!absPath.startsWith(absRoot)) { return {path: absPath, isValid: false}; } return {path: absPath, isValid: fstat.isDirectory()}; From ca409964fc792f22842e41a2c6fbe960c5865aab Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Mon, 25 Apr 2016 23:14:45 -0700 Subject: [PATCH 646/936] Add a `Number.isNaN` polyfill Reviewed By: sahrens Differential Revision: D3222299 fb-gh-sync-id: 601283fb0b140bb305181ea381907e62286f7a37 fbshipit-source-id: 601283fb0b140bb305181ea381907e62286f7a37 --- .../src/Resolver/__tests__/Resolver-test.js | 16 ++++++- react-packager/src/Resolver/index.js | 1 + .../src/Resolver/polyfills/Number.es6.js | 24 ++++++++++ .../polyfills/__tests__/Number.es6.js | 45 +++++++++++++++++++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 react-packager/src/Resolver/polyfills/Number.es6.js create mode 100644 react-packager/src/Resolver/polyfills/__tests__/Number.es6.js diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 971e94e6..f53c6912 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -143,12 +143,21 @@ describe('Resolver', function() { 'polyfills/console.js' ], }, + { id: 'polyfills/Number.es6.js', + file: 'polyfills/Number.es6.js', + dependencies: [ + 'polyfills/polyfills.js', + 'polyfills/console.js', + 'polyfills/error-guard.js' + ], + }, { id: 'polyfills/String.prototype.es6.js', file: 'polyfills/String.prototype.es6.js', dependencies: [ 'polyfills/polyfills.js', 'polyfills/console.js', - 'polyfills/error-guard.js' + 'polyfills/error-guard.js', + 'polyfills/Number.es6.js', ], }, { id: 'polyfills/Array.prototype.es6.js', @@ -157,6 +166,7 @@ describe('Resolver', function() { 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', + 'polyfills/Number.es6.js', 'polyfills/String.prototype.es6.js', ], }, @@ -166,6 +176,7 @@ describe('Resolver', function() { 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', + 'polyfills/Number.es6.js', 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', ], @@ -176,6 +187,7 @@ describe('Resolver', function() { 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', + 'polyfills/Number.es6.js', 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', 'polyfills/Array.es6.js', @@ -187,6 +199,7 @@ describe('Resolver', function() { 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', + 'polyfills/Number.es6.js', 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', 'polyfills/Array.es6.js', @@ -251,6 +264,7 @@ describe('Resolver', function() { 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', + 'polyfills/Number.es6.js', 'polyfills/String.prototype.es6.js', 'polyfills/Array.prototype.es6.js', 'polyfills/Array.es6.js', diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index fd67201f..f508c9b6 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -177,6 +177,7 @@ class Resolver { path.join(__dirname, 'polyfills/polyfills.js'), path.join(__dirname, 'polyfills/console.js'), path.join(__dirname, 'polyfills/error-guard.js'), + path.join(__dirname, 'polyfills/Number.es6.js'), path.join(__dirname, 'polyfills/String.prototype.es6.js'), path.join(__dirname, 'polyfills/Array.prototype.es6.js'), path.join(__dirname, 'polyfills/Array.es6.js'), diff --git a/react-packager/src/Resolver/polyfills/Number.es6.js b/react-packager/src/Resolver/polyfills/Number.es6.js new file mode 100644 index 00000000..333bfb94 --- /dev/null +++ b/react-packager/src/Resolver/polyfills/Number.es6.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2013-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. + * + * @provides Number.es6 + * @polyfill + */ + +if (!Number.isNaN) { + // https://github.com/dherman/tc39-codex-wiki/blob/master/data/es6/number/index.md#polyfill-for-numberisnan + const globalIsNaN = global.isNaN; + Object.defineProperty(Number, 'isNaN', { + configurable: true, + enumerable: false, + value: function isNaN(value) { + return typeof value === 'number' && globalIsNaN(value); + }, + writable: true, + }); +} diff --git a/react-packager/src/Resolver/polyfills/__tests__/Number.es6.js b/react-packager/src/Resolver/polyfills/__tests__/Number.es6.js new file mode 100644 index 00000000..44f93d0c --- /dev/null +++ b/react-packager/src/Resolver/polyfills/__tests__/Number.es6.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2013-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. + * + * @emails oncall+jsinfra + */ + +jest.autoMockOff(); + +describe('Number (ES6)', () => { + describe('isNaN()', () => { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN#Examples + beforeEach(() => { + delete Number.isNaN; + jest.resetModuleRegistry(); + require('../Number.es6'); + }); + it('returns true when fed something that is not-a-number', () => { + [ + NaN, + Number.NaN, + 0 / 0, + ].forEach(value => expect(Number.isNaN(value)).toBe(true)); + }); + it('returns false when fed something other than not-a-number', () => { + [ + 'NaN', + undefined, + {}, + 'blabla', + true, + null, + 37, + '37', + '37.37', + '', + ' ', + ].forEach(value => expect(Number.isNaN(value)).toBe(false)); + }); + }); +}); From d07a52d88728687a766ab8c19848f8dd83c9dd5c Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Tue, 26 Apr 2016 11:23:47 -0700 Subject: [PATCH 647/936] Move `Number` polyfills into the `/polyfills/` directory Reviewed By: vjeux Differential Revision: D3223317 fb-gh-sync-id: a49a14f217b27d6542b65c4780c557e73da2443f fbshipit-source-id: a49a14f217b27d6542b65c4780c557e73da2443f --- .../src/Resolver/polyfills/Number.es6.js | 15 ++++++++ .../{Number.es6.js => Number.es6-test.js} | 34 +++++++++++++++++++ 2 files changed, 49 insertions(+) rename react-packager/src/Resolver/polyfills/__tests__/{Number.es6.js => Number.es6-test.js} (52%) diff --git a/react-packager/src/Resolver/polyfills/Number.es6.js b/react-packager/src/Resolver/polyfills/Number.es6.js index 333bfb94..abd59bba 100644 --- a/react-packager/src/Resolver/polyfills/Number.es6.js +++ b/react-packager/src/Resolver/polyfills/Number.es6.js @@ -10,6 +10,21 @@ * @polyfill */ +if (Number.EPSILON === undefined) { + Object.defineProperty(Number, 'EPSILON', { + value: Math.pow(2, -52), + }); +} +if (Number.MAX_SAFE_INTEGER === undefined) { + Object.defineProperty(Number, 'MAX_SAFE_INTEGER', { + value: Math.pow(2, 53) - 1, + }); +} +if (Number.MIN_SAFE_INTEGER === undefined) { + Object.defineProperty(Number, 'MIN_SAFE_INTEGER', { + value: -(Math.pow(2, 53) - 1), + }); +} if (!Number.isNaN) { // https://github.com/dherman/tc39-codex-wiki/blob/master/data/es6/number/index.md#polyfill-for-numberisnan const globalIsNaN = global.isNaN; diff --git a/react-packager/src/Resolver/polyfills/__tests__/Number.es6.js b/react-packager/src/Resolver/polyfills/__tests__/Number.es6-test.js similarity index 52% rename from react-packager/src/Resolver/polyfills/__tests__/Number.es6.js rename to react-packager/src/Resolver/polyfills/__tests__/Number.es6-test.js index 44f93d0c..d91a3aa5 100644 --- a/react-packager/src/Resolver/polyfills/__tests__/Number.es6.js +++ b/react-packager/src/Resolver/polyfills/__tests__/Number.es6-test.js @@ -12,6 +12,40 @@ jest.autoMockOff(); describe('Number (ES6)', () => { + describe('EPSILON', () => { + beforeEach(() => { + delete Number.EPSILON; + jest.resetModuleRegistry(); + require('../Number.es6'); + }); + it('is 2^(-52)', () => { + expect(Number.EPSILON).toBe(Math.pow(2, -52)); + }); + it('can be used to test equality', () => { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON#Testing_equality + expect(Number.EPSILON).toBeGreaterThan(Math.abs(0.2 - 0.3 + 0.1)); + }); + }); + describe('MAX_SAFE_INTEGER', () => { + beforeEach(() => { + delete Number.MAX_SAFE_INTEGER; + jest.resetModuleRegistry(); + require('../Number.es6'); + }); + it('is 2^53 - 1', () => { + expect(Number.MAX_SAFE_INTEGER).toBe(Math.pow(2, 53) - 1); + }); + }); + describe('MIN_SAFE_INTEGER', () => { + beforeEach(() => { + delete Number.MIN_SAFE_INTEGER; + jest.resetModuleRegistry(); + require('../Number.es6'); + }); + it('is -(2^53 - 1)', () => { + expect(Number.MIN_SAFE_INTEGER).toBe(-(Math.pow(2, 53) - 1)); + }); + }); describe('isNaN()', () => { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN#Examples beforeEach(() => { From d4e704be173523d7de0e085809ee3ffa57407640 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Wed, 27 Apr 2016 19:15:28 -0700 Subject: [PATCH 648/936] Update Jest APIs on fbsource Reviewed By: javache Differential Revision: D3229435 fb-gh-sync-id: b0e252d69e1f399a946fca6e98ef62ff44c2ef9c fbshipit-source-id: b0e252d69e1f399a946fca6e98ef62ff44c2ef9c --- .../src/Activity/__tests__/Activity-test.js | 4 +-- .../AssetServer/__tests__/AssetServer-test.js | 10 +++---- .../src/Bundler/__tests__/Bundle-test.js | 4 +-- .../src/Bundler/__tests__/Bundler-test.js | 8 ++--- .../__tests__/Transformer-test.js | 14 ++++----- .../worker/__tests__/constant-folding-test.js | 2 +- .../__tests__/extract-dependencies-test.js | 2 +- .../worker/__tests__/inline-test.js | 2 +- .../worker/__tests__/minify-test.js | 4 +-- .../worker/__tests__/worker-test.js | 4 +-- .../src/Resolver/__tests__/Resolver-test.js | 30 +++++++++---------- .../polyfills/__tests__/Number.es6-test.js | 2 +- .../polyfills/__tests__/Object.es7-test.js | 2 +- .../src/Server/__tests__/Server-test.js | 23 +++++++------- .../src/lib/__tests__/declareOpts-test.js | 2 +- 15 files changed, 56 insertions(+), 57 deletions(-) diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index 0375ee6f..6c219c22 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); var Activity = require('../'); @@ -16,7 +16,7 @@ describe('Activity', () => { const origConsoleLog = console.log; beforeEach(() => { - console.log = jest.genMockFn(); + console.log = jest.fn(); jest.runOnlyPendingTimers(); }); diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index bf96ae8c..7f59253c 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -9,7 +9,7 @@ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); jest .mock('crypto') @@ -199,8 +199,8 @@ describe('AssetServer', () => { describe('assetServer.getAssetData', () => { pit('should get assetData', () => { const hash = { - update: jest.genMockFn(), - digest: jest.genMockFn(), + update: jest.fn(), + digest: jest.fn(), }; hash.digest.mockImpl(() => 'wow such hash'); @@ -241,8 +241,8 @@ describe('AssetServer', () => { pit('should get assetData for non-png images', () => { const hash = { - update: jest.genMockFn(), - digest: jest.genMockFn(), + update: jest.fn(), + digest: jest.fn(), }; hash.digest.mockImpl(() => 'wow such hash'); diff --git a/react-packager/src/Bundler/__tests__/Bundle-test.js b/react-packager/src/Bundler/__tests__/Bundle-test.js index c560d25b..1f6f68ff 100644 --- a/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); const Bundle = require('../Bundle'); const ModuleTransport = require('../../lib/ModuleTransport'); @@ -21,7 +21,7 @@ describe('Bundle', () => { beforeEach(() => { bundle = new Bundle({sourceMapUrl: 'test_url'}); - bundle.getSourceMap = jest.genMockFn().mockImpl(() => { + bundle.getSourceMap = jest.fn(() => { return 'test-source-map'; }); }); diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index ac5cc0bd..f3d5c952 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); jest .setMock('worker-farm', () => () => undefined) @@ -68,8 +68,8 @@ describe('Bundler', function() { var projectRoots; beforeEach(function() { - getDependencies = jest.genMockFn(); - getModuleSystemDependencies = jest.genMockFn(); + getDependencies = jest.fn(); + getModuleSystemDependencies = jest.fn(); projectRoots = ['/root']; Resolver.mockImpl(function() { @@ -90,7 +90,7 @@ describe('Bundler', function() { }); assetServer = { - getAssetData: jest.genMockFn(), + getAssetData: jest.fn(), }; bundler = new Bundler({ diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index dc93a872..a495e565 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -9,12 +9,12 @@ 'use strict'; jest - .dontMock('../../lib/ModuleTransport') - .dontMock('../'); + .unmock('../../lib/ModuleTransport') + .unmock('../'); -const fs = {writeFileSync: jest.genMockFn()}; +const fs = {writeFileSync: jest.fn()}; const temp = {path: () => '/arbitrary/path'}; -const workerFarm = jest.genMockFn(); +const workerFarm = jest.fn(); jest.setMock('fs', fs); jest.setMock('temp', temp); jest.setMock('worker-farm', workerFarm); @@ -29,15 +29,15 @@ describe('Transformer', function() { const transformModulePath = __filename; beforeEach(function() { - Cache = jest.genMockFn(); - Cache.prototype.get = jest.genMockFn().mockImpl((a, b, c) => c()); + Cache = jest.fn(); + Cache.prototype.get = jest.fn((a, b, c) => c()); fs.writeFileSync.mockClear(); options = {transformModulePath}; workerFarm.mockClear(); workerFarm.mockImpl((opts, path, methods) => { const api = workers = {}; - methods.forEach(method => api[method] = jest.genMockFn()); + methods.forEach(method => api[method] = jest.fn()); return api; }); }); diff --git a/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js b/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js index 64ad751f..c717e6d0 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); const babel = require('babel-core'); const constantFolding = require('../constant-folding'); diff --git a/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js b/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js index fba330ee..1d78e73a 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); const extractDependencies = require('../extract-dependencies'); diff --git a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js index 53fe1ed8..f4d11b53 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); const inline = require('../inline'); const {transform, transformFromAst} = require('babel-core'); diff --git a/react-packager/src/JSTransformer/worker/__tests__/minify-test.js b/react-packager/src/JSTransformer/worker/__tests__/minify-test.js index 184a4aef..8eff672e 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/minify-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/minify-test.js @@ -8,10 +8,10 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); const uglify = { - minify: jest.genMockFunction().mockImplementation(code => { + minify: jest.fn(code => { return { code: code.replace(/(^|\W)\s+/g, '$1'), map: {}, diff --git a/react-packager/src/JSTransformer/worker/__tests__/worker-test.js b/react-packager/src/JSTransformer/worker/__tests__/worker-test.js index b66dabe2..4b6ca5a1 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/worker-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/worker-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); jest.mock('../constant-folding'); jest.mock('../extract-dependencies'); jest.mock('../inline'); @@ -22,7 +22,7 @@ describe('code transformation worker:', () => { beforeEach(() => { extractDependencies = require('../extract-dependencies').mockReturnValue({}); - transform = jest.genMockFunction(); + transform = jest.fn(); }); it('calls the transform with file name, source code, and transform options', function() { diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index f53c6912..0f53e2a0 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.dontMock('../'); +jest.unmock('../'); jest.mock('path'); const Promise = require('promise'); @@ -16,7 +16,7 @@ const Resolver = require('../'); const path = require('path'); -let DependencyGraph = jest.genMockFn(); +let DependencyGraph = jest.fn(); jest.setMock('node-haste', DependencyGraph); let Module; let Polyfill; @@ -24,26 +24,26 @@ let Polyfill; describe('Resolver', function() { beforeEach(function() { DependencyGraph.mockClear(); - Module = jest.genMockFn().mockImpl(function() { - this.getName = jest.genMockFn(); - this.getDependencies = jest.genMockFn(); - this.isPolyfill = jest.genMockFn().mockReturnValue(false); - this.isJSON = jest.genMockFn().mockReturnValue(false); + Module = jest.fn(function() { + this.getName = jest.fn(); + this.getDependencies = jest.fn(); + this.isPolyfill = jest.fn().mockReturnValue(false); + this.isJSON = jest.fn().mockReturnValue(false); }); - Polyfill = jest.genMockFn().mockImpl(function() { + Polyfill = jest.fn(function() { var polyfill = new Module(); polyfill.isPolyfill.mockReturnValue(true); return polyfill; }); DependencyGraph.replacePatterns = require.requireActual('node-haste/lib/lib/replacePatterns'); - DependencyGraph.prototype.createPolyfill = jest.genMockFn(); - DependencyGraph.prototype.getDependencies = jest.genMockFn(); + DependencyGraph.prototype.createPolyfill = jest.fn(); + DependencyGraph.prototype.getDependencies = jest.fn(); // For the polyfillDeps - path.join = jest.genMockFn().mockImpl((a, b) => b); + path.join = jest.fn((a, b) => b); - DependencyGraph.prototype.load = jest.genMockFn().mockImpl(() => Promise.resolve()); + DependencyGraph.prototype.load = jest.fn(() => Promise.resolve()); }); class ResolutionResponseMock { @@ -81,9 +81,9 @@ describe('Resolver', function() { function createPolyfill(id, dependencies) { var polyfill = new Polyfill({}); - polyfill.getName = jest.genMockFn().mockImpl(() => Promise.resolve(id)); + polyfill.getName = jest.fn(() => Promise.resolve(id)); polyfill.getDependencies = - jest.genMockFn().mockImpl(() => Promise.resolve(dependencies)); + jest.fn(() => Promise.resolve(dependencies)); return polyfill; } @@ -415,7 +415,7 @@ describe('Resolver', function() { let depResolver, minifyCode, module, resolutionResponse, sourceMap; beforeEach(() => { - minifyCode = jest.genMockFn().mockImpl((filename, code, map) => + minifyCode = jest.fn((filename, code, map) => Promise.resolve({code, map})); depResolver = new Resolver({ projectRoot: '/root', diff --git a/react-packager/src/Resolver/polyfills/__tests__/Number.es6-test.js b/react-packager/src/Resolver/polyfills/__tests__/Number.es6-test.js index d91a3aa5..c15d38b0 100644 --- a/react-packager/src/Resolver/polyfills/__tests__/Number.es6-test.js +++ b/react-packager/src/Resolver/polyfills/__tests__/Number.es6-test.js @@ -9,7 +9,7 @@ * @emails oncall+jsinfra */ -jest.autoMockOff(); +jest.disableAutomock(); describe('Number (ES6)', () => { describe('EPSILON', () => { diff --git a/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js b/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js index ee7782eb..8d34e2bf 100644 --- a/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js +++ b/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js @@ -6,7 +6,7 @@ /* eslint-disable fb-www/object-create-only-one-param */ -jest.autoMockOff(); +jest.disableAutomock(); describe('Object (ES7)', () => { beforeEach(() => { diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index b54ef2b8..a7e702d3 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); jest.setMock('worker-farm', function() { return () => {}; }) .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) }) @@ -54,20 +54,19 @@ describe('processRequest', () => { ) ); - const invalidatorFunc = jest.genMockFunction(); - const watcherFunc = jest.genMockFunction(); + const invalidatorFunc = jest.fn(); + const watcherFunc = jest.fn(); var requestHandler; var triggerFileChange; beforeEach(() => { FileWatcher = require('node-haste').FileWatcher; - Bundler.prototype.bundle = jest.genMockFunction().mockImpl(() => + Bundler.prototype.bundle = jest.fn(() => Promise.resolve({ getSource: () => 'this is the source', getSourceMap: () => 'this is the source map', getEtag: () => 'this is an etag', - }) - ); + })); FileWatcher.prototype.on = function(eventType, callback) { if (eventType !== 'all') { @@ -198,7 +197,7 @@ describe('processRequest', () => { }); it('does not rebuild the bundles that contain a file when that file is changed', () => { - const bundleFunc = jest.genMockFunction(); + const bundleFunc = jest.fn(); bundleFunc .mockReturnValueOnce( Promise.resolve({ @@ -243,7 +242,7 @@ describe('processRequest', () => { }); it('does not rebuild the bundles that contain a file when that file is changed, even when hot loading is enabled', () => { - const bundleFunc = jest.genMockFunction(); + const bundleFunc = jest.fn(); bundleFunc .mockReturnValueOnce( Promise.resolve({ @@ -301,8 +300,8 @@ describe('processRequest', () => { req = new EventEmitter(); req.url = '/onchange'; res = { - writeHead: jest.genMockFn(), - end: jest.genMockFn() + writeHead: jest.fn(), + end: jest.fn() }; }); @@ -326,7 +325,7 @@ describe('processRequest', () => { describe('/assets endpoint', () => { it('should serve simple case', () => { const req = {url: '/assets/imgs/a.png'}; - const res = {end: jest.genMockFn()}; + const res = {end: jest.fn()}; AssetServer.prototype.get.mockImpl(() => Promise.resolve('i am image')); @@ -337,7 +336,7 @@ describe('processRequest', () => { it('should parse the platform option', () => { const req = {url: '/assets/imgs/a.png?platform=ios'}; - const res = {end: jest.genMockFn()}; + const res = {end: jest.fn()}; AssetServer.prototype.get.mockImpl(() => Promise.resolve('i am image')); diff --git a/react-packager/src/lib/__tests__/declareOpts-test.js b/react-packager/src/lib/__tests__/declareOpts-test.js index 0fa1b7c8..c67fa230 100644 --- a/react-packager/src/lib/__tests__/declareOpts-test.js +++ b/react-packager/src/lib/__tests__/declareOpts-test.js @@ -8,7 +8,7 @@ */ 'use strict'; -jest.autoMockOff(); +jest.disableAutomock(); var declareOpts = require('../declareOpts'); From 789978a9ec695f0ae8fb163a580b73408b1c0d3f Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 28 Apr 2016 06:32:52 -0700 Subject: [PATCH 649/936] Upgrade to node-haste@2.10.0 and allow to specify extra node modules Summary: This upgrades to node-haste@2.10.0 and allows to expose folders as additional node modules from rn-cli.config.js Reviewed By: bestander Differential Revision: D3232595 fb-gh-sync-id: dffca66fec55a79a2b3af1d6ec1b8799b2bbcf59 fbshipit-source-id: dffca66fec55a79a2b3af1d6ec1b8799b2bbcf59 --- react-packager/src/Bundler/index.js | 5 +++++ react-packager/src/Resolver/index.js | 5 +++++ react-packager/src/Server/index.js | 6 +++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index a6041e40..c718bb40 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -57,6 +57,10 @@ const validateOpts = declareOpts({ type:'string', required: false, }, + extraNodeModules: { + type: 'object', + required: false, + }, nonPersistent: { type: 'boolean', default: false, @@ -141,6 +145,7 @@ class Bundler { transformCode: (module, code, options) => this._transformer.transformFile(module.path, code, options), + extraNodeModules: opts.extraNodeModules, minifyCode: this._transformer.minify, }); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index f508c9b6..f2cad3e6 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -54,6 +54,10 @@ const validateOpts = declareOpts({ transformCode: { type: 'function', }, + extraNodeModules: { + type: 'object', + required: false, + }, minifyCode: { type: 'function', }, @@ -107,6 +111,7 @@ class Resolver { cache: opts.cache, shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios', transformCode: opts.transformCode, + extraNodeModules: opts.extraNodeModules, assetDependencies: ['react-native/Libraries/Image/AssetRegistry'], }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 1856fc96..608919b7 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -45,7 +45,11 @@ const validateOpts = declareOpts({ default: false, }, transformModulePath: { - type:'string', + type: 'string', + required: false, + }, + extraNodeModules: { + type: 'object', required: false, }, nonPersistent: { From cc538b9b358c1e2f014be86dfe5dd8668bbb9198 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 28 Apr 2016 07:01:49 -0700 Subject: [PATCH 650/936] Enable console.debug Summary: Apparently what we're using for console.trace is not really the same as what Chrome does (we don't log a stacktrace). Add `console.debug` with the same functionality as `console.trace`, just so we don't crash between browser and local execution. Reviewed By: davidaurelio Differential Revision: D3235053 fb-gh-sync-id: 4bed17ac8aa4c8c100f15cf0aabbc25101c913c1 fbshipit-source-id: 4bed17ac8aa4c8c100f15cf0aabbc25101c913c1 --- react-packager/src/Resolver/polyfills/console.js | 1 + 1 file changed, 1 insertion(+) diff --git a/react-packager/src/Resolver/polyfills/console.js b/react-packager/src/Resolver/polyfills/console.js index 5406db73..27efc320 100644 --- a/react-packager/src/Resolver/polyfills/console.js +++ b/react-packager/src/Resolver/polyfills/console.js @@ -471,6 +471,7 @@ function setupConsole(global) { log: getNativeLogFunction(LOG_LEVELS.info), warn: getNativeLogFunction(LOG_LEVELS.warn), trace: getNativeLogFunction(LOG_LEVELS.trace), + debug: getNativeLogFunction(LOG_LEVELS.trace), table: consoleTablePolyfill }; From 17726e1ae6304a8275f0f8e00bc446147175e266 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 28 Apr 2016 11:22:37 -0700 Subject: [PATCH 651/936] Improve constant inlining, add `process.platform` Reviewed By: bestander Differential Revision: D3235716 fb-gh-sync-id: f9019ec0042827e409fa84ba74f4c426ccad1519 fbshipit-source-id: f9019ec0042827e409fa84ba74f4c426ccad1519 --- .../worker/__tests__/inline-test.js | 75 ++++++++++++++++--- .../src/JSTransformer/worker/inline.js | 37 ++++++--- 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js index f4d11b53..ab598388 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js @@ -35,7 +35,7 @@ describe('inline constants', () => { var a = __DEV__ ? 1 : 2; var b = a.__DEV__; var c = function __DEV__(__DEV__) {}; - }` + }`; const {ast} = inline('arbitrary.js', {code}, {dev: true}); expect(toString(ast)).toEqual(normalize(code.replace(/__DEV__/, 'true'))); }); @@ -44,7 +44,7 @@ describe('inline constants', () => { const code = `function a() { var a = Platform.OS; var b = a.Platform.OS; - }` + }`; const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); }); @@ -55,7 +55,18 @@ describe('inline constants', () => { function a() { if (Platform.OS === 'android') a = function() {}; var b = a.Platform.OS; - }` + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); + }); + + it('replaces Platform.OS in the code if Platform is a top level import from react-native', () => { + const code = ` + var Platform = require('react-native').Platform; + function a() { + if (Platform.OS === 'android') a = function() {}; + var b = a.Platform.OS; + }`; const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); }); @@ -64,7 +75,7 @@ describe('inline constants', () => { const code = `function a() { var a = require('Platform').OS; var b = a.require('Platform').OS; - }` + }`; const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); expect(toString(ast)).toEqual( normalize(code.replace(/require\('Platform'\)\.OS/, '"android"'))); @@ -74,18 +85,27 @@ describe('inline constants', () => { const code = `function a() { var a = React.Platform.OS; var b = a.React.Platform.OS; - }` + }`; const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.OS/, '"ios"'))); }); + it('replaces ReactNative.Platform.OS in the code if ReactNative is a global', () => { + const code = `function a() { + var a = ReactNative.Platform.OS; + var b = a.ReactNative.Platform.OS; + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative\.Platform\.OS/, '"ios"'))); + }); + it('replaces React.Platform.OS in the code if React is a top level import', () => { const code = ` var React = require('React'); function a() { if (React.Platform.OS === 'android') a = function() {}; var b = a.React.Platform.OS; - }` + }`; const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); expect(toString(ast)).toEqual(normalize(code.replace(/React.Platform\.OS/, '"ios"'))); }); @@ -94,19 +114,40 @@ describe('inline constants', () => { const code = `function a() { var a = require('React').Platform.OS; var b = a.require('React').Platform.OS; - }` + }`; const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); expect(toString(ast)).toEqual( normalize(code.replace(/require\('React'\)\.Platform\.OS/, '"android"'))); }); + it('replaces ReactNative.Platform.OS in the code if ReactNative is a top level import', () => { + const code = ` + var ReactNative = require('react-native'); + function a() { + if (ReactNative.Platform.OS === 'android') a = function() {}; + var b = a.ReactNative.Platform.OS; + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative.Platform\.OS/, '"android"'))); + }); + + it('replaces require("react-native").Platform.OS in the code', () => { + const code = `function a() { + var a = require('react-native').Platform.OS; + var b = a.require('react-native').Platform.OS; + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/require\('react-native'\)\.Platform\.OS/, '"android"'))); + }); + it('replaces process.env.NODE_ENV in the code', () => { const code = `function a() { if (process.env.NODE_ENV === 'production') { return require('Prod'); } return require('Dev'); - }` + }`; const {ast} = inline('arbitrary.js', {code}, {dev: false}); expect(toString(ast)).toEqual( normalize(code.replace(/process\.env\.NODE_ENV/, '"production"'))); @@ -118,16 +159,28 @@ describe('inline constants', () => { return require('Prod'); } return require('Dev'); - }` + }`; const {ast} = inline('arbitrary.js', {code}, {dev: true}); expect(toString(ast)).toEqual( normalize(code.replace(/process\.env\.NODE_ENV/, '"development"'))); }); + it('replaces process.platform in the code', () => { + const code = `function a() { + if (process.platform === 'android') { + return require('./android'); + } + return require('./ios'); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/process\.platform\b/, '"ios"'))); + }); + it('accepts an AST as input', function() { - const code = `function ifDev(a,b){return __DEV__?a:b;}`; + const code = 'function ifDev(a,b){return __DEV__?a:b;}'; const {ast} = inline('arbitrary.hs', {ast: toAst(code)}, {dev: false}); - expect(toString(ast)).toEqual(code.replace(/__DEV__/, 'false')) + expect(toString(ast)).toEqual(code.replace(/__DEV__/, 'false')); }); }); diff --git a/react-packager/src/JSTransformer/worker/inline.js b/react-packager/src/JSTransformer/worker/inline.js index a294bcef..996ff51f 100644 --- a/react-packager/src/JSTransformer/worker/inline.js +++ b/react-packager/src/JSTransformer/worker/inline.js @@ -11,7 +11,8 @@ const babel = require('babel-core'); const t = babel.types; -const react = {name: 'React'}; +const React = {name: 'React'}; +const ReactNative = {name: 'ReactNative'}; const platform = {name: 'Platform'}; const os = {name: 'OS'}; const requirePattern = {name: 'require'}; @@ -19,9 +20,12 @@ const requirePattern = {name: 'require'}; const env = {name: 'env'}; const nodeEnv = {name: 'NODE_ENV'}; const processId = {name: 'process'}; +const platformId = {name: 'platform'}; const dev = {name: '__DEV__'}; +const importMap = new Map([['ReactNative', 'react-native']]); + const isGlobal = (binding) => !binding; const isToplevelBinding = (binding) => isGlobal(binding) || !binding.scope.parent; @@ -31,20 +35,27 @@ const isRequireCall = (node, dependencyId, scope) => t.isIdentifier(node.callee, requirePattern) && t.isStringLiteral(node.arguments[0], t.stringLiteral(dependencyId)); -const isImport = (node, scope, pattern) => - t.isIdentifier(node, pattern) && - isToplevelBinding(scope.getBinding(pattern.name)) || - isRequireCall(node, pattern.name, scope); +const isImport = (node, scope, patterns) => + patterns.some(pattern => { + const importName = importMap.get(pattern.name) || pattern.name; + return isRequireCall(node, importName, scope); + }); + +function isImportOrGlobal(node, scope, patterns) { + const identifier = patterns.find(pattern => t.isIdentifier(node, pattern)); + return identifier && isToplevelBinding(scope.getBinding(identifier.name)) || + isImport(node, scope, patterns); +} const isPlatformOS = (node, scope) => t.isIdentifier(node.property, os) && - isImport(node.object, scope, platform); + isImportOrGlobal(node.object, scope, [platform]); const isReactPlatformOS = (node, scope) => t.isIdentifier(node.property, os) && t.isMemberExpression(node.object) && t.isIdentifier(node.object.property, platform) && - isImport(node.object.object, scope, react); + isImportOrGlobal(node.object.object, scope, [React, ReactNative]); const isProcessEnvNodeEnv = (node, scope) => t.isIdentifier(node.property, nodeEnv) && @@ -53,6 +64,11 @@ const isProcessEnvNodeEnv = (node, scope) => t.isIdentifier(node.object.object, processId) && isGlobal(scope.getBinding(processId.name)); +const isProcessPlatform = (node, scope) => + t.isIdentifier(node.property, platformId) && + t.isIdentifier(node.object, processId) && + isGlobal(scope.getBinding(processId.name)); + const isDev = (node, parent, scope) => t.isIdentifier(node, dev) && isGlobal(scope.getBinding(dev.name)) && @@ -71,11 +87,12 @@ const inlinePlugin = { if (isPlatformOS(node, scope) || isReactPlatformOS(node, scope)) { path.replaceWith(t.stringLiteral(state.opts.platform)); - } - - if(isProcessEnvNodeEnv(node, scope)) { + } else if (isProcessEnvNodeEnv(node, scope)) { path.replaceWith( t.stringLiteral(state.opts.dev ? 'development' : 'production')); + } else if (isProcessPlatform(node, scope)) { + path.replaceWith( + t.stringLiteral(state.opts.platform)); } }, }, From cfdd04f2d27a57382ed7a65e991ca9af70b3d7ce Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Fri, 29 Apr 2016 04:13:30 -0700 Subject: [PATCH 652/936] Launch the packager with `react-native run-android` on Windows Summary: Adds support for launching the packager in a new window on Windows. **Test plan (required)** Tested that the packager is launched in a completely independent process. This means that exiting the `react-native run-android` command should not close the packager window and it should exit properly when completed even if the packager window is still opened. Pretty much made sure it behaves exactly like on mac. Also tested that an error in the packager will not close the window immediately to show the error stack trace. Closes https://github.com/facebook/react-native/pull/7129 Differential Revision: D3240628 Pulled By: mkonicek fb-gh-sync-id: 007582250536481d2b2376f9a201f8f415fc1080 fbshipit-source-id: 007582250536481d2b2376f9a201f8f415fc1080 --- launchPackager.bat | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 launchPackager.bat diff --git a/launchPackager.bat b/launchPackager.bat new file mode 100644 index 00000000..b0c395b9 --- /dev/null +++ b/launchPackager.bat @@ -0,0 +1,12 @@ +:: 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. + +@echo off +title React Packager +node "%~dp0..\local-cli\cli.js" start +pause +exit From 2a77da76ae6fddba3887f4b39f1d5394355f9dbe Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Fri, 29 Apr 2016 10:15:26 -0700 Subject: [PATCH 653/936] Unify source map approach for RA bundles on iOS/Android Reviewed By: javache Differential Revision: D3229780 fb-gh-sync-id: a3148d7626b32a2e6803ae8c35ac75025a992a32 fbshipit-source-id: a3148d7626b32a2e6803ae8c35ac75025a992a32 --- react-packager/src/Bundler/Bundle.js | 71 +++++++++------------------- react-packager/src/Bundler/index.js | 9 ++-- 2 files changed, 26 insertions(+), 54 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index a2edd080..4a172d10 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -107,61 +107,23 @@ class Bundle extends BundleBase { } getUnbundle(type) { - if (this._ramBundle) { - return this._ramBundle; - } + this.assertFinalized(); + if (!this._ramBundle) { + const modules = this.getModules().slice(); - switch (type) { - case 'INDEX': - this._ramBundle = this._getAsIndexedFileUnbundle(); - break; - case 'ASSETS': - this._ramBundle = this._getAsAssetsUnbundle(); - break; - default: - throw new Error('Unkown RAM Bundle type:', type); + // separate modules we need to preload from the ones we don't + const [startupModules, lazyModules] = partition(modules, shouldPreload); + + this._ramBundle = { + startupModules, + lazyModules, + allModules: modules, + }; } return this._ramBundle; } - _getAsIndexedFileUnbundle() { - const modules = this.getModules(); - - // separate modules we need to preload from the ones we don't - const shouldPreload = (module) => module.meta && module.meta.preloaded; - const preloaded = modules.filter(module => shouldPreload(module)); - const notPreloaded = modules.filter(module => !shouldPreload(module)); - - // code that will be executed on bridge start up - const startupCode = preloaded.map(({code}) => code).join('\n'); - - return { - // include copy of all modules on the order they're writen on the bundle: - // polyfills, preloaded, additional requires, non preloaded - allModules: preloaded.concat(notPreloaded), - startupCode, // no entries on the index for these modules, only the code - modules: notPreloaded, // we include both the code and entries on the index - }; - } - - _getAsAssetsUnbundle() { - const allModules = this.getModules().slice(); - const prependedModules = this._numPrependedModules; - const requireCalls = this._numRequireCalls; - - const modules = - allModules - .splice(prependedModules, allModules.length - requireCalls - prependedModules); - const startupCode = allModules.map(({code}) => code).join('\n'); - - return { - startupCode, - startupModules: allModules, - modules, - }; - } - /** * Combine each of the sourcemaps multiple modules have into a single big * one. This works well thanks to a neat trick defined on the sourcemap spec @@ -351,4 +313,15 @@ function generateSourceMapForVirtualModule(module) { }; } +function shouldPreload({meta}) { + return meta && meta.preloaded; +} + +function partition(array, predicate) { + const included = []; + const excluded = []; + array.forEach(item => (predicate(item) ? included : excluded).push(item)); + return [included, excluded]; +} + module.exports = Bundle; diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index c718bb40..2c02809e 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -556,12 +556,11 @@ class Bundler { ]).then(( [name, {code, dependencies, dependencyOffsets, map, source}] ) => { + const {preloadedModules} = transformOptions.transform; const preloaded = module.path === entryFilePath || - module.isPolyfill() || ( - transformOptions.transform.preloadedModules && - transformOptions.transform.preloadedModules.hasOwnProperty(module.path) - ); + module.isPolyfill() || + preloadedModules && preloadedModules.hasOwnProperty(module.path); return new ModuleTransport({ name, @@ -571,7 +570,7 @@ class Bundler { meta: {dependencies, dependencyOffsets, preloaded}, sourceCode: source, sourcePath: module.path - }) + }); }); } From 50145b659f9b327cc60f81180fc701ed52422ee8 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Sat, 30 Apr 2016 16:24:55 -0700 Subject: [PATCH 654/936] Use a separate babel config for the local-cli and the packager Summary: This separates the babel config of the local-cli and the packager from the one used by the transforms of the packager since it doesn't run in the same environment and the local-cli/packager doesn't require react specific transforms and runs in node 4 so we can also avoid some es2015 transforms that node already supports. I had to move the code in cli.js so it can still run in node 0.12 that doesn't support `const` since it is no longer transformed. **Test plan** Run the local-cli on node 0.12 and there should be a message saying that it requires at least node 4. Run the local-cli on node 4 and 5 and everything should work the same as before. I was also hoping for some perf gains but there was nothing noticeable. I did benchmark the babel-register call and it stayed pretty much the same. As for runtime performance it can help if there are optimisations for es2015 features in node. Closes https://github.com/facebook/react-native/pull/6155 Differential Revision: D3242754 Pulled By: davidaurelio fb-gh-sync-id: 02880c841c10562d5f107e1c975d668e55cc619f fbshipit-source-id: 02880c841c10562d5f107e1c975d668e55cc619f --- babelRegisterOnly.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/babelRegisterOnly.js b/babelRegisterOnly.js index bb973ba4..703ba338 100644 --- a/babelRegisterOnly.js +++ b/babelRegisterOnly.js @@ -12,20 +12,19 @@ Array.prototype.values || require('core-js/fn/array/values'); Object.entries || require('core-js/fn/object/entries'); Object.values || require('core-js/fn/object/values'); -var fs = require('fs'); -var path = require('path'); - var _only = []; -function readBabelRC() { - var rcpath = path.join(__dirname, 'react-packager', 'rn-babelrc.json'); - var source = fs.readFileSync(rcpath).toString(); - return JSON.parse(source); -} - module.exports = function(onlyList) { _only = _only.concat(onlyList); - var config = readBabelRC(); - config.only = _only; - require('babel-register')(config); + + require('babel-register')({ + presets: ['es2015-node'], + plugins: [ + 'transform-flow-strip-types', + 'syntax-trailing-function-commas', + 'transform-object-rest-spread', + ], + only: _only, + sourceMaps: 'inline', + }); }; From b6d447769b7c245f4ac699df75fd7e948c6ecfee Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Sat, 30 Apr 2016 16:53:52 -0700 Subject: [PATCH 655/936] Reverted commit D3242754 Summary: This separates the babel config of the local-cli and the packager from the one used by the transforms of the packager since it doesn't run in the same environment and the local-cli/packager doesn't require react specific transforms and runs in node 4 so we can also avoid some es2015 transforms that node already supports. I had to move the code in cli.js so it can still run in node 0.12 that doesn't support `const` since it is no longer transformed. **Test plan** Run the local-cli on node 0.12 and there should be a message saying that it requires at least node 4. Run the local-cli on node 4 and 5 and everything should work the same as before. I was also hoping for some perf gains but there was nothing noticeable. I did benchmark the babel-register call and it stayed pretty much the same. As for runtime performance it can help if there are optimisations for es2015 features in node. Closes https://github.com/facebook/react-native/pull/6155 Differential Revision: D3242754 Pulled By: eczarny fb-gh-sync-id: 6cd349e284b7d92a1b2cc8b5c0e26adbfb0d9a2f fbshipit-source-id: 6cd349e284b7d92a1b2cc8b5c0e26adbfb0d9a2f --- babelRegisterOnly.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/babelRegisterOnly.js b/babelRegisterOnly.js index 703ba338..bb973ba4 100644 --- a/babelRegisterOnly.js +++ b/babelRegisterOnly.js @@ -12,19 +12,20 @@ Array.prototype.values || require('core-js/fn/array/values'); Object.entries || require('core-js/fn/object/entries'); Object.values || require('core-js/fn/object/values'); +var fs = require('fs'); +var path = require('path'); + var _only = []; +function readBabelRC() { + var rcpath = path.join(__dirname, 'react-packager', 'rn-babelrc.json'); + var source = fs.readFileSync(rcpath).toString(); + return JSON.parse(source); +} + module.exports = function(onlyList) { _only = _only.concat(onlyList); - - require('babel-register')({ - presets: ['es2015-node'], - plugins: [ - 'transform-flow-strip-types', - 'syntax-trailing-function-commas', - 'transform-object-rest-spread', - ], - only: _only, - sourceMaps: 'inline', - }); + var config = readBabelRC(); + config.only = _only; + require('babel-register')(config); }; From 7ca4e764b6f1c034b8380f8b0baeebbb46e65bf0 Mon Sep 17 00:00:00 2001 From: Mark Oswald Date: Mon, 2 May 2016 08:02:46 -0700 Subject: [PATCH 656/936] call bundle with reset-cache true as default from standard project Summary: addition to #7297 When called from an upstarting app, the bundling process should always be called with a cleared cache. That avoids possible problems with cached files. Closes https://github.com/facebook/react-native/pull/7324 Differential Revision: D3247420 fb-gh-sync-id: 503ad39cb36455512ccea1af7618e89a80942f0c fbshipit-source-id: 503ad39cb36455512ccea1af7618e89a80942f0c --- react-native-xcode.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index 57201bab..ad27792e 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -72,5 +72,6 @@ $NODE_BINARY "$REACT_NATIVE_DIR/local-cli/cli.js" bundle \ --entry-file index.ios.js \ --platform ios \ --dev $DEV \ + --reset-cache true \ --bundle-output "$DEST/main.jsbundle" \ --assets-dest "$DEST" From daa426c46ad3fd585e842a3d563371420d0d29c8 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 3 May 2016 15:18:10 -0700 Subject: [PATCH 657/936] @ignore-signedsource [react-native-packager] use a single require implementation Reviewed By: javache, bestander Differential Revision: D3252718 fb-gh-sync-id: bfd85acc28dd6e2df72a3227743514cb6f8c32f1 fbshipit-source-id: bfd85acc28dd6e2df72a3227743514cb6f8c32f1 --- react-packager/src/Resolver/index.js | 4 +- .../Resolver/polyfills/require-unbundle.js | 117 ----------- .../src/Resolver/polyfills/require.js | 184 ++++++++++-------- 3 files changed, 106 insertions(+), 199 deletions(-) delete mode 100644 react-packager/src/Resolver/polyfills/require-unbundle.js diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index f2cad3e6..9fd7e4aa 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -163,9 +163,7 @@ class Resolver { ? path.join(__dirname, 'polyfills/prelude_dev.js') : path.join(__dirname, 'polyfills/prelude.js'); - const moduleSystem = opts.unbundle - ? path.join(__dirname, 'polyfills/require-unbundle.js') - : path.join(__dirname, 'polyfills/require.js'); + const moduleSystem = path.join(__dirname, 'polyfills/require.js'); return [ prelude, diff --git a/react-packager/src/Resolver/polyfills/require-unbundle.js b/react-packager/src/Resolver/polyfills/require-unbundle.js deleted file mode 100644 index fa774787..00000000 --- a/react-packager/src/Resolver/polyfills/require-unbundle.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright (c) 2013-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'; - -global.require = require; -global.__d = define; - -const modules = Object.create(null); - -function define(moduleId, factory) { - if (moduleId in modules) { - // prevent repeated calls to `global.nativeRequire` to overwrite modules - // that are already loaded - return; - } - modules[moduleId] = { - factory, - hasError: false, - isInitialized: false, - exports: undefined, - }; -} - -function require(moduleId) { - const module = modules[moduleId]; - return module && module.isInitialized - ? module.exports - : guardedLoadModule(moduleId, module); -} - -var inGuard = false; -function guardedLoadModule(moduleId, module) { - if (global.ErrorUtils && !inGuard) { - inGuard = true; - var returnValue; - try { - returnValue = loadModuleImplementation(moduleId, module); - } catch (e) { - global.ErrorUtils.reportFatalError(e); - } - inGuard = false; - return returnValue; - } else { - return loadModuleImplementation(moduleId, module); - } -} - -function loadModuleImplementation(moduleId, module) { - if (!module) { - global.nativeRequire(moduleId); - module = modules[moduleId]; - } - - if (!module) { - throw unknownModuleError(moduleId); - } - - if (module.hasError) { - throw moduleThrewError(moduleId); - } - - // `require` calls int the require polyfill itself are not analyzed and - // replaced so that they use numeric module IDs. - // The systrace module will expose itself on the require function so that - // it can be used here. - // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) - if (__DEV__) { - var {Systrace} = require; - } - - const exports = module.exports = {}; - module.isInitialized = true; - const {factory} = module; - try { - if (__DEV__) { - Systrace.beginEvent('JS_require_' + moduleId); - } - - const moduleObject = {exports}; - factory(global, require, moduleObject, exports); - module.factory = undefined; - - if (__DEV__) { - Systrace.endEvent(); - } - return (module.exports = moduleObject.exports); - } catch (e) { - module.isInitialized = false; - module.hasError = true; - module.exports = undefined; - throw e; - } -} - -function unknownModuleError(id) { - let message = 'Requiring unknown module "' + id + '".'; - if (__DEV__) { - message += - 'If you are sure the module is there, try restarting the packager or running "npm install".'; - } - return Error(message); -} - -function moduleThrewError(id) { - return Error('Requiring module "' + id + '", which threw an exception.'); -} - -if (__DEV__) { - require.Systrace = { beginEvent: () => {}, endEvent: () => {} }; -} diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index bf5c3711..04592721 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -7,65 +7,67 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -/* eslint strict:0 */ -var modules = Object.create(null); -var inGuard = false; +'use strict'; -function define(id, factory) { - modules[id] = { +global.require = require; +global.__d = define; + +const modules = Object.create(null); + +function define(moduleId, factory) { + if (moduleId in modules) { + // prevent repeated calls to `global.nativeRequire` to overwrite modules + // that are already loaded + return; + } + modules[moduleId] = { factory, - module: {exports: {}}, - isInitialized: false, hasError: false, + isInitialized: false, + exports: undefined, }; - if (__DEV__) { // HMR - Object.assign(modules[id].module, { - hot: { - acceptCallback: null, - accept: function(callback) { - modules[id].module.hot.acceptCallback = callback; - } - } - }); + modules[moduleId].hot = createHotReloadingObject(); } } -function require(id) { - var mod = modules[id]; - if (mod && mod.isInitialized) { - return mod.module.exports; - } - - return requireImpl(id); +function require(moduleId) { + const module = modules[moduleId]; + return module && module.isInitialized + ? module.exports + : guardedLoadModule(moduleId, module); } -function requireImpl(id) { - if (global.ErrorUtils && !inGuard) { +var inGuard = false; +function guardedLoadModule(moduleId, module) { + if (!inGuard && global.ErrorUtils) { inGuard = true; var returnValue; try { - returnValue = requireImpl(id); + returnValue = loadModuleImplementation(moduleId, module); } catch (e) { global.ErrorUtils.reportFatalError(e); } inGuard = false; return returnValue; + } else { + return loadModuleImplementation(moduleId, module); + } +} + +function loadModuleImplementation(moduleId, module) { + const nativeRequire = global.nativeRequire; + if (!module && nativeRequire) { + nativeRequire(moduleId); + module = modules[moduleId]; } - var mod = modules[id]; - if (!mod) { - var msg = 'Requiring unknown module "' + id + '"'; - if (__DEV__) { - msg += '. If you are sure the module is there, try restarting the packager or running "npm install".'; - } - throw new Error(msg); + if (!module) { + throw unknownModuleError(moduleId); } - if (mod.hasError) { - throw new Error( - 'Requiring module "' + id + '" which threw an exception' - ); + if (module.hasError) { + throw moduleThrewError(moduleId); } // `require` calls int the require polyfill itself are not analyzed and @@ -77,49 +79,95 @@ function requireImpl(id) { var {Systrace} = require; } + // We must optimistically mark module as initialized before running the + // factory to keep any require cycles inside the factory from causing an + // infinite require loop. + module.isInitialized = true; + const exports = module.exports = {}; + const {factory} = module; try { - // We must optimistically mark mod as initialized before running the factory to keep any - // require cycles inside the factory from causing an infinite require loop. - mod.isInitialized = true; - if (__DEV__) { - Systrace.beginEvent('JS_require_' + id); + Systrace.beginEvent('JS_require_' + moduleId); + } + + const moduleObject = {exports}; + if (__DEV__ && module.hot) { + moduleObject.hot = module.hot; } // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js - mod.factory.call(global, global, require, mod.module, mod.module.exports); - mod.factory = undefined; + factory(global, require, moduleObject, exports); + module.factory = undefined; if (__DEV__) { Systrace.endEvent(); } + return (module.exports = moduleObject.exports); } catch (e) { - mod.hasError = true; - mod.isInitialized = false; + module.hasError = true; + module.isInitialized = false; + module.exports = undefined; throw e; } +} - return mod.module.exports; +function unknownModuleError(id) { + let message = 'Requiring unknown module "' + id + '".'; + if (__DEV__) { + message += + 'If you are sure the module is there, try restarting the packager or running "npm install".'; + } + return Error(message); +} + +function moduleThrewError(id) { + return Error('Requiring module "' + id + '", which threw an exception.'); } if (__DEV__) { require.Systrace = { beginEvent: () => {}, endEvent: () => {} }; -} -global.__d = define; -global.require = require; + // HOT MODULE RELOADING + var createHotReloadingObject = function() { + const hot = { + acceptCallback: null, + accept: callback => { hot.acceptCallback = callback; }, + }; + return hot; + }; -if (__DEV__) { // HMR - function accept(id, factory, inverseDependencies) { - var mod = modules[id]; + const acceptAll = function(dependentModules, inverseDependencies) { + if (!dependentModules || dependentModules.length === 0) { + return true; + } + + const notAccepted = dependentModules.filter( + module => !accept(module, /*factory*/ undefined, inverseDependencies)); + + const parents = []; + for (let i = 0; i < notAccepted.length; i++) { + // if the module has no parents then the change cannot be hot loaded + if (inverseDependencies[notAccepted[i]].length === 0) { + return false; + } + + parents.pushAll(inverseDependencies[notAccepted[i]]); + } + + return acceptAll(parents, inverseDependencies); + }; + + const accept = function(id, factory, inverseDependencies) { + const mod = modules[id]; if (!mod) { define(id, factory); return true; // new modules don't need to be accepted } - if (!mod.module.hot) { + const {hot} = mod; + if (!hot) { console.warn( 'Cannot accept module because Hot Module Replacement ' + 'API was not installed.' @@ -134,8 +182,8 @@ if (__DEV__) { // HMR mod.isInitialized = false; require(id); - if (mod.module.hot.acceptCallback) { - mod.module.hot.acceptCallback(); + if (hot.acceptCallback) { + hot.acceptCallback(); return true; } else { // need to have inverseDependencies to bubble up accept @@ -146,29 +194,7 @@ if (__DEV__) { // HMR // accept parent modules recursively up until all siblings are accepted return acceptAll(inverseDependencies[id], inverseDependencies); } - } - - function acceptAll(modules, inverseDependencies) { - if (!modules || modules.length === 0) { - return true; - } - - var notAccepted = modules.filter(function(module) { - return !accept(module, /*factory*/ undefined, inverseDependencies); - }); - - var parents = []; - for (var i = 0; i < notAccepted.length; i++) { - // if this the module has no parents then the change cannot be hot loaded - if (inverseDependencies[notAccepted[i]].length === 0) { - return false; - } - - parents.pushAll(inverseDependencies[notAccepted[i]]); - } - - return acceptAll(parents, inverseDependencies); - } + }; global.__accept = accept; } From 4695f844486b3697b4c4d5a985bfe43d2b7db28f Mon Sep 17 00:00:00 2001 From: jsdevel Date: Wed, 4 May 2016 02:44:42 -0700 Subject: [PATCH 658/936] Setting current working directory for dev server. Summary: * This allows `react-native` to work for users on fedora. * `react-native run-android` was failing because the launch packager script was unable to find packager.sh (see `source` in `man bash`). * This change sets cwd for the dev server when run with `run-android`. Closes https://github.com/facebook/react-native/pull/7316 Differential Revision: D3255866 fb-gh-sync-id: 88f6b18f7c61636ce8fecef77f7f4e60b1d9a637 fbshipit-source-id: 88f6b18f7c61636ce8fecef77f7f4e60b1d9a637 --- launchPackager.command | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchPackager.command b/launchPackager.command index 56683667..6f894cfd 100755 --- a/launchPackager.command +++ b/launchPackager.command @@ -13,7 +13,7 @@ clear THIS_DIR=$(dirname "$0") pushd "$THIS_DIR" -source packager.sh +source ./packager.sh popd echo "Process terminated. Press to close the window" From 5f53658fb010a9e7cb4e5797b8bf6e56151a5bee Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 4 May 2016 02:50:36 -0700 Subject: [PATCH 659/936] remove support for `process.platform` Reviewed By: javache Differential Revision: D3252666 fb-gh-sync-id: af0e4987c34f43ec2472cbdf52fc112b81050513 fbshipit-source-id: af0e4987c34f43ec2472cbdf52fc112b81050513 --- .../JSTransformer/worker/__tests__/inline-test.js | 12 ------------ react-packager/src/JSTransformer/worker/inline.js | 9 --------- 2 files changed, 21 deletions(-) diff --git a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js index ab598388..102e39ab 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js @@ -165,18 +165,6 @@ describe('inline constants', () => { normalize(code.replace(/process\.env\.NODE_ENV/, '"development"'))); }); - it('replaces process.platform in the code', () => { - const code = `function a() { - if (process.platform === 'android') { - return require('./android'); - } - return require('./ios'); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual( - normalize(code.replace(/process\.platform\b/, '"ios"'))); - }); - it('accepts an AST as input', function() { const code = 'function ifDev(a,b){return __DEV__?a:b;}'; const {ast} = inline('arbitrary.hs', {ast: toAst(code)}, {dev: false}); diff --git a/react-packager/src/JSTransformer/worker/inline.js b/react-packager/src/JSTransformer/worker/inline.js index 996ff51f..13e20b0e 100644 --- a/react-packager/src/JSTransformer/worker/inline.js +++ b/react-packager/src/JSTransformer/worker/inline.js @@ -20,7 +20,6 @@ const requirePattern = {name: 'require'}; const env = {name: 'env'}; const nodeEnv = {name: 'NODE_ENV'}; const processId = {name: 'process'}; -const platformId = {name: 'platform'}; const dev = {name: '__DEV__'}; @@ -64,11 +63,6 @@ const isProcessEnvNodeEnv = (node, scope) => t.isIdentifier(node.object.object, processId) && isGlobal(scope.getBinding(processId.name)); -const isProcessPlatform = (node, scope) => - t.isIdentifier(node.property, platformId) && - t.isIdentifier(node.object, processId) && - isGlobal(scope.getBinding(processId.name)); - const isDev = (node, parent, scope) => t.isIdentifier(node, dev) && isGlobal(scope.getBinding(dev.name)) && @@ -90,9 +84,6 @@ const inlinePlugin = { } else if (isProcessEnvNodeEnv(node, scope)) { path.replaceWith( t.stringLiteral(state.opts.dev ? 'development' : 'production')); - } else if (isProcessPlatform(node, scope)) { - path.replaceWith( - t.stringLiteral(state.opts.platform)); } }, }, From 4608593dce0d6b13fd341ec0c637578a6a615fba Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 4 May 2016 09:32:30 -0700 Subject: [PATCH 660/936] Add verbose module names to dev builds Summary: This intends to make the devx story better after switching to numeric module IDs: - `require(')` works again for dev builds - In dev builds, Systrace will use the verbose names of modules for markers Reviewed By: bestander Differential Revision: D3253318 fb-gh-sync-id: 3425d5086ce28634653a6c8c7f5f11afa1614902 fbshipit-source-id: 3425d5086ce28634653a6c8c7f5f11afa1614902 --- react-packager/src/Bundler/Bundle.js | 4 ++- react-packager/src/Bundler/index.js | 2 +- .../src/Resolver/__tests__/Resolver-test.js | 32 ++++++++++++++++--- react-packager/src/Resolver/index.js | 15 +++++---- .../src/Resolver/polyfills/require.js | 24 ++++++++++++-- 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 4a172d10..c3cdc804 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -17,13 +17,14 @@ const crypto = require('crypto'); const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL='; class Bundle extends BundleBase { - constructor({sourceMapUrl, minify} = {}) { + constructor({sourceMapUrl, dev, minify} = {}) { super(); this._sourceMap = false; this._sourceMapUrl = sourceMapUrl; this._shouldCombineSourceMaps = false; this._numPrependedModules = 0; this._numRequireCalls = 0; + this._dev = dev; this._minify = minify; this._ramBundle = null; // cached RAM Bundle @@ -39,6 +40,7 @@ class Bundle extends BundleBase { map: moduleTransport.map, meta: moduleTransport.meta, minify: this._minify, + dev: this._dev, }).then(({code, map}) => { // If we get a map from the transformer we'll switch to a mode // were we're combining the source maps as opposed to diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 2c02809e..197b24f3 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -169,7 +169,7 @@ class Bundler { const moduleSystemDeps = this._resolver.getModuleSystemDependencies({dev, unbundle}); return this._bundle({ - bundle: new Bundle({minify, sourceMapUrl: options.sourceMapUrl}), + bundle: new Bundle({dev, minify, sourceMapUrl: options.sourceMapUrl}), moduleSystemDeps, ...options, }); diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 0f53e2a0..3d89226d 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -333,7 +333,8 @@ describe('Resolver', function() { module: module, name: 'test module', code, - meta: {dependencyOffsets} + meta: {dependencyOffsets}, + dev: false, }).then(({code: processedCode}) => { expect(processedCode).toEqual([ `__d(${getModuleId(module)} /* test module */, function(global, require, module, exports) {` + @@ -348,6 +349,28 @@ describe('Resolver', function() { }); }); + pit('should add module transport names as third argument to `__d`', () => { + const module = createModule('test module'); + const code = 'arbitrary(code)' + const resolutionResponse = new ResolutionResponseMock({ + dependencies: [module], + mainModuleId: 'test module', + }); + return depResolver.wrapModule({ + resolutionResponse, + code, + module, + name: 'test module', + dev: true, + }).then(({code: processedCode}) => + expect(processedCode).toEqual([ + `__d(${getModuleId(module)} /* test module */, function(global, require, module, exports) {` + + code, + '}, "test module");' + ].join('\n')) + ); + }); + pit('should pass through passed-in source maps', () => { const module = createModule('test module'); const resolutionResponse = new ResolutionResponseMock({ @@ -400,7 +423,7 @@ describe('Resolver', function() { pit('should prefix JSON files with `module.exports=`', () => { return depResolver - .wrapModule({resolutionResponse, module, name: id, code}) + .wrapModule({resolutionResponse, module, name: id, code, dev: false}) .then(({code: processedCode}) => expect(processedCode).toEqual([ `__d(${getModuleId(module)} /* ${id} */, function(global, require, module, exports) {`, @@ -420,7 +443,7 @@ describe('Resolver', function() { depResolver = new Resolver({ projectRoot: '/root', getModuleId, - minifyCode + minifyCode, }); module = createModule(id); module.path = '/arbitrary/path.js'; @@ -440,7 +463,8 @@ describe('Resolver', function() { name: id, code, map: sourceMap, - minify: true + minify: true, + dev: false, }).then(() => { expect(minifyCode).toBeCalledWith(module.path, wrappedCode, sourceMap); }); diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 9fd7e4aa..772c067b 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -241,6 +241,7 @@ class Resolver { map, code, meta = {}, + dev = true, minify = false }) { if (module.isJSON()) { @@ -257,7 +258,7 @@ class Resolver { code, meta.dependencyOffsets ); - code = defineModuleCode(moduleId, code, name); + code = defineModuleCode(moduleId, code, name, dev); } @@ -275,13 +276,15 @@ class Resolver { } } -function defineModuleCode(moduleName, code, verboseName = '') { +function defineModuleCode(moduleName, code, verboseName = '', dev = true) { return [ - `__d(`, + '__d(', `${JSON.stringify(moduleName)} /* ${verboseName} */, `, - `function(global, require, module, exports) {`, - `${code}`, - '\n});', + 'function(global, require, module, exports) {', + code, + '\n}', + dev ? `, ${JSON.stringify(verboseName)}` : '', + ');', ].join(''); } diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 04592721..a8c66673 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -13,6 +13,9 @@ global.require = require; global.__d = define; const modules = Object.create(null); +if (__DEV__) { + var verboseNamesToModuleIds = Object.create(null); +} function define(moduleId, factory) { if (moduleId in modules) { @@ -26,8 +29,14 @@ function define(moduleId, factory) { isInitialized: false, exports: undefined, }; - if (__DEV__) { // HMR + if (__DEV__) { + // HMR modules[moduleId].hot = createHotReloadingObject(); + + // DEBUGGABLE MODULES NAMES + // avoid unnecessary parameter in prod + const verboseName = modules[moduleId].verboseName = arguments[2]; + verboseNamesToModuleIds[verboseName] = moduleId; } } @@ -62,6 +71,17 @@ function loadModuleImplementation(moduleId, module) { module = modules[moduleId]; } + if (__DEV__ && !module) { + // allow verbose module names to be passed as module ID + module = modules[verboseNamesToModuleIds[moduleId]]; + if (module) { + console.warn( + `Requiring module '${moduleId}' by name is only supported for ` + + 'debugging purposes and will break in production' + ); + } + } + if (!module) { throw unknownModuleError(moduleId); } @@ -87,7 +107,7 @@ function loadModuleImplementation(moduleId, module) { const {factory} = module; try { if (__DEV__) { - Systrace.beginEvent('JS_require_' + moduleId); + Systrace.beginEvent('JS_require_' + (module.verboseName || moduleId)); } const moduleObject = {exports}; From f9da45197f5a9afa4dd90c737211246cd424790c Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 4 May 2016 10:49:29 -0700 Subject: [PATCH 661/936] Cleanup InitializeJavascriptAppEngine Reviewed By: davidaurelio Differential Revision: D3235141 fb-gh-sync-id: 86fc844c5e9d9ea57d504696bac30671c2079e7a fbshipit-source-id: 86fc844c5e9d9ea57d504696bac30671c2079e7a --- react-packager/src/Bundler/index.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 197b24f3..2d67fad9 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -248,16 +248,6 @@ class Bundler { entryModuleOnly, resolutionResponse, }) { - if (dev && runBeforeMainModule) { // no runBeforeMainModule for hmr bundles - // `require` calls in the require polyfill itself are not extracted and - // replaced with numeric module IDs, but the require polyfill - // needs Systrace. - // Therefore, we include the Systrace module before the main module, and - // it will set itself as property on the require function. - // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) - runBeforeMainModule = runBeforeMainModule.concat(['Systrace']); - } - const onResolutionResponse = response => { bundle.setMainModuleId(this._getModuleId(getMainModule(response))); if (bundle.setNumPrependedModules) { From 9dcdb156f075136d8a1d555bfc2fb69d539f865b Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 10 May 2016 09:36:54 -0700 Subject: [PATCH 662/936] =?UTF-8?q?Allow=20already=20loaded=20modules=20to?= =?UTF-8?q?=20be=20`require`=E2=80=99d=20by=20name=20string=20in=20dev=20m?= =?UTF-8?q?ode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: The code to require modules by their name (rather than their numeric ID) was buggy, because it didn’t check whether the module factory was already executed and the module already existed. This diff checks the already loaded modules, too, when loading modules by name. Reviewed By: lexs Differential Revision: D3281350 fbshipit-source-id: cef236e152fe5484f21c877d6cee37433fa11c76 --- react-packager/src/Resolver/polyfills/require.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index a8c66673..c31ba095 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -41,17 +41,19 @@ function define(moduleId, factory) { } function require(moduleId) { - const module = modules[moduleId]; + const module = __DEV__ + ? modules[moduleId] || modules[verboseNamesToModuleIds[moduleId]] + : modules[moduleId]; return module && module.isInitialized ? module.exports : guardedLoadModule(moduleId, module); } -var inGuard = false; +let inGuard = false; function guardedLoadModule(moduleId, module) { if (!inGuard && global.ErrorUtils) { inGuard = true; - var returnValue; + let returnValue; try { returnValue = loadModuleImplementation(moduleId, module); } catch (e) { From c6d52e7a8947f39be88fef683b259d276a1dbec6 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 12 May 2016 17:13:12 -0700 Subject: [PATCH 663/936] Use continuous module IDs for random access bundles Reviewed By: bestander Differential Revision: D3292980 fbshipit-source-id: ab5791d31add42a26cf55a0309564330c383eaa2 --- package.json | 2 +- .../src/Bundler/__tests__/Bundler-test.js | 7 +-- react-packager/src/Bundler/index.js | 31 ++++++----- .../src/Resolver/__tests__/Resolver-test.js | 52 ++++++++++++------- react-packager/src/Resolver/index.js | 14 ++--- .../src/Server/__tests__/Server-test.js | 26 ++++++---- react-packager/src/Server/index.js | 18 ++++--- 7 files changed, 88 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index 2a88fc01..36f258d6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.3.1", + "version": "0.3.2", "name": "react-native-packager", "description": "Build native apps with React!", "repository": { diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index f3d5c952..33cca054 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -128,6 +128,7 @@ describe('Bundler', function() { mainModuleId: 'foo', dependencies: modules, transformOptions, + getModuleId: () => 123, }) ); @@ -207,7 +208,8 @@ describe('Bundler', function() { pit('gets the list of dependencies from the resolver', function() { const entryFile = '/root/foo.js'; return bundler.getDependencies({entryFile, recursive: true}).then(() => - expect(getDependencies).toBeCalledWith( + // jest calledWith does not support jasmine.any + expect(getDependencies.mock.calls[0].slice(0, -2)).toEqual([ '/root/foo.js', { dev: true, recursive: true }, { minify: false, @@ -219,8 +221,7 @@ describe('Bundler', function() { projectRoots, } }, - undefined, - ) + ]) ); }); diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 2d67fad9..029846b1 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -141,7 +141,6 @@ class Bundler { fileWatcher: opts.fileWatcher, assetExts: opts.assetExts, cache: this._cache, - getModuleId: this._getModuleId, transformCode: (module, code, options) => this._transformer.transformFile(module.path, code, options), @@ -169,9 +168,9 @@ class Bundler { const moduleSystemDeps = this._resolver.getModuleSystemDependencies({dev, unbundle}); return this._bundle({ + ...options, bundle: new Bundle({dev, minify, sourceMapUrl: options.sourceMapUrl}), moduleSystemDeps, - ...options, }); } @@ -247,9 +246,10 @@ class Bundler { unbundle, entryModuleOnly, resolutionResponse, + isolateModuleIDs, }) { const onResolutionResponse = response => { - bundle.setMainModuleId(this._getModuleId(getMainModule(response))); + bundle.setMainModuleId(response.getModuleId(getMainModule(response))); if (bundle.setNumPrependedModules) { bundle.setNumPrependedModules( response.numPrependedDependencies + moduleSystemDeps.length @@ -273,7 +273,7 @@ class Bundler { ? runBeforeMainModule .map(name => modulesByName[name]) .filter(Boolean) - .map(this._getModuleId, this) + .map(response.getModuleId) : undefined; bundle.finalize({ @@ -294,6 +294,7 @@ class Bundler { resolutionResponse, onResolutionResponse, finalizeBundle, + isolateModuleIDs, }); } @@ -344,6 +345,7 @@ class Bundler { hot, unbundle, resolutionResponse, + isolateModuleIDs, onResolutionResponse = noop, onModuleTransformed = noop, finalizeBundle = noop, @@ -371,6 +373,7 @@ class Bundler { hot, onProgress, minify, + isolateModuleIDs, generateSourceMaps: unbundle, }); } @@ -398,6 +401,7 @@ class Bundler { bundle, entryFilePath, transformOptions: response.transformOptions, + getModuleId: response.getModuleId, }).then(transformed => { modulesByName[transformed.name] = module; onModuleTransformed({ @@ -467,6 +471,7 @@ class Bundler { hot = false, recursive = true, generateSourceMaps = false, + isolateModuleIDs = false, onProgress, }) { return this.getTransformOptions( @@ -491,6 +496,7 @@ class Bundler { {dev, platform, recursive}, transformOptions, onProgress, + isolateModuleIDs ? createModuleIdFactory() : this._getModuleId, ); }); } @@ -527,13 +533,14 @@ class Bundler { ); } - _toModuleTransport({module, bundle, entryFilePath, transformOptions}) { + _toModuleTransport({module, bundle, entryFilePath, transformOptions, getModuleId}) { let moduleTransport; if (module.isAsset_DEPRECATED()) { - moduleTransport = this._generateAssetModule_DEPRECATED(bundle, module); + moduleTransport = + this._generateAssetModule_DEPRECATED(bundle, module, getModuleId); } else if (module.isAsset()) { moduleTransport = this._generateAssetModule( - bundle, module, transformOptions.platform); + bundle, module, getModuleId, transformOptions.platform); } if (moduleTransport) { @@ -554,7 +561,7 @@ class Bundler { return new ModuleTransport({ name, - id: this._getModuleId(module), + id: getModuleId(module), code, map, meta: {dependencies, dependencyOffsets, preloaded}, @@ -568,7 +575,7 @@ class Bundler { return this._resolver.getDebugInfo(); } - _generateAssetModule_DEPRECATED(bundle, module) { + _generateAssetModule_DEPRECATED(bundle, module, getModuleId) { return Promise.all([ sizeOf(module.path), module.getName(), @@ -588,7 +595,7 @@ class Bundler { return new ModuleTransport({ name: id, - id: this._getModuleId(module), + id: getModuleId(module), code: code, sourceCode: code, sourcePath: module.path, @@ -647,7 +654,7 @@ class Bundler { } - _generateAssetModule(bundle, module, platform = null) { + _generateAssetModule(bundle, module, getModuleId, platform = null) { return Promise.all([ module.getName(), this._generateAssetObjAndCode(module, platform), @@ -655,7 +662,7 @@ class Bundler { bundle.addAsset(asset); return new ModuleTransport({ name, - id: this._getModuleId(module), + id: getModuleId(module), code, meta: meta, sourceCode: code, diff --git a/react-packager/src/Resolver/__tests__/Resolver-test.js b/react-packager/src/Resolver/__tests__/Resolver-test.js index 3d89226d..6473ee3c 100644 --- a/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -50,6 +50,7 @@ describe('Resolver', function() { constructor({dependencies, mainModuleId}) { this.dependencies = dependencies; this.mainModuleId = mainModuleId; + this.getModuleId = createGetModuleId(); } prependDependency(dependency) { @@ -95,7 +96,7 @@ describe('Resolver', function() { DependencyGraph.prototype.getDependencies.mockImplementation( () => Promise.reject()); - new Resolver({projectRoot: '/root', }) + new Resolver({projectRoot: '/root'}) .getDependencies(entry, {platform}, transformOptions); expect(DependencyGraph.prototype.getDependencies).toBeCalledWith({ entryPath: entry, @@ -110,7 +111,6 @@ describe('Resolver', function() { var deps = [module]; var depResolver = new Resolver({ - getModuleId: createGetModuleId(), projectRoot: '/root', }); @@ -121,8 +121,14 @@ describe('Resolver', function() { })); }); - return depResolver.getDependencies('/root/index.js', { dev: false }) - .then(function(result) { + return depResolver + .getDependencies( + '/root/index.js', + { dev: false }, + undefined, + undefined, + createGetModuleId() + ).then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies[result.dependencies.length - 1]).toBe(module); expect(DependencyGraph.prototype.createPolyfill.mock.calls.map((call) => call[0])).toEqual([ @@ -227,8 +233,14 @@ describe('Resolver', function() { const polyfill = {}; DependencyGraph.prototype.createPolyfill.mockReturnValueOnce(polyfill); - return depResolver.getDependencies('/root/index.js', { dev: true }) - .then(function(result) { + return depResolver + .getDependencies( + '/root/index.js', + { dev: true }, + undefined, + undefined, + createGetModuleId() + ).then(function(result) { expect(result.mainModuleId).toEqual('index'); expect(DependencyGraph.mock.instances[0].getDependencies) .toBeCalledWith({entryPath: '/root/index.js', recursive: true}); @@ -254,8 +266,14 @@ describe('Resolver', function() { })); }); - return depResolver.getDependencies('/root/index.js', { dev: false }) - .then((result) => { + return depResolver + .getDependencies( + '/root/index.js', + { dev: false }, + undefined, + undefined, + createGetModuleId() + ).then((result) => { expect(result.mainModuleId).toEqual('index'); expect(DependencyGraph.prototype.createPolyfill.mock.calls[result.dependencies.length - 2]).toEqual([ { file: 'some module', @@ -278,12 +296,10 @@ describe('Resolver', function() { }); describe('wrapModule', function() { - let depResolver, getModuleId; + let depResolver; beforeEach(() => { - getModuleId = createGetModuleId(); depResolver = new Resolver({ depResolver, - getModuleId, projectRoot: '/root', }); }); @@ -325,7 +341,8 @@ describe('Resolver', function() { const moduleIds = new Map( resolutionResponse .getResolvedDependencyPairs() - .map(([importId, module]) => [importId, getModuleId(module)]) + .map(([importId, module]) => + [importId, resolutionResponse.getModuleId(module)]) ); return depResolver.wrapModule({ @@ -337,7 +354,7 @@ describe('Resolver', function() { dev: false, }).then(({code: processedCode}) => { expect(processedCode).toEqual([ - `__d(${getModuleId(module)} /* test module */, function(global, require, module, exports) {` + + `__d(${resolutionResponse.getModuleId(module)} /* test module */, function(global, require, module, exports) {` + // require `require(${moduleIds.get('x')} /* x */)`, `require(${moduleIds.get('y')} /* y */)`, @@ -364,7 +381,7 @@ describe('Resolver', function() { dev: true, }).then(({code: processedCode}) => expect(processedCode).toEqual([ - `__d(${getModuleId(module)} /* test module */, function(global, require, module, exports) {` + + `__d(${resolutionResponse.getModuleId(module)} /* test module */, function(global, require, module, exports) {` + code, '}, "test module");' ].join('\n')) @@ -413,7 +430,7 @@ describe('Resolver', function() { let depResolver, module, resolutionResponse; beforeEach(() => { - depResolver = new Resolver({getModuleId, projectRoot: '/root'}); + depResolver = new Resolver({projectRoot: '/root'}); module = createJsonModule(id); resolutionResponse = new ResolutionResponseMock({ dependencies: [module], @@ -426,7 +443,7 @@ describe('Resolver', function() { .wrapModule({resolutionResponse, module, name: id, code, dev: false}) .then(({code: processedCode}) => expect(processedCode).toEqual([ - `__d(${getModuleId(module)} /* ${id} */, function(global, require, module, exports) {`, + `__d(${resolutionResponse.getModuleId(module)} /* ${id} */, function(global, require, module, exports) {`, `module.exports = ${code}\n});`, ].join(''))); }); @@ -442,7 +459,6 @@ describe('Resolver', function() { Promise.resolve({code, map})); depResolver = new Resolver({ projectRoot: '/root', - getModuleId, minifyCode, }); module = createModule(id); @@ -455,7 +471,7 @@ describe('Resolver', function() { }); pit('should invoke the minifier with the wrapped code', () => { - const wrappedCode = `__d(${getModuleId(module)} /* ${id} */, function(global, require, module, exports) {${code}\n});` + const wrappedCode = `__d(${resolutionResponse.getModuleId(module)} /* ${id} */, function(global, require, module, exports) {${code}\n});` return depResolver .wrapModule({ resolutionResponse, diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 772c067b..89fd40b9 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -47,10 +47,6 @@ const validateOpts = declareOpts({ type: 'object', required: true, }, - getModuleId: { - type: 'function', - required: true, - }, transformCode: { type: 'function', }, @@ -115,7 +111,6 @@ class Resolver { assetDependencies: ['react-native/Libraries/Image/AssetRegistry'], }); - this._getModuleId = options.getModuleId; this._minifyCode = opts.minifyCode; this._polyfillModuleNames = opts.polyfillModuleNames || []; @@ -137,7 +132,7 @@ class Resolver { return this._depGraph.getModuleForPath(entryFile); } - getDependencies(entryPath, options, transformOptions, onProgress) { + getDependencies(entryPath, options, transformOptions, onProgress, getModuleId) { const {platform, recursive} = getDependenciesValidateOpts(options); return this._depGraph.getDependencies({ entryPath, @@ -150,8 +145,7 @@ class Resolver { polyfill => resolutionResponse.prependDependency(polyfill) ); - // currently used by HMR - resolutionResponse.getModuleId = this._getModuleId; + resolutionResponse.getModuleId = getModuleId; return resolutionResponse.finalize(); }); } @@ -205,7 +199,7 @@ class Resolver { resolutionResponse.getResolvedDependencyPairs(module) .forEach(([depName, depModule]) => { if (depModule) { - resolvedDeps[depName] = this._getModuleId(depModule); + resolvedDeps[depName] = resolutionResponse.getModuleId(depModule); } }); @@ -251,7 +245,7 @@ class Resolver { if (module.isPolyfill()) { code = definePolyfillCode(code); } else { - const moduleId = this._getModuleId(module); + const moduleId = resolutionResponse.getModuleId(module); code = this.resolveRequires( resolutionResponse, module, diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index a7e702d3..c96b9ce1 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -22,14 +22,14 @@ jest.setMock('worker-farm', function() { return () => {}; }) const Promise = require('promise'); -var Bundler = require('../../Bundler'); -var Server = require('../'); -var AssetServer = require('../../AssetServer'); +const Bundler = require('../../Bundler'); +const Server = require('../'); +const AssetServer = require('../../AssetServer'); -var FileWatcher; +let FileWatcher; describe('processRequest', () => { - var server; + let server; const options = { projectRoots: ['root'], @@ -56,8 +56,8 @@ describe('processRequest', () => { const invalidatorFunc = jest.fn(); const watcherFunc = jest.fn(); - var requestHandler; - var triggerFileChange; + let requestHandler; + let triggerFileChange; beforeEach(() => { FileWatcher = require('node-haste').FileWatcher; @@ -135,7 +135,7 @@ describe('processRequest', () => { requestHandler, 'index.ios.includeRequire.bundle' ).then(response => { - expect(response.body).toEqual('this is the source'); + expect(response.body).toEqual('this is the source') expect(Bundler.prototype.bundle).toBeCalledWith({ entryFile: 'index.ios.js', inlineSourceMap: false, @@ -148,6 +148,7 @@ describe('processRequest', () => { runBeforeMainModule: ['InitializeJavaScriptAppEngine'], unbundle: false, entryModuleOnly: false, + isolateModuleIDs: false, }); }); }); @@ -170,6 +171,7 @@ describe('processRequest', () => { runBeforeMainModule: ['InitializeJavaScriptAppEngine'], unbundle: false, entryModuleOnly: false, + isolateModuleIDs: false, }); }); }); @@ -291,9 +293,9 @@ describe('processRequest', () => { }); describe('/onchange endpoint', () => { - var EventEmitter; - var req; - var res; + let EventEmitter; + let req; + let res; beforeEach(() => { EventEmitter = require.requireActual('events').EventEmitter; @@ -363,6 +365,7 @@ describe('processRequest', () => { runBeforeMainModule: ['InitializeJavaScriptAppEngine'], unbundle: false, entryModuleOnly: false, + isolateModuleIDs: false, }) ); }); @@ -384,6 +387,7 @@ describe('processRequest', () => { runBeforeMainModule: ['InitializeJavaScriptAppEngine'], unbundle: false, entryModuleOnly: false, + isolateModuleIDs: false, }) ); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 608919b7..ecce02fd 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -131,6 +131,10 @@ const bundleOpts = declareOpts({ type: 'boolean', default: false, }, + isolateModuleIDs: { + type: 'boolean', + default: false + } }); const dependencyOpts = declareOpts({ @@ -167,7 +171,7 @@ class Server { const assetGlobs = opts.assetExts.map(ext => '**/*.' + ext); - var watchRootConfigs = opts.projectRoots.map(dir => { + let watchRootConfigs = opts.projectRoots.map(dir => { return { dir: dir, globs: [ @@ -338,7 +342,7 @@ class Server { } _processDebugRequest(reqUrl, res) { - var ret = ''; + let ret = ''; const pathname = url.parse(reqUrl).pathname; const parts = pathname.split('/').filter(Boolean); if (parts.length === 1) { @@ -406,9 +410,9 @@ class Server { processRequest(req, res, next) { const urlObj = url.parse(req.url, true); - var pathname = urlObj.pathname; + const pathname = urlObj.pathname; - var requestType; + let requestType; if (pathname.match(/\.bundle$/)) { requestType = 'bundle'; } else if (pathname.match(/\.map$/)) { @@ -438,7 +442,7 @@ class Server { building.then( p => { if (requestType === 'bundle') { - var bundleSource = p.getSource({ + const bundleSource = p.getSource({ inlineSourceMap: options.inlineSourceMap, minify: options.minify, dev: options.dev, @@ -453,7 +457,7 @@ class Server { } Activity.endEvent(startReqEventId); } else if (requestType === 'map') { - var sourceMap = p.getSourceMap({ + let sourceMap = p.getSourceMap({ minify: options.minify, dev: options.dev, }); @@ -466,7 +470,7 @@ class Server { res.end(sourceMap); Activity.endEvent(startReqEventId); } else if (requestType === 'assets') { - var assetsList = JSON.stringify(p.getAssets()); + const assetsList = JSON.stringify(p.getAssets()); res.setHeader('Content-Type', 'application/json'); res.end(assetsList); Activity.endEvent(startReqEventId); From 56c3a581b04a8e279d2a66cd8d814aff72e1b061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 16 May 2016 10:28:37 -0700 Subject: [PATCH 664/936] Avoid clearing out factory on DEV mode Summary: grabbou pointed out this issue. We recently started cleaning out the factory function after module are required to save some memory. This broke HMR on some edge cases because sometimes the factory function may need to be re-executed. This PR just wraps the optimization into `__DEV__` to make sure we don't use it while developing. Closes https://github.com/facebook/react-native/pull/7568 Differential Revision: D3305120 Pulled By: martinbigio fbshipit-source-id: 741cffbb327d118f0bd0ec34dc1af53d4f94880e --- react-packager/src/Resolver/polyfills/require.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index c31ba095..219a4bad 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -120,7 +120,11 @@ function loadModuleImplementation(moduleId, module) { // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js factory(global, require, moduleObject, exports); - module.factory = undefined; + + // avoid removing factory in DEV mode as it breaks HMR + if (!__DEV__) { + module.factory = undefined; + } if (__DEV__) { Systrace.endEvent(); From 5fbd3751a0f6e1149ccc7e92c13b1518a151011f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 17 May 2016 11:45:49 -0700 Subject: [PATCH 665/936] Reset hasError flag after HMR request Summary: This got broken recently. As a result of this, when a module throws while being required, either by regular load or hot reload, the module object is marked as `hasError`. Form that point all subsequent HMR updates will fail because it will think the module failed while executing the factory but the failure could have been on an old run of an old factory. The fix is very simple: just reset `hasError` to false when accepting a module. Closes https://github.com/facebook/react-native/pull/7567 Differential Revision: D3310685 fbshipit-source-id: 2f0b48ab7432b7c221d0c88a019a28969a8862b2 --- react-packager/src/Resolver/polyfills/require.js | 1 + 1 file changed, 1 insertion(+) diff --git a/react-packager/src/Resolver/polyfills/require.js b/react-packager/src/Resolver/polyfills/require.js index 219a4bad..094bdeff 100644 --- a/react-packager/src/Resolver/polyfills/require.js +++ b/react-packager/src/Resolver/polyfills/require.js @@ -205,6 +205,7 @@ if (__DEV__) { if (factory) { mod.factory = factory; } + mod.hasError = false; mod.isInitialized = false; require(id); From bd7114d00fca021e3d1cf94c5568b214d50e73d0 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Wed, 18 May 2016 08:30:51 -0700 Subject: [PATCH 666/936] Added timeouts to fs operations in packager Reviewed By: davidaurelio Differential Revision: D3316555 fbshipit-source-id: edf6e08569b6cb434219f4460367eec0827530fd --- react-packager/src/AssetServer/index.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 2b25218c..1a7bb75e 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -16,9 +16,21 @@ const fs = require('fs'); const getAssetDataFromName = require('node-haste').getAssetDataFromName; const path = require('path'); -const stat = Promise.denodeify(fs.stat); -const readDir = Promise.denodeify(fs.readdir); -const readFile = Promise.denodeify(fs.readFile); +const createTimeoutPromise = (timeout) => new Promise((resolve, reject) => { + setTimeout(reject, timeout, 'fs operation timeout'); +}); +function timeoutableDenodeify(fsFunc, timeout) { + return function raceWrapper(...args) { + return new Promise.race([ + createTimeoutPromise(timeout), + Promise.denodeify(fsFunc).apply(this, args) + ]); + }; +} + +const stat = timeoutableDenodeify(fs.stat, 5000); +const readDir = timeoutableDenodeify(fs.readdir, 5000); +const readFile = timeoutableDenodeify(fs.readFile, 5000); const validateOpts = declareOpts({ projectRoots: { From e52076df735f0d1df0234c995ebbc1da21b0f192 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 18 May 2016 12:32:20 -0700 Subject: [PATCH 667/936] Add transform-react-jsx-source to react-native preset Summary: Putting this up as request for comments. The PR adds [transform-react-jsx-source](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx-source) to the list of plugins that come by default with the `react-native` preset. It will enable the use of a bunch of really cool tooling around JSX, however those are generally useful only in development mode. Is changing `react-native` preset the right thing to do in this case? Is there a way to enable this transform only in DEV? Should I add this somewhere else? Closes https://github.com/facebook/react-native/pull/6351 Differential Revision: D3302906 Pulled By: frantic fbshipit-source-id: 012d3a4142168f9f90d30d1686115d4dc3996eb9 --- transformer.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/transformer.js b/transformer.js index 18da16f2..faf01669 100644 --- a/transformer.js +++ b/transformer.js @@ -100,15 +100,22 @@ function buildBabelConfig(filename, options) { function transform(src, filename, options) { options = options || {}; - const babelConfig = buildBabelConfig(filename, options); - const result = babel.transform(src, babelConfig); + const OLD_BABEL_ENV = process.env.BABEL_ENV; + process.env.BABEL_ENV = options.dev ? 'development' : 'production'; - return { - ast: result.ast, - code: result.code, - map: result.map, - filename: filename, - }; + try { + const babelConfig = buildBabelConfig(filename, options); + const result = babel.transform(src, babelConfig); + + return { + ast: result.ast, + code: result.code, + map: result.map, + filename: filename, + }; + } finally { + process.env.BABEL_ENV = OLD_BABEL_ENV; + } } module.exports = function(data, callback) { From 17f30d8666a1bb33940a0efd0c97563029491bf4 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Fri, 20 May 2016 05:27:13 -0700 Subject: [PATCH 668/936] Adds packager configuration to support windows platform Summary: This pull request is a prerequisite to enabling the react-native-windows platform extension. In the Resolver component, we need to add 'windows' to the list of platforms that are allowed in the DependencyGraph. We also need to add 'react-native-windows' (the name of the Windows platform extension NPM module) to the `providesModuleNodeModules` option. This allows the node_module folder check in the DependencyGraphHelper from node-haste to be bypassed for *.windows.js files in the Windows NPM package. For good measure, I also included a change to blacklist.js to ensure .windows.js files are ignored when the packager is parameterized on a platform. Closes https://github.com/facebook/react-native/pull/7639 Differential Revision: D3327771 Pulled By: mkonicek fbshipit-source-id: d1080b045ff6aa0cbf05d8070ceb0eb4cdb6dceb --- blacklist.js | 8 ++++++++ react-packager/src/Resolver/index.js | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/blacklist.js b/blacklist.js index 2a1346f9..3b222145 100644 --- a/blacklist.js +++ b/blacklist.js @@ -27,14 +27,22 @@ var platformBlacklists = { web: [ '.ios.js', '.android.js', + '.windows.js' ], ios: [ '.web.js', '.android.js', + '.windows.js', ], android: [ '.web.js', '.ios.js', + '.windows.js' + ], + windows: [ + '.web.js', + '.ios.js', + '.android.js' ], }; diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 89fd40b9..5f5dc98e 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -95,13 +95,14 @@ class Resolver { providesModuleNodeModules: [ 'react', 'react-native', + 'react-native-windows', // Parse requires AsyncStorage. They will // change that to require('react-native') which // should work after this release and we can // remove it from here. 'parse', ], - platforms: ['ios', 'android'], + platforms: ['ios', 'android', 'windows'], preferNativePlatform: true, fileWatcher: opts.fileWatcher, cache: opts.cache, From 31131746d8f23722cc111032b8af4d01c8779e3e Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 20 May 2016 12:12:58 -0700 Subject: [PATCH 669/936] Symbolicate JS stacktrace using RN Packager Summary: The way we currently symbolicate JS stack traces in RN during development time (e.g. inside the RedBox) is the following: we download the source map from RN, parse it and use `source-map` find original file/line numbers. All happens inside running JSC VM in a simulator. The problem with this approach is that the source map size is pretty big and it is very expensive to load/parse. Before we load sourcemaps: {F60869250} After we load sourcemaps: {F60869249} In the past it wasn't a big problem, however the sourcemap file is only getting larger and soon we will be loading it for yellow boxes too: https://github.com/facebook/react-native/pull/7459 Moving stack trace symbolication to server side will let us: - save a bunch of memory on device - improve performance (no need to JSON serialize/deserialize and transfer sourcemap via HTTP and bridge) - remove ugly workaround with `RCTExceptionsManager.updateExceptionMessage` - we will be able to symbolicate from native by simply sending HTTP request, which means symbolication can be more robust (no need to depend on crashed JS to do symbolication) and we can pause JSC to avoid getting too many redboxes that hide original error. - reduce the bundle by ~65KB (the size of source-map parsing library we ship, see SourceMap module) Reviewed By: davidaurelio Differential Revision: D3291793 fbshipit-source-id: 29dce5f40100259264f57254e6715ace8ea70174 --- .../src/Server/__tests__/Server-test.js | 57 +++++++++++++++++ react-packager/src/Server/index.js | 62 +++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index c96b9ce1..9d5ec47a 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -14,6 +14,7 @@ jest.setMock('worker-farm', function() { return () => {}; }) .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) }) .setMock('uglify-js') .setMock('crypto') + .setMock('source-map', { SourceMapConsumer: (fn) => {}}) .mock('../../Bundler') .mock('../../AssetServer') .mock('../../lib/declareOpts') @@ -21,6 +22,7 @@ jest.setMock('worker-farm', function() { return () => {}; }) .mock('../../Activity'); const Promise = require('promise'); +const SourceMapConsumer = require('source-map').SourceMapConsumer; const Bundler = require('../../Bundler'); const Server = require('../'); @@ -392,4 +394,59 @@ describe('processRequest', () => { ); }); }); + + describe('/symbolicate endpoint', () => { + pit('should symbolicate given stack trace', () => { + const body = JSON.stringify({stack: [{ + file: 'foo.bundle?platform=ios', + lineNumber: 2100, + column: 44, + customPropShouldBeLeftUnchanged: 'foo', + }]}); + + SourceMapConsumer.prototype.originalPositionFor = jest.fn((frame) => { + expect(frame.line).toEqual(2100); + expect(frame.column).toEqual(44); + return { + source: 'foo.js', + line: 21, + column: 4, + }; + }); + + return makeRequest( + requestHandler, + '/symbolicate', + { rawBody: body } + ).then(response => { + expect(JSON.parse(response.body)).toEqual({ + stack: [{ + file: 'foo.js', + lineNumber: 21, + column: 4, + customPropShouldBeLeftUnchanged: 'foo', + }] + }); + }); + }); + }); + + describe('/symbolicate handles errors', () => { + pit('should symbolicate given stack trace', () => { + const body = 'clearly-not-json'; + console.error = jest.fn(); + + return makeRequest( + requestHandler, + '/symbolicate', + { rawBody: body } + ).then(response => { + expect(response.statusCode).toEqual(500); + expect(JSON.parse(response.body)).toEqual({ + error: jasmine.any(String), + }); + expect(console.error).toBeCalled(); + }); + }); + }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index ecce02fd..10258d4f 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -14,6 +14,7 @@ const FileWatcher = require('node-haste').FileWatcher; const getPlatformExtension = require('node-haste').getPlatformExtension; const Bundler = require('../Bundler'); const Promise = require('promise'); +const SourceMapConsumer = require('source-map').SourceMapConsumer; const _ = require('lodash'); const declareOpts = require('../lib/declareOpts'); @@ -428,6 +429,9 @@ class Server { } else if (pathname.match(/^\/assets\//)) { this._processAssetsRequest(req, res); return; + } else if (pathname === '/symbolicate') { + this._symbolicate(req, res); + return; } else { next(); return; @@ -480,6 +484,64 @@ class Server { ).done(); } + _symbolicate(req, res) { + const startReqEventId = Activity.startEvent('symbolicate'); + new Promise.resolve(req.rawBody).then(body => { + const stack = JSON.parse(body).stack; + + // In case of multiple bundles / HMR, some stack frames can have + // different URLs from others + const urls = stack.map(frame => frame.file); + const uniqueUrls = urls.filter((elem, idx) => urls.indexOf(elem) === idx); + + const sourceMaps = uniqueUrls.map(sourceUrl => this._sourceMapForURL(sourceUrl)); + return Promise.all(sourceMaps).then(consumers => { + return stack.map(frame => { + const idx = uniqueUrls.indexOf(frame.file); + const consumer = consumers[idx]; + + const original = consumer.originalPositionFor({ + line: frame.lineNumber, + column: frame.column, + }); + + if (!original) { + return frame; + } + + return Object.assign({}, frame, { + file: original.source, + lineNumber: original.line, + column: original.column, + }); + }); + }); + }).then( + stack => res.end(JSON.stringify({stack: stack})), + error => { + console.error(error.stack || error); + res.statusCode = 500; + res.end(JSON.stringify({error: error.message})); + } + ).done(() => { + Activity.endEvent(startReqEventId); + }); + } + + _sourceMapForURL(reqUrl) { + const options = this._getOptionsFromUrl(reqUrl); + const optionsJson = JSON.stringify(options); + const building = this._bundles[optionsJson] || this.buildBundle(options); + this._bundles[optionsJson] = building; + return building.then(p => { + const sourceMap = p.getSourceMap({ + minify: options.minify, + dev: options.dev, + }); + return new SourceMapConsumer(sourceMap); + }); + } + _handleError(res, bundleID, error) { res.writeHead(error.status || 500, { 'Content-Type': 'application/json; charset=UTF-8', From fd406708693cd72799e5e7c110e7026fe7e5a031 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Sat, 21 May 2016 06:53:32 -0700 Subject: [PATCH 670/936] Use a separate babel config for the local-cli and the packager Summary: This separates the babel config of the local-cli and the packager from the one used by the transforms of the packager since it doesn't run in the same environment and the local-cli/packager doesn't require react specific transforms and runs in node 4 so we can also avoid some es2015 transforms that node already supports. I had to move the code in cli.js so it can still run in node 0.12 that doesn't support `const` since it is no longer transformed. **Test plan** Run the local-cli on node 0.12 and there should be a message saying that it requires at least node 4. Run the local-cli on node 4 and 5 and everything should work the same as before. I was also hoping for some perf gains but there was nothing noticeable. I did benchmark the babel-register call and it stayed pretty much the same. As for runtime performance it can help if there are optimisations for es2015 features in node. Closes https://github.com/facebook/react-native/pull/6155 Reviewed By: bestander Differential Revision: D3301008 Pulled By: davidaurelio fbshipit-source-id: 504180d158a1e50bc03e28fb0d1e53d0731ce32f --- babelRegisterOnly.js | 23 +++++++++++------------ package.json | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/babelRegisterOnly.js b/babelRegisterOnly.js index bb973ba4..703ba338 100644 --- a/babelRegisterOnly.js +++ b/babelRegisterOnly.js @@ -12,20 +12,19 @@ Array.prototype.values || require('core-js/fn/array/values'); Object.entries || require('core-js/fn/object/entries'); Object.values || require('core-js/fn/object/values'); -var fs = require('fs'); -var path = require('path'); - var _only = []; -function readBabelRC() { - var rcpath = path.join(__dirname, 'react-packager', 'rn-babelrc.json'); - var source = fs.readFileSync(rcpath).toString(); - return JSON.parse(source); -} - module.exports = function(onlyList) { _only = _only.concat(onlyList); - var config = readBabelRC(); - config.only = _only; - require('babel-register')(config); + + require('babel-register')({ + presets: ['es2015-node'], + plugins: [ + 'transform-flow-strip-types', + 'syntax-trailing-function-commas', + 'transform-object-rest-spread', + ], + only: _only, + sourceMaps: 'inline', + }); }; diff --git a/package.json b/package.json index 36f258d6..f18fc259 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.3.2", + "version": "0.4.0", "name": "react-native-packager", "description": "Build native apps with React!", "repository": { From 69a46e2c6eadbcb3f52177f4e3a297f96f94eaef Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Mon, 23 May 2016 10:27:23 -0700 Subject: [PATCH 671/936] Update node-haste dependency to 2.12.0 Summary: Update to node-haste 2.12.0 to support pass through configuration of supported platforms. Closes https://github.com/facebook/react-native/pull/7660 Differential Revision: D3335034 Pulled By: mkonicek fbshipit-source-id: d238b90a90d51654301d61251ceb26d183fef57a --- react-packager/src/AssetServer/index.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 1a7bb75e..c9b198a5 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -51,7 +51,7 @@ class AssetServer { } get(assetPath, platform = null) { - const assetData = getAssetDataFromName(assetPath); + const assetData = getAssetDataFromName(assetPath, new Set([platform])); return this._getAssetRecord(assetPath, platform).then(record => { for (let i = 0; i < record.scales.length; i++) { if (record.scales[i] >= assetData.resolution) { @@ -64,7 +64,7 @@ class AssetServer { } getAssetData(assetPath, platform = null) { - const nameData = getAssetDataFromName(assetPath); + const nameData = getAssetDataFromName(assetPath, new Set([platform])); const data = { name: nameData.name, type: nameData.type, @@ -115,7 +115,7 @@ class AssetServer { .then(res => { const dir = res[0]; const files = res[1]; - const assetData = getAssetDataFromName(filename); + const assetData = getAssetDataFromName(filename, new Set([platform])); const map = this._buildAssetMap(dir, files, platform); @@ -166,8 +166,8 @@ class AssetServer { }); } - _buildAssetMap(dir, files) { - const assets = files.map(getAssetDataFromName); + _buildAssetMap(dir, files, platform) { + const assets = files.map(this._getAssetDataFromName.bind(this, new Set([platform]))); const map = Object.create(null); assets.forEach(function(asset, i) { const file = files[i]; @@ -194,6 +194,10 @@ class AssetServer { return map; } + + _getAssetDataFromName(platform, file) { + return getAssetDataFromName(file, platform); + } } function getAssetKey(assetName, platform) { From 0dd8af0ea24be6e851f5f22128ec964cbf8b3350 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 24 May 2016 05:09:39 -0700 Subject: [PATCH 672/936] adapt instantiation of node-haste/DependencyGraph to new version Reviewed By: bestander Differential Revision: D3339969 fbshipit-source-id: 2b81f8019223b060f3e3afb940cc58360ed024e5 --- react-packager/src/Resolver/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 5f5dc98e..865cd683 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -102,7 +102,7 @@ class Resolver { // remove it from here. 'parse', ], - platforms: ['ios', 'android', 'windows'], + platforms: ['ios', 'android', 'windows', 'web'], preferNativePlatform: true, fileWatcher: opts.fileWatcher, cache: opts.cache, From fb050c4520f3e1d56fe981908bf97dd7af8de1d3 Mon Sep 17 00:00:00 2001 From: Yann Pringault Date: Wed, 25 May 2016 11:50:53 -0700 Subject: [PATCH 673/936] Add Array.prototype.includes polyfill Summary: Add `Array.prototype.includes` polyfill. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes I had all my `includes` running well on iOS 9 but when switching to Android `includes` threw an error. [The compatibility table](http://kangax.github.io/compat-table/esnext/#test-Array.prototype.includes_Array.prototype.includes) shows that is not supported on Android yet as well as iOS 6-8. With Chrome debugging it's working on both environment. Closes https://github.com/facebook/react-native/pull/7756 Reviewed By: davidaurelio Differential Revision: D3346873 Pulled By: vjeux fbshipit-source-id: 2e17d29992873fbe4448b962df0423e516455b4b --- .../Resolver/polyfills/Array.prototype.es6.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/react-packager/src/Resolver/polyfills/Array.prototype.es6.js b/react-packager/src/Resolver/polyfills/Array.prototype.es6.js index 2752aab5..7e2078f8 100644 --- a/react-packager/src/Resolver/polyfills/Array.prototype.es6.js +++ b/react-packager/src/Resolver/polyfills/Array.prototype.es6.js @@ -53,3 +53,39 @@ if (!Array.prototype.find) { } }); } + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes +if (!Array.prototype.includes) { + Object.defineProperty(Array.prototype, 'includes', { + enumerable: false, + writable: true, + configurable: true, + value: function (searchElement) { + var O = Object(this); + var len = parseInt(O.length) || 0; + if (len === 0) { + return false; + } + var n = parseInt(arguments[1]) || 0; + var k; + if (n >= 0) { + k = n; + } else { + k = len + n; + if (k < 0) { + k = 0; + } + } + var currentElement; + while (k < len) { + currentElement = O[k]; + if (searchElement === currentElement || + (searchElement !== searchElement && currentElement !== currentElement)) { + return true; + } + k++; + } + return false; + } + }); +} From ff0146570183a555b3c0e5c9d18922840077a63e Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Wed, 1 Jun 2016 06:16:51 -0700 Subject: [PATCH 674/936] removed unused code: _numPrependedModules Summary: cc davidaurelio Closes https://github.com/facebook/react-native/pull/7873 Differential Revision: D3371406 Pulled By: javache fbshipit-source-id: 72ed3838a88022ae5c0832dcca5abda75f18dbe1 --- react-packager/src/Bundler/Bundle.js | 7 ------- react-packager/src/Bundler/index.js | 5 ----- 2 files changed, 12 deletions(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index c3cdc804..ccaaffa8 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -22,7 +22,6 @@ class Bundle extends BundleBase { this._sourceMap = false; this._sourceMapUrl = sourceMapUrl; this._shouldCombineSourceMaps = false; - this._numPrependedModules = 0; this._numRequireCalls = 0; this._dev = dev; this._minify = minify; @@ -53,10 +52,6 @@ class Bundle extends BundleBase { }); } - setNumPrependedModules(n) { - this._numPrependedModules = n; - } - finalize(options) { options = options || {}; if (options.runMainModule) { @@ -277,7 +272,6 @@ class Bundle extends BundleBase { return { ...super.toJSON(), sourceMapUrl: this._sourceMapUrl, - numPrependedModules: this._numPrependedModules, numRequireCalls: this._numRequireCalls, shouldCombineSourceMaps: this._shouldCombineSourceMaps, }; @@ -287,7 +281,6 @@ class Bundle extends BundleBase { const bundle = new Bundle({sourceMapUrl: json.sourceMapUrl}); bundle._sourceMapUrl = json.sourceMapUrl; - bundle._numPrependedModules = json.numPrependedModules; bundle._numRequireCalls = json.numRequireCalls; bundle._shouldCombineSourceMaps = json.shouldCombineSourceMaps; diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 029846b1..8b411be9 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -250,11 +250,6 @@ class Bundler { }) { const onResolutionResponse = response => { bundle.setMainModuleId(response.getModuleId(getMainModule(response))); - if (bundle.setNumPrependedModules) { - bundle.setNumPrependedModules( - response.numPrependedDependencies + moduleSystemDeps.length - ); - } if (entryModuleOnly) { response.dependencies = response.dependencies.filter(module => module.path.endsWith(entryFile) From b52ba139507f0c9c67594684177d06fd9200068e Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Thu, 2 Jun 2016 11:38:01 -0700 Subject: [PATCH 675/936] Update `fbjs-scripts` to ^0.7.0 Summary: `fbjs-scripts` 0.4.0 has Babel 5 as a dependency, which causes some amount of havoc when you're trying to use `react-native` in a project that's otherwise dependent on Babel 6 and using the flat-installing npm >=3. Closes https://github.com/facebook/react-native/pull/7855 Reviewed By: frantic Differential Revision: D3371679 Pulled By: steveluscher fbshipit-source-id: 9f7643171d89da0de0492e7e97875f472725e990 --- transformer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transformer.js b/transformer.js index faf01669..0a972982 100644 --- a/transformer.js +++ b/transformer.js @@ -15,7 +15,7 @@ const externalHelpersPlugin = require('babel-plugin-external-helpers'); const fs = require('fs'); const makeHMRConfig = require('babel-preset-react-native/configs/hmr'); const resolvePlugins = require('babel-preset-react-native/lib/resolvePlugins'); -const inlineRequiresPlugin = require('fbjs-scripts/babel-6/inline-requires'); +const inlineRequiresPlugin = require('babel-preset-fbjs/plugins/inline-requires'); const json5 = require('json5'); const path = require('path'); From dee7aadd284cc0845db42a47a34a0988fc7ea661 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Sun, 5 Jun 2016 05:47:26 -0700 Subject: [PATCH 676/936] inline `Platform.select` Summary: We are already inlining `Platform.OS`. This diff adds support to inline calls to `Platform.select` with an object literal as first argument. The transform will replace the call with the property value corresponding to the platform, or `undefined` if it does not exist. Reviewed By: frantic Differential Revision: D3385391 fbshipit-source-id: bb068d17948ed84e381707faeaa0450399c2f306 --- .../worker/__tests__/inline-test.js | 107 ++++++++++++++++++ .../src/JSTransformer/worker/inline.js | 32 ++++++ 2 files changed, 139 insertions(+) diff --git a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js index 102e39ab..b52e07c0 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js @@ -141,6 +141,113 @@ describe('inline constants', () => { normalize(code.replace(/require\('react-native'\)\.Platform\.OS/, '"android"'))); }); + it('inlines Platform.select in the code if Platform is a global and the argument is an object literal', () => { + const code = `function a() { + var a = Platform.select({ios: 1, android: 2}); + var b = a.Platform.select({ios: 1, android: 2}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1'))); + }); + + it('replaces Platform.select in the code if Platform is a top level import', () => { + const code = ` + var Platform = require('Platform'); + function a() { + Platform.select({ios: 1, android: 2}); + var b = a.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2'))); + }); + + it('replaces Platform.select in the code if Platform is a top level import from react-native', () => { + const code = ` + var Platform = require('react-native').Platform; + function a() { + Platform.select({ios: 1, android: 2}); + var b = a.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1'))); + }); + + it('replaces require("Platform").select in the code', () => { + const code = `function a() { + var a = require('Platform').select({ios: 1, android: 2}); + var b = a.require('Platform').select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2'))); + }); + + it('replaces React.Platform.select in the code if React is a global', () => { + const code = `function a() { + var a = React.Platform.select({ios: 1, android: 2}); + var b = a.React.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1'))); + }); + + it('replaces ReactNative.Platform.select in the code if ReactNative is a global', () => { + const code = `function a() { + var a = ReactNative.Platform.select({ios: 1, android: 2}); + var b = a.ReactNative.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative\.Platform\.select[^;]+/, '1'))); + }); + + it('replaces React.Platform.select in the code if React is a top level import', () => { + const code = ` + var React = require('React'); + function a() { + var a = React.Platform.select({ios: 1, android: 2}); + var b = a.React.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1'))); + }); + + it('replaces require("React").Platform.select in the code', () => { + const code = `function a() { + var a = require('React').Platform.select({ios: 1, android: 2}); + var b = a.require('React').Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/require\('React'\)\.Platform\.select[^;]+/, '2'))); + }); + + it('replaces ReactNative.Platform.select in the code if ReactNative is a top level import', () => { + const code = ` + var ReactNative = require('react-native'); + function a() { + var a = ReactNative.Plaftform.select({ios: 1, android: 2}); + var b = a.ReactNative.Platform.select; + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative.Platform\.select[^;]+/, '2'))); + }); + + it('replaces require("react-native").Platform.select in the code', () => { + const code = ` + var a = require('react-native').Platform.select({ios: 1, android: 2}); + var b = a.require('react-native').Platform.select({}); + `; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/require\('react-native'\)\.Platform\.select[^;]+/, '2'))); + }); + + it('replaces non-existing properties with `undefined`', () => { + const code = 'var a = Platform.select({ios: 1, android: 2})'; + const {ast} = inline('arbitrary.js', {code}, {platform: 'doesnotexist'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/Platform\.select[^;]+/, 'undefined'))); + }); + it('replaces process.env.NODE_ENV in the code', () => { const code = `function a() { if (process.env.NODE_ENV === 'production') { diff --git a/react-packager/src/JSTransformer/worker/inline.js b/react-packager/src/JSTransformer/worker/inline.js index 13e20b0e..adc1f71d 100644 --- a/react-packager/src/JSTransformer/worker/inline.js +++ b/react-packager/src/JSTransformer/worker/inline.js @@ -15,6 +15,7 @@ const React = {name: 'React'}; const ReactNative = {name: 'ReactNative'}; const platform = {name: 'Platform'}; const os = {name: 'OS'}; +const select = {name: 'select'}; const requirePattern = {name: 'require'}; const env = {name: 'env'}; @@ -63,11 +64,29 @@ const isProcessEnvNodeEnv = (node, scope) => t.isIdentifier(node.object.object, processId) && isGlobal(scope.getBinding(processId.name)); +const isPlatformSelect = (node, scope) => + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.object, platform) && + t.isIdentifier(node.callee.property, select) && + isImportOrGlobal(node.callee.object, scope, [platform]); + +const isReactPlatformSelect = (node, scope) => + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.property, select) && + t.isMemberExpression(node.callee.object) && + t.isIdentifier(node.callee.object.property, platform) && + isImportOrGlobal(node.callee.object.object, scope, [React, ReactNative]); + const isDev = (node, parent, scope) => t.isIdentifier(node, dev) && isGlobal(scope.getBinding(dev.name)) && !(t.isMemberExpression(parent)); +function findProperty(objectExpression, key) { + const property = objectExpression.properties.find(p => p.key.name === key); + return property ? property.value : t.identifier('undefined'); +} + const inlinePlugin = { visitor: { Identifier(path, state) { @@ -86,6 +105,19 @@ const inlinePlugin = { t.stringLiteral(state.opts.dev ? 'development' : 'production')); } }, + CallExpression(path, state) { + const node = path.node; + const scope = path.scope; + const arg = node.arguments[0]; + + if (isPlatformSelect(node, scope) || isReactPlatformSelect(node, scope)) { + const replacement = t.isObjectExpression(arg) + ? findProperty(arg, state.opts.platform) + : node; + + path.replaceWith(replacement); + } + } }, }; From a4c9a2d0a6f9849b8c80140de4f54b44ce50433b Mon Sep 17 00:00:00 2001 From: Yann Pringault Date: Wed, 8 Jun 2016 06:11:31 -0700 Subject: [PATCH 677/936] Minor typo in Error message Summary: Closes https://github.com/facebook/react-native/pull/8001 Differential Revision: D3404663 fbshipit-source-id: 0d0af84e4f6d31e6ddf79ef4e263737542b81361 --- react-packager/src/Bundler/BundleBase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/BundleBase.js b/react-packager/src/Bundler/BundleBase.js index 53fa9c34..838ffa1d 100644 --- a/react-packager/src/Bundler/BundleBase.js +++ b/react-packager/src/Bundler/BundleBase.js @@ -32,7 +32,7 @@ class BundleBase { addModule(module) { if (!(module instanceof ModuleTransport)) { - throw new Error('Expeceted a ModuleTransport object'); + throw new Error('Expected a ModuleTransport object'); } return this._modules.push(module) - 1; From 94975209637fb233a25e04e7b691ccc0cb2e73c1 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Fri, 10 Jun 2016 12:23:04 -0700 Subject: [PATCH 678/936] RN: Fix Symbolicate Logspew for `/debuggerWorker.js` Summary: When remote debugging is enabled, stack traces start at `/debuggerWorker.js`. Since this is not a valid bundle URL, the packager fails to decipher it to find its sourcemap. This changes the packager to skip the `/debuggerWorker.js` stack frame if one exists. Reviewed By: frantic Differential Revision: D3418341 fbshipit-source-id: 7434aa45dea7d120d9d77c060101dd9403989d0c --- .../src/Server/__tests__/Server-test.js | 24 ++++++++++++++- react-packager/src/Server/index.js | 29 ++++++++++++++----- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 9d5ec47a..d8d2e96d 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -137,7 +137,7 @@ describe('processRequest', () => { requestHandler, 'index.ios.includeRequire.bundle' ).then(response => { - expect(response.body).toEqual('this is the source') + expect(response.body).toEqual('this is the source'); expect(Bundler.prototype.bundle).toBeCalledWith({ entryFile: 'index.ios.js', inlineSourceMap: false, @@ -429,6 +429,28 @@ describe('processRequest', () => { }); }); }); + + pit('ignores `/debuggerWorker.js` stack frames', () => { + const body = JSON.stringify({stack: [{ + file: 'http://localhost:8081/debuggerWorker.js', + lineNumber: 123, + column: 456, + }]}); + + return makeRequest( + requestHandler, + '/symbolicate', + { rawBody: body } + ).then(response => { + expect(JSON.parse(response.body)).toEqual({ + stack: [{ + file: 'http://localhost:8081/debuggerWorker.js', + lineNumber: 123, + column: 456, + }] + }); + }); + }); }); describe('/symbolicate handles errors', () => { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 10258d4f..f040f771 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -362,7 +362,7 @@ class Server { e => { res.writeHead(500); res.end('Internal Error'); - console.log(e.stack); + console.log(e.stack); // eslint-disable-line no-console-disallow } ); } else if (parts[1] === 'graph'){ @@ -491,24 +491,37 @@ class Server { // In case of multiple bundles / HMR, some stack frames can have // different URLs from others - const urls = stack.map(frame => frame.file); - const uniqueUrls = urls.filter((elem, idx) => urls.indexOf(elem) === idx); + const urlIndexes = {}; + const uniqueUrls = []; + stack.forEach(frame => { + const sourceUrl = frame.file; + // Skip `/debuggerWorker.js` which drives remote debugging because it + // does not need to symbolication. + if (!urlIndexes.hasOwnProperty(sourceUrl) && + !sourceUrl.endsWith('/debuggerWorker.js')) { + urlIndexes[sourceUrl] = uniqueUrls.length; + uniqueUrls.push(sourceUrl); + } + }); - const sourceMaps = uniqueUrls.map(sourceUrl => this._sourceMapForURL(sourceUrl)); + const sourceMaps = uniqueUrls.map( + sourceUrl => this._sourceMapForURL(sourceUrl) + ); return Promise.all(sourceMaps).then(consumers => { return stack.map(frame => { - const idx = uniqueUrls.indexOf(frame.file); + const sourceUrl = frame.file; + if (!urlIndexes.hasOwnProperty(sourceUrl)) { + return frame; + } + const idx = urlIndexes[sourceUrl]; const consumer = consumers[idx]; - const original = consumer.originalPositionFor({ line: frame.lineNumber, column: frame.column, }); - if (!original) { return frame; } - return Object.assign({}, frame, { file: original.source, lineNumber: original.line, From b02f773e6a063df72862884ca3c536975f6739d5 Mon Sep 17 00:00:00 2001 From: Nathan Azaria Date: Mon, 13 Jun 2016 15:52:19 -0700 Subject: [PATCH 679/936] Implemented automatic IP detection for iOS Summary: Implemented automatic IP detection for iOS, based on #6345 and #6362. As the previous pull requests did, this works by writing the IP address of the host to a file. Closes https://github.com/facebook/react-native/pull/8091 Differential Revision: D3427657 Pulled By: javache fbshipit-source-id: 3f534c9b32c4d6fb9615fc2e2c3c3aef421454c5 --- react-native-xcode.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/react-native-xcode.sh b/react-native-xcode.sh index ad27792e..eeedfa28 100755 --- a/react-native-xcode.sh +++ b/react-native-xcode.sh @@ -68,6 +68,15 @@ type $NODE_BINARY >/dev/null 2>&1 || nodejs_not_found set -x DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH +if [[ "$CONFIGURATION" = "Debug" && "$PLATFORM_NAME" != "iphonesimulator" ]]; then + PLISTBUDDY='/usr/libexec/PlistBuddy' + PLIST=$TARGET_BUILD_DIR/$INFOPLIST_PATH + IP=$(ipconfig getifaddr en0) + $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" $PLIST + $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:$IP.xip.io:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" $PLIST + echo "$IP.xip.io" > "$DEST/ip.txt" +fi + $NODE_BINARY "$REACT_NATIVE_DIR/local-cli/cli.js" bundle \ --entry-file index.ios.js \ --platform ios \ From 3f058100cd9b5988a8e3d94c76d551cadca69313 Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Tue, 14 Jun 2016 16:22:53 -0700 Subject: [PATCH 680/936] Change the default guard behavior to throw the exception Differential Revision: D3428928 fbshipit-source-id: 7847d7fac6a2dc1e4c58dfd5f97feea97ba58930 --- react-packager/src/Resolver/polyfills/error-guard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Resolver/polyfills/error-guard.js b/react-packager/src/Resolver/polyfills/error-guard.js index d013ec11..eaeea1f3 100644 --- a/react-packager/src/Resolver/polyfills/error-guard.js +++ b/react-packager/src/Resolver/polyfills/error-guard.js @@ -79,7 +79,7 @@ global.ErrorUtils = ErrorUtils; */ function setupErrorGuard() { var onError = function(e) { - global.console.error('Error: ' + e.message + ', stack:\n' + e.stack); + throw e; }; global.ErrorUtils.setGlobalHandler(onError); } From 50d5275fc0af48186a9d6f6ed4ba10bcaa834263 Mon Sep 17 00:00:00 2001 From: Benoit Lemaire Date: Thu, 16 Jun 2016 14:16:22 -0700 Subject: [PATCH 681/936] Cleanup packager dead debug endpoint Summary: When trying to access the debug dependency graph through the `/debug/graph` endpoint of the packager server (documented in the packager README and listed as well when hitting the root `/debug` endpoint), all I am getting back is a nasty HTTP 500 error :'( What triggers this HTTP 500 is a `TypeError` being thrown while trying to access a function that doesn't exists (anymore). Here is the error log of the packager when trying to access this endpoint : ``` TypeError: this._depGraph.getDebugInfo is not a function at Resolver.getDebugInfo (index.js:270:27) at Bundler.getGraphDebugInfo (index.js:575:27) at Server._processDebugRequest (index.js:369:28) at Server.processRequest (index.js:423:12) at next (/Users/blemair/Code/DependencyGraphTest/node_modules/connect/lib/proto.js:174:15) at Object.module.exports [as handle] (cpuProfilerMiddleware.js:17:5) at next (/Users/blemair/Code/DependencyGraphTest/node_modules/connect/lib/proto.js:174:15) at Object.module.exports [as Closes https://github.com/facebook/react-native/pull/8117 Differential Revision: D3445582 fbshipit-source-id: cf5af8bbba293f39773f32814a3b388b7ff67bf7 --- README.md | 4 +--- react-packager/src/Bundler/index.js | 4 ---- react-packager/src/Resolver/index.js | 4 ---- react-packager/src/Server/index.js | 5 ----- 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/README.md b/README.md index 9a89cd9d..c8e314cc 100644 --- a/README.md +++ b/README.md @@ -72,12 +72,10 @@ Here are the current options the packager accepts: ### /debug -This is a page used for debugging, it has links to two pages: +This is a page used for debugging, it offers a link to a single page : * Cached Packages: which shows you the packages that's been already generated and cached -* Dependency Graph: is the in-memory graph of all the modules and - their dependencies ## Programmatic API diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 8b411be9..d2053fa1 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -566,10 +566,6 @@ class Bundler { }); } - getGraphDebugInfo() { - return this._resolver.getDebugInfo(); - } - _generateAssetModule_DEPRECATED(bundle, module, getModuleId) { return Promise.all([ sizeOf(module.path), diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index 865cd683..c8faa60a 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -265,10 +265,6 @@ class Resolver { minifyModule({path, code, map}) { return this._minifyCode(path, code, map); } - - getDebugInfo() { - return this._depGraph.getDebugInfo(); - } } function defineModuleCode(moduleName, code, verboseName = '', dev = true) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index f040f771..ef35844f 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -348,7 +348,6 @@ class Server { const parts = pathname.split('/').filter(Boolean); if (parts.length === 1) { ret += ''; - ret += ''; res.end(ret); } else if (parts[1] === 'bundles') { ret += '

Cached Bundles

'; @@ -365,10 +364,6 @@ class Server { console.log(e.stack); // eslint-disable-line no-console-disallow } ); - } else if (parts[1] === 'graph'){ - ret += '

Dependency Graph

'; - ret += this._bundler.getGraphDebugInfo(); - res.end(ret); } else { res.writeHead('404'); res.end('Invalid debug request'); From 930ef516467c7bb88bfa29205a71ec812fb2c7f5 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Fri, 17 Jun 2016 12:55:43 -0700 Subject: [PATCH 682/936] make module IDs deterministic when bundling Summary: This makes sure that `getModuleId` is called on modules in the order returned by `node-haste`, rather than waiting for a couple of promises to resolve before calling the function. Related: #7758 Reviewed By: frantic Differential Revision: D3450853 fbshipit-source-id: 7f26590b39b94ade32c73a8db9fd31d283d57549 --- react-packager/src/Bundler/index.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index d2053fa1..8ee861d2 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -530,12 +530,14 @@ class Bundler { _toModuleTransport({module, bundle, entryFilePath, transformOptions, getModuleId}) { let moduleTransport; + const moduleId = getModuleId(module); + if (module.isAsset_DEPRECATED()) { moduleTransport = - this._generateAssetModule_DEPRECATED(bundle, module, getModuleId); + this._generateAssetModule_DEPRECATED(bundle, module, moduleId); } else if (module.isAsset()) { moduleTransport = this._generateAssetModule( - bundle, module, getModuleId, transformOptions.platform); + bundle, module, moduleId, transformOptions.platform); } if (moduleTransport) { @@ -556,7 +558,7 @@ class Bundler { return new ModuleTransport({ name, - id: getModuleId(module), + id: moduleId, code, map, meta: {dependencies, dependencyOffsets, preloaded}, @@ -566,7 +568,7 @@ class Bundler { }); } - _generateAssetModule_DEPRECATED(bundle, module, getModuleId) { + _generateAssetModule_DEPRECATED(bundle, module, moduleId) { return Promise.all([ sizeOf(module.path), module.getName(), @@ -586,7 +588,7 @@ class Bundler { return new ModuleTransport({ name: id, - id: getModuleId(module), + id: moduleId, code: code, sourceCode: code, sourcePath: module.path, @@ -645,7 +647,7 @@ class Bundler { } - _generateAssetModule(bundle, module, getModuleId, platform = null) { + _generateAssetModule(bundle, module, moduleId, platform = null) { return Promise.all([ module.getName(), this._generateAssetObjAndCode(module, platform), @@ -653,7 +655,7 @@ class Bundler { bundle.addAsset(asset); return new ModuleTransport({ name, - id: getModuleId(module), + id: moduleId, code, meta: meta, sourceCode: code, From 6345d17c79eed811473564c2e00f199d551809af Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 20 Jun 2016 10:08:50 -0700 Subject: [PATCH 683/936] Print nicer error message if an asset directory is not found in any of the roots Summary: When an asset is included in a module, and the directory for that asset can't be found in any of the roots, it is hard to debug that, because the error message contains neither the name of the requested file, the sub directory it is located in, nor the roots that have been searched for it. It becomes more difficult, because that exception is created asynchronously. It contains the calling promise code. This diff makes the error message more useful by including the name of the file, the sub directory, and the roots. Reviewed By: bestander Differential Revision: D3456738 fbshipit-source-id: 60b81f04626ad386f7120247c5f5361c81c52968 --- react-packager/src/AssetServer/index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index c9b198a5..65f1edcf 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -106,7 +106,8 @@ class AssetServer { return ( this._findRoot( this._roots, - path.dirname(assetPath) + path.dirname(assetPath), + assetPath, ) .then(dir => Promise.all([ dir, @@ -138,7 +139,7 @@ class AssetServer { ); } - _findRoot(roots, dir) { + _findRoot(roots, dir, debugInfoFile) { return Promise.all( roots.map(root => { const absRoot = path.resolve(root); @@ -162,7 +163,9 @@ class AssetServer { return stats[i].path; } } - throw new Error('Could not find any directories'); + + const rootsString = roots.map(s => `'${s}'`).join(', '); + throw new Error(`'${debugInfoFile}' could not be found, because '${dir}' is not a subdirectory of any of the roots (${rootsString})`); }); } @@ -194,7 +197,7 @@ class AssetServer { return map; } - + _getAssetDataFromName(platform, file) { return getAssetDataFromName(file, platform); } From fc38304bf8a57774ecafff2284a2f530677ee10b Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Wed, 22 Jun 2016 08:08:28 -0700 Subject: [PATCH 684/936] Allow rn-cli.config.js to specify the default transformer Summary: This will allow consumers to supply their own transformer to all `react-native` cli commands by simply implementing `rn-cli.config.js` and overriding `getTransformModulePath()`. That way they don't have to fork various parts of the iOS and Android build system that React Native already provides just to add a `--transformer` command line argument. **Test plan:** Applied this patch to the React Native version in my app, implemented `getTransformModulePath()` in my `rn-cli.config.js`, and verified that my custom transformer is invoked. Closes https://github.com/facebook/react-native/pull/7961 Differential Revision: D3404201 Pulled By: foghina fbshipit-source-id: c7eaa85de84d485d06d23a2ffea899821b2cf71c --- rn-cli.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rn-cli.config.js b/rn-cli.config.js index c278051e..1c32238a 100644 --- a/rn-cli.config.js +++ b/rn-cli.config.js @@ -33,4 +33,9 @@ module.exports = { return [path.resolve(__dirname, '..')]; } }, + + getTransformModulePath() { + return require.resolve('./transformer'); + }, + }; From 21be7ecccf5e7147d67151b83888efafbd8ccef9 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Thu, 30 Jun 2016 01:57:30 -0700 Subject: [PATCH 685/936] Update some JS in preparation for some Jest updates. Summary: * Next version of Jest doesn't allow non test files in __tests__ folders. * I'm trying to switch all tests off of jsdom on react-native. This should save 500ms of time when running a single test because jsdom is slow to load and react-native is also not supposed to run in a DOM environment, so let's not pretend we are providing the DOM in tests. * Make the bridge config configurable so that when we disable automocking and we reset the registry we can redefine the value. Oh also, stop using lodash in Server.js. First off, lodash 3 doesn't work in Jest's node env because it does some crazy stuff, second because we don't need to load all of lodash for debounce. Reviewed By: davidaurelio Differential Revision: D3502886 fbshipit-source-id: 1da1cfba9ed12264d81945b702e7a429d5f84424 --- react-packager/src/Server/index.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index ef35844f..c7ab61be 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -16,11 +16,18 @@ const Bundler = require('../Bundler'); const Promise = require('promise'); const SourceMapConsumer = require('source-map').SourceMapConsumer; -const _ = require('lodash'); const declareOpts = require('../lib/declareOpts'); const path = require('path'); const url = require('url'); +function debounce(fn, delay) { + var timeout; + return () => { + clearTimeout(timeout); + timeout = setTimeout(fn, delay); + }; +} + const validateOpts = declareOpts({ projectRoots: { type: 'array', @@ -209,7 +216,7 @@ class Server { this._fileWatcher.on('all', this._onFileChange.bind(this)); - this._debouncedFileChangeHandler = _.debounce(filePath => { + this._debouncedFileChangeHandler = debounce(filePath => { this._clearBundles(); this._informChangeWatchers(); }, 50); From ed0b6b1ef5480e4a8df0e48c6c4b6a0eb10f07b8 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 5 Jul 2016 06:34:00 -0700 Subject: [PATCH 686/936] Remove `node_modules/react` from the list of discoverable haste modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: This removes `node_modules/react` from the list of directories that are used for haste module resolutions. Modules required from React are now imported with `require('react/lib/…')`. Reviewed By: astreet Differential Revision: D3509863 fbshipit-source-id: 32cd34e2b8496f0a6676dbe6bb1eacc18124c01e --- react-packager/src/Resolver/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/react-packager/src/Resolver/index.js b/react-packager/src/Resolver/index.js index c8faa60a..5d577e3d 100644 --- a/react-packager/src/Resolver/index.js +++ b/react-packager/src/Resolver/index.js @@ -93,7 +93,6 @@ class Resolver { (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, providesModuleNodeModules: [ - 'react', 'react-native', 'react-native-windows', // Parse requires AsyncStorage. They will From 8b00d9816787a5257f51a0c78cc73c01d5a72015 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 5 Jul 2016 09:37:00 -0700 Subject: [PATCH 687/936] Don't attempt symbolicating non-http(s) urls Summary: Looks like spaces in function names can happen, but the lib we use for parsing stacktraces doesn't support that. As result, when error is thrown in global scope, new JSC puts "global code" as function name, our parser chokes on it and thinks "global code@http://...." is a file name and sends it to packager. The packager can't resolve that URL and fails the whole symbolication request. Longer term fix here: https://github.com/errwischt/stacktrace-parser/pull/5 Reviewed By: astreet Differential Revision: D3516741 fbshipit-source-id: 4f2bb70084437ed9d37495cd775622a8c981fad1 --- react-packager/src/Server/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index c7ab61be..dadc7379 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -499,8 +499,10 @@ class Server { const sourceUrl = frame.file; // Skip `/debuggerWorker.js` which drives remote debugging because it // does not need to symbolication. + // Skip anything except http(s), because there is no support for that yet if (!urlIndexes.hasOwnProperty(sourceUrl) && - !sourceUrl.endsWith('/debuggerWorker.js')) { + !sourceUrl.endsWith('/debuggerWorker.js') && + sourceUrl.startsWith('http')) { urlIndexes[sourceUrl] = uniqueUrls.length; uniqueUrls.push(sourceUrl); } From 1dcf2e0c7f174d68e997a9be841459cdea722da8 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 5 Jul 2016 10:44:08 -0700 Subject: [PATCH 688/936] Fix test broken by D3516741 Reviewed By: astreet Differential Revision: D3517064 fbshipit-source-id: 8fe6fca1bc2c77872b1bf09bd114a3d490d9c834 --- react-packager/src/Server/__tests__/Server-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index d8d2e96d..c54e503d 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -398,7 +398,7 @@ describe('processRequest', () => { describe('/symbolicate endpoint', () => { pit('should symbolicate given stack trace', () => { const body = JSON.stringify({stack: [{ - file: 'foo.bundle?platform=ios', + file: 'http://foo.bundle?platform=ios', lineNumber: 2100, column: 44, customPropShouldBeLeftUnchanged: 'foo', From e4284364161427661b0df40fb4e119f7a4410f51 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Tue, 5 Jul 2016 11:36:31 -0700 Subject: [PATCH 689/936] Reverted commit D3516741 Summary: Looks like spaces in function names can happen, but the lib we use for parsing stacktraces doesn't support that. As result, when error is thrown in global scope, new JSC puts "global code" as function name, our parser chokes on it and thinks "global code@http://...." is a file name and sends it to packager. The packager can't resolve that URL and fails the whole symbolication request. Longer term fix here: https://github.com/errwischt/stacktrace-parser/pull/5 Reviewed By: astreet Differential Revision: D3516741 fbshipit-source-id: 7f14b52a50a118dc95a3463aee842941e904e984 --- react-packager/src/Server/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index dadc7379..c7ab61be 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -499,10 +499,8 @@ class Server { const sourceUrl = frame.file; // Skip `/debuggerWorker.js` which drives remote debugging because it // does not need to symbolication. - // Skip anything except http(s), because there is no support for that yet if (!urlIndexes.hasOwnProperty(sourceUrl) && - !sourceUrl.endsWith('/debuggerWorker.js') && - sourceUrl.startsWith('http')) { + !sourceUrl.endsWith('/debuggerWorker.js')) { urlIndexes[sourceUrl] = uniqueUrls.length; uniqueUrls.push(sourceUrl); } From 8f5ebd55daab79e2ffa963534a05b7333599ecf6 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Tue, 5 Jul 2016 13:25:18 -0700 Subject: [PATCH 690/936] Revert "Reverted commit D3516741" Summary: Unrevert a revert Closes https://github.com/facebook/react-native/pull/8581 Differential Revision: D3517894 Pulled By: bestander fbshipit-source-id: 19006b9c6438cf05d44ee152eb7b1b17ea4d61a0 --- react-packager/src/Server/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index c7ab61be..dadc7379 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -499,8 +499,10 @@ class Server { const sourceUrl = frame.file; // Skip `/debuggerWorker.js` which drives remote debugging because it // does not need to symbolication. + // Skip anything except http(s), because there is no support for that yet if (!urlIndexes.hasOwnProperty(sourceUrl) && - !sourceUrl.endsWith('/debuggerWorker.js')) { + !sourceUrl.endsWith('/debuggerWorker.js') && + sourceUrl.startsWith('http')) { urlIndexes[sourceUrl] = uniqueUrls.length; uniqueUrls.push(sourceUrl); } From 81961d87563c1d820b5e319cd54e45f3fb05d062 Mon Sep 17 00:00:00 2001 From: Johannes Stein Date: Wed, 6 Jul 2016 08:13:02 -0700 Subject: [PATCH 691/936] Fixes typo in error message Summary: This pull request fixes a small typo when a module fails being transformed through the React Packager. Closes https://github.com/facebook/react-native/pull/8596 Differential Revision: D3522272 Pulled By: mkonicek fbshipit-source-id: e117a26bab5a99573ac68fb0e7618df0a14325a4 --- react-packager/src/JSTransformer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 3ddd95bd..681f864a 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -87,7 +87,7 @@ class Transformer { transformFile(fileName, code, options) { if (!this._transform) { - return Promise.reject(new Error('No transfrom module')); + return Promise.reject(new Error('No transform module')); } debug('transforming file', fileName); return this From 7ae3a72b5b44dfc3e888dfa0e114e31bee2920ab Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Thu, 7 Jul 2016 11:40:52 -0700 Subject: [PATCH 692/936] fix: increased fs timeout for assetserver to test if this helps for large codebases Reviewed By: jingc Differential Revision: D3528913 fbshipit-source-id: f04eff42327bd729ebfcd71856a1d38ef9810986 --- react-packager/src/AssetServer/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 65f1edcf..88a7bebe 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -28,9 +28,9 @@ function timeoutableDenodeify(fsFunc, timeout) { }; } -const stat = timeoutableDenodeify(fs.stat, 5000); -const readDir = timeoutableDenodeify(fs.readdir, 5000); -const readFile = timeoutableDenodeify(fs.readFile, 5000); +const stat = timeoutableDenodeify(fs.stat, 15000); +const readDir = timeoutableDenodeify(fs.readdir, 15000); +const readFile = timeoutableDenodeify(fs.readFile, 15000); const validateOpts = declareOpts({ projectRoots: { From 9d6414281ed4e63473211674dd6c0848e7fae33d Mon Sep 17 00:00:00 2001 From: Mark Oswald Date: Wed, 13 Jul 2016 03:54:38 -0700 Subject: [PATCH 693/936] Use HTTP range requests (responses) to serve mp4 from assets Summary: This PR solves a problem when video assets are used from third-party React Native components (e.g. [react-native-video](https://github.com/brentvatne/react-native-video). The video will not work while the assets are served from the react native packager because the used video component (iOS) relies on HTTP range requests. I added a small fix that allows ranged requests (e.g. mp4) to be served in ranges. To test this: 1. make new react native project 1. add [react-native-video](https://github.com/brentvatne/react-native-video) to xcode project 1. add video component to your project ``` import Video from 'react-native-video'; var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); /* ... /* render() { let source = resolveAssetSource(require('./someVideoFile.mp4')) || {}; return