From 287a0e0616a509ce24ef0efd0ca5ba1a899cb9c7 Mon Sep 17 00:00:00 2001 From: Nicholas Cunningham Date: Wed, 28 Feb 2024 13:53:20 -0700 Subject: [PATCH] fix(nuxt): Add e2e-ci and serve-static targets (#22056) --- .../packages/nuxt/documents/overview.md | 26 +++++++- docs/shared/packages/nuxt/nuxt-plugin.md | 26 +++++++- e2e/nuxt/src/nuxt.test.ts | 13 +++- .../application/application.spec.ts | 2 + .../src/generators/application/lib/add-e2e.ts | 6 ++ .../nuxt/src/generators/init/lib/utils.ts | 1 + .../plugins/__snapshots__/plugin.spec.ts.snap | 60 +++++++++++++++++++ packages/nuxt/src/plugins/plugin.spec.ts | 4 ++ packages/nuxt/src/plugins/plugin.ts | 53 ++++++++++++++++ 9 files changed, 188 insertions(+), 3 deletions(-) diff --git a/docs/generated/packages/nuxt/documents/overview.md b/docs/generated/packages/nuxt/documents/overview.md index f465df1bb1864..cbd16b1c6100d 100644 --- a/docs/generated/packages/nuxt/documents/overview.md +++ b/docs/generated/packages/nuxt/documents/overview.md @@ -56,7 +56,9 @@ The `@nx/nuxt/plugin` is configured in the `plugins` array in `nx.json`. "options": { "buildTargetName": "build", "testTargetName": "test", - "serveTargetName": "serve" + "serveTargetName": "serve", + "buildStaticTargetName": "build-static", + "serveStaticTargetName": "serve-static" } } ] @@ -65,6 +67,8 @@ The `@nx/nuxt/plugin` is configured in the `plugins` array in `nx.json`. The `buildTargetName`, `testTargetName` and `serveTargetName` options control the names of the inferred Nuxt tasks. The default names are `build`, `test` and `serve`. +The `buildStaticTargetName` and `serveStaticTargetName` options control the names of the inferred Nuxt static tasks. The default names are `build-static` and `serve-static`. + ## Using Nuxt ### Generate a new Nuxt app @@ -78,3 +82,23 @@ nx g @nx/nuxt:app my-app Once you are ready to deploy your Nuxt application, you have absolute freedom to choose any hosting provider that fits your needs. We have detailed [how to deploy your Nuxt application to Vercel in a separate guide](/recipes/nuxt/deploy-nuxt-to-vercel). + +### E2E testing + +By default `nuxt` **does not** generate static HTML files when you run the `build` command. However, Nx provides a `build-static` target that you can use to generate static HTML files for your Nuxt application. Essentially, this target runs the `nuxt build --prerender` command to generate static HTML files. + +To perform end-to-end (E2E) testing on static HTML files using a test runner like Cypress. When you create a Nuxt application, Nx automatically creates a `serve-static` target. This target is designed to serve the static HTML files produced by the `build-static` command. + +This feature is particularly useful for testing in continuous integration (CI) pipelines, where resources may be constrained. Unlike the `serve` target, `serve-static` does not require a Nuxt's Nitro server to operate, making it more efficient and faster by eliminating background processes, such as file change monitoring. + +To utilize the `serve-static` target for testing, run the following command: + +```shell +nx serve-static my-nuxt-app-e2e +``` + +This command performs several actions: + +1. It will build the Nuxt application and generate the static HTML files. +2. It will serve the static HTML files using a simple HTTP server. +3. It will run the Cypress tests against the served static HTML files. diff --git a/docs/shared/packages/nuxt/nuxt-plugin.md b/docs/shared/packages/nuxt/nuxt-plugin.md index f465df1bb1864..cbd16b1c6100d 100644 --- a/docs/shared/packages/nuxt/nuxt-plugin.md +++ b/docs/shared/packages/nuxt/nuxt-plugin.md @@ -56,7 +56,9 @@ The `@nx/nuxt/plugin` is configured in the `plugins` array in `nx.json`. "options": { "buildTargetName": "build", "testTargetName": "test", - "serveTargetName": "serve" + "serveTargetName": "serve", + "buildStaticTargetName": "build-static", + "serveStaticTargetName": "serve-static" } } ] @@ -65,6 +67,8 @@ The `@nx/nuxt/plugin` is configured in the `plugins` array in `nx.json`. The `buildTargetName`, `testTargetName` and `serveTargetName` options control the names of the inferred Nuxt tasks. The default names are `build`, `test` and `serve`. +The `buildStaticTargetName` and `serveStaticTargetName` options control the names of the inferred Nuxt static tasks. The default names are `build-static` and `serve-static`. + ## Using Nuxt ### Generate a new Nuxt app @@ -78,3 +82,23 @@ nx g @nx/nuxt:app my-app Once you are ready to deploy your Nuxt application, you have absolute freedom to choose any hosting provider that fits your needs. We have detailed [how to deploy your Nuxt application to Vercel in a separate guide](/recipes/nuxt/deploy-nuxt-to-vercel). + +### E2E testing + +By default `nuxt` **does not** generate static HTML files when you run the `build` command. However, Nx provides a `build-static` target that you can use to generate static HTML files for your Nuxt application. Essentially, this target runs the `nuxt build --prerender` command to generate static HTML files. + +To perform end-to-end (E2E) testing on static HTML files using a test runner like Cypress. When you create a Nuxt application, Nx automatically creates a `serve-static` target. This target is designed to serve the static HTML files produced by the `build-static` command. + +This feature is particularly useful for testing in continuous integration (CI) pipelines, where resources may be constrained. Unlike the `serve` target, `serve-static` does not require a Nuxt's Nitro server to operate, making it more efficient and faster by eliminating background processes, such as file change monitoring. + +To utilize the `serve-static` target for testing, run the following command: + +```shell +nx serve-static my-nuxt-app-e2e +``` + +This command performs several actions: + +1. It will build the Nuxt application and generate the static HTML files. +2. It will serve the static HTML files using a simple HTTP server. +3. It will run the Cypress tests against the served static HTML files. diff --git a/e2e/nuxt/src/nuxt.test.ts b/e2e/nuxt/src/nuxt.test.ts index 4627ae3d534bd..84aaae69af32d 100644 --- a/e2e/nuxt/src/nuxt.test.ts +++ b/e2e/nuxt/src/nuxt.test.ts @@ -3,6 +3,7 @@ import { cleanupProject, killPorts, newProject, + readJson, runCLI, uniq, } from '@nx/e2e/utils'; @@ -16,7 +17,7 @@ describe('Nuxt Plugin', () => { unsetProjectNameAndRootFormat: false, }); runCLI( - `generate @nx/nuxt:app ${app} --unitTestRunner=vitest --projectNameAndRootFormat=as-provided` + `generate @nx/nuxt:app ${app} --unitTestRunner=vitest --projectNameAndRootFormat=as-provided e2eTestRunner=cypress` ); runCLI( `generate @nx/nuxt:component --directory=${app}/src/components/one --name=one --nameAndDirectoryFormat=as-provided --unitTestRunner=vitest` @@ -54,4 +55,14 @@ describe('Nuxt Plugin', () => { runCLI(`run ${app}:build-storybook --verbose`); checkFilesExist(`${app}/storybook-static/index.html`); }, 300_000); + + it('should have build, serve, build-static, server-static targets', () => { + runCLI(`show project ${app} --json > targets.json`); + + const targets = readJson('targets.json'); + expect(targets.targets['build']).toBeDefined(); + expect(targets.targets['serve']).toBeDefined(); + expect(targets.targets['serve-static']).toBeDefined(); + expect(targets.targets['build-static']).toBeDefined(); + }); }); diff --git a/packages/nuxt/src/generators/application/application.spec.ts b/packages/nuxt/src/generators/application/application.spec.ts index a68f86bb83437..c1db687c18f34 100644 --- a/packages/nuxt/src/generators/application/application.spec.ts +++ b/packages/nuxt/src/generators/application/application.spec.ts @@ -22,6 +22,8 @@ describe('app', () => { expect(projectConfig.targets.build).toBeUndefined(); expect(projectConfig.targets.serve).toBeUndefined(); expect(projectConfig.targets.test).toBeUndefined(); + expect(projectConfig.targets['build-static']).toBeUndefined(); + expect(projectConfig.targets['serve-static']).toBeUndefined(); }); it('should create all new files in the correct location', async () => { diff --git a/packages/nuxt/src/generators/application/lib/add-e2e.ts b/packages/nuxt/src/generators/application/lib/add-e2e.ts index 21531be33936c..d88f7eebc1a73 100644 --- a/packages/nuxt/src/generators/application/lib/add-e2e.ts +++ b/packages/nuxt/src/generators/application/lib/add-e2e.ts @@ -28,6 +28,12 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { bundler: 'vite', skipFormat: true, devServerTarget: `${options.projectName}:serve`, + webServerCommands: { + default: `${getPackageManagerCommand().exec} nx serve ${ + options.projectName + }`, + }, + ciWebServerCommand: `nx run ${options.projectName}:serve-static`, baseUrl: 'http://localhost:4200', jsx: true, addPlugin: true, diff --git a/packages/nuxt/src/generators/init/lib/utils.ts b/packages/nuxt/src/generators/init/lib/utils.ts index 0fc9ab9514a4f..48f4b71fc4da9 100644 --- a/packages/nuxt/src/generators/init/lib/utils.ts +++ b/packages/nuxt/src/generators/init/lib/utils.ts @@ -67,6 +67,7 @@ export function addPlugin(tree: Tree) { options: { buildTargetName: 'build', serveTargetName: 'serve', + serveStaticTargetName: 'serve-static', }, }); } diff --git a/packages/nuxt/src/plugins/__snapshots__/plugin.spec.ts.snap b/packages/nuxt/src/plugins/__snapshots__/plugin.spec.ts.snap index ae889ef7d02dd..9ceb903d4c1cd 100644 --- a/packages/nuxt/src/plugins/__snapshots__/plugin.spec.ts.snap +++ b/packages/nuxt/src/plugins/__snapshots__/plugin.spec.ts.snap @@ -6,6 +6,36 @@ exports[`@nx/nuxt/plugin not root project should create nodes 1`] = ` "my-app": { "root": "my-app", "targets": { + "acme-build-static": { + "cache": true, + "command": "nuxt build --prerender", + "dependsOn": [ + "^acme-build-static", + ], + "inputs": [ + "production", + "^production", + { + "externalDependencies": [ + "nuxt", + ], + }, + ], + "options": { + "cwd": "my-app", + }, + "outputs": [ + "{workspaceRoot}/dist/my-app/", + ], + }, + "acme-serve-static": { + "executor": "@nx/web:file-server", + "options": { + "buildTarget": "acme-build-static", + "port": 4200, + "staticFilePath": "{projectRoot}/dist", + }, + }, "build-something": { "cache": true, "command": "nuxt build", @@ -68,12 +98,42 @@ exports[`@nx/nuxt/plugin root project should create nodes 1`] = ` "dist/my-app/", ], }, + "build-static": { + "cache": true, + "command": "nuxt build --prerender", + "dependsOn": [ + "^build-static", + ], + "inputs": [ + "production", + "^production", + { + "externalDependencies": [ + "nuxt", + ], + }, + ], + "options": { + "cwd": ".", + }, + "outputs": [ + "dist/my-app/", + ], + }, "serve": { "command": "nuxt dev", "options": { "cwd": ".", }, }, + "serve-static": { + "executor": "@nx/web:file-server", + "options": { + "buildTarget": "build-static", + "port": 4200, + "staticFilePath": "{projectRoot}/dist", + }, + }, }, }, }, diff --git a/packages/nuxt/src/plugins/plugin.spec.ts b/packages/nuxt/src/plugins/plugin.spec.ts index 0e454de1448dd..60b47d475a3b1 100644 --- a/packages/nuxt/src/plugins/plugin.spec.ts +++ b/packages/nuxt/src/plugins/plugin.spec.ts @@ -51,6 +51,8 @@ describe('@nx/nuxt/plugin', () => { { buildTargetName: 'build', serveTargetName: 'serve', + buildStaticTargetName: 'build-static', + serveStaticTargetName: 'serve-static', }, context ); @@ -89,6 +91,8 @@ describe('@nx/nuxt/plugin', () => { { buildTargetName: 'build-something', serveTargetName: 'my-serve', + buildStaticTargetName: 'acme-build-static', + serveStaticTargetName: 'acme-serve-static', }, context ); diff --git a/packages/nuxt/src/plugins/plugin.ts b/packages/nuxt/src/plugins/plugin.ts index 2ef803a7ff362..653e899b7b480 100644 --- a/packages/nuxt/src/plugins/plugin.ts +++ b/packages/nuxt/src/plugins/plugin.ts @@ -46,6 +46,8 @@ export const createDependencies: CreateDependencies = () => { export interface NuxtPluginOptions { buildTargetName?: string; serveTargetName?: string; + serveStaticTargetName?: string; + buildStaticTargetName?: string; } export const createNodes: CreateNodes = [ @@ -108,6 +110,15 @@ async function buildNuxtTargets( targets[options.serveTargetName] = serveTarget(projectRoot); + targets[options.serveStaticTargetName] = serveStaticTarget(options); + + targets[options.buildStaticTargetName] = buildStaticTarget( + options.buildStaticTargetName, + namedInputs, + buildOutputs, + projectRoot + ); + return targets; } @@ -148,6 +159,46 @@ function serveTarget(projectRoot: string) { return targetConfig; } +function serveStaticTarget(options: NuxtPluginOptions) { + const targetConfig: TargetConfiguration = { + executor: '@nx/web:file-server', + options: { + buildTarget: `${options.buildStaticTargetName}`, + staticFilePath: '{projectRoot}/dist', + port: 4200, + }, + }; + + return targetConfig; +} + +function buildStaticTarget( + buildStaticTargetName: string, + namedInputs: { + [inputName: string]: any[]; + }, + buildOutputs: string[], + projectRoot: string +) { + const targetConfig: TargetConfiguration = { + command: `nuxt build --prerender`, + options: { cwd: projectRoot }, + cache: true, + dependsOn: [`^${buildStaticTargetName}`], + inputs: [ + ...('production' in namedInputs + ? ['production', '^production'] + : ['default', '^default']), + + { + externalDependencies: ['nuxt'], + }, + ], + outputs: buildOutputs, + }; + return targetConfig; +} + async function getInfoFromNuxtConfig( configFilePath: string, context: CreateNodesContext, @@ -216,5 +267,7 @@ function normalizeOptions(options: NuxtPluginOptions): NuxtPluginOptions { options ??= {}; options.buildTargetName ??= 'build'; options.serveTargetName ??= 'serve'; + options.serveStaticTargetName ??= 'serve-static'; + options.buildStaticTargetName ??= 'build-static'; return options; }