feat(nextjs): Add standalone Nextjs option to react selection when running CNW (#16317)

This commit is contained in:
Nicholas Cunningham 2023-04-19 13:29:31 -06:00 committed by GitHub
parent 16e115fd7b
commit 338dc64d91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 278 additions and 28 deletions

View File

@ -109,6 +109,12 @@ Type: `string`
Workspace name (e.g. org name) Workspace name (e.g. org name)
### nextAppDir
Type: `boolean`
Add Experimental app/ layout for next.js
### nxCloud ### nxCloud
Type: `boolean` Type: `boolean`
@ -129,7 +135,7 @@ Package manager to use
Type: `string` Type: `string`
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "react-native", "expo", "next", "nest", "express", "react", "angular", "node-standalone"]. To build your own see https://nx.dev/packages/nx-plugin#preset Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "nextjs-standalone", "react-native", "expo", "next", "nest", "express", "react", "angular", "node-standalone"]. To build your own see https://nx.dev/packages/nx-plugin#preset
### routing ### routing

View File

@ -121,6 +121,13 @@
"default": false, "default": false,
"description": "Enable experimental app directory for the project", "description": "Enable experimental app directory for the project",
"x-prompt": "Do you want to use experimental app/ in this project?" "x-prompt": "Do you want to use experimental app/ in this project?"
},
"rootProject": {
"description": "Create an application at the root of the workspace.",
"type": "boolean",
"default": false,
"hidden": true,
"x-priority": "internal"
} }
}, },
"required": [], "required": [],

View File

@ -36,6 +36,13 @@
"default": false, "default": false,
"description": "Do not add dependencies to `package.json`.", "description": "Do not add dependencies to `package.json`.",
"x-priority": "internal" "x-priority": "internal"
},
"rootProject": {
"description": "Create an application at the root of the workspace.",
"type": "boolean",
"default": false,
"hidden": true,
"x-priority": "internal"
} }
}, },
"required": [], "required": [],

View File

@ -109,6 +109,12 @@ Type: `string`
Workspace name (e.g. org name) Workspace name (e.g. org name)
### nextAppDir
Type: `boolean`
Add Experimental app/ layout for next.js
### nxCloud ### nxCloud
Type: `boolean` Type: `boolean`
@ -129,7 +135,7 @@ Package manager to use
Type: `string` Type: `string`
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "react-native", "expo", "next", "nest", "express", "react", "angular", "node-standalone"]. To build your own see https://nx.dev/packages/nx-plugin#preset Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "nextjs-standalone", "react-native", "expo", "next", "nest", "express", "react", "angular", "node-standalone"]. To build your own see https://nx.dev/packages/nx-plugin#preset
### routing ### routing

View File

@ -68,6 +68,11 @@
"description": "The framework which the application is using", "description": "The framework which the application is using",
"type": "string", "type": "string",
"enum": ["express", "koa", "fastify", "nest", "none"] "enum": ["express", "koa", "fastify", "nest", "none"]
},
"nextAppDir": {
"description": "Enable experimental app directory for the project",
"type": "boolean",
"default": false
} }
}, },
"additionalProperties": true, "additionalProperties": true,

View File

@ -80,6 +80,11 @@
"description": "Generate a Dockerfile", "description": "Generate a Dockerfile",
"type": "boolean", "type": "boolean",
"default": false "default": false
},
"nextAppDir": {
"description": "Enable experimental app/ for the project",
"type": "boolean",
"default": false
} }
}, },
"presets": [] "presets": []

View File

