ts-node icon indicating copy to clipboard operation
ts-node copied to clipboard

ts-node should take into account tsconfig paths in esm mode and keep but transpile imports

Open a-x- opened this issue 2 years ago • 7 comments

Search Terms

  • node-ts and tsconfig paths
  • tsconfig paths

what I found:

  • https://github.com/TypeStrong/ts-node/issues/1948
  • https://stackoverflow.com/questions/72549407/how-to-use-tsconfig-paths-with-ts-node
  • https://typestrong.org/ts-node/docs/imports

Expected Behavior

Take tsconfig.json paths into account even in esm mode. Keep imports, but transpile paths.

Actual Behavior

imports keeping as is in esm mode. One good thing I have: VSCode have not TS errors in this setup now, it was hard to accomplish it too.

Steps to reproduce the problem

  1. create tsconfig.json with at least { "compilerOptions": { "paths": { "foo": "src/bar.ts" } } }
  2. run ts-node-esm --project tsconfig.json src/index.ts
  3. write src/index.ts like import foo from 'foo' and create some src/bar.ts file with default export

Minimal reproduction

will be later

Specifications

  • ts-node v10.9.1
  • node v18.16.0
  • compiler v5.0.4

I have complex monorepo setup with 3 tsconfigs and many other things, I'll show later minimal repo if it really needed in this case... It's vitejs front and ts-node BFF for it I trying to implement now with shared utils.

  • Operating system and version: macOS
server/tsconfig.json
{
  "compilerOptions": {
    "composite": true,

    // enable latest features
    "lib": ["esnext"],
    "module": "esnext",
    "target": "esnext",

    // if TS 5.x+
    // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#--moduleresolution-bundler
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "moduleDetection": "force",

    // "jsx": "react-jsx", // support JSX
    "allowJs": true, // allow importing `.js` from `.ts`
    "esModuleInterop": true, // allow default imports for CommonJS modules

    // best practices
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,

    // monorepo
    "paths": {
      "@date-fns": ["../shared/utils/date.server.ts"],
      "shared/*": ["../shared/*"],
      "@front/*": ["../src/*"]
    },
    "baseUrl": ".",
    "plugins": [
      { "name": "typescript-styled-plugin", "validate": false },
      // Fix import absolute paths
      { "transform": "typescript-transform-paths", "useRootDirs": true }, // Transform paths in output .js files
      { "transform": "typescript-transform-paths", "useRootDirs": true, "afterDeclarations": true } // Transform paths in output .d.ts files (Include this line if you output declarations files)
    ],
    "rootDirs": [".", "../src", "../shared"],

    // other
    "types": ["jest"],
    "resolveJsonModule": true
  },
  "ts-node": {
    // It is faster to skip typechecking.
    // Remove if you want ts-node to do typechecking.
    "transpileOnly": true,
    "esm": true, // import/export instead of require/module.exports
    "experimentalSpecifierResolution": "node", // omit .ts in imports
    "require": ["typescript-transform-paths/register", "tsconfig-paths/register"]
  },
  "include": [
    "./**/*",
    "./.eslintrc.cjs",
    "../jest.config.ts",
    // Don't include: "../src/utils/**/*", As it have browser specific, like utils/hooks.ts
    "../src/**/types.ts",
    "../src/**/config.ts",
    "../src/**/*.types.ts",
    "../src/**/*.d.ts",
    "../shared/**/*"
  ],
  "references": [{ "path": "../shared/tsconfig.json" }, { "path": "../src/tsconfig.json" }]
}
shared/tsconfig.json
{
  "compilerOptions": {
    "composite": true,

    // enable latest features
    "lib": ["esnext"],
    "module": "esnext",
    "target": "esnext",

    // if TS 5.x+
    "moduleResolution": "bundler",
    // "allowImportingTsExtensions": true,
    // "noEmit": true,
    "moduleDetection": "force",

    // "jsx": "react-jsx", // support JSX
    "allowJs": true,
    "esModuleInterop": true,

    // best practices
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,

    "plugins": [
      { "name": "typescript-styled-plugin", "validate": false },
      // Fix import absolute paths
      { "transform": "typescript-transform-paths", "useRootDirs": true }, // Transform paths in output .js files
      { "transform": "typescript-transform-paths", "useRootDirs": true, "afterDeclarations": true } // Transform paths in output .d.ts files (Include this line if you output declarations files)
    ],
    "types": ["jest"],
    "resolveJsonModule": true,
    "paths": {
      "@date-fns": ["./utils/date.server.ts", "./utils/date.client.ts"]
    }
  },
  "ts-node": {
    "transpileOnly": true,
    "require": ["typescript-transform-paths/register", "tsconfig-paths/register"]
  },
  "include": ["./**/*"],
  "exclude": ["./.eslintrc.cjs"]
}
package.json (root)
{
  "name": "rbi-ips-toro-ui",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "yarn install && scripts/dev",
    "prod-server": "cd server && yarn run start",
    "build": "echo type check... && tsc && echo build front with vite... && vite build",
    "lint": "eslint src server shared",
    "mocks": "nodemon ./dev-server/devServer.js",
    "test": "NODE_OPTIONS=--experimental-vm-modules jest"
  },
  "dependencies": {
    "@vitejs/plugin-react": "^3.1.0"
    "date-fns": "^2.29.3",
    "date-fns-tz": "^2.0.0",
    "lodash-es": "^4.17.21",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "react-router": "^6.2.1",
    "react-router-dom": "^6.2.1",
    // some deps are omitted 
  },
  "devDependencies": {
    "@jest/globals": "^29.5.0",
    "@typescript-eslint/eslint-plugin": "^5.59.1",
    "@typescript-eslint/parser": "^5.59.1",
    "esbuild": "^0.17.10",
    "esbuild-css-modules-plugin": "^2.7.1",
    "eslint": "^8.39.0",
    "eslint-config-react-app": "^7.0.1",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-unused-imports": "^2.0.0",
    "jest": "^29.5.0",
    "jest-environment-jsdom": "^29.5.0",
    "ts-jest": "^29.1.0",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.0.4",
    "typescript-plugin-css-modules": "^3.4.0",
    "typescript-plugin-styled-components": "^2.0.0",
    "typescript-transform-paths": "^3.4.6",
    "vite": "^4.1.4",
    "vite-jest": "^0.1.4"
    // some deps are omitted 
  }
}
shared/package.json
{
  "name": "shared",
  "type": "module",
  "version": "1.0.0",
  "main": "index.js",
  "license": "ICS",
  "private": true
}

