From 8b4e5f66160de0b6c1eb6cb3b0fddf980cda6744 Mon Sep 17 00:00:00 2001 From: Caleb Ukle Date: Wed, 5 Apr 2023 11:07:04 -0500 Subject: [PATCH] fix(testing): prevent loop w/ Cypress watch & Vite processor (#16099) --- .../cypress/src/plugins/preprocessor-vite.ts | 112 +++++++++++------- 1 file changed, 70 insertions(+), 42 deletions(-) diff --git a/packages/cypress/src/plugins/preprocessor-vite.ts b/packages/cypress/src/plugins/preprocessor-vite.ts index 978a1d97dde8b..f5d9fa5ca2ed4 100644 --- a/packages/cypress/src/plugins/preprocessor-vite.ts +++ b/packages/cypress/src/plugins/preprocessor-vite.ts @@ -1,35 +1,57 @@ -// Adapted from: https://github.com/mammadataei/cypress-vite +/** + * https://github.com/mammadataei/cypress-vite + * + * MIT License + * + * Copyright (c) 2022 Mohammad Ataei + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + **/ -import * as path from 'path'; +import { dirname, basename, extname } from 'path'; import type { RollupOutput, RollupWatcher, WatcherOptions } from 'rollup'; +import type { InlineConfig } from 'vite'; type CypressPreprocessor = ( file: Record ) => string | Promise; +type BuildResult = RollupWatcher | RollupOutput | RollupOutput[]; + +const cache = new Map(); + /** - * Cypress preprocessor for running e2e tests using vite. - * - * @param {string} userConfigPath - * @example - * setupNodeEvents(on) { - * on( - * 'file:preprocessor', - * vitePreprocessor(path.resolve(__dirname, './vite.config.ts')), - * ) - * }, + * Use Vite as a file preprocess for Cypress test files. + * This preprocessor shouldn't be used directly. + * Instead, use the nxE2EPreset(__filename, { bundler: 'vite' }) function instead. */ 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) - ); + if (cache.has(filePath)) { + return cache.get(filePath); + } + + const fileName = basename(outputPath); + const filenameWithoutExtension = basename(outputPath, extname(outputPath)); - const defaultConfig = { + const defaultConfig: InlineConfig = { logLevel: 'silent', define: { 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), @@ -37,7 +59,7 @@ function vitePreprocessor(userConfigPath?: string): CypressPreprocessor { build: { emptyOutDir: false, minify: false, - outDir: path.dirname(outputPath), + outDir: dirname(outputPath), sourcemap: true, write: true, watch: getWatcherConfig(shouldWatch), @@ -49,41 +71,47 @@ function vitePreprocessor(userConfigPath?: string): CypressPreprocessor { }, }, }; + + cache.set(filePath, outputPath); + const { build } = require('vite'); - const watcher = await build({ + const watcher = (await build({ configFile: userConfigPath, ...defaultConfig, - }); + })) as BuildResult; - if (shouldWatch && isWatcher(watcher)) { - watcher.on('event', (event) => { - if (event.code === 'END') { - file.emit('rerun'); - } + return new Promise((resolve, reject) => { + if (shouldWatch && isWatcher(watcher)) { + watcher.on('event', (event) => { + if (event.code === 'END') { + resolve(outputPath); + file.emit('rerun'); + } - if (event.code === 'ERROR') { - console.error(event); - } - }); + if (event.code === 'ERROR') { + console.error(event); + reject(new Error(event.error.message)); + } + }); - file.on('close', () => { - watcher.close(); - }); - } - - return outputPath; + file.on('close', () => { + cache.delete(filePath); + watcher.close(); + }); + } else { + resolve(outputPath); + } + }); }; } -function getWatcherConfig(shouldWatch: boolean): WatcherOptions | null { - return shouldWatch ? {} : null; +function isWatcher(maybeWatcher: any): maybeWatcher is RollupWatcher { + return maybeWatcher.on !== undefined; } -type BuildResult = RollupWatcher | RollupOutput | RollupOutput[]; - -function isWatcher(watcher: BuildResult): watcher is RollupWatcher { - return (watcher as RollupWatcher).on !== undefined; +function getWatcherConfig(shouldWatch: boolean): WatcherOptions | null { + return shouldWatch ? {} : null; } export default vitePreprocessor;