evanw / evanw/esbuild

onEnd cannot be used to modify file output

Open
#2,999 6 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
40.1k
Forks
1.3k
PR merge metrics
No merged PRs in 30d

Description

I would like to modify the ouput format of my bundle before esbuild writes it to disk. I thought this would be possible with the onEnd hook, because the documentation says it "can modify the build result before returning". Indeed, this seems to have been possible in the past (see how the callback is written here: https://github.com/marvinhagemeister/karma-esbuild/issues/33#issuecomment-1093042415). However, it looks like you changed the way the hook works in version 0.17.0, and now the files are written before the hook is run.

While I can manually overwrite the files in the hook, this makes the plugin integrate less nicely into esbuild. For correctness, the plugin must manually respect the write option, and the write option should be turned off by the plugin to avoid writing outputs twice. Also, the printed summary shows the size of the output before the onEnd hook runs, making it innaccurate.

For context, my use case is that I'm bundling some bookmarklets, so prefixing my output with javascript: and URI encoding it. My plugin looks like this:

const bookmarkletOutput = {
  name: "bookmarklet-output",
  setup(build) {
    const options = build.initialOptions;

    const write = options.write ?? true;
    options.write = false;

    build.onEnd(async ({ errors, outputFiles }) => {
      if (!errors.length && outputFiles?.length && write) {
        await Promise.all(
          outputFiles.map(async (out) => {
            await outputFile(out.path, encodeURI(`javascript:${out.text}`));
          })
        );
      }
    });
  },
};

What I would like to do is modify the file contents directly, with a plugin that looks like this:

const bookmarkletOutput = {
  name: "bookmarklet-output",
  setup(build) {
    const encoder = new TextEncoder();
    const encodeUTF8 = (text) => encoder.encode(text);

    build.onEnd(({ errors, outputFiles }) => {
      if (!errors.length && outputFiles?.length) {
        outputFiles.forEach((out) => {
          out.contents = encodeUTF8(encodeURI(`javascript:${out.text}`));
        });
      }
    });
  },
};

However, as described above, this doesn't work because the files are seemingly already written, so modifying out.contents has no effect.

For completeness, the plugin is being used with a config that looks like this:

await esbuild.build({
  entryPoints: ["src/some-input.js"],
  bundle: true,
  format: "iife",
  minify: true,
  outdir: "dist/",
  logLevel: "info",
  plugins: [bookmarkletOutput],
});

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the behavior with the provided esbuild.build configuration and the onEnd plugin examples, focusing on initialOptions.write, outputFiles, and when files are written. Trace the onEnd handling and output summary path; done means onEnd can modify outputFiles before disk writes, respects write, and reports final sizes accurately.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, javascript
Domain
build-system
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.