Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nextjs): refactor how webpack config is loaded with nextjs #15650

Merged
merged 4 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion nx-dev/nx-dev/next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// nx-ignore-next-line
const withNx = require('@nrwl/next/plugins/with-nx');
const { withNx } = require('@nrwl/next/plugins/with-nx');
const { copySync } = require('fs-extra');
const path = require('path');
const redirectRules = require('./redirect-rules.config');
Expand Down
6 changes: 3 additions & 3 deletions packages/next/plugins/with-nx.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { NextConfigComplete } from 'next/dist/server/config-shared';
import { withNx } from './with-nx';
import { getNextConfig } from './with-nx';

describe('withNx', () => {
describe('svgr', () => {
it('should be used by default', () => {
const config = withNx({});
const config = getNextConfig();

const result = config.webpack(
{
Expand Down Expand Up @@ -32,7 +32,7 @@ describe('withNx', () => {
});

it('should not be used when disabled', () => {
const config = withNx({
const config = getNextConfig({
nx: {
svgr: false,
},
Expand Down
139 changes: 139 additions & 0 deletions packages/next/plugins/with-nx.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import {
createProjectGraphAsync,
joinPathFragments,
offsetFromRoot,
parseTargetString,
ProjectGraph,
ProjectGraphProjectNode,
Target,
workspaceRoot,
} from '@nrwl/devkit';
import {
calculateProjectDependencies,
DependentBuildableProjectNode,
} from '@nrwl/js/src/utils/buildable-libs-utils';
import type { NextConfig } from 'next';

import path = require('path');
import { createWebpackConfig } from '../src/utils/config';
import { NextBuildBuilderOptions } from '@nrwl/next';
export interface WithNxOptions extends NextConfig {
nx?: {
svgr?: boolean;
Expand Down Expand Up @@ -34,7 +51,128 @@ function getWithNxContext(): WithNxContext {
};
}

function getTargetConfig(graph: ProjectGraph, target: Target) {
const projectNode = graph.nodes[target.project];
return projectNode.data.targets[target.target];
}

function getOptions(graph: ProjectGraph, target: Target) {
const targetConfig = getTargetConfig(graph, target);
const options = targetConfig.options;
if (target.configuration) {
Object.assign(options, targetConfig.configurations[target.configuration]);
}

return options;
}

function getNxContext(
graph: ProjectGraph,
target: Target
): {
node: ProjectGraphProjectNode;
options: NextBuildBuilderOptions;
projectName: string;
targetName: string;
configurationName?: string;
} {
const targetConfig = getTargetConfig(graph, target);

if ('@nrwl/next:build' === targetConfig.executor) {
return {
node: graph.nodes[target.project],
options: getOptions(graph, target),
projectName: target.project,
targetName: target.target,
configurationName: target.configuration,
};
}

const targetOptions = getOptions(graph, target);

// If we are running serve or export pull the options from the dependent target first (ex. build)
if (targetOptions.devServerTarget) {
const devServerTarget = parseTargetString(
targetOptions.devServerTarget,
graph
);

return getNxContext(graph, devServerTarget);
} else if (
['@nrwl/next:server', '@nrwl/next:export'].includes(targetConfig.executor)
) {
const buildTarget = parseTargetString(targetOptions.buildTarget, graph);
return getNxContext(graph, buildTarget);
} else {
throw new Error(
'Could not determine the config for this Next application.'
);
}
}

export function withNx(
_nextConfig = {} as WithNxOptions,
context: WithNxContext = getWithNxContext()
): () => Promise<NextConfig> {
return async () => {
let dependencies: DependentBuildableProjectNode[] = [];

const graph = await createProjectGraphAsync();

const originalTarget = {
project: process.env.NX_TASK_TARGET_PROJECT,
target: process.env.NX_TASK_TARGET_TARGET,
configuration: process.env.NX_TASK_TARGET_CONFIGURATION,
};

const {
node: projectNode,
options,
projectName: project,
targetName,
configurationName,
} = getNxContext(graph, originalTarget);
const projectDirectory = projectNode.data.root;

if (!options.buildLibsFromSource && targetName) {
const result = calculateProjectDependencies(
graph,
workspaceRoot,
project,
targetName,
configurationName
);
dependencies = result.dependencies;
}

// Get next config
const nextConfig = getNextConfig(_nextConfig, context);

const outputDir = `${offsetFromRoot(projectDirectory)}${
options.outputPath
}`;
nextConfig.distDir =
nextConfig.distDir && nextConfig.distDir !== '.next'
? joinPathFragments(outputDir, nextConfig.distDir)
: joinPathFragments(outputDir, '.next');

const userWebpackConfig = nextConfig.webpack;

nextConfig.webpack = (a, b) =>
createWebpackConfig(
workspaceRoot,
options.root,
options.fileReplacements,
options.assets,
dependencies,
path.join(workspaceRoot, context.libsDir)
)(userWebpackConfig ? userWebpackConfig(a, b) : a, b);

return nextConfig;
};
}

export function getNextConfig(
nextConfig = {} as WithNxOptions,
context: WithNxContext = getWithNxContext()
): NextConfig {
Expand Down Expand Up @@ -223,3 +361,4 @@ function addNxEnvVariables(config: any) {
module.exports = withNx;
// Support for newer generated code: `const { withNx } = require(...);`
module.exports.withNx = withNx;
module.exports.getNextConfig = getNextConfig;
31 changes: 2 additions & 29 deletions packages/next/src/executors/build/build.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dotenv/config';
import {
ExecutorContext,
readJsonFile,
workspaceLayout,
workspaceRoot,
writeJsonFile,
} from '@nrwl/devkit';
Expand All @@ -12,18 +11,13 @@ import { join, resolve } from 'path';
import { copySync, existsSync, mkdir, writeFileSync } from 'fs-extra';
import { gte } from 'semver';
import { directoryExists } from '@nrwl/workspace/src/utilities/fileutils';
import {
calculateProjectDependencies,
DependentBuildableProjectNode,
} from '@nrwl/js/src/utils/buildable-libs-utils';
import { checkAndCleanWithSemver } from '@nrwl/devkit/src/utils/semver';

import { prepareConfig } from '../../utils/config';
import { updatePackageJson } from './lib/update-package-json';
import { createNextConfigFile } from './lib/create-next-config-file';
import { checkPublicDirectory } from './lib/check-project';
import { NextBuildBuilderOptions } from '../../utils/types';
import { PHASE_PRODUCTION_BUILD } from '../../utils/constants';

import { getLockFileName } from 'nx/src/lock-file/lock-file';

export default async function buildExecutor(
Expand All @@ -33,23 +27,10 @@ export default async function buildExecutor(
// Cast to any to overwrite NODE_ENV
(process.env as any).NODE_ENV ||= 'production';

let dependencies: DependentBuildableProjectNode[] = [];
const root = resolve(context.root, options.root);
const libsDir = join(context.root, workspaceLayout().libsDir);

checkPublicDirectory(root);

if (!options.buildLibsFromSource && context.targetName) {
const result = calculateProjectDependencies(
context.projectGraph,
context.root,
context.projectName,
context.targetName,
context.configurationName
);
dependencies = result.dependencies;
}

// Set `__NEXT_REACT_ROOT` based on installed ReactDOM version
const packageJsonPath = join(root, 'package.json');
const packageJson = existsSync(packageJsonPath)
Expand All @@ -66,15 +47,7 @@ export default async function buildExecutor(
(process.env as any).__NEXT_REACT_ROOT ||= 'true';
}

const config = await prepareConfig(
PHASE_PRODUCTION_BUILD,
options,
context,
dependencies,
libsDir
);

await build(root, config as any);
await build(root);

if (!directoryExists(options.outputPath)) {
mkdir(options.outputPath);
Expand Down
40 changes: 23 additions & 17 deletions packages/next/src/executors/export/export.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
ExecutorContext,
parseTargetString,
readTargetOptions,
runExecutor,
workspaceLayout,
} from '@nrwl/devkit';
import exportApp from 'next/dist/export';
Expand All @@ -13,13 +12,18 @@ import {
DependentBuildableProjectNode,
} from '@nrwl/js/src/utils/buildable-libs-utils';

import { prepareConfig } from '../../utils/config';
import {
NextBuildBuilderOptions,
NextExportBuilderOptions,
} from '../../utils/types';
import { PHASE_EXPORT } from '../../utils/constants';
import nextTrace = require('next/dist/trace');
import { platform } from 'os';
import { execFileSync } from 'child_process';
import * as chalk from 'chalk';

// platform specific command name
const pmCmd = platform() === 'win32' ? `npx.cmd` : 'npx';

export default async function exportExecutor(
options: NextExportBuilderOptions,
Expand All @@ -38,27 +42,25 @@ export default async function exportExecutor(
}

const libsDir = join(context.root, workspaceLayout().libsDir);
const buildTarget = parseTargetString(options.buildTarget);
const build = await runExecutor(buildTarget, {}, context);
const buildTarget = parseTargetString(
options.buildTarget,
context.projectGraph
);

for await (const result of build) {
if (!result.success) {
return result;
}
try {
const args = getBuildTargetCommand(options);
execFileSync(pmCmd, args, {
stdio: [0, 1, 2],
});
} catch {
throw new Error(`Build target failed: ${chalk.bold(options.buildTarget)}`);
}

const buildOptions = readTargetOptions<NextBuildBuilderOptions>(
buildTarget,
context
);
const root = resolve(context.root, buildOptions.root);
const config = await prepareConfig(
PHASE_EXPORT,
buildOptions,
context,
dependencies,
libsDir
);

// Taken from:
// https://github.com/vercel/next.js/blob/ead56eaab68409e96c19f7d9139747bac1197aa9/packages/next/cli/next-export.ts#L13
Expand All @@ -72,9 +74,13 @@ export default async function exportExecutor(
threads: options.threads,
outdir: `${buildOptions.outputPath}/exported`,
} as any,
nextExportCliSpan,
config
nextExportCliSpan
);

return { success: true };
}

function getBuildTargetCommand(options: NextExportBuilderOptions) {
const cmd = ['nx', 'run', options.buildTarget];
return cmd;
}
Loading