From c9897c61e65d876d6214ef20bfd6b3497fff0cc0 Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Thu, 26 May 2022 12:09:27 -0400 Subject: [PATCH] fix(core): swc should be an optional peer dependency (#10461) --- packages/devkit/src/utils/package-json.ts | 7 +- .../js/src/utils/swc/add-swc-dependencies.ts | 2 +- packages/js/src/utils/versions.ts | 3 +- packages/linter/migrations.json | 6 + packages/linter/package.json | 1 + .../workspace-rules-project.ts | 15 ++- .../update-14-1-9/add-swc-deps-if-needed.ts | 17 +++ packages/nx-plugin/migrations.json | 6 + .../nx-plugin/src/generators/plugin/plugin.ts | 28 +++-- .../update-14-1-9/add-swc-deps-if-needed.ts | 17 +++ packages/nx/package.json | 14 ++- packages/nx/src/utils/register.ts | 105 ++++++++++++++++-- packages/nx/src/utils/versions.ts | 2 + .../workspace/src/utilities/plugins/models.ts | 32 +----- scripts/depcheck/index.ts | 4 +- scripts/depcheck/missing.ts | 2 +- 16 files changed, 194 insertions(+), 67 deletions(-) create mode 100644 packages/linter/src/migrations/update-14-1-9/add-swc-deps-if-needed.ts create mode 100644 packages/nx-plugin/src/migrations/update-14-1-9/add-swc-deps-if-needed.ts diff --git a/packages/devkit/src/utils/package-json.ts b/packages/devkit/src/utils/package-json.ts index 811659a710770..797f212443003 100644 --- a/packages/devkit/src/utils/package-json.ts +++ b/packages/devkit/src/utils/package-json.ts @@ -45,10 +45,11 @@ export function addDependenciesToPackageJson( return json; }); + return (): void => { + installPackagesTask(tree); + }; } - return (): void => { - installPackagesTask(tree); - }; + return () => {}; } /** diff --git a/packages/js/src/utils/swc/add-swc-dependencies.ts b/packages/js/src/utils/swc/add-swc-dependencies.ts index 0eadbaaa93936..b36dee699a178 100644 --- a/packages/js/src/utils/swc/add-swc-dependencies.ts +++ b/packages/js/src/utils/swc/add-swc-dependencies.ts @@ -2,7 +2,7 @@ import { addDependenciesToPackageJson, Tree } from '@nrwl/devkit'; import { swcCliVersion, swcCoreVersion, swcHelpersVersion } from '../versions'; export function addSwcDependencies(tree: Tree) { - addDependenciesToPackageJson( + return addDependenciesToPackageJson( tree, { '@swc/helpers': swcHelpersVersion, diff --git a/packages/js/src/utils/versions.ts b/packages/js/src/utils/versions.ts index 9f4d9b2dcaaef..d0b49fd710a7f 100644 --- a/packages/js/src/utils/versions.ts +++ b/packages/js/src/utils/versions.ts @@ -1,5 +1,6 @@ +export { swcCoreVersion } from 'nx/src/utils/versions'; + export const nxVersion = require('../../package.json').version; -export const swcCoreVersion = '~1.2.143'; export const swcCliVersion = '~0.1.55'; export const swcHelpersVersion = '~0.3.3'; diff --git a/packages/linter/migrations.json b/packages/linter/migrations.json index 165d36f15613a..c42dd8aad0ba1 100644 --- a/packages/linter/migrations.json +++ b/packages/linter/migrations.json @@ -64,6 +64,12 @@ "version": "13.3.0-beta.0", "description": "Update eslint-rules jest.config.js in order to support ESLint v8 exports mapping, remove category field", "factory": "./src/migrations/update-13-3-0/eslint-8-updates" + }, + "add-swc-deps": { + "cli": "nx", + "version": "14-1-9-beta.0", + "description": "Adds @swc/core and @swc-node as a dev dep if you are using them", + "factory": "./src/migrations/update-14-1-9/add-swc-deps-if-needed" } }, "packageJsonUpdates": { diff --git a/packages/linter/package.json b/packages/linter/package.json index 821725e1dd831..fc9c238081ea1 100644 --- a/packages/linter/package.json +++ b/packages/linter/package.json @@ -36,6 +36,7 @@ "@nrwl/devkit": "file:../devkit", "@nrwl/jest": "file:../jest", "@phenomnomnominal/tsquery": "4.1.1", + "nx": "file:../nx", "tmp": "~0.2.1", "tslib": "^2.3.0" }, diff --git a/packages/linter/src/generators/workspace-rules-project/workspace-rules-project.ts b/packages/linter/src/generators/workspace-rules-project/workspace-rules-project.ts index 364e8b227cc63..2fcf5c3341398 100644 --- a/packages/linter/src/generators/workspace-rules-project/workspace-rules-project.ts +++ b/packages/linter/src/generators/workspace-rules-project/workspace-rules-project.ts @@ -1,4 +1,5 @@ import { + addDependenciesToPackageJson, addProjectConfiguration, convertNxGenerator, formatFiles, @@ -14,10 +15,11 @@ import { addPropertyToJestConfig, jestProjectGenerator } from '@nrwl/jest'; import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { join } from 'path'; import { workspaceLintPluginDir } from '../../utils/workspace-lint-rules'; +import { swcCoreVersion, swcNodeVersion } from 'nx/src/utils/versions'; export const WORKSPACE_RULES_PROJECT_NAME = 'eslint-rules'; -const WORKSPACE_PLUGIN_DIR = 'tools/eslint-rules'; +export const WORKSPACE_PLUGIN_DIR = 'tools/eslint-rules'; export async function lintWorkspaceRulesProjectGenerator(tree: Tree) { // Noop if the workspace rules project already exists @@ -54,7 +56,7 @@ export async function lintWorkspaceRulesProjectGenerator(tree: Tree) { }); // Add jest to the project and return installation task - const jestInstallationTask = await jestProjectGenerator(tree, { + const installTask = await jestProjectGenerator(tree, { project: WORKSPACE_RULES_PROJECT_NAME, supportTsx: false, skipSerializers: true, @@ -62,6 +64,13 @@ export async function lintWorkspaceRulesProjectGenerator(tree: Tree) { compiler: 'tsc', }); + // Add swc dependencies + addDependenciesToPackageJson( + tree, + {}, + { '@swc-node/register': swcNodeVersion, '@swc/core': swcCoreVersion } + ); + // Add extra config to the jest.config.ts file to allow ESLint 8 exports mapping to work with jest addPropertyToJestConfig( tree, @@ -74,7 +83,7 @@ export async function lintWorkspaceRulesProjectGenerator(tree: Tree) { await formatFiles(tree); - return jestInstallationTask; + return installTask; } export const lintWorkspaceRulesProjectSchematic = convertNxGenerator( diff --git a/packages/linter/src/migrations/update-14-1-9/add-swc-deps-if-needed.ts b/packages/linter/src/migrations/update-14-1-9/add-swc-deps-if-needed.ts new file mode 100644 index 0000000000000..eaffc27d4b689 --- /dev/null +++ b/packages/linter/src/migrations/update-14-1-9/add-swc-deps-if-needed.ts @@ -0,0 +1,17 @@ +import { addDependenciesToPackageJson, formatFiles, Tree } from '@nrwl/devkit'; +import { swcCoreVersion, swcNodeVersion } from 'nx/src/utils/versions'; +import { WORKSPACE_PLUGIN_DIR } from '../../generators/workspace-rules-project/workspace-rules-project'; + +export default async function addSwcNodeIfNeeded(tree: Tree) { + try { + if (tree.exists(WORKSPACE_PLUGIN_DIR)) { + addDependenciesToPackageJson( + tree, + {}, + { '@swc-node/register': swcNodeVersion, '@swc/core': swcCoreVersion } + ); + await formatFiles(tree); + return; + } + } catch {} +} diff --git a/packages/nx-plugin/migrations.json b/packages/nx-plugin/migrations.json index 4c70457eb5c34..90dd93cc3bfa6 100644 --- a/packages/nx-plugin/migrations.json +++ b/packages/nx-plugin/migrations.json @@ -14,6 +14,12 @@ "version": "11.0.17", "description": "Update schema versions for executors and generators", "factory": "./src/migrations/update-11-0-0/update-schema-version-for-executors-and-generators" + }, + "add-swc-deps": { + "cli": "nx", + "version": "14-1-9-beta.0", + "description": "Adds @swc/core and @swc-node as a dev dep if you are using them", + "factory": "./src/migrations/update-14-1-9/add-swc-deps-if-needed" } } } diff --git a/packages/nx-plugin/src/generators/plugin/plugin.ts b/packages/nx-plugin/src/generators/plugin/plugin.ts index cd6d3ef5d801d..4a08e07c5486a 100644 --- a/packages/nx-plugin/src/generators/plugin/plugin.ts +++ b/packages/nx-plugin/src/generators/plugin/plugin.ts @@ -1,4 +1,3 @@ -import type { Tree } from '@nrwl/devkit'; import { addDependenciesToPackageJson, convertNxGenerator, @@ -6,21 +5,25 @@ import { generateFiles, GeneratorCallback, getWorkspaceLayout, + installPackagesTask, joinPathFragments, names, normalizePath, readProjectConfiguration, + Tree, updateProjectConfiguration, } from '@nrwl/devkit'; -import type { Schema } from './schema'; -import { nxVersion } from '../../utils/versions'; -import * as path from 'path'; import { libraryGenerator } from '@nrwl/js'; +import { addSwcDependencies } from '@nrwl/js/src/utils/swc/add-swc-dependencies'; +import { swcNodeVersion } from 'nx/src/utils/versions'; +import * as path from 'path'; + +import { nxVersion } from '../../utils/versions'; import { e2eProjectGenerator } from '../e2e-project/e2e'; -import { generatorGenerator } from '../generator/generator'; import { executorGenerator } from '../executor/executor'; -import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import { generatorGenerator } from '../generator/generator'; +import type { Schema } from './schema'; interface NormalizedSchema extends Schema { name: string; fileName: string; @@ -125,7 +128,6 @@ function updateWorkspaceJson(host: Tree, options: NormalizedSchema) { export async function pluginGenerator(host: Tree, schema: Schema) { const options = normalizeOptions(host, schema); - const tasks: GeneratorCallback[] = []; const libraryTask = await libraryGenerator(host, { ...schema, @@ -134,19 +136,21 @@ export async function pluginGenerator(host: Tree, schema: Schema) { importPath: options.npmPackageName, }); - tasks.push(libraryTask); - - const installTask = addDependenciesToPackageJson( + addDependenciesToPackageJson( host, {}, { '@nrwl/devkit': nxVersion, '@nrwl/jest': nxVersion, '@nrwl/js': nxVersion, + '@swc-node/register': swcNodeVersion, tslib: '^2.0.0', } ); - tasks.push(installTask); + + // Ensures Swc Deps are installed to handle running + // local plugin generators and executors + addSwcDependencies(host); await addFiles(host, options); updateWorkspaceJson(host, options); @@ -161,7 +165,7 @@ export async function pluginGenerator(host: Tree, schema: Schema) { await formatFiles(host); - return runTasksInSerial(...tasks); + return () => installPackagesTask(host); } function resolvePackageName(npmScope: string, name: string): string { diff --git a/packages/nx-plugin/src/migrations/update-14-1-9/add-swc-deps-if-needed.ts b/packages/nx-plugin/src/migrations/update-14-1-9/add-swc-deps-if-needed.ts new file mode 100644 index 0000000000000..dbd3ecfceb38a --- /dev/null +++ b/packages/nx-plugin/src/migrations/update-14-1-9/add-swc-deps-if-needed.ts @@ -0,0 +1,17 @@ +import { + addDependenciesToPackageJson, + formatFiles, + installPackagesTask, + Tree, +} from '@nrwl/devkit'; +import { swcCoreVersion, swcNodeVersion } from 'nx/src/utils/versions'; + +export default async function addSwcNodeIfNeeded(tree: Tree) { + addDependenciesToPackageJson( + tree, + {}, + { '@swc-node/register': swcNodeVersion, '@swc/core': swcCoreVersion } + ); + await formatFiles(tree); + return installPackagesTask(tree); +} diff --git a/packages/nx/package.json b/packages/nx/package.json index d1affb848a354..bb9d3fd8f134f 100644 --- a/packages/nx/package.json +++ b/packages/nx/package.json @@ -32,8 +32,6 @@ "homepage": "https://nx.dev", "dependencies": { "@parcel/watcher": "2.0.4", - "@swc-node/register": "^1.4.2", - "@swc/core": "^1.2.173", "chalk": "4.1.0", "chokidar": "^3.5.1", "cli-cursor": "3.1.0", @@ -61,6 +59,18 @@ "yargs": "^17.4.0", "yargs-parser": "21.0.1" }, + "peerDependencies": { + "@swc-node/register": "^1.4.2", + "@swc/core": "^1.2.173" + }, + "peerDependenciesMeta": { + "@swc-node/register": { + "optional": true + }, + "@swc/core": { + "optional": true + } + }, "nx-migrations": { "migrations": "./migrations.json", "packageGroup": [ diff --git a/packages/nx/src/utils/register.ts b/packages/nx/src/utils/register.ts index 65686786f77d0..93ce107b5fdac 100644 --- a/packages/nx/src/utils/register.ts +++ b/packages/nx/src/utils/register.ts @@ -1,4 +1,5 @@ import { join } from 'path'; +import { logger, NX_PREFIX, stripIndent } from './logger'; /** * Optionally, if swc-node and tsconfig-paths are available in the current workspace, apply the require @@ -7,22 +8,72 @@ import { join } from 'path'; * If ts-node and tsconfig-paths are not available, the user can still provide an index.js file in * the root of their project and the fundamentals will still work (but * workspace path mapping will not, for example). + * + * @returns cleanup function */ export const registerTsProject = ( path: string, configFilename = 'tsconfig.json' -) => { - // These are requires to prevent it from registering when it shouldn't - const { register } = require('@swc-node/register/register'); - const { - readDefaultTsConfig, - } = require('@swc-node/register/read-default-tsconfig'); +): (() => 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'); - try { - const tsConfigPath = join(path, configFilename); const tsConfig = readDefaultTsConfig(tsConfigPath); - register(tsConfig); + registerTranspiler = () => register(tsConfig); + } else { + // We can fall back on ts-node if its available + const tsNodeInstalled = packageIsInstalled('ts-node/register'); + if (tsNodeInstalled) { + warnTsNodeUsage(); + const { register } = require('ts-node') as typeof import('ts-node'); + + // ts-node doesn't provide a cleanup method + registerTranspiler = () => { + register({ + project: tsConfigPath, + transpileOnly: true, + compilerOptions: { + module: 'commonjs', + }, + }); + return () => {}; + }; + } + } + + if (registerTranspiler) { + cleanupFunctions.push(registerTranspiler()); + } else { + warnNoTranspiler(); + } + + // 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 + * @returns cleanup function + */ +export function registerTsConfigPaths(tsConfigPath): () => void { + try { /** * Load the ts config from the source project */ @@ -38,6 +89,38 @@ export const registerTsProject = ( paths: tsConfigResult.paths, }); } - } catch (err) {} + } catch (err) { + warnNoTsconfigPaths(); + } return () => {}; -}; +} + +function warnTsNodeUsage() { + logger.warn( + stripIndent(`${NX_PREFIX} Falling back to ts-node for local typescript execution. This may be a little slower. + - To fix this, ensure @swc-node/register and @swc/core have been installed`) + ); +} + +function warnNoTsconfigPaths() { + logger.warn( + stripIndent(`${NX_PREFIX} Unable to load tsconfig-paths, workspace libraries may be inaccessible. + - To fix this, install tsconfig-paths with npm/yarn/pnpm`) + ); +} + +function warnNoTranspiler() { + logger.warn( + stripIndent(`${NX_PREFIX} Unable to locate swc-node or ts-node. Nx will be unable to run local ts files without transpiling. + - To fix this, ensure @swc-node/register and @swc/core have been installed`) + ); +} + +function packageIsInstalled(m: string) { + try { + const p = require.resolve(m); + return true; + } catch { + return false; + } +} diff --git a/packages/nx/src/utils/versions.ts b/packages/nx/src/utils/versions.ts index 94be463c3e0ff..0bf9b31581bb1 100644 --- a/packages/nx/src/utils/versions.ts +++ b/packages/nx/src/utils/versions.ts @@ -7,3 +7,5 @@ export const tslintVersion = '~6.1.0'; export const typescriptESLintVersion = '~5.18.0'; export const eslintVersion = '~8.12.0'; export const eslintConfigPrettierVersion = '8.1.0'; +export const swcNodeVersion = '^1.4.2'; +export const swcCoreVersion = '^1.2.173'; diff --git a/packages/workspace/src/utilities/plugins/models.ts b/packages/workspace/src/utilities/plugins/models.ts index 2cae45873debd..695dab75f1355 100644 --- a/packages/workspace/src/utilities/plugins/models.ts +++ b/packages/workspace/src/utilities/plugins/models.ts @@ -1,31 +1 @@ -export interface PluginGenerator { - factory: string; - schema: string; - description: string; - aliases: string; - hidden: boolean; -} - -export interface PluginExecutor { - implementation: string; - schema: string; - description: string; -} - -export interface PluginCapabilities { - name: string; - executors: { [name: string]: PluginExecutor }; - generators: { [name: string]: PluginGenerator }; -} - -export interface CorePlugin { - name: string; - capabilities: 'executors' | 'generators' | 'executors,generators'; - link?: string; -} - -export interface CommunityPlugin { - name: string; - url: string; - description: string; -} +export * from 'nx/src/utils/plugins/models'; diff --git a/scripts/depcheck/index.ts b/scripts/depcheck/index.ts index b5b2e8b4f98da..542597d08db4d 100644 --- a/scripts/depcheck/index.ts +++ b/scripts/depcheck/index.ts @@ -48,7 +48,7 @@ const argv = require('yargs') .map((name) => ({ name })) .map(async (project) => { const projectPath = join(packagesDirectory, project.name); - const { dependencies } = JSON.parse( + const { dependencies, peerDependencies } = JSON.parse( readFileSync(`${projectPath}/package.json`).toString() ); @@ -56,7 +56,7 @@ const argv = require('yargs') ? await getMissingDependencies( project.name, projectPath, - dependencies, + { ...dependencies, ...(peerDependencies || {}) }, argv.verbose ) : []; diff --git a/scripts/depcheck/missing.ts b/scripts/depcheck/missing.ts index db1e7fc7383cd..e401d0d360a50 100644 --- a/scripts/depcheck/missing.ts +++ b/scripts/depcheck/missing.ts @@ -101,6 +101,7 @@ const IGNORE_MATCHES = { '@angular-devkit/core', '@angular-devkit/architect', '@angular/cli', + 'ts-node', // We *may* fall back on ts-node, but we want to encourage the use of @swc-node instead so we don't explicitly list ts-node as an optional dep ], web: [ // we don't want to bloat the install of @nrwl/web by including @swc/core and swc-loader as a dependency. @@ -121,7 +122,6 @@ const IGNORE_MATCHES = { '@nrwl/jest', '@nrwl/linter', 'tsconfig-paths', - '@swc-node/register', ], nest: ['semver'], 'make-angular-cli-faster': ['@angular/core'],