From 857b9816a08f8578cecd0691c67a7e8b13834260 Mon Sep 17 00:00:00 2001 From: Anton Nikolaev Date: Tue, 7 Jan 2020 06:54:39 -0800 Subject: [PATCH] Launch without plugin hot reload in case plugin change detection failed to start Summary: Launch without plugin hot reload in case plugin change detection failed to start Reviewed By: mweststrate Differential Revision: D19264418 fbshipit-source-id: 089f818b9101d924c504c7d96f71ebca11c9422f --- .travis.yml | 8 ----- scripts/build-utils.js | 2 +- scripts/start-dev-server.js | 34 ++++++++++++------ static/compilePlugins.js | 72 ++++++++++++++++++++++--------------- static/watchman.js | 19 ++++++---- 5 files changed, 79 insertions(+), 56 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6b646b1da..f4e34d76d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,14 +32,6 @@ matrix: node_js: - "11" - before_install: - - cd .. - - git clone https://github.com/facebook/watchman.git - - cd watchman - - git checkout v4.9.0 - - ./autogen.sh && ./configure && make && sudo make install - - cd ../flipper - install: - yarn diff --git a/scripts/build-utils.js b/scripts/build-utils.js index e2953ba1d..60b6fab6d 100644 --- a/scripts/build-utils.js +++ b/scripts/build-utils.js @@ -29,7 +29,7 @@ function compileDefaultPlugins(defaultPluginDir, skipAll = false) { path.join(__dirname, '..', 'src', 'fb', 'plugins'), ], defaultPluginDir, - {force: true, failSilently: false}, + {force: true, failSilently: false, recompileOnChanges: false}, ) .then(defaultPlugins => fs.writeFileSync( diff --git a/scripts/start-dev-server.js b/scripts/start-dev-server.js index 32cf74b27..1bb968808 100644 --- a/scripts/start-dev-server.js +++ b/scripts/start-dev-server.js @@ -127,17 +127,29 @@ async function addWebsocket(server) { // refresh the app on changes to the src folder // this can be removed once metroServer notifies us about file changes - const watchman = new Watchman(path.resolve(__dirname, '..', 'src')); - await watchman.initialize(); - await watchman.startWatchFiles( - '/', - resp => { - io.emit('refresh'); - }, - { - excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'], - }, - ); + try { + const watchman = new Watchman(path.resolve(__dirname, '..', 'src')); + await watchman.initialize(); + await watchman.startWatchFiles( + '', + () => { + io.emit('refresh'); + }, + { + excludes: [ + '**/__tests__/**/*', + '**/node_modules/**/*', + '**/.*', + 'plugins/**/*', // plugin changes are tracked separately, so exlcuding them here to avoid double reloading. + ], + }, + ); + } catch (err) { + console.error( + 'Failed to start watching for changes using Watchman, continue without hot reloading', + err, + ); + } return io; } diff --git a/static/compilePlugins.js b/static/compilePlugins.js index bf219aecc..3057ac8fe 100644 --- a/static/compilePlugins.js +++ b/static/compilePlugins.js @@ -21,6 +21,7 @@ const Watchman = require('./watchman'); const DEFAULT_COMPILE_OPTIONS = { force: false, failSilently: true, + recompileOnChanges: true, }; module.exports = async ( @@ -29,11 +30,14 @@ module.exports = async ( pluginCache, options = DEFAULT_COMPILE_OPTIONS, ) => { + options = Object.assign({}, DEFAULT_COMPILE_OPTIONS, options); const plugins = pluginEntryPoints(pluginPaths); if (!fs.existsSync(pluginCache)) { fs.mkdirSync(pluginCache); } - watchChanges(plugins, reloadCallback, pluginCache, options); + if (options.recompileOnChanges) { + await startWatchChanges(plugins, reloadCallback, pluginCache, options); + } const compilations = pMap( Object.values(plugins), plugin => { @@ -48,7 +52,24 @@ module.exports = async ( return dynamicPlugins; }; -async function watchChanges( +async function startWatchingPluginsUsingWatchman(plugins, onPluginChanged) { + const rootDir = path.resolve(__dirname, '..'); + const watchman = new Watchman(rootDir); + await watchman.initialize(); + await Promise.all( + plugins.map(plugin => + watchman.startWatchFiles( + path.relative(rootDir, plugin.rootDir), + () => onPluginChanged(plugin), + { + excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'], + }, + ), + ), + ); +} + +async function startWatchChanges( plugins, reloadCallback, pluginCache, @@ -59,38 +80,31 @@ async function watchChanges( const delayedCompilation = {}; const kCompilationDelayMillis = 1000; - const rootDir = path.resolve(__dirname, '..'); - const watchman = new Watchman(rootDir); - await watchman.initialize(); - Object.values(plugins) + const onPluginChanged = plugin => { + if (!delayedCompilation[plugin.name]) { + delayedCompilation[plugin.name] = setTimeout(() => { + delayedCompilation[plugin.name] = null; + // eslint-disable-next-line no-console + console.log(`🕵️‍ Detected changes in ${plugin.name}`); + const watchOptions = Object.assign(options, {force: true}); + compilePlugin(plugin, pluginCache, watchOptions).then(reloadCallback); + }, kCompilationDelayMillis); + } + }; + const filteredPlugins = Object.values(plugins) // no hot reloading for plugins in .flipper folder. This is to prevent // Flipper from reloading, while we are doing changes on thirdparty plugins. .filter( plugin => !plugin.rootDir.startsWith(path.join(HOME_DIR, '.flipper')), - ) - .map(plugin => - watchman.startWatchFiles( - path.relative(rootDir, plugin.rootDir), - resp => { - // only recompile for changes in not hidden files. Watchman might create - // a file called .watchman-cookie - if (!delayedCompilation[plugin.name]) { - delayedCompilation[plugin.name] = setTimeout(() => { - delayedCompilation[plugin.name] = null; - // eslint-disable-next-line no-console - console.log(`🕵️‍ Detected changes in ${plugin.name}`); - const watchOptions = Object.assign(options, {force: true}); - compilePlugin(plugin, pluginCache, watchOptions).then( - reloadCallback, - ); - }, kCompilationDelayMillis); - } - }, - { - excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'], - }, - ), ); + try { + await startWatchingPluginsUsingWatchman(filteredPlugins, onPluginChanged); + } catch (err) { + console.error( + 'Failed to start watching plugin files using Watchman, continue without hot reloading', + err, + ); + } } function hash(string) { let hash = 0; diff --git a/static/watchman.js b/static/watchman.js index 7f01d7f30..cbd9bb3d1 100644 --- a/static/watchman.js +++ b/static/watchman.js @@ -16,28 +16,33 @@ module.exports = class Watchman { this.rootDir = rootDir; } - async initialize() { + initialize() { if (this.client) { return; } this.client = new watchman.Client(); this.client.setMaxListeners(250); return new Promise((resolve, reject) => { + const onError = err => { + this.client.removeAllListeners('error'); + reject(err); + this.client.end(); + delete this.client; + }; + this.client.once('error', onError); this.client.capabilityCheck( {optional: [], required: ['relative_root']}, error => { if (error) { - this.client.end(); - delete this.client; - return reject(error); + onError(error); + return; } this.client.command( ['watch-project', this.rootDir], (error, resp) => { if (error) { - this.client.end(); - delete this.client; - return reject(error); + onError(error); + return; } if ('warning' in resp) { console.warn(resp.warning);