diff --git a/docs/generated/packages/js/generators/init.json b/docs/generated/packages/js/generators/init.json index 47095512a6..33f0477ba4 100644 --- a/docs/generated/packages/js/generators/init.json +++ b/docs/generated/packages/js/generators/init.json @@ -1,6 +1,6 @@ { "name": "init", - "factory": "./src/generators/init/init#initGenerator", + "factory": "./src/generators/init/init#initGeneratorInternal", "schema": { "$schema": "https://json-schema.org/schema", "$id": "NxTypescriptInit", @@ -8,6 +8,12 @@ "title": "Init nx/js", "description": "Init generator placeholder for nx/js.", "properties": { + "formatter": { + "description": "The tool to use for code formatting.", + "type": "string", + "enum": ["none", "prettier"], + "default": "none" + }, "js": { "type": "boolean", "default": false, @@ -40,12 +46,6 @@ "type": "string", "description": "Customize the generated base tsconfig file name.", "x-priority": "internal" - }, - "setUpPrettier": { - "type": "boolean", - "description": "Add Prettier and corresponding configuration files.", - "x-priority": "internal", - "default": false } }, "presets": [] @@ -54,7 +54,7 @@ "x-type": "init", "description": "Initialize a TS/JS workspace.", "hidden": true, - "implementation": "/packages/js/src/generators/init/init#initGenerator.ts", + "implementation": "/packages/js/src/generators/init/init#initGeneratorInternal.ts", "path": "/packages/js/src/generators/init/schema.json", "type": "generator" } diff --git a/docs/generated/packages/js/generators/library.json b/docs/generated/packages/js/generators/library.json index 70859e0d8f..d4e9f02c56 100644 --- a/docs/generated/packages/js/generators/library.json +++ b/docs/generated/packages/js/generators/library.json @@ -21,22 +21,28 @@ "description": "A directory where the lib is placed.", "x-priority": "important" }, - "projectNameAndRootFormat": { - "description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).", + "bundler": { + "description": "The bundler to use. Choosing 'none' means this library is not buildable.", "type": "string", - "enum": ["as-provided", "derived"] + "enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"], + "x-priority": "important" }, "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint", "none"], - "default": "eslint" + "enum": ["none", "eslint"], + "x-priority": "important" }, "unitTestRunner": { - "type": "string", - "enum": ["jest", "vitest", "none"], "description": "Test runner to use for unit tests.", - "x-prompt": "Which unit test runner would you like to use?" + "type": "string", + "enum": ["none", "jest", "vitest"], + "x-priority": "important" + }, + "projectNameAndRootFormat": { + "description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).", + "type": "string", + "enum": ["as-provided", "derived"] }, "tags": { "type": "string", @@ -112,18 +118,9 @@ "compiler": { "type": "string", "enum": ["tsc", "swc"], - "default": "tsc", "description": "The compiler used by the build and test targets", "x-deprecated": "Use the `bundler` option for greater control (swc, tsc, rollup, vite, esbuild, none)." }, - "bundler": { - "description": "The bundler to use. Choosing 'none' means this library is not buildable.", - "type": "string", - "enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"], - "default": "tsc", - "x-prompt": "Which bundler would you like to use to build the library? Choose 'none' to skip build setup.", - "x-priority": "important" - }, "skipTypeCheck": { "type": "boolean", "description": "Whether to skip TypeScript type checking for SWC compiler.", @@ -138,6 +135,10 @@ "description": "Don't include the directory in the generated file name.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["name"], diff --git a/docs/generated/packages/workspace/generators/new.json b/docs/generated/packages/workspace/generators/new.json index 4e69e3a9d9..966acc7342 100644 --- a/docs/generated/packages/workspace/generators/new.json +++ b/docs/generated/packages/workspace/generators/new.json @@ -83,6 +83,12 @@ "prefix": { "description": "The prefix to use for Angular component and directive selectors.", "type": "string" + }, + "formatter": { + "description": "The tool to use for code formatting.", + "type": "string", + "enum": ["none", "prettier"], + "default": "none" } }, "additionalProperties": true, diff --git a/e2e/eslint/src/linter-legacy.test.ts b/e2e/eslint/src/linter-legacy.test.ts index 5402d91183..6d7c0d2f2b 100644 --- a/e2e/eslint/src/linter-legacy.test.ts +++ b/e2e/eslint/src/linter-legacy.test.ts @@ -33,7 +33,7 @@ describe('Linter (legacy)', () => { env: { NX_ADD_PLUGINS: 'false' }, } ); - runCLI(`generate @nx/js:lib ${mylib} --directory=apps/${mylib}`, { + runCLI(`generate @nx/js:lib ${mylib} --directory=libs/${mylib}`, { env: { NX_ADD_PLUGINS: 'false' }, }); }); diff --git a/packages/angular/src/generators/library/library.spec.ts b/packages/angular/src/generators/library/library.spec.ts index 4fe6e67934..ba024058bf 100644 --- a/packages/angular/src/generators/library/library.spec.ts +++ b/packages/angular/src/generators/library/library.spec.ts @@ -1212,34 +1212,34 @@ describe('lib', () => { module.exports = [ ...baseConfig, - ...nx.configs["flat/angular"], - ...nx.configs["flat/angular-template"], - { - files: ["**/*.ts"], - rules: { - "@angular-eslint/directive-selector": [ - "error", - { - type: "attribute", - prefix: "lib", - style: "camelCase" - } - ], - "@angular-eslint/component-selector": [ - "error", - { - type: "element", - prefix: "lib", - style: "kebab-case" - } - ] + ...nx.configs["flat/angular"], + ...nx.configs["flat/angular-template"], + { + files: ["**/*.ts"], + rules: { + "@angular-eslint/directive-selector": [ + "error", + { + type: "attribute", + prefix: "lib", + style: "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + type: "element", + prefix: "lib", + style: "kebab-case" + } + ] + } + }, + { + files: ["**/*.html"], + // Override or add rules here + rules: {} } - }, - { - files: ["**/*.html"], - // Override or add rules here - rules: {} - } ]; " `); diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index b9f3e51a06..a215be4bf1 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -37,9 +37,10 @@ interface BaseArguments extends CreateWorkspaceOptions { interface NoneArguments extends BaseArguments { stack: 'none'; - workspaceType: 'package-based' | 'integrated' | 'standalone'; - js: boolean; - appName: string | undefined; + workspaceType?: 'package-based' | 'integrated' | 'standalone'; + js?: boolean; + appName?: string | undefined; + formatter?: 'none' | 'prettier'; } interface ReactArguments extends BaseArguments { @@ -394,7 +395,11 @@ async function determineStack( choices: [ { name: `none`, - message: `None: Configures a TypeScript/JavaScript project with minimal structure.`, + message: + process.env.NX_ADD_PLUGINS !== 'false' && + process.env.NX_ADD_TS_PLUGIN === 'true' + ? `None: Configures a TypeScript/JavaScript monorepo.` + : `None: Configures a TypeScript/JavaScript project with minimal structure.`, }, { name: `react`, @@ -441,34 +446,14 @@ async function determinePresetOptions( async function determineNoneOptions( parsedArgs: yargs.Arguments ): Promise> { - let preset: Preset; - let workspaceType: 'package-based' | 'standalone' | 'integrated' | undefined = - undefined; - let appName: string | undefined = undefined; - let js: boolean | undefined; - - if (parsedArgs.preset) { - preset = parsedArgs.preset; - } else { - workspaceType = await determinePackageBasedOrIntegratedOrStandalone(); - if (workspaceType === 'standalone') { - preset = Preset.TsStandalone; - } else if (workspaceType === 'integrated') { - preset = Preset.Apps; - } else { - preset = Preset.NPM; - } - } - - if (parsedArgs.js !== undefined) { - js = parsedArgs.js; - } else if (preset === Preset.TsStandalone) { - // Only standalone TS preset generates a default package, so we need to provide --js and --appName options. - appName = parsedArgs.name; - const reply = await enquirer.prompt<{ ts: 'Yes' | 'No' }>([ + if ( + process.env.NX_ADD_PLUGINS !== 'false' && + process.env.NX_ADD_TS_PLUGIN === 'true' + ) { + const reply = await enquirer.prompt<{ prettier: 'Yes' | 'No' }>([ { - name: 'ts', - message: `Would you like to use TypeScript with this project?`, + name: 'prettier', + message: `Would you like to use Prettier for code formatting?`, type: 'autocomplete', choices: [ { @@ -478,14 +463,68 @@ async function determineNoneOptions( name: 'No', }, ], - initial: 0, + initial: 1, skip: !parsedArgs.interactive || isCI(), }, ]); - js = reply.ts === 'No'; - } + return { + preset: Preset.TS, + formatter: reply.prettier === 'Yes' ? 'prettier' : 'none', + }; + } else { + let preset: Preset; + let workspaceType: + | 'package-based' + | 'standalone' + | 'integrated' + | undefined = undefined; + let appName: string | undefined = undefined; + let js: boolean | undefined; - return { preset, js, appName }; + if (parsedArgs.preset) { + preset = parsedArgs.preset; + } else { + workspaceType = await determinePackageBasedOrIntegratedOrStandalone(); + if (workspaceType === 'standalone') { + preset = Preset.TsStandalone; + } else if (workspaceType === 'integrated') { + preset = Preset.Apps; + } else { + preset = Preset.NPM; + } + } + + if (preset === Preset.TS) { + return { preset, formatter: 'prettier' }; + } + + if (parsedArgs.js !== undefined) { + js = parsedArgs.js; + } else if (preset === Preset.TsStandalone) { + // Only standalone TS preset generates a default package, so we need to provide --js and --appName options. + appName = parsedArgs.name; + const reply = await enquirer.prompt<{ ts: 'Yes' | 'No' }>([ + { + name: 'ts', + message: `Would you like to use TypeScript with this project?`, + type: 'autocomplete', + choices: [ + { + name: 'Yes', + }, + { + name: 'No', + }, + ], + initial: 0, + skip: !parsedArgs.interactive || isCI(), + }, + ]); + js = reply.ts === 'No'; + } + + return { preset, js, appName }; + } } async function determineReactOptions( diff --git a/packages/eslint/src/generators/lint-project/lint-project.spec.ts b/packages/eslint/src/generators/lint-project/lint-project.spec.ts index 46525377f1..118e09e566 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.spec.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.spec.ts @@ -50,41 +50,48 @@ describe('@nx/eslint:lint-project', () => { linter: Linter.EsLint, project: 'test-lib', setParserOptionsProject: false, + skipFormat: true, }); expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(` - "const nx = require('@nx/eslint-plugin'); + "const nx = require("@nx/eslint-plugin"); module.exports = [ - ...nx.configs['flat/base'], - ...nx.configs['flat/typescript'], - ...nx.configs['flat/javascript'], - { - ignores: ['**/dist'], - }, - { - files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], - rules: { - '@nx/enforce-module-boundaries': [ - 'error', - { - enforceBuildableLibDependency: true, - allow: ['^.*/eslint(\\\\.base)?\\\\.config\\\\.[cm]?js$'], - depConstraints: [ - { - sourceTag: '*', - onlyDependOnLibsWithTags: ['*'], - }, - ], - }, - ], + ...nx.configs["flat/base"], + ...nx.configs["flat/typescript"], + ...nx.configs["flat/javascript"], + { + ignores: ["**/dist"] + }, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + rules: { "@nx/enforce-module-boundaries": [ + "error", + { + enforceBuildableLibDependency: true, + allow: ["^.*/eslint(\\\\.base)?\\\\.config\\\\.[cm]?js$"], + depConstraints: [{ + sourceTag: "*", + onlyDependOnLibsWithTags: ["*"] + }] + } + ] } + }, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + // Override or add rules here + rules: {} }, - }, - { - files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], - // Override or add rules here - rules: {}, - }, ]; " `); diff --git a/packages/eslint/src/generators/lint-project/lint-project.ts b/packages/eslint/src/generators/lint-project/lint-project.ts index c771839014..6ff40663bb 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.ts @@ -106,6 +106,7 @@ export async function lintProjectGeneratorInternal( (p) => !['./src', '{projectRoot}', projectConfig.root].includes(p) ) ) { + projectConfig.targets ??= {}; projectConfig.targets['lint'] = { command: `eslint ${lintFilePatterns .join(' ') @@ -113,6 +114,7 @@ export async function lintProjectGeneratorInternal( }; } } else { + projectConfig.targets ??= {}; projectConfig.targets['lint'] = { executor: '@nx/eslint:lint', }; diff --git a/packages/eslint/src/generators/utils/eslint-file.spec.ts b/packages/eslint/src/generators/utils/eslint-file.spec.ts index 5d58681dc4..96929ac2f5 100644 --- a/packages/eslint/src/generators/utils/eslint-file.spec.ts +++ b/packages/eslint/src/generators/utils/eslint-file.spec.ts @@ -155,7 +155,8 @@ module.exports = [ }); module.exports = [ - ...compat.extends("plugin:playwright/recommend"), + ...compat.extends("plugin:playwright/recommend"), + ...baseConfig, { files: [ @@ -213,7 +214,8 @@ module.exports = [ }); module.exports = [ - ...fixupConfigRules(compat.extends("plugin:playwright/recommend")), + ...fixupConfigRules(compat.extends("plugin:playwright/recommend")), + ...baseConfig, { files: [ @@ -275,11 +277,16 @@ module.exports = [ }); module.exports = [ - ...compat.extends("plugin:some-plugin1", "plugin:some-plugin2"), - ...fixupConfigRules(compat.extends("incompatible-plugin1")), - ...fixupConfigRules(compat.extends("incompatible-plugin2")), - ...compat.extends("plugin:some-plugin3"), - ...fixupConfigRules(compat.extends("incompatible-plugin3")), + ...compat.extends("plugin:some-plugin1", "plugin:some-plugin2"), + + ...fixupConfigRules(compat.extends("incompatible-plugin1")), + + ...fixupConfigRules(compat.extends("incompatible-plugin2")), + + ...compat.extends("plugin:some-plugin3"), + + ...fixupConfigRules(compat.extends("incompatible-plugin3")), + ...baseConfig, { files: [ @@ -336,7 +343,8 @@ module.exports = [ }); module.exports = [ - ...compat.extends("plugin:playwright/recommend"), + ...compat.extends("plugin:playwright/recommend"), + ...baseConfig, { files: [ @@ -433,39 +441,39 @@ module.exports = [ module.exports = [ ...baseConfig, - ...compat.config({ extends: [ - "plugin:@nx/angular", - "plugin:@angular-eslint/template/process-inline-templates" - ] }).map(config => ({ - ...config, - files: ["**/*.ts"], - rules: { - ...config.rules, - "@angular-eslint/directive-selector": [ - "error", - { - type: "attribute", - prefix: "myOrg", - style: "camelCase" - } - ], - "@angular-eslint/component-selector": [ - "error", - { - type: "element", - prefix: "my-org", - style: "kebab-case" - } - ] - } - })), - ...compat.config({ extends: ["plugin:@nx/angular-template"] }).map(config => ({ - ...config, - files: ["**/*.html"], - rules: { - ...config.rules - } - })), + ...compat.config({ extends: [ + "plugin:@nx/angular", + "plugin:@angular-eslint/template/process-inline-templates" + ] }).map(config => ({ + ...config, + files: ["**/*.ts"], + rules: { + ...config.rules, + "@angular-eslint/directive-selector": [ + "error", + { + type: "attribute", + prefix: "myOrg", + style: "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + type: "element", + prefix: "my-org", + style: "kebab-case" + } + ] + } + })), + ...compat.config({ extends: ["plugin:@nx/angular-template"] }).map(config => ({ + ...config, + files: ["**/*.html"], + rules: { + ...config.rules + } + })), ];" `); }); diff --git a/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts b/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts index d620a52373..6e8947462d 100644 --- a/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts +++ b/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts @@ -172,23 +172,23 @@ describe('ast-utils', () => { }) ); expect(result).toMatchInlineSnapshot(` - "const baseConfig = require("../../eslint.config.js"); - module.exports = [ - ...baseConfig, - { - files: [ - "my-lib/**/*.ts", - "my-lib/**/*.tsx" - ], - rules: {} - }, - { ignores: ["my-lib/.cache/**/*"] }, - { - files: ["**/*.svg"], - rules: { "@nx/do-something-with-svg": "error" } - }, - ];" - `); + "const baseConfig = require("../../eslint.config.js"); + module.exports = [ + ...baseConfig, + { + files: [ + "my-lib/**/*.ts", + "my-lib/**/*.tsx" + ], + rules: {} + }, + { ignores: ["my-lib/.cache/**/*"] }, + { + files: ["**/*.svg"], + rules: { "@nx/do-something-with-svg": "error" } + }, + ];" + `); }); it('should inject spread to the beginning of the file', () => { @@ -210,20 +210,21 @@ describe('ast-utils', () => { { insertAtTheEnd: false } ); expect(result).toMatchInlineSnapshot(` - "const baseConfig = require("../../eslint.config.js"); - module.exports = [ - ...config, - ...baseConfig, - { - files: [ - "my-lib/**/*.ts", - "my-lib/**/*.tsx" - ], - rules: {} - }, - { ignores: ["my-lib/.cache/**/*"] }, - ];" - `); + "const baseConfig = require("../../eslint.config.js"); + module.exports = [ + ...config, + + ...baseConfig, + { + files: [ + "my-lib/**/*.ts", + "my-lib/**/*.tsx" + ], + rules: {} + }, + { ignores: ["my-lib/.cache/**/*"] }, + ];" + `); }); }); @@ -574,33 +575,33 @@ describe('ast-utils', () => { it('should find and replace rules in override', () => { const content = `const baseConfig = require("../../eslint.config.js"); - module.exports = [ - { +module.exports = [ + { files: [ - "my-lib/**/*.ts", - "my-lib/**/*.tsx" + "my-lib/**/*.ts", + "my-lib/**/*.tsx" ], rules: { - 'my-ts-rule': 'error' + 'my-ts-rule': 'error' } - }, - { + }, + { files: [ - "my-lib/**/*.ts", - "my-lib/**/*.js" + "my-lib/**/*.ts", + "my-lib/**/*.js" ], rules: {} - }, - { + }, + { files: [ - "my-lib/**/*.js", - "my-lib/**/*.jsx" + "my-lib/**/*.js", + "my-lib/**/*.jsx" ], rules: { - 'my-js-rule': 'error' + 'my-js-rule': 'error' } - }, - ];`; + }, +];`; const result = replaceOverride( content, @@ -616,61 +617,61 @@ describe('ast-utils', () => { expect(result).toMatchInlineSnapshot(` "const baseConfig = require("../../eslint.config.js"); - module.exports = [ - { - "files": [ - "my-lib/**/*.ts", - "my-lib/**/*.tsx" - ], - "rules": { - "my-rule": "error" - } - }, - { - "files": [ - "my-lib/**/*.ts", - "my-lib/**/*.js" - ], - "rules": { - "my-rule": "error" - } - }, - { + module.exports = [ + { + "files": [ + "my-lib/**/*.ts", + "my-lib/**/*.tsx" + ], + "rules": { + "my-rule": "error" + } + }, + { + "files": [ + "my-lib/**/*.ts", + "my-lib/**/*.js" + ], + "rules": { + "my-rule": "error" + } + }, + { files: [ - "my-lib/**/*.js", - "my-lib/**/*.jsx" + "my-lib/**/*.js", + "my-lib/**/*.jsx" ], rules: { - 'my-js-rule': 'error' + 'my-js-rule': 'error' } - }, - ];" + }, + ];" `); }); it('should append rules in override', () => { const content = `const baseConfig = require("../../eslint.config.js"); - module.exports = [ - { +module.exports = [ + { files: [ - "my-lib/**/*.ts", - "my-lib/**/*.tsx" + "my-lib/**/*.ts", + "my-lib/**/*.tsx" ], rules: { - 'my-ts-rule': 'error' + 'my-ts-rule': 'error' } - }, - { + }, + { files: [ - "my-lib/**/*.js", - "my-lib/**/*.jsx" + "my-lib/**/*.js", + "my-lib/**/*.jsx" ], rules: { - 'my-js-rule': 'error' + 'my-js-rule': 'error' } - }, - ];`; + }, +];`; const result = replaceOverride( content, @@ -687,45 +688,45 @@ describe('ast-utils', () => { expect(result).toMatchInlineSnapshot(` "const baseConfig = require("../../eslint.config.js"); - module.exports = [ - { - "files": [ - "my-lib/**/*.ts", - "my-lib/**/*.tsx" - ], - "rules": { - "my-ts-rule": "error", - "my-new-rule": "error" - } - }, - { + module.exports = [ + { + "files": [ + "my-lib/**/*.ts", + "my-lib/**/*.tsx" + ], + "rules": { + "my-ts-rule": "error", + "my-new-rule": "error" + } + }, + { files: [ - "my-lib/**/*.js", - "my-lib/**/*.jsx" + "my-lib/**/*.js", + "my-lib/**/*.jsx" ], rules: { - 'my-js-rule': 'error' + 'my-js-rule': 'error' } - }, - ];" + }, + ];" `); }); it('should work for compat overrides', () => { const content = `const baseConfig = require("../../eslint.config.js"); - module.exports = [ - ...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({ - ...config, - files: [ - "my-lib/**/*.ts", - "my-lib/**/*.tsx" - ], - rules: { - 'my-ts-rule': 'error' - } - }), - ];`; +module.exports = [ + ...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({ + ...config, + files: [ + "my-lib/**/*.ts", + "my-lib/**/*.tsx" + ], + rules: { + 'my-ts-rule': 'error' + } + }), +];`; const result = replaceOverride( content, @@ -742,19 +743,19 @@ describe('ast-utils', () => { expect(result).toMatchInlineSnapshot(` "const baseConfig = require("../../eslint.config.js"); - module.exports = [ - ...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({ - ...config, - "files": [ - "my-lib/**/*.ts", - "my-lib/**/*.tsx" - ], - "rules": { - "my-ts-rule": "error", - "my-new-rule": "error" - } - }), - ];" + module.exports = [ + ...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({ + ...config, + "files": [ + "my-lib/**/*.ts", + "my-lib/**/*.tsx" + ], + "rules": { + "my-ts-rule": "error", + "my-new-rule": "error" + } + }), + ];" `); }); }); diff --git a/packages/eslint/src/generators/utils/flat-config/ast-utils.ts b/packages/eslint/src/generators/utils/flat-config/ast-utils.ts index 601c869774..351488ffb1 100644 --- a/packages/eslint/src/generators/utils/flat-config/ast-utils.ts +++ b/packages/eslint/src/generators/utils/flat-config/ast-utils.ts @@ -175,12 +175,17 @@ export function replaceOverride( changes.push({ type: ChangeType.Insert, index: start, - text: JSON.stringify(updatedData, null, 2) - // restore any parser require calls that were stripped during JSON parsing - .replace(/"parser": "([^"]+)"/g, (_, parser) => { - return `"parser": require('${parser}')`; - }) - .slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties + // NOTE: Indentation added to format without formatting tools like Prettier. + text: + ' ' + + JSON.stringify(updatedData, null, 2) + // restore any parser require calls that were stripped during JSON parsing + .replace(/"parser": "([^"]+)"/g, (_, parser) => { + return `"parser": require('${parser}')`; + }) + .slice(2, -2) // remove curly braces and start/end line breaks since we are injecting just properties + // Append indentation so file is formatted without Prettier + .replaceAll(/\n/g, '\n '), }); } } @@ -394,7 +399,11 @@ export function addBlockToFlatConfigExport( // base config was not generated by Nx. if (!exportsArray) return content; - const insert = printer.printNode(ts.EmitHint.Expression, config, source); + const insert = + ' ' + + printer + .printNode(ts.EmitHint.Expression, config, source) + .replaceAll(/\n/g, '\n '); if (options.insertAtTheEnd) { const index = exportsArray.length > 0 @@ -414,7 +423,7 @@ export function addBlockToFlatConfigExport( { type: ChangeType.Insert, index, - text: `\n${insert},`, + text: `\n${insert},\n`, }, ]); } diff --git a/packages/jest/src/generators/configuration/configuration.ts b/packages/jest/src/generators/configuration/configuration.ts index 6bf6640b54..a0894bce8c 100644 --- a/packages/jest/src/generators/configuration/configuration.ts +++ b/packages/jest/src/generators/configuration/configuration.ts @@ -1,3 +1,19 @@ +import { + formatFiles, + GeneratorCallback, + output, + readJson, + readNxJson, + readProjectConfiguration, + runTasksInSerial, + Tree, +} from '@nx/devkit'; +import { + getRootTsConfigFileName, + initGenerator as jsInitGenerator, +} from '@nx/js'; +import { JestPluginOptions } from '../../plugins/plugin'; +import { getPresetExt } from '../../utils/config/config-file'; import { jestInitGenerator } from '../init/init'; import { checkForTestTarget } from './lib/check-for-test-target'; import { createFiles } from './lib/create-files'; @@ -7,17 +23,6 @@ import { updateTsConfig } from './lib/update-tsconfig'; import { updateVsCodeRecommendedExtensions } from './lib/update-vscode-recommended-extensions'; import { updateWorkspace } from './lib/update-workspace'; import { JestProjectSchema, NormalizedJestProjectSchema } from './schema'; -import { - formatFiles, - Tree, - GeneratorCallback, - readProjectConfiguration, - readNxJson, - runTasksInSerial, -} from '@nx/devkit'; -import { initGenerator as jsInitGenerator } from '@nx/js'; -import { JestPluginOptions } from '../../plugins/plugin'; -import { getPresetExt } from '../../utils/config/config-file'; const schemaDefaults = { setupFile: 'none', @@ -118,7 +123,39 @@ export async function configurationGeneratorInternal( await formatFiles(tree); } + tasks.push(getUnsupportedModuleResolutionWarningTask(tree)); + return runTasksInSerial(...tasks); } +/** + * For Jest < 30, there is no way to load jest.config.ts file if the tsconfig.json/tsconfig.base.json sets moduleResolution to bundler or nodenext. + * Jest uses ts-node in a way that is not compatible, so until this is fixed we need to log a warning. + * See: https://github.com/jestjs/jest/blob/main/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L145-L153 + */ +function getUnsupportedModuleResolutionWarningTask( + tree: Tree +): GeneratorCallback { + const tsConfigFileName = getRootTsConfigFileName(tree); + if (tsConfigFileName) { + const json = readJson(tree, tsConfigFileName); + if ( + json.compilerOptions.moduleResolution !== 'node' && + json.compilerOptions.moduleResolution !== 'node10' + ) { + return () => { + output.warn({ + title: `Compiler option 'moduleResolution' in ${tsConfigFileName} must be 'node' or 'node10'`, + bodyLines: [ + `Jest requires 'moduleResolution' to be set to 'node' or 'node10' to work properly. It would need to be changed in the "${tsConfigFileName}" file. It's not enough to override the compiler option in the project's tsconfig file.`, + `Alternatively, you can use the environment variable \`TS_NODE_COMPILER_OPTIONS='{"moduleResolution": "node10"}'\` to override Jest's usage of ts-node.`, + ], + }); + }; + } + } + + return () => {}; +} + export default configurationGenerator; diff --git a/packages/jest/src/generators/configuration/lib/__snapshots__/create-jest-config.spec.ts.snap b/packages/jest/src/generators/configuration/lib/__snapshots__/create-jest-config.spec.ts.snap index 88cd8457a2..1de16d5df6 100644 --- a/packages/jest/src/generators/configuration/lib/__snapshots__/create-jest-config.spec.ts.snap +++ b/packages/jest/src/generators/configuration/lib/__snapshots__/create-jest-config.spec.ts.snap @@ -4,7 +4,7 @@ exports[`createJestConfig should generate files 1`] = ` "import { getJestProjectsAsync } from '@nx/jest'; export default async () => ({ -projects: await getJestProjectsAsync() + projects: await getJestProjectsAsync() });" `; @@ -18,7 +18,7 @@ exports[`createJestConfig should generate files with --js flag 1`] = ` "const { getJestProjectsAsync } = require('@nx/jest'); module.exports = async () => ({ -projects: await getJestProjectsAsync() + projects: await getJestProjectsAsync() });" `; diff --git a/packages/jest/src/generators/configuration/lib/create-jest-config.spec.ts b/packages/jest/src/generators/configuration/lib/create-jest-config.spec.ts index e866287528..8a9e690e6b 100644 --- a/packages/jest/src/generators/configuration/lib/create-jest-config.spec.ts +++ b/packages/jest/src/generators/configuration/lib/create-jest-config.spec.ts @@ -9,7 +9,6 @@ jest.mock('@nx/devkit', () => ({ import { addProjectConfiguration as _addProjectConfiguration, readProjectConfiguration, - stripIndents, type ProjectConfiguration, type ProjectGraph, type Tree, @@ -49,24 +48,16 @@ describe('createJestConfig', () => { await createJestConfig(tree, { js: true }, 'js'); expect(tree.exists('jest.config.js')).toBeTruthy(); - expect( - stripIndents`${tree.read('jest.config.js', 'utf-8')}` - ).toMatchSnapshot(); - expect( - stripIndents`${tree.read('jest.preset.js', 'utf-8')}` - ).toMatchSnapshot(); + expect(tree.read('jest.config.js', 'utf-8')).toMatchSnapshot(); + expect(tree.read('jest.preset.js', 'utf-8')).toMatchSnapshot(); }); it('should generate files ', async () => { await createJestConfig(tree, {}, 'js'); expect(tree.exists('jest.config.ts')).toBeTruthy(); - expect( - stripIndents`${tree.read('jest.config.ts', 'utf-8')}` - ).toMatchSnapshot(); - expect( - stripIndents`${tree.read('jest.preset.js', 'utf-8')}` - ).toMatchSnapshot(); + expect(tree.read('jest.config.ts', 'utf-8')).toMatchSnapshot(); + expect(tree.read('jest.preset.js', 'utf-8')).toMatchSnapshot(); }); it('should not override existing files', async () => { @@ -83,7 +74,7 @@ describe('createJestConfig', () => { }, }, }); - const expected = stripIndents` + const expected = ` import { getJestProjects } from '@nx/jest'; export default { projects: getJestProjects(), @@ -165,7 +156,7 @@ export default { .toEqual(`import { getJestProjectsAsync } from '@nx/jest'; export default async () => ({ -projects: await getJestProjectsAsync() + projects: await getJestProjectsAsync() });`); expect(readProjectConfiguration(tree, 'my-project').targets.test) .toMatchInlineSnapshot(` @@ -217,7 +208,7 @@ module.exports = { .toEqual(`const { getJestProjectsAsync } = require('@nx/jest'); module.exports = async () => ({ -projects: await getJestProjectsAsync() + projects: await getJestProjectsAsync() });`); }); }); diff --git a/packages/jest/src/generators/configuration/lib/create-jest-config.ts b/packages/jest/src/generators/configuration/lib/create-jest-config.ts index e64c62d88d..344199b6c7 100644 --- a/packages/jest/src/generators/configuration/lib/create-jest-config.ts +++ b/packages/jest/src/generators/configuration/lib/create-jest-config.ts @@ -32,7 +32,7 @@ export default { ...nxPreset };` tree.write( `jest.preset.${presetExt}`, `const nxPreset = require('@nx/jest/preset').default; - + module.exports = { ...nxPreset };` ); } @@ -139,17 +139,15 @@ module.exports = { ...nxPreset };` function generateGlobalConfig(tree: Tree, isJS: boolean) { const contents = isJS - ? stripIndents` - const { getJestProjectsAsync } = require('@nx/jest'); + ? `const { getJestProjectsAsync } = require('@nx/jest'); - module.exports = async () => ({ - projects: await getJestProjectsAsync() - });` - : stripIndents` - import { getJestProjectsAsync } from '@nx/jest'; +module.exports = async () => ({ + projects: await getJestProjectsAsync() +});` + : `import { getJestProjectsAsync } from '@nx/jest'; - export default async () => ({ - projects: await getJestProjectsAsync() - });`; +export default async () => ({ + projects: await getJestProjectsAsync() +});`; tree.write(`jest.config.${isJS ? 'js' : 'ts'}`, contents); } diff --git a/packages/js/generators.json b/packages/js/generators.json index b4068838ec..2e512b3d3e 100644 --- a/packages/js/generators.json +++ b/packages/js/generators.json @@ -10,7 +10,7 @@ "description": "Create a library" }, "init": { - "factory": "./src/generators/init/init#initGenerator", + "factory": "./src/generators/init/init#initGeneratorInternal", "schema": "./src/generators/init/schema.json", "aliases": ["lib"], "x-type": "init", diff --git a/packages/js/package.json b/packages/js/package.json index 4eb09e41eb..d35a9818a3 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -41,6 +41,7 @@ "@babel/runtime": "^7.22.6", "@nx/devkit": "file:../devkit", "@nx/workspace": "file:../workspace", + "@zkochan/js-yaml": "0.0.7", "babel-plugin-const-enum": "^1.0.1", "babel-plugin-macros": "^2.8.0", "babel-plugin-transform-typescript-metadata": "^0.3.1", 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 affc6f43c2..bcfa9bb823 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 @@ -3,7 +3,7 @@ import 'nx/src/internal-testing-utils/mock-project-graph'; import { readProjectConfiguration, Tree } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { join } from 'path'; -import { LibraryGeneratorSchema } from '../../utils/schema'; +import { LibraryGeneratorSchema } from '../library/schema'; import { libraryGenerator as jsLibraryGenerator } from '../library/library'; import { convertToSwcGenerator } from './convert-to-swc'; diff --git a/packages/js/src/generators/init/files/__fileName__ b/packages/js/src/generators/init/files/non-ts-solution/__fileName__ similarity index 100% rename from packages/js/src/generators/init/files/__fileName__ rename to packages/js/src/generators/init/files/non-ts-solution/__fileName__ diff --git a/packages/js/src/generators/init/files/ts-solution/tsconfig.base.json__tmpl__ b/packages/js/src/generators/init/files/ts-solution/tsconfig.base.json__tmpl__ new file mode 100644 index 0000000000..8dcc429002 --- /dev/null +++ b/packages/js/src/generators/init/files/ts-solution/tsconfig.base.json__tmpl__ @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "allowJs": false, + "allowSyntheticDefaultImports": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "emitDecoratorMetadata": false, + "esModuleInterop": true, + "experimentalDecorators": false, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "incremental": true, + "isolatedModules": true, + "lib": ["es2022"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "pretty": true, + "removeComments": false, + "resolveJsonModule": false, + "skipDefaultLibCheck": false, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "es2022", + "verbatimModuleSyntax": false + } +} diff --git a/packages/js/src/generators/init/files/ts-solution/tsconfig.json__tmpl__ b/packages/js/src/generators/init/files/ts-solution/tsconfig.json__tmpl__ new file mode 100644 index 0000000000..3a2dd7a1ca --- /dev/null +++ b/packages/js/src/generators/init/files/ts-solution/tsconfig.json__tmpl__ @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.base.json", + "compileOnSave": false, + "files": [], + "references": [] +} diff --git a/packages/js/src/generators/init/init.spec.ts b/packages/js/src/generators/init/init.spec.ts index a747059365..0ef60712f6 100644 --- a/packages/js/src/generators/init/init.spec.ts +++ b/packages/js/src/generators/init/init.spec.ts @@ -131,7 +131,7 @@ describe('js init generator', () => { it('should support skipping prettier setup', async () => { await init(tree, { - setUpPrettier: false, + formatter: 'none', }); const packageJson = readJson(tree, 'package.json'); diff --git a/packages/js/src/generators/init/init.ts b/packages/js/src/generators/init/init.ts index 83bdcbb7c6..d2950689af 100644 --- a/packages/js/src/generators/init/init.ts +++ b/packages/js/src/generators/init/init.ts @@ -1,19 +1,24 @@ import { addDependenciesToPackageJson, + createProjectGraphAsync, ensurePackage, formatFiles, generateFiles, GeneratorCallback, readJson, + readNxJson, runTasksInSerial, Tree, } from '@nx/devkit'; +import { addPlugin } from '@nx/devkit/src/utils/add-plugin'; import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver'; import { readModulePackageJson } from 'nx/src/utils/package-json'; import { join } from 'path'; import { satisfies, valid } from 'semver'; +import { createNodesV2 } from '../../plugins/typescript/plugin'; import { generatePrettierSetup } from '../../utils/prettier'; import { getRootTsConfigFileName } from '../../utils/typescript/ts-config'; +import { isUsingTsSolutionSetup } from '../../utils/typescript/ts-solution-setup'; import { nxVersion, prettierVersion, @@ -64,9 +69,12 @@ export async function initGenerator( tree: Tree, schema: InitSchema ): Promise { + schema.addTsPlugin ??= false; + const isUsingNewTsSetup = schema.addTsPlugin || isUsingTsSolutionSetup(tree); + schema.formatter ??= isUsingNewTsSetup ? 'none' : 'prettier'; + return initGeneratorInternal(tree, { addTsConfigBase: true, - setUpPrettier: true, ...schema, }); } @@ -76,12 +84,48 @@ export async function initGeneratorInternal( schema: InitSchema ): Promise { const tasks: GeneratorCallback[] = []; - // add tsconfig.base.json - if (schema.addTsConfigBase && !getRootTsConfigFileName(tree)) { - generateFiles(tree, join(__dirname, './files'), '.', { - fileName: schema.tsConfigName ?? 'tsconfig.base.json', - }); + + const nxJson = readNxJson(tree); + schema.addPlugin ??= + process.env.NX_ADD_PLUGINS !== 'false' && + nxJson.useInferencePlugins !== false; + schema.addTsPlugin ??= + schema.addPlugin && process.env.NX_ADD_TS_PLUGIN === 'true'; + + if (schema.addTsPlugin) { + await addPlugin( + tree, + await createProjectGraphAsync(), + '@nx/js/typescript', + createNodesV2, + { + typecheck: [ + { targetName: 'typecheck' }, + { targetName: 'tsc:typecheck' }, + { targetName: 'tsc-typecheck' }, + ], + build: [ + { targetName: 'build', configName: 'tsconfig.lib.json' }, + { targetName: 'tsc:build', configName: 'tsconfig.lib.json' }, + { targetName: 'tsc-build', configName: 'tsconfig.lib.json' }, + ], + }, + schema.updatePackageScripts + ); } + + if (schema.addTsConfigBase && !getRootTsConfigFileName(tree)) { + if (schema.addTsPlugin) { + generateFiles(tree, join(__dirname, './files/ts-solution'), '.', { + tmpl: '', + }); + } else { + generateFiles(tree, join(__dirname, './files/non-ts-solution'), '.', { + fileName: schema.tsConfigName ?? 'tsconfig.base.json', + }); + } + } + const devDependencies = { '@nx/js': nxVersion, // When loading .ts config files (e.g. webpack.config.ts, jest.config.ts, etc.) @@ -104,7 +148,7 @@ export async function initGeneratorInternal( } } - if (schema.setUpPrettier) { + if (schema.formatter === 'prettier') { const prettierTask = generatePrettierSetup(tree, { skipPackageJson: schema.skipPackageJson, }); @@ -132,7 +176,12 @@ export async function initGeneratorInternal( : () => {}; tasks.push(installTask); - if (!schema.skipPackageJson && schema.setUpPrettier) { + if ( + !schema.skipPackageJson && + // For `create-nx-workspace` or `nx g @nx/js:init`, we want to make sure users didn't set formatter to none. + // For programmatic usage, the formatter is normally undefined, and we want prettier to continue to be ensured, even if not ultimately installed. + schema.formatter !== 'none' + ) { ensurePackage('prettier', prettierVersion); } diff --git a/packages/js/src/generators/init/schema.d.ts b/packages/js/src/generators/init/schema.d.ts index e8b1e8613d..8a58c84a38 100644 --- a/packages/js/src/generators/init/schema.d.ts +++ b/packages/js/src/generators/init/schema.d.ts @@ -1,9 +1,12 @@ export interface InitSchema { addTsConfigBase?: boolean; + formatter?: 'none' | 'prettier'; js?: boolean; keepExistingVersions?: boolean; - setUpPrettier?: boolean; skipFormat?: boolean; skipPackageJson?: boolean; tsConfigName?: string; + addPlugin?: boolean; + updatePackageScripts?: boolean; + addTsPlugin?: boolean; } diff --git a/packages/js/src/generators/init/schema.json b/packages/js/src/generators/init/schema.json index 131bcfead9..a7bfbfab8f 100644 --- a/packages/js/src/generators/init/schema.json +++ b/packages/js/src/generators/init/schema.json @@ -5,6 +5,12 @@ "title": "Init nx/js", "description": "Init generator placeholder for nx/js.", "properties": { + "formatter": { + "description": "The tool to use for code formatting.", + "type": "string", + "enum": ["none", "prettier"], + "default": "none" + }, "js": { "type": "boolean", "default": false, @@ -37,12 +43,6 @@ "type": "string", "description": "Customize the generated base tsconfig file name.", "x-priority": "internal" - }, - "setUpPrettier": { - "type": "boolean", - "description": "Add Prettier and corresponding configuration files.", - "x-priority": "internal", - "default": false } } } diff --git a/packages/js/src/generators/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ b/packages/js/src/generators/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ index 35b0948b95..d410ff0297 100644 --- a/packages/js/src/generators/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ +++ b/packages/js/src/generators/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ @@ -1,7 +1,7 @@ import { <%= propertyName %> } from './<%= fileName %>'; describe('<%= propertyName %>', () => { - it('should work', () => { - expect(<%= propertyName %>()).toEqual('<%= name %>'); - }) -}) \ No newline at end of file + it('should work', () => { + expect(<%= propertyName %>()).toEqual('<%= name %>'); + }) +}) diff --git a/packages/js/src/generators/library/files/lib/src/lib/__fileName__.ts__tmpl__ b/packages/js/src/generators/library/files/lib/src/lib/__fileName__.ts__tmpl__ index ae311e3ac4..ade307131b 100644 --- a/packages/js/src/generators/library/files/lib/src/lib/__fileName__.ts__tmpl__ +++ b/packages/js/src/generators/library/files/lib/src/lib/__fileName__.ts__tmpl__ @@ -1,3 +1,3 @@ export function <%= propertyName %>(): string { - return '<%= name %>'; + return '<%= name %>'; } diff --git a/packages/js/src/generators/library/files/readme/README.md b/packages/js/src/generators/library/files/readme/README.md index 1ea7d6f64b..018b5ae3b7 100644 --- a/packages/js/src/generators/library/files/readme/README.md +++ b/packages/js/src/generators/library/files/readme/README.md @@ -1,19 +1,11 @@ # <%= name %> -This library was generated with [Nx](https://nx.dev). - -<% if (buildable) { %> +This library was generated with [Nx](https://nx.dev).<% if (buildable) { %> ## Building -Run `<%= cliCommand %> build <%= name %>` to build the library. - -<% } %> - -<% if (hasUnitTestRunner) { %> +Run `<%= cliCommand %> build <%= name %>` to build the library.<% } %><% if (unitTestRunner !== 'none') { %> ## Running unit tests -Run `<%= cliCommand %> test <%= name %>` to execute the unit tests via <% if(unitTestRunner === 'jest') { %>[Jest](https://jestjs.io)<% } else { %>[Vitest](https://vitest.dev/)<% } %>. - -<% } %> +Run `<%= cliCommand %> test <%= name %>` to execute the unit tests via <% if(unitTestRunner === 'jest') { %>[Jest](https://jestjs.io)<% } else { %>[Vitest](https://vitest.dev/)<% } %>.<% } %> diff --git a/packages/js/src/generators/library/files/lib/tsconfig.lib.json__tmpl__ b/packages/js/src/generators/library/files/tsconfig-lib/non-ts-solution/tsconfig.lib.json__tmpl__ similarity index 100% rename from packages/js/src/generators/library/files/lib/tsconfig.lib.json__tmpl__ rename to packages/js/src/generators/library/files/tsconfig-lib/non-ts-solution/tsconfig.lib.json__tmpl__ diff --git a/packages/js/src/generators/library/files/tsconfig-lib/ts-solution/tsconfig.lib.json__tmpl__ b/packages/js/src/generators/library/files/tsconfig-lib/ts-solution/tsconfig.lib.json__tmpl__ new file mode 100644 index 0000000000..453d7c021d --- /dev/null +++ b/packages/js/src/generators/library/files/tsconfig-lib/ts-solution/tsconfig.lib.json__tmpl__ @@ -0,0 +1,14 @@ +{ + "extends": "<%= offsetFromRoot %>tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false,<% if (compilerOptions.length) { %> + <%- compilerOptions %>,<% } %> + "types": ["node"] + }, + "include": ["src/**/*.ts"<% if (js) { %>, "src/**/*.js"<% } %>], + "references": [] +} diff --git a/packages/js/src/generators/library/library.spec.ts b/packages/js/src/generators/library/library.spec.ts index c5c4bc66d8..d2b45cb654 100644 --- a/packages/js/src/generators/library/library.spec.ts +++ b/packages/js/src/generators/library/library.spec.ts @@ -10,8 +10,8 @@ import { updateJson, } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; -import { LibraryGeneratorSchema } from '../../utils/schema'; -import libraryGenerator from './library'; +import { libraryGenerator } from './library'; +import type { LibraryGeneratorSchema } from './schema'; describe('lib', () => { let tree: Tree; @@ -155,6 +155,7 @@ describe('lib', () => { { "compilerOptions": { "forceConsistentCasingInFileNames": true, + "importHelpers": true, "module": "commonjs", "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, @@ -1616,4 +1617,73 @@ describe('lib', () => { expect(content).toContain(`environment: 'jsdom'`); }); }); + + describe('--useProjectJson', () => { + it('should generate the nx configuration in the package.json file when using --useProjectJson=false', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'my-lib', + bundler: 'none', + linter: 'none', + unitTestRunner: 'none', + useProjectJson: false, + projectNameAndRootFormat: 'as-provided', + }); + + expect(tree.exists('my-lib/project.json')).toBe(false); + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "dependencies": {}, + "name": "@proj/my-lib", + "nx": { + "name": "my-lib", + }, + "private": true, + "version": "0.0.1", + } + `); + }); + + it('should generate the nx configuration in the project.json file when using --useProjectJson=true', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'my-lib', + bundler: 'none', + useProjectJson: true, + projectNameAndRootFormat: 'as-provided', + }); + + expect(readJson(tree, 'my-lib/project.json')).toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "my-lib", + "projectType": "library", + "sourceRoot": "my-lib/src", + "tags": [], + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + }, + "test": { + "executor": "@nx/jest:jest", + "options": { + "jestConfig": "my-lib/jest.config.ts", + }, + "outputs": [ + "{workspaceRoot}/coverage/{projectRoot}", + ], + }, + }, + } + `); + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "dependencies": {}, + "name": "@proj/my-lib", + "private": true, + "version": "0.0.1", + } + `); + }); + }); }); diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index 845a65daa7..f30e30a4f2 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -18,26 +18,33 @@ import { toJS, Tree, updateJson, + updateNxJson, + updateProjectConfiguration, writeJson, } from '@nx/devkit'; -import { - determineProjectNameAndRootOptions, - type ProjectNameAndRootOptions, -} from '@nx/devkit/src/generators/project-name-and-root-utils'; - +import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; +import { prompt } from 'enquirer'; import { findMatchingProjects } from 'nx/src/utils/find-matching-projects'; +import { isCI } from 'nx/src/utils/is-ci'; import { type PackageJson } from 'nx/src/utils/package-json'; import { join } from 'path'; -import { Bundler, LibraryGeneratorSchema } from '../../utils/schema'; +import type { CompilerOptions } from 'typescript'; +import { getProjectPackageManagerWorkspaceState } from '../../utils/package-manager-workspaces'; import { addSwcConfig } from '../../utils/swc/add-swc-config'; -import { addSwcDependencies } from '../../utils/swc/add-swc-dependencies'; +import { getSwcDependencies } from '../../utils/swc/add-swc-dependencies'; +import { getNeededCompilerOptionOverrides } from '../../utils/typescript/configuration'; import { tsConfigBaseOptions } from '../../utils/typescript/create-ts-config'; import { addTsConfigPath, getRelativePathToRootTsConfig, + getRootTsConfigFileName, } from '../../utils/typescript/ts-config'; +import { + isUsingTsSolutionSetup, + isUsingTypeScriptPlugin, +} from '../../utils/typescript/ts-solution-setup'; import { esbuildVersion, nxVersion, @@ -47,6 +54,16 @@ import { } from '../../utils/versions'; import jsInitGenerator from '../init/init'; import setupVerdaccio from '../setup-verdaccio/generator'; +import type { + Bundler, + LibraryGeneratorSchema, + NormalizedLibraryGeneratorOptions, +} from './schema'; +import { getProjectPackageManagerWorkspaceStateWarningTask } from './utils/package-manager-workspaces'; +import { + ensureProjectIsExcludedFromPluginRegistrations, + ensureProjectIsIncludedInPluginRegistrations, +} from './utils/plugin-registrations'; const defaultOutputDirectory = 'dist'; @@ -56,6 +73,7 @@ export async function libraryGenerator( ) { return await libraryGeneratorInternal(tree, { addPlugin: false, + useProjectJson: true, ...schema, }); } @@ -65,18 +83,22 @@ export async function libraryGeneratorInternal( schema: LibraryGeneratorSchema ) { const tasks: GeneratorCallback[] = []; + tasks.push( await jsInitGenerator(tree, { ...schema, skipFormat: true, tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json', + addTsConfigBase: true, + // In the new setup, Prettier is prompted for and installed during `create-nx-workspace`. + formatter: isUsingTsSolutionSetup(tree) ? 'none' : 'prettier', }) ); const options = await normalizeOptions(tree, schema); createFiles(tree, options); - await addProject(tree, options); + await configureProject(tree, options); if (!options.skipPackageJson) { tasks.push(addProjectDependencies(tree, options)); @@ -160,7 +182,7 @@ export async function libraryGeneratorInternal( ); } - if (!schema.skipTsConfig) { + if (!schema.skipTsConfig && !options.isUsingTsSolutionConfig) { addTsConfigPath(tree, options.importPath, [ joinPathFragments( options.projectRoot, @@ -170,10 +192,45 @@ export async function libraryGeneratorInternal( ]); } + if (options.isUsingTsSolutionConfig && options.unitTestRunner !== 'none') { + updateJson( + tree, + joinPathFragments(options.projectRoot, 'tsconfig.spec.json'), + (json) => { + const rootOffset = offsetFromRoot(options.projectRoot); + // ensure it extends from the root tsconfig.base.json + json.extends = joinPathFragments(rootOffset, 'tsconfig.base.json'); + // ensure outDir is set to the correct value + json.compilerOptions ??= {}; + json.compilerOptions.outDir = joinPathFragments( + rootOffset, + 'dist/out-tsc', + options.projectRoot + ); + // add project reference to the runtime tsconfig.lib.json file + json.references ??= []; + json.references.push({ path: './tsconfig.lib.json' }); + return json; + } + ); + } + if (!options.skipFormat) { await formatFiles(tree); } + if ( + options.isUsingTsSolutionConfig && + options.projectPackageManagerWorkspaceState !== 'included' + ) { + tasks.push( + getProjectPackageManagerWorkspaceStateWarningTask( + options.projectPackageManagerWorkspaceState, + tree.root + ) + ); + } + if (options.publishable) { tasks.push(() => { logNxReleaseDocsInfo(); @@ -187,16 +244,36 @@ export async function libraryGeneratorInternal( return runTasksInSerial(...tasks); } -export interface NormalizedSchema extends LibraryGeneratorSchema { - name: string; - projectNames: ProjectNameAndRootOptions['names']; - fileName: string; - projectRoot: string; - parsedTags: string[]; - importPath?: string; -} +async function configureProject( + tree: Tree, + options: NormalizedLibraryGeneratorOptions +) { + if (options.hasPlugin) { + const nxJson = readNxJson(tree); + if (options.bundler === 'none') { + ensureProjectIsExcludedFromPluginRegistrations( + nxJson, + options.projectRoot + ); + } else { + ensureProjectIsIncludedInPluginRegistrations(nxJson, options.projectRoot); + } + updateNxJson(tree, nxJson); + } + + if (!options.useProjectJson) { + if (options.name !== options.importPath) { + // if the name is different than the package.json name, we need to set + // the proper name in the configuration + updateProjectConfiguration(tree, options.name, { + name: options.name, + root: options.projectRoot, + }); + } + + return; + } -async function addProject(tree: Tree, options: NormalizedSchema) { const projectConfiguration: ProjectConfiguration = { root: options.projectRoot, sourceRoot: joinPathFragments(options.projectRoot, 'src'), @@ -275,21 +352,16 @@ async function addProject(tree: Tree, options: NormalizedSchema) { if (options.config === 'workspace' || options.config === 'project') { addProjectConfiguration(tree, options.name, projectConfiguration); } else { - addProjectConfiguration( - tree, - options.name, - { - root: projectConfiguration.root, - tags: projectConfiguration.tags, - targets: {}, - }, - true - ); + addProjectConfiguration(tree, options.name, { + root: projectConfiguration.root, + tags: projectConfiguration.tags, + targets: {}, + }); } } export type AddLintOptions = Pick< - NormalizedSchema, + NormalizedLibraryGeneratorOptions, | 'name' | 'linter' | 'projectRoot' @@ -407,25 +479,7 @@ export async function addLint( return task; } -function updateTsConfig(tree: Tree, options: NormalizedSchema) { - updateJson(tree, join(options.projectRoot, 'tsconfig.json'), (json) => { - if (options.strict) { - json.compilerOptions = { - ...json.compilerOptions, - forceConsistentCasingInFileNames: true, - strict: true, - noImplicitOverride: true, - noPropertyAccessFromIndexSignature: true, - noImplicitReturns: true, - noFallthroughCasesInSwitch: true, - }; - } - - return json; - }); -} - -function addBabelRc(tree: Tree, options: NormalizedSchema) { +function addBabelRc(tree: Tree, options: NormalizedLibraryGeneratorOptions) { const filename = '.babelrc'; const babelrc = { @@ -435,12 +489,12 @@ function addBabelRc(tree: Tree, options: NormalizedSchema) { writeJson(tree, join(options.projectRoot, filename), babelrc); } -function createFiles(tree: Tree, options: NormalizedSchema) { +function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) { const { className, name, propertyName } = names( options.projectNames.projectFileName ); - createProjectTsConfigJson(tree, options); + createProjectTsConfigs(tree, options); generateFiles(tree, join(__dirname, './files/lib'), options.projectRoot, { ...options, @@ -480,7 +534,6 @@ function createFiles(tree: Tree, options: NormalizedSchema) { } if (options.bundler === 'swc' || options.bundler === 'rollup') { - addSwcDependencies(tree); addSwcConfig( tree, options.projectRoot, @@ -518,6 +571,11 @@ function createFiles(tree: Tree, options: NormalizedSchema) { if (!options.publishable && !options.rootProject) { json.private = true; } + if (options.isUsingTsSolutionConfig && options.publishable) { + // package.json and README.md are always included by default + // https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files + json.files = ['dist', '!**/*.tsbuildinfo']; + } return { ...json, dependencies: { @@ -537,6 +595,11 @@ function createFiles(tree: Tree, options: NormalizedSchema) { if (!options.publishable && !options.rootProject) { packageJson.private = true; } + if (options.isUsingTsSolutionConfig && options.publishable) { + // package.json and README.md are always included by default + // https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files + packageJson.files = ['dist', '!**/*.tsbuildinfo']; + } writeJson(tree, packageJsonPath, packageJson); } @@ -548,23 +611,16 @@ function createFiles(tree: Tree, options: NormalizedSchema) { }; return json; }); - } else if ( - (!options.bundler || options.bundler === 'none') && - !(options.projectRoot === '.') - ) { - tree.delete(packageJsonPath); } if (options.minimal && !(options.projectRoot === '.')) { tree.delete(join(options.projectRoot, 'README.md')); } - - updateTsConfig(tree, options); } async function addJest( tree: Tree, - options: NormalizedSchema + options: NormalizedLibraryGeneratorOptions ): Promise { const { configurationGenerator } = ensurePackage('@nx/jest', nxVersion); return await configurationGenerator(tree, { @@ -585,7 +641,10 @@ async function addJest( }); } -function replaceJestConfig(tree: Tree, options: NormalizedSchema) { +function replaceJestConfig( + tree: Tree, + options: NormalizedLibraryGeneratorOptions +) { const filesDir = join(__dirname, './files/jest-config'); // the existing config has to be deleted otherwise the new config won't overwrite it const existingJestConfig = joinPathFragments( @@ -609,39 +668,162 @@ function replaceJestConfig(tree: Tree, options: NormalizedSchema) { }); } +function isNonInteractive(): boolean { + return ( + isCI() || !process.stdout.isTTY || process.env.NX_INTERACTIVE !== 'true' + ); +} + +async function promptWhenInteractive( + questions: Parameters[0], + defaultValue: T +): Promise { + if (isNonInteractive()) { + return defaultValue; + } + + return await prompt(questions); +} + async function normalizeOptions( tree: Tree, options: LibraryGeneratorSchema -): Promise { +): Promise { const nxJson = readNxJson(tree); - const addPlugin = + options.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; - options.addPlugin ??= addPlugin; - /** - * We are deprecating the compiler and the buildable options. - * However, we want to keep the existing behavior for now. - * - * So, if the user has not provided a bundler, we will use the compiler option, if any. - * - * If the user has not provided a bundler and no compiler, but has set buildable to true, - * we will use tsc, since that is the compiler the old generator used to default to, if buildable was true - * and no compiler was provided. - * - * If the user has not provided a bundler and no compiler, and has not set buildable to true, then - * set the bundler to tsc, to preserve old default behaviour (buildable: true by default). - * - * If it's publishable, we need to build the code before publishing it, so again - * we default to `tsc`. In the previous version of this, it would set `buildable` to true - * and that would default to `tsc`. - * - * In the past, the only way to get a non-buildable library was to set buildable to false. - * Now, the only way to get a non-buildble library is to set bundler to none. - * By default, with nothing provided, libraries are buildable with `@nx/js:tsc`. - */ + const hasPlugin = isUsingTypeScriptPlugin(tree); + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree); - options.bundler = options.bundler ?? options.compiler ?? 'tsc'; + if (isUsingTsSolutionConfig) { + if (options.bundler === 'esbuild' || options.bundler === 'swc') { + throw new Error( + `Cannot use the "${options.bundler}" bundler when using the @nx/js/typescript plugin.` + ); + } + + if (options.bundler === undefined && options.compiler === undefined) { + options.bundler = await promptWhenInteractive<{ bundler: Bundler }>( + { + type: 'select', + name: 'bundler', + message: `Which bundler would you like to use to build the library? Choose 'none' to skip build setup.`, + choices: [ + { name: 'tsc' }, + { name: 'rollup' }, + { name: 'vite' }, + { name: 'none' }, + ], + initial: 0, + }, + { bundler: 'tsc' } + ).then(({ bundler }) => bundler); + } + + options.linter ??= await promptWhenInteractive<{ + linter: 'none' | 'eslint'; + }>( + { + type: 'select', + name: 'linter', + message: `Which linter would you like to use?`, + choices: [{ name: 'none' }, { name: 'eslint' }], + initial: 0, + }, + { linter: 'none' } + ).then(({ linter }) => linter); + + options.unitTestRunner ??= await promptWhenInteractive<{ + unitTestRunner: 'none' | 'jest' | 'vitest'; + }>( + { + type: 'select', + name: 'unitTestRunner', + message: `Which unit test runner would you like to use?`, + choices: [{ name: 'none' }, { name: 'vitest' }, { name: 'jest' }], + initial: 0, + }, + { unitTestRunner: 'none' } + ).then(({ unitTestRunner }) => unitTestRunner); + } else { + if (options.bundler === undefined && options.compiler === undefined) { + options.bundler = await promptWhenInteractive<{ bundler: Bundler }>( + { + type: 'select', + name: 'bundler', + message: `Which bundler would you like to use to build the library? Choose 'none' to skip build setup.`, + choices: [ + { name: 'swc' }, + { name: 'tsc' }, + { name: 'rollup' }, + { name: 'vite' }, + { name: 'esbuild' }, + { name: 'none' }, + ], + initial: 1, + }, + { bundler: 'tsc' } + ).then(({ bundler }) => bundler); + } else { + /** + * We are deprecating the compiler and the buildable options. + * However, we want to keep the existing behavior for now. + * + * So, if the user has not provided a bundler, we will use the compiler option, if any. + * + * If the user has not provided a bundler and no compiler, but has set buildable to true, + * we will use tsc, since that is the compiler the old generator used to default to, if buildable was true + * and no compiler was provided. + * + * If the user has not provided a bundler and no compiler, and has not set buildable to true, then + * set the bundler to tsc, to preserve old default behaviour (buildable: true by default). + * + * If it's publishable, we need to build the code before publishing it, so again + * we default to `tsc`. In the previous version of this, it would set `buildable` to true + * and that would default to `tsc`. + * + * In the past, the only way to get a non-buildable library was to set buildable to false. + * Now, the only way to get a non-buildble library is to set bundler to none. + * By default, with nothing provided, libraries are buildable with `@nx/js:tsc`. + */ + + options.bundler ??= options.compiler; + } + + options.linter ??= await promptWhenInteractive<{ + linter: 'none' | 'eslint'; + }>( + { + type: 'select', + name: 'linter', + message: `Which linter would you like to use?`, + choices: [{ name: 'eslint' }, { name: 'none' }], + initial: 0, + }, + { linter: 'eslint' } + ).then(({ linter }) => linter); + + options.unitTestRunner ??= await promptWhenInteractive<{ + unitTestRunner: 'none' | 'jest' | 'vitest'; + }>( + { + type: 'select', + name: 'unitTestRunner', + message: `Which unit test runner would you like to use?`, + choices: [{ name: 'jest' }, { name: 'vitest' }, { name: 'none' }], + initial: 0, + }, + { unitTestRunner: undefined } + ).then(({ unitTestRunner }) => unitTestRunner); + + if (!options.unitTestRunner && options.bundler === 'vite') { + options.unitTestRunner = 'vitest'; + } else if (!options.unitTestRunner && options.config !== 'npm-scripts') { + options.unitTestRunner = 'jest'; + } + } // ensure programmatic runs have an expected default if (!options.config) { @@ -665,10 +847,9 @@ async function normalizeOptions( options.bundler = 'none'; } - const { Linter } = ensurePackage('@nx/eslint', nxVersion); if (options.config === 'npm-scripts') { options.unitTestRunner = 'none'; - options.linter = Linter.None; + options.linter = 'none'; options.bundler = 'none'; } @@ -679,16 +860,6 @@ async function normalizeOptions( options.skipTypeCheck = false; } - if (!options.unitTestRunner && options.bundler === 'vite') { - options.unitTestRunner = 'vitest'; - } else if (!options.unitTestRunner && options.config !== 'npm-scripts') { - options.unitTestRunner = 'jest'; - } - - if (!options.linter && options.config !== 'npm-scripts') { - options.linter = Linter.EsLint; - } - const { projectName, names: projectNames, @@ -715,6 +886,12 @@ async function normalizeOptions( options.minimal ??= false; + const projectPackageManagerWorkspaceState = + getProjectPackageManagerWorkspaceState(tree, projectRoot); + + // We default to generate a project.json file if the new setup is not being used + options.useProjectJson ??= !isUsingTsSolutionConfig; + return { ...options, fileName, @@ -723,12 +900,15 @@ async function normalizeOptions( projectRoot, parsedTags, importPath, + hasPlugin, + isUsingTsSolutionConfig, + projectPackageManagerWorkspaceState, }; } function addProjectDependencies( tree: Tree, - options: NormalizedSchema + options: NormalizedLibraryGeneratorOptions ): GeneratorCallback { if (options.bundler == 'esbuild') { return addDependenciesToPackageJson( @@ -741,10 +921,28 @@ function addProjectDependencies( } ); } else if (options.bundler == 'rollup') { + const { dependencies, devDependencies } = getSwcDependencies(); + return addDependenciesToPackageJson( + tree, + { ...dependencies }, + { + ...devDependencies, + '@nx/rollup': nxVersion, + '@types/node': typesNodeVersion, + } + ); + } else if (options.bundler === 'tsc') { return addDependenciesToPackageJson( tree, {}, - { '@nx/rollup': nxVersion, '@types/node': typesNodeVersion } + { tslib: tsLibVersion, '@types/node': typesNodeVersion } + ); + } else if (options.bundler === 'swc') { + const { dependencies, devDependencies } = getSwcDependencies(); + return addDependenciesToPackageJson( + tree, + { ...dependencies }, + { ...devDependencies, '@types/node': typesNodeVersion } ); } else { return addDependenciesToPackageJson( @@ -776,7 +974,7 @@ function getBuildExecutor(bundler: Bundler) { } } -function getOutputPath(options: NormalizedSchema) { +function getOutputPath(options: NormalizedLibraryGeneratorOptions) { const parts = [defaultOutputDirectory]; if (options.projectRoot === '.') { parts.push(options.name); @@ -786,15 +984,117 @@ function getOutputPath(options: NormalizedSchema) { return joinPathFragments(...parts); } -function createProjectTsConfigJson(tree: Tree, options: NormalizedSchema) { +function createProjectTsConfigs( + tree: Tree, + options: NormalizedLibraryGeneratorOptions +) { + const rootOffset = offsetFromRoot(options.projectRoot); + + let compilerOptionOverrides: Record = { + module: options.isUsingTsSolutionConfig + ? options.bundler === 'rollup' + ? 'esnext' + : 'nodenext' + : 'commonjs', + ...(options.isUsingTsSolutionConfig + ? options.bundler === 'rollup' + ? { moduleResolution: 'bundler' } + : { moduleResolution: 'nodenext' } + : {}), + ...(options.js ? { allowJs: true } : {}), + ...(options.strict + ? { + forceConsistentCasingInFileNames: true, + strict: true, + importHelpers: true, + noImplicitOverride: true, + noImplicitReturns: true, + noFallthroughCasesInSwitch: true, + ...(!options.isUsingTsSolutionConfig + ? { noPropertyAccessFromIndexSignature: true } + : {}), + } + : {}), + }; + + if (!options.rootProject || options.isUsingTsSolutionConfig) { + // filter out options already set with the same value in root tsconfig file that we're going to extend from + compilerOptionOverrides = getNeededCompilerOptionOverrides( + tree, + compilerOptionOverrides, + // must have been created by now + getRootTsConfigFileName(tree)! + ); + } + + // tsconfig.lib.json + generateFiles( + tree, + join( + __dirname, + 'files/tsconfig-lib', + options.isUsingTsSolutionConfig ? 'ts-solution' : 'non-ts-solution' + ), + options.projectRoot, + { + ...options, + offsetFromRoot: rootOffset, + js: !!options.js, + compilerOptions: Object.entries(compilerOptionOverrides) + .map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`) + .join(',\n '), + tmpl: '', + } + ); + + // tsconfig.json + if (options.isUsingTsSolutionConfig) { + if (options.rootProject) { + // the root tsconfig.json is already created with the expected settings + // for the TS plugin, we just need to update it with the project-specific + // settings + updateJson(tree, 'tsconfig.json', (json) => { + json.references.push({ + path: './tsconfig.lib.json', + }); + return json; + }); + } else { + // create a new tsconfig.json for the project + const tsconfig = { + extends: getRelativePathToRootTsConfig(tree, options.projectRoot), + files: [], + include: [], + references: [{ path: './tsconfig.lib.json' }], + }; + writeJson( + tree, + joinPathFragments(options.projectRoot, 'tsconfig.json'), + tsconfig + ); + + // update root project tsconfig.json references with the new lib tsconfig + updateJson(tree, 'tsconfig.json', (json) => { + json.references ??= []; + json.references.push({ + path: options.projectRoot.startsWith('./') + ? options.projectRoot + : './' + options.projectRoot, + }); + return json; + }); + } + + return; + } + const tsconfig = { extends: options.rootProject ? undefined : getRelativePathToRootTsConfig(tree, options.projectRoot), compilerOptions: { ...(options.rootProject ? tsConfigBaseOptions : {}), - module: 'commonjs', - allowJs: options.js ? true : undefined, + ...compilerOptionOverrides, }, files: [], include: [], @@ -835,14 +1135,18 @@ function determineDependencies( type EntryField = string | { [key: string]: EntryField }; function determineEntryFields( - options: LibraryGeneratorSchema + options: NormalizedLibraryGeneratorOptions ): Record { switch (options.bundler) { case 'tsc': return { type: 'commonjs', - main: './src/index.js', - typings: './src/index.d.ts', + main: options.isUsingTsSolutionConfig + ? './dist/index.js' + : './src/index.js', + typings: options.isUsingTsSolutionConfig + ? './dist/index.d.ts' + : './src/index.d.ts', }; case 'swc': return { @@ -854,16 +1158,26 @@ function determineEntryFields( return { // Since we're publishing both formats, skip the type field. // Bundlers or Node will determine the entry point to use. - main: './index.cjs', - module: './index.js', + main: options.isUsingTsSolutionConfig + ? './dist/index.cjs' + : './index.cjs', + module: options.isUsingTsSolutionConfig + ? './dist/index.js' + : './index.js', }; case 'vite': return { // Since we're publishing both formats, skip the type field. // Bundlers or Node will determine the entry point to use. - main: './index.js', - module: './index.mjs', - typings: './index.d.ts', + main: options.isUsingTsSolutionConfig + ? './dist/index.js' + : './index.js', + module: options.isUsingTsSolutionConfig + ? './dist/index.mjs' + : './index.mjs', + typings: options.isUsingTsSolutionConfig + ? './dist/index.d.ts' + : './index.d.ts', }; case 'esbuild': // For libraries intended for Node, use CJS. @@ -905,7 +1219,7 @@ function projectsConfigMatchesProject( async function addProjectToNxReleaseConfig( tree: Tree, - options: NormalizedSchema, + options: NormalizedLibraryGeneratorOptions, projectConfiguration: ProjectConfiguration ) { const nxJson = readNxJson(tree); diff --git a/packages/js/src/generators/library/schema.d.ts b/packages/js/src/generators/library/schema.d.ts new file mode 100644 index 0000000000..d50cafce23 --- /dev/null +++ b/packages/js/src/generators/library/schema.d.ts @@ -0,0 +1,53 @@ +import type { + ProjectNameAndRootFormat, + ProjectNameAndRootOptions, +} from '@nx/devkit/src/generators/project-name-and-root-utils'; +// nx-ignore-next-line +const { Linter, LinterType } = require('@nx/eslint'); // use require to import to avoid circular dependency +import type { ProjectPackageManagerWorkspaceState } from '../../utils/package-manager-workspaces'; + +export type Compiler = 'tsc' | 'swc'; +export type Bundler = 'swc' | 'tsc' | 'rollup' | 'vite' | 'esbuild' | 'none'; + +export interface LibraryGeneratorSchema { + name: string; + directory?: string; + projectNameAndRootFormat?: ProjectNameAndRootFormat; + skipFormat?: boolean; + tags?: string; + skipTsConfig?: boolean; + skipPackageJson?: boolean; + includeBabelRc?: boolean; + unitTestRunner?: 'jest' | 'vitest' | 'none'; + linter?: Linter | LinterType; + testEnvironment?: 'jsdom' | 'node'; + importPath?: string; + js?: boolean; + pascalCaseFiles?: boolean; + strict?: boolean; + publishable?: boolean; + buildable?: boolean; + setParserOptionsProject?: boolean; + config?: 'workspace' | 'project' | 'npm-scripts'; + compiler?: Compiler; + bundler?: Bundler; + skipTypeCheck?: boolean; + minimal?: boolean; + rootProject?: boolean; + simpleName?: boolean; + addPlugin?: boolean; + useProjectJson?: boolean; +} + +export interface NormalizedLibraryGeneratorOptions + extends LibraryGeneratorSchema { + name: string; + projectNames: ProjectNameAndRootOptions['names']; + fileName: string; + projectRoot: string; + parsedTags: string[]; + importPath?: string; + hasPlugin: boolean; + isUsingTsSolutionConfig: boolean; + projectPackageManagerWorkspaceState: ProjectPackageManagerWorkspaceState; +} diff --git a/packages/js/src/generators/library/schema.json b/packages/js/src/generators/library/schema.json index 639bdfb90e..4c7af83132 100644 --- a/packages/js/src/generators/library/schema.json +++ b/packages/js/src/generators/library/schema.json @@ -21,22 +21,28 @@ "description": "A directory where the lib is placed.", "x-priority": "important" }, - "projectNameAndRootFormat": { - "description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).", + "bundler": { + "description": "The bundler to use. Choosing 'none' means this library is not buildable.", "type": "string", - "enum": ["as-provided", "derived"] + "enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"], + "x-priority": "important" }, "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint", "none"], - "default": "eslint" + "enum": ["none", "eslint"], + "x-priority": "important" }, "unitTestRunner": { - "type": "string", - "enum": ["jest", "vitest", "none"], "description": "Test runner to use for unit tests.", - "x-prompt": "Which unit test runner would you like to use?" + "type": "string", + "enum": ["none", "jest", "vitest"], + "x-priority": "important" + }, + "projectNameAndRootFormat": { + "description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).", + "type": "string", + "enum": ["as-provided", "derived"] }, "tags": { "type": "string", @@ -112,18 +118,9 @@ "compiler": { "type": "string", "enum": ["tsc", "swc"], - "default": "tsc", "description": "The compiler used by the build and test targets", "x-deprecated": "Use the `bundler` option for greater control (swc, tsc, rollup, vite, esbuild, none)." }, - "bundler": { - "description": "The bundler to use. Choosing 'none' means this library is not buildable.", - "type": "string", - "enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"], - "default": "tsc", - "x-prompt": "Which bundler would you like to use to build the library? Choose 'none' to skip build setup.", - "x-priority": "important" - }, "skipTypeCheck": { "type": "boolean", "description": "Whether to skip TypeScript type checking for SWC compiler.", @@ -138,6 +135,10 @@ "description": "Don't include the directory in the generated file name.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["name"], diff --git a/packages/js/src/generators/library/utils/package-manager-workspaces.ts b/packages/js/src/generators/library/utils/package-manager-workspaces.ts new file mode 100644 index 0000000000..776fbf371e --- /dev/null +++ b/packages/js/src/generators/library/utils/package-manager-workspaces.ts @@ -0,0 +1,58 @@ +import { + detectPackageManager, + getPackageManagerVersion, + output, + type GeneratorCallback, +} from '@nx/devkit'; +import { lt } from 'semver'; +import type { ProjectPackageManagerWorkspaceState } from '../../../utils/package-manager-workspaces'; + +export function getProjectPackageManagerWorkspaceStateWarningTask( + projectPackageManagerWorkspaceState: ProjectPackageManagerWorkspaceState, + workspaceRoot: string +): GeneratorCallback { + return (): void => { + const packageManager = detectPackageManager(workspaceRoot); + let packageManagerWorkspaceSetupDocs: string; + if (packageManager === 'pnpm') { + packageManagerWorkspaceSetupDocs = + 'https://pnpm.io/workspaces and https://pnpm.io/pnpm-workspace_yaml'; + } else if (packageManager === 'yarn') { + const yarnVersion = getPackageManagerVersion( + packageManager, + workspaceRoot + ); + if (lt(yarnVersion, '2.0.0')) { + packageManagerWorkspaceSetupDocs = + 'https://classic.yarnpkg.com/lang/en/docs/workspaces/'; + } else { + packageManagerWorkspaceSetupDocs = + 'https://yarnpkg.com/features/workspaces'; + } + } else if (packageManager === 'npm') { + packageManagerWorkspaceSetupDocs = + 'https://docs.npmjs.com/cli/v10/using-npm/workspaces'; + } else if (packageManager === 'bun') { + packageManagerWorkspaceSetupDocs = + 'https://bun.sh/docs/install/workspaces'; + } + + if (projectPackageManagerWorkspaceState === 'no-workspaces') { + output.warn({ + title: `The package manager workspaces feature is not enabled in the workspace`, + bodyLines: [ + 'You must enable the package manager workspaces feature to use the "@nx/js/typescript" plugin.', + `Read more about the ${packageManager} workspaces feature and how to set it up at ${packageManagerWorkspaceSetupDocs}.`, + ], + }); + } else if (projectPackageManagerWorkspaceState === 'excluded') { + output.warn({ + title: `The project is not included in the package manager workspaces configuration`, + bodyLines: [ + 'Please add it to the workspace configuration to use the "@nx/js/typescript" plugin.', + `Read more about the ${packageManager} workspaces feature and how to set it up at ${packageManagerWorkspaceSetupDocs}.`, + ], + }); + } + }; +} diff --git a/packages/js/src/generators/library/utils/plugin-registrations.spec.ts b/packages/js/src/generators/library/utils/plugin-registrations.spec.ts new file mode 100644 index 0000000000..d6b2b9a5e7 --- /dev/null +++ b/packages/js/src/generators/library/utils/plugin-registrations.spec.ts @@ -0,0 +1,428 @@ +import type { NxJsonConfiguration } from '@nx/devkit'; +import { + ensureProjectIsExcludedFromPluginRegistrations, + ensureProjectIsIncludedInPluginRegistrations, +} from './plugin-registrations'; + +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: [ + { + plugin: '@nx/js/typescript', + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }; + const nxJson = structuredClone(originalNxJson); + + ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toEqual(originalNxJson); + }); + + it('should do nothing when `include` is set in a plugin registration that infers both targets and the project is already included', () => { + const originalNxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }; + const nxJson = structuredClone(originalNxJson); + + ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toEqual(originalNxJson); + }); + + it('should do nothing when `exclude` is set in a plugin registration that infers both targets and the project is not excluded', () => { + const originalNxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }; + const nxJson = structuredClone(originalNxJson); + + ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg2'); + + expect(nxJson).toEqual(originalNxJson); + }); + + it('should exclude a project from a string plugin registration and add a new plugin registration that includes it', () => { + const nxJson: NxJsonConfiguration = { plugins: ['@nx/js/typescript'] }; + + ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toStrictEqual({ + plugins: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/pkg1/*'], + }, + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }); + }); + + it('should exclude a project from a plugin registration missing the `typecheck` target and add a new plugin registration that includes it', () => { + const nxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + options: { + typecheck: false, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }; + + ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toStrictEqual({ + plugins: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/pkg1/*'], + options: { + typecheck: false, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }); + }); + + it('should exclude a project from a plugin registration missing the `build` target 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'); + + 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', + 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: [ + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }; + + ensureProjectIsIncludedInPluginRegistrations(nxJson, 'packages/pkg2'); + + expect(nxJson).toStrictEqual({ + plugins: [ + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*', 'packages/pkg2/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + 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: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/**/*'], + 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/**/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }); + }); +}); + +describe('ensureProjectIsExcludedFromPluginRegistrations', () => { + it('should do nothing when there is no `plugin` entry', () => { + const nxJson: NxJsonConfiguration = {}; + + ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toStrictEqual({}); + }); + + it('should do nothing when the there are no plugins', () => { + const nxJson: NxJsonConfiguration = { plugins: [] }; + + ensureProjectIsExcludedFromPluginRegistrations(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'] }; + + ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toStrictEqual({ plugins: ['@foo/bar/plugin'] }); + }); + + it('should do nothing when the plugin registration does not infer any of the targets', () => { + const nxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + options: { typecheck: false }, + }, + ], + }; + + ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toStrictEqual({ + plugins: [ + { + plugin: '@nx/js/typescript', + options: { typecheck: false }, + }, + ], + }); + }); + + it('should do nothing when `include` is set in a plugin registration that infers any of the targets and the project is not included', () => { + const originalNxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + include: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + }, + }, + ], + }; + const nxJson = structuredClone(originalNxJson); + + ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg2'); + + expect(nxJson).toEqual(originalNxJson); + }); + + it('should do nothing when `exclude` is set in a plugin registration that infers any of the targets and the project is already excluded', () => { + const originalNxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/pkg1/*'], + options: { + typecheck: false, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }; + const nxJson = structuredClone(originalNxJson); + + ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toEqual(originalNxJson); + }); + + it('should exclude a project from a string plugin registration', () => { + const nxJson: NxJsonConfiguration = { plugins: ['@nx/js/typescript'] }; + + ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toStrictEqual({ + plugins: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/pkg1/*'], + }, + ], + }); + }); + + it('should exclude a project from a plugin registration that infers any of the targets', () => { + const nxJson: NxJsonConfiguration = { + plugins: [ + { + plugin: '@nx/js/typescript', + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }; + + ensureProjectIsExcludedFromPluginRegistrations(nxJson, 'packages/pkg1'); + + expect(nxJson).toStrictEqual({ + plugins: [ + { + plugin: '@nx/js/typescript', + exclude: ['packages/pkg1/*'], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }, + ], + }); + }); +}); diff --git a/packages/js/src/generators/library/utils/plugin-registrations.ts b/packages/js/src/generators/library/utils/plugin-registrations.ts new file mode 100644 index 0000000000..b9c082b43b --- /dev/null +++ b/packages/js/src/generators/library/utils/plugin-registrations.ts @@ -0,0 +1,141 @@ +import type { + ExpandedPluginConfiguration, + NxJsonConfiguration, +} from '@nx/devkit'; +import { findMatchingConfigFiles } from 'nx/src/devkit-internals'; +import type { TscPluginOptions } from '../../../plugins/typescript/plugin'; + +export function ensureProjectIsIncludedInPluginRegistrations( + nxJson: NxJsonConfiguration, + projectRoot: string +): void { + if ( + !nxJson.plugins?.length || + !nxJson.plugins.some(isTypeScriptPluginRegistration) + ) { + return; + } + + let isIncluded = false; + let index = 0; + for (const registration of nxJson.plugins) { + if (!isTypeScriptPluginRegistration(registration)) { + index++; + continue; + } + + 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 + nxJson.plugins[index] = { + plugin: '@nx/js/typescript', + exclude: [`${projectRoot}/*`], + }; + } else { + // check if the project would be included by the plugin registration + const matchingConfigFiles = findMatchingConfigFiles( + [`${projectRoot}/tsconfig.json`], + '**/tsconfig.json', + registration.include, + registration.exclude + ); + 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 + isIncluded = true; + } else { + // it would not have the `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 + ) { + // 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}/*`); + } + } + 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 + nxJson.plugins.push({ + plugin: '@nx/js/typescript', + include: [`${projectRoot}/*`], + options: { + typecheck: { targetName: 'typecheck' }, + build: { + targetName: 'build', + configName: 'tsconfig.lib.json', + }, + }, + }); + } +} + +export function ensureProjectIsExcludedFromPluginRegistrations( + nxJson: NxJsonConfiguration, + projectRoot: string +): void { + if ( + !nxJson.plugins?.length || + !nxJson.plugins.some(isTypeScriptPluginRegistration) + ) { + return; + } + + let index = 0; + for (const registration of nxJson.plugins) { + if (!isTypeScriptPluginRegistration(registration)) { + index++; + continue; + } + + if (typeof registration === 'string') { + // if it's a string, it includes all projects, so we need to exclude it + nxJson.plugins[index] = { + plugin: '@nx/js/typescript', + exclude: [`${projectRoot}/*`], + }; + } else { + // check if the project would be included by the plugin registration + const matchingConfigFiles = findMatchingConfigFiles( + [`${projectRoot}/tsconfig.json`], + '**/tsconfig.json', + registration.include, + registration.exclude + ); + if ( + matchingConfigFiles.length && + (registration.options?.typecheck !== false || + registration.options?.build) + ) { + // the project is included by a plugin registration that infers any of the targets, so we need to exclude it + registration.exclude ??= []; + registration.exclude.push(`${projectRoot}/*`); + } + } + index++; + } +} + +function isTypeScriptPluginRegistration( + plugin: string | ExpandedPluginConfiguration +): plugin is string | ExpandedPluginConfiguration { + return ( + (typeof plugin === 'string' && plugin === '@nx/js/typescript') || + (typeof plugin !== 'string' && plugin.plugin === '@nx/js/typescript') + ); +} diff --git a/packages/js/src/generators/setup-verdaccio/generator.ts b/packages/js/src/generators/setup-verdaccio/generator.ts index a1dcd44967..e59aff326d 100644 --- a/packages/js/src/generators/setup-verdaccio/generator.ts +++ b/packages/js/src/generators/setup-verdaccio/generator.ts @@ -11,6 +11,7 @@ import { } from '@nx/devkit'; import * as path from 'path'; import { SetupVerdaccioGeneratorSchema } from './schema'; +import { isUsingTsSolutionSetup } from '../../utils/typescript/ts-solution-setup'; import { verdaccioVersion } from '../../utils/versions'; import { execSync } from 'child_process'; @@ -38,21 +39,25 @@ export async function setupVerdaccio( }, }; if (!tree.exists('project.json')) { + const isUsingNewTsSetup = isUsingTsSolutionSetup(tree); + const { name } = readJson(tree, 'package.json'); updateJson(tree, 'package.json', (json) => { - if (!json.nx) { - json.nx = { - includedScripts: [], - }; + json.nx ??= { includedScripts: [] }; + if (isUsingNewTsSetup) { + json.nx.targets ??= {}; + json.nx.targets['local-registry'] ??= verdaccioTarget; } return json; }); - addProjectConfiguration(tree, name, { - root: '.', - targets: { - ['local-registry']: verdaccioTarget, - }, - }); + if (!isUsingNewTsSetup) { + addProjectConfiguration(tree, name, { + root: '.', + targets: { + ['local-registry']: verdaccioTarget, + }, + }); + } } else { // use updateJson instead of updateProjectConfiguration due to unknown project name updateJson(tree, 'project.json', (json: ProjectConfiguration) => { diff --git a/packages/js/src/utils/find-npm-dependencies.spec.ts b/packages/js/src/utils/find-npm-dependencies.spec.ts index a6fe87a3b2..95df84289b 100644 --- a/packages/js/src/utils/find-npm-dependencies.spec.ts +++ b/packages/js/src/utils/find-npm-dependencies.spec.ts @@ -169,6 +169,70 @@ describe('findNpmDependencies', () => { }); }); + it.each` + fileName + ${'tsconfig.base.json'} + ${'tsconfig.json'} + `( + 'should pick up helper npm dependencies when using tsc and run-commands', + ({ fileName }) => { + vol.fromJSON( + { + [`./${fileName}`]: JSON.stringify({ + compilerOptions: { importHelpers: true }, + }), + './nx.json': JSON.stringify(nxJson), + './libs/my-lib/tsconfig.json': JSON.stringify({ + compilerOptions: { + importHelpers: true, + }, + }), + }, + '/root' + ); + const lib = { + name: 'my-lib', + type: 'lib' as const, + data: { + root: 'libs/my-lib', + targets: { + build: { + executor: 'nx:run-commands', + options: { + command: 'tsc --build tsconfig.lib.json --pretty --verbose', + }, + }, + }, + }, + }; + const projectGraph = { + nodes: { + 'my-lib': lib, + }, + externalNodes: { + 'npm:tslib': { + name: 'npm:tslib' as const, + type: 'npm' as const, + data: { + packageName: 'tslib', + version: '2.6.0', + }, + }, + }, + dependencies: {}, + }; + const projectFileMap = { + 'my-lib': [], + }; + + expect( + findNpmDependencies('/root', lib, projectGraph, projectFileMap, 'build') + ).toEqual({ + tslib: '2.6.0', + }); + } + ); + it('should handle missing ts/swc helper packages from externalNodes', () => { vol.fromJSON( { diff --git a/packages/js/src/utils/find-npm-dependencies.ts b/packages/js/src/utils/find-npm-dependencies.ts index 33c7d3767d..54d844921a 100644 --- a/packages/js/src/utils/find-npm-dependencies.ts +++ b/packages/js/src/utils/find-npm-dependencies.ts @@ -10,7 +10,7 @@ import { } from '@nx/devkit'; import { fileExists } from 'nx/src/utils/fileutils'; import { fileDataDepTarget } from 'nx/src/config/project-graph'; -import { readTsConfig } from './typescript/ts-config'; +import { getRootTsConfigFileName, readTsConfig } from './typescript/ts-config'; import { filterUsingGlobPatterns, getTargetInputs, @@ -223,4 +223,21 @@ function collectHelperDependencies( projectGraph.externalNodes['npm:@swc/helpers'].data.version; } } + + // For inferred targets or manually added run-commands, check if user is using `tsc` in build target. + if ( + target.executor === 'nx:run-commands' && + /\btsc\b/.test(target.options.command) + ) { + const tsConfigFileName = getRootTsConfigFileName(); + if (tsConfigFileName) { + const tsConfig = readTsConfig(join(workspaceRoot, tsConfigFileName)); + if ( + tsConfig?.options['importHelpers'] && + projectGraph.externalNodes['npm:tslib']?.type === 'npm' + ) { + npmDeps['tslib'] = projectGraph.externalNodes['npm:tslib'].data.version; + } + } + } } diff --git a/packages/js/src/utils/package-manager-workspaces.ts b/packages/js/src/utils/package-manager-workspaces.ts new file mode 100644 index 0000000000..601cce1049 --- /dev/null +++ b/packages/js/src/utils/package-manager-workspaces.ts @@ -0,0 +1,37 @@ +import { + detectPackageManager, + isWorkspacesEnabled, + readJson, + type Tree, +} from '@nx/devkit'; +import { minimatch } from 'minimatch'; +import { join } from 'node:path/posix'; +import { getGlobPatternsFromPackageManagerWorkspaces } from 'nx/src/plugins/package-json'; + +export type ProjectPackageManagerWorkspaceState = + | 'included' + | 'excluded' + | 'no-workspaces'; + +export function getProjectPackageManagerWorkspaceState( + tree: Tree, + projectRoot: string +): ProjectPackageManagerWorkspaceState { + if (!isUsingPackageManagerWorkspaces(tree)) { + return 'no-workspaces'; + } + + const patterns = getGlobPatternsFromPackageManagerWorkspaces( + tree.root, + (path) => readJson(tree, path, { expectComments: true }) + ); + const isIncluded = patterns.some((p) => + minimatch(join(projectRoot, 'package.json'), p) + ); + + return isIncluded ? 'included' : 'excluded'; +} + +export function isUsingPackageManagerWorkspaces(tree: Tree): boolean { + return isWorkspacesEnabled(detectPackageManager(tree.root), tree.root); +} diff --git a/packages/js/src/utils/prettier.ts b/packages/js/src/utils/prettier.ts index 8b5531f7a2..cc9585a4f8 100644 --- a/packages/js/src/utils/prettier.ts +++ b/packages/js/src/utils/prettier.ts @@ -1,5 +1,6 @@ import { addDependenciesToPackageJson, + readJson, stripIndents, updateJson, writeJson, @@ -96,3 +97,64 @@ export function generatePrettierSetup( ? () => {} : addDependenciesToPackageJson(tree, {}, { prettier: prettierVersion }); } + +export async function resolvePrettierConfigPath( + tree: Tree +): Promise { + let prettier: typeof import('prettier'); + try { + prettier = require('prettier'); + } catch { + return null; + } + + if (prettier) { + const filePath = await prettier.resolveConfigFile(); + if (filePath) { + return filePath; + } + } + + if (!tree) { + return null; + } + + // if we haven't find a config file in the file system, we try to find it in the virtual tree + // https://prettier.io/docs/en/configuration.html + const prettierrcNameOptions = [ + '.prettierrc', + '.prettierrc.json', + '.prettierrc.yml', + '.prettierrc.yaml', + '.prettierrc.json5', + '.prettierrc.js', + '.prettierrc.cjs', + '.prettierrc.mjs', + '.prettierrc.toml', + 'prettier.config.js', + 'prettier.config.cjs', + 'prettier.config.mjs', + ]; + + const filePath = prettierrcNameOptions.find((file) => tree.exists(file)); + if (filePath) { + return filePath; + } + + // check the package.json file + const packageJson = readJson(tree, 'package.json'); + if (packageJson.prettier) { + return 'package.json'; + } + + // check the package.yaml file + if (tree.exists('package.yaml')) { + const { load } = await import('@zkochan/js-yaml'); + const packageYaml = load(tree.read('package.yaml', 'utf-8')); + if (packageYaml.prettier) { + return 'package.yaml'; + } + } + + return null; +} diff --git a/packages/js/src/utils/schema.d.ts b/packages/js/src/utils/schema.d.ts index 9f37dc5211..22f77569ce 100644 --- a/packages/js/src/utils/schema.d.ts +++ b/packages/js/src/utils/schema.d.ts @@ -1,39 +1,7 @@ -import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils'; import type { AssetGlob, FileInputOutput } from './assets/assets'; import { TransformerEntry } from './typescript/types'; -// nx-ignore-next-line -const { Linter, LinterType } = require('@nx/eslint'); // use require to import to avoid circular dependency export type Compiler = 'tsc' | 'swc'; -export type Bundler = 'swc' | 'tsc' | 'rollup' | 'vite' | 'esbuild' | 'none'; - -export interface LibraryGeneratorSchema { - name: string; - directory?: string; - projectNameAndRootFormat?: ProjectNameAndRootFormat; - skipFormat?: boolean; - tags?: string; - skipTsConfig?: boolean; - skipPackageJson?: boolean; - includeBabelRc?: boolean; - unitTestRunner?: 'jest' | 'vitest' | 'none'; - linter?: Linter | LinterType; - testEnvironment?: 'jsdom' | 'node'; - importPath?: string; - js?: boolean; - strict?: boolean; - publishable?: boolean; - buildable?: boolean; - setParserOptionsProject?: boolean; - config?: 'workspace' | 'project' | 'npm-scripts'; - compiler?: Compiler; - bundler?: Bundler; - skipTypeCheck?: boolean; - minimal?: boolean; - rootProject?: boolean; - simpleName?: boolean; - addPlugin?: boolean; -} export interface ExecutorOptions { assets: Array; diff --git a/packages/js/src/utils/swc/add-swc-dependencies.ts b/packages/js/src/utils/swc/add-swc-dependencies.ts index 2f8f3798bc..3bb9abb10f 100644 --- a/packages/js/src/utils/swc/add-swc-dependencies.ts +++ b/packages/js/src/utils/swc/add-swc-dependencies.ts @@ -6,17 +6,25 @@ import { swcNodeVersion, } from '../versions'; +export function getSwcDependencies(): { + dependencies: Record; + devDependencies: Record; +} { + const dependencies = { + '@swc/helpers': swcHelpersVersion, + }; + const devDependencies = { + '@swc/core': swcCoreVersion, + '@swc/cli': swcCliVersion, + }; + + return { dependencies, devDependencies }; +} + export function addSwcDependencies(tree: Tree) { - return addDependenciesToPackageJson( - tree, - { - '@swc/helpers': swcHelpersVersion, - }, - { - '@swc/core': swcCoreVersion, - '@swc/cli': swcCliVersion, - } - ); + const { dependencies, devDependencies } = getSwcDependencies(); + + return addDependenciesToPackageJson(tree, dependencies, devDependencies); } export function addSwcRegisterDependencies(tree: Tree) { diff --git a/packages/js/src/utils/typescript/configuration.ts b/packages/js/src/utils/typescript/configuration.ts new file mode 100644 index 0000000000..217db92238 --- /dev/null +++ b/packages/js/src/utils/typescript/configuration.ts @@ -0,0 +1,102 @@ +import type { Tree } from '@nx/devkit'; +import { dirname } from 'path'; +import type { CompilerOptions, System } from 'typescript'; +import { ensureTypescript } from './ensure-typescript'; + +type CompilerOptionsEnumProps = Pick< + CompilerOptions, + | 'importsNotUsedAsValues' + | 'jsx' + | 'module' + | 'moduleDetection' + | 'moduleResolution' + | 'newLine' + | 'target' +>; +const optionEnumTypeMap: { + [key in keyof CompilerOptionsEnumProps]: keyof typeof ts; +} = { + importsNotUsedAsValues: 'ImportsNotUsedAsValues', + jsx: 'JsxEmit', + module: 'ModuleKind', + moduleDetection: 'ModuleDetectionKind', + moduleResolution: 'ModuleResolutionKind', + newLine: 'NewLineKind', + target: 'ScriptTarget', +}; + +let ts: typeof import('typescript'); + +/** + * Cleans up the provided compiler options to only include the options that are + * different or not set in the provided tsconfig file. + */ +export function getNeededCompilerOptionOverrides( + tree: Tree, + compilerOptions: Record, + tsConfigPath: string +): Record { + if (!ts) { + ts = ensureTypescript(); + } + + const tsSysFromTree: System = { + ...ts.sys, + readFile: (path) => tree.read(path, 'utf-8'), + }; + + const parsed = ts.parseJsonConfigFileContent( + ts.readConfigFile(tsConfigPath, tsSysFromTree.readFile).config, + tsSysFromTree, + dirname(tsConfigPath) + ); + + // ModuleKind: { CommonJS: 'commonjs', ... } => ModuleKind: { commonjs: 'CommonJS', ... } + const reversedCompilerOptionsEnumValues = { + JsxEmit: reverseEnum(ts.server.protocol.JsxEmit), + ModuleKind: reverseEnum(ts.server.protocol.ModuleKind), + ModuleResolutionKind: reverseEnum(ts.server.protocol.ModuleResolutionKind), + NewLineKind: reverseEnum(ts.server.protocol.NewLineKind), + ScriptTarget: reverseEnum(ts.server.protocol.ScriptTarget), + }; + const matchesValue = (key: keyof CompilerOptions) => { + return ( + parsed.options[key] === + ts[optionEnumTypeMap[key]][compilerOptions[key]] || + parsed.options[key] === + ts[optionEnumTypeMap[key]][ + reversedCompilerOptionsEnumValues[optionEnumTypeMap[key]][ + compilerOptions[key] + ] + ] + ); + }; + + let result = {}; + for (const key of Object.keys(compilerOptions)) { + if (optionEnumTypeMap[key]) { + if (parsed.options[key] === undefined) { + result[key] = compilerOptions[key]; + } else if (!matchesValue(key)) { + result[key] = compilerOptions[key]; + } + } else if (parsed.options[key] !== compilerOptions[key]) { + result[key] = compilerOptions[key]; + } + } + + return result; +} + +type Entries = { [K in keyof T]: [K, T[K]] }[keyof T]; +function reverseEnum< + EnumObj extends Record, + Result = { + [K in EnumObj[keyof EnumObj]]: Extract, [any, K]>[0]; + } +>(enumObj: EnumObj): Result { + return Object.keys(enumObj).reduce((acc, key) => { + acc[enumObj[key]] = key; + return acc; + }, {} as Result); +} diff --git a/packages/js/src/utils/typescript/ts-solution-setup.ts b/packages/js/src/utils/typescript/ts-solution-setup.ts new file mode 100644 index 0000000000..3432430318 --- /dev/null +++ b/packages/js/src/utils/typescript/ts-solution-setup.ts @@ -0,0 +1,63 @@ +import { readJson, readNxJson, type Tree } from '@nx/devkit'; +import { isUsingPackageManagerWorkspaces } from '../package-manager-workspaces'; + +export function isUsingTypeScriptPlugin(tree: Tree): boolean { + const nxJson = readNxJson(tree); + + return ( + nxJson?.plugins?.some((p) => + typeof p === 'string' + ? p === '@nx/js/typescript' + : p.plugin === '@nx/js/typescript' + ) ?? false + ); +} + +export function isUsingTsSolutionSetup(tree: Tree): boolean { + return ( + isUsingPackageManagerWorkspaces(tree) && + isWorkspaceSetupWithTsSolution(tree) + ); +} + +function isWorkspaceSetupWithTsSolution(tree: Tree): boolean { + if (!tree.exists('tsconfig.base.json') || !tree.exists('tsconfig.json')) { + return false; + } + + const tsconfigJson = readJson(tree, 'tsconfig.json'); + if (tsconfigJson.extends !== './tsconfig.base.json') { + return false; + } + + /** + * New setup: + * - `files` is defined and set to an empty array + * - `references` is defined and set to an empty array + * - `include` is not defined or is set to an empty array + */ + if ( + !tsconfigJson.files || + tsconfigJson.files.length > 0 || + !tsconfigJson.references || + !!tsconfigJson.include?.length + ) { + return false; + } + + const baseTsconfigJson = readJson(tree, 'tsconfig.base.json'); + if ( + !baseTsconfigJson.compilerOptions || + !baseTsconfigJson.compilerOptions.composite || + !baseTsconfigJson.compilerOptions.declaration + ) { + return false; + } + + const { compilerOptions, ...rest } = baseTsconfigJson; + if (Object.keys(rest).length > 0) { + return false; + } + + return true; +} diff --git a/packages/nest/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/nest/src/generators/library/__snapshots__/library.spec.ts.snap index 491e8008a3..0339e4083e 100644 --- a/packages/nest/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/nest/src/generators/library/__snapshots__/library.spec.ts.snap @@ -31,6 +31,7 @@ exports[`lib --unit-test-runner none should not generate test configuration 1`] { "compilerOptions": { "forceConsistentCasingInFileNames": true, + "importHelpers": true, "module": "commonjs", "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, @@ -53,6 +54,7 @@ exports[`lib nested should create a local tsconfig.json 1`] = ` { "compilerOptions": { "forceConsistentCasingInFileNames": true, + "importHelpers": true, "module": "commonjs", "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, @@ -91,6 +93,7 @@ exports[`lib not nested should create a local tsconfig.json 1`] = ` { "compilerOptions": { "forceConsistentCasingInFileNames": true, + "importHelpers": true, "module": "commonjs", "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, diff --git a/packages/nest/src/generators/library/lib/normalize-options.ts b/packages/nest/src/generators/library/lib/normalize-options.ts index 7b37c9ac41..5f15e9c8fb 100644 --- a/packages/nest/src/generators/library/lib/normalize-options.ts +++ b/packages/nest/src/generators/library/lib/normalize-options.ts @@ -1,7 +1,7 @@ import { Tree, readNxJson } from '@nx/devkit'; import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope'; -import type { LibraryGeneratorSchema as JsLibraryGeneratorSchema } from '@nx/js/src/utils/schema'; +import type { LibraryGeneratorSchema as JsLibraryGeneratorSchema } from '@nx/js/src/generators/library/schema'; import { Linter } from '@nx/eslint'; import type { LibraryGeneratorOptions, NormalizedOptions } from '../schema'; diff --git a/packages/next/src/generators/application/application.spec.ts b/packages/next/src/generators/application/application.spec.ts index b902772451..677ab1328e 100644 --- a/packages/next/src/generators/application/application.spec.ts +++ b/packages/next/src/generators/application/application.spec.ts @@ -625,6 +625,7 @@ describe('app', () => { module.exports = [ ...compat.extends('next', 'next/core-web-vitals'), + ...baseConfig, ...nx.configs['flat/react-typescript'], { ignores: ['.next/**/*'] }, diff --git a/packages/next/src/generators/application/lib/add-linting.spec.ts b/packages/next/src/generators/application/lib/add-linting.spec.ts index 7bda2d12d6..e123896686 100644 --- a/packages/next/src/generators/application/lib/add-linting.spec.ts +++ b/packages/next/src/generators/application/lib/add-linting.spec.ts @@ -113,10 +113,11 @@ describe('updateEslint', () => { }); module.exports = [ - ...compat.extends("next", "next/core-web-vitals"), + ...compat.extends("next", "next/core-web-vitals"), + ...baseConfig, - ...nx.configs["flat/react-typescript"], - { ignores: [".next/**/*"] } + ...nx.configs["flat/react-typescript"], + { ignores: [".next/**/*"] } ]; " `); diff --git a/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap index 798781bfef..8f547ff794 100644 --- a/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap @@ -131,20 +131,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../node_modules/.vite/my-app', - plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/my-app', @@ -498,20 +494,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../node_modules/.vite/myApp', - plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/myApp', diff --git a/packages/nx/src/generators/utils/project-configuration.ts b/packages/nx/src/generators/utils/project-configuration.ts index 74bc8b312f..5011db23a4 100644 --- a/packages/nx/src/generators/utils/project-configuration.ts +++ b/packages/nx/src/generators/utils/project-configuration.ts @@ -115,7 +115,8 @@ function updateProjectConfigurationInPackageJson( const packageJson = readJson(tree, packageJsonFile); - if (packageJson.name === projectConfiguration.name ?? projectName) { + projectConfiguration.name = projectName; + if (packageJson.name === projectConfiguration.name) { delete projectConfiguration.name; } diff --git a/packages/nx/src/utils/package-json.ts b/packages/nx/src/utils/package-json.ts index c586cd812a..56a99a6f92 100644 --- a/packages/nx/src/utils/package-json.ts +++ b/packages/nx/src/utils/package-json.ts @@ -66,6 +66,7 @@ export interface PackageJson { packages: string[]; }; publishConfig?: Record; + files?: string[]; // Nx Project Configuration nx?: NxProjectPackageJsonConfiguration; diff --git a/packages/react-native/src/generators/web-configuration/files/base-vite/vite.config.ts__tmpl__ b/packages/react-native/src/generators/web-configuration/files/base-vite/vite.config.ts__tmpl__ index 87ca57cf87..1514782bd9 100644 --- a/packages/react-native/src/generators/web-configuration/files/base-vite/vite.config.ts__tmpl__ +++ b/packages/react-native/src/generators/web-configuration/files/base-vite/vite.config.ts__tmpl__ @@ -34,14 +34,12 @@ export default defineConfig({ define: { global: 'window', }, - resolve: { extensions, alias: { 'react-native': 'react-native-web', }, }, - build: { reportCompressedSize: true, commonjsOptions: { transformMixedEsModules: true }, @@ -50,7 +48,6 @@ export default defineConfig({ plugins: [rollupPlugin([/react-native-vector-icons/])], }, }, - server: { port: 4200, host: 'localhost', @@ -59,12 +56,10 @@ export default defineConfig({ allow: ['..'], }, }, - preview: { port: 4300, host: 'localhost', }, - optimizeDeps: { esbuildOptions: { resolveExtensions: extensions, @@ -72,9 +67,7 @@ export default defineConfig({ loader: { '.js': 'jsx' }, }, }, - plugins: [react(), nxViteTsPaths()], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], diff --git a/packages/react/src/generators/application/__snapshots__/application.legacy.spec.ts.snap b/packages/react/src/generators/application/__snapshots__/application.legacy.spec.ts.snap index a8050858e2..d84e42d4d5 100644 --- a/packages/react/src/generators/application/__snapshots__/application.legacy.spec.ts.snap +++ b/packages/react/src/generators/application/__snapshots__/application.legacy.spec.ts.snap @@ -1,46 +1,36 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`react app generator (legacy) should setup vite 1`] = ` +"/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +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, + cacheDir: '../node_modules/.vite/my-vite-app', + server:{ + port: 4200, + host: 'localhost', + }, + preview:{ + port: 4300, + host: 'localhost', + }, + plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../dist/my-vite-app', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, +}); " - /// - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - 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, - cacheDir: '../node_modules/.vite/my-vite-app', - - server:{ - port: 4200, - host: 'localhost', - }, - - preview:{ - port: 4300, - host: 'localhost', - }, - - plugins: [react(), -nxViteTsPaths(), -nxCopyAssetsPlugin(['*.md'])], - - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, - - build: { - outDir: '../dist/my-vite-app', - emptyOutDir: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, - }, - - - - });" `; diff --git a/packages/react/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/react/src/generators/application/__snapshots__/application.spec.ts.snap index 805867d097..2c1e65d18c 100644 --- a/packages/react/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/react/src/generators/application/__snapshots__/application.spec.ts.snap @@ -94,48 +94,38 @@ module.exports = { `; exports[`app --style @emotion/styled should not break if bundler is vite 1`] = ` +"/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +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, + cacheDir: '../node_modules/.vite/my-app', + server:{ + port: 4200, + host: 'localhost', + }, + preview:{ + port: 4300, + host: 'localhost', + }, + plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../dist/my-app', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, +}); " - /// - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - 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, - cacheDir: '../node_modules/.vite/my-app', - - server:{ - port: 4200, - host: 'localhost', - }, - - preview:{ - port: 4300, - host: 'localhost', - }, - - plugins: [react(), -nxViteTsPaths(), -nxCopyAssetsPlugin(['*.md'])], - - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, - - build: { - outDir: '../dist/my-app', - emptyOutDir: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, - }, - - - - });" `; exports[`app --style none should exclude styles 1`] = ` @@ -180,104 +170,84 @@ module.exports = { `; exports[`app --style none should not break if bundler is vite 1`] = ` +"/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +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, + cacheDir: '../node_modules/.vite/my-app', + server:{ + port: 4200, + host: 'localhost', + }, + preview:{ + port: 4300, + host: 'localhost', + }, + plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../dist/my-app', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, +}); " - /// - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - 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, - cacheDir: '../node_modules/.vite/my-app', - - server:{ - port: 4200, - host: 'localhost', - }, - - preview:{ - port: 4300, - host: 'localhost', - }, - - plugins: [react(), -nxViteTsPaths(), -nxCopyAssetsPlugin(['*.md'])], - - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, - - build: { - outDir: '../dist/my-app', - emptyOutDir: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, - }, - - - - });" `; exports[`app not nested should add vite types to tsconfigs 1`] = ` -" - /// - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - 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, - cacheDir: '../node_modules/.vite/my-app', - - server:{ - port: 4200, - host: 'localhost', +"/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +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, + cacheDir: '../node_modules/.vite/my-app', + server:{ + port: 4200, + host: 'localhost', + }, + preview:{ + port: 4300, + host: 'localhost', + }, + plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../dist/my-app', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, }, - - preview:{ - port: 4300, - host: 'localhost', - }, - - plugins: [react(), -nxViteTsPaths(), -nxCopyAssetsPlugin(['*.md'])], - - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, - - build: { - outDir: '../dist/my-app', - emptyOutDir: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, - }, - - - test: { + }, + test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/my-app', provider: 'v8', } }, - });" +}); +" `; exports[`app not nested should generate files 1`] = ` @@ -299,59 +269,49 @@ export default App; `; exports[`app not nested should use preview vite types to tsconfigs 1`] = ` -" - /// - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - 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, - cacheDir: '../node_modules/.vite/my-app', - - server:{ - port: 4200, - host: 'localhost', +"/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +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, + cacheDir: '../node_modules/.vite/my-app', + server:{ + port: 4200, + host: 'localhost', + }, + preview:{ + port: 4300, + host: 'localhost', + }, + plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../dist/my-app', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, }, - - preview:{ - port: 4300, - host: 'localhost', - }, - - plugins: [react(), -nxViteTsPaths(), -nxCopyAssetsPlugin(['*.md'])], - - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, - - build: { - outDir: '../dist/my-app', - emptyOutDir: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, - }, - - - test: { + }, + test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/my-app', provider: 'v8', } }, - });" +}); +" `; exports[`app setup React app with --bundler=vite should setup targets with vite configuration 1`] = `null`; @@ -425,48 +385,38 @@ export default App; `; exports[`app should setup vite if bundler is vite 1`] = ` +"/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +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, + cacheDir: '../node_modules/.vite/my-app', + server:{ + port: 4200, + host: 'localhost', + }, + preview:{ + port: 4300, + host: 'localhost', + }, + plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../dist/my-app', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, +}); " - /// - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - 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, - cacheDir: '../node_modules/.vite/my-app', - - server:{ - port: 4200, - host: 'localhost', - }, - - preview:{ - port: 4300, - host: 'localhost', - }, - - plugins: [react(), -nxViteTsPaths(), -nxCopyAssetsPlugin(['*.md'])], - - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, - - build: { - outDir: '../dist/my-app', - emptyOutDir: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, - }, - - - - });" `; exports[`app should setup webpack 1`] = ` diff --git a/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap index 320f72a978..d96e4b37d8 100644 --- a/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap @@ -3,100 +3,81 @@ exports[`lib --bundler none, unit test runner vitest should configure vite 1`] = ` " import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - 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, - cacheDir: '../node_modules/.vite/my-lib', - - - - plugins: [react(), -nxViteTsPaths(), -nxCopyAssetsPlugin(['*.md']), -], - - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, - - - test: { - 'watch': false, -'globals': true, -'environment': "jsdom", -'include': ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], -'reporters': ["default"], -'coverage': {"reportsDirectory":"../coverage/my-lib","provider":"v8"}, +import react from '@vitejs/plugin-react'; +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, + cacheDir: '../node_modules/.vite/my-lib', + plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), ], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + test: { + 'watch': false, + 'globals': true, + 'environment': "jsdom", + 'include': ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], + 'reporters': ["default"], + 'coverage': {"reportsDirectory":"../coverage/my-lib","provider":"v8"}, + }, +}); +" `; exports[`lib should add vite types to tsconfigs 1`] = ` -" - /// - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; +"/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; import dts from 'vite-plugin-dts'; import * as path from 'path'; - 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, - cacheDir: '../node_modules/.vite/my-lib', - - - - plugins: [react(), -nxViteTsPaths(), -nxCopyAssetsPlugin(['*.md']), -dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })], - - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, - - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode - build: { - outDir: '../dist/my-lib', - emptyOutDir: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: 'src/index.ts', - name: 'my-lib', - fileName: 'index', - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. - formats: ['es', 'cjs'] - }, - rollupOptions: { - // External packages that should not be bundled into your library. - external: ['react','react-dom','react/jsx-runtime'] - }, - }, - - test: { +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, + cacheDir: '../node_modules/.vite/my-lib', + plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + outDir: '../dist/my-lib', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'my-lib', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ['react','react-dom','react/jsx-runtime'] + }, + }, + test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/my-lib', provider: 'v8', } }, - });" +}); +" `; diff --git a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap index 3d1c92a463..a4d9d5c2d8 100644 --- a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap +++ b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap @@ -379,21 +379,17 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../node_modules/.vite/test', - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - test: { setupFiles: ['test-setup.ts'], watch: false, globals: true, environment: 'jsdom', include: ['./tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/test', @@ -702,21 +698,17 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: './node_modules/.vite/test', - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - test: { setupFiles: ['test-setup.ts'], watch: false, globals: true, environment: 'jsdom', include: ['./tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: './coverage/test', diff --git a/packages/remix/src/generators/library/__snapshots__/library.impl.spec.ts.snap b/packages/remix/src/generators/library/__snapshots__/library.impl.spec.ts.snap index d2e9beadc8..b133758e06 100644 --- a/packages/remix/src/generators/library/__snapshots__/library.impl.spec.ts.snap +++ b/packages/remix/src/generators/library/__snapshots__/library.impl.spec.ts.snap @@ -31,14 +31,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../node_modules/.vite/test', - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - test: { setupFiles: ['./src/test-setup.ts'], watch: false, diff --git a/packages/remix/src/generators/storybook-configuration/__snapshots__/storybook-configuration.impl.spec.ts.snap b/packages/remix/src/generators/storybook-configuration/__snapshots__/storybook-configuration.impl.spec.ts.snap index ef0abba62a..f401250a1b 100644 --- a/packages/remix/src/generators/storybook-configuration/__snapshots__/storybook-configuration.impl.spec.ts.snap +++ b/packages/remix/src/generators/storybook-configuration/__snapshots__/storybook-configuration.impl.spec.ts.snap @@ -95,14 +95,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/libs/storybook-test', - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - test: { setupFiles: ['./src/test-setup.ts'], watch: false, diff --git a/packages/rollup/src/generators/configuration/configuration.ts b/packages/rollup/src/generators/configuration/configuration.ts index f12e0c584b..a2944ba611 100644 --- a/packages/rollup/src/generators/configuration/configuration.ts +++ b/packages/rollup/src/generators/configuration/configuration.ts @@ -12,6 +12,7 @@ import { 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'; @@ -56,13 +57,16 @@ export async function configurationGenerator( } function createRollupConfig(tree: Tree, options: RollupProjectSchema) { + const isUsingTsPlugin = isUsingTsSolutionSetup(tree); const project = readProjectConfiguration(tree, options.project); const buildOptions: RollupWithNxPluginOptions = { - outputPath: joinPathFragments( - offsetFromRoot(project.root), - 'dist', - project.root === '.' ? project.name : project.root - ), + outputPath: isUsingTsPlugin + ? './dist' + : joinPathFragments( + offsetFromRoot(project.root), + 'dist', + project.root === '.' ? project.name : project.root + ), compiler: options.compiler ?? 'babel', main: options.main ?? './src/index.ts', tsConfig: options.tsConfig ?? './tsconfig.lib.json', @@ -70,21 +74,24 @@ function createRollupConfig(tree: Tree, options: RollupProjectSchema) { tree.write( joinPathFragments(project.root, 'rollup.config.js'), - stripIndents` - const { withNx } = require('@nx/rollup/with-nx'); - - module.exports = withNx({ - main: '${buildOptions.main}', - outputPath: '${buildOptions.outputPath}', - tsConfig: '${buildOptions.tsConfig}', - compiler: '${buildOptions.compiler}', - format: ${JSON.stringify(options.format ?? ['esm'])}, - assets:[{ input: '.', output: '.', glob:'*.md'}], - }, { - // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options - // e.g. - // output: { sourcemap: true }, - });` + `const { withNx } = require('@nx/rollup/with-nx'); + +module.exports = withNx( + { + main: '${buildOptions.main}', + outputPath: '${buildOptions.outputPath}', + tsConfig: '${buildOptions.tsConfig}', + compiler: '${buildOptions.compiler}', + format: ${JSON.stringify(options.format ?? ['esm'])}, + assets: [{ input: '.', output: '.', glob:'*.md' }], + }, + { + // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options + // e.g. + // output: { sourcemap: true }, + } +); +` ); } 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 a04c5722d4..8d3c79c24f 100644 --- a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap +++ b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap @@ -11,7 +11,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../node_modules/.vite/my-lib', - plugins: [ nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), @@ -20,12 +19,10 @@ export default defineConfig({ tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), }), ], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - // Configuration for building your library. // See: https://vitejs.dev/guide/build.html#library-mode build: { @@ -49,13 +46,11 @@ export default defineConfig({ external: [], }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/my-lib', @@ -184,7 +179,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest', - plugins: [ react(), nxViteTsPaths(), @@ -194,12 +188,10 @@ export default defineConfig({ tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), }), ], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - // Configuration for building your library. // See: https://vitejs.dev/guide/build.html#library-mode build: { @@ -239,7 +231,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest', - plugins: [ react(), nxViteTsPaths(), @@ -249,12 +240,10 @@ export default defineConfig({ tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), }), ], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - // Configuration for building your library. // See: https://vitejs.dev/guide/build.html#library-mode build: { @@ -278,13 +267,11 @@ export default defineConfig({ external: ['react', 'react-dom', 'react/jsx-runtime'], }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../../coverage/libs/react-lib-nonb-jest', @@ -361,24 +348,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/apps/my-test-react-app', - server: { port: 4200, host: 'localhost', }, - preview: { port: 4300, host: 'localhost', }, - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - build: { outDir: '../../dist/apps/my-test-react-app', emptyOutDir: true, @@ -420,24 +402,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/apps/my-test-web-app', - server: { port: 4200, host: 'localhost', }, - preview: { port: 4300, host: 'localhost', }, - plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - build: { outDir: '../../dist/apps/my-test-web-app', emptyOutDir: true, @@ -479,24 +456,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/apps/my-test-react-app', - server: { port: 4200, host: 'localhost', }, - preview: { port: 4300, host: 'localhost', }, - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - build: { outDir: '../../dist/apps/my-test-react-app', emptyOutDir: true, @@ -505,13 +477,11 @@ export default defineConfig({ transformMixedEsModules: true, }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../../coverage/apps/my-test-react-app', diff --git a/packages/vite/src/generators/configuration/configuration.spec.ts b/packages/vite/src/generators/configuration/configuration.spec.ts index 6b620c9363..e00fa9f28c 100644 --- a/packages/vite/src/generators/configuration/configuration.spec.ts +++ b/packages/vite/src/generators/configuration/configuration.spec.ts @@ -15,7 +15,7 @@ import { } from '../../utils/test-utils'; import { libraryGenerator as jsLibraryGenerator } from '@nx/js/src/generators/library/library'; -import { LibraryGeneratorSchema } from '@nx/js/src/utils/schema'; +import { LibraryGeneratorSchema } from '@nx/js/src/generators/library/schema'; describe('@nx/vite:configuration', () => { let tree: Tree; @@ -273,7 +273,7 @@ describe('@nx/vite:configuration', () => { ...defaultOptions, name: 'my-lib', bundler: 'vite', - unitTestRunner: undefined, + unitTestRunner: 'vitest', projectNameAndRootFormat: 'as-provided', }); diff --git a/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap b/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap index 2e2dd2b907..c3059ffe49 100644 --- a/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap +++ b/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap @@ -10,14 +10,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/apps/my-test-react-app', - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - define: { 'import.meta.vitest': undefined, }, @@ -52,20 +49,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/apps/my-test-react-app', - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../../coverage/apps/my-test-react-app', @@ -86,20 +79,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest', - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../../coverage/libs/react-lib-nonb-jest', diff --git a/packages/vite/src/utils/__snapshots__/vite-config-edit-utils.spec.ts.snap b/packages/vite/src/utils/__snapshots__/vite-config-edit-utils.spec.ts.snap index 5470068a52..3779a195fa 100644 --- a/packages/vite/src/utils/__snapshots__/vite-config-edit-utils.spec.ts.snap +++ b/packages/vite/src/utils/__snapshots__/vite-config-edit-utils.spec.ts.snap @@ -1,153 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ensureViteConfigIsCorrect should add build and test options if defineConfig is empty 1`] = ` -"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit' - - /// - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; - - export default defineConfig({ - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode - plugins: [react(), -nxViteTsPaths()],build: { - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: 'src/index.ts', - name: 'my-app', - fileName: 'index', - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. - formats: ['es', 'cjs'] - }, - rollupOptions: { - // External packages that should not be bundled into your library. - external: ['react', 'react-dom', 'react/jsx-runtime'] - } - },test: { - globals: true, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - },}); - " -`; - -exports[`ensureViteConfigIsCorrect should add build option but not update test option if test already setup 1`] = ` -"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit' -import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; - - export default defineConfig({ - - - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode - build: { - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: 'src/index.ts', - name: 'my-app', - fileName: 'index', - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. - formats: ['es', 'cjs'] - }, - rollupOptions: { - // External packages that should not be bundled into your library. - external: ['react', 'react-dom', 'react/jsx-runtime'] - } - },cacheDir: '../../node_modules/.vitest', - plugins: [react(), -nxViteTsPaths(), -], - - test: { - globals: true, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - }, - - }); - " -`; - -exports[`ensureViteConfigIsCorrect should add build options if build options don't exist 1`] = ` -"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit' -import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; - - export default defineConfig({ - - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode - build: { - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: 'src/index.ts', - name: 'my-app', - fileName: 'index', - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. - formats: ['es', 'cjs'] - }, - rollupOptions: { - // External packages that should not be bundled into your library. - external: ['react', 'react-dom', 'react/jsx-runtime'] - } - },cacheDir: '../../node_modules/.vitest', - plugins: [react(), -nxViteTsPaths(), -], - - test: { - globals: true, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - }, - - }); - " -`; - -exports[`ensureViteConfigIsCorrect should add build options if defineConfig is not used 1`] = ` -"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit' -import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; - - export default { - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode - build: { - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: 'src/index.ts', - name: 'my-app', - fileName: 'index', - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. - formats: ['es', 'cjs'] - }, - rollupOptions: { - // External packages that should not be bundled into your library. - external: ['react', 'react-dom', 'react/jsx-runtime'] - } - },test: { - globals: true, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - }, - plugins: [react(), -nxViteTsPaths(), -], - }; - " -`; - exports[`ensureViteConfigIsCorrect should add build options if it is using conditional config - do nothing for test 1`] = ` " /// @@ -171,105 +23,4 @@ exports[`ensureViteConfigIsCorrect should add build options if it is using condi " `; -exports[`ensureViteConfigIsCorrect should add new build options if some build options already exist 1`] = ` -"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit' -import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; - - export default defineConfig({ - cacheDir: '../../node_modules/.vitest', - plugins: [react(), -nxViteTsPaths(), -], - - test: { - globals: true, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - }, - - build: { - 'my': 'option', -'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]}, -'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]}, - - } - - }); - " -`; - exports[`ensureViteConfigIsCorrect should not do anything if cannot understand syntax of vite config 1`] = `"console.log('Unknown syntax')"`; - -exports[`ensureViteConfigIsCorrect should not do anything if project has everything setup already 1`] = ` -" -import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; - import dts from 'vite-plugin-dts'; - import { joinPathFragments } from '@nx/devkit'; - - export default defineConfig({ - cacheDir: '../../node_modules/.vitest', - plugins: [dts({ entryRoot: 'src', tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true }), -react(), -nxViteTsPaths(), -], - - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode - build: { - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: 'src/index.ts', - name: 'pure-libs-react-vite', - fileName: 'index', - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. - formats: ['es', 'cjs'], - }, - rollupOptions: { - // External packages that should not be bundled into your library. - external: ['react', 'react-dom', 'react/jsx-runtime'], - }, - }, - - test: { - globals: true, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - }, - }); - " -`; - -exports[`ensureViteConfigIsCorrect should update both test and build options - keep existing settings 1`] = ` -"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit' -import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; - - export default defineConfig({ - plugins: [react(), -nxViteTsPaths(), -], - - test: { - 'my': 'option', -'globals': true, -'environment': "jsdom", -'include': ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], - - }, - - build: { - 'my': 'option', -'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]}, -'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]}, - - } - - }); - " -`; diff --git a/packages/vite/src/utils/generator-utils.spec.ts b/packages/vite/src/utils/generator-utils.spec.ts index 832119ca00..561ee6a0c0 100644 --- a/packages/vite/src/utils/generator-utils.spec.ts +++ b/packages/vite/src/utils/generator-utils.spec.ts @@ -1,4 +1,5 @@ import { + addProjectConfiguration, readProjectConfiguration, Tree, updateProjectConfiguration, @@ -7,12 +8,14 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { findExistingJsBuildTargetInProject, getViteConfigPathForProject, + createOrEditViteConfig, } from './generator-utils'; import { mockReactAppGenerator, mockViteReactAppGenerator, mockAngularAppGenerator, } from './test-utils'; + describe('generator utils', () => { let tree: Tree; @@ -114,4 +117,135 @@ describe('generator utils', () => { }); }); }); + + describe('createOrEditViteConfig', () => { + it('should generate formatted config', () => { + addProjectConfiguration(tree, 'myproj', { + name: 'myproj', + root: 'myproj', + }); + createOrEditViteConfig( + tree, + { + project: 'myproj', + inSourceTests: true, + includeVitest: true, + includeLib: true, + }, + false + ); + + expect(tree.read('myproj/vite.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "/// + import { defineConfig } from 'vite'; + import dts from 'vite-plugin-dts'; + import * as path from 'path'; + 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, + cacheDir: '../node_modules/.vite/myproj', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + outDir: '../dist/myproj', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'myproj', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: [] + }, + }, + define: { + 'import.meta.vitest': undefined + }, + test: { + watch: false, + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../coverage/myproj', + provider: 'v8', + } + }, + }); + " + `); + }); + + it('should generate formatted config without library and in-source tests', () => { + addProjectConfiguration(tree, 'myproj', { + name: 'myproj', + root: 'myproj', + }); + createOrEditViteConfig( + tree, + { + project: 'myproj', + inSourceTests: false, + includeVitest: false, + includeLib: false, + }, + false + ); + + expect(tree.read('myproj/vite.config.ts', 'utf-8')) + .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'; + + export default defineConfig({ + root: __dirname, + cacheDir: '../node_modules/.vite/myproj', + server:{ + port: 4200, + host: 'localhost', + }, + preview:{ + port: 4300, + host: 'localhost', + }, + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../dist/myproj', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, + }); + " + `); + }); + }); }); diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts index 997ec6bcb1..8e76ac4009 100644 --- a/packages/vite/src/utils/generator-utils.ts +++ b/packages/vite/src/utils/generator-utils.ts @@ -9,12 +9,13 @@ import { updateProjectConfiguration, writeJson, } from '@nx/devkit'; +import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { ViteBuildExecutorOptions } from '../executors/build/schema'; import { VitePreviewServerExecutorOptions } from '../executors/preview-server/schema'; import { VitestExecutorOptions } from '../executors/test/schema'; import { ViteConfigurationGeneratorSchema } from '../generators/configuration/schema'; import { ensureViteConfigIsCorrect } from './vite-config-edit-utils'; -import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; export type Target = 'build' | 'serve' | 'test' | 'preview'; export type TargetFlags = Partial>; @@ -362,48 +363,47 @@ export function createOrEditViteConfig( ? `${projectRoot}/vitest.config.ts` : `${projectRoot}/vite.config.ts`; - const buildOutDir = - projectRoot === '.' - ? `./dist/${options.project}` - : `${offsetFromRoot(projectRoot)}dist/${projectRoot}`; + const isUsingTsPlugin = isUsingTsSolutionSetup(tree); + const buildOutDir = isUsingTsPlugin + ? './dist' + : projectRoot === '.' + ? `./dist/${options.project}` + : `${offsetFromRoot(projectRoot)}dist/${projectRoot}`; const buildOption = onlyVitest ? '' : options.includeLib - ? ` - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode - build: { - outDir: '${buildOutDir}', - emptyOutDir: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: 'src/index.ts', - name: '${options.project}', - fileName: 'index', - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. - formats: ['es', 'cjs'] - }, - rollupOptions: { - // External packages that should not be bundled into your library. - external: [${options.rollupOptionsExternal ?? ''}] - }, - },` - : ` - build: { - outDir: '${buildOutDir}', - emptyOutDir: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, + ? ` // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + outDir: '${buildOutDir}', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, }, - `; + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: '${options.project}', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: [${options.rollupOptionsExternal ?? ''}] + }, + },` + : ` build: { + outDir: '${buildOutDir}', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + },`; const imports: string[] = options.imports ? options.imports : []; @@ -432,14 +432,14 @@ export function createOrEditViteConfig( : `${offsetFromRoot(projectRoot)}coverage/${projectRoot}`; const testOption = options.includeVitest - ? `test: { + ? ` test: { watch: false, globals: true, environment: '${options.testEnvironment ?? 'jsdom'}', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - ${ + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],${ options.inSourceTests - ? `includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],` + ? ` + includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],` : '' } reporters: ['default'], @@ -453,7 +453,7 @@ export function createOrEditViteConfig( : ''; const defineOption = options.inSourceTests - ? `define: { + ? ` define: { 'import.meta.vitest': undefined },` : ''; @@ -462,27 +462,24 @@ export function createOrEditViteConfig( ? '' : options.includeLib ? '' - : ` - server:{ - port: 4200, - host: 'localhost', - },`; + : ` server:{ + port: 4200, + host: 'localhost', + },`; const previewServerOption = onlyVitest ? '' : options.includeLib ? '' - : ` - preview:{ - port: 4300, - host: 'localhost', - },`; + : ` preview:{ + port: 4300, + host: 'localhost', + },`; - const workerOption = ` - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // },`; + const workerOption = ` // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // },`; const cacheDir = `cacheDir: '${normalizedJoinPaths( offsetFromRoot(projectRoot), @@ -510,29 +507,34 @@ export function createOrEditViteConfig( return; } - 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, - ${cacheDir} - ${devServerOption} - ${previewServerOption} - - plugins: [${plugins.join(',\n')}], - ${workerOption} - ${buildOption} - ${defineOption} - ${testOption} - });`; + 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, + ${printOptions( + cacheDir, + devServerOption, + previewServerOption, + ` plugins: [${plugins.join(', ')}],`, + workerOption, + buildOption, + defineOption, + testOption + )} +}); +`.replace(/\s+(?=(\n|$))/gm, '\n'); tree.write(viteConfigPath, viteConfigContent); } +function printOptions(...options: string[]): string { + return options.filter(Boolean).join('\n'); +} + export function normalizeViteConfigFilePathWithTree( tree: Tree, projectRoot: string, diff --git a/packages/vite/src/utils/vite-config-edit-utils.spec.ts b/packages/vite/src/utils/vite-config-edit-utils.spec.ts index 1057cabeca..2529eae2dd 100644 --- a/packages/vite/src/utils/vite-config-edit-utils.spec.ts +++ b/packages/vite/src/utils/vite-config-edit-utils.spec.ts @@ -47,7 +47,43 @@ describe('ensureViteConfigIsCorrect', () => { 'PropertyAssignment:has(Identifier[name="build"])' ); expect(buildNode).toBeDefined(); - expect(appFileContent).toMatchSnapshot(); + expect(appFileContent).toMatchInlineSnapshot(` + "import dts from 'vite-plugin-dts'; + import { joinPathFragments } from '@nx/devkit' + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + export default defineConfig({ + + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'my-app', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ['react', 'react-dom', 'react/jsx-runtime'] + } + },cacheDir: '../../node_modules/.vitest', + plugins: [react(), nxViteTsPaths(), ], + + test: { + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + + }); + " + `); }); it('should add new build options if some build options already exist', () => { @@ -71,7 +107,32 @@ describe('ensureViteConfigIsCorrect', () => { 'PropertyAssignment:has(Identifier[name="build"])' ); expect(buildNode).toBeDefined(); - expect(appFileContent).toMatchSnapshot(); + expect(appFileContent).toMatchInlineSnapshot(` + "import dts from 'vite-plugin-dts'; + import { joinPathFragments } from '@nx/devkit' + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + export default defineConfig({ + cacheDir: '../../node_modules/.vitest', + plugins: [react(), nxViteTsPaths(), ], + + test: { + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + + build: { + 'my': 'option', + 'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]}, + 'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]}, + } + + }); + " + `); }); it('should add build and test options if defineConfig is empty', () => { @@ -95,7 +156,39 @@ describe('ensureViteConfigIsCorrect', () => { 'PropertyAssignment:has(Identifier[name="build"])' ); expect(buildNode).toBeDefined(); - expect(appFileContent).toMatchSnapshot(); + expect(appFileContent).toMatchInlineSnapshot(` + "import dts from 'vite-plugin-dts'; + import { joinPathFragments } from '@nx/devkit' + + /// + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + export default defineConfig({ + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + plugins: [react(), nxViteTsPaths()],build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'my-app', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ['react', 'react-dom', 'react/jsx-runtime'] + } + },test: { + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + },}); + " + `); }); it('should add build options if it is using conditional config - do nothing for test', () => { @@ -143,7 +236,39 @@ describe('ensureViteConfigIsCorrect', () => { 'PropertyAssignment:has(Identifier[name="build"])' ); expect(buildNode).toBeDefined(); - expect(appFileContent).toMatchSnapshot(); + expect(appFileContent).toMatchInlineSnapshot(` + "import dts from 'vite-plugin-dts'; + import { joinPathFragments } from '@nx/devkit' + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + export default { + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'my-app', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ['react', 'react-dom', 'react/jsx-runtime'] + } + },test: { + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + plugins: [react(), nxViteTsPaths(), ], + }; + " + `); }); it('should not do anything if cannot understand syntax of vite config', () => { @@ -179,7 +304,44 @@ describe('ensureViteConfigIsCorrect', () => { { build: true, test: true, serve: true } ); const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); - expect(appFileContent).toMatchSnapshot(); + expect(appFileContent).toMatchInlineSnapshot(` + " + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + import dts from 'vite-plugin-dts'; + import { joinPathFragments } from '@nx/devkit'; + + export default defineConfig({ + cacheDir: '../../node_modules/.vitest', + plugins: [dts({ entryRoot: 'src', tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true }), react(), nxViteTsPaths(), ], + + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'pure-libs-react-vite', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es', 'cjs'], + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ['react', 'react-dom', 'react/jsx-runtime'], + }, + }, + + test: { + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + }); + " + `); }); it('should add build option but not update test option if test already setup', () => { @@ -197,7 +359,44 @@ describe('ensureViteConfigIsCorrect', () => { { build: false, test: true, serve: true } ); const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); - expect(appFileContent).toMatchSnapshot(); + expect(appFileContent).toMatchInlineSnapshot(` + "import dts from 'vite-plugin-dts'; + import { joinPathFragments } from '@nx/devkit' + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + export default defineConfig({ + + + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'my-app', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ['react', 'react-dom', 'react/jsx-runtime'] + } + },cacheDir: '../../node_modules/.vitest', + plugins: [react(), nxViteTsPaths(), ], + + test: { + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + + }); + " + `); }); it('should update both test and build options - keep existing settings', () => { @@ -215,6 +414,31 @@ describe('ensureViteConfigIsCorrect', () => { { build: false, test: false, serve: true } ); const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); - expect(appFileContent).toMatchSnapshot(); + expect(appFileContent).toMatchInlineSnapshot(` + "import dts from 'vite-plugin-dts'; + import { joinPathFragments } from '@nx/devkit' + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + export default defineConfig({ + plugins: [react(), nxViteTsPaths(), ], + + test: { + 'my': 'option', + 'globals': true, + 'environment': "jsdom", + 'include': ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], + }, + + build: { + 'my': 'option', + 'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]}, + 'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]}, + } + + }); + " + `); }); }); diff --git a/packages/vite/src/utils/vite-config-edit-utils.ts b/packages/vite/src/utils/vite-config-edit-utils.ts index 9434918fb0..8aeb413f74 100644 --- a/packages/vite/src/utils/vite-config-edit-utils.ts +++ b/packages/vite/src/utils/vite-config-edit-utils.ts @@ -91,19 +91,20 @@ function handleBuildOrTestNode( propName !== 'reportsDirectory' && propName !== 'provider' ) { - updatedPropsString += `'${propName}': ${prop.initializer.getText()},\n`; + // NOTE: Watch for formatting. + updatedPropsString += ` '${propName}': ${prop.initializer.getText()},\n`; } } for (const [propName, propValue] of Object.entries( configContentObject )) { - updatedPropsString += `'${propName}': ${JSON.stringify( + // NOTE: Watch for formatting. + updatedPropsString += ` '${propName}': ${JSON.stringify( propValue )},\n`; } return `${name}: { - ${updatedPropsString} - }`; +${updatedPropsString} }`; } ); } else { @@ -324,7 +325,7 @@ function handlePluginNode( const existingPluginNodes = found?.[0].elements ?? []; for (const plugin of existingPluginNodes) { - updatedPluginsString += `${plugin.getText()},\n`; + updatedPluginsString += `${plugin.getText()}, `; } for (const plugin of plugins) { @@ -333,7 +334,7 @@ function handlePluginNode( node.getText().includes(plugin) ) ) { - updatedPluginsString += `${plugin},\n`; + updatedPluginsString += `${plugin}, `; } } @@ -371,7 +372,7 @@ function handlePluginNode( { type: ChangeType.Insert, index: propertyAssignments[0].getStart(), - text: `plugins: [${plugins.join(',\n')}],`, + text: `plugins: [${plugins.join(', ')}],`, }, ]); writeFile = true; @@ -380,7 +381,7 @@ function handlePluginNode( { type: ChangeType.Insert, index: foundDefineConfig[0].getStart() + 14, - text: `plugins: [${plugins.join(',\n')}],`, + text: `plugins: [${plugins.join(', ')}],`, }, ]); writeFile = true; @@ -400,7 +401,7 @@ function handlePluginNode( { type: ChangeType.Insert, index: startOfObject + 1, - text: `plugins: [${plugins.join(',\n')}],`, + text: `plugins: [${plugins.join(', ')}],`, }, ]); writeFile = true; @@ -411,7 +412,7 @@ function handlePluginNode( } if (writeFile) { const filteredImports = filterImport(appFileContent, imports); - return filteredImports.join(';') + '\n' + appFileContent; + return filteredImports.join(';\n') + '\n' + appFileContent; } } diff --git a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap index 5b6f70e7cc..08a063853b 100644 --- a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap @@ -49,24 +49,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../node_modules/.vite/test', - server: { port: 4200, host: 'localhost', }, - preview: { port: 4300, host: 'localhost', }, - plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - build: { outDir: '../dist/test', emptyOutDir: true, @@ -75,13 +70,11 @@ export default defineConfig({ transformMixedEsModules: true, }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/test', @@ -202,24 +195,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../node_modules/.vite/test', - server: { port: 4200, host: 'localhost', }, - preview: { port: 4300, host: 'localhost', }, - plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - build: { outDir: '../dist/test', emptyOutDir: true, @@ -228,13 +216,11 @@ export default defineConfig({ transformMixedEsModules: true, }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/test', diff --git a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap index 73fd9b8405..3f3e8176a1 100644 --- a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap @@ -12,7 +12,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../node_modules/.vite/my-lib', - plugins: [ vue(), nxViteTsPaths(), @@ -22,12 +21,10 @@ export default defineConfig({ tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), }), ], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - // Configuration for building your library. // See: https://vitejs.dev/guide/build.html#library-mode build: { @@ -51,13 +48,11 @@ export default defineConfig({ external: [], }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/my-lib', @@ -108,7 +103,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../node_modules/.vite/my-lib', - plugins: [ vue(), nxViteTsPaths(), @@ -118,12 +112,10 @@ export default defineConfig({ tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), }), ], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - // Configuration for building your library. // See: https://vitejs.dev/guide/build.html#library-mode build: { @@ -147,13 +139,11 @@ export default defineConfig({ external: [], }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../coverage/my-lib', diff --git a/packages/workspace/src/generators/new/__snapshots__/generate-workspace-files.spec.ts.snap b/packages/workspace/src/generators/new/__snapshots__/generate-workspace-files.spec.ts.snap index ab39b915b5..1f9800bab8 100644 --- a/packages/workspace/src/generators/new/__snapshots__/generate-workspace-files.spec.ts.snap +++ b/packages/workspace/src/generators/new/__snapshots__/generate-workspace-files.spec.ts.snap @@ -1527,20 +1527,26 @@ exports[`@nx/workspace:generateWorkspaceFiles README.md Nx Cloud (github) should [Click here to finish setting up your workspace!](https://test.nx.app/connect?source=readme&token=TEST_NX_CLOUD_TOKEN) +## Generate a library + +\`\`\`sh +npx nx g @nx/js:lib packages/pkg1 --publishable --importPath=@my-org/pkg1 +\`\`\` + ## Run tasks -To run tasks with Nx use: +To build the library use: + +\`\`\`sh +npx nx build pkg1 +\`\`\` + +To run any task with Nx use: \`\`\`sh npx nx \`\`\` -For example: - -\`\`\`sh -npx nx build myproject -\`\`\` - These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the \`project.json\` or \`package.json\` files. [More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) @@ -1557,30 +1563,6 @@ Pass \`--dry-run\` to see what would happen without actually releasing the libra [Learn more about Nx release »](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -## Add new projects - -While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature. - -To install a new plugin you can use the \`nx add\` command. Here's an example of adding the React plugin: - -\`\`\`sh -npx nx add @nx/react -\`\`\` - -Use the plugin's generator to create new projects. For example, to create a new React app or library: - -\`\`\`sh -# Genenerate an app -npx nx g @nx/react:app demo - -# Generate a library -npx nx g @nx/react:lib some-lib -\`\`\` - -You can use \`npx nx list\` to get a list of installed plugins. Then, run \`npx nx list \` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE. - -[Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) - [Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) ## Install Nx Console @@ -3814,20 +3796,26 @@ exports[`@nx/workspace:generateWorkspaceFiles README.md Nx Cloud (skip) should b [Learn more about this workspace setup and its capabilities](https://nx.dev/nx-api/js?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or run \`npx nx graph\` to visually explore what was created. Now, let's get you up to speed! +## Generate a library + +\`\`\`sh +npx nx g @nx/js:lib packages/pkg1 --publishable --importPath=@my-org/pkg1 +\`\`\` + ## Run tasks -To run tasks with Nx use: +To build the library use: + +\`\`\`sh +npx nx build pkg1 +\`\`\` + +To run any task with Nx use: \`\`\`sh npx nx \`\`\` -For example: - -\`\`\`sh -npx nx build myproject -\`\`\` - These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the \`project.json\` or \`package.json\` files. [More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) @@ -3844,30 +3832,6 @@ Pass \`--dry-run\` to see what would happen without actually releasing the libra [Learn more about Nx release »](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -## Add new projects - -While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature. - -To install a new plugin you can use the \`nx add\` command. Here's an example of adding the React plugin: - -\`\`\`sh -npx nx add @nx/react -\`\`\` - -Use the plugin's generator to create new projects. For example, to create a new React app or library: - -\`\`\`sh -# Genenerate an app -npx nx g @nx/react:app demo - -# Generate a library -npx nx g @nx/react:lib some-lib -\`\`\` - -You can use \`npx nx list\` to get a list of installed plugins. Then, run \`npx nx list \` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE. - -[Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) - ## Set up CI! ### Step 1 @@ -5836,20 +5800,26 @@ exports[`@nx/workspace:generateWorkspaceFiles README.md Nx Cloud (yes) should be [Click here to finish setting up your workspace!](https://test.nx.app/connect?source=readme&token=TEST_NX_CLOUD_TOKEN) +## Generate a library + +\`\`\`sh +npx nx g @nx/js:lib packages/pkg1 --publishable --importPath=@my-org/pkg1 +\`\`\` + ## Run tasks -To run tasks with Nx use: +To build the library use: + +\`\`\`sh +npx nx build pkg1 +\`\`\` + +To run any task with Nx use: \`\`\`sh npx nx \`\`\` -For example: - -\`\`\`sh -npx nx build myproject -\`\`\` - These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the \`project.json\` or \`package.json\` files. [More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) @@ -5866,30 +5836,6 @@ Pass \`--dry-run\` to see what would happen without actually releasing the libra [Learn more about Nx release »](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -## Add new projects - -While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature. - -To install a new plugin you can use the \`nx add\` command. Here's an example of adding the React plugin: - -\`\`\`sh -npx nx add @nx/react -\`\`\` - -Use the plugin's generator to create new projects. For example, to create a new React app or library: - -\`\`\`sh -# Genenerate an app -npx nx g @nx/react:app demo - -# Generate a library -npx nx g @nx/react:lib some-lib -\`\`\` - -You can use \`npx nx list\` to get a list of installed plugins. Then, run \`npx nx list \` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE. - -[Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) - [Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) ## Install Nx Console diff --git a/packages/workspace/src/generators/new/files-readme/README.md.template b/packages/workspace/src/generators/new/files-readme/README.md.template index 8bc7c14f47..47ac233b33 100644 --- a/packages/workspace/src/generators/new/files-readme/README.md.template +++ b/packages/workspace/src/generators/new/files-readme/README.md.template @@ -12,15 +12,15 @@ Run `npx nx graph` to visually explore what got created. Now, let's get you up t [Click here to finish setting up your workspace!](<%= nxCloudOnboardingUrl %>) -<% } %> +<% } %><% if (!isEmptyRepo) { %> ## Run tasks -<% if (!isEmptyRepo) { %><% if (isJsStandalone) { %> +<% if (isJsStandalone) { %> To build the library use: ```sh npx nx build ``` - <% } else { %> +<% } else { %> To run the dev server for your app, use: ```sh @@ -38,7 +38,29 @@ To see all available targets to run for a project, run: ```sh npx nx show project <%= appName %> ``` - <% } %><% } else { %> +<% } %><% } else if (isTsPreset) { %> +## Generate a library + +```sh +npx nx g @nx/js:lib packages/pkg1 --publishable --importPath=@my-org/pkg1 +``` + +## Run tasks + +To build the library use: + +```sh +npx nx build pkg1 +``` + +To run any task with Nx use: + +```sh +npx nx +``` +<% } else { %> +## Run tasks + To run tasks with Nx use: ```sh @@ -66,7 +88,25 @@ npx nx release Pass `--dry-run` to see what would happen without actually releasing the library. [Learn more about Nx release »](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -<% } %><% if (!isJsStandalone) { %> +<% } %><% if (isTsPreset && isUsingNewTsSolutionSetup) { %> +## Keep TypeScript project references up to date + +Nx automatically updates TypeScript [project references](https://www.typescriptlang.org/docs/handbook/project-references.html) in `tsconfig.json` files to ensure they remain accurate based on your project dependencies (`import` or `require` statements). This sync is automatically done when running tasks such as `build` or `typecheck`, which require updated references to function correctly. + +To manually trigger the process to sync the project graph dependencies information to the TypeScript project references, run the following command: + +```sh +npx nx sync +``` + +You can enforce that the TypeScript project references are always in the correct state when running in CI by adding a step to your CI job configuration that runs the following command: + +```sh +npx nx sync:check +``` + +[Learn more about nx sync](https://nx.dev/reference/nx-commands#sync) +<% } %><% if (!isJsStandalone && !isTsPreset) { %> ## Add new projects While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature. diff --git a/packages/workspace/src/generators/new/generate-preset.ts b/packages/workspace/src/generators/new/generate-preset.ts index 731fdd0239..24d5e8e35f 100644 --- a/packages/workspace/src/generators/new/generate-preset.ts +++ b/packages/workspace/src/generators/new/generate-preset.ts @@ -84,6 +84,7 @@ export function generatePreset(host: Tree, opts: NormalizedSchema) { opts.ssr ? `--ssr` : null, opts.prefix !== undefined ? `--prefix=${opts.prefix}` : null, opts.nxCloudToken ? `--nxCloudToken=${opts.nxCloudToken}` : null, + opts.formatter ? `--formatter=${opts.formatter}` : null, ].filter((e) => !!e); } } diff --git a/packages/workspace/src/generators/new/generate-workspace-files.ts b/packages/workspace/src/generators/new/generate-workspace-files.ts index 25616f3de3..2c1d792cea 100644 --- a/packages/workspace/src/generators/new/generate-workspace-files.ts +++ b/packages/workspace/src/generators/new/generate-workspace-files.ts @@ -213,12 +213,12 @@ export async function generateWorkspaceFiles( } function setPresetProperty(tree: Tree, options: NormalizedSchema) { - updateJson(tree, join(options.directory, 'nx.json'), (json) => { - if (options.preset === Preset.NPM) { + if (options.preset === Preset.NPM) { + updateJson(tree, join(options.directory, 'nx.json'), (json) => { addPropertyWithStableKeys(json, 'extends', 'nx/presets/npm.json'); - } - return json; - }); + return json; + }); + } } function createNxJson( @@ -274,7 +274,10 @@ function createFiles(tree: Tree, options: NormalizedSchema) { options.preset === Preset.RemixStandalone || options.preset === Preset.TsStandalone ? './files-root-app' - : options.preset === Preset.NPM + : (options.preset === Preset.TS && + process.env.NX_ADD_PLUGINS !== 'false' && + process.env.NX_ADD_TS_PLUGIN === 'true') || + options.preset === Preset.NPM ? './files-package-based-repo' : './files-integrated-repo'; generateFiles(tree, join(__dirname, filesDirName), options.directory, { @@ -309,6 +312,10 @@ async function createReadme( generateFiles(tree, join(__dirname, './files-readme'), directory, { formattedNames, isJsStandalone: preset === Preset.TsStandalone, + isTsPreset: preset === Preset.TS, + isUsingNewTsSolutionSetup: + process.env.NX_ADD_PLUGINS !== 'false' && + process.env.NX_ADD_TS_PLUGIN === 'true', isEmptyRepo: !appName, appName, generateAppCmd: presetInfo.generateAppCmd, @@ -405,7 +412,12 @@ function normalizeOptions(options: NormalizedSchema) { } function setUpWorkspacesInPackageJson(tree: Tree, options: NormalizedSchema) { - if (options.preset === Preset.NPM) { + if ( + options.preset === Preset.NPM || + (options.preset === Preset.TS && + process.env.NX_ADD_PLUGINS !== 'false' && + process.env.NX_ADD_TS_PLUGIN === 'true') + ) { if (options.packageManager === 'pnpm') { tree.write( join(options.directory, 'pnpm-workspace.yaml'), diff --git a/packages/workspace/src/generators/new/new.ts b/packages/workspace/src/generators/new/new.ts index 706175c86a..4e4d027c1c 100644 --- a/packages/workspace/src/generators/new/new.ts +++ b/packages/workspace/src/generators/new/new.ts @@ -37,6 +37,7 @@ interface Schema { prefix?: string; useGitHub?: boolean; nxCloud?: 'yes' | 'skip' | 'circleci' | 'github'; + formatter?: 'none' | 'prettier'; } export interface NormalizedSchema extends Schema { diff --git a/packages/workspace/src/generators/new/schema.json b/packages/workspace/src/generators/new/schema.json index 16ba7d59b2..4f895ab3a8 100644 --- a/packages/workspace/src/generators/new/schema.json +++ b/packages/workspace/src/generators/new/schema.json @@ -86,6 +86,12 @@ "prefix": { "description": "The prefix to use for Angular component and directive selectors.", "type": "string" + }, + "formatter": { + "description": "The tool to use for code formatting.", + "type": "string", + "enum": ["none", "prettier"], + "default": "none" } }, "additionalProperties": true diff --git a/packages/workspace/src/generators/preset/__snapshots__/preset.spec.ts.snap b/packages/workspace/src/generators/preset/__snapshots__/preset.spec.ts.snap index 950da990d8..080376656a 100644 --- a/packages/workspace/src/generators/preset/__snapshots__/preset.spec.ts.snap +++ b/packages/workspace/src/generators/preset/__snapshots__/preset.spec.ts.snap @@ -84,24 +84,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: './node_modules/.vite/proj', - server: { port: 4200, host: 'localhost', }, - preview: { port: 4300, host: 'localhost', }, - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - build: { outDir: './dist/proj', emptyOutDir: true, @@ -110,13 +105,11 @@ export default defineConfig({ transformMixedEsModules: true, }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: './coverage/proj', @@ -176,24 +169,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/apps/proj', - server: { port: 4200, host: 'localhost', }, - preview: { port: 4300, host: 'localhost', }, - plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - build: { outDir: '../../dist/apps/proj', emptyOutDir: true, @@ -202,13 +190,11 @@ export default defineConfig({ transformMixedEsModules: true, }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: '../../coverage/apps/proj', @@ -229,24 +215,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; export default defineConfig({ root: __dirname, cacheDir: './node_modules/.vite/proj', - server: { port: 4200, host: 'localhost', }, - preview: { port: 4300, host: 'localhost', }, - plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. // worker: { // plugins: [ nxViteTsPaths() ], // }, - build: { outDir: './dist/proj', emptyOutDir: true, @@ -255,13 +236,11 @@ export default defineConfig({ transformMixedEsModules: true, }, }, - test: { watch: false, globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], coverage: { reportsDirectory: './coverage/proj', diff --git a/packages/workspace/src/generators/preset/preset.ts b/packages/workspace/src/generators/preset/preset.ts index b802e34751..7601daaa98 100644 --- a/packages/workspace/src/generators/preset/preset.ts +++ b/packages/workspace/src/generators/preset/preset.ts @@ -275,7 +275,12 @@ async function createPreset(tree: Tree, options: Schema) { }); } else if (options.preset === Preset.TS) { const { initGenerator } = require('@nx' + '/js'); - return initGenerator(tree, {}); + return initGenerator(tree, { + formatter: options.formatter, + addTsPlugin: + process.env.NX_ADD_PLUGINS !== 'false' && + process.env.NX_ADD_TS_PLUGIN === 'true', + }); } else if (options.preset === Preset.TsStandalone) { const { libraryGenerator } = require('@nx' + '/js'); return libraryGenerator(tree, { diff --git a/packages/workspace/src/generators/preset/schema.d.ts b/packages/workspace/src/generators/preset/schema.d.ts index ff589eb314..8c9d19f54c 100644 --- a/packages/workspace/src/generators/preset/schema.d.ts +++ b/packages/workspace/src/generators/preset/schema.d.ts @@ -6,6 +6,7 @@ export interface Schema { preset: Preset; style?: string; linter?: string; + formatter?: 'none' | 'prettier'; standaloneConfig?: boolean; framework?: string; packageManager?: PackageManager;