From a6d7f98cfdd7d59b00aacfa390f1874fa18db4a5 Mon Sep 17 00:00:00 2001 From: Andrey Goncharov Date: Tue, 10 May 2022 05:13:24 -0700 Subject: [PATCH] Bundle headless plugins Summary: Current temporary limitations: all headless plugins are bundled with Flipper. Reviewed By: antonk52 Differential Revision: D36098168 fbshipit-source-id: c58abe41776157258a5c39a80a5c1a33cf3f42c5 --- .../plugin-flipper-requires.node.tsx | 34 +++++++++++++++---- .../src/plugin-flipper-requires.tsx | 7 ++++ .../src/replace-flipper-requires.tsx | 1 + .../src/transform-server-dev.tsx | 1 + .../src/transform-server-prod.tsx | 1 + .../scripts/build-flipper-server-release.tsx | 2 ++ desktop/scripts/build-utils.tsx | 31 +++++++++++++++++ .../scripts/generate-plugin-entry-points.tsx | 12 ++++--- desktop/scripts/paths.tsx | 4 +++ desktop/scripts/start-flipper-server-dev.tsx | 2 ++ 10 files changed, 84 insertions(+), 11 deletions(-) diff --git a/desktop/babel-transformer/src/__tests__/plugin-flipper-requires.node.tsx b/desktop/babel-transformer/src/__tests__/plugin-flipper-requires.node.tsx index 12802ab02..d38e89893 100644 --- a/desktop/babel-transformer/src/__tests__/plugin-flipper-requires.node.tsx +++ b/desktop/babel-transformer/src/__tests__/plugin-flipper-requires.node.tsx @@ -18,10 +18,16 @@ const babelOptions = { filename: 'index.js', }; +const babelOptionsPlugin = { + ...babelOptions, + root: 'plugins/randomPlugin/', + filename: 'plugins/randomPlugin/index.js', +}; + test('transform react requires to global object', () => { const src = 'require("react")'; const ast = parse(src); - const transformed = transformFromAstSync(ast, src, babelOptions)!.ast; + const transformed = transformFromAstSync(ast, src, babelOptionsPlugin)!.ast; const {code} = generate(transformed!); expect(code).toBe('global.React;'); }); @@ -29,7 +35,7 @@ test('transform react requires to global object', () => { test('transform react-dom requires to global object', () => { const src = 'require("react-dom")'; const ast = parse(src); - const transformed = transformFromAstSync(ast, src, babelOptions)!.ast; + const transformed = transformFromAstSync(ast, src, babelOptionsPlugin)!.ast; const {code} = generate(transformed!); expect(code).toBe('global.ReactDOM;'); }); @@ -37,7 +43,7 @@ test('transform react-dom requires to global object', () => { test('transform react-dom/client requires to global object', () => { const src = 'require("react-dom/client")'; const ast = parse(src); - const transformed = transformFromAstSync(ast, src, babelOptions)!.ast; + const transformed = transformFromAstSync(ast, src, babelOptionsPlugin)!.ast; const {code} = generate(transformed!); expect(code).toBe('global.ReactDOMClient;'); }); @@ -45,19 +51,35 @@ test('transform react-dom/client requires to global object', () => { test('transform flipper requires to global object', () => { const src = 'require("flipper")'; const ast = parse(src); - const transformed = transformFromAstSync(ast, src, babelOptions)!.ast; + const transformed = transformFromAstSync(ast, src, babelOptionsPlugin)!.ast; const {code} = generate(transformed!); expect(code).toBe('global.Flipper;'); }); +test('do NOT transform flipper requires outside of plugins folder', () => { + const src = 'require("flipper");'; + const ast = parse(src); + const transformed = transformFromAstSync(ast, src, babelOptions)!.ast; + const {code} = generate(transformed!); + expect(code).toBe('require("flipper");'); +}); + test('transform React identifier to global.React', () => { const src = 'React;'; const ast = parse(src); - const transformed = transformFromAstSync(ast, src, babelOptions)!.ast; + const transformed = transformFromAstSync(ast, src, babelOptionsPlugin)!.ast; const {code} = generate(transformed!); expect(code).toBe('global.React;'); }); +test('do NOT transform React identifier to global.React outside of plugins folder', () => { + const src = 'React;'; + const ast = parse(src); + const transformed = transformFromAstSync(ast, src, babelOptions)!.ast; + const {code} = generate(transformed!); + expect(code).toBe('React;'); +}); + test('do NOT transform local React namespace import to global.React', () => { const src = `import * as React from 'react';`; const ast = parse(src, {sourceType: 'module'}); @@ -70,7 +92,7 @@ test('throw error when requiring outside the plugin', () => { const src = 'require("../test.js")'; const ast = parse(src); expect(() => { - transformFromAstSync(ast, src, babelOptions); + transformFromAstSync(ast, src, babelOptionsPlugin); }).toThrow(); }); diff --git a/desktop/babel-transformer/src/plugin-flipper-requires.tsx b/desktop/babel-transformer/src/plugin-flipper-requires.tsx index a3eb664d7..c6ff4074a 100644 --- a/desktop/babel-transformer/src/plugin-flipper-requires.tsx +++ b/desktop/babel-transformer/src/plugin-flipper-requires.tsx @@ -16,9 +16,16 @@ import { tryReplaceGlobalReactUsage, } from './replace-flipper-requires'; +const sourceRootDir = resolve(__dirname, '../..'); +const pluginRootDir = resolve(__dirname, '../../plugin'); + // do not apply this transform for these paths const EXCLUDE_PATHS = ['relay-devtools/DevtoolsUI']; function isExcludedPath(path: string) { + // Replace requires and React for plugins, but not for the Flipper core code which can access bundled React and other Flipper packages + if (path.startsWith(sourceRootDir) && !path.startsWith(pluginRootDir)) { + return true; + } for (const epath of EXCLUDE_PATHS) { if (path.indexOf(epath) > -1) { return true; diff --git a/desktop/babel-transformer/src/replace-flipper-requires.tsx b/desktop/babel-transformer/src/replace-flipper-requires.tsx index 6807b4461..74070bbac 100644 --- a/desktop/babel-transformer/src/replace-flipper-requires.tsx +++ b/desktop/babel-transformer/src/replace-flipper-requires.tsx @@ -51,6 +51,7 @@ export function tryReplaceGlobalReactUsage(path: NodePath) { if ( path.node.name === 'React' && (path.parentPath.node as any).id !== path.node && + path.parent.type !== 'ObjectProperty' && !isReactImportIdentifier(path) ) { path.replaceWith(identifier('global.React')); diff --git a/desktop/babel-transformer/src/transform-server-dev.tsx b/desktop/babel-transformer/src/transform-server-dev.tsx index 49c77e79b..556776aa1 100644 --- a/desktop/babel-transformer/src/transform-server-dev.tsx +++ b/desktop/babel-transformer/src/transform-server-dev.tsx @@ -11,6 +11,7 @@ import {default as doTransform} from './transform'; import {default as getCacheKey} from './get-cache-key'; const presets = [ + '@babel/preset-react', [ '@babel/preset-env', { diff --git a/desktop/babel-transformer/src/transform-server-prod.tsx b/desktop/babel-transformer/src/transform-server-prod.tsx index ee05a96e6..2fc6df3bd 100644 --- a/desktop/babel-transformer/src/transform-server-prod.tsx +++ b/desktop/babel-transformer/src/transform-server-prod.tsx @@ -11,6 +11,7 @@ import {default as doTransform} from './transform'; import {default as getCacheKey} from './get-cache-key'; const presets = [ + '@babel/preset-react', [ '@babel/preset-env', { diff --git a/desktop/scripts/build-flipper-server-release.tsx b/desktop/scripts/build-flipper-server-release.tsx index e6b48bf1c..3c5957a91 100644 --- a/desktop/scripts/build-flipper-server-release.tsx +++ b/desktop/scripts/build-flipper-server-release.tsx @@ -16,6 +16,7 @@ import { genMercurialRevision, getVersionNumber, prepareDefaultPlugins, + prepareHeadlessPlugins, } from './build-utils'; import {defaultPluginsDir, distDir, serverDir, staticDir} from './paths'; import isFB from './isFB'; @@ -330,6 +331,7 @@ async function buildServerRelease() { await compileServerMain(false); await prepareDefaultPlugins(argv.channel === 'insiders'); + await prepareHeadlessPlugins(); await copyStaticResources(dir, versionNumber); await downloadIcons(path.join(dir, 'static')); await buildBrowserBundle(path.join(dir, 'static'), false); diff --git a/desktop/scripts/build-utils.tsx b/desktop/scripts/build-utils.tsx index db38e7a03..d723f85db 100644 --- a/desktop/scripts/build-utils.tsx +++ b/desktop/scripts/build-utils.tsx @@ -37,6 +37,7 @@ import { rootDir, browserUiDir, serverCoreDir, + serverCompanionDir, } from './paths'; import pFilter from 'p-filter'; import child from 'child_process'; @@ -110,6 +111,14 @@ export async function prepareDefaultPlugins(isInsidersBuild: boolean = false) { console.log('✅ Prepared default plugins.'); } +export async function prepareHeadlessPlugins() { + console.log(`⚙️ Preparing headless plugins...`); + const sourcePlugins = await getSourcePlugins(); + const headlessPlugins = sourcePlugins.filter((p) => p.headless); + await generateHeadlessPluginEntryPoints(headlessPlugins); + console.log('✅ Prepared headless plugins.'); +} + function getGeneratedIndex(pluginRequires: string) { return ` /* eslint-disable */ @@ -191,6 +200,28 @@ async function generateDefaultPluginEntryPoints( console.log('✅ Generated bundled plugin entry points.'); } +async function generateHeadlessPluginEntryPoints( + headlessPlugins: InstalledPluginDetails[], +) { + console.log( + `⚙️ Generating entry points for ${headlessPlugins.length} headless plugins...`, + ); + const headlessRequires = headlessPlugins + .map( + (x) => + ` '${x.name}': tryRequire('${x.name}', () => require('${x.name}'))`, + ) + .join(',\n'); + const generatedIndexHeadless = getGeneratedIndex(headlessRequires); + await fs.ensureDir(path.join(serverCompanionDir, 'src', 'defaultPlugins')); + await fs.writeFile( + path.join(serverCompanionDir, 'src', 'defaultPlugins', 'index.tsx'), + generatedIndexHeadless, + ); + + console.log('✅ Generated headless plugin entry points.'); +} + async function buildDefaultPlugins(defaultPlugins: InstalledPluginDetails[]) { if (process.env.FLIPPER_NO_REBUILD_PLUGINS) { console.log( diff --git a/desktop/scripts/generate-plugin-entry-points.tsx b/desktop/scripts/generate-plugin-entry-points.tsx index 52d46fd5a..79b3c0e8d 100644 --- a/desktop/scripts/generate-plugin-entry-points.tsx +++ b/desktop/scripts/generate-plugin-entry-points.tsx @@ -9,9 +9,11 @@ /* eslint-disable flipper/no-console-error-without-context */ -import {prepareDefaultPlugins} from './build-utils'; +import {prepareDefaultPlugins, prepareHeadlessPlugins} from './build-utils'; -prepareDefaultPlugins().catch((err) => { - console.error(err); - process.exit(1); -}); +Promise.all([prepareDefaultPlugins(), prepareHeadlessPlugins()]).catch( + (err) => { + console.error(err); + process.exit(1); + }, +); diff --git a/desktop/scripts/paths.tsx b/desktop/scripts/paths.tsx index 44b6da8c2..daa12d36a 100644 --- a/desktop/scripts/paths.tsx +++ b/desktop/scripts/paths.tsx @@ -15,6 +15,10 @@ 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 serverCompanionDir = path.join( + rootDir, + 'flipper-server-companion', +); export const defaultPluginsDir = path.join(staticDir, 'defaultPlugins'); export const pluginsDir = path.join(rootDir, 'plugins'); export const fbPluginsDir = path.join(pluginsDir, 'fb'); diff --git a/desktop/scripts/start-flipper-server-dev.tsx b/desktop/scripts/start-flipper-server-dev.tsx index a7a449b85..e48954809 100644 --- a/desktop/scripts/start-flipper-server-dev.tsx +++ b/desktop/scripts/start-flipper-server-dev.tsx @@ -14,6 +14,7 @@ import { compileServerMain, launchServer, prepareDefaultPlugins, + prepareHeadlessPlugins, } from './build-utils'; import Watchman from './watchman'; import isFB from './isFB'; @@ -185,6 +186,7 @@ async function startWatchChanges() { await prepareDefaultPlugins( process.env.FLIPPER_RELEASE_CHANNEL === 'insiders', ); + await prepareHeadlessPlugins(); // watch await startWatchChanges();