Update build scripts to support bundling server add-ons

Summary: Summary

Reviewed By: nikoant

Differential Revision: D34170565

fbshipit-source-id: be9904809bf33e85536a4c6ead0e753ef05209ff
This commit is contained in:
Andrey Goncharov
2022-02-28 03:50:34 -08:00
committed by Facebook GitHub Bot
parent 47dd675dc8
commit 5cdb7c952e
9 changed files with 165 additions and 51 deletions

View File

@@ -14,5 +14,6 @@ scripts/generate-changelog.js
static/index.js static/index.js
static/defaultPlugins/* static/defaultPlugins/*
app/src/defaultPlugins/index.tsx app/src/defaultPlugins/index.tsx
flipper-server-core/src/defaultPlugins/index.tsx
generated generated
flipper-server/static flipper-server/static

1
desktop/.gitignore vendored
View File

@@ -6,6 +6,7 @@ node_modules/
/static/defaultPlugins/ /static/defaultPlugins/
/app/src/defaultPlugins/index.tsx /app/src/defaultPlugins/index.tsx
/flipper-ui-browser/src/defaultPlugins/index.tsx /flipper-ui-browser/src/defaultPlugins/index.tsx
/flipper-server-core/src/defaultPlugins/index.tsx
/coverage /coverage
.env .env
tsc-error.log tsc-error.log

View File

@@ -13,6 +13,7 @@ export interface PluginDetails {
version: string; version: string;
source: string; source: string;
main: string; main: string;
serverAddOnSource?: string;
serverAddOn?: string; serverAddOn?: string;
id: string; id: string;
gatekeeper?: string; gatekeeper?: string;
@@ -84,6 +85,7 @@ export interface InstalledPluginDetails extends ConcretePluginDetails {
isActivatable: true; isActivatable: true;
dir: string; dir: string;
entry: string; entry: string;
serverAddOnEntry?: string;
} }
// Describes plugin physically available for activation in Flipper. // Describes plugin physically available for activation in Flipper.
@@ -162,6 +164,7 @@ function getPluginDetailsV2(packageJson: any): PluginDetails {
main: packageJson.main, main: packageJson.main,
serverAddOn: packageJson.serverAddOn, serverAddOn: packageJson.serverAddOn,
source: packageJson.flipperBundlerEntry, source: packageJson.flipperBundlerEntry,
serverAddOnSource: packageJson.flipperBundlerEntryServerAddOn,
id: packageJson.id || packageJson.name, id: packageJson.id || packageJson.name,
gatekeeper: packageJson.gatekeeper, gatekeeper: packageJson.gatekeeper,
icon: packageJson.icon, icon: packageJson.icon,

View File

@@ -34,31 +34,25 @@ async function getMetroDir() {
return __dirname; return __dirname;
} }
type Options = { interface RunMetroConfig {
sourceMapPath?: string | undefined; pluginDir: string;
}; baseConfig: any;
entry: string;
export default async function bundlePlugin( out: string;
pluginDir: string, dev: boolean;
dev: boolean, sourceMapPath?: string;
options?: Options, babelTransformerPath: string;
) {
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, { const config = Object.assign({}, baseConfig, {
reporter: {update: () => {}}, reporter: {update: () => {}},
projectRoot: pluginDir, projectRoot: pluginDir,
@@ -72,7 +66,7 @@ export default async function bundlePlugin(
}, },
transformer: { transformer: {
...baseConfig.transformer, ...baseConfig.transformer,
babelTransformerPath: require.resolve('flipper-babel-transformer'), babelTransformerPath,
minifierPath: require.resolve('metro-minify-terser'), minifierPath: require.resolve('metro-minify-terser'),
minifierConfig: { minifierConfig: {
// see: https://www.npmjs.com/package/terser // see: https://www.npmjs.com/package/terser
@@ -98,7 +92,7 @@ export default async function bundlePlugin(
], ],
}); });
const sourceMapUrl = out.replace(/\.js$/, '.map'); const sourceMapUrl = out.replace(/\.js$/, '.map');
const sourceMap = dev || !!options?.sourceMapPath; const sourceMap = dev || !!sourceMapPath;
await Metro.runBuild(config, { await Metro.runBuild(config, {
dev, dev,
sourceMap, sourceMap,
@@ -113,11 +107,71 @@ export default async function bundlePlugin(
await stripSourceMapComment(out); await stripSourceMapComment(out);
} }
if ( if (
options?.sourceMapPath && sourceMapPath &&
path.resolve(options.sourceMapPath) !== path.resolve(sourceMapUrl) path.resolve(sourceMapPath) !== path.resolve(sourceMapUrl)
) { ) {
console.log(`Moving plugin sourcemap to ${options.sourceMapPath}`); console.log(`Moving plugin sourcemap to ${sourceMapPath}`);
await fs.ensureDir(path.dirname(options.sourceMapPath)); await fs.ensureDir(path.dirname(sourceMapPath));
await fs.move(sourceMapUrl, options.sourceMapPath, {overwrite: true}); 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)));
}

View File

@@ -53,6 +53,7 @@ test('getPluginDetailsV1', async () => {
"main": "dist/bundle.js", "main": "dist/bundle.js",
"name": "flipper-plugin-test", "name": "flipper-plugin-test",
"pluginType": undefined, "pluginType": undefined,
"serverAddOnEntry": undefined,
"source": "src/index.tsx", "source": "src/index.tsx",
"specVersion": 1, "specVersion": 1,
"supportedApps": undefined, "supportedApps": undefined,
@@ -97,6 +98,8 @@ test('getPluginDetailsV2', async () => {
"pluginType": undefined, "pluginType": undefined,
"publishedDocs": undefined, "publishedDocs": undefined,
"serverAddOn": undefined, "serverAddOn": undefined,
"serverAddOnEntry": undefined,
"serverAddOnSource": undefined,
"source": "src/index.tsx", "source": "src/index.tsx",
"specVersion": 2, "specVersion": 2,
"supportedApps": undefined, "supportedApps": undefined,
@@ -141,6 +144,8 @@ test('id used as title if the latter omited', async () => {
"pluginType": undefined, "pluginType": undefined,
"publishedDocs": undefined, "publishedDocs": undefined,
"serverAddOn": undefined, "serverAddOn": undefined,
"serverAddOnEntry": undefined,
"serverAddOnSource": undefined,
"source": "src/index.tsx", "source": "src/index.tsx",
"specVersion": 2, "specVersion": 2,
"supportedApps": undefined, "supportedApps": undefined,
@@ -184,6 +189,8 @@ test('name without "flipper-plugin-" prefix is used as title if the latter omite
"pluginType": undefined, "pluginType": undefined,
"publishedDocs": undefined, "publishedDocs": undefined,
"serverAddOn": undefined, "serverAddOn": undefined,
"serverAddOnEntry": undefined,
"serverAddOnSource": undefined,
"source": "src/index.tsx", "source": "src/index.tsx",
"specVersion": 2, "specVersion": 2,
"supportedApps": undefined, "supportedApps": undefined,
@@ -230,6 +237,8 @@ test('flipper-plugin-version is parsed', async () => {
"pluginType": undefined, "pluginType": undefined,
"publishedDocs": undefined, "publishedDocs": undefined,
"serverAddOn": undefined, "serverAddOn": undefined,
"serverAddOnEntry": undefined,
"serverAddOnSource": undefined,
"source": "src/index.tsx", "source": "src/index.tsx",
"specVersion": 2, "specVersion": 2,
"supportedApps": undefined, "supportedApps": undefined,
@@ -280,6 +289,8 @@ test('plugin type and supported devices parsed', async () => {
"pluginType": "device", "pluginType": "device",
"publishedDocs": undefined, "publishedDocs": undefined,
"serverAddOn": undefined, "serverAddOn": undefined,
"serverAddOnEntry": undefined,
"serverAddOnSource": undefined,
"source": "src/index.tsx", "source": "src/index.tsx",
"specVersion": 2, "specVersion": 2,
"supportedApps": undefined, "supportedApps": undefined,
@@ -346,6 +357,8 @@ test('plugin type and supported apps parsed', async () => {
"pluginType": "client", "pluginType": "client",
"publishedDocs": undefined, "publishedDocs": undefined,
"serverAddOn": undefined, "serverAddOn": undefined,
"serverAddOnEntry": undefined,
"serverAddOnSource": undefined,
"source": "src/index.tsx", "source": "src/index.tsx",
"specVersion": 2, "specVersion": 2,
"supportedApps": Array [ "supportedApps": Array [
@@ -435,6 +448,8 @@ test('can merge two package.json files', async () => {
"setup": true, "setup": true,
}, },
"serverAddOn": undefined, "serverAddOn": undefined,
"serverAddOnEntry": undefined,
"serverAddOnSource": undefined,
"source": "src/index.tsx", "source": "src/index.tsx",
"specVersion": 2, "specVersion": 2,
"supportedApps": undefined, "supportedApps": undefined,

View File

@@ -63,11 +63,15 @@ export async function getInstalledPluginDetails(
`${packageJson.name}@${packageJson.version || '0.0.0'}.js`, `${packageJson.name}@${packageJson.version || '0.0.0'}.js`,
) )
: path.resolve(dir, packageJson.main); : path.resolve(dir, packageJson.main);
const serverAddOnEntry = packageJson.serverAddOn
? path.resolve(dir, packageJson.serverAddOn)
: undefined;
return { return {
...pluginDetails, ...pluginDetails,
isBundled: false, isBundled: false,
isActivatable: true, isActivatable: true,
dir, dir,
entry, entry,
serverAddOnEntry,
}; };
} }

View File

@@ -59,6 +59,12 @@ const argv = yargs
type: 'string', type: 'string',
alias: 'os', alias: 'os',
}, },
'output-sourcemap-server-addon': {
description:
'File path for the server add-on sourcemap to be written. Optional.',
type: 'string',
alias: 'os',
},
}) })
.help() .help()
.strict() .strict()
@@ -72,9 +78,13 @@ async function buildPlugin() {
const outputUnpackedArg = argv['output-unpacked']; const outputUnpackedArg = argv['output-unpacked'];
const minFlipperVersion = argv['min-flipper-version']; const minFlipperVersion = argv['min-flipper-version'];
const outputSourcemapArg = argv['output-sourcemap']; const outputSourcemapArg = argv['output-sourcemap'];
const outputSourcemapServerAddOnArg = argv['output-sourcemap-server-addon'];
const packageJsonPath = path.join(pluginDir, 'package.json'); const packageJsonPath = path.join(pluginDir, 'package.json');
const packageJsonOverridePath = path.join(pluginDir, 'fb', '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); const checksum = await computePackageChecksum(pluginDir);
if (previousChecksum !== checksum && argv.version) { if (previousChecksum !== checksum && argv.version) {
console.log(`Plugin changed. Packaging new version ${argv.version}...`); console.log(`Plugin changed. Packaging new version ${argv.version}...`);

View File

@@ -36,6 +36,7 @@ import {
serverDir, serverDir,
rootDir, rootDir,
browserUiDir, browserUiDir,
serverCoreDir,
} from './paths'; } from './paths';
import pFilter from 'p-filter'; import pFilter from 'p-filter';
import child from 'child_process'; import child from 'child_process';
@@ -108,6 +109,27 @@ export async function prepareDefaultPlugins(isInsidersBuild: boolean = false) {
console.log('✅ Prepared default plugins.'); 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( async function generateDefaultPluginEntryPoints(
defaultPlugins: InstalledPluginDetails[], defaultPlugins: InstalledPluginDetails[],
) { ) {
@@ -124,36 +146,20 @@ async function generateDefaultPluginEntryPoints(
p.flipperSDKVersion === '0.0.0' ? version : p.flipperSDKVersion, p.flipperSDKVersion === '0.0.0' ? version : p.flipperSDKVersion,
dir: undefined, dir: undefined,
entry: undefined, entry: undefined,
serverAddOnEntry: undefined,
} as BundledPluginDetails), } as BundledPluginDetails),
); );
await fs.writeJSON( await fs.writeJSON(
path.join(defaultPluginsDir, 'bundled.json'), path.join(defaultPluginsDir, 'bundled.json'),
bundledPlugins, bundledPlugins,
); );
const pluginRequres = bundledPlugins const pluginRequires = bundledPlugins
.map( .map(
(x) => (x) =>
` '${x.name}': tryRequire('${x.name}', () => require('${x.name}'))`, ` '${x.name}': tryRequire('${x.name}', () => require('${x.name}'))`,
) )
.join(',\n'); .join(',\n');
const generatedIndex = ` const generatedIndex = getGeneratedIndex(pluginRequires);
/* 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
`;
await fs.ensureDir(path.join(appDir, 'src', 'defaultPlugins')); await fs.ensureDir(path.join(appDir, 'src', 'defaultPlugins'));
await fs.writeFile( await fs.writeFile(
path.join(appDir, 'src', 'defaultPlugins', 'index.tsx'), path.join(appDir, 'src', 'defaultPlugins', 'index.tsx'),
@@ -164,6 +170,25 @@ async function generateDefaultPluginEntryPoints(
path.join(browserUiDir, 'src', 'defaultPlugins', 'index.tsx'), path.join(browserUiDir, 'src', 'defaultPlugins', 'index.tsx'),
generatedIndex, 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.'); console.log('✅ Generated bundled plugin entry points.');
} }

View File

@@ -14,6 +14,7 @@ export const appDir = path.join(rootDir, 'app');
export const browserUiDir = path.join(rootDir, 'flipper-ui-browser'); export const browserUiDir = path.join(rootDir, 'flipper-ui-browser');
export const staticDir = path.join(rootDir, 'static'); export const staticDir = path.join(rootDir, 'static');
export const serverDir = path.join(rootDir, 'flipper-server'); 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 defaultPluginsDir = path.join(staticDir, 'defaultPlugins');
export const pluginsDir = path.join(rootDir, 'plugins'); export const pluginsDir = path.join(rootDir, 'plugins');
export const fbPluginsDir = path.join(pluginsDir, 'fb'); export const fbPluginsDir = path.join(pluginsDir, 'fb');