nx icon indicating copy to clipboard operation
nx copied to clipboard

Type check command

Open JakeGinnivan opened this issue 3 years ago โ€ข 33 comments

Description

One way I leverage TypeScript is when making a behavioral change to my project is to ensure the source of the change is a compilation mistake.

Being able to run a type check over the entire project through VS Code and use the TypeScript problem matcher to ensure they are surfaced in the Problems tab

Motivation

Being able to surface all build errors in a repo is very handy, especially when making a change to one to the shared projects.

Suggested Implementation

Until the solution file exists, running tsc -p apps/*/tsconfig.json --noEmit --incremental for each app tsconfig?

Alternate Implementations

Maintain a solution tsconfig.all.json, I read there were some issues with having one so having it named differently should stop it being picked up by VSCode. I think we would then need tsconfig.check.json for each project which sets no-emit and incremental.

JakeGinnivan avatar Sep 05 '20 00:09 JakeGinnivan

I guess a third option is writing a problem matcher for the build cli command?

JakeGinnivan avatar Sep 05 '20 00:09 JakeGinnivan

Build command can not cover the code that is not used in any app yet, so a typescript checker is necessary

zhangciwu avatar Nov 11 '20 07:11 zhangciwu

https://github.com/typescript-eslint/typescript-eslint/issues/352 And eslint can not cover this case, FYI

zhangciwu avatar Nov 11 '20 07:11 zhangciwu

Currently I use tsc -b (in every app/lib dir) for resolution style tsconfig which is by default in nx@10 It does not support noEmit, so need to indicate a path to place the compiled files (in compilerOptions.outDir) Ref: https://www.typescriptlang.org/docs/handbook/project-references.html

zhangciwu avatar Nov 11 '20 07:11 zhangciwu

+1 to see a proper solution to just running type checks. I would love to separate my build from type checks in order to parallize them as well as enabling the possibility of running different bundlers that don't do the type checking as well.

@zhangciwu can you elaborate a little bit more on that setup? I tried to achieve something similar, by adding a run-command to all apps / projects e.g.

        "type-check": {
          "builder": "@nrwl/workspace:run-commands",
          "options": {
            "command": "yarn tsc -b apps/client/tsconfig.app.json --incremental"
          }
        }

The problem is when running it for some libs, which is when I am getting the rootDir error: 'rootDir' is expected to contain all source files. I assume this can be fixed, by manually referencing all projects (a lot of work) and making all of these libs composite and fixing some more configuration issues such as Composite projects may not disable declaration emit.

The final issue I am stuck here with, that this does not seem to pick up the proper extended tsconfig in my referenced projects. Since I am getting this Cannot find name 'process'. Do you need to install type definitions for node? Try npm i @types/nodeand then addnode to the types field in your tsconfig. although I have types node installed and in the respective tsconfig.

Did you manually run through this whole process or is there a more holistic solution?

KeKs0r avatar Nov 22 '20 10:11 KeKs0r

@KeKs0r I wrote a script to walk all the dir of apps/libs, and run tsc -b in cwd set to that path if there is tsconfig there It's kind of rough and not that nx style

Tried with nx command and it works well, the command I use is:

       "type-check": {
          "builder": "@nrwl/workspace:run-commands",
          "options": {
            "command": "npm run tsc -- -b ./apps/content/tsconfig.json --incremental"
          }
        }

Some points:

  • To cover all source files, I have a tsconfig.base.json at root, with "rootDir": ".", so it covers all (apps/libs tsconfig extend it)
  • noEmit is not avaliable for composite projects, so make sure to set outDir properly (I use "../../dist/out-tsc", which seems default by nx)
  • Just use tsconfig.json, it will cover all references like app and spec, (and that's the point of -b option of typescript compiler)

zhangciwu avatar Nov 23 '20 04:11 zhangciwu

I agree we should have this command

@KeKs0r Did you make it? If so, can you share your configuration?

I tried to configure my app tsconfig.json file but it seems not working unless I put a reference to all libs my app use:

// myapp/tsconfig.json
{
    "extends": "../../tsconfig.base.json",
    "compilerOptions": {
        "types": ["node", "jest"]
    },
    "include": ["**/*.ts"],
    "references": [
        {
            "path": "./tsconfig.app.json"
        }
    ]
}

