@nx/vite
Version:
381 lines (380 loc) • 16.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createNodesV2 = exports.createNodes = exports.createDependencies = void 0;
const devkit_1 = require("@nx/devkit");
const path_1 = require("path");
const get_named_inputs_1 = require("@nx/devkit/src/utils/get-named-inputs");
const fs_1 = require("fs");
const calculate_hash_for_create_nodes_1 = require("@nx/devkit/src/utils/calculate-hash-for-create-nodes");
const cache_directory_1 = require("nx/src/utils/cache-directory");
const js_1 = require("@nx/js");
const executor_utils_1 = require("../utils/executor-utils");
const file_hasher_1 = require("nx/src/hasher/file-hasher");
const picomatch = require("picomatch");
const ts_solution_setup_1 = require("@nx/js/src/utils/typescript/ts-solution-setup");
const util_1 = require("@nx/js/src/plugins/typescript/util");
const pmc = (0, devkit_1.getPackageManagerCommand)();
function readTargetsCache(cachePath) {
return process.env.NX_CACHE_PROJECT_GRAPH !== 'false' && (0, fs_1.existsSync)(cachePath)
? (0, devkit_1.readJsonFile)(cachePath)
: {};
}
function writeTargetsToCache(cachePath, results) {
(0, devkit_1.writeJsonFile)(cachePath, results);
}
/**
* @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
*/
const createDependencies = () => {
return [];
};
exports.createDependencies = createDependencies;
const viteVitestConfigGlob = '**/{vite,vitest}.config.{js,ts,mjs,mts,cjs,cts}';
exports.createNodes = [
viteVitestConfigGlob,
async (configFilePaths, options, context) => {
const optionsHash = (0, file_hasher_1.hashObject)(options);
const normalizedOptions = normalizeOptions(options);
const cachePath = (0, path_1.join)(cache_directory_1.workspaceDataDirectory, `vite-${optionsHash}.hash`);
const targetsCache = readTargetsCache(cachePath);
const isUsingTsSolutionSetup = (0, ts_solution_setup_1.isUsingTsSolutionSetup)();
const { roots: projectRoots, configFiles: validConfigFiles } = configFilePaths.reduce((acc, configFile) => {
const potentialRoot = (0, path_1.dirname)(configFile);
if (checkIfConfigFileShouldBeProject(potentialRoot, context)) {
acc.roots.push(potentialRoot);
acc.configFiles.push(configFile);
}
return acc;
}, {
roots: [],
configFiles: [],
});
const lockfile = (0, js_1.getLockFileName)((0, devkit_1.detectPackageManager)(context.workspaceRoot));
const hashes = await (0, calculate_hash_for_create_nodes_1.calculateHashesForCreateNodes)(projectRoots, { ...normalizedOptions, isUsingTsSolutionSetup }, context, projectRoots.map((r) => [lockfile]));
try {
return await (0, devkit_1.createNodesFromFiles)(async (configFile, _, context, idx) => {
const projectRoot = (0, path_1.dirname)(configFile);
// Do not create a project if package.json and project.json isn't there.
const siblingFiles = (0, fs_1.readdirSync)((0, path_1.join)(context.workspaceRoot, projectRoot));
const tsConfigFiles = siblingFiles.filter((p) => picomatch('tsconfig*{.json,.*.json}')(p)) ?? [];
const hasReactRouterConfig = siblingFiles.some((configFile) => {
const parts = configFile.split('.');
return (parts[0] === 'react-router' &&
parts[1] === 'config' &&
parts.length > 2);
});
// results from vitest.config.js will be different from results of vite.config.js
// but the hash will be the same because it is based on the files under the project root.
// Adding the config file path to the hash ensures that the final hash value is different
// for different config files.
const hash = hashes[idx] + configFile;
const { projectType, metadata, targets } = (targetsCache[hash] ??=
await buildViteTargets(configFile, projectRoot, normalizedOptions, tsConfigFiles, hasReactRouterConfig, isUsingTsSolutionSetup, context));
const project = {
root: projectRoot,
targets,
metadata,
};
// If project is buildable, then the project type.
// If it is not buildable, then leave it to other plugins/project.json to set the project type.
if (project.targets[normalizedOptions.buildTargetName]) {
project.projectType = projectType;
}
return {
projects: {
[projectRoot]: project,
},
};
}, validConfigFiles, options, context);
}
finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];
exports.createNodesV2 = exports.createNodes;
async function buildViteTargets(configFilePath, projectRoot, options, tsConfigFiles, hasReactRouterConfig, isUsingTsSolutionSetup, context) {
const absoluteConfigFilePath = (0, devkit_1.joinPathFragments)(context.workspaceRoot, configFilePath);
// Workaround for the `build$3 is not a function` error that we sometimes see in agents.
// This should be removed later once we address the issue properly
try {
const importEsbuild = () => new Function('return import("esbuild")')();
await importEsbuild();
}
catch {
// do nothing
}
const { resolveConfig } = await (0, executor_utils_1.loadViteDynamicImport)();
const viteBuildConfig = await resolveConfig({
configFile: absoluteConfigFilePath,
mode: 'development',
}, 'build');
const { buildOutputs, testOutputs, hasTest, isBuildable, hasServeConfig } = getOutputs(viteBuildConfig, projectRoot, context.workspaceRoot);
const namedInputs = (0, get_named_inputs_1.getNamedInputs)(projectRoot, context);
const targets = {};
// if file is vitest.config or vite.config has definition for test, create target for test
if (configFilePath.includes('vitest.config') || hasTest) {
targets[options.testTargetName] = await testTarget(namedInputs, testOutputs, projectRoot);
}
if (hasReactRouterConfig) {
// If we have a react-router config, we can skip the rest of the targets
return { targets, metadata: {}, projectType: 'application' };
}
// If file is not vitest.config and buildable, create targets for build, serve, preview and serve-static
const hasRemixPlugin = viteBuildConfig.plugins &&
viteBuildConfig.plugins.some((p) => p.name === 'remix');
if (!configFilePath.includes('vitest.config') &&
!hasRemixPlugin &&
isBuildable) {
targets[options.buildTargetName] = await buildTarget(options.buildTargetName, namedInputs, buildOutputs, projectRoot, isUsingTsSolutionSetup);
// If running in library mode, then there is nothing to serve.
if (!viteBuildConfig.build?.lib || hasServeConfig) {
const devTarget = serveTarget(projectRoot, isUsingTsSolutionSetup);
targets[options.serveTargetName] = {
...devTarget,
metadata: {
...devTarget.metadata,
deprecated: 'Use devTargetName instead. This option will be removed in Nx 22.',
},
};
targets[options.devTargetName] = devTarget;
targets[options.previewTargetName] = previewTarget(projectRoot, options.buildTargetName);
targets[options.serveStaticTargetName] = serveStaticTarget(options, isUsingTsSolutionSetup);
}
}
if (tsConfigFiles.length) {
const tsConfigToUse = ['tsconfig.app.json', 'tsconfig.lib.json', 'tsconfig.json'].find((t) => tsConfigFiles.includes(t)) ?? tsConfigFiles[0];
// Check if the project uses Vue plugin
const hasVuePlugin = viteBuildConfig.plugins?.some((p) => p.name === 'vite:vue');
const typeCheckCommand = hasVuePlugin ? 'vue-tsc' : 'tsc';
targets[options.typecheckTargetName] = {
cache: true,
inputs: [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{
externalDependencies: hasVuePlugin
? ['vue-tsc', 'typescript']
: ['typescript'],
},
],
command: isUsingTsSolutionSetup
? `${typeCheckCommand} --build --emitDeclarationOnly`
: `${typeCheckCommand} --noEmit -p ${tsConfigToUse}`,
options: { cwd: (0, devkit_1.joinPathFragments)(projectRoot) },
metadata: {
description: `Runs type-checking for the project.`,
technologies: hasVuePlugin ? ['typescript', 'vue'] : ['typescript'],
help: {
command: isUsingTsSolutionSetup
? `${pmc.exec} ${typeCheckCommand} --build --help`
: `${pmc.exec} ${typeCheckCommand} -p ${tsConfigToUse} --help`,
example: isUsingTsSolutionSetup
? { args: ['--force'] }
: { options: { noEmit: true } },
},
},
};
if (isUsingTsSolutionSetup) {
targets[options.typecheckTargetName].dependsOn = [
`^${options.typecheckTargetName}`,
];
targets[options.typecheckTargetName].syncGenerators = [
'@nx/js:typescript-sync',
];
}
}
(0, util_1.addBuildAndWatchDepsTargets)(context.workspaceRoot, projectRoot, targets, options, pmc);
const metadata = {};
return {
targets,
metadata,
projectType: viteBuildConfig.build?.lib ? 'library' : 'application',
};
}
async function buildTarget(buildTargetName, namedInputs, outputs, projectRoot, isUsingTsSolutionSetup) {
const buildTarget = {
command: `vite build`,
options: { cwd: (0, devkit_1.joinPathFragments)(projectRoot) },
cache: true,
dependsOn: [`^${buildTargetName}`],
inputs: [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{
externalDependencies: ['vite'],
},
],
outputs,
metadata: {
technologies: ['vite'],
description: `Run Vite build`,
help: {
command: `${pmc.exec} vite build --help`,
example: {
options: {
sourcemap: true,
manifest: 'manifest.json',
},
},
},
},
};
if (isUsingTsSolutionSetup) {
buildTarget.syncGenerators = ['@nx/js:typescript-sync'];
}
return buildTarget;
}
function serveTarget(projectRoot, isUsingTsSolutionSetup) {
const targetConfig = {
continuous: true,
command: `vite`,
options: {
cwd: (0, devkit_1.joinPathFragments)(projectRoot),
},
metadata: {
technologies: ['vite'],
description: `Starts Vite dev server`,
help: {
command: `${pmc.exec} vite --help`,
example: {
options: {
port: 3000,
},
},
},
},
};
if (isUsingTsSolutionSetup) {
targetConfig.syncGenerators = ['@nx/js:typescript-sync'];
}
return targetConfig;
}
function previewTarget(projectRoot, buildTargetName) {
const targetConfig = {
continuous: true,
command: `vite preview`,
dependsOn: [buildTargetName],
options: {
cwd: (0, devkit_1.joinPathFragments)(projectRoot),
},
metadata: {
technologies: ['vite'],
description: `Locally preview Vite production build`,
help: {
command: `${pmc.exec} vite preview --help`,
example: {
options: {
port: 3000,
},
},
},
},
};
return targetConfig;
}
async function testTarget(namedInputs, outputs, projectRoot) {
return {
command: `vitest`,
options: { cwd: (0, devkit_1.joinPathFragments)(projectRoot) },
cache: true,
inputs: [
...('production' in namedInputs
? ['default', '^production']
: ['default', '^default']),
{
externalDependencies: ['vitest'],
},
{ env: 'CI' },
],
outputs,
metadata: {
technologies: ['vite'],
description: `Run Vite tests`,
help: {
command: `${pmc.exec} vitest --help`,
example: {
options: {
bail: 1,
coverage: true,
},
},
},
},
};
}
function serveStaticTarget(options, isUsingTsSolutionSetup) {
const targetConfig = {
continuous: true,
executor: '@nx/web:file-server',
options: {
buildTarget: `${options.buildTargetName}`,
spa: true,
},
};
if (isUsingTsSolutionSetup) {
targetConfig.syncGenerators = ['@nx/js:typescript-sync'];
}
return targetConfig;
}
function getOutputs(viteBuildConfig, projectRoot, workspaceRoot) {
const { build, test, server } = viteBuildConfig;
const buildOutputPath = normalizeOutputPath(build?.outDir, projectRoot, workspaceRoot, 'dist');
const isBuildable = build?.lib ||
build?.rollupOptions?.input ||
(0, fs_1.existsSync)((0, path_1.join)(workspaceRoot, projectRoot, 'index.html'));
const hasServeConfig = Boolean(server?.host || server?.port);
const reportsDirectoryPath = normalizeOutputPath(test?.coverage?.reportsDirectory, projectRoot, workspaceRoot, 'coverage');
return {
buildOutputs: [buildOutputPath],
testOutputs: [reportsDirectoryPath],
hasTest: !!test,
isBuildable,
hasServeConfig,
};
}
function normalizeOutputPath(outputPath, projectRoot, workspaceRoot, path) {
if (!outputPath) {
if (projectRoot === '.') {
return `{projectRoot}/${path}`;
}
else {
return `{workspaceRoot}/${path}/{projectRoot}`;
}
}
else {
if ((0, path_1.isAbsolute)(outputPath)) {
return `{workspaceRoot}/${(0, path_1.relative)(workspaceRoot, outputPath)}`;
}
else {
if (outputPath.startsWith('..')) {
return (0, path_1.join)('{workspaceRoot}', (0, path_1.join)(projectRoot, outputPath));
}
else {
return (0, path_1.join)('{projectRoot}', outputPath);
}
}
}
}
function normalizeOptions(options) {
options ??= {};
options.buildTargetName ??= 'build';
options.serveTargetName ??= 'serve';
options.devTargetName ??= 'dev';
options.previewTargetName ??= 'preview';
options.testTargetName ??= 'test';
options.serveStaticTargetName ??= 'serve-static';
options.typecheckTargetName ??= 'typecheck';
return options;
}
function checkIfConfigFileShouldBeProject(projectRoot, context) {
// Do not create a project if package.json and project.json isn't there.
const siblingFiles = (0, fs_1.readdirSync)((0, path_1.join)(context.workspaceRoot, projectRoot));
if (!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')) {
return false;
}
return true;
}