Code clean-ups and createServer migration

Summary:
Scope of the diff:

1. Middleware
`react-native-github/local-cli` and `react-native-internal-cli` uses a very similar set of middlewares (internal cli extends github version), so I decided to move it to a standalone file (middleware manager) in order to remove duplications and increase readability.

2. Types
Seems that after Flow upgrade to version 0.68 there were many type issues to resolve, so all of them were auto-mocked. This is fine, but I'd like to see Flow assists me with `Metro.createServer` -> `Metro.runServer` migration. Hence, I decided to resolve flow mocks, related to runServer.

3. `runServer` signature
In `react-native-github` repo I cleaned up `runServer` signature by removing `startCallback` and `readyCallback` from the function parameters and moved them to `runServer` instead.

4. Replace `createServer` by `runServer`
In `react-native-github` repo, `createServer` has been replaced by `runServer`. __Some of arguments are not mapped__.

Note that this diff will partially break argument mapping. This is intentional. @[100000044482482:ives] will fix it with a new config package.

Reviewed By: mjesun

Differential Revision: D8711717

fbshipit-source-id: a843ab576360ff7242099910d8f25a9cb0a388c0
This commit is contained in:
Alexey Kureev 2018-07-02 09:33:08 -07:00 committed by Facebook Github Bot
parent ee535fafe3
commit c4a66a89a2
4 changed files with 111 additions and 177 deletions

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @strict
* @flow
*/
const compression = require('compression');
const connect = require('connect');
const errorhandler = require('errorhandler');
const morgan = require('morgan');
const path = require('path');
const serveStatic = require('serve-static');
const WebSocketServer = require('ws').Server;
const indexPageMiddleware = require('./indexPage');
const copyToClipBoardMiddleware = require('./copyToClipBoardMiddleware');
const loadRawBodyMiddleware = require('./loadRawBodyMiddleware');
const openStackFrameInEditorMiddleware = require('./openStackFrameInEditorMiddleware');
const statusPageMiddleware = require('./statusPageMiddleware.js');
const systraceProfileMiddleware = require('./systraceProfileMiddleware.js');
const getDevToolsMiddleware = require('./getDevToolsMiddleware');
type Options = {
+watchFolders: $ReadOnlyArray<string>,
+host?: string,
}
type WebSocketProxy = {
server: WebSocketServer,
isChromeConnected: () => boolean,
};
type Connect = any;
module.exports = class MiddlewareManager {
app: Connect;
options: Options;
constructor(options: Options) {
const debuggerUIFolder = path.join(__dirname, 'util', 'debugger-ui');
this.options = options;
this.app = connect()
.use(loadRawBodyMiddleware)
.use(compression())
.use('/debugger-ui', serveStatic(debuggerUIFolder))
.use(openStackFrameInEditorMiddleware(this.options))
.use(copyToClipBoardMiddleware)
.use(statusPageMiddleware)
.use(systraceProfileMiddleware)
.use(indexPageMiddleware)
.use(morgan('combined'))
.use(errorhandler());
}
serveStatic = (folder: string) => {
this.app.use(serveStatic(folder));
};
getConnectInstance = () => this.app;
attachDevToolsSocket = (socket: WebSocketProxy) => {
this.app.use(
getDevToolsMiddleware(this.options, () => socket.isChromeConnected()),
);
};
};

View File

