Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

onEnd cannot be used to modify file output #2999

Open
MageJohn opened this issue Mar 16, 2023 · 3 comments
Open

onEnd cannot be used to modify file output #2999

MageJohn opened this issue Mar 16, 2023 · 3 comments

Comments

@MageJohn
Copy link

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: marvinhagemeister/karma-esbuild#33 (comment)). 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],
});
@KonnorRogers
Copy link

Would something like this work?

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

    build.onLoad({ filter: /.*/ }, (args) => {
      return {
        contents: encodeUTF8(encodeURI(`javascript:${fs.readFileSync(args.path)}`));
      }
    });
  },
};

@MageJohn
Copy link
Author

I don't think so; if I understand that correctly, that will operate on every file that's loaded, whereas I want to operate on the bundled output.

@mayank1513
Copy link

mayank1513 commented Sep 30, 2023

I was facing same issue, I fixed it using following approach

setup(build) {
    const write = build.initialOptions.write;   
    build.initialOptions.write = false;
    ...
    build.onEnd(result => {
       outputFiles.forEach(f =>
         ... 
          f.contents = new TextEncoder().encode(newContents);
         ...
       }
       ...
       if (write === undefined || write) {
           result.outputFiles?.forEach(file => {
           fs.mkdirSync(path.dirname(file.path), { recursive: true });
           fs.writeFileSync(file.path, file.contents);
       });  
    }

Checkout the plugin here - https://github.com/mayank1513/esbuild-plugin-react18

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants