@wular/pnext 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side server-action runtime (Next compat) - the INITIAL half. Installed once per page by the
|
|
3
|
+
* client entry as `window.__PNEXT_ACTIONS__`; the per-module generated stubs and the form-submit
|
|
4
|
+
* interception below both dispatch through it.
|
|
5
|
+
*
|
|
6
|
+
* Only what must exist before the user can interact lives here: the DOM listeners, the
|
|
7
|
+
* `<form action={fn}>` vnode hook, progressive-form enhancement, and synchronous island-prop revival.
|
|
8
|
+
* Everything that can only run once an action has fired - argument encoding, the POST wire,
|
|
9
|
+
* redirect/refresh/error semantics, action-return revival, the error overlay body - lives in
|
|
10
|
+
* action-dispatch.ts and is reached through import(), so it rides a deferred chunk whose fetch
|
|
11
|
+
* overlaps the action's own round trip.
|
|
12
|
+
*/
|
|
13
|
+
import { options as preactOptions, type ComponentType, type VNode } from 'preact';
|
|
14
|
+
import { __pnextPushFormStatus } from '../react/dom';
|
|
15
|
+
import {
|
|
16
|
+
ACTION_ID_FIELD,
|
|
17
|
+
FORM_STATE_FIELD,
|
|
18
|
+
SUBMIT_ACTION_ID_FIELD,
|
|
19
|
+
actionReturnClientReferences,
|
|
20
|
+
isActionError,
|
|
21
|
+
reviveBinaryMarker,
|
|
22
|
+
type ActionFunction,
|
|
23
|
+
} from './action-shared';
|
|
24
|
+
|
|
25
|
+
// Out-of-band carrier for a plain server-action form's id once the client
|
|
26
|
+
// runtime is up. React strips the progressive `$ACTION_ID` hidden inputs from a
|
|
27
|
+
// form's DOM on hydration; a SERVER-component form (not preact-managed) keeps
|
|
28
|
+
// its SSR hidden input forever, so after a soft-nav commit the committed
|
|
29
|
+
// `form.innerHTML` leaks `<input name="$pnext_action_id" ...>`. We move the id
|
|
30
|
+
// to this attribute and drop the hidden input (see stripProgressiveActionIds),
|
|
31
|
+
// keeping submission working via the fallbacks that read it.
|
|
32
|
+
const ACTION_ID_ATTR = 'data-pnext-action-id';
|
|
33
|
+
const PROP_ACTION_MARKER = '$$pnextAction';
|
|
34
|
+
const ACTION_CLICK_ID_ATTR = 'data-pnext-action-click';
|
|
35
|
+
const PROP_ERROR_RESET_MARKER = '$$pnextErrorReset';
|
|
36
|
+
|
|
37
|
+
export { isActionError };
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// dispatch entry point (the wire itself is deferred)
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
// Deferring the dispatch chunk is the point of this split, but deferring it to the CLICK puts its
|
|
44
|
+
// `import()` between the click and the POST - a task hop, not a microtask, so even a
|
|
45
|
+
// fetched-and-evaluated chunk lands behind the frame and a driver reading the DOM the instant
|
|
46
|
+
// `click()` resolves sees no request. Warm on `pnext:hydrated` and remember the entry point, so the
|
|
47
|
+
// warm call is a microtask that drains in the click's own task.
|
|
48
|
+
const dispatchRuntime = () => import('./action-dispatch');
|
|
49
|
+
|
|
50
|
+
let warmDispatch: ((id: string, args: unknown[]) => Promise<unknown>) | undefined;
|
|
51
|
+
|
|
52
|
+
const loadDispatch = () => dispatchRuntime().then(module => (warmDispatch = module.callAction));
|
|
53
|
+
|
|
54
|
+
function callAction(id: string, args: unknown[]): Promise<unknown> {
|
|
55
|
+
return warmDispatch ? warmDispatch(id, args) : loadDispatch().then(call => call(id, args));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function registerClientReference(
|
|
59
|
+
id: string,
|
|
60
|
+
component: ComponentType<Record<string, unknown>>,
|
|
61
|
+
): void {
|
|
62
|
+
actionReturnClientReferences.set(id, component);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// props revival (server-action references serialized into island props)
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
function reviveProps<T>(value: T): T {
|
|
70
|
+
return reviveValue(value) as T;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function reviveValue(value: unknown): unknown {
|
|
74
|
+
if (value === null || typeof value !== 'object') return value;
|
|
75
|
+
if (Array.isArray(value)) return value.map(reviveValue);
|
|
76
|
+
const record = value as Record<string, unknown>;
|
|
77
|
+
const error = reviveErrorMarker(record);
|
|
78
|
+
if (error) return error;
|
|
79
|
+
if (record[PROP_ERROR_RESET_MARKER] === true) return refreshCurrentRoute;
|
|
80
|
+
if (typeof record[PROP_ACTION_MARKER] === 'string') {
|
|
81
|
+
return createAction(record[PROP_ACTION_MARKER]);
|
|
82
|
+
}
|
|
83
|
+
const binary = reviveBinaryMarker(record);
|
|
84
|
+
if (binary !== undefined) return binary;
|
|
85
|
+
const revived: Record<string, unknown> = {};
|
|
86
|
+
for (const [key, item] of Object.entries(record)) revived[key] = reviveValue(item);
|
|
87
|
+
return revived;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function reviveErrorMarker(record: Record<string, unknown>): Error | undefined {
|
|
91
|
+
const marker = record.$$pnextError;
|
|
92
|
+
if (marker === null || typeof marker !== 'object') return undefined;
|
|
93
|
+
const data = marker as Record<string, unknown>;
|
|
94
|
+
const error = new Error(typeof data.message === 'string' ? data.message : '');
|
|
95
|
+
if (typeof data.name === 'string') error.name = data.name;
|
|
96
|
+
if (typeof data.digest === 'string') (error as Error & { digest?: string }).digest = data.digest;
|
|
97
|
+
return error;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function refreshCurrentRoute(): void {
|
|
101
|
+
location.reload();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function createAction(id: string): ActionFunction {
|
|
105
|
+
const action: ActionFunction = async (...args: unknown[]) => callAction(id, args);
|
|
106
|
+
action.$$pnextActionId = id;
|
|
107
|
+
return action;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// <form action={fn}> interception (preact has no function-action support)
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
interface ActionDomNode extends Element {
|
|
115
|
+
__pnextFormAction?: (formData: FormData) => unknown;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// pre-hydration submit replay
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A submission the inline early-capture script (compat/actions/early-submit.ts)
|
|
124
|
+
* queued before this runtime loaded: the FormData snapshot taken at submit
|
|
125
|
+
* time (including the submitter's pair) parked on the form node.
|
|
126
|
+
*/
|
|
127
|
+
interface QueuedSubmission {
|
|
128
|
+
data: FormData;
|
|
129
|
+
submitter: ActionDomNode | null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface QueuedForm extends HTMLFormElement {
|
|
133
|
+
__pnextQueuedSubmit?: QueuedSubmission;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function takeQueuedSubmission(form: HTMLFormElement): QueuedSubmission | undefined {
|
|
137
|
+
const queued = (form as QueuedForm).__pnextQueuedSubmit;
|
|
138
|
+
if (queued) delete (form as QueuedForm).__pnextQueuedSubmit;
|
|
139
|
+
return queued;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Dispatch a captured submission with form-status + post-action reset (the
|
|
143
|
+
* same tail the live submit listener runs). */
|
|
144
|
+
function runFormSubmission(
|
|
145
|
+
form: HTMLFormElement,
|
|
146
|
+
formData: FormData,
|
|
147
|
+
dispatch: (formData: FormData) => unknown,
|
|
148
|
+
): void {
|
|
149
|
+
const pop = pushFormStatus(form, formData);
|
|
150
|
+
void Promise.resolve(dispatch(formData))
|
|
151
|
+
.then(() => {
|
|
152
|
+
// React resets uncontrolled fields after a completed form action.
|
|
153
|
+
form.reset();
|
|
154
|
+
})
|
|
155
|
+
.catch(error => {
|
|
156
|
+
if (!throwActionErrorAt(form, error)) throw error;
|
|
157
|
+
})
|
|
158
|
+
.finally(pop);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function throwActionErrorAt(node: Element, error: unknown): boolean {
|
|
162
|
+
const root = node.closest('pnext-client');
|
|
163
|
+
const rootVNode = (root as unknown as { __k?: VNode } | null)?.__k;
|
|
164
|
+
const vnode = rootVNode ? findVNodeForElement(rootVNode, node) : undefined;
|
|
165
|
+
const handler = (preactOptions as typeof preactOptions & {
|
|
166
|
+
__e?: (error: unknown, vnode: VNode, oldVNode: VNode) => void;
|
|
167
|
+
}).__e;
|
|
168
|
+
if (!vnode || !handler) return false;
|
|
169
|
+
handler(error, vnode, vnode);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function findVNodeForElement(vnode: VNode, element: Element): VNode | undefined {
|
|
174
|
+
if ((vnode as VNode & { __e?: unknown }).__e === element) return vnode;
|
|
175
|
+
const children = (vnode as VNode & { __k?: (VNode | null)[] }).__k;
|
|
176
|
+
if (!children) return undefined;
|
|
177
|
+
for (const child of children) {
|
|
178
|
+
if (!child) continue;
|
|
179
|
+
const found = findVNodeForElement(child, element);
|
|
180
|
+
if (found) return found;
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Replay a pre-hydration submission the moment the form's function action
|
|
186
|
+
* hydrates (called from the stashed ref below). */
|
|
187
|
+
function replayQueuedSubmission(form: HTMLFormElement, fn: (formData: FormData) => unknown): void {
|
|
188
|
+
const queued = takeQueuedSubmission(form);
|
|
189
|
+
if (queued) runFormSubmission(form, queued.data, fn);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Take over from the inline early-capture script: stop capturing (the live submit listener is
|
|
194
|
+
* installed now) and, after hydration has had a chance to claim queued forms via their refs, dispatch
|
|
195
|
+
* whatever remains - server-only progressive forms via the id fields, and anything unclaimed falls
|
|
196
|
+
* back to the native submit the capture prevented.
|
|
197
|
+
*/
|
|
198
|
+
function adoptEarlySubmissions(): void {
|
|
199
|
+
const holder = window as typeof window & {
|
|
200
|
+
__pnextEarlySubmits?: HTMLFormElement[];
|
|
201
|
+
__pnextRemoveEarlySubmit?: () => void;
|
|
202
|
+
};
|
|
203
|
+
holder.__pnextRemoveEarlySubmit?.();
|
|
204
|
+
const forms = holder.__pnextEarlySubmits;
|
|
205
|
+
if (!forms?.length) return;
|
|
206
|
+
const flush = () => {
|
|
207
|
+
for (const form of forms.splice(0)) {
|
|
208
|
+
const queued = takeQueuedSubmission(form);
|
|
209
|
+
if (!queued) continue; // hydration replayed it already
|
|
210
|
+
const stashed =
|
|
211
|
+
queued.submitter?.__pnextFormAction ??
|
|
212
|
+
(form as unknown as ActionDomNode).__pnextFormAction;
|
|
213
|
+
if (stashed) {
|
|
214
|
+
runFormSubmission(form, queued.data, stashed);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const submitterId =
|
|
218
|
+
queued.submitter?.getAttribute('name') === SUBMIT_ACTION_ID_FIELD
|
|
219
|
+
? queued.submitter.getAttribute('value')
|
|
220
|
+
: null;
|
|
221
|
+
const id =
|
|
222
|
+
submitterId ??
|
|
223
|
+
form.querySelector<HTMLInputElement>(`input[name="${cssEscape(ACTION_ID_FIELD)}"]`)
|
|
224
|
+
?.value ??
|
|
225
|
+
form.getAttribute(ACTION_ID_ATTR) ??
|
|
226
|
+
undefined;
|
|
227
|
+
const hasFormState = Boolean(
|
|
228
|
+
form.querySelector(`input[name="${cssEscape(FORM_STATE_FIELD)}"]`),
|
|
229
|
+
);
|
|
230
|
+
if (id && !hasFormState) {
|
|
231
|
+
// Server-only progressive form (no live state to update): dispatch the
|
|
232
|
+
// RPC directly, like the live listener's unhydrated-form branch.
|
|
233
|
+
runFormSubmission(form, queued.data, formData => callAction(id, [formData]));
|
|
234
|
+
} else if (form.isConnected) {
|
|
235
|
+
// A useActionState form whose island never hydrated (or an unknown
|
|
236
|
+
// shape): give it the native progressive POST the capture prevented.
|
|
237
|
+
form.submit();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
// Islands hydrate asynchronously after this runtime installs; give their
|
|
242
|
+
// refs (replayQueuedSubmission) first claim before falling back.
|
|
243
|
+
setTimeout(flush, 300);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
type FunctionActionProps = Record<string, unknown> & {
|
|
247
|
+
action?: unknown;
|
|
248
|
+
formAction?: unknown;
|
|
249
|
+
ref?: unknown;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
function installVNodeHook() {
|
|
253
|
+
const previous = preactOptions.vnode?.bind(preactOptions);
|
|
254
|
+
preactOptions.vnode = (vnode: VNode) => {
|
|
255
|
+
previous?.(vnode);
|
|
256
|
+
if (typeof vnode.type !== 'string') return;
|
|
257
|
+
const props = vnode.props as FunctionActionProps;
|
|
258
|
+
if (vnode.type === 'form' && typeof props.action === 'function') {
|
|
259
|
+
stashFunctionAction(vnode, props, 'action');
|
|
260
|
+
}
|
|
261
|
+
if (
|
|
262
|
+
(vnode.type === 'button' || vnode.type === 'input') &&
|
|
263
|
+
typeof props.formAction === 'function'
|
|
264
|
+
) {
|
|
265
|
+
stashFunctionAction(vnode, props, 'formAction');
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Replace a function action/formAction prop with a ref that stashes the function on the DOM node for
|
|
272
|
+
* the document-level submit listener. The attribute itself is dropped (a function is not a valid URL)
|
|
273
|
+
* - the listener preventDefaults, so the browser never uses the missing attribute.
|
|
274
|
+
*
|
|
275
|
+
* The ref must go on vnode.ref: preact extracts `ref` from props at createElement time, so adding
|
|
276
|
+
* props.ref inside options.vnode is ignored for DOM elements.
|
|
277
|
+
*/
|
|
278
|
+
function stashFunctionAction(
|
|
279
|
+
vnode: VNode,
|
|
280
|
+
props: FunctionActionProps,
|
|
281
|
+
key: 'action' | 'formAction',
|
|
282
|
+
) {
|
|
283
|
+
const fn = props[key] as (formData: FormData) => unknown;
|
|
284
|
+
delete props[key];
|
|
285
|
+
const existingRef = (vnode as { ref?: unknown }).ref;
|
|
286
|
+
(vnode as { ref?: unknown }).ref = (node: ActionDomNode | null) => {
|
|
287
|
+
if (node) {
|
|
288
|
+
node.__pnextFormAction = fn;
|
|
289
|
+
if (key === 'action' && node instanceof HTMLFormElement) {
|
|
290
|
+
enhanceProgressiveForm(node, fn);
|
|
291
|
+
replayQueuedSubmission(node, fn);
|
|
292
|
+
}
|
|
293
|
+
if (key === 'formAction' && node instanceof HTMLElement) {
|
|
294
|
+
const form = (node as { form?: HTMLFormElement | null }).form ?? null;
|
|
295
|
+
const queued = form && (form as QueuedForm).__pnextQueuedSubmit;
|
|
296
|
+
if (form && queued?.submitter === node) {
|
|
297
|
+
delete (form as QueuedForm).__pnextQueuedSubmit;
|
|
298
|
+
runFormSubmission(form, queued.data, fn);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
applyRef(existingRef, node);
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
interface FormStateMeta {
|
|
307
|
+
action?: { $$pnextActionId?: string };
|
|
308
|
+
state?: unknown;
|
|
309
|
+
permalink?: string;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* useActionState forms rendered on the client: mirror the server's progressive enhancement onto the
|
|
314
|
+
* live DOM (method/enctype/hidden id + state fields) so a NATIVE submit - form.submit() fires no
|
|
315
|
+
* submit event our listener could intercept - still posts the multipart action wire. Refreshed on
|
|
316
|
+
* every render, keeping the state field current.
|
|
317
|
+
*/
|
|
318
|
+
function enhanceProgressiveForm(form: HTMLFormElement, fn: unknown) {
|
|
319
|
+
const meta = (fn as { $$pnextFormState?: FormStateMeta }).$$pnextFormState;
|
|
320
|
+
if (!meta) return;
|
|
321
|
+
const id = meta.action?.$$pnextActionId;
|
|
322
|
+
if (!id) return;
|
|
323
|
+
form.setAttribute('method', 'post');
|
|
324
|
+
form.setAttribute('enctype', 'multipart/form-data');
|
|
325
|
+
if (meta.permalink) form.setAttribute('action', meta.permalink);
|
|
326
|
+
setHiddenField(form, ACTION_ID_FIELD, id);
|
|
327
|
+
// Non-JSON-serializable state (an action can return React elements, Maps,
|
|
328
|
+
// circular structures, ...) cannot ride the progressive wire; fall back to
|
|
329
|
+
// null rather than throwing inside the render's ref (which would take the
|
|
330
|
+
// whole island down with it).
|
|
331
|
+
let serializedState = 'null';
|
|
332
|
+
try {
|
|
333
|
+
serializedState = JSON.stringify(meta.state ?? null) ?? 'null';
|
|
334
|
+
} catch {
|
|
335
|
+
serializedState = 'null';
|
|
336
|
+
}
|
|
337
|
+
setHiddenField(form, FORM_STATE_FIELD, serializedState);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function setHiddenField(form: HTMLFormElement, name: string, value: string) {
|
|
341
|
+
let input = form.querySelector<HTMLInputElement>(
|
|
342
|
+
`input[type="hidden"][name="${cssEscape(name)}"]`,
|
|
343
|
+
);
|
|
344
|
+
if (!input) {
|
|
345
|
+
input = document.createElement('input');
|
|
346
|
+
input.type = 'hidden';
|
|
347
|
+
input.name = name;
|
|
348
|
+
form.appendChild(input);
|
|
349
|
+
}
|
|
350
|
+
input.value = value;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function applyRef(ref: unknown, node: Element | null) {
|
|
354
|
+
if (typeof ref === 'function') (ref as (node: Element | null) => void)(node);
|
|
355
|
+
else if (ref && typeof ref === 'object') (ref as { current: Element | null }).current = node;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function installSubmitListener() {
|
|
359
|
+
document.addEventListener(
|
|
360
|
+
'submit',
|
|
361
|
+
event => {
|
|
362
|
+
const form = event.target as HTMLFormElement | null;
|
|
363
|
+
if (!form || !(form instanceof HTMLFormElement)) return;
|
|
364
|
+
const submitter = event.submitter as ActionDomNode | null;
|
|
365
|
+
|
|
366
|
+
const fn =
|
|
367
|
+
submitter?.__pnextFormAction || (form as unknown as ActionDomNode).__pnextFormAction;
|
|
368
|
+
|
|
369
|
+
let dispatch: ((formData: FormData) => unknown) | undefined = fn;
|
|
370
|
+
if (!dispatch) {
|
|
371
|
+
// A useActionState form that never hydrated (rendered by a server
|
|
372
|
+
// component or an unhydrated client layout) has no live state to
|
|
373
|
+
// update — let the native progressive POST run so the server
|
|
374
|
+
// re-renders the page with the new form state.
|
|
375
|
+
if (form.querySelector(`input[name="${cssEscape(FORM_STATE_FIELD)}"]`)) return;
|
|
376
|
+
// Server-rendered progressive form: the id travels in a hidden field
|
|
377
|
+
// (or the submitter button's own name/value pair under its distinct
|
|
378
|
+
// field name, which overrides the form-level default).
|
|
379
|
+
const submitterId =
|
|
380
|
+
submitter?.getAttribute('name') === SUBMIT_ACTION_ID_FIELD
|
|
381
|
+
? submitter.getAttribute('value')
|
|
382
|
+
: null;
|
|
383
|
+
const field = form.querySelector<HTMLInputElement>(
|
|
384
|
+
`input[name="${cssEscape(ACTION_ID_FIELD)}"]`,
|
|
385
|
+
);
|
|
386
|
+
// The hidden id may have been moved out-of-band (stripProgressiveActionIds).
|
|
387
|
+
const id = submitterId ?? field?.value ?? form.getAttribute(ACTION_ID_ATTR);
|
|
388
|
+
if (!id) return; // not an action form; let the browser submit it
|
|
389
|
+
dispatch = formData => callAction(id, [formData]);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
event.preventDefault();
|
|
393
|
+
const formData = new FormData(form, (submitter ?? undefined) as HTMLElement | undefined);
|
|
394
|
+
runFormSubmission(form, formData, dispatch);
|
|
395
|
+
},
|
|
396
|
+
// Bubble phase, so app-level onSubmit handlers that preventDefault
|
|
397
|
+
// themselves run first and can opt out of the default action behavior.
|
|
398
|
+
false,
|
|
399
|
+
);
|
|
400
|
+
adoptEarlySubmissions();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* React parity: a hydrated `<form action={serverAction}>` carries no visible
|
|
405
|
+
* `$ACTION_ID` hidden inputs. A preact ISLAND form already loses its SSR hidden
|
|
406
|
+
* input on hydration (the client vnode never renders one), but a SERVER-COMPONENT
|
|
407
|
+
* form is raw HTML preact never manages, so its progressive-enhancement hidden id
|
|
408
|
+
* survives every soft-nav commit and leaks into `form.innerHTML`. Once the client
|
|
409
|
+
* runtime is up (submissions run through it, not the native POST), move each such
|
|
410
|
+
* form's id out-of-band onto `data-pnext-action-id` and drop the hidden input.
|
|
411
|
+
*
|
|
412
|
+
* useActionState forms (`$pnext_form_state` present) are left untouched: a
|
|
413
|
+
* server-rendered one still relies on its hidden id for the NATIVE progressive
|
|
414
|
+
* POST fallback (installSubmitListener bails to native for form-state forms).
|
|
415
|
+
*/
|
|
416
|
+
function stripProgressiveActionIds() {
|
|
417
|
+
if (typeof document === 'undefined') return;
|
|
418
|
+
const inputs = document.querySelectorAll<HTMLInputElement>(
|
|
419
|
+
`input[type="hidden"][name="${cssEscape(ACTION_ID_FIELD)}"]`,
|
|
420
|
+
);
|
|
421
|
+
for (const input of inputs) {
|
|
422
|
+
const form = input.form;
|
|
423
|
+
if (!form) continue;
|
|
424
|
+
if (form.querySelector(`input[name="${cssEscape(FORM_STATE_FIELD)}"]`)) continue;
|
|
425
|
+
if (input.value && !form.hasAttribute(ACTION_ID_ATTR)) {
|
|
426
|
+
form.setAttribute(ACTION_ID_ATTR, input.value);
|
|
427
|
+
}
|
|
428
|
+
input.remove();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function installClickActionListener() {
|
|
433
|
+
document.addEventListener('click', event => {
|
|
434
|
+
const target =
|
|
435
|
+
event.target instanceof Element
|
|
436
|
+
? event.target.closest<HTMLElement>(`[${ACTION_CLICK_ID_ATTR}]`)
|
|
437
|
+
: null;
|
|
438
|
+
if (!target || (target instanceof HTMLButtonElement && target.disabled)) return;
|
|
439
|
+
const id = target.getAttribute(ACTION_CLICK_ID_ATTR);
|
|
440
|
+
if (!id) return;
|
|
441
|
+
event.preventDefault();
|
|
442
|
+
void callAction(id, []);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function cssEscape(value: string) {
|
|
447
|
+
const escape = (globalThis as { CSS?: { escape?: (v: string) => string } }).CSS?.escape;
|
|
448
|
+
return escape ? escape(value) : value.replace(/[^a-zA-Z0-9_-]/g, match => `\\${match}`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Form-status feed for useFormStatus (react-dom compat).
|
|
452
|
+
function pushFormStatus(form: HTMLFormElement, data: FormData): () => void {
|
|
453
|
+
try {
|
|
454
|
+
return __pnextPushFormStatus({
|
|
455
|
+
pending: true,
|
|
456
|
+
data,
|
|
457
|
+
method: (form.getAttribute('method') || 'get').toLowerCase(),
|
|
458
|
+
action: form.getAttribute('action') ?? '',
|
|
459
|
+
});
|
|
460
|
+
} catch {
|
|
461
|
+
return () => undefined;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ---------------------------------------------------------------------------
|
|
466
|
+
// error boundary overlay (route error.js support for action failures)
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
|
|
469
|
+
// React routes action errors (uncatchable transition/dispatch failures) to the nearest error boundary.
|
|
470
|
+
// pnext's server-rendered pages have no client-side boundary components, so the compat entry installs
|
|
471
|
+
// the route's error.js as an overlay. Only the listeners are initial - the overlay body, which needs
|
|
472
|
+
// preact's renderer and the refresh path, rides the deferred dispatch chunk.
|
|
473
|
+
type ErrorComponentType = ComponentType<{ error: Error; reset: () => void }>;
|
|
474
|
+
|
|
475
|
+
let overlayInstalled = false;
|
|
476
|
+
|
|
477
|
+
export function installActionErrorOverlay(Component: ErrorComponentType) {
|
|
478
|
+
if ((!process.browser && typeof window === 'undefined') || overlayInstalled) return;
|
|
479
|
+
overlayInstalled = true;
|
|
480
|
+
|
|
481
|
+
// Chained, not concurrent: two errors arriving before the chunk lands must
|
|
482
|
+
// still see each other's effect on the overlay's "already showing" guard.
|
|
483
|
+
let chain: Promise<unknown> = Promise.resolve();
|
|
484
|
+
const show = (reason: unknown) => {
|
|
485
|
+
chain = chain.then(async () => {
|
|
486
|
+
const { showActionErrorOverlay } = await dispatchRuntime();
|
|
487
|
+
showActionErrorOverlay(Component, reason);
|
|
488
|
+
});
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
window.addEventListener('unhandledrejection', event => {
|
|
492
|
+
if (isActionError(event.reason)) show(event.reason);
|
|
493
|
+
});
|
|
494
|
+
// useActionState rethrows action errors during render; without a class
|
|
495
|
+
// error boundary in the tree the uncaught render error lands here.
|
|
496
|
+
window.addEventListener('error', event => {
|
|
497
|
+
if (isActionError(event.error)) show(event.error);
|
|
498
|
+
});
|
|
499
|
+
window.addEventListener('pnext:action-error', event => {
|
|
500
|
+
show((event as CustomEvent).detail);
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
// install
|
|
506
|
+
// ---------------------------------------------------------------------------
|
|
507
|
+
|
|
508
|
+
export function installActionRuntime() {
|
|
509
|
+
if (!process.browser && typeof window === 'undefined') return;
|
|
510
|
+
if (!window.__PNEXT_ACTIONS__) {
|
|
511
|
+
window.__PNEXT_ACTIONS__ = {
|
|
512
|
+
call: callAction,
|
|
513
|
+
createAction,
|
|
514
|
+
registerClientReference,
|
|
515
|
+
reviveProps,
|
|
516
|
+
};
|
|
517
|
+
installSubmitListener();
|
|
518
|
+
installClickActionListener();
|
|
519
|
+
// Every mountRoute (initial hydration + each soft-nav commit) fires
|
|
520
|
+
// `pnext:hydrated`; strip leaked progressive action-id inputs from the
|
|
521
|
+
// freshly committed DOM so a hydrated form matches React (no `$ACTION_ID`).
|
|
522
|
+
// Hydration is also where the dispatch chunk gets warmed (see loadDispatch):
|
|
523
|
+
// past first paint, before any click, and `import()` dedups the repeats.
|
|
524
|
+
window.addEventListener('pnext:hydrated', stripProgressiveActionIds);
|
|
525
|
+
window.addEventListener('pnext:hydrated', loadDispatch);
|
|
526
|
+
stripProgressiveActionIds();
|
|
527
|
+
}
|
|
528
|
+
installVNodeHook();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export { createAction, callAction, registerClientReference, reviveProps };
|