22

I've been looking around the web to try and find s nice solution to my problem but so far I have not.

I have a NX monorepo with NextJS and I am trying to create a global types/ folder that can be used by all of my apps and libs.

I had thought this would be quite simple, I would add a folder to the root and put all my .d.ts files in there. Then in the tsconfig.base.json I would add

"typeRoots": [
  "node_modules/@types",
  "node_modules/next/types",
  "types/"
]

I was hoping it would be as simple as that.

Allowing me to use my typing anywhere within the monorepo without having to import them.

Unfortunately, this didn't work. After lots of messing around I did manage to get it to work but only if the types/ folder was inside of the app, for example:

apps/
    /myApp/
          /types/            <- types here were detected
types/                       <- types here were not detected
tsconfig.base.json

This is no good for me as it would mean I would have to duplicate my types across all apps and libs.

The official recommendation from NX is to add a new lib just to store your types and then import it and use it in each of your apps and libs but this feels really clunky to me. Also, this doesn't really work with the idea of adding it to typeRoots within the tsbase.config.json

If anyone knows a better way this can be done I would love to hear about it.

Cheers.

7 Answers 7

13

Battling with the same issue right now. So far, this works for my project, but it's still not an ideal solution. I would much rather have a global provider.

apps/
    /myApp/
          tsconfig.app.json
types/                       <- add your .d.ts files here
tsconfig.base.json

in the tsconfig.app.json of each app add the following:

"include": ["../../types/**/*.d.ts"]

This will import all the type definitions stored in the global folder. But the configuration still has to be handled manually in each library and app.

Let me know if this is helpful or if you have found a better solution.

4
  • Thanks for the answer. As I mentioned in my question this is the suggested way to do it by NX but I was hoping to find a solution a little cleaner that didn't involve repeating code or having to remember to do this when setting up a new lib or app. Commented Feb 1, 2022 at 10:18
  • 2
    I don't hate this solution :) Commented Jan 31, 2023 at 0:05
  • I set a global folder in libs/util/src/types/theme.d.tsand then updated my tsconfig.app.json in each micro-frontend inside apps with "include: [..., "../../libs/util/src/types/*.d.ts"]" and it worked. Thank you!
    – niltonxp
    Commented Mar 17, 2023 at 20:50
  • this works for tsconfig.json as well as tsconfig.app.json Commented Mar 23, 2023 at 11:56
4

I cracked this problem in my repo this way: In the tsconfig.base.json add "./types to "typeRoots".

"typeRoots": [..., "./types", ...]

Each type (in tsconfig "types") you want to reference should be in separate folder in index.d.ts file:

types/my-own-module/index.d.ts

Then you can add the typings to tsconfigs (tsconfig.lib.json, tsconfig.app.json) in each project:

"types": [..., "my-own-module", ...]

The only downside is, that you need to add the typings to each project you need, it does not work globally, because usually each project has different "types" config.

An untested idea is to remove "types" from all configs and just add all needed types into the global tsconfig.

6
  • 1
    Do you have to create a directory + index.d.ts for every single type you want to declare globally? O.o
    – bombillazo
    Commented Nov 23, 2021 at 0:54
  • No, you can has as many types you want in single index.d.ts file, but I reference type as in tsconfig "types" array.
    – kvetis
    Commented Nov 26, 2021 at 13:59
  • Thanks for taking the time to reply, this solution looks promising, although a little bloated with all of the folders, I haven't had time to check this but will come back to you when I do. Commented Feb 1, 2022 at 10:19
  • I'm not saying it is the most beatiful, but it works for me. Personally I'm using it to declare file-loader! imports in many many tsconfigs in my monorepo - I only add ['file-loader'] to types. If you worry about that many folders maybe you could have i single shared index.d.ts in which you'd have everything.
    – kvetis
    Commented Feb 8, 2022 at 12:15
  • 1
    this is the first thing to work after more than a day on it. i really appreciate this answer
    – ALFmachine
    Commented Jul 26, 2022 at 0:44
3

I've found a simple solution... In the root of your project create your types.d.ts file.

For each app and library, in the tsconfig.app.json or tsconfig.lib.json add this to the "files" section:

"../../types.d.ts"

Screenshot example

Works for me with nx version 12.10.1

1
  • Thanks for the answer. As I mentioned in my question this is the suggested way to do it by NX but I was hoping to find a solution a little cleaner that didn't involve repeating code or having to remember to do this when setting up a new lib or app. Commented Feb 1, 2022 at 9:30
2

Problem comes from the fact that extending tsconfig does not mean you're merging configurations. Extend in tsconfig is a complete override for the root level properties you declare in your extender configuration.

You can have a tsconfig.base.json in your project root like this:

...
"typeRoots": [
  "types",
  "node_modules/@types"
],
"types": [
  "node",
  "your-type"
]
...

Then you can place your own typing folder based on it:

<project-root>/types/your-type/index.d.ts

Check your tsconfig.(app|lib).json files and look for overrides for property types and remove it completely.

This "solution" also has some drawbacks depending on the kind of project you are building. If it is a completely node project, i think it's okay to add node to the base tsconfig, but if the project is mixed or you need further app / lib specific type declarations this will fail and you'll end up copy-pasting the defined types from the base into app / lib tsconfig files.

0

I've created this nodejs script to do the dirty job:

/**
* Add an entry in the include property of a tsconfig file. 
* Requires node 14 or later
* Built to aid in global environment vars in nx projects.
* 
* put this script in a file called add-include.js in /usr/local/bin/ an 
* give it 755 permission.
*
* Put your common types in a folder, i.e: create a folder called types in 
* your project root, and put there some declarations files.

* then do a find with exec:
* 
* cd project-root
* find apps/ libs/ -name tsconfig.\*.json -exec add-include.js "../../types/*.d.ts" "{}" \;
*
* Recomendation: this is an undoable operation, so before executing 
* the code commit your work.
*/