src: package.json and tsconfig.json currently are not interesting as they using for client-side/front

a-x- avatar May 17 '23 10:05 a-x-

my error: ERR_INVALID_MODULE_SPECIFIER
cd server && yarn run start
$ NODE_OPTIONS='--loader=ts-node/esm --experimental-specifier-resolution=node' ts-node -r tsconfig-paths/register --project tsconfig.json ./index.ts
(node:88652) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:88652) ExperimentalWarning: The Node.js specifier resolution flag is experimental. It could change or be removed at any time.
(node:88671) ExperimentalWarning: The Node.js specifier resolution flag is experimental. It could change or be removed at any time.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/wzhalmq/raif/ips-cortex-collateral-ui-application/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:695
    throw new ERR_INVALID_MODULE_SPECIFIER(
          ^
cd server && yarn run start
$ NODE_OPTIONS='--loader=ts-node/esm --experimental-specifier-resolution=node' ts-node -r tsconfig-paths/register --project tsconfig.json ./index.ts
(node:88652) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:88652) ExperimentalWarning: The Node.js specifier resolution flag is experimental. It could change or be removed at any time.
(node:88671) ExperimentalWarning: The Node.js specifier resolution flag is experimental. It could change or be removed at any time.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/wzhalmq/raif/ips-cortex-collateral-ui-application/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:695
    throw new ERR_INVALID_MODULE_SPECIFIER(
          ^
CustomError: ERR_INVALID_MODULE_SPECIFIER @date-fns is not a valid package name /Users/wzhalmq/raif/ips-cortex-collateral-ui-application/shared/utils/date.ts

More context

I have some esm only deps, some cjs only deps and some universal ones. Also I have some shared utils for node and browser.

I need paths in this case for implementing two date-fns adapter variants: for server and client: shared/utils/date.server.ts:

export * from "date-fns";

shared/utils/date.client.ts:

export * from "date-fns/esm";

a-x- avatar May 17 '23 10:05 a-x-

Can I implement it myself? Could you help me with a little advice? Where, and how to fix it in the TS node source code?

a-x- avatar May 18 '23 09:05 a-x-

I also found this:

  • https://github.com/TypeStrong/ts-node#paths-and-baseurl, https://typestrong.org/ts-node/docs/paths
  • https://github.com/TypeStrong/ts-node#why-is-this-not-built-in-to-ts-node

I do use tsconfig-paths/register, but looks it does not work (see my tsconfig.json files)

a-x- avatar May 19 '23 08:05 a-x-

I dig into ts-node and tsconfig-paths and realised: function register(params) {} in tsconfig-paths had not even called before error: CustomError: ERR_INVALID_MODULE_SPECIFIER @date-fns is not a valid package name

there is on good thing, registering is works: tsconfig-paths/register.js typescript-transform-paths/register.js

also, I tried remove typescript-transform-paths or swap requirement order

a-x- avatar May 20 '23 09:05 a-x-

@a-x- I am facing similar issue when I am using paths in tsconfig.json with V8 WDIO.

{
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": ".",
    "esModuleInterop": true,
    "moduleResolution": "node",
    "outDir": "dist",
    "paths": {
      "@Constants/*": [
        "test/constants/*"
      ],
      "@DialogBoxes/*": [
        "test/pageobjects/dialogboxes/*"
      ],
      "@EncryptedData/*": [
        "test/encryptedData/*"
      ],
      "@Locators/*": [
        "test/locators/*"
      ],
      "@Component/*": [
        "test/pageobjects/components/*"
      ],
      "@PageObject/*": [
        "test/pageobjects/*"
      ],
      "@Reporter/*": [
        "customReporters/*"
      ],
      "@TestData/*": [
        "test/testdata/*"
      ],
      "@Util/*": [
        "test/util/*"
      ]
    },
    "resolveJsonModule": true,
    "target": "es2022",
    "module": "esnext",
    "types": [
      "node",
      "@wdio/globals/types",
      "webdriverio/async",
      "@wdio/jasmine-framework",
      "expect-webdriverio"
    ],
    // "strict": true,
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
    "customReporters/",
    "test/",
    "wdio.conf.ts"
  ]
}

