@wular/pnext 0.0.1
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/README.md +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { requireRequest, requestApiPromise } from '../../request/context';
|
|
2
|
+
import { assertRequestApiAllowedInAfter } from '../lifecycle/after-scope';
|
|
3
|
+
import { assertNoDynamicApiInsideCache } from '../cache/revalidate';
|
|
4
|
+
import { assertDynamicErrorRequestApi } from './headers';
|
|
5
|
+
|
|
6
|
+
export { after } from '../lifecycle/after';
|
|
7
|
+
export { io } from '../ppr/io';
|
|
8
|
+
export { NextRequest, NextResponse } from '../../api/server';
|
|
9
|
+
export type { NextRequestGeo } from '../../api/server';
|
|
10
|
+
export type { NextFetchEvent } from '../../types';
|
|
11
|
+
export type { ProxyConfig } from '../../proxy';
|
|
12
|
+
export { ImageResponse } from './og';
|
|
13
|
+
// Pure UA/URLPattern helpers live in a client-safe module so the client
|
|
14
|
+
// `next/server` variant (client-server.ts) can share them without pulling this
|
|
15
|
+
// file's node:async_hooks imports into the browser bundle.
|
|
16
|
+
export { URLPattern, userAgent, userAgentFromString } from './user-agent';
|
|
17
|
+
export type { UserAgent } from './user-agent';
|
|
18
|
+
|
|
19
|
+
/** Marks the surrounding scope as dynamic, like headers()/cookies(). */
|
|
20
|
+
export function connection(): Promise<Record<never, never>> {
|
|
21
|
+
assertRequestApiAllowedInAfter('connection()');
|
|
22
|
+
assertDynamicErrorRequestApi('connection()');
|
|
23
|
+
assertNoDynamicApiInsideCache('connection()');
|
|
24
|
+
return requestApiPromise('connection()', () => {
|
|
25
|
+
requireRequest('connection()');
|
|
26
|
+
return {};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// SVGR-style `.svg`-as-component detection + inline SVG component generation (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// Projects commonly wire `@svgr/webpack` into next.config's `webpack()` to import `.svg` files as
|
|
4
|
+
// React/Preact components instead of static asset URLs. pnext has no webpack graph to run that loader
|
|
5
|
+
// against, but the overwhelming majority of configurations use its stock defaults, so detect the
|
|
6
|
+
// reference and reproduce the import-as-component semantics directly against core's `staticAssetModule`
|
|
7
|
+
// seam (see image/static-metadata.ts) instead of executing the loader.
|
|
8
|
+
|
|
9
|
+
let svgAsComponent = false;
|
|
10
|
+
|
|
11
|
+
/** Derived per config load (see config-loader's setNextConfig) — never a process one-shot. */
|
|
12
|
+
export function setSvgAsComponentEnabled(enabled: boolean): void {
|
|
13
|
+
svgAsComponent = enabled;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isSvgAsComponentEnabled(): boolean {
|
|
17
|
+
return svgAsComponent;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** True when next.config's `webpack()` references `@svgr/webpack` (the common custom-loader use). */
|
|
21
|
+
export function webpackReferencesSvgr(config: { webpack?: unknown }): boolean {
|
|
22
|
+
return typeof config.webpack === 'function' && String(config.webpack).includes('@svgr/webpack');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const XML_PROLOGUE = /^\uFEFF?\s*(?:<\?xml[^>]*\?>\s*)?(?:<!DOCTYPE[^>]*>\s*)?/i;
|
|
26
|
+
const SVG_TAG = /^<svg([^>]*)>([\s\S]*)<\/svg>\s*$/i;
|
|
27
|
+
const ATTR = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*"([^"]*)"|([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*'([^']*)'/g;
|
|
28
|
+
|
|
29
|
+
function parseRootAttrs(raw: string): Record<string, string> {
|
|
30
|
+
const attrs: Record<string, string> = {};
|
|
31
|
+
for (const match of raw.matchAll(ATTR)) {
|
|
32
|
+
const name = match[1] ?? match[3];
|
|
33
|
+
const value = match[2] ?? match[4] ?? '';
|
|
34
|
+
if (name) attrs[name] = value;
|
|
35
|
+
}
|
|
36
|
+
return attrs;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generate the ESM module text an SVGR-mode `.svg` import evaluates to: a Preact component rendering the
|
|
41
|
+
* SVG inline. Root attributes spread first so a caller's `className`/`width`/`height`/etc. props override
|
|
42
|
+
* them — matching `@svgr/webpack`'s default output semantics. Identical for server and client bundles, so
|
|
43
|
+
* hydration sees the same markup the server rendered.
|
|
44
|
+
*/
|
|
45
|
+
export function svgComponentModule(bytes: Uint8Array): string {
|
|
46
|
+
const text = new TextDecoder().decode(bytes).replace(XML_PROLOGUE, '').trim();
|
|
47
|
+
const match = SVG_TAG.exec(text);
|
|
48
|
+
const rootAttrs = match ? parseRootAttrs(match[1] ?? '') : {};
|
|
49
|
+
const inner = match ? match[2] : text;
|
|
50
|
+
return `import { h } from 'preact';
|
|
51
|
+
const __pnextSvgAttrs = ${JSON.stringify(rootAttrs)};
|
|
52
|
+
const __pnextSvgInner = ${JSON.stringify(inner)};
|
|
53
|
+
function SvgComponent(props) {
|
|
54
|
+
return h('svg', { ...__pnextSvgAttrs, ...props, dangerouslySetInnerHTML: { __html: __pnextSvgInner } });
|
|
55
|
+
}
|
|
56
|
+
export default SvgComponent;
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Telemetry debug emission (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// Next prints every telemetry event to stdout when NEXT_TELEMETRY_DEBUG is set: `[telemetry] ` plus a
|
|
4
|
+
// pretty-printed `{ eventName, payload }`. Harnesses parse that output with a regex, so the two-space
|
|
5
|
+
// indentation (closing brace at column 0) is part of the contract. pnext sends nothing anywhere - this
|
|
6
|
+
// is wire-format parity only, emitted once per CLI session after next.config loads.
|
|
7
|
+
|
|
8
|
+
import type { NextConfigObject } from './config-loader';
|
|
9
|
+
|
|
10
|
+
const EVENT_CLI_SESSION_STARTED = 'NEXT_CLI_SESSION_STARTED';
|
|
11
|
+
const EVENT_BUILD_FEATURE_USAGE = 'NEXT_BUILD_FEATURE_USAGE';
|
|
12
|
+
|
|
13
|
+
let emitted = false;
|
|
14
|
+
let featureUsageEmitted = false;
|
|
15
|
+
|
|
16
|
+
function configuredStaleTimes(config: NextConfigObject): { static?: number; dynamic?: number } {
|
|
17
|
+
const experimental = config.experimental;
|
|
18
|
+
if (!experimental || typeof experimental !== 'object') return {};
|
|
19
|
+
const staleTimes = (experimental as { staleTimes?: unknown }).staleTimes;
|
|
20
|
+
if (!staleTimes || typeof staleTimes !== 'object') return {};
|
|
21
|
+
const value = staleTimes as { static?: unknown; dynamic?: unknown };
|
|
22
|
+
return {
|
|
23
|
+
...(typeof value.static === 'number' ? { static: value.static } : {}),
|
|
24
|
+
...(typeof value.dynamic === 'number' ? { dynamic: value.dynamic } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Print the session-started telemetry event when NEXT_TELEMETRY_DEBUG is on. */
|
|
29
|
+
export function emitSessionStartedTelemetry(config: NextConfigObject): void {
|
|
30
|
+
if (emitted) return;
|
|
31
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
32
|
+
if (!process.env.NEXT_TELEMETRY_DEBUG) return;
|
|
33
|
+
emitted = true;
|
|
34
|
+
const staleTimes = configuredStaleTimes(config);
|
|
35
|
+
const event = {
|
|
36
|
+
eventName: EVENT_CLI_SESSION_STARTED,
|
|
37
|
+
payload: {
|
|
38
|
+
nodeVersion: process.version,
|
|
39
|
+
cliCommand: process.argv[2] ?? 'unknown',
|
|
40
|
+
staticStaleTime: staleTimes.static ?? null,
|
|
41
|
+
dynamicStaleTime: staleTimes.dynamic ?? null,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
console.log(`[telemetry] ${JSON.stringify(event, null, 2)}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Print `NEXT_BUILD_FEATURE_USAGE` events for the next.config features the suites assert on.
|
|
49
|
+
* Build-command only, matching Next (the event is produced by the build pipeline, not by start). Payload
|
|
50
|
+
* shape is exactly `{ featureName, invocationCount }` - the upstream assertions use `toContainEqual`, so
|
|
51
|
+
* any extra key fails the deep match.
|
|
52
|
+
*/
|
|
53
|
+
export function emitBuildFeatureUsageTelemetry(config: NextConfigObject): void {
|
|
54
|
+
if (featureUsageEmitted) return;
|
|
55
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
56
|
+
if (!process.env.NEXT_TELEMETRY_DEBUG) return;
|
|
57
|
+
if ((process.argv[2] ?? '') !== 'build') return;
|
|
58
|
+
featureUsageEmitted = true;
|
|
59
|
+
const experimental = config.experimental;
|
|
60
|
+
const esmExternals =
|
|
61
|
+
experimental && typeof experimental === 'object'
|
|
62
|
+
? (experimental as { esmExternals?: unknown }).esmExternals
|
|
63
|
+
: undefined;
|
|
64
|
+
// Next counts a feature as "used" only when it is set away from its default.
|
|
65
|
+
if (esmExternals !== undefined && esmExternals !== true) {
|
|
66
|
+
console.log(
|
|
67
|
+
`[telemetry] ${JSON.stringify(
|
|
68
|
+
{
|
|
69
|
+
eventName: EVENT_BUILD_FEATURE_USAGE,
|
|
70
|
+
payload: { featureName: 'esmExternals', invocationCount: 1 },
|
|
71
|
+
},
|
|
72
|
+
null,
|
|
73
|
+
2,
|
|
74
|
+
)}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Pure (client-safe) `next/server` helpers: userAgent parsing + URLPattern.
|
|
2
|
+
//
|
|
3
|
+
// These carry no request scope and import no node:* builtins, so both the
|
|
4
|
+
// server entry (server.ts) and the client-safe entry (client-server.ts)
|
|
5
|
+
// re-export them. Kept in their own module so the client `next/server` variant
|
|
6
|
+
// can pull them in without dragging server.ts's node:async_hooks imports
|
|
7
|
+
// (request/context, ppr, cache/revalidate) into the browser bundle.
|
|
8
|
+
|
|
9
|
+
/** Native URLPattern when the runtime provides one (Bun/Node 23+/browsers). */
|
|
10
|
+
export const URLPattern = (globalThis as { URLPattern?: unknown }).URLPattern;
|
|
11
|
+
|
|
12
|
+
export interface UserAgent {
|
|
13
|
+
ua: string;
|
|
14
|
+
browser: { name?: string; version?: string };
|
|
15
|
+
os: { name?: string; version?: string };
|
|
16
|
+
device: { type?: string; vendor?: string; model?: string };
|
|
17
|
+
engine: { name?: string; version?: string };
|
|
18
|
+
cpu: { architecture?: string };
|
|
19
|
+
isBot: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const botPattern =
|
|
23
|
+
/bot|crawler|spider|crawling|slurp|bingpreview|facebookexternalhit|mediapartners|headless/i;
|
|
24
|
+
|
|
25
|
+
export function userAgentFromString(input: string | undefined): UserAgent {
|
|
26
|
+
const ua = input ?? '';
|
|
27
|
+
return {
|
|
28
|
+
ua,
|
|
29
|
+
browser: parseBrowser(ua),
|
|
30
|
+
os: parseOs(ua),
|
|
31
|
+
device: parseDevice(ua),
|
|
32
|
+
engine: parseEngine(ua),
|
|
33
|
+
cpu: parseCpu(ua),
|
|
34
|
+
isBot: botPattern.test(ua),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function userAgent({ headers }: { headers: Headers }): UserAgent {
|
|
39
|
+
return userAgentFromString(headers.get('user-agent') ?? undefined);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseBrowser(ua: string): UserAgent['browser'] {
|
|
43
|
+
const edge = ua.match(/Edg(?:e|A|iOS)?\/([\d.]+)/);
|
|
44
|
+
if (edge) return { name: 'Edge', version: edge[1] };
|
|
45
|
+
const samsung = ua.match(/SamsungBrowser\/([\d.]+)/);
|
|
46
|
+
if (samsung) return { name: 'Samsung Internet', version: samsung[1] };
|
|
47
|
+
const chrome = ua.match(/(?:Chrome|CriOS)\/([\d.]+)/);
|
|
48
|
+
if (chrome) return { name: 'Chrome', version: chrome[1] };
|
|
49
|
+
const firefox = ua.match(/(?:Firefox|FxiOS)\/([\d.]+)/);
|
|
50
|
+
if (firefox) return { name: 'Firefox', version: firefox[1] };
|
|
51
|
+
if (ua.includes('Safari')) return { name: 'Safari', version: ua.match(/Version\/([\d.]+)/)?.[1] };
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseEngine(ua: string): UserAgent['engine'] {
|
|
56
|
+
const gecko = ua.match(/rv:([\d.]+)\).*Gecko/);
|
|
57
|
+
if (gecko) return { name: 'Gecko', version: gecko[1] };
|
|
58
|
+
const webkit = ua.match(/AppleWebKit\/([\d.]+)/);
|
|
59
|
+
if (webkit) {
|
|
60
|
+
const chrome = ua.match(/(?:Chrome|CriOS|Edg(?:e|A|iOS)?|OPR)\/([\d.]+)/);
|
|
61
|
+
if (chrome) return { name: 'Blink', version: chrome[1] };
|
|
62
|
+
return { name: 'WebKit', version: webkit[1] };
|
|
63
|
+
}
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseCpu(ua: string): UserAgent['cpu'] {
|
|
68
|
+
if (/arm64|aarch64/i.test(ua)) return { architecture: 'arm64' };
|
|
69
|
+
if (/\barm\b/i.test(ua)) return { architecture: 'arm' };
|
|
70
|
+
if (/x86_64|x64|Win64|WOW64/i.test(ua)) return { architecture: 'amd64' };
|
|
71
|
+
if (/i686|\bx86\b/i.test(ua)) return { architecture: 'ia32' };
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseOs(ua: string): UserAgent['os'] {
|
|
76
|
+
if (ua.includes('Windows')) return { name: 'Windows', version: ua.match(/Windows NT ([\d.]+)/)?.[1] };
|
|
77
|
+
if (ua.includes('Android')) return { name: 'Android', version: ua.match(/Android ([\d.]+)/)?.[1] };
|
|
78
|
+
if (/iPhone|iPad|iPod/.test(ua)) {
|
|
79
|
+
return { name: 'iOS', version: ua.match(/OS (\d+(?:[_.]\d+)*)/)?.[1]?.replace(/_/g, '.') };
|
|
80
|
+
}
|
|
81
|
+
if (ua.includes('Mac OS X')) {
|
|
82
|
+
return { name: 'Mac OS', version: ua.match(/Mac OS X (\d+(?:[_.]\d+)*)/)?.[1]?.replace(/_/g, '.') };
|
|
83
|
+
}
|
|
84
|
+
if (ua.includes('Linux')) return { name: 'Linux' };
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseDevice(ua: string): UserAgent['device'] {
|
|
89
|
+
if (ua.includes('iPhone')) return { type: 'mobile', vendor: 'Apple', model: 'iPhone' };
|
|
90
|
+
if (ua.includes('iPad')) return { type: 'tablet', vendor: 'Apple', model: 'iPad' };
|
|
91
|
+
if (ua.includes('Android')) {
|
|
92
|
+
const type = ua.includes('Mobile') ? 'mobile' : 'tablet';
|
|
93
|
+
const samsung = ua.match(/\b(SM-[\w]+)\b/);
|
|
94
|
+
if (samsung) return { type, vendor: 'Samsung', model: samsung[1] };
|
|
95
|
+
const pixel = ua.match(/;\s*(Pixel[\w ]*?)\s*(?:Build|\))/);
|
|
96
|
+
if (pixel) return { type, vendor: 'Google', model: pixel[1] };
|
|
97
|
+
return { type };
|
|
98
|
+
}
|
|
99
|
+
return ua.includes('Mobile') ? { type: 'mobile' } : {};
|
|
100
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// `next/web-vitals` compat surface. Next measures Core Web Vitals (plus its own Next.js-* custom
|
|
4
|
+
// metrics) and invokes the reporter callback per registered hook instance. The Core Web Vitals are
|
|
5
|
+
// backed by the `web-vitals` package - this is browser-bundled client code, so the import stays a plain
|
|
6
|
+
// static import for the bundler to resolve. Next's own custom metrics are not emitted here.
|
|
7
|
+
import { useEffect, useRef } from 'preact/hooks';
|
|
8
|
+
import * as webVitals from 'web-vitals';
|
|
9
|
+
import type { Metric } from 'web-vitals';
|
|
10
|
+
|
|
11
|
+
export interface NextWebVitalsMetric {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
startTime: number;
|
|
15
|
+
value: number;
|
|
16
|
+
label: 'web-vital' | 'custom';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useReportWebVitals(
|
|
20
|
+
reportWebVitalsFn: (metric: NextWebVitalsMetric) => void,
|
|
21
|
+
): void {
|
|
22
|
+
// Route metrics through a ref so the latest reporter identity receives them
|
|
23
|
+
// without resubscribing to web-vitals on every render.
|
|
24
|
+
const reporterRef = useRef(reportWebVitalsFn);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
reporterRef.current = reportWebVitalsFn;
|
|
27
|
+
}, [reportWebVitalsFn]);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const report = (metric: Metric): void => {
|
|
31
|
+
reporterRef.current({
|
|
32
|
+
id: metric.id,
|
|
33
|
+
name: metric.name,
|
|
34
|
+
startTime: metric.entries[0]?.startTime ?? 0,
|
|
35
|
+
value: metric.value,
|
|
36
|
+
label: 'web-vital',
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
webVitals.onTTFB(report);
|
|
41
|
+
webVitals.onFCP(report);
|
|
42
|
+
webVitals.onLCP(report);
|
|
43
|
+
// CLS and INP accumulate over the page lifetime and, by default, only settle
|
|
44
|
+
// when the page is hidden. A page that is never hidden (or one torn down by a
|
|
45
|
+
// navigation before the visibilitychange flush lands) therefore never reports
|
|
46
|
+
// them at all. Both metrics are documented as multi-report: `reportAllChanges`
|
|
47
|
+
// emits each new value as it is determined instead of a single deferred one,
|
|
48
|
+
// so opting in makes delivery deterministic without dropping any information.
|
|
49
|
+
webVitals.onCLS(report, { reportAllChanges: true });
|
|
50
|
+
webVitals.onINP(report, { reportAllChanges: true });
|
|
51
|
+
// `onFID` is deprecated (removed in newer majors); subscribe only if present.
|
|
52
|
+
if (typeof webVitals.onFID === 'function') {
|
|
53
|
+
webVitals.onFID(report);
|
|
54
|
+
}
|
|
55
|
+
}, []);
|
|
56
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// @opentelemetry/api loader + thin typed surface (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// pnext emits Next's span taxonomy through the GLOBAL OpenTelemetry API the app's instrumentation.ts
|
|
4
|
+
// registers. Only `@opentelemetry/api` (the stable facade) is ever touched; the app supplies the SDK.
|
|
5
|
+
// The api package is an OPTIONAL runtime dependency: absent, every helper is an inert no-op so a
|
|
6
|
+
// pure-core app is byte-identical.
|
|
7
|
+
//
|
|
8
|
+
// Resolution: `@opentelemetry/api` is not vendored into pnext (it would pin a version and duplicate the
|
|
9
|
+
// singleton the app registered). It is resolved from the app's own node_modules, so pnext and the app
|
|
10
|
+
// share ONE api singleton - the same module instance the app's provider.register() mutated. A bare
|
|
11
|
+
// import would resolve against pnext's location and never see the registered provider.
|
|
12
|
+
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
|
|
16
|
+
// Structural subset of @opentelemetry/api we use. Kept local so pnext carries no
|
|
17
|
+
// type dependency on the package (it may be absent).
|
|
18
|
+
export interface OtelSpanContext {
|
|
19
|
+
traceId: string;
|
|
20
|
+
spanId: string;
|
|
21
|
+
traceFlags: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface OtelSpan {
|
|
25
|
+
setAttribute(key: string, value: unknown): OtelSpan;
|
|
26
|
+
setAttributes(attrs: Record<string, unknown>): OtelSpan;
|
|
27
|
+
setStatus(status: { code: number; message?: string }): OtelSpan;
|
|
28
|
+
recordException(error: unknown): void;
|
|
29
|
+
updateName(name: string): OtelSpan;
|
|
30
|
+
end(endTime?: number): void;
|
|
31
|
+
spanContext(): OtelSpanContext;
|
|
32
|
+
isRecording(): boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface OtelContext {
|
|
36
|
+
// opaque
|
|
37
|
+
readonly __brand?: 'otel-context';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface OtelTracer {
|
|
41
|
+
startSpan(
|
|
42
|
+
name: string,
|
|
43
|
+
options?: { kind?: number; attributes?: Record<string, unknown>; startTime?: number },
|
|
44
|
+
context?: OtelContext,
|
|
45
|
+
): OtelSpan;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface OtelApi {
|
|
49
|
+
trace: {
|
|
50
|
+
getTracer(name: string, version?: string): OtelTracer;
|
|
51
|
+
getSpan(context: OtelContext): OtelSpan | undefined;
|
|
52
|
+
getSpanContext(context: OtelContext): OtelSpanContext | undefined;
|
|
53
|
+
setSpan(context: OtelContext, span: OtelSpan): OtelContext;
|
|
54
|
+
setSpanContext(context: OtelContext, sc: OtelSpanContext): OtelContext;
|
|
55
|
+
};
|
|
56
|
+
context: {
|
|
57
|
+
active(): OtelContext;
|
|
58
|
+
with<T>(context: OtelContext, fn: () => T): T;
|
|
59
|
+
};
|
|
60
|
+
propagation: {
|
|
61
|
+
extract(context: OtelContext, carrier: unknown, getter?: unknown): OtelContext;
|
|
62
|
+
inject(context: OtelContext, carrier: unknown, setter?: unknown): void;
|
|
63
|
+
fields(): string[];
|
|
64
|
+
};
|
|
65
|
+
ROOT_CONTEXT: OtelContext;
|
|
66
|
+
SpanKind: { INTERNAL: number; SERVER: number; CLIENT: number; PRODUCER: number; CONSUMER: number };
|
|
67
|
+
SpanStatusCode: { UNSET: number; OK: number; ERROR: number };
|
|
68
|
+
TraceFlags: { NONE: number; SAMPLED: number };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let resolved: OtelApi | null | undefined;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Load `@opentelemetry/api` from the app root (once). Returns null when the
|
|
75
|
+
* package is not installed — callers then no-op. Never throws.
|
|
76
|
+
*/
|
|
77
|
+
export function getOtelApi(appRoot: string): OtelApi | null {
|
|
78
|
+
if (resolved !== undefined) return resolved;
|
|
79
|
+
resolved = null;
|
|
80
|
+
try {
|
|
81
|
+
// Resolve against a file inside the app root so Node walks the app's
|
|
82
|
+
// node_modules (where instrumentation.ts's @opentelemetry/api lives).
|
|
83
|
+
const req = createRequire(path.join(appRoot, 'package.json'));
|
|
84
|
+
const api = req('@opentelemetry/api') as OtelApi;
|
|
85
|
+
if (api?.trace && api.context) resolved = api;
|
|
86
|
+
} catch {
|
|
87
|
+
resolved = null;
|
|
88
|
+
}
|
|
89
|
+
return resolved;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Test/bootstrap reset. */
|
|
93
|
+
export function resetOtelApi(): void {
|
|
94
|
+
resolved = undefined;
|
|
95
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// experimental.clientTraceMetadata (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// For each key listed in `experimental.clientTraceMetadata`, inject a `<meta name="<key>"
|
|
4
|
+
// content="<value>">` into the HTML head, where the value comes from running the app's GLOBAL
|
|
5
|
+
// propagator inject() against the active render's span context. The app's propagator decides which keys
|
|
6
|
+
// carry a value; keys with no injected value are skipped, and keys the propagator sets but that are not
|
|
7
|
+
// in the config list are dropped.
|
|
8
|
+
//
|
|
9
|
+
// Gating (matches Next): only DYNAMIC renders, and only in production - a prod STATIC page emits
|
|
10
|
+
// nothing, while dev emits for static too. The tags are produced once per document render, so soft navs
|
|
11
|
+
// never mutate them.
|
|
12
|
+
|
|
13
|
+
import { getNextConfig } from '../next/config-loader';
|
|
14
|
+
import { activeOtelContext, carrierSetter } from './tracer';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Return the clientTraceMetadata `<meta>` tag HTML for the current render, or ''
|
|
18
|
+
* when the feature is off / not applicable. `dynamic` is true when the render is
|
|
19
|
+
* request-dependent (Next only injects on dynamic renders in prod). Values are
|
|
20
|
+
* escaped by the caller's head-composition path if needed; keys/values here come
|
|
21
|
+
* from config + the propagator and are attribute-safe in practice, but we escape
|
|
22
|
+
* defensively.
|
|
23
|
+
*/
|
|
24
|
+
export function clientTraceMetadataTags(input: {
|
|
25
|
+
dynamic: boolean;
|
|
26
|
+
isNextStart: boolean;
|
|
27
|
+
rsc: boolean;
|
|
28
|
+
}): string {
|
|
29
|
+
const keys = configuredKeys();
|
|
30
|
+
if (keys.length === 0) return '';
|
|
31
|
+
if (input.rsc) return '';
|
|
32
|
+
// Prod + static → no injection. Dev injects for static too (handled by the
|
|
33
|
+
// caller passing dynamic=true in dev, since a dev render is never prebuilt).
|
|
34
|
+
if (input.isNextStart && !input.dynamic) return '';
|
|
35
|
+
|
|
36
|
+
const active = activeOtelContext();
|
|
37
|
+
if (!active) return '';
|
|
38
|
+
const { api, context } = active;
|
|
39
|
+
|
|
40
|
+
const carrier: Record<string, string> = {};
|
|
41
|
+
try {
|
|
42
|
+
api.propagation.inject(context, carrier, carrierSetter);
|
|
43
|
+
} catch {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const tags: string[] = [];
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
const value = carrier[key];
|
|
50
|
+
if (value === undefined || value === null) continue;
|
|
51
|
+
tags.push(`<meta name="${escapeAttr(key)}" content="${escapeAttr(String(value))}"/>`);
|
|
52
|
+
}
|
|
53
|
+
return tags.join('');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function configuredKeys(): string[] {
|
|
57
|
+
const experimental = getNextConfig().experimental as
|
|
58
|
+
| { clientTraceMetadata?: unknown }
|
|
59
|
+
| undefined;
|
|
60
|
+
const list = experimental?.clientTraceMetadata;
|
|
61
|
+
if (!Array.isArray(list)) return [];
|
|
62
|
+
return list.filter((k): k is string => typeof k === 'string');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function escapeAttr(value: string): string {
|
|
66
|
+
return value
|
|
67
|
+
.replace(/&/g, '&')
|
|
68
|
+
.replace(/"/g, '"')
|
|
69
|
+
.replace(/</g, '<')
|
|
70
|
+
.replace(/>/g, '>');
|
|
71
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// AppRender.fetch span (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// Wraps globalThis.fetch to emit a `fetch <METHOD> <url>` CLIENT span nested under the request's render,
|
|
4
|
+
// for every outgoing fetch made during a render. Installed AFTER the cache fetch-patch (register order:
|
|
5
|
+
// cache before otel) so the span brackets the cache layer too. Disabled by NEXT_OTEL_FETCH_DISABLED=1.
|
|
6
|
+
// Only emits when a root span is open, so build-time and out-of-request fetches pass through untouched.
|
|
7
|
+
|
|
8
|
+
import { SPAN_TYPE, withChildSpan, rootOtelContext, otelExportInProgress } from './tracer';
|
|
9
|
+
import { getOtelApi } from './api';
|
|
10
|
+
|
|
11
|
+
let installed = false;
|
|
12
|
+
|
|
13
|
+
export function installFetchSpan(appRoot: string): void {
|
|
14
|
+
if (installed) return;
|
|
15
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
16
|
+
if (process.env.NEXT_OTEL_FETCH_DISABLED === '1') return;
|
|
17
|
+
if (!getOtelApi(appRoot)) return;
|
|
18
|
+
installed = true;
|
|
19
|
+
|
|
20
|
+
const original = globalThis.fetch.bind(globalThis);
|
|
21
|
+
const patched = async function fetch(
|
|
22
|
+
input: Parameters<typeof globalThis.fetch>[0],
|
|
23
|
+
init?: Parameters<typeof globalThis.fetch>[1],
|
|
24
|
+
): Promise<Response> {
|
|
25
|
+
// Only trace inside an active request render (root span open).
|
|
26
|
+
if (otelExportInProgress()) return original(input, init);
|
|
27
|
+
if (!rootOtelContext()) return original(input, init);
|
|
28
|
+
const info = describeFetch(input, init);
|
|
29
|
+
if (!info) return original(input, init);
|
|
30
|
+
const name = `fetch ${info.method} ${info.url}`;
|
|
31
|
+
return withChildSpan(
|
|
32
|
+
name,
|
|
33
|
+
SPAN_TYPE.fetch,
|
|
34
|
+
{
|
|
35
|
+
'http.method': info.method,
|
|
36
|
+
'http.url': info.url,
|
|
37
|
+
'net.peer.name': info.host,
|
|
38
|
+
},
|
|
39
|
+
() => original(input, init),
|
|
40
|
+
{ kind: fetchSpanKind() },
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
globalThis.fetch = patched as typeof globalThis.fetch;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function fetchSpanKind(): number | undefined {
|
|
47
|
+
// SpanKind.CLIENT (2) — resolved lazily so we don't hold the api reference.
|
|
48
|
+
return 2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function describeFetch(
|
|
52
|
+
input: Parameters<typeof globalThis.fetch>[0],
|
|
53
|
+
init?: Parameters<typeof globalThis.fetch>[1],
|
|
54
|
+
): { method: string; url: string; host: string } | undefined {
|
|
55
|
+
try {
|
|
56
|
+
let url: string;
|
|
57
|
+
let method = 'GET';
|
|
58
|
+
if (typeof input === 'string' || input instanceof URL) {
|
|
59
|
+
url = String(input);
|
|
60
|
+
method = (init?.method ?? 'GET').toUpperCase();
|
|
61
|
+
} else if (input instanceof Request) {
|
|
62
|
+
url = input.url;
|
|
63
|
+
method = (init?.method ?? input.method).toUpperCase();
|
|
64
|
+
} else {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
if (!/^https?:/i.test(url)) return undefined;
|
|
68
|
+
const parsed = new URL(url);
|
|
69
|
+
return { method, url: parsed.href, host: parsed.hostname };
|
|
70
|
+
} catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function resetFetchSpanForTest(): void {
|
|
76
|
+
installed = false;
|
|
77
|
+
}
|