nx
nx copied to clipboard
Type check command
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.
I guess a third option is writing a problem matcher for the build cli command?
Build command can not cover the code that is not used in any app yet, so a typescript checker is necessary
https://github.com/typescript-eslint/typescript-eslint/issues/352 And eslint can not cover this case, FYI
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
+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 add
node 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
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)
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 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
@AliYusuf95 : unfortunately not. There was a chain of TS issues I ran into and could not resolve them.
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
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 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. ๐
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 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 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 totally, I hope that they a look at my solution and consider it for a way forward
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 ๐ฅ
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 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 };
}
@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
@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.
@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](https://user-images.githubusercontent.com/5626914/144187323-61867fef-8b15-436e-a093-4b36bc77678b.png)
@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], {
@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:
- Copy
tools/executors/tsCheck
- Add to your
workspace.json
to all desired projects:
{
"targets": {
"tsCheck": {
"executor": "./tools/executors/tsCheck:tsCheck"
}
}
}
- Run
nx run <appName>:tsCheck
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 };
}
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.
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)
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.
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?
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.