@pracht/vite-plugin 0.3.1 → 0.4.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 +24 -2
- package/dist/index.d.mts +6 -2
- package/dist/index.mjs +142 -71
- package/dist/pages-router.mjs +30 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install @pracht/vite-plugin
|
|
|
13
13
|
```ts
|
|
14
14
|
// vite.config.ts
|
|
15
15
|
import { defineConfig } from "vite";
|
|
16
|
-
import pracht from "@pracht/vite-plugin";
|
|
16
|
+
import { pracht } from "@pracht/vite-plugin";
|
|
17
17
|
|
|
18
18
|
export default defineConfig({
|
|
19
19
|
plugins: [pracht()],
|
|
@@ -24,9 +24,31 @@ export default defineConfig({
|
|
|
24
24
|
|
|
25
25
|
- Generates virtual modules (`virtual:pracht/client`, `virtual:pracht/server`) from your route manifest
|
|
26
26
|
- Builds client and SSR bundles via Vite's multi-environment mode
|
|
27
|
-
- Pre-renders SSG and ISG routes at build time
|
|
27
|
+
- Pre-renders SSG and ISG routes at build time (`prerenderConcurrency` controls parallelism)
|
|
28
28
|
- Provides HMR during development
|
|
29
29
|
|
|
30
|
+
## TSRX (`.tsrx`) Support
|
|
31
|
+
|
|
32
|
+
`.tsrx` modules — TSRX/Ripple-flavoured Preact components — are supported out
|
|
33
|
+
of the box. Bring your own
|
|
34
|
+
[`@tsrx/vite-plugin-preact`](https://github.com/Ripple-TS/ripple) and add it to
|
|
35
|
+
your `plugins` array alongside `pracht()`:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// vite.config.ts
|
|
39
|
+
import { defineConfig } from "vite";
|
|
40
|
+
import { pracht } from "@pracht/vite-plugin";
|
|
41
|
+
import { tsrxPreact } from "@tsrx/vite-plugin-preact";
|
|
42
|
+
|
|
43
|
+
export default defineConfig({
|
|
44
|
+
plugins: [tsrxPreact(), pracht()],
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The pracht plugin globs `.tsrx` files alongside `.tsx` for routes and shells
|
|
49
|
+
(both manifest- and pages-router modes), and its server-only export stripping
|
|
50
|
+
pass treats them the same way — no separate pracht option is required.
|
|
51
|
+
|
|
30
52
|
## Peer Dependencies
|
|
31
53
|
|
|
32
54
|
- `vite@^8.0.0`
|
package/dist/index.d.mts
CHANGED
|
@@ -30,7 +30,7 @@ interface PrachtAdapter {
|
|
|
30
30
|
* Additional Vite plugins the adapter needs (e.g. `@cloudflare/vite-plugin`).
|
|
31
31
|
* Returned plugins are appended to the plugin array returned by `pracht()`.
|
|
32
32
|
*/
|
|
33
|
-
vitePlugins?(): Plugin[]
|
|
33
|
+
vitePlugins?(): Plugin[];
|
|
34
34
|
/**
|
|
35
35
|
* If true, the adapter owns dev-server request handling and the vite-plugin
|
|
36
36
|
* will not install its own SSR middleware. Used when the adapter contributes
|
|
@@ -60,6 +60,10 @@ interface PrachtPluginOptions {
|
|
|
60
60
|
pagesDir?: string;
|
|
61
61
|
/** Default render mode for pages when RENDER_MODE is not exported. Defaults to "ssr". */
|
|
62
62
|
pagesDefaultRender?: RenderMode$1;
|
|
63
|
+
/** Maximum number of SSG/ISG pages rendered concurrently during `pracht build`. */
|
|
64
|
+
prerenderConcurrency?: number;
|
|
65
|
+
/** Maximum request body size (bytes) accepted by the dev SSR middleware. Defaults to 1 MiB. */
|
|
66
|
+
maxBodySize?: number;
|
|
63
67
|
}
|
|
64
68
|
//#endregion
|
|
65
69
|
//#region src/plugin-codegen.d.ts
|
|
@@ -73,6 +77,6 @@ declare function createPrachtServerModuleSource(options?: PrachtPluginOptions, b
|
|
|
73
77
|
declare function createPrachtRegistryModuleSource(options?: PrachtPluginOptions): string;
|
|
74
78
|
//#endregion
|
|
75
79
|
//#region src/index.d.ts
|
|
76
|
-
declare function pracht(options?: PrachtPluginOptions):
|
|
80
|
+
declare function pracht(options?: PrachtPluginOptions): Plugin[];
|
|
77
81
|
//#endregion
|
|
78
82
|
export { PRACHT_CLIENT_MODULE_ID, PRACHT_SERVER_MODULE_ID, type PrachtAdapter, type PrachtPluginOptions, type RenderMode, createPrachtClientModuleSource, createPrachtRegistryModuleSource, createPrachtServerModuleSource, pracht };
|
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import preact from "@preact/preset-vite";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { parseAst } from "vite";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { createNodeServerEntryModule } from "@pracht/adapter-node";
|
|
6
7
|
//#region src/client-module-query.ts
|
|
7
8
|
const CLIENT_MODULE_QUERY = "pracht-client";
|
|
8
9
|
const PRACHT_CLIENT_MODULE_QUERY = `?${CLIENT_MODULE_QUERY}`;
|
|
@@ -21,6 +22,7 @@ function stripPrachtClientModuleQuery(id) {
|
|
|
21
22
|
function getRolldownLang(id) {
|
|
22
23
|
const path = stripPrachtClientModuleQuery(id).split("?")[0];
|
|
23
24
|
if (/\.(c|m)?tsx$/i.test(path)) return "tsx";
|
|
25
|
+
if (/\.tsrx$/i.test(path)) return "tsx";
|
|
24
26
|
if (/\.(c|m)?ts$/i.test(path)) return "ts";
|
|
25
27
|
if (/\.(c|m)?jsx$/i.test(path)) return "jsx";
|
|
26
28
|
if (/\.mdx?$/i.test(path)) return "jsx";
|
|
@@ -639,7 +641,8 @@ const SERVER_ONLY_EXPORTS = new Set([
|
|
|
639
641
|
"loader",
|
|
640
642
|
"head",
|
|
641
643
|
"headers",
|
|
642
|
-
"getStaticPaths"
|
|
644
|
+
"getStaticPaths",
|
|
645
|
+
"markdown"
|
|
643
646
|
]);
|
|
644
647
|
function stripServerOnlyExportsForClient(code, id = "pracht-client-route.tsx") {
|
|
645
648
|
const states = createStatementStates(parseAst(code, { lang: getRolldownLang(id) }));
|
|
@@ -944,41 +947,7 @@ function createDefaultNodeAdapter() {
|
|
|
944
947
|
id: "node",
|
|
945
948
|
serverImports: "import { resolveApp, resolveApiRoutes } from \"@pracht/core\";",
|
|
946
949
|
createServerEntryModule() {
|
|
947
|
-
return
|
|
948
|
-
"import { existsSync, readFileSync } from \"node:fs\";",
|
|
949
|
-
"import { createServer } from \"node:http\";",
|
|
950
|
-
"import { dirname, resolve } from \"node:path\";",
|
|
951
|
-
"import { fileURLToPath, pathToFileURL } from \"node:url\";",
|
|
952
|
-
"import { createNodeRequestHandler } from \"@pracht/adapter-node\";",
|
|
953
|
-
"",
|
|
954
|
-
"const serverDir = dirname(fileURLToPath(import.meta.url));",
|
|
955
|
-
"const staticDir = resolve(serverDir, \"../client\");",
|
|
956
|
-
"const isgManifestPath = resolve(serverDir, \"isg-manifest.json\");",
|
|
957
|
-
"const isgManifest = existsSync(isgManifestPath)",
|
|
958
|
-
" ? JSON.parse(readFileSync(isgManifestPath, \"utf-8\"))",
|
|
959
|
-
" : {};",
|
|
960
|
-
"",
|
|
961
|
-
"export const handler = createNodeRequestHandler({",
|
|
962
|
-
" app: resolvedApp,",
|
|
963
|
-
" registry,",
|
|
964
|
-
" staticDir,",
|
|
965
|
-
" isgManifest,",
|
|
966
|
-
" apiRoutes,",
|
|
967
|
-
" clientEntryUrl: clientEntryUrl ?? undefined,",
|
|
968
|
-
" cssManifest,",
|
|
969
|
-
" jsManifest,",
|
|
970
|
-
"});",
|
|
971
|
-
"",
|
|
972
|
-
"const entryHref = process.argv[1] ? pathToFileURL(process.argv[1]).href : null;",
|
|
973
|
-
"if (entryHref && import.meta.url === entryHref) {",
|
|
974
|
-
" const server = createServer(handler);",
|
|
975
|
-
" const port = Number(process.env.PORT ?? 3000);",
|
|
976
|
-
" server.listen(port, () => {",
|
|
977
|
-
" console.log(`pracht node server listening on http://localhost:${port}`);",
|
|
978
|
-
" });",
|
|
979
|
-
"}",
|
|
980
|
-
""
|
|
981
|
-
].join("\n");
|
|
950
|
+
return createNodeServerEntryModule();
|
|
982
951
|
}
|
|
983
952
|
};
|
|
984
953
|
}
|
|
@@ -993,13 +962,18 @@ const DEFAULTS = {
|
|
|
993
962
|
serverDir: "/src/server",
|
|
994
963
|
adapter: createDefaultNodeAdapter(),
|
|
995
964
|
pagesDir: "",
|
|
996
|
-
pagesDefaultRender: "ssr"
|
|
965
|
+
pagesDefaultRender: "ssr",
|
|
966
|
+
prerenderConcurrency: 10,
|
|
967
|
+
maxBodySize: 1024 * 1024
|
|
997
968
|
};
|
|
998
969
|
function resolveOptions(options) {
|
|
999
|
-
|
|
970
|
+
const resolved = {
|
|
1000
971
|
...DEFAULTS,
|
|
1001
972
|
...options
|
|
1002
973
|
};
|
|
974
|
+
if (!Number.isInteger(resolved.prerenderConcurrency) || resolved.prerenderConcurrency <= 0) throw new Error("pracht({ prerenderConcurrency }) expects a positive integer.");
|
|
975
|
+
if (!Number.isInteger(resolved.maxBodySize) || resolved.maxBodySize <= 0) throw new Error("pracht({ maxBodySize }) expects a positive integer number of bytes.");
|
|
976
|
+
return resolved;
|
|
1003
977
|
}
|
|
1004
978
|
//#endregion
|
|
1005
979
|
//#region src/plugin-codegen.ts
|
|
@@ -1007,29 +981,51 @@ function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
|
|
|
1007
981
|
const resolved = resolveOptions(options);
|
|
1008
982
|
const isPagesMode = !!resolved.pagesDir;
|
|
1009
983
|
const appImport = isPagesMode ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`;
|
|
1010
|
-
const
|
|
984
|
+
const dirPrefix = isPagesMode ? resolved.pagesDir : resolved.routesDir;
|
|
985
|
+
const routeGlob = `${dirPrefix}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
986
|
+
const routeTsrxGlob = `${dirPrefix}/**/*.tsrx`;
|
|
1011
987
|
const shellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.{ts,tsx,js,jsx}` : `${resolved.shellsDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
988
|
+
const shellTsrxGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.tsrx` : `${resolved.shellsDir}/**/*.tsrx`;
|
|
1012
989
|
return [
|
|
1013
990
|
"import { resolveApp, initClientRouter, readHydrationState } from \"@pracht/core\";",
|
|
1014
991
|
appImport,
|
|
1015
992
|
"",
|
|
1016
|
-
`const routeModules =
|
|
1017
|
-
`
|
|
993
|
+
`const routeModules = {`,
|
|
994
|
+
` ...import.meta.glob(${JSON.stringify(routeGlob)}, { query: ${JSON.stringify(PRACHT_CLIENT_MODULE_QUERY)} }),`,
|
|
995
|
+
` ...import.meta.glob(${JSON.stringify(routeTsrxGlob)}),`,
|
|
996
|
+
`};`,
|
|
997
|
+
`const shellModules = {`,
|
|
998
|
+
` ...import.meta.glob(${JSON.stringify(shellGlob)}, { query: ${JSON.stringify(PRACHT_CLIENT_MODULE_QUERY)} }),`,
|
|
999
|
+
` ...import.meta.glob(${JSON.stringify(shellTsrxGlob)}),`,
|
|
1000
|
+
`};`,
|
|
1018
1001
|
"",
|
|
1019
1002
|
"const resolvedApp = resolveApp(app);",
|
|
1020
1003
|
"",
|
|
1021
1004
|
"function normalizeModuleKey(key) {",
|
|
1022
|
-
" return key.split(\"?\")[0];",
|
|
1005
|
+
" return key.split(\"?\")[0].replace(/^\\.?\\//, \"\");",
|
|
1023
1006
|
"}",
|
|
1024
1007
|
"",
|
|
1025
|
-
"
|
|
1026
|
-
"
|
|
1027
|
-
"
|
|
1008
|
+
"const moduleKeyIndexes = new WeakMap();",
|
|
1009
|
+
"function getModuleKeyIndex(modules) {",
|
|
1010
|
+
" let index = moduleKeyIndexes.get(modules);",
|
|
1011
|
+
" if (index) return index;",
|
|
1012
|
+
" index = new Map();",
|
|
1028
1013
|
" for (const key of Object.keys(modules)) {",
|
|
1029
|
-
" const
|
|
1030
|
-
" if (
|
|
1014
|
+
" const normalized = normalizeModuleKey(key);",
|
|
1015
|
+
" if (!normalized) continue;",
|
|
1016
|
+
" if (!index.has(normalized)) index.set(normalized, key);",
|
|
1017
|
+
" for (let i = normalized.indexOf(\"/\"); i !== -1; i = normalized.indexOf(\"/\", i + 1)) {",
|
|
1018
|
+
" const suffix = normalized.slice(i + 1);",
|
|
1019
|
+
" if (suffix && !index.has(suffix)) index.set(suffix, key);",
|
|
1020
|
+
" }",
|
|
1031
1021
|
" }",
|
|
1032
|
-
"
|
|
1022
|
+
" moduleKeyIndexes.set(modules, index);",
|
|
1023
|
+
" return index;",
|
|
1024
|
+
"}",
|
|
1025
|
+
"",
|
|
1026
|
+
"function findModuleKey(modules, file) {",
|
|
1027
|
+
" if (file in modules) return file;",
|
|
1028
|
+
" return getModuleKeyIndex(modules).get(normalizeModuleKey(file)) ?? null;",
|
|
1033
1029
|
"}",
|
|
1034
1030
|
"",
|
|
1035
1031
|
"const state = readHydrationState();",
|
|
@@ -1069,6 +1065,7 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1069
1065
|
`export const clientEntryUrl = ${JSON.stringify(clientBuild.clientEntryUrl ?? "/@pracht/client.js")};`,
|
|
1070
1066
|
`export const cssManifest = ${JSON.stringify(clientBuild.cssManifest)};`,
|
|
1071
1067
|
`export const jsManifest = ${JSON.stringify(clientBuild.jsManifest)};`,
|
|
1068
|
+
`export const prerenderConcurrency = ${JSON.stringify(resolved.prerenderConcurrency)};`,
|
|
1072
1069
|
"export { prerenderApp };",
|
|
1073
1070
|
""
|
|
1074
1071
|
];
|
|
@@ -1079,10 +1076,18 @@ function createPrachtRegistryModuleSource(options = {}) {
|
|
|
1079
1076
|
const resolved = resolveOptions(options);
|
|
1080
1077
|
const isPagesMode = !!resolved.pagesDir;
|
|
1081
1078
|
const routeGlob = isPagesMode ? `${resolved.pagesDir}/**/*.{ts,tsx,js,jsx,md,mdx}` : `${resolved.routesDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
1079
|
+
const routeTsrxGlob = isPagesMode ? `${resolved.pagesDir}/**/*.tsrx` : `${resolved.routesDir}/**/*.tsrx`;
|
|
1082
1080
|
const shellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.{ts,tsx,js,jsx}` : `${resolved.shellsDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
1081
|
+
const shellTsrxGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.tsrx` : `${resolved.shellsDir}/**/*.tsrx`;
|
|
1083
1082
|
return [
|
|
1084
|
-
`export const routeModules =
|
|
1085
|
-
`
|
|
1083
|
+
`export const routeModules = {`,
|
|
1084
|
+
` ...import.meta.glob(${JSON.stringify(routeGlob)}),`,
|
|
1085
|
+
` ...import.meta.glob(${JSON.stringify(routeTsrxGlob)}),`,
|
|
1086
|
+
`};`,
|
|
1087
|
+
`export const shellModules = {`,
|
|
1088
|
+
` ...import.meta.glob(${JSON.stringify(shellGlob)}),`,
|
|
1089
|
+
` ...import.meta.glob(${JSON.stringify(shellTsrxGlob)}),`,
|
|
1090
|
+
`};`,
|
|
1086
1091
|
`export const middlewareModules = import.meta.glob(${JSON.stringify(`${resolved.middlewareDir}/**/*.{ts,tsx,js,jsx}`)});`,
|
|
1087
1092
|
`export const apiModules = import.meta.glob(${JSON.stringify(`${resolved.apiDir}/**/*.{ts,js,tsx,jsx}`)});`,
|
|
1088
1093
|
`export const dataModules = import.meta.glob(${JSON.stringify(`${resolved.serverDir}/**/*.{ts,js,tsx,jsx}`)});`,
|
|
@@ -1096,19 +1101,33 @@ function createPrachtRegistryModuleSource(options = {}) {
|
|
|
1096
1101
|
"};"
|
|
1097
1102
|
].join("\n");
|
|
1098
1103
|
}
|
|
1104
|
+
const pagesAppSourceCache = /* @__PURE__ */ new Map();
|
|
1105
|
+
function clearPagesAppSourceCache() {
|
|
1106
|
+
pagesAppSourceCache.clear();
|
|
1107
|
+
}
|
|
1099
1108
|
function generatePagesAppInlineSource(options, root = process.cwd()) {
|
|
1100
1109
|
const absPagesDir = resolve(root, options.pagesDir.slice(1));
|
|
1101
|
-
|
|
1110
|
+
const cacheKey = JSON.stringify({
|
|
1111
|
+
absPagesDir,
|
|
1112
|
+
pagesDefaultRender: options.pagesDefaultRender,
|
|
1113
|
+
pagesDirPrefix: options.pagesDir
|
|
1114
|
+
});
|
|
1115
|
+
const cached = pagesAppSourceCache.get(cacheKey);
|
|
1116
|
+
if (cached) return cached;
|
|
1117
|
+
const source = generatePagesManifestSource(scanPagesDirectory(absPagesDir), {
|
|
1102
1118
|
pagesDir: absPagesDir,
|
|
1103
1119
|
pagesDefaultRender: options.pagesDefaultRender,
|
|
1104
1120
|
pagesDirPrefix: options.pagesDir
|
|
1105
1121
|
});
|
|
1122
|
+
pagesAppSourceCache.set(cacheKey, source);
|
|
1123
|
+
return source;
|
|
1106
1124
|
}
|
|
1107
1125
|
//#endregion
|
|
1108
1126
|
//#region src/plugin-dev-ssr.ts
|
|
1109
1127
|
const BODYLESS_METHODS = new Set(["GET", "HEAD"]);
|
|
1110
|
-
const
|
|
1111
|
-
function createDevSSRMiddleware(server) {
|
|
1128
|
+
const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
|
|
1129
|
+
function createDevSSRMiddleware(server, options = {}) {
|
|
1130
|
+
const maxBodySize = options.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
|
|
1112
1131
|
return async (req, res, next) => {
|
|
1113
1132
|
const url = req.url ?? "/";
|
|
1114
1133
|
const pathname = new URL(url, "http://localhost").pathname;
|
|
@@ -1117,7 +1136,7 @@ function createDevSSRMiddleware(server) {
|
|
|
1117
1136
|
const [framework, serverMod] = await Promise.all([server.ssrLoadModule("@pracht/core"), server.ssrLoadModule(PRACHT_SERVER_MODULE_ID)]);
|
|
1118
1137
|
let webRequest;
|
|
1119
1138
|
try {
|
|
1120
|
-
webRequest = await nodeToWebRequest(req);
|
|
1139
|
+
webRequest = await nodeToWebRequest(req, maxBodySize);
|
|
1121
1140
|
} catch (err) {
|
|
1122
1141
|
if (err instanceof Error && err.message === "Request body too large") {
|
|
1123
1142
|
res.statusCode = 413;
|
|
@@ -1161,7 +1180,7 @@ async function handleDevError(server, req, res, next, url, error) {
|
|
|
1161
1180
|
return;
|
|
1162
1181
|
}
|
|
1163
1182
|
try {
|
|
1164
|
-
const { buildErrorOverlayHtml } = await server.ssrLoadModule("pracht/error-overlay");
|
|
1183
|
+
const { buildErrorOverlayHtml } = await server.ssrLoadModule("@pracht/core/error-overlay");
|
|
1165
1184
|
let html = buildErrorOverlayHtml({
|
|
1166
1185
|
message: error instanceof Error ? error.message : String(error),
|
|
1167
1186
|
stack: error instanceof Error ? error.stack : void 0
|
|
@@ -1174,7 +1193,7 @@ async function handleDevError(server, req, res, next, url, error) {
|
|
|
1174
1193
|
next(error);
|
|
1175
1194
|
}
|
|
1176
1195
|
}
|
|
1177
|
-
async function nodeToWebRequest(req) {
|
|
1196
|
+
async function nodeToWebRequest(req, maxBodySize) {
|
|
1178
1197
|
const protocol = "http";
|
|
1179
1198
|
const host = req.headers.host ?? "localhost";
|
|
1180
1199
|
const url = new URL(req.url ?? "/", `${protocol}://${host}`);
|
|
@@ -1195,10 +1214,7 @@ async function nodeToWebRequest(req) {
|
|
|
1195
1214
|
for await (const chunk of req) {
|
|
1196
1215
|
const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
1197
1216
|
totalSize += buf.byteLength;
|
|
1198
|
-
if (totalSize >
|
|
1199
|
-
req.destroy();
|
|
1200
|
-
throw new Error("Request body too large");
|
|
1201
|
-
}
|
|
1217
|
+
if (totalSize > maxBodySize) throw new Error("Request body too large");
|
|
1202
1218
|
chunks.push(buf);
|
|
1203
1219
|
}
|
|
1204
1220
|
const body = Buffer.concat(chunks);
|
|
@@ -1208,7 +1224,7 @@ async function nodeToWebRequest(req) {
|
|
|
1208
1224
|
}
|
|
1209
1225
|
//#endregion
|
|
1210
1226
|
//#region src/index.ts
|
|
1211
|
-
|
|
1227
|
+
function pracht(options = {}) {
|
|
1212
1228
|
const resolved = resolveOptions(options);
|
|
1213
1229
|
const isPagesMode = !!resolved.pagesDir;
|
|
1214
1230
|
let root = process.cwd();
|
|
@@ -1248,7 +1264,8 @@ async function pracht(options = {}) {
|
|
|
1248
1264
|
return null;
|
|
1249
1265
|
},
|
|
1250
1266
|
transform(code, id) {
|
|
1251
|
-
|
|
1267
|
+
const appFileAbs = resolveConfigPath(root, resolved.appFile);
|
|
1268
|
+
if (toPosixPath(id.split("?")[0]) !== appFileAbs) return null;
|
|
1252
1269
|
const transformed = code.replace(/\(\)\s*=>\s*import\(\s*(['"])([^'"]+)\1\s*\)/g, "$1$2$1");
|
|
1253
1270
|
if (transformed === code) return null;
|
|
1254
1271
|
return {
|
|
@@ -1260,13 +1277,15 @@ async function pracht(options = {}) {
|
|
|
1260
1277
|
if (isPagesMode) watchPagesDirectory(server, resolved, root);
|
|
1261
1278
|
if (resolved.adapter.ownsDevServer) return;
|
|
1262
1279
|
return () => {
|
|
1263
|
-
server.middlewares.use(createDevSSRMiddleware(server));
|
|
1280
|
+
server.middlewares.use(createDevSSRMiddleware(server, { maxBodySize: resolved.maxBodySize }));
|
|
1264
1281
|
};
|
|
1265
1282
|
},
|
|
1266
1283
|
handleHotUpdate({ file, server }) {
|
|
1267
|
-
const serverRoot = server.config.root;
|
|
1268
|
-
const
|
|
1284
|
+
const serverRoot = toPosixPath(server.config.root);
|
|
1285
|
+
const normalizedFile = toPosixPath(file);
|
|
1286
|
+
const relative = normalizedFile.startsWith(serverRoot) ? normalizedFile.slice(serverRoot.length) : normalizedFile;
|
|
1269
1287
|
if (isPagesMode && relative.startsWith(resolved.pagesDir)) {
|
|
1288
|
+
clearPagesAppSourceCache();
|
|
1270
1289
|
invalidateVirtualModules(server);
|
|
1271
1290
|
return;
|
|
1272
1291
|
}
|
|
@@ -1299,22 +1318,67 @@ async function pracht(options = {}) {
|
|
|
1299
1318
|
};
|
|
1300
1319
|
}
|
|
1301
1320
|
};
|
|
1321
|
+
const optimizeDepsEntriesPlugin = {
|
|
1322
|
+
name: "pracht:optimize-deps-entries",
|
|
1323
|
+
enforce: "post",
|
|
1324
|
+
config(config) {
|
|
1325
|
+
return withPrachtOptimizeDepsEntries(config, createPrachtOptimizeDepsEntries(resolved));
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1302
1328
|
const plugins = [
|
|
1303
1329
|
...preact(),
|
|
1304
1330
|
prachtPlugin,
|
|
1305
1331
|
clientModuleTransformPlugin
|
|
1306
1332
|
];
|
|
1307
|
-
const adapterPlugins =
|
|
1333
|
+
const adapterPlugins = resolved.adapter.vitePlugins?.();
|
|
1308
1334
|
if (adapterPlugins?.length) plugins.push(...adapterPlugins);
|
|
1335
|
+
plugins.push(optimizeDepsEntriesPlugin);
|
|
1309
1336
|
return plugins;
|
|
1310
1337
|
}
|
|
1338
|
+
function withPrachtOptimizeDepsEntries(config, prachtEntries) {
|
|
1339
|
+
const environments = Object.fromEntries(Object.entries(config.environments ?? {}).map(([name, environment]) => [name, { optimizeDeps: { entries: mergeOptimizeDepsEntries(environment.optimizeDeps?.entries, prachtEntries) } }]));
|
|
1340
|
+
return {
|
|
1341
|
+
optimizeDeps: { entries: mergeOptimizeDepsEntries(config.optimizeDeps?.entries, prachtEntries) },
|
|
1342
|
+
...Object.keys(environments).length > 0 ? { environments } : {}
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
function createPrachtOptimizeDepsEntries(resolved) {
|
|
1346
|
+
const scriptExtensions = "{ts,tsx,js,jsx}";
|
|
1347
|
+
const routeExtensions = "{ts,tsx,js,jsx,md,mdx,tsrx}";
|
|
1348
|
+
const entries = resolved.pagesDir ? [
|
|
1349
|
+
`${toOptimizeDepsEntry(resolved.pagesDir)}/**/*.${routeExtensions}`,
|
|
1350
|
+
`${toOptimizeDepsEntry(resolved.middlewareDir)}/**/*.${scriptExtensions}`,
|
|
1351
|
+
`${toOptimizeDepsEntry(resolved.apiDir)}/**/*.{ts,js,tsx,jsx}`,
|
|
1352
|
+
`${toOptimizeDepsEntry(resolved.serverDir)}/**/*.{ts,js,tsx,jsx}`
|
|
1353
|
+
] : [
|
|
1354
|
+
toOptimizeDepsEntry(resolved.appFile),
|
|
1355
|
+
`${toOptimizeDepsEntry(resolved.routesDir)}/**/*.${routeExtensions}`,
|
|
1356
|
+
`${toOptimizeDepsEntry(resolved.shellsDir)}/**/*.${routeExtensions}`,
|
|
1357
|
+
`${toOptimizeDepsEntry(resolved.middlewareDir)}/**/*.${scriptExtensions}`,
|
|
1358
|
+
`${toOptimizeDepsEntry(resolved.apiDir)}/**/*.{ts,js,tsx,jsx}`,
|
|
1359
|
+
`${toOptimizeDepsEntry(resolved.serverDir)}/**/*.{ts,js,tsx,jsx}`
|
|
1360
|
+
];
|
|
1361
|
+
return [...new Set(entries.filter(Boolean))];
|
|
1362
|
+
}
|
|
1363
|
+
function mergeOptimizeDepsEntries(userEntries, prachtEntries) {
|
|
1364
|
+
return [...new Set([...Array.isArray(userEntries) ? userEntries : userEntries ? [userEntries] : [], ...prachtEntries])];
|
|
1365
|
+
}
|
|
1366
|
+
function toOptimizeDepsEntry(path) {
|
|
1367
|
+
return toPosixPath(path).replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
|
|
1368
|
+
}
|
|
1311
1369
|
function watchPagesDirectory(server, resolved, root) {
|
|
1312
|
-
const abs =
|
|
1370
|
+
const abs = resolveConfigPath(root, resolved.pagesDir);
|
|
1313
1371
|
server.watcher.on("add", (f) => {
|
|
1314
|
-
if (f.startsWith(abs))
|
|
1372
|
+
if (toPosixPath(f).startsWith(toPosixPath(abs))) {
|
|
1373
|
+
clearPagesAppSourceCache();
|
|
1374
|
+
server.restart();
|
|
1375
|
+
}
|
|
1315
1376
|
});
|
|
1316
1377
|
server.watcher.on("unlink", (f) => {
|
|
1317
|
-
if (f.startsWith(abs))
|
|
1378
|
+
if (toPosixPath(f).startsWith(toPosixPath(abs))) {
|
|
1379
|
+
clearPagesAppSourceCache();
|
|
1380
|
+
server.restart();
|
|
1381
|
+
}
|
|
1318
1382
|
});
|
|
1319
1383
|
}
|
|
1320
1384
|
function invalidateVirtualModules(server) {
|
|
@@ -1329,10 +1393,11 @@ const ROUTE_FILE_EXTENSIONS = new Set([
|
|
|
1329
1393
|
".js",
|
|
1330
1394
|
".jsx",
|
|
1331
1395
|
".md",
|
|
1332
|
-
".mdx"
|
|
1396
|
+
".mdx",
|
|
1397
|
+
".tsrx"
|
|
1333
1398
|
]);
|
|
1334
1399
|
function computeRouteFileDirs(root, resolved) {
|
|
1335
|
-
return (resolved.pagesDir ? [resolved.pagesDir] : [resolved.routesDir, resolved.shellsDir]).map((dir) =>
|
|
1400
|
+
return (resolved.pagesDir ? [resolved.pagesDir] : [resolved.routesDir, resolved.shellsDir]).map((dir) => resolveConfigPath(root, dir)).map(withTrailingSep);
|
|
1336
1401
|
}
|
|
1337
1402
|
function isRouteOrShellFile(id, dirs) {
|
|
1338
1403
|
if (dirs.length === 0) return false;
|
|
@@ -1346,6 +1411,12 @@ function isRouteOrShellFile(id, dirs) {
|
|
|
1346
1411
|
const normalized = toPosixPath(path);
|
|
1347
1412
|
return dirs.some((dir) => normalized.startsWith(dir));
|
|
1348
1413
|
}
|
|
1414
|
+
function resolveConfigPath(root, configPath) {
|
|
1415
|
+
const normalizedRoot = toPosixPath(root).replace(/\/$/, "");
|
|
1416
|
+
const relativePath = configPath.replace(/^\//, "");
|
|
1417
|
+
if (normalizedRoot.startsWith("/") && !/^[A-Za-z]:\//.test(normalizedRoot)) return `${normalizedRoot}/${relativePath}`;
|
|
1418
|
+
return toPosixPath(resolve(root, relativePath));
|
|
1419
|
+
}
|
|
1349
1420
|
function toPosixPath(p) {
|
|
1350
1421
|
return p.replace(/\\/g, "/");
|
|
1351
1422
|
}
|
package/dist/pages-router.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
//#region src/pages-router.ts
|
|
4
4
|
const PAGE_EXTENSIONS = new Set([
|
|
5
5
|
".tsx",
|
|
6
|
+
".tsrx",
|
|
6
7
|
".ts",
|
|
7
8
|
".jsx",
|
|
8
9
|
".js",
|
|
@@ -11,6 +12,7 @@ const PAGE_EXTENSIONS = new Set([
|
|
|
11
12
|
]);
|
|
12
13
|
const SHELL_EXTENSIONS = new Set([
|
|
13
14
|
".tsx",
|
|
15
|
+
".tsrx",
|
|
14
16
|
".ts",
|
|
15
17
|
".jsx",
|
|
16
18
|
".js"
|
|
@@ -45,14 +47,14 @@ function scan(dir, root, pages) {
|
|
|
45
47
|
relativePath: rel,
|
|
46
48
|
routePath,
|
|
47
49
|
isIndex: name === "index",
|
|
48
|
-
isCatchAll:
|
|
49
|
-
isDynamic:
|
|
50
|
+
isCatchAll: routePath.split("/").includes("*"),
|
|
51
|
+
isDynamic: routePath.split("/").some((segment) => segment.startsWith(":")),
|
|
50
52
|
renderMode
|
|
51
53
|
});
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
function filePathToRoutePath(relativePath) {
|
|
55
|
-
let route = relativePath.replace(/\.(tsx?|jsx?|mdx?)$/, "");
|
|
57
|
+
let route = relativePath.replace(/\.(tsx?|tsrx|jsx?|mdx?)$/, "");
|
|
56
58
|
route = route.replace(/\\/g, "/");
|
|
57
59
|
if (route === "_app" || route.endsWith("/_app")) return "__shell__";
|
|
58
60
|
if (route === "index") return "/";
|
|
@@ -62,13 +64,31 @@ function filePathToRoutePath(relativePath) {
|
|
|
62
64
|
return `/${route}`;
|
|
63
65
|
}
|
|
64
66
|
function sortRoutes(pages) {
|
|
65
|
-
return [...pages].filter((p) => p.routePath !== "__shell__").sort(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
return [...pages].filter((p) => p.routePath !== "__shell__").sort(comparePagesBySpecificity);
|
|
68
|
+
}
|
|
69
|
+
function comparePagesBySpecificity(left, right) {
|
|
70
|
+
const leftSegments = splitRoutePath(left.routePath);
|
|
71
|
+
const rightSegments = splitRoutePath(right.routePath);
|
|
72
|
+
const length = Math.max(leftSegments.length, rightSegments.length);
|
|
73
|
+
for (let index = 0; index < length; index += 1) {
|
|
74
|
+
const leftSegment = leftSegments[index];
|
|
75
|
+
const rightSegment = rightSegments[index];
|
|
76
|
+
if (!leftSegment) return -1;
|
|
77
|
+
if (!rightSegment) return 1;
|
|
78
|
+
const leftScore = getRouteSegmentSpecificity(leftSegment);
|
|
79
|
+
const rightScore = getRouteSegmentSpecificity(rightSegment);
|
|
80
|
+
if (leftScore !== rightScore) return rightScore - leftScore;
|
|
81
|
+
if (leftScore === 3 && leftSegment !== rightSegment) return leftSegment.localeCompare(rightSegment);
|
|
82
|
+
}
|
|
83
|
+
return left.routePath.localeCompare(right.routePath);
|
|
84
|
+
}
|
|
85
|
+
function splitRoutePath(routePath) {
|
|
86
|
+
return routePath.split("/").filter(Boolean);
|
|
87
|
+
}
|
|
88
|
+
function getRouteSegmentSpecificity(segment) {
|
|
89
|
+
if (segment === "*") return 1;
|
|
90
|
+
if (segment.startsWith(":")) return 2;
|
|
91
|
+
return 3;
|
|
72
92
|
}
|
|
73
93
|
const RENDER_MODE_RE = /export\s+const\s+RENDER_MODE\s*=\s*["'](\w+)["']/;
|
|
74
94
|
function extractRenderMode(source) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pracht/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://github.com/JoviDeCroock/pracht/tree/main/packages/vite-plugin",
|
|
6
6
|
"bugs": {
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@preact/preset-vite": "^2.10.5",
|
|
33
33
|
"@prefresh/vite": "^2.0.0",
|
|
34
|
-
"@pracht/adapter-node": "0.
|
|
35
|
-
"@pracht/core": "0.
|
|
34
|
+
"@pracht/adapter-node": "0.2.0",
|
|
35
|
+
"@pracht/core": "0.6.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"vite": "^8.0.0"
|