Files
flipper/desktop/pkg-lib/src/startWatchPlugins.tsx
Andrey Goncharov fd811db12b Rebuild all plugins if a shared lib changed
Summary: Some plugins import from shared directories. These directories are not plugins themselves, therefore the current plugin root searching mechanism does nto work for them. To support plugin reloading for this scenario, we start re-building all plugins if we fail to find a plugin root.

Reviewed By: lblasa

Differential Revision: D39693820

fbshipit-source-id: 33dd7de4121bd5665a39b0ea96adce4603dc7df0
2022-09-22 04:17:24 -07:00

130 lines
4.0 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import Watchman from './watchman';
import {
getInstalledPluginDetails,
getPluginSourceFolders,
isPluginDir,
} from 'flipper-plugin-lib';
import path from 'path';
import chalk from 'chalk';
import runBuild from './runBuild';
import {InstalledPluginDetails} from 'flipper-common';
import {getDefaultPlugins} from './getDefaultPlugins';
import {buildDefaultPlugins} from './buildDefaultPlugins';
async function rebuildPlugin(pluginPath: string) {
try {
await runBuild(pluginPath, true);
console.info(chalk.green('Rebuilt plugin'), pluginPath);
} catch (e) {
console.error(
chalk.red(
'Failed to compile a plugin, waiting for additional changes...',
),
e,
);
}
}
export default async function startWatchPlugins(
isInsidersBuild: boolean,
onChanged?: (
changedPlugins: InstalledPluginDetails[],
) => void | Promise<void>,
) {
// eslint-disable-next-line no-console
console.log('🕵️‍ Watching for plugin changes');
let delayedCompilation: NodeJS.Timeout | undefined;
const kCompilationDelayMillis = 1000;
const onPluginChangeDetected = (root: string, files: string[]) => {
if (!delayedCompilation) {
delayedCompilation = setTimeout(async () => {
delayedCompilation = undefined;
// eslint-disable-next-line no-console
console.log(`🕵️‍ Detected plugin change`);
try {
const changedDirs = await Promise.all(
// https://facebook.github.io/watchman/docs/nodejs.html#subscribing-to-changes
files.map(async (file: string) => {
const filePathAbs = path.resolve(root, file);
let dirPath = path.dirname(filePathAbs);
while (
// Stop when we reach plugin root
!(await isPluginDir(dirPath))
) {
const relative = path.relative(root, dirPath);
// Stop when we reach desktop/plugins folder
if (!relative || relative.startsWith('..')) {
console.info(
chalk.yellow(
'Failed to find a plugin root for path. Rebuilding all plugins',
),
filePathAbs,
);
throw new Error('REBUILD_ALL');
}
dirPath = path.resolve(dirPath, '..');
}
await rebuildPlugin(dirPath);
return dirPath;
}),
);
const changedPlugins = await Promise.all(
changedDirs.map((dirPath) => getInstalledPluginDetails(dirPath)),
);
onChanged?.(changedPlugins);
} catch (e) {
if (e instanceof Error && e.message === 'REBUILD_ALL') {
const defaultPlugins = await getDefaultPlugins(isInsidersBuild);
await buildDefaultPlugins(defaultPlugins, true);
onChanged?.(defaultPlugins);
return;
}
throw e;
}
}, kCompilationDelayMillis);
}
};
try {
await startWatchingPluginsUsingWatchman(onPluginChangeDetected);
} catch (err) {
console.error(
'Failed to start watching plugin files using Watchman, continue without hot reloading',
err,
);
}
}
async function startWatchingPluginsUsingWatchman(
onChange: (root: string, files: string[]) => void,
) {
const pluginFolders = await getPluginSourceFolders();
await Promise.all(
pluginFolders.map(async (pluginFolder) => {
const watchman = new Watchman(pluginFolder);
await watchman.initialize();
await watchman.startWatchFiles(
'.',
({files}) => onChange(pluginFolder, files),
{
excludes: [
'**/__tests__/**/*',
'**/node_modules/**/*',
'**/dist/*',
'**/.*',
],
},
);
}),
);
}