Unapedra
Unapedra

Reputation: 2403

Node script with Typescript and ts-node: can't get to import anything, no matter the configuration and package type

I have a React Typescript project that has multiple files that share a structure. The code is similar, but quite different between them at some points, and it's pretty common to have to copy-paste files and change bits of information to create some of those new files.

I wanted to create a Node script that, asking the user for some input, would do that automatically. As I don't want to create a package for this (as it's a simple script inside my current project), I just created a folder called scripts and put it there.

So, the project structure is as follows:

- node_modules/
- scripts/
  - myScript/
    - helpers/
    - index.ts
    - package.json
- ...
- src/
- tsconfig.json
- package.json

To be able to execute Typescript, I've installed the ts-node library, and I'm executing this script through an npm script in the main project's package.json:

"run-script": "ts-node ./scripts/myScript"

This project already has code inside its src folder, and this scripts folder is a complement that does not belong to the main application's code, and that's why I keep it separated in an external folder, as it acts more as a helping tool for developers in the team.

The problem

Whenever I try to make an import, I get this error:

throw new ERR_MODULE_NOT_FOUND(
          ^
CustomError: Cannot find module ...

If I use dynamic imports for packages and set esModuleInterop in my tsconfig file, everything works fine. But as soon as I try to import a method with a relative path, it breaks:

const inquirer = (await import('inquirer')).default;
const fs = (await import('fs-extra')).default;
const { getCommonPath } = await import('./helpers/getCommonPath');
Error [ERR_MODULE_NOT_FOUND]: Cannot find module ...

I thought that could have to do with the directory path, but if I use __dirname in the import, it tells me it's not allowed because of the format of the path.

If I try to use non-dynamic imports in stead, I get the following error:

(node:106220) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.

But if I do that, then I get this error:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for ...

Then I thought that would have to do with ts-node configuration, and I read to use ts-node-esm. But if I do that, then the first error appears again. I've also tried multiple configurations for ts-node (esModuleInterop: true, esm: true, module: CommonJS|ES2020|ESNext...) without luck.

The question

What do I have to do to be able to write a Node script with Typescript that I can execute from a command line that allows imports of both, external libraries and relative paths (that can be Typescript aswell)?

Thank you!

Upvotes: 4

Views: 12794

Answers (2)

sundhar winston
sundhar winston

Reputation: 49

  1. Install the necessary dependencies by running the command npm install typescript ts-node @types/node.

  2. Create a new tsconfig.json file in the root of your project with the following contents:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "resolveJsonModule": true,
    "sourceMap": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

This configuration file tells TypeScript how to compile your source code into JavaScript.

  1. Create a new src directory in the root of your project and create your TypeScript files in this directory. For example, create a file called main.ts.

  2. Write your TypeScript code in main.ts. You can use import statements to import external libraries and relative paths.

  3. To run your script from the command line, you can use the ts-node command. For example, to run main.ts, you can use the command ts-node src/main.ts.

You can also add a script to your package.json file to make it easier to run your script. For example:

{
  "scripts": {
    "start": "ts-node src/main.ts"
  }
}

Upvotes: 1

Unapedra
Unapedra

Reputation: 2403

Actually I've been able to make it work finally with normal imports. Steps I've followed. I've followed the documentation of ts-node esm:

  • Install ts-node package.
  • Create a package.json inside the scripts folder and set the "type": "module".
  • Add the following configuration to your tsconfig.json:
  "ts-node": {
    "transpileOnly": true,
    "compilerOptions": {
      "module": "ESNext",
      "esModuleInterop": true
    },
    "esm": true // <--- this is the most important part
  },
  "compilerOptions": {...}
  • Add the file extension (.ts) to your relative imports. Eslint-TS will complain, but it's the only way to make it work (otherwise it won't find the module):
import fs from 'fs-extra';
import { getSrcPath } from './helpers/getSrcPath.ts';
  • Run your node script with ts-node in your package.json:
"run-my-script": "ts-node ./scripts/myScript"

Upvotes: 4

Related Questions