Update config to handle relative frontend/backend during webpack build.

Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
Aramis Sennyey
2022-11-29 12:49:51 -05:00
parent 600810c367
commit 6abf24efd7
12 changed files with 113 additions and 21 deletions
+13 -4
View File
@@ -64,10 +64,19 @@ export const apis = [
createApiFactory({
api: discoveryApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) =>
UrlPatternDiscovery.compile(
`${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`,
),
factory: ({ configApi }) => {
let baseUrl;
try {
// Try parsing the url relative to the current document origin, if it fails the URL is misformed.
baseUrl = new URL(
configApi.getString('backend.baseUrl'),
document.location.origin,
).href.replace(/\/$/, '');
} catch (err) {
baseUrl = configApi.getString('backend.baseUrl');
}
return UrlPatternDiscovery.compile(`${baseUrl}/api/{{ pluginId }}`);
},
}),
createApiFactory({
api: alertApiRef,
@@ -19,15 +19,17 @@ import { resolve as resolvePath } from 'path';
import { buildBundle } from '../../lib/bundler';
import { getEnvironmentParallelism } from '../../lib/parallel';
import { loadCliConfig } from '../../lib/config';
import { CliConfigOptions } from '@backstage/config-loader/src/lib/cli';
interface BuildAppOptions {
targetDir: string;
writeStats: boolean;
cliOptions?: CliConfigOptions;
configPaths: string[];
}
export async function buildFrontend(options: BuildAppOptions) {
const { targetDir, writeStats, configPaths } = options;
const { targetDir, writeStats, configPaths, cliOptions } = options;
const { name } = await fs.readJson(resolvePath(targetDir, 'package.json'));
await buildBundle({
targetDir,
@@ -37,6 +39,7 @@ export async function buildFrontend(options: BuildAppOptions) {
...(await loadCliConfig({
args: configPaths,
fromPackage: name,
cliOptions,
})),
});
}
+8
View File
@@ -39,6 +39,14 @@ export function registerRepoCommand(program: Command) {
'--all',
'Build all packages, including bundled app and backend packages.',
)
.option(
'--public-path <path>',
'Public path for hosting the website on, can be relative.',
)
.option(
'--backend-url <url>',
'Backend url, expects just the origin or sub-route. Do not include /api. Can be relative.',
)
.option(
'--since <ref>',
'Only build packages and their dev dependents that changed since the specified ref',
+4
View File
@@ -153,6 +153,10 @@ export async function command(opts: OptionValues, cmd: Command): Promise<void> {
return;
}
await buildFrontend({
cliOptions: {
publicPath: opts.publicPath,
backendUrl: opts.backendUrl,
},
targetDir: pkg.dir,
configPaths: (buildOptions.config as string[]) ?? [],
writeStats: Boolean(buildOptions.stats),
+4 -2
View File
@@ -36,10 +36,12 @@ import { runPlain } from '../run';
import ESLintPlugin from 'eslint-webpack-plugin';
import pickBy from 'lodash/pickBy';
const DUMMY_URL = 'http://dummyurl.org';
export function resolveBaseUrl(config: Config): URL {
const baseUrl = config.getString('app.baseUrl');
try {
return new URL(baseUrl);
return new URL(baseUrl, DUMMY_URL);
} catch (error) {
throw new Error(`Invalid app.baseUrl, ${error}`);
}
@@ -88,7 +90,7 @@ export async function createConfig(
const externalPkgs = packages.filter(p => !isChildPath(paths.root, p.dir));
const baseUrl = frontendConfig.getString('app.baseUrl');
const validBaseUrl = new URL(baseUrl);
const validBaseUrl = new URL(baseUrl, DUMMY_URL);
const publicPath = validBaseUrl.pathname.replace(/\/$/, '');
if (checksEnabled) {
plugins.push(
+3
View File
@@ -60,6 +60,9 @@ export async function serveBundle(options: ServeOptions) {
// Paths with dots should still use the history fallback.
// See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true,
// The index needs to be rewritten relative to the new public path, including subroutes.
index: config.output?.publicPath ?? '/index.html',
},
https:
url.protocol === 'https:'
+5
View File
@@ -32,6 +32,10 @@ type Options = {
withFilteredKeys?: boolean;
withDeprecatedKeys?: boolean;
fullVisibility?: boolean;
cliOptions?: {
publicPath?: string;
backendUrl?: string;
};
};
export async function loadCliConfig(options: Options) {
@@ -76,6 +80,7 @@ export async function loadCliConfig(options: Options) {
experimentalEnvFunc: options.mockEnv
? async name => process.env[name] || 'x'
: undefined,
cliOptions: options.cliOptions,
configRoot: paths.targetRoot,
configTargets: configTargets,
});
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AppConfig } from '@backstage/config';
import { JsonObject } from '@backstage/types';
export type CliConfigOptions = {
publicPath?: string;
backendUrl?: string;
};
/**
* Read specific parameters from the CLI and add them to the build config.
* @param opts CLI passed parameters.
* @returns Array of config, empty if there is no relevant passed in cli options.
*
* @public
*/
export function readCliConfig(opts?: CliConfigOptions): AppConfig[] {
if (!opts || Object.keys(opts).length === 0) return [];
const data: JsonObject = {};
if (opts.publicPath) {
data.app = {
baseUrl: opts.publicPath,
};
}
if (opts.backendUrl) {
data.backend = {
baseUrl: opts.backendUrl,
};
}
return [{ data, context: 'cli' }];
}
+10 -2
View File
@@ -28,6 +28,7 @@ import {
readEnvConfig,
} from './lib';
import fetch from 'node-fetch';
import { CliConfigOptions, readCliConfig } from './lib/cli';
/** @public */
export type ConfigTarget = { path: string } | { url: string };
@@ -81,6 +82,11 @@ export type LoadConfigOptions = {
* An optional configuration that enables watching of config files.
*/
watch?: LoadConfigOptionsWatch;
/**
* New options from the CLI that affect the build config.
*/
cliOptions?: CliConfigOptions;
};
/**
@@ -230,6 +236,8 @@ export async function loadConfig(
}
}
const cliConfigs = readCliConfig(options.cliOptions);
const envConfigs = readEnvConfig(process.env);
const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => {
@@ -318,7 +326,7 @@ export async function loadConfig(
return {
appConfigs: remote
? [...remoteConfigs, ...fileConfigs, ...envConfigs]
: [...fileConfigs, ...envConfigs],
? [...remoteConfigs, ...fileConfigs, ...envConfigs, ...cliConfigs]
: [...fileConfigs, ...envConfigs, ...cliConfigs],
};
}
+2 -3
View File
@@ -116,7 +116,7 @@ function getBasePath(configApi: Config) {
function readBasePath(configApi: ConfigApi) {
let { pathname } = new URL(
configApi.getOptionalString('app.baseUrl') ?? '/',
'http://dummy.dev', // baseUrl can be specified as just a path
document.location.origin, // baseUrl can be specified as just a path
);
pathname = pathname.replace(/\/*$/, '');
return pathname;
@@ -332,7 +332,6 @@ export class AppManager implements BackstageApp {
routeParents={routing.parents}
routeObjects={routing.objects}
routeBindings={routeBindings}
basePath={getBasePath(loadedConfig.api)}
>
<InternalAppContext.Provider
value={{ routeObjects: routing.objects }}
@@ -426,7 +425,7 @@ export class AppManager implements BackstageApp {
if (isReactRouterBeta()) {
return (
<RouterComponent>
<RouterComponent basename={basePath}>
<RouteTracker routeObjects={routeObjects} />
<SignInPageWrapper component={SignInPageComponent}>
<Routes>
+4
View File
@@ -60,6 +60,10 @@ export type ErrorBoundaryFallbackProps = {
resetError: () => void;
};
export type RouterProps = {
basename: string;
};
/**
* A set of replaceable core components that are part of every Backstage app.
*
@@ -214,15 +214,13 @@ export class RouteResolver {
// Next we figure out the base path, which is the combination of the common parent path
// between our current location and our target location, as well as the additional path
// that is the difference between the parent path and the base of our target location.
const basePath =
this.appBasePath +
resolveBasePath(
targetRef,
relativeSourceLocation,
this.routePaths,
this.routeParents,
this.routeObjects,
);
const basePath = resolveBasePath(
targetRef,
relativeSourceLocation,
this.routePaths,
this.routeParents,
this.routeObjects,
);
const routeFunc: RouteFunc<Params> = (...[params]) => {
return joinPaths(basePath, generatePath(targetPath, params));