fix(nextjs): enhance support for custom server with SWC configuration (#29895)
This pull request contains a few changes to enhance our swc support for
Next.js with a custom server.
### Issues
Currently, we have a few issues with our configuration when using
executors for Next.js with a custom server:
1. The custom server does not have an independent build configuration.
2. The custom server does not have an independent output directory.
3. Serving via `@nx/next-server` or via `@nx/js:node` with
configurations `production` and `development` does not always work.
(These are contained inside `project.json`).
### Changes
All the above issues have been addressed
1. We now have an independent swc build configuration
called`.server.swrc` (_follows the same format as `.eslintrc`,
`.babelrc`_) etc...
2. Now each custom server output will be named `{app}-server` such that
if you have multiple custom servers for multiple apps the names will not
clash.
3. Serving now works out of the box but can be adjusted to suit your
needs via updating the custom server entry file `main.ts`
This commit is contained in:
parent
8bd0bcdd97
commit
29e5ce2963
@ -279,4 +279,44 @@ describe('@nx/next (legacy)', () => {
|
|||||||
await killPort(prodServePort);
|
await killPort(prodServePort);
|
||||||
await killPort(selfContainedPort);
|
await killPort(selfContainedPort);
|
||||||
}, 600_000);
|
}, 600_000);
|
||||||
|
|
||||||
|
it('should support --custom-server flag (swc)', async () => {
|
||||||
|
const appName = uniq('app');
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/next:app ${appName} --no-interactive --custom-server --linter=eslint --unitTestRunner=jest`,
|
||||||
|
{ env: { NX_ADD_PLUGINS: 'false' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for custom server files added to source
|
||||||
|
checkFilesExist(`${appName}/server/main.ts`);
|
||||||
|
checkFilesExist(`${appName}/.server.swcrc`);
|
||||||
|
|
||||||
|
const result = runCLI(`build ${appName}`);
|
||||||
|
|
||||||
|
checkFilesExist(`dist/${appName}-server/server/main.js`);
|
||||||
|
|
||||||
|
expect(result).toContain(
|
||||||
|
`Successfully ran target build for project ${appName}`
|
||||||
|
);
|
||||||
|
}, 300_000);
|
||||||
|
|
||||||
|
it('should support --custom-server flag (tsc)', async () => {
|
||||||
|
const appName = uniq('app');
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/next:app ${appName} --swc=false --no-interactive --custom-server --linter=eslint --unitTestRunner=jest`,
|
||||||
|
{ env: { NX_ADD_PLUGINS: 'false' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
checkFilesExist(`${appName}/server/main.ts`);
|
||||||
|
|
||||||
|
const result = runCLI(`build ${appName}`);
|
||||||
|
|
||||||
|
checkFilesExist(`dist/${appName}-server/server/main.js`);
|
||||||
|
|
||||||
|
expect(result).toContain(
|
||||||
|
`Successfully ran target build for project ${appName}`
|
||||||
|
);
|
||||||
|
}, 300_000);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -158,11 +158,13 @@ describe('Next.js Applications', () => {
|
|||||||
`generate @nx/next:app ${appName} --no-interactive --custom-server --linter=eslint --unitTestRunner=jest`
|
`generate @nx/next:app ${appName} --no-interactive --custom-server --linter=eslint --unitTestRunner=jest`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check for custom server files added to source
|
||||||
checkFilesExist(`${appName}/server/main.ts`);
|
checkFilesExist(`${appName}/server/main.ts`);
|
||||||
|
checkFilesExist(`${appName}/.server.swcrc`);
|
||||||
|
|
||||||
const result = runCLI(`build ${appName}`);
|
const result = runCLI(`build ${appName}`);
|
||||||
|
|
||||||
checkFilesExist(`dist/${appName}/server/main.js`);
|
checkFilesExist(`dist/${appName}-server/server/main.js`);
|
||||||
|
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
`Successfully ran target build for project ${appName}`
|
`Successfully ran target build for project ${appName}`
|
||||||
@ -180,7 +182,7 @@ describe('Next.js Applications', () => {
|
|||||||
|
|
||||||
const result = runCLI(`build ${appName}`);
|
const result = runCLI(`build ${appName}`);
|
||||||
|
|
||||||
checkFilesExist(`dist/${appName}/server/main.js`);
|
checkFilesExist(`dist/${appName}-server/server/main.js`);
|
||||||
|
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
`Successfully ran target build for project ${appName}`
|
`Successfully ran target build for project ${appName}`
|
||||||
|
|||||||
@ -145,7 +145,7 @@ export async function* nodeExecutor(
|
|||||||
// Wait for build to finish.
|
// Wait for build to finish.
|
||||||
const result = await buildResult;
|
const result = await buildResult;
|
||||||
|
|
||||||
if (!result.success) {
|
if (result && !result.success) {
|
||||||
// If in watch-mode, don't throw or else the process exits.
|
// If in watch-mode, don't throw or else the process exits.
|
||||||
if (options.watch) {
|
if (options.watch) {
|
||||||
if (!task.killed) {
|
if (!task.killed) {
|
||||||
|
|||||||
@ -54,11 +54,20 @@ export function addSwcConfig(
|
|||||||
tree: Tree,
|
tree: Tree,
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
type: 'commonjs' | 'es6' = 'commonjs',
|
type: 'commonjs' | 'es6' = 'commonjs',
|
||||||
supportTsx: boolean = false
|
supportTsx: boolean = false,
|
||||||
|
swcName: string = '.swcrc',
|
||||||
|
additionalExcludes: string[] = []
|
||||||
) {
|
) {
|
||||||
const swcrcPath = join(projectDir, '.swcrc');
|
const swcrcPath = join(projectDir, swcName);
|
||||||
if (tree.exists(swcrcPath)) return;
|
if (tree.exists(swcrcPath)) return;
|
||||||
tree.write(swcrcPath, swcOptionsString(type, defaultExclude, supportTsx));
|
tree.write(
|
||||||
|
swcrcPath,
|
||||||
|
swcOptionsString(
|
||||||
|
type,
|
||||||
|
[...defaultExclude, ...additionalExcludes],
|
||||||
|
supportTsx
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addSwcTestConfig(
|
export function addSwcTestConfig(
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import {
|
|||||||
updateTsconfigFiles,
|
updateTsconfigFiles,
|
||||||
} from '@nx/js/src/utils/typescript/ts-solution-setup';
|
} from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||||
import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields';
|
import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields';
|
||||||
|
import { configureForSwc } from '../../utils/add-swc-to-custom-server';
|
||||||
|
|
||||||
export async function applicationGenerator(host: Tree, schema: Schema) {
|
export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||||
return await applicationGeneratorInternal(host, {
|
return await applicationGeneratorInternal(host, {
|
||||||
@ -93,6 +94,11 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
updateCypressTsConfig(host, options);
|
updateCypressTsConfig(host, options);
|
||||||
setDefaults(host, options);
|
setDefaults(host, options);
|
||||||
|
|
||||||
|
if (options.swc) {
|
||||||
|
const swcTask = configureForSwc(host, options.appProjectRoot);
|
||||||
|
tasks.push(swcTask);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.customServer) {
|
if (options.customServer) {
|
||||||
await customServerGenerator(host, {
|
await customServerGenerator(host, {
|
||||||
project: options.projectName,
|
project: options.projectName,
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export async function customServerGenerator(
|
|||||||
options: CustomServerSchema
|
options: CustomServerSchema
|
||||||
) {
|
) {
|
||||||
const project = readProjectConfiguration(host, options.project);
|
const project = readProjectConfiguration(host, options.project);
|
||||||
|
const swcServerName = '.server.swcrc';
|
||||||
|
|
||||||
const nxJson = readNxJson(host);
|
const nxJson = readNxJson(host);
|
||||||
const hasPlugin = nxJson.plugins?.some((p) =>
|
const hasPlugin = nxJson.plugins?.some((p) =>
|
||||||
@ -26,11 +27,7 @@ export async function customServerGenerator(
|
|||||||
: p.plugin === '@nx/next/plugin'
|
: p.plugin === '@nx/next/plugin'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (project.targets?.build?.executor !== '@nx/next:build' && !hasPlugin) {
|
||||||
project.targets?.build?.executor !== '@nx/next:build' &&
|
|
||||||
project.targets?.build?.executor !== '@nrwl/next:build' &&
|
|
||||||
!hasPlugin
|
|
||||||
) {
|
|
||||||
logger.error(
|
logger.error(
|
||||||
`Project ${options.project} is not a Next.js project. Did you generate it with "nx g @nx/next:app"?`
|
`Project ${options.project} is not a Next.js project. Did you generate it with "nx g @nx/next:app"?`
|
||||||
);
|
);
|
||||||
@ -38,9 +35,7 @@ export async function customServerGenerator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// In Nx 18 next artifacts are inside the project root .next/ & dist/ (for custom server)
|
// In Nx 18 next artifacts are inside the project root .next/ & dist/ (for custom server)
|
||||||
const outputPath = hasPlugin
|
const outputPath = `dist/${project.root}-server`;
|
||||||
? `dist/${project.root}`
|
|
||||||
: project.targets?.build?.options?.outputPath;
|
|
||||||
const root = project.root;
|
const root = project.root;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -68,9 +63,9 @@ export async function customServerGenerator(
|
|||||||
|
|
||||||
// In Nx 18 next artifacts are inside the project root .next/ & dist/ (for custom server)
|
// In Nx 18 next artifacts are inside the project root .next/ & dist/ (for custom server)
|
||||||
// So we need ensure the mapping is correct from dist to the project root
|
// So we need ensure the mapping is correct from dist to the project root
|
||||||
const projectPathFromDist = `../../${offsetFromRoot(project.root)}${
|
const projectPathFromDist = hasPlugin
|
||||||
project.root
|
? `../../${offsetFromRoot(project.root)}${project.root}`
|
||||||
}`;
|
: `${offsetFromRoot(`dist/${project.root}`)}${project.root}`;
|
||||||
|
|
||||||
const offset = offsetFromRoot(project.root);
|
const offset = offsetFromRoot(project.root);
|
||||||
const isTsSolution = isUsingTsSolutionSetup(host);
|
const isTsSolution = isUsingTsSolutionSetup(host);
|
||||||
@ -107,6 +102,9 @@ export async function customServerGenerator(
|
|||||||
tsConfig: `${root}/tsconfig.server.json`,
|
tsConfig: `${root}/tsconfig.server.json`,
|
||||||
clean: false,
|
clean: false,
|
||||||
assets: [],
|
assets: [],
|
||||||
|
...(options.compiler === 'tsc'
|
||||||
|
? {}
|
||||||
|
: { swcrc: `${root}/${swcServerName}` }),
|
||||||
},
|
},
|
||||||
configurations: {
|
configurations: {
|
||||||
development: {},
|
development: {},
|
||||||
@ -150,6 +148,11 @@ export async function customServerGenerator(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (options.compiler === 'swc') {
|
if (options.compiler === 'swc') {
|
||||||
return configureForSwc(host, project.root);
|
// Update app swc to exlude server files
|
||||||
|
updateJson(host, join(project.root, '.swcrc'), (json) => {
|
||||||
|
json.exclude = [...(json.exclude ?? []), 'server/**'];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
return configureForSwc(host, project.root, swcServerName, ['src/**/*']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import next from 'next';
|
|||||||
// - The fallback `__dirname` is for production builds.
|
// - The fallback `__dirname` is for production builds.
|
||||||
// - Feel free to change this to suit your needs.
|
// - Feel free to change this to suit your needs.
|
||||||
|
|
||||||
const dir = process.env.NX_NEXT_DIR || <%- hasPlugin ? `path.join(__dirname, '${projectPathFromDist}')` : `path.join(__dirname, '..')`; %>
|
|
||||||
const dev = process.env.NODE_ENV === 'development';
|
const dev = process.env.NODE_ENV === 'development';
|
||||||
|
const dir = process.env.NX_NEXT_DIR || <%- hasPlugin ? `path.join(__dirname, '${projectPathFromDist}')` : `path.join(__dirname, dev ? '..' : '', '${projectPathFromDist}')`; %>
|
||||||
|
|
||||||
// HTTP Server options:
|
// HTTP Server options:
|
||||||
// - Feel free to change this to suit your needs.
|
// - Feel free to change this to suit your needs.
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import {
|
|||||||
installPackagesTask,
|
installPackagesTask,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
readJson,
|
readJson,
|
||||||
updateJson,
|
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import {
|
import {
|
||||||
swcCliVersion,
|
swcCliVersion,
|
||||||
@ -14,8 +13,13 @@ import {
|
|||||||
} from '@nx/js/src/utils/versions';
|
} from '@nx/js/src/utils/versions';
|
||||||
import { addSwcConfig } from '@nx/js/src/utils/swc/add-swc-config';
|
import { addSwcConfig } from '@nx/js/src/utils/swc/add-swc-config';
|
||||||
|
|
||||||
export function configureForSwc(tree: Tree, projectRoot: string) {
|
export function configureForSwc(
|
||||||
const swcConfigPath = joinPathFragments(projectRoot, '.swcrc');
|
tree: Tree,
|
||||||
|
projectRoot: string,
|
||||||
|
swcConfigName = '.swcrc',
|
||||||
|
additonalExludes: string[] = []
|
||||||
|
) {
|
||||||
|
const swcConfigPath = joinPathFragments(projectRoot, swcConfigName);
|
||||||
const rootPackageJson = readJson(tree, 'package.json');
|
const rootPackageJson = readJson(tree, 'package.json');
|
||||||
|
|
||||||
const hasSwcDepedency =
|
const hasSwcDepedency =
|
||||||
@ -27,22 +31,17 @@ export function configureForSwc(tree: Tree, projectRoot: string) {
|
|||||||
rootPackageJson.devDependencies?.['@swc/cli'];
|
rootPackageJson.devDependencies?.['@swc/cli'];
|
||||||
|
|
||||||
if (!tree.exists(swcConfigPath)) {
|
if (!tree.exists(swcConfigPath)) {
|
||||||
addSwcConfig(tree, projectRoot);
|
// We need to create a swc config file specific for custom server
|
||||||
}
|
addSwcConfig(tree, projectRoot, 'commonjs', false, swcConfigName, [
|
||||||
|
...additonalExludes,
|
||||||
if (tree.exists(swcConfigPath)) {
|
'.*.d.ts$',
|
||||||
updateJson(tree, swcConfigPath, (json) => {
|
]);
|
||||||
return {
|
|
||||||
...json,
|
|
||||||
exclude: [...json.exclude, '.*.d.ts$'],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasSwcDepedency || !hasSwcCliDependency) {
|
if (!hasSwcDepedency || !hasSwcCliDependency) {
|
||||||
addSwcDependencies(tree);
|
addSwcDependencies(tree);
|
||||||
return () => installPackagesTask(tree);
|
|
||||||
}
|
}
|
||||||
|
return () => installPackagesTask(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSwcDependencies(tree: Tree) {
|
function addSwcDependencies(tree: Tree) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user