next.js/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/index.ts
index.ts230 lines6.8 KB
import type webpack from 'webpack'
import type { SizeLimit } from '../../../../types'
import type { PagesRouteModuleOptions } from '../../../../server/route-modules/pages/module'
import type { ProxyConfig } from '../../../analysis/get-page-static-info'

import { getModuleBuildInfo } from '../get-module-build-info'
import { WEBPACK_RESOURCE_QUERIES } from '../../../../lib/constants'
import { RouteKind } from '../../../../server/route-kind'
import { normalizePagePath } from '../../../../shared/lib/page-path/normalize-page-path'
import { loadEntrypoint } from '../../../load-entrypoint'
import type { PAGE_TYPES } from '../../../../lib/page-types'

export type EdgeSSRLoaderQuery = {
  absolute500Path: string
  absoluteAppPath: string
  absoluteDocumentPath: string
  absoluteErrorPath: string
  absolutePagePath: string
  dev: boolean
  isServerComponent: boolean
  page: string
  appDirLoader?: string
  pagesType: PAGE_TYPES
  sriEnabled: boolean
  cacheHandler?: string
  cacheHandlers?: string
  preferredRegion: string | string[] | undefined
  middlewareConfig: string
  serverActions?: {
    bodySizeLimit?: SizeLimit
    allowedOrigins?: string[]
  }
}

/*
For pages SSR'd at the edge, we bundle them with the ESM version of Next in order to
benefit from the better tree-shaking and thus, smaller bundle sizes.

The absolute paths for _app, _error and _document, used in this loader, link to the regular CJS modules.
They are generated in `createPagesMapping` where we don't have access to `isEdgeRuntime`,
so we have to do it here. It's not that bad because it keeps all references to ESM modules magic in this place.
*/
function swapDistFolderWithEsmDistFolder(path: string) {
  return path.replace('next/dist/pages', 'next/dist/esm/pages')
}

function getRouteModuleOptions(page: string) {
  const options: Omit<
    PagesRouteModuleOptions,
    'userland' | 'components' | 'distDir' | 'relativeProjectDir'
  > = {
    definition: {
      kind: RouteKind.PAGES,
      page: normalizePagePath(page),
      pathname: page,
      // The following aren't used in production.
      bundlePath: '',
      filename: '',
    },
  }

  return options
}

function getCacheHandlersSetup(
  cacheHandlersStringified: string | undefined,
  contextifyImportPath: (path: string) => string
): {
  cacheHandlerImports: string
  cacheHandlerRegistration: string
} {
  const cacheHandlers = JSON.parse(cacheHandlersStringified || '{}') as Record<
    string,
    string | undefined
  >
  const definedCacheHandlers = Object.entries(cacheHandlers).filter(
    (entry): entry is [string, string] => Boolean(entry[1])
  )

  const cacheHandlerImports: string[] = []
  const cacheHandlerRegistration: string[] = []

  for (const [index, [kind, handlerPath]] of definedCacheHandlers.entries()) {
    const cacheHandlerVarName = `edgeCacheHandler_${index}`
    const cacheHandlerImportPath = contextifyImportPath(handlerPath)
    cacheHandlerImports.push(
      `import ${cacheHandlerVarName} from ${JSON.stringify(
        cacheHandlerImportPath
      )}`
    )
    cacheHandlerRegistration.push(
      `  cacheHandlers.setCacheHandler(${JSON.stringify(
        kind
      )}, ${cacheHandlerVarName})`
    )
  }

  return {
    cacheHandlerImports: cacheHandlerImports.join('\n') || '\n',
    cacheHandlerRegistration: cacheHandlerRegistration.join('\n') || '\n',
  }
}

const edgeSSRLoader: webpack.LoaderDefinitionFunction<EdgeSSRLoaderQuery> =
  async function edgeSSRLoader(this) {
    const {
      page,
      absolutePagePath,
      absoluteAppPath,
      absoluteDocumentPath,
      absolute500Path,
      absoluteErrorPath,
      isServerComponent,
      appDirLoader: appDirLoaderBase64,
      pagesType,
      cacheHandler,
      cacheHandlers: cacheHandlersStringified,
      preferredRegion,
      middlewareConfig: middlewareConfigBase64,
    } = this.getOptions()

    const cacheHandlersSetup = getCacheHandlersSetup(
      cacheHandlersStringified,
      (handlerPath) =>
        this.utils.contextify(this.context || this.rootContext, handlerPath)
    )
    const incrementalCacheHandler = cacheHandler
      ? this.utils.contextify(this.context || this.rootContext, cacheHandler)
      : null

    const middlewareConfig: ProxyConfig = JSON.parse(
      Buffer.from(middlewareConfigBase64, 'base64').toString()
    )

    const appDirLoader = Buffer.from(
      appDirLoaderBase64 || '',
      'base64'
    ).toString()
    const isAppDir = pagesType === 'app'

    const buildInfo = getModuleBuildInfo(this._module as any)
    buildInfo.nextEdgeSSR = {
      isServerComponent,
      page: page,
      isAppDir,
    }
    buildInfo.route = {
      page,
      absolutePagePath,
      preferredRegion,
      middlewareConfig,
    }

    const pagePath = this.utils.contextify(
      this.context || this.rootContext,
      absolutePagePath
    )
    const appPath = absoluteAppPath
      ? this.utils.contextify(
          this.context || this.rootContext,
          swapDistFolderWithEsmDistFolder(absoluteAppPath)
        )
      : ''
    const errorPath = absoluteErrorPath
      ? this.utils.contextify(
          this.context || this.rootContext,
          swapDistFolderWithEsmDistFolder(absoluteErrorPath)
        )
      : ''
    const documentPath = absoluteDocumentPath
      ? this.utils.contextify(
          this.context || this.rootContext,
          swapDistFolderWithEsmDistFolder(absoluteDocumentPath)
        )
      : ''
    const userland500Path = absolute500Path
      ? this.utils.contextify(
          this.context || this.rootContext,
          swapDistFolderWithEsmDistFolder(absolute500Path)
        )
      : null

    const stringifiedPagePath = JSON.stringify(pagePath)

    const pageModPath = `${appDirLoader}${stringifiedPagePath.substring(
      1,
      stringifiedPagePath.length - 1
    )}${isAppDir ? `?${WEBPACK_RESOURCE_QUERIES.edgeSSREntry}` : ''}`

    if (isAppDir) {
      return await loadEntrypoint(
        'edge-ssr-app',
        {
          VAR_USERLAND: pageModPath,
          VAR_PAGE: page,
        },
        cacheHandlersSetup,
        {
          incrementalCacheHandler,
        }
      )
    } else {
      return await loadEntrypoint(
        'edge-ssr',
        {
          VAR_USERLAND: pageModPath,
          VAR_DEFINITION_PATHNAME: page,
          VAR_MODULE_DOCUMENT: documentPath,
          VAR_MODULE_APP: appPath,
          VAR_MODULE_GLOBAL_ERROR: errorPath,
        },
        {
          pageRouteModuleOptions: JSON.stringify(getRouteModuleOptions(page)),
          errorRouteModuleOptions: JSON.stringify(
            getRouteModuleOptions('/_error')
          ),
          user500RouteModuleOptions: JSON.stringify(
            getRouteModuleOptions('/500')
          ),
          ...(cacheHandlersSetup ?? {}),
        },
        {
          userland500Page: userland500Path,
          incrementalCacheHandler,
        }
      )
    }
  }
export default edgeSSRLoader
Quest for Codev2.0.0
/
SIGN IN