nodejs / help

:sparkles: Need help with Node.js? File an Issue here. :rocket:
1.44k stars 276 forks source link

How do we completely reload/re-import modules in ES6? #2751

Open aqilc opened 4 years ago

aqilc commented 4 years ago

How do we do a complete reload of a module in ES6? I don't spot any cache we can access/delete and I haven't found anything helpful on the internet. This is crucial on some of my projects as it lets me re-import files I've edited without restarting the whole project.

import hello from "./some-file";

// . . . *edits hello*

// how to re-import?
import hey from "./some-file";

I have seen some other issues mentioning methods like "hooks" but there isn't much documentation on them and I don't know how to implement them.

aqilc commented 4 years ago

Why won't anyone answer? 3 issues have been answered/closed after mine. Is there really no way?

devsnek commented 4 years ago

The esm cache is not exposed, and even if it were, it is not mutable. What you want is hot-reload functionality, which has been mentioned in v8:10476.

PoojaDurgad commented 3 years ago

@AqilCont - is this issue resolved?

aqilc commented 3 years ago

Not even close, there's no solution I could find for this project and I just went with complete restarts :<. If there is something new though, I would love some info.

LaKing commented 3 years ago

While researching the same subject, I came up with a hack, that works. It is intended to be a method to develop a function within a running system, where full restarts are time consuming. ...

The key point is that once an import of a file is complete, the same file won't be imported again. However, if you create a symlink to the file, and import that symlink, it will be considered as a new file, thus it can be imported.

Use dynamic import, and attach the exported function to a variable in your running code. Once you edit the source code, create a symlink, and run the import again on that symlink, and replace the imported function on the variable.

aqilc commented 3 years ago

That's actually a pretty cool solution! I might actually try it. I would always prefer a native solution though :< The only problem is that I need dynamic imports for a lot of files, and doing this might make the app a lot laggier or it could get very wasteful, so I could have to work around it in the end anyways. Restarts so far haven't been too costly, and they act sort of as a garbage collection too. I'll see if I can make like a private package for this anyways.

Barnie commented 3 years ago

While researching the same subject, I came up with a hack, that works. It is intended to be a method to develop a function within a running system, where full restarts are time consuming. ...

The key point is that once an import of a file is complete, the same file won't be imported again. However, if you create a symlink to the file, and import that symlink, it will be considered as a new file, thus it can be imported.

Use dynamic import, and attach the exported function to a variable in your running code. Once you edit the source code, create a symlink, and run the import again on that symlink, and replace the imported function on the variable.

It seems that there may be a problem that memory accumulates every time a changed file is reloaded.

reignmaker commented 2 years ago

What about dynamic import with a searchParams in the module name? Something like this:

const handlers = {
  a: await import(`./sample.mjs`) // note the .mjs extension, the module format
}
handlers.a() // call an exported function, for example

if (someCondition) {
  handlers.a = await import(`./sample.mjs?version=${Number((new Date()))}`) // here we add a `searchParam` (could be anything) to force reimport, must change from the last inport to trigger reimport
}

It's implicitly described in the docs https://nodejs.org/api/modules.html#module-caching-caveats Not sure of memory leaks/bloat, though.

Teodor-Iancu commented 1 year ago

What about dynamic import with a searchParams in the module name? Something like this:

const handlers = {
  a: await import(`./sample.mjs`) // note the .mjs extension, the module format
}
handlers.a() // call an exported function, for example

if (someCondition) {
  handlers.a = await import(`./sample.mjs?version=${Number((new Date()))}`) // here we add a `searchParam` (could be anything) to force reimport, must change from the last inport to trigger reimport
}

It's implicitly described in the docs https://nodejs.org/api/modules.html#module-caching-caveats Not sure of memory leaks/bloat, though.

Nice hack, Thank you! Here my two cents: Instead of rename files to *.mjs we can use npm install esm to install esm package, which allows to use ES6 modules in Node.js Then add "start": "node -r esm app.js" to your package.json file. (where app.js is your script you whant to run at start) Then npm start

codespearhead commented 1 year ago
`./sample.mjs?version=${Number((new Date()))}`

Conversely, you can just delete the cached module after have used it:

[1] delete require.cache[require.resolve("././sample.js")]

The imported module doesn't even have to be renamed to *.mjs. (Source)

I'm currently using this technique in the entrypoint of this project to leverage hot module replacement.

@aqilc Does [1] solve your problem? If so, would you mind closing this issue?

aqilc commented 1 year ago

@codespearhead That project and solution is irrelevant to the problem presented here. "require" is not a global support by ESM, and the project you referenced does not use ESM. Typescript supports ESM imports, but it converts them to CommonJS in compilation. I'm talking about projects strictly with "type": "module" in their package.json. The reason this problem isn't exactly solved is because the main workaround causes a memory leak(albeit a small one), and still doesn't offer a proper solution to resetting the cache of a module, like how you could do in previous versions of Node.

tkrotoff commented 8 months ago

@reignmaker or simply:

await import(`./sample.mjs?version=${Date.now()}`);

Date.now() returns the number of milliseconds since January 1, 1970, UTC, example: 1698183378232. It's shorter and more readable than Number((new Date())).

Note: this is strange: ?version=... works, ?u=... works, ?X=... works, but ?v=... doesn't work

kpeters-cbsi commented 6 months ago

FWIW, I tried @tkrotoff 's solution in Typescript and it didn't work. I got an error: Cannot find module './index.ts?version=1704325253966'

mastondzn commented 5 months ago

@kpeters-cbsi works for me, i use tsx to start the node process (with type: "module" ofc), my import looks like this:

import(`./module?version=${Date.now()}`)

note that i am not using a file extension for the import, i tested it with .ts .js .mjs and none worked here, dynamic module works too (also without extension!):

import(`./${file}?version=${Date.now()}`);

not sure about the impacts on memory yet

icetbr commented 1 month ago

This landed on node 22: --experimental-require-module. It allows mocha --watch to work with ESM modules, which was broken feature due to require.cache.

Here is the person who found the "fix" https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/

The docs https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require

Here is where I first saw the merge https://github.com/nodejs/node/pull/51977#issuecomment-2093972575