Skip to content

Commit e354b73

Browse files
taty2010piehorinokai
authored
fix: improve support for new requireHooks update (#2313)
* fix: make sure appDir exists before running requireHooks * fix: run prebundledReact if appDir exists in conf * fix: use ExperimentalConfigWithLegacy for appDir * fix: set cache to manual and env as optional * fix: prettier * fix: update appDir check * fix: styled-jsx solution for 13.5 * test: add styled-jsx to included files test * test: added next latest to i18n for testing * fix: remove i18n for wp * fix: use requireHooks based on next -v and appDir * fix: remove fs-extra * fix: add semver to handler * fix: try to import semver * fix: testing something new * fix: reset * fix: add back useHooks * fix: test updates + remove preRender update * test: add useHooks * refactor: use existing resolveModuleRoot function instead of module.createRequire * fix: remove 13.5 --------- Co-authored-by: Michal Piechowiak <[email protected]> Co-authored-by: Rob Stanford <[email protected]>
1 parent b3be3f8 commit e354b73

File tree

12 files changed

+80
-31
lines changed

12 files changed

+80
-31
lines changed

packages/runtime/src/helpers/config.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ export const updateRequiredServerFiles = async (publish: string, modifiedConfig:
7373
await writeJSON(configFile, modifiedConfig)
7474
}
7575

76-
export const resolveModuleRoot = (moduleName) => {
76+
export const resolveModuleRoot = (moduleName, paths = [process.cwd()]) => {
7777
try {
78-
return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })))
78+
return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths })))
7979
} catch {
8080
return null
8181
}
@@ -162,6 +162,15 @@ export const configureHandlerFunctions = async ({
162162
`!${nextRoot}/dist/compiled/webpack/bundle4.js`,
163163
`!${nextRoot}/dist/compiled/webpack/bundle5.js`,
164164
)
165+
166+
// on Next 13.5+ there is no longer statically analyzable import to styled-jsx/style
167+
// so lambda fails to bundle it. Next require hooks actually try to resolve it
168+
// and fail if it is not bundled, so we forcefully add it to lambda.
169+
const styledJsxRoot = resolveModuleRoot('styled-jsx', [join(process.cwd(), nextRoot)])
170+
if (styledJsxRoot) {
171+
const styledJsxStyleModulePath = join(styledJsxRoot, 'style.js')
172+
netlifyConfig.functions[functionName].included_files.push(styledJsxStyleModulePath)
173+
}
165174
}
166175

