Skip to content

Commit

Permalink
fix(js): do not add typecheck target if tsc is used for build (#30211)
Browse files Browse the repository at this point in the history
This PR adds support for skipping `typecheck` targets when using
`@nx/js/typescript`. Inside `tsconfig.json` for each project, you can
set `nx.addTypecheckTarget` to `false` to not infer `typecheck`.

This allows use to skip `typecheck` for JS projects using `tsc` to
build. Since `tsc` is already used during build, we don't need to run
`typecheck` that is just duplicated work.

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
JS libs using `tsc` to build will do typechecking twice. Once during
`build` and once during `typecheck`.

## Expected Behavior
JS libs using `tsc` do not infer `typecheck` by default. And users can
change this behavior by setting `nx.addTypecheckTarget` in
`tsconfig.json`.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Leosvel Pérez Espinosa <[email protected]>
  • Loading branch information
jaysoo and leosvelperez authored Feb 28, 2025
1 parent bd78ac2 commit b992e25
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 13 deletions.
98 changes: 98 additions & 0 deletions docs/generated/packages/js/documents/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,104 @@ Nx 20 updates the TS monorepo setup when using `--preset=ts`. The workspace is s
To create with the older setup for TS monorepo with `compilerOptions.paths`, use `create-nx-workspace --preset=apps`.
{% /callout %}

### How @nx/js Infers Tasks

The `@nx/js/typescript` plugin will add a `typecheck` task to projects that have a `tsconfig.json`.

This plugin adds a `build` task for projects that:

1. Have a runtime tsconfig file (defaults to `tsconfig.lib.json`).
2. Have a `package.json` file containing entry points that are not source files.

For example, this project is buildable and will have a `build` task.

```json {% fileName="packages/pkg1/package.json" %}
{
"name": "@acme/pkg1",
"exports": {
"./package.json": "./package.json",
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
}
```

Whereas this project points to source files and will not have a `build` task.

```json {% fileName="packages/pkg1/package.json" %}
{
"name": "@acme/pkg1",
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
}
```

### View Inferred Tasks

To view inferred tasks for a project, open the [project details view](/concepts/inferred-tasks) in Nx Console or run `nx show project my-project` in the command line.

### @nx/js Configuration

The `@nx/js/typescript` plugin is configured in the `plugins` array in `nx.json`.

```json {% fileName="nx.json" %}
{
"plugins": [
{
"plugin": "@nx/js/typescript",
"options": {
"typecheck": {
"targetName": "typecheck"
},
"build": {
"targetName": "build",
"configName": "tsconfig.lib.json"
}
}
}
]
}
```

You can also set `typecheck` and `build` options to `false` to not infer the corresponding tasks.

```json {% fileName="nx.json" %}
{
"plugins": [
{
"plugin": "@nx/js/typescript",
"options": {
"build": false
}
}
]
}
```

### Disable Typechecking

To disable `typecheck` task for a specific project, set the `nx.addTypecheckTarget` property to `false` in `tsconfig.json`.

```json {% fileName="packages/pkg1/tsconfig.json" highlightLines=["10-12"] %}
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
],
"nx": {
"addTypecheckTarget": false
}
}
```

## Create Libraries

You can add a new JS/TS library with the following command:
Expand Down
98 changes: 98 additions & 0 deletions docs/shared/packages/js/js-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,104 @@ Nx 20 updates the TS monorepo setup when using `--preset=ts`. The workspace is s
To create with the older setup for TS monorepo with `compilerOptions.paths`, use `create-nx-workspace --preset=apps`.
{% /callout %}

### How @nx/js Infers Tasks

The `@nx/js/typescript` plugin will add a `typecheck` task to projects that have a `tsconfig.json`.

This plugin adds a `build` task for projects that:

1. Have a runtime tsconfig file (defaults to `tsconfig.lib.json`).
2. Have a `package.json` file containing entry points that are not source files.

For example, this project is buildable and will have a `build` task.

```json {% fileName="packages/pkg1/package.json" %}
{
"name": "@acme/pkg1",
"exports": {
"./package.json": "./package.json",
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
}
```

Whereas this project points to source files and will not have a `build` task.

```json {% fileName="packages/pkg1/package.json" %}
{
"name": "@acme/pkg1",
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
}
```

### View Inferred Tasks

To view inferred tasks for a project, open the [project details view](/concepts/inferred-tasks) in Nx Console or run `nx show project my-project` in the command line.

### @nx/js Configuration

The `@nx/js/typescript` plugin is configured in the `plugins` array in `nx.json`.

```json {% fileName="nx.json" %}
{
"plugins": [
{
"plugin": "@nx/js/typescript",
"options": {
"typecheck": {
"targetName": "typecheck"
},
"build": {
"targetName": "build",
"configName": "tsconfig.lib.json"
}
}
}
]
}
```

You can also set `typecheck` and `build` options to `false` to not infer the corresponding tasks.

```json {% fileName="nx.json" %}
{
"plugins": [
{
"plugin": "@nx/js/typescript",
"options": {
"build": false
}
}
]
}
```

### Disable Typechecking

To disable `typecheck` task for a specific project, set the `nx.addTypecheckTarget` property to `false` in `tsconfig.json`.

```json {% fileName="packages/pkg1/tsconfig.json" highlightLines=["10-12"] %}
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
],
"nx": {
"addTypecheckTarget": false
}
}
```

## Create Libraries

You can add a new JS/TS library with the following command:
Expand Down
11 changes: 4 additions & 7 deletions e2e/js/src/js-ts-solution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,16 @@ ${content}`

// check typecheck
expect(runCLI(`typecheck ${esbuildParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${esbuildParentLib} and 5 tasks it depends on`
`Successfully ran target typecheck for project @proj/${esbuildParentLib} and 4 tasks it depends on`
);
expect(runCLI(`typecheck ${rollupParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${rollupParentLib} and 5 tasks it depends on`
`Successfully ran target typecheck for project @proj/${rollupParentLib} and 4 tasks it depends on`
);
expect(runCLI(`typecheck ${swcParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${swcParentLib} and 5 tasks it depends on`
);
expect(runCLI(`typecheck ${tscParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${tscParentLib} and 5 tasks it depends on`
`Successfully ran target typecheck for project @proj/${swcParentLib} and 4 tasks it depends on`
);
expect(runCLI(`typecheck ${viteParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${viteParentLib} and 5 tasks it depends on`
`Successfully ran target typecheck for project @proj/${viteParentLib} and 4 tasks it depends on`
);

// check lint
Expand Down
1 change: 0 additions & 1 deletion e2e/node/src/node-ts-solution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ describe('Node Applications', () => {
expect(() => runCLI(`lint ${nodelib}`)).not.toThrow();
expect(() => runCLI(`test ${nodelib}`)).not.toThrow();
expect(() => runCLI(`build ${nodelib}`)).not.toThrow();
expect(() => runCLI(`typecheck ${nodelib}`)).not.toThrow();

const p = await runCommandUntil(
`serve ${nodeapp}`,
Expand Down
3 changes: 0 additions & 3 deletions e2e/plugin/src/nx-plugin-ts-solution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ describe('Nx Plugin (TS solution)', () => {
expect(runCLI(`lint @proj/${plugin}`)).toContain(
`Successfully ran target lint for project @proj/${plugin}`
);
expect(runCLI(`typecheck @proj/${plugin}`)).toContain(
`Successfully ran target typecheck for project @proj/${plugin}`
);
expect(runCLI(`build @proj/${plugin}`)).toContain(
`Successfully ran target build for project @proj/${plugin}`
);
Expand Down
2 changes: 1 addition & 1 deletion e2e/vite/src/vite-ts-solution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ ${content}`

// check typecheck
expect(runCLI(`typecheck ${reactApp}`)).toContain(
`Successfully ran target typecheck for project @proj/${reactApp} and 6 tasks it depends on`
`Successfully ran target typecheck for project @proj/${reactApp} and 5 tasks it depends on`
);
}, 300_000);
});
18 changes: 18 additions & 0 deletions packages/js/src/generators/library/library.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2320,5 +2320,23 @@ describe('lib', () => {
'packages/**',
]);
});

