diff --git a/desktop/scripts/build-utils.tsx b/desktop/scripts/build-utils.tsx index a90d462d9..e854b64d9 100644 --- a/desktop/scripts/build-utils.tsx +++ b/desktop/scripts/build-utils.tsx @@ -37,6 +37,7 @@ import { import pFilter from 'p-filter'; import child from 'child_process'; import pMap from 'p-map'; +import chalk from 'chalk'; const dev = process.env.NODE_ENV !== 'production'; @@ -510,3 +511,17 @@ function defaultResolve(...rest: any[]) { ...rest, ); } + +export 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, + ); + } +} diff --git a/desktop/scripts/start-dev-server.tsx b/desktop/scripts/start-dev-server.tsx index af7bffc06..26fc1aa86 100644 --- a/desktop/scripts/start-dev-server.tsx +++ b/desktop/scripts/start-dev-server.tsx @@ -362,9 +362,6 @@ async function startWatchChanges(io: socketIo.Server) { ), ), ); - await startWatchPlugins(() => { - io.emit('refresh'); - }); } catch (err) { console.error( 'Failed to start watching for changes using Watchman, continue without hot reloading', @@ -448,6 +445,9 @@ function checkDevServer() { await startMetroServer(app, server); outputScreen(socket); await compileMain(); + await startWatchPlugins(() => { + socket.emit('refresh'); + }); if (dotenv && dotenv.parsed) { console.log('✅ Loaded env vars from .env file: ', dotenv.parsed); } diff --git a/desktop/scripts/start-flipper-server-dev.tsx b/desktop/scripts/start-flipper-server-dev.tsx index 924b64c03..21024ae4f 100644 --- a/desktop/scripts/start-flipper-server-dev.tsx +++ b/desktop/scripts/start-flipper-server-dev.tsx @@ -19,6 +19,7 @@ import Watchman from './watchman'; import isFB from './isFB'; import yargs from 'yargs'; import ensurePluginFoldersWatchable from './ensurePluginFoldersWatchable'; +import startWatchPlugins from './startWatchPlugins'; const argv = yargs .usage('yarn flipper-server [args]') @@ -149,12 +150,15 @@ async function startWatchChanges() { // For UI changes, Metro / hot module reloading / fast refresh take care of the changes await Promise.all( [ - 'pkg', 'doctor', + 'pkg-lib', 'plugin-lib', - 'flipper-server', 'flipper-common', + 'flipper-frontend-core', + 'flipper-plugin-core', + 'flipper-server-companion', 'flipper-server-core', + 'flipper-server', ].map((dir) => watchman.startWatchFiles( dir, @@ -191,4 +195,5 @@ async function startWatchChanges() { await restartServer(); // watch await startWatchChanges(); + await startWatchPlugins(); })(); diff --git a/desktop/scripts/startWatchPlugins.tsx b/desktop/scripts/startWatchPlugins.tsx index a0b2e49f2..1c3ecca9f 100644 --- a/desktop/scripts/startWatchPlugins.tsx +++ b/desktop/scripts/startWatchPlugins.tsx @@ -8,23 +8,49 @@ */ import Watchman from './watchman'; -import {getPluginSourceFolders} from 'flipper-plugin-lib'; +import {getPluginSourceFolders, isPluginDir} from 'flipper-plugin-lib'; +import path from 'path'; +import chalk from 'chalk'; +import {rebuildPlugin} from './build-utils'; export default async function startWatchPlugins( - onChanged: () => void | Promise, + onChanged?: () => void | Promise, ) { // eslint-disable-next-line no-console console.log('🕵️‍ Watching for plugin changes'); let delayedCompilation: NodeJS.Timeout | undefined; const kCompilationDelayMillis = 1000; - const onPluginChangeDetected = () => { + const onPluginChangeDetected = (root: string, files: string[]) => { if (!delayedCompilation) { - delayedCompilation = setTimeout(() => { + delayedCompilation = setTimeout(async () => { delayedCompilation = undefined; // eslint-disable-next-line no-console console.log(`🕵️‍ Detected plugin change`); - onChanged(); + 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.warn( + chalk.yellow('Failed to find a plugin root for path'), + filePathAbs, + ); + return; + } + dirPath = path.resolve(dirPath, '..'); + } + await rebuildPlugin(dirPath); + }), + ); + onChanged?.(); }, kCompilationDelayMillis); } }; @@ -38,15 +64,26 @@ export default async function startWatchPlugins( } } -async function startWatchingPluginsUsingWatchman(onChange: () => void) { +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('.', () => onChange(), { - excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'], - }); + await watchman.startWatchFiles( + '.', + ({files}) => onChange(pluginFolder, files), + { + excludes: [ + '**/__tests__/**/*', + '**/node_modules/**/*', + '**/dist/*', + '**/.*', + ], + }, + ); }), ); }