From a2f055902410c6d57a36c0cbab6116f68f83ba63 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 16 Nov 2024 21:58:18 -0700 Subject: [PATCH] cli: switch config injection to index.html Signed-off-by: Patrik Oldsberg --- .changeset/metal-carrots-brush.md | 9 +++ .../ConfigInjectingHtmlWebpackPlugin.ts | 55 +++++++++++++++++++ packages/cli/src/lib/bundler/config.ts | 46 +++++++++++----- 3 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 .changeset/metal-carrots-brush.md create mode 100644 packages/cli/src/lib/bundler/ConfigInjectingHtmlWebpackPlugin.ts diff --git a/.changeset/metal-carrots-brush.md b/.changeset/metal-carrots-brush.md new file mode 100644 index 0000000000..776e1d3e56 --- /dev/null +++ b/.changeset/metal-carrots-brush.md @@ -0,0 +1,9 @@ +--- +'@backstage/cli': patch +--- + +When using the experimental Rspack flag the app build and dev server now injects configuration via a `` tag in `index.html` rather than the `process.env.APP_CONFIG` definition, which will now be defined as an empty array instead. + +This requires the app to be using the config loader from the 1.31 release of Backstage. Make sure your app is using at least that version if you are upgrading to this version of the CLI. + +If you have copied the implementation of the `defaultConfigLoader`, make sure to update it to the new implementation. In particular the config loader needs to be able to read configuration from `script` tags with the type `backstage.io/config`. diff --git a/packages/cli/src/lib/bundler/ConfigInjectingHtmlWebpackPlugin.ts b/packages/cli/src/lib/bundler/ConfigInjectingHtmlWebpackPlugin.ts new file mode 100644 index 0000000000..5aa8b75098 --- /dev/null +++ b/packages/cli/src/lib/bundler/ConfigInjectingHtmlWebpackPlugin.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2024 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 HtmlWebpackPlugin from 'html-webpack-plugin'; + +export class ConfigInjectingHtmlWebpackPlugin extends HtmlWebpackPlugin { + readonly name = 'ConfigInjectingHtmlWebpackPlugin'; + readonly #getFrontendAppConfigs: () => AppConfig[]; + + constructor( + options: HtmlWebpackPlugin.Options, + getFrontendAppConfigs: () => AppConfig[], + ) { + super(options); + this.#getFrontendAppConfigs = getFrontendAppConfigs; + } + + apply: HtmlWebpackPlugin['apply'] = compiler => { + super.apply(compiler); + + compiler.hooks.compilation.tap(this.name, compilation => { + const hooks = HtmlWebpackPlugin.getCompilationHooks(compilation); + hooks.alterAssetTagGroups.tap(this.name, ctx => { + if (ctx.plugin !== this) { + return ctx; + } + return { + ...ctx, + headTags: [ + ...ctx.headTags, + HtmlWebpackPlugin.createHtmlTagObject( + 'script', + { type: 'backstage.io/config' }, + `\n${JSON.stringify(this.#getFrontendAppConfigs(), null, 2)}\n`, + ), + ], + }; + }); + }); + }; +} diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 4e9f67c4fa..5e379783ba 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -37,6 +37,7 @@ import { version } from '../../lib/version'; import yn from 'yn'; import { hasReactDomClient } from './hasReactDomClient'; import { createWorkspaceLinkingPlugins } from './linkWorkspaces'; +import { ConfigInjectingHtmlWebpackPlugin } from './ConfigInjectingHtmlWebpackPlugin'; const BUILD_CACHE_ENV_VAR = 'BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE'; @@ -182,19 +183,35 @@ export async function createConfig( ); if (options.moduleFederation?.mode !== 'remote') { - plugins.push( - // `rspack.HtmlRspackPlugin` does not support object type `templateParameters` value, `frontendConfig` in this case - new HtmlWebpackPlugin({ - meta: { - 'backstage-app-mode': options?.appMode ?? 'public', - }, - template: paths.targetHtml, - templateParameters: { - publicPath, - config: frontendConfig, - }, - }), - ); + const templateOptions = { + meta: { + 'backstage-app-mode': options?.appMode ?? 'public', + }, + template: paths.targetHtml, + templateParameters: { + publicPath, + config: frontendConfig, + }, + }; + if (rspack) { + // With Rspack we inject config via index.html, this is both because we + // can't use APP_CONFIG due to the lack of support for runtime values, but + // also because we are able to do it and it lines up better with what the + // app-backend is doing. + // + // We still use the html plugin from WebPack, since the Rspack one won't + // let us inject complex objects like the config. + plugins.push( + new ConfigInjectingHtmlWebpackPlugin( + templateOptions, + options.getFrontendAppConfigs, + ), + ); + } else { + // Config injection via index.html doesn't work across reloads with + // WebPack, so we rely on the APP_CONFIG injection instead + plugins.push(new HtmlWebpackPlugin(templateOptions)); + } plugins.push( new HtmlWebpackPlugin({ meta: { @@ -284,8 +301,7 @@ export async function createConfig( new bundler.DefinePlugin({ 'process.env.BUILD_INFO': JSON.stringify(buildInfo), 'process.env.APP_CONFIG': rspack - ? // FIXME: see also https://github.com/web-infra-dev/rspack/issues/5606 - JSON.stringify(options.getFrontendAppConfigs()) + ? JSON.stringify([]) // Inject via index.html instead : bundler.DefinePlugin.runtimeValue( () => JSON.stringify(options.getFrontendAppConfigs()), true,