feat(web): add support for TS solution setup for @nx/web (#29583)

This PR adds the new TS setup support to `@nx/web:app` generator.
Previously it errored out since it was not handled.

## Current Behavior
Cannot generate webapp in new setup/

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Can generate webapp in new setup

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

Fixes #
This commit is contained in:
Jack Hsu 2025-01-10 13:52:36 -05:00 committed by GitHub
parent d08ad7504f
commit 42d9e8bcb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 320 additions and 90 deletions

View File

@ -51,8 +51,8 @@
"bundler": { "bundler": {
"type": "string", "type": "string",
"description": "The bundler to use.", "description": "The bundler to use.",
"enum": ["webpack", "none", "vite"], "enum": ["vite", "webpack", "none"],
"default": "webpack", "default": "vite",
"x-prompt": "Which bundler do you want to use?", "x-prompt": "Which bundler do you want to use?",
"x-priority": "important" "x-priority": "important"
}, },
@ -60,7 +60,8 @@
"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": ["eslint", "none"],
"default": "eslint" "default": "none",
"x-prompt": "Which linter would you like to use?"
}, },
"skipFormat": { "skipFormat": {
"description": "Skip formatting files", "description": "Skip formatting files",
@ -71,8 +72,9 @@
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["vitest", "jest", "none"], "enum": ["vitest", "jest", "none"],
"default": "vitest", "default": "none",
"description": "Test runner to use for unit tests. Default value is 'jest' when using 'webpack' or 'none' as the bundler and 'vitest' when using 'vite' as the bundler" "description": "Test runner to use for unit tests. Default value is 'jest' when using 'webpack' or 'none' as the bundler and 'vitest' when using 'vite' as the bundler",
"x-prompt": "What unit test runner should be used?"
}, },
"inSourceTests": { "inSourceTests": {
"type": "boolean", "type": "boolean",
@ -99,12 +101,6 @@
"type": "boolean", "type": "boolean",
"description": "Creates an application with strict mode and strict type checking.", "description": "Creates an application with strict mode and strict type checking.",
"default": true "default": true
},
"standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside workspace.json",
"type": "boolean",
"default": true,
"x-deprecated": "Nx only supports standaloneConfig"
} }
}, },
"required": ["directory"], "required": ["directory"],

View File

