Search code examples
javascriptnode.jscommand-line-interfacenode-moduleses6-modules

Problem using Dynamic import in Node.js with import()


What am I'm doing wrong when trying to use dynamic import in Node.js code?

I am making a small CLI application. I am using dynamic module import (ES6). When using import at the beginning of the code, everything works as expected. Please see the code below:

#!/usr/bin/env node

const printf = console.log;

/*
 * Module dependencies
 */
import os from 'node:os';
import fs from 'node:fs';
import util from 'node:util';

import chalk from 'chalk';  // Chalk 5 has changed to ESM
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers'


const usage = '\nUsage: $0 [options]';
const version = '0.0.1';
const argv = yargs(hideBin(process.argv))
    .usage(usage)
    .option('c', {
        alias: 'colors',
        description: 'Colored output',
        type: 'boolean',
        default: false,
        demandOption: false
    })
    .help('help', 'Show help message')
    .alias('help', 'h')
    .version('version', version)
    .alias('version', 'v')
    .parse()

/*
  Colored output if option 'colored' is true
*/
if (argv.colors == true) {
    printf(chalk.green(' https://nodejs.org'));
} else {
    printf(' https://nodejs.org');
}

printf(' ' + process.version);


printf('');
process.exit(0)

But I want to import the chalk module only if the option '-c/--colored' is set at startup and the variable argv.colors value is true. I want to use dynamic import with import() keyword code is below:

if (argv.colors == true) {
    // console.log(argv.colors);

    import('chalk')
    .then(chalk => {
        // let msg = chalk.green.bold(final_text);
        // console.log(msg);
        console.log(chalk.green.bold('https://nodejs.org'));
    })
    .catch(err => {
        console.error(err);
    });
} 

But the import fails, no errors occur, this code block doesn't work. If it is possible to do as I want, please tell me how and what is my mistake.

Node version is:

$ node --version
v18.14.2

I tried to use async(), but also didn't get the result:

(async () => {
    const colored = await import('chalk');

    console.log(colored);
})().catch(err => console.error(err));

After @Quentin's comment I made the following changes in the code, and the import() worked as it should. Final version:

// import chalk from 'chalk';  // Chalk 5 has changed to ESM
// import * as format from 'chalk';

if (argv.colors == true) {
      console.log(argv.colors);

      import('chalk')
      .then(({default: chalk}) => {
          console.log(chalk.green.bold(' https://nodejs.org'));
      })
      .catch(err => {
          console.error(err);
      });
} else {
    printf(' https://nodejs.org');
}

// process.exit(0)

Console output:


true
 v18.14.2


 https://nodejs.org

Solution

  • But the import fails, no errors occur, this code block doesn't work.

    When I run that code it errors with:

    TypeError: Cannot read properties of undefined (reading 'bold')


    If I read the MDN documentation it says:

    Each module specifier corresponds to a unique module namespace object, so the following is generally true:

    import * as mod from "/my-module.js";
    
    import("/my-module.js").then((mod2) => {
      console.log(mod === mod2); // true
    });
    

    Note the difference between your original code (import chalk from) and the MDN documentation (import * as mod from).

    Here (.then(chalk => {) the value of chalk is all the exports and not just the default export.

    Thus you should be explicit about what you are exporting (and rename it to a meaningful name):

    .then(({default: chalk}) => {
    

    Then you have a secondary problem:

    process.exit(0);
    

    You're explicitly quitting the program because the asynchronous import has finished and triggered the then callback.

    Don't do that.