@ -21,7 +21,6 @@ import {
} from '@nrwl/e2e/utils'; } from '@nrwl/e2e/utils';
import * as http from 'http'; import * as http from 'http';
import { checkApp } from './utils'; import { checkApp } from './utils';
import { removeSync } from 'fs-extra';
describe('Next.js Applications', () => { describe('Next.js Applications', () => {
let proj: string; let proj: string;
@ -426,6 +425,27 @@ describe('Next.js Applications', () => {
checkExport: false, checkExport: false,
}); });
}, 300_000); }, 300_000);
it('should create a generate a next.js app with app layout enabled', async () => {
const appName = uniq('app');
runCLI(
`generate @nrwl/next:app ${appName} --style=css --appDir --no-interactive`
);
checkFilesExist(`apps/${appName}/app/api/hello/route.ts`);
checkFilesExist(`apps/${appName}/app/page.tsx`);
checkFilesExist(`apps/${appName}/app/layout.tsx`);
checkFilesExist(`apps/${appName}/app/global.css`);
checkFilesExist(`apps/${appName}/app/page.module.css`);
await checkApp(appName, {
checkUnitTest: false,
checkLint: false,
checkE2E: false,
checkExport: false,
});
}, 300_000);
}); });
function getData(port, path = ''): Promise<any> { function getData(port, path = ''): Promise<any> {

View File

@ -39,6 +39,7 @@ interface Arguments extends CreateWorkspaceOptions {
framework: Framework; framework: Framework;
standaloneApi: boolean; standaloneApi: boolean;
docker: boolean; docker: boolean;
nextAppDir: boolean;
routing: boolean; routing: boolean;
bundler: Bundler; bundler: Bundler;
} }
@ -104,6 +105,10 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
.option('docker', { .option('docker', {
describe: chalk.dim`Generate a Dockerfile with your node-server`, describe: chalk.dim`Generate a Dockerfile with your node-server`,
type: 'boolean', type: 'boolean',
})
.option('nextAppDir', {
describe: chalk.dim`Add Experimental app/ layout for next.js`,
type: 'boolean',
}), }),
withNxCloud, withNxCloud,
withCI, withCI,
@ -180,6 +185,7 @@ async function normalizeArgsMiddleware(
framework, framework,
bundler, bundler,
docker, docker,
nextAppDir,
routing, routing,
standaloneApi; standaloneApi;
@ -209,7 +215,7 @@ async function normalizeArgsMiddleware(
if (monorepoStyle === 'package-based') { if (monorepoStyle === 'package-based') {
preset = 'npm'; preset = 'npm';
} else if (monorepoStyle === 'react') { } else if (monorepoStyle === 'react') {
preset = Preset.ReactStandalone; preset = await determineReactFramework(argv);
} else if (monorepoStyle === 'angular') { } else if (monorepoStyle === 'angular') {
preset = Preset.AngularStandalone; preset = Preset.AngularStandalone;
} else if (monorepoStyle === 'node-standalone') { } else if (monorepoStyle === 'node-standalone') {
@ -228,7 +234,8 @@ async function normalizeArgsMiddleware(
if ( if (
preset === Preset.ReactStandalone || preset === Preset.ReactStandalone ||
preset === Preset.AngularStandalone || preset === Preset.AngularStandalone ||
preset === Preset.NodeStandalone preset === Preset.NodeStandalone ||
preset === Preset.NextJsStandalone
) { ) {
appName = appName =
argv.appName ?? argv.name ?? (await determineAppName(preset, argv)); argv.appName ?? argv.name ?? (await determineAppName(preset, argv));
@ -241,6 +248,10 @@ async function normalizeArgsMiddleware(
} }
} }
if (preset === Preset.NextJsStandalone) {
nextAppDir = await isNextAppDir(argv);
}
if (preset === Preset.ReactStandalone) { if (preset === Preset.ReactStandalone) {
bundler = await determineBundler(argv); bundler = await determineBundler(argv);
} }
@ -291,6 +302,7 @@ async function normalizeArgsMiddleware(
ci, ci,
bundler, bundler,
docker, docker,
nextAppDir,
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -363,27 +375,27 @@ async function determineMonorepoStyle(): Promise<string> {
{ {
name: 'package-based', name: 'package-based',
message: message:
'Package-based monorepo: Nx makes it fast, but lets you run things your way.', 'Package-based monorepo: Nx makes it fast, but lets you run things your way.',
}, },
{ {
name: 'integrated', name: 'integrated',
message: message:
'Integrated monorepo: Nx configures your favorite frameworks and lets you focus on shipping features.', 'Integrated monorepo: Nx configures your favorite frameworks and lets you focus on shipping features.',
}, },
{ {
name: 'react', name: 'react',
message: message:
'Standalone React app: Nx configures Vite (or Webpack), ESLint, and Cypress.', 'Standalone React app: Nx configures a React app with an optional framework (e.g. Next.js).',
}, },
{ {
name: 'angular', name: 'angular',
message: message:
'Standalone Angular app: Nx configures Jest, ESLint and Cypress.', 'Standalone Angular app: Nx configures Jest, ESLint and Cypress.',
}, },
{ {
name: 'node-standalone', name: 'node-standalone',
message: message:
'Standalone Node app: Nx configures a framework (ex. Express), esbuild, ESlint and Jest.', 'Standalone Node app: Nx configures a framework (e.g. Express), esbuild, ESlint and Jest.',
}, },
], ],
}, },
@ -582,6 +594,64 @@ async function determineDockerfile(
} }
} }
async function determineReactFramework(parsedArgs: yargs.Arguments<Arguments>) {
if (parsedArgs.framework) {
return parsedArgs.framework === 'next.js'
? Preset.NextJsStandalone
: Preset.ReactStandalone;
}
return enquirer
.prompt<{ framework: 'none' | 'next.js' }>([
{
name: 'framework',
message: 'What framework would you like to use?',
type: 'autocomplete',
choices: [
{
name: 'none',
message: 'None',
hint: 'I only want React',
},
{
name: 'next.js',
message: 'Next.js [https://nextjs.org/]',
},
],
initial: 'none' as any,
},
])
.then((choice) =>
choice.framework === 'next.js'
? Preset.NextJsStandalone
: Preset.ReactStandalone
);
}
async function isNextAppDir(parsedArgs: yargs.Arguments<Arguments>) {
if (parsedArgs.nextAppDir === undefined) {
return enquirer
.prompt<{ appDir: 'Yes' | 'No' }>([
{
name: 'appDir',
message: 'Do you want to use experimental app/ in this project?',
type: 'autocomplete',
choices: [
{
name: 'No',
},
{
name: 'Yes',
},
],
initial: 'No' as any,
},
])
.then((choice) => choice.appDir === 'Yes');
} else {
return Promise.resolve(parsedArgs.nextAppDir);
}
}
async function determineStyle( async function determineStyle(
preset: Preset, preset: Preset,
parsedArgs: yargs.Arguments<Arguments> parsedArgs: yargs.Arguments<Arguments>
@ -617,9 +687,12 @@ async function determineStyle(
]; ];
if ( if (
[Preset.ReactMonorepo, Preset.ReactStandalone, Preset.NextJs].includes( [
preset Preset.ReactMonorepo,
) Preset.ReactStandalone,
Preset.NextJs,
Preset.NextJsStandalone,
].includes(preset)
) { ) {
choices.push( choices.push(
{ {

View File

@ -31,6 +31,7 @@ export function pointToTutorialAndCourse(preset: Preset) {
break; break;
case Preset.ReactMonorepo: case Preset.ReactMonorepo:
case Preset.NextJs: case Preset.NextJs:
case Preset.NextJsStandalone:
output.addVerticalSeparator(); output.addVerticalSeparator();
output.note({ output.note({
title, title,

View File

@ -9,6 +9,7 @@ export enum Preset {
AngularStandalone = 'angular-standalone', AngularStandalone = 'angular-standalone',
ReactMonorepo = 'react-monorepo', ReactMonorepo = 'react-monorepo',
ReactStandalone = 'react-standalone', ReactStandalone = 'react-standalone',
NextJsStandalone = 'nextjs-standalone',
ReactNative = 'react-native', ReactNative = 'react-native',
Expo = 'expo', Expo = 'expo',
NextJs = 'next', NextJs = 'next',

View File

@ -11,7 +11,7 @@
const StyledPage = styled.div`<%- pageStyleContent %>`; const StyledPage = styled.div`<%- pageStyleContent %>`;
<% }%> <% }%>
export async function Index() { export default async function Index() {
/* /*
* Replace the elements below with your own. * Replace the elements below with your own.
* *
@ -23,6 +23,4 @@ export async function Index() {
<%- appContent %> <%- appContent %>
</<%= wrapper %>> </<%= wrapper %>>
); );
}; };
export default Index;

View File

@ -11,7 +11,8 @@
"noEmit": true, "noEmit": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"incremental": true "incremental": true,
"plugins": [{ "name": "next" }]
}, },
"include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"], "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"],
"exclude": ["node_modules", "jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] "exclude": ["node_modules", "jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]

View File

@ -15,7 +15,7 @@ export async function addCypress(host: Tree, options: NormalizedSchema) {
return cypressProjectGenerator(host, { return cypressProjectGenerator(host, {
...options, ...options,
linter: Linter.EsLint, linter: Linter.EsLint,
name: `${options.name}-e2e`, name: options.e2eProjectName,
directory: options.directory, directory: options.directory,
project: options.projectName, project: options.projectName,
skipFormat: true, skipFormat: true,

View File

@ -1,5 +1,12 @@
import { join } from 'path'; import { join } from 'path';
import { generateFiles, names, toJS, Tree } from '@nx/devkit'; import {
generateFiles,
names,
readJson,
toJS,
Tree,
updateJson,
} from '@nx/devkit';
import { getRelativePathToRootTsConfig } from '@nx/js'; import { getRelativePathToRootTsConfig } from '@nx/js';
import { NormalizedSchema } from './normalize-options'; import { NormalizedSchema } from './normalize-options';
@ -12,6 +19,7 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
const templateVariables = { const templateVariables = {
...names(options.name), ...names(options.name),
...options, ...options,
dot: '.',
tmpl: '', tmpl: '',
rootTsConfigPath: getRelativePathToRootTsConfig( rootTsConfigPath: getRelativePathToRootTsConfig(
host, host,
@ -49,6 +57,38 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
); );
} }
if (options.rootProject) {
updateJson(host, 'tsconfig.base.json', (json) => {
const appJSON = readJson(host, 'tsconfig.json');
let { extends: _, ...updatedJson } = json;
updatedJson = {
...updateJson,
compilerOptions: {
...updatedJson.compilerOptions,
...appJSON.compilerOptions,
},
include: [
...new Set([
...(updatedJson.include || []),
...(appJSON.include || []),
]),
],
exclude: [
...new Set([
...(updatedJson.exclude || []),
...(appJSON.exclude || []),
'**e2e/**/*',
]),
],
};
return updatedJson;
});
host.delete('tsconfig.json');
host.rename('tsconfig.base.json', 'tsconfig.json');
}
if (options.unitTestRunner === 'none') { if (options.unitTestRunner === 'none') {
host.delete(`${options.appProjectRoot}/specs/${options.fileName}.spec.tsx`); host.delete(`${options.appProjectRoot}/specs/${options.fileName}.spec.tsx`);
} }
@ -60,9 +100,13 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
} }
if (options.styledModule) { if (options.styledModule) {
host.delete( if (options.appDir) {
`${options.appProjectRoot}/pages/${options.fileName}.module.${options.style}` host.delete(`${options.appProjectRoot}/app/page.module.${options.style}`);
); } else {
host.delete(
`${options.appProjectRoot}/pages/${options.fileName}.module.${options.style}`
);
}
} }
if (options.style !== 'styled-components') { if (options.style !== 'styled-components') {

View File

@ -36,10 +36,15 @@ export function normalizeOptions(
const appsDir = layoutDirectory ?? getWorkspaceLayout(host).appsDir; const appsDir = layoutDirectory ?? getWorkspaceLayout(host).appsDir;
const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-'); const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-');
const e2eProjectName = `${appProjectName}-e2e`; const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
const appProjectRoot = joinPathFragments(appsDir, appDirectory); const appProjectRoot = options.rootProject
const e2eProjectRoot = joinPathFragments(appsDir, `${appDirectory}-e2e`); ? '.'
: joinPathFragments(appsDir, appDirectory);
const e2eProjectRoot = options.rootProject
? '.'
: joinPathFragments(appsDir, `${appDirectory}-e2e`);
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())

View File

@ -16,4 +16,5 @@ export interface Schema {
customServer?: boolean; customServer?: boolean;
skipPackageJson?: boolean; skipPackageJson?: boolean;
appDir?: boolean; appDir?: boolean;
rootProject?: boolean;
} }

View File

@ -124,6 +124,13 @@
"default": false, "default": false,
"description": "Enable experimental app directory for the project", "description": "Enable experimental app directory for the project",
"x-prompt": "Do you want to use experimental app/ in this project?" "x-prompt": "Do you want to use experimental app/ in this project?"
},
"rootProject": {
"description": "Create an application at the root of the workspace.",
"type": "boolean",
"default": false,
"hidden": true,
"x-priority": "internal"
} }
}, },
"required": [], "required": [],

