vinext 0.1.8 → 0.2.0
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 +51 -30
- package/dist/build/inject-pregenerated-paths.d.ts +4 -0
- package/dist/build/inject-pregenerated-paths.js +18 -0
- package/dist/build/layout-classification.js +1 -1
- package/dist/build/nitro-route-rules.js +1 -1
- package/dist/build/pages-client-assets-module.d.ts +11 -0
- package/dist/build/pages-client-assets-module.js +27 -0
- package/dist/build/prerender.d.ts +2 -1
- package/dist/build/prerender.js +13 -6
- package/dist/build/report.d.ts +13 -2
- package/dist/build/report.js +99 -2
- package/dist/build/run-prerender.d.ts +7 -0
- package/dist/build/run-prerender.js +10 -1
- package/dist/build/standalone.js +2 -0
- package/dist/cache/cache-adapters-virtual.js +3 -3
- package/dist/check.js +40 -2
- package/dist/cli-args.d.ts +1 -2
- package/dist/cli-args.js +1 -1
- package/dist/cli-dev-config.d.ts +12 -0
- package/dist/cli-dev-config.js +23 -0
- package/dist/cli.js +111 -95
- package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
- package/dist/{server → client}/dev-error-overlay-store.js +1 -1
- package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
- package/dist/{server → client}/dev-error-overlay.js +2 -2
- package/dist/client/navigation-runtime.d.ts +2 -0
- package/dist/client/navigation-runtime.js +3 -1
- package/dist/client/window-next.d.ts +2 -1
- package/dist/cloudflare/index.d.ts +2 -2
- package/dist/cloudflare/index.js +2 -2
- package/dist/config/config-matchers.js +14 -10
- package/dist/config/next-config.d.ts +5 -3
- package/dist/config/next-config.js +44 -1
- package/dist/config/prerender.d.ts +34 -0
- package/dist/config/prerender.js +55 -0
- package/dist/entries/app-rsc-entry.d.ts +2 -1
- package/dist/entries/app-rsc-entry.js +40 -10
- package/dist/entries/app-rsc-manifest.js +2 -0
- package/dist/entries/pages-client-entry.d.ts +3 -0
- package/dist/entries/pages-client-entry.js +15 -1
- package/dist/entries/pages-server-entry.js +5 -2
- package/dist/image/image-adapters-virtual.d.ts +59 -0
- package/dist/image/image-adapters-virtual.js +59 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +167 -100
- package/dist/init-cloudflare.d.ts +43 -0
- package/dist/init-cloudflare.js +839 -0
- package/dist/init-platform.d.ts +40 -0
- package/dist/init-platform.js +195 -0
- package/dist/init.d.ts +16 -38
- package/dist/init.js +209 -99
- package/dist/node_modules/.pnpm/am-i-vibing@0.5.0/node_modules/am-i-vibing/dist/detector-1yx2Hoe0.js +294 -0
- package/dist/node_modules/.pnpm/process-ancestry@0.1.0/node_modules/process-ancestry/dist/index.js +94 -0
- package/dist/{cloudflare → packages/cloudflare}/src/cache/cdn-adapter.runtime.js +1 -1
- package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.d.ts +2 -2
- package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.js +1 -1
- package/dist/packages/cloudflare/src/deploy-config.js +150 -0
- package/dist/packages/cloudflare/src/deploy-help.js +55 -0
- package/dist/packages/cloudflare/src/deploy.js +276 -0
- package/dist/{cloudflare → packages/cloudflare/src}/tpr.d.ts +4 -41
- package/dist/{cloudflare → packages/cloudflare/src}/tpr.js +18 -15
- package/dist/plugins/ast-scope.d.ts +16 -0
- package/dist/plugins/ast-scope.js +62 -0
- package/dist/plugins/ast-utils.d.ts +27 -1
- package/dist/plugins/ast-utils.js +32 -1
- package/dist/plugins/css-module-imports.d.ts +14 -0
- package/dist/plugins/css-module-imports.js +59 -0
- package/dist/plugins/extensionless-dynamic-import.js +2 -2
- package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
- package/dist/plugins/ignore-dynamic-requests.js +530 -0
- package/dist/plugins/typeof-window.d.ts +1 -1
- package/dist/plugins/typeof-window.js +28 -56
- package/dist/routing/app-route-graph.d.ts +23 -2
- package/dist/routing/app-route-graph.js +126 -32
- package/dist/routing/file-matcher.d.ts +3 -0
- package/dist/routing/file-matcher.js +3 -0
- package/dist/routing/pages-router.js +2 -2
- package/dist/server/api-handler.js +9 -14
- package/dist/server/app-browser-action-result.d.ts +4 -1
- package/dist/server/app-browser-action-result.js +8 -1
- package/dist/server/app-browser-entry.js +161 -52
- package/dist/server/app-browser-interception-context.d.ts +1 -0
- package/dist/server/app-browser-interception-context.js +4 -2
- package/dist/server/app-browser-navigation-controller.d.ts +2 -0
- package/dist/server/app-browser-navigation-controller.js +60 -31
- package/dist/server/app-browser-server-action-client.d.ts +1 -1
- package/dist/server/app-browser-server-action-client.js +3 -4
- package/dist/server/app-browser-state.d.ts +11 -1
- package/dist/server/app-browser-state.js +25 -8
- package/dist/server/app-browser-visible-commit.d.ts +6 -1
- package/dist/server/app-browser-visible-commit.js +43 -25
- package/dist/server/app-elements-wire.d.ts +9 -2
- package/dist/server/app-elements-wire.js +8 -1
- package/dist/server/app-elements.d.ts +2 -2
- package/dist/server/app-elements.js +4 -3
- package/dist/server/app-fallback-renderer.d.ts +2 -1
- package/dist/server/app-fallback-renderer.js +10 -6
- package/dist/server/app-hydration-cache-publication.d.ts +11 -0
- package/dist/server/app-hydration-cache-publication.js +43 -0
- package/dist/server/app-middleware.d.ts +4 -3
- package/dist/server/app-middleware.js +8 -3
- package/dist/server/app-optimistic-routing.js +11 -4
- package/dist/server/app-page-cache-finalizer.js +2 -1
- package/dist/server/app-page-cache-render.d.ts +1 -0
- package/dist/server/app-page-cache-render.js +8 -4
- package/dist/server/app-page-cache.d.ts +1 -0
- package/dist/server/app-page-cache.js +5 -2
- package/dist/server/app-page-dispatch.js +1 -0
- package/dist/server/app-page-element-builder.d.ts +2 -1
- package/dist/server/app-page-element-builder.js +58 -4
- package/dist/server/app-page-probe.js +5 -0
- package/dist/server/app-page-render-identity.d.ts +1 -0
- package/dist/server/app-page-render-identity.js +1 -1
- package/dist/server/app-page-render.js +18 -0
- package/dist/server/app-page-request.d.ts +14 -0
- package/dist/server/app-page-request.js +69 -4
- package/dist/server/app-page-route-wiring.d.ts +8 -1
- package/dist/server/app-page-route-wiring.js +32 -18
- package/dist/server/app-page-segment-state.d.ts +2 -1
- package/dist/server/app-page-segment-state.js +3 -1
- package/dist/server/app-page-stream.d.ts +5 -0
- package/dist/server/app-page-stream.js +3 -1
- package/dist/server/app-route-handler-execution.js +5 -1
- package/dist/server/app-route-handler-response.js +1 -0
- package/dist/server/app-route-handler-runtime.js +1 -1
- package/dist/server/app-router-entry.js +9 -2
- package/dist/server/app-rsc-handler.d.ts +1 -0
- package/dist/server/app-rsc-handler.js +62 -31
- package/dist/server/app-rsc-request-normalization.d.ts +3 -2
- package/dist/server/app-rsc-request-normalization.js +6 -3
- package/dist/server/app-segment-config.d.ts +11 -0
- package/dist/server/app-segment-config.js +62 -6
- package/dist/server/app-server-action-execution.d.ts +1 -0
- package/dist/server/app-server-action-execution.js +58 -28
- package/dist/server/app-ssr-entry.d.ts +3 -0
- package/dist/server/app-ssr-entry.js +15 -7
- package/dist/server/app-ssr-stream.d.ts +7 -3
- package/dist/server/app-ssr-stream.js +10 -6
- package/dist/server/app-visited-response-cache.d.ts +6 -0
- package/dist/server/app-visited-response-cache.js +3 -1
- package/dist/server/client-trace-metadata.js +26 -0
- package/dist/server/default-global-not-found-module.d.ts +14 -0
- package/dist/server/default-global-not-found-module.js +14 -0
- package/dist/server/dev-route-files.js +4 -0
- package/dist/server/dev-server.js +19 -18
- package/dist/server/dev-stack-sourcemap.d.ts +1 -1
- package/dist/server/dev-stack-sourcemap.js +1 -1
- package/dist/server/fetch-handler.d.ts +2 -0
- package/dist/server/fetch-handler.js +18 -0
- package/dist/server/headers.d.ts +3 -15
- package/dist/server/headers.js +2 -15
- package/dist/server/image-optimization.d.ts +51 -1
- package/dist/server/image-optimization.js +52 -2
- package/dist/server/implicit-tags.js +1 -1
- package/dist/server/isr-cache.d.ts +1 -1
- package/dist/server/isr-cache.js +2 -2
- package/dist/server/metadata-routes.d.ts +3 -1
- package/dist/server/metadata-routes.js +12 -2
- package/dist/server/middleware-runtime.js +6 -1
- package/dist/server/navigation-planner.d.ts +1 -0
- package/dist/server/navigation-planner.js +14 -3
- package/dist/server/pages-api-route.js +2 -2
- package/dist/server/pages-asset-tags.d.ts +4 -6
- package/dist/server/pages-asset-tags.js +12 -12
- package/dist/server/pages-client-assets.d.ts +12 -0
- package/dist/server/pages-client-assets.js +10 -0
- package/dist/server/pages-node-compat.d.ts +8 -1
- package/dist/server/pages-node-compat.js +32 -4
- package/dist/server/pages-page-data.d.ts +24 -1
- package/dist/server/pages-page-data.js +45 -24
- package/dist/server/pages-page-handler.d.ts +2 -1
- package/dist/server/pages-page-handler.js +19 -12
- package/dist/server/pages-page-response.d.ts +7 -0
- package/dist/server/pages-page-response.js +4 -1
- package/dist/server/pages-request-pipeline.d.ts +2 -0
- package/dist/server/pages-request-pipeline.js +25 -1
- package/dist/server/pages-router-entry.d.ts +30 -0
- package/dist/server/pages-router-entry.js +108 -0
- package/dist/server/prerender-manifest.d.ts +3 -1
- package/dist/server/prerender-route-params.js +1 -1
- package/dist/server/prod-server.d.ts +1 -1
- package/dist/server/prod-server.js +33 -20
- package/dist/server/request-pipeline.js +1 -0
- package/dist/server/seed-cache.js +4 -4
- package/dist/shims/app-router-scroll-state.d.ts +1 -0
- package/dist/shims/app-router-scroll-state.js +1 -0
- package/dist/shims/app-router-scroll.js +2 -1
- package/dist/shims/cache.js +19 -15
- package/dist/shims/cdn-cache.js +1 -1
- package/dist/shims/dynamic-preload-chunks.js +2 -1
- package/dist/shims/form.d.ts +3 -1
- package/dist/shims/form.js +37 -43
- package/dist/shims/headers.d.ts +7 -1
- package/dist/shims/headers.js +17 -3
- package/dist/shims/image.js +75 -14
- package/dist/shims/internal/app-route-detection.d.ts +2 -17
- package/dist/shims/internal/app-route-detection.js +4 -17
- package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +23 -0
- package/dist/shims/internal/hybrid-client-route-owner-direct.js +51 -0
- package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -15
- package/dist/shims/internal/hybrid-client-route-owner.js +9 -60
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +9 -5
- package/dist/shims/internal/pages-data-fetch-dedup.js +73 -14
- package/dist/shims/internal/pages-data-target.d.ts +10 -9
- package/dist/shims/internal/pages-data-target.js +91 -12
- package/dist/shims/internal/pages-router-components.d.ts +7 -0
- package/dist/shims/internal/pages-router-components.js +13 -0
- package/dist/shims/layout-segment-context.d.ts +4 -1
- package/dist/shims/layout-segment-context.js +18 -4
- package/dist/shims/link.js +55 -26
- package/dist/shims/navigation.d.ts +5 -1
- package/dist/shims/navigation.js +45 -10
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/root-params.d.ts +15 -1
- package/dist/shims/root-params.js +21 -1
- package/dist/shims/router.d.ts +5 -7
- package/dist/shims/router.js +362 -93
- package/dist/shims/server.js +3 -2
- package/dist/shims/slot.js +3 -6
- package/dist/utils/client-runtime-metadata.d.ts +2 -18
- package/dist/utils/client-runtime-metadata.js +31 -22
- package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
- package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
- package/dist/utils/domain-locale.d.ts +6 -3
- package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
- package/dist/{server → utils}/middleware-request-headers.js +2 -2
- package/dist/utils/project.d.ts +48 -2
- package/dist/utils/project.js +225 -4
- package/dist/utils/protocol-headers.d.ts +17 -0
- package/dist/utils/protocol-headers.js +17 -0
- package/dist/utils/react-version.d.ts +4 -0
- package/dist/utils/react-version.js +44 -0
- package/package.json +47 -2
- package/dist/deploy.d.ts +0 -197
- package/dist/deploy.js +0 -1040
- package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
- /package/dist/{cloudflare → packages/cloudflare}/src/utils/cache-control-metadata.js +0 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { detectProject, findInNodeModules, formatMissingCloudflarePluginError, getMissingDeps } from "../../../utils/project.js";
|
|
2
|
+
import { loadNextConfig, resolveNextConfig } from "../../../config/next-config.js";
|
|
3
|
+
import { formatVinextPrerenderLabel, loadVinextPrerenderConfigFromViteConfig, resolveVinextPrerenderDecision } from "../../../config/prerender.js";
|
|
4
|
+
import { runPrerender } from "../../../build/run-prerender.js";
|
|
5
|
+
import { loadDotenv } from "../../../config/dotenv.js";
|
|
6
|
+
import { runTPR } from "./tpr.js";
|
|
7
|
+
import { formatImageOptimizationHint, formatMissingCacheAdapterError, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, viteConfigHasImageAdapter, workerEntryHasCacheHandler } from "./deploy-config.js";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { pathToFileURL } from "node:url";
|
|
12
|
+
import { parseArgs } from "node:util";
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
//#region ../cloudflare/src/deploy.ts
|
|
15
|
+
/**
|
|
16
|
+
* vinext-cloudflare deploy — one-command Cloudflare Workers deployment.
|
|
17
|
+
*
|
|
18
|
+
* Takes any Next.js app and deploys it to Cloudflare Workers:
|
|
19
|
+
*
|
|
20
|
+
* 1. Validates the project was prepared by `vinext init --platform=cloudflare`
|
|
21
|
+
* 2. Runs the Vite build
|
|
22
|
+
* 3. Deploys to Cloudflare Workers via Wrangler
|
|
23
|
+
*/
|
|
24
|
+
function parsePositiveIntegerArg(raw, flag) {
|
|
25
|
+
if (raw === "") throw new Error(`${flag} requires a value, but none was provided.`);
|
|
26
|
+
const parsed = Number(raw);
|
|
27
|
+
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${flag} expects a positive integer, but got "${raw}".`);
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
/** Deploy command flag definitions for util.parseArgs. */
|
|
31
|
+
const deployArgOptions = {
|
|
32
|
+
help: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
short: "h",
|
|
35
|
+
default: false
|
|
36
|
+
},
|
|
37
|
+
preview: {
|
|
38
|
+
type: "boolean",
|
|
39
|
+
default: false
|
|
40
|
+
},
|
|
41
|
+
env: { type: "string" },
|
|
42
|
+
name: { type: "string" },
|
|
43
|
+
"skip-build": {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
default: false
|
|
46
|
+
},
|
|
47
|
+
"dry-run": {
|
|
48
|
+
type: "boolean",
|
|
49
|
+
default: false
|
|
50
|
+
},
|
|
51
|
+
"prerender-all": {
|
|
52
|
+
type: "boolean",
|
|
53
|
+
default: false
|
|
54
|
+
},
|
|
55
|
+
"prerender-concurrency": { type: "string" },
|
|
56
|
+
"experimental-tpr": {
|
|
57
|
+
type: "boolean",
|
|
58
|
+
default: false
|
|
59
|
+
},
|
|
60
|
+
"tpr-coverage": { type: "string" },
|
|
61
|
+
"tpr-limit": { type: "string" },
|
|
62
|
+
"tpr-window": { type: "string" }
|
|
63
|
+
};
|
|
64
|
+
function parseDeployArgs(args) {
|
|
65
|
+
const { values } = parseArgs({
|
|
66
|
+
args,
|
|
67
|
+
options: deployArgOptions,
|
|
68
|
+
strict: true
|
|
69
|
+
});
|
|
70
|
+
function parseIntArg(name, raw) {
|
|
71
|
+
if (!raw) return void 0;
|
|
72
|
+
const n = parseInt(raw, 10);
|
|
73
|
+
if (isNaN(n)) {
|
|
74
|
+
console.error(` --${name} must be a number (got: ${raw})`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
return n;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
help: values.help,
|
|
81
|
+
preview: values.preview,
|
|
82
|
+
env: values.env?.trim() || void 0,
|
|
83
|
+
name: values.name?.trim() || void 0,
|
|
84
|
+
skipBuild: values["skip-build"],
|
|
85
|
+
dryRun: values["dry-run"],
|
|
86
|
+
prerenderAll: values["prerender-all"],
|
|
87
|
+
prerenderConcurrency: values["prerender-concurrency"] === void 0 ? void 0 : parsePositiveIntegerArg(values["prerender-concurrency"], "--prerender-concurrency"),
|
|
88
|
+
experimentalTPR: values["experimental-tpr"],
|
|
89
|
+
tprCoverage: parseIntArg("tpr-coverage", values["tpr-coverage"]),
|
|
90
|
+
tprLimit: parseIntArg("tpr-limit", values["tpr-limit"]),
|
|
91
|
+
tprWindow: parseIntArg("tpr-window", values["tpr-window"])
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Run a function with `process.env.CLOUDFLARE_ENV` set to the given value,
|
|
96
|
+
* restoring the previous state (whether set or absent) after the function
|
|
97
|
+
* resolves or throws.
|
|
98
|
+
*
|
|
99
|
+
* The `@cloudflare/vite-plugin` reads `CLOUDFLARE_ENV` from `process.env` to
|
|
100
|
+
* drive the multi-environment merge applied to the emitted `wrangler.json`.
|
|
101
|
+
* Without this propagation the `--env <name>` CLI flag is silently ignored at
|
|
102
|
+
* build time and the top-level config is emitted regardless. See issue #1210.
|
|
103
|
+
*
|
|
104
|
+
* Passing `undefined` is a no-op; the callback runs with `process.env` untouched.
|
|
105
|
+
*/
|
|
106
|
+
async function withCloudflareEnv(env, fn) {
|
|
107
|
+
if (env === void 0 || env === "") return fn();
|
|
108
|
+
const hadPrev = "CLOUDFLARE_ENV" in process.env;
|
|
109
|
+
const prev = process.env.CLOUDFLARE_ENV;
|
|
110
|
+
process.env.CLOUDFLARE_ENV = env;
|
|
111
|
+
try {
|
|
112
|
+
return await fn();
|
|
113
|
+
} finally {
|
|
114
|
+
if (hadPrev) process.env.CLOUDFLARE_ENV = prev;
|
|
115
|
+
else delete process.env.CLOUDFLARE_ENV;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function loadProjectViteApi(root) {
|
|
119
|
+
let vitePath;
|
|
120
|
+
try {
|
|
121
|
+
vitePath = createRequire(path.join(root, "package.json")).resolve("vite");
|
|
122
|
+
} catch {
|
|
123
|
+
vitePath = "vite";
|
|
124
|
+
}
|
|
125
|
+
return await (vitePath === "vite" ? import(vitePath) : import(pathToFileURL(vitePath).href));
|
|
126
|
+
}
|
|
127
|
+
async function runBuild(info, env) {
|
|
128
|
+
console.log("\n Building for Cloudflare Workers...\n");
|
|
129
|
+
const { createBuilder } = await loadProjectViteApi(info.root);
|
|
130
|
+
await withCloudflareEnv(env, async () => {
|
|
131
|
+
await (await createBuilder({ root: info.root })).buildApp();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function validateWranglerEnvName(env) {
|
|
135
|
+
if (env.includes("\0")) throw new Error("Wrangler environment names cannot contain null bytes.");
|
|
136
|
+
return env;
|
|
137
|
+
}
|
|
138
|
+
function buildWranglerDeployArgs(options) {
|
|
139
|
+
const args = ["deploy"];
|
|
140
|
+
const env = options.env || (options.preview ? "preview" : void 0);
|
|
141
|
+
if (env) args.push("--env", validateWranglerEnvName(env));
|
|
142
|
+
return {
|
|
143
|
+
args,
|
|
144
|
+
env
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Resolve Wrangler's JavaScript CLI entrypoint in node_modules.
|
|
149
|
+
*
|
|
150
|
+
* Invoking the JavaScript file through `process.execPath` avoids the `.cmd`
|
|
151
|
+
* shim and command shell that package managers create on Windows.
|
|
152
|
+
*/
|
|
153
|
+
function resolveWranglerBin(root, resolvePackageJson = (projectRoot) => {
|
|
154
|
+
try {
|
|
155
|
+
return createRequire(path.join(projectRoot, "package.json")).resolve("wrangler/package.json");
|
|
156
|
+
} catch {
|
|
157
|
+
return findInNodeModules(projectRoot, "wrangler/package.json");
|
|
158
|
+
}
|
|
159
|
+
}) {
|
|
160
|
+
const packageJsonPath = resolvePackageJson(root);
|
|
161
|
+
if (packageJsonPath) {
|
|
162
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
163
|
+
const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.wrangler;
|
|
164
|
+
if (bin) return path.resolve(path.dirname(packageJsonPath), bin);
|
|
165
|
+
}
|
|
166
|
+
return path.join(root, "node_modules", "wrangler", "bin", "wrangler.js");
|
|
167
|
+
}
|
|
168
|
+
function buildNodeCliInvocation(scriptPath, args, nodeExecutable = process.execPath) {
|
|
169
|
+
return {
|
|
170
|
+
file: nodeExecutable,
|
|
171
|
+
args: [scriptPath, ...args]
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function buildWranglerInvocation(root, options, nodeExecutable = process.execPath) {
|
|
175
|
+
const wranglerBin = resolveWranglerBin(root);
|
|
176
|
+
const { args, env } = buildWranglerDeployArgs(options);
|
|
177
|
+
return {
|
|
178
|
+
...buildNodeCliInvocation(wranglerBin, args, nodeExecutable),
|
|
179
|
+
env
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function runWranglerDeploy(root, options, execute = execFileSync) {
|
|
183
|
+
const execOpts = {
|
|
184
|
+
cwd: root,
|
|
185
|
+
stdio: "pipe",
|
|
186
|
+
encoding: "utf-8",
|
|
187
|
+
shell: false
|
|
188
|
+
};
|
|
189
|
+
const { file, args, env } = buildWranglerInvocation(root, options);
|
|
190
|
+
if (env) console.log(`\n Deploying to env: ${env}...`);
|
|
191
|
+
else console.log("\n Deploying to production...");
|
|
192
|
+
const output = execute(file, args, execOpts);
|
|
193
|
+
const urlMatch = output.match(/https:\/\/[^\s]+\.workers\.dev[^\s]*/);
|
|
194
|
+
const deployedUrl = urlMatch ? urlMatch[0] : null;
|
|
195
|
+
if (output.trim()) for (const line of output.trim().split("\n")) console.log(` ${line}`);
|
|
196
|
+
return deployedUrl ?? "(URL not detected in wrangler output)";
|
|
197
|
+
}
|
|
198
|
+
async function deploy(options) {
|
|
199
|
+
const deployEnv = validateWranglerEnvName(options.env || (options.preview ? "preview" : "production"));
|
|
200
|
+
const root = path.resolve(options.root);
|
|
201
|
+
loadDotenv({
|
|
202
|
+
root,
|
|
203
|
+
mode: "production"
|
|
204
|
+
});
|
|
205
|
+
console.log("\n vinext-cloudflare deploy\n");
|
|
206
|
+
const info = detectProject(root);
|
|
207
|
+
if (!info.isAppRouter && !info.isPagesRouter) {
|
|
208
|
+
console.error(" Error: No app/ or pages/ directory found.");
|
|
209
|
+
console.error(" vinext-cloudflare deploy requires a Next.js project with an app/ or pages/ directory");
|
|
210
|
+
console.error(" (also checks src/app/ and src/pages/).\n");
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
if (options.name) info.projectName = options.name;
|
|
214
|
+
console.log(` Project: ${info.projectName}`);
|
|
215
|
+
console.log(` Router: ${info.isAppRouter ? "App Router" : "Pages Router"}`);
|
|
216
|
+
console.log(` ISR: ${info.hasISR ? "detected" : "none"}`);
|
|
217
|
+
const missingScaffolding = [
|
|
218
|
+
!info.hasViteConfig && "Vite config",
|
|
219
|
+
!info.hasWranglerConfig && "Wrangler config",
|
|
220
|
+
info.isPagesRouter && !info.hasWorkerEntry && "Worker entry"
|
|
221
|
+
].filter((value) => Boolean(value));
|
|
222
|
+
if (missingScaffolding.length > 0) throw new Error(`Missing Cloudflare deployment setup: ${missingScaffolding.join(", ")}. Run \`vinext init --platform=cloudflare\` first.`);
|
|
223
|
+
const missingDeps = getMissingDeps(info);
|
|
224
|
+
if (missingDeps.length > 0) throw new Error(`Missing deployment dependencies: ${missingDeps.map((dependency) => dependency.name).join(", ")}. Run \`vinext init --platform=cloudflare\` first.`);
|
|
225
|
+
if (info.hasViteConfig && !viteConfigHasCloudflarePlugin(root)) throw new Error(formatMissingCloudflarePluginError({ isAppRouter: info.isAppRouter }));
|
|
226
|
+
if (info.hasISR && !viteConfigHasCacheAdapter(root) && !workerEntryHasCacheHandler(root)) throw new Error(formatMissingCacheAdapterError({}));
|
|
227
|
+
if (!viteConfigHasImageAdapter(root)) {
|
|
228
|
+
console.log();
|
|
229
|
+
console.log(formatImageOptimizationHint());
|
|
230
|
+
}
|
|
231
|
+
if (options.dryRun) {
|
|
232
|
+
console.log("\n Dry run complete. No build or deploy performed.\n");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const buildEnv = deployEnv === "production" && !options.env ? void 0 : deployEnv;
|
|
236
|
+
if (!options.skipBuild) await runBuild(info, buildEnv);
|
|
237
|
+
else console.log("\n Skipping build (--skip-build)");
|
|
238
|
+
{
|
|
239
|
+
const nextConfig = await resolveNextConfig(await loadNextConfig(info.root), info.root);
|
|
240
|
+
const vinextPrerenderConfig = !options.prerenderAll && nextConfig.output !== "export" ? await withCloudflareEnv(buildEnv, async () => {
|
|
241
|
+
return loadVinextPrerenderConfigFromViteConfig(await loadProjectViteApi(info.root), info.root);
|
|
242
|
+
}) : null;
|
|
243
|
+
const prerenderDecision = resolveVinextPrerenderDecision({
|
|
244
|
+
prerenderAllFlag: options.prerenderAll,
|
|
245
|
+
vinextPrerenderConfig,
|
|
246
|
+
nextOutput: nextConfig.output
|
|
247
|
+
});
|
|
248
|
+
if (prerenderDecision) {
|
|
249
|
+
console.log(`\n ${formatVinextPrerenderLabel(prerenderDecision)}`);
|
|
250
|
+
if (nextConfig.enablePrerenderSourceMaps) {
|
|
251
|
+
process.setSourceMapsEnabled(true);
|
|
252
|
+
Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 50);
|
|
253
|
+
}
|
|
254
|
+
await runPrerender({
|
|
255
|
+
root: info.root,
|
|
256
|
+
concurrency: options.prerenderConcurrency
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (options.experimentalTPR) {
|
|
261
|
+
console.log();
|
|
262
|
+
const tprResult = await runTPR({
|
|
263
|
+
root,
|
|
264
|
+
coverage: Math.max(1, Math.min(100, options.tprCoverage ?? 90)),
|
|
265
|
+
limit: Math.max(1, options.tprLimit ?? 1e3),
|
|
266
|
+
window: Math.max(1, options.tprWindow ?? 24)
|
|
267
|
+
});
|
|
268
|
+
if (tprResult.skipped) console.log(` TPR: Skipped (${tprResult.skipped})`);
|
|
269
|
+
}
|
|
270
|
+
const url = runWranglerDeploy(root, { env: deployEnv === "production" && !options.env ? void 0 : deployEnv });
|
|
271
|
+
console.log("\n ─────────────────────────────────────────");
|
|
272
|
+
console.log(` Deployed to: ${url}`);
|
|
273
|
+
console.log(" ─────────────────────────────────────────\n");
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
export { deploy, parseDeployArgs };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region ../cloudflare/src/tpr.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* TPR: Traffic-aware Pre-Rendering
|
|
4
4
|
*
|
|
@@ -33,50 +33,13 @@ type TPRResult = {
|
|
|
33
33
|
durationMs: number; /** If TPR was skipped, the reason. */
|
|
34
34
|
skipped?: string;
|
|
35
35
|
};
|
|
36
|
-
type PrerenderResult = {
|
|
37
|
-
html: string;
|
|
38
|
-
status: number;
|
|
39
|
-
headers: Record<string, string>;
|
|
40
|
-
};
|
|
41
|
-
type WranglerConfig = {
|
|
42
|
-
accountId?: string;
|
|
43
|
-
kvNamespaceId?: string;
|
|
44
|
-
customDomain?: string;
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* Parse wrangler config (JSONC or TOML) to extract the fields TPR needs:
|
|
48
|
-
* account_id, VINEXT_KV_CACHE KV namespace ID, and custom domain.
|
|
49
|
-
*/
|
|
50
|
-
declare function parseWranglerConfig(root: string): WranglerConfig | null;
|
|
51
|
-
/**
|
|
52
|
-
* Generate zone lookup candidates from shortest (2-part) to longest.
|
|
53
|
-
* Tries the most common case first (e.g., "example.com") and progressively
|
|
54
|
-
* adds labels for multi-part TLDs (e.g., "co.uk" → "example.co.uk").
|
|
55
|
-
*
|
|
56
|
-
* "shop.example.com" → ["example.com", "shop.example.com"]
|
|
57
|
-
* "shop.example.co.uk" → ["co.uk", "example.co.uk", "shop.example.co.uk"]
|
|
58
|
-
* "example.com" → ["example.com"]
|
|
59
|
-
*/
|
|
60
|
-
declare function domainCandidates(domain: string): string[];
|
|
61
|
-
/**
|
|
62
|
-
* Build KV bulk API pairs from pre-rendered entries.
|
|
63
|
-
*
|
|
64
|
-
* Key format matches the runtime KVCacheHandler exactly:
|
|
65
|
-
* ENTRY_PREFIX + isrCacheKey("app", pathname, buildId) + ":html"
|
|
66
|
-
* → "cache:app:<buildId>:<pathname>:html"
|
|
67
|
-
*/
|
|
68
|
-
declare function buildTprKVPairs(entries: Map<string, PrerenderResult>, buildId: string | undefined, defaultRevalidateSeconds: number): Array<{
|
|
69
|
-
key: string;
|
|
70
|
-
value: string;
|
|
71
|
-
expiration_ttl: number;
|
|
72
|
-
}>;
|
|
73
36
|
/**
|
|
74
37
|
* Run the TPR pipeline: query traffic, select routes, pre-render, upload.
|
|
75
38
|
*
|
|
76
|
-
* Designed to be called between the build step and wrangler deploy in
|
|
77
|
-
*
|
|
39
|
+
* Designed to be called between the build step and wrangler deploy in the
|
|
40
|
+
* `vinext-cloudflare deploy` pipeline. Gracefully skips (never errors) when
|
|
78
41
|
* the prerequisites aren't met.
|
|
79
42
|
*/
|
|
80
43
|
declare function runTPR(options: TPROptions): Promise<TPRResult>;
|
|
81
44
|
//#endregion
|
|
82
|
-
export { TPROptions, TPRResult,
|
|
45
|
+
export { TPROptions, TPRResult, runTPR };
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { VINEXT_REVALIDATE_HEADER } from "
|
|
2
|
-
import { isrCacheKey } from "
|
|
3
|
-
import { buildAppPageCacheTags } from "
|
|
4
|
-
import { ENTRY_PREFIX } from "./
|
|
1
|
+
import { VINEXT_REVALIDATE_HEADER } from "../../../server/headers.js";
|
|
2
|
+
import { isrCacheKey } from "../../../server/isr-cache.js";
|
|
3
|
+
import { buildAppPageCacheTags } from "../../../server/app-page-cache.js";
|
|
4
|
+
import { ENTRY_PREFIX } from "./cache/kv-data-adapter.runtime.js";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
5
6
|
import fs from "node:fs";
|
|
6
7
|
import path from "node:path";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
7
9
|
import { spawn } from "node:child_process";
|
|
8
|
-
//#region src/
|
|
10
|
+
//#region ../cloudflare/src/tpr.ts
|
|
9
11
|
/**
|
|
10
12
|
* TPR: Traffic-aware Pre-Rendering
|
|
11
13
|
*
|
|
@@ -360,17 +362,15 @@ async function prerenderRoutes(routes, root, hostDomain) {
|
|
|
360
362
|
}
|
|
361
363
|
/**
|
|
362
364
|
* Spawn a subprocess running the vinext production server.
|
|
363
|
-
* Uses the same Node.js binary and resolves
|
|
364
|
-
*
|
|
365
|
+
* Uses the same Node.js binary and resolves vinext from the project root so
|
|
366
|
+
* linked/package-manager-managed installs use the app's vinext peer.
|
|
365
367
|
*/
|
|
366
368
|
function startLocalServer(root, port) {
|
|
367
|
-
const prodServerPath =
|
|
369
|
+
const prodServerPath = resolveVinextProdServerPath(root);
|
|
368
370
|
const outDir = path.join(root, "dist");
|
|
369
|
-
const escapedProdServer = prodServerPath.replace(/\\/g, "\\\\");
|
|
370
|
-
const escapedOutDir = outDir.replace(/\\/g, "\\\\");
|
|
371
371
|
const script = [
|
|
372
|
-
`import(
|
|
373
|
-
`.then(m => m.startProdServer({ port: ${port}, host: "127.0.0.1", outDir:
|
|
372
|
+
`import(${JSON.stringify(pathToFileURL(prodServerPath).href)})`,
|
|
373
|
+
`.then(m => m.startProdServer({ port: ${port}, host: "127.0.0.1", outDir: ${JSON.stringify(outDir)} }))`,
|
|
374
374
|
`.catch(e => { console.error("[vinext-tpr] Server failed to start:", e); process.exit(1); });`
|
|
375
375
|
].join("");
|
|
376
376
|
const proc = spawn(process.execPath, [
|
|
@@ -391,6 +391,9 @@ function startLocalServer(root, port) {
|
|
|
391
391
|
});
|
|
392
392
|
return proc;
|
|
393
393
|
}
|
|
394
|
+
function resolveVinextProdServerPath(root) {
|
|
395
|
+
return createRequire(path.join(root, "package.json")).resolve("vinext/server/prod-server");
|
|
396
|
+
}
|
|
394
397
|
/** Poll the local server until it responds or the timeout is reached. */
|
|
395
398
|
async function waitForServer(port, timeoutMs) {
|
|
396
399
|
const start = Date.now();
|
|
@@ -477,8 +480,8 @@ const DEFAULT_REVALIDATE_SECONDS = 3600;
|
|
|
477
480
|
/**
|
|
478
481
|
* Run the TPR pipeline: query traffic, select routes, pre-render, upload.
|
|
479
482
|
*
|
|
480
|
-
* Designed to be called between the build step and wrangler deploy in
|
|
481
|
-
*
|
|
483
|
+
* Designed to be called between the build step and wrangler deploy in the
|
|
484
|
+
* `vinext-cloudflare deploy` pipeline. Gracefully skips (never errors) when
|
|
482
485
|
* the prerequisites aren't met.
|
|
483
486
|
*/
|
|
484
487
|
async function runTPR(options) {
|
|
@@ -555,4 +558,4 @@ async function runTPR(options) {
|
|
|
555
558
|
};
|
|
556
559
|
}
|
|
557
560
|
//#endregion
|
|
558
|
-
export {
|
|
561
|
+
export { runTPR };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AstRecord } from "./ast-utils.js";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/ast-scope.d.ts
|
|
4
|
+
type AstScope = {
|
|
5
|
+
parent: AstScope | null;
|
|
6
|
+
bindings: Set<string>;
|
|
7
|
+
};
|
|
8
|
+
declare function createAstScope<T extends AstScope>(parent: T | null): AstScope;
|
|
9
|
+
declare function hasAstBinding(scope: AstScope, name: string): boolean;
|
|
10
|
+
declare function isFunctionNode(node: AstRecord): boolean;
|
|
11
|
+
declare function collectDirectScopeBindings(node: AstRecord, scope: AstScope, onVariableDeclarator?: (declaration: AstRecord, declarator: AstRecord) => void): void;
|
|
12
|
+
declare function collectLoopScopeBindings(node: AstRecord, scope: AstScope, onVariableDeclarator?: (declaration: AstRecord, declarator: AstRecord) => void): void;
|
|
13
|
+
declare function collectSwitchScopeBindings(node: AstRecord, scope: AstScope, onVariableDeclarator?: (declaration: AstRecord, declarator: AstRecord) => void): void;
|
|
14
|
+
declare function collectVarScopeBindings(node: AstRecord, scope: AstScope, root?: boolean): void;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { AstScope, collectDirectScopeBindings, collectLoopScopeBindings, collectSwitchScopeBindings, collectVarScopeBindings, createAstScope, hasAstBinding, isFunctionNode };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { collectBindingNames, forEachAstChild, isAstRecord, nodeArray } from "./ast-utils.js";
|
|
2
|
+
//#region src/plugins/ast-scope.ts
|
|
3
|
+
function createAstScope(parent) {
|
|
4
|
+
return {
|
|
5
|
+
parent,
|
|
6
|
+
bindings: /* @__PURE__ */ new Set()
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function hasAstBinding(scope, name) {
|
|
10
|
+
for (let current = scope; current; current = current.parent) if (current.bindings.has(name)) return true;
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
function isFunctionNode(node) {
|
|
14
|
+
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
15
|
+
}
|
|
16
|
+
function collectDirectScopeBindings(node, scope, onVariableDeclarator) {
|
|
17
|
+
for (const statementValue of nodeArray(node.body)) {
|
|
18
|
+
const statement = isAstRecord(statementValue) ? statementValue : null;
|
|
19
|
+
if (!statement) continue;
|
|
20
|
+
const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? isAstRecord(statement.declaration) ? statement.declaration : null : statement;
|
|
21
|
+
if (!declaration) continue;
|
|
22
|
+
if (declaration.type === "ImportDeclaration") {
|
|
23
|
+
if (declaration.importKind === "type") continue;
|
|
24
|
+
for (const specifier of nodeArray(declaration.specifiers)) if (isAstRecord(specifier) && specifier.importKind !== "type") collectBindingNames(specifier.local, scope.bindings);
|
|
25
|
+
} else if (declaration.type === "TSImportEqualsDeclaration" && declaration.importKind !== "type") collectBindingNames(declaration.id, scope.bindings);
|
|
26
|
+
else if (declaration.type === "VariableDeclaration" && declaration.declare !== true) for (const declarator of nodeArray(declaration.declarations)) {
|
|
27
|
+
if (!isAstRecord(declarator)) continue;
|
|
28
|
+
collectBindingNames(declarator.id, scope.bindings);
|
|
29
|
+
onVariableDeclarator?.(declaration, declarator);
|
|
30
|
+
}
|
|
31
|
+
else if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && declaration.declare !== true) collectBindingNames(declaration.id, scope.bindings);
|
|
32
|
+
else if ((declaration.type === "TSEnumDeclaration" || declaration.type === "TSModuleDeclaration") && declaration.declare !== true) collectBindingNames(declaration.id, scope.bindings);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function collectLoopScopeBindings(node, scope, onVariableDeclarator) {
|
|
36
|
+
const declarationValue = node.type === "ForStatement" ? node.init : node.left;
|
|
37
|
+
if (!isAstRecord(declarationValue)) return;
|
|
38
|
+
if (declarationValue.type !== "VariableDeclaration" || declarationValue.declare === true) return;
|
|
39
|
+
for (const declarator of nodeArray(declarationValue.declarations)) {
|
|
40
|
+
if (!isAstRecord(declarator)) continue;
|
|
41
|
+
collectBindingNames(declarator.id, scope.bindings);
|
|
42
|
+
onVariableDeclarator?.(declarationValue, declarator);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function collectSwitchScopeBindings(node, scope, onVariableDeclarator) {
|
|
46
|
+
for (const caseValue of nodeArray(node.cases)) {
|
|
47
|
+
if (!isAstRecord(caseValue)) continue;
|
|
48
|
+
collectDirectScopeBindings({
|
|
49
|
+
type: "BlockStatement",
|
|
50
|
+
body: nodeArray(caseValue.consequent)
|
|
51
|
+
}, scope, onVariableDeclarator);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function collectVarScopeBindings(node, scope, root = true) {
|
|
55
|
+
if (!root && (isFunctionNode(node) || node.type === "StaticBlock" || node.type === "TSModuleBlock")) return;
|
|
56
|
+
if (node.type === "VariableDeclaration" && node.kind === "var" && node.declare !== true) {
|
|
57
|
+
for (const declarator of nodeArray(node.declarations)) if (isAstRecord(declarator)) collectBindingNames(declarator.id, scope.bindings);
|
|
58
|
+
}
|
|
59
|
+
forEachAstChild(node, (child) => collectVarScopeBindings(child, scope, false));
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { collectDirectScopeBindings, collectLoopScopeBindings, collectSwitchScopeBindings, collectVarScopeBindings, createAstScope, hasAstBinding, isFunctionNode };
|
|
@@ -9,6 +9,32 @@ type AstRange = AstRecord & {
|
|
|
9
9
|
start: number;
|
|
10
10
|
end: number;
|
|
11
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Cheap pre-parse gate for plugins that only transform *dynamic* `import(...)`.
|
|
14
|
+
*
|
|
15
|
+
* Static imports — `import x from "..."`, `import { ... } from "..."`,
|
|
16
|
+
* `import "..."` — never place a `(` (nor a comment leading to one) immediately
|
|
17
|
+
* after the `import` keyword. Plugins that act only on dynamic `import(...)` use
|
|
18
|
+
* this to skip `parseAst` for the overwhelming majority of modules in a large
|
|
19
|
+
* app: at ~5k routes, where almost every module is a static-import-only page,
|
|
20
|
+
* it removes most of the build's AST-parse/GC cost. This is a deliberate,
|
|
21
|
+
* measured performance filter — keep it a regex, never a parse.
|
|
22
|
+
*
|
|
23
|
+
* It intentionally errs toward over-matching: a false positive costs one
|
|
24
|
+
* redundant parse, whereas a false negative would silently skip a real dynamic
|
|
25
|
+
* import (a correctness bug). `\s*[(/]` therefore tolerates whitespace and
|
|
26
|
+
* block/line comments between the `import` keyword and its parenthesis.
|
|
27
|
+
*
|
|
28
|
+
* Usable directly as a Rolldown `transform.filter.code` regex, or via
|
|
29
|
+
* {@link mayContainDynamicImport} for an in-handler prescan.
|
|
30
|
+
*/
|
|
31
|
+
declare const DYNAMIC_IMPORT_PRESCAN: RegExp;
|
|
32
|
+
/**
|
|
33
|
+
* Whether `code` might contain a dynamic `import(...)` call. See
|
|
34
|
+
* {@link DYNAMIC_IMPORT_PRESCAN} — a cheap, deliberately over-inclusive regex
|
|
35
|
+
* gate used to avoid parsing static-import-only modules.
|
|
36
|
+
*/
|
|
37
|
+
declare function mayContainDynamicImport(code: string): boolean;
|
|
12
38
|
declare function isAstRecord(value: unknown): value is AstRecord;
|
|
13
39
|
declare function nodeArray(value: unknown): unknown[];
|
|
14
40
|
declare function hasRange(node: AstRecord | null): node is AstRange;
|
|
@@ -17,4 +43,4 @@ declare function getAstName(value: unknown): string | null;
|
|
|
17
43
|
declare function forEachAstChild(node: AstRecord, callback: (child: AstRecord) => void): void;
|
|
18
44
|
declare function collectBindingNames(pattern: unknown, target: Set<string>): void;
|
|
19
45
|
//#endregion
|
|
20
|
-
export { AstRange, AstRecord, collectBindingNames, forEachAstChild, getAstName, hasRange, isAstRecord, isIdentifierNamed, nodeArray };
|
|
46
|
+
export { AstRange, AstRecord, DYNAMIC_IMPORT_PRESCAN, collectBindingNames, forEachAstChild, getAstName, hasRange, isAstRecord, isIdentifierNamed, mayContainDynamicImport, nodeArray };
|
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
//#region src/plugins/ast-utils.ts
|
|
2
|
+
/**
|
|
3
|
+
* Cheap pre-parse gate for plugins that only transform *dynamic* `import(...)`.
|
|
4
|
+
*
|
|
5
|
+
* Static imports — `import x from "..."`, `import { ... } from "..."`,
|
|
6
|
+
* `import "..."` — never place a `(` (nor a comment leading to one) immediately
|
|
7
|
+
* after the `import` keyword. Plugins that act only on dynamic `import(...)` use
|
|
8
|
+
* this to skip `parseAst` for the overwhelming majority of modules in a large
|
|
9
|
+
* app: at ~5k routes, where almost every module is a static-import-only page,
|
|
10
|
+
* it removes most of the build's AST-parse/GC cost. This is a deliberate,
|
|
11
|
+
* measured performance filter — keep it a regex, never a parse.
|
|
12
|
+
*
|
|
13
|
+
* It intentionally errs toward over-matching: a false positive costs one
|
|
14
|
+
* redundant parse, whereas a false negative would silently skip a real dynamic
|
|
15
|
+
* import (a correctness bug). `\s*[(/]` therefore tolerates whitespace and
|
|
16
|
+
* block/line comments between the `import` keyword and its parenthesis.
|
|
17
|
+
*
|
|
18
|
+
* Usable directly as a Rolldown `transform.filter.code` regex, or via
|
|
19
|
+
* {@link mayContainDynamicImport} for an in-handler prescan.
|
|
20
|
+
*/
|
|
21
|
+
const DYNAMIC_IMPORT_PRESCAN = /\bimport\s*[(/]/;
|
|
22
|
+
/**
|
|
23
|
+
* Whether `code` might contain a dynamic `import(...)` call. See
|
|
24
|
+
* {@link DYNAMIC_IMPORT_PRESCAN} — a cheap, deliberately over-inclusive regex
|
|
25
|
+
* gate used to avoid parsing static-import-only modules.
|
|
26
|
+
*/
|
|
27
|
+
function mayContainDynamicImport(code) {
|
|
28
|
+
return DYNAMIC_IMPORT_PRESCAN.test(code);
|
|
29
|
+
}
|
|
2
30
|
const SKIP_CHILD_KEYS = new Set([
|
|
3
31
|
"type",
|
|
4
32
|
"parent",
|
|
@@ -59,6 +87,9 @@ function collectBindingNames(pattern, target) {
|
|
|
59
87
|
case "AssignmentPattern":
|
|
60
88
|
collectBindingNames(node.left, target);
|
|
61
89
|
return;
|
|
90
|
+
case "TSParameterProperty":
|
|
91
|
+
collectBindingNames(node.parameter, target);
|
|
92
|
+
return;
|
|
62
93
|
case "ArrayPattern":
|
|
63
94
|
for (const element of nodeArray(node.elements)) collectBindingNames(element, target);
|
|
64
95
|
return;
|
|
@@ -75,4 +106,4 @@ function collectBindingNames(pattern, target) {
|
|
|
75
106
|
}
|
|
76
107
|
}
|
|
77
108
|
//#endregion
|
|
78
|
-
export { collectBindingNames, forEachAstChild, getAstName, hasRange, isAstRecord, isIdentifierNamed, nodeArray };
|
|
109
|
+
export { DYNAMIC_IMPORT_PRESCAN, collectBindingNames, forEachAstChild, getAstName, hasRange, isAstRecord, isIdentifierNamed, mayContainDynamicImport, nodeArray };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
import MagicString from "magic-string";
|
|
3
|
+
|
|
4
|
+
//#region src/plugins/css-module-imports.d.ts
|
|
5
|
+
type ScriptLanguage = "js" | "jsx" | "ts" | "tsx";
|
|
6
|
+
declare function rewriteCssModuleNamespaceImports(code: string, lang?: ScriptLanguage): {
|
|
7
|
+
code: string;
|
|
8
|
+
map: ReturnType<MagicString["generateMap"]>;
|
|
9
|
+
} | null;
|
|
10
|
+
declare function createCssModuleImportCompatibilityPlugin(options?: {
|
|
11
|
+
compiledMdx?: boolean;
|
|
12
|
+
}): Plugin;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { createCssModuleImportCompatibilityPlugin, rewriteCssModuleNamespaceImports };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { parseAst } from "vite";
|
|
2
|
+
import MagicString from "magic-string";
|
|
3
|
+
//#region src/plugins/css-module-imports.ts
|
|
4
|
+
const CSS_MODULE_RE = /\.module\.(?:css|scss|sass)$/i;
|
|
5
|
+
const CSS_MODULE_HINT_RE = /\.module\.(?:css|scss|sass)/i;
|
|
6
|
+
const SCRIPT_RE = /\.(?:[cm]?[jt]sx?)(?:[?#].*)?$/i;
|
|
7
|
+
const MDX_RE = /\.mdx$/i;
|
|
8
|
+
function scriptLanguage(id) {
|
|
9
|
+
const cleanId = id.split("?", 1)[0].toLowerCase();
|
|
10
|
+
if (cleanId.endsWith(".tsx")) return "tsx";
|
|
11
|
+
if (cleanId.endsWith(".ts") || cleanId.endsWith(".mts") || cleanId.endsWith(".cts")) return "ts";
|
|
12
|
+
return "jsx";
|
|
13
|
+
}
|
|
14
|
+
function rewriteCssModuleNamespaceImports(code, lang = "js") {
|
|
15
|
+
let ast;
|
|
16
|
+
try {
|
|
17
|
+
ast = parseAst(code, { lang });
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
let output = null;
|
|
22
|
+
for (const statement of ast.body) {
|
|
23
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
24
|
+
if (statement.importKind === "type") continue;
|
|
25
|
+
if (typeof statement.source?.value !== "string") continue;
|
|
26
|
+
if (!CSS_MODULE_RE.test(statement.source.value)) continue;
|
|
27
|
+
if (statement.attributes && statement.attributes.length > 0) continue;
|
|
28
|
+
if (statement.specifiers?.length !== 1) continue;
|
|
29
|
+
const specifier = statement.specifiers[0];
|
|
30
|
+
if (specifier.type !== "ImportNamespaceSpecifier") continue;
|
|
31
|
+
if (typeof specifier.start !== "number" || typeof specifier.end !== "number") continue;
|
|
32
|
+
if (typeof specifier.local?.name !== "string") continue;
|
|
33
|
+
output ??= new MagicString(code);
|
|
34
|
+
output.overwrite(specifier.start, specifier.end, specifier.local.name);
|
|
35
|
+
}
|
|
36
|
+
if (!output) return null;
|
|
37
|
+
return {
|
|
38
|
+
code: output.toString(),
|
|
39
|
+
map: output.generateMap({ hires: true })
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function createCssModuleImportCompatibilityPlugin(options = {}) {
|
|
43
|
+
const idFilter = options.compiledMdx ? MDX_RE : SCRIPT_RE;
|
|
44
|
+
return {
|
|
45
|
+
name: options.compiledMdx ? "vinext:css-module-import-compatibility-mdx" : "vinext:css-module-import-compatibility",
|
|
46
|
+
enforce: options.compiledMdx ? "post" : "pre",
|
|
47
|
+
transform: {
|
|
48
|
+
filter: {
|
|
49
|
+
id: idFilter,
|
|
50
|
+
code: CSS_MODULE_HINT_RE
|
|
51
|
+
},
|
|
52
|
+
handler(code, id) {
|
|
53
|
+
return rewriteCssModuleNamespaceImports(code, options.compiledMdx ? "jsx" : scriptLanguage(id));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { createCssModuleImportCompatibilityPlugin, rewriteCssModuleNamespaceImports };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forEachAstChild, hasRange, isAstRecord, nodeArray } from "./ast-utils.js";
|
|
1
|
+
import { DYNAMIC_IMPORT_PRESCAN, forEachAstChild, hasRange, isAstRecord, nodeArray } from "./ast-utils.js";
|
|
2
2
|
import { parseAst } from "vite";
|
|
3
3
|
import MagicString from "magic-string";
|
|
4
4
|
//#region src/plugins/extensionless-dynamic-import.ts
|
|
@@ -35,7 +35,7 @@ function createExtensionlessDynamicImportPlugin() {
|
|
|
35
35
|
include: /\.(?:[cm]?[jt]s|[jt]sx)(?:\?.*)?$/i,
|
|
36
36
|
exclude: /[\\/]node_modules[\\/]/
|
|
37
37
|
},
|
|
38
|
-
code:
|
|
38
|
+
code: DYNAMIC_IMPORT_PRESCAN
|
|
39
39
|
},
|
|
40
40
|
handler(code, id) {
|
|
41
41
|
const lang = langForId(id);
|