@wular/pnext 0.0.4 → 0.0.6
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 +11 -11
- package/bin/pnext +1 -1
- package/config/lint/base.js +7 -7
- package/config/ts/base.json +2 -4
- package/config/ts/react.json +2 -6
- package/package.json +23 -2
- package/reference/compat.md +1 -1
- package/reference/config.md +2 -2
- package/reference/css.md +13 -9
- package/reference/dev.md +1 -1
- package/reference/metadata.md +7 -7
- package/reference/navigation.md +35 -28
- package/reference/performance.md +70 -70
- package/reference/rendering.md +17 -17
- package/reference/routing.md +17 -17
- package/reference/typegen.md +12 -8
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +113 -109
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +49 -52
- package/src/api/navigation.ts +59 -58
- package/src/api/server.ts +152 -144
- package/src/api/suspense.ts +4 -4
- package/src/cli/adapters/vercel-warm.ts +126 -121
- package/src/cli/adapters/vercel.ts +437 -443
- package/src/cli/analyze.ts +333 -144
- package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
- package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
- package/src/cli/build.ts +976 -978
- package/src/cli/create.ts +71 -63
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +88 -85
- package/src/cli/migrate/package-json.ts +73 -71
- package/src/cli/migrate/report.ts +32 -33
- package/src/cli/migrate/{index.ts → run.ts} +48 -49
- package/src/cli/migrate/scan.ts +46 -46
- package/src/cli/migrate/spinner.ts +9 -9
- package/src/cli/migrate/tsconfig.ts +35 -35
- package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
- package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
- package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
- package/src/cli/start.ts +65 -66
- package/src/{typegen.ts → cli/typegen.ts} +59 -59
- package/src/client/build.ts +794 -768
- package/src/client/chunk-fold.ts +245 -240
- package/src/client/{paths.ts → chunk-name.ts} +6 -6
- package/src/client/entry.ts +162 -147
- package/src/client/prebuilt.ts +231 -223
- package/src/client/profile.ts +29 -29
- package/src/client/react-compiler.ts +20 -15
- package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
- package/src/client/reference-stub.ts +51 -51
- package/src/client/reference.ts +19 -19
- package/src/{api → client}/router/events.ts +17 -17
- package/src/{api → client}/router/history.ts +16 -16
- package/src/{api → client}/router/hub.ts +52 -53
- package/src/{api/router.ts → client/router/index.ts} +61 -60
- package/src/{api → client}/router/policies.ts +24 -24
- package/src/{api → client}/router/runtime.ts +1988 -1992
- package/src/{api → client}/router/types.ts +93 -98
- package/src/compat/actions/client-plugin.ts +42 -43
- package/src/compat/actions/client-stub.ts +14 -14
- package/src/compat/actions/{action-client.ts → client.ts} +194 -193
- package/src/compat/actions/config.ts +63 -64
- package/src/compat/actions/detect.ts +68 -68
- package/src/compat/actions/discovery.ts +105 -105
- package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
- package/src/compat/actions/early-submit.ts +1 -1
- package/src/compat/actions/endpoint.ts +198 -198
- package/src/compat/actions/extensions.ts +811 -0
- package/src/compat/actions/flight.ts +23 -23
- package/src/compat/actions/form-state.ts +34 -34
- package/src/compat/actions/hoist.ts +382 -236
- package/src/compat/actions/ids.ts +7 -12
- package/src/compat/actions/index.ts +8 -8
- package/src/compat/actions/instances.ts +46 -46
- package/src/compat/actions/origin.ts +47 -48
- package/src/compat/actions/protocol.ts +22 -22
- package/src/compat/actions/registry.ts +19 -19
- package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
- package/src/compat/actions/rewrite.ts +125 -125
- package/src/compat/actions/{action-router.ts → router.ts} +10 -10
- package/src/compat/actions/serve.ts +132 -136
- package/src/compat/actions/server-tag.ts +4 -4
- package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
- package/src/compat/actions/unrecognized-error.ts +4 -4
- package/src/compat/{index.ts → aliases.ts} +112 -109
- package/src/compat/bundler/bun-externals.ts +18 -18
- package/src/compat/bundler/cjs-exports.ts +271 -223
- package/src/compat/bundler/config.ts +116 -111
- package/src/compat/bundler/externals.ts +12 -12
- package/src/compat/bundler/import-meta-url.ts +19 -19
- package/src/compat/bundler/modularize-imports.ts +36 -33
- package/src/compat/bundler/new-url-asset.ts +22 -24
- package/src/compat/bundler/optimize-package-imports.ts +111 -107
- package/src/compat/bundler/polyfill.ts +28 -28
- package/src/compat/bundler/react-compiler.ts +35 -29
- package/src/compat/bundler/react-profiler.tsx +11 -11
- package/src/compat/bundler/relay-transform.ts +48 -47
- package/src/compat/bundler/require-context.ts +119 -113
- package/src/compat/bundler/resolve-extensions.ts +19 -19
- package/src/compat/bundler/source-cache.ts +25 -25
- package/src/compat/bundler/static-imports.ts +7 -7
- package/src/compat/bundler/symlink-imports.ts +46 -46
- package/src/compat/bundler/tsconfig-paths.ts +13 -15
- package/src/compat/bundler/wasm.ts +58 -60
- package/src/compat/bundler/webpack-loaders.ts +254 -241
- package/src/compat/bundler/worker.ts +101 -104
- package/src/compat/cache/build-flags.ts +29 -29
- package/src/compat/cache/build-prerender-errors.ts +40 -44
- package/src/compat/cache/custom-handler.ts +60 -53
- package/src/compat/cache/fetch-patch.ts +240 -240
- package/src/compat/cache/handler.ts +27 -31
- package/src/compat/cache/modern-handler.ts +148 -126
- package/src/compat/cache/resume-data-cache.ts +47 -45
- package/src/compat/cache/revalidate.ts +233 -232
- package/src/compat/cache/runtime-error.ts +35 -35
- package/src/compat/cache/use-cache-transform.ts +442 -417
- package/src/compat/cache/use-cache.ts +650 -614
- package/src/compat/cache-control.ts +140 -142
- package/src/compat/client/base-path.ts +21 -20
- package/src/compat/client/css-order.ts +18 -18
- package/src/compat/client/errors/bare-boundary.ts +11 -11
- package/src/compat/client/errors/control-flow.ts +23 -23
- package/src/compat/client/errors/error-boundary.ts +61 -61
- package/src/compat/client/errors/global-error.ts +101 -91
- package/src/compat/client/errors/install.ts +71 -72
- package/src/compat/client/errors/lazy.ts +23 -23
- package/src/compat/client/errors/primitive-throw.ts +47 -45
- package/src/compat/client/errors/soft-refresh.ts +4 -4
- package/src/compat/client/link-status.ts +33 -33
- package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
- package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
- package/src/compat/client/navigation-scroll.ts +63 -63
- package/src/compat/client/optimistic-routing.ts +93 -88
- package/src/compat/client/prefetch-cache.ts +23 -24
- package/src/compat/client/route-announcer.ts +35 -35
- package/src/compat/client/segment-cache-policy.ts +20 -20
- package/src/compat/client/segment-cache.ts +309 -315
- package/src/compat/client/segment-prefetch.ts +127 -132
- package/src/compat/client/trailing-slash.ts +5 -4
- package/src/compat/css/chunking.ts +113 -116
- package/src/compat/css/inline-css.ts +21 -21
- package/src/compat/css/lightningcss.ts +37 -38
- package/src/compat/css/modules.ts +161 -175
- package/src/compat/css/nonce.ts +7 -7
- package/src/compat/css/sass-plugin.ts +18 -21
- package/src/compat/css/sass.ts +150 -152
- package/src/compat/css/styled-jsx-runtime.ts +27 -27
- package/src/compat/css/styled-jsx.ts +21 -21
- package/src/compat/edge-runtime.ts +27 -27
- package/src/compat/{adapter → export}/build-complete.ts +72 -75
- package/src/compat/export/client.ts +29 -31
- package/src/compat/export/{index.ts → emit.ts} +111 -110
- package/src/compat/export/standalone.ts +62 -54
- package/src/compat/image-optimizer/cache.ts +51 -49
- package/src/compat/image-optimizer/detect.ts +52 -52
- package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
- package/src/compat/image-optimizer/source.ts +77 -80
- package/src/compat/lifecycle/after-scope.ts +26 -26
- package/src/compat/lifecycle/after.ts +48 -45
- package/src/compat/lifecycle/error-funnel.ts +98 -102
- package/src/compat/lifecycle/error-serialize.ts +30 -26
- package/src/compat/lifecycle/error-ui.ts +67 -32
- package/src/compat/lifecycle/instrumentation-client.ts +36 -38
- package/src/compat/lifecycle/instrumentation.ts +85 -85
- package/src/compat/lifecycle/node-console.ts +11 -11
- package/src/compat/lifecycle/testmode.ts +160 -132
- package/src/compat/mdx/compile.ts +60 -57
- package/src/compat/mdx/plugin.ts +11 -11
- package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
- package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
- package/src/compat/metadata.ts +75 -81
- package/src/compat/next/cache.ts +68 -69
- package/src/compat/next/canonical-url.ts +9 -9
- package/src/compat/next/client-cache.ts +19 -19
- package/src/compat/next/client-navigation.ts +112 -116
- package/src/compat/next/client-only.ts +1 -1
- package/src/compat/next/client-script.tsx +99 -93
- package/src/compat/next/client-server.ts +10 -10
- package/src/compat/next/config-loader.ts +176 -173
- package/src/compat/next/config.ts +7 -7
- package/src/compat/next/constants.cjs +6 -6
- package/src/compat/next/constants.ts +6 -6
- package/src/compat/next/custom-server.ts +26 -24
- package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
- package/src/compat/next/dynamic.tsx +21 -18
- package/src/compat/next/error.tsx +52 -52
- package/src/compat/next/font/cache.ts +74 -74
- package/src/compat/next/font/google.ts +2 -2
- package/src/compat/next/font/index.ts +1 -1
- package/src/compat/next/font/local.ts +3 -3
- package/src/compat/next/font/runtime-client.ts +17 -15
- package/src/compat/next/font/runtime.ts +443 -408
- package/src/compat/next/font/shared.ts +120 -108
- package/src/compat/next/form.tsx +63 -63
- package/src/compat/next/head.tsx +2 -2
- package/src/compat/next/headers.ts +103 -95
- package/src/compat/next/image/client.tsx +220 -0
- package/src/compat/next/image/config.ts +56 -58
- package/src/compat/next/image/optimizer.ts +40 -36
- package/src/compat/next/image/patterns.ts +37 -40
- package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
- package/src/compat/next/image/shared.ts +65 -57
- package/src/compat/next/image/static-metadata.ts +98 -107
- package/src/compat/next/image/validate.ts +79 -88
- package/src/compat/next/image.tsx +19 -23
- package/src/compat/next/index.ts +1 -1
- package/src/compat/next/legacy-image.tsx +59 -60
- package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
- package/src/compat/next/link.tsx +158 -158
- package/src/compat/next/navigation.cjs +12 -3
- package/src/compat/next/navigation.ts +48 -50
- package/src/compat/next/offline.ts +27 -27
- package/src/compat/next/og.ts +121 -124
- package/src/compat/next/preferred-region.ts +13 -14
- package/src/compat/next/redirects.ts +58 -56
- package/src/compat/next/resource-hints.ts +73 -76
- package/src/compat/next/rewrites.ts +130 -133
- package/src/compat/next/root-params.ts +45 -45
- package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
- package/src/compat/next/router.cjs +4 -2
- package/src/compat/next/router.ts +58 -61
- package/src/compat/next/script.tsx +108 -108
- package/src/compat/next/server-only.ts +1 -1
- package/src/compat/next/server.ts +19 -19
- package/src/compat/next/svgr.ts +18 -17
- package/src/compat/next/telemetry.ts +24 -24
- package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
- package/src/compat/next/user-agent.ts +53 -49
- package/src/compat/next/web-vitals.ts +22 -24
- package/src/compat/otel/api.ts +41 -41
- package/src/compat/otel/client-trace-metadata.ts +25 -27
- package/src/compat/otel/fetch-span.ts +29 -29
- package/src/compat/otel/tracer.ts +331 -331
- package/src/compat/pages/api.ts +456 -0
- package/src/compat/pages/client-plugin.ts +36 -36
- package/src/compat/pages/router-state.ts +34 -34
- package/src/compat/pages/{index.ts → router.ts} +130 -135
- package/src/compat/ppr/io.ts +12 -12
- package/src/compat/ppr/missing-root-params.ts +34 -38
- package/src/compat/ppr/root-params-scan.ts +66 -66
- package/src/compat/ppr/root-params-transform.ts +24 -26
- package/src/compat/ppr/root-params.ts +30 -30
- package/src/compat/ppr/segment-config-incompat.ts +6 -7
- package/src/compat/protocol.ts +71 -70
- package/src/compat/react/action-state.ts +47 -48
- package/src/compat/react/client-lite.ts +15 -15
- package/src/compat/react/client.ts +4 -4
- package/src/compat/react/compiler-runtime.ts +11 -11
- package/src/compat/react/dom-client.ts +44 -44
- package/src/compat/react/dom-react-server.ts +10 -16
- package/src/compat/react/dom-server.ts +10 -10
- package/src/compat/react/dom.ts +52 -52
- package/src/compat/react/hooks-extra.ts +34 -35
- package/src/compat/react/parity.ts +64 -61
- package/src/compat/react/preact.ts +81 -82
- package/src/compat/react/react-server.ts +28 -28
- package/src/compat/react/router-shim.ts +1 -1
- package/src/compat/react/server-component-use.ts +8 -8
- package/src/compat/react/server-inserted-html.ts +30 -31
- package/src/compat/react/server.ts +53 -55
- package/src/compat/react/use.ts +32 -32
- package/src/compat/react/view-transition.ts +20 -20
- package/src/compat/register/actions.ts +35 -824
- package/src/compat/register/boot.ts +41 -41
- package/src/compat/register/build-tier.ts +5 -5
- package/src/compat/register/build.ts +74 -70
- package/src/compat/register/bundler.ts +161 -156
- package/src/compat/register/cache.ts +20 -20
- package/src/compat/register/client-errors.ts +3 -3
- package/src/compat/register/config.ts +6 -6
- package/src/compat/register/css-extras.ts +30 -34
- package/src/compat/register/edge-runtime.ts +3 -3
- package/src/compat/register/errors.ts +10 -12
- package/src/compat/register/export.ts +16 -16
- package/src/compat/register/font.ts +11 -11
- package/src/compat/register/hooks.ts +2 -2
- package/src/compat/register/image.ts +40 -40
- package/src/compat/register/index.ts +59 -62
- package/src/compat/register/instrumentation-client.ts +10 -10
- package/src/compat/register/lifecycle.ts +25 -28
- package/src/compat/register/mdx.ts +10 -10
- package/src/compat/register/middleware.ts +226 -16
- package/src/compat/register/otel.ts +80 -88
- package/src/compat/register/pages-api.ts +10 -463
- package/src/compat/register/ppr.ts +16 -16
- package/src/compat/register/protocol.ts +17 -18
- package/src/compat/register/proxy.ts +49 -51
- package/src/compat/register/render.ts +79 -76
- package/src/compat/register/routing.ts +162 -159
- package/src/compat/register/segment.ts +24 -1897
- package/src/compat/register/static-image.ts +3 -3
- package/src/compat/register/{misc.ts → taint.ts} +9 -9
- package/src/compat/register/typed-routes.ts +14 -14
- package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
- package/src/compat/register/validation.ts +18 -18
- package/src/compat/segment/loading-boundary.ts +43 -45
- package/src/compat/segment/page-slot.ts +69 -69
- package/src/compat/segment/serve.ts +1884 -0
- package/src/compat/segment/tree.ts +113 -112
- package/src/compat/segment/vary-key.ts +38 -38
- package/src/compat/segment/vary-params.ts +138 -142
- package/src/compat/static-params.ts +14 -12
- package/src/compat/tsconfig-defaults.ts +87 -91
- package/src/compat/typecheck/{index.ts → check.ts} +234 -212
- package/src/compat/typecheck/worker.ts +15 -12
- package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
- package/src/compat/typed-routes/manifest.ts +174 -170
- package/src/compat/typed-routes/typegen.ts +127 -110
- package/src/compat/validation/errors.ts +16 -19
- package/src/compat/validation/prerender-diagnostics.ts +521 -504
- package/src/compat/validation/{index.ts → validate.ts} +647 -648
- package/src/compat-bootstrap.ts +16 -16
- package/src/config.ts +69 -69
- package/src/css/build.ts +230 -227
- package/src/css/postcss.ts +79 -80
- package/src/css/worker.ts +14 -15
- package/src/dev/client-actions.ts +10 -10
- package/src/dev/client-chunk-store.ts +27 -27
- package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
- package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
- package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
- package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
- package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
- package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
- package/src/dev/server.ts +804 -820
- package/src/env.ts +46 -43
- package/src/extensions.ts +491 -478
- package/src/index.ts +8 -8
- package/src/internal.ts +20 -23
- package/src/{islands → render}/boundary-error.ts +3 -3
- package/src/render/hooks.ts +71 -71
- package/src/render/island-context.ts +14 -14
- package/src/render/metadata.ts +310 -310
- package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
- package/src/{ppr.ts → render/ppr.ts} +244 -245
- package/src/render/renderer.ts +2076 -2082
- package/src/render/resource-hints.ts +16 -17
- package/src/render/slots.tsx +224 -235
- package/src/{islands → render}/static-children.ts +9 -12
- package/src/{islands → render}/static-slots.ts +37 -37
- package/src/{cache/context.ts → request/cache.ts} +20 -20
- package/src/request/context.ts +107 -107
- package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +318 -296
- package/src/resolve/source-text.ts +37 -37
- package/src/{dynamic → resolve}/tree-shake.ts +132 -128
- package/src/routing/forwarded.ts +19 -19
- package/src/routing/handler.ts +84 -91
- package/src/routing/href.ts +69 -70
- package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
- package/src/{proxy.ts → routing/proxy.ts} +306 -312
- package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
- package/src/routing/routes.ts +827 -815
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +952 -0
- package/src/{dev → runtime}/module-cache.ts +309 -287
- package/src/{dev → runtime}/module-generations.ts +9 -9
- package/src/{dev → runtime}/module-transform.ts +81 -72
- package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
- package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
- package/src/runtime/vendor.ts +425 -404
- package/src/styles.d.ts +9 -0
- package/src/types.ts +320 -335
- package/src/utils/ansi.ts +5 -5
- package/src/utils/{source.ts → code.ts} +15 -12
- package/src/utils/content-type.ts +3 -3
- package/src/utils/decode.ts +2 -2
- package/src/utils/dev-profile.ts +13 -13
- package/src/utils/error-log.ts +6 -6
- package/src/utils/esbuild.ts +18 -18
- package/src/utils/fs-cache.ts +13 -13
- package/src/utils/fs.ts +57 -49
- package/src/utils/html.ts +20 -24
- package/src/utils/native-require.ts +8 -8
- package/src/utils/serialize.ts +139 -146
- package/src/utils/verbose.ts +18 -18
- package/src/cli/analyze-print.ts +0 -181
- package/src/compat/middleware/manifest.ts +0 -210
- package/src/compat/next/image-client.tsx +0 -215
- package/src/compat/next/link-usage.ts +0 -29
- package/src/css/index.ts +0 -2
- package/src/render/index.ts +0 -1
- package/src/style-modules.d.ts +0 -9
package/src/render/metadata.ts
CHANGED
|
@@ -10,58 +10,58 @@ import type {
|
|
|
10
10
|
ResourceHint,
|
|
11
11
|
MetadataTwitter,
|
|
12
12
|
Viewport,
|
|
13
|
-
} from '../types'
|
|
13
|
+
} from '../types'
|
|
14
14
|
import {
|
|
15
15
|
metadataHasOwnImages,
|
|
16
16
|
staticMetadataLink,
|
|
17
17
|
type StaticMetadataForPath,
|
|
18
|
-
} from '../routing/metadata'
|
|
19
|
-
import { escapeHtml, stringifyHtmlValue } from '../utils/html'
|
|
20
|
-
import { getTrailingSlashUrls, withBasePath } from '../routing/href'
|
|
18
|
+
} from '../routing/metadata-files'
|
|
19
|
+
import { escapeHtml, stringifyHtmlValue } from '../utils/html'
|
|
20
|
+
import { getTrailingSlashUrls, withBasePath } from '../routing/href'
|
|
21
21
|
|
|
22
|
-
export type MetadataExport = Metadata | (() => Metadata | Promise<Metadata>)
|
|
22
|
+
export type MetadataExport = Metadata | (() => Metadata | Promise<Metadata>)
|
|
23
23
|
|
|
24
|
-
export type ViewportExport = Viewport | (() => Viewport | Promise<Viewport>)
|
|
24
|
+
export type ViewportExport = Viewport | (() => Viewport | Promise<Viewport>)
|
|
25
25
|
|
|
26
26
|
export interface MetadataEntry {
|
|
27
|
-
metadata: Metadata
|
|
28
|
-
dir?: string
|
|
29
|
-
kind?: 'layout' | 'page'
|
|
30
|
-
root?: boolean
|
|
27
|
+
metadata: Metadata
|
|
28
|
+
dir?: string
|
|
29
|
+
kind?: 'layout' | 'page'
|
|
30
|
+
root?: boolean
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface MetadataContext {
|
|
34
|
-
url?: URL
|
|
35
|
-
metadataBase?: URL
|
|
34
|
+
url?: URL
|
|
35
|
+
metadataBase?: URL
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export interface MetadataRenderPage {
|
|
39
|
-
metadata: Metadata
|
|
40
|
-
metadataUrl?: string
|
|
41
|
-
fontPreloads: MetadataLink[]
|
|
42
|
-
resourceHints?: ResourceHint[]
|
|
43
|
-
stylesheets: StylesheetLink[]
|
|
44
|
-
modulePreloads?: string[]
|
|
45
|
-
dynamicScriptPreloads?: string[]
|
|
46
|
-
staticMetadata?: StaticMetadataForPath
|
|
47
|
-
suppressIconLinks?: boolean
|
|
39
|
+
metadata: Metadata
|
|
40
|
+
metadataUrl?: string
|
|
41
|
+
fontPreloads: MetadataLink[]
|
|
42
|
+
resourceHints?: ResourceHint[]
|
|
43
|
+
stylesheets: StylesheetLink[]
|
|
44
|
+
modulePreloads?: string[]
|
|
45
|
+
dynamicScriptPreloads?: string[]
|
|
46
|
+
staticMetadata?: StaticMetadataForPath
|
|
47
|
+
suppressIconLinks?: boolean
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export interface StylesheetLink {
|
|
51
|
-
url: string | string[]
|
|
52
|
-
dataPrecedence?: string
|
|
51
|
+
url: string | string[]
|
|
52
|
+
dataPrecedence?: string
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export function metadataContext(page: MetadataRenderPage): MetadataContext {
|
|
56
|
-
const url = page.metadataUrl ? new URL(page.metadataUrl) : undefined
|
|
56
|
+
const url = page.metadataUrl ? new URL(page.metadataUrl) : undefined
|
|
57
57
|
return {
|
|
58
58
|
url,
|
|
59
59
|
metadataBase: coerceMetadataBase(page.metadata.metadataBase, url),
|
|
60
|
-
}
|
|
60
|
+
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function headLinks(page: MetadataRenderPage, context: MetadataContext) {
|
|
64
|
-
const canonical = coerceCanonical(page.metadata.alternates, context)
|
|
64
|
+
const canonical = coerceCanonical(page.metadata.alternates, context)
|
|
65
65
|
const links: (MetadataLink | ResourceHint)[] = [
|
|
66
66
|
...page.fontPreloads,
|
|
67
67
|
...(page.resourceHints ?? []),
|
|
@@ -85,8 +85,8 @@ export function headLinks(page: MetadataRenderPage, context: MetadataContext) {
|
|
|
85
85
|
...appleStartupLinks(page.metadata.appleWebApp, context),
|
|
86
86
|
...coerceIcons(page.metadata.icons, context),
|
|
87
87
|
...(page.metadata.links ?? []),
|
|
88
|
-
]
|
|
89
|
-
return links.map(link => ` <link ${linkAttrs(link)}/>`).join('\n')
|
|
88
|
+
]
|
|
89
|
+
return links.map(link => ` <link ${linkAttrs(link)}/>`).join('\n')
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function stylesheetLinks(stylesheets: StylesheetLink[]): MetadataLink[] {
|
|
@@ -96,25 +96,25 @@ function stylesheetLinks(stylesheets: StylesheetLink[]): MetadataLink[] {
|
|
|
96
96
|
url: href,
|
|
97
97
|
...(dataPrecedence ? { dataPrecedence } : {}),
|
|
98
98
|
})),
|
|
99
|
-
)
|
|
99
|
+
)
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
export function metadataIconLinks(metadata: Metadata, context: MetadataContext, indent = ' ') {
|
|
103
103
|
return coerceIcons(metadata.icons, context)
|
|
104
104
|
.map(link => `${indent}<link ${linkAttrs(link)}/>`)
|
|
105
|
-
.join('\n')
|
|
105
|
+
.join('\n')
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function staticMetadataLinks(page: MetadataRenderPage): MetadataLink[] {
|
|
109
|
-
const metadata = page.staticMetadata
|
|
110
|
-
if (!metadata) return []
|
|
111
|
-
const hasExplicitIcons = page.metadata.icons !== undefined
|
|
112
|
-
const hasExplicitManifest = page.metadata.manifest !== undefined
|
|
109
|
+
const metadata = page.staticMetadata
|
|
110
|
+
if (!metadata) return []
|
|
111
|
+
const hasExplicitIcons = page.metadata.icons !== undefined
|
|
112
|
+
const hasExplicitManifest = page.metadata.manifest !== undefined
|
|
113
113
|
if (page.suppressIconLinks) {
|
|
114
114
|
return prefixLinks([
|
|
115
115
|
...(!hasExplicitManifest && metadata.manifestLink ? [metadata.manifestLink] : []),
|
|
116
116
|
...(!hasExplicitManifest && metadata.manifest ? [staticMetadataLink(metadata.manifest)] : []),
|
|
117
|
-
])
|
|
117
|
+
])
|
|
118
118
|
}
|
|
119
119
|
return prefixLinks([
|
|
120
120
|
...(metadata.favicon ? [staticMetadataLink(metadata.favicon)] : []),
|
|
@@ -127,25 +127,25 @@ function staticMetadataLinks(page: MetadataRenderPage): MetadataLink[] {
|
|
|
127
127
|
...(!hasExplicitIcons && metadata.icons.length === 0
|
|
128
128
|
? metadata.rootIcons.map(staticMetadataLink)
|
|
129
129
|
: []),
|
|
130
|
-
])
|
|
130
|
+
])
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// File-convention metadata assets (favicon, icons, manifest, ...) are served
|
|
134
134
|
// under the configured basePath, so their hrefs carry the prefix. A no-basePath
|
|
135
135
|
// app leaves every href untouched (withBasePath is a no-op).
|
|
136
136
|
function prefixLinks(links: MetadataLink[]): MetadataLink[] {
|
|
137
|
-
return links.map(link => ({ ...link, url: withBasePath(link.url) }))
|
|
137
|
+
return links.map(link => ({ ...link, url: withBasePath(link.url) }))
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
export function applyStaticMetadata(
|
|
141
141
|
metadata: Metadata,
|
|
142
142
|
staticMetadata: StaticMetadataForPath,
|
|
143
143
|
): Metadata {
|
|
144
|
-
let next = metadata
|
|
144
|
+
let next = metadata
|
|
145
145
|
const openGraphImages = [
|
|
146
146
|
...(staticMetadata.openGraphImage ? [staticMetadata.openGraphImage] : []),
|
|
147
147
|
...(staticMetadata.dynamicOpenGraphImages ?? []),
|
|
148
|
-
].map(prefixImageBasePath)
|
|
148
|
+
].map(prefixImageBasePath)
|
|
149
149
|
if (openGraphImages.length > 0 && !metadataHasOwnImages(metadata, 'openGraph')) {
|
|
150
150
|
next = {
|
|
151
151
|
...next,
|
|
@@ -153,12 +153,12 @@ export function applyStaticMetadata(
|
|
|
153
153
|
...(next.openGraph ?? {}),
|
|
154
154
|
images: openGraphImages.length === 1 ? openGraphImages[0] : openGraphImages,
|
|
155
155
|
},
|
|
156
|
-
}
|
|
156
|
+
}
|
|
157
157
|
}
|
|
158
158
|
const twitterImages = [
|
|
159
159
|
...(staticMetadata.twitterImage ? [staticMetadata.twitterImage] : []),
|
|
160
160
|
...(staticMetadata.dynamicTwitterImages ?? []),
|
|
161
|
-
].map(prefixImageBasePath)
|
|
161
|
+
].map(prefixImageBasePath)
|
|
162
162
|
if (twitterImages.length > 0 && !metadataHasOwnImages(metadata, 'twitter')) {
|
|
163
163
|
next = {
|
|
164
164
|
...next,
|
|
@@ -166,9 +166,9 @@ export function applyStaticMetadata(
|
|
|
166
166
|
...(next.twitter ?? {}),
|
|
167
167
|
images: twitterImages.length === 1 ? twitterImages[0] : twitterImages,
|
|
168
168
|
},
|
|
169
|
-
}
|
|
169
|
+
}
|
|
170
170
|
}
|
|
171
|
-
return next
|
|
171
|
+
return next
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// File-convention og/twitter images resolve to a root-relative asset path
|
|
@@ -176,153 +176,153 @@ export function applyStaticMetadata(
|
|
|
176
176
|
// served with that prefix, so the href carries it. String/URL images and a
|
|
177
177
|
// no-basePath app are left untouched.
|
|
178
178
|
function prefixImageBasePath(image: MetadataImage): MetadataImage {
|
|
179
|
-
if (typeof image === 'string') return withBasePath(image)
|
|
180
|
-
if (image instanceof URL) return image
|
|
179
|
+
if (typeof image === 'string') return withBasePath(image)
|
|
180
|
+
if (image instanceof URL) return image
|
|
181
181
|
if (typeof image === 'object' && image !== null && typeof image.url === 'string') {
|
|
182
|
-
return { ...image, url: withBasePath(image.url) }
|
|
182
|
+
return { ...image, url: withBasePath(image.url) }
|
|
183
183
|
}
|
|
184
|
-
return image
|
|
184
|
+
return image
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
// Emits the common metadata <meta> tags. Every value passes through
|
|
188
188
|
// escapeHtml (which itself coerces via String()), so no value shape can throw.
|
|
189
189
|
export function metadataTags(metadata: Metadata, context: MetadataContext): string {
|
|
190
|
-
const meta = metadata
|
|
191
|
-
const tags: string[] = []
|
|
190
|
+
const meta = metadata
|
|
191
|
+
const tags: string[] = []
|
|
192
192
|
const push = (name: string, content: string | undefined) => {
|
|
193
|
-
if (content) tags.push(`<meta name="${escapeHtml(name)}" content="${escapeHtml(content)}"/>`)
|
|
194
|
-
}
|
|
193
|
+
if (content) tags.push(`<meta name="${escapeHtml(name)}" content="${escapeHtml(content)}"/>`)
|
|
194
|
+
}
|
|
195
195
|
const pushProperty = (property: string, content: string | undefined) => {
|
|
196
196
|
if (content)
|
|
197
|
-
tags.push(`<meta property="${escapeHtml(property)}" content="${escapeHtml(content)}"/>`)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
push('description', meta.description)
|
|
201
|
-
push('keywords', coerceKeywords(meta.keywords))
|
|
202
|
-
for (const author of coerceAuthors(meta.authors)) push('author', author)
|
|
203
|
-
push('generator', meta.generator)
|
|
204
|
-
push('application-name', meta.applicationName)
|
|
205
|
-
push('referrer', meta.referrer)
|
|
206
|
-
push('creator', meta.creator)
|
|
207
|
-
push('publisher', meta.publisher)
|
|
208
|
-
push('robots', coerceRobots(meta.robots))
|
|
197
|
+
tags.push(`<meta property="${escapeHtml(property)}" content="${escapeHtml(content)}"/>`)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
push('description', meta.description)
|
|
201
|
+
push('keywords', coerceKeywords(meta.keywords))
|
|
202
|
+
for (const author of coerceAuthors(meta.authors)) push('author', author)
|
|
203
|
+
push('generator', meta.generator)
|
|
204
|
+
push('application-name', meta.applicationName)
|
|
205
|
+
push('referrer', meta.referrer)
|
|
206
|
+
push('creator', meta.creator)
|
|
207
|
+
push('publisher', meta.publisher)
|
|
208
|
+
push('robots', coerceRobots(meta.robots))
|
|
209
209
|
push(
|
|
210
210
|
'googlebot',
|
|
211
211
|
coerceRobots(isRecord(meta.robots) ? (meta.robots.googleBot as MetadataRobots) : undefined),
|
|
212
|
-
)
|
|
213
|
-
push('format-detection', coerceFormatDetection(meta.formatDetection))
|
|
214
|
-
for (const [name, value] of verificationTags(meta.verification)) push(name, value)
|
|
215
|
-
for (const value of list(meta.verification?.other?.me)) push('me', value)
|
|
212
|
+
)
|
|
213
|
+
push('format-detection', coerceFormatDetection(meta.formatDetection))
|
|
214
|
+
for (const [name, value] of verificationTags(meta.verification)) push(name, value)
|
|
215
|
+
for (const value of list(meta.verification?.other?.me)) push('me', value)
|
|
216
216
|
for (const [name, values] of Object.entries(meta.other ?? {})) {
|
|
217
|
-
for (const value of list(values)) push(name, value)
|
|
217
|
+
for (const value of list(values)) push(name, value)
|
|
218
218
|
}
|
|
219
|
-
const itunes = itunesContent(meta.itunes)
|
|
220
|
-
push('apple-itunes-app', itunes)
|
|
221
|
-
const apple = meta.appleWebApp
|
|
219
|
+
const itunes = itunesContent(meta.itunes)
|
|
220
|
+
push('apple-itunes-app', itunes)
|
|
221
|
+
const apple = meta.appleWebApp
|
|
222
222
|
if (apple) {
|
|
223
|
-
push('mobile-web-app-capable', appleWebAppCapable(apple) ? 'yes' : undefined)
|
|
223
|
+
push('mobile-web-app-capable', appleWebAppCapable(apple) ? 'yes' : undefined)
|
|
224
224
|
if (typeof apple === 'object') {
|
|
225
|
-
push('apple-mobile-web-app-title', apple.title)
|
|
226
|
-
push('apple-mobile-web-app-status-bar-style', apple.statusBarStyle)
|
|
225
|
+
push('apple-mobile-web-app-title', apple.title)
|
|
226
|
+
push('apple-mobile-web-app-status-bar-style', apple.statusBarStyle)
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
|
-
if (meta.facebook?.appId) pushProperty('fb:app_id', meta.facebook.appId)
|
|
230
|
-
for (const admin of list(meta.facebook?.admins)) pushProperty('fb:admins', admin)
|
|
229
|
+
if (meta.facebook?.appId) pushProperty('fb:app_id', meta.facebook.appId)
|
|
230
|
+
for (const admin of list(meta.facebook?.admins)) pushProperty('fb:admins', admin)
|
|
231
231
|
if (meta.pinterest?.richPin !== undefined) {
|
|
232
|
-
pushProperty('pinterest-rich-pin', meta.pinterest.richPin ? 'true' : 'false')
|
|
232
|
+
pushProperty('pinterest-rich-pin', meta.pinterest.richPin ? 'true' : 'false')
|
|
233
233
|
}
|
|
234
|
-
for (const [property, value] of appLinkTags(meta.appLinks)) pushProperty(property, value)
|
|
234
|
+
for (const [property, value] of appLinkTags(meta.appLinks)) pushProperty(property, value)
|
|
235
235
|
|
|
236
|
-
tags.push(...openGraphTags(meta.openGraph, context))
|
|
237
|
-
tags.push(...twitterTags(meta.twitter ?? twitterFromOpenGraph(meta.openGraph), context))
|
|
236
|
+
tags.push(...openGraphTags(meta.openGraph, context))
|
|
237
|
+
tags.push(...twitterTags(meta.twitter ?? twitterFromOpenGraph(meta.openGraph), context))
|
|
238
238
|
|
|
239
|
-
return tags.join('\n ')
|
|
239
|
+
return tags.join('\n ')
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
function openGraphTags(og: MetadataOpenGraph | undefined, context: MetadataContext): string[] {
|
|
243
|
-
if (!og) return []
|
|
244
|
-
const tags: string[] = []
|
|
243
|
+
if (!og) return []
|
|
244
|
+
const tags: string[] = []
|
|
245
245
|
const push = (property: string, content: string | undefined) => {
|
|
246
246
|
if (content)
|
|
247
|
-
tags.push(`<meta property="${escapeHtml(property)}" content="${escapeHtml(content)}"/>`)
|
|
248
|
-
}
|
|
249
|
-
push('og:title', og.title === undefined ? undefined : resolveTitleChain([{ title: og.title }]))
|
|
250
|
-
push('og:description', og.description)
|
|
251
|
-
push('og:url', og.url === undefined ? undefined : resolveMetadataUrl(og.url, context))
|
|
252
|
-
push('og:site_name', og.siteName)
|
|
253
|
-
push('og:locale', og.locale)
|
|
254
|
-
push('og:type', og.type)
|
|
247
|
+
tags.push(`<meta property="${escapeHtml(property)}" content="${escapeHtml(content)}"/>`)
|
|
248
|
+
}
|
|
249
|
+
push('og:title', og.title === undefined ? undefined : resolveTitleChain([{ title: og.title }]))
|
|
250
|
+
push('og:description', og.description)
|
|
251
|
+
push('og:url', og.url === undefined ? undefined : resolveMetadataUrl(og.url, context))
|
|
252
|
+
push('og:site_name', og.siteName)
|
|
253
|
+
push('og:locale', og.locale)
|
|
254
|
+
push('og:type', og.type)
|
|
255
255
|
for (const image of coerceImages(og.images, context)) {
|
|
256
|
-
push('og:image', image.url)
|
|
257
|
-
push('og:image:secure_url', image.secureUrl)
|
|
258
|
-
push('og:image:width', image.width)
|
|
259
|
-
push('og:image:height', image.height)
|
|
260
|
-
push('og:image:alt', image.alt)
|
|
261
|
-
push('og:image:type', image.type)
|
|
256
|
+
push('og:image', image.url)
|
|
257
|
+
push('og:image:secure_url', image.secureUrl)
|
|
258
|
+
push('og:image:width', image.width)
|
|
259
|
+
push('og:image:height', image.height)
|
|
260
|
+
push('og:image:alt', image.alt)
|
|
261
|
+
push('og:image:type', image.type)
|
|
262
262
|
}
|
|
263
263
|
for (const video of coerceImages(og.videos, context)) {
|
|
264
|
-
push('og:video', video.url)
|
|
265
|
-
push('og:video:width', video.width)
|
|
266
|
-
push('og:video:height', video.height)
|
|
264
|
+
push('og:video', video.url)
|
|
265
|
+
push('og:video:width', video.width)
|
|
266
|
+
push('og:video:height', video.height)
|
|
267
267
|
}
|
|
268
|
-
for (const audio of coerceImages(og.audio, context)) push('og:audio', audio.url)
|
|
269
|
-
push('article:published_time', og.publishedTime)
|
|
270
|
-
for (const author of list(og.authors)) push('article:author', author)
|
|
271
|
-
for (const email of list(og.emails)) push('og:email', email)
|
|
272
|
-
for (const phone of list(og.phoneNumbers)) push('og:phone_number', phone)
|
|
273
|
-
for (const fax of list(og.faxNumbers)) push('og:fax_number', fax)
|
|
274
|
-
return tags
|
|
268
|
+
for (const audio of coerceImages(og.audio, context)) push('og:audio', audio.url)
|
|
269
|
+
push('article:published_time', og.publishedTime)
|
|
270
|
+
for (const author of list(og.authors)) push('article:author', author)
|
|
271
|
+
for (const email of list(og.emails)) push('og:email', email)
|
|
272
|
+
for (const phone of list(og.phoneNumbers)) push('og:phone_number', phone)
|
|
273
|
+
for (const fax of list(og.faxNumbers)) push('og:fax_number', fax)
|
|
274
|
+
return tags
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
function twitterTags(twitter: MetadataTwitter | undefined, context: MetadataContext): string[] {
|
|
278
|
-
if (!twitter) return []
|
|
279
|
-
const tags: string[] = []
|
|
278
|
+
if (!twitter) return []
|
|
279
|
+
const tags: string[] = []
|
|
280
280
|
const push = (name: string, content: string | undefined) => {
|
|
281
|
-
if (content) tags.push(`<meta name="${escapeHtml(name)}" content="${escapeHtml(content)}"/>`)
|
|
282
|
-
}
|
|
283
|
-
push('twitter:card', twitterCard(twitter))
|
|
284
|
-
push('twitter:site', twitter.site)
|
|
285
|
-
push('twitter:site:id', twitter.siteId)
|
|
286
|
-
push('twitter:creator', twitter.creator)
|
|
287
|
-
push('twitter:creator:id', twitter.creatorId)
|
|
281
|
+
if (content) tags.push(`<meta name="${escapeHtml(name)}" content="${escapeHtml(content)}"/>`)
|
|
282
|
+
}
|
|
283
|
+
push('twitter:card', twitterCard(twitter))
|
|
284
|
+
push('twitter:site', twitter.site)
|
|
285
|
+
push('twitter:site:id', twitter.siteId)
|
|
286
|
+
push('twitter:creator', twitter.creator)
|
|
287
|
+
push('twitter:creator:id', twitter.creatorId)
|
|
288
288
|
push(
|
|
289
289
|
'twitter:title',
|
|
290
290
|
twitter.title === undefined ? undefined : resolveTitleChain([{ title: twitter.title }]),
|
|
291
|
-
)
|
|
292
|
-
push('twitter:description', twitter.description)
|
|
291
|
+
)
|
|
292
|
+
push('twitter:description', twitter.description)
|
|
293
293
|
for (const image of coerceImages(twitter.images, context)) {
|
|
294
|
-
push('twitter:image', image.url)
|
|
295
|
-
push('twitter:image:secure_url', image.secureUrl)
|
|
296
|
-
push('twitter:image:width', image.width)
|
|
297
|
-
push('twitter:image:height', image.height)
|
|
298
|
-
push('twitter:image:alt', image.alt)
|
|
299
|
-
push('twitter:image:type', image.type)
|
|
294
|
+
push('twitter:image', image.url)
|
|
295
|
+
push('twitter:image:secure_url', image.secureUrl)
|
|
296
|
+
push('twitter:image:width', image.width)
|
|
297
|
+
push('twitter:image:height', image.height)
|
|
298
|
+
push('twitter:image:alt', image.alt)
|
|
299
|
+
push('twitter:image:type', image.type)
|
|
300
300
|
}
|
|
301
|
-
push('twitter:player', resolveMetadataUrl(twitter.players?.playerUrl, context))
|
|
302
|
-
push('twitter:player:stream', resolveMetadataUrl(twitter.players?.streamUrl, context))
|
|
303
|
-
push('twitter:player:width', coerceString(twitter.players?.width) || undefined)
|
|
304
|
-
push('twitter:player:height', coerceString(twitter.players?.height) || undefined)
|
|
301
|
+
push('twitter:player', resolveMetadataUrl(twitter.players?.playerUrl, context))
|
|
302
|
+
push('twitter:player:stream', resolveMetadataUrl(twitter.players?.streamUrl, context))
|
|
303
|
+
push('twitter:player:width', coerceString(twitter.players?.width) || undefined)
|
|
304
|
+
push('twitter:player:height', coerceString(twitter.players?.height) || undefined)
|
|
305
305
|
for (const [device, id] of Object.entries(twitter.app?.id ?? {})) {
|
|
306
|
-
push(`twitter:app:id:${device}`, id)
|
|
306
|
+
push(`twitter:app:id:${device}`, id)
|
|
307
307
|
}
|
|
308
308
|
for (const [device, url] of Object.entries(twitter.app?.url ?? {})) {
|
|
309
|
-
push(`twitter:app:url:${device}`, resolveMetadataUrl(url, context))
|
|
309
|
+
push(`twitter:app:url:${device}`, resolveMetadataUrl(url, context))
|
|
310
310
|
}
|
|
311
|
-
return tags
|
|
311
|
+
return tags
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
// theme-color: string | { color, media } | array of those.
|
|
315
315
|
export function themeColorTags(value: Viewport['themeColor']): string {
|
|
316
|
-
if (value === undefined) return ''
|
|
317
|
-
const list = Array.isArray(value) ? value : [value]
|
|
316
|
+
if (value === undefined) return ''
|
|
317
|
+
const list = Array.isArray(value) ? value : [value]
|
|
318
318
|
return list
|
|
319
319
|
.map(entry => {
|
|
320
320
|
if (typeof entry === 'string')
|
|
321
|
-
return `<meta name="theme-color" content="${escapeHtml(entry)}"
|
|
322
|
-
const media = entry.media ? ` media="${escapeHtml(entry.media)}"` : ''
|
|
323
|
-
return `<meta name="theme-color"${media} content="${escapeHtml(entry.color)}"
|
|
321
|
+
return `<meta name="theme-color" content="${escapeHtml(entry)}"/>`
|
|
322
|
+
const media = entry.media ? ` media="${escapeHtml(entry.media)}"` : ''
|
|
323
|
+
return `<meta name="theme-color"${media} content="${escapeHtml(entry.color)}"/>`
|
|
324
324
|
})
|
|
325
|
-
.join('\n ')
|
|
325
|
+
.join('\n ')
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
function linkAttrs(link: MetadataLink | ResourceHint) {
|
|
@@ -341,12 +341,12 @@ function linkAttrs(link: MetadataLink | ResourceHint) {
|
|
|
341
341
|
['nonce', link.nonce],
|
|
342
342
|
]
|
|
343
343
|
.filter((attr): attr is [string, string] => typeof attr[1] === 'string')
|
|
344
|
-
.map(([key, value]) => `${key}="${escapeHtml(value)}"`)
|
|
345
|
-
const attributes = 'attributes' in link ? link.attributes : undefined
|
|
344
|
+
.map(([key, value]) => `${key}="${escapeHtml(value)}"`)
|
|
345
|
+
const attributes = 'attributes' in link ? link.attributes : undefined
|
|
346
346
|
for (const [key, value] of Object.entries(attributes ?? {})) {
|
|
347
|
-
attrs.push(`${escapeHtml(key)}="${escapeHtml(value)}"`)
|
|
347
|
+
attrs.push(`${escapeHtml(key)}="${escapeHtml(value)}"`)
|
|
348
348
|
}
|
|
349
|
-
return attrs.join(' ')
|
|
349
|
+
return attrs.join(' ')
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
export function viewportContent(viewport: Viewport) {
|
|
@@ -364,34 +364,34 @@ export function viewportContent(viewport: Viewport) {
|
|
|
364
364
|
],
|
|
365
365
|
['viewport-fit', viewport.viewportFit],
|
|
366
366
|
['interactive-widget', viewport.interactiveWidget],
|
|
367
|
-
]
|
|
367
|
+
]
|
|
368
368
|
|
|
369
369
|
return entries
|
|
370
370
|
.filter((entry): entry is [string, string | number | boolean] => entry[1] !== undefined)
|
|
371
371
|
.map(([key, value]) => `${key}=${value}`)
|
|
372
|
-
.join(', ')
|
|
372
|
+
.join(', ')
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
// Core metadata is static for a route render: object exports are read as-is and
|
|
376
376
|
// function exports run without request data. Compat registers the Next
|
|
377
377
|
// generateMetadata/generateViewport resolver separately.
|
|
378
378
|
export async function readModuleMetadata(module: {
|
|
379
|
-
metadata?: MetadataExport
|
|
379
|
+
metadata?: MetadataExport
|
|
380
380
|
}): Promise<Metadata | undefined> {
|
|
381
|
-
return typeof module.metadata === 'function' ? module.metadata() : module.metadata
|
|
381
|
+
return typeof module.metadata === 'function' ? module.metadata() : module.metadata
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
export async function readModuleViewport(module: {
|
|
385
|
-
viewport?: ViewportExport
|
|
385
|
+
viewport?: ViewportExport
|
|
386
386
|
}): Promise<Viewport | undefined> {
|
|
387
|
-
return typeof module.viewport === 'function' ? module.viewport() : module.viewport
|
|
387
|
+
return typeof module.viewport === 'function' ? module.viewport() : module.viewport
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
export function mergeMetadataEntries(
|
|
391
391
|
...entryGroups: (MetadataEntry | MetadataEntry[])[]
|
|
392
392
|
): Metadata {
|
|
393
|
-
const entries = entryGroups.flat()
|
|
394
|
-
const items = entries.map(entry => entry.metadata)
|
|
393
|
+
const entries = entryGroups.flat()
|
|
394
|
+
const items = entries.map(entry => entry.metadata)
|
|
395
395
|
const merged = items.reduce<Metadata>(
|
|
396
396
|
(metadata, item) => ({
|
|
397
397
|
...metadata,
|
|
@@ -400,41 +400,41 @@ export function mergeMetadataEntries(
|
|
|
400
400
|
links: [...(metadata.links ?? []), ...(item.links ?? [])],
|
|
401
401
|
}),
|
|
402
402
|
{},
|
|
403
|
-
)
|
|
404
|
-
merged.title = resolveTitleEntries(entries)
|
|
405
|
-
return merged
|
|
403
|
+
)
|
|
404
|
+
merged.title = resolveTitleEntries(entries)
|
|
405
|
+
return merged
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
function mergeIcons(base: Metadata['icons'], next: Metadata['icons']): Metadata['icons'] {
|
|
409
|
-
if (next === undefined) return base
|
|
410
|
-
if (Array.isArray(base) && Array.isArray(next)) return [...base, ...next]
|
|
411
|
-
return next
|
|
409
|
+
if (next === undefined) return base
|
|
410
|
+
if (Array.isArray(base) && Array.isArray(next)) return [...base, ...next]
|
|
411
|
+
return next
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
interface TitleTemplateState {
|
|
415
|
-
template: string | null
|
|
416
|
-
previous: string | null
|
|
417
|
-
dir?: string
|
|
418
|
-
root?: boolean
|
|
415
|
+
template: string | null
|
|
416
|
+
previous: string | null
|
|
417
|
+
dir?: string
|
|
418
|
+
root?: boolean
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
function resolveTitleEntries(entries: MetadataEntry[]): string | undefined {
|
|
422
|
-
let template: TitleTemplateState = { template: null, previous: null }
|
|
423
|
-
let resolved: string | undefined
|
|
422
|
+
let template: TitleTemplateState = { template: null, previous: null }
|
|
423
|
+
let resolved: string | undefined
|
|
424
424
|
|
|
425
425
|
for (const entry of entries) {
|
|
426
|
-
const title = entry.metadata.title
|
|
427
|
-
if (title === undefined) continue
|
|
426
|
+
const title = entry.metadata.title
|
|
427
|
+
if (title === undefined) continue
|
|
428
428
|
const activeTemplate =
|
|
429
429
|
entry.kind === 'page' && entry.dir && entry.dir === template.dir && !template.root
|
|
430
430
|
? template.previous
|
|
431
|
-
: template.template
|
|
431
|
+
: template.template
|
|
432
432
|
|
|
433
433
|
if (typeof title === 'object' && title !== null) {
|
|
434
434
|
if (typeof title.absolute === 'string') {
|
|
435
|
-
resolved = title.absolute
|
|
435
|
+
resolved = title.absolute
|
|
436
436
|
} else if (typeof title.default === 'string') {
|
|
437
|
-
resolved = applyTitleTemplate(activeTemplate, title.default)
|
|
437
|
+
resolved = applyTitleTemplate(activeTemplate, title.default)
|
|
438
438
|
}
|
|
439
439
|
if ('template' in title) {
|
|
440
440
|
template = {
|
|
@@ -442,68 +442,68 @@ function resolveTitleEntries(entries: MetadataEntry[]): string | undefined {
|
|
|
442
442
|
previous: activeTemplate,
|
|
443
443
|
dir: entry.dir,
|
|
444
444
|
root: entry.root,
|
|
445
|
-
}
|
|
445
|
+
}
|
|
446
446
|
}
|
|
447
447
|
} else {
|
|
448
|
-
resolved = applyTitleTemplate(activeTemplate, coerceString(title))
|
|
448
|
+
resolved = applyTitleTemplate(activeTemplate, coerceString(title))
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
|
-
return resolved
|
|
451
|
+
return resolved
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
// Walks the chain root->page tracking the last-seen template. A child's plain
|
|
455
455
|
// string title is wrapped by the nearest ancestor template; an `absolute`
|
|
456
456
|
// bypasses the template; a `default` supplies a title when the child has none.
|
|
457
457
|
function resolveTitleChain(items: Metadata[]): string | undefined {
|
|
458
|
-
return resolveTitleEntries(items.map(metadata => ({ metadata })))
|
|
458
|
+
return resolveTitleEntries(items.map(metadata => ({ metadata })))
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
function applyTitleTemplate(template: string | null, value: string): string {
|
|
462
|
-
return template ? template.replace('%s', value) : value
|
|
462
|
+
return template ? template.replace('%s', value) : value
|
|
463
463
|
}
|
|
464
464
|
|
|
465
465
|
// Coerces any metadata scalar (string | number | URL | unknown) into a string.
|
|
466
466
|
// Numbers go through String(); undefined/null collapse to ''. This is the guard
|
|
467
467
|
// that keeps emission from ever crashing on an unexpected value shape.
|
|
468
468
|
export function coerceString(value: unknown): string {
|
|
469
|
-
return stringifyHtmlValue(value)
|
|
469
|
+
return stringifyHtmlValue(value)
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
// keywords: string | number | Array -> comma-joined string.
|
|
473
473
|
function coerceKeywords(value: Metadata['keywords']): string | undefined {
|
|
474
|
-
const list = normalizeKeywordList(value)
|
|
475
|
-
if (list === undefined) return undefined
|
|
476
|
-
const joined = list.map(coerceString).filter(Boolean).join(',')
|
|
477
|
-
return joined || undefined
|
|
474
|
+
const list = normalizeKeywordList(value)
|
|
475
|
+
if (list === undefined) return undefined
|
|
476
|
+
const joined = list.map(coerceString).filter(Boolean).join(',')
|
|
477
|
+
return joined || undefined
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
function normalizeKeywordList(value: Metadata['keywords']): (string | number)[] | undefined {
|
|
481
|
-
if (value === undefined) return undefined
|
|
482
|
-
return Array.isArray(value) ? value : [value]
|
|
481
|
+
if (value === undefined) return undefined
|
|
482
|
+
return Array.isArray(value) ? value : [value]
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
// authors -> list of display names for meta[name=author].
|
|
486
486
|
function coerceAuthors(value: Metadata['authors']): string[] {
|
|
487
|
-
if (value === undefined) return []
|
|
488
|
-
const list = Array.isArray(value) ? value : [value]
|
|
487
|
+
if (value === undefined) return []
|
|
488
|
+
const list = Array.isArray(value) ? value : [value]
|
|
489
489
|
return list
|
|
490
490
|
.map(author => (typeof author === 'string' ? author : author.name))
|
|
491
491
|
.map(name => coerceString(name))
|
|
492
|
-
.filter(Boolean)
|
|
492
|
+
.filter(Boolean)
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
// robots: string passthrough, or object -> "index, follow, noarchive" style.
|
|
496
496
|
function coerceRobots(value: Metadata['robots']): string | undefined {
|
|
497
|
-
if (value === undefined) return undefined
|
|
498
|
-
if (typeof value === 'string') return value
|
|
499
|
-
const flags: string[] = []
|
|
500
|
-
const r = value
|
|
501
|
-
if (r.index !== undefined) flags.push(r.index ? 'index' : 'noindex')
|
|
502
|
-
if (r.follow !== undefined) flags.push(r.follow ? 'follow' : 'nofollow')
|
|
503
|
-
if (r.noarchive) flags.push('noarchive')
|
|
504
|
-
if (r.nosnippet) flags.push('nosnippet')
|
|
505
|
-
if (r.noimageindex) flags.push('noimageindex')
|
|
506
|
-
if (r.nocache) flags.push('nocache')
|
|
497
|
+
if (value === undefined) return undefined
|
|
498
|
+
if (typeof value === 'string') return value
|
|
499
|
+
const flags: string[] = []
|
|
500
|
+
const r = value
|
|
501
|
+
if (r.index !== undefined) flags.push(r.index ? 'index' : 'noindex')
|
|
502
|
+
if (r.follow !== undefined) flags.push(r.follow ? 'follow' : 'nofollow')
|
|
503
|
+
if (r.noarchive) flags.push('noarchive')
|
|
504
|
+
if (r.nosnippet) flags.push('nosnippet')
|
|
505
|
+
if (r.noimageindex) flags.push('noimageindex')
|
|
506
|
+
if (r.nocache) flags.push('nocache')
|
|
507
507
|
for (const [key, item] of Object.entries(r)) {
|
|
508
508
|
if (
|
|
509
509
|
item === undefined ||
|
|
@@ -516,36 +516,36 @@ function coerceRobots(value: Metadata['robots']): string | undefined {
|
|
|
516
516
|
key === 'nocache' ||
|
|
517
517
|
key === 'googleBot'
|
|
518
518
|
) {
|
|
519
|
-
continue
|
|
519
|
+
continue
|
|
520
520
|
}
|
|
521
|
-
if (item === true) flags.push(key)
|
|
522
|
-
else flags.push(`${key}:${String(item)}`)
|
|
521
|
+
if (item === true) flags.push(key)
|
|
522
|
+
else flags.push(`${key}:${String(item)}`)
|
|
523
523
|
}
|
|
524
|
-
return flags.length ? flags.join(', ') : undefined
|
|
524
|
+
return flags.length ? flags.join(', ') : undefined
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
interface ResolvedImage {
|
|
528
|
-
url: string
|
|
529
|
-
secureUrl?: string
|
|
530
|
-
width?: string
|
|
531
|
-
height?: string
|
|
532
|
-
alt?: string
|
|
533
|
-
type?: string
|
|
528
|
+
url: string
|
|
529
|
+
secureUrl?: string
|
|
530
|
+
width?: string
|
|
531
|
+
height?: string
|
|
532
|
+
alt?: string
|
|
533
|
+
type?: string
|
|
534
534
|
}
|
|
535
535
|
|
|
536
536
|
function coerceImages(
|
|
537
537
|
value: MetadataImage | MetadataImage[] | undefined,
|
|
538
538
|
context: MetadataContext,
|
|
539
539
|
): ResolvedImage[] {
|
|
540
|
-
if (value === undefined) return []
|
|
541
|
-
const list = Array.isArray(value) ? value : [value]
|
|
540
|
+
if (value === undefined) return []
|
|
541
|
+
const list = Array.isArray(value) ? value : [value]
|
|
542
542
|
return list
|
|
543
543
|
.map((image): ResolvedImage | undefined => {
|
|
544
544
|
if (typeof image === 'string')
|
|
545
|
-
return { url: resolveMetadataUrl(image, context, true) ?? image }
|
|
546
|
-
if (image instanceof URL) return { url: image.href }
|
|
545
|
+
return { url: resolveMetadataUrl(image, context, true) ?? image }
|
|
546
|
+
if (image instanceof URL) return { url: image.href }
|
|
547
547
|
if (typeof image === 'object' && image !== null && 'url' in image) {
|
|
548
|
-
const img = image
|
|
548
|
+
const img = image
|
|
549
549
|
return {
|
|
550
550
|
url: resolveMetadataUrl(img.url, context, true) ?? coerceString(img.url),
|
|
551
551
|
secureUrl: resolveMetadataUrl(img.secureUrl, context, true),
|
|
@@ -553,36 +553,36 @@ function coerceImages(
|
|
|
553
553
|
height: img.height === undefined ? undefined : coerceString(img.height),
|
|
554
554
|
alt: img.alt,
|
|
555
555
|
type: img.type,
|
|
556
|
-
}
|
|
556
|
+
}
|
|
557
557
|
}
|
|
558
|
-
return undefined
|
|
558
|
+
return undefined
|
|
559
559
|
})
|
|
560
|
-
.filter((image): image is ResolvedImage => image !== undefined && Boolean(image.url))
|
|
560
|
+
.filter((image): image is ResolvedImage => image !== undefined && Boolean(image.url))
|
|
561
561
|
}
|
|
562
562
|
|
|
563
563
|
function coerceCanonical(
|
|
564
564
|
value: Metadata['alternates'],
|
|
565
565
|
context: MetadataContext,
|
|
566
566
|
): string | undefined {
|
|
567
|
-
const canonical = value?.canonical
|
|
568
|
-
if (canonical === undefined) return undefined
|
|
569
|
-
if (typeof canonical === 'string') return resolveMetadataUrl(canonical, context)
|
|
570
|
-
if (canonical instanceof URL) return canonical.href
|
|
567
|
+
const canonical = value?.canonical
|
|
568
|
+
if (canonical === undefined) return undefined
|
|
569
|
+
if (typeof canonical === 'string') return resolveMetadataUrl(canonical, context)
|
|
570
|
+
if (canonical instanceof URL) return canonical.href
|
|
571
571
|
if (typeof canonical === 'object' && 'url' in canonical)
|
|
572
|
-
return resolveMetadataUrl(canonical.url, context)
|
|
573
|
-
return undefined
|
|
572
|
+
return resolveMetadataUrl(canonical.url, context)
|
|
573
|
+
return undefined
|
|
574
574
|
}
|
|
575
575
|
|
|
576
576
|
function coerceMetadataBase(value: Metadata['metadataBase'], url?: URL): URL | undefined {
|
|
577
|
-
if (value instanceof URL) return value
|
|
577
|
+
if (value instanceof URL) return value
|
|
578
578
|
if (typeof value === 'string' && value) {
|
|
579
579
|
try {
|
|
580
|
-
return new URL(value)
|
|
580
|
+
return new URL(value)
|
|
581
581
|
} catch {
|
|
582
|
-
return url ? new URL(value, url) : undefined
|
|
582
|
+
return url ? new URL(value, url) : undefined
|
|
583
583
|
}
|
|
584
584
|
}
|
|
585
|
-
return undefined
|
|
585
|
+
return undefined
|
|
586
586
|
}
|
|
587
587
|
|
|
588
588
|
function resolveMetadataUrl(
|
|
@@ -590,35 +590,35 @@ function resolveMetadataUrl(
|
|
|
590
590
|
context: MetadataContext,
|
|
591
591
|
fallbackToRequestUrl = false,
|
|
592
592
|
): string | undefined {
|
|
593
|
-
if (value === undefined || value === null) return undefined
|
|
594
|
-
if (value instanceof URL) return value.href
|
|
595
|
-
const raw = coerceString(value)
|
|
596
|
-
if (!raw) return undefined
|
|
597
|
-
if (/^[a-z][a-z\d+.-]*:/i.test(raw)) return raw
|
|
593
|
+
if (value === undefined || value === null) return undefined
|
|
594
|
+
if (value instanceof URL) return value.href
|
|
595
|
+
const raw = coerceString(value)
|
|
596
|
+
if (!raw) return undefined
|
|
597
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(raw)) return raw
|
|
598
598
|
const base =
|
|
599
|
-
context.metadataBase ?? (fallbackToRequestUrl ? requestFallbackBase(context.url) : undefined)
|
|
600
|
-
if (!base) return raw
|
|
601
|
-
const origin = base.origin
|
|
602
|
-
const prefix = base.pathname === '/' ? '' : `/${base.pathname.replace(/^\/+|\/+$/g, '')}
|
|
599
|
+
context.metadataBase ?? (fallbackToRequestUrl ? requestFallbackBase(context.url) : undefined)
|
|
600
|
+
if (!base) return raw
|
|
601
|
+
const origin = base.origin
|
|
602
|
+
const prefix = base.pathname === '/' ? '' : `/${base.pathname.replace(/^\/+|\/+$/g, '')}`
|
|
603
603
|
if (raw.startsWith('./') && context.url) {
|
|
604
|
-
const requestPath = joinUrlPath(prefix, context.url.pathname)
|
|
605
|
-
const directory = requestPath.endsWith('/') ? requestPath : `${requestPath}
|
|
606
|
-
const resolved = new URL(raw, `${origin}${directory}`).pathname
|
|
604
|
+
const requestPath = joinUrlPath(prefix, context.url.pathname)
|
|
605
|
+
const directory = requestPath.endsWith('/') ? requestPath : `${requestPath}/`
|
|
606
|
+
const resolved = new URL(raw, `${origin}${directory}`).pathname
|
|
607
607
|
const pathname =
|
|
608
|
-
resolved.length > 1 && resolved.endsWith('/') ? resolved.slice(0, -1) : resolved
|
|
609
|
-
return joinMetadataUrl(origin, pathname)
|
|
608
|
+
resolved.length > 1 && resolved.endsWith('/') ? resolved.slice(0, -1) : resolved
|
|
609
|
+
return joinMetadataUrl(origin, pathname)
|
|
610
610
|
}
|
|
611
|
-
const relative = metadataRelativePath(raw)
|
|
612
|
-
const resolvedPath = joinUrlPath(prefix, relative)
|
|
613
|
-
return joinMetadataUrl(origin, resolvedPath)
|
|
611
|
+
const relative = metadataRelativePath(raw)
|
|
612
|
+
const resolvedPath = joinUrlPath(prefix, relative)
|
|
613
|
+
return joinMetadataUrl(origin, resolvedPath)
|
|
614
614
|
}
|
|
615
615
|
|
|
616
616
|
function requestFallbackBase(url?: URL) {
|
|
617
|
-
if (!url) return undefined
|
|
618
|
-
if (url.hostname !== 'pnext.local') return new URL(url.origin)
|
|
617
|
+
if (!url) return undefined
|
|
618
|
+
if (url.hostname !== 'pnext.local') return new URL(url.origin)
|
|
619
619
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
620
|
-
const port = process.env.PORT
|
|
621
|
-
return port && port !== '0' ? new URL(`http://localhost:${port}`) : undefined
|
|
620
|
+
const port = process.env.PORT
|
|
621
|
+
return port && port !== '0' ? new URL(`http://localhost:${port}`) : undefined
|
|
622
622
|
}
|
|
623
623
|
|
|
624
624
|
// Join a resolved origin + pathname into an absolute metadata URL, honoring the
|
|
@@ -627,46 +627,46 @@ function requestFallbackBase(url?: URL) {
|
|
|
627
627
|
// bare path, and `/metadata?query=string` must not become `/metadata/?...`.
|
|
628
628
|
function joinMetadataUrl(origin: string, pathname: string) {
|
|
629
629
|
if (/[?#]/.test(pathname)) {
|
|
630
|
-
return pathname === '/' ? origin : `${origin}${pathname}
|
|
630
|
+
return pathname === '/' ? origin : `${origin}${pathname}`
|
|
631
631
|
}
|
|
632
632
|
if (getTrailingSlashUrls()) {
|
|
633
|
-
if (pathname === '/') return `${origin}
|
|
634
|
-
return pathname.endsWith('/') ? `${origin}${pathname}` : `${origin}${pathname}
|
|
633
|
+
if (pathname === '/') return `${origin}/`
|
|
634
|
+
return pathname.endsWith('/') ? `${origin}${pathname}` : `${origin}${pathname}/`
|
|
635
635
|
}
|
|
636
|
-
return pathname === '/' ? origin : `${origin}${pathname}
|
|
636
|
+
return pathname === '/' ? origin : `${origin}${pathname}`
|
|
637
637
|
}
|
|
638
638
|
|
|
639
639
|
function metadataRelativePath(value: string) {
|
|
640
|
-
let next = value
|
|
641
|
-
while (next.startsWith('./')) next = next.slice(2)
|
|
642
|
-
while (next.startsWith('../')) next = next.slice(3)
|
|
643
|
-
while (next.startsWith('/')) next = next.slice(1)
|
|
644
|
-
return next
|
|
640
|
+
let next = value
|
|
641
|
+
while (next.startsWith('./')) next = next.slice(2)
|
|
642
|
+
while (next.startsWith('../')) next = next.slice(3)
|
|
643
|
+
while (next.startsWith('/')) next = next.slice(1)
|
|
644
|
+
return next
|
|
645
645
|
}
|
|
646
646
|
|
|
647
647
|
function joinUrlPath(...parts: string[]) {
|
|
648
|
-
const path = parts.filter(Boolean).join('/').replace(/\/+/g, '/')
|
|
649
|
-
return path.startsWith('/') ? path : `/${path}
|
|
648
|
+
const path = parts.filter(Boolean).join('/').replace(/\/+/g, '/')
|
|
649
|
+
return path.startsWith('/') ? path : `/${path}`
|
|
650
650
|
}
|
|
651
651
|
|
|
652
652
|
// Normalizes every icon form (string | URL | object | group) into <link> descriptors.
|
|
653
653
|
function coerceIcons(value: Metadata['icons'], context: MetadataContext): MetadataLink[] {
|
|
654
|
-
if (value === undefined) return []
|
|
654
|
+
if (value === undefined) return []
|
|
655
655
|
if (isIconGroups(value)) {
|
|
656
656
|
return [
|
|
657
657
|
...iconList(value.icon, 'icon', context),
|
|
658
658
|
...iconList(value.shortcut, 'shortcut icon', context),
|
|
659
659
|
...iconList(value.apple, 'apple-touch-icon', context),
|
|
660
660
|
...iconList(value.other, 'icon', context),
|
|
661
|
-
]
|
|
661
|
+
]
|
|
662
662
|
}
|
|
663
|
-
return iconList(value, 'icon', context)
|
|
663
|
+
return iconList(value, 'icon', context)
|
|
664
664
|
}
|
|
665
665
|
|
|
666
666
|
function isIconGroups(value: NonNullable<Metadata['icons']>): value is MetadataIconGroups {
|
|
667
|
-
if (Array.isArray(value) || typeof value === 'string' || value instanceof URL) return false
|
|
668
|
-
if (typeof value !== 'object') return false
|
|
669
|
-
return 'icon' in value || 'shortcut' in value || 'apple' in value || 'other' in value
|
|
667
|
+
if (Array.isArray(value) || typeof value === 'string' || value instanceof URL) return false
|
|
668
|
+
if (typeof value !== 'object') return false
|
|
669
|
+
return 'icon' in value || 'shortcut' in value || 'apple' in value || 'other' in value
|
|
670
670
|
}
|
|
671
671
|
|
|
672
672
|
function iconList(
|
|
@@ -674,33 +674,33 @@ function iconList(
|
|
|
674
674
|
rel: string,
|
|
675
675
|
context: MetadataContext,
|
|
676
676
|
): MetadataLink[] {
|
|
677
|
-
if (value === undefined) return []
|
|
678
|
-
const list = Array.isArray(value) ? value : [value]
|
|
677
|
+
if (value === undefined) return []
|
|
678
|
+
const list = Array.isArray(value) ? value : [value]
|
|
679
679
|
return list.map((icon): MetadataLink => {
|
|
680
|
-
if (typeof icon === 'string') return { rel, url: resolveMetadataUrl(icon, context) ?? icon }
|
|
681
|
-
if (icon instanceof URL) return { rel, url: icon.href }
|
|
682
|
-
const obj = icon
|
|
680
|
+
if (typeof icon === 'string') return { rel, url: resolveMetadataUrl(icon, context) ?? icon }
|
|
681
|
+
if (icon instanceof URL) return { rel, url: icon.href }
|
|
682
|
+
const obj = icon
|
|
683
683
|
return {
|
|
684
684
|
...obj,
|
|
685
685
|
rel: obj.rel ?? rel,
|
|
686
686
|
url: resolveMetadataUrl(obj.url, context) ?? coerceString(obj.url),
|
|
687
|
-
}
|
|
688
|
-
})
|
|
687
|
+
}
|
|
688
|
+
})
|
|
689
689
|
}
|
|
690
690
|
|
|
691
691
|
function authorLinks(value: Metadata['authors'], context: MetadataContext): MetadataLink[] {
|
|
692
|
-
if (value === undefined) return []
|
|
692
|
+
if (value === undefined) return []
|
|
693
693
|
return (Array.isArray(value) ? value : [value])
|
|
694
694
|
.filter((author): author is MetadataAuthor => typeof author === 'object' && author !== null)
|
|
695
695
|
.flatMap(author => {
|
|
696
|
-
const url = resolveMetadataUrl(author.url, context)
|
|
697
|
-
return url ? [{ rel: 'author', url }] : []
|
|
698
|
-
})
|
|
696
|
+
const url = resolveMetadataUrl(author.url, context)
|
|
697
|
+
return url ? [{ rel: 'author', url }] : []
|
|
698
|
+
})
|
|
699
699
|
}
|
|
700
700
|
|
|
701
701
|
function manifestLink(value: Metadata['manifest'], context: MetadataContext): MetadataLink[] {
|
|
702
|
-
const url = resolveMetadataUrl(value, context)
|
|
703
|
-
return url ? [{ rel: 'manifest', url }] : []
|
|
702
|
+
const url = resolveMetadataUrl(value, context)
|
|
703
|
+
return url ? [{ rel: 'manifest', url }] : []
|
|
704
704
|
}
|
|
705
705
|
|
|
706
706
|
function paginationLinks(value: Metadata['pagination'], context: MetadataContext): MetadataLink[] {
|
|
@@ -716,130 +716,130 @@ function paginationLinks(value: Metadata['pagination'], context: MetadataContext
|
|
|
716
716
|
...(value?.next
|
|
717
717
|
? [{ rel: 'next', url: resolveMetadataUrl(value.next, context) ?? coerceString(value.next) }]
|
|
718
718
|
: []),
|
|
719
|
-
]
|
|
719
|
+
]
|
|
720
720
|
}
|
|
721
721
|
|
|
722
722
|
function alternateLinks(value: Metadata['alternates'], context: MetadataContext): MetadataLink[] {
|
|
723
|
-
if (!value) return []
|
|
724
|
-
const links: MetadataLink[] = []
|
|
723
|
+
if (!value) return []
|
|
724
|
+
const links: MetadataLink[] = []
|
|
725
725
|
for (const [hrefLang, url] of Object.entries(value.languages ?? {})) {
|
|
726
726
|
links.push({
|
|
727
727
|
rel: 'alternate',
|
|
728
728
|
url: resolveMetadataUrl(url, context) ?? coerceString(url),
|
|
729
729
|
hrefLang,
|
|
730
|
-
})
|
|
730
|
+
})
|
|
731
731
|
}
|
|
732
732
|
for (const [media, url] of Object.entries(value.media ?? {})) {
|
|
733
733
|
links.push({
|
|
734
734
|
rel: 'alternate',
|
|
735
735
|
media,
|
|
736
736
|
url: resolveMetadataUrl(url, context) ?? coerceString(url),
|
|
737
|
-
})
|
|
737
|
+
})
|
|
738
738
|
}
|
|
739
739
|
for (const [type, entries] of Object.entries(value.types ?? {})) {
|
|
740
|
-
const listEntries = Array.isArray(entries) ? entries : [entries]
|
|
740
|
+
const listEntries = Array.isArray(entries) ? entries : [entries]
|
|
741
741
|
for (const entry of listEntries) {
|
|
742
742
|
const record =
|
|
743
743
|
typeof entry === 'object' && !(entry instanceof URL) && entry !== null && 'url' in entry
|
|
744
744
|
? entry
|
|
745
|
-
: { url: entry }
|
|
745
|
+
: { url: entry }
|
|
746
746
|
links.push({
|
|
747
747
|
rel: 'alternate',
|
|
748
748
|
type,
|
|
749
749
|
...(record.title ? { title: record.title } : {}),
|
|
750
750
|
url: resolveMetadataUrl(record.url, context) ?? coerceString(record.url),
|
|
751
|
-
})
|
|
751
|
+
})
|
|
752
752
|
}
|
|
753
753
|
}
|
|
754
|
-
return links
|
|
754
|
+
return links
|
|
755
755
|
}
|
|
756
756
|
|
|
757
757
|
function appleStartupLinks(
|
|
758
758
|
value: Metadata['appleWebApp'],
|
|
759
759
|
context: MetadataContext,
|
|
760
760
|
): MetadataLink[] {
|
|
761
|
-
if (!value || typeof value !== 'object' || value.startupImage === undefined) return []
|
|
762
|
-
const images = Array.isArray(value.startupImage) ? value.startupImage : [value.startupImage]
|
|
761
|
+
if (!value || typeof value !== 'object' || value.startupImage === undefined) return []
|
|
762
|
+
const images = Array.isArray(value.startupImage) ? value.startupImage : [value.startupImage]
|
|
763
763
|
return images.map(image => {
|
|
764
764
|
if (typeof image === 'string' || image instanceof URL) {
|
|
765
765
|
return {
|
|
766
766
|
rel: 'apple-touch-startup-image',
|
|
767
767
|
url: resolveMetadataUrl(image, context) ?? coerceString(image),
|
|
768
|
-
}
|
|
768
|
+
}
|
|
769
769
|
}
|
|
770
770
|
return {
|
|
771
771
|
rel: 'apple-touch-startup-image',
|
|
772
772
|
url: resolveMetadataUrl(image.url, context) ?? coerceString(image.url),
|
|
773
773
|
media: image.media,
|
|
774
|
-
}
|
|
775
|
-
})
|
|
774
|
+
}
|
|
775
|
+
})
|
|
776
776
|
}
|
|
777
777
|
|
|
778
778
|
function list(value: unknown): string[] {
|
|
779
|
-
if (value === undefined) return []
|
|
780
|
-
return Array.isArray(value) ? value.map(coerceString) : [coerceString(value)]
|
|
779
|
+
if (value === undefined) return []
|
|
780
|
+
return Array.isArray(value) ? value.map(coerceString) : [coerceString(value)]
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
function coerceFormatDetection(value: Metadata['formatDetection']): string | undefined {
|
|
784
|
-
if (!value) return undefined
|
|
785
|
-
const entries: string[] = []
|
|
786
|
-
if (value.telephone === false) entries.push('telephone=no')
|
|
787
|
-
if (value.address === false) entries.push('address=no')
|
|
788
|
-
if (value.email === false) entries.push('email=no')
|
|
789
|
-
if (value.url === false) entries.push('url=no')
|
|
790
|
-
return entries.length ? entries.join(', ') : undefined
|
|
784
|
+
if (!value) return undefined
|
|
785
|
+
const entries: string[] = []
|
|
786
|
+
if (value.telephone === false) entries.push('telephone=no')
|
|
787
|
+
if (value.address === false) entries.push('address=no')
|
|
788
|
+
if (value.email === false) entries.push('email=no')
|
|
789
|
+
if (value.url === false) entries.push('url=no')
|
|
790
|
+
return entries.length ? entries.join(', ') : undefined
|
|
791
791
|
}
|
|
792
792
|
|
|
793
793
|
function verificationTags(value: Metadata['verification']): [string, string][] {
|
|
794
|
-
if (!value) return []
|
|
794
|
+
if (!value) return []
|
|
795
795
|
return [
|
|
796
796
|
...list(value.google).map(item => ['google-site-verification', item] as [string, string]),
|
|
797
797
|
...list(value.yahoo).map(item => ['y_key', item] as [string, string]),
|
|
798
798
|
...list(value.yandex).map(item => ['yandex-verification', item] as [string, string]),
|
|
799
|
-
]
|
|
799
|
+
]
|
|
800
800
|
}
|
|
801
801
|
|
|
802
802
|
function itunesContent(value: Metadata['itunes']): string | undefined {
|
|
803
|
-
if (!value?.appId) return undefined
|
|
803
|
+
if (!value?.appId) return undefined
|
|
804
804
|
return [
|
|
805
805
|
`app-id=${value.appId}`,
|
|
806
806
|
value.appArgument ? `app-argument=${value.appArgument}` : undefined,
|
|
807
807
|
]
|
|
808
808
|
.filter(Boolean)
|
|
809
|
-
.join(', ')
|
|
809
|
+
.join(', ')
|
|
810
810
|
}
|
|
811
811
|
|
|
812
812
|
function appleWebAppCapable(value: NonNullable<Metadata['appleWebApp']>) {
|
|
813
|
-
return typeof value === 'boolean' ? value : (value.capable ?? true)
|
|
813
|
+
return typeof value === 'boolean' ? value : (value.capable ?? true)
|
|
814
814
|
}
|
|
815
815
|
|
|
816
816
|
function appLinkTags(value: Metadata['appLinks']): [string, string][] {
|
|
817
|
-
if (!value) return []
|
|
818
|
-
const tags: [string, string][] = []
|
|
817
|
+
if (!value) return []
|
|
818
|
+
const tags: [string, string][] = []
|
|
819
819
|
for (const [platform, entries] of Object.entries(value)) {
|
|
820
820
|
for (const [name, item] of Object.entries(entries)) {
|
|
821
|
-
if (item !== undefined) tags.push([`al:${platform}:${name}`, String(item)])
|
|
821
|
+
if (item !== undefined) tags.push([`al:${platform}:${name}`, String(item)])
|
|
822
822
|
}
|
|
823
823
|
}
|
|
824
|
-
return tags
|
|
824
|
+
return tags
|
|
825
825
|
}
|
|
826
826
|
|
|
827
827
|
function twitterCard(twitter: MetadataTwitter) {
|
|
828
|
-
if (twitter.card) return twitter.card
|
|
829
|
-
if (twitter.players) return 'player'
|
|
830
|
-
if (twitter.app) return 'app'
|
|
831
|
-
return twitter.images === undefined ? 'summary' : 'summary_large_image'
|
|
828
|
+
if (twitter.card) return twitter.card
|
|
829
|
+
if (twitter.players) return 'player'
|
|
830
|
+
if (twitter.app) return 'app'
|
|
831
|
+
return twitter.images === undefined ? 'summary' : 'summary_large_image'
|
|
832
832
|
}
|
|
833
833
|
|
|
834
834
|
function twitterFromOpenGraph(
|
|
835
835
|
openGraph: MetadataOpenGraph | undefined,
|
|
836
836
|
): MetadataTwitter | undefined {
|
|
837
|
-
if (!openGraph) return undefined
|
|
837
|
+
if (!openGraph) return undefined
|
|
838
838
|
return {
|
|
839
839
|
title: openGraph.title,
|
|
840
840
|
description: openGraph.description,
|
|
841
841
|
images: openGraph.images,
|
|
842
|
-
}
|
|
842
|
+
}
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
export function mergeViewport(...items: Viewport[]) {
|
|
@@ -849,9 +849,9 @@ export function mergeViewport(...items: Viewport[]) {
|
|
|
849
849
|
...item,
|
|
850
850
|
}),
|
|
851
851
|
{},
|
|
852
|
-
)
|
|
852
|
+
)
|
|
853
853
|
}
|
|
854
854
|
|
|
855
855
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
856
|
-
return typeof value === 'object' && value !== null
|
|
856
|
+
return typeof value === 'object' && value !== null
|
|
857
857
|
}
|