// myapp/tsconfig.app.json
{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "outDir": "../../dist/out-tsc",
        "emitDecoratorMetadata": true,
        "module": "commonjs",
        "target": "es2017",
        "types": ["node"]
    },
    "exclude": ["**/*.spec.ts"],
    "references": [
        {
            "path": "../../libs/lib1/tsconfig.json"
        },
        // <-- ... long list of libs refrence
    ]
}

without referencing libs tsconfig.json I got this error

TS6307: File '...../libs/lib1/src/lib/some-file.ts' is not listed within the file list of project '...../apps/myapp/tsconfig.app.json'. Projects must list all files or use an 'include' pattern.

@zhangciwu Is this how it should be?

AliYusuf95 avatar Dec 28 '20 16:12 AliYusuf95

@AliYusuf95 I put all the lib path config in root tsconfig, and using paths instead of references

"paths": {
      "@myproject/testlib": ["libs/testlib/src/index.ts"],
}

Also I have blank include in myapp/tsconfig.json, and declare the include and exclude in myapp/tsconfig.app.json and myapp/tsconfig.spec.json

zhangciwu avatar Jan 15 '21 10:01 zhangciwu

@AliYusuf95 : unfortunately not. There was a chain of TS issues I ran into and could not resolve them.

KeKs0r avatar Mar 23 '21 11:03 KeKs0r

I created this executor which worked for my use case and then you can run standard commands such as nx run-many --target=tsc --all --parallel

craigbilner avatar Jul 23 '21 15:07 craigbilner

I wrote up a blog post on how we are using TypeScript project references with NX at https://jakeginnivan.medium.com/using-typescript-project-references-in-nx-b3462b2fe6d4

The tl;dr of it is:

  • We no longer use nrwl's app plugins
  • We use esbuild/vite for building everything, these resolve files via path mappings so no type checking/tsc within our application/test/lint tool chains
  • tsc -b in the root of the project type checks everything
  • VSCode is configured to not map files back to source, so we run tsc -b --watch in our editor as a background task to get continual & incremental type checking and keep the editor errors up to date.

JakeGinnivan avatar Jul 28 '21 23:07 JakeGinnivan

@JakeGinnivan that looks nice but I am not sure if the solution is to not use nrwl's app plugins. ๐Ÿ˜… Although I totally understand that this may take months to be resolved...


I know this is open source and everything but do people really use in production something that doesn't tell them typescript errors by default? We can always write our own solution but that kind of kills the point of "standardization". ๐Ÿ™‚ I focus on code quality and this is a major blocker for me.

Every project should have:

  • eslint
  • prettier
  • stylelint (if FE application)
  • git hooks (lint-staged, husky and commit lint)
  • easy type checking (ignoring an error now and then can easily make the project have hundreds of issues later)

otherwise it should never make it to production. ๐Ÿ™‰

developer239 avatar Aug 07 '21 16:08 developer239

I created this executor which worked for my use case and then you can run standard commands such as nx run-many --target=tsc --all --parallel

This looks like a good solution although looking at the documentation the executor needs more than 1 file to actually work. ๐Ÿค”

developer239 avatar Aug 07 '21 16:08 developer239

@developer239 I agree with the standardization and minimum quality bar, which is why we created our own plugin set.

The goal for us is to only type check once, not multiple times and also get a great editor and developer experience. The advantage of our approach is not only do we only type check once, we get full support for incremental compilation with TypeScript project references so the incremental compilation is really fast so we get super quick type checking feedback in our editors as we are developing.

Another advantage of us creating a new set of plugins is that the nrwl team can check out the different approach rather than any discussions just being based on theory.

JakeGinnivan avatar Aug 08 '21 00:08 JakeGinnivan

@JakeGinnivan My post was more like a rhetorical question to the maintainers of this project. I totally understand why you decided to use your own solution. ๐Ÿ™Œ

developer239 avatar Aug 08 '21 03:08 developer239

@developer239 totally, I hope that they a look at my solution and consider it for a way forward

JakeGinnivan avatar Aug 08 '21 08:08 JakeGinnivan

I wrote up a blog post on how we are using TypeScript project references with NX at https://jakeginnivan.medium.com/using-typescript-project-references-in-nx-b3462b2fe6d4

The tl;dr of it is:

  • We no longer use nrwl's app plugins
  • We use esbuild/vite for building everything, these resolve files via path mappings so no type checking/tsc within our application/test/lint tool chains
  • tsc -b in the root of the project type checks everything
  • VSCode is configured to not map files back to source, so we run tsc -b --watch in our editor as a background task to get continual & incremental type checking and keep the editor errors up to date.

