Reputation: 334
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';
Upvotes: 8
Views: 5770
Reputation: 824
As a maintainer of file-type
we recommendation to migrate your project to ESM, as the best future proof solution.
But if you want to stick to CommonJS with your TypeScript project, you can load the pure file-type
ES-Module the following way:
import {loadEsm} from 'load-esm';
(async () => {
const fileType = await loadEsm<typeof import('file-type')>('file-type');
})();
Possibly loading of an ES-Module is less of a problem starting from Node.js version 22, as require()
seems to have some ES-Module loading capabilities.
Upvotes: 1
Reputation: 334
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.
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.
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)
Upvotes: 4