feat(vite): add createNodesV2 function (#26484)

- chore(vite): move single file processing of plugin to own function
- feat(vite): add CreateNodesV2 function

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
<!-- This is the behavior we have today -->
There is no implementation for createNodesV2


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
There should be an implementation for createNodesV2

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Colum Ferry 2024-06-13 00:20:03 +02:00 committed by GitHub
parent bcac55178f
commit 07fa8a6ffa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 269 additions and 200 deletions

View File

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

View File

@ -1,35 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`@nx/vite/plugin root project should create nodes 1`] = ` exports[`@nx/vite/plugin root project should create nodes 1`] = `
{ [
"projects": { [
".": { "vitest.config.ts",
"root": ".", {
"targets": { "projects": {
"test": { ".": {
"cache": true, "metadata": {},
"command": "vitest", "root": ".",
"inputs": [ "targets": {
"default", "test": {
"^production", "cache": true,
{ "command": "vitest",
"externalDependencies": [ "inputs": [
"vitest", "default",
"^production",
{
"externalDependencies": [
"vitest",
],
},
{
"env": "CI",
},
],
"options": {
"cwd": ".",
},
"outputs": [
"{projectRoot}/coverage",
], ],
}, },
{
"env": "CI",
},
],
"options": {
"cwd": ".",
}, },
"outputs": [
"{projectRoot}/coverage",
],
}, },
}, },
}, },
}, ],
} ]
`; `;

View File

@ -1,35 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`@nx/vite/plugin with test node root project should create nodes - with test too 1`] = ` exports[`@nx/vite/plugin with test node root project should create nodes - with test too 1`] = `
{ [
"projects": { [
".": { "vite.config.ts",
"root": ".", {
"targets": { "projects": {
"test": { ".": {
"cache": true, "metadata": {},
"command": "vitest", "root": ".",
"inputs": [ "targets": {
"default", "test": {
"^production", "cache": true,
{ "command": "vitest",
"externalDependencies": [ "inputs": [
"vitest", "default",
"^production",
{
"externalDependencies": [
"vitest",
],
},
{
"env": "CI",
},
],
"options": {
"cwd": ".",
},
"outputs": [
"{projectRoot}/coverage",
], ],
}, },
{
"env": "CI",
},
],
"options": {
"cwd": ".",
}, },
"outputs": [
"{projectRoot}/coverage",
],
}, },
}, },
}, },
}, ],
} ]
`; `;

View File

@ -1,107 +1,119 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`@nx/vite/plugin not root project should create nodes 1`] = ` exports[`@nx/vite/plugin not root project should create nodes 1`] = `
{ [
"projects": { [
"my-app": { "my-app/vite.config.ts",
"root": "my-app", {
"targets": { "projects": {
"build-something": { "my-app": {
"cache": true, "metadata": {},
"command": "vite build", "root": "my-app",
"dependsOn": [ "targets": {
"^build-something", "build-something": {
], "cache": true,
"inputs": [ "command": "vite build",
"production", "dependsOn": [
"^production", "^build-something",
{ ],
"externalDependencies": [ "inputs": [
"vite", "production",
"^production",
{
"externalDependencies": [
"vite",
],
},
],
"options": {
"cwd": "my-app",
},
"outputs": [
"{workspaceRoot}/dist/{projectRoot}",
], ],
}, },
], "my-serve": {
"options": { "command": "vite serve",
"cwd": "my-app", "options": {
}, "cwd": "my-app",
"outputs": [ },
"{workspaceRoot}/dist/{projectRoot}", },
], "preview-site": {
}, "command": "vite preview",
"my-serve": { "options": {
"command": "vite serve", "cwd": "my-app",
"options": { },
"cwd": "my-app", },
}, "serve-static": {
}, "executor": "@nx/web:file-server",
"preview-site": { "options": {
"command": "vite preview", "buildTarget": "build-something",
"options": { "spa": true,
"cwd": "my-app", },
}, },
},
"serve-static": {
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "build-something",
"spa": true,
}, },
}, },
}, },
}, },
}, ],
} ]
`; `;
exports[`@nx/vite/plugin root project should create nodes 1`] = ` exports[`@nx/vite/plugin root project should create nodes 1`] = `
{ [
"projects": { [
".": { "vite.config.ts",
"root": ".", {
"targets": { "projects": {
"build": { ".": {
"cache": true, "metadata": {},
"command": "vite build", "root": ".",
"dependsOn": [ "targets": {
"^build", "build": {
], "cache": true,
"inputs": [ "command": "vite build",
"production", "dependsOn": [
"^production", "^build",
{ ],
"externalDependencies": [ "inputs": [
"vite", "production",
"^production",
{
"externalDependencies": [
"vite",
],
},
],
"options": {
"cwd": ".",
},
"outputs": [
"{projectRoot}/dist",
], ],
}, },
], "preview": {
"options": { "command": "vite preview",
"cwd": ".", "options": {
}, "cwd": ".",
"outputs": [ },
"{projectRoot}/dist", },
], "serve": {
}, "command": "vite serve",
"preview": { "options": {
"command": "vite preview", "cwd": ".",
"options": { },
"cwd": ".", },
}, "serve-static": {
}, "executor": "@nx/web:file-server",
"serve": { "options": {
"command": "vite serve", "buildTarget": "build",
"options": { "spa": true,
"cwd": ".", },
}, },
},
"serve-static": {
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "build",
"spa": true,
}, },
}, },
}, },
}, },
}, ],
} ]
`; `;

