From 3639feef61a749dbd135ba7136540263c2fa91be Mon Sep 17 00:00:00 2001 From: Andrey Goncharov Date: Thu, 15 Sep 2022 10:02:19 -0700 Subject: [PATCH] Watch and rebuild plugins Summary: Now, once we build all plugins before we start Flipper, we need to rebuild some of them when they change. Previously, it was handled by Metro when we included plugins int he bundle, but we no longer include them in the bundle. Reviewed By: lblasa Differential Revision: D39510213 fbshipit-source-id: a352d78946f844a25d9127ac09c26e43e6739ca9 --- desktop/scripts/build-utils.tsx | 15 ++++++ desktop/scripts/start-dev-server.tsx | 6 +-- desktop/scripts/start-flipper-server-dev.tsx | 9 +++- desktop/scripts/startWatchPlugins.tsx | 55 ++++++++++++++++---- 4 files changed, 71 insertions(+), 14 deletions(-) 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/*', + '**/.*', + ], + }, + ); }), ); }