nodejs / loaders

ECMAScript Modules Loaders
MIT License
130 stars 17 forks source link

Customizing `import.meta` #159

Open GeoffreyBooth opened 1 year ago

GeoffreyBooth commented 1 year ago

I feel like users should be able to customize import.meta. For example, https://github.com/nodejs/node/pull/48740 might very well land in core sometime soon, but it also feels like something that should be achievable via the module customization hooks. Likewise for import.meta.resolveURL, a version of import.meta.resolve that returns an URL instance instead of a string.

I assume this can be done already today by prepending some code into the source returned by the load hook, kind of like adding a polyfill but it’s one that runs for every module. Though I suppose if we create a new hook, that would also run for every module, so the performance impact is the same?

There’s also the question of adding functions/methods to import.meta. Say we create a new importMeta hook, with a signature like importMeta(url, context, next). If this runs on the separate thread with all the other hooks, what happens when the return value of the user’s hook has a function on it, like resolveURL? We can’t transfer that across the thread boundary, can we? Is there some other way to make this work, like making the return value of importMeta be a string to evaluate on the main thread; but if we do that, it’s pretty much the same as prepending to load, I’d think. Are there other approaches?

Maybe in the end the “prepend to load“ approach is the best one, and we should perhaps add an example to the docs. But I thought that this should get some discussion.

aduh95 commented 1 year ago

To give a bit more of context, it's already possible to customize import.meta, using globalPreload, but that's going away. Node.js 20.0.0 up to Node.js 20.5.1 are lacking that feature (it was removed "by accident" in the off-thread PR and was re-introduced by https://github.com/nodejs/node/pull/48779), and no one has complained so I think it's fair to say it's not very high priority 🤷‍♂️

But I agree it's worth discussing, and if someone wanted to implement that, it would probably be well received.

Julien-R44 commented 7 months ago

Hey, i'm working on a small library to have some sort of "HMR" with Node ( using the query params hack in the import path ) and I met this need today, so I wanted to share my use case

I need to add two methods in import.meta : import.meta.hot.dipose and import.meta.hot.decline. Because using import.meta.hot.xxx has become a sort a convention since Vite and co.

So to do this, I ended up injecting code via the load hook. But this poses a problem: it totally breaks the source maps. Now, I don't know much about how source maps work, so maybe it's a simple problem to fix ( I will read more about it ), but I thought it was something that could be avoided if Node offered an API to initialize import.meta.

I think, if an API is proposed, it should also be possible to access other properties in import.meta. In my case, import.meta.hot.decline needs import.meta.url. Here's some silly pseudo code to make my point clearer:

// loader.ts

// Let's say we can do that from a `load` hook
export const load: LoadHook = (url, context, nextLoad) => {
  // maybe using the `context` property? I am not super aware of Nodejs internals
  // so this is probably a dumb idea
  context.initializeImportMeta(importMeta => {
    return {
      hot: {
        dispose: () => {
          // I can access the `import.meta` content through `importMeta`
          myFunction(importMeta.url)
        },
        decline: () => {
          myFunction(importMeta.url)
        }
      }
    }
  })  
}

So having an API for that would allow us to avoid doing code transformations and breaking source maps. Using load for the same result seems a bit hackish anyway

laverdet commented 2 weeks ago

@GeoffreyBooth @joyeecheung (et al) I wanted to get your opinion on this:

Re: https://github.com/nodejs/node/pull/54769 & 3ac5b49d85e4edb9efc7a64e880403d3182bf64c

This commit removes responseURL which previously was an undocumented feature allowing the load hook to override the result of import.meta.url. Joyee suggested I put in a PR to re-add the feature w/ proper tests & documentation but I wanted to consult the loaders council first.

I'm certainly happy to submit a PR but it occurs to me that there may be other plans for import.meta. Would the previous behavior be something we want to support? This is detailed plainly in my comment here.

laverdet commented 2 weeks ago

(Unrelated to the above comment)

@Julien-R44 hot-hook looks cool. I made dynohot which implements similar behavior. I've implemented the linking algorithm from the JS modules specification in the loader runtime so we can hot reload without any explicit boundary. And, for example, if module A depends on module B and module B is reloaded, you don't have to reload module A.