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
This commit is contained in:
Anton Nikolaev
2020-01-07 06:54:39 -08:00
committed by Facebook Github Bot
parent 205e04fe6c
commit 857b9816a0
5 changed files with 79 additions and 56 deletions

View File

@@ -32,14 +32,6 @@ matrix:
node_js: node_js:
- "11" - "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: install:
- yarn - yarn

View File

@@ -29,7 +29,7 @@ function compileDefaultPlugins(defaultPluginDir, skipAll = false) {
path.join(__dirname, '..', 'src', 'fb', 'plugins'), path.join(__dirname, '..', 'src', 'fb', 'plugins'),
], ],
defaultPluginDir, defaultPluginDir,
{force: true, failSilently: false}, {force: true, failSilently: false, recompileOnChanges: false},
) )
.then(defaultPlugins => .then(defaultPlugins =>
fs.writeFileSync( fs.writeFileSync(

View File

@@ -127,17 +127,29 @@ async function addWebsocket(server) {
// refresh the app on changes to the src folder // refresh the app on changes to the src folder
// this can be removed once metroServer notifies us about file changes // this can be removed once metroServer notifies us about file changes
try {
const watchman = new Watchman(path.resolve(__dirname, '..', 'src')); const watchman = new Watchman(path.resolve(__dirname, '..', 'src'));
await watchman.initialize(); await watchman.initialize();
await watchman.startWatchFiles( await watchman.startWatchFiles(
'/', '',
resp => { () => {
io.emit('refresh'); io.emit('refresh');
}, },
{ {
excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'], 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; return io;
} }

View File

@@ -21,6 +21,7 @@ const Watchman = require('./watchman');
const DEFAULT_COMPILE_OPTIONS = { const DEFAULT_COMPILE_OPTIONS = {
force: false, force: false,
failSilently: true, failSilently: true,
recompileOnChanges: true,
}; };
module.exports = async ( module.exports = async (
@@ -29,11 +30,14 @@ module.exports = async (
pluginCache, pluginCache,
options = DEFAULT_COMPILE_OPTIONS, options = DEFAULT_COMPILE_OPTIONS,
) => { ) => {
options = Object.assign({}, DEFAULT_COMPILE_OPTIONS, options);
const plugins = pluginEntryPoints(pluginPaths); const plugins = pluginEntryPoints(pluginPaths);
if (!fs.existsSync(pluginCache)) { if (!fs.existsSync(pluginCache)) {
fs.mkdirSync(pluginCache); fs.mkdirSync(pluginCache);
} }
watchChanges(plugins, reloadCallback, pluginCache, options); if (options.recompileOnChanges) {
await startWatchChanges(plugins, reloadCallback, pluginCache, options);
}
const compilations = pMap( const compilations = pMap(
Object.values(plugins), Object.values(plugins),
plugin => { plugin => {
@@ -48,7 +52,24 @@ module.exports = async (
return dynamicPlugins; 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, plugins,
reloadCallback, reloadCallback,
pluginCache, pluginCache,
@@ -59,38 +80,31 @@ async function watchChanges(
const delayedCompilation = {}; const delayedCompilation = {};
const kCompilationDelayMillis = 1000; const kCompilationDelayMillis = 1000;
const rootDir = path.resolve(__dirname, '..'); const onPluginChanged = plugin => {
const watchman = new Watchman(rootDir);
await watchman.initialize();
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]) { if (!delayedCompilation[plugin.name]) {
delayedCompilation[plugin.name] = setTimeout(() => { delayedCompilation[plugin.name] = setTimeout(() => {
delayedCompilation[plugin.name] = null; delayedCompilation[plugin.name] = null;
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(`🕵️‍ Detected changes in ${plugin.name}`); console.log(`🕵️‍ Detected changes in ${plugin.name}`);
const watchOptions = Object.assign(options, {force: true}); const watchOptions = Object.assign(options, {force: true});
compilePlugin(plugin, pluginCache, watchOptions).then( compilePlugin(plugin, pluginCache, watchOptions).then(reloadCallback);
reloadCallback,
);
}, kCompilationDelayMillis); }, kCompilationDelayMillis);
} }
}, };
{ const filteredPlugins = Object.values(plugins)
excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'], // 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')),
); );
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) { function hash(string) {
let hash = 0; let hash = 0;

View File

@@ -16,28 +16,33 @@ module.exports = class Watchman {
this.rootDir = rootDir; this.rootDir = rootDir;
} }
async initialize() { initialize() {
if (this.client) { if (this.client) {
return; return;
} }
this.client = new watchman.Client(); this.client = new watchman.Client();
this.client.setMaxListeners(250); this.client.setMaxListeners(250);
return new Promise((resolve, reject) => { 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( this.client.capabilityCheck(
{optional: [], required: ['relative_root']}, {optional: [], required: ['relative_root']},
error => { error => {
if (error) { if (error) {
this.client.end(); onError(error);
delete this.client; return;
return reject(error);
} }
this.client.command( this.client.command(
['watch-project', this.rootDir], ['watch-project', this.rootDir],
(error, resp) => { (error, resp) => {
if (error) { if (error) {
this.client.end(); onError(error);
delete this.client; return;
return reject(error);
} }
if ('warning' in resp) { if ('warning' in resp) {
console.warn(resp.warning); console.warn(resp.warning);