@nx/remix
Version:
259 lines (258 loc) • 12.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createNodes = exports.createNodesV2 = exports.createDependencies = void 0;
const cache_directory_1 = require("nx/src/utils/cache-directory");
const file_hasher_1 = require("nx/src/hasher/file-hasher");
const devkit_1 = require("@nx/devkit");
const calculate_hash_for_create_nodes_1 = require("@nx/devkit/src/utils/calculate-hash-for-create-nodes");
const get_named_inputs_1 = require("@nx/devkit/src/utils/get-named-inputs");
const config_utils_1 = require("@nx/devkit/src/utils/config-utils");
const js_1 = require("@nx/js");
const path_1 = require("path");
const fs_1 = require("fs");
const executor_utils_1 = require("../utils/executor-utils");
const util_1 = require("@nx/js/src/plugins/typescript/util");
const ts_solution_setup_1 = require("@nx/js/src/utils/typescript/ts-solution-setup");
const pmc = (0, devkit_1.getPackageManagerCommand)();
function readTargetsCache(cachePath) {
return (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 remixConfigGlob = '**/{remix,vite}.config.{js,cjs,mjs,ts,cts,mts}';
exports.createNodesV2 = [
remixConfigGlob,
async (configFilePaths, options, context) => {
const optionsHash = (0, file_hasher_1.hashObject)(options);
const cachePath = (0, path_1.join)(cache_directory_1.workspaceDataDirectory, `remix-${optionsHash}.hash`);
const targetsCache = readTargetsCache(cachePath);
try {
return await (0, devkit_1.createNodesFromFiles)((configFile, options, context) => createNodesInternal(configFile, options, context, targetsCache, (0, ts_solution_setup_1.isUsingTsSolutionSetup)()), configFilePaths, options, context);
}
finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];
exports.createNodes = [
remixConfigGlob,
async (configFilePath, options, context) => {
devkit_1.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, {}, (0, ts_solution_setup_1.isUsingTsSolutionSetup)());
},
];
async function createNodesInternal(configFilePath, options, context, targetsCache, isUsingTsSolutionSetup) {
const projectRoot = (0, path_1.dirname)(configFilePath);
const fullyQualifiedProjectRoot = (0, path_1.join)(context.workspaceRoot, projectRoot);
// Do not create a project if package.json and project.json isn't there
const siblingFiles = (0, fs_1.readdirSync)(fullyQualifiedProjectRoot);
if (!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')) {
return {};
}
options = normalizeOptions(options);
const remixCompiler = determineIsRemixVite(configFilePath, context.workspaceRoot);
if (remixCompiler === RemixCompiler.IsNotRemix) {
return {};
}
const hash = (await (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(projectRoot, { ...options, isUsingTsSolutionSetup }, context, [(0, js_1.getLockFileName)((0, devkit_1.detectPackageManager)(context.workspaceRoot))])) + configFilePath;
targetsCache[hash] ??= await buildRemixTargets(configFilePath, projectRoot, options, context, siblingFiles, remixCompiler, isUsingTsSolutionSetup);
const { targets, metadata } = targetsCache[hash];
const project = {
root: projectRoot,
targets,
metadata,
};
return {
projects: {
[projectRoot]: project,
},
};
}
async function buildRemixTargets(configFilePath, projectRoot, options, context, siblingFiles, remixCompiler, isUsingTsSolutionSetup) {
const namedInputs = (0, get_named_inputs_1.getNamedInputs)(projectRoot, context);
const { buildDirectory, assetsBuildDirectory, serverBuildPath } = await getBuildPaths(configFilePath, projectRoot, context.workspaceRoot, remixCompiler);
const targets = {};
targets[options.buildTargetName] = buildTarget(options.buildTargetName, projectRoot, buildDirectory, assetsBuildDirectory, namedInputs, remixCompiler, isUsingTsSolutionSetup);
targets[options.devTargetName] = devTarget(serverBuildPath, projectRoot, remixCompiler, isUsingTsSolutionSetup);
targets[options.startTargetName] = startTarget(projectRoot, serverBuildPath, options.buildTargetName, remixCompiler, isUsingTsSolutionSetup);
targets[options.serveStaticTargetName] = startTarget(projectRoot, serverBuildPath, options.buildTargetName, remixCompiler, isUsingTsSolutionSetup);
targets[options.typecheckTargetName] = typecheckTarget(options.typecheckTargetName, projectRoot, namedInputs, siblingFiles, isUsingTsSolutionSetup);
(0, util_1.addBuildAndWatchDepsTargets)(context.workspaceRoot, projectRoot, targets, options, pmc);
return { targets, metadata: {} };
}
function buildTarget(buildTargetName, projectRoot, buildDirectory, assetsBuildDirectory, namedInputs, remixCompiler, isUsingTsSolutionSetup) {
const serverBuildOutputPath = projectRoot === '.'
? (0, devkit_1.joinPathFragments)(`{workspaceRoot}`, buildDirectory)
: (0, devkit_1.joinPathFragments)(`{workspaceRoot}`, projectRoot, buildDirectory);
const assetsBuildOutputPath = projectRoot === '.'
? (0, devkit_1.joinPathFragments)(`{workspaceRoot}`, assetsBuildDirectory)
: (0, devkit_1.joinPathFragments)(`{workspaceRoot}`, projectRoot, assetsBuildDirectory);
const outputs = remixCompiler === RemixCompiler.IsVte
? [
projectRoot === '.'
? (0, devkit_1.joinPathFragments)(`{workspaceRoot}`, buildDirectory)
: (0, devkit_1.joinPathFragments)(`{workspaceRoot}`, projectRoot, buildDirectory),
]
: [serverBuildOutputPath, assetsBuildOutputPath];
const buildTarget = {
cache: true,
dependsOn: [`^${buildTargetName}`],
inputs: [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{ externalDependencies: ['@remix-run/dev'] },
],
outputs,
command: remixCompiler === RemixCompiler.IsVte
? 'remix vite:build'
: 'remix build',
options: { cwd: projectRoot },
};
if (isUsingTsSolutionSetup) {
buildTarget.syncGenerators = ['@nx/js:typescript-sync'];
}
return buildTarget;
}
function devTarget(serverBuildPath, projectRoot, remixCompiler, isUsingTsSolutionSetup) {
const devTarget = {
continuous: true,
command: remixCompiler === RemixCompiler.IsVte
? 'remix vite:dev'
: 'remix dev --manual',
options: { cwd: projectRoot },
};
if (isUsingTsSolutionSetup) {
devTarget.syncGenerators = ['@nx/js:typescript-sync'];
}
return devTarget;
}
function startTarget(projectRoot, serverBuildPath, buildTargetName, remixCompiler, isUsingTsSolutionSetup) {
let serverPath = serverBuildPath;
if (remixCompiler === RemixCompiler.IsVte) {
if (serverBuildPath === 'build') {
serverPath = `${serverBuildPath}/server/index.js`;
}
}
const startTarget = {
dependsOn: [buildTargetName],
continuous: true,
command: `remix-serve ${serverPath}`,
options: {
cwd: projectRoot,
},
};
if (isUsingTsSolutionSetup) {
startTarget.syncGenerators = ['@nx/js:typescript-sync'];
}
return startTarget;
}
function typecheckTarget(typecheckTargetName, projectRoot, namedInputs, siblingFiles, isUsingTsSolutionSetup) {
const hasTsConfigAppJson = siblingFiles.includes('tsconfig.app.json');
const typecheckTarget = {
cache: true,
inputs: [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{ externalDependencies: ['typescript'] },
],
command: isUsingTsSolutionSetup
? `tsc --build --emitDeclarationOnly`
: `tsc${hasTsConfigAppJson ? ` -p tsconfig.app.json` : ``} --noEmit`,
options: {
cwd: projectRoot,
},
metadata: {
description: `Runs type-checking for the project.`,
technologies: ['typescript'],
help: {
command: isUsingTsSolutionSetup
? `${pmc.exec} tsc --build --help`
: `${pmc.exec} tsc${hasTsConfigAppJson ? ` -p tsconfig.app.json` : ``} --help`,
example: isUsingTsSolutionSetup
? { args: ['--force'] }
: { options: { noEmit: true } },
},
},
};
if (isUsingTsSolutionSetup) {
typecheckTarget.dependsOn = [`^${typecheckTargetName}`];
typecheckTarget.syncGenerators = ['@nx/js:typescript-sync'];
}
return typecheckTarget;
}
async function getBuildPaths(configFilePath, projectRoot, workspaceRoot, remixCompiler) {
const configPath = (0, path_1.join)(workspaceRoot, configFilePath);
if (remixCompiler === RemixCompiler.IsClassic) {
let appConfig = await (0, config_utils_1.loadConfigFile)(configPath);
return {
buildDirectory: 'build',
serverBuildPath: appConfig.serverBuildPath ?? 'build/index.js',
assetsBuildDirectory: appConfig.assetsBuildDirectory ?? 'public/build',
};
}
else {
// 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: configPath,
mode: 'development',
}, 'build'));
return {
buildDirectory: viteBuildConfig.build?.outDir ?? 'build',
serverBuildPath: viteBuildConfig.build?.outDir
? (0, path_1.join)((0, path_1.dirname)(viteBuildConfig.build?.outDir), `server/${viteBuildConfig.__remixPluginContext?.remixConfig.serverBuildFile}`)
: 'build',
assetsBuildDirectory: 'build/client',
};
}
}
function normalizeOptions(options) {
options ??= {};
options.buildTargetName ??= 'build';
options.devTargetName ??= 'dev';
options.startTargetName ??= 'start';
options.typecheckTargetName ??= 'typecheck';
options.serveStaticTargetName ??= 'serve-static';
return options;
}
function determineIsRemixVite(configFilePath, workspaceRoot) {
if (configFilePath.includes('remix.config')) {
return RemixCompiler.IsClassic;
}
const VITE_PLUGIN_REGEX = /vitePlugin\(\s*(.|\n)*?\s*\)/;
const REMIX_PLUGIN_REGEX = /remix\(\s*(.|\n)*?\s*\)/;
const fileContents = (0, fs_1.readFileSync)((0, path_1.join)(workspaceRoot, configFilePath), 'utf8');
if (fileContents.includes('@remix-run/dev') &&
(VITE_PLUGIN_REGEX.test(fileContents) ||
REMIX_PLUGIN_REGEX.test(fileContents))) {
return RemixCompiler.IsVte;
}
else {
return RemixCompiler.IsNotRemix;
}
}
var RemixCompiler;
(function (RemixCompiler) {
RemixCompiler[RemixCompiler["IsClassic"] = 1] = "IsClassic";
RemixCompiler[RemixCompiler["IsVte"] = 2] = "IsVte";
RemixCompiler[RemixCompiler["IsNotRemix"] = 3] = "IsNotRemix";
})(RemixCompiler || (RemixCompiler = {}));