From 6a6dd9cd2a719f6a53b6c500442c4b77b23aada8 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Sat, 16 Mar 2024 03:25:46 -0700 Subject: [PATCH 1/2] Fixes typo in Route handler static generation error handling (#63345) Error had a typo in it. it should be `force-dynamic` not `dynamic-error`. Closes NEXT-2827 --- .../next/src/server/future/route-modules/app-route/module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/server/future/route-modules/app-route/module.ts b/packages/next/src/server/future/route-modules/app-route/module.ts index e9135eb942a9d..98664424b4013 100644 --- a/packages/next/src/server/future/route-modules/app-route/module.ts +++ b/packages/next/src/server/future/route-modules/app-route/module.ts @@ -314,7 +314,7 @@ export class AppRouteRouteModule extends RouteModule< // We should never be in this case but since it can happen based on the way our build/execution is structured // We defend against it for the time being throw new Error( - 'Invariant: `dynamic-error` during static generation not expected for app routes. This is a bug in Next.js' + 'Invariant: `force-dynamic` during static generation not expected for app routes. This is a bug in Next.js' ) break case 'force-static': From dc2be6483f7aaca86e60bee03abf15d572b872b4 Mon Sep 17 00:00:00 2001 From: Jumpei Ogawa Date: Sun, 17 Mar 2024 01:03:24 +0900 Subject: [PATCH 2/2] Add JSM (ESM) support for next/lib/find-config (#63109) Fixes #34448 Before this PR, next/lib/find-config fails to load \*.config.mjs files and \*.config.js files when `"type": "module"` is set in package.json. It expects CommonJS files although the \*.config.{js|mjs} files are written in JS modules format (i.e. using `import` and `export`). This PR fixes it so that it can load configs written in JS modules format. --------- Co-authored-by: Jiachi Liu --- packages/next/src/lib/find-config.test.ts | 79 +++++++++++++++++++++++ packages/next/src/lib/find-config.ts | 35 ++++++++-- 2 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 packages/next/src/lib/find-config.test.ts diff --git a/packages/next/src/lib/find-config.test.ts b/packages/next/src/lib/find-config.test.ts new file mode 100644 index 0000000000000..dd00d43216266 --- /dev/null +++ b/packages/next/src/lib/find-config.test.ts @@ -0,0 +1,79 @@ +import { mkdtemp, writeFile } from 'node:fs/promises' +import { join } from 'node:path' +import { tmpdir } from 'node:os' +import { findConfig } from './find-config' + +describe('findConfig()', () => { + const exampleConfig = { + basePath: '/docs', + } + const configCode = { + mjs: ` + const config = ${JSON.stringify(exampleConfig)} + + export default config; + `, + cjs: ` + const config = ${JSON.stringify(exampleConfig)} + + module.exports = config; + `, + } + type TestPatterns = { + pkgConfigTypes: ('module' | 'commonjs')[] + exts: ('js' | 'mjs' | 'cjs')[] + } + const testPatterns: TestPatterns = { + pkgConfigTypes: ['module', 'commonjs'], + exts: ['js', 'mjs', 'cjs'], + } + + for (const pkgConfigType of testPatterns.pkgConfigTypes) { + for (const ext of testPatterns.exts) { + it(`should load config properly from *.config.* file (type: "${pkgConfigType}", config: awsome.config.${ext})`, async () => { + // Create fixtures + const tmpDir = await mkdtemp(join(tmpdir(), 'nextjs-test-')) + + await writeFile( + join(tmpDir, 'package.json'), + JSON.stringify({ + name: 'nextjs-test', + type: pkgConfigType, + }) + ) + + let configCodeType = ext + if (configCodeType === 'js') { + configCodeType = pkgConfigType === 'module' ? 'mjs' : 'cjs' + } + await writeFile( + join(tmpDir, `awsome.config.${ext}`), + configCode[configCodeType] + ) + + // Test + const actualConfig = await findConfig(tmpDir, 'awsome') + expect(actualConfig).toStrictEqual(exampleConfig) + }) + } + } + + it(`should load config properly from the config in package.json)`, async () => { + // Create fixtures + const tmpDir = await mkdtemp(join(tmpdir(), 'nextjs-test-')) + + await writeFile( + join(tmpDir, 'package.json'), + JSON.stringify({ + name: 'nextjs-test', + awsome: { + basePath: '/docs', + }, + }) + ) + + // Test + const actualConfig = await findConfig(tmpDir, 'awsome') + expect(actualConfig).toStrictEqual(exampleConfig) + }) +}) diff --git a/packages/next/src/lib/find-config.ts b/packages/next/src/lib/find-config.ts index 4033452bb916d..58b2f6abc3e7a 100644 --- a/packages/next/src/lib/find-config.ts +++ b/packages/next/src/lib/find-config.ts @@ -1,5 +1,5 @@ import findUp from 'next/dist/compiled/find-up' -import fs from 'fs' +import { readFile } from 'fs/promises' import JSON5 from 'next/dist/compiled/json5' type RecursivePartial = { @@ -18,6 +18,7 @@ export function findConfigPath( `${key}.config.json`, `.${key}rc.js`, `${key}.config.js`, + `${key}.config.mjs`, `${key}.config.cjs`, ], { @@ -36,23 +37,45 @@ export async function findConfig( ): Promise | null> { // `package.json` configuration always wins. Let's check that first. const packageJsonPath = await findUp('package.json', { cwd: directory }) + let isESM = false + if (packageJsonPath) { - const packageJson = require(packageJsonPath) - if (packageJson[key] != null && typeof packageJson[key] === 'object') { - return packageJson[key] + try { + const packageJsonStr = await readFile(packageJsonPath, 'utf8') + const packageJson = JSON.parse(packageJsonStr) as { + [key: string]: string + } + + if (typeof packageJson !== 'object') { + throw new Error() // Stop processing and continue + } + + if (packageJson.type === 'module') { + isESM = true + } + + if (packageJson[key] != null && typeof packageJson[key] === 'object') { + return packageJson[key] + } + } catch { + // Ignore error and continue } } const filePath = await findConfigPath(directory, key) if (filePath) { - if (filePath.endsWith('.js') || filePath.endsWith('.cjs')) { + if (filePath.endsWith('.js')) { + return isESM ? (await import(filePath)).default : require(filePath) + } else if (filePath.endsWith('.mjs')) { + return (await import(filePath)).default + } else if (filePath.endsWith('.cjs')) { return require(filePath) } // We load JSON contents with JSON5 to allow users to comment in their // configuration file. This pattern was popularized by TypeScript. - const fileContents = fs.readFileSync(filePath, 'utf8') + const fileContents = await readFile(filePath, 'utf8') return JSON5.parse(fileContents) }