@nx/rsbuild
Version:
241 lines (240 loc) • 10 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createNodesV2 = exports.createNodes = void 0;
const internal_1 = require("@nx/devkit/internal");
const devkit_1 = require("@nx/devkit");
const file_hasher_1 = require("nx/src/hasher/file-hasher");
const cache_directory_1 = require("nx/src/utils/cache-directory");
const internal_2 = require("@nx/js/internal");
const js_1 = require("@nx/js");
const fs_1 = require("fs");
const path_1 = require("path");
const minimatch_1 = require("minimatch");
const rsbuildConfigGlob = '**/rsbuild.config.{js,ts,mjs,mts,cjs,cts}';
exports.createNodes = [
rsbuildConfigGlob,
async (configFilePaths, options, context) => {
const optionsHash = (0, file_hasher_1.hashObject)(options);
const cachePath = (0, path_1.join)(cache_directory_1.workspaceDataDirectory, `rsbuild-${optionsHash}.hash`);
const targetsCache = new internal_1.PluginCache(cachePath);
const isUsingTsSolutionSetup = (0, internal_2.isUsingTsSolutionSetup)();
const packageManager = (0, devkit_1.detectPackageManager)(context.workspaceRoot);
const pmc = (0, devkit_1.getPackageManagerCommand)(packageManager);
const lockFileName = (0, js_1.getLockFileName)(packageManager);
const normalizedOptions = normalizeOptions(options);
try {
const { entries, preErrors } = await filterRsbuildConfigs(configFilePaths, context);
const projectHashes = await (0, internal_1.calculateHashesForCreateNodes)(entries.map((e) => e.projectRoot), { ...normalizedOptions, isUsingTsSolutionSetup }, context, entries.map(() => [lockFileName]));
let results = [];
let nodeErrors = [];
try {
results = await (0, devkit_1.createNodesFromFiles)((configFile, _, ctx, idx) => createNodesInternal(configFile, normalizedOptions, ctx, targetsCache, isUsingTsSolutionSetup, pmc, entries[idx].tsConfigFiles, projectHashes[idx]), entries.map((e) => e.configFile), options, context);
}
catch (e) {
if (e instanceof devkit_1.AggregateCreateNodesError) {
results = e.partialResults ?? [];
nodeErrors = e.errors;
}
else {
throw e;
}
}
const allErrors = [...preErrors, ...nodeErrors];
if (allErrors.length > 0) {
throw new devkit_1.AggregateCreateNodesError(allErrors, results);
}
return results;
}
finally {
targetsCache.writeToDisk();
}
},
];
/**
* @deprecated Use {@link createNodes} instead. This will be removed in Nx 24.
*/
exports.createNodesV2 = exports.createNodes;
async function createNodesInternal(configFilePath, normalizedOptions, context, targetsCache, isUsingTsSolutionSetup, pmc, tsConfigFiles, hash) {
const projectRoot = (0, path_1.dirname)(configFilePath);
if (!targetsCache.has(hash)) {
targetsCache.set(hash, await createRsbuildTargets(configFilePath, projectRoot, normalizedOptions, tsConfigFiles, isUsingTsSolutionSetup, context, pmc));
}
const { targets, metadata } = targetsCache.get(hash);
return {
projects: {
[projectRoot]: {
root: projectRoot,
targets,
metadata,
},
},
};
}
async function createRsbuildTargets(configFilePath, projectRoot, options, tsConfigFiles, isUsingTsSolutionSetup, context, pmc) {
const absoluteConfigFilePath = (0, devkit_1.joinPathFragments)(context.workspaceRoot, configFilePath);
// Required lazily: `@rsbuild/core` is an optional peer dependency, so it
// may be absent when the plugin is loaded in a workspace that doesn't use
// Rsbuild yet (e.g. before a generator installs it).
const { loadConfig } = require('@rsbuild/core');
const rsbuildConfig = await loadConfig({
path: absoluteConfigFilePath,
});
if (!rsbuildConfig.filePath) {
return { targets: {}, metadata: {} };
}
const namedInputs = (0, internal_1.getNamedInputs)(projectRoot, context);
const { buildOutputs } = getOutputs(rsbuildConfig.content, projectRoot, context.workspaceRoot);
const targets = {};
targets[options.buildTargetName] = {
command: `rsbuild build`,
options: { cwd: projectRoot, args: ['--mode=production'] },
cache: true,
dependsOn: [`^${options.buildTargetName}`],
inputs: [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{
externalDependencies: ['@rsbuild/core'],
},
],
outputs: buildOutputs,
metadata: {
technologies: ['rsbuild'],
description: `Run Rsbuild build`,
help: {
command: `${pmc.exec} rsbuild build --help`,
example: {
options: {
watch: false,
},
},
},
},
};
targets[options.devTargetName] = {
continuous: true,
command: `rsbuild dev`,
options: {
cwd: projectRoot,
args: ['--mode=development'],
},
};
targets[options.previewTargetName] = {
continuous: true,
command: `rsbuild preview`,
dependsOn: [`${options.buildTargetName}`, `^${options.buildTargetName}`],
options: {
cwd: projectRoot,
args: ['--mode=production'],
},
};
targets[options.inspectTargetName] = {
command: `rsbuild inspect`,
options: {
cwd: projectRoot,
},
};
if (tsConfigFiles.length) {
const tsConfigToUse = ['tsconfig.app.json', 'tsconfig.lib.json', 'tsconfig.json'].find((t) => tsConfigFiles.includes(t)) ?? tsConfigFiles[0];
targets[options.typecheckTargetName] = {
cache: true,
inputs: [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{ externalDependencies: ['typescript'] },
],
command: isUsingTsSolutionSetup
? `tsc --build --emitDeclarationOnly`
: `tsc -p ${tsConfigToUse} --noEmit`,
options: { cwd: (0, devkit_1.joinPathFragments)(projectRoot) },
metadata: {
description: `Runs type-checking for the project.`,
technologies: ['typescript'],
help: {
command: isUsingTsSolutionSetup
? `${pmc.exec} tsc --build --help`
: `${pmc.exec} tsc -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, internal_2.addBuildAndWatchDepsTargets)(context.workspaceRoot, projectRoot, targets, options, pmc);
return { targets, metadata: {} };
}
function getOutputs(rsbuildConfig, projectRoot, workspaceRoot) {
// `output.distPath.root` is the directory Rsbuild emits the build into, so
// it is the build output as-is. (Don't take its `dirname` - that points at
// the parent directory, which can capture sibling projects' outputs.)
const buildOutputPath = normalizeOutputPath(rsbuildConfig?.output?.distPath?.root, projectRoot, workspaceRoot, 'dist');
return {
buildOutputs: [buildOutputPath],
};
}
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);
}
}
}
}
async function filterRsbuildConfigs(configFiles, context) {
const preErrors = [];
const candidates = await Promise.all(configFiles.map(async (configFile) => {
try {
const projectRoot = (0, path_1.dirname)(configFile);
const siblingFiles = (0, fs_1.readdirSync)((0, path_1.join)(context.workspaceRoot, projectRoot));
if (!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')) {
return null;
}
const tsConfigFiles = siblingFiles.filter((p) => (0, minimatch_1.minimatch)(p, 'tsconfig*{.json,.*.json}')) ?? [];
return { configFile, projectRoot, tsConfigFiles };
}
catch (e) {
preErrors.push([configFile, e]);
return null;
}
}));
return {
entries: candidates.filter((c) => c !== null),
preErrors,
};
}
function normalizeOptions(options) {
options ??= {};
options.buildTargetName ??= 'build';
options.devTargetName ??= 'dev';
options.previewTargetName ??= 'preview';
options.inspectTargetName ??= 'inspect';
options.typecheckTargetName ??= 'typecheck';
return options;
}