UNPKG

@nx/node

Version:

The Node Plugin for Nx contains generators to manage Node applications within an Nx workspace.

500 lines (499 loc) • 20.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.addLintingToApplication = addLintingToApplication; exports.applicationGenerator = applicationGenerator; exports.applicationGeneratorInternal = applicationGeneratorInternal; const devkit_1 = require("@nx/devkit"); const project_name_and_root_utils_1 = require("@nx/devkit/src/generators/project-name-and-root-utils"); const jest_1 = require("@nx/jest"); const js_1 = require("@nx/js"); const versions_1 = require("@nx/js/src/utils/versions"); const eslint_1 = require("@nx/eslint"); const path_1 = require("path"); const versions_2 = require("../../utils/versions"); const e2e_project_1 = require("../e2e-project/e2e-project"); const init_1 = require("../init/init"); const setup_docker_1 = require("../setup-docker/setup-docker"); const has_webpack_plugin_1 = require("../../utils/has-webpack-plugin"); const target_defaults_utils_1 = require("@nx/devkit/src/generators/target-defaults-utils"); const log_show_project_command_1 = require("@nx/devkit/src/utils/log-show-project-command"); const ts_solution_setup_1 = require("@nx/js/src/utils/typescript/ts-solution-setup"); const sort_fields_1 = require("@nx/js/src/utils/package-json/sort-fields"); function getWebpackBuildConfig(project, options) { return { executor: `@nx/webpack:webpack`, outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { target: 'node', compiler: 'tsc', outputPath: options.outputPath, main: (0, devkit_1.joinPathFragments)(project.sourceRoot, 'main' + (options.js ? '.js' : '.ts')), tsConfig: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.app.json'), assets: [(0, devkit_1.joinPathFragments)(project.sourceRoot, 'assets')], webpackConfig: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'webpack.config.js'), generatePackageJson: options.isUsingTsSolutionConfig ? undefined : true, }, configurations: { development: {}, production: { ...(options.docker && { generateLockfile: true }), }, }, }; } function getEsBuildConfig(project, options) { 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: (0, devkit_1.joinPathFragments)(project.sourceRoot, 'main' + (options.js ? '.js' : '.ts')), tsConfig: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.app.json'), assets: [(0, devkit_1.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) { return { 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() { return { executor: 'nx:run-commands', options: { command: 'webpack-cli build', args: ['node-env=production'], }, configurations: { development: { args: ['node-env=development'], }, }, }; } function addProject(tree, options) { const project = { root: options.appProjectRoot, sourceRoot: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src'), projectType: 'application', targets: {}, tags: options.parsedTags, }; if (options.bundler === 'esbuild') { (0, target_defaults_utils_1.addBuildTargetDefaults)(tree, '@nx/esbuild:esbuild'); project.targets.build = getEsBuildConfig(project, options); } else if (options.bundler === 'webpack') { if (!(0, has_webpack_plugin_1.hasWebpackPlugin)(tree)) { (0, target_defaults_utils_1.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 = { 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 { (0, devkit_1.addProjectConfiguration)(tree, options.name, project, options.standaloneConfig); } if (!options.useProjectJson || options.isUsingTsSolutionConfig) { (0, devkit_1.writeJson)(tree, (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'package.json'), packageJson); } } function addAppFiles(tree, options) { (0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, './files/common'), options.appProjectRoot, { ...options, tmpl: '', name: options.name, root: options.appProjectRoot, offset: (0, devkit_1.offsetFromRoot)(options.appProjectRoot), rootTsConfigPath: (0, js_1.getRelativePathToRootTsConfig)(tree, options.appProjectRoot), webpackPluginOptions: (0, has_webpack_plugin_1.hasWebpackPlugin)(tree) ? { outputPath: options.isUsingTsSolutionConfig ? 'dist' : (0, devkit_1.joinPathFragments)((0, devkit_1.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((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'webpack.config.js')); } if (options.framework && options.framework !== 'none') { (0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, `./files/${options.framework}`), options.appProjectRoot, { ...options, tmpl: '', name: options.name, root: options.appProjectRoot, offset: (0, devkit_1.offsetFromRoot)(options.appProjectRoot), rootTsConfigPath: (0, js_1.getRelativePathToRootTsConfig)(tree, options.appProjectRoot), }); } if (options.js) { (0, devkit_1.toJS)(tree); } } function addProxy(tree, options) { const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.frontendProject); if (projectConfig.targets && projectConfig.targets.serve) { const pathToProxyFile = `${projectConfig.root}/proxy.conf.json`; projectConfig.targets.serve.options = { ...projectConfig.targets.serve.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)); } (0, devkit_1.updateProjectConfiguration)(tree, options.frontendProject, projectConfig); } else { devkit_1.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.`); } } async function addLintingToApplication(tree, options) { const lintTask = await (0, eslint_1.lintProjectGenerator)(tree, { linter: options.linter, project: options.name, tsConfigPaths: [ (0, devkit_1.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, options) { const bundlers = { webpack: { '@nx/webpack': versions_2.nxVersion, }, esbuild: { '@nx/esbuild': versions_2.nxVersion, esbuild: versions_1.esbuildVersion, }, }; const frameworkDependencies = { express: { express: versions_2.expressVersion, }, koa: { koa: versions_2.koaVersion, }, fastify: { fastify: versions_2.fastifyVersion, 'fastify-plugin': versions_2.fastifyPluginVersion, '@fastify/autoload': versions_2.fastifyAutoloadVersion, '@fastify/sensible': versions_2.fastifySensibleVersion, }, }; const frameworkDevDependencies = { express: { '@types/express': versions_2.expressTypingsVersion, }, koa: { '@types/koa': versions_2.koaTypingsVersion, }, fastify: {}, }; return (0, devkit_1.addDependenciesToPackageJson)(tree, { ...frameworkDependencies[options.framework], tslib: versions_2.tslibVersion, }, { ...frameworkDevDependencies[options.framework], ...bundlers[options.bundler], '@types/node': versions_2.typesNodeVersion, }); } function updateTsConfigOptions(tree, options) { if (options.isUsingTsSolutionConfig) { return; } (0, devkit_1.updateJson)(tree, `${options.appProjectRoot}/tsconfig.json`, (json) => { if (options.rootProject) { return { compilerOptions: { ...js_1.tsConfigBaseOptions, ...json.compilerOptions, esModuleInterop: true, }, ...json, extends: undefined, exclude: ['node_modules', 'tmp'], }; } else { return { ...json, compilerOptions: { ...json.compilerOptions, esModuleInterop: true, }, }; } }); } async function applicationGenerator(tree, schema) { return await applicationGeneratorInternal(tree, { addPlugin: false, useProjectJson: true, ...schema, }); } async function applicationGeneratorInternal(tree, schema) { const tasks = []; const jsInitTask = await (0, js_1.initGenerator)(tree, { ...schema, tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json', skipFormat: true, addTsPlugin: schema.useTsSolution, }); tasks.push(jsInitTask); const options = await normalizeOptions(tree, schema); if (options.framework === 'nest') { // nx-ignore-next-line const { applicationGenerator } = (0, devkit_1.ensurePackage)('@nx/nest', versions_2.nxVersion); const nestTasks = await applicationGenerator(tree, { ...options, skipFormat: true, }); tasks.push(nestTasks); if (options.docker) { const dockerTask = await (0, setup_docker_1.setupDockerGenerator)(tree, { ...options, project: options.name, skipFormat: true, }); tasks.push(dockerTask); } return (0, devkit_1.runTasksInSerial)(...[ ...tasks, () => { (0, log_show_project_command_1.logShowProjectCommand)(options.name); }, ]); } const initTask = await (0, init_1.initGenerator)(tree, { ...schema, skipFormat: true, }); tasks.push(initTask); const installTask = addProjectDependencies(tree, options); tasks.push(installTask); if (options.bundler === 'webpack') { const { webpackInitGenerator } = (0, devkit_1.ensurePackage)('@nx/webpack', versions_2.nxVersion); const webpackInitTask = await webpackInitGenerator(tree, { skipPackageJson: options.skipPackageJson, skipFormat: true, addPlugin: options.addPlugin, }); tasks.push(webpackInitTask); if (!options.skipPackageJson) { const { ensureDependencies } = await Promise.resolve().then(() => require('@nx/webpack/src/utils/ensure-dependencies')); tasks.push(ensureDependencies(tree, { uiFramework: options.isNest ? 'none' : 'react', })); } } addAppFiles(tree, options); addProject(tree, options); // If we are using the new TS solution // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project if (options.isUsingTsSolutionConfig) { await (0, ts_solution_setup_1.addProjectToTsSolutionWorkspace)(tree, options.appProjectRoot); } updateTsConfigOptions(tree, options); if (options.linter === eslint_1.Linter.EsLint) { const lintTask = await addLintingToApplication(tree, options); tasks.push(lintTask); } if (options.unitTestRunner === 'jest') { const jestTask = await (0, jest_1.configurationGenerator)(tree, { ...options, project: options.name, setupFile: 'none', skipSerializers: true, supportTsx: options.js, testEnvironment: 'node', compiler: options.swcJest ? 'swc' : 'tsc', skipFormat: true, }); tasks.push(jestTask); // There are no tests by default, so set `--passWithNoTests` to avoid test failure on new project. const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.name); projectConfig.targets ??= {}; projectConfig.targets.test = { options: { passWithNoTests: true }, }; (0, devkit_1.updateProjectConfiguration)(tree, options.name, projectConfig); } else { // No need for default spec file if unit testing is not setup. tree.delete((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/app/app.spec.ts')); } if (options.e2eTestRunner === 'jest') { const e2eTask = await (0, e2e_project_1.e2eProjectGenerator)(tree, { ...options, projectType: options.framework === 'none' ? 'cli' : 'server', name: options.rootProject ? 'e2e' : `${options.name}-e2e`, directory: options.rootProject ? 'e2e' : `${options.appProjectRoot}-e2e`, project: options.name, port: options.port, isNest: options.isNest, skipFormat: true, }); tasks.push(e2eTask); } if (options.js) { (0, devkit_1.updateTsConfigsToJs)(tree, { projectRoot: options.appProjectRoot }); } if (options.frontendProject) { addProxy(tree, options); } if (options.docker) { const dockerTask = await (0, setup_docker_1.setupDockerGenerator)(tree, { ...options, project: options.name, skipFormat: true, }); tasks.push(dockerTask); } if (options.isUsingTsSolutionConfig) { (0, ts_solution_setup_1.updateTsconfigFiles)(tree, options.appProjectRoot, 'tsconfig.app.json', { module: 'nodenext', moduleResolution: 'nodenext', }, options.linter === 'eslint' ? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs'] : undefined); } (0, sort_fields_1.sortPackageJsonFields)(tree, options.appProjectRoot); if (!options.skipFormat) { await (0, devkit_1.formatFiles)(tree); } tasks.push(() => { (0, log_show_project_command_1.logShowProjectCommand)(options.name); }); return (0, devkit_1.runTasksInSerial)(...tasks); } async function normalizeOptions(host, options) { await (0, project_name_and_root_utils_1.ensureRootProjectName)(options, 'application'); const { projectName, projectRoot: appProjectRoot, importPath, } = await (0, project_name_and_root_utils_1.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 = (0, devkit_1.readNxJson)(host); const addPlugin = process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; const isUsingTsSolutionConfig = (0, ts_solution_setup_1.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 ? (0, devkit_1.names)(options.frontendProject).fileName : undefined, appProjectRoot, importPath, parsedTags, linter: options.linter ?? eslint_1.Linter.EsLint, unitTestRunner: options.unitTestRunner ?? 'jest', rootProject: options.rootProject ?? false, port: options.port ?? 3000, outputPath: isUsingTsSolutionConfig ? (0, devkit_1.joinPathFragments)(appProjectRoot, 'dist') : (0, devkit_1.joinPathFragments)('dist', options.rootProject ? appProjectName : appProjectRoot), isUsingTsSolutionConfig, swcJest, useProjectJson, }; } exports.default = applicationGenerator;