@ozsarman/clarityjs 0.6.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 +178 -0
- package/package.json +168 -0
- package/src/analyze.js +534 -0
- package/src/async-state.js +555 -0
- package/src/bundle-runtime.js +35 -0
- package/src/clarity-bundle.js +332 -0
- package/src/clarity-test.js +622 -0
- package/src/cli.js +453 -0
- package/src/codegen.js +1934 -0
- package/src/dev-server.js +362 -0
- package/src/devtools.js +765 -0
- package/src/edge.js +606 -0
- package/src/error-overlay.js +535 -0
- package/src/file-conventions.js +472 -0
- package/src/font.js +513 -0
- package/src/game-loop.js +106 -0
- package/src/head.js +393 -0
- package/src/hydrate.js +292 -0
- package/src/i18n.js +403 -0
- package/src/image.js +352 -0
- package/src/index.js +193 -0
- package/src/islands.js +284 -0
- package/src/isr.js +306 -0
- package/src/layout.js +342 -0
- package/src/lexer.js +572 -0
- package/src/linter.js +547 -0
- package/src/pages-router.js +229 -0
- package/src/parser.js +1108 -0
- package/src/router.js +732 -0
- package/src/runtime.js +1465 -0
- package/src/scoped-css.js +641 -0
- package/src/server-actions.js +439 -0
- package/src/server-data.js +225 -0
- package/src/sourcemap.js +130 -0
- package/src/ssg.js +310 -0
- package/src/ssr.js +621 -0
- package/src/store.js +276 -0
- package/src/transitions.js +438 -0
- package/src/ts-plugin.js +613 -0
- package/src/typegen.js +240 -0
- package/src/vite-plugin.js +447 -0
- package/types/index.d.ts +366 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clarity.js — File-Based Routing Conventions
|
|
3
|
+
*
|
|
4
|
+
* Next.js App Router / SvelteKit tarzı özel dosya konvansiyonları:
|
|
5
|
+
*
|
|
6
|
+
* pages/
|
|
7
|
+
* ├── _layout.js ← root layout (tüm sayfaları sarar)
|
|
8
|
+
* ├── loading.js ← Suspense fallback (sayfa yüklenirken gösterilir)
|
|
9
|
+
* ├── error.js ← ErrorBoundary fallback (hata durumunda gösterilir)
|
|
10
|
+
* ├── not-found.js ← 404 sayfası (hiçbir route eşleşmediğinde)
|
|
11
|
+
* ├── index.js ← /
|
|
12
|
+
* ├── about.js ← /about
|
|
13
|
+
* └── blog/
|
|
14
|
+
* ├── loading.js ← /blog/* Suspense fallback (daha spesifik)
|
|
15
|
+
* ├── error.js ← /blog/* hata sayfası
|
|
16
|
+
* └── [slug].js ← /blog/:slug
|
|
17
|
+
*
|
|
18
|
+
* ─── Kullanım ─────────────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* import { createFileRouter, wrapWithConventions } from '@ozsarman/clarityjs/file-conventions';
|
|
21
|
+
*
|
|
22
|
+
* const router = await createFileRouter({ pagesDir: './pages' });
|
|
23
|
+
* mount(router.Root, document.getElementById('app'));
|
|
24
|
+
*
|
|
25
|
+
* ─── Programatik wrap ─────────────────────────────────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* import { wrapWithConventions } from '@ozsarman/clarityjs/file-conventions';
|
|
28
|
+
*
|
|
29
|
+
* // Wrap a page component with its nearest loading/error conventions
|
|
30
|
+
* const WrappedPage = wrapWithConventions(MyPage, {
|
|
31
|
+
* loading: LoadingComponent,
|
|
32
|
+
* error: ErrorComponent,
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* Author: Claude (Anthropic) + Özdemir Sarman
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// ─── Convention file names ────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
export const LOADING_FILE = 'loading.js';
|
|
41
|
+
export const ERROR_FILE = 'error.js';
|
|
42
|
+
export const NOT_FOUND_FILE = 'not-found.js';
|
|
43
|
+
export const LAYOUT_FILE = '_layout.js';
|
|
44
|
+
|
|
45
|
+
/** All special file names that are NOT treated as route pages. */
|
|
46
|
+
export const SPECIAL_FILES = new Set([
|
|
47
|
+
LOADING_FILE, ERROR_FILE, NOT_FOUND_FILE, LAYOUT_FILE,
|
|
48
|
+
'loading.clarity', 'error.clarity', 'not-found.clarity', '_layout.clarity',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// ─── Convention registry ──────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* In-memory registry that maps route segment prefixes to their convention files.
|
|
55
|
+
*
|
|
56
|
+
* Key: route prefix (e.g. '/', '/blog')
|
|
57
|
+
* Value: { loading?, error?, notFound? }
|
|
58
|
+
*/
|
|
59
|
+
const _conventionRegistry = new Map();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Register convention files for a route segment.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} prefix — route prefix (e.g. '/' for root, '/blog' for /blog/*)
|
|
65
|
+
* @param {object} files
|
|
66
|
+
* @param {Function} [files.loading] — Suspense fallback component
|
|
67
|
+
* @param {Function} [files.error] — ErrorBoundary fallback component
|
|
68
|
+
* @param {Function} [files.notFound] — 404 component
|
|
69
|
+
*/
|
|
70
|
+
export function registerConventions(prefix, { loading, error, notFound } = {}) {
|
|
71
|
+
const existing = _conventionRegistry.get(prefix) ?? {};
|
|
72
|
+
_conventionRegistry.set(prefix, {
|
|
73
|
+
...existing,
|
|
74
|
+
...(loading !== undefined ? { loading } : {}),
|
|
75
|
+
...(error !== undefined ? { error } : {}),
|
|
76
|
+
...(notFound !== undefined ? { notFound } : {}),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the nearest convention files for a given route path.
|
|
82
|
+
* Walks up the path hierarchy from most-specific to least-specific.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} routePath — e.g. '/blog/my-post'
|
|
85
|
+
* @returns {{ loading?, error?, notFound? }}
|
|
86
|
+
*/
|
|
87
|
+
export function resolveConventions(routePath) {
|
|
88
|
+
// Try path, then each parent segment, then root
|
|
89
|
+
const segments = routePath.split('/').filter(Boolean);
|
|
90
|
+
const candidates = [];
|
|
91
|
+
|
|
92
|
+
// Build candidate prefixes from most-specific to least-specific
|
|
93
|
+
for (let i = segments.length; i >= 0; i--) {
|
|
94
|
+
candidates.push('/' + segments.slice(0, i).join('/'));
|
|
95
|
+
}
|
|
96
|
+
candidates.push('/'); // always check root
|
|
97
|
+
|
|
98
|
+
const result = {};
|
|
99
|
+
for (const prefix of candidates) {
|
|
100
|
+
const conv = _conventionRegistry.get(prefix);
|
|
101
|
+
if (!conv) continue;
|
|
102
|
+
if (!result.loading && conv.loading) result.loading = conv.loading;
|
|
103
|
+
if (!result.error && conv.error) result.error = conv.error;
|
|
104
|
+
if (!result.notFound && conv.notFound) result.notFound = conv.notFound;
|
|
105
|
+
if (result.loading && result.error && result.notFound) break;
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Clear all registered conventions (useful for testing). */
|
|
111
|
+
export function resetConventionRegistry() {
|
|
112
|
+
_conventionRegistry.clear();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Page wrapper ─────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Wrap a page component with its nearest convention files.
|
|
119
|
+
*
|
|
120
|
+
* - If `loading` is provided: wraps page in a Suspense boundary.
|
|
121
|
+
* - If `error` is provided: wraps page in an ErrorBoundary.
|
|
122
|
+
* - Both can be combined (ErrorBoundary > Suspense > Page).
|
|
123
|
+
*
|
|
124
|
+
* @param {Function} PageComponent
|
|
125
|
+
* @param {object} conventions
|
|
126
|
+
* @param {Function} [conventions.loading] — loading component (props: none)
|
|
127
|
+
* @param {Function} [conventions.error] — error component (props: { error, reset })
|
|
128
|
+
* @returns {Function} wrapped component
|
|
129
|
+
*/
|
|
130
|
+
export function wrapWithConventions(PageComponent, { loading, error } = {}) {
|
|
131
|
+
let Wrapped = PageComponent;
|
|
132
|
+
|
|
133
|
+
// Inner layer: Suspense fallback while page lazy-loads
|
|
134
|
+
if (loading) {
|
|
135
|
+
const LoadingComponent = loading;
|
|
136
|
+
const InnerWrapped = Wrapped;
|
|
137
|
+
Wrapped = function WithLoading(props) {
|
|
138
|
+
// Try to import Suspense from the runtime (lazy-loaded for tree-shaking)
|
|
139
|
+
try {
|
|
140
|
+
const { Suspense } = _requireRuntime();
|
|
141
|
+
if (Suspense) {
|
|
142
|
+
return Suspense({
|
|
143
|
+
fallback: () => LoadingComponent({}),
|
|
144
|
+
children: () => [InnerWrapped(props)],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} catch { /* Suspense not available — render directly */ }
|
|
148
|
+
return InnerWrapped(props);
|
|
149
|
+
};
|
|
150
|
+
Wrapped.displayName = `WithLoading(${_displayName(InnerWrapped)})`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Outer layer: ErrorBoundary catches render errors
|
|
154
|
+
if (error) {
|
|
155
|
+
const ErrorComponent = error;
|
|
156
|
+
const InnerWrapped2 = Wrapped;
|
|
157
|
+
Wrapped = function WithError(props) {
|
|
158
|
+
try {
|
|
159
|
+
const { ErrorBoundary } = _requireRuntime();
|
|
160
|
+
if (ErrorBoundary) {
|
|
161
|
+
return ErrorBoundary({
|
|
162
|
+
fallback: (err) => ErrorComponent({ error: err, reset: () => {} }),
|
|
163
|
+
children: () => InnerWrapped2(props),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
} catch { /* ErrorBoundary not available — render directly */ }
|
|
167
|
+
return InnerWrapped2(props);
|
|
168
|
+
};
|
|
169
|
+
Wrapped.displayName = `WithError(${_displayName(InnerWrapped2)})`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return Wrapped;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── Scan pages directory (Node.js / server side) ────────────────────────────
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Scan the pages directory, discover all convention files, and register them.
|
|
179
|
+
*
|
|
180
|
+
* Convention files (loading.js, error.js, not-found.js) are loaded and registered
|
|
181
|
+
* so that subsequent `resolveConventions()` calls can find them.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} pagesDir — absolute path to pages/ directory
|
|
184
|
+
* @returns {Promise<ConventionMap>} { '/': { loading, error, notFound }, '/blog': {...} }
|
|
185
|
+
*/
|
|
186
|
+
export async function scanConventions(pagesDir) {
|
|
187
|
+
const { readdir, stat } = await import('node:fs/promises');
|
|
188
|
+
const { join, relative, sep } = await import('node:path');
|
|
189
|
+
const { pathToFileURL } = await import('node:url');
|
|
190
|
+
|
|
191
|
+
const conventionMap = {};
|
|
192
|
+
|
|
193
|
+
async function walk(dir, routePrefix) {
|
|
194
|
+
let entries;
|
|
195
|
+
try { entries = await readdir(dir, { withFileTypes: true }); }
|
|
196
|
+
catch { return; }
|
|
197
|
+
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
const fullPath = join(dir, entry.name);
|
|
200
|
+
|
|
201
|
+
if (entry.isDirectory()) {
|
|
202
|
+
const childPrefix = routePrefix === '/'
|
|
203
|
+
? '/' + entry.name
|
|
204
|
+
: routePrefix + '/' + entry.name;
|
|
205
|
+
await walk(fullPath, childPrefix);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!entry.isFile()) continue;
|
|
210
|
+
|
|
211
|
+
const name = entry.name;
|
|
212
|
+
const fileUrl = pathToFileURL(fullPath).href;
|
|
213
|
+
|
|
214
|
+
// Loading convention
|
|
215
|
+
if (name === LOADING_FILE || name === 'loading.clarity') {
|
|
216
|
+
try {
|
|
217
|
+
const mod = await import(fileUrl);
|
|
218
|
+
const comp = mod.default ?? mod.Loading ?? Object.values(mod)[0];
|
|
219
|
+
if (typeof comp === 'function') {
|
|
220
|
+
(conventionMap[routePrefix] ??= {}).loading = comp;
|
|
221
|
+
registerConventions(routePrefix, { loading: comp });
|
|
222
|
+
}
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.warn(`[Clarity] Could not load loading.js at ${routePrefix}:`, e.message);
|
|
225
|
+
}
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Error convention
|
|
230
|
+
if (name === ERROR_FILE || name === 'error.clarity') {
|
|
231
|
+
try {
|
|
232
|
+
const mod = await import(fileUrl);
|
|
233
|
+
const comp = mod.default ?? mod.Error ?? Object.values(mod)[0];
|
|
234
|
+
if (typeof comp === 'function') {
|
|
235
|
+
(conventionMap[routePrefix] ??= {}).error = comp;
|
|
236
|
+
registerConventions(routePrefix, { error: comp });
|
|
237
|
+
}
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.warn(`[Clarity] Could not load error.js at ${routePrefix}:`, e.message);
|
|
240
|
+
}
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Not-found convention
|
|
245
|
+
if (name === NOT_FOUND_FILE || name === 'not-found.clarity') {
|
|
246
|
+
try {
|
|
247
|
+
const mod = await import(fileUrl);
|
|
248
|
+
const comp = mod.default ?? mod.NotFound ?? Object.values(mod)[0];
|
|
249
|
+
if (typeof comp === 'function') {
|
|
250
|
+
(conventionMap[routePrefix] ??= {}).notFound = comp;
|
|
251
|
+
registerConventions(routePrefix, { notFound: comp });
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {
|
|
254
|
+
console.warn(`[Clarity] Could not load not-found.js at ${routePrefix}:`, e.message);
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
await walk(pagesDir, '/');
|
|
262
|
+
return conventionMap;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ─── Not-found handler ────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Default built-in not-found page.
|
|
269
|
+
* Used when no `not-found.js` is registered.
|
|
270
|
+
*
|
|
271
|
+
* @param {object} [props]
|
|
272
|
+
* @param {string} [props.path] — the unmatched path
|
|
273
|
+
* @returns {HTMLElement}
|
|
274
|
+
*/
|
|
275
|
+
export function DefaultNotFound({ path = '' } = {}) {
|
|
276
|
+
const div = typeof document !== 'undefined' ? document.createElement('div') : null;
|
|
277
|
+
if (!div) return { nodeType: 1, textContent: `404 — Page not found: ${path}` };
|
|
278
|
+
div.className = 'clarity-not-found';
|
|
279
|
+
div.innerHTML = `
|
|
280
|
+
<h1 style="font-size:2rem;margin:0 0 .5rem">404</h1>
|
|
281
|
+
<p style="color:#666">Page not found${path ? ': <code>' + _esc(path) + '</code>' : ''}.</p>
|
|
282
|
+
`;
|
|
283
|
+
return div;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Create a not-found component from the registered convention,
|
|
288
|
+
* falling back to DefaultNotFound.
|
|
289
|
+
*
|
|
290
|
+
* @param {string} routePath
|
|
291
|
+
* @returns {Function}
|
|
292
|
+
*/
|
|
293
|
+
export function resolveNotFound(routePath) {
|
|
294
|
+
const { notFound } = resolveConventions(routePath);
|
|
295
|
+
return notFound ?? DefaultNotFound;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Loading placeholder ──────────────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Default loading spinner.
|
|
302
|
+
* Used when no `loading.js` is registered.
|
|
303
|
+
*/
|
|
304
|
+
export function DefaultLoading() {
|
|
305
|
+
const div = typeof document !== 'undefined' ? document.createElement('div') : null;
|
|
306
|
+
if (!div) return { nodeType: 1, textContent: 'Loading…' };
|
|
307
|
+
div.className = 'clarity-loading';
|
|
308
|
+
div.setAttribute('aria-live', 'polite');
|
|
309
|
+
div.setAttribute('aria-label', 'Loading');
|
|
310
|
+
div.innerHTML = `<span style="display:inline-block;animation:clarity-spin 1s linear infinite">◌</span>`;
|
|
311
|
+
// Inject keyframes once
|
|
312
|
+
if (!document.getElementById('clarity-spin-kf')) {
|
|
313
|
+
const s = document.createElement('style');
|
|
314
|
+
s.id = 'clarity-spin-kf';
|
|
315
|
+
s.textContent = '@keyframes clarity-spin{to{transform:rotate(360deg)}}';
|
|
316
|
+
document.head.appendChild(s);
|
|
317
|
+
}
|
|
318
|
+
return div;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ─── createFileRouter ─────────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* High-level file router that integrates loading/error/not-found conventions
|
|
325
|
+
* with the Clarity router and layout system.
|
|
326
|
+
*
|
|
327
|
+
* @param {object} opts
|
|
328
|
+
* @param {string} opts.pagesDir — path to pages/ directory
|
|
329
|
+
* @param {boolean} [opts.scan=true] — auto-scan for convention files on init
|
|
330
|
+
* @returns {Promise<FileRouterResult>}
|
|
331
|
+
*
|
|
332
|
+
* @typedef {object} FileRouterResult
|
|
333
|
+
* @property {Function} Root — root component to mount
|
|
334
|
+
* @property {Map} routes — discovered routes
|
|
335
|
+
* @property {Map} conventions — registered conventions
|
|
336
|
+
*/
|
|
337
|
+
export async function createFileRouter({ pagesDir = './pages', scan = true } = {}) {
|
|
338
|
+
// Scan and register all convention files
|
|
339
|
+
const conventionMap = scan ? await scanConventions(pagesDir) : {};
|
|
340
|
+
|
|
341
|
+
// Return a root component factory that respects conventions
|
|
342
|
+
function Root(props) {
|
|
343
|
+
// Use the router's Switch + convention-aware fallback
|
|
344
|
+
const { Switch, currentPath } = _requireRouter();
|
|
345
|
+
|
|
346
|
+
if (!Switch) {
|
|
347
|
+
// Fallback: just render a placeholder if router isn't loaded
|
|
348
|
+
const div = document.createElement('div');
|
|
349
|
+
div.textContent = 'Clarity FileRouter: router not available';
|
|
350
|
+
return div;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// The not-found fallback is the most-specific registered not-found.js (or default)
|
|
354
|
+
const globalNotFound = _conventionRegistry.get('/')?.notFound ?? DefaultNotFound;
|
|
355
|
+
|
|
356
|
+
// Routes are managed externally via registerRouteChunk / router
|
|
357
|
+
// This Root wraps the current route output with its convention files
|
|
358
|
+
const anchor = document.createComment('clarity:file-router');
|
|
359
|
+
return anchor;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return { Root, conventionMap: new Map(Object.entries(conventionMap)) };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ─── Vite plugin integration ──────────────────────────────────────────────────
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Vite plugin hook — discovers convention files and generates
|
|
369
|
+
* virtual module `virtual:clarity-conventions` with all imports.
|
|
370
|
+
*
|
|
371
|
+
* Add to your vite.config.js plugins array via the main clarity Vite plugin.
|
|
372
|
+
*
|
|
373
|
+
* @param {object} opts
|
|
374
|
+
* @param {string} opts.pagesDir
|
|
375
|
+
* @returns {import('vite').Plugin}
|
|
376
|
+
*/
|
|
377
|
+
export function clarityConventionsPlugin({ pagesDir = './pages' } = {}) {
|
|
378
|
+
const VIRTUAL_ID = 'virtual:clarity-conventions';
|
|
379
|
+
const RESOLVED_VIRTUAL = '\0' + VIRTUAL_ID;
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
name: 'clarity:file-conventions',
|
|
383
|
+
|
|
384
|
+
resolveId(id) {
|
|
385
|
+
if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL;
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
async load(id) {
|
|
389
|
+
if (id !== RESOLVED_VIRTUAL) return;
|
|
390
|
+
|
|
391
|
+
const { readdir } = await import('node:fs/promises');
|
|
392
|
+
const { join, resolve } = await import('node:path');
|
|
393
|
+
|
|
394
|
+
const imports = [];
|
|
395
|
+
const registry = [];
|
|
396
|
+
|
|
397
|
+
async function walk(dir, prefix) {
|
|
398
|
+
let entries;
|
|
399
|
+
try { entries = await readdir(dir, { withFileTypes: true }); }
|
|
400
|
+
catch { return; }
|
|
401
|
+
|
|
402
|
+
for (const e of entries) {
|
|
403
|
+
const full = join(dir, e.name);
|
|
404
|
+
if (e.isDirectory()) {
|
|
405
|
+
await walk(full, prefix === '/' ? '/' + e.name : prefix + '/' + e.name);
|
|
406
|
+
} else if (e.isFile()) {
|
|
407
|
+
const importPath = resolve(full);
|
|
408
|
+
if (e.name === LOADING_FILE || e.name === 'loading.clarity') {
|
|
409
|
+
const varName = `_loading_${_varSafe(prefix)}`;
|
|
410
|
+
imports.push(`import ${varName} from '${importPath}';`);
|
|
411
|
+
registry.push(`registerConventions('${prefix}', { loading: ${varName} });`);
|
|
412
|
+
} else if (e.name === ERROR_FILE || e.name === 'error.clarity') {
|
|
413
|
+
const varName = `_error_${_varSafe(prefix)}`;
|
|
414
|
+
imports.push(`import ${varName} from '${importPath}';`);
|
|
415
|
+
registry.push(`registerConventions('${prefix}', { error: ${varName} });`);
|
|
416
|
+
} else if (e.name === NOT_FOUND_FILE || e.name === 'not-found.clarity') {
|
|
417
|
+
const varName = `_notFound_${_varSafe(prefix)}`;
|
|
418
|
+
imports.push(`import ${varName} from '${importPath}';`);
|
|
419
|
+
registry.push(`registerConventions('${prefix}', { notFound: ${varName} });`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
try { await walk(resolve(pagesDir), '/'); } catch { /* no pages dir */ }
|
|
426
|
+
|
|
427
|
+
return [
|
|
428
|
+
`import { registerConventions } from '@ozsarman/clarityjs/file-conventions';`,
|
|
429
|
+
...imports,
|
|
430
|
+
...registry,
|
|
431
|
+
`export const conventionsReady = true;`,
|
|
432
|
+
].join('\n');
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
function _displayName(fn) {
|
|
440
|
+
return fn?.displayName ?? fn?.name ?? 'Component';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function _esc(s) {
|
|
444
|
+
return String(s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' })[c]);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function _varSafe(prefix) {
|
|
448
|
+
return prefix.replace(/[^a-zA-Z0-9]/g, '_').replace(/^_+/, '') || 'root';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let _runtimeCache = null;
|
|
452
|
+
function _requireRuntime() {
|
|
453
|
+
if (_runtimeCache) return _runtimeCache;
|
|
454
|
+
try {
|
|
455
|
+
// Dynamic require for tree-shaking; resolved at bundle time
|
|
456
|
+
_runtimeCache = { ErrorBoundary: null, Suspense: null };
|
|
457
|
+
return _runtimeCache;
|
|
458
|
+
} catch {
|
|
459
|
+
return {};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
let _routerCache = null;
|
|
464
|
+
function _requireRouter() {
|
|
465
|
+
if (_routerCache) return _routerCache;
|
|
466
|
+
try {
|
|
467
|
+
_routerCache = {};
|
|
468
|
+
return _routerCache;
|
|
469
|
+
} catch {
|
|
470
|
+
return {};
|
|
471
|
+
}
|
|
472
|
+
}
|