Search code examples
javascript

ES6 export equivalent for conditional static require exports?


I have seen in many projects (like React), they use require() to conditionally re-export properties like so:

// index.js

if (process.env.NODE_ENV === 'production' {
  exports = require('./cjs/react.production.min.js')
} else {
  exports = require('./cjs/react.development.js')
}

I am trying to convert this to its equivalent valid ES6 syntax, preserving individual exported members (for tree shaking).

I have tried this and it works, but you cannot import individual members:

import * as prod from './cjs/react.production.min.js'
import * as dev from './cjs/react.development.js'

let exports = {}

if (process.env.NODE_ENV === 'production') {
  exports = prod
} else {
  exports = dev
}

export default exports

Is there an equivalent for the original using valid ES6 syntax?

I suppose with a smart enough compiler, the assigned keys within the nested modules could be propagated to the top level at build time like this:

import { value_1 as prod_value_1 } from './cjs/react.production.min.js'
import { value_1 as dev_value_1 } from './cjs/react.development.js'

let value_1 = undefined

if (process.env.NODE_ENV === 'production') {
  value_1 = prod_value_1
} else {
  value_1 = dev_value_1
}

export { value_1 /* value_2, etc */ }

UPDATE: A little less janky but still requires knowing the exported key names:

import * as prod from './cjs/react.production.min.js'
import * as dev from './cjs/react.development.js'

let selected = undefined

if (true) {
  selected = prod
} else {
  selected = dev
}

export const { value_1, value_2, etc } = selected

Solution

  • Converting the conditional require() statements in CommonJS to ES6 syntax while preserving tree shaking capabilities can be a bit challenging. The approach you've described seems to be on the right track.

    However, it's important to understand that in ES6, import and export statements are static and can't be used inside conditional blocks like if statements.

    The approach you've described in the latter part of your question, where you import individual members and then conditionally assign them, is one way to handle this.

    Here's an expanded and slightly refined version of your approach:

    import * as prod from './cjs/react.production.min.js';
    import * as dev from './cjs/react.development.js';
    
    let finalExports = {};
    
    const keys = Object.keys(prod);
    keys.forEach(key => {
      finalExports[key] = process.env.NODE_ENV === 'production' ? prod[key] : dev[key];
    });
    
    // Explicitly export each member
    export const {
      Component1,
      Component2,
      // ... other exports
    } = finalExports;
    

    But this has some downsides:

    1. Explicit Listing of Exports: You need to manually list all the exports. This can be cumbersome and error-prone, especially if the number of exports is large or changes frequently.
    2. Build-Time Optimization: While this approach may work at runtime, it might not be as efficient for build-time optimizations like tree shaking. Since the actual export used is determined at runtime, static analysis tools might not be able to determine which exports are actually used in the consuming code.
    3. Bundle Size: Both prod and dev versions are imported and included in the bundle, which could lead to a larger bundle size.

    An alternative approach is to use a build tool like Webpack or Rollup to handle environment-specific bundling. These tools can replace imports based on the environment during the build process, which allows for more efficient tree shaking and smaller bundle sizes.

    Here's an example using Webpack's DefinePlugin:

    // webpack.config.js
    
    const webpack = require('webpack');
    
    module.exports = {
      // ... other webpack config
      plugins: [
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
        })
      ],
      resolve: {
        alias: {
          'react': process.env.NODE_ENV === 'production' 
                   ? './cjs/react.production.min.js' 
                   : './cjs/react.development.js'
        }
      }
    };
    

    In your source code, you would then just import from react, and Webpack will handle replacing it with the correct file based on the environment.