halo-dev/halo · error · Error
ESM UI provider output must contain at most one entry styles
Error message
ESM UI provider output must contain at most one entry stylesheet.
What it means
Thrown in generateBundle when the single ESM entry chunk imports more than one CSS stylesheet (viteMetadata.importedCss has 2+ entries). The ESM provider manifest supports at most one entry style (the 'style' field), so multiple stylesheets cannot be represented; emitting a manifest that silently drops styles would break the plugin's UI.
Source
Thrown at ui/packages/ui-plugin-bundler-kit/src/vite-esm.ts:55
const chunks = Object.values(bundle).filter(
(item) => item.type === "chunk"
);
for (const chunk of chunks) {
await validator.validateSource(chunk.code, chunk.fileName);
}
const entries = chunks.filter((chunk) => chunk.isEntry);
if (entries.length !== 1 || !entries[0].exports.includes("default")) {
throw new Error(
"ESM UI provider output must contain one entry with a default PluginModule export."
);
}
const entryStyles = [
...((entries[0] as ViteOutputChunkMetadata).viteMetadata
?.importedCss || []),
].sort();
if (entryStyles.length > 1) {
throw new Error(
"ESM UI provider output must contain at most one entry stylesheet."
);
}
const manifest = validateEsmProviderManifest({
format: "esm",
entry: `./${entries[0].fileName}`,
...(entryStyles[0] ? { style: `./${entryStyles[0]}` } : {}),
});
this.emitFile({
type: "asset",
fileName: ESM_PROVIDER_MANIFEST,
source: `${JSON.stringify(manifest, null, 2)}\n`,
});
const report = validator.getBuildReport();
resolvedConfig.logger.info(report.summary);
if (report.warning) {
resolvedConfig.logger.warn(report.warning);View on GitHub (pinned to d2f5165f9c)
Solutions
- Consolidate all entry CSS into a single file (e.g. one main.css that @imports or @layers the others) and import only that from the entry.
- Remove the second static CSS import from the entry; load peripheral styles lazily if needed.
- If a dependency injects CSS, configure Vite to inline/merge CSS for the ESM build (build.cssCodeSplit: false) so only one stylesheet reaches the entry.
Example fix
// before (entry.ts) import "./styles/base.css"; import "./styles/theme.css"; // after import "./styles/index.css"; // index.css contains both via @import
Defensive patterns
Strategy: validation
Validate before calling
// Pre-build: ensure the entry imports at most one CSS file.
import { readFileSync } from "node:fs";
const entry = readFileSync("src/index.ts", "utf8");
const cssImports = entry.match(/^\s*import\s+["'].*\.css["']/gm) ?? [];
if (cssImports.length > 1) {
throw new Error(`Entry imports ${cssImports.length} CSS files; consolidate into one.`);
} Type guard
function entryHasAtMostOneStyle(importedCss: Iterable<string> | undefined): boolean {
const count = importedCss ? [...importedCss].length : 0;
return count <= 1;
} Prevention
- Consolidate entry CSS into a single file using @import or @layer.
- Set build.cssCodeSplit: false for the ESM build so Vite emits one stylesheet.
- Audit third-party CSS imports that attach to the entry chunk.
When it happens
Trigger: The entry module statically imports two or more distinct .css files (e.g. `import './a.css'` and `import './b.css'`), or imports a CSS file plus a component library that ships its own CSS as a separate chunk import.
Common situations: Importing Tailwind directives plus a third-party component CSS; an entry that imports two style files for theming; a CSS split caused by async chunking that Vite attributes to the entry's importedCss set.
Related errors
- ${root} resolved to invalid version ${resolved.version} at $
- ${root} snapshot version must be stable semver.
- ${root} snapshot exports must be unique identifiers.
- ${root} snapshot runtime descriptor is invalid.
- Unsupported shared dependency subpath ${specifier} imported
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/95e0c78db615a7b9.
Report an issue: GitHub.