feat(js): generate experimental simplified library with ts solution setup (#27910)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

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

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

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

Fixes #

---------

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
This commit is contained in:
Leosvel Pérez Espinosa 2024-09-27 20:14:19 +02:00 committed by GitHub
parent 5724debed6
commit 49c5a73cd0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
80 changed files with 3002 additions and 1469 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "init", "name": "init",
"factory": "./src/generators/init/init#initGenerator", "factory": "./src/generators/init/init#initGeneratorInternal",
"schema": { "schema": {
"$schema": "https://json-schema.org/schema", "$schema": "https://json-schema.org/schema",
"$id": "NxTypescriptInit", "$id": "NxTypescriptInit",
@ -8,6 +8,12 @@
"title": "Init nx/js", "title": "Init nx/js",
"description": "Init generator placeholder for nx/js.", "description": "Init generator placeholder for nx/js.",
"properties": { "properties": {
"formatter": {
"description": "The tool to use for code formatting.",
"type": "string",
"enum": ["none", "prettier"],
"default": "none"
},
"js": { "js": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
@ -40,12 +46,6 @@
"type": "string", "type": "string",
"description": "Customize the generated base tsconfig file name.", "description": "Customize the generated base tsconfig file name.",
"x-priority": "internal" "x-priority": "internal"
},
"setUpPrettier": {
"type": "boolean",
"description": "Add Prettier and corresponding configuration files.",
"x-priority": "internal",
"default": false
} }
}, },
"presets": [] "presets": []
@ -54,7 +54,7 @@
"x-type": "init", "x-type": "init",
"description": "Initialize a TS/JS workspace.", "description": "Initialize a TS/JS workspace.",
"hidden": true, "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", "path": "/packages/js/src/generators/init/schema.json",
"type": "generator" "type": "generator"
} }

View File

@ -21,22 +21,28 @@
"description": "A directory where the lib is placed.", "description": "A directory where the lib is placed.",
"x-priority": "important" "x-priority": "important"
}, },
"projectNameAndRootFormat": { "bundler": {
"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`).", "description": "The bundler to use. Choosing 'none' means this library is not buildable.",
"type": "string", "type": "string",
"enum": ["as-provided", "derived"] "enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"],
"x-priority": "important"
}, },
"linter": { "linter": {
"description": "The tool to use for running lint checks.", "description": "The tool to use for running lint checks.",
"type": "string", "type": "string",
"enum": ["eslint", "none"], "enum": ["none", "eslint"],
"default": "eslint" "x-priority": "important"
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string",
"enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "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": { "tags": {
"type": "string", "type": "string",
@ -112,18 +118,9 @@
"compiler": { "compiler": {
"type": "string", "type": "string",
"enum": ["tsc", "swc"], "enum": ["tsc", "swc"],
"default": "tsc",
"description": "The compiler used by the build and test targets", "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)." "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": { "skipTypeCheck": {
"type": "boolean", "type": "boolean",
"description": "Whether to skip TypeScript type checking for SWC compiler.", "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.", "description": "Don't include the directory in the generated file name.",
"type": "boolean", "type": "boolean",
"default": false "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"], "required": ["name"],

View File

@ -83,6 +83,12 @@
"prefix": { "prefix": {
"description": "The prefix to use for Angular component and directive selectors.", "description": "The prefix to use for Angular component and directive selectors.",
"type": "string" "type": "string"
},
"formatter": {
"description": "The tool to use for code formatting.",
"type": "string",
"enum": ["none", "prettier"],
"default": "none"
} }
}, },
"additionalProperties": true, "additionalProperties": true,

View File

@ -33,7 +33,7 @@ describe('Linter (legacy)', () => {
env: { NX_ADD_PLUGINS: 'false' }, 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' }, env: { NX_ADD_PLUGINS: 'false' },
}); });
}); });

View File

