Search code examples
javascriptnode.jsbabeljses6-modules

How to conditionally modify exports from an ES6 Javascript module


I'm wondering if it's possible to somehow still access the module exports of an ES6 module from within the module, like you do in CommonJS with module.exports.

For clarity, I have a js module (Config.js) that I use to export all of my config variables like so.

export const DatabaseName = "myDbName";
export const DatabasePort = 3000;
export const DatabaseHosts = ["174.292.292.32"];
export const MaxWebRequest = 50;
export const MaxImageRequests = 50;
export const WebRequestTimeout = 30;
etc...

And then I have a separate Dev.Config.js file which only holds the overrides for my dev environment.

export const DatabaseHosts = ["localhost"];
export const DatabasePort = 5500;

In my main Config.js file, I have this logic at the bottom.

try {
    var environmentConfig = `./${process.env.NODE_ENV}.Config.js`;
    var localConfig = require(environmentConfig)
    module.exports = Object.assign(module.exports, localConfig)
} catch (error) {
    console.log("Error overriding config with local values. " + error)
}

And then finally, in my consuming code, I'm able to just import my config.js file like so

import * as Config from "./Config.js";

console.log(Config.DatabaseHosts) // Gives me the correct "overridden" value on my dev environment

Currently I've been using babel to transpile my code all back into CommonJS which I guess is how I'm able to sort of mix and match import/export syntax, and still reference module.exports like I've done above.

My question is, how would I replicate this pattern in a pure ES6 module without needing to transpile this using babel where I cannot modify my module.exports from within the module itself?


Solution

  • Conditional exports isn't a supported pattern in ESM.

    In order to modify exported values using dynamic import from another module whose specifier is derived from an environment variable (in a try...catch statement so that a failed attempt won't throw an uncaught exception at the top level), you can modify the structure of your exports so that they are exposed as properties on an object. Below is a reproducible example to demonstrate:

    ./package.json:

    {
      "name": "so-77465699",
      "version": "0.1.0",
      "type": "module",
      "scripts": {
        "dev": "NODE_ENV=Dev node main.js",
        "prod": "NODE_ENV=production node main.js"
      },
      "license": "MIT"
    }
    
    

    ./Dev.Config.js:

    export const Config = {
      DatabaseHosts: ["localhost"],
      DatabasePort: 5500,
    };
    
    export default Config;
    
    

    ./Config.js:

    const Config = {
      DatabaseName: "myDbName",
      DatabasePort: 3000,
      DatabaseHosts: ["174.292.292.32"],
      MaxWebRequest: 50,
      MaxImageRequests: 50,
      WebRequestTimeout: 30,
    };
    
    try {
      // Import a module specifier based on
      // the value of the NODE_ENV environemnt variable.
      // Destructure and rename the default export:
      const { default: envConfig } = await import(
        import.meta.resolve(`./${process.env.NODE_ENV}.Config.js`)
      );
    
      // Iterate the keys and values, updating the existing Config object:
      for (const [key, value] of Object.entries(envConfig)) {
        Config[key] = value;
      }
    } catch (cause) {
      console.log(`Error overriding config with local values: ${cause}`);
    }
    
    export { Config, Config as default };
    
    

    ./main.js:

    // Import named export:
    import { Config } from "./Config.js";
    
    // Alternatively, since it's also the default export:
    // import { default as Config } from "./Config.js";
    
    // Or, using syntax sugar:
    // import Config from "./Config.js";
    
    console.log(Config.DatabaseHosts);
    
    

    In the terminal:

    % node --version
    v20.9.0
    
    % npm run dev
    
    > [email protected] dev
    > NODE_ENV=Dev node main.js
    
    [ 'localhost' ]
    
    % npm run prod
    
    > [email protected] prod
    > NODE_ENV=production node main.js
    
    Error overriding config with local values: Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/node/so-77465699/production.Config.js' imported from /Users/node/so-77465699/Config.js
    [ '174.292.292.32' ]