Files
flipper/desktop/flipper-server/src/attachDevServer.tsx
Andrey Goncharov 8dc5f4d3ea Remove stale build options
Summary: Now that we build all plugins at all times and it is super-fast, these options are redundant

Reviewed By: lblasa

Differential Revision: D39542723

fbshipit-source-id: 1b30ba384267ec4fd0c35b4dc14f0223ffe414c9
2022-09-15 10:02:19 -07:00

174 lines
5.4 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import chalk from 'chalk';
import {Express} from 'express';
import http from 'http';
import path from 'path';
import fs from 'fs-extra';
import {WebSocketServer} from 'ws';
import pFilter from 'p-filter';
import {homedir} from 'os';
import {InstalledPluginDetails} from 'flipper-common';
// This file is heavily inspired by scripts/start-dev-server.ts!
// part of that is done by start-flipper-server-dev (compiling "main"),
// the other part ("renderer") here.
const uiSourceDirs = [
'flipper-ui-browser',
'flipper-ui-core',
'flipper-plugin-core',
'flipper-plugin',
'flipper-frontend-core',
'flipper-common',
];
// copied from plugin-lib/src/pluginPaths
export async function getPluginSourceFolders(): Promise<string[]> {
const pluginFolders: string[] = [];
const flipperConfigPath = path.join(homedir(), '.flipper', 'config.json');
if (await fs.pathExists(flipperConfigPath)) {
const config = await fs.readJson(flipperConfigPath);
if (config.pluginPaths) {
pluginFolders.push(...config.pluginPaths);
}
}
pluginFolders.push(path.resolve(__dirname, '..', '..', 'plugins', 'public'));
pluginFolders.push(path.resolve(__dirname, '..', '..', 'plugins', 'fb'));
return pFilter(pluginFolders, (p) => fs.pathExists(p));
}
/**
* Attaches the necessary routing and middleware to observe
* for local changes and apply them to the running instance.
* @param app Express app.
* @param server HTTP server.
* @param socket Web Socket server.
* @param rootDir Root directory.
*/
export async function attachDevServer(
app: Express,
server: http.Server,
socket: WebSocketServer,
rootDir: string,
) {
// prevent bundling!
const Metro = electronRequire('metro');
const MetroResolver = electronRequire('metro-resolver');
const {getWatchFolders, startWatchPlugins} =
electronRequire('flipper-pkg-lib');
const babelTransformationsDir = path.resolve(
rootDir,
'babel-transformer',
'lib', // Note: required pre-compiled!
);
const electronRequires = path.join(
babelTransformationsDir,
'electron-requires.js',
);
const stubModules = new Set<string>(
electronRequire(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(),
]);
const baseConfig = await Metro.loadConfig();
const config = Object.assign({}, baseConfig, {
projectRoot: rootDir,
watchFolders,
transformer: {
...baseConfig.transformer,
babelTransformerPath: path.join(
babelTransformationsDir,
'transform-browser',
),
},
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);
}
// stubbed modules are modules that don't make sense outside a Node / Electron context,
// like fs, child_process etc etc.
// UI / plugins using these features should use the corresponding RenderHost api's instead
// Ideally we'd fail hard on those, but not all plugins are properly converted yet, and some
// libraries try to require them for feature detection (e.g. jsbase64)
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,
);
},
},
watch: true,
});
const connectMiddleware = await Metro.createConnectMiddleware(config);
app.use(connectMiddleware.middleware);
connectMiddleware.attachHmrServer(server);
app.use(function (err: any, _req: any, _res: any, next: any) {
console.error(chalk.red('\n\nCompile error in client bundle\n'), err);
socket.clients.forEach((client) => {
client.send(
JSON.stringify({event: 'hasErrors', payload: err.toString()}),
);
});
next();
});
await startWatchPlugins((changedPlugins: InstalledPluginDetails[]) => {
socket.clients.forEach((client) => {
client.send(
JSON.stringify({
event: 'plugins-source-updated',
payload: changedPlugins,
}),
);
});
});
console.log('DEV webserver started.');
}
async function dedupeFolders(paths: string[]): Promise<string[]> {
return pFilter(
paths.filter((value, index, self) => self.indexOf(value) === index),
(f) => fs.pathExists(f),
);
}