halo-dev/halo · error · Error

ESM UI provider output must contain one entry with a default

Error message

ESM UI provider output must contain one entry with a default PluginModule export.

What it means

Thrown in the generateBundle hook of the halo:esm-ui-provider Vite plugin when the ESM output does not contain exactly one entry chunk that exports 'default'. The ESM provider contract requires a single entry module with `export default definePluginModule(...)` (a PluginModule) so the host can discover and activate it; zero entries, multiple entries, or a missing default export all violate the contract.

Source

Thrown at ui/packages/ui-plugin-bundler-kit/src/vite-esm.ts:46

    },
    async transform(code, id) {
      if (!id.includes("\0")) {
        await validator.validateSource(code, id);
      }
    },
    generateBundle: {
      order: "post",
      async handler(_outputOptions, bundle) {
        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({

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Ensure the entry file has exactly `export default definePluginModule({ ... })` as its default export.
  2. Configure rollupOptions.input (or build.lib) to a single entry file for the ESM build.
  3. Grep the entry for 'export default' and confirm it is the PluginModule; remove any secondary entry inputs from the ESM build config.

Example fix

// before (entry.ts)
export const plugin = definePluginModule({ routes: [] });

// after
export default definePluginModule({ routes: [] });
Defensive patterns

Strategy: validation

Validate before calling

// Pre-build check: ensure the entry file has a single default PluginModule export.
import { readFileSync } from "node:fs";
const entry = readFileSync("src/index.ts", "utf8");
if (!/export\s+default\s+definePluginModule/.test(entry)) {
  throw new Error("Entry must `export default definePluginModule(...)`.");
}
// And in vite.config ensure a single input for the esm build.

Type guard

function hasSingleDefaultEntry<T extends { isEntry: boolean; exports: readonly string[] }>(chunks: T[]): boolean {
  const entries = chunks.filter((c) => c.isEntry);
  return entries.length === 1 && entries[0].exports.includes("default");
}

Prevention

When it happens

Trigger: Build output has 0 entry chunks (e.g. build.lib misconfigured, or all chunks are non-entry), 2+ entry chunks (multiple inputs), or exactly one entry whose exports array lacks 'default' (the entry uses named exports only, or exports nothing).

Common situations: Vite build.rollupOptions.input lists more than one entry; the entry file calls definePluginModule but assigns it to a const without a default export; a tool/plugin strips the default export; entry is a CSS/asset-only chunk.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/355a859a7e32fd8b. Report an issue: GitHub.