@JakeGinnivan awesome work! i've been looking at your plugin and method as a starting point for a similar approach in our monorepo. one question, when you say:

VSCode is configured to not map files back to source, so we run tsc -b --watch in our editor as a background task to get continual & incremental type checking and keep the editor errors up to date.

could you elaborate on how you configured VSCode? i am not super familiar with running background tasks and how to make sure VSCode is using the tsc -b --watch command instead of spawning its own typescript server.

tsc -b in the root of the project type checks everything

is working great and is really ๐Ÿ”ฅ

stephenlaughton avatar Aug 17 '21 00:08 stephenlaughton

could you elaborate on how you configured VSCode? i am not super familiar with running background tasks and how to make sure VSCode is using the tsc -b --watch command instead of spawning its own typescript server.

For sure, the language service only really picks up issues of open files, but it also provides intellisense and such so the approach doesn't replace it, it just augments it.

This is my .vscode/tasks.json, I can cmd+shift+b when I open the repo to start the build incrementally in the background, it also surfaces build errors into the Problems panel of vscode.

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "typescript",
            "tsconfig": "tsconfig.json",
            "option": "watch",
            "problemMatcher": ["$tsc-watch"],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "label": "tsc: watch - tsconfig.json"
        }
    ]
}

JakeGinnivan avatar Aug 18 '21 02:08 JakeGinnivan

@JakeGinnivan We wrote an internal executor for our company and added it to every app and lib in workspace. Something like this <333

import { ExecutorContext } from '@nrwl/devkit';
import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager';
import { spawn } from 'child_process';

export default async function tscExecutor(_, context: ExecutorContext) {
  const packageManager = detectPackageManager();
  const packageManagerCmd = packageManager === 'pnpm' ? 'pnpx' : packageManager === 'yarn' ? 'yarn' : 'npx';

  const libRoot = context.workspace.projects[context.projectName].root;

  const executionCode = await new Promise((resolve) => {
    const child = spawn(packageManagerCmd, ['tsc', '-p', libRoot, '--noEmit'], { stdio: 'inherit' });
    child.on('data', (args) => console.log(args));
    child.on('close', (code) => resolve(code));
  });

  return { success: executionCode === 0 };
}

always-maap avatar Oct 04 '21 23:10 always-maap

@JakeGinnivan We wrote an internal executor for our company and added it to every app and lib in workspace. Something like this <333

import { ExecutorContext } from '@nrwl/devkit';
import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager';
import { spawn } from 'child_process';

export default async function tscExecutor(_, context: ExecutorContext) {
  const packageManager = detectPackageManager();
  const packageManagerCmd = packageManager === 'pnpm' ? 'pnpx' : packageManager === 'yarn' ? 'yarn' : 'npx';

  const libRoot = context.workspace.projects[context.projectName].root;

  const executionCode = await new Promise((resolve) => {
    const child = spawn(packageManagerCmd, ['tsc', '-p', libRoot, '--noEmit'], { stdio: 'inherit' });
    child.on('data', (args) => console.log(args));
    child.on('close', (code) => resolve(code));
  });

  return { success: executionCode === 0 };
}

I'm trying to replicate your executor but when I run nx run node:tsCheck I get error:

Cannot use import statement outside a module

Do you how to solve it? I've uploaded the repo here: https://github.com/petrkrejcik/nx-test

petrkrejcik avatar Nov 30 '21 13:11 petrkrejcik

@JakeGinnivan We wrote an internal executor for our company and added it to every app and lib in workspace. Something like this <333

import { ExecutorContext } from '@nrwl/devkit';
import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager';
import { spawn } from 'child_process';

export default async function tscExecutor(_, context: ExecutorContext) {
  const packageManager = detectPackageManager();
  const packageManagerCmd = packageManager === 'pnpm' ? 'pnpx' : packageManager === 'yarn' ? 'yarn' : 'npx';

  const libRoot = context.workspace.projects[context.projectName].root;

  const executionCode = await new Promise((resolve) => {
    const child = spawn(packageManagerCmd, ['tsc', '-p', libRoot, '--noEmit'], { stdio: 'inherit' });
    child.on('data', (args) => console.log(args));
    child.on('close', (code) => resolve(code));
  });

  return { success: executionCode === 0 };
}

I'm trying to replicate your executor but when I run nx run node:tsCheck I get error:

Cannot use import statement outside a module

