feat(remix): infer targets for remix vite in @nx/remix/plugin (#27713)

- feat(remix): add createnodesv2
- feat(remix): support vite in the plugin
- feat(remix): support remix vite in plugin
- fix(remix): init should ues addPlugin v2

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
Nx does not currently infer targets when someone is using remix w/ vite


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Nx should infer targets for remix vite apps

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Colum Ferry 2024-09-04 13:44:19 +01:00 committed by GitHub
parent 27a01861c3
commit ef7b66800a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 846 additions and 346 deletions

View File

@ -1,5 +1,6 @@
export { export {
createNodes, createNodes,
createNodesV2,
createDependencies, createDependencies,
RemixPluginOptions, RemixPluginOptions,
} from './src/plugins/plugin'; } from './src/plugins/plugin';

View File

@ -39,6 +39,7 @@ describe('Remix Init Generator', () => {
"options": { "options": {
"buildTargetName": "build", "buildTargetName": "build",
"devTargetName": "dev", "devTargetName": "dev",
"serveStaticTargetName": "serve-static",
"startTargetName": "start", "startTargetName": "start",
"typecheckTargetName": "typecheck", "typecheckTargetName": "typecheck",
}, },

View File

@ -8,10 +8,10 @@ import {
createProjectGraphAsync, createProjectGraphAsync,
} from '@nx/devkit'; } from '@nx/devkit';
import { import {
addPluginV1, addPlugin,
generateCombinations, generateCombinations,
} from '@nx/devkit/src/utils/add-plugin'; } from '@nx/devkit/src/utils/add-plugin';
import { createNodes } from '../../plugins/plugin'; import { createNodesV2 } from '../../plugins/plugin';
import { nxVersion, remixVersion } from '../../utils/versions'; import { nxVersion, remixVersion } from '../../utils/versions';
import { type Schema } from './schema'; import { type Schema } from './schema';
@ -44,11 +44,11 @@ export async function remixInitGeneratorInternal(tree: Tree, options: Schema) {
nxJson.useInferencePlugins !== false; nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPluginDefault; options.addPlugin ??= addPluginDefault;
if (options.addPlugin) { if (options.addPlugin) {
await addPluginV1( await addPlugin(
tree, tree,
await createProjectGraphAsync(), await createProjectGraphAsync(),
'@nx/remix/plugin', '@nx/remix/plugin',
createNodes, createNodesV2,
{ {
startTargetName: ['start', 'remix:start', 'remix-start'], startTargetName: ['start', 'remix:start', 'remix-start'],
buildTargetName: ['build', 'remix:build', 'remix-build'], buildTargetName: ['build', 'remix:build', 'remix-build'],
@ -58,6 +58,11 @@ export async function remixInitGeneratorInternal(tree: Tree, options: Schema) {
'remix:typecheck', 'remix:typecheck',
'remix-typecheck', 'remix-typecheck',
], ],
serveStaticTargetName: [
'serve-static',
'vite:serve-static',
'vite-serve-static',
],
}, },
options.updatePackageScripts options.updatePackageScripts
); );

View File

@ -1,9 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`@nx/remix/plugin non-root project should create nodes 1`] = ` exports[`@nx/remix/plugin Remix Classic Compiler non-root project should create nodes 1`] = `
[
[
"my-app/remix.config.cjs",
{ {
"projects": { "projects": {
"my-app": { "my-app": {
"metadata": {},
"root": "my-app", "root": "my-app",
"targets": { "targets": {
"build": { "build": {
@ -81,13 +85,19 @@ exports[`@nx/remix/plugin non-root project should create nodes 1`] = `
}, },
}, },
}, },
} },
],
]
`; `;
exports[`@nx/remix/plugin root project should create nodes 1`] = ` exports[`@nx/remix/plugin Remix Classic Compiler root project should create nodes 1`] = `
[
[
"remix.config.cjs",
{ {
"projects": { "projects": {
".": { ".": {
"metadata": {},
"root": ".", "root": ".",
"targets": { "targets": {
"build": { "build": {
@ -165,5 +175,185 @@ exports[`@nx/remix/plugin root project should create nodes 1`] = `
}, },
}, },
}, },
} },
],
]
`;
exports[`@nx/remix/plugin Remix Vite Compiler non-root project should create nodes 1`] = `
[
[
"my-app/vite.config.js",
{
"projects": {
"my-app": {
"metadata": {},
"root": "my-app",
"targets": {
"build": {
"cache": true,
"command": "remix vite:build",
"dependsOn": [
"^build",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"@remix-run/dev",
],
},
],
"options": {
"cwd": "my-app",
},
"outputs": [
"{workspaceRoot}/my-app/build",
],
},
"dev": {
"command": "remix vite:dev",
"options": {
"cwd": "my-app",
},
},
"serve-static": {
"command": "remix-serve build/server/index.js",
"dependsOn": [
"build",
],
"options": {
"cwd": "my-app",
},
},
"start": {
"command": "remix-serve build/server/index.js",
"dependsOn": [
"build",
],
"options": {
"cwd": "my-app",
},
},
"static-serve": {
"command": "remix-serve build/server/index.js",
"dependsOn": [
"build",
],
"options": {
"cwd": "my-app",
},
},
"tsc": {
"cache": true,
"command": "tsc",
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"options": {
"cwd": "my-app",
},
},
},
},
},
},
],
]
`;
exports[`@nx/remix/plugin Remix Vite Compiler root project should create nodes 1`] = `
[
[
"vite.config.js",
{
"projects": {
".": {
"metadata": {},
"root": ".",
"targets": {
"build": {
"cache": true,
"command": "remix vite:build",
"dependsOn": [
"^build",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"@remix-run/dev",
],
},
],
"options": {
"cwd": ".",
},
"outputs": [
"{workspaceRoot}/build",
],
},
"dev": {
"command": "remix vite:dev",
"options": {
"cwd": ".",
},
},
"serve-static": {
"command": "remix-serve build/server/index.js",
"dependsOn": [
"build",
],
"options": {
"cwd": ".",
},
},
"start": {
"command": "remix-serve build/server/index.js",
"dependsOn": [
"build",
],
"options": {
"cwd": ".",
},
},
"static-serve": {
"command": "remix-serve build/server/index.js",
"dependsOn": [
"build",
],
"options": {
"cwd": ".",
},
},
"typecheck": {
"cache": true,
"command": "tsc",
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"options": {
"cwd": ".",
},
},
},
},
},
},
],
]
`; `;

View File

@ -1,12 +1,20 @@
import { type CreateNodesContext, joinPathFragments } from '@nx/devkit'; import { type CreateNodesContext, joinPathFragments } from '@nx/devkit';
import { createNodes } from './plugin'; import { createNodesV2 as createNodes } from './plugin';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs'; import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { loadViteDynamicImport } from '../utils/executor-utils';
jest.mock('../utils/executor-utils', () => ({
loadViteDynamicImport: jest.fn().mockResolvedValue({
resolveConfig: jest.fn().mockResolvedValue({}),
}),
}));
describe('@nx/remix/plugin', () => { describe('@nx/remix/plugin', () => {
let createNodesFunction = createNodes[1]; let createNodesFunction = createNodes[1];
let context: CreateNodesContext; let context: CreateNodesContext;
let cwd = process.cwd(); let cwd = process.cwd();
describe('Remix Classic Compiler', () => {
describe('root project', () => { describe('root project', () => {
const tempFs = new TempFs('test'); const tempFs = new TempFs('test');
@ -63,7 +71,7 @@ module.exports = {
it('should create nodes', async () => { it('should create nodes', async () => {
// ACT // ACT
const nodes = await createNodesFunction( const nodes = await createNodesFunction(
'remix.config.cjs', ['remix.config.cjs'],
{ {
buildTargetName: 'build', buildTargetName: 'build',
devTargetName: 'dev', devTargetName: 'dev',
@ -123,7 +131,7 @@ module.exports = {
it('should create nodes', async () => { it('should create nodes', async () => {
// ACT // ACT
const nodes = await createNodesFunction( const nodes = await createNodesFunction(
'my-app/remix.config.cjs', ['my-app/remix.config.cjs'],
{ {
buildTargetName: 'build', buildTargetName: 'build',
devTargetName: 'dev', devTargetName: 'dev',
@ -139,3 +147,152 @@ module.exports = {
}); });
}); });
}); });
describe('Remix Vite Compiler', () => {
describe('root project', () => {
const tempFs = new TempFs('test');
beforeEach(() => {
context = {
nxJsonConfiguration: {
targetDefaults: {
build: {
cache: false,
inputs: ['foo', '^foo'],
},
dev: {
command: 'npm run dev',
},
start: {
command: 'npm run start',
},
typecheck: {
command: 'tsc',
},
},
namedInputs: {
default: ['{projectRoot}/**/*'],
production: ['!{projectRoot}/**/*.spec.ts'],
},
},
workspaceRoot: tempFs.tempDir,
configFiles: [],
};
tempFs.createFileSync(
'package.json',
JSON.stringify('{name: "my-app", type: "module"}')
);
tempFs.createFileSync(
'vite.config.js',
`const {defineConfig} = require('vite');
const { vitePlugin: remix } = require('@remix-run/dev');
module.exports = defineConfig({
plugins:[remix()]
});`
);
process.chdir(tempFs.tempDir);
(loadViteDynamicImport as jest.Mock).mockResolvedValue({
resolveConfig: jest.fn().mockResolvedValue({
build: {
lib: {
entry: 'index.ts',
name: 'my-app',
},
},
}),
});
});
afterEach(() => {
jest.resetModules();
tempFs.cleanup();
process.chdir(cwd);
});
it('should create nodes', async () => {
// ACT
const nodes = await createNodesFunction(
['vite.config.js'],
{
buildTargetName: 'build',
devTargetName: 'dev',
startTargetName: 'start',
typecheckTargetName: 'typecheck',
staticServeTargetName: 'static-serve',
},
context
);
// ASSERT
expect(nodes).toMatchSnapshot();
});
});
describe('non-root project', () => {
const tempFs = new TempFs('test');
beforeEach(() => {
context = {
nxJsonConfiguration: {
namedInputs: {
default: ['{projectRoot}/**/*'],
production: ['!{projectRoot}/**/*.spec.ts'],
},
},
workspaceRoot: tempFs.tempDir,
configFiles: [],
};
tempFs.createFileSync(
'my-app/project.json',
JSON.stringify({ name: 'my-app' })
);
tempFs.createFileSync(
'my-app/vite.config.js',
`const {defineConfig} = require('vite');
const { vitePlugin: remix } = require('@remix-run/dev');
module.exports = defineConfig({
plugins:[remix()]
});`
);
(loadViteDynamicImport as jest.Mock).mockResolvedValue({
resolveConfig: jest.fn().mockResolvedValue({
build: {
lib: {
entry: 'index.ts',
name: 'my-app',
},
},
}),
});
process.chdir(tempFs.tempDir);
});
afterEach(() => {
jest.resetModules();
tempFs.cleanup();
process.chdir(cwd);
});
it('should create nodes', async () => {
// ACT
const nodes = await createNodesFunction(
['my-app/vite.config.js'],
{
buildTargetName: 'build',
devTargetName: 'dev',
startTargetName: 'start',
typecheckTargetName: 'tsc',
staticServeTargetName: 'static-serve',
},
context
);
// ASSERT
expect(nodes).toMatchSnapshot();
});
});
});
});

View File

@ -1,44 +1,27 @@
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { hashObject } from 'nx/src/hasher/file-hasher';
import { import {
type CreateDependencies, type CreateDependencies,
type CreateNodes, type CreateNodes,
type CreateNodesContext, type CreateNodesContext,
createNodesFromFiles,
CreateNodesV2,
detectPackageManager, detectPackageManager,
joinPathFragments, joinPathFragments,
logger,
ProjectConfiguration,
readJsonFile, readJsonFile,
type TargetConfiguration, type TargetConfiguration,
writeJsonFile, writeJsonFile,
} from '@nx/devkit'; } from '@nx/devkit';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes'; import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs'; import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
import { getLockFileName } from '@nx/js'; import { getLockFileName } from '@nx/js';
import { type AppConfig } from '@remix-run/dev'; import { type AppConfig } from '@remix-run/dev';
import { dirname, join } from 'path'; import { dirname, isAbsolute, join, relative } from 'path';
import { existsSync, readdirSync } from 'fs'; import { existsSync, readdirSync, readFileSync } from 'fs';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils'; import { loadViteDynamicImport } from '../utils/executor-utils';
const cachePath = join(workspaceDataDirectory, 'remix.hash');
const targetsCache = readTargetsCache();
function readTargetsCache(): Record<
string,
Record<string, TargetConfiguration>
> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
}
function writeTargetsToCache() {
const oldCache = readTargetsCache();
writeJsonFile(cachePath, {
...oldCache,
...targetsCache,
});
}
export const createDependencies: CreateDependencies = () => {
writeTargetsToCache();
return [];
};
export interface RemixPluginOptions { export interface RemixPluginOptions {
buildTargetName?: string; buildTargetName?: string;
@ -51,60 +34,133 @@ export interface RemixPluginOptions {
staticServeTargetName?: string; staticServeTargetName?: string;
serveStaticTargetName?: string; serveStaticTargetName?: string;
} }
type RemixTargets = Pick<ProjectConfiguration, 'targets' | 'metadata'>;
function readTargetsCache(
cachePath: string
): Record<string, Record<string, TargetConfiguration>> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
}
function writeTargetsToCache(
cachePath: string,
results: Record<string, RemixTargets>
) {
writeJsonFile(cachePath, results);
}
/**
* @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
*/
export const createDependencies: CreateDependencies = () => {
return [];
};
const remixConfigGlob = '**/{remix,vite}.config.{js,cjs,mjs}';
export const createNodesV2: CreateNodesV2<RemixPluginOptions> = [
remixConfigGlob,
async (configFilePaths, options, context) => {
const optionsHash = hashObject(options);
const cachePath = join(workspaceDataDirectory, `remix-${optionsHash}.hash`);
const targetsCache = readTargetsCache(cachePath);
try {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context, targetsCache),
configFilePaths,
options,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];
export const createNodes: CreateNodes<RemixPluginOptions> = [ export const createNodes: CreateNodes<RemixPluginOptions> = [
'**/remix.config.{js,cjs,mjs}', remixConfigGlob,
async (configFilePath, options, context) => { async (configFilePath, options, context) => {
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, {});
},
];
async function createNodesInternal(
configFilePath: string,
options: RemixPluginOptions,
context: CreateNodesContext,
targetsCache: Record<string, RemixTargets>
) {
const projectRoot = dirname(configFilePath); const projectRoot = dirname(configFilePath);
const fullyQualifiedProjectRoot = join(context.workspaceRoot, projectRoot); const fullyQualifiedProjectRoot = join(context.workspaceRoot, projectRoot);
// Do not create a project if package.json and project.json isn't there // Do not create a project if package.json and project.json isn't there
const siblingFiles = readdirSync(fullyQualifiedProjectRoot); const siblingFiles = readdirSync(fullyQualifiedProjectRoot);
if ( if (
!siblingFiles.includes('package.json') && !siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json') && !siblingFiles.includes('project.json')
!siblingFiles.includes('vite.config.ts') &&
!siblingFiles.includes('vite.config.js')
) { ) {
return {}; return {};
} }
options = normalizeOptions(options); options = normalizeOptions(options);
const hash = await calculateHashForCreateNodes( const remixCompiler = determineIsRemixVite(
projectRoot, configFilePath,
options, context.workspaceRoot
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
); );
if (remixCompiler === RemixCompiler.IsNotRemix) {
return {};
}
const hash =
(await calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
])) + configFilePath;
targetsCache[hash] ??= await buildRemixTargets( targetsCache[hash] ??= await buildRemixTargets(
configFilePath, configFilePath,
projectRoot, projectRoot,
options, options,
context, context,
siblingFiles siblingFiles,
remixCompiler
); );
const { targets, metadata } = targetsCache[hash];
const project: ProjectConfiguration = {
root: projectRoot,
targets,
metadata,
};
return { return {
projects: { projects: {
[projectRoot]: { [projectRoot]: project,
root: projectRoot,
targets: targetsCache[hash],
},
}, },
}; };
}, }
];
async function buildRemixTargets( async function buildRemixTargets(
configFilePath: string, configFilePath: string,
projectRoot: string, projectRoot: string,
options: RemixPluginOptions, options: RemixPluginOptions,
context: CreateNodesContext, context: CreateNodesContext,
siblingFiles: string[] siblingFiles: string[],
remixCompiler: RemixCompiler
) { ) {
const namedInputs = getNamedInputs(projectRoot, context); const namedInputs = getNamedInputs(projectRoot, context);
const { buildDirectory, assetsBuildDirectory, serverBuildPath } = const { buildDirectory, assetsBuildDirectory, serverBuildPath } =
await getBuildPaths(configFilePath, context.workspaceRoot); await getBuildPaths(
configFilePath,
projectRoot,
context.workspaceRoot,
remixCompiler
);
const targets: Record<string, TargetConfiguration> = {}; const targets: Record<string, TargetConfiguration> = {};
targets[options.buildTargetName] = buildTarget( targets[options.buildTargetName] = buildTarget(
@ -112,24 +168,32 @@ async function buildRemixTargets(
projectRoot, projectRoot,
buildDirectory, buildDirectory,
assetsBuildDirectory, assetsBuildDirectory,
namedInputs namedInputs,
remixCompiler
);
targets[options.devTargetName] = devTarget(
serverBuildPath,
projectRoot,
remixCompiler
); );
targets[options.devTargetName] = devTarget(serverBuildPath, projectRoot);
targets[options.startTargetName] = startTarget( targets[options.startTargetName] = startTarget(
projectRoot, projectRoot,
serverBuildPath, serverBuildPath,
options.buildTargetName options.buildTargetName,
remixCompiler
); );
// TODO(colum): Remove for Nx 21 // TODO(colum): Remove for Nx 21
targets[options.staticServeTargetName] = startTarget( targets[options.staticServeTargetName] = startTarget(
projectRoot, projectRoot,
serverBuildPath, serverBuildPath,
options.buildTargetName options.buildTargetName,
remixCompiler
); );
targets[options.serveStaticTargetName] = startTarget( targets[options.serveStaticTargetName] = startTarget(
projectRoot, projectRoot,
serverBuildPath, serverBuildPath,
options.buildTargetName options.buildTargetName,
remixCompiler
); );
targets[options.typecheckTargetName] = typecheckTarget( targets[options.typecheckTargetName] = typecheckTarget(
projectRoot, projectRoot,
@ -137,7 +201,7 @@ async function buildRemixTargets(
siblingFiles siblingFiles
); );
return targets; return { targets, metadata: {} };
} }
function buildTarget( function buildTarget(
@ -145,7 +209,8 @@ function buildTarget(
projectRoot: string, projectRoot: string,
buildDirectory: string, buildDirectory: string,
assetsBuildDirectory: string, assetsBuildDirectory: string,
namedInputs: { [inputName: string]: any[] } namedInputs: { [inputName: string]: any[] },
remixCompiler: RemixCompiler
): TargetConfiguration { ): TargetConfiguration {
const serverBuildOutputPath = const serverBuildOutputPath =
projectRoot === '.' projectRoot === '.'
@ -157,6 +222,15 @@ function buildTarget(
? joinPathFragments(`{workspaceRoot}`, assetsBuildDirectory) ? joinPathFragments(`{workspaceRoot}`, assetsBuildDirectory)
: joinPathFragments(`{workspaceRoot}`, projectRoot, assetsBuildDirectory); : joinPathFragments(`{workspaceRoot}`, projectRoot, assetsBuildDirectory);
const outputs =
remixCompiler === RemixCompiler.IsVte
? [
projectRoot === '.'
? joinPathFragments(`{workspaceRoot}`, buildDirectory)
: joinPathFragments(`{workspaceRoot}`, projectRoot, buildDirectory),
]
: [serverBuildOutputPath, assetsBuildOutputPath];
return { return {
cache: true, cache: true,
dependsOn: [`^${buildTargetName}`], dependsOn: [`^${buildTargetName}`],
@ -166,18 +240,25 @@ function buildTarget(
: ['default', '^default']), : ['default', '^default']),
{ externalDependencies: ['@remix-run/dev'] }, { externalDependencies: ['@remix-run/dev'] },
], ],
outputs: [serverBuildOutputPath, assetsBuildOutputPath], outputs,
command: 'remix build', command:
remixCompiler === RemixCompiler.IsVte
? 'remix vite:build'
: 'remix build',
options: { cwd: projectRoot }, options: { cwd: projectRoot },
}; };
} }
function devTarget( function devTarget(
serverBuildPath: string, serverBuildPath: string,
projectRoot: string projectRoot: string,
remixCompiler: RemixCompiler
): TargetConfiguration { ): TargetConfiguration {
return { return {
command: 'remix dev --manual', command:
remixCompiler === RemixCompiler.IsVte
? 'remix vite:dev'
: 'remix dev --manual',
options: { cwd: projectRoot }, options: { cwd: projectRoot },
}; };
} }
@ -185,11 +266,19 @@ function devTarget(
function startTarget( function startTarget(
projectRoot: string, projectRoot: string,
serverBuildPath: string, serverBuildPath: string,
buildTargetName: string buildTargetName: string,
remixCompiler: RemixCompiler
): TargetConfiguration { ): TargetConfiguration {
let serverPath = serverBuildPath;
if (remixCompiler === RemixCompiler.IsVte) {
if (serverBuildPath === 'build') {
serverPath = `${serverBuildPath}/server/index.js`;
}
}
return { return {
dependsOn: [buildTargetName], dependsOn: [buildTargetName],
command: `remix-serve ${serverBuildPath}`, command: `remix-serve ${serverPath}`,
options: { options: {
cwd: projectRoot, cwd: projectRoot,
}, },
@ -222,19 +311,46 @@ function typecheckTarget(
async function getBuildPaths( async function getBuildPaths(
configFilePath: string, configFilePath: string,
workspaceRoot: string projectRoot: string,
workspaceRoot: string,
remixCompiler: RemixCompiler
): Promise<{ ): Promise<{
buildDirectory: string; buildDirectory: string;
assetsBuildDirectory: string; assetsBuildDirectory?: string;
serverBuildPath: string; serverBuildPath?: string;
}> { }> {
const configPath = join(workspaceRoot, configFilePath); const configPath = join(workspaceRoot, configFilePath);
if (remixCompiler === RemixCompiler.IsClassic) {
let appConfig = await loadConfigFile<AppConfig>(configPath); let appConfig = await loadConfigFile<AppConfig>(configPath);
return { return {
buildDirectory: 'build', buildDirectory: 'build',
serverBuildPath: appConfig.serverBuildPath ?? 'build/index.js', serverBuildPath: appConfig.serverBuildPath ?? 'build/index.js',
assetsBuildDirectory: appConfig.assetsBuildDirectory ?? 'public/build', 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 loadViteDynamicImport();
const viteBuildConfig = await resolveConfig(
{
configFile: configPath,
mode: 'development',
},
'build'
);
return {
buildDirectory: viteBuildConfig.build?.outDir ?? 'build',
serverBuildPath: viteBuildConfig.build?.outDir ?? 'build',
assetsBuildDirectory: 'build/client',
};
}
} }
function normalizeOptions(options: RemixPluginOptions) { function normalizeOptions(options: RemixPluginOptions) {
@ -249,3 +365,28 @@ function normalizeOptions(options: RemixPluginOptions) {
return options; return options;
} }
function determineIsRemixVite(configFilePath: string, workspaceRoot: string) {
if (configFilePath.includes('remix.config')) {
return RemixCompiler.IsClassic;
}
const fileContents = readFileSync(
join(workspaceRoot, configFilePath),
'utf8'
);
if (
fileContents.includes('@remix-run/dev') &&
(fileContents.includes('vitePlugin()') || fileContents.includes('remix()'))
) {
return RemixCompiler.IsVte;
} else {
return RemixCompiler.IsNotRemix;
}
}
enum RemixCompiler {
IsClassic = 1,
IsVte = 2,
IsNotRemix = 3,
}

View File

@ -0,0 +1,3 @@
export function loadViteDynamicImport() {
return Function('return import("vite")')() as Promise<typeof import('vite')>;
}

View File

@ -83,6 +83,7 @@ describe('@nx/vite:init', () => {
"serveStaticTargetName": "serve-static", "serveStaticTargetName": "serve-static",
"serveTargetName": "serve", "serveTargetName": "serve",
"testTargetName": "test", "testTargetName": "test",
"typecheckTargetName": "typecheck",
}, },
"plugin": "@nx/vite/plugin", "plugin": "@nx/vite/plugin",
}, },

View File

@ -76,6 +76,7 @@ export async function initGeneratorInternal(
'vite:serve-static', 'vite:serve-static',
'vite-serve-static', 'vite-serve-static',
], ],
typecheckTargetName: ['typecheck', 'vite:typecheck', 'vite-typecheck'],
}, },
schema.updatePackageScripts schema.updatePackageScripts
); );