@wular/pnext 0.0.3 → 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 -1961
- 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 -1887
- 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} +152 -143
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +442 -186
- 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 +833 -848
- 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
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
-
import { createHash } from 'node:crypto'
|
|
3
|
-
import { createRequire } from 'node:module'
|
|
4
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
5
|
-
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
6
|
-
import path from 'node:path'
|
|
7
|
-
import { fileURLToPath } from 'node:url'
|
|
8
|
-
import type { ResolvedConfig } from '../../../config'
|
|
9
|
-
import { onExtensionHostReset, registerResponseFinalizers } from '../../../extensions'
|
|
10
|
-
import { getWorkUnit } from '../../../request/context'
|
|
11
|
-
import type { MetadataLink } from '../../../types'
|
|
12
|
-
import { persistFont, persistedFont } from './cache'
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
5
|
+
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import type { ResolvedConfig } from '../../../config'
|
|
9
|
+
import { onExtensionHostReset, registerResponseFinalizers } from '../../../extensions'
|
|
10
|
+
import { getWorkUnit } from '../../../request/context'
|
|
11
|
+
import type { MetadataLink } from '../../../types'
|
|
12
|
+
import { persistFont, persistedFont } from './cache'
|
|
13
|
+
import { escapeRegex } from '../../../utils/code'
|
|
13
14
|
import {
|
|
14
15
|
familyStack,
|
|
15
16
|
fontStyle,
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
type LocalFontSource,
|
|
24
25
|
type NextFont,
|
|
25
26
|
type NextFontWithVariable,
|
|
26
|
-
} from './shared'
|
|
27
|
+
} from './shared'
|
|
27
28
|
|
|
28
29
|
export type {
|
|
29
30
|
FontDisplay,
|
|
@@ -32,87 +33,88 @@ export type {
|
|
|
32
33
|
LocalFontSource,
|
|
33
34
|
NextFont,
|
|
34
35
|
NextFontWithVariable,
|
|
35
|
-
}
|
|
36
|
+
}
|
|
36
37
|
|
|
37
38
|
interface FontScope {
|
|
38
|
-
fonts: Map<string, FontDefinition
|
|
39
|
+
fonts: Map<string, FontDefinition>
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
interface FontDefinition {
|
|
42
|
-
className: string
|
|
43
|
-
family: string
|
|
44
|
-
key: string
|
|
45
|
-
resolve(context: FontResolveContext): Promise<ResolvedFont
|
|
46
|
-
style: NextFont['style']
|
|
47
|
-
variable: string
|
|
48
|
-
hasVariable: boolean
|
|
43
|
+
className: string
|
|
44
|
+
family: string
|
|
45
|
+
key: string
|
|
46
|
+
resolve(context: FontResolveContext): Promise<ResolvedFont>
|
|
47
|
+
style: NextFont['style']
|
|
48
|
+
variable: string
|
|
49
|
+
hasVariable: boolean
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
interface FontResolveContext {
|
|
52
|
-
config: ResolvedConfig
|
|
53
|
-
dev?: boolean
|
|
53
|
+
config: ResolvedConfig
|
|
54
|
+
dev?: boolean
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
export interface ResolvedFontAssets {
|
|
57
|
-
css: string
|
|
58
|
-
preloads: MetadataLink[]
|
|
58
|
+
css: string
|
|
59
|
+
preloads: MetadataLink[]
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
interface ResolvedFont {
|
|
62
|
-
css: string
|
|
63
|
+
css: string
|
|
63
64
|
/** Preloadable font links (empty when `preload:false`). Delivered via the
|
|
64
65
|
* `Link` response header in prod, not as HTML `<link>` tags. */
|
|
65
|
-
preloads: MetadataLink[]
|
|
66
|
+
preloads: MetadataLink[]
|
|
66
67
|
/** Whether this definition emitted any font file (used to decide preconnect). */
|
|
67
|
-
usesFonts: boolean
|
|
68
|
+
usesFonts: boolean
|
|
68
69
|
/** Emitted font files on disk, re-checked on every resolve — a rebuild can
|
|
69
70
|
* wipe the output directory under a running server. */
|
|
70
|
-
files: string[]
|
|
71
|
+
files: string[]
|
|
71
72
|
/** Local source files this resolution read; the persisted record stats them. */
|
|
72
|
-
sources?: string[]
|
|
73
|
+
sources?: string[]
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
interface GoogleFontMetadata {
|
|
76
|
-
axes?: { tag: string; min: number; max: number; defaultValue?: number }[]
|
|
77
|
-
styles: string[]
|
|
78
|
-
subsets: string[]
|
|
79
|
-
weights: string[]
|
|
77
|
+
axes?: { tag: string; min: number; max: number; defaultValue?: number }[]
|
|
78
|
+
styles: string[]
|
|
79
|
+
subsets: string[]
|
|
80
|
+
weights: string[]
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
interface NormalizedGoogleOptions {
|
|
83
|
-
axes?: string[]
|
|
84
|
-
adjustFontFallback: boolean
|
|
85
|
-
display: FontDisplay
|
|
86
|
-
fallback: string[]
|
|
87
|
-
preload: boolean
|
|
88
|
-
styles: string[]
|
|
89
|
-
subsets?: string[]
|
|
90
|
-
variable?: string
|
|
91
|
-
weights: string[]
|
|
84
|
+
axes?: string[]
|
|
85
|
+
adjustFontFallback: boolean
|
|
86
|
+
display: FontDisplay
|
|
87
|
+
fallback: string[]
|
|
88
|
+
preload: boolean
|
|
89
|
+
styles: string[]
|
|
90
|
+
subsets?: string[]
|
|
91
|
+
variable?: string
|
|
92
|
+
weights: string[]
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
interface NormalizedLocalSource {
|
|
95
|
-
file: string
|
|
96
|
-
style?: string
|
|
97
|
-
weight?: string
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const fontStorageKey = Symbol.for('pnext.compat.font.storage')
|
|
101
|
-
const fontRuntimeGlobal = globalThis as typeof globalThis &
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
96
|
+
file: string
|
|
97
|
+
style?: string
|
|
98
|
+
weight?: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const fontStorageKey = Symbol.for('pnext.compat.font.storage')
|
|
102
|
+
const fontRuntimeGlobal = globalThis as typeof globalThis &
|
|
103
|
+
Record<symbol, AsyncLocalStorage<FontScope> | undefined>
|
|
104
|
+
const fontStorage = (fontRuntimeGlobal[fontStorageKey] ??= new AsyncLocalStorage<FontScope>())
|
|
105
|
+
const allowedDisplayValues = new Set(['auto', 'block', 'swap', 'fallback', 'optional'])
|
|
106
|
+
const require = createRequire(import.meta.url)
|
|
107
|
+
const googleCssCache = new Map<string, Promise<string>>()
|
|
108
|
+
const googleFontFileCache = new Map<string, Promise<Uint8Array>>()
|
|
109
|
+
const resolvedFontCache = new Map<string, Promise<ResolvedFont>>()
|
|
110
|
+
let googleMetadataCache: Record<string, GoogleFontMetadata> | undefined
|
|
109
111
|
|
|
110
112
|
/**
|
|
111
113
|
* Every font definition constructed so far, whether or not a render has read its `.className` yet.
|
|
112
114
|
* Definitions are created when the app's font module evaluates - well before the render that registers
|
|
113
115
|
* them - which is what makes the resolve prewarmable.
|
|
114
116
|
*/
|
|
115
|
-
const declaredFonts = new Map<string, FontDefinition>()
|
|
117
|
+
const declaredFonts = new Map<string, FontDefinition>()
|
|
116
118
|
|
|
117
119
|
/**
|
|
118
120
|
* Resolve the declared fonts ahead of the render that will ask for them. Fire-and-forget: this only
|
|
@@ -124,53 +126,53 @@ export async function prewarmFonts(
|
|
|
124
126
|
options: { dev?: boolean } = {},
|
|
125
127
|
): Promise<void> {
|
|
126
128
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
127
|
-
if (process.env.PNEXT_PREWARM_FONTS === '0' || declaredFonts.size === 0) return
|
|
129
|
+
if (process.env.PNEXT_PREWARM_FONTS === '0' || declaredFonts.size === 0) return
|
|
128
130
|
await Promise.allSettled(
|
|
129
131
|
[...declaredFonts.values()].map(font => resolveFont(font, { config, dev: options.dev })),
|
|
130
|
-
)
|
|
132
|
+
)
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
export function runWithFontScope<T>(callback: () => T): T {
|
|
134
136
|
// Ensure the `Link`-header finalizer is registered before any render runs.
|
|
135
137
|
// Idempotent; register-font also calls it (canonical orchestrator seam).
|
|
136
|
-
registerFontFinalizerOnce()
|
|
137
|
-
return fontStorage.run({ fonts: new Map() }, callback)
|
|
138
|
+
registerFontFinalizerOnce()
|
|
139
|
+
return fontStorage.run({ fonts: new Map() }, callback)
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
/** Work-unit stash key for the font `Link` header preloads collected in a render. */
|
|
141
|
-
const FONT_HEADER_PRELOADS = Symbol.for('pnext.compat.font.headerPreloads')
|
|
143
|
+
const FONT_HEADER_PRELOADS = Symbol.for('pnext.compat.font.headerPreloads')
|
|
142
144
|
|
|
143
|
-
export async function fontAssets(
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
export async function fontAssets(
|
|
146
|
+
config: ResolvedConfig,
|
|
147
|
+
options: { dev?: boolean } = {},
|
|
148
|
+
): Promise<ResolvedFontAssets> {
|
|
149
|
+
const scope = fontStorage.getStore()
|
|
150
|
+
if (!scope || scope.fonts.size === 0) return { css: '', preloads: [] }
|
|
146
151
|
|
|
147
152
|
const resolved = await Promise.all(
|
|
148
153
|
[...scope.fonts.values()].map(font => resolveFont(font, { config, dev: options.dev })),
|
|
149
|
-
)
|
|
154
|
+
)
|
|
150
155
|
|
|
151
|
-
const preloads = dedupePreloads(resolved.flatMap(font => font.preloads))
|
|
156
|
+
const preloads = dedupePreloads(resolved.flatMap(font => font.preloads))
|
|
152
157
|
// Next delivers font preloads via a `Link` response header (not HTML link
|
|
153
158
|
// tags). Stash them on the request work unit; the response finalizer
|
|
154
159
|
// (registerFontResponseFinalizer) reads them back and sets the header.
|
|
155
|
-
stashHeaderPreloads(preloads)
|
|
160
|
+
stashHeaderPreloads(preloads)
|
|
156
161
|
|
|
157
|
-
const usesFonts = resolved.some(font => font.usesFonts)
|
|
162
|
+
const usesFonts = resolved.some(font => font.usesFonts)
|
|
158
163
|
return {
|
|
159
|
-
css: resolved
|
|
164
|
+
css: resolved
|
|
165
|
+
.map(font => font.css)
|
|
166
|
+
.filter(Boolean)
|
|
167
|
+
.join('\n'),
|
|
160
168
|
// Prod matches Next: font preloads travel EXCLUSIVELY in the `Link` response header, so the SSR
|
|
161
169
|
// document carries no `<link as="font">` tags. The live DOM still gets the links post-load via
|
|
162
170
|
// fontPreloadDomScript, which derives them from the emitted @font-face CSS - Next's client inserts
|
|
163
171
|
// them the same way, and the navigation suite counts them in the browser. Dev keeps the document
|
|
164
172
|
// links, since it has no header path.
|
|
165
173
|
preloads:
|
|
166
|
-
preloads.length > 0
|
|
167
|
-
|
|
168
|
-
? preloads
|
|
169
|
-
: []
|
|
170
|
-
: usesFonts
|
|
171
|
-
? [fontPreconnect()]
|
|
172
|
-
: [],
|
|
173
|
-
};
|
|
174
|
+
preloads.length > 0 ? (options.dev ? preloads : []) : usesFonts ? [fontPreconnect()] : [],
|
|
175
|
+
}
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
/**
|
|
@@ -190,31 +192,31 @@ export function fontPreloadDomScript(): string {
|
|
|
190
192
|
'var u;try{u=new URL(m[1],location.href).href}catch(e){continue}if(seen[u])continue;seen[u]=1;' +
|
|
191
193
|
'var l=document.createElement("link");l.rel="preload";l.as="font";l.type="font/woff2";l.setAttribute("crossorigin","");l.href=u;document.head.appendChild(l)}}}' +
|
|
192
194
|
'document.readyState==="loading"?document.addEventListener("DOMContentLoaded",a):a()}catch(e){}})();</script>'
|
|
193
|
-
)
|
|
195
|
+
)
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
function stashHeaderPreloads(preloads: MetadataLink[]): void {
|
|
197
|
-
if (preloads.length === 0) return
|
|
198
|
-
const unit = getWorkUnit()
|
|
199
|
-
if (!unit) return
|
|
200
|
-
const compat = (unit.compat ??= {})
|
|
201
|
-
const existing = (compat[FONT_HEADER_PRELOADS] as MetadataLink[] | undefined) ?? []
|
|
202
|
-
compat[FONT_HEADER_PRELOADS] = dedupePreloads([...existing, ...preloads])
|
|
199
|
+
if (preloads.length === 0) return
|
|
200
|
+
const unit = getWorkUnit()
|
|
201
|
+
if (!unit) return
|
|
202
|
+
const compat = (unit.compat ??= {})
|
|
203
|
+
const existing = (compat[FONT_HEADER_PRELOADS] as MetadataLink[] | undefined) ?? []
|
|
204
|
+
compat[FONT_HEADER_PRELOADS] = dedupePreloads([...existing, ...preloads])
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
/** Read the font `Link` header preloads stashed during the current render. */
|
|
206
208
|
export function takeFontHeaderPreloads(): MetadataLink[] {
|
|
207
|
-
const unit = getWorkUnit()
|
|
208
|
-
const compat = unit?.compat
|
|
209
|
-
if (!compat) return []
|
|
210
|
-
const preloads = (compat[FONT_HEADER_PRELOADS] as MetadataLink[] | undefined) ?? []
|
|
211
|
-
compat[FONT_HEADER_PRELOADS] = undefined
|
|
212
|
-
return preloads
|
|
209
|
+
const unit = getWorkUnit()
|
|
210
|
+
const compat = unit?.compat
|
|
211
|
+
if (!compat) return []
|
|
212
|
+
const preloads = (compat[FONT_HEADER_PRELOADS] as MetadataLink[] | undefined) ?? []
|
|
213
|
+
compat[FONT_HEADER_PRELOADS] = undefined
|
|
214
|
+
return preloads
|
|
213
215
|
}
|
|
214
216
|
|
|
215
|
-
let fontFinalizerRegistered = false
|
|
217
|
+
let fontFinalizerRegistered = false
|
|
216
218
|
// Host-scoped guard: the finalizer lives on the host, so a fresh host needs it again.
|
|
217
|
-
onExtensionHostReset(() => (fontFinalizerRegistered = false))
|
|
219
|
+
onExtensionHostReset(() => (fontFinalizerRegistered = false))
|
|
218
220
|
|
|
219
221
|
/**
|
|
220
222
|
* Register the response finalizer that flushes the stashed font preloads to the
|
|
@@ -223,30 +225,30 @@ onExtensionHostReset(() => (fontFinalizerRegistered = false));
|
|
|
223
225
|
* header even before the orchestrator wires register-font in.
|
|
224
226
|
*/
|
|
225
227
|
export function registerFontFinalizerOnce(): void {
|
|
226
|
-
if (fontFinalizerRegistered) return
|
|
227
|
-
fontFinalizerRegistered = true
|
|
228
|
+
if (fontFinalizerRegistered) return
|
|
229
|
+
fontFinalizerRegistered = true
|
|
228
230
|
registerResponseFinalizers(ctx => {
|
|
229
|
-
const preloads = takeFontHeaderPreloads()
|
|
230
|
-
if (preloads.length === 0) return
|
|
231
|
-
const value = preloads.map(fontLinkHeaderValue).join(', ')
|
|
232
|
-
const existing = ctx.headers.get('link')
|
|
233
|
-
ctx.headers.set('link', existing ? `${existing}, ${value}` : value)
|
|
234
|
-
})
|
|
231
|
+
const preloads = takeFontHeaderPreloads()
|
|
232
|
+
if (preloads.length === 0) return
|
|
233
|
+
const value = preloads.map(fontLinkHeaderValue).join(', ')
|
|
234
|
+
const existing = ctx.headers.get('link')
|
|
235
|
+
ctx.headers.set('link', existing ? `${existing}, ${value}` : value)
|
|
236
|
+
})
|
|
235
237
|
}
|
|
236
238
|
|
|
237
239
|
/** `<url>; rel=preload; as=font; type=font/woff2; crossorigin=""` */
|
|
238
240
|
export function fontLinkHeaderValue(link: MetadataLink): string {
|
|
239
|
-
const parts = [`<${link.url}>`, `rel=${link.rel}`]
|
|
240
|
-
if (link.as) parts.push(`as=${link.as}`)
|
|
241
|
-
if (link.type) parts.push(`type=${link.type}`)
|
|
242
|
-
parts.push('crossorigin=""')
|
|
243
|
-
return parts.join('; ')
|
|
241
|
+
const parts = [`<${link.url}>`, `rel=${link.rel}`]
|
|
242
|
+
if (link.as) parts.push(`as=${link.as}`)
|
|
243
|
+
if (link.type) parts.push(`type=${link.type}`)
|
|
244
|
+
parts.push('crossorigin=""')
|
|
245
|
+
return parts.join('; ')
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
export function googleFont(family: string) {
|
|
247
249
|
return (options: GoogleFontOptions = {}): NextFontWithVariable => {
|
|
248
|
-
const naming = googleFontNaming(family, options)
|
|
249
|
-
const { className, variable, fontFamily, hash, style, hasVariable } = naming
|
|
250
|
+
const naming = googleFontNaming(family, options)
|
|
251
|
+
const { className, variable, fontFamily, hash, style, hasVariable } = naming
|
|
250
252
|
const definition: FontDefinition = {
|
|
251
253
|
className,
|
|
252
254
|
family: fontFamily,
|
|
@@ -254,38 +256,39 @@ export function googleFont(family: string) {
|
|
|
254
256
|
style,
|
|
255
257
|
variable,
|
|
256
258
|
hasVariable,
|
|
257
|
-
resolve: context =>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
259
|
+
resolve: context =>
|
|
260
|
+
resolveGoogleFont(context, {
|
|
261
|
+
className,
|
|
262
|
+
family,
|
|
263
|
+
fontFamily,
|
|
264
|
+
hash,
|
|
265
|
+
options,
|
|
266
|
+
variable,
|
|
267
|
+
}),
|
|
268
|
+
}
|
|
266
269
|
|
|
267
|
-
return fontObject(definition)
|
|
268
|
-
}
|
|
270
|
+
return fontObject(definition)
|
|
271
|
+
}
|
|
269
272
|
}
|
|
270
273
|
|
|
271
274
|
export interface LocalFontLoader {
|
|
272
|
-
(options: LocalFontOptions): NextFontWithVariable
|
|
273
|
-
withFile: typeof localFontWithFile
|
|
275
|
+
(options: LocalFontOptions): NextFontWithVariable
|
|
276
|
+
withFile: typeof localFontWithFile
|
|
274
277
|
}
|
|
275
278
|
|
|
276
279
|
export const localFont: LocalFontLoader = Object.assign(
|
|
277
280
|
(options: LocalFontOptions) => localFontWithFile(inferLocalFontCallerFile())(options),
|
|
278
281
|
{ withFile: localFontWithFile },
|
|
279
|
-
)
|
|
282
|
+
)
|
|
280
283
|
|
|
281
284
|
export function localFontWithFile(callerFile: string | undefined, family?: string) {
|
|
282
285
|
return (options: LocalFontOptions): NextFontWithVariable => {
|
|
283
|
-
const naming = localFontNaming(callerFile, options)
|
|
286
|
+
const naming = localFontNaming(callerFile, options)
|
|
284
287
|
if (family) {
|
|
285
|
-
naming.fontFamily = family
|
|
286
|
-
naming.style = fontStyle(family, options, options.fallback ?? [])
|
|
288
|
+
naming.fontFamily = family
|
|
289
|
+
naming.style = fontStyle(family, options, options.fallback ?? [])
|
|
287
290
|
}
|
|
288
|
-
const { className, variable, fontFamily, hash, style, hasVariable } = naming
|
|
291
|
+
const { className, variable, fontFamily, hash, style, hasVariable } = naming
|
|
289
292
|
const definition: FontDefinition = {
|
|
290
293
|
className,
|
|
291
294
|
family: fontFamily,
|
|
@@ -293,73 +296,74 @@ export function localFontWithFile(callerFile: string | undefined, family?: strin
|
|
|
293
296
|
style,
|
|
294
297
|
variable,
|
|
295
298
|
hasVariable,
|
|
296
|
-
resolve: context =>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
299
|
+
resolve: context =>
|
|
300
|
+
resolveLocalFont(context, {
|
|
301
|
+
callerFile,
|
|
302
|
+
className,
|
|
303
|
+
fontFamily,
|
|
304
|
+
hash,
|
|
305
|
+
options,
|
|
306
|
+
variable,
|
|
307
|
+
}),
|
|
308
|
+
}
|
|
305
309
|
|
|
306
|
-
return fontObject(definition)
|
|
307
|
-
}
|
|
310
|
+
return fontObject(definition)
|
|
311
|
+
}
|
|
308
312
|
}
|
|
309
313
|
|
|
310
314
|
function fontObject(definition: FontDefinition): NextFontWithVariable {
|
|
311
315
|
// Recorded at construction, not at first `.className` read: the whole point
|
|
312
316
|
// is to know the font exists before any render registers it.
|
|
313
|
-
declaredFonts.set(definition.key, definition)
|
|
317
|
+
declaredFonts.set(definition.key, definition)
|
|
314
318
|
// `variable` is exposed only when the loader call passed a `variable` option
|
|
315
319
|
// (matches Next; an extra key would break `toEqual` on the font object).
|
|
316
320
|
const descriptors: PropertyDescriptorMap = {
|
|
317
321
|
className: {
|
|
318
322
|
enumerable: true,
|
|
319
323
|
get() {
|
|
320
|
-
registerFont(definition)
|
|
321
|
-
return definition.className
|
|
324
|
+
registerFont(definition)
|
|
325
|
+
return definition.className
|
|
322
326
|
},
|
|
323
327
|
},
|
|
324
328
|
style: {
|
|
325
329
|
enumerable: true,
|
|
326
330
|
get() {
|
|
327
|
-
registerFont(definition)
|
|
328
|
-
return definition.style
|
|
331
|
+
registerFont(definition)
|
|
332
|
+
return definition.style
|
|
329
333
|
},
|
|
330
334
|
},
|
|
331
|
-
}
|
|
335
|
+
}
|
|
332
336
|
if (definition.hasVariable) {
|
|
333
337
|
descriptors.variable = {
|
|
334
338
|
enumerable: true,
|
|
335
339
|
get() {
|
|
336
|
-
registerFont(definition)
|
|
337
|
-
return definition.variable
|
|
340
|
+
registerFont(definition)
|
|
341
|
+
return definition.variable
|
|
338
342
|
},
|
|
339
|
-
}
|
|
343
|
+
}
|
|
340
344
|
}
|
|
341
|
-
return Object.defineProperties({}, descriptors) as NextFontWithVariable
|
|
345
|
+
return Object.defineProperties({}, descriptors) as NextFontWithVariable
|
|
342
346
|
}
|
|
343
347
|
|
|
344
348
|
function registerFont(definition: FontDefinition) {
|
|
345
|
-
fontStorage.getStore()?.fonts.set(definition.key, definition)
|
|
349
|
+
fontStorage.getStore()?.fonts.set(definition.key, definition)
|
|
346
350
|
}
|
|
347
351
|
|
|
348
352
|
async function resolveFont(definition: FontDefinition, context: FontResolveContext) {
|
|
349
|
-
const key = `${context.config.outPath}:${context.dev ? 'dev' : 'build'}:${definition.key}
|
|
350
|
-
let resolved = resolvedFontCache.get(key)
|
|
353
|
+
const key = `${context.config.outPath}:${context.dev ? 'dev' : 'build'}:${definition.key}`
|
|
354
|
+
let resolved = resolvedFontCache.get(key)
|
|
351
355
|
if (!resolved) {
|
|
352
|
-
resolved = resolveThroughCache(definition, context, key)
|
|
353
|
-
resolvedFontCache.set(key, resolved)
|
|
356
|
+
resolved = resolveThroughCache(definition, context, key)
|
|
357
|
+
resolvedFontCache.set(key, resolved)
|
|
354
358
|
}
|
|
355
|
-
const font = await resolved
|
|
359
|
+
const font = await resolved
|
|
356
360
|
// A `pnext build` wiping the output directory under a running server must
|
|
357
361
|
// not leave cached font URLs pointing at deleted files; re-emit them.
|
|
358
362
|
// Font bytes stay cached in memory, so this never refetches the network.
|
|
359
|
-
if (font.files.every(file => existsSync(file))) return font
|
|
360
|
-
const fresh = resolveAndPersist(definition, context, key)
|
|
361
|
-
resolvedFontCache.set(key, fresh)
|
|
362
|
-
return fresh
|
|
363
|
+
if (font.files.every(file => existsSync(file))) return font
|
|
364
|
+
const fresh = resolveAndPersist(definition, context, key)
|
|
365
|
+
resolvedFontCache.set(key, fresh)
|
|
366
|
+
return fresh
|
|
363
367
|
}
|
|
364
368
|
|
|
365
369
|
/**
|
|
@@ -372,10 +376,10 @@ async function resolveThroughCache(
|
|
|
372
376
|
context: FontResolveContext,
|
|
373
377
|
key: string,
|
|
374
378
|
): Promise<ResolvedFont> {
|
|
375
|
-
if (!context.dev) return definition.resolve(context)
|
|
376
|
-
const persisted = await persistedFont(context.config, key)
|
|
377
|
-
if (persisted) return persisted
|
|
378
|
-
return resolveAndPersist(definition, context, key)
|
|
379
|
+
if (!context.dev) return definition.resolve(context)
|
|
380
|
+
const persisted = await persistedFont(context.config, key)
|
|
381
|
+
if (persisted) return persisted
|
|
382
|
+
return resolveAndPersist(definition, context, key)
|
|
379
383
|
}
|
|
380
384
|
|
|
381
385
|
function resolveAndPersist(
|
|
@@ -383,31 +387,32 @@ function resolveAndPersist(
|
|
|
383
387
|
context: FontResolveContext,
|
|
384
388
|
key: string,
|
|
385
389
|
): Promise<ResolvedFont> {
|
|
386
|
-
const resolved = definition.resolve(context)
|
|
390
|
+
const resolved = definition.resolve(context)
|
|
387
391
|
if (context.dev) {
|
|
388
|
-
void resolved.then(font => persistFont(context.config, key, font)).catch(() => undefined)
|
|
392
|
+
void resolved.then(font => persistFont(context.config, key, font)).catch(() => undefined)
|
|
389
393
|
}
|
|
390
|
-
return resolved
|
|
394
|
+
return resolved
|
|
391
395
|
}
|
|
392
396
|
|
|
393
397
|
async function resolveGoogleFont(
|
|
394
398
|
context: FontResolveContext,
|
|
395
399
|
request: {
|
|
396
|
-
className: string
|
|
397
|
-
family: string
|
|
398
|
-
fontFamily: string
|
|
399
|
-
hash: string
|
|
400
|
-
options: GoogleFontOptions
|
|
401
|
-
variable: string
|
|
400
|
+
className: string
|
|
401
|
+
family: string
|
|
402
|
+
fontFamily: string
|
|
403
|
+
hash: string
|
|
404
|
+
options: GoogleFontOptions
|
|
405
|
+
variable: string
|
|
402
406
|
},
|
|
403
407
|
): Promise<ResolvedFont> {
|
|
404
|
-
const metadata = googleMetadata()[request.family]
|
|
405
|
-
if (!metadata) throw new Error(`Unknown font \`${request.family}\``)
|
|
406
|
-
const options = normalizeGoogleOptions(request.family, metadata, request.options)
|
|
407
|
-
const adjustFontFallback = options.adjustFontFallback
|
|
408
|
-
const url = googleFontsUrl(request.family, googleFontAxes(metadata, options), options.display)
|
|
409
|
-
const css =
|
|
410
|
-
|
|
408
|
+
const metadata = googleMetadata()[request.family]
|
|
409
|
+
if (!metadata) throw new Error(`Unknown font \`${request.family}\``)
|
|
410
|
+
const options = normalizeGoogleOptions(request.family, metadata, request.options)
|
|
411
|
+
const adjustFontFallback = options.adjustFontFallback
|
|
412
|
+
const url = googleFontsUrl(request.family, googleFontAxes(metadata, options), options.display)
|
|
413
|
+
const css =
|
|
414
|
+
(await googleCss(url, request.family, Boolean(context.dev))).split('body {', 1)[0] ?? ''
|
|
415
|
+
const fontFiles = findGoogleFontFiles(css, options.preload ? options.subsets : undefined)
|
|
411
416
|
const emitted = await Promise.all(
|
|
412
417
|
fontFiles.map(async fontFile => ({
|
|
413
418
|
...fontFile,
|
|
@@ -416,20 +421,29 @@ async function resolveGoogleFont(
|
|
|
416
421
|
adjustFontFallback,
|
|
417
422
|
}),
|
|
418
423
|
})),
|
|
419
|
-
)
|
|
424
|
+
)
|
|
420
425
|
|
|
421
|
-
let rewrittenCss = css
|
|
426
|
+
let rewrittenCss = css
|
|
422
427
|
for (const item of emitted) {
|
|
423
|
-
rewrittenCss = rewrittenCss.replaceAll(item.googleFontFileUrl, item.emitted.url)
|
|
428
|
+
rewrittenCss = rewrittenCss.replaceAll(item.googleFontFileUrl, item.emitted.url)
|
|
424
429
|
}
|
|
425
430
|
|
|
426
|
-
const stack = familyStack(request.fontFamily, options.fallback, adjustFontFallback)
|
|
431
|
+
const stack = familyStack(request.fontFamily, options.fallback, adjustFontFallback)
|
|
427
432
|
const scopedCss = [
|
|
428
433
|
rewriteFontFamily(rewrittenCss, request.family, request.fontFamily),
|
|
429
434
|
adjustFontFallback ? fallbackFaceCss(request.fontFamily) : '',
|
|
430
|
-
classRule(
|
|
431
|
-
|
|
432
|
-
|
|
435
|
+
classRule(
|
|
436
|
+
request.className,
|
|
437
|
+
stack,
|
|
438
|
+
singleCssValue(options.weights),
|
|
439
|
+
singleCssValue(options.styles),
|
|
440
|
+
),
|
|
441
|
+
request.options.variable
|
|
442
|
+
? variableRule(request.variable, customProperty(request.options.variable), stack)
|
|
443
|
+
: '',
|
|
444
|
+
]
|
|
445
|
+
.filter(Boolean)
|
|
446
|
+
.join('\n')
|
|
433
447
|
|
|
434
448
|
return {
|
|
435
449
|
usesFonts: emitted.length > 0,
|
|
@@ -438,44 +452,52 @@ async function resolveGoogleFont(
|
|
|
438
452
|
.filter(item => item.preloadFontFile)
|
|
439
453
|
.map(item => fontPreload(item.emitted.url, item.emitted.type)),
|
|
440
454
|
files: emitted.map(item => item.emitted.file),
|
|
441
|
-
}
|
|
455
|
+
}
|
|
442
456
|
}
|
|
443
457
|
|
|
444
458
|
async function resolveLocalFont(
|
|
445
459
|
context: FontResolveContext,
|
|
446
460
|
request: {
|
|
447
|
-
callerFile: string | undefined
|
|
448
|
-
className: string
|
|
449
|
-
fontFamily: string
|
|
450
|
-
hash: string
|
|
451
|
-
options: LocalFontOptions
|
|
452
|
-
variable: string
|
|
461
|
+
callerFile: string | undefined
|
|
462
|
+
className: string
|
|
463
|
+
fontFamily: string
|
|
464
|
+
hash: string
|
|
465
|
+
options: LocalFontOptions
|
|
466
|
+
variable: string
|
|
453
467
|
},
|
|
454
468
|
): Promise<ResolvedFont> {
|
|
455
|
-
const display = validateDisplay(request.options.display ?? 'swap')
|
|
469
|
+
const display = validateDisplay(request.options.display ?? 'swap')
|
|
456
470
|
const adjustFontFallback = request.options.declarations?.some(item => item.prop === 'font-family')
|
|
457
471
|
? false
|
|
458
|
-
: true
|
|
459
|
-
const preload = request.options.preload !== false
|
|
460
|
-
const sources = normalizeLocalSources(request.options, request.callerFile)
|
|
461
|
-
const emitted = await Promise.all(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
472
|
+
: true
|
|
473
|
+
const preload = request.options.preload !== false
|
|
474
|
+
const sources = normalizeLocalSources(request.options, request.callerFile)
|
|
475
|
+
const emitted = await Promise.all(
|
|
476
|
+
sources.map(async source => ({
|
|
477
|
+
...source,
|
|
478
|
+
emitted: await emitLocalFontFile(context, source.file, { preload, adjustFontFallback }),
|
|
479
|
+
})),
|
|
480
|
+
)
|
|
481
|
+
const stack = familyStack(request.fontFamily, request.options.fallback ?? [], adjustFontFallback)
|
|
482
|
+
const declarations = request.options.declarations ?? []
|
|
467
483
|
const faceCss = emitted.map(source =>
|
|
468
484
|
[
|
|
469
485
|
'@font-face{',
|
|
470
486
|
`font-family:${singleQuoteFamily(request.fontFamily)};`,
|
|
471
487
|
`src:url("${source.emitted.url}") format("${fontFormat(source.emitted.ext)}");`,
|
|
472
488
|
`font-display:${display};`,
|
|
473
|
-
|
|
474
|
-
|
|
489
|
+
(source.weight ?? request.options.weight)
|
|
490
|
+
? `font-weight:${cssIdentifier(source.weight ?? request.options.weight ?? '')};`
|
|
491
|
+
: '',
|
|
492
|
+
(source.style ?? request.options.style)
|
|
493
|
+
? `font-style:${cssIdentifier(source.style ?? request.options.style ?? '')};`
|
|
494
|
+
: '',
|
|
475
495
|
...declarations.map(item => `${cssProperty(item.prop)}:${cssDeclarationValue(item.value)};`),
|
|
476
496
|
'}',
|
|
477
|
-
]
|
|
478
|
-
|
|
497
|
+
]
|
|
498
|
+
.filter(Boolean)
|
|
499
|
+
.join(''),
|
|
500
|
+
)
|
|
479
501
|
|
|
480
502
|
return {
|
|
481
503
|
usesFonts: emitted.length > 0,
|
|
@@ -483,14 +505,16 @@ async function resolveLocalFont(
|
|
|
483
505
|
...faceCss,
|
|
484
506
|
adjustFontFallback ? fallbackFaceCss(request.fontFamily) : '',
|
|
485
507
|
classRule(request.className, stack, request.options.weight, request.options.style),
|
|
486
|
-
request.options.variable
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
508
|
+
request.options.variable
|
|
509
|
+
? variableRule(request.variable, customProperty(request.options.variable), stack)
|
|
510
|
+
: '',
|
|
511
|
+
]
|
|
512
|
+
.filter(Boolean)
|
|
513
|
+
.join('\n'),
|
|
514
|
+
preloads: preload ? emitted.map(item => fontPreload(item.emitted.url, item.emitted.type)) : [],
|
|
491
515
|
files: emitted.map(item => item.emitted.file),
|
|
492
516
|
sources: emitted.map(source => source.file),
|
|
493
|
-
}
|
|
517
|
+
}
|
|
494
518
|
}
|
|
495
519
|
|
|
496
520
|
function normalizeGoogleOptions(
|
|
@@ -498,50 +522,54 @@ function normalizeGoogleOptions(
|
|
|
498
522
|
metadata: GoogleFontMetadata,
|
|
499
523
|
options: GoogleFontOptions,
|
|
500
524
|
): NormalizedGoogleOptions {
|
|
501
|
-
const display = validateDisplay(options.display ?? 'swap')
|
|
502
|
-
const preload = options.preload ?? true
|
|
503
|
-
const subsets = options.subsets
|
|
525
|
+
const display = validateDisplay(options.display ?? 'swap')
|
|
526
|
+
const preload = options.preload ?? true
|
|
527
|
+
const subsets = options.subsets
|
|
504
528
|
if (metadata.subsets.length === 0) {
|
|
505
529
|
// Matches Next: fonts without preloadable subsets silently disable preload.
|
|
506
530
|
} else if (preload) {
|
|
507
531
|
if (!subsets) {
|
|
508
532
|
throw new Error(
|
|
509
533
|
`Preload is enabled but no subsets were specified for font \`${family}\`. ` +
|
|
510
|
-
|
|
511
|
-
)
|
|
534
|
+
`Please specify subsets or disable preloading.`,
|
|
535
|
+
)
|
|
512
536
|
}
|
|
513
537
|
for (const subset of subsets) {
|
|
514
538
|
if (!metadata.subsets.includes(subset)) {
|
|
515
|
-
throw new Error(`Unknown subset \`${subset}\` for font \`${family}\`.`)
|
|
539
|
+
throw new Error(`Unknown subset \`${subset}\` for font \`${family}\`.`)
|
|
516
540
|
}
|
|
517
541
|
}
|
|
518
542
|
}
|
|
519
543
|
|
|
520
|
-
const weights = uniqueArray(options.weight ? arrayValue(options.weight) : [])
|
|
544
|
+
const weights = uniqueArray(options.weight ? arrayValue(options.weight) : [])
|
|
521
545
|
if (weights.length === 0) {
|
|
522
|
-
if (metadata.weights.includes('variable')) weights.push('variable')
|
|
523
|
-
else throw new Error(`Missing weight for font \`${family}\`.`)
|
|
546
|
+
if (metadata.weights.includes('variable')) weights.push('variable')
|
|
547
|
+
else throw new Error(`Missing weight for font \`${family}\`.`)
|
|
524
548
|
}
|
|
525
549
|
if (weights.length > 1 && weights.includes('variable')) {
|
|
526
|
-
throw new Error(`Unexpected \`variable\` in weight array for font \`${family}\`.`)
|
|
550
|
+
throw new Error(`Unexpected \`variable\` in weight array for font \`${family}\`.`)
|
|
527
551
|
}
|
|
528
552
|
for (const weight of weights) {
|
|
529
|
-
if (!metadata.weights.includes(weight))
|
|
553
|
+
if (!metadata.weights.includes(weight))
|
|
554
|
+
throw new Error(`Unknown weight \`${weight}\` for font \`${family}\`.`)
|
|
530
555
|
}
|
|
531
556
|
|
|
532
|
-
const styles = uniqueArray(options.style ? arrayValue(options.style) : [])
|
|
533
|
-
if (styles.length === 0)
|
|
557
|
+
const styles = uniqueArray(options.style ? arrayValue(options.style) : [])
|
|
558
|
+
if (styles.length === 0)
|
|
559
|
+
styles.push(metadata.styles.length === 1 ? (metadata.styles[0] ?? 'normal') : 'normal')
|
|
534
560
|
for (const style of styles) {
|
|
535
|
-
if (!metadata.styles.includes(style))
|
|
561
|
+
if (!metadata.styles.includes(style))
|
|
562
|
+
throw new Error(`Unknown style \`${style}\` for font \`${family}\`.`)
|
|
536
563
|
}
|
|
537
564
|
|
|
538
565
|
if (options.axes) {
|
|
539
566
|
if (!metadata.weights.includes('variable') || weights[0] !== 'variable') {
|
|
540
|
-
throw new Error('Axes can only be defined for variable fonts.')
|
|
567
|
+
throw new Error('Axes can only be defined for variable fonts.')
|
|
541
568
|
}
|
|
542
|
-
const definableAxes = (metadata.axes ?? []).map(axis => axis.tag).filter(tag => tag !== 'wght')
|
|
569
|
+
const definableAxes = (metadata.axes ?? []).map(axis => axis.tag).filter(tag => tag !== 'wght')
|
|
543
570
|
for (const axis of options.axes) {
|
|
544
|
-
if (!definableAxes.includes(axis))
|
|
571
|
+
if (!definableAxes.includes(axis))
|
|
572
|
+
throw new Error(`Invalid axes value \`${axis}\` for font \`${family}\`.`)
|
|
545
573
|
}
|
|
546
574
|
}
|
|
547
575
|
|
|
@@ -555,30 +583,35 @@ function normalizeGoogleOptions(
|
|
|
555
583
|
subsets,
|
|
556
584
|
variable: options.variable,
|
|
557
585
|
weights,
|
|
558
|
-
}
|
|
586
|
+
}
|
|
559
587
|
}
|
|
560
588
|
|
|
561
589
|
function googleFontAxes(metadata: GoogleFontMetadata, options: NormalizedGoogleOptions) {
|
|
562
|
-
const hasItalic = options.styles.includes('italic')
|
|
563
|
-
const hasNormal = options.styles.includes('normal')
|
|
564
|
-
const ital = hasItalic ? [...(hasNormal ? ['0'] : []), '1'] : undefined
|
|
590
|
+
const hasItalic = options.styles.includes('italic')
|
|
591
|
+
const hasNormal = options.styles.includes('normal')
|
|
592
|
+
const ital = hasItalic ? [...(hasNormal ? ['0'] : []), '1'] : undefined
|
|
565
593
|
|
|
566
594
|
if (options.weights[0] !== 'variable') {
|
|
567
|
-
return {
|
|
595
|
+
return {
|
|
596
|
+
ital,
|
|
597
|
+
wght: options.weights,
|
|
598
|
+
variableAxes: undefined as [string, string][] | undefined,
|
|
599
|
+
}
|
|
568
600
|
}
|
|
569
601
|
|
|
570
|
-
let weightAxis: string | undefined
|
|
571
|
-
const variableAxes: [string, string][] = []
|
|
602
|
+
let weightAxis: string | undefined
|
|
603
|
+
const variableAxes: [string, string][] = []
|
|
572
604
|
for (const axis of metadata.axes ?? []) {
|
|
573
|
-
if (axis.tag === 'wght') weightAxis = `${axis.min}..${axis.max}
|
|
574
|
-
else if (options.axes?.includes(axis.tag))
|
|
605
|
+
if (axis.tag === 'wght') weightAxis = `${axis.min}..${axis.max}`
|
|
606
|
+
else if (options.axes?.includes(axis.tag))
|
|
607
|
+
variableAxes.push([axis.tag, `${axis.min}..${axis.max}`])
|
|
575
608
|
}
|
|
576
609
|
|
|
577
610
|
return {
|
|
578
611
|
ital,
|
|
579
612
|
wght: weightAxis ? [weightAxis] : undefined,
|
|
580
613
|
variableAxes: variableAxes.length > 0 ? variableAxes : undefined,
|
|
581
|
-
}
|
|
614
|
+
}
|
|
582
615
|
}
|
|
583
616
|
|
|
584
617
|
function googleFontsUrl(
|
|
@@ -586,116 +619,120 @@ function googleFontsUrl(
|
|
|
586
619
|
axes: { ital?: string[]; wght?: string[]; variableAxes?: [string, string][] },
|
|
587
620
|
display: FontDisplay,
|
|
588
621
|
) {
|
|
589
|
-
const variants: [string, string][][] = []
|
|
622
|
+
const variants: [string, string][][] = []
|
|
590
623
|
if (axes.wght) {
|
|
591
624
|
for (const wght of axes.wght) {
|
|
592
625
|
if (!axes.ital) {
|
|
593
|
-
variants.push([['wght', wght], ...(axes.variableAxes ?? [])])
|
|
626
|
+
variants.push([['wght', wght], ...(axes.variableAxes ?? [])])
|
|
594
627
|
} else {
|
|
595
|
-
for (const ital of axes.ital)
|
|
628
|
+
for (const ital of axes.ital)
|
|
629
|
+
variants.push([['ital', ital], ['wght', wght], ...(axes.variableAxes ?? [])])
|
|
596
630
|
}
|
|
597
631
|
}
|
|
598
632
|
} else if (axes.variableAxes) {
|
|
599
|
-
variants.push([...axes.variableAxes])
|
|
633
|
+
variants.push([...axes.variableAxes])
|
|
600
634
|
}
|
|
601
635
|
|
|
602
636
|
if (axes.variableAxes) {
|
|
603
637
|
for (const variant of variants) {
|
|
604
638
|
variant.sort(([a], [b]) => {
|
|
605
|
-
const aLower = a.charCodeAt(0) > 96
|
|
606
|
-
const bLower = b.charCodeAt(0) > 96
|
|
607
|
-
if (aLower && !bLower) return -1
|
|
608
|
-
if (bLower && !aLower) return 1
|
|
609
|
-
return a > b ? 1 : -1
|
|
610
|
-
})
|
|
639
|
+
const aLower = a.charCodeAt(0) > 96
|
|
640
|
+
const bLower = b.charCodeAt(0) > 96
|
|
641
|
+
if (aLower && !bLower) return -1
|
|
642
|
+
if (bLower && !aLower) return 1
|
|
643
|
+
return a > b ? 1 : -1
|
|
644
|
+
})
|
|
611
645
|
}
|
|
612
646
|
}
|
|
613
647
|
|
|
614
|
-
let url = `https://fonts.googleapis.com/css2?family=${family.replace(/ /g, '+')}
|
|
615
|
-
const firstVariant = variants[0]
|
|
648
|
+
let url = `https://fonts.googleapis.com/css2?family=${family.replace(/ /g, '+')}`
|
|
649
|
+
const firstVariant = variants[0]
|
|
616
650
|
if (firstVariant) {
|
|
617
651
|
url += `:${firstVariant.map(([key]) => key).join(',')}@${variants
|
|
618
652
|
.map(variant => variant.map(([, value]) => value).join(','))
|
|
619
653
|
.sort(sortFontVariantValues)
|
|
620
|
-
.join(';')}
|
|
654
|
+
.join(';')}`
|
|
621
655
|
}
|
|
622
|
-
return `${url}&display=${display}
|
|
656
|
+
return `${url}&display=${display}`
|
|
623
657
|
}
|
|
624
658
|
|
|
625
659
|
async function googleCss(url: string, family: string, dev: boolean) {
|
|
626
|
-
let cached = googleCssCache.get(url)
|
|
660
|
+
let cached = googleCssCache.get(url)
|
|
627
661
|
if (!cached) {
|
|
628
|
-
cached = fetchGoogleCss(url, family, dev)
|
|
629
|
-
googleCssCache.set(url, cached)
|
|
662
|
+
cached = fetchGoogleCss(url, family, dev)
|
|
663
|
+
googleCssCache.set(url, cached)
|
|
630
664
|
}
|
|
631
|
-
return cached
|
|
665
|
+
return cached
|
|
632
666
|
}
|
|
633
667
|
|
|
634
668
|
async function fetchGoogleCss(url: string, family: string, dev: boolean) {
|
|
635
|
-
const mocked = mockedGoogleResponse(url)
|
|
636
|
-
if (typeof mocked === 'string') return mocked
|
|
669
|
+
const mocked = mockedGoogleResponse(url)
|
|
670
|
+
if (typeof mocked === 'string') return mocked
|
|
637
671
|
|
|
638
672
|
const response = await fetch(url, {
|
|
639
673
|
headers: {
|
|
640
674
|
// Google returns modern woff2 CSS for current browsers.
|
|
641
675
|
'user-agent': 'Mozilla/5.0 AppleWebKit/537.36 Chrome/120 Safari/537.36',
|
|
642
676
|
},
|
|
643
|
-
})
|
|
677
|
+
})
|
|
644
678
|
if (!response.ok) {
|
|
645
|
-
const message = `Failed to fetch font \`${family}\` from Google Fonts: ${response.status} ${response.statusText}
|
|
646
|
-
throw new Error(
|
|
679
|
+
const message = `Failed to fetch font \`${family}\` from Google Fonts: ${response.status} ${response.statusText}`
|
|
680
|
+
throw new Error(
|
|
681
|
+
dev ? `${message}. Use NEXT_FONT_GOOGLE_MOCKED_RESPONSES for offline tests.` : message,
|
|
682
|
+
)
|
|
647
683
|
}
|
|
648
|
-
return response.text()
|
|
684
|
+
return response.text()
|
|
649
685
|
}
|
|
650
686
|
|
|
651
687
|
function findGoogleFontFiles(css: string, subsetsToPreload: string[] | undefined) {
|
|
652
|
-
const fontFiles: { googleFontFileUrl: string; preloadFontFile: boolean }[] = []
|
|
653
|
-
let currentSubset = ''
|
|
688
|
+
const fontFiles: { googleFontFileUrl: string; preloadFontFile: boolean }[] = []
|
|
689
|
+
let currentSubset = ''
|
|
654
690
|
for (const line of css.split('\n')) {
|
|
655
|
-
const subset = /\/\* (.+?) \*\//.exec(line)?.[1]
|
|
691
|
+
const subset = /\/\* (.+?) \*\//.exec(line)?.[1]
|
|
656
692
|
if (subset) {
|
|
657
|
-
currentSubset = subset
|
|
658
|
-
continue
|
|
693
|
+
currentSubset = subset
|
|
694
|
+
continue
|
|
659
695
|
}
|
|
660
696
|
|
|
661
|
-
const googleFontFileUrl = /src: url\((.+?)\)/.exec(line)?.[1]
|
|
662
|
-
if (!googleFontFileUrl || fontFiles.some(file => file.googleFontFileUrl === googleFontFileUrl))
|
|
697
|
+
const googleFontFileUrl = /src: url\((.+?)\)/.exec(line)?.[1]
|
|
698
|
+
if (!googleFontFileUrl || fontFiles.some(file => file.googleFontFileUrl === googleFontFileUrl))
|
|
699
|
+
continue
|
|
663
700
|
fontFiles.push({
|
|
664
701
|
googleFontFileUrl,
|
|
665
702
|
preloadFontFile: Boolean(subsetsToPreload?.includes(currentSubset)),
|
|
666
|
-
})
|
|
703
|
+
})
|
|
667
704
|
}
|
|
668
|
-
return fontFiles
|
|
705
|
+
return fontFiles
|
|
669
706
|
}
|
|
670
707
|
|
|
671
708
|
interface EmitFontOptions {
|
|
672
|
-
preload: boolean
|
|
673
|
-
adjustFontFallback: boolean
|
|
709
|
+
preload: boolean
|
|
710
|
+
adjustFontFallback: boolean
|
|
674
711
|
}
|
|
675
712
|
|
|
676
713
|
async function emitRemoteFontFile(context: FontResolveContext, url: string, emit: EmitFontOptions) {
|
|
677
|
-
let cached = googleFontFileCache.get(url)
|
|
714
|
+
let cached = googleFontFileCache.get(url)
|
|
678
715
|
if (!cached) {
|
|
679
|
-
cached = fetchFontFile(url)
|
|
680
|
-
googleFontFileCache.set(url, cached)
|
|
716
|
+
cached = fetchFontFile(url)
|
|
717
|
+
googleFontFileCache.set(url, cached)
|
|
681
718
|
}
|
|
682
|
-
const bytes = await cached
|
|
683
|
-
return emitFontBytes(context, bytes, fontExt(url), emit)
|
|
719
|
+
const bytes = await cached
|
|
720
|
+
return emitFontBytes(context, bytes, fontExt(url), emit)
|
|
684
721
|
}
|
|
685
722
|
|
|
686
723
|
async function fetchFontFile(url: string) {
|
|
687
|
-
const mocked = mockedGoogleResponse(url)
|
|
688
|
-
if (mocked instanceof Uint8Array) return mocked
|
|
689
|
-
if (typeof mocked === 'string') return new TextEncoder().encode(mocked)
|
|
724
|
+
const mocked = mockedGoogleResponse(url)
|
|
725
|
+
if (mocked instanceof Uint8Array) return mocked
|
|
726
|
+
if (typeof mocked === 'string') return new TextEncoder().encode(mocked)
|
|
690
727
|
|
|
691
|
-
const response = await fetch(url)
|
|
692
|
-
if (!response.ok) throw new Error(`Failed to fetch font file from \`${url}\`.`)
|
|
693
|
-
return new Uint8Array(await response.arrayBuffer())
|
|
728
|
+
const response = await fetch(url)
|
|
729
|
+
if (!response.ok) throw new Error(`Failed to fetch font file from \`${url}\`.`)
|
|
730
|
+
return new Uint8Array(await response.arrayBuffer())
|
|
694
731
|
}
|
|
695
732
|
|
|
696
733
|
async function emitLocalFontFile(context: FontResolveContext, file: string, emit: EmitFontOptions) {
|
|
697
|
-
const bytes = await readFile(file)
|
|
698
|
-
return emitFontBytes(context, bytes, fontExt(file), emit, file)
|
|
734
|
+
const bytes = await readFile(file)
|
|
735
|
+
return emitFontBytes(context, bytes, fontExt(file), emit, file)
|
|
699
736
|
}
|
|
700
737
|
|
|
701
738
|
async function emitFontBytes(
|
|
@@ -705,165 +742,168 @@ async function emitFontBytes(
|
|
|
705
742
|
emit: EmitFontOptions,
|
|
706
743
|
sourceFile?: string,
|
|
707
744
|
) {
|
|
708
|
-
const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 16)
|
|
745
|
+
const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 16)
|
|
709
746
|
// Next's loader names emitted files `[hash]-s.p.[ext]`: `-s` when a
|
|
710
747
|
// size-adjust fallback font is used, `.p` when the font is preloaded.
|
|
711
|
-
const filename = `${hash}${emit.adjustFontFallback ? '-s' : ''}${emit.preload ? '.p' : ''}.${ext}
|
|
748
|
+
const filename = `${hash}${emit.adjustFontFallback ? '-s' : ''}${emit.preload ? '.p' : ''}.${ext}`
|
|
712
749
|
// Served from `/_next/static/media/` (files under public/ map 1:1 to the URL
|
|
713
750
|
// path). Dev stashes under cache/ but keeps the same relative layout.
|
|
714
|
-
const mediaSegments = ['_next', 'static', 'media']
|
|
751
|
+
const mediaSegments = ['_next', 'static', 'media']
|
|
715
752
|
const outRoot = context.dev
|
|
716
753
|
? path.join(context.config.outPath, 'cache')
|
|
717
|
-
: path.join(context.config.outPath, 'public')
|
|
718
|
-
const outDir = path.join(outRoot, ...mediaSegments)
|
|
719
|
-
const file = path.join(outDir, filename)
|
|
720
|
-
await mkdir(outDir, { recursive: true })
|
|
754
|
+
: path.join(context.config.outPath, 'public')
|
|
755
|
+
const outDir = path.join(outRoot, ...mediaSegments)
|
|
756
|
+
const file = path.join(outDir, filename)
|
|
757
|
+
await mkdir(outDir, { recursive: true })
|
|
721
758
|
if (!existsSync(file)) {
|
|
722
|
-
if (sourceFile) await copyFile(sourceFile, file)
|
|
723
|
-
else await writeFile(file, bytes)
|
|
759
|
+
if (sourceFile) await copyFile(sourceFile, file)
|
|
760
|
+
else await writeFile(file, bytes)
|
|
724
761
|
}
|
|
725
|
-
return { ext, file, type: fontMimeType(ext), url: `/${mediaSegments.join('/')}/${filename}` }
|
|
762
|
+
return { ext, file, type: fontMimeType(ext), url: `/${mediaSegments.join('/')}/${filename}` }
|
|
726
763
|
}
|
|
727
764
|
|
|
728
|
-
function normalizeLocalSources(
|
|
729
|
-
|
|
730
|
-
|
|
765
|
+
function normalizeLocalSources(
|
|
766
|
+
options: LocalFontOptions,
|
|
767
|
+
callerFile: string | undefined,
|
|
768
|
+
): NormalizedLocalSource[] {
|
|
769
|
+
if (!callerFile) throw new Error('next/font/local requires PNext server source transforms.')
|
|
770
|
+
const values = Array.isArray(options.src) ? options.src : [options.src]
|
|
731
771
|
return values.map(value => {
|
|
732
|
-
const item = typeof value === 'string' ? { path: value } : value
|
|
733
|
-
if (!item.path) throw new Error('next/font/local src entries require a path.')
|
|
734
|
-
const file = path.resolve(path.dirname(callerFile), item.path)
|
|
735
|
-
if (!existsSync(file)) throw new Error(`Font file not found: ${item.path}`)
|
|
736
|
-
return { file, style: item.style, weight: item.weight }
|
|
737
|
-
})
|
|
772
|
+
const item = typeof value === 'string' ? { path: value } : value
|
|
773
|
+
if (!item.path) throw new Error('next/font/local src entries require a path.')
|
|
774
|
+
const file = path.resolve(path.dirname(callerFile), item.path)
|
|
775
|
+
if (!existsSync(file)) throw new Error(`Font file not found: ${item.path}`)
|
|
776
|
+
return { file, style: item.style, weight: item.weight }
|
|
777
|
+
})
|
|
738
778
|
}
|
|
739
779
|
|
|
740
780
|
function inferLocalFontCallerFile(): string | undefined {
|
|
741
|
-
const stack = new Error().stack
|
|
742
|
-
if (!stack) return undefined
|
|
781
|
+
const stack = new Error().stack
|
|
782
|
+
if (!stack) return undefined
|
|
743
783
|
for (const line of stack.split('\n')) {
|
|
744
|
-
const file = stackFrameFile(line)
|
|
745
|
-
if (!file) continue
|
|
746
|
-
if (file.replace(/\\/g, '/').includes('/src/compat/next/font/')) continue
|
|
747
|
-
return originalSourceFile(file)
|
|
784
|
+
const file = stackFrameFile(line)
|
|
785
|
+
if (!file) continue
|
|
786
|
+
if (file.replace(/\\/g, '/').includes('/src/compat/next/font/')) continue
|
|
787
|
+
return originalSourceFile(file)
|
|
748
788
|
}
|
|
749
|
-
return undefined
|
|
789
|
+
return undefined
|
|
750
790
|
}
|
|
751
791
|
|
|
752
792
|
function stackFrameFile(line: string) {
|
|
753
|
-
const match =
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
793
|
+
const match =
|
|
794
|
+
/\(?((?:file:\/\/)?\/[^():]+\.[cm]?[jt]sx?|(?:file:\/\/)?\/[^():]+\.mjs):\d+:\d+\)?/.exec(line)
|
|
795
|
+
const file = match?.[1]
|
|
796
|
+
if (!file) return undefined
|
|
797
|
+
return file.startsWith('file://') ? fileURLToPath(file) : file
|
|
757
798
|
}
|
|
758
799
|
|
|
759
800
|
function originalSourceFile(file: string) {
|
|
760
|
-
const source = sourceComment(file)
|
|
761
|
-
if (!source) return file
|
|
762
|
-
const normalized = source.replace(/\\/g, '/')
|
|
763
|
-
if (normalized.startsWith('file://')) return fileURLToPath(normalized)
|
|
764
|
-
if (path.isAbsolute(normalized)) return existsSync(normalized) ? normalized : file
|
|
765
|
-
const nodeModulesIndex = normalized.indexOf('node_modules/')
|
|
766
|
-
const relative = nodeModulesIndex === -1 ? normalized : normalized.slice(nodeModulesIndex)
|
|
767
|
-
const candidate = path.resolve(compiledProjectRoot(file), relative)
|
|
768
|
-
return existsSync(candidate) ? candidate : file
|
|
801
|
+
const source = sourceComment(file)
|
|
802
|
+
if (!source) return file
|
|
803
|
+
const normalized = source.replace(/\\/g, '/')
|
|
804
|
+
if (normalized.startsWith('file://')) return fileURLToPath(normalized)
|
|
805
|
+
if (path.isAbsolute(normalized)) return existsSync(normalized) ? normalized : file
|
|
806
|
+
const nodeModulesIndex = normalized.indexOf('node_modules/')
|
|
807
|
+
const relative = nodeModulesIndex === -1 ? normalized : normalized.slice(nodeModulesIndex)
|
|
808
|
+
const candidate = path.resolve(compiledProjectRoot(file), relative)
|
|
809
|
+
return existsSync(candidate) ? candidate : file
|
|
769
810
|
}
|
|
770
811
|
|
|
771
812
|
function sourceComment(file: string) {
|
|
772
|
-
if (!existsSync(file)) return undefined
|
|
773
|
-
const firstLine = readFileSync(file, 'utf8').split(/\r?\n/, 1)[0]?.trim()
|
|
774
|
-
return /^\/\/\s+(.+)$/.exec(firstLine ?? '')?.[1]
|
|
813
|
+
if (!existsSync(file)) return undefined
|
|
814
|
+
const firstLine = readFileSync(file, 'utf8').split(/\r?\n/, 1)[0]?.trim()
|
|
815
|
+
return /^\/\/\s+(.+)$/.exec(firstLine ?? '')?.[1]
|
|
775
816
|
}
|
|
776
817
|
|
|
777
818
|
function compiledProjectRoot(file: string) {
|
|
778
|
-
const marker = `${path.sep}.pnext${path.sep}
|
|
779
|
-
const index = file.indexOf(marker)
|
|
780
|
-
return index === -1 ? process.cwd() : file.slice(0, index)
|
|
819
|
+
const marker = `${path.sep}.pnext${path.sep}`
|
|
820
|
+
const index = file.indexOf(marker)
|
|
821
|
+
return index === -1 ? process.cwd() : file.slice(0, index)
|
|
781
822
|
}
|
|
782
823
|
|
|
783
824
|
function googleMetadata() {
|
|
784
825
|
if (!googleMetadataCache) {
|
|
785
|
-
googleMetadataCache = loadGoogleMetadata()
|
|
826
|
+
googleMetadataCache = loadGoogleMetadata()
|
|
786
827
|
}
|
|
787
|
-
return googleMetadataCache
|
|
828
|
+
return googleMetadataCache
|
|
788
829
|
}
|
|
789
830
|
|
|
790
831
|
function loadGoogleMetadata(): Record<string, GoogleFontMetadata> {
|
|
791
|
-
const override = process.env.PNEXT_FONT_GOOGLE_METADATA
|
|
832
|
+
const override = process.env.PNEXT_FONT_GOOGLE_METADATA
|
|
792
833
|
if (override) {
|
|
793
|
-
const metadata = readGoogleMetadataFile(override)
|
|
794
|
-
if (metadata) return metadata
|
|
795
|
-
throw new Error(`Invalid Google font metadata file: ${override}`)
|
|
834
|
+
const metadata = readGoogleMetadataFile(override)
|
|
835
|
+
if (metadata) return metadata
|
|
836
|
+
throw new Error(`Invalid Google font metadata file: ${override}`)
|
|
796
837
|
}
|
|
797
838
|
|
|
798
839
|
const metadata =
|
|
799
|
-
tryLoadGoogleMetadata('next-font/google/font-data.json') ??
|
|
800
|
-
|
|
801
|
-
if (metadata) return metadata;
|
|
840
|
+
tryLoadGoogleMetadata('next-font/google/font-data.json') ?? tryLoadGoogleMetadata('next-font')
|
|
841
|
+
if (metadata) return metadata
|
|
802
842
|
|
|
803
|
-
throw new Error(
|
|
804
|
-
'next/font/google requires Google font metadata. Install `next-font`.',
|
|
805
|
-
);
|
|
843
|
+
throw new Error('next/font/google requires Google font metadata. Install `next-font`.')
|
|
806
844
|
}
|
|
807
845
|
|
|
808
846
|
function tryLoadGoogleMetadata(specifier: string): Record<string, GoogleFontMetadata> | undefined {
|
|
809
847
|
try {
|
|
810
|
-
const loaded = require(specifier) as
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
848
|
+
const loaded = require(specifier) as
|
|
849
|
+
| {
|
|
850
|
+
default?: unknown
|
|
851
|
+
googleFontsMetadata?: unknown
|
|
852
|
+
}
|
|
853
|
+
| Record<string, GoogleFontMetadata>
|
|
814
854
|
const metadata =
|
|
815
855
|
'googleFontsMetadata' in loaded
|
|
816
856
|
? loaded.googleFontsMetadata
|
|
817
857
|
: 'default' in loaded
|
|
818
858
|
? loaded.default
|
|
819
|
-
: loaded
|
|
820
|
-
return isGoogleMetadata(metadata) ? metadata : undefined
|
|
859
|
+
: loaded
|
|
860
|
+
return isGoogleMetadata(metadata) ? metadata : undefined
|
|
821
861
|
} catch {
|
|
822
|
-
return undefined
|
|
862
|
+
return undefined
|
|
823
863
|
}
|
|
824
864
|
}
|
|
825
865
|
|
|
826
866
|
function readGoogleMetadataFile(file: string): Record<string, GoogleFontMetadata> | undefined {
|
|
827
|
-
const metadata: unknown = JSON.parse(readFileSync(file, 'utf8'))
|
|
828
|
-
return isGoogleMetadata(metadata) ? metadata : undefined
|
|
867
|
+
const metadata: unknown = JSON.parse(readFileSync(file, 'utf8'))
|
|
868
|
+
return isGoogleMetadata(metadata) ? metadata : undefined
|
|
829
869
|
}
|
|
830
870
|
|
|
831
871
|
function isGoogleMetadata(value: unknown): value is Record<string, GoogleFontMetadata> {
|
|
832
|
-
if (!value || typeof value !== 'object') return false
|
|
833
|
-
const inter = (value as Record<string, GoogleFontMetadata>).Inter
|
|
872
|
+
if (!value || typeof value !== 'object') return false
|
|
873
|
+
const inter = (value as Record<string, GoogleFontMetadata>).Inter
|
|
834
874
|
return Boolean(
|
|
835
875
|
inter &&
|
|
836
876
|
Array.isArray(inter.weights) &&
|
|
837
877
|
Array.isArray(inter.styles) &&
|
|
838
878
|
Array.isArray(inter.subsets),
|
|
839
|
-
)
|
|
879
|
+
)
|
|
840
880
|
}
|
|
841
881
|
|
|
842
882
|
function mockedGoogleResponse(url: string) {
|
|
843
|
-
const mockFile = process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES
|
|
844
|
-
if (!mockFile) return undefined
|
|
845
|
-
const mocked = require(mockFile) as Record<string, string
|
|
846
|
-
const value = mocked[url]
|
|
847
|
-
if (value === undefined) throw new Error(`Missing mocked response for URL: ${url}`)
|
|
883
|
+
const mockFile = process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES
|
|
884
|
+
if (!mockFile) return undefined
|
|
885
|
+
const mocked = require(mockFile) as Record<string, string>
|
|
886
|
+
const value = mocked[url]
|
|
887
|
+
if (value === undefined) throw new Error(`Missing mocked response for URL: ${url}`)
|
|
848
888
|
if (typeof value === 'string' && value.startsWith('/') && existsSync(value)) {
|
|
849
|
-
return new Uint8Array(readFileSync(value))
|
|
889
|
+
return new Uint8Array(readFileSync(value))
|
|
850
890
|
}
|
|
851
|
-
return value
|
|
891
|
+
return value
|
|
852
892
|
}
|
|
853
893
|
|
|
854
894
|
function rewriteFontFamily(css: string, originalFamily: string, scopedFamily: string) {
|
|
855
|
-
const familyPattern = escapeRegex(originalFamily)
|
|
895
|
+
const familyPattern = escapeRegex(originalFamily)
|
|
856
896
|
return css.replace(
|
|
857
897
|
new RegExp(`font-family:\\s*(["']?)${familyPattern}\\1`, 'g'),
|
|
858
898
|
`font-family:${singleQuoteFamily(scopedFamily)}`,
|
|
859
|
-
)
|
|
899
|
+
)
|
|
860
900
|
}
|
|
861
901
|
|
|
862
902
|
/** Metrics-adjusted fallback `@font-face` (`'<family> Fallback'`). We omit the
|
|
863
903
|
* ascent/descent/size-adjust metrics (would need fontkit); the local() source
|
|
864
904
|
* never loads, so the browser resolves the real font or the next stack entry. */
|
|
865
905
|
function fallbackFaceCss(family: string) {
|
|
866
|
-
return `@font-face{font-family:${singleQuoteFamily(`${family} Fallback`)};src:local("Arial");}
|
|
906
|
+
return `@font-face{font-family:${singleQuoteFamily(`${family} Fallback`)};src:local("Arial");}`
|
|
867
907
|
}
|
|
868
908
|
|
|
869
909
|
function classRule(className: string, familyStack: string, weight?: string, style?: string) {
|
|
@@ -872,103 +912,98 @@ function classRule(className: string, familyStack: string, weight?: string, styl
|
|
|
872
912
|
weight && !isRangeValue(weight) ? `font-weight:${cssIdentifier(weight)};` : '',
|
|
873
913
|
style && !isRangeValue(style) ? `font-style:${cssIdentifier(style)};` : '',
|
|
874
914
|
'}',
|
|
875
|
-
].join('')
|
|
915
|
+
].join('')
|
|
876
916
|
}
|
|
877
917
|
|
|
878
918
|
function variableRule(className: string, variable: string, familyStack: string) {
|
|
879
|
-
return `.${className}{${variable}:${familyStack};}
|
|
919
|
+
return `.${className}{${variable}:${familyStack};}`
|
|
880
920
|
}
|
|
881
921
|
|
|
882
922
|
function fontPreload(url: string, type: string): MetadataLink {
|
|
883
|
-
return { rel: 'preload', url, as: 'font', type, crossOrigin: '' }
|
|
923
|
+
return { rel: 'preload', url, as: 'font', type, crossOrigin: '' }
|
|
884
924
|
}
|
|
885
925
|
|
|
886
926
|
/** Single `<link rel="preconnect" href="/" crossorigin="">` for the
|
|
887
927
|
* `preload:false` (no preloaded fonts) case. */
|
|
888
928
|
function fontPreconnect(): MetadataLink {
|
|
889
|
-
return { rel: 'preconnect', url: '/', crossOrigin: '' }
|
|
929
|
+
return { rel: 'preconnect', url: '/', crossOrigin: '' }
|
|
890
930
|
}
|
|
891
931
|
|
|
892
932
|
function dedupePreloads(preloads: MetadataLink[]) {
|
|
893
|
-
const seen = new Set<string>()
|
|
933
|
+
const seen = new Set<string>()
|
|
894
934
|
return preloads.filter(preload => {
|
|
895
|
-
if (seen.has(preload.url)) return false
|
|
896
|
-
seen.add(preload.url)
|
|
897
|
-
return true
|
|
898
|
-
})
|
|
935
|
+
if (seen.has(preload.url)) return false
|
|
936
|
+
seen.add(preload.url)
|
|
937
|
+
return true
|
|
938
|
+
})
|
|
899
939
|
}
|
|
900
940
|
|
|
901
941
|
function validateDisplay(value: FontDisplay) {
|
|
902
|
-
if (!allowedDisplayValues.has(value)) throw new Error(`Invalid font display value \`${value}\`.`)
|
|
903
|
-
return value
|
|
942
|
+
if (!allowedDisplayValues.has(value)) throw new Error(`Invalid font display value \`${value}\`.`)
|
|
943
|
+
return value
|
|
904
944
|
}
|
|
905
945
|
|
|
906
946
|
function fontExt(value: string) {
|
|
907
|
-
return /\.(woff2?|eot|ttf|otf)(?:[?#]|$)/.exec(value)?.[1] ?? 'woff2'
|
|
947
|
+
return /\.(woff2?|eot|ttf|otf)(?:[?#]|$)/.exec(value)?.[1] ?? 'woff2'
|
|
908
948
|
}
|
|
909
949
|
|
|
910
950
|
function fontMimeType(ext: string) {
|
|
911
|
-
if (ext === 'woff') return 'font/woff'
|
|
912
|
-
if (ext === 'ttf') return 'font/ttf'
|
|
913
|
-
if (ext === 'otf') return 'font/otf'
|
|
914
|
-
if (ext === 'eot') return 'application/vnd.ms-fontobject'
|
|
915
|
-
return 'font/woff2'
|
|
951
|
+
if (ext === 'woff') return 'font/woff'
|
|
952
|
+
if (ext === 'ttf') return 'font/ttf'
|
|
953
|
+
if (ext === 'otf') return 'font/otf'
|
|
954
|
+
if (ext === 'eot') return 'application/vnd.ms-fontobject'
|
|
955
|
+
return 'font/woff2'
|
|
916
956
|
}
|
|
917
957
|
|
|
918
958
|
function fontFormat(ext: string) {
|
|
919
|
-
if (ext === 'ttf') return 'truetype'
|
|
920
|
-
if (ext === 'otf') return 'opentype'
|
|
921
|
-
return ext
|
|
959
|
+
if (ext === 'ttf') return 'truetype'
|
|
960
|
+
if (ext === 'otf') return 'opentype'
|
|
961
|
+
return ext
|
|
922
962
|
}
|
|
923
963
|
|
|
924
964
|
function singleCssValue(values: string[]) {
|
|
925
|
-
return values.length === 1 ? values[0] : undefined
|
|
965
|
+
return values.length === 1 ? values[0] : undefined
|
|
926
966
|
}
|
|
927
967
|
|
|
928
968
|
function isRangeValue(value: string) {
|
|
929
|
-
return value.trim().includes(' ')
|
|
969
|
+
return value.trim().includes(' ')
|
|
930
970
|
}
|
|
931
971
|
|
|
932
972
|
function arrayValue<T>(value: T | T[]) {
|
|
933
|
-
return Array.isArray(value) ? value : [value]
|
|
973
|
+
return Array.isArray(value) ? value : [value]
|
|
934
974
|
}
|
|
935
975
|
|
|
936
976
|
function uniqueArray<T>(items: T[]) {
|
|
937
|
-
return [...new Set(items)]
|
|
977
|
+
return [...new Set(items)]
|
|
938
978
|
}
|
|
939
979
|
|
|
940
|
-
|
|
941
980
|
function customProperty(value: string) {
|
|
942
|
-
if (!/^--[-_a-zA-Z0-9]+$/.test(value)) throw new Error(`Invalid font variable name: ${value}`)
|
|
943
|
-
return value
|
|
981
|
+
if (!/^--[-_a-zA-Z0-9]+$/.test(value)) throw new Error(`Invalid font variable name: ${value}`)
|
|
982
|
+
return value
|
|
944
983
|
}
|
|
945
984
|
|
|
946
985
|
function cssIdentifier(value: string) {
|
|
947
|
-
if (!/^[-_a-zA-Z0-9. ]+$/.test(value)) throw new Error(`Invalid font option value: ${value}`)
|
|
948
|
-
return value
|
|
986
|
+
if (!/^[-_a-zA-Z0-9. ]+$/.test(value)) throw new Error(`Invalid font option value: ${value}`)
|
|
987
|
+
return value
|
|
949
988
|
}
|
|
950
989
|
|
|
951
990
|
function cssProperty(value: string) {
|
|
952
|
-
if (!/^[-_a-zA-Z0-9]+$/.test(value)) throw new Error(`Invalid CSS property: ${value}`)
|
|
953
|
-
return value
|
|
991
|
+
if (!/^[-_a-zA-Z0-9]+$/.test(value)) throw new Error(`Invalid CSS property: ${value}`)
|
|
992
|
+
return value
|
|
954
993
|
}
|
|
955
994
|
|
|
956
995
|
function cssDeclarationValue(value: string) {
|
|
957
|
-
if (/[;{}]/.test(value)) throw new Error(`Invalid CSS declaration value: ${value}`)
|
|
958
|
-
return value
|
|
996
|
+
if (/[;{}]/.test(value)) throw new Error(`Invalid CSS declaration value: ${value}`)
|
|
997
|
+
return value
|
|
959
998
|
}
|
|
960
999
|
|
|
961
1000
|
function sortFontVariantValues(a: string, b: string) {
|
|
962
1001
|
if (a.includes(',') && b.includes(',')) {
|
|
963
|
-
const [aPrefix, aSuffix] = a.split(',', 2)
|
|
964
|
-
const [bPrefix, bSuffix] = b.split(',', 2)
|
|
1002
|
+
const [aPrefix, aSuffix] = a.split(',', 2)
|
|
1003
|
+
const [bPrefix, bSuffix] = b.split(',', 2)
|
|
965
1004
|
return aPrefix === bPrefix
|
|
966
1005
|
? parseInt(aSuffix ?? '0', 10) - parseInt(bSuffix ?? '0', 10)
|
|
967
|
-
: parseInt(aPrefix ?? '0', 10) - parseInt(bPrefix ?? '0', 10)
|
|
1006
|
+
: parseInt(aPrefix ?? '0', 10) - parseInt(bPrefix ?? '0', 10)
|
|
968
1007
|
}
|
|
969
|
-
return parseInt(a, 10) - parseInt(b, 10)
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
function escapeRegex(value: string) {
|
|
973
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1008
|
+
return parseInt(a, 10) - parseInt(b, 10)
|
|
974
1009
|
}
|