Skip to content

Commit

Permalink
Alias next public api to esm on edge runtime (#42709)
Browse files Browse the repository at this point in the history
Cases like `next/link` and `next/router` imports are not alias since
they're not matching the existing alias pattern setting for edge
runtime, which causes router-context being bundled twice (both with cjs
and esm) into edge worker bundle.
so we resolve their paths and alias them to esm bundle for webpack
bundling.

Other minor changes:

* update `require` calls to `import` expressions in edge ssr loaders
* remove client layer for apps without `appDir` enabled
* export `type` for ts typings in next/image to avoid alias to break
resolving

## Bug

- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have a helpful link attached, see `contributing.md`
  • Loading branch information
huozhi committed Nov 10, 2022
1 parent 2c199c4 commit 3174c73
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 22 deletions.
2 changes: 1 addition & 1 deletion packages/next/build/entries.ts
Expand Up @@ -207,7 +207,7 @@ export function getEdgeServerEntry(opts: {
// The Edge bundle includes the server in its entrypoint, so it has to
// be in the SSR layer — we later convert the page request to the RSC layer
// via a webpack rule.
layer: WEBPACK_LAYERS.client,
layer: opts.appDirLoader ? WEBPACK_LAYERS.client : undefined,
}
}

Expand Down
21 changes: 19 additions & 2 deletions packages/next/build/webpack-config.ts
Expand Up @@ -887,6 +887,21 @@ export default async function getBaseWebpackConfig(
'next/dist/client': 'next/dist/esm/client',
'next/dist/shared': 'next/dist/esm/shared',
'next/dist/pages': 'next/dist/esm/pages',
'next/dist/lib': 'next/dist/esm/lib',

// Alias the usage of next public APIs
[require.resolve('next/dist/client/link')]:
'next/dist/esm/client/link',
[require.resolve('next/dist/client/image')]:
'next/dist/esm/client/image',
[require.resolve('next/dist/client/script')]:
'next/dist/esm/client/script',
[require.resolve('next/dist/client/router')]:
'next/dist/esm/client/router',
[require.resolve('next/dist/shared/lib/head')]:
'next/dist/esm/shared/lib/head',
[require.resolve('next/dist/shared/lib/dynamic')]:
'next/dist/esm/shared/lib/dynamic',
}
: undefined),

