denoland/deno · error

failed to locate output for HTML entry

Error message

failed to locate output for HTML entry '{}'; {js_entry_name}

What it means

During `deno bundle` of an HTML entrypoint, Deno looks up the JavaScript output esbuild produced for the page's script entry by its internal entry name. If no output file matches, this error fires: the HTML pipeline needs that JS output to rewrite the `<script>` tag and hash-rename files, so it cannot proceed.

Solutions

  1. Make sure the HTML references at least one real, non-empty local script module
  2. Use only `--outdir` with HTML entrypoints (drop `--output`)
  3. Clear the outdir and re-run to rule out stale hashed outputs colliding with the lookup
  4. If it persists with a plain page, file a deno issue with the HTML and exact command — this is an internal invariant

Example fix

<!-- before: empty script module -->
<script type="module" src="./empty.js"></script>

<!-- after: script with actual local code -->
<script type="module" src="./main.js"></script>
Defensive patterns

Strategy: validation

Validate before calling

// Before bundling, verify every script entry referenced by the HTML is a non-empty local file
const html = await Deno.readTextFile("index.html");
for (const m of html.matchAll(/<script[^>]+src="([^"]+)"/g)) {
  const url = new URL(m[1], import.meta.resolve("./index.html"));
  if (url.protocol !== "file:") throw new Error(`script ${m[1]} is not local`);
  const stat = await Deno.stat(url);
  if (stat.size === 0) throw new Error(`script ${m[1]} is empty`);
}

Prevention

When it happens

Trigger: Bundling an HTML page where esbuild emitted no matching JS entry output — the referenced script module is empty or fully externalized, output naming was altered, or the response shape changed between esbuild versions.

Common situations: An HTML page whose script points at an empty module or one with no local code; stale hashed outputs in the outdir from a previous run; mixing `--output` with HTML entrypoints.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/4117db53ea0d026f. Report an issue: GitHub.

Appendix: source

Thrown at cli/tools/bundle/html.rs:509

    }

    let entry_name = format!("{}{}", self.entry_name, VIRTUAL_ENTRY_SUFFIX);
    let js_entry_name = format!("{}.js", entry_name);

    let mut js_out_no_hash = None;
    let js_out = html_output_files
      .get_and_update_path(&js_entry_name, |p, f| {
        let p = p.to_string_lossy();
        js_out_no_hash = Some(
          p.replace(entry_name.as_str(), &original_entry_name)
            .replace(&format!("-{}", f.hash), "")
            .into(),
        );

        p.replace(entry_name.as_str(), &original_entry_name).into()
      })
      .ok_or_else(|| {
        anyhow::anyhow!(
          "failed to locate output for HTML entry '{}'; {js_entry_name}",
          self.entry_name
        )
      })?;
    let html_out_path = js_out_no_hash
      .unwrap_or_else(|| js_out.clone())
      .with_extension("html");

    // esbuild emits the external/linked sourcemap (`*.js.map`) for the entry
    // under the internal virtual-entry name. Rename it to match the renamed JS
    // bundle (e.g. `index-HASH.js.map`) and fix the JS `sourceMappingURL`
    // reference so the emitted output doesn't leak the `deno-bundle-html.entry`
    // implementation detail. See denoland/deno#30750.
    let map_entry_name = format!("{}.js.map", entry_name);
    let mut old_map_name = None;
    let new_map_out =
      html_output_files.get_and_update_path(&map_entry_name, |p, _| {
        old_map_name = p

View on GitHub (pinned to 9ad36f7a2c)