@ -1212,34 +1212,34 @@ describe('lib', () => {
module.exports = [ module.exports = [
...baseConfig, ...baseConfig,
...nx.configs["flat/angular"], ...nx.configs["flat/angular"],
...nx.configs["flat/angular-template"], ...nx.configs["flat/angular-template"],
{ {
files: ["**/*.ts"], files: ["**/*.ts"],
rules: { rules: {
"@angular-eslint/directive-selector": [ "@angular-eslint/directive-selector": [
"error", "error",
{ {
type: "attribute", type: "attribute",
prefix: "lib", prefix: "lib",
style: "camelCase" style: "camelCase"
} }
], ],
"@angular-eslint/component-selector": [ "@angular-eslint/component-selector": [
"error", "error",
{ {
type: "element", type: "element",
prefix: "lib", prefix: "lib",
style: "kebab-case" style: "kebab-case"
} }
] ]
}
},
{
files: ["**/*.html"],
// Override or add rules here
rules: {}
} }
},
{
files: ["**/*.html"],
// Override or add rules here
rules: {}
}
]; ];
" "
`); `);

View File

@ -37,9 +37,10 @@ interface BaseArguments extends CreateWorkspaceOptions {
interface NoneArguments extends BaseArguments { interface NoneArguments extends BaseArguments {
stack: 'none'; stack: 'none';
workspaceType: 'package-based' | 'integrated' | 'standalone'; workspaceType?: 'package-based' | 'integrated' | 'standalone';
js: boolean; js?: boolean;
appName: string | undefined; appName?: string | undefined;
formatter?: 'none' | 'prettier';
} }
interface ReactArguments extends BaseArguments { interface ReactArguments extends BaseArguments {
@ -394,7 +395,11 @@ async function determineStack(
choices: [ choices: [
{ {
name: `none`, 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`, name: `react`,
@ -441,34 +446,14 @@ async function determinePresetOptions(
async function determineNoneOptions( async function determineNoneOptions(
parsedArgs: yargs.Arguments<NoneArguments> parsedArgs: yargs.Arguments<NoneArguments>
): Promise<Partial<NoneArguments>> { ): Promise<Partial<NoneArguments>> {
let preset: Preset; if (
let workspaceType: 'package-based' | 'standalone' | 'integrated' | undefined = process.env.NX_ADD_PLUGINS !== 'false' &&
undefined; process.env.NX_ADD_TS_PLUGIN === 'true'
let appName: string | undefined = undefined; ) {
let js: boolean | undefined; const reply = await enquirer.prompt<{ prettier: 'Yes' | 'No' }>([
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' }>([
{ {
name: 'ts', name: 'prettier',
message: `Would you like to use TypeScript with this project?`, message: `Would you like to use Prettier for code formatting?`,
type: 'autocomplete', type: 'autocomplete',
choices: [ choices: [
{ {
@ -478,14 +463,68 @@ async function determineNoneOptions(
name: 'No', name: 'No',
}, },
], ],
initial: 0, initial: 1,
skip: !parsedArgs.interactive || isCI(), 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( async function determineReactOptions(

View File

@ -50,41 +50,48 @@ describe('@nx/eslint:lint-project', () => {
linter: Linter.EsLint, linter: Linter.EsLint,
project: 'test-lib', project: 'test-lib',
setParserOptionsProject: false, setParserOptionsProject: false,
skipFormat: true,
}); });
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(` expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
"const nx = require('@nx/eslint-plugin'); "const nx = require("@nx/eslint-plugin");
module.exports = [ module.exports = [
...nx.configs['flat/base'], ...nx.configs["flat/base"],
...nx.configs['flat/typescript'], ...nx.configs["flat/typescript"],
...nx.configs['flat/javascript'], ...nx.configs["flat/javascript"],
{ {
ignores: ['**/dist'], ignores: ["**/dist"]
}, },
{ {
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], files: [
rules: { "**/*.ts",
'@nx/enforce-module-boundaries': [ "**/*.tsx",
'error', "**/*.js",
{ "**/*.jsx"
enforceBuildableLibDependency: true, ],
allow: ['^.*/eslint(\\\\.base)?\\\\.config\\\\.[cm]?js$'], rules: { "@nx/enforce-module-boundaries": [
depConstraints: [ "error",
{ {
sourceTag: '*', enforceBuildableLibDependency: true,
onlyDependOnLibsWithTags: ['*'], 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: {},
},
]; ];
" "
`); `);

View File

@ -106,6 +106,7 @@ export async function lintProjectGeneratorInternal(
(p) => !['./src', '{projectRoot}', projectConfig.root].includes(p) (p) => !['./src', '{projectRoot}', projectConfig.root].includes(p)
) )
) { ) {
projectConfig.targets ??= {};
projectConfig.targets['lint'] = { projectConfig.targets['lint'] = {
command: `eslint ${lintFilePatterns command: `eslint ${lintFilePatterns
.join(' ') .join(' ')
@ -113,6 +114,7 @@ export async function lintProjectGeneratorInternal(
}; };
} }
} else { } else {
projectConfig.targets ??= {};
projectConfig.targets['lint'] = { projectConfig.targets['lint'] = {
executor: '@nx/eslint:lint', executor: '@nx/eslint:lint',
}; };

View File

@ -155,7 +155,8 @@ module.exports = [
}); });
module.exports = [ module.exports = [
...compat.extends("plugin:playwright/recommend"), ...compat.extends("plugin:playwright/recommend"),
...baseConfig, ...baseConfig,
{ {
files: [ files: [
@ -213,7 +214,8 @@ module.exports = [
}); });
module.exports = [ module.exports = [
...fixupConfigRules(compat.extends("plugin:playwright/recommend")), ...fixupConfigRules(compat.extends("plugin:playwright/recommend")),
...baseConfig, ...baseConfig,
{ {
files: [ files: [
@ -275,11 +277,16 @@ module.exports = [
}); });
module.exports = [ module.exports = [
...compat.extends("plugin:some-plugin1", "plugin:some-plugin2"), ...compat.extends("plugin:some-plugin1", "plugin:some-plugin2"),
...fixupConfigRules(compat.extends("incompatible-plugin1")),
...fixupConfigRules(compat.extends("incompatible-plugin2")), ...fixupConfigRules(compat.extends("incompatible-plugin1")),
...compat.extends("plugin:some-plugin3"),
...fixupConfigRules(compat.extends("incompatible-plugin3")), ...fixupConfigRules(compat.extends("incompatible-plugin2")),
...compat.extends("plugin:some-plugin3"),
...fixupConfigRules(compat.extends("incompatible-plugin3")),
...baseConfig, ...baseConfig,
{ {
files: [ files: [
@ -336,7 +343,8 @@ module.exports = [
}); });
module.exports = [ module.exports = [
...compat.extends("plugin:playwright/recommend"), ...compat.extends("plugin:playwright/recommend"),
...baseConfig, ...baseConfig,
{ {
files: [ files: [
@ -433,39 +441,39 @@ module.exports = [
module.exports = [ module.exports = [
...baseConfig, ...baseConfig,
...compat.config({ extends: [ ...compat.config({ extends: [
"plugin:@nx/angular", "plugin:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates" "plugin:@angular-eslint/template/process-inline-templates"
] }).map(config => ({ ] }).map(config => ({
...config, ...config,
files: ["**/*.ts"], files: ["**/*.ts"],
rules: { rules: {
...config.rules, ...config.rules,
"@angular-eslint/directive-selector": [ "@angular-eslint/directive-selector": [
"error", "error",
{ {
type: "attribute", type: "attribute",
prefix: "myOrg", prefix: "myOrg",
style: "camelCase" style: "camelCase"
} }
], ],
"@angular-eslint/component-selector": [ "@angular-eslint/component-selector": [
"error", "error",
{ {
type: "element", type: "element",
prefix: "my-org", prefix: "my-org",
style: "kebab-case" style: "kebab-case"
} }
] ]
} }
})), })),
...compat.config({ extends: ["plugin:@nx/angular-template"] }).map(config => ({ ...compat.config({ extends: ["plugin:@nx/angular-template"] }).map(config => ({
...config, ...config,
files: ["**/*.html"], files: ["**/*.html"],
rules: { rules: {
...config.rules ...config.rules
} }
})), })),
];" ];"
`); `);
}); });

View File

@ -172,23 +172,23 @@ describe('ast-utils', () => {
}) })
); );
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js"); "const baseConfig = require("../../eslint.config.js");
module.exports = [ module.exports = [
...baseConfig, ...baseConfig,
{ {
files: [ files: [
"my-lib/**/*.ts", "my-lib/**/*.ts",
"my-lib/**/*.tsx" "my-lib/**/*.tsx"
], ],
rules: {} rules: {}
}, },
{ ignores: ["my-lib/.cache/**/*"] }, { ignores: ["my-lib/.cache/**/*"] },
{ {
files: ["**/*.svg"], files: ["**/*.svg"],
rules: { "@nx/do-something-with-svg": "error" } rules: { "@nx/do-something-with-svg": "error" }
}, },
];" ];"
`); `);
}); });
it('should inject spread to the beginning of the file', () => { it('should inject spread to the beginning of the file', () => {
@ -210,20 +210,21 @@ describe('ast-utils', () => {
{ insertAtTheEnd: false } { insertAtTheEnd: false }
); );
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js"); "const baseConfig = require("../../eslint.config.js");
module.exports = [ module.exports = [
...config, ...config,
...baseConfig,
{ ...baseConfig,
files: [ {
"my-lib/**/*.ts", files: [
"my-lib/**/*.tsx" "my-lib/**/*.ts",
], "my-lib/**/*.tsx"
rules: {} ],
}, rules: {}
{ ignores: ["my-lib/.cache/**/*"] }, },
];" { ignores: ["my-lib/.cache/**/*"] },
`); ];"
`);
}); });
}); });
@ -574,33 +575,33 @@ describe('ast-utils', () => {
it('should find and replace rules in override', () => { it('should find and replace rules in override', () => {
const content = `const baseConfig = require("../../eslint.config.js"); const content = `const baseConfig = require("../../eslint.config.js");
module.exports = [ module.exports = [
{ {
files: [ files: [
"my-lib/**/*.ts", "my-lib/**/*.ts",
"my-lib/**/*.tsx" "my-lib/**/*.tsx"
], ],
rules: { rules: {
'my-ts-rule': 'error' 'my-ts-rule': 'error'
} }
}, },
{ {
files: [ files: [
"my-lib/**/*.ts", "my-lib/**/*.ts",
"my-lib/**/*.js" "my-lib/**/*.js"
], ],
rules: {} rules: {}
}, },
{ {
files: [ files: [
"my-lib/**/*.js", "my-lib/**/*.js",
"my-lib/**/*.jsx" "my-lib/**/*.jsx"
], ],
rules: { rules: {
'my-js-rule': 'error' 'my-js-rule': 'error'
} }
}, },
];`; ];`;
const result = replaceOverride( const result = replaceOverride(
content, content,
@ -616,61 +617,61 @@ describe('ast-utils', () => {
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js"); "const baseConfig = require("../../eslint.config.js");
module.exports = [ module.exports = [
{ {
"files": [ "files": [
"my-lib/**/*.ts", "my-lib/**/*.ts",
"my-lib/**/*.tsx" "my-lib/**/*.tsx"
], ],
"rules": { "rules": {
"my-rule": "error" "my-rule": "error"
} }
}, },
{ {
"files": [ "files": [
"my-lib/**/*.ts", "my-lib/**/*.ts",
"my-lib/**/*.js" "my-lib/**/*.js"
], ],
"rules": { "rules": {
"my-rule": "error" "my-rule": "error"
} }
}, },
{ {
files: [ files: [
"my-lib/**/*.js", "my-lib/**/*.js",
"my-lib/**/*.jsx" "my-lib/**/*.jsx"
], ],
rules: { rules: {
'my-js-rule': 'error' 'my-js-rule': 'error'
} }
}, },
];" ];"
`); `);
}); });
it('should append rules in override', () => { it('should append rules in override', () => {
const content = `const baseConfig = require("../../eslint.config.js"); const content = `const baseConfig = require("../../eslint.config.js");
module.exports = [ module.exports = [
{ {
files: [ files: [
"my-lib/**/*.ts", "my-lib/**/*.ts",
"my-lib/**/*.tsx" "my-lib/**/*.tsx"
], ],
rules: { rules: {
'my-ts-rule': 'error' 'my-ts-rule': 'error'
} }
}, },
{ {
files: [ files: [
"my-lib/**/*.js", "my-lib/**/*.js",
"my-lib/**/*.jsx" "my-lib/**/*.jsx"
], ],
rules: { rules: {
'my-js-rule': 'error' 'my-js-rule': 'error'
} }
}, },
];`; ];`;
const result = replaceOverride( const result = replaceOverride(
content, content,
@ -687,45 +688,45 @@ describe('ast-utils', () => {
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js"); "const baseConfig = require("../../eslint.config.js");
module.exports = [ module.exports = [
{ {
"files": [ "files": [
"my-lib/**/*.ts", "my-lib/**/*.ts",
"my-lib/**/*.tsx" "my-lib/**/*.tsx"
], ],
"rules": { "rules": {
"my-ts-rule": "error", "my-ts-rule": "error",
"my-new-rule": "error" "my-new-rule": "error"
} }
}, },
{ {
files: [ files: [
"my-lib/**/*.js", "my-lib/**/*.js",
"my-lib/**/*.jsx" "my-lib/**/*.jsx"
], ],
rules: { rules: {
'my-js-rule': 'error' 'my-js-rule': 'error'
} }
}, },
];" ];"
`); `);
}); });
it('should work for compat overrides', () => { it('should work for compat overrides', () => {
const content = `const baseConfig = require("../../eslint.config.js"); const content = `const baseConfig = require("../../eslint.config.js");
module.exports = [ module.exports = [
...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({ ...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({
...config, ...config,
files: [ files: [
"my-lib/**/*.ts", "my-lib/**/*.ts",
"my-lib/**/*.tsx" "my-lib/**/*.tsx"
], ],
rules: { rules: {
'my-ts-rule': 'error' 'my-ts-rule': 'error'
} }
}), }),
];`; ];`;
const result = replaceOverride( const result = replaceOverride(
content, content,
@ -742,19 +743,19 @@ describe('ast-utils', () => {
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js"); "const baseConfig = require("../../eslint.config.js");
module.exports = [ module.exports = [
...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({ ...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({
...config, ...config,
"files": [ "files": [
"my-lib/**/*.ts", "my-lib/**/*.ts",
"my-lib/**/*.tsx" "my-lib/**/*.tsx"
], ],
"rules": { "rules": {
"my-ts-rule": "error", "my-ts-rule": "error",
"my-new-rule": "error" "my-new-rule": "error"
} }
}), }),
];" ];"
`); `);
}); });
}); });

View File

@ -175,12 +175,17 @@ export function replaceOverride(
changes.push({ changes.push({
type: ChangeType.Insert, type: ChangeType.Insert,
index: start, index: start,
text: JSON.stringify(updatedData, null, 2) // NOTE: Indentation added to format without formatting tools like Prettier.
// restore any parser require calls that were stripped during JSON parsing text:
.replace(/"parser": "([^"]+)"/g, (_, parser) => { ' ' +
return `"parser": require('${parser}')`; JSON.stringify(updatedData, null, 2)
}) // restore any parser require calls that were stripped during JSON parsing
.slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties .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. // base config was not generated by Nx.
if (!exportsArray) return content; 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) { if (options.insertAtTheEnd) {
const index = const index =
exportsArray.length > 0 exportsArray.length > 0
@ -414,7 +423,7 @@ export function addBlockToFlatConfigExport(
{ {
type: ChangeType.Insert, type: ChangeType.Insert,
index, index,
text: `\n${insert},`, text: `\n${insert},\n`,
}, },
]); ]);
} }

View File

@ -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 { jestInitGenerator } from '../init/init';
import { checkForTestTarget } from './lib/check-for-test-target'; import { checkForTestTarget } from './lib/check-for-test-target';
import { createFiles } from './lib/create-files'; 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 { updateVsCodeRecommendedExtensions } from './lib/update-vscode-recommended-extensions';
import { updateWorkspace } from './lib/update-workspace'; import { updateWorkspace } from './lib/update-workspace';
import { JestProjectSchema, NormalizedJestProjectSchema } from './schema'; 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 = { const schemaDefaults = {
setupFile: 'none', setupFile: 'none',
@ -118,7 +123,39 @@ export async function configurationGeneratorInternal(
await formatFiles(tree); await formatFiles(tree);
} }
tasks.push(getUnsupportedModuleResolutionWarningTask(tree));
return runTasksInSerial(...tasks); 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; export default configurationGenerator;

View File

@ -4,7 +4,7 @@ exports[`createJestConfig should generate files 1`] = `
"import { getJestProjectsAsync } from '@nx/jest'; "import { getJestProjectsAsync } from '@nx/jest';
export default async () => ({ 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'); "const { getJestProjectsAsync } = require('@nx/jest');
module.exports = async () => ({ module.exports = async () => ({
projects: await getJestProjectsAsync() projects: await getJestProjectsAsync()
});" });"
`; `;

View File

@ -9,7 +9,6 @@ jest.mock('@nx/devkit', () => ({
import { import {
addProjectConfiguration as _addProjectConfiguration, addProjectConfiguration as _addProjectConfiguration,
readProjectConfiguration, readProjectConfiguration,
stripIndents,
type ProjectConfiguration, type ProjectConfiguration,
type ProjectGraph, type ProjectGraph,
type Tree, type Tree,
@ -49,24 +48,16 @@ describe('createJestConfig', () => {
await createJestConfig(tree, { js: true }, 'js'); await createJestConfig(tree, { js: true }, 'js');
expect(tree.exists('jest.config.js')).toBeTruthy(); expect(tree.exists('jest.config.js')).toBeTruthy();
expect( expect(tree.read('jest.config.js', 'utf-8')).toMatchSnapshot();
stripIndents`${tree.read('jest.config.js', 'utf-8')}` expect(tree.read('jest.preset.js', 'utf-8')).toMatchSnapshot();
).toMatchSnapshot();
expect(
stripIndents`${tree.read('jest.preset.js', 'utf-8')}`
).toMatchSnapshot();
}); });
it('should generate files ', async () => { it('should generate files ', async () => {
await createJestConfig(tree, {}, 'js'); await createJestConfig(tree, {}, 'js');
expect(tree.exists('jest.config.ts')).toBeTruthy(); expect(tree.exists('jest.config.ts')).toBeTruthy();
expect( expect(tree.read('jest.config.ts', 'utf-8')).toMatchSnapshot();
stripIndents`${tree.read('jest.config.ts', 'utf-8')}` expect(tree.read('jest.preset.js', 'utf-8')).toMatchSnapshot();
).toMatchSnapshot();
expect(
stripIndents`${tree.read('jest.preset.js', 'utf-8')}`
).toMatchSnapshot();
}); });
it('should not override existing files', async () => { it('should not override existing files', async () => {
@ -83,7 +74,7 @@ describe('createJestConfig', () => {
}, },
}, },
}); });
const expected = stripIndents` const expected = `
import { getJestProjects } from '@nx/jest'; import { getJestProjects } from '@nx/jest';
export default { export default {
projects: getJestProjects(), projects: getJestProjects(),
@ -165,7 +156,7 @@ export default {
.toEqual(`import { getJestProjectsAsync } from '@nx/jest'; .toEqual(`import { getJestProjectsAsync } from '@nx/jest';
export default async () => ({ export default async () => ({
projects: await getJestProjectsAsync() projects: await getJestProjectsAsync()
});`); });`);
expect(readProjectConfiguration(tree, 'my-project').targets.test) expect(readProjectConfiguration(tree, 'my-project').targets.test)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
@ -217,7 +208,7 @@ module.exports = {
.toEqual(`const { getJestProjectsAsync } = require('@nx/jest'); .toEqual(`const { getJestProjectsAsync } = require('@nx/jest');
module.exports = async () => ({ module.exports = async () => ({
projects: await getJestProjectsAsync() projects: await getJestProjectsAsync()
});`); });`);
}); });
}); });

View File

@ -32,7 +32,7 @@ export default { ...nxPreset };`
tree.write( tree.write(
`jest.preset.${presetExt}`, `jest.preset.${presetExt}`,
`const nxPreset = require('@nx/jest/preset').default; `const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset };` module.exports = { ...nxPreset };`
); );
} }
@ -139,17 +139,15 @@ module.exports = { ...nxPreset };`
function generateGlobalConfig(tree: Tree, isJS: boolean) { function generateGlobalConfig(tree: Tree, isJS: boolean) {
const contents = isJS const contents = isJS
? stripIndents` ? `const { getJestProjectsAsync } = require('@nx/jest');
const { getJestProjectsAsync } = require('@nx/jest');
module.exports = async () => ({ module.exports = async () => ({
projects: await getJestProjectsAsync() projects: await getJestProjectsAsync()
});` });`
: stripIndents` : `import { getJestProjectsAsync } from '@nx/jest';
import { getJestProjectsAsync } from '@nx/jest';
export default async () => ({ export default async () => ({
projects: await getJestProjectsAsync() projects: await getJestProjectsAsync()
});`; });`;
tree.write(`jest.config.${isJS ? 'js' : 'ts'}`, contents); tree.write(`jest.config.${isJS ? 'js' : 'ts'}`, contents);
} }

View File

@ -10,7 +10,7 @@
"description": "Create a library" "description": "Create a library"
}, },
"init": { "init": {
"factory": "./src/generators/init/init#initGenerator", "factory": "./src/generators/init/init#initGeneratorInternal",
"schema": "./src/generators/init/schema.json", "schema": "./src/generators/init/schema.json",
"aliases": ["lib"], "aliases": ["lib"],
"x-type": "init", "x-type": "init",

View File

@ -41,6 +41,7 @@
"@babel/runtime": "^7.22.6", "@babel/runtime": "^7.22.6",
"@nx/devkit": "file:../devkit", "@nx/devkit": "file:../devkit",
"@nx/workspace": "file:../workspace", "@nx/workspace": "file:../workspace",
"@zkochan/js-yaml": "0.0.7",
"babel-plugin-const-enum": "^1.0.1", "babel-plugin-const-enum": "^1.0.1",
"babel-plugin-macros": "^2.8.0", "babel-plugin-macros": "^2.8.0",
"babel-plugin-transform-typescript-metadata": "^0.3.1", "babel-plugin-transform-typescript-metadata": "^0.3.1",

View File

@ -3,7 +3,7 @@ import 'nx/src/internal-testing-utils/mock-project-graph';
import { readProjectConfiguration, Tree } from '@nx/devkit'; import { readProjectConfiguration, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { join } from 'path'; import { join } from 'path';
import { LibraryGeneratorSchema } from '../../utils/schema'; import { LibraryGeneratorSchema } from '../library/schema';
import { libraryGenerator as jsLibraryGenerator } from '../library/library'; import { libraryGenerator as jsLibraryGenerator } from '../library/library';
import { convertToSwcGenerator } from './convert-to-swc'; import { convertToSwcGenerator } from './convert-to-swc';

View File

@ -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
}
}

View File

@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"compileOnSave": false,
"files": [],
"references": []
}

View File

@ -131,7 +131,7 @@ describe('js init generator', () => {
it('should support skipping prettier setup', async () => { it('should support skipping prettier setup', async () => {
await init(tree, { await init(tree, {
setUpPrettier: false, formatter: 'none',
}); });
const packageJson = readJson(tree, 'package.json'); const packageJson = readJson(tree, 'package.json');

View File

@ -1,19 +1,24 @@
import { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
createProjectGraphAsync,
ensurePackage, ensurePackage,
formatFiles, formatFiles,
generateFiles, generateFiles,
GeneratorCallback, GeneratorCallback,
readJson, readJson,
readNxJson,
runTasksInSerial, runTasksInSerial,
Tree, Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver'; import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';
import { readModulePackageJson } from 'nx/src/utils/package-json'; import { readModulePackageJson } from 'nx/src/utils/package-json';
import { join } from 'path'; import { join } from 'path';
import { satisfies, valid } from 'semver'; import { satisfies, valid } from 'semver';
import { createNodesV2 } from '../../plugins/typescript/plugin';
import { generatePrettierSetup } from '../../utils/prettier'; import { generatePrettierSetup } from '../../utils/prettier';
import { getRootTsConfigFileName } from '../../utils/typescript/ts-config'; import { getRootTsConfigFileName } from '../../utils/typescript/ts-config';
import { isUsingTsSolutionSetup } from '../../utils/typescript/ts-solution-setup';
import { import {
nxVersion, nxVersion,
prettierVersion, prettierVersion,
@ -64,9 +69,12 @@ export async function initGenerator(
tree: Tree, tree: Tree,
schema: InitSchema schema: InitSchema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
schema.addTsPlugin ??= false;
const isUsingNewTsSetup = schema.addTsPlugin || isUsingTsSolutionSetup(tree);
schema.formatter ??= isUsingNewTsSetup ? 'none' : 'prettier';
return initGeneratorInternal(tree, { return initGeneratorInternal(tree, {
addTsConfigBase: true, addTsConfigBase: true,
setUpPrettier: true,
...schema, ...schema,
}); });
} }
@ -76,12 +84,48 @@ export async function initGeneratorInternal(
schema: InitSchema schema: InitSchema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
const tasks: GeneratorCallback[] = []; const tasks: GeneratorCallback[] = [];
// add tsconfig.base.json
if (schema.addTsConfigBase && !getRootTsConfigFileName(tree)) { const nxJson = readNxJson(tree);
generateFiles(tree, join(__dirname, './files'), '.', { schema.addPlugin ??=
fileName: schema.tsConfigName ?? 'tsconfig.base.json', 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 = { const devDependencies = {
'@nx/js': nxVersion, '@nx/js': nxVersion,
// When loading .ts config files (e.g. webpack.config.ts, jest.config.ts, etc.) // 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, { const prettierTask = generatePrettierSetup(tree, {
skipPackageJson: schema.skipPackageJson, skipPackageJson: schema.skipPackageJson,
}); });
@ -132,7 +176,12 @@ export async function initGeneratorInternal(
: () => {}; : () => {};
tasks.push(installTask); 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); ensurePackage('prettier', prettierVersion);
} }

View File

@ -1,9 +1,12 @@
export interface InitSchema { export interface InitSchema {
addTsConfigBase?: boolean; addTsConfigBase?: boolean;
formatter?: 'none' | 'prettier';
js?: boolean; js?: boolean;
keepExistingVersions?: boolean; keepExistingVersions?: boolean;
setUpPrettier?: boolean;
skipFormat?: boolean; skipFormat?: boolean;
skipPackageJson?: boolean; skipPackageJson?: boolean;
tsConfigName?: string; tsConfigName?: string;
addPlugin?: boolean;
updatePackageScripts?: boolean;
addTsPlugin?: boolean;
} }

View File

@ -5,6 +5,12 @@
"title": "Init nx/js", "title": "Init nx/js",
"description": "Init generator placeholder for nx/js.", "description": "Init generator placeholder for nx/js.",
"properties": { "properties": {
"formatter": {
"description": "The tool to use for code formatting.",
"type": "string",
"enum": ["none", "prettier"],
"default": "none"
},
"js": { "js": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
@ -37,12 +43,6 @@
"type": "string", "type": "string",
"description": "Customize the generated base tsconfig file name.", "description": "Customize the generated base tsconfig file name.",
"x-priority": "internal" "x-priority": "internal"
},
"setUpPrettier": {
"type": "boolean",
"description": "Add Prettier and corresponding configuration files.",
"x-priority": "internal",
"default": false
} }
} }
} }

View File

@ -1,7 +1,7 @@
import { <%= propertyName %> } from './<%= fileName %>'; import { <%= propertyName %> } from './<%= fileName %>';
describe('<%= propertyName %>', () => { describe('<%= propertyName %>', () => {
it('should work', () => { it('should work', () => {
expect(<%= propertyName %>()).toEqual('<%= name %>'); expect(<%= propertyName %>()).toEqual('<%= name %>');
}) })
}) })

View File

@ -1,3 +1,3 @@
export function <%= propertyName %>(): string { export function <%= propertyName %>(): string {
return '<%= name %>'; return '<%= name %>';
} }

View File

@ -1,19 +1,11 @@
# <%= name %> # <%= name %>
This library was generated with [Nx](https://nx.dev). This library was generated with [Nx](https://nx.dev).<% if (buildable) { %>
<% if (buildable) { %>
## Building ## Building
Run `<%= cliCommand %> build <%= name %>` to build the library. Run `<%= cliCommand %> build <%= name %>` to build the library.<% } %><% if (unitTestRunner !== 'none') { %>
<% } %>
<% if (hasUnitTestRunner) { %>
## Running unit tests ## 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/)<% } %>.<% } %>
<% } %>

View File

@ -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": []
}

View File

@ -10,8 +10,8 @@ import {
updateJson, updateJson,
} from '@nx/devkit'; } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; 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', () => { describe('lib', () => {
let tree: Tree; let tree: Tree;
@ -155,6 +155,7 @@ describe('lib', () => {
{ {
"compilerOptions": { "compilerOptions": {
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"importHelpers": true,
"module": "commonjs", "module": "commonjs",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noImplicitOverride": true, "noImplicitOverride": true,
@ -1616,4 +1617,73 @@ describe('lib', () => {
expect(content).toContain(`environment: 'jsdom'`); 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",
}
`);
});
});
}); });

View File

@ -18,26 +18,33 @@ import {
toJS, toJS,
Tree, Tree,
updateJson, updateJson,
updateNxJson,
updateProjectConfiguration,
writeJson, writeJson,
} from '@nx/devkit'; } from '@nx/devkit';
import { import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
determineProjectNameAndRootOptions,
type ProjectNameAndRootOptions,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; 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 { 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 { type PackageJson } from 'nx/src/utils/package-json';
import { join } from 'path'; 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 { 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 { tsConfigBaseOptions } from '../../utils/typescript/create-ts-config';
import { import {
addTsConfigPath, addTsConfigPath,
getRelativePathToRootTsConfig, getRelativePathToRootTsConfig,
getRootTsConfigFileName,
} from '../../utils/typescript/ts-config'; } from '../../utils/typescript/ts-config';
import {
isUsingTsSolutionSetup,
isUsingTypeScriptPlugin,
} from '../../utils/typescript/ts-solution-setup';
import { import {
esbuildVersion, esbuildVersion,
nxVersion, nxVersion,
@ -47,6 +54,16 @@ import {
} from '../../utils/versions'; } from '../../utils/versions';
import jsInitGenerator from '../init/init'; import jsInitGenerator from '../init/init';
import setupVerdaccio from '../setup-verdaccio/generator'; 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'; const defaultOutputDirectory = 'dist';
@ -56,6 +73,7 @@ export async function libraryGenerator(
) { ) {
return await libraryGeneratorInternal(tree, { return await libraryGeneratorInternal(tree, {
addPlugin: false, addPlugin: false,
useProjectJson: true,
...schema, ...schema,
}); });
} }
@ -65,18 +83,22 @@ export async function libraryGeneratorInternal(
schema: LibraryGeneratorSchema schema: LibraryGeneratorSchema
) { ) {
const tasks: GeneratorCallback[] = []; const tasks: GeneratorCallback[] = [];
tasks.push( tasks.push(
await jsInitGenerator(tree, { await jsInitGenerator(tree, {
...schema, ...schema,
skipFormat: true, skipFormat: true,
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json', 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); const options = await normalizeOptions(tree, schema);
createFiles(tree, options); createFiles(tree, options);
await addProject(tree, options); await configureProject(tree, options);
if (!options.skipPackageJson) { if (!options.skipPackageJson) {
tasks.push(addProjectDependencies(tree, options)); 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, [ addTsConfigPath(tree, options.importPath, [
joinPathFragments( joinPathFragments(
options.projectRoot, 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) { if (!options.skipFormat) {
await formatFiles(tree); await formatFiles(tree);
} }
if (
options.isUsingTsSolutionConfig &&
options.projectPackageManagerWorkspaceState !== 'included'
) {
tasks.push(
getProjectPackageManagerWorkspaceStateWarningTask(
options.projectPackageManagerWorkspaceState,
tree.root
)
);
}
if (options.publishable) { if (options.publishable) {
tasks.push(() => { tasks.push(() => {
logNxReleaseDocsInfo(); logNxReleaseDocsInfo();
@ -187,16 +244,36 @@ export async function libraryGeneratorInternal(
return runTasksInSerial(...tasks); return runTasksInSerial(...tasks);
} }
export interface NormalizedSchema extends LibraryGeneratorSchema { async function configureProject(
name: string; tree: Tree,
projectNames: ProjectNameAndRootOptions['names']; options: NormalizedLibraryGeneratorOptions
fileName: string; ) {
projectRoot: string; if (options.hasPlugin) {
parsedTags: string[]; const nxJson = readNxJson(tree);
importPath?: string; 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 = { const projectConfiguration: ProjectConfiguration = {
root: options.projectRoot, root: options.projectRoot,
sourceRoot: joinPathFragments(options.projectRoot, 'src'), sourceRoot: joinPathFragments(options.projectRoot, 'src'),
@ -275,21 +352,16 @@ async function addProject(tree: Tree, options: NormalizedSchema) {
if (options.config === 'workspace' || options.config === 'project') { if (options.config === 'workspace' || options.config === 'project') {
addProjectConfiguration(tree, options.name, projectConfiguration); addProjectConfiguration(tree, options.name, projectConfiguration);
} else { } else {
addProjectConfiguration( addProjectConfiguration(tree, options.name, {
tree, root: projectConfiguration.root,
options.name, tags: projectConfiguration.tags,
{ targets: {},
root: projectConfiguration.root, });
tags: projectConfiguration.tags,
targets: {},
},
true
);
} }
} }
export type AddLintOptions = Pick< export type AddLintOptions = Pick<
NormalizedSchema, NormalizedLibraryGeneratorOptions,
| 'name' | 'name'
| 'linter' | 'linter'
| 'projectRoot' | 'projectRoot'
@ -407,25 +479,7 @@ export async function addLint(
return task; return task;
} }
function updateTsConfig(tree: Tree, options: NormalizedSchema) { function addBabelRc(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
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) {
const filename = '.babelrc'; const filename = '.babelrc';
const babelrc = { const babelrc = {
@ -435,12 +489,12 @@ function addBabelRc(tree: Tree, options: NormalizedSchema) {
writeJson(tree, join(options.projectRoot, filename), babelrc); writeJson(tree, join(options.projectRoot, filename), babelrc);
} }
function createFiles(tree: Tree, options: NormalizedSchema) { function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
const { className, name, propertyName } = names( const { className, name, propertyName } = names(
options.projectNames.projectFileName options.projectNames.projectFileName
); );
createProjectTsConfigJson(tree, options); createProjectTsConfigs(tree, options);
generateFiles(tree, join(__dirname, './files/lib'), options.projectRoot, { generateFiles(tree, join(__dirname, './files/lib'), options.projectRoot, {
...options, ...options,
@ -480,7 +534,6 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
} }
if (options.bundler === 'swc' || options.bundler === 'rollup') { if (options.bundler === 'swc' || options.bundler === 'rollup') {
addSwcDependencies(tree);
addSwcConfig( addSwcConfig(
tree, tree,
options.projectRoot, options.projectRoot,
@ -518,6 +571,11 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
if (!options.publishable && !options.rootProject) { if (!options.publishable && !options.rootProject) {
json.private = true; 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 { return {
...json, ...json,
dependencies: { dependencies: {
@ -537,6 +595,11 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
if (!options.publishable && !options.rootProject) { if (!options.publishable && !options.rootProject) {
packageJson.private = true; 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<PackageJson>(tree, packageJsonPath, packageJson); writeJson<PackageJson>(tree, packageJsonPath, packageJson);
} }
@ -548,23 +611,16 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
}; };
return json; return json;
}); });
} else if (
(!options.bundler || options.bundler === 'none') &&
!(options.projectRoot === '.')
) {
tree.delete(packageJsonPath);
} }
if (options.minimal && !(options.projectRoot === '.')) { if (options.minimal && !(options.projectRoot === '.')) {
tree.delete(join(options.projectRoot, 'README.md')); tree.delete(join(options.projectRoot, 'README.md'));
} }
updateTsConfig(tree, options);
} }
async function addJest( async function addJest(
tree: Tree, tree: Tree,
options: NormalizedSchema options: NormalizedLibraryGeneratorOptions
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
const { configurationGenerator } = ensurePackage('@nx/jest', nxVersion); const { configurationGenerator } = ensurePackage('@nx/jest', nxVersion);
return await configurationGenerator(tree, { 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'); const filesDir = join(__dirname, './files/jest-config');
// the existing config has to be deleted otherwise the new config won't overwrite it // the existing config has to be deleted otherwise the new config won't overwrite it
const existingJestConfig = joinPathFragments( 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<T>(
questions: Parameters<typeof prompt>[0],
defaultValue: T
): Promise<T> {
if (isNonInteractive()) {
return defaultValue;
}
return await prompt(questions);
}
async function normalizeOptions( async function normalizeOptions(
tree: Tree, tree: Tree,
options: LibraryGeneratorSchema options: LibraryGeneratorSchema
): Promise<NormalizedSchema> { ): Promise<NormalizedLibraryGeneratorOptions> {
const nxJson = readNxJson(tree); const nxJson = readNxJson(tree);
const addPlugin = options.addPlugin ??=
process.env.NX_ADD_PLUGINS !== 'false' && process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false; nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPlugin;
/** const hasPlugin = isUsingTypeScriptPlugin(tree);
* We are deprecating the compiler and the buildable options. const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree);
* 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.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 // ensure programmatic runs have an expected default
if (!options.config) { if (!options.config) {
@ -665,10 +847,9 @@ async function normalizeOptions(
options.bundler = 'none'; options.bundler = 'none';
} }
const { Linter } = ensurePackage('@nx/eslint', nxVersion);
if (options.config === 'npm-scripts') { if (options.config === 'npm-scripts') {
options.unitTestRunner = 'none'; options.unitTestRunner = 'none';
options.linter = Linter.None; options.linter = 'none';
options.bundler = 'none'; options.bundler = 'none';
} }
@ -679,16 +860,6 @@ async function normalizeOptions(
options.skipTypeCheck = false; 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 { const {
projectName, projectName,
names: projectNames, names: projectNames,
@ -715,6 +886,12 @@ async function normalizeOptions(
options.minimal ??= false; 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 { return {
...options, ...options,
fileName, fileName,
@ -723,12 +900,15 @@ async function normalizeOptions(
projectRoot, projectRoot,
parsedTags, parsedTags,
importPath, importPath,
hasPlugin,
isUsingTsSolutionConfig,
projectPackageManagerWorkspaceState,
}; };
} }
function addProjectDependencies( function addProjectDependencies(
tree: Tree, tree: Tree,
options: NormalizedSchema options: NormalizedLibraryGeneratorOptions
): GeneratorCallback { ): GeneratorCallback {
if (options.bundler == 'esbuild') { if (options.bundler == 'esbuild') {
return addDependenciesToPackageJson( return addDependenciesToPackageJson(
@ -741,10 +921,28 @@ function addProjectDependencies(
} }
); );
} else if (options.bundler == 'rollup') { } 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( return addDependenciesToPackageJson(
tree, 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 { } else {
return addDependenciesToPackageJson( return addDependenciesToPackageJson(
@ -776,7 +974,7 @@ function getBuildExecutor(bundler: Bundler) {
} }
} }
function getOutputPath(options: NormalizedSchema) { function getOutputPath(options: NormalizedLibraryGeneratorOptions) {
const parts = [defaultOutputDirectory]; const parts = [defaultOutputDirectory];
if (options.projectRoot === '.') { if (options.projectRoot === '.') {
parts.push(options.name); parts.push(options.name);
@ -786,15 +984,117 @@ function getOutputPath(options: NormalizedSchema) {
return joinPathFragments(...parts); return joinPathFragments(...parts);
} }
function createProjectTsConfigJson(tree: Tree, options: NormalizedSchema) { function createProjectTsConfigs(
tree: Tree,
options: NormalizedLibraryGeneratorOptions
) {
const rootOffset = offsetFromRoot(options.projectRoot);
let compilerOptionOverrides: Record<keyof CompilerOptions, any> = {
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 = { const tsconfig = {
extends: options.rootProject extends: options.rootProject
? undefined ? undefined
: getRelativePathToRootTsConfig(tree, options.projectRoot), : getRelativePathToRootTsConfig(tree, options.projectRoot),
compilerOptions: { compilerOptions: {
...(options.rootProject ? tsConfigBaseOptions : {}), ...(options.rootProject ? tsConfigBaseOptions : {}),
module: 'commonjs', ...compilerOptionOverrides,
allowJs: options.js ? true : undefined,
}, },
files: [], files: [],
include: [], include: [],
@ -835,14 +1135,18 @@ function determineDependencies(
type EntryField = string | { [key: string]: EntryField }; type EntryField = string | { [key: string]: EntryField };
function determineEntryFields( function determineEntryFields(
options: LibraryGeneratorSchema options: NormalizedLibraryGeneratorOptions
): Record<string, EntryField> { ): Record<string, EntryField> {
switch (options.bundler) { switch (options.bundler) {
case 'tsc': case 'tsc':
return { return {
type: 'commonjs', type: 'commonjs',
main: './src/index.js', main: options.isUsingTsSolutionConfig
typings: './src/index.d.ts', ? './dist/index.js'
: './src/index.js',
typings: options.isUsingTsSolutionConfig
? './dist/index.d.ts'
: './src/index.d.ts',
}; };
case 'swc': case 'swc':
return { return {
@ -854,16 +1158,26 @@ function determineEntryFields(
return { return {
// Since we're publishing both formats, skip the type field. // Since we're publishing both formats, skip the type field.
// Bundlers or Node will determine the entry point to use. // Bundlers or Node will determine the entry point to use.
main: './index.cjs', main: options.isUsingTsSolutionConfig
module: './index.js', ? './dist/index.cjs'
: './index.cjs',
module: options.isUsingTsSolutionConfig
? './dist/index.js'
: './index.js',
}; };
case 'vite': case 'vite':
return { return {
// Since we're publishing both formats, skip the type field. // Since we're publishing both formats, skip the type field.
// Bundlers or Node will determine the entry point to use. // Bundlers or Node will determine the entry point to use.
main: './index.js', main: options.isUsingTsSolutionConfig
module: './index.mjs', ? './dist/index.js'
typings: './index.d.ts', : './index.js',
module: options.isUsingTsSolutionConfig
? './dist/index.mjs'
: './index.mjs',
typings: options.isUsingTsSolutionConfig
? './dist/index.d.ts'
: './index.d.ts',
}; };
case 'esbuild': case 'esbuild':
// For libraries intended for Node, use CJS. // For libraries intended for Node, use CJS.
@ -905,7 +1219,7 @@ function projectsConfigMatchesProject(
async function addProjectToNxReleaseConfig( async function addProjectToNxReleaseConfig(
tree: Tree, tree: Tree,
options: NormalizedSchema, options: NormalizedLibraryGeneratorOptions,
projectConfiguration: ProjectConfiguration projectConfiguration: ProjectConfiguration
) { ) {
const nxJson = readNxJson(tree); const nxJson = readNxJson(tree);

View File

@ -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;
}

View File

@ -21,22 +21,28 @@
"description": "A directory where the lib is placed.", "description": "A directory where the lib is placed.",
"x-priority": "important" "x-priority": "important"
}, },
"projectNameAndRootFormat": { "bundler": {
"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`).", "description": "The bundler to use. Choosing 'none' means this library is not buildable.",
"type": "string", "type": "string",
"enum": ["as-provided", "derived"] "enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"],
"x-priority": "important"
}, },
"linter": { "linter": {
"description": "The tool to use for running lint checks.", "description": "The tool to use for running lint checks.",
"type": "string", "type": "string",
"enum": ["eslint", "none"], "enum": ["none", "eslint"],
"default": "eslint" "x-priority": "important"
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string",
"enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "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": { "tags": {
"type": "string", "type": "string",
@ -112,18 +118,9 @@
"compiler": { "compiler": {
"type": "string", "type": "string",
"enum": ["tsc", "swc"], "enum": ["tsc", "swc"],
"default": "tsc",
"description": "The compiler used by the build and test targets", "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)." "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": { "skipTypeCheck": {
"type": "boolean", "type": "boolean",
"description": "Whether to skip TypeScript type checking for SWC compiler.", "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.", "description": "Don't include the directory in the generated file name.",
"type": "boolean", "type": "boolean",
"default": false "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"], "required": ["name"],

View File

@ -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}.`,
],
});
}
};
}

View File

@ -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',
},
},
},
],
});
});
});

View File

@ -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<TscPluginOptions> {
return (
(typeof plugin === 'string' && plugin === '@nx/js/typescript') ||
(typeof plugin !== 'string' && plugin.plugin === '@nx/js/typescript')
);
}

View File

@ -11,6 +11,7 @@ import {
} from '@nx/devkit'; } from '@nx/devkit';
import * as path from 'path'; import * as path from 'path';
import { SetupVerdaccioGeneratorSchema } from './schema'; import { SetupVerdaccioGeneratorSchema } from './schema';
import { isUsingTsSolutionSetup } from '../../utils/typescript/ts-solution-setup';
import { verdaccioVersion } from '../../utils/versions'; import { verdaccioVersion } from '../../utils/versions';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
@ -38,21 +39,25 @@ export async function setupVerdaccio(
}, },
}; };
if (!tree.exists('project.json')) { if (!tree.exists('project.json')) {
const isUsingNewTsSetup = isUsingTsSolutionSetup(tree);
const { name } = readJson(tree, 'package.json'); const { name } = readJson(tree, 'package.json');
updateJson(tree, 'package.json', (json) => { updateJson(tree, 'package.json', (json) => {
if (!json.nx) { json.nx ??= { includedScripts: [] };
json.nx = { if (isUsingNewTsSetup) {
includedScripts: [], json.nx.targets ??= {};
}; json.nx.targets['local-registry'] ??= verdaccioTarget;
} }
return json; return json;
}); });
addProjectConfiguration(tree, name, { if (!isUsingNewTsSetup) {
root: '.', addProjectConfiguration(tree, name, {
targets: { root: '.',
['local-registry']: verdaccioTarget, targets: {
}, ['local-registry']: verdaccioTarget,
}); },
});
}
} else { } else {
// use updateJson instead of updateProjectConfiguration due to unknown project name // use updateJson instead of updateProjectConfiguration due to unknown project name
updateJson(tree, 'project.json', (json: ProjectConfiguration) => { updateJson(tree, 'project.json', (json: ProjectConfiguration) => {

View File

@ -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', () => { it('should handle missing ts/swc helper packages from externalNodes', () => {
vol.fromJSON( vol.fromJSON(
{ {

View File

@ -10,7 +10,7 @@ import {
} from '@nx/devkit'; } from '@nx/devkit';
import { fileExists } from 'nx/src/utils/fileutils'; import { fileExists } from 'nx/src/utils/fileutils';
import { fileDataDepTarget } from 'nx/src/config/project-graph'; import { fileDataDepTarget } from 'nx/src/config/project-graph';
import { readTsConfig } from './typescript/ts-config'; import { getRootTsConfigFileName, readTsConfig } from './typescript/ts-config';
import { import {
filterUsingGlobPatterns, filterUsingGlobPatterns,
getTargetInputs, getTargetInputs,
@ -223,4 +223,21 @@ function collectHelperDependencies(
projectGraph.externalNodes['npm:@swc/helpers'].data.version; 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;
}
}
}
} }

View File

@ -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);
}

View File

@ -1,5 +1,6 @@
import { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
readJson,
stripIndents, stripIndents,
updateJson, updateJson,
writeJson, writeJson,
@ -96,3 +97,64 @@ export function generatePrettierSetup(
? () => {} ? () => {}
: addDependenciesToPackageJson(tree, {}, { prettier: prettierVersion }); : addDependenciesToPackageJson(tree, {}, { prettier: prettierVersion });
} }
export async function resolvePrettierConfigPath(
tree: Tree
): Promise<string | null> {
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;
}

View File

@ -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 type { AssetGlob, FileInputOutput } from './assets/assets';
import { TransformerEntry } from './typescript/types'; 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 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 { export interface ExecutorOptions {
assets: Array<AssetGlob | string>; assets: Array<AssetGlob | string>;

View File

@ -6,17 +6,25 @@ import {
swcNodeVersion, swcNodeVersion,
} from '../versions'; } from '../versions';
export function getSwcDependencies(): {
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
} {
const dependencies = {
'@swc/helpers': swcHelpersVersion,
};
const devDependencies = {
'@swc/core': swcCoreVersion,
'@swc/cli': swcCliVersion,
};
return { dependencies, devDependencies };
}
export function addSwcDependencies(tree: Tree) { export function addSwcDependencies(tree: Tree) {
return addDependenciesToPackageJson( const { dependencies, devDependencies } = getSwcDependencies();
tree,
{ return addDependenciesToPackageJson(tree, dependencies, devDependencies);
'@swc/helpers': swcHelpersVersion,
},
{
'@swc/core': swcCoreVersion,
'@swc/cli': swcCliVersion,
}
);
} }
export function addSwcRegisterDependencies(tree: Tree) { export function addSwcRegisterDependencies(tree: Tree) {

View File

@ -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<keyof CompilerOptions, any>,
tsConfigPath: string
): Record<keyof CompilerOptions, any> {
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<T extends object> = { [K in keyof T]: [K, T[K]] }[keyof T];
function reverseEnum<
EnumObj extends Record<keyof EnumObj, string>,
Result = {
[K in EnumObj[keyof EnumObj]]: Extract<Entries<EnumObj>, [any, K]>[0];
}
>(enumObj: EnumObj): Result {
return Object.keys(enumObj).reduce((acc, key) => {
acc[enumObj[key]] = key;
return acc;
}, {} as Result);
}

View File

@ -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;
}

View File

@ -31,6 +31,7 @@ exports[`lib --unit-test-runner none should not generate test configuration 1`]
{ {
"compilerOptions": { "compilerOptions": {
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"importHelpers": true,
"module": "commonjs", "module": "commonjs",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noImplicitOverride": true, "noImplicitOverride": true,
@ -53,6 +54,7 @@ exports[`lib nested should create a local tsconfig.json 1`] = `
{ {
"compilerOptions": { "compilerOptions": {
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"importHelpers": true,
"module": "commonjs", "module": "commonjs",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noImplicitOverride": true, "noImplicitOverride": true,
@ -91,6 +93,7 @@ exports[`lib not nested should create a local tsconfig.json 1`] = `
{ {
"compilerOptions": { "compilerOptions": {
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"importHelpers": true,
"module": "commonjs", "module": "commonjs",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noImplicitOverride": true, "noImplicitOverride": true,

View File

@ -1,7 +1,7 @@
import { Tree, readNxJson } from '@nx/devkit'; import { Tree, readNxJson } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'; 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 { 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 { Linter } from '@nx/eslint';
import type { LibraryGeneratorOptions, NormalizedOptions } from '../schema'; import type { LibraryGeneratorOptions, NormalizedOptions } from '../schema';

View File

@ -625,6 +625,7 @@ describe('app', () => {
module.exports = [ module.exports = [
...compat.extends('next', 'next/core-web-vitals'), ...compat.extends('next', 'next/core-web-vitals'),
...baseConfig, ...baseConfig,
...nx.configs['flat/react-typescript'], ...nx.configs['flat/react-typescript'],
{ ignores: ['.next/**/*'] }, { ignores: ['.next/**/*'] },

View File

@ -113,10 +113,11 @@ describe('updateEslint', () => {
}); });
module.exports = [ module.exports = [
...compat.extends("next", "next/core-web-vitals"), ...compat.extends("next", "next/core-web-vitals"),
...baseConfig, ...baseConfig,
...nx.configs["flat/react-typescript"], ...nx.configs["flat/react-typescript"],
{ ignores: [".next/**/*"] } { ignores: [".next/**/*"] }
]; ];
" "
`); `);

View File

@ -131,20 +131,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/my-app', cacheDir: '../node_modules/.vite/my-app',
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/my-app', reportsDirectory: '../coverage/my-app',
@ -498,20 +494,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/myApp', cacheDir: '../node_modules/.vite/myApp',
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/myApp', reportsDirectory: '../coverage/myApp',

View File

@ -115,7 +115,8 @@ function updateProjectConfigurationInPackageJson(
const packageJson = readJson<PackageJson>(tree, packageJsonFile); const packageJson = readJson<PackageJson>(tree, packageJsonFile);
if (packageJson.name === projectConfiguration.name ?? projectName) { projectConfiguration.name = projectName;
if (packageJson.name === projectConfiguration.name) {
delete projectConfiguration.name; delete projectConfiguration.name;
} }

View File

@ -66,6 +66,7 @@ export interface PackageJson {
packages: string[]; packages: string[];
}; };
publishConfig?: Record<string, string>; publishConfig?: Record<string, string>;
files?: string[];
// Nx Project Configuration // Nx Project Configuration
nx?: NxProjectPackageJsonConfiguration; nx?: NxProjectPackageJsonConfiguration;

View File

@ -34,14 +34,12 @@ export default defineConfig({
define: { define: {
global: 'window', global: 'window',
}, },
resolve: { resolve: {
extensions, extensions,
alias: { alias: {
'react-native': 'react-native-web', 'react-native': 'react-native-web',
}, },
}, },
build: { build: {
reportCompressedSize: true, reportCompressedSize: true,
commonjsOptions: { transformMixedEsModules: true }, commonjsOptions: { transformMixedEsModules: true },
@ -50,7 +48,6 @@ export default defineConfig({
plugins: [rollupPlugin([/react-native-vector-icons/])], plugins: [rollupPlugin([/react-native-vector-icons/])],
}, },
}, },
server: { server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
@ -59,12 +56,10 @@ export default defineConfig({
allow: ['..'], allow: ['..'],
}, },
}, },
preview: { preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
optimizeDeps: { optimizeDeps: {
esbuildOptions: { esbuildOptions: {
resolveExtensions: extensions, resolveExtensions: extensions,
@ -72,9 +67,7 @@ export default defineConfig({
loader: { '.js': 'jsx' }, loader: { '.js': 'jsx' },
}, },
}, },
plugins: [react(), nxViteTsPaths()], plugins: [react(), nxViteTsPaths()],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],

View File

@ -1,46 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`react app generator (legacy) should setup vite 1`] = ` exports[`react app generator (legacy) should setup vite 1`] = `
"/// <reference types='vitest' />
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,
},
},
});
" "
/// <reference types='vitest' />
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,
},
},
});"
`; `;

View File

@ -94,48 +94,38 @@ module.exports = {
`; `;
exports[`app --style @emotion/styled should not break if bundler is vite 1`] = ` exports[`app --style @emotion/styled should not break if bundler is vite 1`] = `
"/// <reference types='vitest' />
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,
},
},
});
" "
/// <reference types='vitest' />
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`] = ` 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`] = ` exports[`app --style none should not break if bundler is vite 1`] = `
"/// <reference types='vitest' />
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,
},
},
});
" "
/// <reference types='vitest' />
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`] = ` exports[`app not nested should add vite types to tsconfigs 1`] = `
" "/// <reference types='vitest' />
/// <reference types='vitest' /> import { defineConfig } from 'vite';
import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react';
import react from '@vitejs/plugin-react'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
export default defineConfig({ root: __dirname,
root: __dirname, cacheDir: '../node_modules/.vite/my-app',
cacheDir: '../node_modules/.vite/my-app', server:{
port: 4200,
server:{ host: 'localhost',
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:{ test: {
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: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/my-app', reportsDirectory: '../coverage/my-app',
provider: 'v8', provider: 'v8',
} }
}, },
});" });
"
`; `;
exports[`app not nested should generate files 1`] = ` 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`] = ` exports[`app not nested should use preview vite types to tsconfigs 1`] = `
" "/// <reference types='vitest' />
/// <reference types='vitest' /> import { defineConfig } from 'vite';
import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react';
import react from '@vitejs/plugin-react'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
export default defineConfig({ root: __dirname,
root: __dirname, cacheDir: '../node_modules/.vite/my-app',
cacheDir: '../node_modules/.vite/my-app', server:{
port: 4200,
server:{ host: 'localhost',
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:{ test: {
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: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/my-app', reportsDirectory: '../coverage/my-app',
provider: 'v8', provider: 'v8',
} }
}, },
});" });
"
`; `;
exports[`app setup React app with --bundler=vite should setup targets with vite configuration 1`] = `null`; 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`] = ` exports[`app should setup vite if bundler is vite 1`] = `
"/// <reference types='vitest' />
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,
},
},
});
" "
/// <reference types='vitest' />
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`] = ` exports[`app should setup webpack 1`] = `

View File

@ -3,100 +3,81 @@
exports[`lib --bundler none, unit test runner vitest should configure vite 1`] = ` exports[`lib --bundler none, unit test runner vitest should configure vite 1`] = `
" "
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.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"},
}, 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`] = ` exports[`lib should add vite types to tsconfigs 1`] = `
" "/// <reference types='vitest' />
/// <reference types='vitest' /> import { defineConfig } from 'vite';
import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts'; import dts from 'vite-plugin-dts';
import * as path from 'path'; import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/my-lib', 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: [react(), // plugins: [ nxViteTsPaths() ],
nxViteTsPaths(), // },
nxCopyAssetsPlugin(['*.md']), // Configuration for building your library.
dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })], // See: https://vitejs.dev/guide/build.html#library-mode
build: {
// Uncomment this if you are using workers. outDir: '../dist/my-lib',
// worker: { emptyOutDir: true,
// plugins: [ nxViteTsPaths() ], reportCompressedSize: true,
// }, commonjsOptions: {
transformMixedEsModules: true,
// Configuration for building your library. },
// See: https://vitejs.dev/guide/build.html#library-mode lib: {
build: { // Could also be a dictionary or array of multiple entry points.
outDir: '../dist/my-lib', entry: 'src/index.ts',
emptyOutDir: true, name: 'my-lib',
reportCompressedSize: true, fileName: 'index',
commonjsOptions: { // Change this to the formats you want to support.
transformMixedEsModules: true, // Don't forget to update your package.json as well.
}, formats: ['es', 'cjs']
lib: { },
// Could also be a dictionary or array of multiple entry points. rollupOptions: {
entry: 'src/index.ts', // External packages that should not be bundled into your library.
name: 'my-lib', external: ['react','react-dom','react/jsx-runtime']
fileName: 'index', },
// Change this to the formats you want to support. },
// Don't forget to update your package.json as well. test: {
formats: ['es', 'cjs']
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/my-lib', reportsDirectory: '../coverage/my-lib',
provider: 'v8', provider: 'v8',
} }
}, },
});" });
"
`; `;

View File

@ -379,21 +379,17 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/test', cacheDir: '../node_modules/.vite/test',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
test: { test: {
setupFiles: ['test-setup.ts'], setupFiles: ['test-setup.ts'],
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: 'jsdom',
include: ['./tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], include: ['./tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/test', reportsDirectory: '../coverage/test',
@ -702,21 +698,17 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: './node_modules/.vite/test', cacheDir: './node_modules/.vite/test',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
test: { test: {
setupFiles: ['test-setup.ts'], setupFiles: ['test-setup.ts'],
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: 'jsdom',
include: ['./tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], include: ['./tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: './coverage/test', reportsDirectory: './coverage/test',

View File

@ -31,14 +31,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/test', cacheDir: '../node_modules/.vite/test',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
test: { test: {
setupFiles: ['./src/test-setup.ts'], setupFiles: ['./src/test-setup.ts'],
watch: false, watch: false,

View File

@ -95,14 +95,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/libs/storybook-test', cacheDir: '../../node_modules/.vite/libs/storybook-test',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
test: { test: {
setupFiles: ['./src/test-setup.ts'], setupFiles: ['./src/test-setup.ts'],
watch: false, watch: false,

View File

@ -12,6 +12,7 @@ import {
writeJson, writeJson,
} from '@nx/devkit'; } from '@nx/devkit';
import { getImportPath } from '@nx/js/src/utils/get-import-path'; 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 { rollupInitGenerator } from '../init/init';
import { RollupExecutorOptions } from '../../executors/rollup/schema'; import { RollupExecutorOptions } from '../../executors/rollup/schema';
@ -56,13 +57,16 @@ export async function configurationGenerator(
} }
function createRollupConfig(tree: Tree, options: RollupProjectSchema) { function createRollupConfig(tree: Tree, options: RollupProjectSchema) {
const isUsingTsPlugin = isUsingTsSolutionSetup(tree);
const project = readProjectConfiguration(tree, options.project); const project = readProjectConfiguration(tree, options.project);
const buildOptions: RollupWithNxPluginOptions = { const buildOptions: RollupWithNxPluginOptions = {
outputPath: joinPathFragments( outputPath: isUsingTsPlugin
offsetFromRoot(project.root), ? './dist'
'dist', : joinPathFragments(
project.root === '.' ? project.name : project.root offsetFromRoot(project.root),
), 'dist',
project.root === '.' ? project.name : project.root
),
compiler: options.compiler ?? 'babel', compiler: options.compiler ?? 'babel',
main: options.main ?? './src/index.ts', main: options.main ?? './src/index.ts',
tsConfig: options.tsConfig ?? './tsconfig.lib.json', tsConfig: options.tsConfig ?? './tsconfig.lib.json',
@ -70,21 +74,24 @@ function createRollupConfig(tree: Tree, options: RollupProjectSchema) {
tree.write( tree.write(
joinPathFragments(project.root, 'rollup.config.js'), joinPathFragments(project.root, 'rollup.config.js'),
stripIndents` `const { withNx } = require('@nx/rollup/with-nx');
const { withNx } = require('@nx/rollup/with-nx');
module.exports = withNx(
module.exports = withNx({ {
main: '${buildOptions.main}', main: '${buildOptions.main}',
outputPath: '${buildOptions.outputPath}', outputPath: '${buildOptions.outputPath}',
tsConfig: '${buildOptions.tsConfig}', tsConfig: '${buildOptions.tsConfig}',
compiler: '${buildOptions.compiler}', compiler: '${buildOptions.compiler}',
format: ${JSON.stringify(options.format ?? ['esm'])}, format: ${JSON.stringify(options.format ?? ['esm'])},
assets:[{ input: '.', output: '.', glob:'*.md'}], assets: [{ input: '.', output: '.', glob:'*.md' }],
}, { },
// Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options {
// e.g. // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options
// output: { sourcemap: true }, // e.g.
});` // output: { sourcemap: true },
}
);
`
); );
} }

View File

@ -11,7 +11,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/my-lib', cacheDir: '../node_modules/.vite/my-lib',
plugins: [ plugins: [
nxViteTsPaths(), nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md']), nxCopyAssetsPlugin(['*.md']),
@ -20,12 +19,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}), }),
], ],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
// Configuration for building your library. // Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode // See: https://vitejs.dev/guide/build.html#library-mode
build: { build: {
@ -49,13 +46,11 @@ export default defineConfig({
external: [], external: [],
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/my-lib', reportsDirectory: '../coverage/my-lib',
@ -184,7 +179,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest', cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest',
plugins: [ plugins: [
react(), react(),
nxViteTsPaths(), nxViteTsPaths(),
@ -194,12 +188,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}), }),
], ],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
// Configuration for building your library. // Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode // See: https://vitejs.dev/guide/build.html#library-mode
build: { build: {
@ -239,7 +231,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest', cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest',
plugins: [ plugins: [
react(), react(),
nxViteTsPaths(), nxViteTsPaths(),
@ -249,12 +240,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}), }),
], ],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
// Configuration for building your library. // Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode // See: https://vitejs.dev/guide/build.html#library-mode
build: { build: {
@ -278,13 +267,11 @@ export default defineConfig({
external: ['react', 'react-dom', 'react/jsx-runtime'], external: ['react', 'react-dom', 'react/jsx-runtime'],
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../../coverage/libs/react-lib-nonb-jest', reportsDirectory: '../../coverage/libs/react-lib-nonb-jest',
@ -361,24 +348,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-react-app', cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
server: { server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview: { preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
build: { build: {
outDir: '../../dist/apps/my-test-react-app', outDir: '../../dist/apps/my-test-react-app',
emptyOutDir: true, emptyOutDir: true,
@ -420,24 +402,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-web-app', cacheDir: '../../node_modules/.vite/apps/my-test-web-app',
server: { server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview: { preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
build: { build: {
outDir: '../../dist/apps/my-test-web-app', outDir: '../../dist/apps/my-test-web-app',
emptyOutDir: true, emptyOutDir: true,
@ -479,24 +456,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-react-app', cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
server: { server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview: { preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
build: { build: {
outDir: '../../dist/apps/my-test-react-app', outDir: '../../dist/apps/my-test-react-app',
emptyOutDir: true, emptyOutDir: true,
@ -505,13 +477,11 @@ export default defineConfig({
transformMixedEsModules: true, transformMixedEsModules: true,
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../../coverage/apps/my-test-react-app', reportsDirectory: '../../coverage/apps/my-test-react-app',

View File

@ -15,7 +15,7 @@ import {
} from '../../utils/test-utils'; } from '../../utils/test-utils';
import { libraryGenerator as jsLibraryGenerator } from '@nx/js/src/generators/library/library'; 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', () => { describe('@nx/vite:configuration', () => {
let tree: Tree; let tree: Tree;
@ -273,7 +273,7 @@ describe('@nx/vite:configuration', () => {
...defaultOptions, ...defaultOptions,
name: 'my-lib', name: 'my-lib',
bundler: 'vite', bundler: 'vite',
unitTestRunner: undefined, unitTestRunner: 'vitest',
projectNameAndRootFormat: 'as-provided', projectNameAndRootFormat: 'as-provided',
}); });

View File

@ -10,14 +10,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-react-app', cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
define: { define: {
'import.meta.vitest': undefined, 'import.meta.vitest': undefined,
}, },
@ -52,20 +49,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/apps/my-test-react-app', cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../../coverage/apps/my-test-react-app', reportsDirectory: '../../coverage/apps/my-test-react-app',
@ -86,20 +79,16 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest', cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../../coverage/libs/react-lib-nonb-jest', reportsDirectory: '../../coverage/libs/react-lib-nonb-jest',

View File

@ -1,153 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // 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'
/// <reference types="vitest" />
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`] = ` exports[`ensureViteConfigIsCorrect should add build options if it is using conditional config - do nothing for test 1`] = `
" "
/// <reference types="vitest" /> /// <reference types="vitest" />
@ -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 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"]},
}
});
"
`;

View File

@ -1,4 +1,5 @@
import { import {
addProjectConfiguration,
readProjectConfiguration, readProjectConfiguration,
Tree, Tree,
updateProjectConfiguration, updateProjectConfiguration,
@ -7,12 +8,14 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { import {
findExistingJsBuildTargetInProject, findExistingJsBuildTargetInProject,
getViteConfigPathForProject, getViteConfigPathForProject,
createOrEditViteConfig,
} from './generator-utils'; } from './generator-utils';
import { import {
mockReactAppGenerator, mockReactAppGenerator,
mockViteReactAppGenerator, mockViteReactAppGenerator,
mockAngularAppGenerator, mockAngularAppGenerator,
} from './test-utils'; } from './test-utils';
describe('generator utils', () => { describe('generator utils', () => {
let tree: Tree; 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(`
"/// <reference types='vitest' />
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(`
"/// <reference types='vitest' />
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,
},
},
});
"
`);
});
});
}); });

View File

@ -9,12 +9,13 @@ import {
updateProjectConfiguration, updateProjectConfiguration,
writeJson, writeJson,
} from '@nx/devkit'; } 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 { ViteBuildExecutorOptions } from '../executors/build/schema';
import { VitePreviewServerExecutorOptions } from '../executors/preview-server/schema'; import { VitePreviewServerExecutorOptions } from '../executors/preview-server/schema';
import { VitestExecutorOptions } from '../executors/test/schema'; import { VitestExecutorOptions } from '../executors/test/schema';
import { ViteConfigurationGeneratorSchema } from '../generators/configuration/schema'; import { ViteConfigurationGeneratorSchema } from '../generators/configuration/schema';
import { ensureViteConfigIsCorrect } from './vite-config-edit-utils'; 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 Target = 'build' | 'serve' | 'test' | 'preview';
export type TargetFlags = Partial<Record<Target, boolean>>; export type TargetFlags = Partial<Record<Target, boolean>>;
@ -362,48 +363,47 @@ export function createOrEditViteConfig(
? `${projectRoot}/vitest.config.ts` ? `${projectRoot}/vitest.config.ts`
: `${projectRoot}/vite.config.ts`; : `${projectRoot}/vite.config.ts`;
const buildOutDir = const isUsingTsPlugin = isUsingTsSolutionSetup(tree);
projectRoot === '.' const buildOutDir = isUsingTsPlugin
? `./dist/${options.project}` ? './dist'
: `${offsetFromRoot(projectRoot)}dist/${projectRoot}`; : projectRoot === '.'
? `./dist/${options.project}`
: `${offsetFromRoot(projectRoot)}dist/${projectRoot}`;
const buildOption = onlyVitest const buildOption = onlyVitest
? '' ? ''
: options.includeLib : options.includeLib
? ` ? ` // Configuration for building your library.
// Configuration for building your library. // See: https://vitejs.dev/guide/build.html#library-mode
// See: https://vitejs.dev/guide/build.html#library-mode build: {
build: { outDir: '${buildOutDir}',
outDir: '${buildOutDir}', emptyOutDir: true,
emptyOutDir: true, reportCompressedSize: true,
reportCompressedSize: true, commonjsOptions: {
commonjsOptions: { transformMixedEsModules: true,
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,
},
}, },
`; 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 : []; const imports: string[] = options.imports ? options.imports : [];
@ -432,14 +432,14 @@ export function createOrEditViteConfig(
: `${offsetFromRoot(projectRoot)}coverage/${projectRoot}`; : `${offsetFromRoot(projectRoot)}coverage/${projectRoot}`;
const testOption = options.includeVitest const testOption = options.includeVitest
? `test: { ? ` test: {
watch: false, watch: false,
globals: true, globals: true,
environment: '${options.testEnvironment ?? 'jsdom'}', 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 options.inSourceTests
? `includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],` ? `
includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],`
: '' : ''
} }
reporters: ['default'], reporters: ['default'],
@ -453,7 +453,7 @@ export function createOrEditViteConfig(
: ''; : '';
const defineOption = options.inSourceTests const defineOption = options.inSourceTests
? `define: { ? ` define: {
'import.meta.vitest': undefined 'import.meta.vitest': undefined
},` },`
: ''; : '';
@ -462,27 +462,24 @@ export function createOrEditViteConfig(
? '' ? ''
: options.includeLib : options.includeLib
? '' ? ''
: ` : ` server:{
server:{ port: 4200,
port: 4200, host: 'localhost',
host: 'localhost', },`;
},`;
const previewServerOption = onlyVitest const previewServerOption = onlyVitest
? '' ? ''
: options.includeLib : options.includeLib
? '' ? ''
: ` : ` preview:{
preview:{ port: 4300,
port: 4300, host: 'localhost',
host: 'localhost', },`;
},`;
const workerOption = ` const workerOption = ` // Uncomment this if you are using workers.
// Uncomment this if you are using workers. // worker: {
// worker: { // plugins: [ nxViteTsPaths() ],
// plugins: [ nxViteTsPaths() ], // },`;
// },`;
const cacheDir = `cacheDir: '${normalizedJoinPaths( const cacheDir = `cacheDir: '${normalizedJoinPaths(
offsetFromRoot(projectRoot), offsetFromRoot(projectRoot),
@ -510,29 +507,34 @@ export function createOrEditViteConfig(
return; return;
} }
viteConfigContent = ` viteConfigContent = `/// <reference types='vitest' />
/// <reference types='vitest' /> import { defineConfig } from 'vite';
import { defineConfig } from 'vite'; ${imports.join(';\n')}${imports.length ? ';' : ''}
${imports.join(';\n')}${imports.length ? ';' : ''} import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
export default defineConfig({ root: __dirname,
root: __dirname, ${printOptions(
${cacheDir} cacheDir,
${devServerOption} devServerOption,
${previewServerOption} previewServerOption,
` plugins: [${plugins.join(', ')}],`,
plugins: [${plugins.join(',\n')}], workerOption,
${workerOption} buildOption,
${buildOption} defineOption,
${defineOption} testOption
${testOption} )}
});`; });
`.replace(/\s+(?=(\n|$))/gm, '\n');
tree.write(viteConfigPath, viteConfigContent); tree.write(viteConfigPath, viteConfigContent);
} }
function printOptions(...options: string[]): string {
return options.filter(Boolean).join('\n');
}
export function normalizeViteConfigFilePathWithTree( export function normalizeViteConfigFilePathWithTree(
tree: Tree, tree: Tree,
projectRoot: string, projectRoot: string,

View File

@ -47,7 +47,43 @@ describe('ensureViteConfigIsCorrect', () => {
'PropertyAssignment:has(Identifier[name="build"])' 'PropertyAssignment:has(Identifier[name="build"])'
); );
expect(buildNode).toBeDefined(); 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', () => { it('should add new build options if some build options already exist', () => {
@ -71,7 +107,32 @@ describe('ensureViteConfigIsCorrect', () => {
'PropertyAssignment:has(Identifier[name="build"])' 'PropertyAssignment:has(Identifier[name="build"])'
); );
expect(buildNode).toBeDefined(); 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', () => { it('should add build and test options if defineConfig is empty', () => {
@ -95,7 +156,39 @@ describe('ensureViteConfigIsCorrect', () => {
'PropertyAssignment:has(Identifier[name="build"])' 'PropertyAssignment:has(Identifier[name="build"])'
); );
expect(buildNode).toBeDefined(); expect(buildNode).toBeDefined();
expect(appFileContent).toMatchSnapshot(); expect(appFileContent).toMatchInlineSnapshot(`
"import dts from 'vite-plugin-dts';
import { joinPathFragments } from '@nx/devkit'
/// <reference types="vitest" />
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', () => { 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"])' 'PropertyAssignment:has(Identifier[name="build"])'
); );
expect(buildNode).toBeDefined(); 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', () => { it('should not do anything if cannot understand syntax of vite config', () => {
@ -179,7 +304,44 @@ describe('ensureViteConfigIsCorrect', () => {
{ build: true, test: true, serve: true } { build: true, test: true, serve: true }
); );
const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); 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', () => { 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 } { build: false, test: true, serve: true }
); );
const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); 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', () => { it('should update both test and build options - keep existing settings', () => {
@ -215,6 +414,31 @@ describe('ensureViteConfigIsCorrect', () => {
{ build: false, test: false, serve: true } { build: false, test: false, serve: true }
); );
const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); 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"]},
}
});
"
`);
}); });
}); });

