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

Bun Runtime plugin onLoad method is not being called #9446

Open
TomasHubelbauer opened this issue Mar 15, 2024 · 2 comments
Open

Bun Runtime plugin onLoad method is not being called #9446

TomasHubelbauer opened this issue Mar 15, 2024 · 2 comments
Labels
bug Something isn't working bundler Something to do with the bundler

Comments

@TomasHubelbauer
Copy link

TomasHubelbauer commented Mar 15, 2024

What version of Bun is running?

1.0.31+e25675121

What platform is your computer?

Darwin 23.4.0 arm64 arm

What steps can reproduce the bug?

Follow the Runtime Plugins example to create a catch-all custom plugin:

import { plugin } from "bun";

plugin({
  name: "test",
  setup(build) {
    build.onLoad({ filter: /.*/ }, async (args) => {
      console.log("onLoad", args);
      throw new Error("Not implemented");
    });

    build.onResolve({ filter: /.*/ }, async (args) => {
      console.log("onResolve", args);
      throw new Error("Not implemented");
    });

    console.log("setup", build);
  },
});

Register the plugin in bunfig.toml:

preload = ["./test-plugin.ts"]

Run a test script to see what it does:
bun -e "import test from './hi.txt'; console.log(test);"

What is the expected behavior?

onResolve to be called if it is specified and matches, onLoad to also be called if it is specified and matches.

My ultimate goal is to create a plugin for web import support, so I will change the filter to https?:\/\/ once this issue is solved and then will attempt to fetch the URL and return { sourcecode } in the onLoad hook.

What do you see instead?

onResolve is called:

setup {
  target: "bun",
  onLoad: [Function: onLoad],
  onResolve: [Function: onResolve],
  module: [Function: module],
}
onResolve {
  path: "./hi.txt",
  importer: "/Users/tom/Desktop/tom-and-kate/[eval]",
}
onResolve {
  path: "./hi.txt",
  importer: "/Users/tom/Desktop/tom-and-kate/[eval]",
}

onLoad is not called.

Additional information

When I remove the onResolve from my plugin and leave only onLoad, it is not called:

import { plugin } from "bun";

plugin({
  name: "test",
  setup(build) {
    build.onLoad({ filter: /.*/ }, async (args) => {
      console.log("onLoad", args);
      throw new Error("Not implemented");
    });

    console.log("setup", build);
  },
});

Run: bun -e "import test from './hi.txt'; console.log(test);":

setup {
  target: "bun",
  onLoad: [Function: onLoad],
  onResolve: [Function: onResolve],
  module: [Function: module],
}
error: Cannot find module "./hi.txt" from "/Users/tom/Desktop/tom-and-kate/[eval]"
@TomasHubelbauer TomasHubelbauer added the bug Something isn't working label Mar 15, 2024
@Electroid
Copy link
Contributor

Related: #9373

@Electroid Electroid added the bundler Something to do with the bundler label Mar 15, 2024
@TomasHubelbauer
Copy link
Author

This was caused by my not reading the documentation properly. I was trying to use a runtime plugin's onLoad with a non-existent path in the module specifier and it seems like that is not supported.

I learnt about virtual modules, which seems like what I want and I was able to make one work, but the specifiers there need to be strings, not regexes, which foils my plan of adding a loader for a custom protocol.

I see a note about virtual modules not being available for bundler plugins and the possibility of using a combination of onResolve and onLoad to replace them in that context. I looked at what onResolve does and the documentation is not too complete on that, but from what I was able to gather, it seems as though I am able to translate an arbitrary import specifier into a path which I can then run the loader against:

import { plugin } from "bun";

plugin({
  name: "test",
  setup(build) {
    console.log("Plugin setup was called!", build);

    build.onResolve({ filter: /some:/ }, (args) => {
      console.log("Plugin onResolve was called!", args);
      return { path: './some.thing' };
    });

    build.onLoad({ filter: /\.thing$/ }, (args) => {
      console.log("Plugin onLoad was called!", args);
      return { contents: JSON.stringify(args) };
    });
  },
});

However it looks like onResolve only gets applied to path import specifiers, not arbitrary/custom protocol ones? So ultimately this is not possible to achieve as of current I don't think.

Could any maintainers confirm my findings here, please?

If I am correct, I will create a feature request ticket for either loosening the requirement for the module/package to exist prior to the loader being applied to it or perhaps for a new loader hook whose regex will be applied against the bare specifier string not the path.

I am imagining there should be a way to do something like this to make import something from 'proto:col'; (where the col part is variable) work:

import { plugin } from "bun";

plugin({
  name: "test",
  setup(build) {
    console.log("Plugin setup was called!", build);

    build.onLoad({ filter: /proto:/ }, async (args) => {
      console.log("Plugin onLoad was called!", args);
      await ;
      return { contents: 'export default "yay!";', loader: 'ts' };
    });
  },
});

Or potentially this?

import Bun, { plugin } from "bun";
import fs from 'fs';

plugin({
  name: "test",
  setup(build) {
    console.log("Plugin setup was called!", build);

    build.onResolve({ filter: /proto:/ }, async (args) => {
      console.log("Plugin onResolve was called!", args);

      // Drop a temporary file here
      await Bun.write('./some.thing', 'YAY');
      return { path: './some.thing' };
    });

    build.onLoad({ filter: /\.thing$/ }, (args) => {
      console.log("Plugin onLoad was called!", args);

      // Delete the temporary file (use it or not)
      await fs.promises.unlink('./some.thing');

      // Return whatever virtual content I want for the `proto:col` URL here
      return { contents: JSON.stringify(args) };
    });
  },
});

Or maybe the simplest? This would be the best actually, I think.

import { plugin } from "bun";

plugin({
  name: "test",
  setup(build) {
    console.log("Plugin setup was called!", build);

    build.module(/some:/, async () => {
      console.log("Plugin module was called!");
      await ;
      return { contents: 'export default "yay!";', loader: 'ts' };
    });
  },
});

Thanks for the feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working bundler Something to do with the bundler
Projects
None yet
Development

No branches or pull requests

2 participants