cli: refactor package detection to support inc/ecl + entry point
Co-authored-by: Philipp Hugenroth <philipph@spotify.com> Co-authored-by: Johan Haals <johan.haals@gmail.com> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -27,7 +27,7 @@ import { createConfig, resolveBaseUrl } from './config';
|
||||
import { BuildOptions } from './types';
|
||||
import { resolveBundlingPaths } from './paths';
|
||||
import chalk from 'chalk';
|
||||
import { buildDetectedPlugins } from './discover';
|
||||
import { createDetectedModulesEntrypoint } from './discover';
|
||||
|
||||
// TODO(Rugvip): Limits from CRA, we might want to tweak these though.
|
||||
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
|
||||
@@ -42,17 +42,18 @@ export async function buildBundle(options: BuildOptions) {
|
||||
|
||||
const paths = resolveBundlingPaths(options);
|
||||
|
||||
const detectedModulesEntryPoint = await createDetectedModulesEntrypoint({
|
||||
config: options.fullConfig,
|
||||
targetPath: paths.targetPath,
|
||||
});
|
||||
|
||||
const config = await createConfig(paths, {
|
||||
...options,
|
||||
checksEnabled: false,
|
||||
isDev: false,
|
||||
baseUrl: resolveBaseUrl(options.frontendConfig),
|
||||
getFrontendAppConfigs: () => options.frontendAppConfigs,
|
||||
});
|
||||
|
||||
await buildDetectedPlugins({
|
||||
config: options.fullConfig,
|
||||
targetPath: paths.targetPath,
|
||||
additionalEntryPoints: detectedModulesEntryPoint,
|
||||
});
|
||||
|
||||
const isCi = yn(process.env.CI, { default: false });
|
||||
|
||||
@@ -161,7 +161,7 @@ export async function createConfig(
|
||||
},
|
||||
devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map',
|
||||
context: paths.targetPath,
|
||||
entry: paths.targetEntry,
|
||||
entry: [...(options.additionalEntryPoints ?? []), paths.targetEntry],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx', '.json', '.wasm'],
|
||||
mainFields: ['browser', 'module', 'main'],
|
||||
|
||||
@@ -21,29 +21,72 @@ import fs from 'fs-extra';
|
||||
import { join as joinPath, resolve as resolvePath } from 'path';
|
||||
import { paths as cliPaths } from '../../lib/paths';
|
||||
|
||||
export async function buildDetectedPlugins(options: {
|
||||
config: Config;
|
||||
targetPath: string;
|
||||
watch?: () => void;
|
||||
}) {
|
||||
const { watch, targetPath } = options;
|
||||
if (watch) {
|
||||
const watcher = chokidar.watch(resolvePath(targetPath, 'package.json'));
|
||||
const DETECTED_MODULES_MODULE_NAME = '__backstage-autodetected-plugins__';
|
||||
|
||||
watcher.on('change', async () => {
|
||||
await writeDetectedPluginsModule(options);
|
||||
watch();
|
||||
});
|
||||
}
|
||||
|
||||
await writeDetectedPluginsModule(options);
|
||||
interface PackageDetectionConfig {
|
||||
include?: string[];
|
||||
exclude?: string[];
|
||||
}
|
||||
|
||||
async function writeDetectedPluginsModule(options: {
|
||||
config: Config;
|
||||
targetPath: string;
|
||||
}) {
|
||||
const requirePackageScript = (await detectPlugins(options))
|
||||
function readPackageDetectionConfig(
|
||||
config: Config,
|
||||
): PackageDetectionConfig | undefined {
|
||||
const packages = config.getOptional('app.experimental.packages');
|
||||
if (!packages) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof packages === 'string') {
|
||||
if (packages !== 'all') {
|
||||
throw new Error(
|
||||
`Invalid app.experimental.packages mode, got '${packages}', expected 'all'`,
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
include: config.getOptionalStringArray('app.experimental.packages.include'),
|
||||
exclude: config.getOptionalStringArray('app.experimental.packages.exclude'),
|
||||
};
|
||||
}
|
||||
|
||||
async function detectPackages(
|
||||
targetPath: string,
|
||||
{ include, exclude }: PackageDetectionConfig,
|
||||
) {
|
||||
const pkg: BackstagePackageJson = await fs.readJson(
|
||||
resolvePath(targetPath, 'package.json'),
|
||||
);
|
||||
|
||||
return Object.keys(pkg.dependencies ?? {})
|
||||
.flatMap(depName => {
|
||||
const depPackageJson: BackstagePackageJson = require(require.resolve(
|
||||
`${depName}/package.json`,
|
||||
{ paths: [targetPath] },
|
||||
));
|
||||
if (
|
||||
['frontend-plugin', 'frontend-plugin-module'].includes(
|
||||
depPackageJson.backstage?.role ?? '',
|
||||
)
|
||||
) {
|
||||
return [depName];
|
||||
}
|
||||
return [];
|
||||
})
|
||||
.filter(name => {
|
||||
if (exclude?.includes(name)) {
|
||||
return false;
|
||||
}
|
||||
if (include && !include.includes(name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
async function writeDetectedPackagesModule(packageNames: string[]) {
|
||||
const requirePackageScript = packageNames
|
||||
?.map(pkg => `{name: '${pkg}', module: require('${pkg}')}`)
|
||||
.join(',');
|
||||
|
||||
@@ -51,47 +94,38 @@ async function writeDetectedPluginsModule(options: {
|
||||
joinPath(
|
||||
cliPaths.targetRoot,
|
||||
'node_modules',
|
||||
'__backstage-autodetected-plugins__.js',
|
||||
`${DETECTED_MODULES_MODULE_NAME}.js`,
|
||||
),
|
||||
`export const modules = [${requirePackageScript}];`,
|
||||
);
|
||||
}
|
||||
|
||||
async function detectPlugins({
|
||||
config,
|
||||
targetPath,
|
||||
}: {
|
||||
export async function createDetectedModulesEntrypoint(options: {
|
||||
config: Config;
|
||||
targetPath: string;
|
||||
}) {
|
||||
const pkg: BackstagePackageJson = await fs.readJson(
|
||||
resolvePath(targetPath, 'package.json'),
|
||||
watch?: () => void;
|
||||
}): Promise<string[]> {
|
||||
const { config, watch, targetPath } = options;
|
||||
|
||||
const detectionConfig = readPackageDetectionConfig(config);
|
||||
if (!detectionConfig) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (watch) {
|
||||
const watcher = chokidar.watch(resolvePath(targetPath, 'package.json'));
|
||||
|
||||
watcher.on('change', async () => {
|
||||
await writeDetectedPackagesModule(
|
||||
await detectPackages(targetPath, detectionConfig),
|
||||
);
|
||||
watch();
|
||||
});
|
||||
}
|
||||
|
||||
await writeDetectedPackagesModule(
|
||||
await detectPackages(targetPath, detectionConfig),
|
||||
);
|
||||
// TODO: proper
|
||||
// Assumption for config string based on https://github.com/backstage/backstage/issues/18372 ^
|
||||
|
||||
const packageDetectionMode =
|
||||
config.getOptional('app.experimental.packages') || 'all';
|
||||
|
||||
const allowedPackages =
|
||||
packageDetectionMode === 'all'
|
||||
? Object.keys(pkg.dependencies ?? {})
|
||||
: config.getStringArray('app.experimental.packages');
|
||||
|
||||
return allowedPackages
|
||||
.map(depName => {
|
||||
const depPackageJson: BackstagePackageJson = require(require.resolve(
|
||||
`${depName}/package.json`,
|
||||
{ paths: [targetPath] },
|
||||
));
|
||||
if (
|
||||
['frontend-plugin', 'frontend-plugin-module'].includes(
|
||||
depPackageJson.backstage?.role || '',
|
||||
)
|
||||
) {
|
||||
return depName;
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter((d): d is string => !!d);
|
||||
return [DETECTED_MODULES_MODULE_NAME];
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import { paths as libPaths } from '../../lib/paths';
|
||||
import { loadCliConfig } from '../config';
|
||||
import { Lockfile } from '../versioning';
|
||||
import { createConfig, resolveBaseUrl } from './config';
|
||||
import { buildDetectedPlugins } from './discover';
|
||||
import { createDetectedModulesEntrypoint as createDetectedModulesEntryPoint } from './discover';
|
||||
import { resolveBundlingPaths } from './paths';
|
||||
import { ServeOptions } from './types';
|
||||
|
||||
@@ -119,6 +119,14 @@ export async function serveBundle(options: ServeOptions) {
|
||||
Number(url.port) ||
|
||||
(url.protocol === 'https:' ? 443 : 80);
|
||||
|
||||
const detectedModulesEntryPoint = await createDetectedModulesEntryPoint({
|
||||
config: fullConfig,
|
||||
targetPath: paths.targetPath,
|
||||
watch() {
|
||||
server?.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const config = await createConfig(paths, {
|
||||
...options,
|
||||
checksEnabled: options.checksEnabled,
|
||||
@@ -129,14 +137,7 @@ export async function serveBundle(options: ServeOptions) {
|
||||
getFrontendAppConfigs: () => {
|
||||
return latestFrontendAppConfigs;
|
||||
},
|
||||
});
|
||||
|
||||
await buildDetectedPlugins({
|
||||
config: fullConfig,
|
||||
targetPath: paths.targetPath,
|
||||
watch() {
|
||||
server?.invalidate();
|
||||
},
|
||||
additionalEntryPoints: detectedModulesEntryPoint,
|
||||
});
|
||||
|
||||
const compiler = webpack(config);
|
||||
|
||||
@@ -26,6 +26,7 @@ export type BundlingOptions = {
|
||||
frontendAppConfigs: AppConfig[];
|
||||
baseUrl: URL;
|
||||
parallelism?: number;
|
||||
additionalEntryPoints?: string[];
|
||||
};
|
||||
|
||||
export type ServeOptions = BundlingPathsOptions & {
|
||||
|
||||
Reference in New Issue
Block a user