feat(testing): cypress vite (#13474)
This commit is contained in:
parent
92d33f9539
commit
1ef01f8ccc
@ -111,10 +111,23 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`."
|
||||
},
|
||||
"rootProject": {
|
||||
"description": "Create a application at the root of the workspace",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The Cypress bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack", "none"],
|
||||
"x-prompt": "Which Cypress bundler do you want to use?",
|
||||
"default": "webpack"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"examplesFile": "Adding Cypress to an existing application requires two options. The name of the e2e app to create and what project that e2e app is for.\n\n```bash\nnx g cypress-project --name=my-app-e2e --project=my-app\n```\n\nWhen providing `--project` option, the generator will look for the `serve` target in that given project. This allows the [cypress executor](/packages/cypress/executors/cypress) to spin up the project and start the cypress runner.\n\nIf you prefer to not have the project served automatically, you can provide a `--base-url` argument in place of `--project`\n\n```bash\nnx g cypress-project --name=my-app-e2e --base-url=http://localhost:1234\n```\n\n{% callout type=\"note\" title=\"What about API Projects?\" %}\nYou can also run the `cypress-project` generator against API projects like a [Nest API](/packages/nest/generators/application#@nrwl/nest:application).\nIf there is a URL to visit then you can test it with Cypress!\n{% /callout %}\n",
|
||||
"examplesFile": "Adding Cypress to an existing application requires two options. The name of the e2e app to create and what project that e2e app is for.\n\n```bash\nnx g cypress-project --name=my-app-e2e --project=my-app\n```\n\nWhen providing `--project` option, the generator will look for the `serve` target in that given project. This allows the [cypress executor](/packages/cypress/executors/cypress) to spin up the project and start the cypress runner.\n\nIf you prefer to not have the project served automatically, you can provide a `--base-url` argument in place of `--project`\n\n```bash\nnx g cypress-project --name=my-app-e2e --base-url=http://localhost:1234\n```\n\n{% callout type=\"note\" title=\"What about API Projects?\" %}\nYou can also run the `cypress-project` generator against API projects like a [Nest API](/packages/nest/generators/application#@nrwl/nest:application).\nIf there is a URL to visit then you can test it with Cypress!\n{% /callout %}\n\n## Using Cypress with Vite.js\n\nNow, you can generate your Cypress project with Vite.js as the bundler:\n\n```bash\nnx g cypress-project --name=my-app-e2e --project=my-app --bundler=vite\n```\n\nThis generator will pass the `bundler` information (`bundler: 'vite'`) to our `nxE2EPreset`, in your project's `cypress.config.ts` file (eg. `my-app-e2e/cypress.config.ts`).\n\n### Customizing the Vite.js configuration\n\nThe `nxE2EPreset` will then use the `bundler` information to generate the correct settings for your Cypress project to use Vite.js. In the background, the way this works is that it's using a custom Vite preprocessor for your files, that's called on the `file:preprocessor` event. If you want to customize this behaviour, you can do so like this in your project's `cypress.config.ts` file:\n\n```ts\nimport { defineConfig } from 'cypress';\nimport { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';\n\nconst config = nxE2EPreset(__filename, { bundler: 'vite' });\nexport default defineConfig({\n e2e: {\n ...config,\n setupNodeEvents(on, config): {\n config.setupNodeEvents(on);\n // Your settings here\n }\n },\n});\n```\n",
|
||||
"presets": []
|
||||
},
|
||||
"description": "Add a Cypress E2E Project.",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -114,6 +114,7 @@
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack"],
|
||||
"x-prompt": "Which bundler do you want to use?",
|
||||
"default": "webpack"
|
||||
@ -196,7 +197,10 @@
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The Storybook builder to use.",
|
||||
"enum": ["vite", "webpack"]
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack"],
|
||||
"x-prompt": "Which Storybook builder do you want to use?",
|
||||
"default": "webpack"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
|
||||
@ -85,7 +85,7 @@ ${e.stack ? e.stack : e}`
|
||||
const offset = offsetFromRoot(normalizedFromWorkspaceRootPath);
|
||||
const buildContext = createExecutorContext(
|
||||
graph,
|
||||
graph.nodes[buildTarget.project].data.targets,
|
||||
graph.nodes[buildTarget.project]?.data.targets,
|
||||
buildTarget.project,
|
||||
buildTarget.target,
|
||||
buildTarget.configuration
|
||||
@ -117,7 +117,7 @@ ${e.stack ? e.stack : e}`
|
||||
|
||||
function getBuildableTarget(ctContext: ExecutorContext) {
|
||||
const targets =
|
||||
ctContext.projectGraph.nodes[ctContext.projectName].data?.targets;
|
||||
ctContext.projectGraph.nodes[ctContext.projectName]?.data?.targets;
|
||||
const targetConfig = targets?.[ctContext.targetName];
|
||||
|
||||
if (!targetConfig) {
|
||||
@ -232,7 +232,7 @@ function normalizeBuildTargetOptions(
|
||||
buildOptions.stylePreprocessorOptions = { includePaths: [] };
|
||||
}
|
||||
const { root, sourceRoot } =
|
||||
buildContext.projectGraph.nodes[buildContext.projectName].data;
|
||||
buildContext.projectGraph.nodes[buildContext.projectName]?.data;
|
||||
return {
|
||||
root: joinPathFragments(offset, root),
|
||||
sourceRoot: joinPathFragments(offset, sourceRoot),
|
||||
@ -280,7 +280,7 @@ function withSchemaDefaults(options: any): BrowserBuilderSchema {
|
||||
function getTempStylesForTailwind(ctExecutorContext: ExecutorContext) {
|
||||
const ctProjectConfig = ctExecutorContext.projectGraph.nodes[
|
||||
ctExecutorContext.projectName
|
||||
].data as ProjectConfiguration;
|
||||
]?.data as ProjectConfiguration;
|
||||
// angular only supports `tailwind.config.{js,cjs}`
|
||||
const ctProjectTailwindConfig = join(
|
||||
ctExecutorContext.root,
|
||||
|
||||
@ -13,6 +13,15 @@ export default defineConfig({
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`e2e migrator cypress with project root at "" cypress version >=10 should create a cypress.config.ts file when it does not exist 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname)
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`e2e migrator cypress with project root at "" cypress version >=10 should keep paths in the e2e config when they differ from the nx preset defaults 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
@ -41,11 +50,11 @@ exports[`e2e migrator cypress with project root at "" cypress version >=10 shoul
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
export default defineConfig({
|
||||
e2e: {...nxE2EPreset(__dirname),
|
||||
baseUrl: 'http://localhost:4200'
|
||||
},
|
||||
});"
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`e2e migrator cypress with project root at "" cypress version >=10 should update paths in the config 1`] = `
|
||||
@ -79,6 +88,15 @@ export default defineConfig({
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`e2e migrator cypress with project root at "projects/app1" cypress version >=10 should create a cypress.config.ts file when it does not exist 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname)
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`e2e migrator cypress with project root at "projects/app1" cypress version >=10 should keep paths in the e2e config when they differ from the nx preset defaults 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
@ -107,11 +125,11 @@ exports[`e2e migrator cypress with project root at "projects/app1" cypress versi
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
export default defineConfig({
|
||||
e2e: {...nxE2EPreset(__dirname),
|
||||
baseUrl: 'http://localhost:4200'
|
||||
},
|
||||
});"
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`e2e migrator cypress with project root at "projects/app1" cypress version >=10 should update paths in the config 1`] = `
|
||||
|
||||
@ -840,13 +840,8 @@ describe('e2e migrator', () => {
|
||||
'apps/app1-e2e/cypress.config.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(cypressConfig).toBe(`import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname)
|
||||
});
|
||||
`);
|
||||
expect(cypressConfig).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update e2e config with the nx preset', async () => {
|
||||
@ -856,11 +851,11 @@ export default defineConfig({
|
||||
joinPathFragments(root, 'cypress.config.ts'),
|
||||
`import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:4200'
|
||||
},
|
||||
});`
|
||||
});`
|
||||
);
|
||||
const project = addProject('app1', {
|
||||
root,
|
||||
|
||||
@ -16,3 +16,33 @@ nx g cypress-project --name=my-app-e2e --base-url=http://localhost:1234
|
||||
You can also run the `cypress-project` generator against API projects like a [Nest API](/packages/nest/generators/application#@nrwl/nest:application).
|
||||
If there is a URL to visit then you can test it with Cypress!
|
||||
{% /callout %}
|
||||
|
||||
## Using Cypress with Vite.js
|
||||
|
||||
Now, you can generate your Cypress project with Vite.js as the bundler:
|
||||
|
||||
```bash
|
||||
nx g cypress-project --name=my-app-e2e --project=my-app --bundler=vite
|
||||
```
|
||||
|
||||
This generator will pass the `bundler` information (`bundler: 'vite'`) to our `nxE2EPreset`, in your project's `cypress.config.ts` file (eg. `my-app-e2e/cypress.config.ts`).
|
||||
|
||||
### Customizing the Vite.js configuration
|
||||
|
||||
The `nxE2EPreset` will then use the `bundler` information to generate the correct settings for your Cypress project to use Vite.js. In the background, the way this works is that it's using a custom Vite preprocessor for your files, that's called on the `file:preprocessor` event. If you want to customize this behaviour, you can do so like this in your project's `cypress.config.ts` file:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
const config = nxE2EPreset(__filename, { bundler: 'vite' });
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...config,
|
||||
setupNodeEvents(on, config): {
|
||||
config.setupNodeEvents(on);
|
||||
// Your settings here
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@ -2,6 +2,8 @@ import { workspaceRoot } from '@nrwl/devkit';
|
||||
import { dirname, join, relative } from 'path';
|
||||
import { lstatSync } from 'fs';
|
||||
|
||||
import vitePreprocessor from '../src/plugins/preprocessor-vite';
|
||||
|
||||
interface BaseCypressPreset {
|
||||
videosFolder: string;
|
||||
screenshotsFolder: string;
|
||||
@ -15,8 +17,10 @@ export interface NxComponentTestingOptions {
|
||||
* this is only when customized away from the default value of `component-test`
|
||||
* @example 'component-test'
|
||||
*/
|
||||
ctTargetName: string;
|
||||
ctTargetName?: string;
|
||||
bundler?: 'vite' | 'webpack';
|
||||
}
|
||||
|
||||
export function nxBaseCypressPreset(pathToConfig: string): BaseCypressPreset {
|
||||
// prevent from placing path outside the root of the workspace
|
||||
// if they pass in a file or directory
|
||||
@ -58,12 +62,25 @@ 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?: { bundler?: string }
|
||||
) {
|
||||
const baseConfig = {
|
||||
...nxBaseCypressPreset(pathToConfig),
|
||||
fileServerFolder: '.',
|
||||
supportFile: 'src/support/e2e.ts',
|
||||
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
|
||||
fixturesFolder: 'src/fixtures',
|
||||
};
|
||||
|
||||
if (options?.bundler === 'vite') {
|
||||
return {
|
||||
...baseConfig,
|
||||
setupNodeEvents(on) {
|
||||
on('file:preprocessor', vitePreprocessor());
|
||||
},
|
||||
};
|
||||
}
|
||||
return baseConfig;
|
||||
}
|
||||
|
||||
@ -193,14 +193,26 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 for bundler:vite should pass the bundler info to nxE2EPreset in \`cypress.config.ts\` 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname,
|
||||
{
|
||||
bundler: 'vite'
|
||||
}
|
||||
)
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 nested should set right path names in \`cypress.config.ts\` 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname)
|
||||
});
|
||||
"
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 nested should set right path names in \`tsconfig.e2e.json\` 1`] = `
|
||||
@ -243,8 +255,7 @@ import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname)
|
||||
});
|
||||
"
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 should set right path names in \`tsconfig.e2e.json\` 1`] = `
|
||||
|
||||
@ -189,6 +189,22 @@ describe('Cypress Project', () => {
|
||||
expect(tsConfig.extends).toBe('../../tsconfig.json');
|
||||
});
|
||||
|
||||
describe('for bundler:vite', () => {
|
||||
it('should pass the bundler info to nxE2EPreset in `cypress.config.ts`', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
bundler: 'vite',
|
||||
});
|
||||
const cypressConfig = tree.read(
|
||||
'apps/my-app-e2e/cypress.config.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(cypressConfig).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested', () => {
|
||||
it('should set right path names in `cypress.config.ts`', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
extractLayoutDirectory,
|
||||
formatFiles,
|
||||
generateFiles,
|
||||
getProjects,
|
||||
getWorkspaceLayout,
|
||||
joinPathFragments,
|
||||
logger,
|
||||
@ -16,7 +17,6 @@ import {
|
||||
toJS,
|
||||
Tree,
|
||||
updateJson,
|
||||
getProjects,
|
||||
} from '@nrwl/devkit';
|
||||
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||
@ -32,6 +32,7 @@ import { filePathPrefix } from '../../utils/project-name';
|
||||
import {
|
||||
cypressVersion,
|
||||
eslintPluginCypressVersion,
|
||||
viteVersion,
|
||||
} from '../../utils/versions';
|
||||
import { cypressInitGenerator } from '../init/init';
|
||||
// app
|
||||
@ -63,6 +64,7 @@ function createFiles(tree: Tree, options: CypressProjectSchema) {
|
||||
tree,
|
||||
options.projectRoot
|
||||
),
|
||||
bundler: options.bundler,
|
||||
}
|
||||
);
|
||||
|
||||
@ -271,6 +273,19 @@ export async function cypressProjectGenerator(host: Tree, schema: Schema) {
|
||||
if (!cypressVersion) {
|
||||
tasks.push(cypressInitGenerator(host, options));
|
||||
}
|
||||
|
||||
if (schema.bundler === 'vite') {
|
||||
tasks.push(
|
||||
addDependenciesToPackageJson(
|
||||
host,
|
||||
{},
|
||||
{
|
||||
vite: viteVersion,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
createFiles(host, options);
|
||||
addProject(host, options);
|
||||
const installTask = await addLinter(host, options);
|
||||
@ -320,6 +335,7 @@ function normalizeOptions(host: Tree, options: Schema): CypressProjectSchema {
|
||||
}
|
||||
|
||||
options.linter = options.linter || Linter.EsLint;
|
||||
options.bundler = options.bundler || 'webpack';
|
||||
return {
|
||||
...options,
|
||||
// other generators depend on the rootProject flag down stream
|
||||
|
||||
@ -2,5 +2,9 @@ import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname)
|
||||
e2e: nxE2EPreset(__dirname<% if (bundler === 'vite'){ %>,
|
||||
{
|
||||
bundler: 'vite'
|
||||
}
|
||||
<% } %>)
|
||||
});
|
||||
@ -12,4 +12,5 @@ export interface Schema {
|
||||
standaloneConfig?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
rootProject?: boolean;
|
||||
bundler?: 'webpack' | 'vite' | 'none';
|
||||
}
|
||||
|
||||
@ -59,6 +59,19 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`."
|
||||
},
|
||||
"rootProject": {
|
||||
"description": "Create a application at the root of the workspace",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The Cypress bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack", "none"],
|
||||
"x-prompt": "Which Cypress bundler do you want to use?",
|
||||
"default": "webpack"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
|
||||
89
packages/cypress/src/plugins/preprocessor-vite.ts
Normal file
89
packages/cypress/src/plugins/preprocessor-vite.ts
Normal file
@ -0,0 +1,89 @@
|
||||
// Adapted from: https://github.com/mammadataei/cypress-vite
|
||||
|
||||
import * as path from 'path';
|
||||
import type { RollupOutput, RollupWatcher, WatcherOptions } from 'rollup';
|
||||
|
||||
type CypressPreprocessor = (
|
||||
file: Record<string, any>
|
||||
) => string | Promise<string>;
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
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 { build } = require('vite');
|
||||
|
||||
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;
|
||||
@ -4,3 +4,4 @@ export const typesNodeVersion = '16.11.7';
|
||||
export const cypressVersion = '^11.0.0';
|
||||
export const cypressWebpackVersion = '^2.0.0';
|
||||
export const webpackHttpPluginVersion = '^5.5.0';
|
||||
export const viteVersion = '^4.0.1';
|
||||
|
||||
@ -16,12 +16,29 @@ nx g @nrwl/react:cypress-component-project --project=my-cool-react-project
|
||||
|
||||
Running this generator, adds the required files to the specified project with a preconfigured `cypress.config.ts` designed for Nx workspaces.
|
||||
|
||||
The following file will be added to projects where the Component Testing build target is using `webpack` for bundling:
|
||||
|
||||
```ts {% fileName="cypress.config.ts" %}
|
||||
import { defineConfig } from 'cypress';
|
||||
import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing';
|
||||
|
||||
export default defineConfig({
|
||||
component: nxComponentTestingPreset(__filename),
|
||||
component: nxComponentTestingPreset(__filename, {
|
||||
bundler: 'webpack',
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
The following file will be added to projects where the Component Testing build target is using `vite` for bundling:
|
||||
|
||||
```ts {% fileName="cypress.config.ts" %}
|
||||
import { defineConfig } from 'cypress';
|
||||
import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing';
|
||||
|
||||
export default defineConfig({
|
||||
component: nxComponentTestingPreset(__filename, {
|
||||
bundler: 'vite',
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
@ -33,12 +50,20 @@ import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
...nxComponentTestingPreset(__filename),
|
||||
...nxComponentTestingPreset(__filename, {
|
||||
bundler: 'webpack',
|
||||
}),
|
||||
// extra options here
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## The `bundler` option
|
||||
|
||||
Component testing supports two different bundlers: `webpack` and `vite`. The Nx generator will pick up the bundler used in the specified project's build target. If the build target is using `@nrwl/webpack:webpack`, then the generator will use `webpack` as the bundler. If the build target is using `@nrwl/vite:build`, then the generator will use `vite` as the bundler.
|
||||
|
||||
You can manually set the bundler by passing `--bundler=webpack` or `--bundler=vite` to the generator, but that is not needed since the generator will pick up the correct bundler for you. However, if you want to use a different bundler than the one that is used in the build target, then you can manually set it using that flag.
|
||||
|
||||
## Specifying a Build Target
|
||||
|
||||
Component testing requires a _build target_ to correctly run the component test dev server. This option can be manually specified with `--build-target=some-react-app:build`, but Nx will infer this usage from the [project graph](/concepts/mental-model#the-project-graph) if one isn't provided.
|
||||
|
||||
@ -14,11 +14,6 @@ import {
|
||||
Target,
|
||||
workspaceRoot,
|
||||
} from '@nrwl/devkit';
|
||||
import type { WebpackExecutorOptions } from '@nrwl/webpack/src/executors/webpack/schema';
|
||||
import { normalizeOptions } from '@nrwl/webpack/src/executors/webpack/lib/normalize-options';
|
||||
import { getWebpackConfig } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config';
|
||||
import { resolveCustomWebpackConfig } from '@nrwl/webpack/src/utils/webpack/custom-webpack';
|
||||
import { buildBaseWebpackConfig } from './webpack-fallback';
|
||||
import {
|
||||
createExecutorContext,
|
||||
getProjectConfigByPath,
|
||||
@ -45,7 +40,29 @@ import {
|
||||
export function nxComponentTestingPreset(
|
||||
pathToConfig: string,
|
||||
options?: NxComponentTestingOptions
|
||||
) {
|
||||
): {
|
||||
specPattern: string;
|
||||
devServer: {
|
||||
framework?: 'react';
|
||||
bundler?: 'vite' | 'webpack';
|
||||
viteConfig?: any;
|
||||
webpackConfig?: any;
|
||||
};
|
||||
videosFolder: string;
|
||||
screenshotsFolder: string;
|
||||
video: boolean;
|
||||
chromeWebSecurity: boolean;
|
||||
} {
|
||||
if (options.bundler === 'vite') {
|
||||
return {
|
||||
...nxBaseCypressPreset(pathToConfig),
|
||||
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
|
||||
devServer: {
|
||||
...({ framework: 'react', bundler: 'vite' } as const),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let webpackConfig;
|
||||
try {
|
||||
const graph = readCachedProjectGraph();
|
||||
@ -88,11 +105,14 @@ export function nxComponentTestingPreset(
|
||||
Falling back to default webpack config.`
|
||||
);
|
||||
logger.warn(e);
|
||||
|
||||
const { buildBaseWebpackConfig } = require('./webpack-fallback');
|
||||
webpackConfig = buildBaseWebpackConfig({
|
||||
tsConfigPath: 'cypress/tsconfig.cy.json',
|
||||
compiler: 'babel',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...nxBaseCypressPreset(pathToConfig),
|
||||
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
|
||||
@ -109,11 +129,8 @@ export function nxComponentTestingPreset(
|
||||
/**
|
||||
* apply the schema.json defaults from the @nrwl/web:webpack executor to the target options
|
||||
*/
|
||||
function withSchemaDefaults(
|
||||
target: Target,
|
||||
context: ExecutorContext
|
||||
): WebpackExecutorOptions {
|
||||
const options = readTargetOptions<WebpackExecutorOptions>(target, context);
|
||||
function withSchemaDefaults(target: Target, context: ExecutorContext) {
|
||||
const options = readTargetOptions(target, context);
|
||||
|
||||
options.compiler ??= 'babel';
|
||||
options.deleteOutputPath ??= true;
|
||||
@ -161,6 +178,16 @@ function buildTargetWebpack(
|
||||
parsed.target
|
||||
);
|
||||
|
||||
const {
|
||||
normalizeOptions,
|
||||
} = require('@nrwl/webpack/src/executors/webpack/lib/normalize-options');
|
||||
const {
|
||||
resolveCustomWebpackConfig,
|
||||
} = require('@nrwl/webpack/src/utils/webpack/custom-webpack');
|
||||
const {
|
||||
getWebpackConfig,
|
||||
} = require('@nrwl/webpack/src/executors/webpack/lib/get-webpack-config');
|
||||
|
||||
const options = normalizeOptions(
|
||||
withSchemaDefaults(parsed, context),
|
||||
workspaceRoot,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { getCSSModuleLocalIdent } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config';
|
||||
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
|
||||
import { Configuration } from 'webpack';
|
||||
import { getCSSModuleLocalIdent } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config';
|
||||
|
||||
export function buildBaseWebpackConfig({
|
||||
tsConfigPath = 'tsconfig.cy.json',
|
||||
|
||||
@ -1,5 +1,49 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`React:CypressComponentTestConfiguration should generate cypress component test config with --build-target 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing';
|
||||
|
||||
export default defineConfig({
|
||||
component: nxComponentTestingPreset(__filename, {
|
||||
bundler: 'vite'
|
||||
}) as any,
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`React:CypressComponentTestConfiguration should generate cypress component test config with project graph 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing';
|
||||
|
||||
export default defineConfig({
|
||||
component: nxComponentTestingPreset(__filename, {
|
||||
bundler: 'vite'
|
||||
}) as any,
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`React:CypressComponentTestConfiguration should generate cypress component test config with webpack 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing';
|
||||
|
||||
export default defineConfig({
|
||||
component: nxComponentTestingPreset(__filename, {
|
||||
bundler: 'webpack'
|
||||
}) as any,
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`React:CypressComponentTestConfiguration should generate cypress config with vite 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing';
|
||||
|
||||
export default defineConfig({
|
||||
component: nxComponentTestingPreset(__filename, {
|
||||
bundler: 'vite'
|
||||
}) as any,
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`React:CypressComponentTestConfiguration should generate tests for existing js components 1`] = `
|
||||
"import * as React from 'react'
|
||||
import SomeCmp from './some-cmp'
|
||||
|
||||
@ -30,6 +30,63 @@ describe('React:CypressComponentTestConfiguration', () => {
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
});
|
||||
|
||||
it('should generate cypress config with vite', async () => {
|
||||
mockedAssertCypressVersion.mockReturnValue();
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
e2eTestRunner: 'none',
|
||||
linter: Linter.EsLint,
|
||||
skipFormat: true,
|
||||
style: 'scss',
|
||||
unitTestRunner: 'none',
|
||||
name: 'my-app',
|
||||
bundler: 'vite',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
linter: Linter.EsLint,
|
||||
name: 'some-lib',
|
||||
skipFormat: true,
|
||||
skipTsConfig: false,
|
||||
style: 'scss',
|
||||
unitTestRunner: 'none',
|
||||
component: true,
|
||||
});
|
||||
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
'my-app': {
|
||||
name: 'my-app',
|
||||
type: 'app',
|
||||
data: {
|
||||
...readProjectConfiguration(tree, 'my-app'),
|
||||
},
|
||||
},
|
||||
'some-lib': {
|
||||
name: 'some-lib',
|
||||
type: 'lib',
|
||||
data: {
|
||||
...readProjectConfiguration(tree, 'some-lib'),
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'my-app': [
|
||||
{ type: DependencyType.static, source: 'my-app', target: 'some-lib' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await cypressComponentConfigGenerator(tree, {
|
||||
project: 'some-lib',
|
||||
generateTests: false,
|
||||
buildTarget: 'my-app:build',
|
||||
});
|
||||
|
||||
const config = tree.read('libs/some-lib/cypress.config.ts', 'utf-8');
|
||||
expect(config).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate cypress component test config with --build-target', async () => {
|
||||
mockedAssertCypressVersion.mockReturnValue();
|
||||
|
||||
@ -83,12 +140,7 @@ describe('React:CypressComponentTestConfiguration', () => {
|
||||
});
|
||||
|
||||
const config = tree.read('libs/some-lib/cypress.config.ts', 'utf-8');
|
||||
expect(config).toContain(
|
||||
"import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing"
|
||||
);
|
||||
expect(config).toContain(
|
||||
'component: nxComponentTestingPreset(__filename),'
|
||||
);
|
||||
expect(config).toMatchSnapshot();
|
||||
|
||||
expect(
|
||||
readProjectConfiguration(tree, 'some-lib').targets['component-test']
|
||||
@ -154,12 +206,7 @@ describe('React:CypressComponentTestConfiguration', () => {
|
||||
});
|
||||
|
||||
const config = tree.read('libs/some-lib/cypress.config.ts', 'utf-8');
|
||||
expect(config).toContain(
|
||||
"import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing"
|
||||
);
|
||||
expect(config).toContain(
|
||||
'component: nxComponentTestingPreset(__filename),'
|
||||
);
|
||||
expect(config).toMatchSnapshot();
|
||||
|
||||
expect(
|
||||
readProjectConfiguration(tree, 'some-lib').targets['component-test']
|
||||
@ -174,6 +221,71 @@ describe('React:CypressComponentTestConfiguration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate cypress component test config with webpack', async () => {
|
||||
mockedAssertCypressVersion.mockReturnValue();
|
||||
await applicationGenerator(tree, {
|
||||
e2eTestRunner: 'none',
|
||||
linter: Linter.EsLint,
|
||||
skipFormat: true,
|
||||
style: 'scss',
|
||||
unitTestRunner: 'none',
|
||||
name: 'my-app',
|
||||
bundler: 'webpack',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
linter: Linter.EsLint,
|
||||
name: 'some-lib',
|
||||
skipFormat: true,
|
||||
skipTsConfig: false,
|
||||
style: 'scss',
|
||||
unitTestRunner: 'none',
|
||||
component: true,
|
||||
});
|
||||
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
'my-app': {
|
||||
name: 'my-app',
|
||||
type: 'app',
|
||||
data: {
|
||||
...readProjectConfiguration(tree, 'my-app'),
|
||||
},
|
||||
},
|
||||
'some-lib': {
|
||||
name: 'some-lib',
|
||||
type: 'lib',
|
||||
data: {
|
||||
...readProjectConfiguration(tree, 'some-lib'),
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'my-app': [
|
||||
{ type: DependencyType.static, source: 'my-app', target: 'some-lib' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await cypressComponentConfigGenerator(tree, {
|
||||
project: 'some-lib',
|
||||
generateTests: false,
|
||||
});
|
||||
|
||||
const config = tree.read('libs/some-lib/cypress.config.ts', 'utf-8');
|
||||
expect(config).toMatchSnapshot();
|
||||
|
||||
expect(
|
||||
readProjectConfiguration(tree, 'some-lib').targets['component-test']
|
||||
).toEqual({
|
||||
executor: '@nrwl/cypress:cypress',
|
||||
options: {
|
||||
cypressConfig: 'libs/some-lib/cypress.config.ts',
|
||||
devServerTarget: 'my-app:build',
|
||||
skipServe: true,
|
||||
testingType: 'component',
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should generate tests for existing tsx components', async () => {
|
||||
mockedAssertCypressVersion.mockReturnValue();
|
||||
await applicationGenerator(tree, {
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
} from '@nrwl/devkit';
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
import { addFiles } from './lib/add-files';
|
||||
import { updateProjectConfig } from './lib/update-configs';
|
||||
import { FoundTarget, updateProjectConfig } from './lib/update-configs';
|
||||
import { CypressComponentConfigurationSchema } from './schema.d';
|
||||
|
||||
/**
|
||||
@ -26,8 +26,8 @@ export async function cypressComponentConfigGenerator(
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
await updateProjectConfig(tree, options);
|
||||
await addFiles(tree, projectConfig, options);
|
||||
const found: FoundTarget = await updateProjectConfig(tree, options);
|
||||
await addFiles(tree, projectConfig, options, found);
|
||||
if (options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
@ -2,5 +2,7 @@ import { defineConfig } from 'cypress';
|
||||
import { nxComponentTestingPreset } from '@nrwl/react/plugins/component-testing';
|
||||
|
||||
export default defineConfig({
|
||||
component: nxComponentTestingPreset(__filename),
|
||||
component: nxComponentTestingPreset(__filename, {
|
||||
bundler: '<%= bundler %>'
|
||||
}) as any,
|
||||
});
|
||||
@ -1,14 +1,20 @@
|
||||
import {
|
||||
ensurePackage,
|
||||
generateFiles,
|
||||
joinPathFragments,
|
||||
logger,
|
||||
parseTargetString,
|
||||
ProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
visitNotIgnoredFiles,
|
||||
} from '@nrwl/devkit';
|
||||
import { nxVersion } from 'nx/src/utils/versions';
|
||||
import * as ts from 'typescript';
|
||||
import { getComponentNode } from '../../../utils/ast-utils';
|
||||
import { componentTestGenerator } from '../../component-test/component-test';
|
||||
import { CypressComponentConfigurationSchema } from '../schema';
|
||||
import { FoundTarget } from './update-configs';
|
||||
|
||||
const allowedFileExt = new RegExp(/\.[jt]sx?/g);
|
||||
const isSpecFile = new RegExp(/(spec|test)\./g);
|
||||
@ -16,7 +22,8 @@ const isSpecFile = new RegExp(/(spec|test)\./g);
|
||||
export async function addFiles(
|
||||
tree: Tree,
|
||||
projectConfig: ProjectConfiguration,
|
||||
options: CypressComponentConfigurationSchema
|
||||
options: CypressComponentConfigurationSchema,
|
||||
found: FoundTarget
|
||||
) {
|
||||
const cypressConfigPath = joinPathFragments(
|
||||
projectConfig.root,
|
||||
@ -26,15 +33,32 @@ export async function addFiles(
|
||||
tree.delete(cypressConfigPath);
|
||||
}
|
||||
|
||||
const actualBundler = getBundler(found, tree);
|
||||
|
||||
if (options.bundler && options.bundler !== actualBundler) {
|
||||
logger.warn(
|
||||
`You have specified ${options.bundler} as the bundler but this project is configured to use ${actualBundler}.
|
||||
This may cause errors. If you are seeing errors, try removing the --bundler option.`
|
||||
);
|
||||
}
|
||||
|
||||
generateFiles(
|
||||
tree,
|
||||
joinPathFragments(__dirname, '..', 'files'),
|
||||
projectConfig.root,
|
||||
{
|
||||
tpl: '',
|
||||
bundler: options.bundler ?? actualBundler,
|
||||
}
|
||||
);
|
||||
|
||||
if (
|
||||
options.bundler === 'webpack' ||
|
||||
(!options.bundler && actualBundler === 'webpack')
|
||||
) {
|
||||
await ensurePackage(tree, '@nrwl/webpack', nxVersion);
|
||||
}
|
||||
|
||||
if (options.generateTests) {
|
||||
const filePaths = [];
|
||||
visitNotIgnoredFiles(tree, projectConfig.sourceRoot, (filePath) => {
|
||||
@ -52,6 +76,18 @@ export async function addFiles(
|
||||
}
|
||||
}
|
||||
|
||||
function getBundler(found: FoundTarget, tree: Tree): 'vite' | 'webpack' {
|
||||
if (found.target && found.config?.executor) {
|
||||
return found.config.executor === '@nrwl/vite:build' ? 'vite' : 'webpack';
|
||||
}
|
||||
|
||||
const { target, project } = parseTargetString(found.target);
|
||||
const projectConfig = readProjectConfiguration(tree, project);
|
||||
return projectConfig?.targets?.[target]?.executor === '@nrwl/vite:build'
|
||||
? 'vite'
|
||||
: 'webpack';
|
||||
}
|
||||
|
||||
function isComponent(tree: Tree, filePath: string): boolean {
|
||||
if (isSpecFile.test(filePath) || !allowedFileExt.test(filePath)) {
|
||||
return false;
|
||||
|
||||
@ -1,14 +1,20 @@
|
||||
import {
|
||||
readProjectConfiguration,
|
||||
TargetConfiguration,
|
||||
Tree,
|
||||
updateProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { CypressComponentConfigurationSchema } from '../schema';
|
||||
|
||||
export interface FoundTarget {
|
||||
config?: TargetConfiguration;
|
||||
target: string;
|
||||
}
|
||||
|
||||
export async function updateProjectConfig(
|
||||
tree: Tree,
|
||||
options: CypressComponentConfigurationSchema
|
||||
) {
|
||||
): Promise<FoundTarget> {
|
||||
const { findBuildConfig } = await import(
|
||||
'@nrwl/cypress/src/utils/find-target-options'
|
||||
);
|
||||
@ -30,6 +36,8 @@ export async function updateProjectConfig(
|
||||
skipServe: true,
|
||||
};
|
||||
updateProjectConfiguration(tree, options.project, projectConfig);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
function assetValidConfig(config: unknown) {
|
||||
|
||||
@ -3,4 +3,5 @@ export interface CypressComponentConfigurationSchema {
|
||||
generateTests: boolean;
|
||||
skipFormat?: boolean;
|
||||
buildTarget?: string;
|
||||
bundler?: 'webpack' | 'vite';
|
||||
}
|
||||
|
||||
@ -37,6 +37,12 @@
|
||||
"type": "boolean",
|
||||
"description": "Skip formatting files",
|
||||
"default": false
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The bundler to use for Cypress Component Testing.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack"],
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
"required": ["project"],
|
||||
|
||||
@ -67,7 +67,10 @@
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The Storybook builder to use.",
|
||||
"enum": ["vite", "webpack"]
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack"],
|
||||
"x-prompt": "Which Storybook builder do you want to use?",
|
||||
"default": "webpack"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack"],
|
||||
"x-prompt": "Which bundler do you want to use?",
|
||||
"default": "webpack"
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './src/utils/versions';
|
||||
export * from './src/utils/generator-utils';
|
||||
export { viteConfigurationGenerator } from './src/generators/configuration/configuration';
|
||||
export { vitestGenerator } from './src/generators/vitest/vitest-generator';
|
||||
|
||||
@ -2,15 +2,14 @@ import {
|
||||
applyChangesToString,
|
||||
ChangeType,
|
||||
formatFiles,
|
||||
joinPathFragments,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
workspaceRoot,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils';
|
||||
import { findNodes } from 'nx/src/utils/typescript';
|
||||
import ts = require('typescript');
|
||||
import { normalizeViteConfigFilePathWithTree } from '../../utils/generator-utils';
|
||||
|
||||
export async function removeProjectsFromViteTsConfigPaths(tree: Tree) {
|
||||
findAllProjectsWithViteConfig(tree);
|
||||
@ -22,11 +21,10 @@ export default removeProjectsFromViteTsConfigPaths;
|
||||
function findAllProjectsWithViteConfig(tree: Tree): void {
|
||||
forEachExecutorOptions(tree, '@nrwl/vite:build', (options, project) => {
|
||||
const projectConfiguration = readProjectConfiguration(tree, project);
|
||||
const viteConfig = normalizeConfigFilePathWithTree(
|
||||
const viteConfig = normalizeViteConfigFilePathWithTree(
|
||||
tree,
|
||||
projectConfiguration.root,
|
||||
options?.['configFile'],
|
||||
workspaceRoot
|
||||
options?.['configFile']
|
||||
);
|
||||
if (viteConfig) {
|
||||
const file = getTsSourceFile(tree, viteConfig);
|
||||
@ -85,18 +83,3 @@ export function getTsSourceFile(host: Tree, path: string): ts.SourceFile {
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
function normalizeConfigFilePathWithTree(
|
||||
tree: Tree,
|
||||
projectRoot: string,
|
||||
configFile?: string,
|
||||
workspaceRoot?: string
|
||||
): string {
|
||||
return configFile
|
||||
? joinPathFragments(`${workspaceRoot}/${configFile}`)
|
||||
: tree.exists(joinPathFragments(`${projectRoot}/vite.config.ts`))
|
||||
? joinPathFragments(`${projectRoot}/vite.config.ts`)
|
||||
: tree.exists(joinPathFragments(`${projectRoot}/vite.config.js`))
|
||||
? joinPathFragments(`${projectRoot}/vite.config.js`)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
111
packages/vite/src/utils/generator-util.test.ts
Normal file
111
packages/vite/src/utils/generator-util.test.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import {
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
updateProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
|
||||
import {
|
||||
findExistingTargets,
|
||||
getViteConfigPathForProject,
|
||||
} from './generator-utils';
|
||||
import { mockReactAppGenerator, mockViteReactAppGenerator } from './test-utils';
|
||||
describe('generator utils', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyV1Workspace();
|
||||
});
|
||||
|
||||
describe('getViteConfigPathForProject', () => {
|
||||
beforeEach(() => {
|
||||
mockViteReactAppGenerator(tree);
|
||||
});
|
||||
it('should return correct path for vite.config file if no configFile is set', () => {
|
||||
const viteConfigPath = getViteConfigPathForProject(
|
||||
tree,
|
||||
'my-test-react-vite-app'
|
||||
);
|
||||
expect(viteConfigPath).toEqual(
|
||||
'apps/my-test-react-vite-app/vite.config.ts'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct path for vite.config file if custom configFile is set', () => {
|
||||
const projectConfig = readProjectConfiguration(
|
||||
tree,
|
||||
'my-test-react-vite-app'
|
||||
);
|
||||
updateProjectConfiguration(tree, 'my-test-react-vite-app', {
|
||||
...projectConfig,
|
||||
targets: {
|
||||
...projectConfig.targets,
|
||||
build: {
|
||||
...projectConfig.targets.build,
|
||||
options: {
|
||||
...projectConfig.targets.build.options,
|
||||
configFile: 'apps/my-test-react-vite-app/vite.config.custom.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
tree.write(`apps/my-test-react-vite-app/vite.config.custom.ts`, '');
|
||||
|
||||
const viteConfigPath = getViteConfigPathForProject(
|
||||
tree,
|
||||
'my-test-react-vite-app'
|
||||
);
|
||||
expect(viteConfigPath).toEqual(
|
||||
'apps/my-test-react-vite-app/vite.config.custom.ts'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct path for vite.config file given a target name', () => {
|
||||
const projectConfig = readProjectConfiguration(
|
||||
tree,
|
||||
'my-test-react-vite-app'
|
||||
);
|
||||
updateProjectConfiguration(tree, 'my-test-react-vite-app', {
|
||||
...projectConfig,
|
||||
targets: {
|
||||
...projectConfig.targets,
|
||||
'other-build': {
|
||||
...projectConfig.targets.build,
|
||||
options: {
|
||||
...projectConfig.targets.build.options,
|
||||
configFile: 'apps/my-test-react-vite-app/vite.other.custom.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
tree.write(`apps/my-test-react-vite-app/vite.other.custom.ts`, '');
|
||||
|
||||
const viteConfigPath = getViteConfigPathForProject(
|
||||
tree,
|
||||
'my-test-react-vite-app',
|
||||
'other-build'
|
||||
);
|
||||
expect(viteConfigPath).toEqual(
|
||||
'apps/my-test-react-vite-app/vite.other.custom.ts'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findExistingTargets', () => {
|
||||
beforeEach(() => {
|
||||
mockReactAppGenerator(tree);
|
||||
});
|
||||
it('should return the correct targets', () => {
|
||||
const { targets } = readProjectConfiguration(tree, 'my-test-react-app');
|
||||
|
||||
const existingTargets = findExistingTargets(targets);
|
||||
expect(existingTargets).toMatchObject({
|
||||
buildTarget: 'build',
|
||||
serveTarget: 'serve',
|
||||
testTarget: 'test',
|
||||
unsuppored: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -462,3 +462,38 @@ ${options.includeVitest ? '/// <reference types="vitest" />' : ''}
|
||||
|
||||
tree.write(viteConfigPath, viteConfigContent);
|
||||
}
|
||||
|
||||
export function normalizeViteConfigFilePathWithTree(
|
||||
tree: Tree,
|
||||
projectRoot: string,
|
||||
configFile?: string
|
||||
): string {
|
||||
return configFile && tree.exists(configFile)
|
||||
? configFile
|
||||
: tree.exists(joinPathFragments(`${projectRoot}/vite.config.ts`))
|
||||
? joinPathFragments(`${projectRoot}/vite.config.ts`)
|
||||
: tree.exists(joinPathFragments(`${projectRoot}/vite.config.js`))
|
||||
? joinPathFragments(`${projectRoot}/vite.config.js`)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function getViteConfigPathForProject(
|
||||
tree: Tree,
|
||||
projectName: string,
|
||||
target?: string
|
||||
) {
|
||||
let viteConfigPath: string | undefined;
|
||||
const { targets, root } = readProjectConfiguration(tree, projectName);
|
||||
if (target) {
|
||||
viteConfigPath = targets[target]?.options?.configFile;
|
||||
} else {
|
||||
const buildTarget = Object.entries(targets).find(
|
||||
([_targetName, targetConfig]) => {
|
||||
return targetConfig.executor === '@nrwl/vite:build';
|
||||
}
|
||||
);
|
||||
viteConfigPath = buildTarget?.[1]?.options?.configFile;
|
||||
}
|
||||
|
||||
return normalizeViteConfigFilePathWithTree(tree, root, viteConfigPath);
|
||||
}
|
||||
|
||||
@ -32,11 +32,7 @@ export async function getBuildAndSharedConfig(
|
||||
mode: options.mode ?? context.configurationName,
|
||||
root: projectRoot,
|
||||
base: options.base,
|
||||
configFile: normalizeConfigFilePath(
|
||||
projectRoot,
|
||||
options.configFile,
|
||||
context.root
|
||||
),
|
||||
configFile: normalizeViteConfigFilePath(projectRoot, options.configFile),
|
||||
plugins: [replaceFiles(options.fileReplacements)],
|
||||
build: getViteBuildOptions(
|
||||
options as ViteDevServerExecutorOptions & ViteBuildExecutorOptions,
|
||||
@ -45,13 +41,12 @@ export async function getBuildAndSharedConfig(
|
||||
} as InlineConfig);
|
||||
}
|
||||
|
||||
export function normalizeConfigFilePath(
|
||||
export function normalizeViteConfigFilePath(
|
||||
projectRoot: string,
|
||||
configFile?: string,
|
||||
workspaceRoot?: string
|
||||
configFile?: string
|
||||
): string {
|
||||
return configFile
|
||||
? joinPathFragments(`${workspaceRoot}/${configFile}`)
|
||||
return configFile && existsSync(joinPathFragments(configFile))
|
||||
? configFile
|
||||
: existsSync(joinPathFragments(`${projectRoot}/vite.config.ts`))
|
||||
? joinPathFragments(`${projectRoot}/vite.config.ts`)
|
||||
: existsSync(joinPathFragments(`${projectRoot}/vite.config.js`))
|
||||
|
||||
@ -53,7 +53,7 @@ const IGNORE_MATCHES_IN_PACKAGE = {
|
||||
'tailwindcss',
|
||||
],
|
||||
cli: ['nx'],
|
||||
cypress: ['cypress', '@angular-devkit/schematics', '@nrwl/cypress'],
|
||||
cypress: ['cypress', '@angular-devkit/schematics', '@nrwl/cypress', 'vite'],
|
||||
devkit: ['@angular-devkit/architect', 'rxjs', 'webpack'],
|
||||
'eslint-plugin-nx': ['@angular-eslint/eslint-plugin'],
|
||||
jest: [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user