8

I have changed the code of a project made in NodeJS to Typescript. Everything is working fine, except for the fact that apparently a third-part package (file-type, https://www.npmjs.com/package/file-type) does not seem to accept the require that is generated in the compiled .js files. To change that, I have to change the "module" property of tsconfig.json to another value other than "commonjs". However, it breaks the code and generates a lot of problems.

My tsconfig.json:

{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs",
        "allowJs": true,
        "lib": ["ES6"],
        "esModuleInterop": true,
        "moduleResolution": "node",
        "outDir": "build",
        "rootDir": "src",
        "skipLibCheck": true,
        "strict": true
    }
}

The error I get:

const filetype = __importStar(require("file-type"));
                              ^

Error [ERR_REQUIRE_ESM]: require() of ES Module C:\Users\user\Desktop\repos\art-api\node_modules\file-type\index.js from C:\Users\user\Desktop\repos\art-api\build\middlewares\process-image.js not supported.
Instead change the require of index.js in C:\Users\user\Desktop\repos\art-api\build\middlewares\process-image.js to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (C:\Users\user\Desktop\repos\art-api\build\middlewares\process-image.js:33:31)
    at Object.<anonymous> (C:\Users\user\Desktop\repos\art-api\build\controllers\artworkControllers.js:19:25)
    at Object.<anonymous> (C:\Users\user\Desktop\repos\art-api\build\routers\artworkRouter.js:7:30)
    at Object.<anonymous> (C:\Users\user\Desktop\repos\art-api\build\server.js:9:41) {
  code: 'ERR_REQUIRE_ESM'
}

Apparently, the problem is that the code generated in JavaScript conflicts with the code of the package, that uses the ES6 export syntax. If this is correct, how can I fix this issue? Is there a way to generate .js code with import syntax only for this particular package, or some workaround like that? Other parts of the code don't give me any problems, only the import of this package(file-type).

Just in case, this is the index.js of 'file-type', where it has the import the compiler is complaining about:

import * as strtok3 from 'strtok3';
import {fileTypeFromTokenizer} from './core.js';

export async function fileTypeFromFile(path) {
    const tokenizer = await strtok3.fromFile(path);
    try {
        return await fileTypeFromTokenizer(tokenizer);
    } finally {
        await tokenizer.close();
    }
}

export * from './core.js';

wrongbyte
  • 314
  • 4
  • 11
  • 1
    I encountered the exact same issue earlier today trying to use https://www.npmjs.com/package/callsites with typescript, wasn't able to find a solution. – axtck Jan 24 '22 at 21:38
  • 2
    FWIW, [here](https://nodejs.org/dist/latest-v16.x/docs/api/esm.html#interoperability-with-commonjs)'s the Node.js documentation on using ESM and CommonJS together. I don't know how TypeScript has to be configured to handle this though. – FZs Jan 24 '22 at 22:03
  • @axtck I finally found a way to solve it, try to test if this solution works for you – wrongbyte Jan 31 '22 at 01:06
  • @wrongbyte will take a look! – axtck Feb 06 '22 at 12:09
  • "*I have changed the code of a project made in NodeJS to Typescript.*" - are you saying that you had a working version of the project in plain JS? What import style did it use? – Bergi May 04 '22 at 22:28
  • @Bergi yes. First I was using require(), but then I changed to ES6 modules (import), and a few time later, changed it from JS to TS (and in the meantime I started using the problematic package that led me to post this question) – wrongbyte May 06 '22 at 23:29

1 Answers1

3

Short answer: no; you will have to choose either CommonJS or ES6 modules.

Well, after some research I was able to understand and fix the issue.

The point is that the package I am using, file-type, does not have backwards compatibility with CommonJS require() syntax. Therefore, when setting

"module": "commonJS"

In my tsconfig.json file, the equivalent transpiled javascript file would use require(); more specifically, this way:

const file_type_1 = __importDefault(require("file-type"));

Or some variations like:

const filetype = __importStar(require("file-type"));

It conflicted with the index.js file of file-type, which uses a default export, as you can see on the code posted along with my question.

So, as I intended to keep using this package, I had to change the entire import/export system of the files, what involved the following steps:

1 - Change "module": "commonJS" to "module": "ES6" on tsconfig.json

It will probably result on errors such as "Cannot use import statement outside a module". It happens because we've just changed the syntax of transpiled .js files, that now have the import/export syntax instead of require(). In order to make NodeJS handle this, we must follow the second step:

2 - Add "type":"module" on package.json

3 - Change extension of relative imports

Second step will solve the first problem, but then we will have to use the full path when importing files - it is, adding .js on the relative imports. So,

import getImageRouter from './routers/imageRouter';

becomes

import getImageRouter from './routers/imageRouter.js';

Yes, even though it seems weird, we use the .js extension on the imports. Typescript will be smart enough to resolve it for us during compilation time.

These steps solved the issue for me.

References for further reading:

Great explanation of tsconfig.json properties: https://medium.com/@tommedema/typescript-confusion-tsconfig-json-module-moduleresolution-target-lib-explained-65db2c44b491

ECMAScript Modules in Node.js: https://www.typescriptlang.org/docs/handbook/esm-node.html

A bit more about importing files with .js extension: Appending .js extension on relative import statements during Typescript compilation (ES6 modules)

wrongbyte
  • 314
  • 4
  • 11