rip-lang 3.7.4 → 3.8.9

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.
@@ -0,0 +1,962 @@
1
+ let PATH_RE, PROXIES, RAW, SIGNALS, STASH, __batch, __effect, __state, _keysVersion, _proxy, _toFn, _writeVersion, arraysEqual, buildComponentMap, buildRoutes, compileAndImport, connectWatch, fileToComponentName, fileToPattern, findAllComponents, findComponent, getContext, getLayoutChain, getSignal, hasContext, keysSignal, makeProxy, matchRoute, patternToRegex, setContext, stashGet, stashSet, walk, wrapDeep;
2
+
3
+ ({__state, __effect, __batch} = globalThis.__rip);
4
+ ({setContext, getContext, hasContext} = globalThis.__ripComponent || {});
5
+ STASH = Symbol('stash');
6
+ SIGNALS = Symbol('signals');
7
+ RAW = Symbol('raw');
8
+ PROXIES = new WeakMap();
9
+ _keysVersion = 0;
10
+ _writeVersion = __state(0);
11
+ getSignal = function(target, prop) {
12
+ let sig;
13
+ if (!target[SIGNALS]) {
14
+ Object.defineProperty(target, SIGNALS, {value: new Map(), enumerable: false});
15
+ }
16
+ sig = target[SIGNALS].get(prop);
17
+ if (!sig) {
18
+ sig = __state(target[prop]);
19
+ target[SIGNALS].set(prop, sig);
20
+ }
21
+ return sig;
22
+ };
23
+ keysSignal = function(target) {
24
+ return getSignal(target, Symbol.for('keys'));
25
+ };
26
+ wrapDeep = function(value) {
27
+ let existing;
28
+ if (!((value != null) && (typeof value === 'object'))) return value;
29
+ if (value[STASH]) return value;
30
+ if ((value instanceof Date) || (value instanceof RegExp) || (value instanceof Map) || (value instanceof Set) || (value instanceof Promise)) return value;
31
+ existing = PROXIES.get(value);
32
+ if (existing) return existing;
33
+ return makeProxy(value);
34
+ };
35
+ makeProxy = function(target) {
36
+ let handler, proxy;
37
+ proxy = null;
38
+ handler = {get: (function(target, prop) {
39
+ let sig, val;
40
+ if (prop === STASH) return true;
41
+ if (prop === RAW) return target;
42
+ if (typeof prop === 'symbol') return Reflect.get(target, prop);
43
+ if ((prop === 'length') && Array.isArray(target)) {
44
+ keysSignal(target).value;
45
+ return target.length;
46
+ }
47
+ if (prop === 'get') return (function(path) {
48
+ return stashGet(proxy, path);
49
+ });
50
+ if (prop === 'set') return (function(path, val) {
51
+ return stashSet(proxy, path, val);
52
+ });
53
+ sig = getSignal(target, prop);
54
+ val = sig.value;
55
+ if ((val != null) && (typeof val === 'object')) return wrapDeep(val);
56
+ return val;
57
+ }), set: (function(target, prop, value) {
58
+ let old, r;
59
+ old = target[prop];
60
+ r = value?.[RAW] ? value[RAW] : value;
61
+ if (r === old) return true;
62
+ target[prop] = r;
63
+ if (target[SIGNALS]?.has(prop)) {
64
+ target[SIGNALS].get(prop).value = r;
65
+ }
66
+ if ((old === undefined) && (r !== undefined)) {
67
+ keysSignal(target).value = ++_keysVersion;
68
+ }
69
+ (_writeVersion.value++);
70
+ return true;
71
+ }), deleteProperty: (function(target, prop) {
72
+ let sig;
73
+ (delete target[prop]);
74
+ sig = target[SIGNALS]?.get(prop);
75
+ if (sig) sig.value = undefined;
76
+ keysSignal(target).value = ++_keysVersion;
77
+ return true;
78
+ }), ownKeys: (function(target) {
79
+ keysSignal(target).value;
80
+ return Reflect.ownKeys(target);
81
+ })};
82
+ proxy = new Proxy(target, handler);
83
+ PROXIES.set(target, proxy);
84
+ return proxy;
85
+ };
86
+ PATH_RE = /([./][^./\[\s]+|\[[-+]?\d+\]|\[(?:"[^"]+"|'[^']+')\])/;
87
+ walk = function(path) {
88
+ let chr, i, list, part, result;
89
+ list = ('.' + path).split(PATH_RE);
90
+ list.shift();
91
+ result = [];
92
+ i = 0;
93
+ while (i < list.length) {
94
+ part = list[i];
95
+ chr = part[0];
96
+ if ((chr === '.') || (chr === '/')) {
97
+ result.push(part.slice(1));
98
+ } else if (chr === '[') {
99
+ if ((part[1] === '"') || (part[1] === "'")) {
100
+ result.push(part.slice(2, -2));
101
+ } else {
102
+ result.push(+part.slice(1, -1));
103
+ }
104
+ }
105
+ i += 2;
106
+ }
107
+ return result;
108
+ };
109
+ stashGet = function(proxy, path) {
110
+ let obj, segs;
111
+ segs = walk(path);
112
+ obj = proxy;
113
+ for (const seg of segs) {
114
+ if (!(obj != null)) return undefined;
115
+ obj = obj[seg];
116
+ }
117
+ return obj;
118
+ };
119
+ stashSet = function(proxy, path, value) {
120
+ let obj, segs;
121
+ segs = walk(path);
122
+ obj = proxy;
123
+ for (let i = 0; i < segs.length; i++) {
124
+ const seg = segs[i];
125
+ if (i === (segs.length - 1)) {
126
+ obj[seg] = value;
127
+ } else {
128
+ if (!(obj[seg] != null)) obj[seg] = {};
129
+ obj = obj[seg];
130
+ }
131
+ }
132
+ return value;
133
+ };
134
+ _toFn = function(source) {
135
+ return ((typeof source === 'function') ? source : (function() {
136
+ return source.value;
137
+ }));
138
+ };
139
+ _proxy = function(out, source) {
140
+ let obj;
141
+ obj = {read: (function() {
142
+ return out.read();
143
+ })};
144
+ Object.defineProperty(obj, 'value', {get: (function() {
145
+ return out.value;
146
+ }), set: (function(v) {
147
+ return (source.value = v);
148
+ })});
149
+ return obj;
150
+ };
151
+ fileToPattern = function(rel) {
152
+ let pattern;
153
+ pattern = rel.replace(/\.rip$/, '');
154
+ pattern = pattern.replace(/\[\.\.\.(\w+)\]/g, '*$1');
155
+ pattern = pattern.replace(/\[(\w+)\]/g, ':$1');
156
+ if (pattern === 'index') return '/';
157
+ pattern = pattern.replace(/\/index$/, '');
158
+ return ('/' + pattern);
159
+ };
160
+ patternToRegex = function(pattern) {
161
+ let names, str;
162
+ names = [];
163
+ str = pattern.replace(/\*(\w+)/g, function(_, name) {
164
+ names.push(name);
165
+ return '(.+)';
166
+ }).replace(/:(\w+)/g, function(_, name) {
167
+ names.push(name);
168
+ return '([^/]+)';
169
+ });
170
+ return {regex: new RegExp(('^' + str) + '$'), names};
171
+ };
172
+ matchRoute = function(path, routes) {
173
+ let match, params;
174
+ for (const route of routes) {
175
+ match = path.match(route.regex.regex);
176
+ if (match) {
177
+ params = {};
178
+ for (let i = 0; i < route.regex.names.length; i++) {
179
+ const name = route.regex.names[i];
180
+ params[name] = decodeURIComponent(match[i + 1]);
181
+ }
182
+ return {route, params};
183
+ }
184
+ }
185
+ return null;
186
+ };
187
+ buildRoutes = function(components, root = 'components') {
188
+ let allFiles, dir, layouts, name, regex, rel, routes, urlPattern;
189
+ routes = [];
190
+ layouts = new Map();
191
+ allFiles = components.listAll(root);
192
+ for (const filePath of allFiles) {
193
+ rel = filePath.slice(root.length + 1);
194
+ if (!rel.endsWith('.rip')) continue;
195
+ name = rel.split('/').pop();
196
+ if (name === '_layout.rip') {
197
+ dir = (rel === '_layout.rip') ? '' : rel.slice(0, -'/_layout.rip'.length);
198
+ layouts.set(dir, filePath);
199
+ continue;
200
+ }
201
+ if (name.startsWith('_')) continue;
202
+ urlPattern = fileToPattern(rel);
203
+ regex = patternToRegex(urlPattern);
204
+ routes.push({pattern: urlPattern, regex, file: filePath, rel});
205
+ }
206
+ routes.sort(function(a, b) {
207
+ let aCatch, aDyn, bCatch, bDyn;
208
+ aDyn = (a.pattern.match(/:/g) || []).length;
209
+ bDyn = (b.pattern.match(/:/g) || []).length;
210
+ aCatch = a.pattern.includes('*') ? 1 : 0;
211
+ bCatch = b.pattern.includes('*') ? 1 : 0;
212
+ if (aCatch !== bCatch) return (aCatch - bCatch);
213
+ if (aDyn !== bDyn) return (aDyn - bDyn);
214
+ return a.pattern.localeCompare(b.pattern);
215
+ });
216
+ return {routes, layouts};
217
+ };
218
+ getLayoutChain = function(routeFile, root, layouts) {
219
+ let chain, dir, rel, segments;
220
+ chain = [];
221
+ rel = routeFile.slice(root.length + 1);
222
+ segments = rel.split('/');
223
+ dir = '';
224
+ if (layouts.has('')) chain.push(layouts.get(''));
225
+ for (let i = 0; i < segments.length; i++) {
226
+ const seg = segments[i];
227
+ if (i === (segments.length - 1)) break;
228
+ dir = dir ? ((dir + '/') + seg) : seg;
229
+ if (layouts.has(dir)) chain.push(layouts.get(dir));
230
+ }
231
+ return chain;
232
+ };
233
+ arraysEqual = function(a, b) {
234
+ if (a.length !== b.length) return false;
235
+ for (let i = 0; i < a.length; i++) {
236
+ const item = a[i];
237
+ if (item !== b[i]) return false;
238
+ }
239
+ return true;
240
+ };
241
+ findComponent = function(mod) {
242
+ for (const key in mod) {
243
+ const val = mod[key];
244
+ if ((typeof val === 'function') && (val.prototype?.mount || val.prototype?._create)) return val;
245
+ }
246
+ return ((typeof mod.default === 'function') ? mod.default : undefined);
247
+ };
248
+ findAllComponents = function(mod) {
249
+ let result;
250
+ result = {};
251
+ for (const key in mod) {
252
+ const val = mod[key];
253
+ if ((typeof val === 'function') && (val.prototype?.mount || val.prototype?._create)) {
254
+ result[key] = val;
255
+ }
256
+ }
257
+ return result;
258
+ };
259
+ fileToComponentName = function(filePath) {
260
+ let name;
261
+ name = filePath.split('/').pop().replace(/\.rip$/, '');
262
+ return name.replace(/(^|[-_])([a-z])/g, function(_, sep, ch) {
263
+ return ch.toUpperCase();
264
+ });
265
+ };
266
+ buildComponentMap = function(components, root = 'components') {
267
+ let fileName, map, name;
268
+ map = {};
269
+ for (const path of components.listAll(root)) {
270
+ if (!path.endsWith('.rip')) continue;
271
+ fileName = path.split('/').pop();
272
+ if (fileName.startsWith('_')) continue;
273
+ name = fileToComponentName(path);
274
+ if (map[name]) {
275
+ console.warn(`[Rip] Component name collision: ${name} (${map[name]} vs ${path})`);
276
+ }
277
+ map[name] = path;
278
+ }
279
+ return map;
280
+ };
281
+ compileAndImport = async function(source, compile, components = null, path = null, resolver = null) {
282
+ let blob, cached, depMod, depSource, found, js, mod, names, needed, preamble, url;
283
+ if (components && path) {
284
+ cached = components.getCompiled(path);
285
+ if (cached) return cached;
286
+ }
287
+ js = compile(source);
288
+ if (resolver) {
289
+ needed = {};
290
+ for (const name in resolver.map) {
291
+ const depPath = resolver.map[name];
292
+ if ((depPath !== path) && js.includes(`new ${name}(`)) {
293
+ if (!resolver.classes[name]) {
294
+ depSource = components.read(depPath);
295
+ if (depSource) {
296
+ depMod = await compileAndImport(depSource, compile, components, depPath, resolver);
297
+ found = findAllComponents(depMod);
298
+ for (const k in found) {
299
+ const v = found[k];
300
+ resolver.classes[k] = v;
301
+ };
302
+ }
303
+ }
304
+ if (resolver.classes[name]) needed[name] = true;
305
+ }
306
+ }
307
+ names = Object.keys(needed);
308
+ if (names.length > 0) {
309
+ preamble = `const {${names.join(', ')}} = globalThis['${resolver.key}'];\n`;
310
+ js = preamble + js;
311
+ }
312
+ }
313
+ blob = new Blob([js], {type: 'application/javascript'});
314
+ url = URL.createObjectURL(blob);
315
+ try {
316
+ mod = await import(url);
317
+ } finally {
318
+ URL.revokeObjectURL(url);
319
+ }
320
+ if (resolver) {
321
+ found = findAllComponents(mod);
322
+ for (const k in found) {
323
+ const v = found[k];
324
+ resolver.classes[k] = v;
325
+ };
326
+ }
327
+ if (components && path) components.setCompiled(path, mod);
328
+ return mod;
329
+ };
330
+ connectWatch = function(components, router, renderer, url, base = '') {
331
+ let connect, maxDelay, retryDelay;
332
+ retryDelay = 1000;
333
+ maxDelay = 30000;
334
+ connect = function() {
335
+ let es;
336
+ es = new EventSource(url);
337
+ es.addEventListener('connected', function() {
338
+ retryDelay = 1000;
339
+ return console.log('[Rip] Hot reload connected');
340
+ });
341
+ es.addEventListener('changed', async function(e) {
342
+ let current, failed, paths, results, toFetch;
343
+ ({paths} = JSON.parse(e.data));
344
+ for (const path of paths) {
345
+ components.del(path);
346
+ }
347
+ router.rebuild();
348
+ current = router.current;
349
+ toFetch = paths.filter(function(p) {
350
+ return ((p === current.route?.file) || current.layouts?.includes(p));
351
+ });
352
+ if ((toFetch.length > 0)) {
353
+ results = await Promise.allSettled(toFetch.map(async function(path) {
354
+ let content, res;
355
+ res = await fetch((base + '/') + path);
356
+ content = await res.text();
357
+ return components.write(path, content);
358
+ }));
359
+ failed = results.filter(function(r) {
360
+ return (r.status === 'rejected');
361
+ });
362
+ for (const r of failed) {
363
+ console.error('[Rip] Hot reload fetch error:', r.reason);
364
+ };
365
+ return renderer.remount();
366
+ } });
367
+ return (es.onerror = function() {
368
+ es.close();
369
+ console.log(`[Rip] Hot reload reconnecting in ${(retryDelay / 1000)}s...`);
370
+ setTimeout(connect, retryDelay);
371
+ return (retryDelay = Math.min(retryDelay * 2, maxDelay));
372
+ });
373
+ };
374
+ return connect();
375
+ };
376
+ export { setContext, getContext, hasContext };
377
+ export const stash = (function(data = {}) {
378
+ return makeProxy(data);
379
+ });
380
+ export const raw = (function(proxy) {
381
+ return (proxy?.[RAW] ? proxy[RAW] : proxy);
382
+ });
383
+ export const isStash = (function(obj) {
384
+ return (obj?.[STASH] === true);
385
+ });
386
+ export const createResource = (function(fn, opts = {}) {
387
+ let _data, _error, _loading, load, resource;
388
+ _data = __state(opts.initial || null);
389
+ _loading = __state(false);
390
+ _error = __state(null);
391
+ load = async function() {
392
+ let result;
393
+ _loading.value = true;
394
+ _error.value = null;
395
+ return (async () => { try {
396
+ result = await fn();
397
+ return (_data.value = result);
398
+ } catch (err) {
399
+ return (_error.value = err);
400
+ } finally {
401
+ _loading.value = false;
402
+ } })();
403
+ };
404
+ resource = {data: undefined, loading: undefined, error: undefined, refetch: load};
405
+ Object.defineProperty(resource, 'data', {get: (function() {
406
+ return _data.value;
407
+ })});
408
+ Object.defineProperty(resource, 'loading', {get: (function() {
409
+ return _loading.value;
410
+ })});
411
+ Object.defineProperty(resource, 'error', {get: (function() {
412
+ return _error.value;
413
+ })});
414
+ if (!opts.lazy) load();
415
+ return resource;
416
+ });
417
+ export const delay = (function(ms, source) {
418
+ let fn, out;
419
+ fn = _toFn(source);
420
+ out = __state(!(!fn()));
421
+ __effect(function() {
422
+ let t;
423
+ if (fn()) {
424
+ t = setTimeout(function() {
425
+ return (out.value = true);
426
+ }, ms);
427
+ return (function() {
428
+ return clearTimeout(t);
429
+ });
430
+ } else {
431
+ return (out.value = false);
432
+ } });
433
+ return ((typeof source !== 'function') ? _proxy(out, source) : out);
434
+ });
435
+ export const debounce = (function(ms, source) {
436
+ let fn, out;
437
+ fn = _toFn(source);
438
+ out = __state(fn());
439
+ __effect(function() {
440
+ let t, val;
441
+ val = fn();
442
+ t = setTimeout(function() {
443
+ return (out.value = val);
444
+ }, ms);
445
+ return (function() {
446
+ return clearTimeout(t);
447
+ });
448
+ });
449
+ return ((typeof source !== 'function') ? _proxy(out, source) : out);
450
+ });
451
+ export const throttle = (function(ms, source) {
452
+ let fn, last, out;
453
+ fn = _toFn(source);
454
+ out = __state(fn());
455
+ last = 0;
456
+ __effect(function() {
457
+ let now, remaining, t, val;
458
+ val = fn();
459
+ now = Date.now();
460
+ remaining = ms - (now - last);
461
+ if ((remaining <= 0)) {
462
+ out.value = val;
463
+ return (last = now);
464
+ } else {
465
+ t = setTimeout(function() {
466
+ out.value = fn();
467
+ return (last = Date.now());
468
+ }, remaining);
469
+ return (function() {
470
+ return clearTimeout(t);
471
+ });
472
+ } });
473
+ return ((typeof source !== 'function') ? _proxy(out, source) : out);
474
+ });
475
+ export const hold = (function(ms, source) {
476
+ let fn, out;
477
+ fn = _toFn(source);
478
+ out = __state(!(!fn()));
479
+ __effect(function() {
480
+ let t;
481
+ if (fn()) {
482
+ return (out.value = true);
483
+ } else {
484
+ t = setTimeout(function() {
485
+ return (out.value = false);
486
+ }, ms);
487
+ return (function() {
488
+ return clearTimeout(t);
489
+ });
490
+ } });
491
+ return ((typeof source !== 'function') ? _proxy(out, source) : out);
492
+ });
493
+ export const createComponents = (function() {
494
+ let compiled, files, notify, watchers;
495
+ files = new Map();
496
+ watchers = [];
497
+ compiled = new Map();
498
+ notify = function(event, path) {
499
+ for (const watcher of watchers) {
500
+ watcher(event, path);
501
+ }
502
+ };
503
+ return {read: (function(path) {
504
+ return files.get(path);
505
+ }), write: (function(path, content) {
506
+ let isNew;
507
+ isNew = !files.has(path);
508
+ files.set(path, content);
509
+ compiled.delete(path);
510
+ return notify(isNew ? 'create' : 'change', path);
511
+ }), del: (function(path) {
512
+ files.delete(path);
513
+ compiled.delete(path);
514
+ return notify('delete', path);
515
+ }), exists: (function(path) {
516
+ return files.has(path);
517
+ }), size: (function() {
518
+ return files.size;
519
+ }), list: (function(dir = '') {
520
+ let prefix, rest, result;
521
+ result = [];
522
+ prefix = dir ? (dir + '/') : '';
523
+ for (const [path] of files) {
524
+ if (path.startsWith(prefix)) {
525
+ rest = path.slice(prefix.length);
526
+ if (rest.includes('/')) continue;
527
+ result.push(path);
528
+ }
529
+ }
530
+ return result;
531
+ }), listAll: (function(dir = '') {
532
+ let prefix, result;
533
+ result = [];
534
+ prefix = dir ? (dir + '/') : '';
535
+ for (const [path] of files) {
536
+ if (path.startsWith(prefix)) result.push(path);
537
+ }
538
+ return result;
539
+ }), load: (function(obj) {
540
+ for (const key in obj) {
541
+ const content = obj[key];
542
+ files.set(key, content);
543
+ }
544
+ }), watch: (function(fn) {
545
+ watchers.push(fn);
546
+ return (function() {
547
+ return watchers.splice(watchers.indexOf(fn), 1);
548
+ });
549
+ }), getCompiled: (function(path) {
550
+ return compiled.get(path);
551
+ }), setCompiled: (function(path, result) {
552
+ return compiled.set(path, result);
553
+ })};
554
+ });
555
+ export const createRouter = (function(components, opts = {}) {
556
+ let _hash, _layouts, _navigating, _params, _path, _query, _route, addBase, base, hashMode, navCallbacks, onClick, onError, onPopState, readUrl, resolve, root, router, stripBase, tree, writeUrl;
557
+ root = opts.root || 'components';
558
+ base = opts.base || '';
559
+ hashMode = opts.hash || false;
560
+ onError = opts.onError || null;
561
+ stripBase = function(url) {
562
+ return ((base && url.startsWith(base)) ? (url.slice(base.length) || '/') : url);
563
+ };
564
+ addBase = function(path) {
565
+ return (base ? (base + path) : path);
566
+ };
567
+ readUrl = function() {
568
+ let h;
569
+ if (hashMode) {
570
+ h = location.hash.slice(1);
571
+ if (!h) return '/';
572
+ return ((h[0] === '/') ? h : ('/' + h));
573
+ } else {
574
+ return ((location.pathname + location.search) + location.hash);
575
+ } };
576
+ writeUrl = function(path) {
577
+ return (hashMode ? ((path === '/') ? location.pathname : ('#' + path.slice(1))) : addBase(path));
578
+ };
579
+ _path = __state(stripBase(hashMode ? readUrl() : location.pathname));
580
+ _params = __state({});
581
+ _route = __state(null);
582
+ _layouts = __state([]);
583
+ _query = __state({});
584
+ _hash = __state('');
585
+ _navigating = delay(100, __state(false));
586
+ tree = buildRoutes(components, root);
587
+ navCallbacks = new Set();
588
+ components.watch(function(event, path) {
589
+ if (!path.startsWith(root + '/')) return;
590
+ return (tree = buildRoutes(components, root));
591
+ });
592
+ resolve = function(url) {
593
+ let hash, path, queryStr, rawPath, result;
594
+ rawPath = url.split('?')[0].split('#')[0];
595
+ path = stripBase(rawPath);
596
+ path = (path[0] === '/') ? path : ('/' + path);
597
+ queryStr = url.split('?')[1]?.split('#')[0] || '';
598
+ hash = url.includes('#') ? url.split('#')[1] : '';
599
+ result = matchRoute(path, tree.routes);
600
+ if (result) {
601
+ __batch(function() {
602
+ _path.value = path;
603
+ _params.value = result.params;
604
+ _route.value = result.route;
605
+ _layouts.value = getLayoutChain(result.route.file, root, tree.layouts);
606
+ _query.value = Object.fromEntries(new URLSearchParams(queryStr));
607
+ return (_hash.value = hash);
608
+ });
609
+ for (const cb of navCallbacks) {
610
+ cb(router.current);
611
+ };
612
+ return true;
613
+ }
614
+ if (onError) onError({status: 404, path});
615
+ return false;
616
+ };
617
+ onPopState = function() {
618
+ return resolve(readUrl());
619
+ };
620
+ if (typeof window !== 'undefined') window.addEventListener('popstate', onPopState);
621
+ onClick = function(e) {
622
+ let dest, target, url;
623
+ if ((e.button !== 0) || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
624
+ target = e.target;
625
+ while (target && (target.tagName !== 'A')) {
626
+ target = target.parentElement;
627
+ }
628
+ if (!target?.href) return;
629
+ url = new URL(target.href, location.origin);
630
+ if (url.origin !== location.origin) return;
631
+ if ((target.target === '_blank') || target.hasAttribute('data-external')) return;
632
+ e.preventDefault();
633
+ dest = (hashMode && url.hash) ? (url.hash.slice(1) || '/') : ((url.pathname + url.search) + url.hash);
634
+ return router.push(dest);
635
+ };
636
+ if (typeof document !== 'undefined') document.addEventListener('click', onClick);
637
+ router = {push: (function(url) {
638
+ return (resolve(url) ? history.pushState(null, '', writeUrl(_path.read())) : undefined);
639
+ }), replace: (function(url) {
640
+ return (resolve(url) ? history.replaceState(null, '', writeUrl(_path.read())) : undefined);
641
+ }), back: (function() {
642
+ return history.back();
643
+ }), forward: (function() {
644
+ return history.forward();
645
+ }), current: undefined, path: undefined, params: undefined, route: undefined, layouts: undefined, query: undefined, hash: undefined, navigating: undefined, onNavigate: (function(cb) {
646
+ navCallbacks.add(cb);
647
+ return (function() {
648
+ return navCallbacks.delete(cb);
649
+ });
650
+ }), rebuild: (function() {
651
+ return (tree = buildRoutes(components, root));
652
+ }), routes: undefined, init: (function() {
653
+ resolve(readUrl());
654
+ return router;
655
+ }), destroy: (function() {
656
+ if (typeof window !== 'undefined') window.removeEventListener('popstate', onPopState);
657
+ if (typeof document !== 'undefined') document.removeEventListener('click', onClick);
658
+ return navCallbacks.clear();
659
+ })};
660
+ Object.defineProperty(router, 'current', {get: (function() {
661
+ return {path: _path.value, params: _params.value, route: _route.value, layouts: _layouts.value, query: _query.value, hash: _hash.value};
662
+ })});
663
+ Object.defineProperty(router, 'path', {get: (function() {
664
+ return _path.value;
665
+ })});
666
+ Object.defineProperty(router, 'params', {get: (function() {
667
+ return _params.value;
668
+ })});
669
+ Object.defineProperty(router, 'route', {get: (function() {
670
+ return _route.value;
671
+ })});
672
+ Object.defineProperty(router, 'layouts', {get: (function() {
673
+ return _layouts.value;
674
+ })});
675
+ Object.defineProperty(router, 'query', {get: (function() {
676
+ return _query.value;
677
+ })});
678
+ Object.defineProperty(router, 'hash', {get: (function() {
679
+ return _hash.value;
680
+ })});
681
+ Object.defineProperty(router, 'navigating', {get: (function() {
682
+ return _navigating.value;
683
+ }), set: (function(v) {
684
+ return (_navigating.value = v);
685
+ })});
686
+ Object.defineProperty(router, 'routes', {get: (function() {
687
+ return tree.routes;
688
+ })});
689
+ return router;
690
+ });
691
+ export const createRenderer = (function(opts = {}) {
692
+ let app, cacheComponent, compile, componentCache, components, container, currentComponent, currentLayouts, currentRoute, disposeEffect, generation, layoutInstances, maxCacheSize, mountPoint, mountRoute, onError, renderer, resolver, router, target, unmount;
693
+ ({router, app, components, resolver, compile, target, onError} = opts);
694
+ container = (typeof target === 'string') ? document.querySelector(target) : (target || document.getElementById('app'));
695
+ if (!container) {
696
+ container = document.createElement('div');
697
+ container.id = 'app';
698
+ document.body.appendChild(container);
699
+ }
700
+ container.style.opacity = '0';
701
+ currentComponent = null;
702
+ currentRoute = null;
703
+ currentLayouts = [];
704
+ layoutInstances = [];
705
+ mountPoint = container;
706
+ generation = 0;
707
+ disposeEffect = null;
708
+ componentCache = new Map();
709
+ maxCacheSize = opts.cacheSize || 10;
710
+ cacheComponent = function() {
711
+ let evicted, oldest;
712
+ if ((currentComponent && currentRoute)) {
713
+ if (currentComponent.beforeUnmount) currentComponent.beforeUnmount();
714
+ componentCache.set(currentRoute, currentComponent);
715
+ if (componentCache.size > maxCacheSize) {
716
+ oldest = componentCache.keys().next().value;
717
+ evicted = componentCache.get(oldest);
718
+ if (evicted.unmounted) evicted.unmounted();
719
+ componentCache.delete(oldest);
720
+ };
721
+ currentComponent = null;
722
+ return (currentRoute = null);
723
+ } };
724
+ unmount = function() {
725
+ cacheComponent();
726
+ for (let _i = layoutInstances.length - 1; _i >= 0; _i--) {
727
+ const inst = layoutInstances[_i];
728
+ if (inst.beforeUnmount) inst.beforeUnmount();
729
+ if (inst.unmounted) inst.unmounted();
730
+ inst._root?.remove();
731
+ }
732
+ layoutInstances = [];
733
+ return (mountPoint = container);
734
+ };
735
+ components.watch(function(event, path) {
736
+ let evicted;
737
+ if (componentCache.has(path)) {
738
+ evicted = componentCache.get(path);
739
+ if (evicted.unmounted) evicted.unmounted();
740
+ return componentCache.delete(path);
741
+ } });
742
+ mountRoute = async function(info) {
743
+ let Component, LayoutClass, cached, gen, handled, inst, instance, layoutFiles, layoutMod, layoutSource, layoutsChanged, mod, mp, oldRoot, pageWrapper, params, pre, query, route, slot, source, wrapper;
744
+ ({route, params, layouts: layoutFiles, query} = info);
745
+ if (!route) return;
746
+ if (route.file === currentRoute) return;
747
+ gen = ++generation;
748
+ router.navigating = true;
749
+ return (async () => { try {
750
+ source = components.read(route.file);
751
+ if (!source) {
752
+ if (onError) onError({status: 404, message: `File not found: ${route.file}`});
753
+ router.navigating = false;
754
+ return;
755
+ };
756
+ mod = await compileAndImport(source, compile, components, route.file, resolver);
757
+ if (gen !== generation) {
758
+ router.navigating = false;
759
+ return;
760
+ };
761
+ Component = findComponent(mod);
762
+ if (!Component) {
763
+ if (onError) onError({status: 500, message: `No component found in ${route.file}`});
764
+ router.navigating = false;
765
+ return;
766
+ };
767
+ layoutsChanged = !arraysEqual(layoutFiles, currentLayouts);
768
+ oldRoot = currentComponent?._root;
769
+ if (layoutsChanged) {
770
+ unmount();
771
+ } else {
772
+ cacheComponent();
773
+ };
774
+ mp = layoutsChanged ? container : mountPoint;
775
+ if (layoutsChanged && (layoutFiles.length > 0)) {
776
+ container.innerHTML = '';
777
+ mp = container;
778
+ for (const layoutFile of layoutFiles) {
779
+ layoutSource = components.read(layoutFile);
780
+ if (!layoutSource) continue;
781
+ layoutMod = await compileAndImport(layoutSource, compile, components, layoutFile, resolver);
782
+ if (gen !== generation) {
783
+ router.navigating = false;
784
+ return;
785
+ }
786
+ LayoutClass = findComponent(layoutMod);
787
+ if (!LayoutClass) continue;
788
+ inst = new LayoutClass({app, params, router});
789
+ if (inst.beforeMount) inst.beforeMount();
790
+ wrapper = document.createElement('div');
791
+ wrapper.setAttribute('data-layout', layoutFile);
792
+ mp.appendChild(wrapper);
793
+ inst.mount(wrapper);
794
+ layoutInstances.push(inst);
795
+ slot = wrapper.querySelector('#content') || wrapper;
796
+ mp = slot;
797
+ }
798
+ currentLayouts = [...layoutFiles];
799
+ mountPoint = mp;
800
+ } else if (layoutsChanged) {
801
+ container.innerHTML = '';
802
+ currentLayouts = [];
803
+ mountPoint = container;
804
+ };
805
+ cached = componentCache.get(route.file);
806
+ if (cached) {
807
+ componentCache.delete(route.file);
808
+ mp.appendChild(cached._root);
809
+ currentComponent = cached;
810
+ currentRoute = route.file;
811
+ } else {
812
+ pageWrapper = document.createElement('div');
813
+ pageWrapper.setAttribute('data-component', route.file);
814
+ mp.appendChild(pageWrapper);
815
+ instance = new Component({app, params, query, router});
816
+ if (instance.beforeMount) instance.beforeMount();
817
+ instance.mount(pageWrapper);
818
+ currentComponent = instance;
819
+ currentRoute = route.file;
820
+ if (instance.load) await instance.load(params, query);
821
+ };
822
+ oldRoot?.remove();
823
+ router.navigating = false;
824
+ return ((container.style.opacity === '0') ? document.fonts.ready.then(function() {
825
+ return requestAnimationFrame(function() {
826
+ container.style.transition = 'opacity 150ms ease-in';
827
+ return (container.style.opacity = '1');
828
+ });
829
+ }) : undefined);
830
+ } catch (err) {
831
+ router.navigating = false;
832
+ container.style.opacity = '1';
833
+ console.error(`Renderer: error mounting ${route.file}:`, err);
834
+ if (onError) onError({status: 500, message: err.message, error: err});
835
+ handled = false;
836
+ for (let _i = layoutInstances.length - 1; _i >= 0; _i--) {
837
+ const inst = layoutInstances[_i];
838
+ if (inst.onError) {
839
+ try {
840
+ inst.onError(err);
841
+ handled = true;
842
+ break;
843
+ } catch (boundaryErr) {
844
+ console.error("Renderer: error boundary failed:", boundaryErr);
845
+ }
846
+ }
847
+ };
848
+ return (() => { if (!handled) {
849
+ pre = document.createElement('pre');
850
+ pre.style.cssText = 'color:red;padding:1em';
851
+ pre.textContent = err.stack || err.message;
852
+ container.innerHTML = '';
853
+ return container.appendChild(pre);
854
+ } })();
855
+ } })();
856
+ };
857
+ renderer = {start: (function() {
858
+ disposeEffect = __effect(function() {
859
+ let current;
860
+ current = router.current;
861
+ return (current.route ? mountRoute(current) : undefined);
862
+ });
863
+ router.init();
864
+ return renderer;
865
+ }), stop: (function() {
866
+ unmount();
867
+ if (disposeEffect) {
868
+ disposeEffect();
869
+ disposeEffect = null;
870
+ }
871
+ return (container.innerHTML = '');
872
+ }), remount: (function() {
873
+ let current;
874
+ current = router.current;
875
+ return (current.route ? mountRoute(current) : undefined);
876
+ }), cache: componentCache};
877
+ return renderer;
878
+ });
879
+ export const launch = (async function(appBase = '', opts = {}) {
880
+ let _save, _storage, _storageKey, app, appComponents, bundle, bundleUrl, classesKey, compile, el, hash, persist, renderer, res, resolver, router, saved, savedData, target;
881
+ appBase = appBase.replace(/\/+$/, '');
882
+ target = opts.target || '#app';
883
+ compile = opts.compile || null;
884
+ persist = opts.persist || false;
885
+ hash = opts.hash || false;
886
+ if (!compile) {
887
+ compile = globalThis?.compileToJS || null;
888
+ }
889
+ if ((typeof document !== 'undefined') && (!document.querySelector(target))) {
890
+ el = document.createElement('div');
891
+ el.id = target.replace(/^#/, '');
892
+ document.body.prepend(el);
893
+ }
894
+ if (opts.bundle) {
895
+ bundle = opts.bundle;
896
+ } else {
897
+ bundleUrl = `${appBase}/bundle`;
898
+ res = await fetch(bundleUrl);
899
+ if (!res.ok) {
900
+ throw new Error(`launch: ${bundleUrl} (${res.status})`);
901
+ };
902
+ bundle = await res.json();
903
+ }
904
+ app = stash({components: {}, routes: {}, data: {}});
905
+ if (bundle.data) app.data = bundle.data;
906
+ if (bundle.routes) {
907
+ app.routes = bundle.routes;
908
+ }
909
+ if (persist && (typeof sessionStorage !== 'undefined')) {
910
+ _storageKey = `__rip_${appBase}`;
911
+ _storage = (persist === 'local') ? localStorage : sessionStorage;
912
+ try {
913
+ saved = _storage.getItem(_storageKey);
914
+ if (saved) {
915
+ savedData = JSON.parse(saved);
916
+ for (const k in savedData) {
917
+ const v = savedData[k];
918
+ app.data[k] = v;
919
+ };
920
+ }
921
+ } catch {
922
+ null;
923
+ }
924
+ _save = function() {
925
+ return (() => { try {
926
+ return _storage.setItem(_storageKey, JSON.stringify(raw(app.data)));
927
+ } catch {
928
+ return null;
929
+ } })();
930
+ };
931
+ __effect(function() {
932
+ let t;
933
+ _writeVersion.value;
934
+ t = setTimeout(_save, 2000);
935
+ return (function() {
936
+ return clearTimeout(t);
937
+ });
938
+ });
939
+ window.addEventListener('beforeunload', _save);
940
+ }
941
+ appComponents = createComponents();
942
+ if (bundle.components) appComponents.load(bundle.components);
943
+ classesKey = `__rip_${(appBase.replace(/\//g, '_') || 'app')}`;
944
+ resolver = {map: buildComponentMap(appComponents), classes: {}, key: classesKey};
945
+ if (typeof globalThis !== 'undefined') globalThis[classesKey] = resolver.classes;
946
+ if (app.data.title && (typeof document !== 'undefined')) document.title = app.data.title;
947
+ router = createRouter(appComponents, {root: 'components', base: appBase, hash: hash, onError: (function(err) {
948
+ return console.error(`[Rip] Error ${err.status}: ${(err.message || err.path)}`);
949
+ })});
950
+ renderer = createRenderer({router: router, app: app, components: appComponents, resolver: resolver, compile: compile, target: target, onError: (function(err) {
951
+ return console.error(`[Rip] ${err.message}`, err.error);
952
+ })});
953
+ renderer.start();
954
+ if (bundle.data?.watch) {
955
+ connectWatch(appComponents, router, renderer, `${appBase}/watch`, appBase);
956
+ }
957
+ if (typeof window !== 'undefined') {
958
+ window.app = app;
959
+ window.__RIP__ = {app: app, components: appComponents, router: router, renderer: renderer, cache: renderer.cache, version: '0.3.0'};
960
+ }
961
+ return {app, components: appComponents, router, renderer};
962
+ });