@wular/pnext 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/dist/server/_chunks/{aliases-j47a10ce.js → aliases-pycbtjt1.js} +7 -5
  2. package/dist/server/_chunks/{analyze-arswqe6s.js → analyze-4d9e00g9.js} +17 -13
  3. package/dist/server/_chunks/{boot-af0mv3dy.js → boot-bhemypxz.js} +13 -11
  4. package/dist/server/_chunks/{build-spy3dxp4.js → build-c991dv65.js} +7 -5
  5. package/dist/server/_chunks/{build-t9h0hec3.js → build-kh5e3x48.js} +977 -1186
  6. package/dist/server/_chunks/{build-tier-m7yvqz61.js → build-tier-0a96kmx3.js} +59 -75
  7. package/dist/server/_chunks/{compile-53j02j27.js → compile-040r79gj.js} +2 -2
  8. package/dist/server/_chunks/{config-n2z7yxps.js → config-0tvz03a6.js} +6 -4
  9. package/dist/server/_chunks/{config-1yzwexq6.js → config-27rj7830.js} +12 -3
  10. package/dist/server/_chunks/{create-meec9hs6.js → create-9rvwshsp.js} +4 -3
  11. package/dist/server/_chunks/{dev-fjmqg6en.js → dev-ex9exynt.js} +2 -2
  12. package/dist/server/_chunks/{generate-n9sh6hzt.js → generate-myw9hef7.js} +5 -4
  13. package/dist/server/_chunks/{index-91x12ykq.js → index-0r5eqfc7.js} +6 -1
  14. package/dist/server/_chunks/{index-3xbr4eyv.js → index-0yjv5v47.js} +3 -3
  15. package/dist/server/_chunks/{index-ncnes8c6.js → index-11xpbfej.js} +2 -2
  16. package/dist/server/_chunks/{index-357na4a8.js → index-141vcwk6.js} +1 -1
  17. package/dist/server/_chunks/{index-nyrba2kr.js → index-1pzm1hdy.js} +11 -11
  18. package/dist/server/_chunks/{index-fkp000p1.js → index-29pq0kax.js} +2 -2
  19. package/dist/server/_chunks/{index-wpfhfbs4.js → index-38xpdxwd.js} +4 -4
  20. package/dist/server/_chunks/{index-tvqkq6e6.js → index-3e075kny.js} +1 -1
  21. package/dist/server/_chunks/{index-kq8kpzye.js → index-3ycf5ckv.js} +3 -3
  22. package/dist/server/_chunks/{index-20ppawv7.js → index-4da76t62.js} +3 -3
  23. package/dist/server/_chunks/{index-cf4rvxv6.js → index-52k54ykj.js} +1 -1
  24. package/dist/server/_chunks/{index-x93hv0sc.js → index-71885zaq.js} +23 -10
  25. package/dist/server/_chunks/{index-jepjvcrf.js → index-8ct7rqy3.js} +1 -1
  26. package/dist/server/_chunks/{index-sjpckxqn.js → index-9f49w76j.js} +1 -1
  27. package/dist/server/_chunks/{index-4hgjrqh7.js → index-9hz3s0m8.js} +2 -2
  28. package/dist/server/_chunks/{index-9n2914ww.js → index-akkrvjn6.js} +1 -1
  29. package/dist/server/_chunks/{index-9ych9ma8.js → index-b7j671dj.js} +2 -2
  30. package/dist/server/_chunks/index-bzd2ef7t.js +10 -0
  31. package/dist/server/_chunks/{index-p77b4kcx.js → index-cn86z041.js} +1 -1
  32. package/dist/server/_chunks/{index-hkdfj58q.js → index-cvr4z22m.js} +1 -1
  33. package/dist/server/_chunks/{index-y2jr2n72.js → index-d158y67j.js} +4 -4
  34. package/dist/server/_chunks/{index-yz54pxrh.js → index-d1jsmhhk.js} +4 -9
  35. package/dist/server/_chunks/{index-ggk2j965.js → index-dgr390q7.js} +56 -50
  36. package/dist/server/_chunks/{index-sctcgyvv.js → index-drwv61cc.js} +2 -2
  37. package/dist/server/_chunks/{index-9hx2641q.js → index-entm8bmz.js} +1 -1
  38. package/dist/server/_chunks/{index-jx7bqvw1.js → index-evhd413j.js} +1 -1
  39. package/dist/server/_chunks/{index-1011cscr.js → index-fjv96re1.js} +61 -29
  40. package/dist/server/_chunks/{index-c0g2wf3z.js → index-gcmj16xy.js} +1 -1
  41. package/dist/server/_chunks/{index-ytjvpbm2.js → index-gkndxkxf.js} +3 -3
  42. package/dist/server/_chunks/index-hdbwtdv2.js +352 -0
  43. package/dist/server/_chunks/{index-z84ab1w0.js → index-he6n2bvp.js} +3 -3
  44. package/dist/server/_chunks/{index-eajaspjk.js → index-j0m1jxqa.js} +1 -1
  45. package/dist/server/_chunks/{index-4q2gbfwk.js → index-j681c48a.js} +2 -2
  46. package/dist/server/_chunks/index-jbj7ehef.js +50 -0
  47. package/dist/server/_chunks/{index-8k7pfejn.js → index-jn9rbhrx.js} +7 -7
  48. package/dist/server/_chunks/index-jt8e1pje.js +12 -0
  49. package/dist/server/_chunks/{index-eeaanp2e.js → index-kdhxpwyq.js} +3 -3
  50. package/dist/server/_chunks/{index-6e4fqv30.js → index-m63czgq5.js} +2 -0
  51. package/dist/server/_chunks/{index-2dv4y4wd.js → index-n629dy38.js} +16 -10
  52. package/dist/server/_chunks/{index-sv8m6789.js → index-nf3df4jy.js} +5 -5
  53. package/dist/server/_chunks/{index-06nyqntx.js → index-q3r9z8mq.js} +1 -1
  54. package/dist/server/_chunks/{index-3b3at80h.js → index-qcrvt6er.js} +6 -3
  55. package/dist/server/_chunks/{index-egykcqg3.js → index-rgj5mx84.js} +1 -1
  56. package/dist/server/_chunks/{index-vvmmknm1.js → index-rjegyfx4.js} +19 -15
  57. package/dist/server/_chunks/{index-ym67zqxp.js → index-tf4j19ge.js} +1 -1
  58. package/dist/server/_chunks/{index-5gyr4emz.js → index-tq3b39a5.js} +4 -4
  59. package/dist/server/_chunks/{index-qn4z5s3c.js → index-typcbcsv.js} +3 -3
  60. package/dist/server/_chunks/{index-11sfk4hb.js → index-vmr35jjw.js} +1 -1
  61. package/dist/server/_chunks/{index-danncfhy.js → index-wm36rj4v.js} +1 -1
  62. package/dist/server/_chunks/{index-3tf1xjsp.js → index-x2m17v9c.js} +11 -11
  63. package/dist/server/_chunks/{index-z4z9zv3b.js → index-x9c55pzy.js} +141 -3
  64. package/dist/server/_chunks/{index-k1g3epw0.js → index-xh1nbk46.js} +10 -10
  65. package/dist/server/_chunks/{index-0374j2b9.js → index-y4d02znn.js} +1 -1
  66. package/dist/server/_chunks/{index-qm6kmhqs.js → index-y7mzvq2q.js} +1 -1
  67. package/dist/server/_chunks/{index-rn8dyr1f.js → index-yrjkqtfj.js} +8 -48
  68. package/dist/server/_chunks/{index-tq0pc7eg.js → index-zfrwq472.js} +3 -3
  69. package/dist/server/_chunks/{loader-7yw0j98z.js → loader-5en7wpt5.js} +12 -10
  70. package/dist/server/_chunks/{modules-jn8bmbn8.js → modules-t92favpj.js} +14 -12
  71. package/dist/server/_chunks/{pipeline-yy9jhydf.js → pipeline-4t9q43ht.js} +25 -21
  72. package/dist/server/_chunks/{production-jar9ztm3.js → production-385bw8df.js} +14 -3
  73. package/dist/server/_chunks/{proxy-config-j84dgksn.js → proxy-config-60gprphe.js} +7 -5
  74. package/dist/server/_chunks/{proxy-w1d0wmsn.js → proxy-gfhmktbk.js} +12 -10
  75. package/dist/server/_chunks/{router-k1reahfn.js → router-xnrva921.js} +10 -7
  76. package/dist/server/_chunks/{routes-2s277fc6.js → routes-ht868pdc.js} +12 -10
  77. package/dist/server/_chunks/{run-pvd8bs1t.js → run-d40d63ag.js} +5 -4
  78. package/dist/server/_chunks/{server-mt4a994c.js → server-2p8fgqq2.js} +38 -34
  79. package/dist/server/_chunks/{tsconfig-defaults-1nb3308g.js → tsconfig-defaults-bcn34eqb.js} +5 -4
  80. package/dist/server/_chunks/{typegen-0qr0459b.js → typegen-dvtcjtaw.js} +15 -13
  81. package/dist/server/cli/index.js +12 -15
  82. package/dist/server/cli/start.js +35 -14
  83. package/dist/server/compat/next/cache.js +5 -5
  84. package/dist/server/compat/next/config-headers.js +8 -6
  85. package/dist/server/compat/next/config-loader.js +1 -1
  86. package/dist/server/compat/next/dist/server/web/spec-extension/revalidate.js +5 -5
  87. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-cache.js +5 -5
  88. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-no-store.js +5 -5
  89. package/dist/server/compat/next/dynamic.js +3 -14
  90. package/dist/server/compat/next/font/cache.js +6 -4
  91. package/dist/server/compat/next/font/local.js +7 -5
  92. package/dist/server/compat/next/font/runtime.js +7 -5
  93. package/dist/server/compat/next/headers.js +4 -4
  94. package/dist/server/compat/next/image/config.js +2 -2
  95. package/dist/server/compat/next/image/optimizer.js +3 -3
  96. package/dist/server/compat/next/image/static-metadata.js +4 -4
  97. package/dist/server/compat/next/legacy-request-apis.js +3 -3
  98. package/dist/server/compat/next/link-transform.js +4 -3
  99. package/dist/server/compat/next/navigation.js +5 -4
  100. package/dist/server/compat/next/redirects.js +8 -6
  101. package/dist/server/compat/next/resource-hints.js +2 -2
  102. package/dist/server/compat/next/rewrites.js +7 -5
  103. package/dist/server/compat/next/root-params.js +3 -3
  104. package/dist/server/compat/next/route-state.js +2 -2
  105. package/dist/server/compat/next/script.js +2 -2
  106. package/dist/server/compat/next/server.js +5 -5
  107. package/dist/server/generation.json +1 -1
  108. package/package.json +1 -1
  109. package/reference/compat.md +1 -1
  110. package/reference/config.md +2 -2
  111. package/reference/dev.md +1 -1
  112. package/reference/getting-started.md +1 -1
  113. package/reference/rendering.md +4 -4
  114. package/src/api/dynamic.tsx +84 -4
  115. package/src/cli/adapters/vercel-warm.ts +40 -29
  116. package/src/cli/adapters/vercel.ts +95 -1262
  117. package/src/cli/analyze.ts +3 -2
  118. package/src/cli/build/cache.ts +6 -6
  119. package/src/cli/build.ts +33 -33
  120. package/src/cli/index.ts +4 -6
  121. package/src/cli/serve/pipeline.ts +16 -10
  122. package/src/cli/serve/standalone-worker.ts +8 -0
  123. package/src/cli/serve/standalone.ts +703 -0
  124. package/src/cli/start.ts +29 -8
  125. package/src/client/build.ts +1 -1
  126. package/src/client/reference-stub.ts +2 -34
  127. package/src/compat/actions/extensions.ts +3 -2
  128. package/src/compat/bundler/require-context.ts +2 -1
  129. package/src/compat/bundler/webpack-loaders.ts +3 -2
  130. package/src/compat/bundler/worker.ts +2 -2
  131. package/src/compat/css/inline-css.ts +1 -1
  132. package/src/compat/export/build-complete.ts +1 -1
  133. package/src/compat/export/emit.ts +4 -4
  134. package/src/compat/export/standalone.ts +30 -65
  135. package/src/compat/image-optimizer/optimize.ts +1 -1
  136. package/src/compat/lifecycle/instrumentation-client.ts +4 -3
  137. package/src/compat/metadata-artifacts.ts +1 -1
  138. package/src/compat/next/config-loader.ts +3 -0
  139. package/src/compat/next/dynamic.tsx +10 -23
  140. package/src/compat/next/font/cache.ts +2 -1
  141. package/src/compat/next/font/runtime.ts +1 -1
  142. package/src/compat/register/image.ts +1 -1
  143. package/src/compat/typecheck/check.ts +6 -4
  144. package/src/config.ts +28 -5
  145. package/src/css/assets.ts +1 -1
  146. package/src/css/build.ts +16 -9
  147. package/src/dev/client-chunk-store.ts +2 -1
  148. package/src/dev/restart/client-key.ts +2 -1
  149. package/src/dev/restart/global-css.ts +2 -1
  150. package/src/dev/restart/route-bundle-key.ts +2 -1
  151. package/src/dev/restart/route-facts.ts +2 -1
  152. package/src/dev/server.ts +8 -7
  153. package/src/out-paths.ts +10 -0
  154. package/src/render/renderer.ts +1 -1
  155. package/src/resolve/dynamic.ts +4 -0
  156. package/src/resolve/engine.ts +2 -12
  157. package/src/resolve/scan-facts.ts +185 -1
  158. package/src/routing/metadata-files.ts +1 -1
  159. package/src/runtime/loader.ts +5 -3
  160. package/src/runtime/module-cache.ts +2 -1
  161. package/src/runtime/modules.ts +50 -16
  162. package/src/runtime/prebundle.ts +23 -3
  163. package/src/runtime/production.ts +105 -40
  164. package/src/runtime/release-graph.ts +21 -13
  165. package/src/runtime/vendor-build.ts +45 -23
  166. package/src/types.ts +2 -3
  167. package/src/utils/directive.ts +40 -0
  168. package/src/utils/fs.ts +7 -2
  169. package/src/utils/resource-query.ts +12 -0
  170. package/dist/server/_chunks/index-tqn2qnnn.js +0 -241
  171. package/dist/server/_chunks/index-y36a0ybw.js +0 -57
  172. package/src/cli/serve/entry.ts +0 -52
