@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,685 @@
|
|
|
1
|
+
// `turbopack.rules` loader-chain support (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// Next's turbopack.rules let a project route specific extensions through custom "loader" modules -
|
|
4
|
+
// plain functions receiving the raw file content and returning replacement module source. pnext has no
|
|
5
|
+
// webpack/turbopack graph to run these against, so this plugin re-implements the loader-invocation
|
|
6
|
+
// contract directly: resolve each configured loader module, call it with a minimal loader-context
|
|
7
|
+
// (resourcePath, resourceQuery, fs, async/callback, getOptions, getResolve, addDependency,
|
|
8
|
+
// importModule), and feed the result to esbuild via the loader picked from the rule's `as` or the
|
|
9
|
+
// source file's own extension - matching the sync/async/callback-style loaders, raw-Buffer content and
|
|
10
|
+
// chained-output-as-module-source shapes the fixtures exercise.
|
|
11
|
+
//
|
|
12
|
+
// Also supports the conditional-array rule form (condition.query selects a chain by the import's
|
|
13
|
+
// query), `turbopack.resolveAlias`, and no-loader `type` asset rules.
|
|
14
|
+
//
|
|
15
|
+
// Registered first in both client and server esbuild plugin arrays so a configured rule preempts
|
|
16
|
+
// pnext's default handling for the same extension.
|
|
17
|
+
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
import { existsSync, readFile, readFileSync, realpathSync } from 'node:fs';
|
|
20
|
+
import { mkdir, readFile as readFileP, writeFile } from 'node:fs/promises';
|
|
21
|
+
import { createRequire } from 'node:module';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { pathToFileURL } from 'node:url';
|
|
24
|
+
import type { Plugin } from 'esbuild';
|
|
25
|
+
import type { ResolvedConfig } from '../../config';
|
|
26
|
+
import { rewriteSpecifierLiterals } from '../../resolve/scan-facts';
|
|
27
|
+
import { turbopackLoaderRules, turbopackResolveAlias, type WebpackLoaderRule } from './config';
|
|
28
|
+
|
|
29
|
+
interface LoaderFn {
|
|
30
|
+
(this: LoaderContext, content: string | Buffer): unknown;
|
|
31
|
+
raw?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface LoaderContext {
|
|
35
|
+
resourcePath: string;
|
|
36
|
+
resourceQuery: string;
|
|
37
|
+
mode: string;
|
|
38
|
+
fs: {
|
|
39
|
+
readFile: typeof readFile;
|
|
40
|
+
readFileSync: typeof readFileSync;
|
|
41
|
+
};
|
|
42
|
+
addDependency: (file: string) => void;
|
|
43
|
+
getOptions: () => Record<string, unknown>;
|
|
44
|
+
async: () => (err: unknown, result?: unknown) => void;
|
|
45
|
+
callback: (err: unknown, result?: unknown, ...rest: unknown[]) => void;
|
|
46
|
+
getResolve: (options?: unknown) => (context: string, request: string) => Promise<string>;
|
|
47
|
+
emitFile: (name: string, content: unknown) => void;
|
|
48
|
+
importModule: (request: string) => Promise<unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Where a loader chain runs — client builds cannot support `this.importModule`. */
|
|
52
|
+
type LoaderContextTarget = 'client' | 'server';
|
|
53
|
+
|
|
54
|
+
const RESOLVE_EXTENSIONS = ['', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
|
|
55
|
+
|
|
56
|
+
function stripLeadingWildcard(glob: string): string {
|
|
57
|
+
return glob.startsWith('*') ? glob.slice(1) : glob;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function escapeRegExp(value: string): string {
|
|
61
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ruleMatches(rule: WebpackLoaderRule, filePath: string): boolean {
|
|
65
|
+
return rule.glob.startsWith('*')
|
|
66
|
+
? filePath.endsWith(stripLeadingWildcard(rule.glob))
|
|
67
|
+
: path.basename(filePath) === rule.glob;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function conditionMatches(rule: WebpackLoaderRule, resourceQuery: string): boolean {
|
|
71
|
+
if (!rule.condition) return true;
|
|
72
|
+
const query = rule.condition.query;
|
|
73
|
+
return typeof query === 'string' ? resourceQuery === query : query.test(resourceQuery);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** First rule whose glob matches (ignoring any `condition`). */
|
|
77
|
+
function findRule(rules: WebpackLoaderRule[], filePath: string): WebpackLoaderRule | undefined {
|
|
78
|
+
return rules.find(rule => ruleMatches(rule, filePath));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** First rule whose glob matches AND whose `condition.query` accepts `resourceQuery`. */
|
|
82
|
+
function findRuleForResource(
|
|
83
|
+
rules: WebpackLoaderRule[],
|
|
84
|
+
filePath: string,
|
|
85
|
+
resourceQuery: string,
|
|
86
|
+
): WebpackLoaderRule | undefined {
|
|
87
|
+
return rules.find(rule => ruleMatches(rule, filePath) && conditionMatches(rule, resourceQuery));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Whether a configured `turbopack.rules` loader chain claims this file. The generic "ignored
|
|
92
|
+
* asset"/static-image resolvers redirect those extensions to their own namespace unconditionally, ahead
|
|
93
|
+
* of the bundler-extension plugins where this plugin lives, so a configured rule for e.g. `*.svg` would
|
|
94
|
+
* never get a chance without this escape hatch. Registered as the sole `hasLoaderRuleFor`
|
|
95
|
+
* implementation so all three asset-claiming resolvers share one check. Accepts a raw specifier with an
|
|
96
|
+
* optional query or hash suffix and strips it here.
|
|
97
|
+
*/
|
|
98
|
+
export function hasWebpackLoaderRuleFor(filePath: string): boolean {
|
|
99
|
+
const [sourcePath = filePath] = filePath.split(/[?#]/, 1);
|
|
100
|
+
return findRule(turbopackLoaderRules(), sourcePath) !== undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Run the loader chain a `turbopack.rules` entry configures for `specifier` (alias-aware,
|
|
105
|
+
* query-carrying) and materialize its output as a real module on disk, returning that path - or
|
|
106
|
+
* undefined when no rule claims it.
|
|
107
|
+
*
|
|
108
|
+
* This is the SERVER-graph counterpart of the esbuild plugin. Server modules are not one esbuild graph:
|
|
109
|
+
* each app module is compiled on its own and route files are inlined into a route bundle, so a rule
|
|
110
|
+
* source never reaches a plugin onLoad with its importer's query intact. Core's resolvers ask this seam
|
|
111
|
+
* instead and import the artifact directly. Loaders may be effectful, so each (file, query) pair runs
|
|
112
|
+
* exactly once per process and every resolver shares the result.
|
|
113
|
+
*/
|
|
114
|
+
export function webpackLoaderRuleModule(
|
|
115
|
+
config: ResolvedConfig,
|
|
116
|
+
specifier: string,
|
|
117
|
+
importer: string,
|
|
118
|
+
): string | undefined | Promise<string | undefined> {
|
|
119
|
+
const rules = turbopackLoaderRules();
|
|
120
|
+
if (rules.length === 0) return undefined;
|
|
121
|
+
const [base = specifier] = specifier.split(/[?#]/, 1);
|
|
122
|
+
const query = specifier.slice(base.length);
|
|
123
|
+
const fromDir = path.dirname(originalResourcePath(importer, config));
|
|
124
|
+
const resourcePath = resolveSpecifier(config.root, fromDir, base) ?? base;
|
|
125
|
+
if (!findRuleForResource(rules, resourcePath, query)) return undefined;
|
|
126
|
+
const key = `${resourcePath}\0${query}`;
|
|
127
|
+
const existing = ruleModuleArtifacts.get(key);
|
|
128
|
+
if (existing) return existing;
|
|
129
|
+
const built = buildRuleModuleArtifact(config, rules, resourcePath, query).catch(error => {
|
|
130
|
+
ruleModuleArtifacts.delete(key);
|
|
131
|
+
throw error;
|
|
132
|
+
});
|
|
133
|
+
ruleModuleArtifacts.set(key, built);
|
|
134
|
+
return built;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const ruleModuleArtifacts = new Map<string, Promise<string | undefined>>();
|
|
138
|
+
|
|
139
|
+
async function buildRuleModuleArtifact(
|
|
140
|
+
config: ResolvedConfig,
|
|
141
|
+
rules: WebpackLoaderRule[],
|
|
142
|
+
resourcePath: string,
|
|
143
|
+
query: string,
|
|
144
|
+
): Promise<string | undefined> {
|
|
145
|
+
const loaded = await loadRuleModule(config.root, rules, resourcePath, query, 'server', config);
|
|
146
|
+
if (!loaded) return undefined;
|
|
147
|
+
// Named by the source name + a hash of (path, query, output): distinct
|
|
148
|
+
// `?query` imports of one file stay distinct modules, and a re-run that
|
|
149
|
+
// produces the same text reuses the same artifact.
|
|
150
|
+
const hash = createHash('sha256')
|
|
151
|
+
.update(`${resourcePath}\0${query}\0${loaded.contents}`)
|
|
152
|
+
.digest('hex')
|
|
153
|
+
.slice(0, 16);
|
|
154
|
+
const name = path.basename(resourcePath).replace(/[^A-Za-z0-9_.-]+/g, '-');
|
|
155
|
+
// The chain's output loader doubles as the artifact extension, so Bun and
|
|
156
|
+
// esbuild both parse it the way the rule's `as` declared.
|
|
157
|
+
const outFile = path.join(config.outPath, 'cache', 'loader-rules', `${name}.${hash}.${loaded.loader}`);
|
|
158
|
+
await mkdir(path.dirname(outFile), { recursive: true });
|
|
159
|
+
await writeFile(outFile, loaded.contents);
|
|
160
|
+
return outFile;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** esbuild/Bun `loader` for a rule's declared (or inferred) output extension. */
|
|
164
|
+
function outputLoader(rule: WebpackLoaderRule, resourcePath: string): 'ts' | 'tsx' | 'jsx' {
|
|
165
|
+
const ext = (rule.as ? stripLeadingWildcard(rule.as) : path.extname(resourcePath)).toLowerCase();
|
|
166
|
+
if (ext.endsWith('.tsx')) return 'tsx';
|
|
167
|
+
if (ext.endsWith('.ts')) return 'ts';
|
|
168
|
+
return 'jsx';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function toBuffer(content: string | Buffer): Buffer {
|
|
172
|
+
return Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function toStringContent(content: string | Buffer): string {
|
|
176
|
+
return Buffer.isBuffer(content) ? content.toString('utf8') : content;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Specifier resolution (resolveAlias + relative/absolute, with extension probe)
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
// Absolute path of pnext's own `src/` (or `dist/` in a built install) — modules
|
|
184
|
+
// under it are framework runtime, not app code, and are exempt from user
|
|
185
|
+
// `resolveAlias`/`resolve.alias` entries.
|
|
186
|
+
const PNEXT_RUNTIME_ROOT = path.resolve(import.meta.dirname, '../..');
|
|
187
|
+
|
|
188
|
+
function isPNextRuntimeImporter(importer: string): boolean {
|
|
189
|
+
if (!importer) return false;
|
|
190
|
+
const rel = path.relative(PNEXT_RUNTIME_ROOT, importer);
|
|
191
|
+
return Boolean(rel) && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function firstExisting(base: string): string | undefined {
|
|
195
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
196
|
+
if (existsSync(base + ext)) return base + ext;
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Map a specifier through `turbopack.resolveAlias`, returning the resolved absolute target path, or
|
|
203
|
+
* undefined when no alias matches. Supports exact keys and trailing-glob keys.
|
|
204
|
+
*/
|
|
205
|
+
function resolveAliasSpecifier(specifier: string, root: string): string | undefined {
|
|
206
|
+
for (const [key, target] of Object.entries(turbopackResolveAlias())) {
|
|
207
|
+
if (key.endsWith('/*')) {
|
|
208
|
+
const prefix = key.slice(0, -1); // e.g. '@/*' -> '@/'
|
|
209
|
+
if (specifier.startsWith(prefix)) {
|
|
210
|
+
const rest = specifier.slice(prefix.length);
|
|
211
|
+
const mapped = target.endsWith('/*') ? target.slice(0, -1) + rest : target.replace('*', rest);
|
|
212
|
+
return resolveAliasTarget(mapped, root);
|
|
213
|
+
}
|
|
214
|
+
} else if (specifier === key) {
|
|
215
|
+
return resolveAliasTarget(target, root);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function resolveAliasTarget(target: string, root: string): string {
|
|
222
|
+
if (path.isAbsolute(target)) return target;
|
|
223
|
+
if (target.startsWith('.')) return path.resolve(root, target);
|
|
224
|
+
return target;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Resolve a specifier (alias-aware) to an absolute on-disk path. Returns the best candidate even when
|
|
229
|
+
* the file cannot be probed; bare non-alias specifiers return undefined and are left to the host
|
|
230
|
+
* resolver.
|
|
231
|
+
*/
|
|
232
|
+
function resolveSpecifier(root: string, fromDir: string, specifier: string): string | undefined {
|
|
233
|
+
const aliased = resolveAliasSpecifier(specifier, root);
|
|
234
|
+
if (aliased) return firstExisting(aliased) ?? aliased;
|
|
235
|
+
if (path.isAbsolute(specifier)) return firstExisting(specifier) ?? specifier;
|
|
236
|
+
if (specifier.startsWith('.')) {
|
|
237
|
+
const base = path.resolve(fromDir, specifier);
|
|
238
|
+
return firstExisting(base) ?? base;
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Rewrite `turbopack.resolveAlias` BARE specifiers to their resolved absolute paths in server source.
|
|
245
|
+
*
|
|
246
|
+
* Both esbuild passes and the runtime Bun.plugin hook below map aliases in onResolve - but Bun's runtime
|
|
247
|
+
* resolver rejects an unknown bare package specifier before any plugin onResolve runs, so a module the
|
|
248
|
+
* SERVER RUNTIME loads directly can only be fixed in its source. That is the case for a
|
|
249
|
+
* `this.importModule()` target's own transitive imports: the direct request is alias-resolved by
|
|
250
|
+
* importModuleFor, everything past it is plain import() resolution. Relative and absolute specifiers
|
|
251
|
+
* are left alone - those resolve natively.
|
|
252
|
+
*/
|
|
253
|
+
export function rewriteResolveAliasImports(source: string, file: string, root: string): string {
|
|
254
|
+
const aliases = turbopackResolveAlias();
|
|
255
|
+
if (Object.keys(aliases).length === 0) return source;
|
|
256
|
+
return rewriteSpecifierLiterals(source, file, (specifier, kind) => {
|
|
257
|
+
// Same scope as the `from`-anchored regex this replaced: side-effect
|
|
258
|
+
// imports stay with the host resolver.
|
|
259
|
+
if (kind === 'side-effect') return undefined;
|
|
260
|
+
if (specifier.startsWith('.') || path.isAbsolute(specifier)) return undefined;
|
|
261
|
+
const aliased = resolveAliasSpecifier(specifier, root);
|
|
262
|
+
if (!aliased) return undefined;
|
|
263
|
+
const target = firstExisting(aliased) ?? aliased;
|
|
264
|
+
// A bare alias target (e.g. `'preact/compat': 'react'`) is not a file path;
|
|
265
|
+
// leave the host resolver to handle it.
|
|
266
|
+
if (!path.isAbsolute(target)) return undefined;
|
|
267
|
+
return JSON.stringify(target);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** `this.getResolve()(context, request)` — alias-aware, extension-probing. */
|
|
272
|
+
function resolveLoaderRequest(root: string, context: string, request: string): Promise<string> {
|
|
273
|
+
const resolved = resolveSpecifier(root, context, request);
|
|
274
|
+
if (resolved && existsSync(resolved)) return Promise.resolve(resolved);
|
|
275
|
+
return Promise.reject(new Error(`Cannot resolve '${request}' from '${context}'`));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Server bundles compile against materialized COPIES of the app tree, not symlinks, so sibling files do
|
|
280
|
+
* not exist next to the materialized copy. A loader's relative `this.importModule('../x')` /
|
|
281
|
+
* `this.getResolve()` must therefore resolve against the ORIGINAL file's directory. Recover it by
|
|
282
|
+
* rebasing the materialized path onto `config.workspaceRoot`, stripping the cache root and the leading
|
|
283
|
+
* profile segment; out-of-workspace modules are keyed `external/<hash>` and simply fail the existence
|
|
284
|
+
* check, falling through unchanged. `realpathSync` is kept only as a secondary fallback for the
|
|
285
|
+
* symlinked-asset case - it is a no-op for the real copied code-module files this path mainly handles.
|
|
286
|
+
*/
|
|
287
|
+
function originalResourcePath(resourcePath: string, config: ResolvedConfig | undefined): string {
|
|
288
|
+
if (config) {
|
|
289
|
+
const cacheRoot = path.join(config.outPath, 'cache', 'server');
|
|
290
|
+
const rel = path.relative(cacheRoot, resourcePath);
|
|
291
|
+
if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) {
|
|
292
|
+
const segments = rel.split(path.sep);
|
|
293
|
+
segments.shift(); // drop <profileDir>
|
|
294
|
+
const original = path.join(config.workspaceRoot, ...segments);
|
|
295
|
+
if (existsSync(original)) return original;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
return realpathSync(resourcePath);
|
|
300
|
+
} catch {
|
|
301
|
+
return resourcePath;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* `this.importModule(request)`: resolve (alias + relative/absolute aware) then dynamically import the
|
|
307
|
+
* module in the server/runtime context, letting Bun's onLoad hooks handle the transitive graph. Never
|
|
308
|
+
* available in the client build context.
|
|
309
|
+
*
|
|
310
|
+
* Only the DIRECT request goes through `resolveSpecifier`; the imported module's own imports are
|
|
311
|
+
* resolved by Bun. Those transitive specifiers still need the same resolveAlias and loader-rule
|
|
312
|
+
* treatment, which is exactly what the runtime Bun.plugin hook provides, so ensure it is installed
|
|
313
|
+
* before importing. Registration is idempotent per root, so a build that already did it pays nothing.
|
|
314
|
+
*/
|
|
315
|
+
async function importModuleFor(
|
|
316
|
+
root: string,
|
|
317
|
+
resourcePath: string,
|
|
318
|
+
request: string,
|
|
319
|
+
config: ResolvedConfig | undefined,
|
|
320
|
+
): Promise<unknown> {
|
|
321
|
+
if (config) registerWebpackLoaderRuleRuntime(config);
|
|
322
|
+
const resolved = resolveSpecifier(root, path.dirname(resourcePath), request);
|
|
323
|
+
if (!resolved) {
|
|
324
|
+
throw new Error(`this.importModule: cannot resolve '${request}' from '${resourcePath}'`);
|
|
325
|
+
}
|
|
326
|
+
return import(pathToFileURL(resolved).href);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Loader invocation
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
/** Invokes a single loader function, bridging its sync/promise/callback return conventions. */
|
|
334
|
+
function runLoader(
|
|
335
|
+
fn: LoaderFn,
|
|
336
|
+
root: string,
|
|
337
|
+
resourcePath: string,
|
|
338
|
+
resourceQuery: string,
|
|
339
|
+
input: string | Buffer,
|
|
340
|
+
target: LoaderContextTarget,
|
|
341
|
+
config: ResolvedConfig | undefined,
|
|
342
|
+
): Promise<string | Buffer> {
|
|
343
|
+
return new Promise((resolve, reject) => {
|
|
344
|
+
let settled = false;
|
|
345
|
+
const finish = (err: unknown, result?: unknown) => {
|
|
346
|
+
if (settled) return;
|
|
347
|
+
settled = true;
|
|
348
|
+
if (err) reject(err instanceof Error ? err : new Error(JSON.stringify(err)));
|
|
349
|
+
else resolve(result as string | Buffer);
|
|
350
|
+
};
|
|
351
|
+
const ctx: LoaderContext = {
|
|
352
|
+
resourcePath,
|
|
353
|
+
resourceQuery,
|
|
354
|
+
mode: 'production',
|
|
355
|
+
fs: { readFile, readFileSync },
|
|
356
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function -- loader deps aren't tracked
|
|
357
|
+
addDependency: () => {},
|
|
358
|
+
getOptions: () => ({}),
|
|
359
|
+
async: () => finish,
|
|
360
|
+
callback: finish,
|
|
361
|
+
getResolve: () => (context: string, request: string) => resolveLoaderRequest(root, context, request),
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function -- emitted files aren't tracked
|
|
363
|
+
emitFile: () => {},
|
|
364
|
+
importModule:
|
|
365
|
+
target === 'server'
|
|
366
|
+
? (request: string) => importModuleFor(root, resourcePath, request, config)
|
|
367
|
+
: () =>
|
|
368
|
+
Promise.reject(
|
|
369
|
+
new Error('this.importModule() is not supported in the client build context'),
|
|
370
|
+
),
|
|
371
|
+
};
|
|
372
|
+
try {
|
|
373
|
+
const result = fn.call(ctx, input);
|
|
374
|
+
if (result === undefined) return; // loader signaled async via this.async()/this.callback()
|
|
375
|
+
if (result && typeof (result as Promise<unknown>).then === 'function') {
|
|
376
|
+
(result as Promise<unknown>).then(value => finish(null, value), finish);
|
|
377
|
+
} else {
|
|
378
|
+
finish(null, result);
|
|
379
|
+
}
|
|
380
|
+
} catch (error) {
|
|
381
|
+
finish(error);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function resolveLoaderExport(mod: unknown): LoaderFn {
|
|
387
|
+
if (typeof mod === 'function') return mod as LoaderFn;
|
|
388
|
+
const withDefault = mod as { default?: unknown } | null | undefined;
|
|
389
|
+
if (withDefault && typeof withDefault.default === 'function') return withDefault.default as LoaderFn;
|
|
390
|
+
return mod as LoaderFn;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// A loader commonly returns CJS-style source on the assumption webpack's CJS/ESM interop will bind a
|
|
394
|
+
// default import to it. esbuild's build() pass does that automatically, but Bun's plugin onLoad - used
|
|
395
|
+
// for the runtime's per-file SSR loading - does not synthesize a default export from a bare
|
|
396
|
+
// `module.exports` assignment, so normalize to a real ESM default export.
|
|
397
|
+
function normalizeToEsmDefault(contents: string): string {
|
|
398
|
+
const match = /^\s*module\.exports\s*=\s*([\s\S]*?);?\s*$/.exec(contents);
|
|
399
|
+
return match ? `export default (${match[1]});` : contents;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function runLoaderChain(
|
|
403
|
+
root: string,
|
|
404
|
+
rule: WebpackLoaderRule,
|
|
405
|
+
resourcePath: string,
|
|
406
|
+
resourceQuery: string,
|
|
407
|
+
target: LoaderContextTarget,
|
|
408
|
+
config: ResolvedConfig | undefined,
|
|
409
|
+
): Promise<string> {
|
|
410
|
+
const requireFromRoot = createRequire(path.join(root, 'noop-require.cjs'));
|
|
411
|
+
// esbuild loads the materialized copy, but the loader context (`this.resourcePath`
|
|
412
|
+
// and the base dir for relative `importModule`/`getResolve`) must point at the
|
|
413
|
+
// original source so sibling modules resolve. Reading the materialized copy
|
|
414
|
+
// yields identical bytes.
|
|
415
|
+
const contextPath = originalResourcePath(resourcePath, config);
|
|
416
|
+
let content: string | Buffer = await readFileP(resourcePath);
|
|
417
|
+
for (const specifier of rule.loaders) {
|
|
418
|
+
const resolvedSpecifier = path.isAbsolute(specifier) ? specifier : requireFromRoot.resolve(specifier);
|
|
419
|
+
const mod: unknown = requireFromRoot(resolvedSpecifier);
|
|
420
|
+
const fn = resolveLoaderExport(mod);
|
|
421
|
+
const input = fn.raw ? toBuffer(content) : toStringContent(content);
|
|
422
|
+
content = await runLoader(fn, root, contextPath, resourceQuery, input, target, config);
|
|
423
|
+
}
|
|
424
|
+
return normalizeToEsmDefault(toStringContent(content));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ---------------------------------------------------------------------------
|
|
428
|
+
// `type` asset rules (no loaders)
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
|
|
431
|
+
/** asset/resource: emit the file under a hashed `/_next/static/media` URL, export the URL string. */
|
|
432
|
+
async function emitAssetResourceModule(
|
|
433
|
+
config: ResolvedConfig,
|
|
434
|
+
resourcePath: string,
|
|
435
|
+
bytes: Uint8Array,
|
|
436
|
+
): Promise<string> {
|
|
437
|
+
const ext = path.extname(resourcePath).toLowerCase() || '.bin';
|
|
438
|
+
const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 8);
|
|
439
|
+
const base = path.basename(resourcePath, path.extname(resourcePath)).replace(/[^A-Za-z0-9_-]+/g, '-');
|
|
440
|
+
const relative = path.posix.join('_next', 'static', 'media', `${base}.${hash}${ext}`);
|
|
441
|
+
const target = path.join(config.outPath, 'public', ...relative.split('/'));
|
|
442
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
443
|
+
if (!existsSync(target)) await writeFile(target, bytes);
|
|
444
|
+
const src = `/${relative}`;
|
|
445
|
+
return `const src = ${JSON.stringify(src)};\nexport default src;\nexport { src };\n`;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function loadTypeAssetModule(
|
|
449
|
+
rule: WebpackLoaderRule,
|
|
450
|
+
resourcePath: string,
|
|
451
|
+
config: ResolvedConfig | undefined,
|
|
452
|
+
): Promise<string> {
|
|
453
|
+
const bytes = new Uint8Array(await readFileP(resourcePath));
|
|
454
|
+
if (rule.assetType === 'bytes') {
|
|
455
|
+
return `export default new Uint8Array(${JSON.stringify(Array.from(bytes))});`;
|
|
456
|
+
}
|
|
457
|
+
if (rule.assetType === 'source') {
|
|
458
|
+
return `export default ${JSON.stringify(Buffer.from(bytes).toString('utf8'))};`;
|
|
459
|
+
}
|
|
460
|
+
// asset/resource — needs a config to know the output tree; without one, fall
|
|
461
|
+
// back to exporting the basename so a build still resolves the import.
|
|
462
|
+
if (config) return emitAssetResourceModule(config, resourcePath, bytes);
|
|
463
|
+
return `export default ${JSON.stringify(`/${path.basename(resourcePath)}`)};`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ---------------------------------------------------------------------------
|
|
467
|
+
// Rule-module loading (shared by esbuild plugins and the Bun runtime hook)
|
|
468
|
+
// ---------------------------------------------------------------------------
|
|
469
|
+
|
|
470
|
+
interface LoadedRuleModule {
|
|
471
|
+
contents: string;
|
|
472
|
+
loader: 'ts' | 'tsx' | 'jsx' | 'js';
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function loadRuleModule(
|
|
476
|
+
root: string,
|
|
477
|
+
rules: WebpackLoaderRule[],
|
|
478
|
+
resourcePath: string,
|
|
479
|
+
resourceQuery: string,
|
|
480
|
+
target: LoaderContextTarget,
|
|
481
|
+
config: ResolvedConfig | undefined,
|
|
482
|
+
): Promise<LoadedRuleModule | undefined> {
|
|
483
|
+
const rule = findRuleForResource(rules, resourcePath, resourceQuery);
|
|
484
|
+
if (!rule) return undefined;
|
|
485
|
+
if (rule.assetType) {
|
|
486
|
+
return { contents: await loadTypeAssetModule(rule, resourcePath, config), loader: 'js' };
|
|
487
|
+
}
|
|
488
|
+
const contents = await runLoaderChain(root, rule, resourcePath, resourceQuery, target, config);
|
|
489
|
+
return { contents, loader: outputLoader(rule, resourcePath) };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function ruleSuffixFilter(rules: WebpackLoaderRule[]): RegExp | undefined {
|
|
493
|
+
const suffixes = [
|
|
494
|
+
...new Set(
|
|
495
|
+
rules.filter(rule => rule.glob.startsWith('*')).map(rule => stripLeadingWildcard(rule.glob)),
|
|
496
|
+
),
|
|
497
|
+
];
|
|
498
|
+
if (suffixes.length === 0) return undefined;
|
|
499
|
+
return new RegExp(`(?:${suffixes.map(escapeRegExp).join('|')})$`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Build the alias-key onResolve filter (exact keys + trailing-`/*` prefixes). */
|
|
503
|
+
function aliasSpecifierFilter(): RegExp | undefined {
|
|
504
|
+
const keys = Object.keys(turbopackResolveAlias());
|
|
505
|
+
if (keys.length === 0) return undefined;
|
|
506
|
+
const alternatives = keys.map(key =>
|
|
507
|
+
key.endsWith('/*') ? escapeRegExp(key.slice(0, -1)) : `${escapeRegExp(key)}$`,
|
|
508
|
+
);
|
|
509
|
+
return new RegExp(`^(?:${alternatives.join('|')})`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// A null byte separates a virtual query module's real path from its threaded
|
|
513
|
+
// resourceQuery (real paths never contain a null byte); used for the Bun
|
|
514
|
+
// runtime namespace so two `?query` imports of one file stay distinct modules.
|
|
515
|
+
const QUERY_SEPARATOR = '\0';
|
|
516
|
+
const QUERY_NAMESPACE = 'pnext-webpack-loader-query';
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* esbuild plugin applying configured `turbopack.rules` loader chains (plus
|
|
520
|
+
* `resolveAlias` and `?query` threading). Returns `undefined` when the project
|
|
521
|
+
* configures no rules and no aliases, so callers can skip registering it.
|
|
522
|
+
*/
|
|
523
|
+
export function webpackLoaderRulesPlugin(
|
|
524
|
+
config: ResolvedConfig,
|
|
525
|
+
target: LoaderContextTarget,
|
|
526
|
+
): Plugin | undefined {
|
|
527
|
+
const rules = turbopackLoaderRules();
|
|
528
|
+
const filter = ruleSuffixFilter(rules);
|
|
529
|
+
const aliasFilter = aliasSpecifierFilter();
|
|
530
|
+
if (!filter && !aliasFilter) return undefined;
|
|
531
|
+
return {
|
|
532
|
+
name: 'pnext-compat-webpack-loader-rules',
|
|
533
|
+
setup(build) {
|
|
534
|
+
// `?query`-suffixed specifiers whose base matches a loader rule: strip the
|
|
535
|
+
// query, resolve the base (alias-aware), thread the query via esbuild's
|
|
536
|
+
// `suffix` so the two `?query` imports of one file stay distinct modules.
|
|
537
|
+
if (filter) {
|
|
538
|
+
build.onResolve({ filter: /\?/ }, args => {
|
|
539
|
+
const queryIndex = args.path.indexOf('?');
|
|
540
|
+
if (queryIndex === -1) return undefined;
|
|
541
|
+
const base = args.path.slice(0, queryIndex);
|
|
542
|
+
const query = args.path.slice(queryIndex);
|
|
543
|
+
const resolved = resolveSpecifier(config.root, args.resolveDir, base);
|
|
544
|
+
if (!resolved || !findRule(rules, resolved)) return undefined;
|
|
545
|
+
return { path: resolved, suffix: query };
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
// `resolveAlias` specifier rewrites (without a query — query handled above).
|
|
549
|
+
if (aliasFilter) {
|
|
550
|
+
build.onResolve({ filter: aliasFilter }, async args => {
|
|
551
|
+
if (args.path.includes('?')) return undefined;
|
|
552
|
+
// A user alias describes the APP's module graph. pnext's own runtime imports preact/compat to
|
|
553
|
+
// build the react shim, so a config alias redirecting preact/compat to react points the shim
|
|
554
|
+
// at the module pnext maps back to the shim - a self-import that throws on client bundle
|
|
555
|
+
// init, killing all client JS. Framework modules resolve unaliased.
|
|
556
|
+
if (isPNextRuntimeImporter(args.importer)) return undefined;
|
|
557
|
+
const resolved = resolveAliasSpecifier(args.path, config.root);
|
|
558
|
+
if (!resolved) return undefined;
|
|
559
|
+
const target = firstExisting(resolved) ?? resolved;
|
|
560
|
+
// A bare alias target (e.g. `'preact/compat': 'react'`) is not a
|
|
561
|
+
// file path — hand it back to esbuild's own resolver.
|
|
562
|
+
if (!path.isAbsolute(target)) {
|
|
563
|
+
if (target === args.path) return undefined;
|
|
564
|
+
return build.resolve(target, {
|
|
565
|
+
resolveDir: args.resolveDir,
|
|
566
|
+
kind: args.kind,
|
|
567
|
+
importer: args.importer,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return { path: target };
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
if (filter) {
|
|
574
|
+
build.onLoad({ filter, namespace: 'file' }, async args => {
|
|
575
|
+
const resourcePath = path.resolve(args.path);
|
|
576
|
+
const resourceQuery = args.suffix ?? '';
|
|
577
|
+
try {
|
|
578
|
+
const loaded = await loadRuleModule(
|
|
579
|
+
config.root,
|
|
580
|
+
rules,
|
|
581
|
+
resourcePath,
|
|
582
|
+
resourceQuery,
|
|
583
|
+
target,
|
|
584
|
+
config,
|
|
585
|
+
);
|
|
586
|
+
return loaded ?? undefined;
|
|
587
|
+
} catch (error) {
|
|
588
|
+
return { errors: [{ text: error instanceof Error ? error.message : String(error) }] };
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const runtimeRegisteredRoots = new Set<string>();
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Registers a Bun.plugin hook applying `turbopack.rules` loader chains (plus resolveAlias and query
|
|
600
|
+
* threading) for the SERVER runtime's own per-file module loading - the on-demand transform, not an
|
|
601
|
+
* esbuild build() pass. Idempotent per project root; a no-op when nothing is configured.
|
|
602
|
+
*/
|
|
603
|
+
export function registerWebpackLoaderRuleRuntime(config: ResolvedConfig): void {
|
|
604
|
+
const rules = turbopackLoaderRules();
|
|
605
|
+
const suffixFilter = ruleSuffixFilter(rules);
|
|
606
|
+
const aliasFilter = aliasSpecifierFilter();
|
|
607
|
+
if (!suffixFilter && !aliasFilter) return;
|
|
608
|
+
const root = path.resolve(config.root);
|
|
609
|
+
if (runtimeRegisteredRoots.has(root)) return;
|
|
610
|
+
runtimeRegisteredRoots.add(root);
|
|
611
|
+
const ruleFileFilter = suffixFilter
|
|
612
|
+
? new RegExp(`^${escapeRegExp(root)}(?:/.*)?${suffixFilter.source}`)
|
|
613
|
+
: undefined;
|
|
614
|
+
Bun.plugin({
|
|
615
|
+
name: `pnext-compat-webpack-loader-runtime-${root}`,
|
|
616
|
+
setup(plugin) {
|
|
617
|
+
// `?query`-suffixed specifiers whose base matches a loader rule: resolve
|
|
618
|
+
// the base (alias-aware), thread the query through the returned path so
|
|
619
|
+
// Bun keeps each `?query` import a distinct module (keyed by namespace +
|
|
620
|
+
// path). The onLoad below reads the query back off the path.
|
|
621
|
+
if (suffixFilter) {
|
|
622
|
+
plugin.onResolve({ filter: /\?/ }, args => {
|
|
623
|
+
const queryIndex = args.path.indexOf('?');
|
|
624
|
+
if (queryIndex === -1) return undefined;
|
|
625
|
+
const base = args.path.slice(0, queryIndex);
|
|
626
|
+
const query = args.path.slice(queryIndex);
|
|
627
|
+
const fromDir = args.importer ? path.dirname(args.importer) : root;
|
|
628
|
+
const resolved = resolveSpecifier(root, fromDir, base);
|
|
629
|
+
if (!resolved || !findRule(rules, resolved)) return undefined;
|
|
630
|
+
return { path: `${resolved}${QUERY_SEPARATOR}${query}`, namespace: QUERY_NAMESPACE };
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
// `resolveAlias` specifier rewrites (without a query — query handled above).
|
|
634
|
+
if (aliasFilter) {
|
|
635
|
+
plugin.onResolve({ filter: aliasFilter }, args => {
|
|
636
|
+
if (args.path.includes('?')) return undefined;
|
|
637
|
+
// See the build-time alias hook: pnext's own runtime graph is exempt
|
|
638
|
+
// from user aliases, or the react shim self-imports.
|
|
639
|
+
if (isPNextRuntimeImporter(args.importer)) return undefined;
|
|
640
|
+
const resolved = resolveAliasSpecifier(args.path, root);
|
|
641
|
+
if (!resolved) return undefined;
|
|
642
|
+
const target = firstExisting(resolved) ?? resolved;
|
|
643
|
+
// Bare alias target: resolve through Bun's resolver relative to the
|
|
644
|
+
// importer so a specifier like 'react' becomes a real file path.
|
|
645
|
+
if (!path.isAbsolute(target)) {
|
|
646
|
+
if (target === args.path) return undefined;
|
|
647
|
+
const fromDir = args.importer ? path.dirname(args.importer) : root;
|
|
648
|
+
try {
|
|
649
|
+
return { path: Bun.resolveSync(target, fromDir) };
|
|
650
|
+
} catch {
|
|
651
|
+
return undefined;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return { path: target };
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
plugin.onLoad({ filter: /.*/, namespace: QUERY_NAMESPACE }, async ({ path: virtualPath }) => {
|
|
658
|
+
const sep = virtualPath.indexOf(QUERY_SEPARATOR);
|
|
659
|
+
const realPath = sep === -1 ? virtualPath : virtualPath.slice(0, sep);
|
|
660
|
+
const resourceQuery = sep === -1 ? '' : virtualPath.slice(sep + 1);
|
|
661
|
+
const loaded = await loadRuleModule(root, rules, realPath, resourceQuery, 'server', config);
|
|
662
|
+
return loaded ?? { contents: await readFileP(realPath, 'utf8'), loader: 'text' };
|
|
663
|
+
});
|
|
664
|
+
// `.mjs` sources are NOT claimed by core's runtime load hook (its filter is `[jt]sx?`), so they
|
|
665
|
+
// never see the registered server source transforms - including the resolveAlias rewrite. Bun then
|
|
666
|
+
// resolves their bare imports natively and an aliased specifier throws, since its onResolve hook is
|
|
667
|
+
// never consulted for bare specifiers. Claim them here; Bun rejects an undefined onLoad return, so
|
|
668
|
+
// the hook always hands the source back. Kept synchronous, and `.cjs` deliberately left out: an
|
|
669
|
+
// onLoad hook turns the module async, which breaks require() of the materialized next.config.
|
|
670
|
+
if (aliasFilter) {
|
|
671
|
+
const esmFilter = new RegExp(`^${escapeRegExp(root)}(?!.*/node_modules/)(?:/.*)?\\.mjs$`);
|
|
672
|
+
plugin.onLoad({ filter: esmFilter }, ({ path: file }) => ({
|
|
673
|
+
contents: rewriteResolveAliasImports(readFileSync(file, 'utf8'), file, root),
|
|
674
|
+
loader: 'js',
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
if (ruleFileFilter) {
|
|
678
|
+
plugin.onLoad({ filter: ruleFileFilter }, async ({ path: file }) => {
|
|
679
|
+
const loaded = await loadRuleModule(root, rules, path.resolve(file), '', 'server', config);
|
|
680
|
+
return loaded ?? { contents: await readFileP(file, 'utf8'), loader: 'text' };
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
}
|