Expand Down Expand Up @@ -1128,7 +1143,7 @@ export default async function getBaseWebpackConfig(
// Treat next internals as non-external for server layer
layer === WEBPACK_LAYERS.server
? false
: /next[/\\]dist[/\\](shared|server)[/\\](?!lib[/\\](router[/\\]router|dynamic))/.test(
: /next[/\\]dist[/\\](esm[\\/])?(shared|server)[/\\](?!lib[/\\](router[/\\]router|dynamic))/.test(
localRes
)

Expand Down Expand Up @@ -1194,7 +1209,9 @@ export default async function getBaseWebpackConfig(
const externalType = isEsm ? 'module' : 'commonjs'

if (
/next[/\\]dist[/\\]shared[/\\](?!lib[/\\]router[/\\]router)/.test(res) ||
/next[/\\]dist[/\\](esm[\\/])?shared[/\\](?!lib[/\\]router[/\\]router)/.test(
res
) ||
/next[/\\]dist[/\\]compiled[/\\].*\.[mc]?js$/.test(res)
) {
return `${externalType} ${request}`
Expand Down
23 changes: 12 additions & 11 deletions packages/next/build/webpack/loaders/next-edge-ssr-loader/index.ts
Expand Up @@ -97,28 +97,29 @@ export default async function edgeSSRLoader(this: any) {
${
isAppDir
? `
import { renderToHTMLOrFlight as appRenderToHTML } from 'next/dist/esm/server/app-render'
import * as pageMod from ${JSON.stringify(pageModPath)}
const Document = null
const appRenderToHTML = require('next/dist/esm/server/app-render').renderToHTMLOrFlight
const pagesRenderToHTML = null
const pageMod = require(${JSON.stringify(pageModPath)})
const appMod = null
const errorMod = null
const error500Mod = null
`
: `
const Document = require(${stringifiedDocumentPath}).default
const appRenderToHTML = null
const pagesRenderToHTML = require('next/dist/esm/server/render').renderToHTML
const pageMod = require(${stringifiedPagePath})
const appMod = require(${stringifiedAppPath})
const errorMod = require(${stringifiedErrorPath})
const error500Mod = ${
stringified500Path ? `require(${stringified500Path})` : 'null'
import Document from ${stringifiedDocumentPath}
import { renderToHTML as pagesRenderToHTML } from 'next/dist/esm/server/render'
import * as pageMod from ${stringifiedPagePath}
import * as appMod from ${stringifiedAppPath}
import * as errorMod from ${stringifiedErrorPath}
${
stringified500Path
? `import * as error500Mod from ${stringified500Path}`
: `const error500Mod = null`
}
const appRenderToHTML = null
`
}
const buildManifest = self.__BUILD_MANIFEST
const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST
const rscManifest = self.__RSC_MANIFEST
Expand Down
2 changes: 1 addition & 1 deletion packages/next/client/dev/error-overlay/hot-dev-client.js
Expand Up @@ -119,7 +119,7 @@ function handleWarnings(warnings) {
})

if (typeof console !== 'undefined' && typeof console.warn === 'function') {
for (let i = 0; i < formatted.warnings.length; i++) {
for (let i = 0; i < formatted.warnings?.length; i++) {
if (i === 5) {
console.warn(
'There were more warnings in other files.\n' +
Expand Down
6 changes: 3 additions & 3 deletions packages/next/client/image.tsx
Expand Up @@ -10,12 +10,12 @@ import React, {
} from 'react'
import Head from '../shared/lib/head'
import { getImageBlurSvg } from '../shared/lib/image-blur-svg'
import {
import type {
ImageConfigComplete,
imageConfigDefault,
ImageLoaderProps,
ImageLoaderPropsWithConfig,
} from '../shared/lib/image-config'
import { imageConfigDefault } from '../shared/lib/image-config'
import { ImageConfigContext } from '../shared/lib/image-config-context'
import { warnOnce } from '../shared/lib/utils/warn-once'
// @ts-ignore - This is replaced by webpack alias
Expand All @@ -36,7 +36,7 @@ const VALID_LOADING_VALUES = ['lazy', 'eager', undefined] as const
type LoadingValue = typeof VALID_LOADING_VALUES[number]
type ImageConfig = ImageConfigComplete & { allSizes: number[] }

export { ImageLoaderProps }
export type { ImageLoaderProps }
export type ImageLoader = (p: ImageLoaderProps) => string

// Do not export - this is an internal type only
Expand Down
5 changes: 1 addition & 4 deletions packages/next/script.js
@@ -1,4 +1 @@
module.exports =
process.env.NEXT_RUNTIME === 'edge'
? require('./dist/esm/client/script')
: require('./dist/client/script')
module.exports = require('./dist/client/script')
3 changes: 3 additions & 0 deletions test/e2e/prerender.test.ts
Expand Up @@ -2072,6 +2072,9 @@ describe('Prerender', () => {
const { version, files } = JSON.parse(contents)
expect(version).toBe(1)

console.log(
check.tests.map((item) => files.some((file) => item.test(file)))
)
expect(
check.tests.every((item) => files.some((file) => item.test(file)))
).toBe(true)
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/streaming-ssr/index.test.ts
Expand Up @@ -65,6 +65,11 @@ describe('react 18 streaming SSR with custom next configs', () => {
expect(html).toContain('home')
})

it('should render next/router correctly in edge runtime', async () => {
const html = await renderViaHTTP(next.url, '/router')
expect(html).toContain('link')
})

it('should render multi-byte characters correctly in streaming', async () => {
const html = await renderViaHTTP(next.url, '/multi-byte')
expect(html).toContain('マルチバイト'.repeat(28))
Expand Down
11 changes: 11 additions & 0 deletions test/e2e/streaming-ssr/streaming-ssr/pages/router.js
@@ -0,0 +1,11 @@
import { useRouter } from 'next/router'
import Link from 'next/link'

export default () => {
useRouter()
return <Link href="/">link</Link>
}

export const config = {
runtime: 'experimental-edge',
}

0 comments on commit 3174c73

Please sign in to comment.