167176
excludedModules.forEach((moduleName) => {

packages/runtime/src/helpers/edge.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getRequiredServerFiles, NextConfig } from './config'
1616
import { getPluginVersion } from './functionsMetaData'
1717
import { makeLocaleOptional, stripLookahead, transformCaptureGroups } from './matchers'
1818
import { RoutesManifest } from './types'
19+
1920
// This is the format as of [email protected]
2021
interface EdgeFunctionDefinitionV1 {
2122
env: string[]
@@ -38,7 +39,7 @@ export interface MiddlewareMatcher {
3839

3940
// This is the format after [email protected]
4041
interface EdgeFunctionDefinitionV2 {
41-
env: string[]
42+
env?: string[]
4243
files: string[]
4344
name: string
4445
page: string
@@ -376,7 +377,6 @@ export const writeEdgeFunctions = async ({
376377
const { publish } = netlifyConfig.build
377378
const nextConfigFile = await getRequiredServerFiles(publish)
378379
const nextConfig = nextConfigFile.config
379-
const usesAppDir = nextConfig.experimental?.appDir
380380

381381
await copy(getEdgeTemplatePath('../vendor'), join(edgeFunctionRoot, 'vendor'))
382382
await copy(getEdgeTemplatePath('../edge-shared'), join(edgeFunctionRoot, 'edge-shared'))
@@ -462,8 +462,7 @@ export const writeEdgeFunctions = async ({
462462
function: functionName,
463463
name: edgeFunctionDefinition.name,
464464
pattern,
465-
// cache: "manual" is currently experimental, so we restrict it to sites that use experimental appDir
466-
cache: usesAppDir ? 'manual' : undefined,
465+
cache: 'manual',
467466
generator,
468467
})
469468
// pages-dir page routes also have a data route. If there's a match, add an entry mapping that to the function too
@@ -473,7 +472,7 @@ export const writeEdgeFunctions = async ({
473472
function: functionName,
474473
name: edgeFunctionDefinition.name,
475474
pattern: dataRoute,
476-
cache: usesAppDir ? 'manual' : undefined,
475+
cache: 'manual',
477476
generator,
478477
})
479478
}

packages/runtime/src/helpers/functions.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { getResolverForPages, getResolverForSourceFiles } from '../templates/get
2828
import { ApiConfig, extractConfigFromFile, isEdgeConfig } from './analysis'
2929
import { getRequiredServerFiles } from './config'
3030
import { getDependenciesOfFile, getServerFile, getSourceFileForPage } from './files'
31-
import { writeFunctionConfiguration } from './functionsMetaData'
31+
import { writeFunctionConfiguration, useRequireHooks } from './functionsMetaData'
3232
import { pack } from './pack'
3333
import { ApiRouteType } from './types'
3434
import { getFunctionNameForPage } from './utils'
@@ -132,11 +132,13 @@ export const generateFunctions = async (
132132
}
133133

134134
const writeHandler = async (functionName: string, functionTitle: string, isODB: boolean) => {
135+
const useHooks = await useRequireHooks()
135136
const handlerSource = getHandler({
136137
isODB,
137138
publishDir,
138139
appDir: relative(functionDir, appDir),
139140
nextServerModuleRelativeLocation,
141+
useHooks,
140142
})
141143
await ensureDir(join(functionsDir, functionName))
142144

packages/runtime/src/helpers/functionsMetaData.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { existsSync, readJSON, writeFile } from 'fs-extra'
22
import { join } from 'pathe'
3+
import { satisfies } from 'semver'
34

45
import { NEXT_PLUGIN, NEXT_PLUGIN_NAME } from '../constants'
56

@@ -17,8 +18,8 @@ const getNextRuntimeVersion = async (packageJsonPath: string, useNodeModulesPath
1718

1819
const PLUGIN_PACKAGE_PATH = '.netlify/plugins/package.json'
1920

20-
const nextPluginVersion = async () => {
21-
const moduleRoot = resolveModuleRoot(NEXT_PLUGIN)
21+
const nextPluginVersion = async (module?: string) => {
22+
const moduleRoot = resolveModuleRoot(module || NEXT_PLUGIN)
2223
const nodeModulesPath = moduleRoot ? join(moduleRoot, 'package.json') : null
2324

2425
return (
@@ -31,6 +32,8 @@ const nextPluginVersion = async () => {
3132

3233
export const getPluginVersion = async () => `${NEXT_PLUGIN_NAME}@${await nextPluginVersion()}`
3334

35+
export const useRequireHooks = async () => satisfies(await nextPluginVersion('next'), '13.3.3 - 13.4.9')
36+
3437
// The information needed to create a function configuration file
3538
export interface FunctionInfo {
3639
// The name of the function, e.g. `___netlify-handler`

packages/runtime/src/helpers/utils.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ export const getFunctionNameForPage = (page: string, background = false) =>
2424
.replace(DYNAMIC_PARAMETER_REGEX, '_$1-PARAM')
2525
.replace(RESERVED_FILENAME, '_')}-${background ? 'background' : 'handler'}`
2626

27-
type ExperimentalConfigWithLegacy = ExperimentalConfig & {
27+
export type ExperimentalConfigWithLegacy = ExperimentalConfig & {
2828
images?: Pick<ImageConfigComplete, 'remotePatterns'>
29+
appDir?: boolean
2930
}
3031

3132
export const toNetlifyRoute = (nextRoute: string): Array<string> => {

packages/runtime/src/index.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ import {
3131
getSSRLambdas,
3232
} from './helpers/functions'
3333
import { generateRedirects, generateStaticRedirects } from './helpers/redirects'
34-
import { shouldSkip, isNextAuthInstalled, getCustomImageResponseHeaders, getRemotePatterns } from './helpers/utils'
34+
import {
35+
shouldSkip,
36+
isNextAuthInstalled,
37+
getCustomImageResponseHeaders,
38+
getRemotePatterns,
39+
ExperimentalConfigWithLegacy,
40+
} from './helpers/utils'
3541
import {
3642
verifyNetlifyBuildVersion,
3743
checkNextSiteHasBuilt,
@@ -248,7 +254,11 @@ const plugin: NetlifyPlugin = {
248254
await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`))
249255
const nextConfig = await getNextConfig({ publish, failBuild })
250256

251-
const { basePath, appDir, experimental } = nextConfig
257+
const {
258+
basePath,
259+
appDir,
260+
experimental,
261+
}: { basePath: string; appDir?: string; experimental: ExperimentalConfigWithLegacy } = nextConfig
252262

253263
generateCustomHeaders(nextConfig, headers)
254264

packages/runtime/src/templates/getHandler.ts

+20-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Bridge as NodeBridge } from '@vercel/node-bridge/bridge'
44
import { outdent as javascript } from 'outdent'
55

66
import type { NextConfig } from '../helpers/config'
7+
import { ExperimentalConfigWithLegacy } from '../helpers/utils'
78

89
import type { NextServerType } from './handlerUtils'
910
import type { NetlifyNextServerType } from './server'
@@ -39,11 +40,20 @@ type MakeHandlerParams = {
3940
NextServer: NextServerType
4041
staticManifest: Array<[string, string]>
4142
mode: 'ssr' | 'odb'
43+
useHooks: boolean
4244
}
4345

4446
// We return a function and then call `toString()` on it to serialise it as the launcher function
4547
// eslint-disable-next-line max-lines-per-function
46-
const makeHandler = ({ conf, app, pageRoot, NextServer, staticManifest = [], mode = 'ssr' }: MakeHandlerParams) => {
48+
const makeHandler = ({
49+
conf,
50+
app,
51+
pageRoot,
52+
NextServer,
53+
staticManifest = [],
54+
mode = 'ssr',
55+
useHooks,
56+
}: MakeHandlerParams) => {
4757
// Change working directory into the site root, unless using Nx, which moves the
4858
// dist directory and handles this itself
4959
const dir = path.resolve(__dirname, app)
@@ -57,10 +67,13 @@ const makeHandler = ({ conf, app, pageRoot, NextServer, staticManifest = [], mod
5767
require.resolve('./pages.js')
5868
} catch {}
5969

70+
const { appDir }: ExperimentalConfigWithLegacy = conf.experimental
6071
// Next 13.4 conditionally uses different React versions and we need to make sure we use the same one
61-
overrideRequireHooks(conf)
72+
// With the release of 13.5 experimental.appDir is no longer used.
73+
// we will need to check if appDir is set and Next version before running requireHooks
74+
if (appDir && useHooks) overrideRequireHooks(conf.experimental)
6275
const NetlifyNextServer: NetlifyNextServerType = getNetlifyNextServer(NextServer)
63-
applyRequireHooks()
76+
if (appDir && useHooks) applyRequireHooks()
6477

6578
const ONE_YEAR_IN_SECONDS = 31536000
6679

@@ -205,6 +218,7 @@ export const getHandler = ({
205218
publishDir = '../../../.next',
206219
appDir = '../../..',
207220
nextServerModuleRelativeLocation,
221+
useHooks,
208222
}): string =>
209223
// This is a string, but if you have the right editor plugin it should format as js (e.g. bierner.comment-tagged-templates in VS Code)
210224
javascript/* javascript */ `
@@ -218,7 +232,7 @@ export const getHandler = ({
218232
const { promises } = require("fs");
219233
// We copy the file here rather than requiring from the node module
220234
const { Bridge } = require("./bridge");
221-
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, normalizePath } = require('./handlerUtils')
235+
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, normalizePath, nextVersionNum } = require('./handlerUtils')
222236
const { overrideRequireHooks, applyRequireHooks } = require("./requireHooks")
223237
const { getNetlifyNextServer } = require("./server")
224238
const NextServer = require(${JSON.stringify(nextServerModuleRelativeLocation)}).default
@@ -232,7 +246,7 @@ export const getHandler = ({
232246
const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "server"));
233247
exports.handler = ${
234248
isODB
235-
? `builder((${makeHandler.toString()})({ conf: config, app: "${appDir}", pageRoot, NextServer, staticManifest, mode: 'odb' }));`
236-
: `(${makeHandler.toString()})({ conf: config, app: "${appDir}", pageRoot, NextServer, staticManifest, mode: 'ssr' });`
249+
? `builder((${makeHandler.toString()})({ conf: config, app: "${appDir}", pageRoot, NextServer, staticManifest, mode: 'odb', useHooks: ${useHooks}}));`
250+
: `(${makeHandler.toString()})({ conf: config, app: "${appDir}", pageRoot, NextServer, staticManifest, mode: 'ssr', useHooks: ${useHooks}});`
237251
}
238252
`

packages/runtime/src/templates/requireHooks.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55

66
import mod from 'module'
77

8-
import type { NextConfig } from '../helpers/config'
8+
import type { ExperimentalConfigWithLegacy } from '../helpers/utils'
99

1010
const resolveFilename = (mod as any)._resolveFilename
1111
const requireHooks = new Map<string, Map<string, string>>()
1212

13-
export const overrideRequireHooks = (config: NextConfig) => {
14-
setRequireHooks(config)
13+
export const overrideRequireHooks = (experimental: ExperimentalConfigWithLegacy) => {
14+
setRequireHooks(experimental)
1515
resolveRequireHooks()
1616
}
1717

18-
const setRequireHooks = (config: NextConfig) => {
18+
const setRequireHooks = (experimental: ExperimentalConfigWithLegacy) => {
1919
requireHooks.set(
2020
'default',
2121
new Map([
@@ -24,8 +24,8 @@ const setRequireHooks = (config: NextConfig) => {
2424
]),
2525
)
2626

27-
if (config.experimental.appDir) {
28-
if (config.experimental.serverActions) {
27+
if (experimental.appDir) {
28+
if (experimental.serverActions) {
2929
requireHooks.set(
3030
'experimental',
3131
new Map([

packages/runtime/src/templates/server.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { PrerenderManifest } from 'next/dist/build'
66
import type { BaseNextResponse } from 'next/dist/server/base-http'
77
import type { NodeRequestHandler, Options } from 'next/dist/server/next-server'
88

9+
import { ExperimentalConfigWithLegacy } from '../helpers/utils'
10+
911
import {
1012
netlifyApiFetch,
1113
NextServerType,
@@ -19,6 +21,9 @@ import {
1921
interface NetlifyConfig {
2022
revalidateToken?: string
2123
}
24+
interface NextConfigWithAppDir extends NextConfig {
25+
experimental: ExperimentalConfigWithLegacy
26+
}
2227

2328
// eslint-disable-next-line max-lines-per-function
2429
const getNetlifyNextServer = (NextServer: NextServerType) => {
@@ -53,7 +58,9 @@ const getNetlifyNextServer = (NextServer: NextServerType) => {
5358
const { url, headers } = req
5459

5560
// conditionally use the prebundled React module
56-
this.netlifyPrebundleReact(url, this.nextConfig, parsedUrl)
61+
// PrebundledReact should only apply when appDir is set it falls between the specified Next versions
62+
const { experimental }: NextConfigWithAppDir = this.nextConfig
63+
if (experimental?.appDir) this.netlifyPrebundleReact(url, this.nextConfig, parsedUrl)
5764

5865
// intercept on-demand revalidation requests and handle with the Netlify API
5966
if (headers['x-prerender-revalidate'] && this.netlifyConfig.revalidateToken) {

test/index.spec.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ describe('onBuild()', () => {
455455
`!node_modules/next/dist/next-server/server/lib/squoosh/**/*.wasm`,
456456
'!node_modules/next/dist/compiled/webpack/bundle4.js',
457457
'!node_modules/next/dist/compiled/webpack/bundle5.js',
458+
'node_modules/styled-jsx/style.js',
458459
'!node_modules/sharp/**/*',
459460
]
460461
// Relative paths in Windows are different
@@ -558,10 +559,10 @@ describe('onBuild()', () => {
558559
expect(existsSync(odbHandlerFile)).toBeTruthy()
559560

560561
expect(readFileSync(handlerFile, 'utf8')).toMatch(
561-
`({ conf: config, app: "../../..", pageRoot, NextServer, staticManifest, mode: 'ssr' })`,
562+
`({ conf: config, app: "../../..", pageRoot, NextServer, staticManifest, mode: 'ssr', useHooks: false})`,
562563
)
563564
expect(readFileSync(odbHandlerFile, 'utf8')).toMatch(
564-
`({ conf: config, app: "../../..", pageRoot, NextServer, staticManifest, mode: 'odb' })`,
565+
`({ conf: config, app: "../../..", pageRoot, NextServer, staticManifest, mode: 'odb', useHooks: false})`,
565566
)
566567
expect(readFileSync(handlerFile, 'utf8')).toMatch(`require("../../../.next/required-server-files.json")`)
567568
expect(readFileSync(odbHandlerFile, 'utf8')).toMatch(`require("../../../.next/required-server-files.json")`)

test/templates/server.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ describe('the netlify next server', () => {
296296
await requestHandler(new NodeNextRequest(mockReq), new NodeNextResponse(mockRes))
297297

298298
// eslint-disable-next-line no-underscore-dangle
299-
expect(process.env.__NEXT_PRIVATE_PREBUNDLED_REACT).toBe('')
299+
expect(process.env.__NEXT_PRIVATE_PREBUNDLED_REACT).toBeFalsy()
300300
})
301301

302302
it('resolves the prebundled react version for app routes', async () => {

test/test-utils.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import path, { dirname } from 'path'
22

33
import cpy from 'cpy'
4-
import { writeJSON, existsSync, ensureDir, readJson, copy } from 'fs-extra'
4+
import { writeJSON, writeFile, existsSync, ensureDir, readJson, copy } from 'fs-extra'
55
import { dir as getTmpDir } from 'tmp-promise'
66

77
const FIXTURES_DIR = `${__dirname}/fixtures`
@@ -26,7 +26,7 @@ const rewriteAppDir = async function (dir = '.next') {
2626

2727
// Move .next from sample project to current directory
2828
export const moveNextDist = async function (dir = '.next', copyMods = false) {
29-
await (copyMods ? copyModules(['next', 'sharp']) : stubModules(['next', 'sharp']))
29+
await (copyMods ? copyModules(['next', 'sharp', 'styled-jsx']) : stubModules(['next', 'sharp', 'styled-jsx']))
3030
await ensureDir(dirname(dir))
3131
await copy(path.join(SAMPLE_PROJECT_DIR, '.next'), path.join(process.cwd(), dir))
3232

@@ -53,6 +53,9 @@ export const stubModules = async function (modules) {
5353
const dir = path.join(process.cwd(), 'node_modules', mod)
5454
await ensureDir(dir)
5555
await writeJSON(path.join(dir, 'package.json'), { name: mod })
56+
if (mod === `styled-jsx`) {
57+
await writeFile('style.js', '')
58+
}
5659
}
5760
}
5861

0 commit comments

Comments
 (0)