Package.json

{ "dependencies": { "@rpii/wdio-report-events": "^8.0.2", "axios": "^0.27.2", "chrome-har": "^0.13.0", "eslint-config-prettier": "^8.5.0", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-prettier": "^4.0.0", "generate-password": "^1.7.0", "heroku-client": "^3.1.0", "log-timestamp": "^0.3.0", "log4js": "^6.4.6", "node-fetch": "^2.6.7", "prettier": "^2.6.2", "randomstring": "^1.2.2", "request": "^2.88.2", "shelljs": "^0.8.5", "simple-statistics": "^7.8.3", "totp-generator": "^0.0.13", "underscore": "^1.13.4", "url": "^0.11.0", "wdio-junit-reporter": "^0.4.4" }, "devDependencies": { "@types/node-fetch": "^2.6.2", "@typescript-eslint/eslint-plugin": "^5.25.0", "@typescript-eslint/parser": "^5.25.0", "@wdio/cli": "^8.14.4", "@wdio/devtools-service": "^8.14.2", "@wdio/dot-reporter": "^8.14.0", "@wdio/jasmine-framework": "^8.1.3", "@wdio/junit-reporter": "^8.1.2", "@wdio/local-runner": "^8.1.3", "@wdio/reporter": "8.14.0", "@wdio/sauce-service": "^8.1.3", "@wdio/selenium-standalone-service": "^8.1.2", "@wdio/spec-reporter": "^8.1.2", "@wdio/sumologic-reporter": "^8.14.0", "aws-sdk": "^2.1354.0", "eslint": "^8.31.0", "eslint-plugin-wdio": "^7.19.4", "mailgun.js": "^8.0.0", "ts-mixer": "^6.0.1", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^4.9.4", "wdio-edgedriver-service": "^3.0.3", "wdio-html-nice-reporter": "8.1.0", "wdio-json-reporter": "^3.0.0", "wdio-tesults-service": "^1.2.1", "wdio-video-reporter": "^4.0.3" }, "engines": { "node": "^20.5.0" }, "name": "ui-e2e-tests", "type": "module", "private": true, "scripts": { "lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix", "wdio": "wdio run wdio.conf.ts" }, "version": "0.1.0" }

Getting Error:

[2023-08-08T12:15:07.941Z] 2023-08-08T12:15:07.941Z ERROR @wdio/runner: Error: Cannot find package '@Util/GetCustomerLoginData' imported from ./test/util/LoginUtil.ts [0-0] at packageResolve (./node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:757:9) [0-0] at moduleResolve (./node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:798:18) [0-0] at Object.defaultResolve (./node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:912:11) [0-0] at ./node_modules/ts-node/src/esm.ts:218:35 [0-0] at entrypointFallback (./node_modules/ts-node/src/esm.ts:168:34) [0-0] at ./node_modules/ts-node/src/esm.ts:217:14 [0-0] at addShortCircuitFlag (./node_modules/ts-node/src/esm.ts:409:21) [0-0] at resolve (./node_modules/ts-node/src/esm.ts:197:12) [0-0] at nextResolve (node:internal/modules/esm/hooks:733:28) [0-0] at Hooks.resolve (node:internal/modules/esm/hooks:242:30) [2023-08-08T12:15:07.941Z] [0-0] Error: Cannot find package '@Util/GetCustomerLoginData' imported from ./test/util/LoginUtil.ts

How did you resolve it?

aaggarwal-sumo avatar Aug 08 '23 12:08 aaggarwal-sumo

This has done the trick for me: https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115

mettini avatar Aug 15 '23 18:08 mettini