diff --git a/packager/packager.js b/packager/packager.js index ddda537f9e..70324e6440 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -20,10 +20,10 @@ const isAbsolutePath = require('absolute-path'); const blacklist = require('./blacklist.js'); const chalk = require('chalk'); -const checkNodeVersion = require('./checkNodeVersion'); +const checkNodeVersion = require('../private-cli/src/server/checkNodeVersion'); const cpuProfilerMiddleware = require('./cpuProfilerMiddleware'); const connect = require('connect'); -const formatBanner = require('./formatBanner'); +const formatBanner = require('../private-cli/src/server/formatBanner'); const getDevToolsMiddleware = require('./getDevToolsMiddleware'); const loadRawBodyMiddleware = require('./loadRawBodyMiddleware'); const openStackFrameInEditorMiddleware = require('./openStackFrameInEditorMiddleware'); diff --git a/packager/rn-cli.config.js b/packager/rn-cli.config.js new file mode 100644 index 0000000000..760f1e8742 --- /dev/null +++ b/packager/rn-cli.config.js @@ -0,0 +1,40 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +/** + * React Native CLI configuration file + */ +'use strict'; + +const blacklist = require('./blacklist.js'); +const path = require('path'); + +module.exports = { + getProjectRoots() { + return this._getRoots(); + }, + + getAssetRoots() { + return this._getRoots(); + }, + + getBlacklistRE() { + return blacklist(''); + }, + + getTransformModulePath() { + return require.resolve('./transformer'); + }, + + _getRoots() { + // match on either path separator + if (__dirname.match(/node_modules[\/\\]react-native[\/\\]packager$/)) { + // packager is running from node_modules of another project + return [path.resolve(__dirname, '../../..')]; + } else if (__dirname.match(/Pods\/React\/packager$/)) { + // packager is running from node_modules of another project + return [path.resolve(__dirname, '../../..')]; + } else { + return [path.resolve(__dirname, '..')]; + } + }, +}; diff --git a/packager/webSocketProxy.js b/packager/webSocketProxy.js index 4f88379b49..0562f050ad 100644 --- a/packager/webSocketProxy.js +++ b/packager/webSocketProxy.js @@ -8,9 +8,9 @@ */ 'use strict'; -var WebSocketServer = require('ws').Server; function attachToServer(server, path) { + var WebSocketServer = require('ws').Server; var wss = new WebSocketServer({ server: server, path: path diff --git a/private-cli/index.js b/private-cli/index.js index 8661807844..dc2f8993d6 100644 --- a/private-cli/index.js +++ b/private-cli/index.js @@ -8,7 +8,10 @@ */ 'use strict'; -require('../packager/babelRegisterOnly')([/private-cli\/src/]); +require('../packager/babelRegisterOnly')([ + /private-cli\/src/, + /packager\/[^\/]*/ +]); var cli = require('./src/cli'); var fs = require('fs'); diff --git a/private-cli/src/cli.js b/private-cli/src/cli.js index 0f1212d749..0c6b20e03c 100644 --- a/private-cli/src/cli.js +++ b/private-cli/src/cli.js @@ -12,10 +12,12 @@ const bundle = require('./bundle/bundle'); const Config = require('./util/Config'); const dependencies = require('./dependencies/dependencies'); const Promise = require('promise'); +const server = require('./server/server'); const documentedCommands = { bundle: bundle, dependencies: dependencies, + server: server, }; const hiddenCommands = { diff --git a/packager/checkNodeVersion.js b/private-cli/src/server/checkNodeVersion.js similarity index 76% rename from packager/checkNodeVersion.js rename to private-cli/src/server/checkNodeVersion.js index 388a0e2faa..5d73f9e463 100644 --- a/packager/checkNodeVersion.js +++ b/private-cli/src/server/checkNodeVersion.js @@ -8,15 +8,17 @@ */ 'use strict'; -var chalk = require('chalk'); -var semver = require('semver'); +const chalk = require('chalk'); +const formatBanner = require('./formatBanner'); +const semver = require('semver'); -var formatBanner = require('./formatBanner'); - -function checkNodeVersion() { +module.exports = function() { if (!semver.satisfies(process.version, '>=4')) { - var engine = semver.satisfies(process.version, '<1 >=4') ? 'Node' : 'io.js'; - var message = 'You are currently running ' + engine + ' ' + + const engine = semver.satisfies(process.version, '<1 >=4') + ? 'Node' + : 'io.js'; + + const message = 'You are currently running ' + engine + ' ' + process.version + '.\n' + '\n' + 'React Native runs on Node 4.0 or newer. There are several ways to ' + @@ -35,6 +37,4 @@ function checkNodeVersion() { paddingBottom: 1, })); } -} - -module.exports = checkNodeVersion; +}; diff --git a/packager/formatBanner.js b/private-cli/src/server/formatBanner.js similarity index 100% rename from packager/formatBanner.js rename to private-cli/src/server/formatBanner.js diff --git a/private-cli/src/server/runServer.js b/private-cli/src/server/runServer.js new file mode 100644 index 0000000000..7f33d83dbf --- /dev/null +++ b/private-cli/src/server/runServer.js @@ -0,0 +1,68 @@ +/** + * 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 connect = require('connect'); +// TODO: move middlewares to private-cli/src/server +const cpuProfilerMiddleware = require('../../../packager/cpuProfilerMiddleware'); +const getDevToolsMiddleware = require('../../../packager/getDevToolsMiddleware'); +const http = require('http'); +const isAbsolutePath = require('absolute-path'); +const loadRawBodyMiddleware = require('../../../packager/loadRawBodyMiddleware'); +const openStackFrameInEditorMiddleware = require('../../../packager/openStackFrameInEditorMiddleware'); +const path = require('path'); +const ReactPackager = require('../../../packager/react-packager'); +const statusPageMiddleware = require('../../../packager/statusPageMiddleware.js'); +const systraceProfileMiddleware = require('../../../packager/systraceProfileMiddleware.js'); + +function runServer(args, config, readyCallback) { + const app = connect() + .use(loadRawBodyMiddleware) + .use(getDevToolsMiddleware(args)) + .use(openStackFrameInEditorMiddleware) + .use(statusPageMiddleware) + .use(systraceProfileMiddleware) + .use(cpuProfilerMiddleware) + // Temporarily disable flow check until it's more stable + //.use(getFlowTypeCheckMiddleware(args)) + .use(getAppMiddleware(args, config)); + + args.projectRoots.forEach(root => app.use(connect.static(root))); + + app.use(connect.logger()) + .use(connect.compress()) + .use(connect.errorHandler()); + + return http.createServer(app).listen(args.port, '::', readyCallback); +} + +function getAppMiddleware(args, config) { + let transformerPath = args.transformer; + if (!isAbsolutePath(transformerPath)) { + transformerPath = path.resolve(process.cwd(), transformerPath); + } + + return ReactPackager.middleware({ + nonPersistent: args.nonPersistent, + projectRoots: args.projectRoots, + blacklistRE: config.getBlacklistRE(), + cacheVersion: '3', + transformModulePath: transformerPath, + assetRoots: args.assetRoots, + assetExts: ['png', 'jpeg', 'jpg'], + resetCache: args.resetCache || args['reset-cache'], + polyfillModuleNames: [ + require.resolve( + '../../../Libraries/JavaScriptAppEngine/polyfills/document.js' + ), + ], + }); +} + +module.exports = runServer; diff --git a/private-cli/src/server/server.js b/private-cli/src/server/server.js new file mode 100644 index 0000000000..21c29ed07c --- /dev/null +++ b/private-cli/src/server/server.js @@ -0,0 +1,148 @@ +/** + * 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 chalk = require('chalk'); +const checkNodeVersion = require('./checkNodeVersion'); +const formatBanner = require('./formatBanner'); +const parseCommandLine = require('../../../packager/parseCommandLine'); +const path = require('path'); +const Promise = require('promise'); +const runServer = require('./runServer'); +const webSocketProxy = require('../../../packager/webSocketProxy.js'); + +/** + * Starts the React Native Packager Server. + */ +function server(argv, config) { + return new Promise((resolve, reject) => { + _server(argv, config, resolve, reject); + }); +} + +function _server(argv, config, resolve, reject) { + const args = 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: 'skipflow', + description: 'Disable flow checks' + }, { + command: 'nonPersistent', + description: 'Disable file watcher' + }, { + command: 'transformer', + type: 'string', + default: config.getTransformModulePath(), + description: 'Specify a custom transformer to be used (absolute path)' + }, { + command: 'resetCache', + description: 'Removes cached files', + default: false, + }, { + command: 'reset-cache', + description: 'Removes cached files', + default: false, + }]); + + args.projectRoots = args.projectRoots + ? argToArray(args.projectRoots) + : config.getProjectRoots(); + + if (args.root) { + const additionalRoots = argToArray(args.root); + additionalRoots.forEach(root => { + args.projectRoots.push(path.resolve(root)); + }); + } + + args.assetRoots = args.assetRoots + ? argToArray(args.projectRoots).map(dir => + path.resolve(process.cwd(), dir) + ) + : config.getAssetRoots(); + + checkNodeVersion(); + + console.log(formatBanner( + 'Running packager on port ' + args.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( + 'Looking for JS files in\n ', + chalk.dim(args.projectRoots.join('\n ')), + '\n' + ); + + process.on('uncaughtException', error => { + if (error.code === 'EADDRINUSE') { + console.log( + chalk.bgRed.bold(' ERROR '), + chalk.red('Packager can\'t listen on port', chalk.bold(args.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:' + args.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(error.message)); + const errorAttributes = JSON.stringify(error); + if (errorAttributes !== '{}') { + console.error(chalk.red(errorAttributes)); + } + console.error(chalk.red(error.stack)); + } + console.log('\nSee', chalk.underline('http://facebook.github.io/react-native/docs/troubleshooting.html')); + console.log('for common problems and solutions.'); + reject(); + }); + + // TODO: remove once we deprecate this arg + if (args.resetCache) { + console.log( + 'Please start using `--reset-cache` instead. ' + + 'We\'ll deprecate this argument soon.' + ); + } + + resolve(startServer(args, config)); +} + +function startServer(args, config) { + const serverInstance = runServer(args, config, () => + console.log('\nReact packager ready.\n') + ); + + webSocketProxy.attachToServer(serverInstance, '/debugger-proxy'); +} + +function argToArray(arg) { + return Array.isArray(arg) ? arg : arg.split(','); +} + +module.exports = server;