it('should add nx.addTypecheckTarget to tsconfig.json when using tsc to build to avoid duplicated typechecks', async () => {
await libraryGenerator(tree, {
...defaultOptions,
useProjectJson: false,
directory: 'my-ts-lib',
bundler: 'tsc',
unitTestRunner: 'none',
linter: 'none',
});

expect(readJson(tree, 'my-ts-lib/tsconfig.json').nx)
.toMatchInlineSnapshot(`
{
"addTypecheckTarget": false,
}
`);
});
});
});
12 changes: 12 additions & 0 deletions packages/js/src/generators/library/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,12 @@ function createProjectTsConfigs(
json.references.push({
path: './tsconfig.lib.json',
});
// If using `tsc` to build, then we do not want a typecheck target that duplicates the work, since both run `tsc`.
// This applies to `@nx/js/typescript` plugin only.
if (options.bundler === 'tsc') {
json['nx'] ??= {};
json['nx'].addTypecheckTarget = false;
}
return json;
});
} else {
Expand All @@ -1059,6 +1065,12 @@ function createProjectTsConfigs(
include: [],
references: [{ path: './tsconfig.lib.json' }],
};
// If using `tsc` to build, then we do not want a typecheck target that duplicates the work, since both run `tsc`.
// This applies to `@nx/js/typescript` plugin only.
if (options.bundler === 'tsc') {
tsconfig['nx'] ??= {};
tsconfig['nx'].addTypecheckTarget = false;
}
writeJson(
tree,
joinPathFragments(options.projectRoot, 'tsconfig.json'),
Expand Down
71 changes: 71 additions & 0 deletions packages/js/src/plugins/typescript/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,77 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
).resolves.not.toThrow();
});

it('should not infer typecheck target when nx.addTypecheckTarget is false in tsconfig.json', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': JSON.stringify({
nx: { addTypecheckTarget: false },
}),
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
compilerOptions: { outDir: 'out-tsc/my-lib', rootDir: 'src' },
files: ['src/main.ts'],
}),
'libs/my-lib/package.json': `{}`,
});

await expect(
invokeCreateNodesOnMatchingFiles(context, {
build: {
configName: 'tsconfig.lib.json',
},
})
).resolves.toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/out-tsc/my-lib",
"{projectRoot}/out-tsc/*.tsbuildinfo",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});

describe('inputs', () => {
it('should add the config file and the `include` and `exclude` patterns', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
Expand Down
Loading

0 comments on commit b992e25

Please sign in to comment.