@ -27,8 +27,8 @@ function launchDevTools({host, watchFolders}, isChromeConnected) {
// Explicit config always wins
var customDebugger = process.env.REACT_DEBUGGER;
if (customDebugger) {
var projects = watchFolders.map(escapePath).join(' ');
var command = customDebugger + ' ' + projects;
var folders = watchFolders.map(escapePath).join(' ');
var command = customDebugger + ' ' + folders;
console.log('Starting custom debugger by executing: ' + command);
exec(command, function(error, stdout, stderr) {
if (error !== null) {

View File

@ -14,132 +14,69 @@ require('../../setupBabel')();
const Metro = require('metro');
const HmrServer = require('metro/src/HmrServer');
const {Terminal} = require('metro-core');
const attachWebsocketServer = require('metro/src/lib/attachWebsocketServer');
const compression = require('compression');
const connect = require('connect');
const copyToClipBoardMiddleware = require('./middleware/copyToClipBoardMiddleware');
const defaultAssetExts = Metro.defaults.assetExts;
const defaultSourceExts = Metro.defaults.sourceExts;
const defaultPlatforms = Metro.defaults.platforms;
const defaultProvidesModuleNodeModules =
Metro.defaults.providesModuleNodeModules;
const errorhandler = require('errorhandler');
const fs = require('fs');
const getDevToolsMiddleware = require('./middleware/getDevToolsMiddleware');
const http = require('http');
const https = require('https');
const indexPageMiddleware = require('./middleware/indexPage');
const loadRawBodyMiddleware = require('./middleware/loadRawBodyMiddleware');
const messageSocket = require('./util/messageSocket.js');
const morgan = require('morgan');
const openStackFrameInEditorMiddleware = require('./middleware/openStackFrameInEditorMiddleware');
const path = require('path');
const serveStatic = require('serve-static');
const statusPageMiddleware = require('./middleware/statusPageMiddleware.js');
const systraceProfileMiddleware = require('./middleware/systraceProfileMiddleware.js');
const webSocketProxy = require('./util/webSocketProxy.js');
const {ASSET_REGISTRY_PATH} = require('../core/Constants');
const MiddlewareManager = require('./middleware/MiddlewareManager');
import type {ConfigT} from 'metro';
/* $FlowFixMe(site=react_native_oss) */
import type {Reporter} from 'metro/src/lib/reporting';
export type Args = {|
+assetExts: $ReadOnlyArray<string>,
+cert: string,
+customLogReporterPath?: string,
+host: string,
+https: boolean,
+maxWorkers: number,
+key: string,
+nonPersistent: boolean,
+platforms: $ReadOnlyArray<string>,
+port: number,
+projectRoot: string,
+providesModuleNodeModules: Array<string>,
+resetCache: boolean,
+sourceExts: $ReadOnlyArray<string>,
+transformer?: string,
+verbose: boolean,
+watchFolders: $ReadOnlyArray<string>,
|};
function runServer(
args: Args,
config: ConfigT,
// FIXME: this is weird design. The top-level should pass down a custom
// reporter rather than passing it up as argument to an event.
startedCallback: (reporter: Reporter) => mixed,
readyCallback: (reporter: Reporter) => mixed,
) {
var wsProxy = null;
var ms = null;
async function runServer(args: Args, config: ConfigT) {
const terminal = new Terminal(process.stdout);
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an error
* found when Flow v0.68 was deployed. To see the error delete this comment
* and run Flow. */
const ReporterImpl = getReporterImpl(args.customLogReporterPath || null);
const reporter = new ReporterImpl(terminal);
const packagerServer = getPackagerServer(args, config, reporter);
startedCallback(reporter);
const middlewareManager = new MiddlewareManager(args);
const app = connect()
.use(loadRawBodyMiddleware)
.use(compression())
.use(
'/debugger-ui',
serveStatic(path.join(__dirname, 'util', 'debugger-ui')),
)
.use(
getDevToolsMiddleware(args, () => wsProxy && wsProxy.isChromeConnected()),
)
.use(getDevToolsMiddleware(args, () => ms && ms.isChromeConnected()))
.use(openStackFrameInEditorMiddleware(args))
.use(copyToClipBoardMiddleware)
.use(statusPageMiddleware)
.use(systraceProfileMiddleware)
.use(indexPageMiddleware)
.use(packagerServer.processRequest.bind(packagerServer));
args.watchFolders.forEach(middlewareManager.serveStatic);
args.watchFolders.forEach(root => app.use(serveStatic(root)));
app.use(morgan('combined')).use(errorhandler());
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an error
* found when Flow v0.68 was deployed. To see the error delete this comment
* and run Flow. */
if (args.https && (!args.key || !args.cert)) {
throw new Error('Cannot use https without specifying key and cert options');
}
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an error
* found when Flow v0.68 was deployed. To see the error delete this comment
* and run Flow. */
const serverInstance = args.https
? https.createServer(
{
key: fs.readFileSync(args.key),
cert: fs.readFileSync(args.cert),
},
app,
)
: http.createServer(app);
serverInstance.listen(args.port, args.host, 511, function() {
attachWebsocketServer({
httpServer: serverInstance,
path: '/hot',
websocketServer: new HmrServer(packagerServer),
});
wsProxy = webSocketProxy.attachToServer(serverInstance, '/debugger-proxy');
ms = messageSocket.attachToServer(serverInstance, '/message');
readyCallback(reporter);
const serverInstance = await Metro.runServer({
config: {
...config,
hmrEnabled: true,
maxWorkers: args.maxWorkers,
reporter,
secure: args.https,
secureKey: args.key,
secureCert: args.cert,
transformModulePath: args.transformer
? path.resolve(args.transformer)
: config.getTransformModulePath(),
watch: !args.nonPersistent,
},
});
// Disable any kind of automatic timeout behavior for incoming
// requests in case it takes the packager more than the default
// timeout of 120 seconds to respond to a request.
serverInstance.timeout = 0;
// In Node 8, the default keep-alive for an HTTP connection is 5 seconds. In
// early versions of Node 8, this was implemented in a buggy way which caused
// some HTTP responses (like those containing large JS bundles) to be
// terminated early.
//
// As a workaround, arbitrarily increase the keep-alive from 5 to 30 seconds,
// which should be enough to send even the largest of JS bundles.
//
// For more info: https://github.com/nodejs/node/issues/13391
//
// $FlowFixMe
serverInstance.keepAliveTimeout = 30000;
}
function getReporterImpl(customLogReporterPath: ?string) {
@ -162,53 +99,4 @@ function getReporterImpl(customLogReporterPath: ?string) {
}
}
function getPackagerServer(args, config, reporter) {
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an error
* found when Flow v0.68 was deployed. To see the error delete this comment
* and run Flow. */
const transformModulePath = args.transformer
? path.resolve(args.transformer)
: config.getTransformModulePath();
const providesModuleNodeModules =
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.68 was deployed. To see the error delete this
* comment and run Flow. */
args.providesModuleNodeModules || defaultProvidesModuleNodeModules;
return Metro.createServer({
asyncRequireModulePath: config.getAsyncRequireModulePath(),
assetExts: defaultAssetExts.concat(args.assetExts),
assetRegistryPath: ASSET_REGISTRY_PATH,
blacklistRE: config.getBlacklistRE(),
cacheStores: config.cacheStores,
cacheVersion: '3',
enableBabelRCLookup: config.getEnableBabelRCLookup(),
extraNodeModules: config.extraNodeModules,
dynamicDepsInPackages: config.dynamicDepsInPackages,
getModulesRunBeforeMainModule: config.getModulesRunBeforeMainModule,
getPolyfills: config.getPolyfills,
getResolverMainFields: config.getResolverMainFields,
getRunModuleStatement: config.getRunModuleStatement,
getTransformOptions: config.getTransformOptions,
hasteImplModulePath: config.hasteImplModulePath,
maxWorkers: args.maxWorkers,
platforms: defaultPlatforms.concat(args.platforms),
polyfillModuleNames: config.getPolyfillModuleNames(),
postMinifyProcess: config.postMinifyProcess,
postProcessBundleSourcemap: config.postProcessBundleSourcemap,
projectRoot: args.projectRoot,
providesModuleNodeModules: providesModuleNodeModules,
reporter,
resetCache: args.resetCache,
resolveRequest: config.resolveRequest,
sourceExts: args.sourceExts.concat(defaultSourceExts),
transformModulePath: transformModulePath,
verbose: args.verbose,
watch: !args.nonPersistent,
watchFolders: args.watchFolders,
workerPath: config.getWorkerPath(),
});
}
module.exports = runServer;

View File

@ -19,34 +19,10 @@ import type {Args as RunServerArgs} from './runServer';
/**
* Starts the React Native Packager Server.
*/
function server(argv: mixed, config: RNConfig, args: Object) {
const startedCallback = logReporter => {
logReporter.update({
type: 'initialize_started',
port: args.port,
projectRoots: args.watchFolders,
});
process.on('uncaughtException', error => {
logReporter.update({
type: 'initialize_failed',
port: args.port,
error,
});
process.exit(11);
});
};
const readyCallback = logReporter => {
logReporter.update({
type: 'initialize_done',
});
};
const runServerArgs: RunServerArgs = args;
function server(argv: mixed, config: RNConfig, args: RunServerArgs) {
/* $FlowFixMe(site=react_native_fb) ConfigT shouldn't be extendable. */
const configT: ConfigT = config;
runServer(runServerArgs, configT, startedCallback, readyCallback);
runServer(args, configT);
}
module.exports = {