I'm building a full-stack web application in a monorepo managed by NX, the back-end server is a NodeJS/Express monolith written in typescript (this app was created using nx g @nx/node:application app).

The structure of the monorepo is as follows

root/
  apps/
    api/
      config/
        config.ts // This builds a config object from env vars, etc...
        index.ts // index file exporting config object
      src/
        routes/..
        resources/..
        app.ts
        init.ts
        main.ts // entry point
      tsconfig.app.json // extends ./tsconfig.json
      tsconfig.json // extends ../../tsconfig.base.json
  project.json
  package.json
  tsconfig.base.json

For local development I'm using the generated nx serve app command, compilation is successful however when node tries to run the compiled Javascript I'm greeted with this error:

Error: Directory import 'dist/apps/api/apps/api/config' is not supported resolving ES modules imported from dist/apps/api/apps/api/src/main.js
    at new NodeError (node:internal/errors:406:5)
    at finalizeResolution (node:internal/modules/esm/resolve:227:11)
    at moduleResolve (node:internal/modules/esm/resolve:845:10)
    at defaultResolve (node:internal/modules/esm/resolve:1043:11)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:383:12)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:352:25)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:228:38)
    at ModuleLoader.import (node:internal/modules/esm/loader:315:34)
    at importModuleDynamically (node:internal/modules/cjs/loader:1164:37)
    at importModuleDynamicallyWrapper (node:internal/vm/module:431:21)

I've not seen this error in past typescript apps I've written. So I created a reference app, in the same monorepo, generated with nx g @nx/node:application reference to see if the same error would appear. I structured it like this:

root/
  apps/
   reference/
     foo/
       foo.ts
       index.ts
     src/
       main.ts
     // the default generated tsconfig's from nx

Code:

reference/foo/foo.ts

export const foo = () => console.log('Foo');

reference/foo/index.ts

export * from './foo';

reference/src/main.ts

import * as Foo from '../foo';
Foo.foo();

I've also tried refactoring the import to target the file directly: import { config } from '../config'; -> import { config } from '../config/config';. But I end up with this error:

Error: Cannot find module 'dist/apps/api/apps/api/config/config' imported from dist/apps/api/apps/api/src/main.js
    at new NodeError (node:internal/errors:406:5)
    at finalizeResolution (node:internal/modules/esm/resolve:233:11)
    at moduleResolve (node:internal/modules/esm/resolve:845:10)
    at defaultResolve (node:internal/modules/esm/resolve:1043:11)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:383:12)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:352:25)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:228:38)
    at ModuleLoader.import (node:internal/modules/esm/loader:315:34)
    at importModuleDynamically (node:internal/modules/cjs/loader:1164:37)
    at importModuleDynamicallyWrapper (node:internal/vm/module:431:21)

The reference app built and ran without error. So my guess is that something is wrong with my TypeScript configuration in my api app. I can't find anything online short of implementing babel to transpile my compiled JS - I shouldn't have to do this as reference works fine. I've included the config for api below.

tsconfig.base.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "allowUnusedLabels": false,
    "alwaysStrict": true,
    "baseUrl": ".",
    "declaration": false,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "forceConsistentCasingInFileNames": true,
    "importHelpers": true,
    "importsNotUsedAsValues": "remove",
    "incremental": true,
    "isolatedModules": true,
    "noImplicitOverride": false,
    "noImplicitReturns": false,
    "noUnusedLocals": true,
    "strict": true,
    "rootDir": ".",
    "sourceMap": true,
    "moduleResolution": "Node",
    "target": "ES2020",
    "module": "esnext",
    "lib": [
      "es2020",
      "dom"
    ],
    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    "resolveJsonModule": true
  },
  "exclude": [
    "node_modules",
    "tmp"
  ]
}

apps/api/tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "files": [],
  "references": [
    {
      "path": "./tsconfig.app.json"
    },
    {
      "path": "./tsconfig.spec.json"
    }
  ],
  "compilerOptions": {
    "esModuleInterop": true
  }
}

apps/api/tsconfig.app.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/out-tsc",
    "module": "commonjs",
    "types": [
      "node"
    ]
  },
  "exclude": [
    "jest.config.ts",
    "src/**/*.spec.ts",
    "src/**/*.test.ts"
  ],
  "include": [
    "src/**/*.ts",
    "config/**/*.ts"
  ]
}

It's worth mentioning that main.ts imports config asynchronously in an initialization function: const { config } = await import('../config');. In the compiled javascript tsc keeps it as:

  const { config } = await import("../config");

Whereas for the reference app, the import is converted to:

var foo = __toESM(require("../foo"));

Any help would be incredibly appreciated!

0

There are 0 answers