Skip to content

Commit

Permalink
feat(devkit): add loadConfigFile function for plugins to use (#21511)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo authored Feb 2, 2024
1 parent ea5befb commit 05b0848
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 108 deletions.
6 changes: 0 additions & 6 deletions packages/cypress/src/plugins/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { join } from 'path';
import { nxE2EPreset } from '../../plugins/cypress-preset';

// Jest can't handle the dynamic import, and mocking it doesn't work either.
// we overwrite the dynamic import function to use the regular syntax, which
// jest does handle.
import * as lcf from '../utils/load-config-file';
(lcf as any).dynamicImport = (m) => require(m.split('?')[0]);

describe('@nx/cypress/plugin', () => {
let createNodesFunction = createNodes[1];
let context: CreateNodesContext;
Expand Down
9 changes: 4 additions & 5 deletions packages/cypress/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import {
TargetConfiguration,
writeJsonFile,
} from '@nx/devkit';
import { dirname, extname, join, relative } from 'path';
import { registerTsProject } from '@nx/js/src/internal';
import { dirname, join, relative } from 'path';

import { getLockFileName, getRootTsConfigPath } from '@nx/js';
import { getLockFileName } from '@nx/js';

import { CypressExecutorOptions } from '../executors/cypress/cypress.impl';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
Expand All @@ -20,7 +19,7 @@ import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
import { NX_PLUGIN_OPTIONS } from '../utils/symbols';
import { getCypressConfig } from '../utils/load-config-file';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';

export interface CypressPluginOptions {
ciTargetName?: string;
Expand Down Expand Up @@ -152,7 +151,7 @@ async function buildCypressTargets(
options: CypressPluginOptions,
context: CreateNodesContext
) {
const cypressConfig = await getCypressConfig(
const cypressConfig = await loadConfigFile(
join(context.workspaceRoot, configFilePath)
);

Expand Down
29 changes: 0 additions & 29 deletions packages/cypress/src/utils/load-config-file.ts

This file was deleted.

78 changes: 78 additions & 0 deletions packages/devkit/src/utils/config-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { extname, join } from 'path';
import { existsSync } from 'fs';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { workspaceRoot } from 'nx/src/devkit-exports';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { registerTsProject } from 'nx/src/plugins/js/utils/register';

export let dynamicImport = new Function(
'modulePath',
'return import(modulePath);'
);

export async function loadConfigFile<T extends object = any>(
configFilePath: string
): Promise<T> {
{
let module: any;

if (extname(configFilePath) === '.ts') {
const tsConfigPath = getRootTsConfigPath();
if (tsConfigPath) {
const unregisterTsProject = registerTsProject(tsConfigPath);
try {
module = await load(configFilePath);
} finally {
unregisterTsProject();
}
} else {
module = await load(configFilePath);
}
} else {
module = await load(configFilePath);
}
return module.default ?? module;
}
}

export function getRootTsConfigPath(): string | null {
const tsConfigFileName = getRootTsConfigFileName();
return tsConfigFileName ? join(workspaceRoot, tsConfigFileName) : null;
}

export function getRootTsConfigFileName(): string | null {
for (const tsConfigName of ['tsconfig.base.json', 'tsconfig.json']) {
const pathExists = existsSync(join(workspaceRoot, tsConfigName));
if (pathExists) {
return tsConfigName;
}
}

return null;
}

/**
* Load the module after ensuring that the require cache is cleared.
*/
async function load(path: string): Promise<any> {
// Clear cache if the path is in the cache
if (require.cache[path]) {
for (const k of Object.keys(require.cache)) {
delete require.cache[k];
}
}

try {
// Try using `require` first, which works for CJS modules.
// Modules are CJS unless it is named `.mjs` or `package.json` sets type to "module".
return require(path);
} catch (e: any) {
if (e.code === 'ERR_REQUIRE_ESM') {
// If `require` fails to load ESM, try dynamic `import()`.
return await dynamicImport(`${path}?t=${Date.now()}`);
}

// Re-throw all other errors
throw e;
}
}
6 changes: 0 additions & 6 deletions packages/playwright/src/plugins/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ import { TempFs } from '@nx/devkit/internal-testing-utils';
import { createNodes } from './plugin';
import { PlaywrightTestConfig } from '@playwright/test';

// Jest can't handle the dynamic import, and mocking it doesn't work either.
// we overwrite the dynamic import function to use the regular syntax, which
// jest does handle.
import * as lcf from '../utils/load-config-file';
(lcf as any).dynamicImport = (m) => require(m.split('?')[0]);

describe('@nx/playwright/plugin', () => {
let createNodesFunction = createNodes[1];
let context: CreateNodesContext;
Expand Down
9 changes: 7 additions & 2 deletions packages/playwright/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash
import type { PlaywrightTestConfig } from '@playwright/test';
import { getFilesInDirectoryUsingContext } from 'nx/src/utils/workspace-context';
import { minimatch } from 'minimatch';
import { loadPlaywrightConfig } from '../utils/load-config-file';
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
import { getLockFileName } from '@nx/js';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';

export interface PlaywrightPluginOptions {
targetName?: string;
Expand Down Expand Up @@ -106,7 +106,12 @@ async function buildPlaywrightTargets(
options: NormalizedOptions,
context: CreateNodesContext
) {
const playwrightConfig: PlaywrightTestConfig = await loadPlaywrightConfig(
// Playwright forbids importing the `@playwright/test` module twice. This would affect running the tests,
// but we're just reading the config so let's delete the variable they are using to detect this.
// See: https://github.com/microsoft/playwright/pull/11218/files
delete (process as any)['__pw_initiator__'];

const playwrightConfig = await loadConfigFile<PlaywrightTestConfig>(
join(context.workspaceRoot, configFilePath)
);

Expand Down
38 changes: 0 additions & 38 deletions packages/playwright/src/utils/load-config-file.ts

This file was deleted.

27 changes: 5 additions & 22 deletions packages/remix/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import {
type CreateDependencies,
type CreateNodes,
type CreateNodesContext,
type TargetConfiguration,
detectPackageManager,
readJsonFile,
type TargetConfiguration,
writeJsonFile,
} from '@nx/devkit';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { getLockFileName, getRootTsConfigPath } from '@nx/js';
import { registerTsProject } from '@nx/js/src/internal';
import { getLockFileName } from '@nx/js';
import { type AppConfig } from '@remix-run/dev';
import { join, dirname } from 'path';
import { dirname, join } from 'path';
import { existsSync, readdirSync } from 'fs';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';

const cachePath = join(projectGraphCacheDirectory, 'remix.hash');
const targetsCache = existsSync(cachePath) ? readTargetsCache() : {};
Expand Down Expand Up @@ -181,24 +181,7 @@ async function getServerBuildPath(
workspaceRoot: string
): Promise<string> {
const configPath = join(workspaceRoot, configFilePath);
let appConfig: AppConfig = {};
try {
let appConfigModule: any;
try {
appConfigModule = await Function(
`return import("${configPath}?t=${Date.now()}")`
)();
} catch {
appConfigModule = require(configPath);
}

appConfig = appConfigModule?.default || appConfigModule;
} catch (error) {
throw new Error(
`Error loading Remix config at ${configFilePath}\n${String(error)}`
);
}

let appConfig = await loadConfigFile<AppConfig>(configPath);
return appConfig.serverBuildPath ?? 'build/index.js';
}

Expand Down

0 comments on commit 05b0848

Please sign in to comment.