const fs = require("fs");

if (process.argv.length < 4) {
  console.log(`Usage: add-include.js path-of-includes files`);
  console.log(`sample: add-include.js "../../*.d.ts" "{}"`);
  process.exit(-1);
}

const [,,newlib,...archivos] = process.argv;
archivos.forEach(a => {
  if (!fs.existsSync(a)) {
    console.log(`file ${a} not found, aborting`);
    process.exit(-1);
  }
})


archivos.forEach(a => {
  console.log(`processing ${a}`);
  const data = fs.readFileSync(a);
  const config = JSON.parse(data);

  if (!Array.isArray(config.include)) {
    console.log(`invalid file ${a}, no include property found, skipping`);
    return;
  }
  if (config.include.find(s => s === newlib)) {
    console.log(`${a} already has the value, skipping`);
    return;
  }
  config.include.push(newlib);
  fs.writeFileSync(a, JSON.stringify(config));
});

console.log(`Done, modified ${archivos.length} files`);

Just created a plugin for this: https://www.npmjs.com/package/@hanspoo/global-types

0

Here's the best solution I could find:

  1. Use `` to generate a proper tsconfig structure.

  2. Migrate using

pnpm nx generate @wanews/nx-typescript-project-references:migrate

  1. Use the files option to add the .d.ts files in the tsconfig.base.json at the workspace root
  "files": [
    "./types/references.d.ts",
  ],
  1. IF you need to customize the included files on a project, use the include property
-1

Below setup works fine for all apps but not libs...

I have index.d.ts in a root directory:


    // /index.d.ts
    declare global {
      namespace MyNamespace {
         interface MyInterface { ... }
      }
    }

In tsconfig.base.json I have added "esModuleInterop": true, and "typeRoots": ["index.d.ts"] in compilerOptions. Then in each /apps/my-app/tsconfg.json I added "../../index.d.ts" in include array.

This allows me to use global declarations in apps without the need to import anything.

const myVar: MyNamespace.MyInterface = ...

I tried to do the same for libs but it does not work.

There are three tsconfig.jsons in lib directory:

  • tsconfig.json (main)
  • tsconfig.lib.json (lib)
  • tsconfig.spec.json (spec)
  • and if i use a storybook for a lib then there is fourth /.storybook/tsconfig.json (storybook).

lib, spec and storybook configs extend main config.

There is error in the file:

Referenced project '/.../libs/shared-one/tsconfig.lib.json' must have setting "composite": true.

if i set composite to true for lib, spec, storybook configs then I get following error there:

Composite projects may not disable declaration emit.

What is wrong? How do I share global type declarations among nx libs? I hoped this will work out of the box.

example of tsconfig.lib.json (lib)


    {
      "extends": "./tsconfig.json",
      "compilerOptions": {
        "composite": true,
        "outDir": "../../dist/out-tsc",
        "types": ["node"]
      },
      "files": [
        "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
        "../../node_modules/@nrwl/react/typings/image.d.ts"
      ],
      "exclude": [
        "**/*.spec.ts",
        "**/*.spec.tsx",
        "**/*.stories.ts",
        "**/*.stories.js",
        "**/*.stories.jsx",
        "**/*.stories.tsx"
      ],
      "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
    }

/libs/my-lib tsconfig.json (main)

    {
      "extends": "../../tsconfig.base.json",
      "compilerOptions": {
        "jsx": "react-jsx",
        "allowJs": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,
      },
      "files": [],
      "include": ["../../index.d.ts"],
      "references": [
        {
          "path": "./tsconfig.lib.json"
        },
        {
          "path": "./tsconfig.spec.json"
        },
        {
          "path": "./.storybook/tsconfig.json"
        }
      ]
    }

tsconfig.base.json: (base)


    {
      "compileOnSave": false,
      "compilerOptions": {
        "rootDir": ".",
        "sourceMap": true,
        "declaration": false,
        "moduleResolution": "node",
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "esModuleInterop": true,
        "importHelpers": true,
        "target": "es2015",
        "module": "esnext",
        "lib": ["es2017", "dom"],
        "skipLibCheck": true,
        "skipDefaultLibCheck": true,
        "baseUrl": ".",
        "paths": {
          "@my-org/shared-one": ["libs/shared-one/src/index.ts"],
          "@my-org/shared-two": ["libs/shared-two/src/index.ts"]
        }
      },
      "exclude": ["node_modules", "tmp"],
      "include": ["index.d.ts"]
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.