View File

@ -1,5 +1,5 @@
import { CreateNodesContext } from '@nx/devkit'; import { CreateNodesContext } from '@nx/devkit';
import { createNodes } from './plugin'; import { createNodesV2 } from './plugin';
jest.mock('vite', () => ({ jest.mock('vite', () => ({
resolveConfig: jest.fn().mockImplementation(() => { resolveConfig: jest.fn().mockImplementation(() => {
@ -22,7 +22,7 @@ jest.mock('../utils/executor-utils', () => ({
})); }));
describe('@nx/vite/plugin', () => { describe('@nx/vite/plugin', () => {
let createNodesFunction = createNodes[1]; let createNodesFunction = createNodesV2[1];
let context: CreateNodesContext; let context: CreateNodesContext;
describe('root project', () => { describe('root project', () => {
beforeEach(async () => { beforeEach(async () => {
@ -35,6 +35,7 @@ describe('@nx/vite/plugin', () => {
}, },
}, },
workspaceRoot: '', workspaceRoot: '',
configFiles: [],
}; };
}); });
@ -44,7 +45,7 @@ describe('@nx/vite/plugin', () => {
it('should create nodes', async () => { it('should create nodes', async () => {
const nodes = await createNodesFunction( const nodes = await createNodesFunction(
'vitest.config.ts', ['vitest.config.ts'],
{ {
testTargetName: 'test', testTargetName: 'test',
}, },

View File

@ -1,5 +1,5 @@
import { CreateNodesContext } from '@nx/devkit'; import { CreateNodesContext } from '@nx/devkit';
import { createNodes } from './plugin'; import { createNodes, createNodesV2 } from './plugin';
// This will only create test targets since no build targets are defined in vite.config.ts // This will only create test targets since no build targets are defined in vite.config.ts
@ -28,7 +28,7 @@ jest.mock('../utils/executor-utils', () => ({
})); }));
describe('@nx/vite/plugin with test node', () => { describe('@nx/vite/plugin with test node', () => {
let createNodesFunction = createNodes[1]; let createNodesFunction = createNodesV2[1];
let context: CreateNodesContext; let context: CreateNodesContext;
describe('root project', () => { describe('root project', () => {
beforeEach(async () => { beforeEach(async () => {
@ -47,6 +47,7 @@ describe('@nx/vite/plugin with test node', () => {
}, },
}, },
workspaceRoot: '', workspaceRoot: '',
configFiles: [],
}; };
}); });
@ -56,7 +57,7 @@ describe('@nx/vite/plugin with test node', () => {
it('should create nodes - with test too', async () => { it('should create nodes - with test too', async () => {
const nodes = await createNodesFunction( const nodes = await createNodesFunction(
'vite.config.ts', ['vite.config.ts'],
{ {
buildTargetName: 'build', buildTargetName: 'build',
serveTargetName: 'serve', serveTargetName: 'serve',

View File

@ -1,5 +1,5 @@
import { CreateNodesContext } from '@nx/devkit'; import { CreateNodesContext } from '@nx/devkit';
import { createNodes } from './plugin'; import { createNodes, createNodesV2 } from './plugin';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs'; import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
jest.mock('vite', () => ({ jest.mock('vite', () => ({
@ -24,7 +24,7 @@ jest.mock('../utils/executor-utils', () => ({
})); }));
describe('@nx/vite/plugin', () => { describe('@nx/vite/plugin', () => {
let createNodesFunction = createNodes[1]; let createNodesFunction = createNodesV2[1];
let context: CreateNodesContext; let context: CreateNodesContext;
describe('root project', () => { describe('root project', () => {
let tempFs; let tempFs;
@ -45,7 +45,9 @@ describe('@nx/vite/plugin', () => {
}, },
}, },
workspaceRoot: tempFs.tempDir, workspaceRoot: tempFs.tempDir,
configFiles: [],
}; };
tempFs.createFileSync('vite.config.ts', '');
tempFs.createFileSync('index.html', ''); tempFs.createFileSync('index.html', '');
tempFs.createFileSync('package.json', ''); tempFs.createFileSync('package.json', '');
}); });
@ -56,7 +58,7 @@ describe('@nx/vite/plugin', () => {
it('should create nodes', async () => { it('should create nodes', async () => {
const nodes = await createNodesFunction( const nodes = await createNodesFunction(
'vite.config.ts', ['vite.config.ts'],
{ {
buildTargetName: 'build', buildTargetName: 'build',
serveTargetName: 'serve', serveTargetName: 'serve',
@ -83,6 +85,7 @@ describe('@nx/vite/plugin', () => {
}, },
}, },
workspaceRoot: tempFs.tempDir, workspaceRoot: tempFs.tempDir,
configFiles: [],
}; };
tempFs.createFileSync( tempFs.createFileSync(
@ -99,7 +102,7 @@ describe('@nx/vite/plugin', () => {
it('should create nodes', async () => { it('should create nodes', async () => {
const nodes = await createNodesFunction( const nodes = await createNodesFunction(
'my-app/vite.config.ts', ['my-app/vite.config.ts'],
{ {
buildTargetName: 'build-something', buildTargetName: 'build-something',
serveTargetName: 'my-serve', serveTargetName: 'my-serve',

View File

@ -2,8 +2,12 @@ import {
CreateDependencies, CreateDependencies,
CreateNodes, CreateNodes,
CreateNodesContext, CreateNodesContext,
createNodesFromFiles,
CreateNodesV2,
detectPackageManager, detectPackageManager,
joinPathFragments, joinPathFragments,
logger,
ProjectConfiguration,
readJsonFile, readJsonFile,
TargetConfiguration, TargetConfiguration,
writeJsonFile, writeJsonFile,
@ -15,6 +19,7 @@ import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { getLockFileName } from '@nx/js'; import { getLockFileName } from '@nx/js';
import { loadViteDynamicImport } from '../utils/executor-utils'; import { loadViteDynamicImport } from '../utils/executor-utils';
import { hashObject } from 'nx/src/hasher/file-hasher';
export interface VitePluginOptions { export interface VitePluginOptions {
buildTargetName?: string; buildTargetName?: string;
@ -23,76 +28,109 @@ export interface VitePluginOptions {
previewTargetName?: string; previewTargetName?: string;
serveStaticTargetName?: string; serveStaticTargetName?: string;
} }
type ViteTargets = Pick<ProjectConfiguration, 'targets' | 'metadata'>;
const cachePath = join(workspaceDataDirectory, 'vite.hash'); function readTargetsCache(cachePath: string): Record<string, ViteTargets> {
const targetsCache = readTargetsCache();
function readTargetsCache(): Record<
string,
Record<string, TargetConfiguration>
> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {}; return existsSync(cachePath) ? readJsonFile(cachePath) : {};
} }
function writeTargetsToCache() { function writeTargetsToCache(cachePath, results?: Record<string, ViteTargets>) {
const oldCache = readTargetsCache(); writeJsonFile(cachePath, results);
writeJsonFile(cachePath, {
...oldCache,
...targetsCache,
});
} }
/**
* @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
*/
export const createDependencies: CreateDependencies = () => { export const createDependencies: CreateDependencies = () => {
writeTargetsToCache();
return []; return [];
}; };
export const createNodes: CreateNodes<VitePluginOptions> = [ const viteVitestConfigGlob = '**/{vite,vitest}.config.{js,ts,mjs,mts,cjs,cts}';
'**/{vite,vitest}.config.{js,ts,mjs,mts,cjs,cts}',
async (configFilePath, options, context) => { export const createNodesV2: CreateNodesV2<VitePluginOptions> = [
const projectRoot = dirname(configFilePath); viteVitestConfigGlob,
// Do not create a project if package.json and project.json isn't there. async (configFilePaths, options, context) => {
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); const optionsHash = hashObject(options);
if ( const cachePath = join(workspaceDataDirectory, `vite-${optionsHash}.hash`);
!siblingFiles.includes('package.json') && const targetsCache = readTargetsCache(cachePath);
!siblingFiles.includes('project.json') try {
) { return await createNodesFromFiles(
return {}; (configFile, options, context) =>
createNodesInternal(configFile, options, context, targetsCache),
configFilePaths,
options,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
} }
options = normalizeOptions(options);
// We do not want to alter how the hash is calculated, so appending the config file path to the hash
// to prevent vite/vitest files overwriting the target cache created by the other
const hash =
(await calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
])) + configFilePath;
targetsCache[hash] ??= await buildViteTargets(
configFilePath,
projectRoot,
options,
context
);
return {
projects: {
[projectRoot]: {
root: projectRoot,
targets: targetsCache[hash],
},
},
};
}, },
]; ];
export const createNodes: CreateNodes<VitePluginOptions> = [
viteVitestConfigGlob,
async (configFilePath, options, context) => {
logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
);
return createNodesInternal(configFilePath, options, context, {});
},
];
async function createNodesInternal(
configFilePath: string,
options: VitePluginOptions,
context: CreateNodesContext,
targetsCache: Record<string, ViteTargets>
) {
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 {};
}
const normalizedOptions = normalizeOptions(options);
// We do not want to alter how the hash is calculated, so appending the config file path to the hash
// to prevent vite/vitest files overwriting the target cache created by the other
const hash =
(await calculateHashForCreateNodes(
projectRoot,
normalizedOptions,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
)) + configFilePath;
targetsCache[hash] ??= await buildViteTargets(
configFilePath,
projectRoot,
normalizedOptions,
context
);
const { targets, metadata } = targetsCache[hash];
return {
projects: {
[projectRoot]: {
root: projectRoot,
targets,
metadata,
},
},
};
}
async function buildViteTargets( async function buildViteTargets(
configFilePath: string, configFilePath: string,
projectRoot: string, projectRoot: string,
options: VitePluginOptions, options: VitePluginOptions,
context: CreateNodesContext context: CreateNodesContext
) { ): Promise<ViteTargets> {
const absoluteConfigFilePath = joinPathFragments( const absoluteConfigFilePath = joinPathFragments(
context.workspaceRoot, context.workspaceRoot,
configFilePath configFilePath
@ -156,7 +194,8 @@ async function buildViteTargets(
); );
} }
return targets; const metadata = {};
return { targets, metadata };
} }
async function buildTarget( async function buildTarget(