From 5cc093945401ea665d099377583ff7f46d25be31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Tue, 28 Nov 2017 04:17:21 -0800 Subject: [PATCH] Automatically watches the metro configuration file Reviewed By: BYK Differential Revision: D6408358 fbshipit-source-id: d167534c9c51c3c079148d982ef4ab44c8be0d75 --- packages/metro-bundler/src/cli-utils.js | 33 +++++++++--- packages/metro-bundler/src/commands/build.js | 6 +-- packages/metro-bundler/src/commands/serve.js | 57 ++++++++++++++++---- packages/metro-bundler/src/index.js | 30 ++++++----- 4 files changed, 92 insertions(+), 34 deletions(-) diff --git a/packages/metro-bundler/src/cli-utils.js b/packages/metro-bundler/src/cli-utils.js index 7157902b..aecc0355 100644 --- a/packages/metro-bundler/src/cli-utils.js +++ b/packages/metro-bundler/src/cli-utils.js @@ -19,12 +19,20 @@ import type {ConfigT} from './Config'; const METRO_CONFIG_FILENAME = 'metro.config.js'; -exports.findMetroConfig = async function( - filename: ?string, -): Promise<$Shape> { +exports.watchFile = async function( + filename: string, + callback: () => *, +): Promise { + fs.watchFile(filename, () => { + callback(); + }); + + await callback(); +}; + +exports.findMetroConfig = async function(filename: ?string): Promise { if (filename) { - // $FlowFixMe: We want this require to be dynamic - return require(path.resolve(process.cwd(), filename)); + return path.resolve(process.cwd(), filename); } else { let previous; let current = process.cwd(); @@ -33,14 +41,25 @@ exports.findMetroConfig = async function( const filename = path.join(current, METRO_CONFIG_FILENAME); if (fs.existsSync(filename)) { - // $FlowFixMe: We want this require to be dynamic - return require(filename); + return filename; } previous = current; current = path.dirname(current); } while (previous !== current); + return null; + } +}; + +exports.fetchMetroConfig = async function( + filename: ?string, +): Promise<$Shape> { + const location = await exports.findMetroConfig(filename); + if (location) { + // $FlowFixMe: We want this require to be dynamic + return require(location); + } else { return {}; } }; diff --git a/packages/metro-bundler/src/commands/build.js b/packages/metro-bundler/src/commands/build.js index 9637504d..b4a13c0d 100644 --- a/packages/metro-bundler/src/commands/build.js +++ b/packages/metro-bundler/src/commands/build.js @@ -16,7 +16,7 @@ const MetroApi = require('..'); const os = require('os'); -const {findMetroConfig, makeAsyncCommand} = require('../cli-utils'); +const {fetchMetroConfig, makeAsyncCommand} = require('../cli-utils'); import typeof Yargs from 'yargs'; @@ -52,6 +52,6 @@ exports.builder = (yargs: Yargs) => { // eslint-disable-next-line no-unclear-flowtypes exports.handler = makeAsyncCommand(async (argv: any) => { - argv.config = await findMetroConfig(argv.config); - await MetroApi.runBuild(argv); + const config = await fetchMetroConfig(argv.config); + await MetroApi.runBuild({...argv, config}); }); diff --git a/packages/metro-bundler/src/commands/serve.js b/packages/metro-bundler/src/commands/serve.js index dd4ab94f..f7359a53 100644 --- a/packages/metro-bundler/src/commands/serve.js +++ b/packages/metro-bundler/src/commands/serve.js @@ -16,7 +16,13 @@ const MetroApi = require('..'); const os = require('os'); -const {findMetroConfig, makeAsyncCommand} = require('../cli-utils'); +const { + findMetroConfig, + fetchMetroConfig, + watchFile, + makeAsyncCommand, +} = require('../cli-utils'); +const {promisify} = require('util'); import typeof Yargs from 'yargs'; @@ -49,15 +55,44 @@ exports.builder = (yargs: Yargs) => { // eslint-disable-next-line no-unclear-flowtypes exports.handler = makeAsyncCommand(async (argv: any) => { - argv.config = await findMetroConfig(argv.config); + let server = null; + let restarting = false; - await MetroApi.runServer({ - ...argv, - onReady(server) { - console.log( - `The HTTP server is ready to accept requests on ${server.address() - .address}:${server.address().port}`, - ); - }, - }); + async function restart() { + if (restarting) { + return; + } else { + restarting = true; + } + + if (server) { + console.log('Configuration changed... restarting the server...'); + await promisify(server.close).call(server); + } + + const config = await fetchMetroConfig(argv.config); + + server = await MetroApi.runServer({ + ...argv, + config, + onReady, + }); + + restarting = false; + } + + function onReady(server) { + console.log( + `The HTTP server is ready to accept requests on ${server.address() + .address}:${server.address().port}`, + ); + } + + const metroConfigLocation = await findMetroConfig(argv.config); + + if (metroConfigLocation) { + await watchFile(metroConfigLocation, restart); + } else { + await restart(); + } }); diff --git a/packages/metro-bundler/src/index.js b/packages/metro-bundler/src/index.js index 0e1e99d1..bf8cdea1 100644 --- a/packages/metro-bundler/src/index.js +++ b/packages/metro-bundler/src/index.js @@ -145,8 +145,13 @@ exports.createConnectMiddleware = async function( watch: true, }); - return (req: IncomingMessage, res: ServerResponse) => { - return metroServer.processRequest(req, res); + return { + middleware(req: IncomingMessage, res: ServerResponse) { + return metroServer.processRequest(req, res); + }, + end() { + metroServer.end(); + }, }; }; @@ -163,13 +168,13 @@ type RunServerOptions = {| exports.runServer = async (options: RunServerOptions) => { const serverApp = connect(); - const metroMiddleware = exports.createConnectMiddleware({ + const {middleware, end} = await exports.createConnectMiddleware({ config: options.config, maxWorkers: options.maxWorkers, projectRoots: options.projectRoots, }); - serverApp.use(metroMiddleware); + serverApp.use(middleware); let httpServer; @@ -185,7 +190,6 @@ exports.runServer = async (options: RunServerOptions) => { httpServer = Http.createServer(serverApp); } - // $FlowFixMe: The port parameter IS optional httpServer.listen(options.port, options.host, () => { options.onReady && options.onReady(httpServer); }); @@ -195,15 +199,15 @@ exports.runServer = async (options: RunServerOptions) => { // timeout of 120 seconds to respond to a request. httpServer.timeout = 0; - return new Promise((resolve, reject) => { - httpServer.on('error', error => { - reject(error); - }); - - httpServer.on('close', () => { - resolve(); - }); + httpServer.on('error', error => { + end(); }); + + httpServer.on('close', () => { + end(); + }); + + return httpServer; }; type RunBuildOptions = {|