feat(vite): nodes for build, serve, test, preview targets (#20086)

This commit is contained in:
Katerina Skroumpelou 2023-11-30 22:56:16 +02:00 committed by GitHub
parent 5a47eaf684
commit 83db767b27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 846 additions and 116 deletions

View File

@ -0,0 +1,59 @@
import { cleanupProject, newProject, runCLI, uniq } from '@nx/e2e/utils';
const myApp = uniq('my-app');
describe('@nx/vite/plugin', () => {
let proj: string;
let originalEnv: string;
beforeAll(() => {
originalEnv = process.env.NX_PCV3;
process.env.NX_PCV3 = 'true';
});
afterAll(() => {
process.env.NODE_ENV = originalEnv;
});
describe('build and test React Vite app', () => {
beforeAll(() => {
proj = newProject();
runCLI(
`generate @nx/react:app ${myApp} --bundler=vite --unitTestRunner=vitest`
);
});
afterAll(() => cleanupProject());
it('should build application', () => {
const result = runCLI(`build ${myApp}`);
expect(result).toContain('Successfully ran target build');
}, 200_000);
it('should test application', () => {
const result = runCLI(`test ${myApp}`);
expect(result).toContain('Successfully ran target test');
}, 200_000);
});
describe('build and test Vue app', () => {
beforeAll(() => {
proj = newProject();
runCLI(`generate @nx/vue:app ${myApp} --unitTestRunner=vitest`);
});
afterAll(() => {
cleanupProject();
});
it('should build application', () => {
const result = runCLI(`build ${myApp}`);
expect(result).toContain('Successfully ran target build');
}, 200_000);
it('should test application', () => {
const result = runCLI(`test ${myApp}`);
expect(result).toContain('Successfully ran target test');
}, 200_000);
});
});

View File

@ -1,5 +1,6 @@
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import {
getProjects,
readNxJson,
readProjectConfiguration,
Tree,
@ -33,6 +34,7 @@ describe('react app generator (PCv3)', () => {
const nxJson = readNxJson(appTree);
nxJson.plugins ??= [];
nxJson.plugins.push('@nx/webpack/plugin');
nxJson.plugins.push('@nx/vite/plugin');
updateNxJson(appTree, nxJson);
});
@ -57,4 +59,17 @@ describe('react app generator (PCv3)', () => {
`assets: ['./src/favicon.ico', './src/assets']`
);
});
it('should not add targets for vite', async () => {
await applicationGenerator(appTree, {
...schema,
name: 'my-vite-app',
bundler: 'vite',
});
const projects = getProjects(appTree);
expect(projects.get('my-vite-app').targets.build).toBeUndefined();
expect(projects.get('my-vite-app').targets.serve).toBeUndefined();
expect(projects.get('my-vite-app').targets.preview).toBeUndefined();
expect(projects.get('my-vite-app').targets.test).toBeUndefined();
});
});

View File

