diff --git a/e2e/node/src/node-ts-solution.test.ts b/e2e/node/src/node-ts-solution.test.ts index ec15e7a5aa..4323815b3c 100644 --- a/e2e/node/src/node-ts-solution.test.ts +++ b/e2e/node/src/node-ts-solution.test.ts @@ -49,8 +49,8 @@ describe('Node Applications', () => { updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`); runCLI(`build ${nodeapp}`); - checkFilesExist(`dist/apps/${nodeapp}/main.js`); - const result = execSync(`node dist/apps/${nodeapp}/main.js`, { + checkFilesExist(`apps/${nodeapp}/dist/main.js`); + const result = execSync(`node apps/${nodeapp}/dist/main.js`, { cwd: tmpProjPath(), }).toString(); expect(result).toContain('Hello World!'); @@ -144,7 +144,7 @@ describe('Node Applications', () => { expect(() => runCLI(`test ${nestapp}`)).not.toThrow(); runCLI(`build ${nestapp}`); - checkFilesExist(`dist/apps/${nestapp}/main.js`); + checkFilesExist(`apps/${nestapp}/dist/main.js`); const p = await runCommandUntil( `serve ${nestapp}`, diff --git a/packages/express/src/generators/application/application.spec.ts b/packages/express/src/generators/application/application.spec.ts index a9fd9acb39..c2ce54def5 100644 --- a/packages/express/src/generators/application/application.spec.ts +++ b/packages/express/src/generators/application/application.spec.ts @@ -202,7 +202,7 @@ describe('app', () => { ], "compiler": "tsc", "main": "myapp/src/main.ts", - "outputPath": "dist/myapp", + "outputPath": "myapp/dist", "target": "node", "tsConfig": "myapp/tsconfig.app.json", "webpackConfig": "myapp/webpack.config.js", diff --git a/packages/node/src/generators/application/application.spec.ts b/packages/node/src/generators/application/application.spec.ts index 185920fa48..c0b2b702b2 100644 --- a/packages/node/src/generators/application/application.spec.ts +++ b/packages/node/src/generators/application/application.spec.ts @@ -775,5 +775,67 @@ describe('app', () => { " `); }); + + it('should configure webpack correctly with the output contained within the project root', async () => { + await applicationGenerator(tree, { + directory: 'apps/my-app', + bundler: 'webpack', + addPlugin: true, + skipFormat: true, + }); + + expect(tree.read('apps/my-app/webpack.config.js', 'utf-8')) + .toMatchInlineSnapshot(` + "const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin'); + const { join } = require('path'); + + module.exports = { + output: { + path: join(__dirname, 'dist'), + }, + plugins: [ + new NxAppWebpackPlugin({ + target: 'node', + compiler: 'tsc', + main: './src/main.ts', + tsConfig: './tsconfig.app.json', + assets: ["./src/assets"], + optimization: false, + outputHashing: 'none', + generatePackageJson: true, + }) + ], + }; + " + `); + }); + + it('should configure webpack build task correctly with the output contained within the project root', async () => { + await applicationGenerator(tree, { + directory: 'apps/my-app', + bundler: 'webpack', + addPlugin: false, + skipFormat: true, + }); + + expect( + readProjectConfiguration(tree, 'my-app').targets.build.options + .outputPath + ).toBe('apps/my-app/dist'); + }); + + it('should configure esbuild build task correctly with the output contained within the project root', async () => { + await applicationGenerator(tree, { + directory: 'apps/my-app', + bundler: 'esbuild', + addPlugin: false, + skipFormat: true, + }); + + expect( + readProjectConfiguration(tree, 'my-app').targets.build.options + .outputPath + ).toBe('apps/my-app/dist'); + }); }); }); diff --git a/packages/node/src/generators/application/application.ts b/packages/node/src/generators/application/application.ts index bf96a69dcb..79f7fa6b0b 100644 --- a/packages/node/src/generators/application/application.ts +++ b/packages/node/src/generators/application/application.ts @@ -246,7 +246,13 @@ function addAppFiles(tree: Tree, options: NormalizedSchema) { ), webpackPluginOptions: hasWebpackPlugin(tree) ? { - outputPath: options.outputPath, + 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'], @@ -650,10 +656,12 @@ async function normalizeOptions( unitTestRunner: options.unitTestRunner ?? 'jest', rootProject: options.rootProject ?? false, port: options.port ?? 3000, - outputPath: joinPathFragments( - 'dist', - options.rootProject ? options.name : appProjectRoot - ), + outputPath: isUsingTsSolutionConfig + ? joinPathFragments(appProjectRoot, 'dist') + : joinPathFragments( + 'dist', + options.rootProject ? options.name : appProjectRoot + ), isUsingTsSolutionConfig, swcJest, }; diff --git a/packages/node/src/generators/application/files/common/webpack.config.js__tmpl__ b/packages/node/src/generators/application/files/common/webpack.config.js__tmpl__ index 77ab0d9c05..bbd7a45077 100644 --- a/packages/node/src/generators/application/files/common/webpack.config.js__tmpl__ +++ b/packages/node/src/generators/application/files/common/webpack.config.js__tmpl__ @@ -4,7 +4,7 @@ const { join } = require('path'); module.exports = { output: { - path: join(__dirname, '<%= offset %><%= webpackPluginOptions.outputPath %>'), + path: join(__dirname, '<%= webpackPluginOptions.outputPath %>'), }, plugins: [ new NxAppWebpackPlugin({ diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 01177ceaff..67dee48993 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -5,6 +5,7 @@ import { ProjectGraph, readJson, readNxJson, + readProjectConfiguration, Tree, updateJson, updateNxJson, @@ -1554,6 +1555,92 @@ describe('app', () => { 'packages/shared/*', ]); }); + + it('should configure webpack correctly with the output contained within the project root', async () => { + await applicationGenerator(appTree, { + directory: 'apps/my-app', + bundler: 'webpack', + linter: Linter.EsLint, + style: 'none', + e2eTestRunner: 'none', + addPlugin: true, + skipFormat: true, + }); + + expect(appTree.read('apps/my-app/webpack.config.js', 'utf-8')) + .toMatchInlineSnapshot(` + "const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin'); + const { NxReactWebpackPlugin } = require('@nx/react/webpack-plugin'); + const { join } = require('path'); + + module.exports = { + output: { + path: join(__dirname, 'dist'), + }, + devServer: { + port: 4200, + historyApiFallback: { + index: '/index.html', + disableDotRule: true, + htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], + }, + }, + plugins: [ + new NxAppWebpackPlugin({ + tsConfig: './tsconfig.app.json', + compiler: 'babel', + main: './src/main.tsx', + index: './src/index.html', + baseHref: '/', + assets: ["./src/favicon.ico","./src/assets"], + styles: [], + outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none', + optimization: process.env['NODE_ENV'] === 'production', + }), + new NxReactWebpackPlugin({ + // Uncomment this line if you don't want to use SVGR + // See: https://react-svgr.com/ + // svgr: false + }), + ], + }; + " + `); + }); + + it('should configure webpack build task correctly with the output contained within the project root', async () => { + await applicationGenerator(appTree, { + directory: 'apps/my-app', + bundler: 'webpack', + linter: Linter.EsLint, + style: 'none', + e2eTestRunner: 'none', + addPlugin: false, + skipFormat: true, + }); + + expect( + readProjectConfiguration(appTree, '@proj/my-app').targets.build.options + .outputPath + ).toBe('apps/my-app/dist'); + }); + + it('should configure rspack build task correctly with the output contained within the project root', async () => { + await applicationGenerator(appTree, { + directory: 'apps/my-app', + bundler: 'rspack', + linter: Linter.EsLint, + style: 'none', + e2eTestRunner: 'none', + addPlugin: true, + skipFormat: true, + }); + + expect( + readProjectConfiguration(appTree, '@proj/my-app').targets.build.options + .outputPath + ).toBe('apps/my-app/dist'); + }); }); describe('--bundler=rsbuild', () => { diff --git a/packages/react/src/generators/application/files/base-rspack/rspack.config.js__tmpl__ b/packages/react/src/generators/application/files/base-rspack/rspack.config.js__tmpl__ index 7d59123572..b6fc7fad8b 100644 --- a/packages/react/src/generators/application/files/base-rspack/rspack.config.js__tmpl__ +++ b/packages/react/src/generators/application/files/base-rspack/rspack.config.js__tmpl__ @@ -5,7 +5,7 @@ const { join } = require('path'); module.exports = { output: { - path: join(__dirname, '<%= offsetFromRoot %><%= rspackPluginOptions.outputPath %>'), + path: join(__dirname, '<%= rspackPluginOptions.outputPath %>'), }, devServer: { port: 4200, diff --git a/packages/react/src/generators/application/files/base-webpack/webpack.config.js__tmpl__ b/packages/react/src/generators/application/files/base-webpack/webpack.config.js__tmpl__ index d5b037cf0c..8da4f74349 100644 --- a/packages/react/src/generators/application/files/base-webpack/webpack.config.js__tmpl__ +++ b/packages/react/src/generators/application/files/base-webpack/webpack.config.js__tmpl__ @@ -5,7 +5,7 @@ const { join } = require('path'); module.exports = { output: { - path: join(__dirname, '<%= offsetFromRoot %><%= webpackPluginOptions.outputPath %>'), + path: join(__dirname, '<%= webpackPluginOptions.outputPath %>'), }, devServer: { port: 4200, diff --git a/packages/react/src/generators/application/lib/add-project.ts b/packages/react/src/generators/application/lib/add-project.ts index 8dfa4e2cef..675da5d2a0 100644 --- a/packages/react/src/generators/application/lib/add-project.ts +++ b/packages/react/src/generators/application/lib/add-project.ts @@ -5,12 +5,12 @@ import { ProjectConfiguration, TargetConfiguration, Tree, + updateProjectConfiguration, writeJson, } from '@nx/devkit'; import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin'; import { maybeJs } from '../../../utils/maybe-js'; import { hasRspackPlugin } from '../../../utils/has-rspack-plugin'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; export function addProject(host: Tree, options: NormalizedSchema) { const project: ProjectConfiguration = { @@ -43,11 +43,6 @@ export function addProject(host: Tree, options: NormalizedSchema) { name: options.projectName, version: '0.0.1', private: true, - nx: options.parsedTags?.length - ? { - tags: options.parsedTags, - } - : undefined, }); } @@ -55,6 +50,16 @@ export function addProject(host: Tree, options: NormalizedSchema) { addProjectConfiguration(host, options.projectName, { ...project, }); + } else if ( + options.parsedTags?.length || + Object.keys(project.targets).length + ) { + const updatedProject: ProjectConfiguration = { + root: options.appProjectRoot, + targets: project.targets, + tags: options.parsedTags?.length ? options.parsedTags : undefined, + }; + updateProjectConfiguration(host, options.projectName, updatedProject); } } @@ -66,7 +71,14 @@ function createRspackBuildTarget( outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { - outputPath: joinPathFragments('dist', options.appProjectRoot), + outputPath: options.isUsingTsSolutionConfig + ? joinPathFragments(options.appProjectRoot, 'dist') + : joinPathFragments( + 'dist', + options.appProjectRoot !== '.' + ? options.appProjectRoot + : options.projectName + ), index: joinPathFragments(options.appProjectRoot, 'src/index.html'), baseHref: '/', main: joinPathFragments( @@ -139,12 +151,14 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration { defaultConfiguration: 'production', options: { compiler: options.compiler ?? 'babel', - outputPath: joinPathFragments( - 'dist', - options.appProjectRoot != '.' - ? options.appProjectRoot - : options.projectName - ), + outputPath: options.isUsingTsSolutionConfig + ? joinPathFragments(options.appProjectRoot, 'dist') + : joinPathFragments( + 'dist', + options.appProjectRoot !== '.' + ? options.appProjectRoot + : options.projectName + ), index: joinPathFragments(options.appProjectRoot, 'src/index.html'), baseHref: '/', main: joinPathFragments( diff --git a/packages/react/src/generators/application/lib/create-application-files.ts b/packages/react/src/generators/application/lib/create-application-files.ts index 5935b106da..54c1bf8559 100644 --- a/packages/react/src/generators/application/lib/create-application-files.ts +++ b/packages/react/src/generators/application/lib/create-application-files.ts @@ -86,7 +86,10 @@ export async function createApplicationFiles( { ...templateVariables, webpackPluginOptions: hasWebpackPlugin(host) - ? createNxWebpackPluginOptions(options) + ? createNxWebpackPluginOptions( + options, + templateVariables.offsetFromRoot + ) : null, } ); @@ -151,7 +154,10 @@ export async function createApplicationFiles( { ...templateVariables, rspackPluginOptions: hasRspackPlugin(host) - ? createNxRspackPluginOptions(options) + ? createNxRspackPluginOptions( + options, + templateVariables.offsetFromRoot + ) : null, } ); @@ -211,17 +217,21 @@ export async function createApplicationFiles( } function createNxWebpackPluginOptions( - options: NormalizedSchema + options: NormalizedSchema, + rootOffset: string ): WithNxOptions & WithReactOptions { return { target: 'web', compiler: options.compiler ?? 'babel', - outputPath: joinPathFragments( - 'dist', - options.appProjectRoot != '.' - ? options.appProjectRoot - : options.projectName - ), + outputPath: options.isUsingTsSolutionConfig + ? 'dist' + : joinPathFragments( + rootOffset, + 'dist', + options.appProjectRoot != '.' + ? options.appProjectRoot + : options.projectName + ), index: './src/index.html', baseHref: '/', main: maybeJs( @@ -245,16 +255,20 @@ function createNxWebpackPluginOptions( } function createNxRspackPluginOptions( - options: NormalizedSchema + options: NormalizedSchema, + rootOffset: string ): WithNxOptions & WithReactOptions { return { target: 'web', - outputPath: joinPathFragments( - 'dist', - options.appProjectRoot != '.' - ? options.appProjectRoot - : options.projectName - ), + outputPath: options.isUsingTsSolutionConfig + ? 'dist' + : joinPathFragments( + rootOffset, + 'dist', + options.appProjectRoot != '.' + ? options.appProjectRoot + : options.projectName + ), index: './src/index.html', baseHref: '/', main: maybeJs( diff --git a/packages/rspack/src/utils/generator-utils.ts b/packages/rspack/src/utils/generator-utils.ts index 575291ff67..fa694b1752 100644 --- a/packages/rspack/src/utils/generator-utils.ts +++ b/packages/rspack/src/utils/generator-utils.ts @@ -9,6 +9,7 @@ import { updateProjectConfiguration, } from '@nx/devkit'; import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { type RspackExecutorSchema } from '../executors/rspack/schema'; import { type ConfigurationSchema } from '../generators/configuration/schema'; import { type Framework } from '../generators/init/schema'; @@ -171,13 +172,17 @@ export function addOrChangeBuildTarget( assets.push(joinPathFragments(project.root, 'src/assets')); } + const isTsSolutionSetup = isUsingTsSolutionSetup(tree); + const buildOptions: RspackExecutorSchema = { target: options.target ?? 'web', - outputPath: joinPathFragments( - 'dist', - // If standalone project then use the project's name in dist. - project.root === '.' ? project.name : project.root - ), + outputPath: isTsSolutionSetup + ? joinPathFragments(project.root, 'dist') + : joinPathFragments( + 'dist', + // If standalone project then use the project's name in dist. + project.root === '.' ? project.name : project.root + ), index: joinPathFragments(project.root, 'src/index.html'), main: determineMain(tree, options), tsConfig: determineTsConfig(tree, options), diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index 4e72714bfb..e5fdf3356b 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -697,6 +697,48 @@ describe('app', () => { }); }); + describe('--bundler=webpack', () => { + it('should configure webpack correctly', async () => { + await applicationGenerator(tree, { + directory: 'apps/my-app', + bundler: 'webpack', + addPlugin: true, + skipFormat: true, + }); + + expect(tree.read('apps/my-app/webpack.config.js', 'utf-8')) + .toMatchInlineSnapshot(` + " + const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin'); + const { join } = require('path'); + + module.exports = { + output: { + path: join(__dirname, '../../dist/apps/my-app'), + }, + devServer: { + port: 4200 + }, + plugins: [ + new NxAppWebpackPlugin({ + tsConfig: './tsconfig.app.json', + compiler: 'babel', + main: './src/main.ts', + index: './src/index.html', + baseHref: '/', + assets: ["./src/favicon.ico","./src/assets"], + styles: ["./src/styles.css"], + outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none', + optimization: process.env['NODE_ENV'] === 'production', + }) + ], + }; + + " + `); + }); + }); + describe('TS solution setup', () => { beforeEach(() => { tree = createTreeWithEmptyWorkspace(); @@ -852,5 +894,45 @@ describe('app', () => { } `); }); + + it('should configure webpack correctly with the output contained within the project root', async () => { + await applicationGenerator(tree, { + directory: 'apps/my-app', + bundler: 'webpack', + addPlugin: true, + skipFormat: true, + }); + + expect(tree.read('apps/my-app/webpack.config.js', 'utf-8')) + .toMatchInlineSnapshot(` + " + const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin'); + const { join } = require('path'); + + module.exports = { + output: { + path: join(__dirname, 'dist'), + }, + devServer: { + port: 4200 + }, + plugins: [ + new NxAppWebpackPlugin({ + tsConfig: './tsconfig.app.json', + compiler: 'babel', + main: './src/main.ts', + index: './src/index.html', + baseHref: '/', + assets: ["./src/favicon.ico","./src/assets"], + styles: ["./src/styles.css"], + outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none', + optimization: process.env['NODE_ENV'] === 'production', + }) + ], + }; + + " + `); + }); }); }); diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 8a0eaaa9cd..6fae37f034 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -83,6 +83,7 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) { } ); } else { + const rootOffset = offsetFromRoot(options.appProjectRoot); generateFiles( tree, join(__dirname, './files/app-webpack'), @@ -91,18 +92,21 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) { ...options, ...names(options.name), tmpl: '', - offsetFromRoot: offsetFromRoot(options.appProjectRoot), + offsetFromRoot: rootOffset, rootTsConfigPath, webpackPluginOptions: hasWebpackPlugin(tree) ? { compiler: options.compiler, target: 'web', - outputPath: joinPathFragments( - 'dist', - options.appProjectRoot != '.' - ? options.appProjectRoot - : options.projectName - ), + outputPath: options.isUsingTsSolutionConfig + ? 'dist' + : joinPathFragments( + rootOffset, + 'dist', + options.appProjectRoot !== '.' + ? options.appProjectRoot + : options.projectName + ), tsConfig: './tsconfig.app.json', main: './src/main.ts', assets: ['./src/favicon.ico', './src/assets'], @@ -181,7 +185,7 @@ async function setupBundler(tree: Tree, options: NormalizedSchema) { addPlugin: options.addPlugin, }); const project = readProjectConfiguration(tree, options.projectName); - if (project.targets.build) { + if (project.targets?.build) { const prodConfig = project.targets.build.configurations.production; const buildOptions = project.targets.build.options; buildOptions.assets = assets; diff --git a/packages/web/src/generators/application/files/app-webpack/webpack.config.js__tmpl__ b/packages/web/src/generators/application/files/app-webpack/webpack.config.js__tmpl__ index e92be324f4..23983478b7 100644 --- a/packages/web/src/generators/application/files/app-webpack/webpack.config.js__tmpl__ +++ b/packages/web/src/generators/application/files/app-webpack/webpack.config.js__tmpl__ @@ -4,7 +4,7 @@ const { join } = require('path'); module.exports = { output: { - path: join(__dirname, '<%= offsetFromRoot %><%= webpackPluginOptions.outputPath %>'), + path: join(__dirname, '<%= webpackPluginOptions.outputPath %>'), }, devServer: { port: 4200