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
This commit is contained in:
Andrey Goncharov
2022-09-22 04:17:24 -07:00
committed by Facebook GitHub Bot
parent 6889446bc5
commit fd811db12b
9 changed files with 165 additions and 115 deletions

View File

@@ -151,16 +151,19 @@ export async function attachDevServer(
next(); next();
}); });
await startWatchPlugins((changedPlugins: InstalledPluginDetails[]) => { await startWatchPlugins(
socket.clients.forEach((client) => { process.env.FLIPPER_RELEASE_CHANNEL === 'insiders',
client.send( (changedPlugins: InstalledPluginDetails[]) => {
JSON.stringify({ socket.clients.forEach((client) => {
event: 'plugins-source-updated', client.send(
payload: changedPlugins, JSON.stringify({
}), event: 'plugins-source-updated',
); payload: changedPlugins,
}); }),
}); );
});
},
);
console.log('DEV webserver started.'); console.log('DEV webserver started.');
} }

View File

@@ -18,7 +18,8 @@
"metro": "^0.70.2", "metro": "^0.70.2",
"metro-cache": "^0.70.2", "metro-cache": "^0.70.2",
"metro-minify-terser": "^0.70.2", "metro-minify-terser": "^0.70.2",
"npm-packlist": "^4.0.0" "npm-packlist": "^4.0.0",
"p-map": "^4"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^9.0.13", "@types/fs-extra": "^9.0.13",

View File

