From 6b6577ae538f6d5397daad24942888205800f801 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Thu, 1 Dec 2022 18:23:48 +0200 Subject: [PATCH] feat(testing): custom preprocessor --- docs/generated/packages/cypress.json | 2 +- packages/cypress/plugins/cypress-preset.ts | 29 +++++- .../cypress-project/cypress-project.ts | 3 +- .../v10-and-after/cypress.config.ts__tmpl__ | 19 ++-- .../generators/cypress-project/schema.d.ts | 2 +- .../generators/cypress-project/schema.json | 2 +- .../cypress/src/plugins/preprocessor-vite.ts | 89 +++++++++++++++++++ packages/vite/src/utils/generator-utils.ts | 5 +- packages/vite/src/utils/options-utils.ts | 24 +++-- 9 files changed, 147 insertions(+), 28 deletions(-) create mode 100644 packages/cypress/src/plugins/preprocessor-vite.ts diff --git a/docs/generated/packages/cypress.json b/docs/generated/packages/cypress.json index 9012fc7fa882ef..48fd9b0700c73f 100644 --- a/docs/generated/packages/cypress.json +++ b/docs/generated/packages/cypress.json @@ -121,7 +121,7 @@ "bundler": { "description": "The Cypress builder to use.", "type": "string", - "enum": ["vite", "webpack"], + "enum": ["vite", "webpack", "none"], "x-prompt": "Which Cypress builder do you want to use?", "default": "webpack" } diff --git a/packages/cypress/plugins/cypress-preset.ts b/packages/cypress/plugins/cypress-preset.ts index 6b3230c848bca5..ac3eec14ebdf7e 100644 --- a/packages/cypress/plugins/cypress-preset.ts +++ b/packages/cypress/plugins/cypress-preset.ts @@ -15,6 +15,8 @@ import { createProjectRootMappings, findProjectForPath, } from 'nx/src/project-graph/utils/find-project-for-path'; +import path = require('path'); +import vitePreprocessor from '../src/plugins/preprocessor-vite'; interface BaseCypressPreset { videosFolder: string; @@ -72,14 +74,37 @@ export function nxBaseCypressPreset(pathToConfig: string): BaseCypressPreset { * * @param pathToConfig will be used to construct the output paths for videos and screenshots */ -export function nxE2EPreset(pathToConfig: string) { - return { +export function nxE2EPreset( + pathToConfig: string, + options?: { vite: { viteConfigPath: string } } +) { + const baseConfig = { ...nxBaseCypressPreset(pathToConfig), fileServerFolder: '.', supportFile: 'src/support/e2e.ts', specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}', fixturesFolder: 'src/fixtures', }; + + /** + * - Not necessary to have the path to vite config + * - vite.config if it exists it can be cypress-project specific + * - generate without path to vite-config, add in docs how user can provide it + * - add in the docs how to set up custom/more setupNodeEvents + */ + + if (options.vite?.viteConfigPath) { + return { + ...baseConfig, + setupNodeEvents(on) { + on( + 'file:preprocessor', + vitePreprocessor(`${options.vite?.viteConfigPath}`) + ); + }, + }; + } + return baseConfig; } export function getProjectConfigByPath( diff --git a/packages/cypress/src/generators/cypress-project/cypress-project.ts b/packages/cypress/src/generators/cypress-project/cypress-project.ts index 73149a9fc58d4e..6ffc7c97e85604 100644 --- a/packages/cypress/src/generators/cypress-project/cypress-project.ts +++ b/packages/cypress/src/generators/cypress-project/cypress-project.ts @@ -16,7 +16,6 @@ import { toJS, Tree, updateJson, - workspaceRoot, } from '@nrwl/devkit'; import { Linter, lintProjectGenerator } from '@nrwl/linter'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; @@ -57,7 +56,7 @@ function createFiles(tree: Tree, options: CypressProjectSchema) { // We cannot assume the file name const projectViteConfigFile = getViteConfigFileFullPath( tree, - options.projectName, + options.projectName.replace(`-e2e`, ``), options.projectRoot ); diff --git a/packages/cypress/src/generators/cypress-project/files/v10-and-after/cypress.config.ts__tmpl__ b/packages/cypress/src/generators/cypress-project/files/v10-and-after/cypress.config.ts__tmpl__ index 3059ce18416f4b..d7af4463c63a20 100644 --- a/packages/cypress/src/generators/cypress-project/files/v10-and-after/cypress.config.ts__tmpl__ +++ b/packages/cypress/src/generators/cypress-project/files/v10-and-after/cypress.config.ts__tmpl__ @@ -2,18 +2,11 @@ import { defineConfig } from 'cypress'; import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset'; export default defineConfig({ - <% if (bundler !== 'vite'){ %> - e2e: nxE2EPreset(__dirname) - <% } %> - <% if (bundler === 'vite'){ %> - e2e: { - ...nxE2EPreset(__dirname), - setupNodeEvents(on) { - on( - 'file:preprocessor', - vitePreprocessor(path.resolve(__dirname, '<%= offsetFromRoot %><%= projectViteConfigFile %>')) - ); - }, + e2e: nxE2EPreset(__dirname<% if (bundler === 'vite'){ %>, + { + vite: { + viteConfigPath: '<%= cypressProjectSpecificViteConfigPath %>' + } } - <% } %> + <% } %>) }); \ No newline at end of file diff --git a/packages/cypress/src/generators/cypress-project/schema.d.ts b/packages/cypress/src/generators/cypress-project/schema.d.ts index 1e8f2bafb694a1..b991f6cad09730 100644 --- a/packages/cypress/src/generators/cypress-project/schema.d.ts +++ b/packages/cypress/src/generators/cypress-project/schema.d.ts @@ -12,5 +12,5 @@ export interface Schema { standaloneConfig?: boolean; skipPackageJson?: boolean; rootProject?: boolean; - bundler?: 'webpack' | 'vite'; + bundler?: 'webpack' | 'vite' | 'none'; } diff --git a/packages/cypress/src/generators/cypress-project/schema.json b/packages/cypress/src/generators/cypress-project/schema.json index 7e8ec07df25769..347714e9fe1742 100644 --- a/packages/cypress/src/generators/cypress-project/schema.json +++ b/packages/cypress/src/generators/cypress-project/schema.json @@ -69,7 +69,7 @@ "bundler": { "description": "The Cypress builder to use.", "type": "string", - "enum": ["vite", "webpack"], + "enum": ["vite", "webpack", "none"], "x-prompt": "Which Cypress builder do you want to use?", "default": "webpack" } diff --git a/packages/cypress/src/plugins/preprocessor-vite.ts b/packages/cypress/src/plugins/preprocessor-vite.ts new file mode 100644 index 00000000000000..f20c2c89cee434 --- /dev/null +++ b/packages/cypress/src/plugins/preprocessor-vite.ts @@ -0,0 +1,89 @@ +// copied from https://github.com/mammadataei/cypress-vite + +import * as path from 'path'; +import { build, InlineConfig } from 'vite'; +import type { RollupOutput, RollupWatcher, WatcherOptions } from 'rollup'; + +type CypressPreprocessor = ( + file: Record +) => string | Promise; + +/** + * Cypress preprocessor for running e2e tests using vite. + * + * @param {string} userConfigPath + * @example + * setupNodeEvents(on) { + * on( + * 'file:preprocessor', + * vitePreprocessor(path.resolve(__dirname, './vite.config.ts')), + * ) + * }, + */ +function vitePreprocessor(userConfigPath?: string): CypressPreprocessor { + return async (file) => { + const { outputPath, filePath, shouldWatch } = file; + + const fileName = path.basename(outputPath); + const filenameWithoutExtension = path.basename( + outputPath, + path.extname(outputPath) + ); + + const defaultConfig: InlineConfig = { + logLevel: 'silent', + define: { + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + }, + build: { + emptyOutDir: false, + minify: false, + outDir: path.dirname(outputPath), + sourcemap: true, + write: true, + watch: getWatcherConfig(shouldWatch), + lib: { + entry: filePath, + fileName: () => fileName, + formats: ['umd'], + name: filenameWithoutExtension, + }, + }, + }; + + const watcher = await build({ + configFile: userConfigPath, + ...defaultConfig, + }); + + if (shouldWatch && isWatcher(watcher)) { + watcher.on('event', (event) => { + if (event.code === 'END') { + file.emit('rerun'); + } + + if (event.code === 'ERROR') { + console.error(event); + } + }); + + file.on('close', () => { + watcher.close(); + }); + } + + return outputPath; + }; +} + +function getWatcherConfig(shouldWatch: boolean): WatcherOptions | null { + return shouldWatch ? {} : null; +} + +type BuildResult = RollupWatcher | RollupOutput | RollupOutput[]; + +function isWatcher(watcher: BuildResult): watcher is RollupWatcher { + return (watcher as RollupWatcher).on !== undefined; +} + +export default vitePreprocessor; diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts index 3c84c0d2bf110c..458bba38213290 100644 --- a/packages/vite/src/utils/generator-utils.ts +++ b/packages/vite/src/utils/generator-utils.ts @@ -475,10 +475,11 @@ export function getViteConfigFileFullPath( return normalizeConfigFilePath( projectViteConfigFileName, workspaceRoot, - projectConfig.root + projectConfig.root, + tree ); } else if (projectRoot) { - return normalizeConfigFilePath(undefined, workspaceRoot, projectRoot); + return normalizeConfigFilePath(undefined, workspaceRoot, projectRoot, tree); } return undefined; } diff --git a/packages/vite/src/utils/options-utils.ts b/packages/vite/src/utils/options-utils.ts index 480e0b90dac01a..40aa0bf8f5b0c1 100644 --- a/packages/vite/src/utils/options-utils.ts +++ b/packages/vite/src/utils/options-utils.ts @@ -4,6 +4,7 @@ import { logger, parseTargetString, readTargetOptions, + Tree, } from '@nrwl/devkit'; import { existsSync } from 'fs'; import { join, relative } from 'path'; @@ -44,17 +45,28 @@ export async function getBuildAndSharedConfig( } as InlineConfig); } +// I understand that this may seem like an overkill, +// but since we're giving the user the option to set their own vite config +// file, we need to make sure that we're referencing the correct file. +// We cannot assume the file name export function normalizeConfigFilePath( configFile: string, workspaceRoot: string, - projectRoot: string + projectRoot: string, + tree?: Tree ): string { + const customViteFilePath = joinPathFragments( + `${workspaceRoot}/${configFile}` + ); + const jsViteFilePath = joinPathFragments(`${projectRoot}/vite.config.js`); + const tsViteFilePath = joinPathFragments(`${projectRoot}/vite.config.ts`); + return configFile - ? joinPathFragments(`${workspaceRoot}/${configFile}`) - : existsSync(joinPathFragments(`${projectRoot}/vite.config.ts`)) - ? joinPathFragments(`${projectRoot}/vite.config.ts`) - : existsSync(joinPathFragments(`${projectRoot}/vite.config.js`)) - ? joinPathFragments(`${projectRoot}/vite.config.js`) + ? customViteFilePath + : tree?.exists(tsViteFilePath) || existsSync(tsViteFilePath) + ? tsViteFilePath + : tree?.exists(jsViteFilePath) || existsSync(jsViteFilePath) + ? jsViteFilePath : undefined; }