diff --git a/docs/generated/packages/esbuild/generators/configuration.json b/docs/generated/packages/esbuild/generators/configuration.json index 6df248a749..f22e96b8e7 100644 --- a/docs/generated/packages/esbuild/generators/configuration.json +++ b/docs/generated/packages/esbuild/generators/configuration.json @@ -61,6 +61,12 @@ "description": "The build target to add.", "type": "string", "default": "build" + }, + "format": { + "description": "The format to build the library (esm or cjs).", + "type": "array", + "items": { "type": "string", "enum": ["esm", "cjs"] }, + "default": ["esm"] } }, "required": [], diff --git a/docs/generated/packages/js/executors/swc.json b/docs/generated/packages/js/executors/swc.json index 7f93c917c8..9fd0d2da00 100644 --- a/docs/generated/packages/js/executors/swc.json +++ b/docs/generated/packages/js/executors/swc.json @@ -130,13 +130,13 @@ "description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.", "default": false, "x-priority": "internal" + }, + "stripLeadingPaths": { + "type": "boolean", + "description": "Remove leading directory from output (e.g. src). See: https://swc.rs/docs/usage/cli#--strip-leading-paths", + "default": false } }, - "stripLeadingPaths": { - "type": "boolean", - "description": "Remove leading directory from output (e.g. src). See: https://swc.rs/docs/usage/cli#--strip-leading-paths", - "default": false - }, "required": ["main", "outputPath", "tsConfig"], "definitions": { "assetPattern": { diff --git a/docs/generated/packages/rollup/generators/configuration.json b/docs/generated/packages/rollup/generators/configuration.json index 813d3e3b3c..ac8c20276c 100644 --- a/docs/generated/packages/rollup/generators/configuration.json +++ b/docs/generated/packages/rollup/generators/configuration.json @@ -26,13 +26,13 @@ }, "main": { "type": "string", - "description": "Path relative to the workspace root for the main entry file. Defaults to '/src/main.ts'.", + "description": "Path relative to the workspace root for the main entry file. Defaults to '/src/index.ts'.", "alias": "entryFile", "x-priority": "important" }, "tsConfig": { "type": "string", - "description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '/tsconfig.app.json'.", + "description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '/tsconfig.lib.json'.", "x-priority": "important" }, "skipFormat": { diff --git a/e2e/js/src/js-packaging.test.ts b/e2e/js/src/js-packaging.test.ts index 3a4b3c4ab6..3f8fae5256 100644 --- a/e2e/js/src/js-packaging.test.ts +++ b/e2e/js/src/js-packaging.test.ts @@ -214,14 +214,20 @@ describe('packaging libs', () => { expect(readJson(`dist/libs/${tscLib}/package.json`).exports).toEqual({ './package.json': './package.json', - '.': './src/index.js', + '.': { + default: './src/index.js', + types: './src/index.d.ts', + }, './foo/bar': './src/foo/bar.js', './foo/faz': './src/foo/faz.js', }); expect(readJson(`dist/libs/${swcLib}/package.json`).exports).toEqual({ './package.json': './package.json', - '.': './src/index.js', + '.': { + default: './src/index.js', + types: './src/index.d.ts', + }, './foo/bar': './src/foo/bar.js', './foo/faz': './src/foo/faz.js', }); diff --git a/packages/esbuild/src/executors/esbuild/esbuild.impl.ts b/packages/esbuild/src/executors/esbuild/esbuild.impl.ts index ef8a5205bb..13827d7fc2 100644 --- a/packages/esbuild/src/executors/esbuild/esbuild.impl.ts +++ b/packages/esbuild/src/executors/esbuild/esbuild.impl.ts @@ -18,7 +18,10 @@ import { import * as esbuild from 'esbuild'; import { normalizeOptions } from './lib/normalize'; -import { EsBuildExecutorOptions } from './schema'; +import { + EsBuildExecutorOptions, + NormalizedEsBuildExecutorOptions, +} from './schema'; import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable'; import { buildEsbuildOptions, @@ -213,7 +216,7 @@ export async function* esbuildExecutor( } function getTypeCheckOptions( - options: EsBuildExecutorOptions, + options: NormalizedEsBuildExecutorOptions, context: ExecutorContext ) { const { watch, tsConfig, outputPath } = options; @@ -243,7 +246,7 @@ function getTypeCheckOptions( } async function runTypeCheck( - options: EsBuildExecutorOptions, + options: NormalizedEsBuildExecutorOptions, context: ExecutorContext ) { const { errors, warnings } = await _runTypeCheck( diff --git a/packages/esbuild/src/executors/esbuild/lib/build-esbuild-options.ts b/packages/esbuild/src/executors/esbuild/lib/build-esbuild-options.ts index b3d32498fe..32df88ab18 100644 --- a/packages/esbuild/src/executors/esbuild/lib/build-esbuild-options.ts +++ b/packages/esbuild/src/executors/esbuild/lib/build-esbuild-options.ts @@ -125,7 +125,7 @@ export function buildEsbuildOptions( export function getOutExtension( format: 'cjs' | 'esm', - options: NormalizedEsBuildExecutorOptions + options: Pick ): '.cjs' | '.mjs' | '.js' { const userDefinedExt = options.userDefinedBuildOptions?.outExtension?.['.js']; // Allow users to change the output extensions from default CJS and ESM extensions. diff --git a/packages/esbuild/src/executors/esbuild/lib/normalize.ts b/packages/esbuild/src/executors/esbuild/lib/normalize.ts index 6ba0776dd8..8dd8c9afe4 100644 --- a/packages/esbuild/src/executors/esbuild/lib/normalize.ts +++ b/packages/esbuild/src/executors/esbuild/lib/normalize.ts @@ -17,7 +17,7 @@ export function normalizeOptions( const isTsSolutionSetup = isUsingTsSolutionSetup(); if (isTsSolutionSetup && options.generatePackageJson) { throw new Error( - `Setting 'generatePackageJson: true' is not allowed with the current TypeScript setup. Please update the 'package.json' file at the project root as needed and don't set the 'generatePackageJson' option.` + `Setting 'generatePackageJson: true' is not supported with the current TypeScript setup. Update the 'package.json' file at the project root as needed and unset the 'generatePackageJson' option.` ); } @@ -26,7 +26,7 @@ export function normalizeOptions( // If we're not generating package.json file, then copy it as-is as an asset when not using ts solution setup. const assets = options.generatePackageJson || isTsSolutionSetup - ? options.assets + ? options.assets ?? [] : [ ...options.assets, joinPathFragments( diff --git a/packages/esbuild/src/executors/esbuild/schema.d.ts b/packages/esbuild/src/executors/esbuild/schema.d.ts index ff0b926d1e..9dbf36f109 100644 --- a/packages/esbuild/src/executors/esbuild/schema.d.ts +++ b/packages/esbuild/src/executors/esbuild/schema.d.ts @@ -5,7 +5,7 @@ type Compiler = 'babel' | 'swc'; export interface EsBuildExecutorOptions { additionalEntryPoints?: string[]; - assets: (AssetGlob | string)[]; + assets?: (AssetGlob | string)[]; bundle?: boolean; declaration?: boolean; declarationRootDir?: string; @@ -32,7 +32,8 @@ export interface EsBuildExecutorOptions { export interface NormalizedEsBuildExecutorOptions extends Omit { + assets: (AssetGlob | string)[]; singleEntry: boolean; external: string[]; - userDefinedBuildOptions: esbuild.BuildOptions; + userDefinedBuildOptions: esbuild.BuildOptions | undefined; } diff --git a/packages/esbuild/src/generators/configuration/configuration.ts b/packages/esbuild/src/generators/configuration/configuration.ts index 51e6329e68..4225d3f6ff 100644 --- a/packages/esbuild/src/generators/configuration/configuration.ts +++ b/packages/esbuild/src/generators/configuration/configuration.ts @@ -1,33 +1,38 @@ import { formatFiles, joinPathFragments, + readJson, + readNxJson, readProjectConfiguration, Tree, updateProjectConfiguration, writeJson, } from '@nx/devkit'; - -import { getImportPath } from '@nx/js/src/utils/get-import-path'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; - -import { esbuildInitGenerator } from '../init/init'; -import { EsBuildExecutorOptions } from '../../executors/esbuild/schema'; -import { EsBuildProjectSchema } from './schema'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; +import { getOutputDir, getUpdatedPackageJsonContent } from '@nx/js'; +import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { basename, dirname, join } from 'node:path/posix'; +import { mergeTargetConfigurations } from 'nx/src/devkit-internals'; +import { PackageJson } from 'nx/src/utils/package-json'; +import { getOutExtension } from '../../executors/esbuild/lib/build-esbuild-options'; +import { EsBuildExecutorOptions } from '../../executors/esbuild/schema'; +import { esbuildInitGenerator } from '../init/init'; +import { EsBuildProjectSchema } from './schema'; export async function configurationGenerator( tree: Tree, options: EsBuildProjectSchema ) { - assertNotUsingTsSolutionSetup(tree, 'esbuild', 'configuration'); - const task = await esbuildInitGenerator(tree, { ...options, skipFormat: true, }); options.buildTarget ??= 'build'; + const isTsSolutionSetup = isUsingTsSolutionSetup(tree); checkForTargetConflicts(tree, options); - addBuildTarget(tree, options); + addBuildTarget(tree, options, isTsSolutionSetup); + updatePackageJson(tree, options, isTsSolutionSetup); await formatFiles(tree); return task; } @@ -42,53 +47,58 @@ function checkForTargetConflicts(tree: Tree, options: EsBuildProjectSchema) { } } -function addBuildTarget(tree: Tree, options: EsBuildProjectSchema) { +function addBuildTarget( + tree: Tree, + options: EsBuildProjectSchema, + isTsSolutionSetup: boolean +) { addBuildTargetDefaults(tree, '@nx/esbuild:esbuild', options.buildTarget); const project = readProjectConfiguration(tree, options.project); - const packageJsonPath = joinPathFragments(project.root, 'package.json'); - - if (!tree.exists(packageJsonPath)) { - const importPath = - options.importPath || getImportPath(tree, options.project); - writeJson(tree, packageJsonPath, { - name: importPath, - version: '0.0.1', - }); - } const prevBuildOptions = project.targets?.[options.buildTarget]?.options; - const tsConfig = prevBuildOptions?.tsConfig ?? getTsConfigFile(tree, options); + let outputPath = prevBuildOptions?.outputPath; + if (!outputPath) { + outputPath = isTsSolutionSetup + ? joinPathFragments(project.root, 'dist') + : joinPathFragments( + 'dist', + project.root === '.' ? options.project : project.root + ); + } + const buildOptions: EsBuildExecutorOptions = { main: prevBuildOptions?.main ?? getMainFile(tree, options), - outputPath: - prevBuildOptions?.outputPath ?? - joinPathFragments( - 'dist', - project.root === '.' ? options.project : project.root - ), + outputPath, outputFileName: 'main.js', tsConfig, - assets: [], platform: options.platform, + format: options.format, }; + if (isTsSolutionSetup) { + buildOptions.declarationRootDir = + project.sourceRoot ?? tree.exists(`${project.root}/src`) + ? `${project.root}/src` + : project.root; + } else { + buildOptions.assets = []; + + if (tree.exists(joinPathFragments(project.root, 'README.md'))) { + buildOptions.assets.push({ + glob: `${project.root}/README.md`, + input: '.', + output: '.', + }); + } + } + if (options.platform === 'browser') { buildOptions.outputHashing = 'all'; buildOptions.minify = true; } - if (tree.exists(joinPathFragments(project.root, 'README.md'))) { - buildOptions.assets = [ - { - glob: `${project.root}/README.md`, - input: '.', - output: '.', - }, - ]; - } - updateProjectConfiguration(tree, options.project, { ...project, targets: { @@ -111,6 +121,89 @@ function addBuildTarget(tree: Tree, options: EsBuildProjectSchema) { }); } +function updatePackageJson( + tree: Tree, + options: EsBuildProjectSchema, + isTsSolutionSetup: boolean +) { + const project = readProjectConfiguration(tree, options.project); + + const packageJsonPath = join(project.root, 'package.json'); + let packageJson: PackageJson; + if (tree.exists(packageJsonPath)) { + if (!isTsSolutionSetup) { + return; + } + + packageJson = readJson(tree, packageJsonPath); + } else { + packageJson = { + name: getImportPath(tree, options.project), + version: '0.0.1', + }; + } + + if (isTsSolutionSetup) { + const nxJson = readNxJson(tree); + const projectTarget = project.targets[options.buildTarget]; + const mergedTarget = mergeTargetConfigurations( + projectTarget, + (projectTarget.executor + ? nxJson.targetDefaults?.[projectTarget.executor] + : undefined) ?? nxJson.targetDefaults?.[options.buildTarget] + ); + + const { + declarationRootDir = '.', + main, + outputPath, + outputFileName, + // the executor option defaults to [esm] + format = ['esm'], + esbuildOptions, + } = mergedTarget.options; + + // can't use the declarationRootDir as rootDir because it only affects the typings, + // not the runtime entry point + packageJson = getUpdatedPackageJsonContent(packageJson, { + main, + outputPath, + projectRoot: project.root, + generateExportsField: true, + packageJsonPath, + format, + outputFileName, + outputFileExtensionForCjs: getOutExtension('cjs', { + // there's very little chance that the user would have defined a custom esbuild config + // since that's an Nx specific file that we're not generating here and we're setting up + // the build for esbuild now + userDefinedBuildOptions: esbuildOptions, + }), + outputFileExtensionForEsm: getOutExtension('esm', { + userDefinedBuildOptions: esbuildOptions, + }), + }); + + if (declarationRootDir !== dirname(main)) { + // the declaration file entry point will be output to a location + // different than the runtime entry point, adjust accodingly + const outputDir = getOutputDir({ + main, + outputPath, + projectRoot: project.root, + packageJsonPath, + rootDir: declarationRootDir, + }); + const mainFile = basename(options.main).replace(/\.[tj]s$/, ''); + const typingsFile = `${outputDir}${mainFile}.d.ts`; + packageJson.types = typingsFile; + packageJson.exports['.'].types = typingsFile; + } + } + + writeJson(tree, packageJsonPath, packageJson); +} + function getMainFile(tree: Tree, options: EsBuildProjectSchema) { const project = readProjectConfiguration(tree, options.project); const candidates = [ diff --git a/packages/esbuild/src/generators/configuration/schema.d.ts b/packages/esbuild/src/generators/configuration/schema.d.ts index 90c2ad3673..ca2bd75234 100644 --- a/packages/esbuild/src/generators/configuration/schema.d.ts +++ b/packages/esbuild/src/generators/configuration/schema.d.ts @@ -1,3 +1,5 @@ +import type { SupportedFormat } from '@nx/js'; + export interface EsBuildProjectSchema { project: string; main?: string; @@ -10,4 +12,5 @@ export interface EsBuildProjectSchema { esbuildConfig?: string; platform?: 'node' | 'browser' | 'neutral'; buildTarget?: string; + format?: SupportedFormat[]; } diff --git a/packages/esbuild/src/generators/configuration/schema.json b/packages/esbuild/src/generators/configuration/schema.json index a5797f1951..d656ea3de5 100644 --- a/packages/esbuild/src/generators/configuration/schema.json +++ b/packages/esbuild/src/generators/configuration/schema.json @@ -60,6 +60,15 @@ "description": "The build target to add.", "type": "string", "default": "build" + }, + "format": { + "description": "The format to build the library (esm or cjs).", + "type": "array", + "items": { + "type": "string", + "enum": ["esm", "cjs"] + }, + "default": ["esm"] } }, "required": [], diff --git a/packages/esbuild/src/generators/init/init.ts b/packages/esbuild/src/generators/init/init.ts index a6543e7fa1..2e6d9ed0f2 100644 --- a/packages/esbuild/src/generators/init/init.ts +++ b/packages/esbuild/src/generators/init/init.ts @@ -4,14 +4,11 @@ import { GeneratorCallback, Tree, } from '@nx/devkit'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { esbuildVersion } from '@nx/js/src/utils/versions'; import { nxVersion } from '../../utils/versions'; import { Schema } from './schema'; export async function esbuildInitGenerator(tree: Tree, schema: Schema) { - assertNotUsingTsSolutionSetup(tree, 'esbuild', 'init'); - let installTask: GeneratorCallback = () => {}; if (!schema.skipPackageJson) { installTask = addDependenciesToPackageJson( diff --git a/packages/expo/src/generators/library/library.ts b/packages/expo/src/generators/library/library.ts index b9377948c9..2c94266ed8 100644 --- a/packages/expo/src/generators/library/library.ts +++ b/packages/expo/src/generators/library/library.ts @@ -73,13 +73,13 @@ export async function expoLibraryGeneratorInternal( } initRootBabelConfig(host); + createFiles(host, options); + const addProjectTask = await addProject(host, options); if (addProjectTask) { tasks.push(addProjectTask); } - createFiles(host, options); - const lintTask = await addLinting(host, { ...options, projectName: options.name, diff --git a/packages/js/src/executors/swc/schema.json b/packages/js/src/executors/swc/schema.json index d5c07b2e76..aba847c74a 100644 --- a/packages/js/src/executors/swc/schema.json +++ b/packages/js/src/executors/swc/schema.json @@ -113,13 +113,13 @@ "description": "Generate a lockfile (e.g. package-lock.json) that matches the workspace lockfile to ensure package versions match.", "default": false, "x-priority": "internal" + }, + "stripLeadingPaths": { + "type": "boolean", + "description": "Remove leading directory from output (e.g. src). See: https://swc.rs/docs/usage/cli#--strip-leading-paths", + "default": false } }, - "stripLeadingPaths": { - "type": "boolean", - "description": "Remove leading directory from output (e.g. src). See: https://swc.rs/docs/usage/cli#--strip-leading-paths", - "default": false - }, "required": ["main", "outputPath", "tsConfig"], "definitions": { "assetPattern": { diff --git a/packages/js/src/generators/convert-to-swc/convert-to-swc.spec.ts b/packages/js/src/generators/convert-to-swc/convert-to-swc.spec.ts index b1d2d8f864..1d6daefe2c 100644 --- a/packages/js/src/generators/convert-to-swc/convert-to-swc.spec.ts +++ b/packages/js/src/generators/convert-to-swc/convert-to-swc.spec.ts @@ -1,6 +1,12 @@ import 'nx/src/internal-testing-utils/mock-project-graph'; -import { readProjectConfiguration, Tree } from '@nx/devkit'; +import { + addProjectConfiguration, + readJson, + readProjectConfiguration, + Tree, + writeJson, +} from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { join } from 'path'; import { LibraryGeneratorSchema } from '../library/schema'; @@ -23,10 +29,8 @@ describe('convert to swc', () => { bundler: 'tsc', }; - beforeAll(() => { + beforeEach(() => { tree = createTreeWithEmptyWorkspace(); - tree.write('/.gitignore', ''); - tree.write('/.gitignore', ''); }); it('should convert tsc to swc', async () => { @@ -50,9 +54,33 @@ describe('convert to swc', () => { join(readProjectConfiguration(tree, 'tsc-lib').root, '.swcrc') ) ).toEqual(true); - expect(tree.read('package.json', 'utf-8')).toContain('@swc/core'); - expect(tree.read('tsc-lib/package.json', 'utf-8')).toContain( - '@swc/helpers' - ); + expect( + readJson(tree, 'package.json').devDependencies['@swc/core'] + ).toBeDefined(); + expect( + readJson(tree, 'tsc-lib/package.json').dependencies['@swc/helpers'] + ).toBeDefined(); + }); + + it('should handle project configuration without targets', async () => { + addProjectConfiguration(tree, 'lib1', { root: 'lib1' }); + + await expect( + convertToSwcGenerator(tree, { project: 'lib1' }) + ).resolves.not.toThrow(); + }); + + it('should not add swc dependencies when no target was updated', async () => { + addProjectConfiguration(tree, 'lib1', { root: 'lib1' }); + writeJson(tree, 'lib1/package.json', { dependencies: {} }); + + await convertToSwcGenerator(tree, { project: 'lib1' }); + + expect( + readJson(tree, 'package.json').devDependencies['@swc/core'] + ).not.toBeDefined(); + expect( + readJson(tree, 'lib1/package.json').dependencies['@swc/helpers'] + ).not.toBeDefined(); }); }); diff --git a/packages/js/src/generators/convert-to-swc/convert-to-swc.ts b/packages/js/src/generators/convert-to-swc/convert-to-swc.ts index ee1c5aa5f2..e4fca79575 100644 --- a/packages/js/src/generators/convert-to-swc/convert-to-swc.ts +++ b/packages/js/src/generators/convert-to-swc/convert-to-swc.ts @@ -20,13 +20,14 @@ export async function convertToSwcGenerator( const options = normalizeOptions(schema); const projectConfiguration = readProjectConfiguration(tree, options.project); - updateProjectBuildTargets( + const updated = updateProjectBuildTargets( tree, projectConfiguration, options.project, options.targets ); - return checkSwcDependencies(tree, projectConfiguration); + + return updated ? checkSwcDependencies(tree, projectConfiguration) : () => {}; } function normalizeOptions( @@ -47,8 +48,9 @@ function updateProjectBuildTargets( projectName: string, projectTargets: string[] ) { + let updated = false; for (const target of projectTargets) { - const targetConfiguration = projectConfiguration.targets[target]; + const targetConfiguration = projectConfiguration.targets?.[target]; if ( !targetConfiguration || (targetConfiguration.executor !== '@nx/js:tsc' && @@ -56,9 +58,14 @@ function updateProjectBuildTargets( ) continue; targetConfiguration.executor = '@nx/js:swc'; + updated = true; } - updateProjectConfiguration(tree, projectName, projectConfiguration); + if (updated) { + updateProjectConfiguration(tree, projectName, projectConfiguration); + } + + return updated; } function checkSwcDependencies( diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index 7ddd9a26c0..f3309958ca 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -67,7 +67,7 @@ import { getProjectPackageManagerWorkspaceStateWarningTask } from './utils/packa import { ensureProjectIsExcludedFromPluginRegistrations, ensureProjectIsIncludedInPluginRegistrations, -} from './utils/plugin-registrations'; +} from '../../utils/typescript/plugin'; const defaultOutputDirectory = 'dist'; diff --git a/packages/js/src/generators/setup-build/generator.ts b/packages/js/src/generators/setup-build/generator.ts index c39416c3e3..aa7f4d73f7 100644 --- a/packages/js/src/generators/setup-build/generator.ts +++ b/packages/js/src/generators/setup-build/generator.ts @@ -1,18 +1,37 @@ import { ensurePackage, formatFiles, - type GeneratorCallback, joinPathFragments, + readJson, + readNxJson, readProjectConfiguration, runTasksInSerial, - type Tree, + updateNxJson, updateProjectConfiguration, + writeJson, + type GeneratorCallback, + type ProjectConfiguration, + type Tree, } from '@nx/devkit'; +import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; +import { basename, dirname, join } from 'node:path/posix'; +import { mergeTargetConfigurations } from 'nx/src/devkit-internals'; +import type { PackageJson } from 'nx/src/utils/package-json'; +import { ensureProjectIsIncludedInPluginRegistrations } from '../..//utils/typescript/plugin'; +import { getImportPath } from '../../utils/get-import-path'; +import { + getUpdatedPackageJsonContent, + type SupportedFormat, +} from '../../utils/package-json/update-package-json'; import { addSwcConfig } from '../../utils/swc/add-swc-config'; import { addSwcDependencies } from '../../utils/swc/add-swc-dependencies'; +import { ensureTypescript } from '../../utils/typescript/ensure-typescript'; +import { readTsConfig } from '../../utils/typescript/ts-config'; +import { isUsingTsSolutionSetup } from '../../utils/typescript/ts-solution-setup'; import { nxVersion } from '../../utils/versions'; import { SetupBuildGeneratorSchema } from './schema'; -import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; + +let ts: typeof import('typescript'); export async function setupBuildGenerator( tree: Tree, @@ -20,8 +39,8 @@ export async function setupBuildGenerator( ): Promise { const tasks: GeneratorCallback[] = []; const project = readProjectConfiguration(tree, options.project); - const buildTarget = options.buildTarget ?? 'build'; - const prevBuildOptions = project.targets?.[buildTarget]?.options; + options.buildTarget ??= 'build'; + const prevBuildOptions = project.targets?.[options.buildTarget]?.options; project.targets ??= {}; @@ -49,6 +68,7 @@ export async function setupBuildGenerator( `Cannot locate a main file for ${options.project}. Please specify one using --main=.` ); } + options.main = mainFile; let tsConfigFile: string; if (prevBuildOptions?.tsConfig) { @@ -73,6 +93,13 @@ export async function setupBuildGenerator( `Cannot locate a tsConfig file for ${options.project}. Please specify one using --tsConfig=.` ); } + options.tsConfig = tsConfigFile; + + const isTsSolutionSetup = isUsingTsSolutionSetup(tree); + const nxJson = readNxJson(tree); + const addPlugin = + process.env.NX_ADD_PLUGINS !== 'false' && + nxJson.useInferencePlugins !== false; switch (options.bundler) { case 'vite': { @@ -83,10 +110,11 @@ export async function setupBuildGenerator( const task = await viteConfigurationGenerator(tree, { buildTarget: options.buildTarget, project: options.project, - newProject: true, + newProject: false, uiFramework: 'none', includeVitest: false, includeLib: true, + addPlugin, skipFormat: true, }); tasks.push(task); @@ -103,6 +131,7 @@ export async function setupBuildGenerator( project: options.project, skipFormat: true, skipValidation: true, + format: ['cjs'], }); tasks.push(task); break; @@ -116,6 +145,7 @@ export async function setupBuildGenerator( project: options.project, compiler: 'tsc', format: ['cjs', 'esm'], + addPlugin, skipFormat: true, skipValidation: true, }); @@ -123,39 +153,61 @@ export async function setupBuildGenerator( break; } case 'tsc': { - addBuildTargetDefaults(tree, '@nx/js:tsc'); + if (isTsSolutionSetup) { + const nxJson = readNxJson(tree); + ensureProjectIsIncludedInPluginRegistrations( + nxJson, + project.root, + options.buildTarget + ); + updateNxJson(tree, nxJson); + updatePackageJsonForTsc(tree, options, project); + } else { + addBuildTargetDefaults(tree, '@nx/js:tsc'); - const outputPath = joinPathFragments('dist', project.root); - project.targets[buildTarget] = { - executor: `@nx/js:tsc`, - outputs: ['{options.outputPath}'], - options: { - outputPath, - main: mainFile, - tsConfig: tsConfigFile, - assets: [], - }, - }; - updateProjectConfiguration(tree, options.project, project); + const outputPath = joinPathFragments('dist', project.root); + project.targets[options.buildTarget] = { + executor: `@nx/js:tsc`, + outputs: ['{options.outputPath}'], + options: { + outputPath, + main: mainFile, + tsConfig: tsConfigFile, + assets: [], + }, + }; + updateProjectConfiguration(tree, options.project, project); + } break; } case 'swc': { addBuildTargetDefaults(tree, '@nx/js:swc'); - const outputPath = joinPathFragments('dist', project.root); - project.targets[buildTarget] = { + const outputPath = isTsSolutionSetup + ? joinPathFragments(project.root, 'dist') + : joinPathFragments('dist', project.root); + project.targets[options.buildTarget] = { executor: `@nx/js:swc`, outputs: ['{options.outputPath}'], options: { outputPath, main: mainFile, tsConfig: tsConfigFile, - assets: [], }, }; + + if (isTsSolutionSetup) { + project.targets[options.buildTarget].options.stripLeadingPaths = true; + } else { + project.targets[options.buildTarget].options.assets = []; + } + updateProjectConfiguration(tree, options.project, project); - addSwcDependencies(tree); - addSwcConfig(tree, project.root, 'es6'); + tasks.push(addSwcDependencies(tree)); + addSwcConfig(tree, project.root, 'commonjs'); + if (isTsSolutionSetup) { + updatePackageJsonForSwc(tree, options, project); + } } } @@ -165,3 +217,138 @@ export async function setupBuildGenerator( } export default setupBuildGenerator; + +function updatePackageJsonForTsc( + tree: Tree, + options: SetupBuildGeneratorSchema, + project: ProjectConfiguration +) { + if (!ts) { + ts = ensureTypescript(); + } + + const tsconfig = readTsConfig(options.tsConfig, { + ...ts.sys, + readFile: (p) => tree.read(p, 'utf-8'), + fileExists: (p) => tree.exists(p), + }); + + let main: string; + let rootDir: string; + let outputPath: string; + if (project.targets?.[options.buildTarget]) { + const mergedTarget = mergeTargetDefaults( + tree, + project, + options.buildTarget + ); + ({ main, rootDir, outputPath } = mergedTarget.options); + } else { + main = options.main; + + ({ rootDir = project.root, outDir: outputPath } = tsconfig.options); + const tsOutFile = tsconfig.options.outFile; + + if (tsOutFile) { + main = join(project.root, basename(tsOutFile)); + outputPath = dirname(tsOutFile); + } + + if (!outputPath) { + outputPath = project.root; + } + } + + const module = Object.keys(ts.ModuleKind).find( + (m) => ts.ModuleKind[m] === tsconfig.options.module + ); + const format: SupportedFormat[] = module.toLowerCase().startsWith('es') + ? ['esm'] + : ['cjs']; + + updatePackageJson( + tree, + options.project, + project.root, + main, + outputPath, + rootDir, + format + ); +} + +function updatePackageJsonForSwc( + tree: Tree, + options: SetupBuildGeneratorSchema, + project: ProjectConfiguration +) { + const mergedTarget = mergeTargetDefaults(tree, project, options.buildTarget); + const { + main, + outputPath, + swcrc: swcrcPath = join(project.root, '.swcrc'), + } = mergedTarget.options; + + const swcrc = readJson(tree, swcrcPath); + const format: SupportedFormat[] = swcrc.module?.type?.startsWith('es') + ? ['esm'] + : ['cjs']; + + updatePackageJson( + tree, + options.project, + project.root, + main, + outputPath, + // we set the `stripLeadingPaths` option, so the rootDir would match the dirname of the entry point + dirname(main), + format + ); +} + +function updatePackageJson( + tree: Tree, + projectName: string, + projectRoot: string, + main: string, + outputPath: string, + rootDir: string, + format?: SupportedFormat[] +) { + const packageJsonPath = join(projectRoot, 'package.json'); + let packageJson: PackageJson; + if (tree.exists(packageJsonPath)) { + packageJson = readJson(tree, packageJsonPath); + } else { + packageJson = { + name: getImportPath(tree, projectName), + version: '0.0.1', + }; + } + packageJson = getUpdatedPackageJsonContent(packageJson, { + main, + outputPath, + projectRoot, + generateExportsField: true, + packageJsonPath, + rootDir, + format, + }); + writeJson(tree, packageJsonPath, packageJson); +} + +function mergeTargetDefaults( + tree: Tree, + project: ProjectConfiguration, + buildTarget: string +) { + const nxJson = readNxJson(tree); + const projectTarget = project.targets[buildTarget]; + + return mergeTargetConfigurations( + projectTarget, + (projectTarget.executor + ? nxJson.targetDefaults?.[projectTarget.executor] + : undefined) ?? nxJson.targetDefaults?.[buildTarget] + ); +} diff --git a/packages/js/src/utils/package-json/update-package-json.spec.ts b/packages/js/src/utils/package-json/update-package-json.spec.ts index 55558a3244..784cd866c1 100644 --- a/packages/js/src/utils/package-json/update-package-json.spec.ts +++ b/packages/js/src/utils/package-json/update-package-json.spec.ts @@ -156,7 +156,10 @@ describe('getUpdatedPackageJsonContent', () => { types: './src/index.d.ts', version: '0.0.1', exports: { - '.': './src/index.js', + '.': { + import: './src/index.js', + types: './src/index.d.ts', + }, './package.json': './package.json', }, }); @@ -185,7 +188,10 @@ describe('getUpdatedPackageJsonContent', () => { version: '0.0.1', type: 'commonjs', exports: { - '.': './src/index.cjs', + '.': { + default: './src/index.cjs', + types: './src/index.d.ts', + }, './package.json': './package.json', }, }); @@ -220,7 +226,10 @@ describe('getUpdatedPackageJsonContent', () => { types: './src/index.d.ts', version: '0.0.1', exports: { - '.': './src/index.js', + '.': { + default: './src/index.js', + types: './src/index.d.ts', + }, './foo': './src/foo.js', './bar': './src/bar.js', './package.json': './package.json', @@ -258,7 +267,10 @@ describe('getUpdatedPackageJsonContent', () => { types: './src/index.d.ts', version: '0.0.1', exports: { - '.': './src/index.js', + '.': { + import: './src/index.js', + types: './src/index.d.ts', + }, './foo': './src/foo.js', './bar': './src/bar.js', './package.json': './package.json', @@ -298,6 +310,7 @@ describe('getUpdatedPackageJsonContent', () => { '.': { import: './src/index.js', default: './src/index.cjs', + types: './src/index.d.ts', }, './foo': { import: './src/foo.js', @@ -351,6 +364,7 @@ describe('getUpdatedPackageJsonContent', () => { '.': { import: './src/index.js', default: './src/index.cjs', + types: './src/index.d.ts', }, './package.json': './package.json', './custom': './custom.js', @@ -383,7 +397,10 @@ describe('getUpdatedPackageJsonContent', () => { version: '0.0.1', type: 'module', exports: { - '.': './src/index.cjs', + '.': { + default: './src/index.cjs', + types: './src/index.d.ts', + }, './package.json': './package.json', }, }); diff --git a/packages/js/src/utils/package-json/update-package-json.ts b/packages/js/src/utils/package-json/update-package-json.ts index 0e53f7f6b1..523a12774c 100644 --- a/packages/js/src/utils/package-json/update-package-json.ts +++ b/packages/js/src/utils/package-json/update-package-json.ts @@ -22,7 +22,7 @@ import { } from '@nx/devkit'; import { DependentBuildableProjectNode } from '../buildable-libs-utils'; import { existsSync, writeFileSync } from 'node:fs'; -import { basename, join, parse } from 'path'; +import { basename, dirname, join, parse, relative } from 'path'; import { fileExists } from 'nx/src/utils/fileutils'; import type { PackageJson } from 'nx/src/utils/package-json'; import { readFileMapCache } from 'nx/src/project-graph/nx-deps-cache'; @@ -47,6 +47,7 @@ export interface UpdatePackageJsonOption { updateBuildableProjectDepsInPackageJson?: boolean; buildableProjectDepsInPackageJsonType?: 'dependencies' | 'peerDependencies'; generateLockfile?: boolean; + packageJsonPath?: string; } export function updatePackageJson( @@ -233,21 +234,18 @@ export function getExports( | 'projectRoot' | 'outputFileName' | 'additionalEntryPoints' + | 'outputPath' + | 'packageJsonPath' > & { fileExt: string; } ): Exports { + const outputDir = getOutputDir(options); const mainFile = options.outputFileName ? options.outputFileName.replace(/\.[tj]s$/, '') : basename(options.main).replace(/\.[tj]s$/, ''); - const relativeMainFileDir = options.outputFileName - ? './' - : getRelativeDirectoryToProjectRoot( - options.main, - options.rootDir ?? options.projectRoot - ); const exports: Exports = { - '.': relativeMainFileDir + mainFile + options.fileExt, + '.': outputDir + mainFile + options.fileExt, }; if (options.additionalEntryPoints) { @@ -289,6 +287,24 @@ export function getUpdatedPackageJsonContent( packageJson.exports['./package.json'] ??= './package.json'; } + if (!options.skipTypings) { + const mainFile = basename(options.main).replace(/\.[tj]s$/, ''); + const outputDir = getOutputDir(options); + const typingsFile = `${outputDir}${mainFile}.d.ts`; + packageJson.types ??= typingsFile; + + if (options.generateExportsField) { + if (!packageJson.exports['.']) { + packageJson.exports['.'] = { types: typingsFile }; + } else if ( + typeof packageJson.exports['.'] === 'object' && + !packageJson.exports['.'].types + ) { + packageJson.exports['.'].types = typingsFile; + } + } + } + if (hasEsmFormat) { const esmExports = getExports({ ...options, @@ -304,9 +320,13 @@ export function getUpdatedPackageJsonContent( if (options.generateExportsField) { for (const [exportEntry, filePath] of Object.entries(esmExports)) { - packageJson.exports[exportEntry] ??= hasCjsFormat - ? { import: filePath } - : filePath; + if (!packageJson.exports[exportEntry]) { + packageJson.exports[exportEntry] ??= hasCjsFormat + ? { import: filePath } + : filePath; + } else if (typeof packageJson.exports[exportEntry] === 'object') { + packageJson.exports[exportEntry].import ??= filePath; + } } } } @@ -327,24 +347,39 @@ export function getUpdatedPackageJsonContent( if (options.generateExportsField) { for (const [exportEntry, filePath] of Object.entries(cjsExports)) { - if (hasEsmFormat) { - packageJson.exports[exportEntry]['default'] ??= filePath; - } else { - packageJson.exports[exportEntry] ??= filePath; + if (!packageJson.exports[exportEntry]) { + packageJson.exports[exportEntry] ??= hasEsmFormat + ? { default: filePath } + : filePath; + } else if (typeof packageJson.exports[exportEntry] === 'object') { + packageJson.exports[exportEntry].default ??= filePath; } } } } - if (!options.skipTypings) { - const mainFile = basename(options.main).replace(/\.[tj]s$/, ''); - const relativeMainFileDir = getRelativeDirectoryToProjectRoot( - options.main, - options.projectRoot - ); - const typingsFile = `${relativeMainFileDir}${mainFile}.d.ts`; - packageJson.types ??= typingsFile; - } - return packageJson; } + +export function getOutputDir( + options: Pick< + UpdatePackageJsonOption, + | 'main' + | 'rootDir' + | 'projectRoot' + | 'outputFileName' + | 'outputPath' + | 'packageJsonPath' + > +): string { + const packageJsonDir = options.packageJsonPath + ? dirname(options.packageJsonPath) + : options.outputPath; + const relativeOutputPath = relative(packageJsonDir, options.outputPath); + const relativeMainDir = options.outputFileName + ? '' + : relative(options.rootDir ?? options.projectRoot, dirname(options.main)); + const outputDir = join(relativeOutputPath, relativeMainDir); + + return outputDir === '.' ? `./` : `./${outputDir}/`; +} diff --git a/packages/js/src/generators/library/utils/plugin-registrations.spec.ts b/packages/js/src/utils/typescript/plugin.spec.ts similarity index 78% rename from packages/js/src/generators/library/utils/plugin-registrations.spec.ts rename to packages/js/src/utils/typescript/plugin.spec.ts index d6b2b9a5e7..a5283939d5 100644 --- a/packages/js/src/generators/library/utils/plugin-registrations.spec.ts +++ b/packages/js/src/utils/typescript/plugin.spec.ts @@ -2,33 +2,9 @@ import type { NxJsonConfiguration } from '@nx/devkit'; import { ensureProjectIsExcludedFromPluginRegistrations, ensureProjectIsIncludedInPluginRegistrations, -} from './plugin-registrations'; +} from './plugin'; describe('ensureProjectIsIncludedInPluginRegistrations', () => { - it('should do nothing when there is no `plugin` entry', () => { - const nxJson: NxJsonConfiguration = {}; - - ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1'); - - expect(nxJson).toStrictEqual({}); - }); - - it('should do nothing when the there are no plugins', () => { - const nxJson: NxJsonConfiguration = { plugins: [] }; - - ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1'); - - expect(nxJson).toStrictEqual({ plugins: [] }); - }); - - it('should do nothing when the are no registrations for the `@nx/js/typescript` plugin', () => { - const nxJson: NxJsonConfiguration = { plugins: ['@foo/bar/plugin'] }; - - ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1'); - - expect(nxJson).toStrictEqual({ plugins: ['@foo/bar/plugin'] }); - }); - it('should do nothing when `include`/`exclude` are not set in a plugin registration that infers both targets', () => { const originalNxJson: NxJsonConfiguration = { plugins: [ @@ -203,6 +179,44 @@ describe('ensureProjectIsIncludedInPluginRegistrations', () => { }); }); + it('should exclude a project from a plugin registration with a different build target nama and add a new plugin registration that includes it', () => { + const nxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + options: { typecheck: { targetName: 'typecheck' } }, + }, + ], + }; + + ensureProjectIsIncludedInPluginRegistrations( + nxJson, + 'packages/pkg1', + 'build-tsc' + ); + + expect(nxJson).toStrictEqual({ + plugins: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/pkg1/*'], + options: { typecheck: { targetName: 'typecheck' } }, + }, + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build-tsc', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }); + }); + it('should include a project in a plugin registration that infers both targets and with `include` set but not including the project', () => { const nxJson: NxJsonConfiguration = { plugins: [ @@ -239,6 +253,57 @@ describe('ensureProjectIsIncludedInPluginRegistrations', () => { }); }); + it('should not include a project in a plugin registration that infers both targets with a different build target name and with `include` set but not including the project', () => { + const nxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }; + + ensureProjectIsIncludedInPluginRegistrations( + nxJson, + 'packages/pkg2', + 'build-tsc' + ); + + expect(nxJson).toStrictEqual({ + plugins: [ + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + { + plugin: '@nx/js/typescript', + include: ['packages/pkg2/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build-tsc', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }); + }); + it('should add a new plugin registration including the project when there is an existing plugin registration that infers both targets and with `exclude` set excluding the project', () => { const nxJson: NxJsonConfiguration = { plugins: [ @@ -285,6 +350,42 @@ describe('ensureProjectIsIncludedInPluginRegistrations', () => { ], }); }); + + it('should remove glob pattern from `exclude` when it matches exactly the project root glob pattern', () => { + const nxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/pkg1/*', 'packages/pkg2/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }; + + ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toStrictEqual({ + plugins: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/pkg2/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }); + }); }); describe('ensureProjectIsExcludedFromPluginRegistrations', () => { diff --git a/packages/js/src/generators/library/utils/plugin-registrations.ts b/packages/js/src/utils/typescript/plugin.ts similarity index 58% rename from packages/js/src/generators/library/utils/plugin-registrations.ts rename to packages/js/src/utils/typescript/plugin.ts index b9c082b43b..9666d9f53c 100644 --- a/packages/js/src/generators/library/utils/plugin-registrations.ts +++ b/packages/js/src/utils/typescript/plugin.ts @@ -3,19 +3,14 @@ import type { NxJsonConfiguration, } from '@nx/devkit'; import { findMatchingConfigFiles } from 'nx/src/devkit-internals'; -import type { TscPluginOptions } from '../../../plugins/typescript/plugin'; +import type { TscPluginOptions } from '../../plugins/typescript/plugin'; export function ensureProjectIsIncludedInPluginRegistrations( nxJson: NxJsonConfiguration, - projectRoot: string + projectRoot: string, + buildTargetName: string = 'build' ): void { - if ( - !nxJson.plugins?.length || - !nxJson.plugins.some(isTypeScriptPluginRegistration) - ) { - return; - } - + nxJson.plugins ??= []; let isIncluded = false; let index = 0; for (const registration of nxJson.plugins) { @@ -26,7 +21,7 @@ export function ensureProjectIsIncludedInPluginRegistrations( if (typeof registration === 'string') { // if it's a string all projects are included but the are no user-specified options - // and the `build` task is not inferred by default, so we need to exclude it + // and the build task is not inferred by default, so we need to exclude it nxJson.plugins[index] = { plugin: '@nx/js/typescript', exclude: [`${projectRoot}/*`], @@ -41,43 +36,58 @@ export function ensureProjectIsIncludedInPluginRegistrations( ); if (matchingConfigFiles.length) { // it's included by the plugin registration, check if the user-specified options would result - // in a `build` task being inferred, if not, we need to exclude it - if (registration.options?.typecheck && registration.options?.build) { - // it has the desired options, do nothing + // in the appropriate build task being inferred, if not, we need to exclude it + if ( + registration.options?.typecheck !== false && + matchesBuildTarget(registration.options?.build, buildTargetName) + ) { + // it has the desired options, do nothing, but continue processing + // other registrations to exclude as needed isIncluded = true; } else { - // it would not have the `build` task inferred, so we need to exclude it + // it would not have the typecheck or build task inferred, so we need to exclude it registration.exclude ??= []; registration.exclude.push(`${projectRoot}/*`); } } else if ( - registration.options?.typecheck && - registration.options?.build && - !registration.exclude?.length + !isIncluded && + registration.options?.typecheck !== false && + matchesBuildTarget(registration.options?.build, buildTargetName) ) { - // negative pattern are not supported by the `exclude` option so we - // can't update it to not exclude the project, so we only update the - // plugin registration if there's no `exclude` option, in which case - // the plugin registration should have an `include` options that doesn't - // include the project - isIncluded = true; - registration.include ??= []; - registration.include.push(`${projectRoot}/*`); + if (!registration.exclude?.length) { + // negative pattern are not supported by the `exclude` option so we + // can't update it to not exclude the project, so we only update the + // plugin registration if there's no `exclude` option, in which case + // the plugin registration should have an `include` options that doesn't + // include the project + isIncluded = true; + registration.include ??= []; + registration.include.push(`${projectRoot}/*`); + } else if (registration.exclude?.includes(`${projectRoot}/*`)) { + isIncluded = true; + registration.exclude = registration.exclude.filter( + (e) => e !== `${projectRoot}/*` + ); + if (!registration.exclude.length) { + // if there's no `exclude` option left, we can remove the exclude option + delete registration.exclude; + } + } } } index++; } if (!isIncluded) { - // the project is not included by any plugin registration with an inferred `build` task, - // so we create a new plugin registration for it + // the project is not included by any plugin registration with an inferred build task + // with the given name, so we create a new plugin registration for it nxJson.plugins.push({ plugin: '@nx/js/typescript', include: [`${projectRoot}/*`], options: { typecheck: { targetName: 'typecheck' }, build: { - targetName: 'build', + targetName: buildTargetName, configName: 'tsconfig.lib.json', }, }, @@ -139,3 +149,21 @@ function isTypeScriptPluginRegistration( (typeof plugin !== 'string' && plugin.plugin === '@nx/js/typescript') ); } + +function matchesBuildTarget( + buildOptions: TscPluginOptions['build'], + buildTargetName: string +): boolean { + if (buildOptions === undefined || buildOptions === false) { + return false; + } + + if (buildOptions === true && buildTargetName === 'build') { + return true; + } + + return ( + typeof buildOptions === 'object' && + buildOptions.targetName === buildTargetName + ); +} diff --git a/packages/js/src/utils/typescript/ts-config.ts b/packages/js/src/utils/typescript/ts-config.ts index 236e367f7f..a68d798a3f 100644 --- a/packages/js/src/utils/typescript/ts-config.ts +++ b/packages/js/src/utils/typescript/ts-config.ts @@ -1,22 +1,25 @@ import { offsetFromRoot, Tree, updateJson, workspaceRoot } from '@nx/devkit'; import { existsSync } from 'fs'; import { dirname, join } from 'path'; -import * as ts from 'typescript'; +import type * as ts from 'typescript'; import { ensureTypescript } from './ensure-typescript'; let tsModule: typeof import('typescript'); -export function readTsConfig(tsConfigPath: string): ts.ParsedCommandLine { +export function readTsConfig( + tsConfigPath: string, + sys?: ts.System +): ts.ParsedCommandLine { if (!tsModule) { tsModule = require('typescript'); } - const readResult = tsModule.readConfigFile( - tsConfigPath, - tsModule.sys.readFile - ); + + sys ??= tsModule.sys; + + const readResult = tsModule.readConfigFile(tsConfigPath, sys.readFile); return tsModule.parseJsonConfigFileContent( readResult.config, - tsModule.sys, + sys, dirname(tsConfigPath) ); } diff --git a/packages/react-native/src/generators/library/library.ts b/packages/react-native/src/generators/library/library.ts index 01c65d63cf..7e5a8b3d4f 100644 --- a/packages/react-native/src/generators/library/library.ts +++ b/packages/react-native/src/generators/library/library.ts @@ -71,13 +71,13 @@ export async function reactNativeLibraryGeneratorInternal( tasks.push(ensureDependencies(host)); } + createFiles(host, options); + const addProjectTask = await addProject(host, options); if (addProjectTask) { tasks.push(addProjectTask); } - createFiles(host, options); - const lintTask = await addLinting(host, { ...options, projectName: options.name, diff --git a/packages/rollup/src/generators/configuration/configuration.spec.ts b/packages/rollup/src/generators/configuration/configuration.spec.ts index d16b98d28f..e68d286da1 100644 --- a/packages/rollup/src/generators/configuration/configuration.spec.ts +++ b/packages/rollup/src/generators/configuration/configuration.spec.ts @@ -56,7 +56,7 @@ describe('configurationGenerator', () => { it('should support --main option', async () => { await configurationGenerator(tree, { project: 'mypkg', - main: './src/index.ts', + main: './libs/mypkg/src/index.ts', }); const rollupConfig = tree.read('libs/mypkg/rollup.config.js', 'utf-8'); @@ -85,7 +85,7 @@ module.exports = withNx( it('should support --tsConfig option', async () => { await configurationGenerator(tree, { project: 'mypkg', - tsConfig: './tsconfig.custom.json', + tsConfig: 'libs/mypkg/tsconfig.custom.json', }); const rollupConfig = tree.read('libs/mypkg/rollup.config.js', 'utf-8'); diff --git a/packages/rollup/src/generators/configuration/configuration.ts b/packages/rollup/src/generators/configuration/configuration.ts index a2944ba611..df1096bf57 100644 --- a/packages/rollup/src/generators/configuration/configuration.ts +++ b/packages/rollup/src/generators/configuration/configuration.ts @@ -3,24 +3,31 @@ import { GeneratorCallback, joinPathFragments, offsetFromRoot, + readJson, readNxJson, readProjectConfiguration, runTasksInSerial, - stripIndents, Tree, + updateJson, updateProjectConfiguration, writeJson, } from '@nx/devkit'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; -import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; - -import { rollupInitGenerator } from '../init/init'; -import { RollupExecutorOptions } from '../../executors/rollup/schema'; -import { RollupProjectSchema } from './schema'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; +import { getUpdatedPackageJsonContent, readTsConfig } from '@nx/js'; +import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { dirname, join, relative } from 'node:path/posix'; +import { mergeTargetConfigurations } from 'nx/src/devkit-internals'; +import type { PackageJson } from 'nx/src/utils/package-json'; +import { RollupExecutorOptions } from '../../executors/rollup/schema'; +import { RollupWithNxPluginOptions } from '../../plugins/with-nx/with-nx-options'; import { ensureDependencies } from '../../utils/ensure-dependencies'; import { hasPlugin } from '../../utils/has-plugin'; -import { RollupWithNxPluginOptions } from '../../plugins/with-nx/with-nx-options'; +import { rollupInitGenerator } from '../init/init'; +import { RollupProjectSchema } from './schema'; + +let ts: typeof import('typescript'); export async function configurationGenerator( tree: Tree, @@ -39,15 +46,20 @@ export async function configurationGenerator( tasks.push(ensureDependencies(tree, options)); } + const isTsSolutionSetup = isUsingTsSolutionSetup(tree); + let outputConfig: OutputConfig | undefined; if (hasPlugin(tree)) { - createRollupConfig(tree, options); + outputConfig = createRollupConfig(tree, options, isTsSolutionSetup); } else { options.buildTarget ??= 'build'; checkForTargetConflicts(tree, options); - addBuildTarget(tree, options); + addBuildTarget(tree, options, isTsSolutionSetup); } - addPackageJson(tree, options); + updatePackageJson(tree, options, outputConfig, isTsSolutionSetup); + if (isTsSolutionSetup) { + updateTsConfig(tree, options); + } if (!options.skipFormat) { await formatFiles(tree); @@ -56,20 +68,34 @@ export async function configurationGenerator( return runTasksInSerial(...tasks); } -function createRollupConfig(tree: Tree, options: RollupProjectSchema) { - const isUsingTsPlugin = isUsingTsSolutionSetup(tree); +type OutputConfig = { + main: string; + outputPath: string; +}; +function createRollupConfig( + tree: Tree, + options: RollupProjectSchema, + isTsSolutionSetup: boolean +): OutputConfig { const project = readProjectConfiguration(tree, options.project); + const main = options.main + ? `./${relative(project.root, options.main)}` + : './src/index.ts'; + const outputPath = isTsSolutionSetup + ? './dist' + : joinPathFragments( + offsetFromRoot(project.root), + 'dist', + project.root === '.' ? project.name : project.root + ); + const buildOptions: RollupWithNxPluginOptions = { - outputPath: isUsingTsPlugin - ? './dist' - : joinPathFragments( - offsetFromRoot(project.root), - 'dist', - project.root === '.' ? project.name : project.root - ), + outputPath, compiler: options.compiler ?? 'babel', - main: options.main ?? './src/index.ts', - tsConfig: options.tsConfig ?? './tsconfig.lib.json', + main, + tsConfig: options.tsConfig + ? `./${relative(project.root, options.tsConfig)}` + : './tsconfig.lib.json', }; tree.write( @@ -82,8 +108,12 @@ module.exports = withNx( outputPath: '${buildOptions.outputPath}', tsConfig: '${buildOptions.tsConfig}', compiler: '${buildOptions.compiler}', - format: ${JSON.stringify(options.format ?? ['esm'])}, - assets: [{ input: '.', output: '.', glob:'*.md' }], + format: ${JSON.stringify(options.format ?? ['esm'])},${ + !isTsSolutionSetup + ? ` + assets: [{ input: '.', output: '.', glob:'*.md' }],` + : '' + } }, { // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options @@ -93,6 +123,11 @@ module.exports = withNx( ); ` ); + + return { + main: joinPathFragments(project.root, main), + outputPath: joinPathFragments(project.root, outputPath), + }; } function checkForTargetConflicts(tree: Tree, options: RollupProjectSchema) { @@ -105,60 +140,129 @@ function checkForTargetConflicts(tree: Tree, options: RollupProjectSchema) { } } -function addPackageJson(tree: Tree, options: RollupProjectSchema) { +function updatePackageJson( + tree: Tree, + options: RollupProjectSchema, + outputConfig: OutputConfig | undefined, + isTsSolutionSetup: boolean +) { const project = readProjectConfiguration(tree, options.project); - const packageJsonPath = joinPathFragments(project.root, 'package.json'); - if (!tree.exists(packageJsonPath)) { - const importPath = - options.importPath || getImportPath(tree, options.project); - writeJson(tree, packageJsonPath, { - name: importPath, + const packageJsonPath = join(project.root, 'package.json'); + let packageJson: PackageJson; + if (tree.exists(packageJsonPath)) { + if (!isTsSolutionSetup) { + return; + } + + packageJson = readJson(tree, packageJsonPath); + } else { + packageJson = { + name: options.importPath || getImportPath(tree, options.project), version: '0.0.1', - }); + }; } + + if (isTsSolutionSetup) { + let main: string; + let outputPath: string; + if (outputConfig) { + ({ main, outputPath } = outputConfig); + } else { + // target must exist if we don't receive an outputConfig + const projectTarget = project.targets[options.buildTarget]; + const nxJson = readNxJson(tree); + const mergedTarget = mergeTargetConfigurations( + projectTarget, + (projectTarget.executor + ? nxJson.targetDefaults?.[projectTarget.executor] + : undefined) ?? nxJson.targetDefaults?.[options.buildTarget] + ); + ({ main, outputPath } = mergedTarget.options); + } + + packageJson = getUpdatedPackageJsonContent(packageJson, { + main, + outputPath, + projectRoot: project.root, + rootDir: dirname(main), + generateExportsField: true, + packageJsonPath, + format: options.format ?? ['esm'], + outputFileExtensionForCjs: '.cjs.js', + outputFileExtensionForEsm: '.esm.js', + }); + + // rollup has a specific declaration file generation not handled by the util above, + // adjust accordingly + const typingsFile = (packageJson.module ?? packageJson.main).replace( + /\.js$/, + '.d.ts' + ); + packageJson.types = typingsFile; + packageJson.exports['.'].types = typingsFile; + } + + writeJson(tree, packageJsonPath, packageJson); } -function addBuildTarget(tree: Tree, options: RollupProjectSchema) { +function addBuildTarget( + tree: Tree, + options: RollupProjectSchema, + isTsSolutionSetup: boolean +) { addBuildTargetDefaults(tree, '@nx/rollup:rollup', options.buildTarget); const project = readProjectConfiguration(tree, options.project); const prevBuildOptions = project.targets?.[options.buildTarget]?.options; + options.tsConfig ??= + prevBuildOptions?.tsConfig ?? + joinPathFragments(project.root, 'tsconfig.lib.json'); + + let outputPath = prevBuildOptions?.outputPath; + if (!outputPath) { + outputPath = isTsSolutionSetup + ? joinPathFragments(project.root, 'dist') + : joinPathFragments( + 'dist', + project.root === '.' ? project.name : project.root + ); + } + const buildOptions: RollupExecutorOptions = { main: options.main ?? prevBuildOptions?.main ?? joinPathFragments(project.root, 'src/index.ts'), - outputPath: - prevBuildOptions?.outputPath ?? - joinPathFragments( - 'dist', - project.root === '.' ? project.name : project.root - ), - tsConfig: - options.tsConfig ?? - prevBuildOptions?.tsConfig ?? - joinPathFragments(project.root, 'tsconfig.lib.json'), - additionalEntryPoints: prevBuildOptions?.additionalEntryPoints, - generateExportsField: prevBuildOptions?.generateExportsField, + outputPath, + tsConfig: options.tsConfig, + // TODO(leo): see if we can use this when updating the package.json for the new setup + // additionalEntryPoints: prevBuildOptions?.additionalEntryPoints, + // generateExportsField: prevBuildOptions?.generateExportsField, compiler: options.compiler ?? 'babel', project: `${project.root}/package.json`, external: options.external, - format: options.format, + format: options.format ?? isTsSolutionSetup ? ['esm'] : undefined, }; if (options.rollupConfig) { buildOptions.rollupConfig = options.rollupConfig; } - if (tree.exists(joinPathFragments(project.root, 'README.md'))) { - buildOptions.assets = [ - { - glob: `${project.root}/README.md`, - input: '.', - output: '.', - }, - ]; + if (!isTsSolutionSetup) { + buildOptions.additionalEntryPoints = + prevBuildOptions?.additionalEntryPoints; + buildOptions.generateExportsField = prevBuildOptions?.generateExportsField; + + if (tree.exists(joinPathFragments(project.root, 'README.md'))) { + buildOptions.assets = [ + { + glob: `${project.root}/README.md`, + input: '.', + output: '.', + }, + ]; + } } updateProjectConfiguration(tree, options.project, { @@ -174,4 +278,35 @@ function addBuildTarget(tree: Tree, options: RollupProjectSchema) { }); } +function updateTsConfig(tree: Tree, options: RollupProjectSchema): void { + const project = readProjectConfiguration(tree, options.project); + const tsconfigPath = + options.tsConfig ?? joinPathFragments(project.root, 'tsconfig.lib.json'); + if (!tree.exists(tsconfigPath)) { + throw new Error( + `The '${tsconfigPath}' file doesn't exist. Provide the 'tsConfig' option with the correct path pointing to the tsconfig file to use for builds.` + ); + } + + if (!ts) { + ts = ensureTypescript(); + } + + const parsedTsConfig = readTsConfig(tsconfigPath, { + ...ts.sys, + readFile: (p) => tree.read(p, 'utf-8'), + fileExists: (p) => tree.exists(p), + }); + + updateJson(tree, tsconfigPath, (json) => { + if (parsedTsConfig.options.module === ts.ModuleKind.NodeNext) { + json.compilerOptions ??= {}; + json.compilerOptions.module = 'esnext'; + json.compilerOptions.moduleResolution = 'bundler'; + } + + return json; + }); +} + export default configurationGenerator; diff --git a/packages/rollup/src/generators/configuration/schema.json b/packages/rollup/src/generators/configuration/schema.json index 1b417f4311..eade26a9e8 100644 --- a/packages/rollup/src/generators/configuration/schema.json +++ b/packages/rollup/src/generators/configuration/schema.json @@ -25,13 +25,13 @@ }, "main": { "type": "string", - "description": "Path relative to the workspace root for the main entry file. Defaults to '/src/main.ts'.", + "description": "Path relative to the workspace root for the main entry file. Defaults to '/src/index.ts'.", "alias": "entryFile", "x-priority": "important" }, "tsConfig": { "type": "string", - "description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '/tsconfig.app.json'.", + "description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '/tsconfig.lib.json'.", "x-priority": "important" }, "skipFormat": { diff --git a/packages/rollup/src/plugins/with-nx/with-nx-options.ts b/packages/rollup/src/plugins/with-nx/with-nx-options.ts index 70d76c206e..1265e478ed 100644 --- a/packages/rollup/src/plugins/with-nx/with-nx-options.ts +++ b/packages/rollup/src/plugins/with-nx/with-nx-options.ts @@ -76,6 +76,12 @@ export interface RollupWithNxPluginOptions { * The path to tsconfig file. */ tsConfig: string; + /** + * Whether to generate a package.json file in the output path. It's not supported when the workspace is + * set up with TypeScript Project References along with the package managers' Workspaces feature. Otherwise, + * it defaults to `true`. + */ + generatePackageJson?: boolean; } export interface AssetGlobPattern { diff --git a/packages/rollup/src/plugins/with-nx/with-nx.ts b/packages/rollup/src/plugins/with-nx/with-nx.ts index 3c14757b4a..4a642efe7e 100644 --- a/packages/rollup/src/plugins/with-nx/with-nx.ts +++ b/packages/rollup/src/plugins/with-nx/with-nx.ts @@ -28,6 +28,7 @@ import { deleteOutput } from '../delete-output'; import { AssetGlobPattern, RollupWithNxPluginOptions } from './with-nx-options'; import { normalizeOptions } from './normalize-options'; import { PackageJson } from 'nx/src/utils/package-json'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; // These use require because the ES import isn't correct. const commonjs = require('@rollup/plugin-commonjs'); @@ -182,6 +183,22 @@ export function withNx( } if (!global.NX_GRAPH_CREATION) { + const isTsSolutionSetup = isUsingTsSolutionSetup(); + if (isTsSolutionSetup) { + if (options.generatePackageJson) { + throw new Error( + `Setting 'generatePackageJson: true' is not supported with the current TypeScript setup. Update the 'package.json' file at the project root as needed and unset the 'generatePackageJson' option.` + ); + } + if (options.generateExportsField) { + throw new Error( + `Setting 'generateExportsField: true' is not supported with the current TypeScript setup. Set 'exports' field in the 'package.json' file at the project root and unset the 'generateExportsField' option.` + ); + } + } else { + options.generatePackageJson ??= true; + } + finalConfig.plugins = [ copy({ targets: convertCopyAssetsToRollupOptions( @@ -247,7 +264,7 @@ export function withNx( }), commonjs(), analyze(), - generatePackageJson(options, packageJson), + options.generatePackageJson && generatePackageJson(options, packageJson), ]; if (Array.isArray(rollupConfig.plugins)) { finalConfig.plugins.push(...rollupConfig.plugins); diff --git a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap index 8d3c79c24f..1b07895e18 100644 --- a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap +++ b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap @@ -285,6 +285,7 @@ export default defineConfig({ exports[`@nx/vite:configuration library mode should set up non buildable library which already has vite.config.ts correctly 1`] = ` "import dts from 'vite-plugin-dts'; import * as path from 'path'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; @@ -395,7 +396,6 @@ exports[`@nx/vite:configuration transform React app to use Vite should move inde exports[`@nx/vite:configuration transform Web app to use Vite should create vite.config file at the root of the app 1`] = ` "/// import { defineConfig } from 'vite'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; diff --git a/packages/vite/src/generators/configuration/configuration.ts b/packages/vite/src/generators/configuration/configuration.ts index 0b314c3391..57e8c50257 100644 --- a/packages/vite/src/generators/configuration/configuration.ts +++ b/packages/vite/src/generators/configuration/configuration.ts @@ -2,27 +2,34 @@ import { formatFiles, GeneratorCallback, joinPathFragments, + readJson, readNxJson, readProjectConfiguration, runTasksInSerial, Tree, updateJson, + writeJson, } from '@nx/devkit'; -import { initGenerator as jsInitGenerator } from '@nx/js'; - +import { + getUpdatedPackageJsonContent, + initGenerator as jsInitGenerator, +} from '@nx/js'; +import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { join } from 'node:path/posix'; +import type { PackageJson } from 'nx/src/utils/package-json'; +import { ensureDependencies } from '../../utils/ensure-dependencies'; import { addBuildTarget, - addServeTarget, addPreviewTarget, + addServeTarget, createOrEditViteConfig, TargetFlags, } from '../../utils/generator-utils'; - import initGenerator from '../init/init'; import vitestGenerator from '../vitest/vitest-generator'; -import { ViteConfigurationGeneratorSchema } from './schema'; -import { ensureDependencies } from '../../utils/ensure-dependencies'; import { convertNonVite } from './lib/convert-non-vite'; +import { ViteConfigurationGeneratorSchema } from './schema'; export function viteConfigurationGenerator( host: Tree, @@ -103,20 +110,10 @@ export async function viteConfigurationGeneratorInternal( tree, joinPathFragments(projectRoot, 'tsconfig.lib.json'), (json) => { - if (!json.compilerOptions) { - json.compilerOptions = {}; - } - if (!json.compilerOptions.types) { - json.compilerOptions.types = []; - } + json.compilerOptions ??= {}; + json.compilerOptions.types ??= []; if (!json.compilerOptions.types.includes('vite/client')) { - return { - ...json, - compilerOptions: { - ...json.compilerOptions, - types: [...json.compilerOptions.types, 'vite/client'], - }, - }; + json.compilerOptions.types.push('vite/client'); } return json; } @@ -168,6 +165,10 @@ export async function viteConfigurationGeneratorInternal( tasks.push(vitestTask); } + if (isUsingTsSolutionSetup(tree)) { + updatePackageJson(tree, schema); + } + if (!schema.skipFormat) { await formatFiles(tree); } @@ -176,3 +177,44 @@ export async function viteConfigurationGeneratorInternal( } export default viteConfigurationGenerator; + +function updatePackageJson( + tree: Tree, + options: ViteConfigurationGeneratorSchema +) { + const project = readProjectConfiguration(tree, options.project); + + const packageJsonPath = join(project.root, 'package.json'); + let packageJson: PackageJson; + if (tree.exists(packageJsonPath)) { + packageJson = readJson(tree, packageJsonPath); + } else { + packageJson = { + name: getImportPath(tree, options.project), + version: '0.0.1', + }; + } + + // we always write/override the vite and project config with some set values, + // so we can rely on them + const main = join(project.root, 'src/index.ts'); + // we configure the dts plugin with the entryRoot set to `src` + const rootDir = join(project.root, 'src'); + const outputPath = joinPathFragments(project.root, 'dist'); + + packageJson = getUpdatedPackageJsonContent(packageJson, { + main, + outputPath, + projectRoot: project.root, + rootDir, + generateExportsField: true, + packageJsonPath, + format: ['esm', 'cjs'], + // when building both formats, we don't set the package.json "type" field, so + // we need to set the esm extension to ".mjs" to match vite output + // see the "File Extensions" callout in https://vite.dev/guide/build.html#library-mode + outputFileExtensionForEsm: '.mjs', + }); + + writeJson(tree, packageJsonPath, packageJson); +} diff --git a/packages/vite/src/utils/generator-utils.spec.ts b/packages/vite/src/utils/generator-utils.spec.ts index 561ee6a0c0..0facc368a6 100644 --- a/packages/vite/src/utils/generator-utils.spec.ts +++ b/packages/vite/src/utils/generator-utils.spec.ts @@ -215,7 +215,6 @@ describe('generator utils', () => { .toMatchInlineSnapshot(` "/// import { defineConfig } from 'vite'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts index 8e76ac4009..81f06fd1c7 100644 --- a/packages/vite/src/utils/generator-utils.ts +++ b/packages/vite/src/utils/generator-utils.ts @@ -118,11 +118,15 @@ export function addBuildTarget( ) { addBuildTargetDefaults(tree, '@nx/vite:build'); const project = readProjectConfiguration(tree, options.project); + + const isTsSolutionSetup = isUsingTsSolutionSetup(tree); const buildOptions: ViteBuildExecutorOptions = { - outputPath: joinPathFragments( - 'dist', - project.root != '.' ? project.root : options.project - ), + outputPath: isTsSolutionSetup + ? joinPathFragments(project.root, 'dist') + : joinPathFragments( + 'dist', + project.root != '.' ? project.root : options.project + ), }; project.targets ??= {}; project.targets[target] = { @@ -228,7 +232,15 @@ export function editTsConfig( ) { const projectConfig = readProjectConfiguration(tree, options.project); - const config = readJson(tree, `${projectConfig.root}/tsconfig.json`); + let tsconfigPath = joinPathFragments(projectConfig.root, 'tsconfig.json'); + const isTsSolutionSetup = isUsingTsSolutionSetup(tree); + if (isTsSolutionSetup) { + tsconfigPath = [ + joinPathFragments(projectConfig.root, 'tsconfig.app.json'), + joinPathFragments(projectConfig.root, 'tsconfig.lib.json'), + ].find((p) => tree.exists(p)); + } + const config = readJson(tree, tsconfigPath); switch (options.uiFramework) { case 'react': @@ -241,21 +253,23 @@ export function editTsConfig( }; break; case 'none': - config.compilerOptions = { - module: 'commonjs', - forceConsistentCasingInFileNames: true, - strict: true, - noImplicitOverride: true, - noPropertyAccessFromIndexSignature: true, - noImplicitReturns: true, - noFallthroughCasesInSwitch: true, - }; + if (!isTsSolutionSetup) { + config.compilerOptions = { + module: 'commonjs', + forceConsistentCasingInFileNames: true, + strict: true, + noImplicitOverride: true, + noPropertyAccessFromIndexSignature: true, + noImplicitReturns: true, + noFallthroughCasesInSwitch: true, + }; + } break; default: break; } - writeJson(tree, `${projectConfig.root}/tsconfig.json`, config); + writeJson(tree, tsconfigPath, config); } export function deleteWebpackConfig( @@ -405,7 +419,8 @@ export function createOrEditViteConfig( }, },`; - const imports: string[] = options.imports ? options.imports : []; + const imports: string[] = options.imports ? [...options.imports] : []; + const plugins: string[] = options.plugins ? [...options.plugins] : []; if (!onlyVitest && options.includeLib) { imports.push( @@ -414,11 +429,13 @@ export function createOrEditViteConfig( ); } - let viteConfigContent = ''; - - const plugins = options.plugins - ? [...options.plugins, `nxViteTsPaths()`, `nxCopyAssetsPlugin(['*.md'])`] - : [`nxViteTsPaths()`, `nxCopyAssetsPlugin(['*.md'])`]; + if (!isUsingTsPlugin) { + imports.push( + `import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'`, + `import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'` + ); + plugins.push(`nxViteTsPaths()`, `nxCopyAssetsPlugin(['*.md'])`); + } if (!onlyVitest && options.includeLib) { plugins.push( @@ -507,11 +524,9 @@ export function createOrEditViteConfig( return; } - viteConfigContent = `/// + const viteConfigContent = `/// import { defineConfig } from 'vite'; ${imports.join(';\n')}${imports.length ? ';' : ''} -import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; -import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname,