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

Plugin onTransform API #647

Open
intrnl opened this issue Jan 6, 2021 · 3 comments
Open

Plugin onTransform API #647

intrnl opened this issue Jan 6, 2021 · 3 comments
Labels

Comments

@intrnl
Copy link

intrnl commented Jan 6, 2021

This proposes onTransform, a hook that runs transformations on a given file after the file has been loaded by a plugin or esbuild's default loader, and before any transformations done by esbuild itself

function onTransform (
  opts: OnTransformOptions,
  fn: (args: OnTransformArgs) => Promisable<OnTransformResult>
): void;

interface OnTransformOptions {
  filter: RegExp,
  namespace?: string,
  loader?: string,
}

interface OnTransformArgs {
  path: string,
  contents: string,
  namespace: string,
  loader: string,
}

interface OnTransformResult {
  contents?: string | Uint8Array,
  loader?: Loader,
  resolveDir?: string,
  errors?: Message[],
  warnings?: Message[],
  pluginName?: string,
}

The addition of loader in OnTransformArgs means that say, in a plugin that deals with CSS transforms, it wouldn't encounter JS source code whose filename ends with .css, and the plugin is also allowed to change the loader in the result if it encounters something like CSS modules and want to output the class name mappings

import postcss from 'postcss';

let processor = postcss([ /* plugins */ ]);

let postcssPlugin = {
  name: 'postcss',
  setup (build) {
    build.onTransform({ filter: /.*/, loader: 'css' }, async (args) => {
      let result = await processor.process(args.contents, { from: args.path });
      // could do some other stuff here like mapping postcss' messages
      // to esbuild's warnings and errors
      return { contents: result.css };
    });
  },
};

Additionally it might be worthwhile to support passing sourcemaps in result, this should be useful for onLoad as well so this could probably go as a separate issue or something.

@intrnl intrnl changed the title Plugin transform API Plugin onTransform API Jan 6, 2021
@remorses
Copy link
Contributor

remorses commented Apr 9, 2021

onTransform API could be implemented as a js only feature that simply processes the output of existing onLoad plugins

If there are no onLoad plugins present then a default onLoad plugin is used (this can be done simply by adding an onLoad plugin at the end of the plugins list with the same filter regex)

@evanw Would you like a PR for this?

@intrnl
Copy link
Author

intrnl commented Oct 27, 2022

The one thing esbuild has done right with the plugin API, I feel, is the ability for plugins to set loaders on their own during the load hook, this separation of loaders and file paths is huge, and it would be awesome if plugins can arbitrarily define custom loaders that other plugins can tap into as well.

This allows for plugins to have their own virtual file naming scheme that doesn't involve having to name the files in such a way that would invoke the load/transform hooks it wants, something I've observed with Rollup plugins.

This extension to the plugin API is already possible with namespaces in some regards, but I'm not sure if namespaces are fit for this, and I think plugins would appreciate being able to call transforms in their own namespaces as well.

let sveltePlugin = {
  name: 'svelte-plugin',
  async setup (build) {
    // we just want esbuild to know that `.svelte` files should be handled by `svelte` loader
    // this is really the wrong way to go about doing it, but you get the idea.
    build.onResolve({ /\.svelte($|\?)/i, namespace: 'file' }, (args) => {
      return { loader: 'svelte' };
    });

    // any plugins can get this transform without having to name their virtual files as above
    build.onTransform({ loader: 'svelte' }, async (args) => {
      const result = await compile(args.contents, {
        filename: args.path,
      });

      return {
        loader: 'js',
        contents: result.js,
      };
    });
  },
};

I suppose another way would be for plugins to extend the loaders option with its own, and esbuild would check if there's been any plugins so far that has added a load or transform hook for that specific loader name.

@hyrious
Copy link

hyrious commented Oct 27, 2022

One drawback of implementing the onTransform hook in JS side is that you cannot reuse the default loader easily because it is implemented in Go side. You can of course use fs.readFileSync() to mock one though.

@evanw evanw added the plugins label Apr 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants