'How to reimport module with ES6 import

I need to reimport module integration-test/integration upon every run as this module can have code dynamically changed in it at run time. I am using NodeJS with experimental modules in order to be able to run ES6 Javascript.

It seems require enables you to delete modules after you require them with the following code delete require.cache[require.resolve('./integration-test/integration.js')]. How do I replicate this with ES6 import?

//FIXME delete cached module here
import("./integration-test/integration").then((module) => {
    console.log('Importing integration');
    stub = module;
    if (module) resolve();
    else reject();
});

I do have a very inelegant solution to this in which I write a new file with a new name and then import that as a different module, however, this is far less than ideal and would like to avoid it if possible due to the memory leak issues.



Solution 1:[1]

You can use query string to ignore cache. See https://github.com/nodejs/help/issues/1399.

Here is the sample code.

// currentTime.mjs
export const currentTime = Date.now();
// main.mjs
(async () => {
  console.log('import', await import('./currentTime.mjs'));

  // wait for 1 sec
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log('import again', await import('./currentTime.mjs'));

  // wait for 1 sec
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log('import again with query', await import('./currentTime.mjs?foo=bar'));
})();

Run this with node --experimental-modules main.mjs.

$ node --experimental-modules main.mjs 
(node:79178) ExperimentalWarning: The ESM module loader is experimental.
import [Module] { currentTime: 1569746632637 }
import again [Module] { currentTime: 1569746632637 }
import again with query [Module] { currentTime: 1569746634652 }

As you can see, currentTime.mjs is reimported and the new value is assigned to currentTime when it is imported with query string.

Solution 2:[2]

I ran into this issue while making an app that basically allowed developers to actively try out their code and make quick on-the-fly changes without recompiling the rest of the project every time. I didn’t want to have to reload the whole app every time a change was made. Using a query string (which is the current accepted answer) works wells when each module is standalone. But as mentioned does not work if the imported module also imports other modules itself. For example: module A imports module B imports module C. If you make changes to C and then try reimporting A or B by adding a query string, then the original version of C is still used and not reimported. The solution I found was to change how I was serving the js files I planned on dynamically (re)importing. I serve those files using a ResourceHandler on a jetty server (in java). So I override it's get Resource method to allow for an optional path part.

  ResourceHandler staticRes = new ResourceHandler() {
    
    @Override
    public Resource getResource(String path) {
      Resource resource = super.getResource(path);
      if( !resource.exists()) {
        int trimIndex = path.indexOf( "/", 1 );
        if(trimIndex > 1) {
          resource = super.getResource(path.substring(trimIndex));
        }
      }
      return resource;
    }
  };
  
  staticRes.setResourceBase( "../plugins/javascript/" );
  
  ContextHandler staticCtx = new ContextHandler();
  staticCtx.setContextPath( "/import" );
  staticCtx.setHandler( staticRes );
  ...

On my client side (TypeScript/JavaScript) I had a helper class that would do the dynamic imports and create the import URL

let importURL = serverURL + "/import/" + Date.now() + "/" + desiredImportFile;

As long as module A B and C all use relative imports then reimporting would import the newest versions of all the files. I am sure there is a better way to do this with jetty; I just knew I could make that way work. I suspect whatever tool you use to serve your dynamic js probably has some way of adding an optional path part as well. This is not really an elegant solution as it means that every time you import a module to use it you will have a different copy but the old imported modules are never cleaned up. This is not really a concern for me as this was really meant for a training and active development application. In production Date.now() would not be added to the path and the server doesn't even need to change.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2