Fix generation of bundled.json, make source maps work in prod builds

Summary:
This diff fixes several issues around loading plugin, such as:

* make suresource maps work in the flipper-server production build
* make sure default plugins are no symlinked, which wouldn't work anywhere else but on the the system where it was build
* support release channel param for flipper-server

Bundled flipper-server is now 42MB (with icons (see later diffs) and plugins
```
ll flipper-server-v0.0.0.tgz
-rw-r--r--  1 mweststrate  staff    42M 23 Dec 15:29 flipper-server-v0.0.0.tgz
```

Reviewed By: nikoant

Differential Revision: D33294677

fbshipit-source-id: 63538dc8127f883fee6a3608673ad11ce239b350
This commit is contained in:
Michel Weststrate
2021-12-24 02:15:25 -08:00
committed by Facebook GitHub Bot
parent b1d960e4c4
commit 72fa481d27
10 changed files with 86 additions and 30 deletions

View File

@@ -81,6 +81,9 @@ export class FlipperServerImpl implements FlipperServer {
keytarModule?: KeytarModule,
) {
setFlipperServerConfig(config);
console.log(
'Loaded flipper config, paths: ' + JSON.stringify(config.paths, null, 2),
);
const server = (this.server = new ServerController(this));
this.android = new AndroidDeviceManager(this);
this.ios = new IOSDeviceManager(this);

View File

@@ -45,6 +45,11 @@ export async function loadDynamicPlugins(): Promise<InstalledPluginDetails[]> {
)
).map((p: any) => p.name) as string[],
);
console.log(
`✅ Detected ${bundledPlugins.size} bundled plugins: ${Array.from(
bundledPlugins,
).join(', ')}.`,
);
const [installedPlugins, unfilteredSourcePlugins] = await Promise.all([
process.env.FLIPPER_NO_PLUGIN_MARKETPLACE
? Promise.resolve([])
@@ -60,23 +65,23 @@ export async function loadDynamicPlugins(): Promise<InstalledPluginDetails[]> {
const defaultPlugins = await getAllInstalledPluginsInDir(defaultPluginsDir);
if (defaultPlugins.length > 0) {
console.log(
`✅ Loaded ${defaultPlugins.length} default plugins: ${defaultPlugins
.map((x) => x.title)
.join(', ')}.`,
`✅ Loaded ${defaultPlugins.length} default plugins:\n${defaultPlugins
.map((x) => `${x.title}@${x.version}`)
.join('\n')}.`,
);
}
if (installedPlugins.length > 0) {
console.log(
`✅ Loaded ${installedPlugins.length} installed plugins: ${Array.from(
new Set(installedPlugins.map((x) => x.title)),
).join(', ')}.`,
`✅ Loaded ${installedPlugins.length} installed plugins:\n${Array.from(
new Set(installedPlugins.map((x) => `${x.title}@${x.version}`)),
).join('\n')}.`,
);
}
if (sourcePlugins.length > 0) {
console.log(
`✅ Loaded ${sourcePlugins.length} source plugins: ${sourcePlugins
.map((x) => x.title)
.join(', ')}.`,
`✅ Loaded ${sourcePlugins.length} source plugins:\n${sourcePlugins
.map((x) => `${x.title} - ${x.dir}`)
.join('\n')}.`,
);
}
return [...defaultPlugins, ...installedPlugins, ...sourcePlugins];

View File

@@ -17,7 +17,7 @@ import pFilter from 'p-filter';
import {homedir} from 'os';
// This file is heavily inspired by scripts/start-dev-server.ts!
// part of that is done by start-flipper-server (compiling "main"),
// part of that is done by start-flipper-server-dev (compiling "main"),
// the other part ("renderer") here.
const uiSourceDirs = [

View File

@@ -59,10 +59,18 @@ export function initializeRenderHost(
},
flipperServer,
async requirePlugin(path) {
// TODO: use `await import(path)`?
const source = await flipperServer.exec('plugin-source', path);
// eslint-disable-next-line no-new-func
const cjsLoader = new Function('module', source);
let source = await flipperServer.exec('plugin-source', path);
// append source url (to make sure a file entry shows up in the debugger)
source += `\n//# sourceURL=file://${path}`;
// and source map url (to get source code if available)
source += `\n//# sourceMappingURL=file://${path.replace(/.js$/, '.map')}`;
// Plugins are compiled as typical CJS modules, referring to the global
// 'module', which we'll make available by loading the source into a closure that captures 'module'.
// Note that we use 'eval', and not 'new Function', because the latter will cause the source maps
// to be off by two lines (as the function declaration uses two lines in the generated source)
// eslint-disable-next-line no-eval
const cjsLoader = eval('(module) => {' + source + '\n}');
const theModule = {exports: {}};
cjsLoader(theModule);
return theModule.exports;

View File

@@ -128,7 +128,7 @@
"predev-server": "yarn build:tsc",
"dev-server": "cross-env NODE_ENV=development ./ts-node scripts/start-dev-server.ts",
"preflipper-server": "yarn build:tsc",
"flipper-server": "cross-env NODE_ENV=development ./ts-node scripts/start-flipper-server.ts",
"flipper-server": "cross-env NODE_ENV=development ./ts-node scripts/start-flipper-server-dev.ts",
"fix": "eslint . --fix --ext .js,.ts,.tsx",
"lint": "yarn lint:eslint && yarn lint:tsc && yarn tsc-plugins",
"lint:eslint": "eslint . --ext .js,.ts,.tsx",

View File

@@ -15,10 +15,18 @@ import {
launchServer,
prepareDefaultPlugins,
} from './build-utils';
import {serverStaticDir, staticDir} from './paths';
import {
defaultPluginsDir,
serverDefaultPluginsDir,
serverStaticDir,
staticDir,
} from './paths';
import isFB from './isFB';
import yargs from 'yargs';
import {copy, mkdir, remove} from 'fs-extra';
import fs from 'fs-extra';
import copyPackageWithDependencies, {
copyPackageWithDependenciesRecursive,
} from './copy-package-with-dependencies';
const argv = yargs
.usage('yarn build-flipper-server [args]')
@@ -51,6 +59,11 @@ const argv = yargs
'Load only specified plugins and skip loading rest. This is useful when you are developing only one or few plugins. Plugins to load can be specified as a comma-separated list with either plugin id or name used as identifier, e.g. "--enabled-plugins network,inspector". The flag is not provided by default which means that all plugins loaded.',
type: 'array',
},
channel: {
description: 'Release channel for the build',
choices: ['stable', 'insiders'],
default: 'stable',
},
})
.version('DEV')
.help()
@@ -60,7 +73,8 @@ if (isFB) {
process.env.FLIPPER_FB = 'true';
}
// Don't bundle any plugins into the UI
process.env.FLIPPER_RELEASE_CHANNEL = argv.channel;
process.env.FLIPPER_NO_BUNDLED_PLUGINS = 'true';
// Don't rebuild default plugins, mostly to speed up testing
@@ -93,10 +107,10 @@ if (argv['enabled-plugins'] !== undefined) {
}
// clear and re-create static dir
await remove(serverStaticDir);
await mkdir(serverStaticDir);
await fs.remove(serverStaticDir);
await fs.mkdir(serverStaticDir);
await prepareDefaultPlugins(false);
await prepareDefaultPlugins(argv.channel === 'insiders');
await compileServerMain(false);
await buildBrowserBundle(false);
@@ -111,11 +125,32 @@ if (argv['enabled-plugins'] !== undefined) {
});
async function copyStaticResources() {
console.log(`⚙️ Copying default plugins...`);
await fs.mkdirp(serverDefaultPluginsDir);
const plugins = await fs.readdir(defaultPluginsDir);
for (const plugin of plugins) {
let source = path.join(defaultPluginsDir, plugin);
// static/defaultPlugins will symlink, resolve those first
while ((await fs.lstat(source)).isSymbolicLink()) {
source = await fs.readlink(source);
}
const target = path.join(serverDefaultPluginsDir, plugin);
if ((await fs.stat(source)).isDirectory()) {
// for plugins, only copy package.json & dist, to keep impact minimal
await fs.copy(
path.join(source, 'package.json'),
path.join(target, 'package.json'),
);
await fs.copy(path.join(source, 'dist'), path.join(target, 'dist'));
} else {
await fs.copy(source, target);
}
}
console.log(`⚙️ Copying static resources...`);
// static folder, without the things that are only for Electron
const thingsToCopy = [
'defaultPlugins',
'facebook',
'icons',
'native-modules',
@@ -134,7 +169,7 @@ async function copyStaticResources() {
await Promise.all(
thingsToCopy.map((e) =>
copy(path.join(staticDir, e), path.join(serverStaticDir, e)),
fs.copy(path.join(staticDir, e), path.join(serverStaticDir, e)),
),
);
console.log('✅ Copied static resources.');

View File

@@ -31,7 +31,7 @@ import {
import fetch from '@adobe/node-fetch-retry';
import isFB from './isFB';
import copyPackageWithDependencies from './copy-package-with-dependencies';
import {staticDir, distDir} from './paths';
import {staticDir, distDir, defaultPluginsDir} from './paths';
import yargs from 'yargs';
import {WinPackager} from 'app-builder-lib/out/winPackager';
// eslint-disable-next-line node/no-extraneous-import

View File

@@ -420,9 +420,6 @@ export async function compileServerMain(dev: boolean) {
resetCache: !dev,
});
console.log('✅ Compiled server bundle.');
if (!dev) {
stripSourceMapComment(out);
}
}
// TODO: needed?
@@ -503,9 +500,6 @@ export async function buildBrowserBundle(dev: boolean) {
inlineSourceMap: false,
});
console.log('✅ Compiled browser bundle.');
if (!dev) {
stripSourceMapComment(out);
}
}
async function dedupeFolders(paths: string[]): Promise<string[]> {

View File

@@ -16,6 +16,10 @@ export const staticDir = path.join(rootDir, 'static');
export const serverDir = path.join(rootDir, 'flipper-server');
export const serverStaticDir = path.join(serverDir, 'static'); // for pre-bundled server, static resources are copied here
export const defaultPluginsDir = path.join(staticDir, 'defaultPlugins');
export const serverDefaultPluginsDir = path.join(
serverStaticDir,
'defaultPlugins',
);
export const pluginsDir = path.join(rootDir, 'plugins');
export const fbPluginsDir = path.join(pluginsDir, 'fb');
export const publicPluginsDir = path.join(pluginsDir, 'public');

View File

@@ -65,6 +65,11 @@ const argv = yargs
type: 'boolean',
default: true,
},
channel: {
description: 'Release channel for the build',
choices: ['stable', 'insiders'],
default: 'stable',
},
})
.version('DEV')
.help()
@@ -74,6 +79,8 @@ if (isFB) {
process.env.FLIPPER_FB = 'true';
}
process.env.FLIPPER_RELEASE_CHANNEL = argv.channel;
if (argv['default-plugins'] === true) {
delete process.env.FLIPPER_NO_DEFAULT_PLUGINS;
} else if (argv['default-plugins'] === false) {