@nx/vite
Version:
285 lines (280 loc) • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.vitestGenerator = vitestGenerator;
exports.vitestGeneratorInternal = vitestGeneratorInternal;
const devkit_1 = require("@nx/devkit");
const js_1 = require("@nx/js");
const ts_solution_setup_1 = require("@nx/js/src/utils/typescript/ts-solution-setup");
const versions_1 = require("@nx/js/src/utils/versions");
const path_1 = require("path");
const ensure_dependencies_1 = require("../../utils/ensure-dependencies");
const generator_utils_1 = require("../../utils/generator-utils");
const init_1 = require("../init/init");
const detect_ui_framework_1 = require("../../utils/detect-ui-framework");
const version_utils_1 = require("../../utils/version-utils");
const semver_1 = require("semver");
/**
* @param hasPlugin some frameworks (e.g. Nuxt) provide their own plugin. Their generators handle the plugin detection.
*/
function vitestGenerator(tree, schema, hasPlugin = false) {
return vitestGeneratorInternal(tree, { addPlugin: false, ...schema }, hasPlugin);
}
async function vitestGeneratorInternal(tree, schema, hasPlugin = false) {
// Setting default to jsdom since it is the most common use case (React, Web).
// The @nx/js:lib generator specifically sets this to node to be more generic.
schema.testEnvironment ??= 'jsdom';
const tasks = [];
const { root, projectType: _projectType } = (0, devkit_1.readProjectConfiguration)(tree, schema.project);
const projectType = schema.projectType ?? _projectType;
const uiFramework = schema.uiFramework ?? (await (0, detect_ui_framework_1.detectUiFramework)(schema.project));
const isRootProject = root === '.';
tasks.push(await (0, js_1.initGenerator)(tree, { ...schema, skipFormat: true }));
const pkgJson = (0, devkit_1.readJson)(tree, 'package.json');
const useVite5 = (0, semver_1.major)((0, semver_1.coerce)(pkgJson.devDependencies['vite']) ?? '6.0.0') === 5;
const initTask = await (0, init_1.default)(tree, {
projectRoot: root,
skipFormat: true,
addPlugin: schema.addPlugin,
useViteV5: useVite5,
});
tasks.push(initTask);
tasks.push((0, ensure_dependencies_1.ensureDependencies)(tree, { ...schema, uiFramework }));
(0, generator_utils_1.addOrChangeTestTarget)(tree, schema, hasPlugin);
if (!schema.skipViteConfig) {
if (uiFramework === 'angular') {
const relativeTestSetupPath = (0, devkit_1.joinPathFragments)('src', 'test-setup.ts');
const setupFile = (0, devkit_1.joinPathFragments)(root, relativeTestSetupPath);
if (!tree.exists(setupFile)) {
if (isAngularV20(tree)) {
tree.write(setupFile, `import '@angular/compiler';
import '@analogjs/vitest-angular/setup-zone';
import {
BrowserTestingModule,
platformBrowserTesting,
} from '@angular/platform-browser/testing';
import { getTestBed } from '@angular/core/testing';
getTestBed().initTestEnvironment(
BrowserTestingModule,
platformBrowserTesting()
);
`);
}
else {
tree.write(setupFile, `import '@analogjs/vitest-angular/setup-zone';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import { getTestBed } from '@angular/core/testing';
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
`);
}
}
(0, generator_utils_1.createOrEditViteConfig)(tree, {
project: schema.project,
includeLib: false,
includeVitest: true,
inSourceTests: false,
imports: [`import angular from '@analogjs/vite-plugin-angular'`],
plugins: ['angular()'],
setupFile: relativeTestSetupPath,
useEsmExtension: true,
}, true);
}
else if (uiFramework === 'react') {
(0, generator_utils_1.createOrEditViteConfig)(tree, {
project: schema.project,
includeLib: (0, ts_solution_setup_1.getProjectType)(tree, root, projectType) === 'library',
includeVitest: true,
inSourceTests: schema.inSourceTests,
rollupOptionsExternal: [
"'react'",
"'react-dom'",
"'react/jsx-runtime'",
],
imports: [
schema.compiler === 'swc'
? `import react from '@vitejs/plugin-react-swc'`
: `import react from '@vitejs/plugin-react'`,
],
plugins: ['react()'],
coverageProvider: schema.coverageProvider,
}, true);
}
else {
(0, generator_utils_1.createOrEditViteConfig)(tree, {
...schema,
includeVitest: true,
includeLib: (0, ts_solution_setup_1.getProjectType)(tree, root, projectType) === 'library',
}, true);
}
}
const isTsSolutionSetup = (0, ts_solution_setup_1.isUsingTsSolutionSetup)(tree);
createFiles(tree, schema, root, isTsSolutionSetup);
updateTsConfig(tree, schema, root, projectType);
if (isTsSolutionSetup) {
// in the TS solution setup, the test target depends on the build outputs
// so we need to setup the task pipeline accordingly
const nxJson = (0, devkit_1.readNxJson)(tree);
const testTarget = schema.testTarget ?? 'test';
nxJson.targetDefaults ??= {};
nxJson.targetDefaults[testTarget] ??= {};
nxJson.targetDefaults[testTarget].dependsOn ??= [];
nxJson.targetDefaults[testTarget].dependsOn = Array.from(new Set([...nxJson.targetDefaults[testTarget].dependsOn, '^build']));
(0, devkit_1.updateNxJson)(tree, nxJson);
}
const devDependencies = await getCoverageProviderDependency(tree, schema.coverageProvider);
devDependencies['@types/node'] = versions_1.typesNodeVersion;
const installDependenciesTask = (0, devkit_1.addDependenciesToPackageJson)(tree, {}, devDependencies);
tasks.push(installDependenciesTask);
// Setup workspace config file (https://vitest.dev/guide/workspace.html)
if (!isRootProject &&
!tree.exists(`vitest.workspace.ts`) &&
!tree.exists(`vitest.workspace.js`) &&
!tree.exists(`vitest.workspace.json`) &&
!tree.exists(`vitest.projects.ts`) &&
!tree.exists(`vitest.projects.js`) &&
!tree.exists(`vitest.projects.json`)) {
tree.write('vitest.workspace.ts', `export default ['**/vite.config.{mjs,js,ts,mts}', '**/vitest.config.{mjs,js,ts,mts}'];`);
}
if (!schema.skipFormat) {
await (0, devkit_1.formatFiles)(tree);
}
return (0, devkit_1.runTasksInSerial)(...tasks);
}
function updateTsConfig(tree, options, projectRoot, projectType) {
const setupFile = tryFindSetupFile(tree, projectRoot);
if (tree.exists((0, devkit_1.joinPathFragments)(projectRoot, 'tsconfig.spec.json'))) {
(0, devkit_1.updateJson)(tree, (0, devkit_1.joinPathFragments)(projectRoot, 'tsconfig.spec.json'), (json) => {
if (!json.compilerOptions?.types?.includes('vitest')) {
if (json.compilerOptions?.types) {
json.compilerOptions.types.push('vitest');
}
else {
json.compilerOptions ??= {};
json.compilerOptions.types = ['vitest'];
}
}
if (setupFile) {
json.files = [...(json.files ?? []), setupFile];
}
return json;
});
(0, devkit_1.updateJson)(tree, (0, devkit_1.joinPathFragments)(projectRoot, 'tsconfig.json'), (json) => {
if (json.references &&
!json.references.some((r) => r.path === './tsconfig.spec.json')) {
json.references.push({
path: './tsconfig.spec.json',
});
}
return json;
});
}
else {
(0, devkit_1.updateJson)(tree, (0, devkit_1.joinPathFragments)(projectRoot, 'tsconfig.json'), (json) => {
if (!json.compilerOptions?.types?.includes('vitest')) {
if (json.compilerOptions?.types) {
json.compilerOptions.types.push('vitest');
}
else {
json.compilerOptions ??= {};
json.compilerOptions.types = ['vitest'];
}
}
return json;
});
}
let runtimeTsconfigPath = (0, devkit_1.joinPathFragments)(projectRoot, (0, ts_solution_setup_1.getProjectType)(tree, projectRoot, projectType) === 'application'
? 'tsconfig.app.json'
: 'tsconfig.lib.json');
if (options.runtimeTsconfigFileName) {
runtimeTsconfigPath = (0, devkit_1.joinPathFragments)(projectRoot, options.runtimeTsconfigFileName);
if (!tree.exists(runtimeTsconfigPath)) {
throw new Error(`Cannot find the specified runtimeTsConfigFileName ("${options.runtimeTsconfigFileName}") at the project root "${projectRoot}".`);
}
}
if (tree.exists(runtimeTsconfigPath)) {
(0, devkit_1.updateJson)(tree, runtimeTsconfigPath, (json) => {
if (options.inSourceTests) {
(json.compilerOptions.types ??= []).push('vitest/importMeta');
}
else {
const uniqueExclude = new Set([
...(json.exclude || []),
'vite.config.ts',
'vite.config.mts',
'vitest.config.ts',
'vitest.config.mts',
'src/**/*.test.ts',
'src/**/*.spec.ts',
'src/**/*.test.tsx',
'src/**/*.spec.tsx',
'src/**/*.test.js',
'src/**/*.spec.js',
'src/**/*.test.jsx',
'src/**/*.spec.jsx',
]);
json.exclude = [...uniqueExclude];
}
if (setupFile) {
json.exclude = [...(json.exclude ?? []), setupFile];
}
return json;
});
}
else {
devkit_1.logger.warn(`Couldn't find a runtime tsconfig file at ${runtimeTsconfigPath} to exclude the test files from. ` +
`If you're using a different filename for your runtime tsconfig, please provide it with the '--runtimeTsconfigFileName' flag.`);
}
}
function createFiles(tree, options, projectRoot, isTsSolutionSetup) {
const rootOffset = (0, devkit_1.offsetFromRoot)(projectRoot);
(0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, 'files'), projectRoot, {
tmpl: '',
...options,
projectRoot,
extendedConfig: isTsSolutionSetup
? `${rootOffset}tsconfig.base.json`
: './tsconfig.json',
outDir: isTsSolutionSetup
? `./out-tsc/vitest`
: `${rootOffset}dist/out-tsc`,
});
}
async function getCoverageProviderDependency(tree, coverageProvider) {
const { vitestCoverageV8, vitestCoverageIstanbul } = await (0, version_utils_1.getVitestDependenciesVersionsToInstall)(tree);
switch (coverageProvider) {
case 'v8':
return {
'@vitest/coverage-v8': vitestCoverageV8,
};
case 'istanbul':
return {
'@vitest/coverage-istanbul': vitestCoverageIstanbul,
};
default:
return {
'@vitest/coverage-v8': vitestCoverageV8,
};
}
}
function tryFindSetupFile(tree, projectRoot) {
const setupFile = (0, devkit_1.joinPathFragments)('src', 'test-setup.ts');
if (tree.exists((0, devkit_1.joinPathFragments)(projectRoot, setupFile))) {
return setupFile;
}
}
function isAngularV20(tree) {
const { dependencies, devDependencies } = (0, devkit_1.readJson)(tree, 'package.json');
const angularVersion = dependencies?.['@angular/core'] ?? devDependencies?.['@angular/core'];
if (!angularVersion) {
// assume the latest version will be installed, which will be 20 or later
return true;
}
const cleanedAngularVersion = (0, semver_1.clean)(angularVersion) ?? (0, semver_1.coerce)(angularVersion).version;
return (0, semver_1.major)(cleanedAngularVersion) >= 20;
}
exports.default = vitestGenerator;