DEV Community

Cover image for ES-Next dynamic import
Nathaniel
Nathaniel

Posted on • Edited on

2 1

ES-Next dynamic import

Problem

Recently when writing a project of mine, I've written some javascript like this:

import itemA from '../items/itemA';
import itemB from '../items/itemB';
import itemC from '../items/itemC';
import itemD from '../items/itemD';
Enter fullscreen mode Exit fullscreen mode

I'd really like some syntax like this:

for(const id of ['A','B','C','D']) {
    import (item+id) from '../items/item' + id;
}
Enter fullscreen mode Exit fullscreen mode

Turned out there's this stage 4 proposal of ECMAScript called dynamic import that goes like:

(async () => {
  await import('./my-app.mjs');
})();
Enter fullscreen mode Exit fullscreen mode

and it's supported by all modern browsers as well as node.

Failed Attempt

Then I went on writing some line code like this:

importedItems = await Promise.all(
  itemNames.map(async itemName => {
    try {
      const imported = await import(`../items/${itemName}`);
      logger.debug(`Imported item: ${itemName}`);
      return imported;
    } catch (err) {
      logger.warning(`Failed to import item: ${itemName}`);
      return null;
    }
  })
);
Enter fullscreen mode Exit fullscreen mode

But for later use of importedItems, a TypeError has been raised stating that the importedItems are actually type of Module instead of expected item. I tried manual type casting like this:

return Item(imported);
Enter fullscreen mode Exit fullscreen mode

But it didn't work and since it's not yet standardized feature (Due to be standardized in ECMAScript 2020), there's little information about it so I have to figure it out myself.

After a long time with JSON.stringify() and console.log I finally figured it out.

Solution

It should work like this:

return imported.default;
Enter fullscreen mode Exit fullscreen mode

Full working snippet

importedItems = await Promise.all(
  itemNames.map(async itemName => {
    try {
      const imported = await import(`../items/${itemName}`);
      logger.debug(`Imported item: ${itemName}`);
      return imported.default;
    } catch (err) {
      logger.warning(`Failed to import item: ${itemName}`);
      return null;
    }
  })
);
Enter fullscreen mode Exit fullscreen mode

Reason

The reason is that when we use an import statement like this:

import item from './itemA';
Enter fullscreen mode Exit fullscreen mode

it automatically loads the default export of module 'itemA' into the item.

But when we do expression like dynamic import like this:

(async () => {
  const item = await import('./itemA');
})();
Enter fullscreen mode Exit fullscreen mode

the item is a Module , by accessing Module.default we are able to fetch its default export, same goes for any other exports.

originally posted on: https://blog.llldar.io/Article/View/44

Image of Datadog

Master Mobile Monitoring for iOS Apps

Monitor your app’s health with real-time insights into crash-free rates, start times, and more. Optimize performance and prevent user churn by addressing critical issues like app hangs, and ANRs. Learn how to keep your iOS app running smoothly across all devices by downloading this eBook.

Get The eBook

Top comments (2)

Collapse
 
aminnairi profile image
Amin • Edited

Let's say I have three modules.

$ touch {main,fibonacci,luhn}.mjs
// fibonacci.mjs
function fibonacci(number, oldFibonacci = 1) {
  if (number <= 1) {
    return oldFibonacci
  }

  return fibonacci(number - 1, oldFibonacci * number)
}

export default fibonacci
// luhn.mjs
function luhn(number) {
  return Array.from(number.toString()).reduce(function(sum, digit, index) {
    if (index % 2 === 0) {
      return sum + digit
    }

    const doubled = digit * 2

    if (doubled > 9) {
      return sum + (doubled - 9)
    }

    return sum + doubled
  }, 0) % 10 === 0
}

export default luhn

We could write an intermediary function that will return the default exported module out of a dynamic imported one.

// main.mjs
function importDefault(path) {
  return import(path).then(function(module) {
    return module.default
  })
}

Promise.all([
  importDefault("./fibonacci.mjs"),
  importDefault("./luhn.mjs")
]).then(function([ fibonacci, luhn ]) {
  console.log(fibonacci(5)) // 120
  console.log(luhn(732829320)) // true
  console.log(luhn(732829321)) // false
})

Of course, you can import named exported modules as well like usual.

// main.mjs
Promise.all([
  importDefault("./fibonacci.mjs"),
  importDefault("./luhn.mjs"),
  import("fs")
]).then(function([ fibonacci, luhn, { writeFile } ]) {
  console.log(fibonacci(5)) // 120
  console.log(luhn(732829320)) // true
  console.log(luhn(732829321)) // false

  writeFile("test.txt", "hello world!", function(error) {
    if (error) {
      console.error("test file error")
    } else {
      console.log("test file written")
    }
  })
})

N.B.: if you want to test this code, you'll have to run this command with a compliant version of Node.js supporting experimental modules.

$ node --experimental-modules ./main.mjs
Collapse
 
natelindev profile image
Nathaniel

Nice complement, I didn't mention I was using esm. If anyone want to use the import export without having to name your file .mjs on node, try it out.

SurveyJS custom survey software

JavaScript UI Library for Surveys and Forms

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

View demo

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay