chore(node): refactor application generator for more clarity (#31523)

## Current Behavior

The Node.js application generator
(`packages/node/src/generators/application/application.ts`) is
implemented as a single large file containing ~469 lines of code. All
generator logic is mixed together in one file including:

- Option normalization and validation
- Project configuration creation  
- File generation
- Build/serve target setup
- Dependency management
- ESLint configuration
- Proxy setup

This makes the code harder to maintain, test, and understand as the file
handles multiple responsibilities.

## Expected Behavior

The generator is now refactored into smaller, focused modules organized
in a `lib/` directory:

- `normalize-options.ts` - handles option normalization and validation
- `normalized-schema.ts` - defines the normalized schema interface  
- `create-project.ts` - handles project configuration creation (exported
as `addProject`)
- `create-files.ts` - handles file generation (exported as
`addAppFiles`)
- `create-targets.ts` - handles build/serve target configuration
- `add-dependencies.ts` - handles dependency management (exported as
`addProjectDependencies`)
- `add-linting.ts` - handles ESLint setup (exported as
`addLintingToApplication`)
- `add-proxy.ts` - handles proxy configuration (exported as `addProxy`)
- `index.ts` - exports all the functions

The main `application.ts` file is now much cleaner at ~15 lines,
focusing on orchestrating the generator workflow by calling the
extracted functions. This separation of concerns improves:

- **Maintainability**: Each file has a single responsibility
- **Testability**: Individual functions can be tested in isolation
- **Readability**: Easier to understand what each part does
- **Reusability**: Functions can be potentially reused by other
generators
This commit is contained in:
Colum Ferry 2025-06-10 16:57:46 +01:00 committed by GitHub
parent 8bfa9f90de
commit 8daad98992
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 521 additions and 469 deletions

View File

@ -1,429 +1,37 @@
import { import {
addDependenciesToPackageJson,
addProjectConfiguration,
ensurePackage, ensurePackage,
formatFiles, formatFiles,
generateFiles,
GeneratorCallback, GeneratorCallback,
joinPathFragments, joinPathFragments,
logger,
names,
offsetFromRoot,
ProjectConfiguration,
readNxJson,
readProjectConfiguration, readProjectConfiguration,
runTasksInSerial, runTasksInSerial,
TargetConfiguration,
toJS,
Tree, Tree,
updateJson, updateJson,
updateProjectConfiguration, updateProjectConfiguration,
updateTsConfigsToJs, updateTsConfigsToJs,
writeJson,
} from '@nx/devkit'; } from '@nx/devkit';
import {
determineProjectNameAndRootOptions,
ensureRootProjectName,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { configurationGenerator } from '@nx/jest'; import { configurationGenerator } from '@nx/jest';
import { initGenerator as jsInitGenerator, tsConfigBaseOptions } from '@nx/js';
import { import {
getRelativePathToRootTsConfig, addProjectToTsSolutionWorkspace,
initGenerator as jsInitGenerator, updateTsconfigFiles,
tsConfigBaseOptions, } from '@nx/js/src/utils/typescript/ts-solution-setup';
} from '@nx/js'; import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields';
import { esbuildVersion } from '@nx/js/src/utils/versions'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { lintProjectGenerator } from '@nx/eslint'; import { nxVersion } from '../../utils/versions';
import { join } from 'path';
import {
expressTypingsVersion,
expressVersion,
fastifyAutoloadVersion,
fastifyPluginVersion,
fastifySensibleVersion,
fastifyVersion,
koaTypingsVersion,
koaVersion,
nxVersion,
tslibVersion,
typesNodeVersion,
} from '../../utils/versions';
import { e2eProjectGenerator } from '../e2e-project/e2e-project'; import { e2eProjectGenerator } from '../e2e-project/e2e-project';
import { initGenerator } from '../init/init'; import { initGenerator } from '../init/init';
import { setupDockerGenerator } from '../setup-docker/setup-docker'; import { setupDockerGenerator } from '../setup-docker/setup-docker';
import { Schema } from './schema'; import { Schema } from './schema';
import { hasWebpackPlugin } from '../../utils/has-webpack-plugin';
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { import {
addProjectToTsSolutionWorkspace, addAppFiles,
isUsingTsSolutionSetup, addLintingToApplication,
updateTsconfigFiles, addProject,
} from '@nx/js/src/utils/typescript/ts-solution-setup'; addProjectDependencies,
import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; addProxy,
import type { PackageJson } from 'nx/src/utils/package-json'; normalizeOptions,
NormalizedSchema,
export interface NormalizedSchema extends Omit<Schema, 'useTsSolution'> { } from './lib';
appProjectRoot: string;
parsedTags: string[];
outputPath: string;
importPath: string;
isUsingTsSolutionConfig: boolean;
}
function getWebpackBuildConfig(
project: ProjectConfiguration,
options: NormalizedSchema
): TargetConfiguration {
return {
executor: `@nx/webpack:webpack`,
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: {
target: 'node',
compiler: 'tsc',
outputPath: options.outputPath,
main: joinPathFragments(
project.sourceRoot,
'main' + (options.js ? '.js' : '.ts')
),
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
assets: [joinPathFragments(project.sourceRoot, 'assets')],
webpackConfig: joinPathFragments(
options.appProjectRoot,
'webpack.config.js'
),
generatePackageJson: options.isUsingTsSolutionConfig ? undefined : true,
},
configurations: {
development: {
outputHashing: 'none',
},
production: {
...(options.docker && { generateLockfile: true }),
},
},
};
}
function getEsBuildConfig(
project: ProjectConfiguration,
options: NormalizedSchema
): TargetConfiguration {
return {
executor: '@nx/esbuild:esbuild',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: {
platform: 'node',
outputPath: options.outputPath,
// Use CJS for Node apps for widest compatibility.
format: ['cjs'],
bundle: false,
main: joinPathFragments(
project.sourceRoot,
'main' + (options.js ? '.js' : '.ts')
),
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
assets: [joinPathFragments(project.sourceRoot, 'assets')],
generatePackageJson: options.isUsingTsSolutionConfig ? undefined : true,
esbuildOptions: {
sourcemap: true,
// Generate CJS files as .js so imports can be './foo' rather than './foo.cjs'.
outExtension: { '.js': '.js' },
},
},
configurations: {
development: {},
production: {
...(options.docker && { generateLockfile: true }),
esbuildOptions: {
sourcemap: false,
// Generate CJS files as .js so imports can be './foo' rather than './foo.cjs'.
outExtension: { '.js': '.js' },
},
},
},
};
}
function getServeConfig(options: NormalizedSchema): TargetConfiguration {
return {
continuous: true,
executor: '@nx/js:node',
defaultConfiguration: 'development',
// Run build, which includes dependency on "^build" by default, so the first run
// won't error out due to missing build artifacts.
dependsOn: ['build'],
options: {
buildTarget: `${options.name}:build`,
// Even though `false` is the default, set this option so users know it
// exists if they want to always run dependencies during each rebuild.
runBuildTargetDependencies: false,
},
configurations: {
development: {
buildTarget: `${options.name}:build:development`,
},
production: {
buildTarget: `${options.name}:build:production`,
},
},
};
}
function getNestWebpackBuildConfig(): TargetConfiguration {
return {
executor: 'nx:run-commands',
options: {
command: 'webpack-cli build',
args: ['node-env=production'],
},
configurations: {
development: {
args: ['node-env=development'],
},
},
};
}
function addProject(tree: Tree, options: NormalizedSchema) {
const project: ProjectConfiguration = {
root: options.appProjectRoot,
sourceRoot: joinPathFragments(options.appProjectRoot, 'src'),
projectType: 'application',
targets: {},
tags: options.parsedTags,
};
if (options.bundler === 'esbuild') {
addBuildTargetDefaults(tree, '@nx/esbuild:esbuild');
project.targets.build = getEsBuildConfig(project, options);
} else if (options.bundler === 'webpack') {
if (!hasWebpackPlugin(tree) && options.addPlugin === false) {
addBuildTargetDefaults(tree, `@nx/webpack:webpack`);
project.targets.build = getWebpackBuildConfig(project, options);
} else if (options.isNest) {
// If we are using Nest that has the webpack plugin we need to override the
// build target so that node-env can be set to production or development so the serve target can be run in development mode
project.targets.build = getNestWebpackBuildConfig();
}
}
project.targets.serve = getServeConfig(options);
const packageJson: PackageJson = {
name: options.importPath,
version: '0.0.1',
private: true,
};
if (!options.useProjectJson) {
packageJson.nx = {
name: options.name !== options.importPath ? options.name : undefined,
targets: project.targets,
tags: project.tags?.length ? project.tags : undefined,
};
} else {
addProjectConfiguration(
tree,
options.name,
project,
options.standaloneConfig
);
}
if (!options.useProjectJson || options.isUsingTsSolutionConfig) {
writeJson(
tree,
joinPathFragments(options.appProjectRoot, 'package.json'),
packageJson
);
}
}
function addAppFiles(tree: Tree, options: NormalizedSchema) {
generateFiles(
tree,
join(__dirname, './files/common'),
options.appProjectRoot,
{
...options,
tmpl: '',
name: options.name,
root: options.appProjectRoot,
offset: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
tree,
options.appProjectRoot
),
webpackPluginOptions:
hasWebpackPlugin(tree) && options.addPlugin !== false
? {
outputPath: options.isUsingTsSolutionConfig
? 'dist'
: joinPathFragments(
offsetFromRoot(options.appProjectRoot),
'dist',
options.rootProject ? options.name : options.appProjectRoot
),
main: './src/main' + (options.js ? '.js' : '.ts'),
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
}
: null,
}
);
if (options.bundler !== 'webpack') {
tree.delete(joinPathFragments(options.appProjectRoot, 'webpack.config.js'));
}
if (options.framework && options.framework !== 'none') {
generateFiles(
tree,
join(__dirname, `./files/${options.framework}`),
options.appProjectRoot,
{
...options,
tmpl: '',
name: options.name,
root: options.appProjectRoot,
offset: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
tree,
options.appProjectRoot
),
}
);
}
if (options.js) {
toJS(tree);
}
}
function addProxy(tree: Tree, options: NormalizedSchema) {
const projectConfig = readProjectConfiguration(tree, options.frontendProject);
if (
projectConfig.targets &&
['serve', 'dev'].find((t) => !!projectConfig.targets[t])
) {
const targetName = ['serve', 'dev'].find((t) => !!projectConfig.targets[t]);
projectConfig.targets[targetName].dependsOn = [
...(projectConfig.targets[targetName].dependsOn ?? []),
`${options.name}:serve`,
];
const pathToProxyFile = `${projectConfig.root}/proxy.conf.json`;
projectConfig.targets[targetName].options = {
...projectConfig.targets[targetName].options,
proxyConfig: pathToProxyFile,
};
if (!tree.exists(pathToProxyFile)) {
tree.write(
pathToProxyFile,
JSON.stringify(
{
'/api': {
target: `http://localhost:${options.port}`,
secure: false,
},
},
null,
2
)
);
} else {
//add new entry to existing config
const proxyFileContent = tree.read(pathToProxyFile).toString();
const proxyModified = {
...JSON.parse(proxyFileContent),
[`/${options.name}-api`]: {
target: `http://localhost:${options.port}`,
secure: false,
},
};
tree.write(pathToProxyFile, JSON.stringify(proxyModified, null, 2));
}
updateProjectConfiguration(tree, options.frontendProject, projectConfig);
} else {
logger.warn(
`Skip updating proxy for frontend project "${options.frontendProject}" since "serve" target is not found in project.json. For more information, see: https://nx.dev/recipes/node/application-proxies.`
);
}
}
export async function addLintingToApplication(
tree: Tree,
options: NormalizedSchema
): Promise<GeneratorCallback> {
const lintTask = await lintProjectGenerator(tree, {
linter: options.linter,
project: options.name,
tsConfigPaths: [
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
],
unitTestRunner: options.unitTestRunner,
skipFormat: true,
setParserOptionsProject: options.setParserOptionsProject,
rootProject: options.rootProject,
addPlugin: options.addPlugin,
});
return lintTask;
}
function addProjectDependencies(
tree: Tree,
options: NormalizedSchema
): GeneratorCallback {
const bundlers = {
webpack: {
'@nx/webpack': nxVersion,
},
esbuild: {
'@nx/esbuild': nxVersion,
esbuild: esbuildVersion,
},
};
const frameworkDependencies = {
express: {
express: expressVersion,
},
koa: {
koa: koaVersion,
},
fastify: {
fastify: fastifyVersion,
'fastify-plugin': fastifyPluginVersion,
'@fastify/autoload': fastifyAutoloadVersion,
'@fastify/sensible': fastifySensibleVersion,
},
};
const frameworkDevDependencies = {
express: {
'@types/express': expressTypingsVersion,
},
koa: {
'@types/koa': koaTypingsVersion,
},
fastify: {},
};
return addDependenciesToPackageJson(
tree,
{
...frameworkDependencies[options.framework],
tslib: tslibVersion,
},
{
...frameworkDevDependencies[options.framework],
...bundlers[options.bundler],
'@types/node': typesNodeVersion,
}
);
}
function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) { function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) {
if (options.isUsingTsSolutionConfig) { if (options.isUsingTsSolutionConfig) {
@ -635,66 +243,4 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) {
return runTasksInSerial(...tasks); return runTasksInSerial(...tasks);
} }
async function normalizeOptions(
host: Tree,
options: Schema
): Promise<NormalizedSchema> {
await ensureRootProjectName(options, 'application');
const {
projectName,
projectRoot: appProjectRoot,
importPath,
} = await determineProjectNameAndRootOptions(host, {
name: options.name,
projectType: 'application',
directory: options.directory,
rootProject: options.rootProject,
});
options.rootProject = appProjectRoot === '.';
options.bundler = options.bundler ?? 'esbuild';
options.e2eTestRunner = options.e2eTestRunner ?? 'jest';
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
: [];
const nxJson = readNxJson(host);
const addPlugin =
process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
const swcJest = options.swcJest ?? isUsingTsSolutionConfig;
const appProjectName =
!isUsingTsSolutionConfig || options.name ? projectName : importPath;
const useProjectJson = options.useProjectJson ?? !isUsingTsSolutionConfig;
return {
addPlugin,
...options,
name: appProjectName,
frontendProject: options.frontendProject
? names(options.frontendProject).fileName
: undefined,
appProjectRoot,
importPath,
parsedTags,
linter: options.linter ?? 'eslint',
unitTestRunner: options.unitTestRunner ?? 'jest',
rootProject: options.rootProject ?? false,
port: options.port ?? 3000,
outputPath: isUsingTsSolutionConfig
? joinPathFragments(appProjectRoot, 'dist')
: joinPathFragments(
'dist',
options.rootProject ? appProjectName : appProjectRoot
),
isUsingTsSolutionConfig,
swcJest,
useProjectJson,
};
}
export default applicationGenerator; export default applicationGenerator;

View File

@ -0,0 +1,71 @@
import {
addDependenciesToPackageJson,
GeneratorCallback,
Tree,
} from '@nx/devkit';
import { esbuildVersion } from '@nx/js/src/utils/versions';
import {
expressTypingsVersion,
expressVersion,
fastifyAutoloadVersion,
fastifyPluginVersion,
fastifySensibleVersion,
fastifyVersion,
koaTypingsVersion,
koaVersion,
nxVersion,
tslibVersion,
typesNodeVersion,
} from '../../../utils/versions';
import { NormalizedSchema } from './normalized-schema';
export function addProjectDependencies(
tree: Tree,
options: NormalizedSchema
): GeneratorCallback {
const bundlers = {
webpack: {
'@nx/webpack': nxVersion,
},
esbuild: {
'@nx/esbuild': nxVersion,
esbuild: esbuildVersion,
},
};
const frameworkDependencies = {
express: {
express: expressVersion,
},
koa: {
koa: koaVersion,
},
fastify: {
fastify: fastifyVersion,
'fastify-plugin': fastifyPluginVersion,
'@fastify/autoload': fastifyAutoloadVersion,
'@fastify/sensible': fastifySensibleVersion,
},
};
const frameworkDevDependencies = {
express: {
'@types/express': expressTypingsVersion,
},
koa: {
'@types/koa': koaTypingsVersion,
},
fastify: {},
};
return addDependenciesToPackageJson(
tree,
{
...frameworkDependencies[options.framework],
tslib: tslibVersion,
},
{
...frameworkDevDependencies[options.framework],
...bundlers[options.bundler],
'@types/node': typesNodeVersion,
}
);
}

View File

@ -0,0 +1,23 @@
import { GeneratorCallback, joinPathFragments, Tree } from '@nx/devkit';
import { lintProjectGenerator } from '@nx/eslint';
import { NormalizedSchema } from './normalized-schema';
export async function addLintingToApplication(
tree: Tree,
options: NormalizedSchema
): Promise<GeneratorCallback> {
const lintTask = await lintProjectGenerator(tree, {
linter: options.linter,
project: options.name,
tsConfigPaths: [
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
],
unitTestRunner: options.unitTestRunner,
skipFormat: true,
setParserOptionsProject: options.setParserOptionsProject,
rootProject: options.rootProject,
addPlugin: options.addPlugin,
});
return lintTask;
}

View File

@ -0,0 +1,61 @@
import {
logger,
readProjectConfiguration,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
import { NormalizedSchema } from './normalized-schema';
export function addProxy(tree: Tree, options: NormalizedSchema) {
const projectConfig = readProjectConfiguration(tree, options.frontendProject);
if (
projectConfig.targets &&
['serve', 'dev'].find((t) => !!projectConfig.targets[t])
) {
const targetName = ['serve', 'dev'].find((t) => !!projectConfig.targets[t]);
projectConfig.targets[targetName].dependsOn = [
...(projectConfig.targets[targetName].dependsOn ?? []),
`${options.name}:serve`,
];
const pathToProxyFile = `${projectConfig.root}/proxy.conf.json`;
projectConfig.targets[targetName].options = {
...projectConfig.targets[targetName].options,
proxyConfig: pathToProxyFile,
};
if (!tree.exists(pathToProxyFile)) {
tree.write(
pathToProxyFile,
JSON.stringify(
{
'/api': {
target: `http://localhost:${options.port}`,
secure: false,
},
},
null,
2
)
);
} else {
//add new entry to existing config
const proxyFileContent = tree.read(pathToProxyFile).toString();
const proxyModified = {
...JSON.parse(proxyFileContent),
[`/${options.name}-api`]: {
target: `http://localhost:${options.port}`,
secure: false,
},
};
tree.write(pathToProxyFile, JSON.stringify(proxyModified, null, 2));
}
updateProjectConfiguration(tree, options.frontendProject, projectConfig);
} else {
logger.warn(
`Skip updating proxy for frontend project "${options.frontendProject}" since "serve" target is not found in project.json. For more information, see: https://nx.dev/recipes/node/application-proxies.`
);
}
}

View File

@ -0,0 +1,71 @@
import {
generateFiles,
joinPathFragments,
offsetFromRoot,
toJS,
Tree,
} from '@nx/devkit';
import { getRelativePathToRootTsConfig } from '@nx/js';
import { join } from 'path';
import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin';
import { NormalizedSchema } from './normalized-schema';
export function addAppFiles(tree: Tree, options: NormalizedSchema) {
generateFiles(
tree,
join(__dirname, '../files/common'),
options.appProjectRoot,
{
...options,
tmpl: '',
name: options.name,
root: options.appProjectRoot,
offset: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
tree,
options.appProjectRoot
),
webpackPluginOptions:
hasWebpackPlugin(tree) && options.addPlugin !== false
? {
outputPath: options.isUsingTsSolutionConfig
? 'dist'
: joinPathFragments(
offsetFromRoot(options.appProjectRoot),
'dist',
options.rootProject ? options.name : options.appProjectRoot
),
main: './src/main' + (options.js ? '.js' : '.ts'),
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
}
: null,
}
);
if (options.bundler !== 'webpack') {
tree.delete(joinPathFragments(options.appProjectRoot, 'webpack.config.js'));
}
if (options.framework && options.framework !== 'none') {
generateFiles(
tree,
join(__dirname, `../files/${options.framework}`),
options.appProjectRoot,
{
...options,
tmpl: '',
name: options.name,
root: options.appProjectRoot,
offset: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
tree,
options.appProjectRoot
),
}
);
}
if (options.js) {
toJS(tree);
}
}

View File

@ -0,0 +1,71 @@
import {
addProjectConfiguration,
joinPathFragments,
ProjectConfiguration,
Tree,
writeJson,
} from '@nx/devkit';
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import type { PackageJson } from 'nx/src/utils/package-json';
import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin';
import { NormalizedSchema } from './normalized-schema';
import {
getEsBuildConfig,
getNestWebpackBuildConfig,
getServeConfig,
getWebpackBuildConfig,
} from './create-targets';
export function addProject(tree: Tree, options: NormalizedSchema) {
const project: ProjectConfiguration = {
root: options.appProjectRoot,
sourceRoot: joinPathFragments(options.appProjectRoot, 'src'),
projectType: 'application',
targets: {},
tags: options.parsedTags,
};
if (options.bundler === 'esbuild') {
addBuildTargetDefaults(tree, '@nx/esbuild:esbuild');
project.targets.build = getEsBuildConfig(project, options);
} else if (options.bundler === 'webpack') {
if (!hasWebpackPlugin(tree) && options.addPlugin === false) {
addBuildTargetDefaults(tree, `@nx/webpack:webpack`);
project.targets.build = getWebpackBuildConfig(project, options);
} else if (options.isNest) {
// If we are using Nest that has the webpack plugin we need to override the
// build target so that node-env can be set to production or development so the serve target can be run in development mode
project.targets.build = getNestWebpackBuildConfig();
}
}
project.targets.serve = getServeConfig(options);
const packageJson: PackageJson = {
name: options.importPath,
version: '0.0.1',
private: true,
};
if (!options.useProjectJson) {
packageJson.nx = {
name: options.name !== options.importPath ? options.name : undefined,
targets: project.targets,
tags: project.tags?.length ? project.tags : undefined,
};
} else {
addProjectConfiguration(
tree,
options.name,
project,
options.standaloneConfig
);
}
if (!options.useProjectJson || options.isUsingTsSolutionConfig) {
writeJson(
tree,
joinPathFragments(options.appProjectRoot, 'package.json'),
packageJson
);
}
}

View File

@ -0,0 +1,122 @@
import {
joinPathFragments,
ProjectConfiguration,
TargetConfiguration,
} from '@nx/devkit';
import { NormalizedSchema } from './normalized-schema';
export function getWebpackBuildConfig(
project: ProjectConfiguration,
options: NormalizedSchema
): TargetConfiguration {
return {
executor: `@nx/webpack:webpack`,
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: {
target: 'node',
compiler: 'tsc',
outputPath: options.outputPath,
main: joinPathFragments(
project.sourceRoot,
'main' + (options.js ? '.js' : '.ts')
),
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
assets: [joinPathFragments(project.sourceRoot, 'assets')],
webpackConfig: joinPathFragments(
options.appProjectRoot,
'webpack.config.js'
),
generatePackageJson: options.isUsingTsSolutionConfig ? undefined : true,
},
configurations: {
development: {
outputHashing: 'none',
},
production: {
...(options.docker && { generateLockfile: true }),
},
},
};
}
export function getEsBuildConfig(
project: ProjectConfiguration,
options: NormalizedSchema
): TargetConfiguration {
return {
executor: '@nx/esbuild:esbuild',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: {
platform: 'node',
outputPath: options.outputPath,
// Use CJS for Node apps for widest compatibility.
format: ['cjs'],
bundle: false,
main: joinPathFragments(
project.sourceRoot,
'main' + (options.js ? '.js' : '.ts')
),
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
assets: [joinPathFragments(project.sourceRoot, 'assets')],
generatePackageJson: options.isUsingTsSolutionConfig ? undefined : true,
esbuildOptions: {
sourcemap: true,
// Generate CJS files as .js so imports can be './foo' rather than './foo.cjs'.
outExtension: { '.js': '.js' },
},
},
configurations: {
development: {},
production: {
...(options.docker && { generateLockfile: true }),
esbuildOptions: {
sourcemap: false,
// Generate CJS files as .js so imports can be './foo' rather than './foo.cjs'.
outExtension: { '.js': '.js' },
},
},
},
};
}
export function getServeConfig(options: NormalizedSchema): TargetConfiguration {
return {
continuous: true,
executor: '@nx/js:node',
defaultConfiguration: 'development',
// Run build, which includes dependency on "^build" by default, so the first run
// won't error out due to missing build artifacts.
dependsOn: ['build'],
options: {
buildTarget: `${options.name}:build`,
// Even though `false` is the default, set this option so users know it
// exists if they want to always run dependencies during each rebuild.
runBuildTargetDependencies: false,
},
configurations: {
development: {
buildTarget: `${options.name}:build:development`,
},
production: {
buildTarget: `${options.name}:build:production`,
},
},
};
}
export function getNestWebpackBuildConfig(): TargetConfiguration {
return {
executor: 'nx:run-commands',
options: {
command: 'webpack-cli build',
args: ['node-env=production'],
},
configurations: {
development: {
args: ['node-env=development'],
},
},
};
}

