#Issues with custom hot-reloading (cjs/ESNext)

8 messages · Page 1 of 1 (latest)

jovial hedge
#

Hello,

I've run into some headache-ing level issues while trying to implement some sort of custom hot-reloading for my discord bot, using ts compiled to cjs with tsc, running on node
-# You might think it's overkill but it's actually really useful considering i'm running everything on docker

Currently compiling ts to cjs, and i've noticed it looks like in ts-compiled cjs, that imported functions are actually copied locally, now idk if it's a shallow or actual copy (assuming the latter given the issue)
Because when invalidating the cache for the exports of a file, and replacing them with the new content, other files using those exported functions would still use the old, invalidated version, unless i was to also reload those files.

The hot-reloading "toolchain" works as follow:
I have a tsc daemon running, watching for changes on the .ts files
When changes are detected, the new files are compiled to cjs and moved to the mounted volume
Inside the bot is a watcher module that will look for changes on the .js files, invalidate the cache for these files and load the new exported contents

Now, i've noticed that imports work differently on ESM but i am not accostumed yet with ESM and am neither a "pro" of TS.

Would compiling to ESM make my life easier regarding a hot-reload of this sort? Or is there some setting of tsc i'm not aware of to help here?

Sorry for the long read, tried to be as explicit as possible !

#

oh yeah here is my tsconfig.json btw

{
    "compilerOptions": {
        "target": "ESNext",
        "module": "CommonJS",
        "lib": ["ESNext", "dom"],
        "rootDir": "src",
        "outDir": "dist",
        "incremental": true,
        "tsBuildInfoFile": "./.tsbuildinfo",
        "strict": true,
        "importHelpers": true,
        "esModuleInterop": true,
        "resolveJsonModule": true,
        "forceConsistentCasingInFileNames": true,
        "skipLibCheck": true
    },
    "include": ["src"]
}
#

Issues with custom hot-reloading (cjs/ESNext)

crisp rivet
#

Not much of a typescript question but import cache is not publically exposed in esm like it does in cjs with require.cache. So not really, you can't clear them. One way people use is cache busting using dynamic import. File paths behaves like url in node, so everytime you import a file, you append a query param to file path like ./file.js?date=${Date.now()}, so node essentially treats it as new file everytime (since the resulting url will be different) and bypasses the cache.

This does lead to some issues however. As the previous imports are not cleared, it keeps adding to cache everytime you "hot-reload" and may eventually lead to memory leak. That's why it is generally not recommended to use in production only in dev

jovial hedge
#

So I guess there's not really any elegant way to deal with this then

crisp rivet
#

Not with esm yeah. Honestly imo, hot reloading is just not something you should use in production. Maybe in development. Have you thought about using ci/cd ? For example i just push to github, and workflows runs when files are changed which then deploys to docker. You can have more specific checks and stuff. But that's just the superficial desc

jovial hedge
#

I was looking for a solution for dev tbh
-# rather a better-working or easier solution than my current lol
Testing discord bots can be really tedious and having to restart the bot when changing a helper file gets really annoying lol

jovial hedge
# crisp rivet Not with esm yeah. Honestly imo, hot reloading is just not something you should ...

But basically, if in the import url i include that query param, what exactly will happen ?
Will every function exported from another file be loaded up fresh from that file again when used?
Because the behaviour I was trying to create was, if a.js gets changed, its cache entry gets invalidated and repopulated with the new file contents, and then b.js that imports functions from a.js will automatically use the new cached functions instead of the old ones (so depending on the global cache rather than the local copy)