Skip to content

Commit 33483e9

Browse files
authored
Merge pull request #18 from netlify/rs/back-to-nft
feat: revert to NFT file tracing
2 parents ab2ec38 + 163111f commit 33483e9

File tree

10 files changed

+8311
-7498
lines changed

10 files changed

+8311
-7498
lines changed

package-lock.json

Lines changed: 8219 additions & 7347 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@
3434
},
3535
"homepage": "https://github.com/netlify/next-runtime-minimal#readme",
3636
"dependencies": {
37-
"@fastly/http-compute-js": "^1.1.0",
37+
"@fastly/http-compute-js": "github:orinokai/http-compute-js",
3838
"@netlify/blobs": "^1.6.0",
3939
"@netlify/build": "^29.20.6",
4040
"@netlify/functions": "^2.0.1",
41-
"esbuild": "^0.19.4",
41+
"@vercel/nft": "^0.24.3",
4242
"fs-extra": "^11.1.1"
4343
},
4444
"devDependencies": {

src/helpers/config.ts

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,23 @@
1-
import { readFileSync } from 'fs'
1+
import { readFile } from 'node:fs/promises'
22

3-
import type { NetlifyConfig } from '@netlify/build'
4-
import { copySync, moveSync } from 'fs-extra/esm'
5-
6-
import { MODULE_DIR, NETLIFY_PUBLISH_DIR, NETLIFY_TEMP_DIR } from './constants.js'
3+
import { RUNTIME_DIR } from './constants.js'
74

85
/**
9-
* Modify the user's next.config.js to use standalone mode and cache handler
6+
* Enable Next.js standalone mode
107
*/
11-
export const modifyNextConfig = () => {
12-
// revert any previous changes
13-
revertNextConfig()
14-
15-
// TODO: find a better way to do this because there's a ton of different ways
16-
// to configure next.config.js and the user could be using any of them
17-
// https://github.com/netlify/next-runtime-minimal/issues/12
18-
moveSync('next.config.js', `${NETLIFY_TEMP_DIR}/next.config.js`)
19-
copySync(`${MODULE_DIR}/../templates/next-config.cjs`, 'next.config.js')
8+
export const setBuildConfig = () => {
9+
process.env.NEXT_PRIVATE_STANDALONE = 'true'
2010
}
2111

22-
export const revertNextConfig = () => {
23-
// check if modified, then revert
24-
if (readFileSync('next.config.js').includes('Netlify generated code')) {
25-
moveSync(`${NETLIFY_TEMP_DIR}/next.config.js`, 'next.config.js', { overwrite: true })
12+
export const setRequestConfig = async () => {
13+
const runtimeConfig = JSON.parse(await readFile('./.next/required-server-files.json', 'utf-8'))
14+
15+
// set the path to the cache handler
16+
runtimeConfig.config.experimental = {
17+
...runtimeConfig.config.experimental,
18+
incrementalCacheHandlerPath: `${RUNTIME_DIR}/dist/templates/cache-handler.cjs`,
2619
}
27-
}
2820

29-
/**
30-
* Modify the user's netlify.toml to use our new publish directory
31-
* @param config Netlify config
32-
*/
33-
export const modifyNetlifyConfig = (config: NetlifyConfig) => {
34-
// TODO: once onEnd is fixed, we can remove this
35-
// https://github.com/netlify/cli/issues/6050
36-
config.build.publish = NETLIFY_PUBLISH_DIR
21+
// set config from the build output
22+
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(runtimeConfig.config)
3723
}

src/helpers/constants.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { resolve } from 'node:path'
22
import { fileURLToPath } from 'node:url'
33

4-
export const MODULE_DIR = fileURLToPath(new URL('.', import.meta.url))
4+
let dir
5+
try {
6+
dir = __dirname
7+
} catch {
8+
dir = fileURLToPath(new URL('.', import.meta.url))
9+
}
10+
11+
export const MODULE_DIR = dir
512
export const PLUGIN_DIR = resolve(`${MODULE_DIR}../..`)
13+
export const RUNTIME_DIR = resolve(`${MODULE_DIR}../..`)
614

7-
export const NETLIFY_PUBLISH_DIR = '.netlify/publish'
8-
export const NETLIFY_TEMP_DIR = '.netlify/temp'
15+
export const NEXT_BUILD_DIR = '.netlify/.next'
916

1017
export const FUNCTIONS_INTERNAL_DIR = '.netlify/functions-internal'
1118
export const FUNCTIONS_URL = '/.netlify/functions'

src/helpers/files.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import { existsSync } from 'node:fs'
22

3-
import { copySync } from 'fs-extra/esm'
3+
import { NetlifyPluginConstants } from '@netlify/build'
4+
import { copySync, moveSync } from 'fs-extra/esm'
45

5-
import { NETLIFY_PUBLISH_DIR } from './constants.js'
6+
import { NEXT_BUILD_DIR } from './constants.js'
67

78
/**
8-
* Ensure static assets get uploaded to the Netlify CDN
9-
* @param publishDir The publish directory
9+
* Move the Next.js build output to a temporary directory
1010
*/
11-
export const publishStaticAssets = (publishDir: string) => {
11+
export const moveBuildOutput = ({ PUBLISH_DIR }: NetlifyPluginConstants) => {
12+
moveSync(PUBLISH_DIR, NEXT_BUILD_DIR, { overwrite: true })
13+
}
14+
15+
/**
16+
* Move static assets so they are uploaded to the Netlify CDN
17+
*/
18+
export const moveStaticAssets = ({ PUBLISH_DIR }: NetlifyPluginConstants) => {
1219
if (existsSync('public')) {
13-
copySync('public', NETLIFY_PUBLISH_DIR, { overwrite: true })
20+
copySync('public', PUBLISH_DIR)
1421
}
15-
copySync(`${publishDir}/static/`, `${NETLIFY_PUBLISH_DIR}/_next/static`, { overwrite: true })
22+
copySync(`${NEXT_BUILD_DIR}/static/`, `${PUBLISH_DIR}/_next/static`)
1623
}

src/helpers/functions.ts

Lines changed: 26 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,54 @@
11
import { writeFileSync } from 'fs'
22

3-
import { NetlifyConfig } from '@netlify/build'
4-
import { build } from 'esbuild'
3+
import { nodeFileTrace } from '@vercel/nft'
54
import { copySync, emptyDirSync, readJsonSync, writeJSONSync } from 'fs-extra/esm'
65

7-
import {
8-
NETLIFY_TEMP_DIR,
9-
SERVER_HANDLER_DIR,
10-
SERVER_HANDLER_NAME,
11-
SERVER_HANDLER_URL,
12-
PLUGIN_DIR,
13-
} from './constants.js'
6+
import { NEXT_BUILD_DIR, SERVER_HANDLER_DIR, SERVER_HANDLER_NAME, PLUGIN_DIR } from './constants.js'
7+
8+
const pkg = readJsonSync(`${PLUGIN_DIR}/package.json`)
149

1510
/**
1611
* Create a Netlify function to run the Next.js server
1712
* @param publishDir The publish directory
1813
* @param config Netlify config
1914
*/
20-
export const createServerHandler = async (publishDir: string, config: NetlifyConfig) => {
21-
// clear the server handler directory
15+
export const createServerHandler = async () => {
16+
// clear the handler directory
2217
emptyDirSync(SERVER_HANDLER_DIR)
2318

24-
// copy the next.js standalone build output to the server handler directory
25-
copySync(`${publishDir}/standalone/.next`, `${SERVER_HANDLER_DIR}/.next`)
26-
copySync(`${publishDir}/standalone/node_modules`, `${SERVER_HANDLER_DIR}/node_modules`)
19+
// trace the handler dependencies
20+
const { fileList } = await nodeFileTrace(
21+
[`${PLUGIN_DIR}/dist/templates/server-handler.js`, `${PLUGIN_DIR}/dist/templates/cache-handler.cjs`],
22+
{ base: PLUGIN_DIR, ignore: ['package.json', 'node_modules/next/**'] },
23+
)
24+
25+
// copy the handler dependencies
26+
fileList.forEach((path) => {
27+
copySync(`${PLUGIN_DIR}/${path}`, `${SERVER_HANDLER_DIR}/${path}`)
28+
})
2729

28-
const pkg = readJsonSync(`${PLUGIN_DIR}/package.json`)
30+
// copy the next.js standalone build output to the handler directory
31+
copySync(`${NEXT_BUILD_DIR}/standalone/.next`, `${SERVER_HANDLER_DIR}/.next`)
32+
copySync(`${NEXT_BUILD_DIR}/standalone/node_modules`, `${SERVER_HANDLER_DIR}/node_modules`)
2933

30-
// create the server handler metadata file
34+
// create the handler metadata file
3135
writeJSONSync(`${SERVER_HANDLER_DIR}/${SERVER_HANDLER_NAME}.json`, {
3236
config: {
3337
name: 'Next.js Server Handler',
3438
generator: `${pkg.name}@${pkg.version}`,
3539
nodeBundler: 'none',
36-
// TODO: remove the final include when Netlify Functions v2 fixes the default exports bug
37-
includedFiles: ['.next/**', 'node_modules/**', `${SERVER_HANDLER_NAME}*`],
40+
includedFiles: [`${SERVER_HANDLER_NAME}*`, 'package.json', 'dist/**', '.next/**', 'node_modules/**'],
3841
includedFilesBasePath: SERVER_HANDLER_DIR,
3942
},
4043
version: 1,
4144
})
4245

43-
// bundle the cache handler
44-
await build({
45-
entryPoints: [`${PLUGIN_DIR}/dist/templates/cache-handler.js`],
46-
bundle: true,
47-
platform: 'node',
48-
target: ['node18'],
49-
format: 'cjs',
50-
outfile: `${SERVER_HANDLER_DIR}/${SERVER_HANDLER_NAME}-cache.js`,
51-
})
46+
// config ESM
47+
writeFileSync(`${SERVER_HANDLER_DIR}/package.json`, JSON.stringify({ type: 'module' }))
5248

53-
// bundle the server handler
54-
await build({
55-
entryPoints: [`${PLUGIN_DIR}/dist/templates/server-handler.js`],
56-
bundle: true,
57-
platform: 'node',
58-
target: ['node18'],
59-
format: 'esm',
60-
external: ['next'],
61-
outfile: `${SERVER_HANDLER_DIR}/${SERVER_HANDLER_NAME}-actual.mjs`,
62-
})
63-
64-
// TODO: remove when Netlify Functions v2 fixes the default exports bug
65-
// https://github.com/netlify/pod-dev-foundations/issues/599
49+
// write the root handler file
6650
writeFileSync(
67-
`${SERVER_HANDLER_DIR}/${SERVER_HANDLER_NAME}.mjs`,
68-
`import handler from './${SERVER_HANDLER_NAME}-actual.mjs';export default (request) => handler(request);`,
51+
`${SERVER_HANDLER_DIR}/${SERVER_HANDLER_NAME}.js`,
52+
`import handler from './dist/templates/server-handler.js';export default handler;export const config = { path: '/*' }`,
6953
)
70-
71-
// TODO: remove this when we can use inline config
72-
// https://github.com/netlify/next-runtime-minimal/issues/13
73-
config.redirects ||= []
74-
config.redirects.push({ from: `/*`, to: SERVER_HANDLER_URL, status: 200 })
75-
}
76-
77-
export const createCacheHandler = async () => {
78-
await build({
79-
entryPoints: [`${PLUGIN_DIR}/dist/templates/cache-handler.js`],
80-
bundle: true,
81-
platform: 'node',
82-
target: ['node18'],
83-
format: 'cjs',
84-
outfile: `${NETLIFY_TEMP_DIR}/cache-handler.js`,
85-
})
8654
}

src/index.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
import type { NetlifyPluginOptions } from '@netlify/build'
22

3-
import { modifyNetlifyConfig, modifyNextConfig, revertNextConfig } from './helpers/config.js'
4-
import { publishStaticAssets } from './helpers/files.js'
5-
import { createServerHandler, createCacheHandler } from './helpers/functions.js'
3+
import { setBuildConfig } from './helpers/config.js'
4+
import { moveStaticAssets, moveBuildOutput } from './helpers/files.js'
5+
import { createServerHandler } from './helpers/functions.js'
66

77
type NetlifyPluginOptionsWithFlags = NetlifyPluginOptions & { featureFlags?: Record<string, unknown> }
88

9-
export const onPreBuild = async () => {
10-
await createCacheHandler()
11-
modifyNextConfig()
9+
export const onPreBuild = () => {
10+
setBuildConfig()
1211
}
1312

14-
export const onBuild = async ({ constants, netlifyConfig }: NetlifyPluginOptionsWithFlags) => {
15-
publishStaticAssets(constants.PUBLISH_DIR)
16-
await createServerHandler(constants.PUBLISH_DIR, netlifyConfig)
17-
modifyNetlifyConfig(netlifyConfig)
18-
}
19-
20-
export const onEnd = () => {
21-
revertNextConfig()
13+
export const onBuild = async ({ constants }: NetlifyPluginOptionsWithFlags) => {
14+
moveBuildOutput(constants)
15+
moveStaticAssets(constants)
16+
await createServerHandler()
2217
}
File renamed without changes.

src/templates/next-config.cts

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/templates/server-handler.ts

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
1-
import { readFile } from 'node:fs/promises'
2-
import { fileURLToPath } from 'node:url'
3-
41
import { toComputeResponse, toReqRes } from '@fastly/http-compute-js'
2+
import type { WorkerRequestHandler } from 'next/dist/server/lib/types.js'
53

6-
import { SERVER_HANDLER_NAME } from '../helpers/constants.js'
7-
8-
const dir = fileURLToPath(new URL('.', import.meta.url))
9-
10-
// json config generated at build time
11-
const requiredServerFiles = JSON.parse(await readFile('./.next/required-server-files.json', 'utf-8'))
4+
import { RUNTIME_DIR } from '../helpers/constants.js'
125

13-
// set the path to the cache handler
14-
requiredServerFiles.config.experimental = {
15-
...requiredServerFiles.config.experimental,
16-
incrementalCacheHandlerPath: `${dir}/${SERVER_HANDLER_NAME}-cache.js`,
17-
}
18-
19-
// set config from the build output
20-
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(requiredServerFiles.config)
6+
let nextHandler: WorkerRequestHandler
217

228
export default async (request: Request) => {
23-
const { getRequestHandlers } = await import('next/dist/server/lib/start-server.js')
24-
25-
// let Next.js initialize and create the request handler
26-
const [nextHandler] = await getRequestHandlers({
27-
port: 3000,
28-
hostname: 'localhost',
29-
dir,
30-
isDev: false,
31-
})
9+
if (!nextHandler) {
10+
// set the server config
11+
const { setRequestConfig } = await import('../helpers/config.js')
12+
await setRequestConfig()
13+
14+
// let Next.js initialize and create the request handler
15+
const { getRequestHandlers } = await import('next/dist/server/lib/start-server.js')
16+
;[nextHandler] = await getRequestHandlers({
17+
port: 3000,
18+
hostname: 'localhost',
19+
dir: RUNTIME_DIR,
20+
isDev: false,
21+
})
22+
}
3223

3324
const { req, res } = toReqRes(request)
3425

0 commit comments

Comments
 (0)