Performance improvements for "build-plugin" task

Summary:
Few improvements for "build-plugin" task which together with Sandcastle command changes (D26872427) helps to build all plugins in CI ~30% faster if most of them has not changed (which is usually the case):
1) compute package checksum in the same script to not call additional yarn scripts for each plugin
2) avoid packaging plugin if it's checksum has not changed since last release

Reviewed By: mweststrate

Differential Revision: D26872253

fbshipit-source-id: 968102d32a1550ea7503f1169f0ef2863296383f
This commit is contained in:
Anton Nikolaev
2021-03-10 08:06:19 -08:00
committed by Facebook GitHub Bot
parent 5df0fd6e52
commit baeb8ba5be
10 changed files with 105 additions and 76 deletions

View File

@@ -10,9 +10,11 @@
"bugs": "https://github.com/facebook/flipper/issues", "bugs": "https://github.com/facebook/flipper/issues",
"dependencies": { "dependencies": {
"flipper-babel-transformer": "0.0.0", "flipper-babel-transformer": "0.0.0",
"flipper-plugin-lib": "0.0.0",
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"metro": "^0.65.2", "metro": "^0.65.2",
"metro-minify-terser": "^0.65.2" "metro-minify-terser": "^0.65.2",
"npm-packlist": "^2.1.4"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^9.0.1", "@types/fs-extra": "^9.0.1",

View File

@@ -9,3 +9,4 @@
export {default as runBuild} from './runBuild'; export {default as runBuild} from './runBuild';
export {default as getWatchFolders} from './getWatchFolders'; export {default as getWatchFolders} from './getWatchFolders';
export {default as computePackageChecksum} from './computePackageChecksum';

View File

@@ -11,6 +11,7 @@ import Metro from 'metro';
import getWatchFolders from './getWatchFolders'; import getWatchFolders from './getWatchFolders';
import path from 'path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import {getInstalledPluginDetails} from 'flipper-plugin-lib';
let metroDir: string | undefined; let metroDir: string | undefined;
const metroDirPromise = getMetroDir().then((dir) => (metroDir = dir)); const metroDirPromise = getMetroDir().then((dir) => (metroDir = dir));
@@ -30,19 +31,29 @@ async function getMetroDir() {
return __dirname; return __dirname;
} }
export default async function runBuild( export default async function bundlePlugin(pluginDir: string, dev: boolean) {
inputDirectory: string, const stat = await fs.lstat(pluginDir);
entry: string, if (!stat.isDirectory()) {
out: string, throw new Error(`Plugin source ${pluginDir} is not a directory.`);
dev: boolean, }
) { 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 sourceMapUrl = null; // inline source map const sourceMapUrl = null; // inline source map
const baseConfig = await Metro.loadConfig(); const baseConfig = await Metro.loadConfig();
const config = Object.assign({}, baseConfig, { const config = Object.assign({}, baseConfig, {
reporter: {update: () => {}}, reporter: {update: () => {}},
projectRoot: inputDirectory, projectRoot: pluginDir,
watchFolders: [metroDir || (await metroDirPromise)].concat( watchFolders: [metroDir || (await metroDirPromise)].concat(
await getWatchFolders(inputDirectory), await getWatchFolders(pluginDir),
), ),
serializer: { serializer: {
...baseConfig.serializer, ...baseConfig.serializer,

View File

@@ -4,7 +4,19 @@
"outDir": "lib", "outDir": "lib",
"rootDir": "src" "rootDir": "src"
}, },
"references": [{"path": "../babel-transformer"}], "references": [
"include": ["src"], {
"exclude": ["node_modules", "**/__tests__/*"] "path": "../babel-transformer"
},
{
"path": "../plugin-lib"
}
],
"include": [
"src"
],
"exclude": [
"node_modules",
"**/__tests__/*"
]
} }

View File

@@ -25,7 +25,6 @@
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"inquirer": "^7.3.3", "inquirer": "^7.3.3",
"lodash": "^4.17.19", "lodash": "^4.17.19",
"npm-packlist": "^2.1.4",
"recursive-readdir": "^2.2.2" "recursive-readdir": "^2.2.2"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -12,7 +12,6 @@ import {args} from '@oclif/parser';
import fs from 'fs-extra'; import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import {runBuild} from 'flipper-pkg-lib'; import {runBuild} from 'flipper-pkg-lib';
import {getInstalledPluginDetails} from 'flipper-plugin-lib';
export default class Bundle extends Command { export default class Bundle extends Command {
public static description = 'transpiles and bundles plugin'; public static description = 'transpiles and bundles plugin';
@@ -45,42 +44,18 @@ export default class Bundle extends Command {
public async run() { public async run() {
const {args, flags} = this.parse(Bundle); const {args, flags} = this.parse(Bundle);
const inputDirectory: string = path.resolve(process.cwd(), args.directory); const inputDirectory: string = path.resolve(process.cwd(), args.directory);
const stat = await fs.lstat(inputDirectory); const success = await runBuildOnce(inputDirectory, !flags.production);
if (!stat.isDirectory()) {
this.error(`Plugin source ${inputDirectory} is not a directory.`);
}
const packageJsonPath = path.join(inputDirectory, 'package.json');
if (!(await fs.pathExists(packageJsonPath))) {
this.error(
`package.json is not found in plugin source directory ${inputDirectory}.`,
);
}
const plugin = await getInstalledPluginDetails(inputDirectory);
const out = path.resolve(inputDirectory, plugin.main);
await fs.ensureDir(path.dirname(out));
const success = await runBuildOnce(
inputDirectory,
plugin.source,
out,
!flags.production,
);
if (!flags.watch) { if (!flags.watch) {
process.exit(success ? 0 : 1); process.exit(success ? 0 : 1);
} else { } else {
enterWatchMode(inputDirectory, plugin.source, out, !flags.production); enterWatchMode(inputDirectory, !flags.production);
} }
} }
} }
async function runBuildOnce( async function runBuildOnce(inputDirectory: string, dev: boolean) {
inputDirectory: string,
source: string,
out: string,
dev: boolean,
) {
try { try {
await runBuild(inputDirectory, source, out, dev); await runBuild(inputDirectory, dev);
console.log('✅ Build succeeded'); console.log('✅ Build succeeded');
return true; return true;
} catch (e) { } catch (e) {
@@ -90,12 +65,7 @@ async function runBuildOnce(
} }
} }
function enterWatchMode( function enterWatchMode(inputDirectory: string, dev: boolean) {
inputDirectory: string,
source: string,
out: string,
dev: boolean,
) {
console.log(`⏳ Waiting for changes...`); console.log(`⏳ Waiting for changes...`);
let isBuilding = false; let isBuilding = false;
let pendingChanges = false; let pendingChanges = false;
@@ -112,7 +82,7 @@ function enterWatchMode(
isBuilding = true; isBuilding = true;
while (pendingChanges) { while (pendingChanges) {
pendingChanges = false; pendingChanges = false;
await runBuildOnce(inputDirectory, source, out, dev); await runBuildOnce(inputDirectory, dev);
} }
isBuilding = false; isBuilding = false;
console.log(`⏳ Waiting for changes...`); console.log(`⏳ Waiting for changes...`);

View File

@@ -10,7 +10,7 @@
import {Command} from '@oclif/command'; import {Command} from '@oclif/command';
import {args} from '@oclif/parser'; import {args} from '@oclif/parser';
import path from 'path'; import path from 'path';
import computePackageChecksum from '../utils/computePackageChecksum'; import {computePackageChecksum} from 'flipper-pkg-lib';
export default class Lint extends Command { export default class Lint extends Command {
public static description = public static description =

View File

@@ -10,13 +10,12 @@
import {Command, flags} from '@oclif/command'; import {Command, flags} from '@oclif/command';
import {args} from '@oclif/parser'; import {args} from '@oclif/parser';
import {promises as fs} from 'fs'; import {promises as fs} from 'fs';
import {mkdirp, pathExists, readJSON, ensureDir} from 'fs-extra'; import {mkdirp, pathExists, readJSON} from 'fs-extra';
import * as inquirer from 'inquirer'; import * as inquirer from 'inquirer';
import * as path from 'path'; import * as path from 'path';
import * as yarn from '../utils/yarn'; import * as yarn from '../utils/yarn';
import cli from 'cli-ux'; import cli from 'cli-ux';
import {runBuild} from 'flipper-pkg-lib'; import {runBuild} from 'flipper-pkg-lib';
import {getInstalledPluginDetails} from 'flipper-plugin-lib';
async function deriveOutputFileName(inputDirectory: string): Promise<string> { async function deriveOutputFileName(inputDirectory: string): Promise<string> {
const packageJson = await readJSON(path.join(inputDirectory, 'package.json')); const packageJson = await readJSON(path.join(inputDirectory, 'package.json'));
@@ -115,14 +114,8 @@ export default class Pack extends Command {
await yarn.install(inputDirectory); await yarn.install(inputDirectory);
cli.action.stop(); cli.action.stop();
cli.action.start('Reading plugin details');
const plugin = await getInstalledPluginDetails(inputDirectory);
const out = path.resolve(inputDirectory, plugin.main);
cli.action.stop(`done. Source: ${plugin.source}. Main: ${plugin.main}.`);
cli.action.start(`Compiling`); cli.action.start(`Compiling`);
await ensureDir(path.dirname(out)); await runBuild(inputDirectory, parsedFlags.production);
await runBuild(inputDirectory, plugin.source, out, parsedFlags.production);
cli.action.stop(); cli.action.stop();
cli.action.start(`Packing to ${outputFile}`); cli.action.start(`Packing to ${outputFile}`);

View File

@@ -12,27 +12,68 @@ import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import {execSync} from 'child_process'; import {execSync} from 'child_process';
import {resolvePluginDir} from './workspaces'; import {resolvePluginDir} from './workspaces';
import {runBuild, computePackageChecksum} from 'flipper-pkg-lib';
import yargs from 'yargs';
async function buildPlugin(argv: string[]) { const argv = yargs
const pluginName = argv[2]; .usage('yarn build-plugin [args]')
.version(false)
.options({
plugin: {
description:
'Plugin ID or path relative to "plugins" dir (e.g. "layout")',
type: 'string',
demandOption: true,
alias: 'p',
},
version: {
description: 'New version to set',
type: 'string',
alias: 'v',
},
checksum: {
description:
'Checksum of the previous plugin package which is used to determine whether the plugin is changed or not. If it is not changed, it will not be packaged.',
type: 'string',
alias: 'c',
},
output: {
description: 'Where to save the plugin package',
type: 'string',
alias: 'o',
},
})
.help()
.strict()
.parse(process.argv.slice(1));
async function buildPlugin() {
const pluginName = argv.plugin;
const previousChecksum = argv.checksum;
const pluginDir = await resolvePluginDir(pluginName); const pluginDir = await resolvePluginDir(pluginName);
const outputFileArg = argv.length > 3 ? argv[3] : null; const outputFileArg = argv.output;
const outputFile = outputFileArg await runBuild(pluginDir, false);
? path.resolve(outputFileArg) const checksum = await computePackageChecksum(pluginDir);
: path.join( if (previousChecksum !== checksum && argv.version) {
distDir, console.log(`Plugin changed. Packaging new version ${argv.version}...`);
'plugins', const outputFile = outputFileArg
path.relative(pluginsDir, pluginDir) + '.tgz', ? path.resolve(outputFileArg)
); : path.join(
await fs.ensureDir(path.dirname(outputFile)); distDir,
await fs.remove(outputFile); 'plugins',
const bundleCmd = `yarn flipper-pkg bundle "${pluginDir}" --production`; path.relative(pluginsDir, pluginDir) + '.tgz',
const packCmd = `yarn pack --cwd "${pluginDir}" --filename ${outputFile}`; );
execSync(bundleCmd, {cwd: rootDir, stdio: 'inherit'}); await fs.ensureDir(path.dirname(outputFile));
execSync(packCmd, {cwd: rootDir, stdio: 'inherit'}); await fs.remove(outputFile);
const versionCmd = `yarn version --cwd "${pluginDir}" --new-version ${argv.version}`;
execSync(versionCmd, {cwd: rootDir, stdio: 'inherit'});
const packCmd = `yarn pack --cwd "${pluginDir}" --filename ${outputFile}`;
execSync(packCmd, {cwd: rootDir, stdio: 'inherit'});
await fs.writeFile(outputFile + '.hash', checksum);
}
} }
buildPlugin(process.argv) buildPlugin()
.then(() => { .then(() => {
process.exit(0); process.exit(0);
}) })