UNPKG

next

Version:

The React Framework

833 lines (795 loc) 36.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "NextTypesPlugin", { enumerable: true, get: function() { return NextTypesPlugin; } }); const _promises = /*#__PURE__*/ _interop_require_default(require("fs/promises")); const _webpack = require("next/dist/compiled/webpack/webpack"); const _pathtoregexp = require("next/dist/compiled/path-to-regexp"); const _path = /*#__PURE__*/ _interop_require_default(require("path")); const _constants = require("../../../../lib/constants"); const _denormalizepagepath = require("../../../../shared/lib/page-path/denormalize-page-path"); const _ensureleadingslash = require("../../../../shared/lib/page-path/ensure-leading-slash"); const _normalizepathsep = require("../../../../shared/lib/page-path/normalize-path-sep"); const _http = require("../../../../server/web/http"); const _utils = require("../../../../shared/lib/router/utils"); const _apppaths = require("../../../../shared/lib/router/utils/app-paths"); const _entries = require("../../../entries"); const _shared = require("./shared"); const _buildcontext = require("../../../build-context"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const PLUGIN_NAME = 'NextTypesPlugin'; function createTypeGuardFile(fullPath, relativePath, options) { return `// File: ${fullPath} import * as entry from '${relativePath}.js' ${options.type === 'route' ? `import type { NextRequest } from 'next/server.js'` : `import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'`} type TEntry = typeof import('${relativePath}.js') type SegmentParams<T extends Object = any> = T extends Record<string, any> ? { [K in keyof T]: T[K] extends string ? string | string[] | undefined : never } : T // Check that the entry is a valid entry checkFields<Diff<{ ${options.type === 'route' ? _http.HTTP_METHODS.map((method)=>`${method}?: Function`).join('\n ') : 'default: Function'} config?: {} generateStaticParams?: Function revalidate?: RevalidateRange<TEntry> | false dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static' dynamicParams?: boolean fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache' preferredRegion?: 'auto' | 'global' | 'home' | string | string[] runtime?: 'nodejs' | 'experimental-edge' | 'edge' maxDuration?: number ${options.type === 'route' ? '' : ` metadata?: any generateMetadata?: Function viewport?: any generateViewport?: Function experimental_ppr?: boolean `} }, TEntry, ''>>() ${options.type === 'route' ? `type RouteContext = { params: Promise<SegmentParams> }` : ''} ${options.type === 'route' ? _http.HTTP_METHODS.map((method)=>`// Check the prop type of the entry function if ('${method}' in entry) { checkFields< Diff< ParamCheck<Request | NextRequest>, { __tag__: '${method}' __param_position__: 'first' __param_type__: FirstArg<MaybeField<TEntry, '${method}'>> }, '${method}' > >() checkFields< Diff< ParamCheck<RouteContext>, { __tag__: '${method}' __param_position__: 'second' __param_type__: SecondArg<MaybeField<TEntry, '${method}'>> }, '${method}' > >() ${''} checkFields< Diff< { __tag__: '${method}', __return_type__: Response | void | never | Promise<Response | void | never> }, { __tag__: '${method}', __return_type__: ReturnType<MaybeField<TEntry, '${method}'>> }, '${method}' > >() } `).join('') : `// Check the prop type of the entry function checkFields<Diff<${options.type === 'page' ? 'PageProps' : 'LayoutProps'}, FirstArg<TEntry['default']>, 'default'>>() // Check the arguments and return type of the generateMetadata function if ('generateMetadata' in entry) { checkFields<Diff<${options.type === 'page' ? 'PageProps' : 'LayoutProps'}, FirstArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>() checkFields<Diff<ResolvingMetadata, SecondArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>() } // Check the arguments and return type of the generateViewport function if ('generateViewport' in entry) { checkFields<Diff<${options.type === 'page' ? 'PageProps' : 'LayoutProps'}, FirstArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>() checkFields<Diff<ResolvingViewport, SecondArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>() } `} // Check the arguments and return type of the generateStaticParams function if ('generateStaticParams' in entry) { checkFields<Diff<{ params: SegmentParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>() checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>() } export interface PageProps { params?: Promise<SegmentParams> searchParams?: Promise<any> } export interface LayoutProps { children?: React.ReactNode ${options.slots ? options.slots.map((slot)=>` ${slot}: React.ReactNode`).join('\n') : ''} params?: Promise<SegmentParams> } // ============= // Utility types type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never // If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>. type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K> type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message> type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never ${options.type === 'route' ? `type ParamCheck<T> = { __tag__: string __param_position__: string __param_type__: T }` : ''} function checkFields<_ extends { [k in keyof any]: never }>() {} // https://github.com/sindresorhus/type-fest type Numeric = number | bigint type Zero = 0 | 0n type Negative<T extends Numeric> = T extends Zero ? never : \`\${T}\` extends \`-\${string}\` ? T : never type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__' `; } async function collectNamedSlots(layoutPath) { const layoutDir = _path.default.dirname(layoutPath); const items = await _promises.default.readdir(layoutDir, { withFileTypes: true }); const slots = []; for (const item of items){ if (item.isDirectory() && item.name.startsWith('@') && // `@children slots are matched to the children prop, and should not be handled separately for type-checking item.name !== '@children') { slots.push(item.name.slice(1)); } } return slots; } // By exposing the static route types separately as string literals, // editors can provide autocompletion for them. However it's currently not // possible to provide the same experience for dynamic routes. const pluginState = (0, _buildcontext.getProxiedPluginState)({ collectedRootParams: {}, routeTypes: { edge: { static: '', dynamic: '' }, node: { static: '', dynamic: '' }, extra: { static: '', dynamic: '' } } }); function formatRouteToRouteType(route) { const isDynamic = (0, _utils.isDynamicRoute)(route); if (isDynamic) { route = route.split('/').map((part)=>{ if (part.startsWith('[') && part.endsWith(']')) { if (part.startsWith('[...')) { // /[...slug] return `\${CatchAllSlug<T>}`; } else if (part.startsWith('[[...') && part.endsWith(']]')) { // /[[...slug]] return `\${OptionalCatchAllSlug<T>}`; } // /[slug] return `\${SafeSlug<T>}`; } return part; }).join('/'); } return { isDynamic, routeType: `\n | \`${route}\`` }; } // Whether redirects and rewrites have been converted into routeTypes or not. let redirectsRewritesTypesProcessed = false; // Convert redirects and rewrites into routeTypes. function addRedirectsRewritesRouteTypes(rewrites, redirects) { function addExtraRoute(source) { let tokens; try { tokens = (0, _pathtoregexp.parse)(source); } catch { // Ignore invalid routes - they will be handled by other checks. } if (Array.isArray(tokens)) { const possibleNormalizedRoutes = [ '' ]; let slugCnt = 1; function append(suffix) { for(let i = 0; i < possibleNormalizedRoutes.length; i++){ possibleNormalizedRoutes[i] += suffix; } } function fork(suffix) { const currentLength = possibleNormalizedRoutes.length; for(let i = 0; i < currentLength; i++){ possibleNormalizedRoutes.push(possibleNormalizedRoutes[i] + suffix); } } for (const token of tokens){ if (typeof token === 'object') { // Make sure the slug is always named. const slug = token.name || (slugCnt++ === 1 ? 'slug' : `slug${slugCnt}`); if (token.modifier === '*') { append(`${token.prefix}[[...${slug}]]`); } else if (token.modifier === '+') { append(`${token.prefix}[...${slug}]`); } else if (token.modifier === '') { if (token.pattern === '[^\\/#\\?]+?') { // A safe slug append(`${token.prefix}[${slug}]`); } else if (token.pattern === '.*') { // An optional catch-all slug append(`${token.prefix}[[...${slug}]]`); } else if (token.pattern === '.+') { // A catch-all slug append(`${token.prefix}[...${slug}]`); } else { // Other regex patterns are not supported. Skip this route. return; } } else if (token.modifier === '?') { if (/^[a-zA-Z0-9_/]*$/.test(token.pattern)) { // An optional slug with plain text only, fork the route. append(token.prefix); fork(token.pattern); } else { // Optional modifier `?` and regex patterns are not supported. return; } } } else if (typeof token === 'string') { append(token); } } for (const normalizedRoute of possibleNormalizedRoutes){ const { isDynamic, routeType } = formatRouteToRouteType(normalizedRoute); pluginState.routeTypes.extra[isDynamic ? 'dynamic' : 'static'] += routeType; } } } if (rewrites) { for (const rewrite of rewrites.beforeFiles){ addExtraRoute(rewrite.source); } for (const rewrite of rewrites.afterFiles){ addExtraRoute(rewrite.source); } for (const rewrite of rewrites.fallback){ addExtraRoute(rewrite.source); } } if (redirects) { for (const redirect of redirects){ // Skip internal redirects // https://github.com/vercel/next.js/blob/8ff3d7ff57836c24088474175d595b4d50b3f857/packages/next/src/lib/load-custom-routes.ts#L704-L710 if (!('internal' in redirect)) { addExtraRoute(redirect.source); } } } } function createRouteDefinitions() { let staticRouteTypes = ''; let dynamicRouteTypes = ''; for (const type of [ 'edge', 'node', 'extra' ]){ staticRouteTypes += pluginState.routeTypes[type].static; dynamicRouteTypes += pluginState.routeTypes[type].dynamic; } // If both StaticRoutes and DynamicRoutes are empty, fallback to type 'string & {}'. const routeTypesFallback = !staticRouteTypes && !dynamicRouteTypes ? 'string & {}' : ''; return `// Type definitions for Next.js routes /** * Internal types used by the Next.js router and Link component. * These types are not meant to be used directly. * @internal */ declare namespace __next_route_internal_types__ { type SearchOrHash = \`?\${string}\` | \`#\${string}\` type WithProtocol = \`\${string}:\${string}\` type Suffix = '' | SearchOrHash type SafeSlug<S extends string> = S extends \`\${string}/\${string}\` ? never : S extends \`\${string}\${SearchOrHash}\` ? never : S extends '' ? never : S type CatchAllSlug<S extends string> = S extends \`\${string}\${SearchOrHash}\` ? never : S extends '' ? never : S type OptionalCatchAllSlug<S extends string> = S extends \`\${string}\${SearchOrHash}\` ? never : S type StaticRoutes = ${staticRouteTypes || 'never'} type DynamicRoutes<T extends string = string> = ${dynamicRouteTypes || 'never'} type RouteImpl<T> = ${routeTypesFallback || ` ${// This keeps autocompletion working for static routes. '| StaticRoutes'} | SearchOrHash | WithProtocol | \`\${StaticRoutes}\${SearchOrHash}\` | (T extends \`\${DynamicRoutes<infer _>}\${Suffix}\` ? T : never) `} } declare module 'next' { export { default } from 'next/types.js' export * from 'next/types.js' export type Route<T extends string = string> = __next_route_internal_types__.RouteImpl<T> } declare module 'next/link' { import type { LinkProps as OriginalLinkProps } from 'next/dist/client/link.js' import type { AnchorHTMLAttributes, DetailedHTMLProps } from 'react' import type { UrlObject } from 'url' type LinkRestProps = Omit< Omit< DetailedHTMLProps< AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement >, keyof OriginalLinkProps > & OriginalLinkProps, 'href' > export type LinkProps<RouteInferType> = LinkRestProps & { /** * The path or URL to navigate to. This is the only required prop. It can also be an object. * @see https://nextjs.org/docs/api-reference/next/link */ href: __next_route_internal_types__.RouteImpl<RouteInferType> | UrlObject } export default function Link<RouteType>(props: LinkProps<RouteType>): JSX.Element } declare module 'next/navigation' { export * from 'next/dist/client/components/navigation.js' import type { NavigateOptions, AppRouterInstance as OriginalAppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime.js' interface AppRouterInstance extends OriginalAppRouterInstance { /** * Navigate to the provided href. * Pushes a new history entry. */ push<RouteType>(href: __next_route_internal_types__.RouteImpl<RouteType>, options?: NavigateOptions): void /** * Navigate to the provided href. * Replaces the current history entry. */ replace<RouteType>(href: __next_route_internal_types__.RouteImpl<RouteType>, options?: NavigateOptions): void /** * Prefetch the provided href. */ prefetch<RouteType>(href: __next_route_internal_types__.RouteImpl<RouteType>): void } export function useRouter(): AppRouterInstance; } declare module 'next/form' { import type { FormProps as OriginalFormProps } from 'next/dist/client/form.js' type FormRestProps = Omit<OriginalFormProps, 'action'> export type FormProps<RouteInferType> = { /** * \`action\` can be either a \`string\` or a function. * - If \`action\` is a string, it will be interpreted as a path or URL to navigate to when the form is submitted. * The path will be prefetched when the form becomes visible. * - If \`action\` is a function, it will be called when the form is submitted. See the [React docs](https://react.dev/reference/react-dom/components/form#props) for more. */ action: __next_route_internal_types__.RouteImpl<RouteInferType> | ((formData: FormData) => void) } & FormRestProps export default function Form<RouteType>(props: FormProps<RouteType>): JSX.Element } `; } function formatTimespan(seconds) { if (seconds > 0) { if (seconds === 18748800) { return '1 month'; } if (seconds === 18144000) { return '1 month'; } if (seconds === 604800) { return '1 week'; } if (seconds === 86400) { return '1 day'; } if (seconds === 3600) { return '1 hour'; } if (seconds === 60) { return '1 minute'; } if (seconds % 18748800 === 0) { return seconds / 18748800 + ' months'; } if (seconds % 18144000 === 0) { return seconds / 18144000 + ' months'; } if (seconds % 604800 === 0) { return seconds / 604800 + ' weeks'; } if (seconds % 86400 === 0) { return seconds / 86400 + ' days'; } if (seconds % 3600 === 0) { return seconds / 3600 + ' hours'; } if (seconds % 60 === 0) { return seconds / 60 + ' minutes'; } } return seconds + ' seconds'; } function formatTimespanWithSeconds(seconds) { if (seconds === undefined) { return 'default'; } if (seconds >= 0xfffffffe) { return 'never'; } const text = seconds + ' seconds'; const descriptive = formatTimespan(seconds); if (descriptive === text) { return text; } return text + ' (' + descriptive + ')'; } function getRootParamsFromLayouts(layouts) { // Sort layouts by depth (descending) const sortedLayouts = Object.entries(layouts).sort((a, b)=>b[0].split('/').length - a[0].split('/').length); if (!sortedLayouts.length) { return []; } // we assume the shorted layout path is the root layout let rootLayout = sortedLayouts[sortedLayouts.length - 1][0]; let rootParams = new Set(); let isMultipleRootLayouts = false; for (const [layoutPath, params] of sortedLayouts){ const allSegmentsAreDynamic = layoutPath.split('/').slice(1, -1)// match dynamic params but not catch-all or optional catch-all .every((segment)=>/^\[[^[.\]]+\]$/.test(segment)); if (allSegmentsAreDynamic) { if (isSubpath(rootLayout, layoutPath)) { // Current path is a subpath of the root layout, update root rootLayout = layoutPath; rootParams = new Set(params); } else { // Found another potential root layout isMultipleRootLayouts = true; // Add any new params for (const param of params){ rootParams.add(param); } } } } // Create result array const result = Array.from(rootParams).map((param)=>({ param, optional: isMultipleRootLayouts })); return result; } function isSubpath(parentLayoutPath, potentialChildLayoutPath) { // we strip off the `layout` part of the path as those will always conflict with being a subpath const parentSegments = parentLayoutPath.split('/').slice(1, -1); const childSegments = potentialChildLayoutPath.split('/').slice(1, -1); // child segments should be shorter or equal to parent segments to be a subpath if (childSegments.length > parentSegments.length || !childSegments.length) return false; // Verify all segment values are equal return childSegments.every((childSegment, index)=>childSegment === parentSegments[index]); } function createServerDefinitions(rootParams) { return ` declare module 'next/server' { import type { AsyncLocalStorage as NodeAsyncLocalStorage } from 'async_hooks' declare global { var AsyncLocalStorage: typeof NodeAsyncLocalStorage } export { NextFetchEvent } from 'next/dist/server/web/spec-extension/fetch-event' export { NextRequest } from 'next/dist/server/web/spec-extension/request' export { NextResponse } from 'next/dist/server/web/spec-extension/response' export { NextMiddleware, MiddlewareConfig } from 'next/dist/server/web/types' export { userAgentFromString } from 'next/dist/server/web/spec-extension/user-agent' export { userAgent } from 'next/dist/server/web/spec-extension/user-agent' export { URLPattern } from 'next/dist/compiled/@edge-runtime/primitives/url' export { ImageResponse } from 'next/dist/server/web/spec-extension/image-response' export type { ImageResponseOptions } from 'next/dist/compiled/@vercel/og/types' export { after } from 'next/dist/server/after' export { connection } from 'next/dist/server/request/connection' export type { UnsafeUnwrappedSearchParams } from 'next/dist/server/request/search-params' export type { UnsafeUnwrappedParams } from 'next/dist/server/request/params' export function unstable_rootParams(): Promise<{ ${rootParams.map(({ param, optional })=>// ensure params with dashes are valid keys `${param.includes('-') ? `'${param}'` : param}${optional ? '?' : ''}: string`).join(', ')} }> } `; } function createCustomCacheLifeDefinitions(cacheLife) { let overloads = ''; const profileNames = Object.keys(cacheLife); for(let i = 0; i < profileNames.length; i++){ const profileName = profileNames[i]; const profile = cacheLife[profileName]; if (typeof profile !== 'object' || profile === null) { continue; } let description = ''; if (profile.stale === undefined) { description += ` * This cache may be stale on clients for the default stale time of the scope before checking with the server.`; } else if (profile.stale >= 0xfffffffe) { description += ` * This cache may be stale on clients indefinitely before checking with the server.`; } else { description += ` * This cache may be stale on clients for ${formatTimespan(profile.stale)} before checking with the server.`; } if (profile.revalidate !== undefined && profile.expire !== undefined && profile.revalidate >= profile.expire) { description += ` * This cache will expire after ${formatTimespan(profile.expire)}. The next request will recompute it.`; } else { if (profile.revalidate === undefined) { description += ` * It will inherit the default revalidate time of its scope since it does not define its own.`; } else if (profile.revalidate >= 0xfffffffe) { // Nothing to mention. } else { description += ` * If the server receives a new request after ${formatTimespan(profile.revalidate)}, start revalidating new values in the background.`; } if (profile.expire === undefined) { description += ` * It will inherit the default expiration time of its scope since it does not define its own.`; } else if (profile.expire >= 0xfffffffe) { description += ` * It lives for the maximum age of the server cache. If this entry has no traffic for a while, it may serve an old value the next request.`; } else { description += ` * If this entry has no traffic for ${formatTimespan(profile.expire)} it will expire. The next request will recompute it.`; } } overloads += ` /** * Cache this \`"use cache"\` for a timespan defined by the \`${JSON.stringify(profileName)}\` profile. * \`\`\` * stale: ${formatTimespanWithSeconds(profile.stale)} * revalidate: ${formatTimespanWithSeconds(profile.revalidate)} * expire: ${formatTimespanWithSeconds(profile.expire)} * \`\`\` * ${description} */ export function unstable_cacheLife(profile: ${JSON.stringify(profileName)}): void `; } overloads += ` /** * Cache this \`"use cache"\` using a custom timespan. * \`\`\` * stale: ... // seconds * revalidate: ... // seconds * expire: ... // seconds * \`\`\` * * This is similar to Cache-Control: max-age=\`stale\`,s-max-age=\`revalidate\`,stale-while-revalidate=\`expire-revalidate\` * * If a value is left out, the lowest of other cacheLife() calls or the default, is used instead. */ export function unstable_cacheLife(profile: { /** * This cache may be stale on clients for ... seconds before checking with the server. */ stale?: number, /** * If the server receives a new request after ... seconds, start revalidating new values in the background. */ revalidate?: number, /** * If this entry has no traffic for ... seconds it will expire. The next request will recompute it. */ expire?: number }): void `; // Redefine the cacheLife() accepted arguments. return `// Type definitions for Next.js cacheLife configs declare module 'next/cache' { export { unstable_cache } from 'next/dist/server/web/spec-extension/unstable-cache' export { revalidateTag, revalidatePath, unstable_expireTag, unstable_expirePath, } from 'next/dist/server/web/spec-extension/revalidate' export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store' ${overloads} export { cacheTag as unstable_cacheTag } from 'next/dist/server/use-cache/cache-tag' } `; } const appTypesBasePath = _path.default.join('types', 'app'); class NextTypesPlugin { constructor(options){ this.dir = options.dir; this.distDir = options.distDir; this.appDir = options.appDir; this.dev = options.dev; this.isEdgeServer = options.isEdgeServer; this.pageExtensions = options.pageExtensions; this.pagesDir = _path.default.join(this.appDir, '..', 'pages'); this.typedRoutes = options.typedRoutes; this.cacheLifeConfig = options.cacheLifeConfig; this.distDirAbsolutePath = _path.default.join(this.dir, this.distDir); if (this.typedRoutes && !redirectsRewritesTypesProcessed) { redirectsRewritesTypesProcessed = true; addRedirectsRewritesRouteTypes(options.originalRewrites, options.originalRedirects); } } getRelativePathFromAppTypesDir(moduleRelativePathToAppDir) { const moduleAbsolutePath = _path.default.join(this.appDir, moduleRelativePathToAppDir); const moduleInAppTypesAbsolutePath = _path.default.join(this.distDirAbsolutePath, appTypesBasePath, moduleRelativePathToAppDir); return _path.default.relative(moduleInAppTypesAbsolutePath + '/..', moduleAbsolutePath); } collectPage(filePath) { if (!this.typedRoutes) return; const isApp = filePath.startsWith(this.appDir + _path.default.sep); const isPages = !isApp && filePath.startsWith(this.pagesDir + _path.default.sep); if (!isApp && !isPages) { return; } // Filter out non-page and non-route files in app dir if (isApp && !/[/\\](?:page|route)\.[^.]+$/.test(filePath)) { return; } // Filter out non-page files in pages dir if (isPages && /[/\\](?:_app|_document|_error|404|500)\.[^.]+$/.test(filePath)) { return; } let route = (isApp ? _apppaths.normalizeAppPath : _denormalizepagepath.denormalizePagePath)((0, _ensureleadingslash.ensureLeadingSlash)((0, _entries.getPageFromPath)(_path.default.relative(isApp ? this.appDir : this.pagesDir, filePath), this.pageExtensions))); const { isDynamic, routeType } = formatRouteToRouteType(route); pluginState.routeTypes[this.isEdgeServer ? 'edge' : 'node'][isDynamic ? 'dynamic' : 'static'] += routeType; } apply(compiler) { // From asset root to dist root const assetDirRelative = this.dev ? '..' : this.isEdgeServer ? '..' : '../..'; const handleModule = async (mod, compilation)=>{ if (!mod.resource) return; const pageExtensionsRegex = new RegExp(`\\.(${this.pageExtensions.join('|')})$`); if (!pageExtensionsRegex.test(mod.resource)) return; if (!mod.resource.startsWith(this.appDir + _path.default.sep)) { if (!this.dev) { if (mod.resource.startsWith(this.pagesDir + _path.default.sep)) { this.collectPage(mod.resource); } } return; } if (mod.layer !== _constants.WEBPACK_LAYERS.reactServerComponents) return; // skip for /app/_private dir convention // matches <app-dir>/**/_* const IS_PRIVATE = /(?:\/[^/]+)*\/_.*$/.test(mod.resource.replace(this.appDir, '')); if (IS_PRIVATE) return; const IS_LAYOUT = /[/\\]layout\.[^./\\]+$/.test(mod.resource); const IS_PAGE = !IS_LAYOUT && /[/\\]page\.[^.]+$/.test(mod.resource); const IS_ROUTE = !IS_PAGE && /[/\\]route\.[^.]+$/.test(mod.resource); const IS_IMPORTABLE = /\.(js|jsx|ts|tsx|mjs|cjs)$/.test(mod.resource); const relativePathToApp = _path.default.relative(this.appDir, mod.resource); if (!this.dev) { if (IS_PAGE || IS_ROUTE) { this.collectPage(mod.resource); } } const typePath = _path.default.join(appTypesBasePath, relativePathToApp.replace(pageExtensionsRegex, '.ts')); const relativeImportPath = (0, _normalizepathsep.normalizePathSep)(_path.default.join(this.getRelativePathFromAppTypesDir(relativePathToApp)).replace(pageExtensionsRegex, '')); const assetPath = _path.default.join(assetDirRelative, typePath); // Typescript won’t allow relative-importing (for example) a .mdx file using the .js extension // so for now we only generate “type guard files” for files that typescript can transform if (!IS_IMPORTABLE) return; if (IS_LAYOUT) { const rootLayoutPath = (0, _apppaths.normalizeAppPath)((0, _ensureleadingslash.ensureLeadingSlash)((0, _entries.getPageFromPath)(_path.default.relative(this.appDir, mod.resource), this.pageExtensions))); const foundParams = Array.from(rootLayoutPath.matchAll(/\[(.*?)\]/g), (match)=>match[1]); pluginState.collectedRootParams[rootLayoutPath] = foundParams; const slots = await collectNamedSlots(mod.resource); compilation.emitAsset(assetPath, new _webpack.sources.RawSource(createTypeGuardFile(mod.resource, relativeImportPath, { type: 'layout', slots }))); } else if (IS_PAGE) { compilation.emitAsset(assetPath, new _webpack.sources.RawSource(createTypeGuardFile(mod.resource, relativeImportPath, { type: 'page' }))); } else if (IS_ROUTE) { compilation.emitAsset(assetPath, new _webpack.sources.RawSource(createTypeGuardFile(mod.resource, relativeImportPath, { type: 'route' }))); } }; compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation)=>{ compilation.hooks.processAssets.tapAsync({ name: PLUGIN_NAME, stage: _webpack.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH }, async (_, callback)=>{ const promises = []; // Clear routes if (this.isEdgeServer) { pluginState.routeTypes.edge.dynamic = ''; pluginState.routeTypes.edge.static = ''; } else { pluginState.routeTypes.node.dynamic = ''; pluginState.routeTypes.node.static = ''; } compilation.chunkGroups.forEach((chunkGroup)=>{ chunkGroup.chunks.forEach((chunk)=>{ if (!chunk.name) return; // Here we only track page and route chunks. if (!chunk.name.startsWith('pages/') && !(chunk.name.startsWith('app/') && (chunk.name.endsWith('/page') || chunk.name.endsWith('/route')))) { return; } const chunkModules = compilation.chunkGraph.getChunkModulesIterable(chunk); for (const mod of chunkModules){ promises.push(handleModule(mod, compilation)); // If this is a concatenation, register each child to the parent ID. const anyModule = mod; if (anyModule.modules) { anyModule.modules.forEach((concatenatedMod)=>{ promises.push(handleModule(concatenatedMod, compilation)); }); } } }); }); await Promise.all(promises); const rootParams = getRootParamsFromLayouts(pluginState.collectedRootParams); // If we discovered rootParams, we'll override the `next/server` types // since we're able to determine the root params at build time. if (rootParams.length > 0) { const serverTypesPath = _path.default.join(assetDirRelative, 'types/server.d.ts'); compilation.emitAsset(serverTypesPath, new _webpack.sources.RawSource(createServerDefinitions(rootParams))); } // Support `"moduleResolution": "Node16" | "NodeNext"` with `"type": "module"` const packageJsonAssetPath = _path.default.join(assetDirRelative, 'types/package.json'); compilation.emitAsset(packageJsonAssetPath, new _webpack.sources.RawSource('{"type": "module"}')); if (this.typedRoutes) { if (this.dev && !this.isEdgeServer) { _shared.devPageFiles.forEach((file)=>{ this.collectPage(file); }); } const linkAssetPath = _path.default.join(assetDirRelative, 'types/link.d.ts'); compilation.emitAsset(linkAssetPath, new _webpack.sources.RawSource(createRouteDefinitions())); } if (this.cacheLifeConfig) { const cacheLifeAssetPath = _path.default.join(assetDirRelative, 'types/cache-life.d.ts'); compilation.emitAsset(cacheLifeAssetPath, new _webpack.sources.RawSource(createCustomCacheLifeDefinitions(this.cacheLifeConfig))); } callback(); }); }); } } //# sourceMappingURL=index.js.map