package/src/cli/start.ts CHANGED
@@ -4,8 +4,10 @@
4
4
  * dynamically once the port is bound, so neither the listen syscall nor the
5
5
  * ready banner waits on parsing it.
6
6
  */
7
+ import { existsSync } from 'node:fs'
7
8
  import { readFile } from 'node:fs/promises'
8
9
  import path from 'node:path'
10
+ import { pathToFileURL } from 'node:url'
9
11
  import { loadConfig } from '../config'
10
12
  import type { ResolvedConfig } from '../config'
11
13
  import { bootstrapCompat } from '../compat-bootstrap'
@@ -17,6 +19,7 @@ import { registerPrebundleResolve } from '../runtime/prebundle'
17
19
  import { browserHost, ensurePortFree, openBrowser, printServerReady } from './serve/ui'
18
20
  import type { BuildManifest } from '../types'
19
21
  import type { PeerAddressSource } from '../routing/forwarded'
22
+ import { buildIndexFile, buildManifestFile, linkStandaloneOrigins } from '../runtime/production'
20
23
 
21
24
  interface StartOptions {
22
25
  root?: string
@@ -35,8 +38,24 @@ export async function createRequestHandler(
35
38
  options: { root?: string; config?: ResolvedConfig; manifest?: BuildManifest } = {},
36
39
  ): Promise<RequestHandler> {
37
40
  registerPrebundleResolve()
41
+ const config = options.config ?? (await loadConfig(options.root, { serve: true }))
42
+ // A release this copy cannot serve in place runs on the framework copy it ships.
43
+ if (shipsElsewhere(config)) {
44
+ const shipped = await shippedStart(config)
45
+ return shipped.createRequestHandler({ root: options.root, manifest: options.manifest })
46
+ }
38
47
  const { createRequestHandler: create } = await import('./serve/pipeline')
39
- return create(options)
48
+ return create({ ...options, config })
49
+ }
50
+
51
+ /** A built release this copy cannot serve in place; a missing build falls through to its error. */
52
+ function shipsElsewhere(config: ResolvedConfig) {
53
+ return existsSync(buildIndexFile(config.outPath)) && !linkStandaloneOrigins(config.outPath)
54
+ }
55
+
56
+ function shippedStart(config: ResolvedConfig) {
57
+ const entry = pathToFileURL(path.join(config.outPath, 'server', 'entry.js')).href
58
+ return import(entry) as Promise<typeof import('./start')>
40
59
  }
41
60
 
42
61
  export async function maybeBuiltFile(
@@ -51,8 +70,14 @@ export async function start(options: StartOptions = {}) {
51
70
  const port = options.port ?? 3000
52
71
  // Next's documented default. Bun resolves 'localhost' to ::1 only, refusing IPv4 clients.
53
72
  const hostname = options.hostname ?? '0.0.0.0'
73
+ const config = await loadConfig(options.root, { serve: true })
74
+ // Delegate before compat bootstraps here, so only the shipped copy patches process globals.
75
+ if (shipsElsewhere(config)) {
76
+ await (await shippedStart(config)).start(options)
77
+ return
78
+ }
54
79
  // Export builds are not served; pnext serves its standalone output directly.
55
- const config = await guardUnservableOutputMode(options.root)
80
+ await guardUnservableOutputMode(config)
56
81
  const manifest = await readProductionManifest(config)
57
82
  await ensurePortFree(port, hostname)
58
83
 
@@ -110,9 +135,7 @@ function installProcessErrorHandlers(): void {
110
135
 
111
136
  export async function readProductionManifest(config: ResolvedConfig): Promise<BuildManifest> {
112
137
  try {
113
- return JSON.parse(
114
- await readFile(path.join(config.outPath, 'manifest.json'), 'utf8'),
115
- ) as BuildManifest
138
+ return JSON.parse(await readFile(buildManifestFile(config.outPath), 'utf8')) as BuildManifest
116
139
  } catch (error) {
117
140
  if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
118
141
  throw new Error("No production build found. Run 'pnext build' first.")
@@ -122,8 +145,7 @@ export async function readProductionManifest(config: ResolvedConfig): Promise<Bu
122
145
  }
123
146
 
124
147
  // Export output is unservable; pnext production output remains serveable for standalone apps.
125
- async function guardUnservableOutputMode(root?: string): Promise<ResolvedConfig> {
126
- const config = await loadConfig(root, { serve: true })
148
+ async function guardUnservableOutputMode(config: ResolvedConfig) {
127
149
  await bootstrapCompat(config)
128
150
  const mode = getBuildExtensions().compat.nextOutputMode()
129
151
  if (mode === 'export') {
@@ -142,7 +164,6 @@ async function guardUnservableOutputMode(root?: string): Promise<ResolvedConfig>
142
164
  'Use "node .next/standalone/server.js" instead.',
143
165
  )
144
166
  }
145
- return config
146
167
  }
147
168
 
148
169
  // Graceful stop: quit accepting connections, wait for in-flight after() flushes (bounded), then exit
@@ -1438,7 +1438,7 @@ async function staticImageModuleSource(config: ResolvedConfig, file: string) {
1438
1438
  const compat = await getAssetExtensions().staticAssetModule({ sourcePath, bytes, emit })
1439
1439
  const source = compat ?? coreStaticAssetModule(sourcePath, bytes, emit)
1440
1440
  for (const relative of emitted) {
1441
- const target = path.join(config.outPath, 'public', ...relative.split('/'))
1441
+ const target = path.join(config.outPath, 'static', ...relative.split('/'))
1442
1442
  await mkdir(path.dirname(target), { recursive: true })
1443
1443
  if (!existsSync(target)) await copyFile(sourcePath, target)
1444
1444
  }
@@ -11,6 +11,7 @@ import type { ResolvedConfig } from '../config'
11
11
  import { resolveImport } from '../resolve/imports'
12
12
  import { moduleExportNames, moduleExportStars } from '../resolve/scan-facts'
13
13
  import { readText } from '../utils/fs'
14
+ import { hasModuleDirective } from '../utils/directive'
14
15
 
15
16
  export const clientReferenceRuntimeFile = path.resolve(import.meta.dirname, 'reference.ts')
16
17
 
@@ -122,40 +123,7 @@ export async function clientReferenceExportNames(
122
123
  * linear and allocation-free.
123
124
  */
124
125
  export function hasUseClientDirective(source: string) {
125
- const isSpace = (index: number) => {
126
- const code = source.charCodeAt(index)
127
- return code === 32 || (code >= 9 && code <= 13) || (code > 127 && /\s/.test(source[index]!))
128
- }
129
- let i = 0
130
- for (;;) {
131
- while (i < source.length && isSpace(i)) i += 1
132
- if (source.startsWith('//', i)) {
133
- const newline = source.indexOf('\n', i)
134
- if (newline === -1) return false
135
- i = newline + 1
136
- continue
137
- }
138
- if (source.startsWith('/*', i)) {
139
- const end = source.indexOf('*/', i + 2)
140
- if (end === -1) return false
141
- i = end + 2
142
- continue
143
- }
144
- // Anything that is not a string literal ends the prologue.
145
- const quote = source[i]
146
- if (quote !== '"' && quote !== "'") return false
147
- let end = i + 1
148
- while (end < source.length) {
149
- const char = source[end]!
150
- if (char === quote || char === '\n' || char === '\\') break
151
- end += 1
152
- }
153
- if (source[end] !== quote) return false
154
- if (source.slice(i + 1, end) === 'use client') return true
155
- i = end + 1
156
- while (i < source.length && isSpace(i)) i += 1
157
- if (source[i] === ';') i += 1
158
- }
126
+ return hasModuleDirective(source, 'use client')
159
127
  }
160
128
 
161
129
  function isInside(root: string, file: string) {
@@ -70,6 +70,7 @@ import { serverActionId, tagServerAction } from './server-tag'
70
70
  import { opensWithUseServerDirective } from './detect'
71
71
  import { inlineActionModuleKey, isInlineActionId } from './rewrite'
72
72
  import { reportActionErrorToUser } from '../lifecycle/error-funnel'
73
+ import { buildManifestFile } from '../../runtime/production'
73
74
 
74
75
  // ---------------------------------------------------------------------------
75
76
  // Build step: action discovery/bundling + server-reference manifest.
@@ -541,7 +542,7 @@ export async function ensureActionsRegistered(runtime: RequestRuntime): Promise<
541
542
  // client-action module set (normally armed by the build): render-time form/prop
542
543
  // tagging reads it to give module-level actions their stable wire ids.
543
544
  async function registerManifestActions(config: ResolvedConfig): Promise<void> {
544
- const manifestPath = path.join(config.outPath, 'manifest.json')
545
+ const manifestPath = buildManifestFile(config.outPath)
545
546
  let manifest: BuildManifest
546
547
  try {
547
548
  manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as BuildManifest
@@ -700,7 +701,7 @@ function requestWouldMiss(config: ResolvedConfig, pathname: string): boolean {
700
701
  }
701
702
 
702
703
  function servableFile(config: ResolvedConfig, pathname: string): boolean {
703
- const publicRoot = path.join(config.outPath, 'public')
704
+ const publicRoot = path.join(config.outPath, 'static')
704
705
  const trimmed = pathname.replace(/^\/+|\/+$/g, '')
705
706
  const file = trimmed ? path.join(publicRoot, trimmed) : publicRoot
706
707
  // Never let a traversal-shaped path claim to be servable (it is not).
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { existsSync, readdirSync, statSync } from 'node:fs'
14
14
  import path from 'node:path'
15
+ import { outCachePath } from '../../out-paths'
15
16
 
16
17
  /** Extensions whose files also get an extension-less request key (webpack resolves both). */
17
18
  const RESOLVABLE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.json']
@@ -210,7 +211,7 @@ function resolveContextDir(
210
211
  const direct = path.resolve(path.dirname(file), specifier)
211
212
  if (existsSync(direct)) return direct
212
213
  if (paths?.outPath && paths.workspaceRoot) {
213
- const cacheRoot = path.join(paths.outPath, 'cache', 'server')
214
+ const cacheRoot = path.join(outCachePath(paths.outPath), 'server')
214
215
  const relative = path.relative(cacheRoot, file)
215
216
  if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
216
217
  const segments = relative.split(path.sep)
@@ -25,6 +25,7 @@ import type { Plugin } from 'esbuild'
25
25
  import type { ResolvedConfig } from '../../config'
26
26
  import { rewriteSpecifierLiterals } from '../../resolve/scan-facts'
27
27
  import { turbopackLoaderRules, turbopackResolveAlias, type WebpackLoaderRule } from './config'
28
+ import { outCachePath } from '../../out-paths'
28
29
 
29
30
  interface LoaderFn {
30
31
  (this: LoaderContext, content: string | Buffer): unknown
@@ -293,7 +294,7 @@ function resolveLoaderRequest(root: string, context: string, request: string): P
293
294
  */
294
295
  function originalResourcePath(resourcePath: string, config: ResolvedConfig | undefined): string {
295
296
  if (config) {
296
- const cacheRoot = path.join(config.outPath, 'cache', 'server')
297
+ const cacheRoot = path.join(outCachePath(config.outPath), 'server')
297
298
  const rel = path.relative(cacheRoot, resourcePath)
298
299
  if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) {
299
300
  const segments = rel.split(path.sep)
@@ -451,7 +452,7 @@ async function emitAssetResourceModule(
451
452
  .basename(resourcePath, path.extname(resourcePath))
452
453
  .replace(/[^A-Za-z0-9_-]+/g, '-')
453
454
  const relative = path.posix.join('_next', 'static', 'media', `${base}.${hash}${ext}`)
454
- const target = path.join(config.outPath, 'public', ...relative.split('/'))
455
+ const target = path.join(config.outPath, 'static', ...relative.split('/'))
455
456
  await mkdir(path.dirname(target), { recursive: true })
456
457
  if (!existsSync(target)) await writeFile(target, bytes)
457
458
  const src = `/${relative}`
@@ -128,7 +128,7 @@ async function workerAssetModuleSource(config: ResolvedConfig, file: string): Pr
128
128
  source = `const src = ${JSON.stringify(src)};\nexport default { src };\nexport { src };\n`
129
129
  }
130
130
  for (const relative of emitted) {
131
- const target = path.join(config.outPath, 'public', ...relative.split('/'))
131
+ const target = path.join(config.outPath, 'static', ...relative.split('/'))
132
132
  await mkdir(path.dirname(target), { recursive: true })
133
133
  if (!existsSync(target)) await copyFile(sourcePath, target)
134
134
  }
@@ -208,7 +208,7 @@ async function bundleWorkerEntry(config: ResolvedConfig, entry: string): Promise
208
208
  const hash = createHash('sha256').update(output.contents).digest('hex').slice(0, 8)
209
209
  const base = path.basename(entry, path.extname(entry)).replace(/[^A-Za-z0-9_-]+/g, '-')
210
210
  const relative = path.posix.join('assets', 'workers', `${base}.${hash}.js`)
211
- const target = path.join(config.outPath, 'public', ...relative.split('/'))
211
+ const target = path.join(config.outPath, 'static', ...relative.split('/'))
212
212
  await mkdir(path.dirname(target), { recursive: true })
213
213
  await writeFile(target, output.contents)
214
214
  return `/${relative}`
@@ -65,7 +65,7 @@ export function inlineCssStylesheets(
65
65
  ): string[] | undefined {
66
66
  if (options.dev || suppressed.getStore() === true || !inlineCssEnabled(config)) return undefined
67
67
  const assets = options.assetNames.map(asset =>
68
- readCss(`${config.outPath}/public/assets/${asset}`),
68
+ readCss(`${config.outPath}/static/assets/${asset}`),
69
69
  )
70
70
  if (assets.some(css => css === undefined)) return undefined
71
71
  const css = [options.prependCss, ...assets].filter(Boolean).join('\n')
@@ -130,7 +130,7 @@ function collectOutputs(config: ResolvedConfig, manifest: BuildManifest): Adapte
130
130
  const prerenderedRoutes = new Set<string>()
131
131
 
132
132
  for (const [relative, metadata] of Object.entries(manifest.staticFiles ?? {})) {
133
- const filePath = path.join(config.outPath, 'public', relative)
133
+ const filePath = path.join(config.outPath, 'static', relative)
134
134
  // The manifest also records prerenders that produced no body on disk
135
135
  // (an empty response, `_not-found` / `_global-error`). Those are not
136
136
  // deployable artifacts: leave them out, and leave the owning route as a
@@ -42,7 +42,7 @@ export async function emitStaticExport({ config, manifest, log }: EmitStaticExpo
42
42
  // the running server - client runtime code fetches it unconditionally, and Next's own asset-404 tests
43
43
  // probe it as "a known valid asset path". Export mode additionally snapshots a standalone `out/` tree
44
44
  // below; the manifest files it needs are the same bytes, so write the server-served copy first.
45
- await log.step('build manifest', () => writeBuildManifests(path.join(config.outPath, 'public')))
45
+ await log.step('build manifest', () => writeBuildManifests(path.join(config.outPath, 'static')))
46
46
  const output = getNextConfig().output
47
47
  if (output === 'standalone') {
48
48
  await emitStandalone({ config, manifest, log })
@@ -74,7 +74,7 @@ async function writeExportTree(config: ResolvedConfig, manifest: BuildManifest):
74
74
  * can be pointed at their new location.
75
75
  */
76
76
  async function copyRuntimeAssets(config: ResolvedConfig, outDir: string): Promise<Set<string>> {
77
- const assetsDir = path.join(config.outPath, 'public', 'assets')
77
+ const assetsDir = path.join(config.outPath, 'static', 'assets')
78
78
  const assets = new Set<string>()
79
79
  for (const file of await listFiles(assetsDir)) {
80
80
  const relative = toPosixPath(path.relative(assetsDir, file))
@@ -91,7 +91,7 @@ async function copyPublicTree(
91
91
  outDir: string,
92
92
  assets: ReadonlySet<string>,
93
93
  ): Promise<void> {
94
- const publicDir = path.join(config.outPath, 'public')
94
+ const publicDir = path.join(config.outPath, 'static')
95
95
  for (const file of await listFiles(publicDir)) {
96
96
  const relative = toPosixPath(path.relative(publicDir, file))
97
97
  if (relative.startsWith('assets/')) continue
@@ -159,7 +159,7 @@ function exportPages(config: ResolvedConfig, manifest: BuildManifest): ExportPag
159
159
  if (metadata.kind !== 'page' || !relative.endsWith('.html')) continue
160
160
  const route = metadata.routeId ? routeById.get(metadata.routeId) : undefined
161
161
  if (route?.kind !== 'page') continue
162
- const htmlFile = path.join(config.outPath, 'public', ...relative.split('/'))
162
+ const htmlFile = path.join(config.outPath, 'static', ...relative.split('/'))
163
163
  pages.push({
164
164
  route,
165
165
  htmlFile,
@@ -1,18 +1,13 @@
1
1
  // `output: 'standalone'` emitter (COMPAT).
2
2
  //
3
- // Next's standalone build drops a self-contained `.next/standalone/` tree whose `server.js` boots the
4
- // production server on PORT/HOSTNAME. The e2e suites MOVE that folder to a fresh tmp dir, run
5
- // `node server.js`, wait for the `- Local:` ready banner, and expect route requests to 200. The trace
6
- // suite additionally reads a `.nft.json` beside each page.
7
- //
8
- // pnext has no ahead-of-time Node bundle to drop in, and its production server runs under Bun, so the
9
- // emitted `server.js` is a thin Node launcher that re-execs the real pnext server via Bun, pointed back
10
- // at the original build root. The child prints the `- Local:` banner itself, which the launcher inherits
11
- // straight onto its own stdout, so the harness's readiness regex fires without a proxy hop. The original
12
- // build tree outlives the moved standalone folder for the duration of the test, so absolute references
13
- // back into it are safe.
3
+ // Every pnext build writes `<outDir>/standalone/`, the self-contained server; with `output:
4
+ // 'standalone'` the out dir defaults to `.next` (compat/next/config-loader), so it lands where Next's
5
+ // does. Next's contract on top: a `server.js` that boots on PORT/HOSTNAME under `node server.js`, and
6
+ // the `.nft.json` traces its suites read. The e2e suites MOVE the folder to a fresh tmp dir, run
7
+ // `node server.js` and wait for the `- Local:` banner; `server.js` hands over to Bun, which serves the
8
+ // folder's own `server/entry.js`.
14
9
  import { existsSync, readFileSync } from 'node:fs'
15
- import { mkdir, rm, writeFile } from 'node:fs/promises'
10
+ import { mkdir, writeFile } from 'node:fs/promises'
16
11
  import path from 'node:path'
17
12
  import type { ResolvedConfig } from '../../config'
18
13
  import type { BuildManifest, RouteManifestEntry } from '../../types'
@@ -37,68 +32,38 @@ export async function emitStandalone({
37
32
  }
38
33
 
39
34
  async function writeStandalone(config: ResolvedConfig, manifest: BuildManifest): Promise<void> {
40
- const standaloneDir = path.join(config.root, '.next', 'standalone')
41
- await rm(standaloneDir, { recursive: true, force: true })
42
- await mkdir(standaloneDir, { recursive: true })
43
- await writeServerEntry(config, standaloneDir)
35
+ const standaloneDir = config.outPath
36
+ await writeFile(path.join(standaloneDir, 'server.js'), standaloneServerSource())
44
37
  await writeTraceFiles(config, standaloneDir)
45
38
  await writeMiddlewareManifest(manifest, standaloneDir)
46
39
  }
47
40
 
48
41
  /**
49
- * The self-contained launcher plus its package.json. The launcher is always CommonJS (a
50
- * `{ "type": "commonjs" }` sidecar pins the module system even when the app - and the tmp dir the test
51
- * moves the folder into - is ESM), so it runs unchanged under `node server.js` regardless of the
52
- * surrounding package type.
42
+ * Starts `server/entry.js` under Bun on the PORT/HOSTNAME the standalone contract passes through the
43
+ * environment. The child's `- Local:` banner is inherited onto this process's stdout, so the harness's
44
+ * readiness regex fires directly. Signals are forwarded so the harness's `killApp` (SIGTERM) tears the
45
+ * whole tree down and lets pending after() callbacks drain.
53
46
  */
54
- async function writeServerEntry(config: ResolvedConfig, standaloneDir: string): Promise<void> {
55
- await writeFile(
56
- path.join(standaloneDir, 'package.json'),
57
- `${JSON.stringify({ name: 'pnext-standalone', version: '0.0.0', type: 'commonjs', private: true }, null, 2)}\n`,
58
- )
59
- await writeFile(path.join(standaloneDir, 'server.js'), standaloneServerSource(config))
60
- }
47
+ function standaloneServerSource(): string {
48
+ return `// pnext standalone launcher, generated by pnext build (src/compat/export/standalone.ts).
49
+ import { spawn } from 'node:child_process';
61
50
 
62
- /**
63
- * Boots `pnext start` under Bun against the original build root, on the
64
- * PORT/HOSTNAME the standalone contract passes through the environment. The
65
- * child's `- Local:` banner is inherited onto this process's stdout, so the
66
- * harness's readiness regex fires directly. Signals are forwarded so the
67
- * harness's `killApp` (SIGTERM) tears the whole tree down and lets pending
68
- * after() callbacks drain.
69
- */
70
- function standaloneServerSource(config: ResolvedConfig): string {
71
- const cliPath = path.resolve(import.meta.dirname, '..', '..', 'cli', 'index.ts')
72
- return `'use strict';
73
- // pnext standalone launcher. Generated by pnext build; see
74
- // src/compat/export/standalone.ts. Spawned as \`node server.js\` from the moved
75
- // standalone directory; re-execs the real pnext server under Bun.
76
- const { spawn } = require('node:child_process');
77
-
78
- const PNEXT_CLI = ${JSON.stringify(cliPath)};
79
- const PNEXT_ROOT = ${JSON.stringify(config.root)};
80
- const port = String(process.env.PORT || '3000');
51
+ const port = Number(process.env.PORT || '3000');
81
52
  const hostname = process.env.HOSTNAME || '0.0.0.0';
53
+ const entry = new URL('./server/entry.js', import.meta.url).href;
54
+ const boot = \`const { start } = await import(\${JSON.stringify(entry)}); await start(\${JSON.stringify({ port, hostname })});\`;
82
55
 
83
- const child = spawn(
84
- 'bun',
85
- ['--conditions=react-server', PNEXT_CLI, 'start', '--port', port, '--hostname', hostname],
86
- {
87
- cwd: PNEXT_ROOT,
88
- env: {
89
- ...process.env,
90
- // Suppresses \`pnext start\`'s "output: standalone" warning (Next gates the
91
- // same warning on this private env being set by its standalone server).
92
- __NEXT_PRIVATE_STANDALONE_CONFIG: '1',
93
- PNEXT_COMPAT: 'next',
94
- BROWSER: 'none',
95
- NODE_ENV: 'production',
96
- PORT: port,
97
- HOSTNAME: hostname,
98
- },
99
- stdio: ['ignore', 'inherit', 'inherit'],
100
- }
101
- );
56
+ const child = spawn('bun', ['--conditions=react-server', '-e', boot], {
57
+ env: {
58
+ ...process.env,
59
+ // Suppresses \`pnext start\`'s "output: standalone" warning (Next gates the
60
+ // same warning on this private env being set by its standalone server).
61
+ __NEXT_PRIVATE_STANDALONE_CONFIG: '1',
62
+ BROWSER: 'none',
63
+ NODE_ENV: 'production',
64
+ },
65
+ stdio: ['ignore', 'inherit', 'inherit'],
66
+ });
102
67
 
103
68
  child.on('error', (error) => {
104
69
  console.error('pnext standalone: failed to start the pnext server:', error);
@@ -378,7 +378,7 @@ async function fetchUpstream(
378
378
  }
379
379
  const publicFile = await readPublicFile(
380
380
  [
381
- path.join(coreConfig.outPath, 'public'),
381
+ path.join(coreConfig.outPath, 'static'),
382
382
  coreConfig.publicPath,
383
383
  path.join(coreConfig.root, 'public'),
384
384
  ],
@@ -23,6 +23,7 @@ import { build } from '../../utils/esbuild'
23
23
  import type { ResolvedConfig } from '../../config'
24
24
  import { getNextConfig } from '../next/config-loader'
25
25
  import { instrumentationLookupRoots } from './instrumentation'
26
+ import { outCachePath } from '../../out-paths'
26
27
 
27
28
  const CLIENT_BASENAMES = [
28
29
  'instrumentation-client.ts',
@@ -90,10 +91,10 @@ export async function buildInstrumentationClient(config: ResolvedConfig): Promis
90
91
  const userFile = findInstrumentationClient(config)
91
92
  if (!userFile) return
92
93
  const modules = [...injectEntries(config), userFile]
93
- const workDir = path.join(config.outPath, 'cache', 'instrumentation-client')
94
+ const workDir = path.join(outCachePath(config.outPath), 'instrumentation-client')
94
95
  mkdirSync(workDir, { recursive: true })
95
96
  const entryFile = path.join(workDir, 'entry.ts')
96
- const outfile = path.join(config.outPath, 'public', INSTRUMENTATION_CLIENT_ASSET)
97
+ const outfile = path.join(config.outPath, 'static', INSTRUMENTATION_CLIENT_ASSET)
97
98
  mkdirSync(path.dirname(outfile), { recursive: true })
98
99
  await Bun.write(entryFile, entrySource(modules))
99
100
  const tsconfig = path.join(config.root, 'tsconfig.json')
@@ -116,7 +117,7 @@ let bundlePresence: boolean | undefined
116
117
 
117
118
  /** Whether the built instrumentation-client bundle exists (cached). */
118
119
  function hasInstrumentationClientBundle(config: ResolvedConfig): boolean {
119
- bundlePresence ??= existsSync(path.join(config.outPath, 'public', INSTRUMENTATION_CLIENT_ASSET))
120
+ bundlePresence ??= existsSync(path.join(config.outPath, 'static', INSTRUMENTATION_CLIENT_ASSET))
120
121
  return bundlePresence
121
122
  }
122
123
 
@@ -329,7 +329,7 @@ async function writeStaticMetadataArtifacts(
329
329
  metadata: StaticFileMetadata | undefined,
330
330
  ): Promise<void> {
331
331
  if (!metadata) return
332
- const source = path.join(outPath, 'public', relative)
332
+ const source = path.join(outPath, 'static', relative)
333
333
  const outputBase = path.join(root, 'server', 'app', toPosixPath(relative))
334
334
  await mkdir(path.dirname(outputBase), { recursive: true })
335
335
  const routeMeta = JSON.stringify(toRouteMeta(metadata))
@@ -488,6 +488,9 @@ function mapOverrides(config: NextConfigObject): NextConfigCoreOverrides {
488
488
  config.output !== 'export'
489
489
  ) {
490
490
  overrides.outDir = config.distDir
491
+ } else if (config.output === 'standalone') {
492
+ // Next's `.next/standalone`: the folder its standalone contract names.
493
+ overrides.outDir = '.next'
491
494
  }
492
495
  return overrides
493
496
  }
@@ -1,6 +1,5 @@
1
1
  /** @jsxImportSource preact */
2
2
  import { h, type ComponentType } from 'preact'
3
- import { lazy, Suspense } from 'preact/compat'
4
3
  import {
5
4
  dynamic as pnextDynamic,
6
5
  dynamicReferenceSymbol,
@@ -19,25 +18,17 @@ export default function dynamic<Props extends object = object>(
19
18
  // Injected by the server compile; see rewriteDynamicCallTargets.
20
19
  target?: DynamicTarget,
21
20
  ) {
22
- if (typeof loader === 'string') return pnextDynamic(loader, options, target)
23
-
24
21
  const coreDynamic = pnextDynamic(loader, options, target) as DynamicWithReference<Props>
25
- const Lazy = lazy(() => loader().then(module => ({ default: componentFromModule(module) })))
26
- const DynamicComponent = ((props: Props) => {
27
- const Loading = options.loading
28
- // ssr:false renders nothing on the server, so the client mount is a *hydration* whose only child
29
- // suspends. preact/compat nulls a hydrating vnode's type when it unmounts, and Suspense reuses the
30
- // same props.children vnode on its resolve re-render - the poisoned element then renders as text.
31
- // The hooks-based core component loads without suspending, so that boundary is never entered.
32
- if (options.ssr === false) {
33
- return h(
34
- 'pnext-dynamic',
35
- { style: { display: 'contents' } },
36
- !process.browser && typeof window === 'undefined' ? null : h(coreDynamic, props),
37
- )
38
- }
39
- return h(Suspense, { fallback: Loading ? h(Loading, props) : null }, h(Lazy, props))
40
- }) as DynamicWithReference<Props>
22
+ // Core's component server-renders and hydrates like Next, waiting for a chunk still loading.
23
+ if (typeof loader === 'string' || options.ssr !== false) return coreDynamic
24
+
25
+ // ssr:false renders nothing on the server; the client renders `loading`, then the component.
26
+ const DynamicComponent = ((props: Props) =>
27
+ h(
28
+ 'pnext-dynamic',
29
+ { style: { display: 'contents' } },
30
+ !process.browser && typeof window === 'undefined' ? null : h(coreDynamic, props),
31
+ )) as DynamicWithReference<Props>
41
32
 
42
33
  DynamicComponent[dynamicReferenceSymbol] = coreDynamic[dynamicReferenceSymbol]
43
34
  return DynamicComponent
@@ -48,7 +39,3 @@ export { dynamic }
48
39
  type DynamicWithReference<Props extends object> = ComponentType<Props> & {
49
40
  [dynamicReferenceSymbol]?: DynamicReference<Props>
50
41
  }
51
-
52
- function componentFromModule<Props>(module: DynamicModule<Props>) {
53
- return typeof module === 'function' ? module : module.default
54
- }
@@ -15,6 +15,7 @@ import { restartCacheEnabled } from '../../../dev/restart/enabled'
15
15
  import { frameworkFingerprint } from '../../../runtime/fingerprint'
16
16
  import { writeFileAtomic } from '../../../utils/fs'
17
17
  import type { MetadataLink } from '../../../types'
18
+ import { outCachePath } from '../../../out-paths'
18
19
 
19
20
  export interface PersistableFont {
20
21
  css: string
@@ -54,7 +55,7 @@ interface FontIndex {
54
55
  const indexes = new Map<string, FontIndex>()
55
56
 
56
57
  function fontIndex(config: ResolvedConfig): FontIndex {
57
- const file = path.join(config.outPath, 'cache', 'fonts', 'index.json')
58
+ const file = path.join(outCachePath(config.outPath), 'fonts', 'index.json')
58
59
  const existing = indexes.get(file)
59
60
  if (existing) return existing
60
61
  const created: FontIndex = { file, fonts: readIndex(file), dirty: false }
@@ -805,7 +805,7 @@ async function emitFontBytes(
805
805
  // Served from `/_next/static/media/` — files under the out dir's public/ map 1:1 to the URL path,
806
806
  // which is the only place dev and build both look them up.
807
807
  const mediaSegments = ['_next', 'static', 'media']
808
- const outDir = path.join(context.config.outPath, 'public', ...mediaSegments)
808
+ const outDir = path.join(context.config.outPath, 'static', ...mediaSegments)
809
809
  const file = path.join(outDir, filename)
810
810
  const asset = {
811
811
  ext,
@@ -122,7 +122,7 @@ async function writeImagesManifestStep(ctx: BuildStepContext): Promise<void> {
122
122
  async function mirrorStaticMedia(config: ResolvedConfig): Promise<void> {
123
123
  const { cp } = await import('node:fs/promises')
124
124
  for (const dir of ['media', 'immutable/media']) {
125
- const source = path.join(config.outPath, 'public', '_next', 'static', dir)
125
+ const source = path.join(config.outPath, 'static', '_next', 'static', dir)
126
126
  const target = path.join(config.root, '.next', 'static', dir)
127
127
  try {
128
128
  await cp(source, target, { recursive: true, force: true })
@@ -42,6 +42,7 @@ export function typecheckDisabled(): boolean {
42
42
  export interface TypecheckWorkerInput {
43
43
  root: string
44
44
  outPath: string
45
+ outRootPath: string
45
46
  compat: ResolvedConfig['compat']
46
47
  nextConfig: NextConfigObject
47
48
  }
@@ -64,6 +65,7 @@ export async function validateTypesOffThread(config: ResolvedConfig): Promise<vo
64
65
  const input: TypecheckWorkerInput = {
65
66
  root: config.root,
66
67
  outPath: config.outPath,
68
+ outRootPath: config.outRootPath,
67
69
  compat: config.compat,
68
70
  nextConfig: cloneable({
69
71
  typescript: next.typescript,
@@ -210,12 +212,12 @@ function appDiagnostic(file: string | undefined, config: ResolvedConfig): boolea
210
212
  if (resolved === generatedProjectPath(config)) return true
211
213
  if (!resolved.startsWith(config.root + path.sep)) return false
212
214
  if (resolved.includes(`${path.sep}node_modules${path.sep}`)) return false
213
- if (resolved.startsWith(path.resolve(config.outPath) + path.sep)) return false
215
+ if (resolved.startsWith(path.resolve(config.outRootPath) + path.sep)) return false
214
216
  return true
215
217
  }
216
218
 
217
219
  function generatedProjectPath(config: ResolvedConfig): string {
218
- return path.resolve(config.outPath, 'typecheck', 'tsconfig.json')
220
+ return path.resolve(config.outRootPath, 'typecheck', 'tsconfig.json')
219
221
  }
220
222
 
221
223
  /**
@@ -292,7 +294,7 @@ async function writeTypecheckProject(
292
294
  config: ResolvedConfig,
293
295
  native: string | undefined,
294
296
  ): Promise<string> {
295
- const dir = path.join(config.outPath, 'typecheck')
297
+ const dir = path.join(config.outRootPath, 'typecheck')
296
298
  await mkdir(dir, { recursive: true })
297
299
  const reactTypes = resolveTypesDir('react', config.root)
298
300
  const hasReactTypes = reactTypes !== undefined
@@ -420,7 +422,7 @@ async function writeTypecheckProject(
420
422
  }
421
423
 
422
424
  function preactTypeShimPath(config: ResolvedConfig): string {
423
- return path.join(config.outPath, 'typecheck', 'preact-compat.d.ts')
425
+ return path.join(config.outRootPath, 'typecheck', 'preact-compat.d.ts')
424
426
  }
425
427
 
426
428
  function preactTypeShim(preact: string, react: string): string {