@@ -0,0 +1,54 @@
/**
* 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 {InstalledPluginDetails} from 'flipper-common';
import pMap from 'p-map';
import path from 'path';
import fs from 'fs-extra';
import runBuild from './runBuild';
const defaultPluginsDir = path.join(__dirname, '../../static/defaultPlugins');
export async function buildDefaultPlugins(
defaultPlugins: InstalledPluginDetails[],
dev: boolean,
) {
if (process.env.FLIPPER_NO_REBUILD_PLUGINS) {
console.log(
`⚙️ Including ${
defaultPlugins.length
} plugins into the default plugins list. Skipping rebuilding because "no-rebuild-plugins" option provided. List of default plugins: ${defaultPlugins
.map((p) => p.id)
.join(', ')}`,
);
}
await pMap(
defaultPlugins,
async function (plugin) {
try {
if (!process.env.FLIPPER_NO_REBUILD_PLUGINS) {
console.log(
`⚙️ Building plugin ${plugin.id} to include it into the default plugins list...`,
);
await runBuild(plugin.dir, dev);
}
await fs.ensureSymlink(
plugin.dir,
path.join(defaultPluginsDir, plugin.name),
'junction',
);
} catch (err) {
console.error(`✖ Failed to build plugin ${plugin.id}`, err);
}
},
{
concurrency: 16,
},
);
}

View File

@@ -0,0 +1,43 @@
/**
* 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 {getSourcePlugins} from 'flipper-plugin-lib';
// For insiders builds we bundle top 5 popular device plugins,
// plus top 10 popular "universal" plugins enabled by more than 100 users.
const hardcodedPlugins = new Set<string>([
// Popular device plugins
'DeviceLogs',
'CrashReporter',
'MobileBuilds',
'Hermesdebuggerrn',
'React',
// Popular client plugins
'Inspector',
'Network',
'AnalyticsLogging',
'GraphQL',
'UIPerf',
'MobileConfig',
'Databases',
'FunnelLogger',
'Navigation',
'Fresco',
'Preferences',
]);
export const getDefaultPlugins = async (isInsidersBuild: boolean) => {
const sourcePlugins = await getSourcePlugins();
const defaultPlugins = sourcePlugins
// we only include headless plugins and a predefined set of regular plugins into insiders release
.filter(
(p) => !isInsidersBuild || hardcodedPlugins.has(p.id) || p.headless,
);
return defaultPlugins;
};

View File

@@ -13,3 +13,5 @@ export {default as computePackageChecksum} from './computePackageChecksum';
export {default as stripSourceMapComment} from './stripSourceMap'; export {default as stripSourceMapComment} from './stripSourceMap';
export {default as startWatchPlugins} from './startWatchPlugins'; export {default as startWatchPlugins} from './startWatchPlugins';
export {default as Watchman} from './watchman'; export {default as Watchman} from './watchman';
export {buildDefaultPlugins} from './buildDefaultPlugins';
export {getDefaultPlugins} from './getDefaultPlugins';

View File

@@ -17,6 +17,8 @@ import path from 'path';
import chalk from 'chalk'; import chalk from 'chalk';
import runBuild from './runBuild'; import runBuild from './runBuild';
import {InstalledPluginDetails} from 'flipper-common'; import {InstalledPluginDetails} from 'flipper-common';
import {getDefaultPlugins} from './getDefaultPlugins';
import {buildDefaultPlugins} from './buildDefaultPlugins';
async function rebuildPlugin(pluginPath: string) { async function rebuildPlugin(pluginPath: string) {
try { try {
@@ -33,6 +35,7 @@ async function rebuildPlugin(pluginPath: string) {
} }
export default async function startWatchPlugins( export default async function startWatchPlugins(
isInsidersBuild: boolean,
onChanged?: ( onChanged?: (
changedPlugins: InstalledPluginDetails[], changedPlugins: InstalledPluginDetails[],
) => void | Promise<void>, ) => void | Promise<void>,
@@ -48,36 +51,46 @@ export default async function startWatchPlugins(
delayedCompilation = undefined; delayedCompilation = undefined;
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(`🕵️‍ Detected plugin change`); console.log(`🕵️‍ Detected plugin change`);
const changedDirs = await Promise.all( try {
// https://facebook.github.io/watchman/docs/nodejs.html#subscribing-to-changes const changedDirs = await Promise.all(
files.map(async (file: string) => { // https://facebook.github.io/watchman/docs/nodejs.html#subscribing-to-changes
const filePathAbs = path.resolve(root, file); files.map(async (file: string) => {
let dirPath = path.dirname(filePathAbs); const filePathAbs = path.resolve(root, file);
while ( let dirPath = path.dirname(filePathAbs);
// Stop when we reach plugin root while (
!(await isPluginDir(dirPath)) // Stop when we reach plugin root
) { !(await isPluginDir(dirPath))
const relative = path.relative(root, dirPath); ) {
// Stop when we reach desktop/plugins folder const relative = path.relative(root, dirPath);
if (!relative || relative.startsWith('..')) { // Stop when we reach desktop/plugins folder
console.warn( if (!relative || relative.startsWith('..')) {
chalk.yellow('Failed to find a plugin root for path'), console.info(
filePathAbs, chalk.yellow(
); 'Failed to find a plugin root for path. Rebuilding all plugins',
return; ),
filePathAbs,
);
throw new Error('REBUILD_ALL');
}
dirPath = path.resolve(dirPath, '..');
} }
dirPath = path.resolve(dirPath, '..'); await rebuildPlugin(dirPath);
} return dirPath;
await rebuildPlugin(dirPath); }),
return dirPath; );
}), const changedPlugins = await Promise.all(
); changedDirs.map((dirPath) => getInstalledPluginDetails(dirPath)),
const changedPlugins = await Promise.all( );
changedDirs onChanged?.(changedPlugins);
.filter((dirPath): dirPath is string => !!dirPath) } catch (e) {
.map((dirPath) => getInstalledPluginDetails(dirPath)), if (e instanceof Error && e.message === 'REBUILD_ALL') {
); const defaultPlugins = await getDefaultPlugins(isInsidersBuild);
onChanged?.(changedPlugins); await buildDefaultPlugins(defaultPlugins, true);
onChanged?.(defaultPlugins);
return;
}
throw e;
}
}, kCompilationDelayMillis); }, kCompilationDelayMillis);
} }
}; };

View File

@@ -18,13 +18,13 @@ import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import {spawn, exec} from 'promisify-child-process'; import {spawn, exec} from 'promisify-child-process';
import { import {
buildDefaultPlugins,
getDefaultPlugins,
getWatchFolders, getWatchFolders,
runBuild,
stripSourceMapComment, stripSourceMapComment,
} from 'flipper-pkg-lib'; } from 'flipper-pkg-lib';
import getAppWatchFolders from './get-app-watch-folders'; import getAppWatchFolders from './get-app-watch-folders';
import {getSourcePlugins, getPluginSourceFolders} from 'flipper-plugin-lib'; import {getPluginSourceFolders} from 'flipper-plugin-lib';
import type {InstalledPluginDetails} from 'flipper-common';
import { import {
appDir, appDir,
staticDir, staticDir,
@@ -36,34 +36,9 @@ import {
} from './paths'; } from './paths';
import pFilter from 'p-filter'; import pFilter from 'p-filter';
import child from 'child_process'; import child from 'child_process';
import pMap from 'p-map';
import chalk from 'chalk';
const dev = process.env.NODE_ENV !== 'production'; const dev = process.env.NODE_ENV !== 'production';
// For insiders builds we bundle top 5 popular device plugins,
// plus top 10 popular "universal" plugins enabled by more than 100 users.
const hardcodedPlugins = new Set<string>([
// Popular device plugins
'DeviceLogs',
'CrashReporter',
'MobileBuilds',
'Hermesdebuggerrn',
'React',
// Popular client plugins
'Inspector',
'Network',
'AnalyticsLogging',
'GraphQL',
'UIPerf',
'MobileConfig',
'Databases',
'FunnelLogger',
'Navigation',
'Fresco',
'Preferences',
]);
export function die(err: Error) { export function die(err: Error) {
console.error('Script termnated.', err); console.error('Script termnated.', err);
process.exit(1); process.exit(1);
@@ -87,52 +62,12 @@ export async function prepareDefaultPlugins(isInsidersBuild: boolean = false) {
}); });
console.log('✅ Copied the provided default plugins dir.'); console.log('✅ Copied the provided default plugins dir.');
} else { } else {
const sourcePlugins = await getSourcePlugins(); const defaultPlugins = await getDefaultPlugins(isInsidersBuild);
const defaultPlugins = sourcePlugins await buildDefaultPlugins(defaultPlugins, dev);
// we only include headless plugins and a predefined set of regular plugins into insiders release
.filter(
(p) => !isInsidersBuild || hardcodedPlugins.has(p.id) || p.headless,
);
await buildDefaultPlugins(defaultPlugins);
} }
console.log('✅ Prepared default plugins.'); console.log('✅ Prepared default plugins.');
} }
async function buildDefaultPlugins(defaultPlugins: InstalledPluginDetails[]) {
if (process.env.FLIPPER_NO_REBUILD_PLUGINS) {
console.log(
`⚙️ Including ${
defaultPlugins.length
} plugins into the default plugins list. Skipping rebuilding because "no-rebuild-plugins" option provided. List of default plugins: ${defaultPlugins
.map((p) => p.id)
.join(', ')}`,
);
}
await pMap(
defaultPlugins,
async function (plugin) {
try {
if (!process.env.FLIPPER_NO_REBUILD_PLUGINS) {
console.log(
`⚙️ Building plugin ${plugin.id} to include it into the default plugins list...`,
);
await runBuild(plugin.dir, dev);
}
await fs.ensureSymlink(
plugin.dir,
path.join(defaultPluginsDir, plugin.name),
'junction',
);
} catch (err) {
console.error(`✖ Failed to build plugin ${plugin.id}`, err);
}
},
{
concurrency: 16,
},
);
}
const minifierConfig = { const minifierConfig = {
minifierPath: require.resolve('metro-minify-terser'), minifierPath: require.resolve('metro-minify-terser'),
minifierConfig: { minifierConfig: {

View File

@@ -403,10 +403,9 @@ function checkDevServer() {
} }
(async () => { (async () => {
const isInsidersBuild = process.env.FLIPPER_RELEASE_CHANNEL === 'insiders';
checkDevServer(); checkDevServer();
await prepareDefaultPlugins( await prepareDefaultPlugins(isInsidersBuild);
process.env.FLIPPER_RELEASE_CHANNEL === 'insiders',
);
await ensurePluginFoldersWatchable(); await ensurePluginFoldersWatchable();
const port = await detect(DEFAULT_PORT); const port = await detect(DEFAULT_PORT);
const {app, server} = await startAssetServer(port); const {app, server} = await startAssetServer(port);
@@ -414,7 +413,7 @@ function checkDevServer() {
await startMetroServer(app, server); await startMetroServer(app, server);
outputScreen(socket); outputScreen(socket);
await compileMain(); await compileMain();
await startWatchPlugins((changedPlugins) => { await startWatchPlugins(isInsidersBuild, (changedPlugins) => {
socket.emit('plugins-source-updated', changedPlugins); socket.emit('plugins-source-updated', changedPlugins);
}); });
if (dotenv && dotenv.parsed) { if (dotenv && dotenv.parsed) {

View File

@@ -11293,7 +11293,7 @@ p-map@^2.0.0:
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
p-map@^4.0.0: p-map@^4, p-map@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==