nx/packages/vite/src/plugins/plugin.ts

297 lines
7.1 KiB
TypeScript

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;
}