In the Node.js Cli tools project developed with TypeScript, the output of tsconfig.json is set to CommonJS.

When importing external dependencies such as chalk, boxen, etc., the latest versions of these packages are pure ES Module packages, resulting in an error like the following and not available.

1
2
3
4
5
6
Error [ERR_REQUIRE_ESM]: require() of ES Module xxx\node_modules\boxen\index.js from abc.ts not supported.
Instead change the require of index.js in xxx.ts to a dynamic import() which is available in all CommonJS modules.
    at Object.newLoader [as .js] (xxx\index.js:141:7)
    at Object.checkPkgUpdate (abc.ts:86:29) {
  code: 'ERR_REQUIRE_ESM'
}

Solution

Through search queries and analysis, the general solutions are as follows.

1. Change your own project to ESM scheme

If possible, change your project to ESM solution, e.g. change "module": "CommonJS" in tsconfig.json to "module": "ESNext". In practice, however, since there are many differences between the CommonJS and ESM solutions, the actual code logic may also need a lot of modification to adapt.

2. Keep using the most recent older version of a dependency package that supports the CommonJs scheme

This solution is fine in principle, and many open source libraries do the same. However, there is a potential security issue in that none of these packages can be updated, and if they and their indirect dependencies are exposed to security problems, these security risks cannot be fixed.

3. Using the await import(...) method

NodeJs’ CommonJS scheme supports the use of the import(...) to dynamically import.

Example:

1
2
3
4
5
// ESM import
import boxen from 'boxen';
 
// Modify to dynamic import method
const { default: boxen } = await import('boxen');

However, since tsc compiles the output as CommonJS, it actually compiles all dynamic imports as require(...) method.

4. use eval("import(...)") method

Based on the above dynamic import scheme, the problem can be solved by simply avoiding tsc compilation. Using the dynamic compilation feature of eval, you can use the following example method to achieve this.

Example.

1
2
3
4
5
import type Boxen from 'boxen';
 
const tips = '...';
const { default: boxen } = await (eval(`import('boxen')`) as Promise<{ default: typeof Boxen }>);
console.log(boxen(defaultTemplate));

After testing, a dynamic compilation solution based on eval solves this kind of problem, for now as a simple interim solution.

Of course, the long-term option that is most in line with the development of community standards is still option 1.