@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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive useActionState support, server side. After a no-JS form
|
|
3
|
+
* submission runs `action(state, formData)`, the page re-renders with the
|
|
4
|
+
* result as the form's state:
|
|
5
|
+
*
|
|
6
|
+
* - the consumed-once global override (`__PNEXT_ACTION_STATE__`) feeds the
|
|
7
|
+
* server render's useActionState initial value, and
|
|
8
|
+
* - an inline script mirrors the same value to the browser so hydration
|
|
9
|
+
* adopts the updated state instead of the original initial state.
|
|
10
|
+
*/
|
|
11
|
+
export async function renderWithFormStateOverride(
|
|
12
|
+
state: unknown,
|
|
13
|
+
actionId: string,
|
|
14
|
+
render: () => Promise<Response>,
|
|
15
|
+
): Promise<Response | null> {
|
|
16
|
+
const initial = await render();
|
|
17
|
+
if (initial.status !== 200) return null;
|
|
18
|
+
const contentType = initial.headers.get('content-type') ?? '';
|
|
19
|
+
if (!contentType.includes('text/html')) return null;
|
|
20
|
+
const skip = formStateCallsBefore(await initial.text(), actionId);
|
|
21
|
+
if (skip === undefined) return null;
|
|
22
|
+
|
|
23
|
+
const holder = globalThis as { __PNEXT_ACTION_STATE__?: unknown };
|
|
24
|
+
holder.__PNEXT_ACTION_STATE__ = { value: state, skip };
|
|
25
|
+
try {
|
|
26
|
+
const response = await render();
|
|
27
|
+
if (response.status !== 200) return null;
|
|
28
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
29
|
+
if (!contentType.includes('text/html')) return null;
|
|
30
|
+
const html = await response.text();
|
|
31
|
+
const script = `<script>window.__PNEXT_ACTION_STATE__ = ${inlineJson({ value: state, skip })};</script>`;
|
|
32
|
+
const output = html.includes('</head>')
|
|
33
|
+
? html.replace('</head>', `${script}</head>`)
|
|
34
|
+
: script + html;
|
|
35
|
+
const headers = new Headers(response.headers);
|
|
36
|
+
headers.delete('content-length');
|
|
37
|
+
headers.delete('content-encoding');
|
|
38
|
+
return new Response(output, { status: 200, headers });
|
|
39
|
+
} finally {
|
|
40
|
+
delete holder.__PNEXT_ACTION_STATE__;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function formStateCallsBefore(html: string, actionId: string): number | undefined {
|
|
45
|
+
const forms = html.match(/<form\b[\s\S]*?<\/form>/g) ?? [];
|
|
46
|
+
let count = 0;
|
|
47
|
+
for (const form of forms) {
|
|
48
|
+
if (!form.includes('name="$pnext_form_state"')) continue;
|
|
49
|
+
const id = /name="\$pnext_action_id" value="([^"]+)"/.exec(form)?.[1];
|
|
50
|
+
if (id === actionId) return count;
|
|
51
|
+
count++;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function inlineJson(value: unknown): string {
|
|
57
|
+
return JSON.stringify(value ?? null).replace(/[<>&\u2028\u2029]/g, char => {
|
|
58
|
+
switch (char) {
|
|
59
|
+
case '<':
|
|
60
|
+
return '\\u003c';
|
|
61
|
+
case '>':
|
|
62
|
+
return '\\u003e';
|
|
63
|
+
case '&':
|
|
64
|
+
return '\\u0026';
|
|
65
|
+
case '\u2028':
|
|
66
|
+
return '\\u2028';
|
|
67
|
+
case '\u2029':
|
|
68
|
+
return '\\u2029';
|
|
69
|
+
default:
|
|
70
|
+
return char;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { inlineUseServerFunctions, moduleLevelUseServer } from './detect';
|
|
2
|
+
import { anonymousInlineActionSpans } from './rewrite';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hoist anonymous inline server actions that capture nothing to module-scope consts, which
|
|
6
|
+
* rewriteInlineActionTags then gives stable `if:` ids. That lets the action endpoint resolve them by
|
|
7
|
+
* module import alone - the per-render `i:` instance path re-renders the whole route on the first
|
|
8
|
+
* dispatch against a prebuilt page.
|
|
9
|
+
*
|
|
10
|
+
* No JS parser here, so the capture analysis FAILS CLOSED: every referenced identifier must resolve to
|
|
11
|
+
* a module-scope binding, a known global, or the function's own declarations; anything unclassifiable
|
|
12
|
+
* (`this`, `arguments`, destructured params, JSX) leaves the action on the per-render path.
|
|
13
|
+
*/
|
|
14
|
+
export function hoistNonCapturingInlineActions(source: string): string {
|
|
15
|
+
if (!source.includes('use server')) return source;
|
|
16
|
+
if (moduleLevelUseServer(source)) return source;
|
|
17
|
+
// Some pipelines transform a module twice (a bundled external is rewritten on
|
|
18
|
+
// load and again on its esbuild output). The pass must be idempotent: a second
|
|
19
|
+
// hoist would re-emit declarations under names the first pass already used.
|
|
20
|
+
if (source.includes(HOIST_PREFIX)) return source;
|
|
21
|
+
|
|
22
|
+
const spans = anonymousInlineActionSpans(source);
|
|
23
|
+
if (spans.length === 0) return source;
|
|
24
|
+
|
|
25
|
+
// The span scan is text-based, so `'use server'` inside a STRING (a codegen
|
|
26
|
+
// template, a test fixture, a docs snippet) looks exactly like an action. This
|
|
27
|
+
// pass moves code, so acting on one would rewrite the string's contents:
|
|
28
|
+
// require every hoisted span to start at real, unquoted code.
|
|
29
|
+
const masked = blankLiterals(source);
|
|
30
|
+
if (masked === undefined) return source;
|
|
31
|
+
|
|
32
|
+
// Skip actions a NAMED declaration already covers (`const a = async () => {
|
|
33
|
+
// 'use server' }`). At module scope rewriteInlineActionTags already gives them
|
|
34
|
+
// an `if:` id where they stand; at any other position the name is what the
|
|
35
|
+
// surrounding code refers to, so moving the initializer only adds an alias.
|
|
36
|
+
// Not line-anchored: bundled output indents module-scope declarations, and
|
|
37
|
+
// hoisting one there would also read the binding before its declaration.
|
|
38
|
+
const named = inlineUseServerFunctions(source);
|
|
39
|
+
const candidates = spans.filter(
|
|
40
|
+
span =>
|
|
41
|
+
masked[span.start] === source[span.start] &&
|
|
42
|
+
!named.some(action => action.directiveEnd > span.start && action.directiveEnd <= span.end) &&
|
|
43
|
+
// Outermost only: an action declared inside another action's body would be
|
|
44
|
+
// hoisted out of a scope that is itself a function.
|
|
45
|
+
!spans.some(other => other !== span && other.start <= span.start && other.end >= span.end),
|
|
46
|
+
);
|
|
47
|
+
if (candidates.length === 0) return source;
|
|
48
|
+
|
|
49
|
+
const moduleBindings = moduleScopeBindings(source);
|
|
50
|
+
const hoists: { start: number; end: number; name: string }[] = [];
|
|
51
|
+
for (const span of candidates) {
|
|
52
|
+
if (!capturesNothing(source.slice(span.start, span.end), moduleBindings)) continue;
|
|
53
|
+
hoists.push({ start: span.start, end: span.end, name: `${HOIST_PREFIX}${hoists.length}` });
|
|
54
|
+
}
|
|
55
|
+
if (hoists.length === 0) return source;
|
|
56
|
+
|
|
57
|
+
let out = source;
|
|
58
|
+
const declarations: string[] = [];
|
|
59
|
+
for (const { start, end, name } of [...hoists].sort((a, b) => b.start - a.start)) {
|
|
60
|
+
declarations.unshift(`const ${name} = ${out.slice(start, end)};`);
|
|
61
|
+
out = `${out.slice(0, start)}${name}${out.slice(end)}`;
|
|
62
|
+
}
|
|
63
|
+
// Emitted at the TOP of the module (just after any directive prologue), each on its own line so
|
|
64
|
+
// rewriteInlineActionTags - which only tags line-anchored declarations - picks them up. Top, not
|
|
65
|
+
// bottom: an action passed to a module-scope call is read while the module evaluates, and a const
|
|
66
|
+
// declared below that point would still be in its temporal dead zone. Import bindings are
|
|
67
|
+
// initialized before the module body runs, and everything else the body reads is only reached when
|
|
68
|
+
// the action is called.
|
|
69
|
+
const at = prologueEnd(out);
|
|
70
|
+
return `${out.slice(0, at)}\n${declarations.join('\n')}\n${out.slice(at)}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Offset just past the module's directive prologue (and any shebang/leading comments) - the first
|
|
75
|
+
* point at which a statement may be inserted without displacing a directive.
|
|
76
|
+
*/
|
|
77
|
+
function prologueEnd(source: string): number {
|
|
78
|
+
let i = source.startsWith('') ? 1 : 0;
|
|
79
|
+
if (source.startsWith('#!', i)) {
|
|
80
|
+
const nl = source.indexOf('\n', i);
|
|
81
|
+
i = nl === -1 ? source.length : nl + 1;
|
|
82
|
+
}
|
|
83
|
+
for (;;) {
|
|
84
|
+
let j = i;
|
|
85
|
+
while (j < source.length && /\s/.test(source[j] ?? '')) j++;
|
|
86
|
+
if (source.startsWith('//', j)) {
|
|
87
|
+
const nl = source.indexOf('\n', j);
|
|
88
|
+
i = nl === -1 ? source.length : nl + 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (source.startsWith('/*', j)) {
|
|
92
|
+
const end = source.indexOf('*/', j + 2);
|
|
93
|
+
if (end === -1) return i;
|
|
94
|
+
i = end + 2;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const directive = /^(['"])[^'"\n]*\1\s*;?/.exec(source.slice(j));
|
|
98
|
+
if (!directive) return i;
|
|
99
|
+
i = j + directive[0].length;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* True when every identifier the function references is resolvable without the
|
|
105
|
+
* enclosing function scopes. Conservative: "cannot tell" is false.
|
|
106
|
+
*/
|
|
107
|
+
function capturesNothing(text: string, moduleBindings: Set<string>): boolean {
|
|
108
|
+
// Literal spans are blanked (same offsets, content replaced by spaces) so that
|
|
109
|
+
// neither the identifier scan nor the parameter scan can read code out of a
|
|
110
|
+
// string, comment, or regex. Template `${...}` interpolations are kept: the
|
|
111
|
+
// identifiers in them are real references.
|
|
112
|
+
const code = blankLiterals(text);
|
|
113
|
+
if (code === undefined) return false;
|
|
114
|
+
const tokens = identifierTokens(code);
|
|
115
|
+
if (tokens === undefined) return false;
|
|
116
|
+
const locals = localBindings(code, tokens);
|
|
117
|
+
if (locals === undefined) return false;
|
|
118
|
+
return tokens.every(
|
|
119
|
+
token =>
|
|
120
|
+
token.kind !== 'reference' ||
|
|
121
|
+
locals.has(token.value) ||
|
|
122
|
+
moduleBindings.has(token.value) ||
|
|
123
|
+
GLOBALS.has(token.value),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type TokenKind = 'reference' | 'property' | 'key' | 'keyword';
|
|
128
|
+
|
|
129
|
+
interface Token {
|
|
130
|
+
value: string;
|
|
131
|
+
kind: TokenKind;
|
|
132
|
+
/** Offset of the identifier's first character within the scanned text. */
|
|
133
|
+
at: number;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Identifier tokens of already-blanked code. `undefined` when the code uses a
|
|
138
|
+
* construct whose scoping this scanner cannot reason about.
|
|
139
|
+
*/
|
|
140
|
+
function identifierTokens(code: string): Token[] | undefined {
|
|
141
|
+
const tokens: Token[] = [];
|
|
142
|
+
let previous = '';
|
|
143
|
+
let i = 0;
|
|
144
|
+
while (i < code.length) {
|
|
145
|
+
const ch = code[i] ?? '';
|
|
146
|
+
if (/\s/.test(ch)) {
|
|
147
|
+
i++;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (!/[A-Za-z_$]/.test(ch)) {
|
|
151
|
+
previous = ch;
|
|
152
|
+
i++;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
let j = i;
|
|
156
|
+
while (j < code.length && /[\w$]/.test(code[j] ?? '')) j++;
|
|
157
|
+
const value = code.slice(i, j);
|
|
158
|
+
if (OPAQUE.has(value)) return undefined;
|
|
159
|
+
let k = j;
|
|
160
|
+
while (k < code.length && /\s/.test(code[k] ?? '')) k++;
|
|
161
|
+
const kind: TokenKind = KEYWORDS.has(value)
|
|
162
|
+
? 'keyword'
|
|
163
|
+
: previous === '.'
|
|
164
|
+
? 'property'
|
|
165
|
+
: code[k] === ':' && (previous === '{' || previous === ',')
|
|
166
|
+
? 'key'
|
|
167
|
+
: 'reference';
|
|
168
|
+
tokens.push({ value, kind, at: i });
|
|
169
|
+
previous = 'a';
|
|
170
|
+
i = j;
|
|
171
|
+
}
|
|
172
|
+
return tokens;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Names the function binds itself: its own parameters plus every parameter and declaration nested
|
|
177
|
+
* inside it. `undefined` for a binding form this scanner will not read precisely (destructuring,
|
|
178
|
+
* defaults) - guessing either way risks hoisting a function that does capture.
|
|
179
|
+
*/
|
|
180
|
+
function localBindings(code: string, tokens: Token[]): Set<string> | undefined {
|
|
181
|
+
const locals = new Set<string>();
|
|
182
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
183
|
+
const token = tokens[index];
|
|
184
|
+
if (token?.kind !== 'keyword') continue;
|
|
185
|
+
if (token.value === 'const' || token.value === 'let' || token.value === 'var') {
|
|
186
|
+
const bound = declaredName(code, token);
|
|
187
|
+
if (bound === undefined) return undefined;
|
|
188
|
+
locals.add(bound);
|
|
189
|
+
} else if (token.value === 'function' || token.value === 'class' || token.value === 'catch') {
|
|
190
|
+
const next = tokens[index + 1];
|
|
191
|
+
if (next?.kind === 'reference') locals.add(next.value);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
for (const params of parameterLists(code)) {
|
|
195
|
+
if (params === undefined) return undefined;
|
|
196
|
+
for (const name of params) locals.add(name);
|
|
197
|
+
}
|
|
198
|
+
return locals;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The single name bound by the `const`/`let`/`var` at `token`, if simple. */
|
|
202
|
+
function declaredName(code: string, token: Token): string | undefined {
|
|
203
|
+
let i = token.at + token.value.length;
|
|
204
|
+
while (i < code.length && /\s/.test(code[i] ?? '')) i++;
|
|
205
|
+
if (!/[A-Za-z_$]/.test(code[i] ?? '')) return undefined; // destructuring pattern
|
|
206
|
+
let j = i;
|
|
207
|
+
while (j < code.length && /[\w$]/.test(code[j] ?? '')) j++;
|
|
208
|
+
return code.slice(i, j);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Every parameter list in `code` - the scanned function's own and any nested one. Yields `undefined`
|
|
213
|
+
* for a list this scanner will not read precisely, which makes the whole function unhoistable.
|
|
214
|
+
*/
|
|
215
|
+
function* parameterLists(code: string): Generator<string[] | undefined> {
|
|
216
|
+
const arrow = /=>/g;
|
|
217
|
+
for (let match; (match = arrow.exec(code)); ) {
|
|
218
|
+
let i = match.index - 1;
|
|
219
|
+
while (i >= 0 && /\s/.test(code[i] ?? '')) i--;
|
|
220
|
+
if (code[i] === ')') {
|
|
221
|
+
const open = matchingOpenParen(code, i);
|
|
222
|
+
yield open === undefined ? undefined : parameterNames(code.slice(open + 1, i));
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
let j = i;
|
|
226
|
+
while (j >= 0 && /[\w$]/.test(code[j] ?? '')) j--;
|
|
227
|
+
const name = code.slice(j + 1, i + 1);
|
|
228
|
+
yield /^[A-Za-z_$][\w$]*$/.test(name) ? [name] : undefined;
|
|
229
|
+
}
|
|
230
|
+
const fnDecl = /\bfunction\b\s*\*?\s*(?:[A-Za-z_$][\w$]*)?\s*\(/g;
|
|
231
|
+
for (let match; (match = fnDecl.exec(code)); ) {
|
|
232
|
+
const open = match.index + match[0].length - 1;
|
|
233
|
+
const close = matchingCloseParen(code, open);
|
|
234
|
+
yield close === undefined ? undefined : parameterNames(code.slice(open + 1, close));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Names bound by a parameter list's source text. `undefined` for destructuring, default values, or a
|
|
240
|
+
* nested function type - forms where a name in the list is not simply a binding.
|
|
241
|
+
*/
|
|
242
|
+
function parameterNames(params: string): string[] | undefined {
|
|
243
|
+
const stripped = params.trim();
|
|
244
|
+
if (stripped === '') return [];
|
|
245
|
+
if (/[{}[\]=(]/.test(stripped)) return undefined;
|
|
246
|
+
const names: string[] = [];
|
|
247
|
+
for (const part of stripped.split(',')) {
|
|
248
|
+
// `name: Type` (TS annotation) binds `name`; the annotation is types only.
|
|
249
|
+
const name = (part.split(':')[0] ?? '').trim().replace(/^\.\.\./, '');
|
|
250
|
+
if (name === '') continue;
|
|
251
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name)) return undefined;
|
|
252
|
+
names.push(name);
|
|
253
|
+
}
|
|
254
|
+
return names;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Value bindings introduced at module scope. Line-anchored on purpose: an
|
|
259
|
+
* indented declaration belongs to some inner scope and must not be mistaken for
|
|
260
|
+
* one a hoisted function could still reach.
|
|
261
|
+
*/
|
|
262
|
+
function moduleScopeBindings(source: string): Set<string> {
|
|
263
|
+
const names = new Set<string>();
|
|
264
|
+
const declaration =
|
|
265
|
+
/^(?:export\s+)?(?:declare\s+)?(?:async\s+)?(?:const|let|var|function\s*\*?|class)\s+([A-Za-z_$][\w$]*)/gm;
|
|
266
|
+
for (let match; (match = declaration.exec(source)); ) if (match[1]) names.add(match[1]);
|
|
267
|
+
|
|
268
|
+
const importClause = /^import\s+(?!type\b)([\s\S]*?)\s+from\s+['"]/gm;
|
|
269
|
+
for (let match; (match = importClause.exec(source)); ) {
|
|
270
|
+
const clause = match[1] ?? '';
|
|
271
|
+
const namespace = /\*\s+as\s+([A-Za-z_$][\w$]*)/.exec(clause);
|
|
272
|
+
if (namespace?.[1]) names.add(namespace[1]);
|
|
273
|
+
const defaultName = /^([A-Za-z_$][\w$]*)/.exec(clause.trim());
|
|
274
|
+
if (defaultName?.[1]) names.add(defaultName[1]);
|
|
275
|
+
const braced = /\{([^}]*)\}/.exec(clause);
|
|
276
|
+
for (const part of (braced?.[1] ?? '').split(',')) {
|
|
277
|
+
const trimmed = part.trim();
|
|
278
|
+
if (trimmed === '' || trimmed.startsWith('type ')) continue;
|
|
279
|
+
const renamed = /\bas\s+([A-Za-z_$][\w$]*)\s*$/.exec(trimmed);
|
|
280
|
+
const name = renamed?.[1] ?? trimmed;
|
|
281
|
+
if (/^[A-Za-z_$][\w$]*$/.test(name)) names.add(name);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return names;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Replace the CONTENT of strings, comments and regex literals with spaces,
|
|
289
|
+
* preserving every offset, so later scans only ever see real code. Template
|
|
290
|
+
* literals keep their `${...}` interpolations (real expressions) and blank the
|
|
291
|
+
* literal chunks. `undefined` when a literal is unterminated.
|
|
292
|
+
*/
|
|
293
|
+
function blankLiterals(text: string): string | undefined {
|
|
294
|
+
const out = text.split('');
|
|
295
|
+
const blank = (from: number, to: number) => {
|
|
296
|
+
for (let i = from; i < to; i++) if (out[i] !== '\n') out[i] = ' ';
|
|
297
|
+
};
|
|
298
|
+
// Template nesting: 'template' = inside literal text, 'interp' = inside a
|
|
299
|
+
// `${...}` (which is ordinary code and may itself open a template).
|
|
300
|
+
const stack: ({ kind: 'template' } | { kind: 'interp'; depth: number })[] = [];
|
|
301
|
+
let previous = '';
|
|
302
|
+
let i = 0;
|
|
303
|
+
while (i < text.length) {
|
|
304
|
+
const ch = text[i] ?? '';
|
|
305
|
+
const top = stack[stack.length - 1];
|
|
306
|
+
if (top?.kind === 'template') {
|
|
307
|
+
if (ch === '\\') {
|
|
308
|
+
blank(i, i + 2);
|
|
309
|
+
i += 2;
|
|
310
|
+
} else if (ch === '`') {
|
|
311
|
+
stack.pop();
|
|
312
|
+
previous = '`';
|
|
313
|
+
i++;
|
|
314
|
+
} else if (ch === '$' && text[i + 1] === '{') {
|
|
315
|
+
// Blank the `${` delimiter too — only the expression inside it is code.
|
|
316
|
+
stack.push({ kind: 'interp', depth: 0 });
|
|
317
|
+
blank(i, i + 2);
|
|
318
|
+
previous = '(';
|
|
319
|
+
i += 2;
|
|
320
|
+
} else {
|
|
321
|
+
blank(i, i + 1);
|
|
322
|
+
i++;
|
|
323
|
+
}
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (ch === '/' && text[i + 1] === '/') {
|
|
327
|
+
const nl = text.indexOf('\n', i);
|
|
328
|
+
const end = nl === -1 ? text.length : nl;
|
|
329
|
+
blank(i, end);
|
|
330
|
+
i = end;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (ch === '/' && text[i + 1] === '*') {
|
|
334
|
+
const end = text.indexOf('*/', i + 2);
|
|
335
|
+
if (end === -1) return undefined;
|
|
336
|
+
blank(i, end + 2);
|
|
337
|
+
i = end + 2;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (ch === '"' || ch === "'") {
|
|
341
|
+
const end = endOfQuoted(text, i, ch);
|
|
342
|
+
if (end === undefined) {
|
|
343
|
+
// No closing quote before the newline, so this is not a string at all —
|
|
344
|
+
// most often an apostrophe in JSX text (`<p>don't</p>`). Treating it as
|
|
345
|
+
// one would mask the rest of the file.
|
|
346
|
+
previous = ch;
|
|
347
|
+
i++;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
blank(i + 1, end - 1);
|
|
351
|
+
previous = ch;
|
|
352
|
+
i = end;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (ch === '/' && REGEX_PRECEDERS.has(previous)) {
|
|
356
|
+
const end = endOfRegex(text, i);
|
|
357
|
+
if (end === undefined) {
|
|
358
|
+
// Not a regex after all — in TSX this is nearly always a JSX slash
|
|
359
|
+
// (`<p />`, `</p>`), whose preceding character looks like an operator.
|
|
360
|
+
previous = ch;
|
|
361
|
+
i++;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
blank(i, end);
|
|
365
|
+
previous = ' ';
|
|
366
|
+
i = end;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (ch === '`') stack.push({ kind: 'template' });
|
|
370
|
+
else if (top?.kind === 'interp') {
|
|
371
|
+
if (ch === '{') top.depth++;
|
|
372
|
+
else if (ch === '}') {
|
|
373
|
+
if (top.depth === 0) {
|
|
374
|
+
stack.pop();
|
|
375
|
+
blank(i, i + 1);
|
|
376
|
+
} else top.depth--;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
previous = ch;
|
|
380
|
+
i++;
|
|
381
|
+
}
|
|
382
|
+
return stack.length === 0 ? out.join('') : undefined;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Offset just past the string starting at `i`, or undefined if unterminated. */
|
|
386
|
+
function endOfQuoted(text: string, i: number, quote: string): number | undefined {
|
|
387
|
+
for (let j = i + 1; j < text.length; j++) {
|
|
388
|
+
if (text[j] === '\\') {
|
|
389
|
+
j++;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (text[j] === quote) return j + 1;
|
|
393
|
+
if (text[j] === '\n') return undefined;
|
|
394
|
+
}
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Offset just past the regex literal (with flags) starting at `i`. */
|
|
399
|
+
function endOfRegex(text: string, i: number): number | undefined {
|
|
400
|
+
let inClass = false;
|
|
401
|
+
for (let j = i + 1; j < text.length; j++) {
|
|
402
|
+
const ch = text[j];
|
|
403
|
+
if (ch === '\\') {
|
|
404
|
+
j++;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (ch === '\n') return undefined;
|
|
408
|
+
if (ch === '[') inClass = true;
|
|
409
|
+
else if (ch === ']') inClass = false;
|
|
410
|
+
else if (ch === '/' && !inClass) {
|
|
411
|
+
let k = j + 1;
|
|
412
|
+
while (k < text.length && /[a-z]/.test(text[k] ?? '')) k++;
|
|
413
|
+
return k;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function matchingOpenParen(text: string, close: number): number | undefined {
|
|
420
|
+
let depth = 0;
|
|
421
|
+
for (let i = close; i >= 0; i--) {
|
|
422
|
+
if (text[i] === ')') depth++;
|
|
423
|
+
else if (text[i] === '(') {
|
|
424
|
+
depth--;
|
|
425
|
+
if (depth === 0) return i;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function matchingCloseParen(text: string, open: number): number | undefined {
|
|
432
|
+
let depth = 0;
|
|
433
|
+
for (let i = open; i < text.length; i++) {
|
|
434
|
+
if (text[i] === '(') depth++;
|
|
435
|
+
else if (text[i] === ')') {
|
|
436
|
+
depth--;
|
|
437
|
+
if (depth === 0) return i;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Name prefix of every hoisted declaration; also the idempotence marker. */
|
|
444
|
+
const HOIST_PREFIX = '__pnextInlineAction$';
|
|
445
|
+
|
|
446
|
+
/** Constructs whose scoping this scanner refuses to reason about. */
|
|
447
|
+
const OPAQUE = new Set(['this', 'arguments', 'super', 'eval', 'with']);
|
|
448
|
+
|
|
449
|
+
const KEYWORDS = new Set([
|
|
450
|
+
'as', 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
451
|
+
'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for', 'from', 'function',
|
|
452
|
+
'if', 'import', 'in', 'instanceof', 'let', 'new', 'of', 'return', 'satisfies', 'static',
|
|
453
|
+
'switch', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'yield',
|
|
454
|
+
// TS type positions the scanner walks through without resolving.
|
|
455
|
+
'any', 'asserts', 'bigint', 'boolean', 'declare', 'enum', 'infer', 'interface', 'is', 'keyof',
|
|
456
|
+
'namespace', 'never', 'number', 'object', 'readonly', 'string', 'symbol', 'type', 'undefined',
|
|
457
|
+
'unique', 'unknown',
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
/** Globals a module-scope function reaches exactly as an inner one would. */
|
|
461
|
+
const GLOBALS = new Set([
|
|
462
|
+
'AbortController', 'AbortSignal', 'AggregateError', 'Array', 'ArrayBuffer', 'Atomics',
|
|
463
|
+
'BigInt', 'BigInt64Array', 'BigUint64Array', 'Blob', 'Boolean', 'Buffer',
|
|
464
|
+
'DataView', 'Date', 'Error', 'EvalError', 'Event', 'EventTarget',
|
|
465
|
+
'File', 'FinalizationRegistry', 'Float32Array', 'Float64Array', 'FormData', 'Function',
|
|
466
|
+
'Headers', 'Infinity', 'Int16Array', 'Int32Array', 'Int8Array', 'Intl', 'Iterator',
|
|
467
|
+
'JSON', 'Map', 'Math', 'NaN', 'Number', 'Object', 'Promise', 'Proxy',
|
|
468
|
+
'RangeError', 'ReadableStream', 'ReferenceError', 'Reflect', 'RegExp', 'Request', 'Response',
|
|
469
|
+
'Set', 'SharedArrayBuffer', 'String', 'Symbol', 'SyntaxError', 'TextDecoder', 'TextEncoder',
|
|
470
|
+
'TransformStream', 'TypeError', 'URIError', 'URL', 'URLSearchParams', 'Uint16Array',
|
|
471
|
+
'Uint32Array', 'Uint8Array', 'Uint8ClampedArray', 'WeakMap', 'WeakRef', 'WeakSet',
|
|
472
|
+
'WritableStream', 'atob', 'btoa', 'clearInterval', 'clearTimeout', 'console', 'crypto',
|
|
473
|
+
'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'fetch', 'globalThis',
|
|
474
|
+
'isFinite', 'isNaN', 'parseFloat', 'parseInt', 'performance', 'process', 'queueMicrotask',
|
|
475
|
+
'setInterval', 'setTimeout', 'structuredClone',
|
|
476
|
+
]);
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Characters after which a `/` opens a regex literal rather than dividing. An
|
|
480
|
+
* identifier or literal never precedes a regex, so the default is division.
|
|
481
|
+
*/
|
|
482
|
+
const REGEX_PRECEDERS = new Set([
|
|
483
|
+
'', '(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';', '+', '-', '*', '%', '<', '>',
|
|
484
|
+
'~', '^', '\n',
|
|
485
|
+
]);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stable, deterministic id for a server action export. This id is the wire
|
|
6
|
+
* contract between the generated client stub (which sends it in a header) and
|
|
7
|
+
* the runtime endpoint (which looks the action up by it). It must be:
|
|
8
|
+
*
|
|
9
|
+
* - Deterministic: same input -> same output across processes and builds. No
|
|
10
|
+
* Date.now / Math.random. A build machine and a serving machine compute the
|
|
11
|
+
* same id from the same (path, name).
|
|
12
|
+
* - Path-stable: computed from the workspace-relative POSIX path so absolute
|
|
13
|
+
* prefixes (which differ between machines / CI) never leak into the id.
|
|
14
|
+
*
|
|
15
|
+
* `root` is the project/workspace root. When omitted the raw `filePath` is used
|
|
16
|
+
* as-is (already-relative paths pass through unchanged).
|
|
17
|
+
*/
|
|
18
|
+
export function actionId(filePath: string, exportName: string, root?: string): string {
|
|
19
|
+
const key = workspaceRelative(filePath, root);
|
|
20
|
+
return createHash('sha256')
|
|
21
|
+
.update(key)
|
|
22
|
+
.update('\0')
|
|
23
|
+
.update(exportName)
|
|
24
|
+
.digest('hex')
|
|
25
|
+
.slice(0, 12);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Normalize a module path to a stable, workspace-relative POSIX key. Exported so
|
|
30
|
+
* the registry and detection share one notion of a module's identity.
|
|
31
|
+
*/
|
|
32
|
+
export function workspaceRelative(filePath: string, root?: string): string {
|
|
33
|
+
const rel = root ? path.relative(root, filePath) : filePath;
|
|
34
|
+
return toPosix(rel);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toPosix(value: string): string {
|
|
38
|
+
return value.split(path.sep).join('/');
|
|
39
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export { actionId, workspaceRelative } from './ids';
|
|
2
|
+
export {
|
|
3
|
+
moduleLevelUseServer,
|
|
4
|
+
inlineUseServerFunctions,
|
|
5
|
+
moduleActionExports,
|
|
6
|
+
type InlineAction,
|
|
7
|
+
} from './detect';
|
|
8
|
+
export {
|
|
9
|
+
registerActionModule,
|
|
10
|
+
lookupAction,
|
|
11
|
+
allActions,
|
|
12
|
+
clearActions,
|
|
13
|
+
type ActionEntry,
|
|
14
|
+
} from './registry';
|
|
15
|
+
export {
|
|
16
|
+
createInstanceScope,
|
|
17
|
+
registerActionInstance,
|
|
18
|
+
lookupActionInstance,
|
|
19
|
+
isInstanceActionId,
|
|
20
|
+
instanceActionRouteId,
|
|
21
|
+
clearActionInstances,
|
|
22
|
+
type InstanceScope,
|
|
23
|
+
} from './instances';
|
|
24
|
+
export { runAction, DEFAULT_BODY_LIMIT_BYTES, type RunActionOptions } from './endpoint';
|
|
25
|
+
export {
|
|
26
|
+
serveAction,
|
|
27
|
+
isActionRequest,
|
|
28
|
+
isProgressiveActionRequest,
|
|
29
|
+
type ServeActionOptions,
|
|
30
|
+
} from './serve';
|
|
31
|
+
export { clientStubSource, type ClientStubModule } from './client-stub';
|
|
32
|
+
export {
|
|
33
|
+
ACTION_ID_HEADER,
|
|
34
|
+
ACTION_ID_FIELD,
|
|
35
|
+
ACTION_REDIRECT_HEADER,
|
|
36
|
+
ACTION_REFRESH_HEADER,
|
|
37
|
+
NEXT_ACTION_REVALIDATED_HEADER,
|
|
38
|
+
ACTION_NOT_FOUND_HEADER,
|
|
39
|
+
PROP_ACTION_MARKER,
|
|
40
|
+
type ActionSuccessBody,
|
|
41
|
+
} from './protocol';
|