View File

@ -91,19 +91,20 @@ function handleBuildOrTestNode(
propName !== 'reportsDirectory' && propName !== 'reportsDirectory' &&
propName !== 'provider' 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( for (const [propName, propValue] of Object.entries(
configContentObject configContentObject
)) { )) {
updatedPropsString += `'${propName}': ${JSON.stringify( // NOTE: Watch for formatting.
updatedPropsString += ` '${propName}': ${JSON.stringify(
propValue propValue
)},\n`; )},\n`;
} }
return `${name}: { return `${name}: {
${updatedPropsString} ${updatedPropsString} }`;
}`;
} }
); );
} else { } else {
@ -324,7 +325,7 @@ function handlePluginNode(
const existingPluginNodes = found?.[0].elements ?? []; const existingPluginNodes = found?.[0].elements ?? [];
for (const plugin of existingPluginNodes) { for (const plugin of existingPluginNodes) {
updatedPluginsString += `${plugin.getText()},\n`; updatedPluginsString += `${plugin.getText()}, `;
} }
for (const plugin of plugins) { for (const plugin of plugins) {
@ -333,7 +334,7 @@ function handlePluginNode(
node.getText().includes(plugin) node.getText().includes(plugin)
) )
) { ) {
updatedPluginsString += `${plugin},\n`; updatedPluginsString += `${plugin}, `;
} }
} }
@ -371,7 +372,7 @@ function handlePluginNode(
{ {
type: ChangeType.Insert, type: ChangeType.Insert,
index: propertyAssignments[0].getStart(), index: propertyAssignments[0].getStart(),
text: `plugins: [${plugins.join(',\n')}],`, text: `plugins: [${plugins.join(', ')}],`,
}, },
]); ]);
writeFile = true; writeFile = true;
@ -380,7 +381,7 @@ function handlePluginNode(
{ {
type: ChangeType.Insert, type: ChangeType.Insert,
index: foundDefineConfig[0].getStart() + 14, index: foundDefineConfig[0].getStart() + 14,
text: `plugins: [${plugins.join(',\n')}],`, text: `plugins: [${plugins.join(', ')}],`,
}, },
]); ]);
writeFile = true; writeFile = true;
@ -400,7 +401,7 @@ function handlePluginNode(
{ {
type: ChangeType.Insert, type: ChangeType.Insert,
index: startOfObject + 1, index: startOfObject + 1,
text: `plugins: [${plugins.join(',\n')}],`, text: `plugins: [${plugins.join(', ')}],`,
}, },
]); ]);
writeFile = true; writeFile = true;
@ -411,7 +412,7 @@ function handlePluginNode(
} }
if (writeFile) { if (writeFile) {
const filteredImports = filterImport(appFileContent, imports); const filteredImports = filterImport(appFileContent, imports);
return filteredImports.join(';') + '\n' + appFileContent; return filteredImports.join(';\n') + '\n' + appFileContent;
} }
} }

