Skip to content

Commit

Permalink
Merge branch '14-2-1' into ijjk/backport-fix-res-clone
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk authored Oct 1, 2024
2 parents 0364e9d + 73f6b7d commit 8f3ecff
Show file tree
Hide file tree
Showing 31 changed files with 444 additions and 22 deletions.
13 changes: 13 additions & 0 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,8 @@ export default async function getBaseWebpackConfig(
'next-metadata-route-loader',
'modularize-import-loader',
'next-barrel-loader',
'next-server-binary-loader',
'next-error-browser-binary-loader',
].reduce((alias, loader) => {
// using multiple aliases to replace `resolveLoader.modules`
alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader)
Expand Down Expand Up @@ -1276,6 +1278,17 @@ export default async function getBaseWebpackConfig(
or: WEBPACK_LAYERS.GROUP.nonClientServerTarget,
},
},
{
test: /[\\/].*?\.node$/,
loader: isNodeServer
? 'next-server-binary-loader'
: 'next-error-browser-binary-loader',
// On server side bundling, only apply to app router, do not apply to pages router;
// On client side or edge runtime bundling, always error.
...(isNodeServer && {
issuerLayer: isWebpackAppLayer,
}),
},
...(hasAppDir
? [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { webpack } from 'next/dist/compiled/webpack/webpack'

export default function nextErrorBrowserBinaryLoader(
this: webpack.LoaderContext<any>
) {
const { resourcePath, rootContext } = this
const relativePath = resourcePath.slice(rootContext.length + 1)
throw new Error(
`Node.js binary module ./${relativePath} is not supported in the browser. Please only use the module on server side`
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,27 @@ function errorOnBadHandler(resourcePath: string) {
`
}

/* re-export the userland route configs */
async function createReExportsCode(
resourcePath: string,
loaderContext: webpack.LoaderContext<any>
) {
const exportNames = await getLoaderModuleNamedExports(
resourcePath,
loaderContext
)
// Re-export configs but avoid conflicted exports
const reExportNames = exportNames.filter(
(name) => name !== 'default' && name !== 'generateSitemaps'
)

return reExportNames.length > 0
? `export { ${reExportNames.join(', ')} } from ${JSON.stringify(
resourcePath
)}\n`
: ''
}

const cacheHeader = {
none: 'no-cache, no-store',
longCache: 'public, immutable, no-transform, max-age=31536000',
Expand Down Expand Up @@ -83,7 +104,10 @@ export const dynamic = 'force-static'
return code
}

function getDynamicTextRouteCode(resourcePath: string) {
async function getDynamicTextRouteCode(
resourcePath: string,
loaderContext: webpack.LoaderContext<any>
) {
return `\
/* dynamic asset route */
import { NextResponse } from 'next/server'
Expand All @@ -94,6 +118,7 @@ const contentType = ${JSON.stringify(getContentType(resourcePath))}
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
${errorOnBadHandler(resourcePath)}
${await createReExportsCode(resourcePath, loaderContext)}
export async function GET() {
const data = await handler()
Expand All @@ -110,7 +135,10 @@ export async function GET() {
}

// <metadata-image>/[id]/route.js
function getDynamicImageRouteCode(resourcePath: string) {
async function getDynamicImageRouteCode(
resourcePath: string,
loaderContext: webpack.LoaderContext<any>
) {
return `\
/* dynamic image route */
import { NextResponse } from 'next/server'
Expand All @@ -122,6 +150,7 @@ const handler = imageModule.default
const generateImageMetadata = imageModule.generateImageMetadata
${errorOnBadHandler(resourcePath)}
${await createReExportsCode(resourcePath, loaderContext)}
export async function GET(_, ctx) {
const { __metadata_id__, ...params } = ctx.params || {}
Expand Down Expand Up @@ -160,10 +189,6 @@ async function getDynamicSiteMapRouteCode(
resourcePath,
loaderContext
)
// Re-export configs but avoid conflicted exports
const reExportNames = exportNames.filter(
(name) => name !== 'default' && name !== 'generateSitemaps'
)

const hasGenerateSiteMaps = exportNames.includes('generateSitemaps')
if (
Expand Down Expand Up @@ -197,15 +222,7 @@ const contentType = ${JSON.stringify(getContentType(resourcePath))}
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
${errorOnBadHandler(resourcePath)}
${'' /* re-export the userland route configs */}
${
reExportNames.length > 0
? `export { ${reExportNames.join(', ')} } from ${JSON.stringify(
resourcePath
)}\n`
: ''
}
${await createReExportsCode(resourcePath, loaderContext)}
export async function GET(_, ctx) {
const { __metadata_id__, ...params } = ctx.params || {}
Expand Down Expand Up @@ -266,11 +283,11 @@ const nextMetadataRouterLoader: webpack.LoaderDefinitionFunction<MetadataRouteLo
let code = ''
if (isDynamic === '1') {
if (fileBaseName === 'robots' || fileBaseName === 'manifest') {
code = getDynamicTextRouteCode(filePath)
code = await getDynamicTextRouteCode(filePath, this)
} else if (fileBaseName === 'sitemap') {
code = await getDynamicSiteMapRouteCode(filePath, page, this)
} else {
code = getDynamicImageRouteCode(filePath)
code = await getDynamicImageRouteCode(filePath, this)
}
} else {
code = await getStaticAssetRouteCode(filePath, fileBaseName)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { webpack } from 'next/dist/compiled/webpack/webpack'
import path from 'path'

export default function nextErrorBrowserBinaryLoader(
this: webpack.LoaderContext<any>
) {
let relativePath = path.relative(this.rootContext, this.resourcePath)
if (!relativePath.startsWith('.')) {
relativePath = './' + relativePath
}
return `module.exports = __non_webpack_require__(${JSON.stringify(
relativePath
)})`
}
23 changes: 21 additions & 2 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,23 @@ async function generateFlight(
}
)

return new FlightRenderResult(flightReadableStream)
const resultOptions: RenderResultOptions = {
metadata: {},
}

if (
ctx.staticGenerationStore.pendingRevalidates ||
ctx.staticGenerationStore.revalidatedTags
) {
resultOptions.waitUntil = Promise.all([
ctx.staticGenerationStore.incrementalCache?.revalidateTag(
ctx.staticGenerationStore.revalidatedTags || []
),
...Object.values(ctx.staticGenerationStore.pendingRevalidates || {}),
])
}

return new FlightRenderResult(flightReadableStream, resultOptions)
}

type RenderToStreamResult = {
Expand Down Expand Up @@ -1349,7 +1365,10 @@ async function renderToHTMLOrFlightImpl(
})

// If we have pending revalidates, wait until they are all resolved.
if (staticGenerationStore.pendingRevalidates) {
if (
staticGenerationStore.pendingRevalidates ||
staticGenerationStore.revalidatedTags
) {
options.waitUntil = Promise.all([
staticGenerationStore.incrementalCache?.revalidateTag(
staticGenerationStore.revalidatedTags || []
Expand Down
13 changes: 10 additions & 3 deletions packages/next/src/server/app-render/flight-render-result.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { RSC_CONTENT_TYPE_HEADER } from '../../client/components/app-router-headers'
import RenderResult from '../render-result'
import RenderResult, { type RenderResultOptions } from '../render-result'

/**
* Flight Response is always set to RSC_CONTENT_TYPE_HEADER to ensure it does not get interpreted as HTML.
*/
export class FlightRenderResult extends RenderResult {
constructor(response: string | ReadableStream<Uint8Array>) {
super(response, { contentType: RSC_CONTENT_TYPE_HEADER, metadata: {} })
constructor(
response: string | ReadableStream<Uint8Array>,
options?: RenderResultOptions
) {
super(response, {
contentType: RSC_CONTENT_TYPE_HEADER,
waitUntil: options?.waitUntil,
metadata: options?.metadata ?? {},
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ReactNode } from 'react'
export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client'

import { foo } from 'foo-browser-import-binary'

export default function Page() {
return <p>{foo()}</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { nextTestSetup } from 'e2e-utils'
import {
hasRedbox,
getRedboxDescription,
getRedboxSource,
} from 'next-test-utils'
;(process.env.TURBOPACK ? describe.skip : describe)(
'externalize-node-binary-browser-error',
() => {
const { next } = nextTestSetup({
files: __dirname,
})

it('should error when import node binary on browser side', async () => {
const browser = await next.browser('/')
await hasRedbox(browser)
const redbox = {
description: await getRedboxDescription(browser),
source: await getRedboxSource(browser),
}

expect(redbox.description).toBe('Failed to compile')
expect(redbox.source).toMatchInlineSnapshot(`
"./node_modules/foo-browser-import-binary/binary.node
Error: Node.js binary module ./node_modules/foo-browser-import-binary/binary.node is not supported in the browser. Please only use the module on server side"
`)
})
}
)

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions test/e2e/app-dir/externalize-node-binary/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ReactNode } from 'react'
export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
5 changes: 5 additions & 0 deletions test/e2e/app-dir/externalize-node-binary/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { foo } from 'foo'

export default function Page() {
return <p>{foo()}</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { nextTestSetup } from 'e2e-utils'

describe('externalize-node-binary', () => {
const { next } = nextTestSetup({
files: __dirname,
})

it('should render correctly when node_modules require node binary module', async () => {
const { status } = await next.fetch('/')
expect(status).toBe(200)

const browser = await next.browser('/')
expect(await browser.elementByCss('p').text()).toBe('I am foo')
})
})

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions test/e2e/app-dir/revalidatetag-rsc/app/RevalidateViaForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client'

import { revalidate } from './actions/revalidate'

export default function RevalidateViaForm({ tag }: { tag: string }) {
const handleRevalidate = async () => {
await revalidate(tag)
}

return (
<form action={handleRevalidate}>
<button type="submit" id="submit-form" className="underline">
Revalidate via form
</button>
</form>
)
}
11 changes: 11 additions & 0 deletions test/e2e/app-dir/revalidatetag-rsc/app/actions/revalidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use server'

import { revalidateTag } from 'next/cache'

export const revalidate = async (
tag: string
): Promise<{ revalidated: boolean }> => {
revalidateTag(tag)

return { revalidated: true }
}
9 changes: 9 additions & 0 deletions test/e2e/app-dir/revalidatetag-rsc/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ReactNode } from 'react'

export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
Loading

0 comments on commit 8f3ecff

Please sign in to comment.