View File

@ -65,6 +65,7 @@ export async function nextInitGenerator(host: Tree, schema: InitSchema) {
const reactTask = await reactInitGenerator(host, { const reactTask = await reactInitGenerator(host, {
...schema, ...schema,
skipFormat: true, skipFormat: true,
skipBabelConfig: true,
}); });
tasks.push(reactTask); tasks.push(reactTask);

View File

@ -4,4 +4,5 @@ export interface InitSchema {
skipFormat?: boolean; skipFormat?: boolean;
js?: boolean; js?: boolean;
skipPackageJson?: boolean; skipPackageJson?: boolean;
rootProject?: boolean;
} }

View File

@ -33,6 +33,13 @@
"default": false, "default": false,
"description": "Do not add dependencies to `package.json`.", "description": "Do not add dependencies to `package.json`.",
"x-priority": "internal" "x-priority": "internal"
},
"rootProject": {
"description": "Create an application at the root of the workspace.",
"type": "boolean",
"default": false,
"hidden": true,
"x-priority": "internal"
} }
}, },
"required": [] "required": []

View File

@ -230,6 +230,31 @@ Visit the [Nx Documentation](https://nx.dev) to learn more.
" "
`; `;
exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for NextJsStandalone preset 1`] = `
"# Proj
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
✨ **This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)** ✨
## Development server
Run \`nx serve app1\` for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.
## Understand this workspace
Run \`nx graph\` to see a diagram of the dependencies of the projects.
## Remote caching
Run \`npx nx connect-to-nx-cloud\` to enable [remote caching](https://nx.app) and make CI faster.
## Further help
Visit the [Nx Documentation](https://nx.dev) to learn more.
"
`;
exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for NodeStandalone preset 1`] = ` exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for NodeStandalone preset 1`] = `
"# Proj "# Proj

