Make flipper-server stand-alone servable

Summary:
This diff makes flipper-server stand-alone servable as a single package.

It basically works as follows:

- (default) plugins are build as usually into static folder
- static folder is copied into flipper-server/static (as far as relevant)
- `flipper-server/src/index.tsx` is bundled into `flipper-server/dist/index.js`
- `flipper-ui-browser/src/index.tsx` is bundled into `flipper-server/static/bundle.js`

If flipper-server is started without the `--bundler` config, the `bundle.js` will be served statically, rather than starting up the Metro bundler with all the typical transforms (in a distributed version of the package those all wouldn't resolve)

Things to be done in next diffs:

* make sure plugins actually load in the non bundled setup
* make sure flipper-server gets published properly
* make sure build is created as part of CI

At the end of this stack, running `npx flipper-server` on any machine should basically be a viable approach to get flipper up and running locally :)

Reviewed By: nikoant

Differential Revision: D33171107

fbshipit-source-id: 4af8ac2699467a0b55283fe084640482700744c2
This commit is contained in:
Michel Weststrate
2021-12-24 02:15:25 -08:00
committed by Facebook GitHub Bot
parent d48fbd8e50
commit 6ff4abbc67
13 changed files with 515 additions and 84 deletions

View File

@@ -9,6 +9,9 @@
// @ts-ignore
import Metro from 'metro';
// provided by Metro
// eslint-disable-next-line
import MetroResolver from 'metro-resolver';
import tmp from 'tmp';
import path from 'path';
import fs from 'fs-extra';
@@ -32,7 +35,11 @@ import {
serverDir,
rootDir,
browserUiDir,
serverStaticDir,
} from './paths';
import pFilter from 'p-filter';
import child from 'child_process';
import pMap from 'p-map';
// eslint-disable-next-line flipper/no-relative-imports-across-packages
const {version} = require('../package.json');
@@ -170,23 +177,29 @@ async function buildDefaultPlugins(defaultPlugins: InstalledPluginDetails[]) {
.join(', ')}`,
);
}
for (const plugin of defaultPlugins) {
try {
if (!process.env.FLIPPER_NO_REBUILD_PLUGINS) {
console.log(
`⚙️ Building plugin ${plugin.id} to include it into the default plugins list...`,
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',
);
await runBuild(plugin.dir, dev);
} catch (err) {
console.error(`✖ Failed to build plugin ${plugin.id}`, err);
}
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 = {
@@ -371,7 +384,7 @@ export function genMercurialRevision(): Promise<string | null> {
.catch(() => null);
}
export async function compileServerMain() {
export async function compileServerMain(dev: boolean) {
await fs.promises.mkdir(path.join(serverDir, 'dist'), {recursive: true});
const out = path.join(serverDir, 'dist', 'index.js');
console.log('⚙️ Compiling server bundle...');
@@ -406,3 +419,130 @@ export async function compileServerMain() {
stripSourceMapComment(out);
}
}
// TODO: needed?
const uiSourceDirs = [
'flipper-ui-browser',
'flipper-ui-core',
'flipper-plugin',
'flipper-common',
];
export async function buildBrowserBundle(dev: boolean) {
console.log('⚙️ Compiling browser bundle...');
const out = path.join(serverStaticDir, 'bundle.js');
const electronRequires = path.join(
babelTransformationsDir,
'electron-requires',
);
const stubModules = new Set<string>(require(electronRequires).BUILTINS);
if (!stubModules.size) {
throw new Error('Failed to load list of Node builtins');
}
const watchFolders = await dedupeFolders([
...(
await Promise.all(
uiSourceDirs.map((dir) => getWatchFolders(path.resolve(rootDir, dir))),
)
).flat(),
...(await getPluginSourceFolders()),
]);
const baseConfig = await Metro.loadConfig();
const config = Object.assign({}, baseConfig, {
projectRoot: rootDir,
watchFolders,
transformer: {
...baseConfig.transformer,
babelTransformerPath: path.join(
babelTransformationsDir,
'transform-browser',
),
...(!dev ? minifierConfig : undefined),
},
resolver: {
...baseConfig.resolver,
resolverMainFields: ['flipperBundlerEntry', 'browser', 'module', 'main'],
blacklistRE: [/\.native\.js$/],
sourceExts: ['js', 'jsx', 'ts', 'tsx', 'json', 'mjs', 'cjs'],
resolveRequest(context: any, moduleName: string, ...rest: any[]) {
// flipper is special cased, for plugins that we bundle,
// we want to resolve `impoSrt from 'flipper'` to 'flipper-ui-core', which
// defines all the deprecated exports
if (moduleName === 'flipper') {
return MetroResolver.resolve(context, 'flipper-ui-core', ...rest);
}
if (stubModules.has(moduleName)) {
console.warn(
`Found a reference to built-in module '${moduleName}', which will be stubbed out. Referer: ${context.originModulePath}`,
);
return {
type: 'empty',
};
}
return MetroResolver.resolve(
{
...context,
resolveRequest: null,
},
moduleName,
...rest,
);
},
},
});
await Metro.runBuild(config, {
platform: 'web',
entry: path.join(browserUiDir, 'src', 'index.tsx'),
out,
dev,
minify: !dev,
sourceMap: true,
sourceMapUrl: dev ? 'index.map' : undefined,
inlineSourceMap: false,
});
console.log('✅ Compiled browser bundle.');
if (!dev) {
stripSourceMapComment(out);
}
}
async function dedupeFolders(paths: string[]): Promise<string[]> {
return pFilter(
paths.filter((value, index, self) => self.indexOf(value) === index),
(f) => fs.pathExists(f),
);
}
export function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}
let proc: child.ChildProcess | undefined;
export async function launchServer(startBundler: boolean, open: boolean) {
if (proc) {
console.log('⚙️ Killing old flipper-server...');
proc.kill(9);
await sleep(1000);
}
console.log('⚙️ Launching flipper-server...');
proc = child.spawn(
'node',
[
'--inspect=9229',
`../flipper-server/server.js`,
startBundler ? `--bundler` : `--no-bundler`,
open ? `--open` : `--no-open`,
],
{
cwd: serverDir,
env: {
...process.env,
},
stdio: 'inherit',
},
);
}