@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.
- package/dist/server/_chunks/{aliases-j47a10ce.js → aliases-pycbtjt1.js} +7 -5
- package/dist/server/_chunks/{analyze-arswqe6s.js → analyze-4d9e00g9.js} +17 -13
- package/dist/server/_chunks/{boot-af0mv3dy.js → boot-bhemypxz.js} +13 -11
- package/dist/server/_chunks/{build-spy3dxp4.js → build-c991dv65.js} +7 -5
- package/dist/server/_chunks/{build-t9h0hec3.js → build-kh5e3x48.js} +977 -1186
- package/dist/server/_chunks/{build-tier-m7yvqz61.js → build-tier-0a96kmx3.js} +59 -75
- package/dist/server/_chunks/{compile-53j02j27.js → compile-040r79gj.js} +2 -2
- package/dist/server/_chunks/{config-n2z7yxps.js → config-0tvz03a6.js} +6 -4
- package/dist/server/_chunks/{config-1yzwexq6.js → config-27rj7830.js} +12 -3
- package/dist/server/_chunks/{create-meec9hs6.js → create-9rvwshsp.js} +4 -3
- package/dist/server/_chunks/{dev-fjmqg6en.js → dev-ex9exynt.js} +2 -2
- package/dist/server/_chunks/{generate-n9sh6hzt.js → generate-myw9hef7.js} +5 -4
- package/dist/server/_chunks/{index-91x12ykq.js → index-0r5eqfc7.js} +6 -1
- package/dist/server/_chunks/{index-3xbr4eyv.js → index-0yjv5v47.js} +3 -3
- package/dist/server/_chunks/{index-ncnes8c6.js → index-11xpbfej.js} +2 -2
- package/dist/server/_chunks/{index-357na4a8.js → index-141vcwk6.js} +1 -1
- package/dist/server/_chunks/{index-nyrba2kr.js → index-1pzm1hdy.js} +11 -11
- package/dist/server/_chunks/{index-fkp000p1.js → index-29pq0kax.js} +2 -2
- package/dist/server/_chunks/{index-wpfhfbs4.js → index-38xpdxwd.js} +4 -4
- package/dist/server/_chunks/{index-tvqkq6e6.js → index-3e075kny.js} +1 -1
- package/dist/server/_chunks/{index-kq8kpzye.js → index-3ycf5ckv.js} +3 -3
- package/dist/server/_chunks/{index-20ppawv7.js → index-4da76t62.js} +3 -3
- package/dist/server/_chunks/{index-cf4rvxv6.js → index-52k54ykj.js} +1 -1
- package/dist/server/_chunks/{index-x93hv0sc.js → index-71885zaq.js} +23 -10
- package/dist/server/_chunks/{index-jepjvcrf.js → index-8ct7rqy3.js} +1 -1
- package/dist/server/_chunks/{index-sjpckxqn.js → index-9f49w76j.js} +1 -1
- package/dist/server/_chunks/{index-4hgjrqh7.js → index-9hz3s0m8.js} +2 -2
- package/dist/server/_chunks/{index-9n2914ww.js → index-akkrvjn6.js} +1 -1
- package/dist/server/_chunks/{index-9ych9ma8.js → index-b7j671dj.js} +2 -2
- package/dist/server/_chunks/index-bzd2ef7t.js +10 -0
- package/dist/server/_chunks/{index-p77b4kcx.js → index-cn86z041.js} +1 -1
- package/dist/server/_chunks/{index-hkdfj58q.js → index-cvr4z22m.js} +1 -1
- package/dist/server/_chunks/{index-y2jr2n72.js → index-d158y67j.js} +4 -4
- package/dist/server/_chunks/{index-yz54pxrh.js → index-d1jsmhhk.js} +4 -9
- package/dist/server/_chunks/{index-ggk2j965.js → index-dgr390q7.js} +56 -50
- package/dist/server/_chunks/{index-sctcgyvv.js → index-drwv61cc.js} +2 -2
- package/dist/server/_chunks/{index-9hx2641q.js → index-entm8bmz.js} +1 -1
- package/dist/server/_chunks/{index-jx7bqvw1.js → index-evhd413j.js} +1 -1
- package/dist/server/_chunks/{index-1011cscr.js → index-fjv96re1.js} +61 -29
- package/dist/server/_chunks/{index-c0g2wf3z.js → index-gcmj16xy.js} +1 -1
- package/dist/server/_chunks/{index-ytjvpbm2.js → index-gkndxkxf.js} +3 -3
- package/dist/server/_chunks/index-hdbwtdv2.js +352 -0
- package/dist/server/_chunks/{index-z84ab1w0.js → index-he6n2bvp.js} +3 -3
- package/dist/server/_chunks/{index-eajaspjk.js → index-j0m1jxqa.js} +1 -1
- package/dist/server/_chunks/{index-4q2gbfwk.js → index-j681c48a.js} +2 -2
- package/dist/server/_chunks/index-jbj7ehef.js +50 -0
- package/dist/server/_chunks/{index-8k7pfejn.js → index-jn9rbhrx.js} +7 -7
- package/dist/server/_chunks/index-jt8e1pje.js +12 -0
- package/dist/server/_chunks/{index-eeaanp2e.js → index-kdhxpwyq.js} +3 -3
- package/dist/server/_chunks/{index-6e4fqv30.js → index-m63czgq5.js} +2 -0
- package/dist/server/_chunks/{index-2dv4y4wd.js → index-n629dy38.js} +16 -10
- package/dist/server/_chunks/{index-sv8m6789.js → index-nf3df4jy.js} +5 -5
- package/dist/server/_chunks/{index-06nyqntx.js → index-q3r9z8mq.js} +1 -1
- package/dist/server/_chunks/{index-3b3at80h.js → index-qcrvt6er.js} +6 -3
- package/dist/server/_chunks/{index-egykcqg3.js → index-rgj5mx84.js} +1 -1
- package/dist/server/_chunks/{index-vvmmknm1.js → index-rjegyfx4.js} +19 -15
- package/dist/server/_chunks/{index-ym67zqxp.js → index-tf4j19ge.js} +1 -1
- package/dist/server/_chunks/{index-5gyr4emz.js → index-tq3b39a5.js} +4 -4
- package/dist/server/_chunks/{index-qn4z5s3c.js → index-typcbcsv.js} +3 -3
- package/dist/server/_chunks/{index-11sfk4hb.js → index-vmr35jjw.js} +1 -1
- package/dist/server/_chunks/{index-danncfhy.js → index-wm36rj4v.js} +1 -1
- package/dist/server/_chunks/{index-3tf1xjsp.js → index-x2m17v9c.js} +11 -11
- package/dist/server/_chunks/{index-z4z9zv3b.js → index-x9c55pzy.js} +141 -3
- package/dist/server/_chunks/{index-k1g3epw0.js → index-xh1nbk46.js} +10 -10
- package/dist/server/_chunks/{index-0374j2b9.js → index-y4d02znn.js} +1 -1
- package/dist/server/_chunks/{index-qm6kmhqs.js → index-y7mzvq2q.js} +1 -1
- package/dist/server/_chunks/{index-rn8dyr1f.js → index-yrjkqtfj.js} +8 -48
- package/dist/server/_chunks/{index-tq0pc7eg.js → index-zfrwq472.js} +3 -3
- package/dist/server/_chunks/{loader-7yw0j98z.js → loader-5en7wpt5.js} +12 -10
- package/dist/server/_chunks/{modules-jn8bmbn8.js → modules-t92favpj.js} +14 -12
- package/dist/server/_chunks/{pipeline-yy9jhydf.js → pipeline-4t9q43ht.js} +25 -21
- package/dist/server/_chunks/{production-jar9ztm3.js → production-385bw8df.js} +14 -3
- package/dist/server/_chunks/{proxy-config-j84dgksn.js → proxy-config-60gprphe.js} +7 -5
- package/dist/server/_chunks/{proxy-w1d0wmsn.js → proxy-gfhmktbk.js} +12 -10
- package/dist/server/_chunks/{router-k1reahfn.js → router-xnrva921.js} +10 -7
- package/dist/server/_chunks/{routes-2s277fc6.js → routes-ht868pdc.js} +12 -10
- package/dist/server/_chunks/{run-pvd8bs1t.js → run-d40d63ag.js} +5 -4
- package/dist/server/_chunks/{server-mt4a994c.js → server-2p8fgqq2.js} +38 -34
- package/dist/server/_chunks/{tsconfig-defaults-1nb3308g.js → tsconfig-defaults-bcn34eqb.js} +5 -4
- package/dist/server/_chunks/{typegen-0qr0459b.js → typegen-dvtcjtaw.js} +15 -13
- package/dist/server/cli/index.js +12 -15
- package/dist/server/cli/start.js +35 -14
- package/dist/server/compat/next/cache.js +5 -5
- package/dist/server/compat/next/config-headers.js +8 -6
- package/dist/server/compat/next/config-loader.js +1 -1
- package/dist/server/compat/next/dist/server/web/spec-extension/revalidate.js +5 -5
- package/dist/server/compat/next/dist/server/web/spec-extension/unstable-cache.js +5 -5
- package/dist/server/compat/next/dist/server/web/spec-extension/unstable-no-store.js +5 -5
- package/dist/server/compat/next/dynamic.js +3 -14
- package/dist/server/compat/next/font/cache.js +6 -4
- package/dist/server/compat/next/font/local.js +7 -5
- package/dist/server/compat/next/font/runtime.js +7 -5
- package/dist/server/compat/next/headers.js +4 -4
- package/dist/server/compat/next/image/config.js +2 -2
- package/dist/server/compat/next/image/optimizer.js +3 -3
- package/dist/server/compat/next/image/static-metadata.js +4 -4
- package/dist/server/compat/next/legacy-request-apis.js +3 -3
- package/dist/server/compat/next/link-transform.js +4 -3
- package/dist/server/compat/next/navigation.js +5 -4
- package/dist/server/compat/next/redirects.js +8 -6
- package/dist/server/compat/next/resource-hints.js +2 -2
- package/dist/server/compat/next/rewrites.js +7 -5
- package/dist/server/compat/next/root-params.js +3 -3
- package/dist/server/compat/next/route-state.js +2 -2
- package/dist/server/compat/next/script.js +2 -2
- package/dist/server/compat/next/server.js +5 -5
- package/dist/server/generation.json +1 -1
- package/package.json +1 -1
- package/reference/compat.md +1 -1
- package/reference/config.md +2 -2
- package/reference/dev.md +1 -1
- package/reference/getting-started.md +1 -1
- package/reference/rendering.md +4 -4
- package/src/api/dynamic.tsx +84 -4
- package/src/cli/adapters/vercel-warm.ts +40 -29
- package/src/cli/adapters/vercel.ts +95 -1262
- package/src/cli/analyze.ts +3 -2
- package/src/cli/build/cache.ts +6 -6
- package/src/cli/build.ts +33 -33
- package/src/cli/index.ts +4 -6
- package/src/cli/serve/pipeline.ts +16 -10
- package/src/cli/serve/standalone-worker.ts +8 -0
- package/src/cli/serve/standalone.ts +703 -0
- package/src/cli/start.ts +29 -8
- package/src/client/build.ts +1 -1
- package/src/client/reference-stub.ts +2 -34
- package/src/compat/actions/extensions.ts +3 -2
- package/src/compat/bundler/require-context.ts +2 -1
- package/src/compat/bundler/webpack-loaders.ts +3 -2
- package/src/compat/bundler/worker.ts +2 -2
- package/src/compat/css/inline-css.ts +1 -1
- package/src/compat/export/build-complete.ts +1 -1
- package/src/compat/export/emit.ts +4 -4
- package/src/compat/export/standalone.ts +30 -65
- package/src/compat/image-optimizer/optimize.ts +1 -1
- package/src/compat/lifecycle/instrumentation-client.ts +4 -3
- package/src/compat/metadata-artifacts.ts +1 -1
- package/src/compat/next/config-loader.ts +3 -0
- package/src/compat/next/dynamic.tsx +10 -23
- package/src/compat/next/font/cache.ts +2 -1
- package/src/compat/next/font/runtime.ts +1 -1
- package/src/compat/register/image.ts +1 -1
- package/src/compat/typecheck/check.ts +6 -4
- package/src/config.ts +28 -5
- package/src/css/assets.ts +1 -1
- package/src/css/build.ts +16 -9
- package/src/dev/client-chunk-store.ts +2 -1
- package/src/dev/restart/client-key.ts +2 -1
- package/src/dev/restart/global-css.ts +2 -1
- package/src/dev/restart/route-bundle-key.ts +2 -1
- package/src/dev/restart/route-facts.ts +2 -1
- package/src/dev/server.ts +8 -7
- package/src/out-paths.ts +10 -0
- package/src/render/renderer.ts +1 -1
- package/src/resolve/dynamic.ts +4 -0
- package/src/resolve/engine.ts +2 -12
- package/src/resolve/scan-facts.ts +185 -1
- package/src/routing/metadata-files.ts +1 -1
- package/src/runtime/loader.ts +5 -3
- package/src/runtime/module-cache.ts +2 -1
- package/src/runtime/modules.ts +50 -16
- package/src/runtime/prebundle.ts +23 -3
- package/src/runtime/production.ts +105 -40
- package/src/runtime/release-graph.ts +21 -13
- package/src/runtime/vendor-build.ts +45 -23
- package/src/types.ts +2 -3
- package/src/utils/directive.ts +40 -0
- package/src/utils/fs.ts +7 -2
- package/src/utils/resource-query.ts +12 -0
- package/dist/server/_chunks/index-tqn2qnnn.js +0 -241
- package/dist/server/_chunks/index-y36a0ybw.js +0 -57
- package/src/cli/serve/entry.ts +0 -52
package/src/config.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from 'node:fs'
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
+
import { outCachePath, standaloneOutSegment } from './out-paths'
|
|
4
5
|
import { loadEnv } from './env'
|
|
5
6
|
import { findWorkspaceRoot } from './resolve/package-json'
|
|
6
7
|
import { PREFETCH_MODES, type PNextConfig } from './types'
|
|
7
8
|
import { setCacheComponents } from './render/ppr'
|
|
8
9
|
import { setLegacyRequestAPIs } from './request/context'
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
attachRelease,
|
|
12
|
+
buildIndexFile,
|
|
13
|
+
linkStandaloneOrigins,
|
|
14
|
+
readBuildIndex,
|
|
15
|
+
releaseConfig,
|
|
16
|
+
releaseSourceRoot,
|
|
17
|
+
servingReleaseDir,
|
|
18
|
+
} from './runtime/production'
|
|
10
19
|
|
|
11
20
|
export type ResolvedConfig = Required<Pick<PNextConfig, 'outDir' | 'basePath'>> &
|
|
12
21
|
Omit<PNextConfig, 'outDir' | 'basePath'> & {
|
|
@@ -16,7 +25,7 @@ export type ResolvedConfig = Required<Pick<PNextConfig, 'outDir' | 'basePath'>>
|
|
|
16
25
|
publicDir: 'public'
|
|
17
26
|
appPath: string
|
|
18
27
|
publicPath: string
|
|
19
|
-
/** Where this process writes and serves from:
|
|
28
|
+
/** Where this process writes and serves from: `<outRoot>/standalone`, or `<outRoot>/dev` in dev. */
|
|
20
29
|
outPath: string
|
|
21
30
|
/** The out root itself (`.pnext`), identical in dev and build. */
|
|
22
31
|
outRootPath: string
|
|
@@ -97,7 +106,7 @@ export async function loadConfig(
|
|
|
97
106
|
options: { dev?: boolean; serve?: boolean; warnings?: boolean } = {},
|
|
98
107
|
): Promise<ResolvedConfig> {
|
|
99
108
|
const root = canonicalRoot(path.resolve(rootInput))
|
|
100
|
-
const released = options.serve ?
|
|
109
|
+
const released = options.serve ? servingReleaseDir() : undefined
|
|
101
110
|
if (released) return loadReleasedConfig(root, released, options)
|
|
102
111
|
await loadEnv(root, options)
|
|
103
112
|
const configPath = path.join(root, 'pnext.config.ts')
|
|
@@ -129,6 +138,12 @@ export async function loadConfig(
|
|
|
129
138
|
const sourceOverrides = stripUndefined(await resolveConfigSource(config, root, options))
|
|
130
139
|
const merged = { ...defaultConfig, ...config, ...sourceOverrides }
|
|
131
140
|
const outRootPath = path.resolve(root, merged.outDir)
|
|
141
|
+
// `pnext start` reads outDir from the config, as `next start` reads distDir, then serves the release.
|
|
142
|
+
const release = path.join(outRootPath, standaloneOutSegment)
|
|
143
|
+
if (options.serve && existsSync(buildIndexFile(release))) {
|
|
144
|
+
// The source config above already warned; its compiled copy must not warn again.
|
|
145
|
+
return loadReleasedConfig(root, release, { ...options, warnings: false })
|
|
146
|
+
}
|
|
132
147
|
// A build materializes pages into its output, so the release names them portably.
|
|
133
148
|
const appPath = await resolveAppPath(
|
|
134
149
|
root,
|
|
@@ -163,7 +178,7 @@ export async function loadConfig(
|
|
|
163
178
|
// Dev owns `<outRoot>/dev` exclusively so a concurrent `pnext build` — which
|
|
164
179
|
// wipes its own outputs under the out root — can never pull a running dev
|
|
165
180
|
// server's cache, manifest or assets out from under it.
|
|
166
|
-
outPath:
|
|
181
|
+
outPath: path.join(outRootPath, options.dev ? devOutSegment : standaloneOutSegment),
|
|
167
182
|
outRootPath,
|
|
168
183
|
typesPath: path.join(outRootPath, 'types'),
|
|
169
184
|
checksPath: path.join(outRootPath, 'typecheck', 'checks'),
|
|
@@ -180,7 +195,9 @@ async function loadReleasedConfig(
|
|
|
180
195
|
options: { dev?: boolean; serve?: boolean; warnings?: boolean },
|
|
181
196
|
): Promise<ResolvedConfig> {
|
|
182
197
|
const index = readBuildIndex(outPath)
|
|
183
|
-
|
|
198
|
+
linkStandaloneOrigins(outPath)
|
|
199
|
+
const sourceRoot = releaseSourceRoot(outPath, index.config.outPath as string)
|
|
200
|
+
const config = releaseConfig(index.config, sourceRoot, outPath)
|
|
184
201
|
await loadEnv(root, options)
|
|
185
202
|
setLegacyRequestAPIs(
|
|
186
203
|
typeof config.compat?.next === 'object' && config.compat.next.legacyRequestAPIs === true,
|
|
@@ -219,6 +236,8 @@ function validateConfig(config: PNextConfig) {
|
|
|
219
236
|
/** Dev's private subtree under the out root. Build never touches it. */
|
|
220
237
|
export const devOutSegment = 'dev'
|
|
221
238
|
|
|
239
|
+
export { outCachePath, standaloneOutSegment }
|
|
240
|
+
|
|
222
241
|
// Relative-import resolution realpaths the importing file (so a materialized shim resolves `./sibling`
|
|
223
242
|
// against real source), so every containment check against root/workspaceRoot compares realpaths.
|
|
224
243
|
// Canonicalize the roots to match - otherwise an app served through a symlinked directory (a macOS
|
|
@@ -297,6 +316,10 @@ export function frameworkRuntimeAliasEntries(): Record<FrameworkRuntimeAlias, st
|
|
|
297
316
|
return frameworkRuntimeAliases
|
|
298
317
|
}
|
|
299
318
|
|
|
319
|
+
/** Packages the framework resolves at runtime by name, not by import. */
|
|
320
|
+
export const frameworkRuntimeSpecifiers = (): string[] =>
|
|
321
|
+
Object.keys(frameworkRuntimeAliasEntries())
|
|
322
|
+
|
|
300
323
|
function resolveRuntimeModule(specifier: string) {
|
|
301
324
|
return fileURLToPath(import.meta.resolve(specifier))
|
|
302
325
|
}
|
package/src/css/assets.ts
CHANGED
|
@@ -116,7 +116,7 @@ export async function emitFontCssStylesheet(
|
|
|
116
116
|
baseAsset: string | undefined,
|
|
117
117
|
options: { dev: boolean },
|
|
118
118
|
) {
|
|
119
|
-
const outDir = path.join(config.outPath, options.dev ? 'cache' : '
|
|
119
|
+
const outDir = path.join(config.outPath, options.dev ? 'cache' : 'static', 'assets')
|
|
120
120
|
await ensureDir(outDir)
|
|
121
121
|
const baseFile = baseAsset ? path.join(outDir, baseAsset) : undefined
|
|
122
122
|
const baseCss = baseFile && existsSync(baseFile) ? await readFile(baseFile, 'utf8') : ''
|
package/src/css/build.ts
CHANGED
|
@@ -40,6 +40,7 @@ export {
|
|
|
40
40
|
withAssetPrefix,
|
|
41
41
|
type AssetHrefConfig,
|
|
42
42
|
} from './assets'
|
|
43
|
+
import { outCachePath } from '../out-paths'
|
|
43
44
|
|
|
44
45
|
interface Pending {
|
|
45
46
|
resolve: () => void
|
|
@@ -114,14 +115,20 @@ export async function stopCssWorker(): Promise<void> {
|
|
|
114
115
|
if (active) await Promise.resolve(active.terminate())
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
// Tailwind skips the out root and caches candidates in it; dev keeps its own subtree.
|
|
119
|
+
function scanOutDir(config: Pick<ResolvedConfig, 'outPath' | 'outRootPath'>, dev: boolean) {
|
|
120
|
+
return dev ? config.outPath : config.outRootPath
|
|
121
|
+
}
|
|
122
|
+
|
|
117
123
|
export async function runPostcssOffThread(
|
|
118
|
-
config: Pick<ResolvedConfig, 'root' | 'outPath'>,
|
|
124
|
+
config: Pick<ResolvedConfig, 'root' | 'outPath' | 'outRootPath'>,
|
|
119
125
|
cssFile: string,
|
|
120
126
|
options: { dev?: boolean },
|
|
121
127
|
from?: string,
|
|
122
128
|
) {
|
|
123
|
-
const { root, outPath: outDir } = config
|
|
124
129
|
const dev = options.dev ?? false
|
|
130
|
+
const { root } = config
|
|
131
|
+
const outDir = scanOutDir(config, dev)
|
|
125
132
|
const sent = send(id => ({ kind: 'process', id, root, dev, outDir, cssFile, from }))
|
|
126
133
|
if (sent) return sent
|
|
127
134
|
return runPostcss(root, cssFile, { dev, outDir }, from)
|
|
@@ -132,7 +139,7 @@ export async function runPostcssOffThread(
|
|
|
132
139
|
* stylesheet, so a cold Tailwind boot overlaps with the rest of dev startup.
|
|
133
140
|
*/
|
|
134
141
|
export function warmCssWorker(
|
|
135
|
-
config: Pick<ResolvedConfig, 'root' | 'outPath'>,
|
|
142
|
+
config: Pick<ResolvedConfig, 'root' | 'outPath' | 'outRootPath'>,
|
|
136
143
|
options: { dev?: boolean } = {},
|
|
137
144
|
) {
|
|
138
145
|
send(id => ({
|
|
@@ -140,7 +147,7 @@ export function warmCssWorker(
|
|
|
140
147
|
id,
|
|
141
148
|
root: config.root,
|
|
142
149
|
dev: options.dev ?? false,
|
|
143
|
-
outDir: config.
|
|
150
|
+
outDir: scanOutDir(config, options.dev ?? false),
|
|
144
151
|
}))?.catch(() => {
|
|
145
152
|
// A broken postcss config surfaces on the real build; warming stays quiet.
|
|
146
153
|
})
|
|
@@ -265,7 +272,7 @@ function rootLayoutFile(appPath: string, missing?: Set<string>) {
|
|
|
265
272
|
* instead of landing on the first request. No-op without a postcss config.
|
|
266
273
|
*/
|
|
267
274
|
export function warmCssPipeline(
|
|
268
|
-
config: Pick<ResolvedConfig, 'root' | 'outPath'>,
|
|
275
|
+
config: Pick<ResolvedConfig, 'root' | 'outPath' | 'outRootPath'>,
|
|
269
276
|
options: CssBuildOptions = {},
|
|
270
277
|
) {
|
|
271
278
|
if (!hasPostcssConfig(config.root)) return
|
|
@@ -281,8 +288,8 @@ export async function buildGlobalCss(config: ResolvedConfig, options: CssBuildOp
|
|
|
281
288
|
}
|
|
282
289
|
|
|
283
290
|
const outDir = options.dev
|
|
284
|
-
? path.join(config.outPath, '
|
|
285
|
-
: path.join(config.outPath, '
|
|
291
|
+
? path.join(outCachePath(config.outPath), 'assets')
|
|
292
|
+
: path.join(config.outPath, 'static', 'assets')
|
|
286
293
|
const outfile = path.join(outDir, 'global.css')
|
|
287
294
|
await ensureDir(outDir)
|
|
288
295
|
|
|
@@ -606,8 +613,8 @@ async function writeCssChunk(
|
|
|
606
613
|
const log = createVerboseLogger(options.verbose ?? false, 'css')
|
|
607
614
|
|
|
608
615
|
const outDir = options.dev
|
|
609
|
-
? path.join(config.outPath, '
|
|
610
|
-
: path.join(config.outPath, '
|
|
616
|
+
? path.join(outCachePath(config.outPath), 'assets')
|
|
617
|
+
: path.join(config.outPath, 'static', 'assets')
|
|
611
618
|
const outfile = path.join(outDir, assetName)
|
|
612
619
|
await ensureDir(outDir)
|
|
613
620
|
|
|
@@ -9,9 +9,10 @@ import { createHash } from 'node:crypto'
|
|
|
9
9
|
import { link, mkdir, readFile, readdir, rm, stat, unlink } from 'node:fs/promises'
|
|
10
10
|
import path from 'node:path'
|
|
11
11
|
import { listFiles } from '../utils/fs'
|
|
12
|
+
import { outCachePath } from '../out-paths'
|
|
12
13
|
|
|
13
14
|
export function clientChunkStoreDir(outPath: string) {
|
|
14
|
-
return path.join(outPath, '
|
|
15
|
+
return path.join(outCachePath(outPath), 'client-store')
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
function contentHash(data: Buffer) {
|
|
@@ -23,6 +23,7 @@ import type { ResolvedConfig } from '../../config'
|
|
|
23
23
|
import type { RouteManifestEntry } from '../../types'
|
|
24
24
|
import { writeFileAtomic } from '../../utils/fs'
|
|
25
25
|
import { restartCacheEnabled } from './enabled'
|
|
26
|
+
import { outCachePath } from '../../out-paths'
|
|
26
27
|
|
|
27
28
|
/** `[file, mtimeMs, size, contentHash]` — the same record shape the module index uses. */
|
|
28
29
|
type SourceEntry = [string, number, number, string]
|
|
@@ -53,7 +54,7 @@ interface KeyCache {
|
|
|
53
54
|
const caches = new Map<string, KeyCache>()
|
|
54
55
|
|
|
55
56
|
function keyCache(config: ResolvedConfig): KeyCache {
|
|
56
|
-
const file = path.join(config.outPath, '
|
|
57
|
+
const file = path.join(outCachePath(config.outPath), 'client', 'keys.json')
|
|
57
58
|
const existing = caches.get(file)
|
|
58
59
|
if (existing) return existing
|
|
59
60
|
const created: KeyCache = { file, routes: readIndex(file), dirty: false, retries: 0 }
|
|
@@ -17,6 +17,7 @@ import { setGlobalCssSourceStore, type GlobalCssSourceStore } from '../../css/bu
|
|
|
17
17
|
import { traceEnabled } from '../../utils/trace-flags'
|
|
18
18
|
import { restartCacheEnabled } from './enabled'
|
|
19
19
|
import { hash, lazyRecordCacheKey, revalidate, sourceEntries, type SourceEntry } from './record'
|
|
20
|
+
import { outCachePath } from '../../out-paths'
|
|
20
21
|
|
|
21
22
|
interface RecordFile {
|
|
22
23
|
version: number
|
|
@@ -34,7 +35,7 @@ export function installGlobalCssCache(config: ResolvedConfig) {
|
|
|
34
35
|
if (!restartCacheEnabled()) return
|
|
35
36
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
36
37
|
if (process.env.PNEXT_DEV_GLOBAL_CSS_CACHE === '0') return
|
|
37
|
-
const dir = path.join(config.outPath, '
|
|
38
|
+
const dir = path.join(outCachePath(config.outPath), 'global-css')
|
|
38
39
|
setGlobalCssSourceStore(createStore(dir, lazyRecordCacheKey(config, RECORD_VERSION)))
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type DevGraphSource,
|
|
26
26
|
} from '../../runtime/module-cache'
|
|
27
27
|
import { restartCacheEnabled } from './enabled'
|
|
28
|
+
import { outCachePath } from '../../out-paths'
|
|
28
29
|
|
|
29
30
|
interface RecordFile {
|
|
30
31
|
version: number
|
|
@@ -49,7 +50,7 @@ function enabled() {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
function recordFile(config: ResolvedConfig, routeId: string) {
|
|
52
|
-
return path.join(config.outPath, '
|
|
53
|
+
return path.join(outCachePath(config.outPath), 'route-bundles', `${hash(routeId)}.json`)
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
/**
|
|
@@ -20,6 +20,7 @@ import { setRouteFactsStore, type RouteFacts, type RouteFactsStore } from '../..
|
|
|
20
20
|
import { traceEnabled } from '../../utils/trace-flags'
|
|
21
21
|
import { restartCacheEnabled } from './enabled'
|
|
22
22
|
import { hash, lazyRecordCacheKey, revalidate, sourceEntry, type SourceEntry } from './record'
|
|
23
|
+
import { outCachePath } from '../../out-paths'
|
|
23
24
|
|
|
24
25
|
interface RecordFile {
|
|
25
26
|
version: number
|
|
@@ -45,7 +46,7 @@ export function installRouteFactsCache(config: ResolvedConfig) {
|
|
|
45
46
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
46
47
|
if (process.env.PNEXT_DEV_FACTS_CACHE === '0') return
|
|
47
48
|
const cache: FactsCache = {
|
|
48
|
-
dir: path.join(config.outPath, '
|
|
49
|
+
dir: path.join(outCachePath(config.outPath), 'route-facts'),
|
|
49
50
|
key: lazyRecordCacheKey(config, RECORD_VERSION),
|
|
50
51
|
written: new Set(),
|
|
51
52
|
}
|
package/src/dev/server.ts
CHANGED
|
@@ -121,6 +121,7 @@ import { cacheRoot, setDevWatcherFreshness } from '../runtime/module-cache'
|
|
|
121
121
|
import { abortUpstreamFetchOnDisconnect } from '../runtime/fetch-host'
|
|
122
122
|
import { markBoot } from '../cli/boot/trace'
|
|
123
123
|
import { formatDuration } from '../utils/verbose'
|
|
124
|
+
import { outCachePath } from '../out-paths'
|
|
124
125
|
|
|
125
126
|
interface DevServerOptions {
|
|
126
127
|
config: ResolvedConfig
|
|
@@ -217,7 +218,7 @@ async function evictStaleClientCaches(outPath: string) {
|
|
|
217
218
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
218
219
|
const keep = Number(process.env.PNEXT_DEV_CLIENT_CACHE_KEEP || 32)
|
|
219
220
|
if (!Number.isFinite(keep) || keep <= 0) return
|
|
220
|
-
const clientRoot = path.join(outPath, '
|
|
221
|
+
const clientRoot = path.join(outCachePath(outPath), 'client')
|
|
221
222
|
try {
|
|
222
223
|
const entries = await readdir(clientRoot)
|
|
223
224
|
if (entries.length <= keep) return
|
|
@@ -597,7 +598,7 @@ export async function startDevServer(options: DevServerOptions) {
|
|
|
597
598
|
.some(prefix => url.pathname.startsWith(prefix))
|
|
598
599
|
) {
|
|
599
600
|
const emitted = await maybeStaticFile(
|
|
600
|
-
path.join(config.outPath, '
|
|
601
|
+
path.join(config.outPath, 'static'),
|
|
601
602
|
url.pathname,
|
|
602
603
|
exactNames,
|
|
603
604
|
)
|
|
@@ -613,7 +614,7 @@ export async function startDevServer(options: DevServerOptions) {
|
|
|
613
614
|
)
|
|
614
615
|
if (staticAssetPathname) {
|
|
615
616
|
const outPublicStatic = await maybeStaticFile(
|
|
616
|
-
path.join(config.outPath, '
|
|
617
|
+
path.join(config.outPath, 'static'),
|
|
617
618
|
staticAssetPathname,
|
|
618
619
|
exactNames,
|
|
619
620
|
)
|
|
@@ -1274,7 +1275,7 @@ async function buildDevClient(config: ResolvedConfig, route: RouteManifestEntry)
|
|
|
1274
1275
|
batchRoutes.length === 1
|
|
1275
1276
|
? cacheKeys[0]!
|
|
1276
1277
|
: `batch-${clientSourceHash([...cacheKeys].sort().join('\n'))}`
|
|
1277
|
-
const outDir = path.join(config.outPath, '
|
|
1278
|
+
const outDir = path.join(outCachePath(config.outPath), 'client', cacheKey)
|
|
1278
1279
|
const outFile = path.join(outDir, `${clientEntryName(route)}.js`)
|
|
1279
1280
|
|
|
1280
1281
|
// Dedup in-flight builds first. A single page view fires up to three requests
|
|
@@ -1361,7 +1362,7 @@ async function resolveDevClientChunk(config: ResolvedConfig, name: string) {
|
|
|
1361
1362
|
const settled = clientChunks.get(name)
|
|
1362
1363
|
if (settled && existsSync(settled)) return settled
|
|
1363
1364
|
|
|
1364
|
-
const clientRoot = path.join(config.outPath, '
|
|
1365
|
+
const clientRoot = path.join(outCachePath(config.outPath), 'client')
|
|
1365
1366
|
for (const generation of await readdir(clientRoot).catch(() => [])) {
|
|
1366
1367
|
const chunksDir = path.join(clientRoot, generation, 'chunks')
|
|
1367
1368
|
const file = path.join(chunksDir, name)
|
|
@@ -1500,8 +1501,8 @@ async function maybeBuiltAsset(
|
|
|
1500
1501
|
}
|
|
1501
1502
|
|
|
1502
1503
|
const outPath = config.outPath
|
|
1503
|
-
const filePath = path.join(outPath,
|
|
1504
|
-
if (!isInside(path.join(outPath
|
|
1504
|
+
const filePath = path.join(outCachePath(outPath), pathname.replace(/^\/+/, ''))
|
|
1505
|
+
if (!isInside(path.join(outCachePath(outPath)), filePath) || !existsSync(filePath)) return null
|
|
1505
1506
|
const fileStat = await stat(filePath)
|
|
1506
1507
|
if (!fileStat.isFile()) return null
|
|
1507
1508
|
return new Response(await readFile(filePath), {
|
package/src/out-paths.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
/** The release a build writes under the out root: everything a production server reads. */
|
|
4
|
+
export const standaloneOutSegment = 'standalone'
|
|
5
|
+
|
|
6
|
+
/** The build cache beside a release (`<outRoot>/cache`); dev keeps its own. */
|
|
7
|
+
export function outCachePath(outPath: string) {
|
|
8
|
+
const owner = path.basename(outPath) === standaloneOutSegment ? path.dirname(outPath) : outPath
|
|
9
|
+
return path.join(owner, 'cache')
|
|
10
|
+
}
|
package/src/render/renderer.ts
CHANGED
|
@@ -3403,7 +3403,7 @@ export function withDevReloadScript(html: string, request?: Request): string {
|
|
|
3403
3403
|
}
|
|
3404
3404
|
|
|
3405
3405
|
function withGlobalErrorStylesheet(html: string, config: ResolvedConfig): string {
|
|
3406
|
-
const asset = `${config.outPath}/
|
|
3406
|
+
const asset = `${config.outPath}/static/assets/${emittedAssetName(config, 'global-error.css')}`
|
|
3407
3407
|
if (!existsSync(asset)) return html
|
|
3408
3408
|
const link = `<link rel="stylesheet" href="${assetHref(config, 'global-error.css')}">`
|
|
3409
3409
|
if (html.includes('</head>')) return html.replace('</head>', `${link}</head>`)
|
package/src/resolve/dynamic.ts
CHANGED
|
@@ -224,6 +224,7 @@ export function deferredDynamicImportSpecifiers(source: string, file?: string) {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
const facts = dynamicCallFacts(source, file)
|
|
227
|
+
const rendered = new Set<string>()
|
|
227
228
|
for (const call of dynamicCallsFromSource(source, dynamicNames)) {
|
|
228
229
|
const target = loaderImportTargetForCall(call, facts)
|
|
229
230
|
if (!target) continue
|
|
@@ -239,7 +240,10 @@ export function deferredDynamicImportSpecifiers(source: string, file?: string) {
|
|
|
239
240
|
const visible =
|
|
240
241
|
/\bload\s*:\s*['"]visible['"]/.test(options) && !/\bssr\s*:\s*true\b/.test(options)
|
|
241
242
|
if (ssrFalse || visible) deferred.add(target.specifier)
|
|
243
|
+
else rendered.add(target.specifier)
|
|
242
244
|
}
|
|
245
|
+
// A target another call server-renders must compile for the server.
|
|
246
|
+
for (const specifier of rendered) deferred.delete(specifier)
|
|
243
247
|
return deferred
|
|
244
248
|
}
|
|
245
249
|
|
package/src/resolve/engine.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import type { ResolverFactory, NapiResolveOptions } from 'oxc-resolver'
|
|
4
4
|
import { loadNative } from '../utils/native-require'
|
|
5
|
+
import { splitResourceQuery } from '../utils/resource-query'
|
|
5
6
|
|
|
6
7
|
// Lazy: the native binding costs ~1.3 MB RSS and a prod server may never resolve.
|
|
7
8
|
function createResolverFactory(options: NapiResolveOptions): ResolverFactory {
|
|
@@ -95,18 +96,7 @@ export function tsConfigFileFor(root: string): string | undefined {
|
|
|
95
96
|
return file
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
* Split a webpack resource specifier into its file request and its `?query#fragment` suffix - loader
|
|
100
|
-
* plumbing (turbopack rules, `.wasm?module`) that takes no part in the on-disk lookup. A leading `#` is a
|
|
101
|
-
* package-imports specifier, not a fragment.
|
|
102
|
-
*/
|
|
103
|
-
export function splitResourceQuery(specifier: string): { path: string; query: string } {
|
|
104
|
-
const offset = specifier.startsWith('#') ? 1 : 0
|
|
105
|
-
const match = /[?#]/.exec(specifier.slice(offset))
|
|
106
|
-
if (!match) return { path: specifier, query: '' }
|
|
107
|
-
const index = offset + match.index
|
|
108
|
-
return { path: specifier.slice(0, index), query: specifier.slice(index) }
|
|
109
|
-
}
|
|
99
|
+
export { splitResourceQuery }
|
|
110
100
|
|
|
111
101
|
/**
|
|
112
102
|
* Resolve `specifier` from `dir`. `tsConfigRoot` opts the lookup into that
|
|
@@ -76,6 +76,8 @@ export interface RewriteFacts {
|
|
|
76
76
|
edges: SpecifierEdge[]
|
|
77
77
|
imports: ImportStatement[]
|
|
78
78
|
importMetas: { start: number; end: number }[]
|
|
79
|
+
/** `import()` calls whose argument is not a plain string literal. */
|
|
80
|
+
computedImports: { start: number; end: number }[]
|
|
79
81
|
/**
|
|
80
82
|
* The parse recovered from errors, so the record may be incomplete — folded
|
|
81
83
|
* passes fall back to their textual form rather than silently under-rewrite.
|
|
@@ -146,6 +148,7 @@ const emptyRewriteFacts: RewriteFacts = {
|
|
|
146
148
|
edges: [],
|
|
147
149
|
imports: [],
|
|
148
150
|
importMetas: [],
|
|
151
|
+
computedImports: [],
|
|
149
152
|
unreliable: false,
|
|
150
153
|
}
|
|
151
154
|
|
|
@@ -373,10 +376,14 @@ function parseFacts(file: string, source: string, lang: ParserLang) {
|
|
|
373
376
|
imports.sort((a, b) => a.index - b.index)
|
|
374
377
|
|
|
375
378
|
const dynamicImports: { specifier: string; index: number }[] = []
|
|
379
|
+
const computedImports: { start: number; end: number }[] = []
|
|
376
380
|
for (const entry of result.module.dynamicImports) {
|
|
377
381
|
const raw = source.slice(entry.moduleRequest.start, entry.moduleRequest.end)
|
|
378
382
|
const specifier = literalSpecifier(raw)
|
|
379
|
-
if (specifier === undefined)
|
|
383
|
+
if (specifier === undefined) {
|
|
384
|
+
computedImports.push({ start: entry.start, end: entry.end })
|
|
385
|
+
continue
|
|
386
|
+
}
|
|
380
387
|
dynamicImports.push({ specifier, index: entry.start })
|
|
381
388
|
// The literal's own span, without the whitespace `moduleRequest` includes.
|
|
382
389
|
const lead = raw.length - raw.trimStart().length
|
|
@@ -419,6 +426,7 @@ function parseFacts(file: string, source: string, lang: ParserLang) {
|
|
|
419
426
|
edges,
|
|
420
427
|
imports: importStatements,
|
|
421
428
|
importMetas: result.module.importMetas.map(meta => ({ start: meta.start, end: meta.end })),
|
|
429
|
+
computedImports,
|
|
422
430
|
unreliable: result.errors.length > 0,
|
|
423
431
|
},
|
|
424
432
|
}
|
|
@@ -581,6 +589,182 @@ function child(node: AstNode | undefined, key: string): AstNode | undefined {
|
|
|
581
589
|
return isNode(value) ? value : undefined
|
|
582
590
|
}
|
|
583
591
|
|
|
592
|
+
/** A string literal argument: its cooked value and the span of its quoted text. */
|
|
593
|
+
export interface LiteralSpan {
|
|
594
|
+
specifier: string
|
|
595
|
+
start: number
|
|
596
|
+
end: number
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** How a module refers to its own location (see `importMetaRefs`). */
|
|
600
|
+
export interface ImportMetaRefs {
|
|
601
|
+
/** `new URL('<literal>', import.meta.url)`. */
|
|
602
|
+
urls: LiteralSpan[]
|
|
603
|
+
/** `import.meta.resolve('<literal>')`. */
|
|
604
|
+
resolves: LiteralSpan[]
|
|
605
|
+
/** Reads `import.meta.url`/`dirname`/`filename`/`resolve`, or takes `import.meta` whole. */
|
|
606
|
+
readsLocation: boolean
|
|
607
|
+
/** Spans of each `import()` whose argument is not a string literal. */
|
|
608
|
+
computedImports: { start: number; end: number }[]
|
|
609
|
+
/** The parse recovered from errors: callers keep their conservative answer. */
|
|
610
|
+
unreliable: boolean
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const noMetaRefs: ImportMetaRefs = {
|
|
614
|
+
urls: [],
|
|
615
|
+
resolves: [],
|
|
616
|
+
readsLocation: false,
|
|
617
|
+
computedImports: [],
|
|
618
|
+
unreliable: false,
|
|
619
|
+
}
|
|
620
|
+
const metaRefMemo = new Map<string, ImportMetaRefs>()
|
|
621
|
+
const locationMembers = new Set(['url', 'dirname', 'filename', 'resolve'])
|
|
622
|
+
|
|
623
|
+
/** Every reference a module makes to its own location, from the AST: never a comment or a string. */
|
|
624
|
+
export function importMetaRefs(file: string, source: string): ImportMetaRefs {
|
|
625
|
+
if (stylesheetOrData.test(file)) return noMetaRefs
|
|
626
|
+
if (!source.includes('import.meta') && !source.includes('import(')) return noMetaRefs
|
|
627
|
+
// The memoized module record answers computed imports and whether `import.meta` occurs at all;
|
|
628
|
+
// only a module that really uses it pays for the AST.
|
|
629
|
+
const record = rewriteFacts(file, source)
|
|
630
|
+
if (record.importMetas.length === 0)
|
|
631
|
+
return { ...noMetaRefs, computedImports: record.computedImports, unreliable: record.unreliable }
|
|
632
|
+
const lang = langForFile(file)
|
|
633
|
+
const key = `${lang}\0${source.length}\0${bun.hash(source).toString()}`
|
|
634
|
+
const cached = metaRefMemo.get(key)
|
|
635
|
+
if (cached) return cached
|
|
636
|
+
const result = parseSync(file, source, { lang })
|
|
637
|
+
const refs: ImportMetaRefs = {
|
|
638
|
+
...noMetaRefs,
|
|
639
|
+
urls: [],
|
|
640
|
+
resolves: [],
|
|
641
|
+
computedImports: record.computedImports,
|
|
642
|
+
}
|
|
643
|
+
refs.unreliable = result.errors.length > 0
|
|
644
|
+
const isImportMeta = (node: AstNode | undefined) =>
|
|
645
|
+
node?.type === 'MetaProperty' &&
|
|
646
|
+
child(node, 'meta')?.name === 'import' &&
|
|
647
|
+
child(node, 'property')?.name === 'meta'
|
|
648
|
+
const memberName = (node: AstNode) =>
|
|
649
|
+
node.computed ? stringValue(child(node, 'property')) : (child(node, 'property')?.name as string)
|
|
650
|
+
const literal = (node: AstNode | undefined): LiteralSpan | undefined => {
|
|
651
|
+
if (!node) return undefined
|
|
652
|
+
const value =
|
|
653
|
+
stringValue(node) ??
|
|
654
|
+
(node.type === 'TemplateLiteral' && (node.expressions as unknown[]).length === 0
|
|
655
|
+
? ((node.quasis as AstNode[])[0]?.value as { cooked?: string } | undefined)?.cooked
|
|
656
|
+
: undefined)
|
|
657
|
+
return value === undefined ? undefined : { specifier: value, start: node.start, end: node.end }
|
|
658
|
+
}
|
|
659
|
+
const visit = (value: unknown, parent: AstNode | undefined) => {
|
|
660
|
+
if (Array.isArray(value)) {
|
|
661
|
+
for (const item of value) visit(item, parent)
|
|
662
|
+
return
|
|
663
|
+
}
|
|
664
|
+
if (!isNode(value)) return
|
|
665
|
+
if (isImportMeta(value)) {
|
|
666
|
+
const member = parent?.type === 'MemberExpression' && parent.object === value
|
|
667
|
+
if (!member || locationMembers.has(memberName(parent) ?? '')) refs.readsLocation = true
|
|
668
|
+
} else if (value.type === 'NewExpression' && child(value, 'callee')?.name === 'URL') {
|
|
669
|
+
const [target, base] = value.arguments as AstNode[]
|
|
670
|
+
const span = literal(target)
|
|
671
|
+
if (span && base?.type === 'MemberExpression' && isImportMeta(child(base, 'object')))
|
|
672
|
+
if (memberName(base) === 'url') refs.urls.push(span)
|
|
673
|
+
} else if (value.type === 'CallExpression') {
|
|
674
|
+
const callee = child(value, 'callee')
|
|
675
|
+
if (callee?.type === 'MemberExpression' && isImportMeta(child(callee, 'object'))) {
|
|
676
|
+
const span = literal((value.arguments as AstNode[])[0])
|
|
677
|
+
if (span && memberName(callee) === 'resolve') refs.resolves.push(span)
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
for (const key in value) {
|
|
681
|
+
if (key !== 'start' && key !== 'end' && key !== 'type') visit(value[key], value)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
visit(result.program, undefined)
|
|
685
|
+
metaRefMemo.set(key, refs)
|
|
686
|
+
return refs
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Whether the module declares top-level state: a `const`/`let`/`var` initialized by `new`, or by a
|
|
691
|
+
* call through a plain name (not `require`, not `/* @__PURE__ *\/`), from the AST: never a line
|
|
692
|
+
* inside a string, and every declarator counts.
|
|
693
|
+
*/
|
|
694
|
+
export function hasModuleState(file: string, source: string): boolean {
|
|
695
|
+
const result = parseSync(file, source, { lang: langForFile(file) })
|
|
696
|
+
const pureEnds = new Set(
|
|
697
|
+
result.comments
|
|
698
|
+
.filter(comment => /^\s*[#@]__PURE__\s*$/.test(comment.value))
|
|
699
|
+
.map(comment => comment.end),
|
|
700
|
+
)
|
|
701
|
+
const annotatedPure = (node: AstNode) => {
|
|
702
|
+
for (let index = node.start - 1; index >= 0; index -= 1) {
|
|
703
|
+
if (pureEnds.has(index + 1)) return true
|
|
704
|
+
if (!/\s/.test(source[index]!)) return false
|
|
705
|
+
}
|
|
706
|
+
return false
|
|
707
|
+
}
|
|
708
|
+
const plainCallee = (node: AstNode | undefined): boolean =>
|
|
709
|
+
node?.type === 'Identifier' ||
|
|
710
|
+
(node?.type === 'MemberExpression' && !node.computed && plainCallee(child(node, 'object')))
|
|
711
|
+
const declarations = (result.program.body as unknown as AstNode[]).flatMap(statement => {
|
|
712
|
+
const declaration =
|
|
713
|
+
statement.type === 'ExportNamedDeclaration' ? child(statement, 'declaration') : statement
|
|
714
|
+
return declaration?.type === 'VariableDeclaration'
|
|
715
|
+
? (declaration.declarations as AstNode[])
|
|
716
|
+
: []
|
|
717
|
+
})
|
|
718
|
+
return declarations.some(declarator => {
|
|
719
|
+
const init = unwrapExpression(child(declarator, 'init'))
|
|
720
|
+
if (init?.type === 'NewExpression') return true
|
|
721
|
+
if (init?.type !== 'CallExpression') return false
|
|
722
|
+
const callee = child(init, 'callee')
|
|
723
|
+
return plainCallee(callee) && callee?.name !== 'require' && !annotatedPure(init)
|
|
724
|
+
})
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** A string literal holding an absolute path or `file:` URL, and whether it is an object key. */
|
|
728
|
+
export interface PathLiteral {
|
|
729
|
+
value: string
|
|
730
|
+
start: number
|
|
731
|
+
end: number
|
|
732
|
+
key: boolean
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/** Absolute path literals in emitted code, from the AST; undefined when the parse recovered. */
|
|
736
|
+
export function absolutePathLiterals(file: string, source: string): PathLiteral[] | undefined {
|
|
737
|
+
const result = parseSync(file, source, { lang: langForFile(file) })
|
|
738
|
+
if (result.errors.length > 0) return undefined
|
|
739
|
+
const found: PathLiteral[] = []
|
|
740
|
+
const visit = (value: unknown, parent: AstNode | undefined) => {
|
|
741
|
+
if (Array.isArray(value)) {
|
|
742
|
+
for (const item of value) visit(item, parent)
|
|
743
|
+
return
|
|
744
|
+
}
|
|
745
|
+
if (!isNode(value)) return
|
|
746
|
+
const literal = stringValue(value)
|
|
747
|
+
if (literal !== undefined && (literal.startsWith('/') || literal.startsWith('file:///'))) {
|
|
748
|
+
const key = parent?.type === 'Property' && parent.key === value && !parent.computed
|
|
749
|
+
found.push({ value: literal, start: value.start, end: value.end, key })
|
|
750
|
+
}
|
|
751
|
+
for (const key in value) {
|
|
752
|
+
if (key !== 'start' && key !== 'end' && key !== 'type') visit(value[key], value)
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
visit(result.program, undefined)
|
|
756
|
+
return found
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** Line comments (`// …`), from the parse; undefined when it recovered. */
|
|
760
|
+
export function lineComments(file: string, source: string) {
|
|
761
|
+
const result = parseSync(file, source, { lang: langForFile(file) })
|
|
762
|
+
if (result.errors.length > 0) return undefined
|
|
763
|
+
return result.comments
|
|
764
|
+
.filter(comment => comment.type === 'Line')
|
|
765
|
+
.map(comment => ({ value: comment.value, start: comment.start, end: comment.end }))
|
|
766
|
+
}
|
|
767
|
+
|
|
584
768
|
/**
|
|
585
769
|
* Drop `type: "json"` attributes from the import declarations and `import()` calls whose target
|
|
586
770
|
* `compiled` claims: the target is JS, which a native load would parse as JSON.
|
|
@@ -216,7 +216,7 @@ export function staticMetadataLink(file: StaticMetadataFile): MetadataLink {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
export function staticMetadataOutputFile(outPath: string, file: StaticMetadataFile) {
|
|
219
|
-
return path.join(outPath, '
|
|
219
|
+
return path.join(outPath, 'static', file.outputPath)
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
export function metadataHasOwnImages(metadata: Metadata, field: 'openGraph' | 'twitter') {
|