DEV Community

Cover image for ES-Next dynamic import
Nathaniel
Nathaniel

Posted on • Edited on

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

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.