View File

@ -77,6 +77,7 @@ export function generatePreset(host: Tree, opts: NormalizedSchema) {
opts.bundler ? `--bundler=${opts.bundler}` : null, opts.bundler ? `--bundler=${opts.bundler}` : null,
opts.framework ? `--framework=${opts.framework}` : null, opts.framework ? `--framework=${opts.framework}` : null,
opts.docker ? `--docker=${opts.docker}` : null, opts.docker ? `--docker=${opts.docker}` : null,
opts.nextAppDir ? `--nextAppDir=${opts.nextAppDir}` : null,
opts.packageManager ? `--packageManager=${opts.packageManager}` : null, opts.packageManager ? `--packageManager=${opts.packageManager}` : null,
opts.standaloneApi !== undefined opts.standaloneApi !== undefined
? `--standaloneApi=${opts.standaloneApi}` ? `--standaloneApi=${opts.standaloneApi}`
@ -116,6 +117,7 @@ function getPresetDependencies({
}; };
case Preset.NextJs: case Preset.NextJs:
case Preset.NextJsStandalone:
return { dependencies: { '@nx/next': nxVersion }, dev: {} }; return { dependencies: { '@nx/next': nxVersion }, dev: {} };
case Preset.ReactMonorepo: case Preset.ReactMonorepo:

View File

@ -41,6 +41,7 @@ describe('@nx/workspace:generateWorkspaceFiles', () => {
Preset.WebComponents, Preset.WebComponents,
Preset.Express, Preset.Express,
Preset.NodeStandalone, Preset.NodeStandalone,
Preset.NextJsStandalone,
].includes(Preset[preset]) ].includes(Preset[preset])
) { ) {
appName = 'app1'; appName = 'app1';

View File

@ -67,6 +67,7 @@ function createAppsAndLibsFolders(tree: Tree, options: NormalizedSchema) {
options.preset === Preset.AngularStandalone || options.preset === Preset.AngularStandalone ||
options.preset === Preset.ReactStandalone || options.preset === Preset.ReactStandalone ||
options.preset === Preset.NodeStandalone || options.preset === Preset.NodeStandalone ||
options.preset === Preset.NextJsStandalone ||
options.isCustomPreset options.isCustomPreset
) { ) {
// don't generate any folders // don't generate any folders
@ -126,7 +127,8 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
const filesDirName = const filesDirName =
options.preset === Preset.AngularStandalone || options.preset === Preset.AngularStandalone ||
options.preset === Preset.ReactStandalone || options.preset === Preset.ReactStandalone ||
options.preset === Preset.NodeStandalone options.preset === Preset.NodeStandalone ||
options.preset === Preset.NextJsStandalone
? './files-root-app' ? './files-root-app'
: options.preset === Preset.NPM || options.preset === Preset.Core : options.preset === Preset.NPM || options.preset === Preset.Core
? './files-package-based-repo' ? './files-package-based-repo'
@ -177,7 +179,8 @@ function addNpmScripts(tree: Tree, options: NormalizedSchema) {
if ( if (
options.preset === Preset.AngularStandalone || options.preset === Preset.AngularStandalone ||
options.preset === Preset.ReactStandalone || options.preset === Preset.ReactStandalone ||
options.preset === Preset.NodeStandalone options.preset === Preset.NodeStandalone ||
options.preset === Preset.NextJsStandalone
) { ) {
updateJson(tree, join(options.directory, 'package.json'), (json) => { updateJson(tree, join(options.directory, 'package.json'), (json) => {
Object.assign(json.scripts, { Object.assign(json.scripts, {

View File

@ -24,6 +24,7 @@ interface Schema {
defaultBase: string; defaultBase: string;
framework?: string; framework?: string;
docker?: boolean; docker?: boolean;
nextAppDir?: boolean;
linter?: Linter; linter?: Linter;
bundler?: 'vite' | 'webpack'; bundler?: 'vite' | 'webpack';
standaloneApi?: boolean; standaloneApi?: boolean;

View File

@ -71,6 +71,11 @@
"description": "The framework which the application is using", "description": "The framework which the application is using",
"type": "string", "type": "string",
"enum": ["express", "koa", "fastify", "nest", "none"] "enum": ["express", "koa", "fastify", "nest", "none"]
},
"nextAppDir": {
"description": "Enable experimental app directory for the project",
"type": "boolean",
"default": false
} }
}, },
"additionalProperties": true "additionalProperties": true

View File

@ -79,6 +79,16 @@ async function createPreset(tree: Tree, options: Schema) {
style: options.style, style: options.style,
linter: options.linter, linter: options.linter,
}); });
} else if (options.preset === Preset.NextJsStandalone) {
const { applicationGenerator: nextApplicationGenerator } = require('@nx' +
'/next');
return nextApplicationGenerator(tree, {
name: options.name,
style: options.style,
linter: options.linter,
appDir: options.nextAppDir,
rootProject: true,
});
} else if (options.preset === Preset.WebComponents) { } else if (options.preset === Preset.WebComponents) {
const { applicationGenerator: webApplicationGenerator } = require('@nx' + const { applicationGenerator: webApplicationGenerator } = require('@nx' +
'/web'); '/web');

View File

@ -12,6 +12,7 @@ export interface Schema {
packageManager?: PackageManager; packageManager?: PackageManager;
bundler?: 'vite' | 'webpack' | 'rspack'; bundler?: 'vite' | 'webpack' | 'rspack';
docker?: boolean; docker?: boolean;
nextAppDir?: boolean;
routing?: boolean; routing?: boolean;
standaloneApi?: boolean; standaloneApi?: boolean;
} }

View File

@ -83,6 +83,11 @@
"description": "Generate a Dockerfile", "description": "Generate a Dockerfile",
"type": "boolean", "type": "boolean",
"default": false "default": false
},
"nextAppDir": {
"description": "Enable experimental app/ for the project",
"type": "boolean",
"default": false
} }
} }
} }

View File

@ -9,6 +9,7 @@ export enum Preset {
AngularStandalone = 'angular-standalone', AngularStandalone = 'angular-standalone',
ReactMonorepo = 'react-monorepo', ReactMonorepo = 'react-monorepo',
ReactStandalone = 'react-standalone', ReactStandalone = 'react-standalone',
NextJsStandalone = 'nextjs-standalone',
ReactNative = 'react-native', ReactNative = 'react-native',
Expo = 'expo', Expo = 'expo',
NextJs = 'next', NextJs = 'next',