fix(core): tsconfig.base.json module setting should not break local plugins (#14610)

This commit is contained in:
Craigory Coppola 2023-02-13 16:28:32 -05:00 committed by GitHub
parent 2b5bdd50e4
commit b2df831076
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 104 additions and 32 deletions

View File

@ -155,3 +155,9 @@ To make sure that assets are copied to the dist folder, open the plugin's `proje
## Using your Nx Plugin
To use your plugin, simply list it in `nx.json` or use its generators and executors as you would for any other plugin. This could look like `nx g @my-org/my-plugin:lib` for generators or `"executor": "@my-org/my-plugin:build"` for executors. It should be usable in all of the same ways as published plugins in your local workspace immediately after generating it. This includes setting it up as the default collection in `nx.json`, which would allow you to run `nx g lib` and hit your plugin's generator.
{% callout type="warning" title="string" %}
Nx uses the paths from tsconfig.base.json when running plugins locally, but uses the recommended tsconfig for node 16 for other compiler options. See https://github.com/tsconfig/bases/blob/main/bases/node16.json
{% /callout %}

View File

@ -155,3 +155,9 @@ To make sure that assets are copied to the dist folder, open the plugin's `proje
## Using your Nx Plugin
To use your plugin, simply list it in `nx.json` or use its generators and executors as you would for any other plugin. This could look like `nx g @my-org/my-plugin:lib` for generators or `"executor": "@my-org/my-plugin:build"` for executors. It should be usable in all of the same ways as published plugins in your local workspace immediately after generating it. This includes setting it up as the default collection in `nx.json`, which would allow you to run `nx g lib` and hit your plugin's generator.
{% callout type="warning" title="string" %}
Nx uses the paths from tsconfig.base.json when running plugins locally, but uses the recommended tsconfig for node 16 for other compiler options. See https://github.com/tsconfig/bases/blob/main/bases/node16.json
{% /callout %}

View File

@ -95,6 +95,12 @@ To run a generator, invoke the `nx generate` command with the name of the genera
nx generate @myorg/my-plugin:my-generator mylib
```
{% callout type="warning" title="string" %}
Nx uses the paths from `tsconfig.base.json` when running plugins locally, but uses the recommended tsconfig for node 16 for other compiler options. See https://github.com/tsconfig/bases/blob/main/bases/node16.json
{% /callout %}
## Debugging generators
### With Visual Studio Code

View File

@ -134,6 +134,12 @@ Options: {
Hello World
```
{% callout type="warning" title="string" %}
Nx uses the paths from `tsconfig.base.json` when running plugins locally, but uses the recommended tsconfig for node 16 for other compiler options. See https://github.com/tsconfig/bases/blob/main/bases/node16.json
{% /callout %}
## Using Node Child Process
[Nodes `childProcess`](https://nodejs.org/api/child_process.html) is often useful in executors.

View File

@ -70,10 +70,9 @@ describe('Nx Plugin', () => {
// we should change it to point to the right collection using relative path
// TODO: Re-enable this to work with pnpm
xit(`should run the plugin's e2e tests`, async () => {
if (isNotWindows()) {
const plugin = uniq('plugin-name');
runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=eslint`);
if (isNotWindows()) {
const e2eResults = runCLI(`e2e ${plugin}-e2e`);
expect(e2eResults).toContain('Successfully ran target e2e');
expect(await killPorts()).toBeTruthy();
@ -281,7 +280,7 @@ describe('Nx Plugin', () => {
/**
* @todo(@AgentEnder): reenable after figuring out @swc-node
*/
xdescribe('local plugins', () => {
describe('local plugins', () => {
let plugin: string;
beforeEach(() => {
plugin = uniq('plugin');

View File

@ -10,7 +10,7 @@ import {
PackageJson,
readModulePackageJsonWithoutFallbacks,
} from './package-json';
import { registerTsProject } from './register';
import { registerTranspiler, registerTsConfigPaths } from './register';
import {
ProjectConfiguration,
ProjectsConfigurations,
@ -22,6 +22,7 @@ import {
findProjectForPath,
} from '../project-graph/utils/find-project-for-path';
import { normalizePath } from './path';
import { join } from 'path';
export type ProjectTargetConfigurator = (
file: string
@ -179,9 +180,20 @@ export function resolveLocalNxPlugin(
}
let tsNodeAndPathsRegistered = false;
function registerTSTranspiler() {
if (!tsNodeAndPathsRegistered) {
registerTsProject(workspaceRoot, 'tsconfig.base.json');
// nx-ignore-next-line
const ts: typeof import('typescript') = require('typescript');
registerTsConfigPaths(join(workspaceRoot, 'tsconfig.base.json'));
registerTranspiler({
lib: ['es2021'],
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2021,
esModuleInterop: true,
skipLibCheck: true,
});
}
tsNodeAndPathsRegistered = true;
}

View File

@ -1,6 +1,10 @@
import { join } from 'path';
import { dirname, join } from 'path';
import type { CompilerOptions } from 'typescript';
import { logger, NX_PREFIX, stripIndent } from './logger';
const swcNodeInstalled = packageIsInstalled('@swc-node/register');
const tsNodeInstalled = packageIsInstalled('ts-node/register');
/**
* Optionally, if swc-node and tsconfig-paths are available in the current workspace, apply the require
* register hooks so that .ts files can be used for writing custom workspace projects.
@ -15,37 +19,51 @@ export const registerTsProject = (
path: string,
configFilename = 'tsconfig.json'
): (() => void) => {
const tsConfigPath = join(path, configFilename);
const compilerOptions: CompilerOptions = readCompilerOptions(tsConfigPath);
const cleanupFunctions = [
registerTsConfigPaths(tsConfigPath),
registerTranspiler(compilerOptions),
];
return () => {
for (const fn of cleanupFunctions) {
fn();
}
};
};
/**
* Register ts-node or swc-node given a set of compiler options.
*
* Note: Several options require enums from typescript. To avoid importing typescript,
* use import type + raw values
*
* @returns cleanup method
*/
export function registerTranspiler(
compilerOptions: CompilerOptions
): () => void {
// Function to register transpiler that returns cleanup function
let registerTranspiler: () => () => void;
const tsConfigPath = join(path, configFilename);
const cleanupFunctions = [registerTsConfigPaths(tsConfigPath)];
const swcNodeInstalled = packageIsInstalled('@swc-node/register');
if (swcNodeInstalled) {
// These are requires to prevent it from registering when it shouldn't
const { register } =
require('@swc-node/register/register') as typeof import('@swc-node/register/register');
const {
readDefaultTsConfig,
} = require('@swc-node/register/read-default-tsconfig');
const tsConfig = readDefaultTsConfig(tsConfigPath);
registerTranspiler = () => register(tsConfig);
registerTranspiler = () => register(compilerOptions);
} else {
// We can fall back on ts-node if its available
const tsNodeInstalled = packageIsInstalled('ts-node/register');
if (tsNodeInstalled) {
const { register } = require('ts-node') as typeof import('ts-node');
// ts-node doesn't provide a cleanup method
registerTranspiler = () => {
const service = register({
project: tsConfigPath,
transpileOnly: true,
compilerOptions: {
module: 'commonjs',
},
compilerOptions,
});
// Don't warn if a faster transpiler is enabled
if (!service.options.transpiler && !service.options.swc) {
@ -57,19 +75,12 @@ export const registerTsProject = (
}
if (registerTranspiler) {
cleanupFunctions.push(registerTranspiler());
return registerTranspiler();
} else {
warnNoTranspiler();
return () => {};
}
// Overall cleanup method cleans up tsconfig path resolution
// as well as ts transpiler
return () => {
for (const f of cleanupFunctions) {
f();
}
};
};
}
/**
* @param tsConfigPath Adds the paths from a tsconfig file into node resolutions
@ -98,6 +109,32 @@ export function registerTsConfigPaths(tsConfigPath): () => void {
return () => {};
}
function readCompilerOptions(tsConfigPath): CompilerOptions {
if (swcNodeInstalled) {
const {
readDefaultTsConfig,
}: typeof import('@swc-node/register/read-default-tsconfig') = require('@swc-node/register/read-default-tsconfig');
return readDefaultTsConfig(tsConfigPath);
} else {
return readCompilerOptionsWithTypescript(tsConfigPath);
}
}
function readCompilerOptionsWithTypescript(tsConfigPath) {
const { readConfigFile, parseJsonConfigFileContent, sys } =
require('typescript') as typeof import('typescript');
const jsonContent = readConfigFile(tsConfigPath, sys.readFile);
const { options } = parseJsonConfigFileContent(
jsonContent,
sys,
dirname(tsConfigPath)
);
// This property is returned in compiler options for some reason, but not part of the typings.
// ts-node fails on unknown props, so we have to remove it.
delete options.configFilePath;
return options;
}
function warnTsNodeUsage() {
logger.warn(
stripIndent(`${NX_PREFIX} Falling back to ts-node for local typescript execution. This may be a little slower.