Summary: Sorry for long diff! I can try to split it if necessary, but many changes here are 1-1 replacements / renames. **Preambule** Currently we bundle default plugins into the Flipper main bundle. This helps us to reduce bundle size, because of plugin dependencies re-use. E.g. if multiple plugins use "lodash" when they are bundled together, only one copy of "lodash" added. When they are bundled separately, the same dependency might be added to each of them. However as we're not going to include most of plugins into Flipper distributive anymore and going to rely on Marketplace instead, this bundling doesn't provide significant size benefits anymore. In addition to that, bundling makes it impossible to differentiate whether thrown errors are originated from Flipper core or one of its plugins. Why don't we remove plugin bundling at all? Because for "dev mode" it actually quite useful. It makes dev build start much faster and also enables using of Fast Refresh for plugin development (fast refresh won't work for plugins loaded from disk). **Changes** This diff introduces new option "no-bundled-plugins" for "yarn start" and "yarn build" commands. For now, by default, we will continue bundling default plugins into the Flipper main bundle, but if this option provided then we will build each default plugin separately and include their packages into the Flipper distributive as "pre-installed" to be able to load them from disk even without access to Marketplace. For "yarn start", we're adding symlinks to plugin folders in "static/defaultPlugins" and then they are loaded by Flipper. For "yarn build" we are dereferencing these symlinks to include physical files of plugins into folder "defaultPlugins" of the produced distributive. Folder "defaultPlugins" is excluded from asar, because loading of plugins from asar archive might introduce some unexpected issues depending on their implementation. Reviewed By: mweststrate Differential Revision: D28431838 fbshipit-source-id: f7757e9f5ba9183ed918d70252de3ce0e823177d
290 lines
7.8 KiB
TypeScript
290 lines
7.8 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its 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 Metro from 'metro';
|
|
import tmp from 'tmp';
|
|
import path from 'path';
|
|
import fs from 'fs-extra';
|
|
import {spawn} from 'promisify-child-process';
|
|
import {getWatchFolders, runBuild} from 'flipper-pkg-lib';
|
|
import getAppWatchFolders from './get-app-watch-folders';
|
|
import {
|
|
getSourcePlugins,
|
|
getPluginSourceFolders,
|
|
BundledPluginDetails,
|
|
InstalledPluginDetails,
|
|
} from 'flipper-plugin-lib';
|
|
import {
|
|
appDir,
|
|
staticDir,
|
|
defaultPluginsIndexDir,
|
|
babelTransformationsDir,
|
|
} from './paths';
|
|
|
|
const {version} = require('../package.json');
|
|
|
|
const dev = process.env.NODE_ENV !== 'production';
|
|
|
|
// For insiders builds we bundle top 5 popular device plugins,
|
|
// plus top 10 popular "universal" plugins enabled by more than 100 users.
|
|
const hardcodedPlugins = new Set<string>([
|
|
// Popular device plugins
|
|
'DeviceLogs',
|
|
'CrashReporter',
|
|
'MobileBuilds',
|
|
'Hermesdebuggerrn',
|
|
'React',
|
|
// Popular client plugins
|
|
'Inspector',
|
|
'Network',
|
|
'AnalyticsLogging',
|
|
'GraphQL',
|
|
'UIPerf',
|
|
'MobileConfig',
|
|
'Databases',
|
|
'FunnelLogger',
|
|
'Navigation',
|
|
'Fresco',
|
|
'Preferences',
|
|
]);
|
|
|
|
export function die(err: Error) {
|
|
console.error(err);
|
|
process.exit(1);
|
|
}
|
|
|
|
export async function prepareDefaultPlugins(isInsidersBuild: boolean = false) {
|
|
console.log(
|
|
`⚙️ Preparing default plugins (isInsidersBuild=${isInsidersBuild})...`,
|
|
);
|
|
await fs.emptyDir(defaultPluginsIndexDir);
|
|
const sourcePlugins = process.env.FLIPPER_NO_DEFAULT_PLUGINS
|
|
? []
|
|
: await getSourcePlugins();
|
|
const defaultPlugins = sourcePlugins
|
|
// we only include predefined set of plugins into insiders release
|
|
.filter((p) => !isInsidersBuild || hardcodedPlugins.has(p.id));
|
|
if (isInsidersBuild || process.env.FLIPPER_NO_BUNDLED_PLUGINS) {
|
|
await buildDefaultPlugins(defaultPlugins);
|
|
await generateDefaultPluginEntryPoints([]); // calling it here just to generate empty indexes
|
|
} else {
|
|
await generateDefaultPluginEntryPoints(defaultPlugins);
|
|
}
|
|
}
|
|
|
|
async function generateDefaultPluginEntryPoints(
|
|
defaultPlugins: InstalledPluginDetails[],
|
|
) {
|
|
const bundledPlugins = defaultPlugins.map(
|
|
(p) =>
|
|
({
|
|
...p,
|
|
isBundled: true,
|
|
version: p.version === '0.0.0' ? version : p.version,
|
|
flipperSDKVersion:
|
|
p.flipperSDKVersion === '0.0.0' ? version : p.flipperSDKVersion,
|
|
dir: undefined,
|
|
entry: undefined,
|
|
} as BundledPluginDetails),
|
|
);
|
|
await fs.writeJSON(
|
|
path.join(defaultPluginsIndexDir, 'bundled.json'),
|
|
bundledPlugins,
|
|
);
|
|
const pluginRequres = bundledPlugins
|
|
.map((x) => ` '${x.name}': require('${x.name}')`)
|
|
.join(',\n');
|
|
const generatedIndex = `
|
|
/* eslint-disable */
|
|
// THIS FILE IS AUTO-GENERATED by function "generateDefaultPluginEntryPoints" in "build-utils.ts".
|
|
export default {\n${pluginRequres}\n} as any
|
|
`;
|
|
await fs.ensureDir(path.join(appDir, 'src', 'defaultPlugins'));
|
|
await fs.writeFile(
|
|
path.join(appDir, 'src', 'defaultPlugins', 'index.tsx'),
|
|
generatedIndex,
|
|
);
|
|
console.log('✅ Generated plugin entry points.');
|
|
}
|
|
|
|
async function buildDefaultPlugins(defaultPlugins: InstalledPluginDetails[]) {
|
|
if (process.env.FLIPPER_NO_REBUILD_PLUGINS) {
|
|
console.log(
|
|
`⚙️ Including ${
|
|
defaultPlugins.length
|
|
} plugins into the default plugins list. Skipping rebuilding because "no-rebuild-plugins" option provided. List of default plugins: ${defaultPlugins
|
|
.map((p) => p.id)
|
|
.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 runBuild(plugin.dir, dev);
|
|
}
|
|
await fs.ensureSymlink(
|
|
plugin.dir,
|
|
path.join(defaultPluginsIndexDir, plugin.name),
|
|
'junction',
|
|
);
|
|
} catch (err) {
|
|
console.error(`✖ Failed to build plugin ${plugin.id}`, err);
|
|
}
|
|
}
|
|
}
|
|
|
|
const minifierConfig = {
|
|
minifierPath: require.resolve('metro-minify-terser'),
|
|
minifierConfig: {
|
|
// see: https://www.npmjs.com/package/terser
|
|
keep_fnames: true,
|
|
module: true,
|
|
warnings: true,
|
|
mangle: false,
|
|
compress: false,
|
|
},
|
|
};
|
|
|
|
async function compile(
|
|
buildFolder: string,
|
|
projectRoot: string,
|
|
watchFolders: string[],
|
|
entry: string,
|
|
) {
|
|
const out = path.join(buildFolder, 'bundle.js');
|
|
const sourceMapUrl = dev ? 'bundle.map' : undefined;
|
|
await Metro.runBuild(
|
|
{
|
|
reporter: {update: () => {}},
|
|
projectRoot,
|
|
watchFolders,
|
|
serializer: {},
|
|
transformer: {
|
|
babelTransformerPath: path.join(
|
|
babelTransformationsDir,
|
|
'transform-app',
|
|
),
|
|
...minifierConfig,
|
|
},
|
|
resolver: {
|
|
resolverMainFields: ['flipperBundlerEntry', 'module', 'main'],
|
|
blacklistRE: /\.native\.js$/,
|
|
sourceExts: ['js', 'jsx', 'ts', 'tsx', 'json', 'mjs', 'cjs'],
|
|
},
|
|
},
|
|
{
|
|
dev,
|
|
minify: !dev,
|
|
resetCache: !dev,
|
|
sourceMap: dev,
|
|
sourceMapUrl,
|
|
entry,
|
|
out,
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function compileRenderer(buildFolder: string) {
|
|
console.log(`⚙️ Compiling renderer bundle...`);
|
|
const watchFolders = [
|
|
...(await getAppWatchFolders()),
|
|
...(await getPluginSourceFolders()),
|
|
];
|
|
try {
|
|
await compile(
|
|
buildFolder,
|
|
appDir,
|
|
watchFolders,
|
|
path.join(appDir, 'src', 'init.tsx'),
|
|
);
|
|
console.log('✅ Compiled renderer bundle.');
|
|
} catch (err) {
|
|
die(err);
|
|
}
|
|
}
|
|
|
|
export async function compileMain() {
|
|
const out = path.join(staticDir, 'main.bundle.js');
|
|
process.env.FLIPPER_ELECTRON_VERSION =
|
|
require('electron/package.json').version;
|
|
console.log('⚙️ Compiling main bundle...');
|
|
try {
|
|
const config = Object.assign({}, await Metro.loadConfig(), {
|
|
reporter: {update: () => {}},
|
|
projectRoot: staticDir,
|
|
watchFolders: await getWatchFolders(staticDir),
|
|
transformer: {
|
|
babelTransformerPath: path.join(
|
|
babelTransformationsDir,
|
|
'transform-main',
|
|
),
|
|
...minifierConfig,
|
|
},
|
|
resolver: {
|
|
sourceExts: ['tsx', 'ts', 'js'],
|
|
resolverMainFields: ['flipperBundlerEntry', 'module', 'main'],
|
|
blacklistRE: /\.native\.js$/,
|
|
},
|
|
});
|
|
await Metro.runBuild(config, {
|
|
platform: 'web',
|
|
entry: path.join(staticDir, 'main.ts'),
|
|
out,
|
|
dev,
|
|
minify: !dev,
|
|
sourceMap: dev,
|
|
resetCache: !dev,
|
|
});
|
|
console.log('✅ Compiled main bundle.');
|
|
} catch (err) {
|
|
die(err);
|
|
}
|
|
}
|
|
export function buildFolder(): Promise<string> {
|
|
// eslint-disable-next-line no-console
|
|
console.log('Creating build directory');
|
|
return new Promise<string>((resolve, reject) => {
|
|
tmp.dir({prefix: 'flipper-build-'}, (err, buildFolder) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(buildFolder);
|
|
}
|
|
});
|
|
}).catch((e) => {
|
|
die(e);
|
|
return '';
|
|
});
|
|
}
|
|
export function getVersionNumber(buildNumber?: number) {
|
|
let {version} = require('../package.json');
|
|
if (buildNumber) {
|
|
// Unique build number is passed as --version parameter from Sandcastle
|
|
version = [...version.split('.').slice(0, 2), buildNumber].join('.');
|
|
}
|
|
return version;
|
|
}
|
|
|
|
// Asynchronously determine current mercurial revision as string or `null` in case of any error.
|
|
export function genMercurialRevision(): Promise<string | null> {
|
|
return spawn('hg', ['log', '-r', '.', '-T', '{node}'], {encoding: 'utf8'})
|
|
.then(
|
|
(res) =>
|
|
(res &&
|
|
(typeof res.stdout === 'string'
|
|
? res.stdout
|
|
: res.stdout?.toString())) ||
|
|
null,
|
|
)
|
|
.catch(() => null);
|
|
}
|