@ -22,7 +22,9 @@ describe('file-server', () => {
const appName = uniq('app'); const appName = uniq('app');
const port = 4301; const port = 4301;
runCLI(`generate @nx/web:app apps/${appName} --no-interactive`); runCLI(
`generate @nx/web:app apps/${appName} --no-interactive --bundler=webpack`
);
updateJson(join('apps', appName, 'project.json'), (config) => { updateJson(join('apps', appName, 'project.json'), (config) => {
config.targets['serve'] = { config.targets['serve'] = {
executor: '@nx/web:file-server', executor: '@nx/web:file-server',
@ -52,7 +54,9 @@ describe('file-server', () => {
const appName = uniq('app'); const appName = uniq('app');
const port = 4301; const port = 4301;
runCLI(`generate @nx/web:app apps/${appName} --no-interactive`); runCLI(
`generate @nx/web:app apps/${appName} --no-interactive --bundler=webpack`
);
// Used to copy index.html rather than the normal webpack build. // Used to copy index.html rather than the normal webpack build.
updateFile( updateFile(
`apps/${appName}/copy-index.js`, `apps/${appName}/copy-index.js`,

View File

@ -19,7 +19,7 @@ describe('Web Components Applications with bundler set as vite', () => {
it('should be able to generate a web app', async () => { it('should be able to generate a web app', async () => {
const appName = uniq('app'); const appName = uniq('app');
runCLI( runCLI(
`generate @nx/web:app apps/${appName} --bundler=vite --no-interactive` `generate @nx/web:app apps/${appName} --bundler=vite --no-interactive --linter=eslint --unitTestRunner=vitest`
); );
const lintResults = runCLI(`lint ${appName}`); const lintResults = runCLI(`lint ${appName}`);
@ -50,10 +50,10 @@ describe('Web Components Applications with bundler set as vite', () => {
const libName = uniq('lib'); const libName = uniq('lib');
runCLI( runCLI(
`generate @nx/web:app apps/${appName} --bundler=vite --no-interactive` `generate @nx/web:app apps/${appName} --bundler=vite --no-interactive --linter=eslint --unitTestRunner=vitest`
); );
runCLI( runCLI(
`generate @nx/react:lib libs/${libName} --bundler=vite --no-interactive --unitTestRunner=vitest` `generate @nx/react:lib libs/${libName} --bundler=vite --no-interactive --unitTestRunner=vitest --linter=eslint`
); );
createFile(`dist/apps/${appName}/_should_remove.txt`); createFile(`dist/apps/${appName}/_should_remove.txt`);

View File

@ -26,7 +26,7 @@ describe('Web Components Applications', () => {
it('should be able to generate a web app', async () => { it('should be able to generate a web app', async () => {
const appName = uniq('app'); const appName = uniq('app');
runCLI( runCLI(
`generate @nx/web:app apps/${appName} --bundler=webpack --no-interactive` `generate @nx/web:app apps/${appName} --bundler=webpack --no-interactive --unitTestRunner=vitest --linter=eslint`
); );
const lintResults = runCLI(`lint ${appName}`); const lintResults = runCLI(`lint ${appName}`);
@ -110,7 +110,7 @@ describe('Web Components Applications', () => {
it('should emit decorator metadata when --compiler=babel and it is enabled in tsconfig', async () => { it('should emit decorator metadata when --compiler=babel and it is enabled in tsconfig', async () => {
const appName = uniq('app'); const appName = uniq('app');
runCLI( runCLI(
`generate @nx/web:app apps/${appName} --bundler=webpack --compiler=babel --no-interactive` `generate @nx/web:app apps/${appName} --bundler=webpack --compiler=babel --no-interactive --unitTestRunner=vitest --linter=eslint`
); );
updateFile(`apps/${appName}/src/app/app.element.ts`, (content) => { updateFile(`apps/${appName}/src/app/app.element.ts`, (content) => {
@ -175,7 +175,7 @@ describe('Web Components Applications', () => {
it('should emit decorator metadata when using --compiler=swc', async () => { it('should emit decorator metadata when using --compiler=swc', async () => {
const appName = uniq('app'); const appName = uniq('app');
runCLI( runCLI(
`generate @nx/web:app apps/${appName} --bundler=webpack --compiler=swc --no-interactive` `generate @nx/web:app apps/${appName} --bundler=webpack --compiler=swc --no-interactive --unitTestRunner=vitest --linter=eslint`
); );
updateFile(`apps/${appName}/src/app/app.element.ts`, (content) => { updateFile(`apps/${appName}/src/app/app.element.ts`, (content) => {
@ -223,7 +223,7 @@ describe('Web Components Applications', () => {
const appName = uniq('app1'); const appName = uniq('app1');
runCLI( runCLI(
`generate @nx/web:app ${appName} --bundler=webpack --no-interactive` `generate @nx/web:app ${appName} --bundler=webpack --no-interactive --unitTestRunner=vitest --linter=eslint`
); );
// check files are generated without the layout directory ("apps/") and // check files are generated without the layout directory ("apps/") and
@ -285,7 +285,7 @@ describe('CLI - Environment Variables', () => {
`; `;
runCLI( runCLI(
`generate @nx/web:app apps/${appName} --bundler=webpack --no-interactive --compiler=babel` `generate @nx/web:app apps/${appName} --bundler=webpack --no-interactive --compiler=babel --unitTestRunner=vitest --linter=eslint`
); );
const content = readFile(main); const content = readFile(main);
@ -310,7 +310,7 @@ describe('CLI - Environment Variables', () => {
const newCode2 = `const envVars = [process.env.NODE_ENV, process.env.NX_PUBLIC_WS_BASE, process.env.NX_PUBLIC_WS_ENV_LOCAL, process.env.NX_PUBLIC_WS_LOCAL_ENV, process.env.NX_PUBLIC_APP_BASE, process.env.NX_PUBLIC_APP_ENV_LOCAL, process.env.NX_PUBLIC_APP_LOCAL_ENV, process.env.NX_PUBLIC_SHARED_ENV];`; const newCode2 = `const envVars = [process.env.NODE_ENV, process.env.NX_PUBLIC_WS_BASE, process.env.NX_PUBLIC_WS_ENV_LOCAL, process.env.NX_PUBLIC_WS_LOCAL_ENV, process.env.NX_PUBLIC_APP_BASE, process.env.NX_PUBLIC_APP_ENV_LOCAL, process.env.NX_PUBLIC_APP_LOCAL_ENV, process.env.NX_PUBLIC_SHARED_ENV];`;
runCLI( runCLI(
`generate @nx/web:app apps/${appName2} --bundler=webpack --no-interactive --compiler=babel` `generate @nx/web:app apps/${appName2} --bundler=webpack --no-interactive --compiler=babel --unitTestRunner=vitest --linter=eslint`
); );
const content2 = readFile(main2); const content2 = readFile(main2);
@ -351,7 +351,7 @@ describe('index.html interpolation', () => {
const appName = uniq('app'); const appName = uniq('app');
runCLI( runCLI(
`generate @nx/web:app apps/${appName} --bundler=webpack --no-interactive` `generate @nx/web:app apps/${appName} --bundler=webpack --no-interactive --unitTestRunner=vitest --linter=eslint`
); );
const srcPath = `apps/${appName}/src`; const srcPath = `apps/${appName}/src`;

View File

@ -5,7 +5,9 @@ import {
readNxJson, readNxJson,
readProjectConfiguration, readProjectConfiguration,
Tree, Tree,
updateJson,
updateNxJson, updateNxJson,
writeJson,
} from '@nx/devkit'; } from '@nx/devkit';
import { getProjects, readJson } from '@nx/devkit'; import { getProjects, readJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
@ -694,4 +696,161 @@ describe('app', () => {
).toBe(false); ).toBe(false);
}); });
}); });
describe('TS solution setup', () => {
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
updateJson(tree, 'package.json', (json) => {
json.workspaces = ['packages/*', 'apps/*'];
return json;
});
writeJson(tree, 'tsconfig.base.json', {
compilerOptions: {
composite: true,
declaration: true,
},
});
writeJson(tree, 'tsconfig.json', {
extends: './tsconfig.base.json',
files: [],
references: [],
});
});
it('should add project references when using TS solution', async () => {
await applicationGenerator(tree, {
directory: 'apps/myapp',
addPlugin: true,
linter: 'none',
style: 'none',
bundler: 'vite',
unitTestRunner: 'vitest',
e2eTestRunner: 'playwright',
});
expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(`
[
{
"path": "./apps/myapp-e2e",
},
{
"path": "./apps/myapp",
},
]
`);
expect(readJson(tree, 'apps/myapp/tsconfig.json')).toMatchInlineSnapshot(`
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json",
},
{
"path": "./tsconfig.spec.json",
},
],
}
`);
expect(readJson(tree, 'apps/myapp/tsconfig.app.json'))
.toMatchInlineSnapshot(`
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"outDir": "out-tsc/myapp",
"rootDir": "src",
"tsBuildInfoFile": "out-tsc/myapp/tsconfig.app.tsbuildinfo",
"types": [
"node",
],
},
"exclude": [
"out-tsc",
"dist",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
],
"extends": "../../tsconfig.base.json",
"include": [
"src/**/*.ts",
],
}
`);
expect(readJson(tree, 'apps/myapp/tsconfig.spec.json'))
.toMatchInlineSnapshot(`
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest",
],
},
"extends": "../../tsconfig.base.json",
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts",
],
"references": [
{
"path": "./tsconfig.app.json",
},
],
}
`);
expect(readJson(tree, 'apps/myapp-e2e/tsconfig.json'))
.toMatchInlineSnapshot(`
{
"compilerOptions": {
"allowJs": true,
"outDir": "out-tsc/playwright",
"sourceMap": false,
},
"exclude": [
"out-tsc",
"test-output",
],
"extends": "../../tsconfig.base.json",
"include": [
"**/*.ts",
"**/*.js",
"playwright.config.ts",
"src/**/*.spec.ts",
"src/**/*.spec.js",
"src/**/*.test.ts",
"src/**/*.test.js",
"src/**/*.d.ts",
],
}
`);
});
});
}); });

View File

@ -13,7 +13,6 @@ import {
readNxJson, readNxJson,
readProjectConfiguration, readProjectConfiguration,
runTasksInSerial, runTasksInSerial,
TargetConfiguration,
Tree, Tree,
updateJson, updateJson,
updateNxJson, updateNxJson,
@ -49,7 +48,12 @@ import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-com
import staticServeConfiguration from '../static-serve/static-serve-configuration'; import staticServeConfiguration from '../static-serve/static-serve-configuration';
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
import { E2EWebServerDetails } from '@nx/devkit/src/generators/e2e-web-server-info-utils'; import { E2EWebServerDetails } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import {
addProjectToTsSolutionWorkspace,
isUsingTsSolutionSetup,
updateTsconfigFiles,
} from '@nx/js/src/utils/typescript/ts-solution-setup';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
interface NormalizedSchema extends Schema { interface NormalizedSchema extends Schema {
projectName: string; projectName: string;
@ -57,9 +61,14 @@ interface NormalizedSchema extends Schema {
e2eProjectName: string; e2eProjectName: string;
e2eProjectRoot: string; e2eProjectRoot: string;
parsedTags: string[]; parsedTags: string[];
isUsingTsSolutionConfig: boolean;
} }
function createApplicationFiles(tree: Tree, options: NormalizedSchema) { function createApplicationFiles(tree: Tree, options: NormalizedSchema) {
const rootTsConfigPath = getRelativePathToRootTsConfig(
tree,
options.appProjectRoot
);
if (options.bundler === 'vite') { if (options.bundler === 'vite') {
generateFiles( generateFiles(
tree, tree,
@ -70,10 +79,7 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) {
...names(options.name), ...names(options.name),
tmpl: '', tmpl: '',
offsetFromRoot: offsetFromRoot(options.appProjectRoot), offsetFromRoot: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig( rootTsConfigPath,
tree,
options.appProjectRoot
),
} }
); );
} else { } else {
@ -86,10 +92,7 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) {
...names(options.name), ...names(options.name),
tmpl: '', tmpl: '',
offsetFromRoot: offsetFromRoot(options.appProjectRoot), offsetFromRoot: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig( rootTsConfigPath,
tree,
options.appProjectRoot
),
webpackPluginOptions: hasWebpackPlugin(tree) webpackPluginOptions: hasWebpackPlugin(tree)
? { ? {
compiler: options.compiler, compiler: options.compiler,
@ -116,6 +119,22 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) {
); );
} }
} }
if (options.isUsingTsSolutionConfig) {
updateJson(
tree,
joinPathFragments(options.appProjectRoot, 'tsconfig.json'),
() => ({
extends: rootTsConfigPath,
files: [],
include: [],
references: [
{
path: './tsconfig.app.json',
},
],
})
);
} else {
updateJson( updateJson(
tree, tree,
joinPathFragments(options.appProjectRoot, 'tsconfig.json'), joinPathFragments(options.appProjectRoot, 'tsconfig.json'),
@ -130,6 +149,7 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) {
} }
); );
} }
}
async function setupBundler(tree: Tree, options: NormalizedSchema) { async function setupBundler(tree: Tree, options: NormalizedSchema) {
const main = joinPathFragments(options.appProjectRoot, 'src/main.ts'); const main = joinPathFragments(options.appProjectRoot, 'src/main.ts');
@ -205,6 +225,7 @@ async function setupBundler(tree: Tree, options: NormalizedSchema) {
} else if (options.bundler === 'none') { } else if (options.bundler === 'none') {
const project = readProjectConfiguration(tree, options.projectName); const project = readProjectConfiguration(tree, options.projectName);
addBuildTargetDefaults(tree, `@nx/js:${options.compiler}`); addBuildTargetDefaults(tree, `@nx/js:${options.compiler}`);
project.targets ??= {};
project.targets.build = { project.targets.build = {
executor: `@nx/js:${options.compiler}`, executor: `@nx/js:${options.compiler}`,
outputs: ['{options.outputPath}'], outputs: ['{options.outputPath}'],
@ -221,20 +242,27 @@ async function setupBundler(tree: Tree, options: NormalizedSchema) {
} }
async function addProject(tree: Tree, options: NormalizedSchema) { async function addProject(tree: Tree, options: NormalizedSchema) {
const targets: Record<string, TargetConfiguration> = {}; if (options.isUsingTsSolutionConfig) {
writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), {
addProjectConfiguration( name: getImportPath(tree, options.name),
tree, version: '0.0.1',
options.projectName, private: true,
{ nx: {
name: options.name,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
});
} else {
addProjectConfiguration(tree, options.projectName, {
projectType: 'application', projectType: 'application',
root: options.appProjectRoot, root: options.appProjectRoot,
sourceRoot: joinPathFragments(options.appProjectRoot, 'src'), sourceRoot: joinPathFragments(options.appProjectRoot, 'src'),
tags: options.parsedTags, tags: options.parsedTags,
targets, targets: {},
}, });
options.standaloneConfig }
);
} }
function setDefaults(tree: Tree, options: NormalizedSchema) { function setDefaults(tree: Tree, options: NormalizedSchema) {
@ -258,8 +286,6 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
} }
export async function applicationGeneratorInternal(host: Tree, schema: Schema) { export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
assertNotUsingTsSolutionSetup(host, 'web', 'application');
const options = await normalizeOptions(host, schema); const options = await normalizeOptions(host, schema);
const tasks: GeneratorCallback[] = []; const tasks: GeneratorCallback[] = [];
@ -432,6 +458,22 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
>('@nx/cypress', nxVersion); >('@nx/cypress', nxVersion);
if (options.isUsingTsSolutionConfig) {
writeJson(
host,
joinPathFragments(options.e2eProjectRoot, 'package.json'),
{
name: options.e2eProjectName,
version: '0.0.1',
private: true,
nx: {
projectType: 'application',
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
implicitDependencies: [options.projectName],
},
}
);
} else {
addProjectConfiguration(host, options.e2eProjectName, { addProjectConfiguration(host, options.e2eProjectName, {
root: options.e2eProjectRoot, root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
@ -440,6 +482,7 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
tags: [], tags: [],
implicitDependencies: [options.projectName], implicitDependencies: [options.projectName],
}); });
}
const cypressTask = await configurationGenerator(host, { const cypressTask = await configurationGenerator(host, {
...options, ...options,
project: options.e2eProjectName, project: options.e2eProjectName,
@ -493,6 +536,22 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
const { configurationGenerator: playwrightConfigGenerator } = ensurePackage< const { configurationGenerator: playwrightConfigGenerator } = ensurePackage<
typeof import('@nx/playwright') typeof import('@nx/playwright')
>('@nx/playwright', nxVersion); >('@nx/playwright', nxVersion);
if (options.isUsingTsSolutionConfig) {
writeJson(
host,
joinPathFragments(options.e2eProjectRoot, 'package.json'),
{
name: options.e2eProjectName,
version: '0.0.1',
private: true,
nx: {
projectType: 'application',
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
implicitDependencies: [options.projectName],
},
}
);
} else {
addProjectConfiguration(host, options.e2eProjectName, { addProjectConfiguration(host, options.e2eProjectName, {
root: options.e2eProjectRoot, root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
@ -501,6 +560,7 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
tags: [], tags: [],
implicitDependencies: [options.projectName], implicitDependencies: [options.projectName],
}); });
}
const playwrightTask = await playwrightConfigGenerator(host, { const playwrightTask = await playwrightConfigGenerator(host, {
project: options.e2eProjectName, project: options.e2eProjectName,
skipFormat: true, skipFormat: true,
@ -592,7 +652,24 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
) )
); );
if (!schema.skipFormat) { updateTsconfigFiles(
host,
options.appProjectRoot,
'tsconfig.app.json',
{
module: 'esnext',
moduleResolution: 'bundler',
},
options.linter === 'eslint'
? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs']
: undefined
);
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
}
if (!options.skipFormat) {
await formatFiles(host); await formatFiles(host);
} }
@ -622,6 +699,7 @@ async function normalizeOptions(
const e2eProjectName = `${appProjectName}-e2e`; const e2eProjectName = `${appProjectName}-e2e`;
const e2eProjectRoot = `${appProjectRoot}-e2e`; const e2eProjectRoot = `${appProjectRoot}-e2e`;
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
const npmScope = getNpmScope(host); const npmScope = getNpmScope(host);
@ -646,6 +724,7 @@ async function normalizeOptions(
e2eProjectRoot, e2eProjectRoot,
e2eProjectName, e2eProjectName,
parsedTags, parsedTags,
isUsingTsSolutionConfig,
}; };
} }

View File

@ -13,7 +13,6 @@ export interface Schema {
inSourceTests?: boolean; inSourceTests?: boolean;
e2eTestRunner?: 'cypress' | 'playwright' | 'none'; e2eTestRunner?: 'cypress' | 'playwright' | 'none';
linter?: Linter | LinterType; linter?: Linter | LinterType;
standaloneConfig?: boolean;
setParserOptionsProject?: boolean; setParserOptionsProject?: boolean;
strict?: boolean; strict?: boolean;
addPlugin?: boolean; addPlugin?: boolean;

View File

@ -54,8 +54,8 @@
"bundler": { "bundler": {
"type": "string", "type": "string",
"description": "The bundler to use.", "description": "The bundler to use.",
"enum": ["webpack", "none", "vite"], "enum": ["vite", "webpack", "none"],
"default": "webpack", "default": "vite",
"x-prompt": "Which bundler do you want to use?", "x-prompt": "Which bundler do you want to use?",
"x-priority": "important" "x-priority": "important"
}, },
@ -63,7 +63,8 @@
"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": ["eslint", "none"],
"default": "eslint" "default": "none",
"x-prompt": "Which linter would you like to use?"
}, },
"skipFormat": { "skipFormat": {
"description": "Skip formatting files", "description": "Skip formatting files",
@ -74,8 +75,9 @@
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["vitest", "jest", "none"], "enum": ["vitest", "jest", "none"],
"default": "vitest", "default": "none",
"description": "Test runner to use for unit tests. Default value is 'jest' when using 'webpack' or 'none' as the bundler and 'vitest' when using 'vite' as the bundler" "description": "Test runner to use for unit tests. Default value is 'jest' when using 'webpack' or 'none' as the bundler and 'vitest' when using 'vite' as the bundler",
"x-prompt": "What unit test runner should be used?"
}, },
"inSourceTests": { "inSourceTests": {
"type": "boolean", "type": "boolean",
@ -102,12 +104,6 @@
"type": "boolean", "type": "boolean",
"description": "Creates an application with strict mode and strict type checking.", "description": "Creates an application with strict mode and strict type checking.",
"default": true "default": true
},
"standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside workspace.json",
"type": "boolean",
"default": true,
"x-deprecated": "Nx only supports standaloneConfig"
} }
}, },
"required": ["directory"], "required": ["directory"],

View File

@ -6,7 +6,6 @@ import {
runTasksInSerial, runTasksInSerial,
Tree, Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { nxVersion } from '../../utils/versions'; import { nxVersion } from '../../utils/versions';
import { Schema } from './schema'; import { Schema } from './schema';
@ -27,8 +26,6 @@ function updateDependencies(tree: Tree, schema: Schema) {
} }
export async function webInitGenerator(tree: Tree, schema: Schema) { export async function webInitGenerator(tree: Tree, schema: Schema) {
assertNotUsingTsSolutionSetup(tree, 'web', 'init');
let installTask: GeneratorCallback = () => {}; let installTask: GeneratorCallback = () => {};
if (!schema.skipPackageJson) { if (!schema.skipPackageJson) {
installTask = updateDependencies(tree, schema); installTask = updateDependencies(tree, schema);