237
votes

I need to do something like:

if (condition) {
    import something from 'something';
}
// ...
if (something) {
    something.doStuff();
}

The above code does not compile; it throws SyntaxError: ... 'import' and 'export' may only appear at the top level.

I tried using System.import as shown here, but I don't know where System comes from. Is it an ES6 proposal that didn't end up being accepted? The link to "programmatic API" from that article dumps me to a deprecated docs page.

13
My use case: I want to make it easy to have an optional dependency. If the dep is not needed, the user removes it from package.json; my gulpfile then checks if that dependency exists before performing some build steps. - ericsoco
Another use case: for testing purposes. I am using webpack and babel to transpile es6 to es5. Projects like webpack-rewire and similar are not to help here - github.com/jhnns/rewire-webpack/issues/12 . One way to set the test doubles OR to remove problematic dependencies could be the conditional import. - Amio.io
+1. Being able to use a module in multiple environments where dependencies may or may not work is critical, particularly when modules may refer to dependencies that would only work in the browser (e.g. where webpack is used to convert stylesheets into modules that insert the relevant styles into the DOM when they're imported) but the module also needs to run outside of the browser (e.g. for unit testing). - Jules
If this (condition) can be resolved at build time then different preprocessed versions of the product can be prepared and the condition removed. E.g., (condition) is meant to distinguish front end (browser) vs back end (common js). Then the condition statement becomes unnecessary. - Craig Hicks

13 Answers

194
votes

We do have dynamic imports proposal now with ECMA. This is in stage 3. This is also available as babel-preset.

Following is way to do conditional rendering as per your case.

if (condition) {
    import('something')
    .then((something) => {
       console.log(something.something);
    });
}

This basically returns a promise. Resolution of promise is expected to have the module. The proposal also have other features like multiple dynamic imports, default imports, js file import etc. You can find more information about dynamic imports here.

111
votes

If you'd like, you could use require. This is a way to have a conditional require statement.

let something = null;
let other = null;

if (condition) {
    something = require('something');
    other = require('something').other;
}
if (something && other) {
    something.doStuff();
    other.doOtherStuff();
}
66
votes

You can't import conditionally, but you can do the opposite: export something conditionally. It depends on your use case, so this work around might not be for you.

You can do:

api.js

import mockAPI from './mockAPI'
import realAPI from './realAPI'

const exportedAPI = shouldUseMock ? mockAPI : realAPI
export default exportedAPI

apiConsumer.js

import API from './api'
...

I use that to mock analytics libs like mixpanel, etc... because I can't have multiple builds or our frontend currently. Not the most elegant, but works. I just have a few 'if' here and there depending on the environment because in the case of mixpanel, it needs initialization.

9
votes

Looks like the answer is that, as of now, you can't.

http://exploringjs.com/es6/ch_modules.html#sec_module-loader-api

I think the intent is to enable static analysis as much as possible, and conditionally imported modules break that. Also worth mentioning -- I'm using Babel, and I'm guessing that System is not supported by Babel because the module loader API didn't become an ES6 standard.

8
votes

2020 Update

You can now call the import keyword as a function (i.e. import()) to load a module at runtime.

Example:

const mymodule = await import(modulename);

or:

import(modulename)
    .then(mymodule => /* ... */);

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports

3
votes

require() is a way to import some module on the run time and it equally qualifies for static analysis like import if used with string literal paths. This is required by bundler to pick dependencies for the bundle.

const defaultOne = require('path/to/component').default;
const NamedOne = require('path/to/component').theName;

For dynamic module resolution with complete static analysis support, first index modules in an indexer(index.js) and import indexer in host module.

// index.js
export { default as ModuleOne } from 'path/to/module/one';
export { default as ModuleTwo } from 'path/to/module/two';
export { SomeNamedModule } from 'path/to/named/module';

// host.js
import * as indexer from 'index';
const moduleName = 'ModuleOne';
const Module = require(indexer[moduleName]);
2
votes

Important difference if you use dynamic import Webpack mode eager:

if (normalCondition) {
  // this will be included to bundle, whether you use it or not
  import(...);
}

if (process.env.SOMETHING === 'true') {
  // this will not be included to bundle, if SOMETHING is not 'true'
  import(...);
}
1
votes

obscuring it in an eval worked for me, hiding it from the static analyzer ...

if (typeof __CLI__ !== 'undefined') {
  eval("require('fs');")
}
1
votes

Conditional imports could also be achieved with a ternary and require()s:

const logger = DEBUG ? require('dev-logger') : require('logger');

This example was taken from the ES Lint global-require docs: https://eslint.org/docs/rules/global-require

0
votes

I was able to achieve this using an immediately-invoked function and require statement.

const something = (() => (
  condition ? require('something') : null
))();

if(something) {
  something.doStuff();
}
0
votes

Look at this example for clear understanding of how dynamic import works.

Dynamic Module Imports Example

To have Basic Understanding of importing and exporting Modules.

JavaScript modules Github

Javascript Modules MDN

0
votes

No, you can't!

However, having bumped into that issue should make you rethink on how you organize your code.

Before ES6 modules, we had CommonJS modules which used the require() syntax. These modules were "dynamic", meaning that we could import new modules based on conditions in our code. - source: https://bitsofco.de/what-is-tree-shaking/

I guess one of the reasons they dropped that support on ES6 onward is the fact that compiling it would be very difficult or impossible.

0
votes

Import and Export Conditionally in JS

const value = (
    await import(`${condtion ? `./file1.js` : `./file2.js`}`)
).default

export default value