From cad0b68d9ccde7b9c15db140864e0da74545cd46 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 9 Feb 2026 17:30:35 +0100 Subject: [PATCH] cli: Integrate CSS bundling as a Rollup plugin Instead of having a separate post-build step for CSS entry points, the CSS bundling is now integrated directly into the Rollup build as a plugin. This is cleaner and will enable better watch mode support in the future. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .../src/modules/build/lib/builder/config.ts | 3 +- .../cli/src/modules/build/lib/builder/css.ts | 66 ------------------- .../src/modules/build/lib/builder/packager.ts | 8 --- .../src/modules/build/lib/builder/plugins.ts | 45 +++++++++++++ 4 files changed, 47 insertions(+), 75 deletions(-) delete mode 100644 packages/cli/src/modules/build/lib/builder/css.ts diff --git a/packages/cli/src/modules/build/lib/builder/config.ts b/packages/cli/src/modules/build/lib/builder/config.ts index ff084148db..91ddb68233 100644 --- a/packages/cli/src/modules/build/lib/builder/config.ts +++ b/packages/cli/src/modules/build/lib/builder/config.ts @@ -37,7 +37,7 @@ import { OutputPlugin, } from 'rollup'; -import { forwardFileImports } from './plugins'; +import { forwardFileImports, cssEntryPoints } from './plugins'; import { BuildOptions, Output } from './types'; import { paths } from '../../../../lib/paths'; import { BackstagePackageJson } from '@backstage/cli-node'; @@ -275,6 +275,7 @@ export async function makeRollupConfigs( target: 'ES2023', minify: options.minify, }), + cssEntryPoints({ entryPoints, targetDir }), ], }); } diff --git a/packages/cli/src/modules/build/lib/builder/css.ts b/packages/cli/src/modules/build/lib/builder/css.ts deleted file mode 100644 index 497c963878..0000000000 --- a/packages/cli/src/modules/build/lib/builder/css.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 fs from 'fs-extra'; -import postcss from 'postcss'; -// @ts-expect-error - no types available -import postcssImport from 'postcss-import'; -import { resolve as resolvePath, dirname } from 'node:path'; -import { EntryPoint } from '../../../../lib/entryPoints'; - -/** - * Bundles a CSS entry point, resolving @import statements. - */ -export async function buildCSSEntryPoint( - entryPoint: EntryPoint, - targetDir: string, -): Promise { - const sourcePath = resolvePath(targetDir, entryPoint.path); - // Convert src/ path to dist/ path - const outputPath = resolvePath( - targetDir, - entryPoint.path.replace(/^(\.\/)?src\//, 'dist/'), - ); - - // Read source CSS - const source = await fs.readFile(sourcePath, 'utf8'); - - // Bundle @import statements using postcss-import - const result = await postcss([postcssImport()]).process(source, { - from: sourcePath, - to: outputPath, - }); - - // Ensure output directory exists - await fs.mkdir(dirname(outputPath), { recursive: true }); - - // Write the bundled CSS - await fs.writeFile(outputPath, result.css); -} - -/** - * Builds all CSS entry points for a package. - */ -export async function buildCSSEntryPoints( - entryPoints: EntryPoint[], - targetDir: string, -): Promise { - const cssEntryPoints = entryPoints.filter(ep => ep.ext === '.css'); - - for (const entryPoint of cssEntryPoints) { - await buildCSSEntryPoint(entryPoint, targetDir); - } -} diff --git a/packages/cli/src/modules/build/lib/builder/packager.ts b/packages/cli/src/modules/build/lib/builder/packager.ts index 4f149c5fd0..12017b694c 100644 --- a/packages/cli/src/modules/build/lib/builder/packager.ts +++ b/packages/cli/src/modules/build/lib/builder/packager.ts @@ -23,8 +23,6 @@ import { makeRollupConfigs } from './config'; import { BuildOptions, Output } from './types'; import { PackageRoles } from '@backstage/cli-node'; import { runParallelWorkers } from '../../../../lib/parallel'; -import { buildCSSEntryPoints } from './css'; -import { readEntryPoints } from '../../../../lib/entryPoints'; export function formatErrorMessage(error: any) { let msg = ''; @@ -115,12 +113,6 @@ export const buildPackage = async (options: BuildOptions) => { const buildTasks = rollupConfigs.map(rollupBuild); await Promise.all(buildTasks); - - // Build CSS entry points after the main build - if (options.packageJson) { - const entryPoints = readEntryPoints(options.packageJson); - await buildCSSEntryPoints(entryPoints, targetDir); - } }; export const buildPackages = async (options: BuildOptions[]) => { diff --git a/packages/cli/src/modules/build/lib/builder/plugins.ts b/packages/cli/src/modules/build/lib/builder/plugins.ts index b597f8fa21..416b2945e4 100644 --- a/packages/cli/src/modules/build/lib/builder/plugins.ts +++ b/packages/cli/src/modules/build/lib/builder/plugins.ts @@ -15,6 +15,9 @@ */ import fs from 'fs-extra'; +import postcss from 'postcss'; +// @ts-expect-error - no types available +import postcssImport from 'postcss-import'; import { dirname, resolve as resolvePath, @@ -27,6 +30,7 @@ import { OutputChunk, HasModuleSideEffects, } from 'rollup'; +import { EntryPoint } from '../../../../lib/entryPoints'; type ForwardFileImportsOptions = { include: Array | string | RegExp | null; @@ -169,3 +173,44 @@ export function forwardFileImports(options: ForwardFileImportsOptions) { }, } satisfies Plugin; } + +interface CssEntryPointsOptions { + entryPoints: EntryPoint[]; + targetDir: string; +} + +/** + * Rollup plugin that bundles CSS entry points using postcss-import. + * CSS files declared in package.json exports are processed and emitted + * as part of the Rollup bundle. + */ +export function cssEntryPoints(options: CssEntryPointsOptions): Plugin { + const cssEntries = options.entryPoints.filter(ep => ep.ext === '.css'); + + return { + name: 'backstage-css-entry-points', + + async generateBundle() { + for (const entryPoint of cssEntries) { + const sourcePath = resolvePath(options.targetDir, entryPoint.path); + // Convert src/ path to dist/ path for output filename + const outputPath = entryPoint.path.replace(/^(\.\/)?src\//, ''); + + // Read source CSS + const source = await fs.readFile(sourcePath, 'utf8'); + + // Bundle @import statements using postcss-import + const result = await postcss([postcssImport()]).process(source, { + from: sourcePath, + }); + + // Emit the bundled CSS as an asset + this.emitFile({ + type: 'asset', + fileName: outputPath, + source: result.css, + }); + } + }, + }; +}