Fix custom cache handler importing on windows (#60312)

### Fixing a bug

### What?
Custom cache handler doesn't work on Windows

### Why?

It broke in a recent fix, when adding ESM support - #59863. The problem
is not new - dynamic imports consider an absolute path in Windows as a
protocol:

`ERR! Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme
in: file, data are supported by the default ESM loader. On Windows,
absolute paths must be valid file:// URLs. Received protocol 'C:'`

### How?

As a solution, it is necessary to explicitly indicate that it is indeed
an absolute path, for example by adding a / at the beginning, but the
most reliable way is to use pathToFileURL.

Since the logic is repeated in 4 places - I created a common function.

Fixes #58509

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
This commit is contained in:
Alexander Savelyev 2024-01-10 00:16:47 +02:00 committed by GitHub
parent 46370d816b
commit 51bda321f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 21 deletions

View file

@ -163,6 +163,7 @@ import { getStartServerInfo, logStartInfo } from '../server/lib/app-info-log'
import type { NextEnabledDirectories } from '../server/base-server'
import { hasCustomExportOutput } from '../export/utils'
import { interopDefault } from '../lib/interop-default'
import { formatDynamicImportPath } from '../lib/format-dynamic-import-path'
interface ExperimentalBypassForInfo {
experimentalBypassFor?: RouteHas[]
@ -1537,10 +1538,8 @@ export default async function build(
if (incrementalCacheHandlerPath) {
CacheHandler = interopDefault(
await import(
path.isAbsolute(incrementalCacheHandlerPath)
? incrementalCacheHandlerPath
: path.join(dir, incrementalCacheHandlerPath)
)
formatDynamicImportPath(dir, incrementalCacheHandlerPath)
).then((mod) => mod.default || mod)
)
}

View file

@ -82,6 +82,7 @@ import { RouteKind } from '../server/future/route-kind'
import { isAppRouteRouteModule } from '../server/future/route-modules/checks'
import { interopDefault } from '../lib/interop-default'
import type { PageExtensions } from './page-extensions-type'
import { formatDynamicImportPath } from '../lib/format-dynamic-import-path'
export type ROUTER_TYPE = 'pages' | 'app'
@ -1314,10 +1315,8 @@ export async function buildAppStaticPaths({
if (incrementalCacheHandlerPath) {
CacheHandler = interopDefault(
await import(
path.isAbsolute(incrementalCacheHandlerPath)
? incrementalCacheHandlerPath
: path.join(dir, incrementalCacheHandlerPath)
)
formatDynamicImportPath(dir, incrementalCacheHandlerPath)
).then((mod) => mod.default || mod)
)
}

View file

@ -5,6 +5,7 @@ import { IncrementalCache } from '../../server/lib/incremental-cache'
import { hasNextSupport } from '../../telemetry/ci-info'
import { nodeFs } from '../../server/lib/node-fs-methods'
import { interopDefault } from '../../lib/interop-default'
import { formatDynamicImportPath } from '../../lib/format-dynamic-import-path'
export async function createIncrementalCache({
incrementalCacheHandlerPath,
@ -29,13 +30,9 @@ export async function createIncrementalCache({
let CacheHandler: any
if (incrementalCacheHandlerPath) {
CacheHandler = interopDefault(
(
await import(
path.isAbsolute(incrementalCacheHandlerPath)
? incrementalCacheHandlerPath
: path.join(dir, incrementalCacheHandlerPath)
)
).default
await import(
formatDynamicImportPath(dir, incrementalCacheHandlerPath)
).then((mod) => mod.default || mod)
)
}

View file

@ -0,0 +1,19 @@
import path from 'path'
import { pathToFileURL } from 'url'
/**
* The path for a dynamic route must be URLs with a valid scheme.
*
* When an absolute Windows path is passed to it, it interprets the beginning of the path as a protocol (`C:`).
* Therefore, it is important to always construct a complete path.
* @param dir File directory
* @param filePath Absolute or relative path
*/
export const formatDynamicImportPath = (dir: string, filePath: string) => {
const absoluteFilePath = path.isAbsolute(filePath)
? filePath
: path.join(dir, filePath)
const formattedFilePath = pathToFileURL(absoluteFilePath).toString()
return formattedFilePath
}

View file

@ -26,7 +26,7 @@ import type { ParsedUrl } from '../shared/lib/router/utils/parse-url'
import type { Revalidate } from './lib/revalidate'
import fs from 'fs'
import { join, resolve, isAbsolute } from 'path'
import { join, resolve } from 'path'
import { getRouteMatcher } from '../shared/lib/router/utils/route-matcher'
import { addRequestMeta, getRequestMeta } from './request-meta'
import {
@ -101,6 +101,7 @@ import { loadManifest } from './load-manifest'
import { lazyRenderAppPage } from './future/route-modules/app-page/module.render'
import { lazyRenderPagesPage } from './future/route-modules/pages/module.render'
import { interopDefault } from '../lib/interop-default'
import { formatDynamicImportPath } from '../lib/format-dynamic-import-path'
export * from './base-server'
@ -108,8 +109,9 @@ declare const __non_webpack_require__: NodeRequire
// For module that can be both CJS or ESM
const dynamicImportEsmDefault = process.env.NEXT_MINIMAL
? __non_webpack_require__
: async (mod: string) => (await import(mod)).default
? (id: string) =>
import(/* webpackIgnore: true */ id).then((mod) => mod.default || mod)
: (id: string) => import(id).then((mod) => mod.default || mod)
// For module that will be compiled to CJS, e.g. instrument
const dynamicRequire = process.env.NEXT_MINIMAL
@ -310,9 +312,7 @@ export default class NextNodeServer extends BaseServer {
if (incrementalCacheHandlerPath) {
CacheHandler = interopDefault(
await dynamicImportEsmDefault(
isAbsolute(incrementalCacheHandlerPath)
? incrementalCacheHandlerPath
: join(this.distDir, incrementalCacheHandlerPath)
formatDynamicImportPath(this.distDir, incrementalCacheHandlerPath)
)
)
}