Do you how to solve it? I've uploaded the repo here: https://github.com/petrkrejcik/nx-test

I did not test your code but I think "implementation": "./tsCheck". you need to use js as impl.

always-maap avatar Nov 30 '21 15:11 always-maap

@JakeGinnivan

I did not test your code but I think "implementation": "./tsCheck". you need to use js as impl.

Oh yeah, it was that, thank you!

Do you know if there is something else that I need to setup (except the steps in the docs for building custom executors)? Currently the command doesn't throw any error but if I run tsc agains one specific file the error is thrown correctly.

image

petrkrejcik avatar Dec 01 '21 07:12 petrkrejcik

@JakeGinnivan

I did not test your code but I think "implementation": "./tsCheck". you need to use js as impl.

Oh yeah, it was that, thank you!

Do you know if there is something else that I need to setup (except the steps in the docs for building custom executors)? Currently the command doesn't throw any error but if I run tsc agains one specific file the error is thrown correctly.

change this line in your impl.ts and build ts file again const child = spawn(packageManagerCmd, ['tsc', '-b', libRoot], {

always-maap avatar Dec 01 '21 07:12 always-maap

@JakeGinnivan Again - thank you ๐Ÿ‘

For anybody interested: this is a repo with working command tsCheck- https://github.com/petrkrejcik/nx-test Steps to add it to your monorepo:

  1. Copy tools/executors/tsCheck
  2. Add to your workspace.json to all desired projects:
{
  "targets": {
    "tsCheck": {
      "executor": "./tools/executors/tsCheck:tsCheck"
    }
  }
}
  1. Run nx run <appName>:tsCheck

petrkrejcik avatar Dec 01 '21 08:12 petrkrejcik

For Windows users a slight fix is needed. Add shell: true to the spawn options. Without this option Windows expects "npx.cmd" instead of "npx".

import { ExecutorContext } from '@nrwl/devkit';
import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager';
import { spawn } from 'child_process';

export default async function tscExecutor(_, context: ExecutorContext) {
  const packageManager = detectPackageManager();
  const packageManagerCmd = packageManager === 'pnpm' ? 'pnpx' : packageManager === 'yarn' ? 'yarn' : 'npx';

  const libRoot = context.workspace.projects[context.projectName].root;

  const executionCode = await new Promise((resolve) => {
    const child = spawn(packageManagerCmd, ['tsc', '-p', libRoot, '--noEmit'], { 
        stdio: 'inherit',
        shell: true // Windows fix "Error: spawn npx ENOENT"
    });
    child.on('data', (args) => console.log(args));
    child.on('close', (code) => resolve(code));
  });

  return { success: executionCode === 0 };
}

dobromyslov avatar Jan 30 '22 11:01 dobromyslov

On my side something is off, when running the upper executor on a project it will detect assets like .svg's as errors ' Cannot find module './icons/trash.svg' or its corresponding type declarations. It will also complain inside the shared components we have outside the project.

The upper script will use the tsconfig from a specific project even for the shared stuff across the repo, I am not yet sure how to fix this, but in the IDE (VSCode) I don't seem to have the same issues.

istvandesign avatar Feb 28 '22 16:02 istvandesign

That's a "normal" typescript thing - ts itself doesn't know what to do with svg files, it's your bundler that's handling those. You'll need to throw a declare module '*.svg'; somewhere (most likely a .d.ts file in your source somewhere)

luxaritas avatar Feb 28 '22 16:02 luxaritas

That's a "normal" typescript thing - ts itself doesn't know what to do with svg files, it's your bundler that's handling those. You'll need to throw a declare module '*.svg'; somewhere (most likely a .d.ts file in your source somewhere)

I have the declare in @nrwl/react/types, but when running in a project it ignores this for the shared libs. VSCode doesn't complain.

istvandesign avatar Mar 01 '22 04:03 istvandesign

Maybe the specific tsconfig you're running tsc against isn't configured to pick it up, but vs code is resolving to a different tsconfig or something?

luxaritas avatar Mar 01 '22 04:03 luxaritas

Maybe the specific tsconfig you're running tsc against isn't configured to pick it up, but vs code is resolving to a different tsconfig or something?

This is my tsconfig.lib.json in all projects (including shared components):

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

Which is why I say it's weird when running project:tsCheck and gives me errors about svg when everywhere I import the "../../node_modules/@nrwl/react/typings/image.d.ts" file.

istvandesign avatar Mar 01 '22 05:03 istvandesign