View File

@ -0,0 +1,8 @@
export * from './normalized-schema';
export * from './normalize-options';
export * from './create-targets';
export * from './create-project';
export * from './create-files';
export * from './add-dependencies';
export * from './add-linting';
export * from './add-proxy';

View File

@ -0,0 +1,70 @@
import { joinPathFragments, names, readNxJson, Tree } from '@nx/devkit';
import {
determineProjectNameAndRootOptions,
ensureRootProjectName,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { Schema } from '../schema';
import { NormalizedSchema } from './normalized-schema';
export async function normalizeOptions(
host: Tree,
options: Schema
): Promise<NormalizedSchema> {
await ensureRootProjectName(options, 'application');
const {
projectName,
projectRoot: appProjectRoot,
importPath,
} = await determineProjectNameAndRootOptions(host, {
name: options.name,
projectType: 'application',
directory: options.directory,
rootProject: options.rootProject,
});
options.rootProject = appProjectRoot === '.';
options.bundler = options.bundler ?? 'esbuild';
options.e2eTestRunner = options.e2eTestRunner ?? 'jest';
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
: [];
const nxJson = readNxJson(host);
const addPlugin =
process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
const swcJest = options.swcJest ?? isUsingTsSolutionConfig;
const appProjectName =
!isUsingTsSolutionConfig || options.name ? projectName : importPath;
const useProjectJson = options.useProjectJson ?? !isUsingTsSolutionConfig;
return {
addPlugin,
...options,
name: appProjectName,
frontendProject: options.frontendProject
? names(options.frontendProject).fileName
: undefined,
appProjectRoot,
importPath,
parsedTags,
linter: options.linter ?? 'eslint',
unitTestRunner: options.unitTestRunner ?? 'jest',
rootProject: options.rootProject ?? false,
port: options.port ?? 3000,
outputPath: isUsingTsSolutionConfig
? joinPathFragments(appProjectRoot, 'dist')
: joinPathFragments(
'dist',
options.rootProject ? appProjectName : appProjectRoot
),
isUsingTsSolutionConfig,
swcJest,
useProjectJson,
};
}

View File

@ -0,0 +1,9 @@
import { Schema } from '../schema';
export interface NormalizedSchema extends Omit<Schema, 'useTsSolution'> {
appProjectRoot: string;
parsedTags: string[];
outputPath: string;
importPath: string;
isUsingTsSolutionConfig: boolean;
}