From 5cdb7c952e4d79447254c5139dd5d260757e14f2 Mon Sep 17 00:00:00 2001 From: Andrey Goncharov Date: Mon, 28 Feb 2022 03:50:34 -0800 Subject: [PATCH] Update build scripts to support bundling server add-ons Summary: Summary Reviewed By: nikoant Differential Revision: D34170565 fbshipit-source-id: be9904809bf33e85536a4c6ead0e753ef05209ff --- desktop/.eslintignore | 1 + desktop/.gitignore | 1 + desktop/flipper-common/src/PluginDetails.tsx | 3 + desktop/pkg-lib/src/runBuild.tsx | 116 +++++++++++++----- .../src/__tests__/getPluginDetails.node.tsx | 15 +++ desktop/plugin-lib/src/getPluginDetails.tsx | 4 + desktop/scripts/build-plugin.tsx | 12 +- desktop/scripts/build-utils.tsx | 63 +++++++--- desktop/scripts/paths.tsx | 1 + 9 files changed, 165 insertions(+), 51 deletions(-) diff --git a/desktop/.eslintignore b/desktop/.eslintignore index 97e108004..a5200da84 100644 --- a/desktop/.eslintignore +++ b/desktop/.eslintignore @@ -14,5 +14,6 @@ scripts/generate-changelog.js static/index.js static/defaultPlugins/* app/src/defaultPlugins/index.tsx +flipper-server-core/src/defaultPlugins/index.tsx generated flipper-server/static diff --git a/desktop/.gitignore b/desktop/.gitignore index d0a04bfba..98f3f4a19 100644 --- a/desktop/.gitignore +++ b/desktop/.gitignore @@ -6,6 +6,7 @@ node_modules/ /static/defaultPlugins/ /app/src/defaultPlugins/index.tsx /flipper-ui-browser/src/defaultPlugins/index.tsx +/flipper-server-core/src/defaultPlugins/index.tsx /coverage .env tsc-error.log diff --git a/desktop/flipper-common/src/PluginDetails.tsx b/desktop/flipper-common/src/PluginDetails.tsx index 8c459a2f3..dd009a706 100644 --- a/desktop/flipper-common/src/PluginDetails.tsx +++ b/desktop/flipper-common/src/PluginDetails.tsx @@ -13,6 +13,7 @@ export interface PluginDetails { version: string; source: string; main: string; + serverAddOnSource?: string; serverAddOn?: string; id: string; gatekeeper?: string; @@ -84,6 +85,7 @@ export interface InstalledPluginDetails extends ConcretePluginDetails { isActivatable: true; dir: string; entry: string; + serverAddOnEntry?: string; } // Describes plugin physically available for activation in Flipper. @@ -162,6 +164,7 @@ function getPluginDetailsV2(packageJson: any): PluginDetails { main: packageJson.main, serverAddOn: packageJson.serverAddOn, source: packageJson.flipperBundlerEntry, + serverAddOnSource: packageJson.flipperBundlerEntryServerAddOn, id: packageJson.id || packageJson.name, gatekeeper: packageJson.gatekeeper, icon: packageJson.icon, diff --git a/desktop/pkg-lib/src/runBuild.tsx b/desktop/pkg-lib/src/runBuild.tsx index 6253e29d4..ea6282b4c 100644 --- a/desktop/pkg-lib/src/runBuild.tsx +++ b/desktop/pkg-lib/src/runBuild.tsx @@ -34,31 +34,25 @@ async function getMetroDir() { return __dirname; } -type Options = { - sourceMapPath?: string | undefined; -}; +interface RunMetroConfig { + pluginDir: string; + baseConfig: any; + entry: string; + out: string; + dev: boolean; + sourceMapPath?: string; + babelTransformerPath: string; +} -export default async function bundlePlugin( - pluginDir: string, - dev: boolean, - options?: Options, -) { - const stat = await fs.lstat(pluginDir); - if (!stat.isDirectory()) { - throw new Error(`Plugin source ${pluginDir} is not a directory.`); - } - const packageJsonPath = path.join(pluginDir, 'package.json'); - if (!(await fs.pathExists(packageJsonPath))) { - throw new Error( - `package.json is not found in plugin source directory ${pluginDir}.`, - ); - } - const plugin = await getInstalledPluginDetails(pluginDir); - const entry = plugin.source; - const out = path.resolve(pluginDir, plugin.main); - await fs.ensureDir(path.dirname(out)); - - const baseConfig = await Metro.loadConfig(); +async function runMetro({ + pluginDir, + baseConfig, + entry, + out, + dev, + sourceMapPath, + babelTransformerPath, +}: RunMetroConfig) { const config = Object.assign({}, baseConfig, { reporter: {update: () => {}}, projectRoot: pluginDir, @@ -72,7 +66,7 @@ export default async function bundlePlugin( }, transformer: { ...baseConfig.transformer, - babelTransformerPath: require.resolve('flipper-babel-transformer'), + babelTransformerPath, minifierPath: require.resolve('metro-minify-terser'), minifierConfig: { // see: https://www.npmjs.com/package/terser @@ -98,7 +92,7 @@ export default async function bundlePlugin( ], }); const sourceMapUrl = out.replace(/\.js$/, '.map'); - const sourceMap = dev || !!options?.sourceMapPath; + const sourceMap = dev || !!sourceMapPath; await Metro.runBuild(config, { dev, sourceMap, @@ -113,11 +107,71 @@ export default async function bundlePlugin( await stripSourceMapComment(out); } if ( - options?.sourceMapPath && - path.resolve(options.sourceMapPath) !== path.resolve(sourceMapUrl) + sourceMapPath && + path.resolve(sourceMapPath) !== path.resolve(sourceMapUrl) ) { - console.log(`Moving plugin sourcemap to ${options.sourceMapPath}`); - await fs.ensureDir(path.dirname(options.sourceMapPath)); - await fs.move(sourceMapUrl, options.sourceMapPath, {overwrite: true}); + console.log(`Moving plugin sourcemap to ${sourceMapPath}`); + await fs.ensureDir(path.dirname(sourceMapPath)); + await fs.move(sourceMapUrl, sourceMapPath, {overwrite: true}); } } + +type Options = { + sourceMapPath?: string | undefined; + sourceMapPathServerAddOn?: string | undefined; +}; + +export default async function bundlePlugin( + pluginDir: string, + dev: boolean, + options?: Options, +) { + const stat = await fs.lstat(pluginDir); + if (!stat.isDirectory()) { + throw new Error(`Plugin source ${pluginDir} is not a directory.`); + } + const packageJsonPath = path.join(pluginDir, 'package.json'); + if (!(await fs.pathExists(packageJsonPath))) { + throw new Error( + `package.json is not found in plugin source directory ${pluginDir}.`, + ); + } + const plugin = await getInstalledPluginDetails(pluginDir); + const baseConfig = await Metro.loadConfig(); + + const bundleConfigs: RunMetroConfig[] = []; + + await fs.ensureDir(path.dirname(plugin.entry)); + bundleConfigs.push({ + pluginDir, + baseConfig, + entry: plugin.source, + out: plugin.entry, + dev, + sourceMapPath: options?.sourceMapPath, + babelTransformerPath: require.resolve('flipper-babel-transformer'), + }); + + if ( + plugin.serverAddOnSource && + plugin.serverAddOn && + plugin.serverAddOnEntry + ) { + await fs.ensureDir(path.dirname(plugin.serverAddOnEntry)); + bundleConfigs.push({ + pluginDir, + baseConfig, + entry: plugin.serverAddOnSource, + out: plugin.serverAddOnEntry, + dev, + sourceMapPath: options?.sourceMapPathServerAddOn, + babelTransformerPath: require.resolve( + `flipper-babel-transformer/${ + dev ? 'lib/transform-server-dev' : 'lib/transform-server-prod' + }`, + ), + }); + } + + await Promise.all(bundleConfigs.map((config) => runMetro(config))); +} diff --git a/desktop/plugin-lib/src/__tests__/getPluginDetails.node.tsx b/desktop/plugin-lib/src/__tests__/getPluginDetails.node.tsx index cb313aedf..caf7c6850 100644 --- a/desktop/plugin-lib/src/__tests__/getPluginDetails.node.tsx +++ b/desktop/plugin-lib/src/__tests__/getPluginDetails.node.tsx @@ -53,6 +53,7 @@ test('getPluginDetailsV1', async () => { "main": "dist/bundle.js", "name": "flipper-plugin-test", "pluginType": undefined, + "serverAddOnEntry": undefined, "source": "src/index.tsx", "specVersion": 1, "supportedApps": undefined, @@ -97,6 +98,8 @@ test('getPluginDetailsV2', async () => { "pluginType": undefined, "publishedDocs": undefined, "serverAddOn": undefined, + "serverAddOnEntry": undefined, + "serverAddOnSource": undefined, "source": "src/index.tsx", "specVersion": 2, "supportedApps": undefined, @@ -141,6 +144,8 @@ test('id used as title if the latter omited', async () => { "pluginType": undefined, "publishedDocs": undefined, "serverAddOn": undefined, + "serverAddOnEntry": undefined, + "serverAddOnSource": undefined, "source": "src/index.tsx", "specVersion": 2, "supportedApps": undefined, @@ -184,6 +189,8 @@ test('name without "flipper-plugin-" prefix is used as title if the latter omite "pluginType": undefined, "publishedDocs": undefined, "serverAddOn": undefined, + "serverAddOnEntry": undefined, + "serverAddOnSource": undefined, "source": "src/index.tsx", "specVersion": 2, "supportedApps": undefined, @@ -230,6 +237,8 @@ test('flipper-plugin-version is parsed', async () => { "pluginType": undefined, "publishedDocs": undefined, "serverAddOn": undefined, + "serverAddOnEntry": undefined, + "serverAddOnSource": undefined, "source": "src/index.tsx", "specVersion": 2, "supportedApps": undefined, @@ -280,6 +289,8 @@ test('plugin type and supported devices parsed', async () => { "pluginType": "device", "publishedDocs": undefined, "serverAddOn": undefined, + "serverAddOnEntry": undefined, + "serverAddOnSource": undefined, "source": "src/index.tsx", "specVersion": 2, "supportedApps": undefined, @@ -346,6 +357,8 @@ test('plugin type and supported apps parsed', async () => { "pluginType": "client", "publishedDocs": undefined, "serverAddOn": undefined, + "serverAddOnEntry": undefined, + "serverAddOnSource": undefined, "source": "src/index.tsx", "specVersion": 2, "supportedApps": Array [ @@ -435,6 +448,8 @@ test('can merge two package.json files', async () => { "setup": true, }, "serverAddOn": undefined, + "serverAddOnEntry": undefined, + "serverAddOnSource": undefined, "source": "src/index.tsx", "specVersion": 2, "supportedApps": undefined, diff --git a/desktop/plugin-lib/src/getPluginDetails.tsx b/desktop/plugin-lib/src/getPluginDetails.tsx index 91d13383a..9a58ca771 100644 --- a/desktop/plugin-lib/src/getPluginDetails.tsx +++ b/desktop/plugin-lib/src/getPluginDetails.tsx @@ -63,11 +63,15 @@ export async function getInstalledPluginDetails( `${packageJson.name}@${packageJson.version || '0.0.0'}.js`, ) : path.resolve(dir, packageJson.main); + const serverAddOnEntry = packageJson.serverAddOn + ? path.resolve(dir, packageJson.serverAddOn) + : undefined; return { ...pluginDetails, isBundled: false, isActivatable: true, dir, entry, + serverAddOnEntry, }; } diff --git a/desktop/scripts/build-plugin.tsx b/desktop/scripts/build-plugin.tsx index 9fa47162a..5eb4452de 100644 --- a/desktop/scripts/build-plugin.tsx +++ b/desktop/scripts/build-plugin.tsx @@ -59,6 +59,12 @@ const argv = yargs type: 'string', alias: 'os', }, + 'output-sourcemap-server-addon': { + description: + 'File path for the server add-on sourcemap to be written. Optional.', + type: 'string', + alias: 'os', + }, }) .help() .strict() @@ -72,9 +78,13 @@ async function buildPlugin() { const outputUnpackedArg = argv['output-unpacked']; const minFlipperVersion = argv['min-flipper-version']; const outputSourcemapArg = argv['output-sourcemap']; + const outputSourcemapServerAddOnArg = argv['output-sourcemap-server-addon']; const packageJsonPath = path.join(pluginDir, 'package.json'); const packageJsonOverridePath = path.join(pluginDir, 'fb', 'package.json'); - await runBuild(pluginDir, false, {sourceMapPath: outputSourcemapArg}); + await runBuild(pluginDir, false, { + sourceMapPath: outputSourcemapArg, + sourceMapPathServerAddOn: outputSourcemapServerAddOnArg, + }); const checksum = await computePackageChecksum(pluginDir); if (previousChecksum !== checksum && argv.version) { console.log(`Plugin changed. Packaging new version ${argv.version}...`); diff --git a/desktop/scripts/build-utils.tsx b/desktop/scripts/build-utils.tsx index 8a485459d..6ffa1f68e 100644 --- a/desktop/scripts/build-utils.tsx +++ b/desktop/scripts/build-utils.tsx @@ -36,6 +36,7 @@ import { serverDir, rootDir, browserUiDir, + serverCoreDir, } from './paths'; import pFilter from 'p-filter'; import child from 'child_process'; @@ -108,6 +109,27 @@ export async function prepareDefaultPlugins(isInsidersBuild: boolean = false) { console.log('✅ Prepared default plugins.'); } +function getGeneratedIndex(pluginRequires: string) { + return ` + /* eslint-disable */ + // THIS FILE IS AUTO-GENERATED by function "generateDefaultPluginEntryPoints" in "build-utils.ts". + + declare const require: any; + + // This function exists to make sure that if one require fails in its module initialisation, not everything fails + function tryRequire(module: string, fn: () => any): any { + try { + return fn(); + } catch (e) { + console.error(\`Could not require \${module}: \`, e) + return {}; + } + } + + export default {\n${pluginRequires}\n} as any + `; +} + async function generateDefaultPluginEntryPoints( defaultPlugins: InstalledPluginDetails[], ) { @@ -124,36 +146,20 @@ async function generateDefaultPluginEntryPoints( p.flipperSDKVersion === '0.0.0' ? version : p.flipperSDKVersion, dir: undefined, entry: undefined, + serverAddOnEntry: undefined, } as BundledPluginDetails), ); await fs.writeJSON( path.join(defaultPluginsDir, 'bundled.json'), bundledPlugins, ); - const pluginRequres = bundledPlugins + const pluginRequires = bundledPlugins .map( (x) => ` '${x.name}': tryRequire('${x.name}', () => require('${x.name}'))`, ) .join(',\n'); - const generatedIndex = ` - /* eslint-disable */ - // THIS FILE IS AUTO-GENERATED by function "generateDefaultPluginEntryPoints" in "build-utils.ts". - - declare const require: any; - - // This function exists to make sure that if one require fails in its module initialisation, not everything fails - function tryRequire(module: string, fn: () => any): any { - try { - return fn(); - } catch (e) { - console.error(\`Could not require \${module}: \`, e) - return {}; - } - } - - export default {\n${pluginRequres}\n} as any - `; + const generatedIndex = getGeneratedIndex(pluginRequires); await fs.ensureDir(path.join(appDir, 'src', 'defaultPlugins')); await fs.writeFile( path.join(appDir, 'src', 'defaultPlugins', 'index.tsx'), @@ -164,6 +170,25 @@ async function generateDefaultPluginEntryPoints( path.join(browserUiDir, 'src', 'defaultPlugins', 'index.tsx'), generatedIndex, ); + + const serverAddOns = defaultPlugins.filter( + ({serverAddOnSource}) => !!serverAddOnSource, + ); + if (serverAddOns.length) { + const serverAddOnRequires = serverAddOns + .map( + (x) => + ` '${x.name}': tryRequire('${x.serverAddOnSource}', () => require('${x.serverAddOnSource}'))`, + ) + .join(',\n'); + const generatedIndexServerAddOns = getGeneratedIndex(serverAddOnRequires); + await fs.ensureDir(path.join(serverCoreDir, 'src', 'defaultPlugins')); + await fs.writeFile( + path.join(serverCoreDir, 'src', 'defaultPlugins', 'index.tsx'), + generatedIndexServerAddOns, + ); + } + console.log('✅ Generated bundled plugin entry points.'); } diff --git a/desktop/scripts/paths.tsx b/desktop/scripts/paths.tsx index c0fc8c4ce..44b6da8c2 100644 --- a/desktop/scripts/paths.tsx +++ b/desktop/scripts/paths.tsx @@ -14,6 +14,7 @@ export const appDir = path.join(rootDir, 'app'); export const browserUiDir = path.join(rootDir, 'flipper-ui-browser'); export const staticDir = path.join(rootDir, 'static'); export const serverDir = path.join(rootDir, 'flipper-server'); +export const serverCoreDir = path.join(rootDir, 'flipper-server-core'); export const defaultPluginsDir = path.join(staticDir, 'defaultPlugins'); export const pluginsDir = path.join(rootDir, 'plugins'); export const fbPluginsDir = path.join(pluginsDir, 'fb');