View File

@ -49,24 +49,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/test', cacheDir: '../node_modules/.vite/test',
server: { server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview: { preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
build: { build: {
outDir: '../dist/test', outDir: '../dist/test',
emptyOutDir: true, emptyOutDir: true,
@ -75,13 +70,11 @@ export default defineConfig({
transformMixedEsModules: true, transformMixedEsModules: true,
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/test', reportsDirectory: '../coverage/test',
@ -202,24 +195,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/test', cacheDir: '../node_modules/.vite/test',
server: { server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview: { preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
build: { build: {
outDir: '../dist/test', outDir: '../dist/test',
emptyOutDir: true, emptyOutDir: true,
@ -228,13 +216,11 @@ export default defineConfig({
transformMixedEsModules: true, transformMixedEsModules: true,
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/test', reportsDirectory: '../coverage/test',

View File

@ -12,7 +12,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/my-lib', cacheDir: '../node_modules/.vite/my-lib',
plugins: [ plugins: [
vue(), vue(),
nxViteTsPaths(), nxViteTsPaths(),
@ -22,12 +21,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}), }),
], ],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
// Configuration for building your library. // Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode // See: https://vitejs.dev/guide/build.html#library-mode
build: { build: {
@ -51,13 +48,11 @@ export default defineConfig({
external: [], external: [],
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/my-lib', reportsDirectory: '../coverage/my-lib',
@ -108,7 +103,6 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/my-lib', cacheDir: '../node_modules/.vite/my-lib',
plugins: [ plugins: [
vue(), vue(),
nxViteTsPaths(), nxViteTsPaths(),
@ -118,12 +112,10 @@ export default defineConfig({
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}), }),
], ],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
// Configuration for building your library. // Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode // See: https://vitejs.dev/guide/build.html#library-mode
build: { build: {
@ -147,13 +139,11 @@ export default defineConfig({
external: [], external: [],
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/my-lib', reportsDirectory: '../coverage/my-lib',

View File

@ -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) [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 ## 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 \`\`\`sh
npx nx <target> <project-name> npx nx <target> <project-name>
\`\`\` \`\`\`
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. 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 &raquo;](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) [More about running tasks in the docs &raquo;](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 &raquo;](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) [Learn more about Nx release &raquo;](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 <plugin-name>\` 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 &raquo;](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry &raquo;](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) [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 ## 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! [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 ## 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 \`\`\`sh
npx nx <target> <project-name> npx nx <target> <project-name>
\`\`\` \`\`\`
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. 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 &raquo;](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) [More about running tasks in the docs &raquo;](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 &raquo;](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) [Learn more about Nx release &raquo;](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 <plugin-name>\` 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 &raquo;](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry &raquo;](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Set up CI! ## Set up CI!
### Step 1 ### 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) [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 ## 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 \`\`\`sh
npx nx <target> <project-name> npx nx <target> <project-name>
\`\`\` \`\`\`
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. 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 &raquo;](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) [More about running tasks in the docs &raquo;](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 &raquo;](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) [Learn more about Nx release &raquo;](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 <plugin-name>\` 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 &raquo;](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry &raquo;](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) [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 ## Install Nx Console

View File

@ -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 %>) [Click here to finish setting up your workspace!](<%= nxCloudOnboardingUrl %>)
<% } %> <% } %><% if (!isEmptyRepo) { %>
## Run tasks ## Run tasks
<% if (!isEmptyRepo) { %><% if (isJsStandalone) { %> <% if (isJsStandalone) { %>
To build the library use: To build the library use:
```sh ```sh
npx nx build npx nx build
``` ```
<% } else { %> <% } else { %>
To run the dev server for your app, use: To run the dev server for your app, use:
```sh ```sh
@ -38,7 +38,29 @@ To see all available targets to run for a project, run:
```sh ```sh
npx nx show project <%= appName %> 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 <target> <project-name>
```
<% } else { %>
## Run tasks
To run tasks with Nx use: To run tasks with Nx use:
```sh ```sh
@ -66,7 +88,25 @@ npx nx release
Pass `--dry-run` to see what would happen without actually releasing the library. Pass `--dry-run` to see what would happen without actually releasing the library.
[Learn more about Nx release &raquo;](hhttps://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) [Learn more about Nx release &raquo;](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 ## 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. 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.

View File

@ -84,6 +84,7 @@ export function generatePreset(host: Tree, opts: NormalizedSchema) {
opts.ssr ? `--ssr` : null, opts.ssr ? `--ssr` : null,
opts.prefix !== undefined ? `--prefix=${opts.prefix}` : null, opts.prefix !== undefined ? `--prefix=${opts.prefix}` : null,
opts.nxCloudToken ? `--nxCloudToken=${opts.nxCloudToken}` : null, opts.nxCloudToken ? `--nxCloudToken=${opts.nxCloudToken}` : null,
opts.formatter ? `--formatter=${opts.formatter}` : null,
].filter((e) => !!e); ].filter((e) => !!e);
} }
} }

View File

@ -213,12 +213,12 @@ export async function generateWorkspaceFiles(
} }
function setPresetProperty(tree: Tree, options: NormalizedSchema) { 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'); addPropertyWithStableKeys(json, 'extends', 'nx/presets/npm.json');
} return json;
return json; });
}); }
} }
function createNxJson( function createNxJson(
@ -274,7 +274,10 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
options.preset === Preset.RemixStandalone || options.preset === Preset.RemixStandalone ||
options.preset === Preset.TsStandalone options.preset === Preset.TsStandalone
? './files-root-app' ? './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-package-based-repo'
: './files-integrated-repo'; : './files-integrated-repo';
generateFiles(tree, join(__dirname, filesDirName), options.directory, { generateFiles(tree, join(__dirname, filesDirName), options.directory, {
@ -309,6 +312,10 @@ async function createReadme(
generateFiles(tree, join(__dirname, './files-readme'), directory, { generateFiles(tree, join(__dirname, './files-readme'), directory, {
formattedNames, formattedNames,
isJsStandalone: preset === Preset.TsStandalone, isJsStandalone: preset === Preset.TsStandalone,
isTsPreset: preset === Preset.TS,
isUsingNewTsSolutionSetup:
process.env.NX_ADD_PLUGINS !== 'false' &&
process.env.NX_ADD_TS_PLUGIN === 'true',
isEmptyRepo: !appName, isEmptyRepo: !appName,
appName, appName,
generateAppCmd: presetInfo.generateAppCmd, generateAppCmd: presetInfo.generateAppCmd,
@ -405,7 +412,12 @@ function normalizeOptions(options: NormalizedSchema) {
} }
function setUpWorkspacesInPackageJson(tree: Tree, 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') { if (options.packageManager === 'pnpm') {
tree.write( tree.write(
join(options.directory, 'pnpm-workspace.yaml'), join(options.directory, 'pnpm-workspace.yaml'),

View File

@ -37,6 +37,7 @@ interface Schema {
prefix?: string; prefix?: string;
useGitHub?: boolean; useGitHub?: boolean;
nxCloud?: 'yes' | 'skip' | 'circleci' | 'github'; nxCloud?: 'yes' | 'skip' | 'circleci' | 'github';
formatter?: 'none' | 'prettier';
} }
export interface NormalizedSchema extends Schema { export interface NormalizedSchema extends Schema {

View File

@ -86,6 +86,12 @@
"prefix": { "prefix": {
"description": "The prefix to use for Angular component and directive selectors.", "description": "The prefix to use for Angular component and directive selectors.",
"type": "string" "type": "string"
},
"formatter": {
"description": "The tool to use for code formatting.",
"type": "string",
"enum": ["none", "prettier"],
"default": "none"
} }
}, },
"additionalProperties": true "additionalProperties": true

View File

@ -84,24 +84,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: './node_modules/.vite/proj', cacheDir: './node_modules/.vite/proj',
server: { server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview: { preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
build: { build: {
outDir: './dist/proj', outDir: './dist/proj',
emptyOutDir: true, emptyOutDir: true,
@ -110,13 +105,11 @@ export default defineConfig({
transformMixedEsModules: true, transformMixedEsModules: true,
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: './coverage/proj', reportsDirectory: './coverage/proj',
@ -176,24 +169,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../../node_modules/.vite/apps/proj', cacheDir: '../../node_modules/.vite/apps/proj',
server: { server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview: { preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
build: { build: {
outDir: '../../dist/apps/proj', outDir: '../../dist/apps/proj',
emptyOutDir: true, emptyOutDir: true,
@ -202,13 +190,11 @@ export default defineConfig({
transformMixedEsModules: true, transformMixedEsModules: true,
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../../coverage/apps/proj', reportsDirectory: '../../coverage/apps/proj',
@ -229,24 +215,19 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: './node_modules/.vite/proj', cacheDir: './node_modules/.vite/proj',
server: { server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview: { preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
// }, // },
build: { build: {
outDir: './dist/proj', outDir: './dist/proj',
emptyOutDir: true, emptyOutDir: true,
@ -255,13 +236,11 @@ export default defineConfig({
transformMixedEsModules: true, transformMixedEsModules: true,
}, },
}, },
test: { test: {
watch: false, watch: false,
globals: true, globals: true,
environment: 'jsdom', environment: '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}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: './coverage/proj', reportsDirectory: './coverage/proj',

View File

@ -275,7 +275,12 @@ async function createPreset(tree: Tree, options: Schema) {
}); });
} else if (options.preset === Preset.TS) { } else if (options.preset === Preset.TS) {
const { initGenerator } = require('@nx' + '/js'); 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) { } else if (options.preset === Preset.TsStandalone) {
const { libraryGenerator } = require('@nx' + '/js'); const { libraryGenerator } = require('@nx' + '/js');
return libraryGenerator(tree, { return libraryGenerator(tree, {

View File

@ -6,6 +6,7 @@ export interface Schema {
preset: Preset; preset: Preset;
style?: string; style?: string;
linter?: string; linter?: string;
formatter?: 'none' | 'prettier';
standaloneConfig?: boolean; standaloneConfig?: boolean;
framework?: string; framework?: string;
packageManager?: PackageManager; packageManager?: PackageManager;