@pracht/vite-plugin 0.7.6 → 0.9.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.
@@ -1,254 +0,0 @@
1
- import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
- import { basename, extname, join, relative } from "node:path";
3
- //#region src/route-loader-hints.ts
4
- const ROUTE_EXTENSIONS = new Set([
5
- ".tsx",
6
- ".ts",
7
- ".jsx",
8
- ".js",
9
- ".md",
10
- ".mdx"
11
- ]);
12
- const LOADER_DECLARATION_RE = /export\s+(?:async\s+)?(?:function|const|let|var)\s+loader\b/;
13
- const EXPORT_BLOCK_RE = /export\s*\{([^}]*)\}\s*(?:from\s*["'][^"']+["'])?/g;
14
- const EXPORT_ALL_RE = /export\s+\*\s+from\s*["'][^"']+["']/;
15
- function exportSpecifiersIncludeLoader(specifiers) {
16
- return specifiers.split(",").map((specifier) => specifier.trim()).filter(Boolean).some((specifier) => {
17
- const match = /^(?:type\s+)?([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/.exec(specifier);
18
- if (!match) return false;
19
- const [, localName, exportedName] = match;
20
- return (exportedName ?? localName) === "loader";
21
- });
22
- }
23
- function detectLoaderExport(source) {
24
- if (LOADER_DECLARATION_RE.test(source)) return true;
25
- for (const match of source.matchAll(EXPORT_BLOCK_RE)) if (exportSpecifiersIncludeLoader(match[1])) return true;
26
- return EXPORT_ALL_RE.test(source);
27
- }
28
- function scanRouteFiles(dir, files) {
29
- let entries;
30
- try {
31
- entries = readdirSync(dir);
32
- } catch {
33
- return;
34
- }
35
- for (const entry of entries) {
36
- const abs = join(dir, entry);
37
- if (statSync(abs).isDirectory()) {
38
- scanRouteFiles(abs, files);
39
- continue;
40
- }
41
- if (ROUTE_EXTENSIONS.has(extname(entry))) files.push(abs);
42
- }
43
- }
44
- function toPosixPath(path) {
45
- return path.replace(/\\/g, "/");
46
- }
47
- function createRouteLoaderHints(routesDir, options = {}) {
48
- const files = [];
49
- const hints = {};
50
- scanRouteFiles(routesDir, files);
51
- for (const file of files) {
52
- const hasLoader = detectLoaderExport(readFileSync(file, "utf-8"));
53
- const relativeToRoutesDir = toPosixPath(relative(routesDir, file));
54
- const routeRootPrefix = options.rootRelativePrefix?.replace(/\/$/, "");
55
- const appFileDir = options.appFileDir;
56
- const keys = /* @__PURE__ */ new Set();
57
- if (appFileDir) {
58
- const relativeToAppFile = toPosixPath(relative(appFileDir, file));
59
- keys.add(relativeToAppFile.startsWith(".") ? relativeToAppFile : `./${relativeToAppFile}`);
60
- }
61
- if (routeRootPrefix) keys.add(`${routeRootPrefix}/${relativeToRoutesDir}`);
62
- for (const key of keys) hints[key] = hasLoader;
63
- }
64
- return hints;
65
- }
66
- //#endregion
67
- //#region src/pages-router.ts
68
- const PAGE_EXTENSIONS = new Set([
69
- ".tsx",
70
- ".tsrx",
71
- ".ts",
72
- ".jsx",
73
- ".js",
74
- ".md",
75
- ".mdx"
76
- ]);
77
- const SHELL_EXTENSIONS = new Set([
78
- ".tsx",
79
- ".tsrx",
80
- ".ts",
81
- ".jsx",
82
- ".js"
83
- ]);
84
- function scanPagesDirectory(pagesDir) {
85
- const pages = [];
86
- scan(pagesDir, pagesDir, pages);
87
- return sortRoutes(pages);
88
- }
89
- function scan(dir, root, pages) {
90
- let entries;
91
- try {
92
- entries = readdirSync(dir);
93
- } catch {
94
- return;
95
- }
96
- for (const entry of entries) {
97
- const abs = join(dir, entry);
98
- if (statSync(abs).isDirectory()) {
99
- scan(abs, root, pages);
100
- continue;
101
- }
102
- const ext = extname(entry);
103
- if (!PAGE_EXTENSIONS.has(ext)) continue;
104
- const name = basename(entry, ext);
105
- if (name.startsWith("_") && name !== "_app") continue;
106
- const rel = relative(root, abs);
107
- const routePath = filePathToRoutePath(rel);
108
- const source = readFileSync(abs, "utf-8");
109
- const renderMode = extractRenderMode(source);
110
- const hydrationMode = extractHydrationMode(source);
111
- const hasLoader = detectLoaderExport(source);
112
- pages.push({
113
- absolutePath: abs,
114
- relativePath: rel,
115
- routePath,
116
- isIndex: name === "index",
117
- isCatchAll: routePath.split("/").includes("*"),
118
- isDynamic: routePath.split("/").some((segment) => segment.startsWith(":")),
119
- renderMode,
120
- hydrationMode,
121
- hasLoader
122
- });
123
- }
124
- }
125
- function filePathToRoutePath(relativePath) {
126
- let route = relativePath.replace(/\.(tsx?|tsrx|jsx?|mdx?)$/, "");
127
- route = route.replace(/\\/g, "/");
128
- if (route === "_app" || route.endsWith("/_app")) return "__shell__";
129
- if (route === "index") return "/";
130
- route = route.replace(/\/index$/, "");
131
- route = route.replace(/\[([^\].]+)\]/g, ":$1");
132
- route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*");
133
- return `/${route}`;
134
- }
135
- function sortRoutes(pages) {
136
- return [...pages].filter((p) => p.routePath !== "__shell__").sort(comparePagesBySpecificity);
137
- }
138
- function comparePagesBySpecificity(left, right) {
139
- const leftSegments = splitRoutePath(left.routePath);
140
- const rightSegments = splitRoutePath(right.routePath);
141
- const length = Math.max(leftSegments.length, rightSegments.length);
142
- for (let index = 0; index < length; index += 1) {
143
- const leftSegment = leftSegments[index];
144
- const rightSegment = rightSegments[index];
145
- if (!leftSegment) return -1;
146
- if (!rightSegment) return 1;
147
- const leftScore = getRouteSegmentSpecificity(leftSegment);
148
- const rightScore = getRouteSegmentSpecificity(rightSegment);
149
- if (leftScore !== rightScore) return rightScore - leftScore;
150
- if (leftScore === 3 && leftSegment !== rightSegment) return leftSegment.localeCompare(rightSegment);
151
- }
152
- return left.routePath.localeCompare(right.routePath);
153
- }
154
- function splitRoutePath(routePath) {
155
- return routePath.split("/").filter(Boolean);
156
- }
157
- function getRouteSegmentSpecificity(segment) {
158
- if (segment === "*") return 1;
159
- if (segment.startsWith(":")) return 2;
160
- return 3;
161
- }
162
- const RENDER_MODE_RE = /export\s+const\s+RENDER_MODE\s*=\s*["'](\w+)["']/;
163
- function extractRenderMode(source) {
164
- const match = RENDER_MODE_RE.exec(source);
165
- return match ? match[1] : void 0;
166
- }
167
- const HYDRATION_RE = /export\s+const\s+HYDRATION\s*=\s*["'](\w+)["']/;
168
- function extractHydrationMode(source) {
169
- const match = HYDRATION_RE.exec(source);
170
- return match ? match[1] : void 0;
171
- }
172
- function generatePagesManifestSource(pages, options) {
173
- const pagesDir = options.pagesDir;
174
- const defaultRender = options.pagesDefaultRender ?? "ssr";
175
- const prefix = options.pagesDirPrefix;
176
- const useImport = options.useImportSyntax ?? false;
177
- const appFile = scanAllFiles(pagesDir).find((f) => basename(f, extname(f)) === "_app" && SHELL_EXTENSIONS.has(extname(f)));
178
- const lines = ["import { defineApp, group, route } from \"@pracht/core/manifest\";", ""];
179
- const routeEntries = [];
180
- const notFoundPage = pages.find((page) => page.routePath === "/404");
181
- for (const page of pages) {
182
- if (page === notFoundPage) continue;
183
- const render = page.renderMode ?? defaultRender;
184
- const filePath = prefix ? `${prefix}/${page.relativePath.replace(/\\/g, "/")}` : `./${page.relativePath.replace(/\\/g, "/")}`;
185
- const fileRef = useImport ? `() => import(${JSON.stringify(filePath)})` : JSON.stringify(filePath);
186
- const metaParts = [`render: ${JSON.stringify(render)}`, `hasLoader: ${page.hasLoader ? "true" : "false"}`];
187
- if (page.hydrationMode) metaParts.push(`hydration: ${JSON.stringify(page.hydrationMode)}`);
188
- routeEntries.push(` route(${JSON.stringify(page.routePath)}, ${fileRef}, { ${metaParts.join(", ")} })`);
189
- }
190
- const notFoundEntry = notFoundPage ? buildNotFoundEntry(notFoundPage, {
191
- prefix,
192
- useImport,
193
- withShell: !!appFile
194
- }) : null;
195
- if (appFile) {
196
- const appPath = prefix ? `${prefix}/_app.${extname(appFile).slice(1)}` : `./${relative(join(pagesDir, ".."), appFile).replace(/\\/g, "/")}`;
197
- const shellRef = useImport ? `() => import(${JSON.stringify(appPath)})` : JSON.stringify(appPath);
198
- lines.push("const app = defineApp({");
199
- lines.push(" shells: {");
200
- lines.push(` pages: ${shellRef},`);
201
- lines.push(" },");
202
- lines.push(" routes: [");
203
- lines.push(` group({ shell: "pages" }, [`);
204
- lines.push(routeEntries.join(",\n"));
205
- lines.push(" ]),");
206
- lines.push(" ],");
207
- if (notFoundEntry) lines.push(notFoundEntry);
208
- lines.push("});");
209
- } else {
210
- lines.push("const app = defineApp({");
211
- lines.push(" routes: [");
212
- lines.push(routeEntries.join(",\n"));
213
- lines.push(" ],");
214
- if (notFoundEntry) lines.push(notFoundEntry);
215
- lines.push("});");
216
- }
217
- lines.push("");
218
- return lines.join("\n");
219
- }
220
- function buildNotFoundEntry(page, options) {
221
- const filePath = options.prefix ? `${options.prefix}/${page.relativePath.replace(/\\/g, "/")}` : `./${page.relativePath.replace(/\\/g, "/")}`;
222
- const configParts = [`component: ${options.useImport ? `() => import(${JSON.stringify(filePath)})` : JSON.stringify(filePath)}`];
223
- if (options.withShell) configParts.push("shell: \"pages\"");
224
- if (page.hydrationMode) configParts.push(`hydration: ${JSON.stringify(page.hydrationMode)}`);
225
- return ` notFound: { ${configParts.join(", ")} },`;
226
- }
227
- function scanAllFiles(dir) {
228
- const results = [];
229
- let entries;
230
- try {
231
- entries = readdirSync(dir);
232
- } catch {
233
- return results;
234
- }
235
- for (const entry of entries) {
236
- const abs = join(dir, entry);
237
- if (statSync(abs).isDirectory()) results.push(...scanAllFiles(abs));
238
- else results.push(abs);
239
- }
240
- return results;
241
- }
242
- function generateRoutesFile(pagesDir, outputPath, options) {
243
- writeFileSync(outputPath, [
244
- "// Auto-generated from pages/ directory by @pracht/vite-plugin.",
245
- "// Customize this file and remove `pagesDir` from pracht config to use it directly.",
246
- "",
247
- generatePagesManifestSource(scanPagesDirectory(pagesDir), {
248
- ...options,
249
- useImportSyntax: true
250
- }).replace("const app = defineApp(", "export const app = defineApp(")
251
- ].join("\n"), "utf-8");
252
- }
253
- //#endregion
254
- export { sortRoutes as a, scanPagesDirectory as i, generatePagesManifestSource as n, createRouteLoaderHints as o, generateRoutesFile as r, filePathToRoutePath as t };