@ -9,6 +9,7 @@ import { webStaticServeGenerator } from '@nx/web';
import { nxVersion } from '../../../utils/versions';
import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin';
import { hasVitePlugin } from '../../../utils/has-vite-plugin';
import { NormalizedSchema } from '../schema';
export async function addE2e(
@ -17,7 +18,10 @@ export async function addE2e(
): Promise<GeneratorCallback> {
switch (options.e2eTestRunner) {
case 'cypress': {
if (!hasWebpackPlugin(tree)) {
if (
(options.bundler === 'webpack' && !hasWebpackPlugin(tree)) ||
(options.bundler === 'vite' && !hasVitePlugin(tree))
) {
webStaticServeGenerator(tree, {
buildTarget: `${options.projectName}:build`,
targetName: 'serve-static',

View File

@ -0,0 +1,10 @@
import { readNxJson, Tree } from '@nx/devkit';
export function hasVitePlugin(tree: Tree) {
const nxJson = readNxJson(tree);
return !!nxJson.plugins?.some((p) =>
typeof p === 'string'
? p === '@nx/vite/plugin'
: p.plugin === '@nx/vite/plugin'
);
}

View File

@ -3,6 +3,16 @@ import json = require('./migrations.json');
import { assertValidMigrationPaths } from '@nx/devkit/internal-testing-utils';
import { MigrationsJson } from '@nx/devkit';
jest.mock('vite', () => ({
loadConfigFromFile: jest.fn().mockImplementation(() => {
return Promise.resolve({
path: 'vite.config.ts',
config: {},
dependencies: [],
});
}),
}));
describe('vite migrations', () => {
assertValidMigrationPaths(json as MigrationsJson, __dirname);
});

View File

@ -29,10 +29,10 @@
"migrations": "./migrations.json"
},
"dependencies": {
"@nx/devkit": "file:../devkit",
"@phenomnomnominal/tsquery": "~5.0.1",
"@swc/helpers": "~0.5.0",
"enquirer": "~2.3.6",
"@nx/devkit": "file:../devkit",
"@nx/js": "file:../js",
"tsconfig-paths": "^4.1.2"
},
@ -45,6 +45,7 @@
},
"exports": {
".": "./index.js",
"./plugin": "./plugin.js",
"./package.json": "./package.json",
"./migrations.json": "./migrations.json",
"./generators.json": "./generators.json",

5
packages/vite/plugin.ts Normal file
View File

@ -0,0 +1,5 @@
export {
createNodes,
VitePluginOptions,
createDependencies,
} from './src/plugins/plugin';

View File

@ -2,6 +2,7 @@ import {
formatFiles,
GeneratorCallback,
joinPathFragments,
readNxJson,
readProjectConfiguration,
runTasksInSerial,
Tree,
@ -164,6 +165,14 @@ export async function viteConfigurationGenerator(
});
tasks.push(initTask);
const nxJson = readNxJson(tree);
const hasPlugin = nxJson.plugins?.some((p) =>
typeof p === 'string'
? p === '@nx/vite/plugin'
: p.plugin === '@nx/vite/plugin'
);
if (!hasPlugin) {
if (!projectAlreadyHasViteTargets.build) {
addOrChangeBuildTarget(tree, schema, buildTargetName);
}
@ -176,7 +185,7 @@ export async function viteConfigurationGenerator(
addPreviewTarget(tree, schema, serveTargetName);
}
}
}
if (projectType === 'library') {
// update tsconfig.lib.json to include vite/client
updateJson(
@ -225,7 +234,8 @@ export async function viteConfigurationGenerator(
],
plugins: ['react()'],
},
false
false,
undefined
);
} else {
createOrEditViteConfig(tree, schema, false, projectAlreadyHasViteTargets);

View File

@ -1,82 +1,13 @@
import {
addDependenciesToPackageJson,
logger,
readJson,
readNxJson,
runTasksInSerial,
Tree,
updateJson,
updateNxJson,
} from '@nx/devkit';
import { readNxJson, runTasksInSerial, Tree, updateNxJson } from '@nx/devkit';
import { initGenerator as jsInitGenerator } from '@nx/js';
import {
edgeRuntimeVmVersion,
happyDomVersion,
jsdomVersion,
nxVersion,
vitePluginDtsVersion,
vitePluginReactSwcVersion,
vitePluginReactVersion,
vitestVersion,
viteVersion,
} from '../../utils/versions';
import { InitGeneratorSchema } from './schema';
function checkDependenciesInstalled(host: Tree, schema: InitGeneratorSchema) {
const packageJson = readJson(host, 'package.json');
const devDependencies = {};
const dependencies = {};
packageJson.dependencies = packageJson.dependencies || {};
packageJson.devDependencies = packageJson.devDependencies || {};
// base deps
devDependencies['@nx/vite'] = nxVersion;
devDependencies['vite'] = viteVersion;
devDependencies['vitest'] = vitestVersion;
devDependencies['@vitest/ui'] = vitestVersion;
if (schema.testEnvironment === 'jsdom') {
devDependencies['jsdom'] = jsdomVersion;
} else if (schema.testEnvironment === 'happy-dom') {
devDependencies['happy-dom'] = happyDomVersion;
} else if (schema.testEnvironment === 'edge-runtime') {
devDependencies['@edge-runtime/vm'] = edgeRuntimeVmVersion;
} else if (schema.testEnvironment !== 'node' && schema.testEnvironment) {
logger.info(
`A custom environment was provided: ${schema.testEnvironment}. You need to install it manually.`
);
}
if (schema.uiFramework === 'react') {
if (schema.compiler === 'swc') {
devDependencies['@vitejs/plugin-react-swc'] = vitePluginReactSwcVersion;
} else {
devDependencies['@vitejs/plugin-react'] = vitePluginReactVersion;
}
}
if (schema.includeLib) {
devDependencies['vite-plugin-dts'] = vitePluginDtsVersion;
}
return addDependenciesToPackageJson(host, dependencies, devDependencies);
}
function moveToDevDependencies(tree: Tree) {
updateJson(tree, 'package.json', (packageJson) => {
packageJson.dependencies = packageJson.dependencies || {};
packageJson.devDependencies = packageJson.devDependencies || {};
if (packageJson.dependencies['@nx/vite']) {
packageJson.devDependencies['@nx/vite'] =
packageJson.dependencies['@nx/vite'];
delete packageJson.dependencies['@nx/vite'];
}
return packageJson;
});
}
import {
addPlugin,
checkDependenciesInstalled,
moveToDevDependencies,
} from './lib/utils';
export function updateNxJsonSettings(tree: Tree) {
const nxJson = readNxJson(tree);
@ -127,7 +58,10 @@ export async function initGenerator(tree: Tree, schema: InitGeneratorSchema) {
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
})
);
const addPlugins = process.env.NX_PCV3 === 'true';
if (addPlugins) {
addPlugin(tree);
}
tasks.push(checkDependenciesInstalled(tree, schema));
return runTasksInSerial(...tasks);
}

View File

@ -0,0 +1,122 @@
import {
addDependenciesToPackageJson,
logger,
readJson,
readNxJson,
Tree,
updateJson,
updateNxJson,
} from '@nx/devkit';
import {
edgeRuntimeVmVersion,
happyDomVersion,
jsdomVersion,
nxVersion,
vitePluginDtsVersion,
vitePluginReactSwcVersion,
vitePluginReactVersion,
vitestVersion,
viteVersion,
} from '../../../utils/versions';
import { InitGeneratorSchema } from '../schema';
export function checkDependenciesInstalled(
host: Tree,
schema: InitGeneratorSchema
) {
const packageJson = readJson(host, 'package.json');
const devDependencies = {};
const dependencies = {};
packageJson.dependencies = packageJson.dependencies || {};
packageJson.devDependencies = packageJson.devDependencies || {};
// base deps
devDependencies['@nx/vite'] = nxVersion;
devDependencies['vite'] = viteVersion;
devDependencies['vitest'] = vitestVersion;
devDependencies['@vitest/ui'] = vitestVersion;
if (schema.testEnvironment === 'jsdom') {
devDependencies['jsdom'] = jsdomVersion;
} else if (schema.testEnvironment === 'happy-dom') {
devDependencies['happy-dom'] = happyDomVersion;
} else if (schema.testEnvironment === 'edge-runtime') {
devDependencies['@edge-runtime/vm'] = edgeRuntimeVmVersion;
} else if (schema.testEnvironment !== 'node' && schema.testEnvironment) {
logger.info(
`A custom environment was provided: ${schema.testEnvironment}. You need to install it manually.`
);
}
if (schema.uiFramework === 'react') {
if (schema.compiler === 'swc') {
devDependencies['@vitejs/plugin-react-swc'] = vitePluginReactSwcVersion;
} else {
devDependencies['@vitejs/plugin-react'] = vitePluginReactVersion;
}
}
if (schema.includeLib) {
devDependencies['vite-plugin-dts'] = vitePluginDtsVersion;
}
return addDependenciesToPackageJson(host, dependencies, devDependencies);
}
export function moveToDevDependencies(tree: Tree) {
updateJson(tree, 'package.json', (packageJson) => {
packageJson.dependencies = packageJson.dependencies || {};
packageJson.devDependencies = packageJson.devDependencies || {};
if (packageJson.dependencies['@nx/vite']) {
packageJson.devDependencies['@nx/vite'] =
packageJson.dependencies['@nx/vite'];
delete packageJson.dependencies['@nx/vite'];
}
return packageJson;
});
}
export function createVitestConfig(tree: Tree) {
const nxJson = readNxJson(tree);
const productionFileSet = nxJson.namedInputs?.production;
if (productionFileSet) {
productionFileSet.push(
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
'!{projectRoot}/tsconfig.spec.json'
);
nxJson.namedInputs.production = Array.from(new Set(productionFileSet));
}
updateNxJson(tree, nxJson);
}
export function addPlugin(tree: Tree) {
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
for (const plugin of nxJson.plugins) {
if (
typeof plugin === 'string'
? plugin === '@nx/vite/plugin'
: plugin.plugin === '@nx/vite/plugin'
) {
return;
}
}
nxJson.plugins.push({
plugin: '@nx/vite/plugin',
options: {
buildTargetName: 'build',
previewTargetName: 'preview',
testTargetName: 'test',
serveTargetName: 'serve',
serveStaticTargetName: 'serve-static',
},
});
updateNxJson(tree, nxJson);
}

View File

@ -5,6 +5,7 @@ import {
GeneratorCallback,
joinPathFragments,
offsetFromRoot,
readNxJson,
readProjectConfiguration,
runTasksInSerial,
Tree,
@ -42,10 +43,20 @@ export async function vitestGenerator(
findExistingTargetsInProject(targets).validFoundTargetName.test ??
'test';
if (!hasPlugin) {
const nxJson = readNxJson(tree);
const hasPluginCheck = nxJson.plugins?.some(
(p) =>
(typeof p === 'string'
? p === '@nx/vite/plugin'
: p.plugin === '@nx/vite/plugin') || hasPlugin
);
if (!hasPluginCheck) {
const testTarget =
schema.testTarget ??
findExistingTargetsInProject(targets).validFoundTargetName.test ??
'test';
addOrChangeTestTarget(tree, schema, testTarget);
}
const initTask = await initGenerator(tree, {
uiFramework: schema.uiFramework,
testEnvironment: schema.testEnvironment,

View File

@ -36,7 +36,9 @@ function findAllProjectsWithViteConfig(tree: Tree): void {
);
let startOfProjects, endOfProjects;
defineConfig?.[0]?.getChildren().forEach((defineConfigContentNode) => {
defineConfig?.[0]
?.getChildren()
.forEach((defineConfigContentNode: any) => {
// Make sure it's the one we are looking for
// We cannot assume that it's called tsConfigPaths
// So make sure it includes `projects` and `root`

View File

@ -2,7 +2,6 @@ import { Tree, getProjects, joinPathFragments } from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { ViteBuildExecutorOptions } from '../../executors/build/schema';
import { tsquery } from '@phenomnomnominal/tsquery';
import { ImportDeclaration } from 'typescript';
export default function update(tree: Tree) {
const projects = getProjects(tree);
@ -20,7 +19,7 @@ export default function update(tree: Tree) {
const configContents = tree.read(config, 'utf-8');
const oldTsConfigPathPlugin =
tsquery.query<ImportDeclaration>(
tsquery.query(
configContents,
'ImportDeclaration:has(StringLiteral[value="vite-tsconfig-paths"])'
) ?? [];
@ -30,7 +29,7 @@ export default function update(tree: Tree) {
}
const importName =
oldTsConfigPathPlugin[0]?.importClause?.name?.text ??
oldTsConfigPathPlugin[0]?.['importClause']?.name?.text ??
'viteTsConfigPaths';
const updatedContent = tsquery.replace(
configContents,

View File

@ -0,0 +1,137 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`@nx/vite/plugin not root project should create nodes 1`] = `
{
"projects": {
"my-app": {
"root": "my-app",
"targets": {
"build-something": {
"cache": true,
"command": "vite build",
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"vite",
],
},
],
"options": {
"cwd": "my-app",
},
"outputs": [
"{options.outputPath}",
],
},
"my-serve": {
"command": "vite serve",
"options": {
"cwd": "my-app",
},
},
"preview-site": {
"command": "vite preview",
"options": {
"cwd": "my-app",
},
},
"serve-static": {
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "build-something",
},
},
"vitest": {
"cache": true,
"command": "vitest run",
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"vitest",
],
},
],
"options": {
"cwd": "my-app",
},
"outputs": [
"{options.reportsDirectory}",
],
},
},
},
},
}
`;
exports[`@nx/vite/plugin root project should create nodes 1`] = `
{
"projects": {
".": {
"root": ".",
"targets": {
"build": {
"cache": true,
"command": "vite build",
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"vite",
],
},
],
"options": {
"cwd": ".",
},
"outputs": [
"{options.outputPath}",
],
},
"preview": {
"command": "vite preview",
"options": {
"cwd": ".",
},
},
"serve": {
"command": "vite serve",
"options": {
"cwd": ".",
},
},
"serve-static": {
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "build",
},
},
"test": {
"cache": true,
"command": "vitest run",
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"vitest",
],
},
],
"options": {
"cwd": ".",
},
"outputs": [
"{options.reportsDirectory}",
],
},
},
},
},
}
`;

View File

@ -0,0 +1,93 @@
import { CreateNodesContext } from '@nx/devkit';
import { createNodes } from './plugin';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
jest.mock('vite', () => ({
loadConfigFromFile: jest.fn().mockImplementation(() => {
return Promise.resolve({
path: 'vite.config.ts',
config: {},
dependencies: [],
});
}),
}));
describe('@nx/vite/plugin', () => {
let createNodesFunction = createNodes[1];
let context: CreateNodesContext;
describe('root project', () => {
beforeEach(async () => {
context = {
nxJsonConfiguration: {
namedInputs: {
default: ['{projectRoot}/**/*'],
production: ['!{projectRoot}/**/*.spec.ts'],
},
},
workspaceRoot: '',
};
});
afterEach(() => {
jest.resetModules();
});
it('should create nodes', async () => {
const nodes = await createNodesFunction(
'vite.config.ts',
{
buildTargetName: 'build',
serveTargetName: 'serve',
previewTargetName: 'preview',
testTargetName: 'test',
serveStaticTargetName: 'serve-static',
},
context
);
expect(nodes).toMatchSnapshot();
});
});
// some issue wiht the tempfs
describe('not root project', () => {
const tempFs = new TempFs('test');
beforeEach(() => {
context = {
nxJsonConfiguration: {
namedInputs: {
default: ['{projectRoot}/**/*'],
production: ['!{projectRoot}/**/*.spec.ts'],
},
},
workspaceRoot: tempFs.tempDir,
};
tempFs.createFileSync(
'my-app/project.json',
JSON.stringify({ name: 'my-app' })
);
tempFs.createFileSync('my-app/vite.config.ts', '');
});
afterEach(() => {
jest.resetModules();
});
it('should create nodes', async () => {
const nodes = await createNodesFunction(
'my-app/vite.config.ts',
{
buildTargetName: 'build-something',
serveTargetName: 'my-serve',
previewTargetName: 'preview-site',
testTargetName: 'vitest',
serveStaticTargetName: 'serve-static',
},
context
);
expect(nodes).toMatchSnapshot();
});
});
});

View File

@ -0,0 +1,296 @@
import {
CreateDependencies,
CreateNodes,
CreateNodesContext,
TargetConfiguration,
detectPackageManager,
joinPathFragments,
readJsonFile,
workspaceRoot,
writeJsonFile,
} from '@nx/devkit';
import { dirname, isAbsolute, join, relative, resolve } from 'path';
import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { UserConfig, loadConfigFromFile } from 'vite';
import { existsSync, readdirSync } from 'fs';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
import { getLockFileName } from '@nx/js';
export interface VitePluginOptions {
buildTargetName?: string;
testTargetName?: string;
serveTargetName?: string;
previewTargetName?: string;
serveStaticTargetName?: string;
}
const cachePath = join(projectGraphCacheDirectory, 'vite.hash');
const targetsCache = existsSync(cachePath) ? readTargetsCache() : {};
const calculatedTargets: Record<
string,
Record<string, TargetConfiguration>
> = {};
function readTargetsCache(): Record<
string,
Record<string, TargetConfiguration>
> {
return readJsonFile(cachePath);
}
function writeTargetsToCache(
targets: Record<string, Record<string, TargetConfiguration>>
) {
writeJsonFile(cachePath, targets);
}
export const createDependencies: CreateDependencies = () => {
writeTargetsToCache(calculatedTargets);
return [];
};
export const createNodes: CreateNodes<VitePluginOptions> = [
'**/vite.config.{js,ts}',
async (configFilePath, options, context) => {
const projectRoot = dirname(configFilePath);
// Do not create a project if package.json and project.json isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
}
options = normalizeOptions(options);
const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const targets = targetsCache[hash]
? targetsCache[hash]
: await buildViteTargets(configFilePath, projectRoot, options, context);
calculatedTargets[hash] = targets;
return {
projects: {
[projectRoot]: {
root: projectRoot,
targets,
},
},
};
},
];
async function buildViteTargets(
configFilePath: string,
projectRoot: string,
options: VitePluginOptions,
context: CreateNodesContext
) {
const viteConfig = await loadConfigFromFile(
{
command: 'build',
mode: 'production',
},
configFilePath
);
const { buildOutputs, testOutputs } = getOutputs(
projectRoot,
viteConfig?.config
);
const namedInputs = getNamedInputs(projectRoot, context);
const targets: Record<string, TargetConfiguration> = {};
targets[options.buildTargetName] = await buildTarget(
context,
namedInputs,
buildOutputs,
options,
projectRoot
);
targets[options.serveTargetName] = serveTarget(projectRoot);
targets[options.previewTargetName] = previewTarget(projectRoot);
targets[options.testTargetName] = await testTarget(
context,
namedInputs,
testOutputs,
options,
projectRoot
);
targets[options.serveStaticTargetName] = serveStaticTarget(options) as {};
return targets;
}
async function buildTarget(
context: CreateNodesContext,
namedInputs: {
[inputName: string]: any[];
},
outputs: string[],
options: VitePluginOptions,
projectRoot: string
) {
const targetDefaults = readTargetDefaultsForTarget(
options.buildTargetName,
context.nxJsonConfiguration.targetDefaults
);
const targetConfig: TargetConfiguration = {
command: `vite build`,
options: {
cwd: joinPathFragments(projectRoot),
},
};
if (targetDefaults?.outputs === undefined) {
targetConfig.outputs = outputs;
}
if (targetDefaults?.cache === undefined) {
targetConfig.cache = true;
}
if (targetDefaults?.inputs === undefined) {
targetConfig.inputs = [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{
externalDependencies: ['vite'],
},
];
}
return targetConfig;
}
function serveTarget(projectRoot: string) {
const targetConfig: TargetConfiguration = {
command: `vite serve`,
options: {
cwd: joinPathFragments(projectRoot),
},
};
return targetConfig;
}
function previewTarget(projectRoot: string) {
const targetConfig: TargetConfiguration = {
command: `vite preview`,
options: {
cwd: joinPathFragments(projectRoot),
},
};
return targetConfig;
}
async function testTarget(
context: CreateNodesContext,
namedInputs: {
[inputName: string]: any[];
},
outputs: string[],
options: VitePluginOptions,
projectRoot: string
) {
const targetDefaults = readTargetDefaultsForTarget(
options.testTargetName,
context.nxJsonConfiguration.targetDefaults
);
const targetConfig: TargetConfiguration = {
command: `vitest run`,
options: {
cwd: joinPathFragments(projectRoot),
},
};
if (targetDefaults?.outputs === undefined) {
targetConfig.outputs = outputs;
}
if (targetDefaults?.cache === undefined) {
targetConfig.cache = true;
}
if (targetDefaults?.inputs === undefined) {
targetConfig.inputs = [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{
externalDependencies: ['vitest'],
},
];
}
return targetConfig;
}
function serveStaticTarget(options: VitePluginOptions) {
const targetConfig: TargetConfiguration = {
executor: '@nx/web:file-server',
options: {
buildTarget: `${options.buildTargetName}`,
},
};
return targetConfig;
}
function getOutputs(
projectRoot: string,
viteConfig: UserConfig
): {
buildOutputs: string[];
testOutputs: string[];
} {
const { build, test } = viteConfig;
const buildOutputs = ['{options.outputPath}'];
const testOutputs = ['{options.reportsDirectory}'];
function getOutput(path: string, projectRoot: string): string {
if (path.startsWith('..')) {
return join('{workspaceRoot}', join(projectRoot, path));
} else if (isAbsolute(resolve(path))) {
return `{workspaceRoot}/${relative(workspaceRoot, path)}`;
} else {
return join('{projectRoot}', path);
}
}
if (build?.outDir) {
buildOutputs.push(getOutput(build.outDir, projectRoot));
}
if (test?.coverage?.reportsDirectory) {
testOutputs.push(getOutput(test.coverage.reportsDirectory, projectRoot));
}
return { buildOutputs, testOutputs };
}
function normalizeOptions(options: VitePluginOptions): VitePluginOptions {
options ??= {};
options.buildTargetName ??= 'build';
options.serveTargetName ??= 'serve';
options.previewTargetName ??= 'preview';
options.testTargetName ??= 'test';
options.serveStaticTargetName ??= 'serve-static';
return options;
}

View File

@ -4,6 +4,7 @@ import {
ensurePackage,
getPackageManagerCommand,
joinPathFragments,
readNxJson,
} from '@nx/devkit';
import { webStaticServeGenerator } from '@nx/web';
@ -16,10 +17,18 @@ export async function addE2e(
): Promise<GeneratorCallback> {
switch (options.e2eTestRunner) {
case 'cypress': {
const nxJson = readNxJson(tree);
const hasPlugin = nxJson.plugins?.some((p) =>
typeof p === 'string'
? p === '@nx/vite/plugin'
: p.plugin === '@nx/vite/plugin'
);
if (!hasPlugin) {
webStaticServeGenerator(tree, {
buildTarget: `${options.projectName}:build`,
targetName: 'serve-static',
});
}
const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress')

View File

@ -1,5 +1,6 @@
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import {
getProjects,
readNxJson,
readProjectConfiguration,
Tree,
@ -28,6 +29,7 @@ describe('web app generator (PCv3)', () => {
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push('@nx/webpack/plugin');
nxJson.plugins.push('@nx/vite/plugin');
updateNxJson(tree, nxJson);
});
@ -50,4 +52,16 @@ describe('web app generator (PCv3)', () => {
`assets: ['./src/favicon.ico', './src/assets']`
);
});
it('should not add targets for vite', async () => {
await applicationGenerator(tree, {
name: 'my-vite-app',
bundler: 'vite',
});
const projects = getProjects(tree);
expect(projects.get('my-vite-app').targets.build).toBeUndefined();
expect(projects.get('my-vite-app').targets.serve).toBeUndefined();
expect(projects.get('my-vite-app').targets.preview).toBeUndefined();
expect(projects.get('my-vite-app').targets.test).toBeUndefined();
});
});

View File

@ -16,7 +16,6 @@ import {
workspaceRoot,
logger,
readJsonFile,
ProjectGraphProjectNode,
joinPathFragments,
} from '@nx/devkit';
import { existsSync } from 'fs';