UNPKG

@nx/remix

Version:

The Remix plugin for Nx contains executors and generators for managing Remix applications and libraries within an Nx workspace. It provides: - Integration with libraries such as Vitest, Jest, Playwright, Cypress, and Storybook. - Generators for applica

259 lines (258 loc) • 12.1 kB
"use strict"; 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 = {}));