@emkodev/emroute 1.7.2 → 1.7.3
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/dist/emroute.js +135 -31
- package/dist/emroute.js.map +2 -2
- package/dist/runtime/abstract.runtime.d.ts +11 -0
- package/dist/runtime/abstract.runtime.js +53 -0
- package/dist/runtime/abstract.runtime.js.map +1 -1
- package/dist/runtime/bun/fs/bun-fs.runtime.js +3 -1
- package/dist/runtime/bun/fs/bun-fs.runtime.js.map +1 -1
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js +3 -1
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js.map +1 -1
- package/dist/runtime/universal/fs/universal-fs.runtime.js +3 -1
- package/dist/runtime/universal/fs/universal-fs.runtime.js.map +1 -1
- package/dist/server/build.util.js +35 -6
- package/dist/server/build.util.js.map +1 -1
- package/dist/server/emroute.server.js +17 -7
- package/dist/server/emroute.server.js.map +1 -1
- package/dist/server/server-api.type.d.ts +3 -0
- package/dist/src/component/abstract.component.d.ts +1 -1
- package/dist/src/component/abstract.component.js.map +1 -1
- package/dist/src/element/component.element.js +17 -3
- package/dist/src/element/component.element.js.map +1 -1
- package/dist/src/element/markdown.element.js +1 -1
- package/dist/src/element/markdown.element.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/renderer/spa/thin-client.js +28 -6
- package/dist/src/renderer/spa/thin-client.js.map +1 -1
- package/dist/src/renderer/ssr/html.renderer.js +1 -1
- package/dist/src/renderer/ssr/html.renderer.js.map +1 -1
- package/dist/src/renderer/ssr/md.renderer.js +2 -2
- package/dist/src/renderer/ssr/md.renderer.js.map +1 -1
- package/dist/src/renderer/ssr/ssr.renderer.js +6 -6
- package/dist/src/renderer/ssr/ssr.renderer.js.map +1 -1
- package/dist/src/route/route.core.js +21 -7
- package/dist/src/route/route.core.js.map +1 -1
- package/dist/src/type/element.type.d.ts +19 -0
- package/dist/src/type/element.type.js +9 -0
- package/dist/src/type/element.type.js.map +1 -0
- package/dist/src/util/widget-resolve.util.js +1 -1
- package/dist/src/util/widget-resolve.util.js.map +1 -1
- package/dist/src/widget/widget.registry.js +1 -1
- package/dist/src/widget/widget.registry.js.map +1 -1
- package/package.json +1 -1
- package/runtime/abstract.runtime.ts +67 -0
- package/runtime/bun/fs/bun-fs.runtime.ts +2 -0
- package/runtime/bun/sqlite/bun-sqlite.runtime.ts +2 -0
- package/runtime/universal/fs/universal-fs.runtime.ts +2 -0
- package/server/build.util.ts +37 -5
- package/server/emroute.server.ts +20 -6
- package/server/server-api.type.ts +4 -0
- package/src/component/abstract.component.ts +1 -1
- package/src/element/component.element.ts +16 -4
- package/src/element/markdown.element.ts +1 -1
- package/src/index.ts +1 -0
- package/src/renderer/spa/thin-client.ts +30 -5
- package/src/renderer/ssr/html.renderer.ts +1 -1
- package/src/renderer/ssr/md.renderer.ts +5 -4
- package/src/renderer/ssr/ssr.renderer.ts +6 -6
- package/src/route/route.core.ts +17 -8
- package/src/type/element.type.ts +22 -0
- package/src/util/widget-resolve.util.ts +4 -4
- package/src/widget/widget.registry.ts +1 -1
package/dist/emroute.js
CHANGED
|
@@ -177,7 +177,7 @@ var MarkdownElement = class _MarkdownElement extends HTMLElementBase {
|
|
|
177
177
|
async loadFromSrc(src) {
|
|
178
178
|
const signal = this.abortController?.signal;
|
|
179
179
|
try {
|
|
180
|
-
const response = await fetch(src, { signal });
|
|
180
|
+
const response = await fetch(src, signal ? { signal } : {});
|
|
181
181
|
if (!response.ok) {
|
|
182
182
|
throw new Error(`Failed to fetch ${src}: ${response.status}`);
|
|
183
183
|
}
|
|
@@ -205,6 +205,17 @@ var MarkdownElement = class _MarkdownElement extends HTMLElementBase {
|
|
|
205
205
|
};
|
|
206
206
|
|
|
207
207
|
// dist/src/element/component.element.js
|
|
208
|
+
function filterUndefined(obj) {
|
|
209
|
+
const result = {};
|
|
210
|
+
let hasValue = false;
|
|
211
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
212
|
+
if (v !== void 0) {
|
|
213
|
+
result[k] = v;
|
|
214
|
+
hasValue = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return hasValue ? result : void 0;
|
|
218
|
+
}
|
|
208
219
|
var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
209
220
|
/** Shared file content cache — deduplicates fetches across all widget instances. */
|
|
210
221
|
static fileCache = /* @__PURE__ */ new Map();
|
|
@@ -367,12 +378,13 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
367
378
|
if (signal.aborted)
|
|
368
379
|
return;
|
|
369
380
|
const currentUrl = globalThis.location ? new URL(location.href) : new URL("http://localhost/");
|
|
381
|
+
const filteredFiles = filterUndefined(files);
|
|
370
382
|
const base = {
|
|
371
383
|
url: currentUrl,
|
|
372
384
|
pathname: currentUrl.pathname,
|
|
373
385
|
searchParams: currentUrl.searchParams,
|
|
374
386
|
params: this.params ?? {},
|
|
375
|
-
|
|
387
|
+
...filteredFiles ? { files: filteredFiles } : {}
|
|
376
388
|
};
|
|
377
389
|
this.context = _ComponentElement.extendContext ? _ComponentElement.extendContext(base) : base;
|
|
378
390
|
if (this.hasAttribute(SSR_ATTR)) {
|
|
@@ -459,7 +471,7 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
459
471
|
filePaths.md ? _ComponentElement.loadFile(filePaths.md) : void 0,
|
|
460
472
|
filePaths.css ? _ComponentElement.loadFile(filePaths.css) : void 0
|
|
461
473
|
]);
|
|
462
|
-
return { html, md, css };
|
|
474
|
+
return filterUndefined({ html, md, css }) ?? {};
|
|
463
475
|
}
|
|
464
476
|
async loadData() {
|
|
465
477
|
if (this.params === null)
|
|
@@ -470,7 +482,7 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
470
482
|
try {
|
|
471
483
|
const promise = this.component.getData({
|
|
472
484
|
params: this.params,
|
|
473
|
-
signal,
|
|
485
|
+
...signal ? { signal } : {},
|
|
474
486
|
context: this.context
|
|
475
487
|
});
|
|
476
488
|
this.dataPromise = promise;
|
|
@@ -549,7 +561,7 @@ var WidgetRegistry = class {
|
|
|
549
561
|
name,
|
|
550
562
|
modulePath: name,
|
|
551
563
|
tagName: `widget-${name}`,
|
|
552
|
-
files: widget.files
|
|
564
|
+
...widget.files ? { files: widget.files } : {}
|
|
553
565
|
};
|
|
554
566
|
widgets.push(entry);
|
|
555
567
|
moduleLoaders[name] = () => Promise.resolve({ default: widget.constructor });
|
|
@@ -585,7 +597,7 @@ function toRouteConfig(resolved) {
|
|
|
585
597
|
pattern: resolved.pattern,
|
|
586
598
|
type: node.redirect ? "redirect" : "page",
|
|
587
599
|
modulePath: node.redirect ?? node.files?.ts ?? node.files?.js ?? node.files?.html ?? node.files?.md ?? "",
|
|
588
|
-
files: node.files
|
|
600
|
+
...node.files ? { files: node.files } : {}
|
|
589
601
|
};
|
|
590
602
|
}
|
|
591
603
|
var RouteCore = class {
|
|
@@ -659,7 +671,7 @@ var RouteCore = class {
|
|
|
659
671
|
pattern: `/${status}`,
|
|
660
672
|
type: "page",
|
|
661
673
|
modulePath: node.files?.ts ?? node.files?.js ?? node.files?.html ?? node.files?.md ?? "",
|
|
662
|
-
files: node.files
|
|
674
|
+
...node.files ? { files: node.files } : {}
|
|
663
675
|
};
|
|
664
676
|
}
|
|
665
677
|
/** Get global error handler (root errorBoundary). */
|
|
@@ -692,7 +704,7 @@ var RouteCore = class {
|
|
|
692
704
|
pattern,
|
|
693
705
|
type: node.redirect ? "redirect" : "page",
|
|
694
706
|
modulePath: node.redirect ?? node.files?.ts ?? node.files?.js ?? node.files?.html ?? node.files?.md ?? "",
|
|
695
|
-
files: node.files
|
|
707
|
+
...node.files ? { files: node.files } : {}
|
|
696
708
|
};
|
|
697
709
|
}
|
|
698
710
|
/**
|
|
@@ -772,7 +784,14 @@ var RouteCore = class {
|
|
|
772
784
|
widgetFiles.md ? load(widgetFiles.md) : void 0,
|
|
773
785
|
widgetFiles.css ? load(widgetFiles.css) : void 0
|
|
774
786
|
]);
|
|
775
|
-
|
|
787
|
+
const result = {};
|
|
788
|
+
if (html != null)
|
|
789
|
+
result.html = html;
|
|
790
|
+
if (md != null)
|
|
791
|
+
result.md = md;
|
|
792
|
+
if (css != null)
|
|
793
|
+
result.css = css;
|
|
794
|
+
return result;
|
|
776
795
|
}
|
|
777
796
|
/**
|
|
778
797
|
* Build a RouteInfo from a matched route and the resolved URL pathname.
|
|
@@ -822,13 +841,20 @@ var RouteCore = class {
|
|
|
822
841
|
rf?.css ? fetchFile(rf.css) : void 0
|
|
823
842
|
]);
|
|
824
843
|
}
|
|
844
|
+
const files = {};
|
|
845
|
+
if (html != null)
|
|
846
|
+
files.html = html;
|
|
847
|
+
if (md != null)
|
|
848
|
+
files.md = md;
|
|
849
|
+
if (css != null)
|
|
850
|
+
files.css = css;
|
|
825
851
|
const base = {
|
|
826
852
|
...routeInfo,
|
|
827
853
|
pathname: routeInfo.url.pathname,
|
|
828
854
|
searchParams: routeInfo.url.searchParams,
|
|
829
|
-
files
|
|
830
|
-
signal,
|
|
831
|
-
isLeaf
|
|
855
|
+
files,
|
|
856
|
+
...signal ? { signal } : {},
|
|
857
|
+
...isLeaf != null ? { isLeaf } : {}
|
|
832
858
|
};
|
|
833
859
|
return this.contextProvider ? this.contextProvider(base) : base;
|
|
834
860
|
}
|
|
@@ -1051,7 +1077,7 @@ function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider
|
|
|
1051
1077
|
...routeInfo,
|
|
1052
1078
|
pathname: routeInfo.url.pathname,
|
|
1053
1079
|
searchParams: routeInfo.url.searchParams,
|
|
1054
|
-
files
|
|
1080
|
+
...files ? { files } : {}
|
|
1055
1081
|
};
|
|
1056
1082
|
const context = contextProvider ? contextProvider(baseContext) : baseContext;
|
|
1057
1083
|
const data = await widget.getData({ params, context });
|
|
@@ -1274,7 +1300,7 @@ var SsrRenderer = class _SsrRenderer {
|
|
|
1274
1300
|
try {
|
|
1275
1301
|
const ri = { url, params: {} };
|
|
1276
1302
|
const result = await this.renderRouteContent(ri, statusPage, void 0, signal);
|
|
1277
|
-
return { content: this.stripSlots(result.content), status: 404, title: result.title };
|
|
1303
|
+
return { content: this.stripSlots(result.content), status: 404, ...result.title != null ? { title: result.title } : {} };
|
|
1278
1304
|
} catch (e) {
|
|
1279
1305
|
logger.error(`[${this.label}] Failed to render 404 status page for ${url.pathname}`, e instanceof Error ? e : void 0);
|
|
1280
1306
|
}
|
|
@@ -1294,7 +1320,7 @@ var SsrRenderer = class _SsrRenderer {
|
|
|
1294
1320
|
const routeInfo = this.core.toRouteInfo(matched, url);
|
|
1295
1321
|
try {
|
|
1296
1322
|
const { content, title } = await this.renderPage(routeInfo, matched, signal);
|
|
1297
|
-
return { content, status: 200, title };
|
|
1323
|
+
return { content, status: 200, ...title != null ? { title } : {} };
|
|
1298
1324
|
} catch (error) {
|
|
1299
1325
|
if (error instanceof Response) {
|
|
1300
1326
|
const statusPage = this.core.getStatusPage(error.status);
|
|
@@ -1305,7 +1331,7 @@ var SsrRenderer = class _SsrRenderer {
|
|
|
1305
1331
|
return {
|
|
1306
1332
|
content: this.stripSlots(result.content),
|
|
1307
1333
|
status: error.status,
|
|
1308
|
-
title: result.title
|
|
1334
|
+
...result.title != null ? { title: result.title } : {}
|
|
1309
1335
|
};
|
|
1310
1336
|
} catch (e) {
|
|
1311
1337
|
logger.error(`[${this.label}] Failed to render ${error.status} status page for ${url.pathname}`, e instanceof Error ? e : void 0);
|
|
@@ -1368,7 +1394,7 @@ var SsrRenderer = class _SsrRenderer {
|
|
|
1368
1394
|
lastRenderedPattern = segments[i].route.pattern;
|
|
1369
1395
|
}
|
|
1370
1396
|
result = this.stripSlots(result);
|
|
1371
|
-
return { content: result, title: pageTitle };
|
|
1397
|
+
return { content: result, ...pageTitle != null ? { title: pageTitle } : {} };
|
|
1372
1398
|
}
|
|
1373
1399
|
/** Load component, build context, get data, render content, get title. */
|
|
1374
1400
|
async loadRouteContent(routeInfo, route, isLeaf, signal) {
|
|
@@ -1376,10 +1402,10 @@ var SsrRenderer = class _SsrRenderer {
|
|
|
1376
1402
|
const tsModule = files.ts ?? files.js;
|
|
1377
1403
|
const component = tsModule ? (await this.core.loadModule(tsModule)).default : page_component_default;
|
|
1378
1404
|
const context = await this.core.buildComponentContext(routeInfo, route, signal, isLeaf);
|
|
1379
|
-
const data = await component.getData({ params: routeInfo.params, signal, context });
|
|
1405
|
+
const data = await component.getData({ params: routeInfo.params, ...signal ? { signal } : {}, context });
|
|
1380
1406
|
const content = this.renderContent(component, { data, params: routeInfo.params, context });
|
|
1381
1407
|
const title = component.getTitle({ data, params: routeInfo.params, context });
|
|
1382
|
-
return { content, title };
|
|
1408
|
+
return { content, ...title != null ? { title } : {} };
|
|
1383
1409
|
}
|
|
1384
1410
|
/** Render a component for error boundary/handler with minimal context. */
|
|
1385
1411
|
renderComponent(component, data, context) {
|
|
@@ -1443,7 +1469,7 @@ var SsrHtmlRouter = class extends SsrRenderer {
|
|
|
1443
1469
|
return files ? this.core.loadWidgetFiles(files) : Promise.resolve({});
|
|
1444
1470
|
}, this.core.contextProvider);
|
|
1445
1471
|
}
|
|
1446
|
-
return { content, title };
|
|
1472
|
+
return { content, ...title != null ? { title } : {} };
|
|
1447
1473
|
}
|
|
1448
1474
|
renderContent(component, args) {
|
|
1449
1475
|
return component.renderHTML(args);
|
|
@@ -1565,7 +1591,7 @@ var SsrMdRouter = class extends SsrRenderer {
|
|
|
1565
1591
|
if (this.widgets) {
|
|
1566
1592
|
content = await this.resolveWidgets(content, routeInfo);
|
|
1567
1593
|
}
|
|
1568
|
-
return { content, title };
|
|
1594
|
+
return { content, ...title != null ? { title } : {} };
|
|
1569
1595
|
}
|
|
1570
1596
|
renderContent(component, args) {
|
|
1571
1597
|
return component.renderMarkdown(args);
|
|
@@ -1606,7 +1632,7 @@ Path: \`${url.pathname}\``;
|
|
|
1606
1632
|
...routeInfo,
|
|
1607
1633
|
pathname: routeInfo.url.pathname,
|
|
1608
1634
|
searchParams: routeInfo.url.searchParams,
|
|
1609
|
-
files
|
|
1635
|
+
...files ? { files } : {}
|
|
1610
1636
|
};
|
|
1611
1637
|
const context = this.core.contextProvider ? this.core.contextProvider(baseContext) : baseContext;
|
|
1612
1638
|
const data = await widget.getData({ params: block.params, context });
|
|
@@ -1660,8 +1686,10 @@ function resolveTargetNode(node, name, isRoot) {
|
|
|
1660
1686
|
// dist/runtime/abstract.runtime.js
|
|
1661
1687
|
var DEFAULT_ROUTES_DIR = "/routes";
|
|
1662
1688
|
var DEFAULT_WIDGETS_DIR = "/widgets";
|
|
1689
|
+
var DEFAULT_ELEMENTS_DIR = "/elements";
|
|
1663
1690
|
var ROUTES_MANIFEST_PATH = "/routes.manifest.json";
|
|
1664
1691
|
var WIDGETS_MANIFEST_PATH = "/widgets.manifest.json";
|
|
1692
|
+
var ELEMENTS_MANIFEST_PATH = "/elements.manifest.json";
|
|
1665
1693
|
var Runtime = class {
|
|
1666
1694
|
config;
|
|
1667
1695
|
constructor(config = {}) {
|
|
@@ -1743,10 +1771,12 @@ var Runtime = class {
|
|
|
1743
1771
|
// ── Manifest resolution ─────────────────────────────────────────────
|
|
1744
1772
|
routesManifestCache = null;
|
|
1745
1773
|
widgetsManifestCache = null;
|
|
1774
|
+
elementsManifestCache = null;
|
|
1746
1775
|
/** Clear cached manifests so the next query triggers a fresh scan. */
|
|
1747
1776
|
invalidateManifests() {
|
|
1748
1777
|
this.routesManifestCache = null;
|
|
1749
1778
|
this.widgetsManifestCache = null;
|
|
1779
|
+
this.elementsManifestCache = null;
|
|
1750
1780
|
}
|
|
1751
1781
|
/**
|
|
1752
1782
|
* Resolve the routes manifest. Called when the concrete runtime returns
|
|
@@ -1780,6 +1810,22 @@ var Runtime = class {
|
|
|
1780
1810
|
this.widgetsManifestCache = Response.json(entries);
|
|
1781
1811
|
return this.widgetsManifestCache.clone();
|
|
1782
1812
|
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Resolve the elements manifest. Called when the concrete runtime returns
|
|
1815
|
+
* 404 for ELEMENTS_MANIFEST_PATH. Scans `config.elementsDir` (or default).
|
|
1816
|
+
*/
|
|
1817
|
+
async resolveElementsManifest() {
|
|
1818
|
+
if (this.elementsManifestCache)
|
|
1819
|
+
return this.elementsManifestCache.clone();
|
|
1820
|
+
const elementsDir = this.config.elementsDir ?? DEFAULT_ELEMENTS_DIR;
|
|
1821
|
+
const dirResponse = await this.query(elementsDir + "/");
|
|
1822
|
+
if (dirResponse.status === 404) {
|
|
1823
|
+
return new Response("Not Found", { status: 404 });
|
|
1824
|
+
}
|
|
1825
|
+
const entries = await this.scanElements(elementsDir, elementsDir.replace(/^\//, ""));
|
|
1826
|
+
this.elementsManifestCache = Response.json(entries);
|
|
1827
|
+
return this.elementsManifestCache.clone();
|
|
1828
|
+
}
|
|
1783
1829
|
// ── Scanning ──────────────────────────────────────────────────────────
|
|
1784
1830
|
async *walkDirectory(dir) {
|
|
1785
1831
|
const trailingDir = dir.endsWith("/") ? dir : dir + "/";
|
|
@@ -1880,6 +1926,37 @@ var Runtime = class {
|
|
|
1880
1926
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1881
1927
|
return entries;
|
|
1882
1928
|
}
|
|
1929
|
+
async scanElements(elementsDir, pathPrefix) {
|
|
1930
|
+
const entries = [];
|
|
1931
|
+
const trailingDir = elementsDir.endsWith("/") ? elementsDir : elementsDir + "/";
|
|
1932
|
+
const response = await this.query(trailingDir);
|
|
1933
|
+
const listing = await response.json();
|
|
1934
|
+
for (const item of listing) {
|
|
1935
|
+
if (!item.endsWith("/"))
|
|
1936
|
+
continue;
|
|
1937
|
+
const name = item.slice(0, -1);
|
|
1938
|
+
if (!name.includes("-")) {
|
|
1939
|
+
console.warn(`[emroute] Skipping element "${name}": custom element names must contain a hyphen (e.g. "my-element")`);
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
let moduleFile = `${name}.element.ts`;
|
|
1943
|
+
let modulePath = `${trailingDir}${name}/${moduleFile}`;
|
|
1944
|
+
if ((await this.query(modulePath)).status === 404) {
|
|
1945
|
+
moduleFile = `${name}.element.js`;
|
|
1946
|
+
modulePath = `${trailingDir}${name}/${moduleFile}`;
|
|
1947
|
+
if ((await this.query(modulePath)).status === 404)
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
const prefix = pathPrefix ? `${pathPrefix}/` : "";
|
|
1951
|
+
entries.push({
|
|
1952
|
+
name,
|
|
1953
|
+
modulePath: `${prefix}${name}/${moduleFile}`,
|
|
1954
|
+
tagName: name
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1958
|
+
return entries;
|
|
1959
|
+
}
|
|
1883
1960
|
};
|
|
1884
1961
|
|
|
1885
1962
|
// dist/server/emroute.server.js
|
|
@@ -2018,6 +2095,11 @@ async function createEmrouteServer(config, runtime) {
|
|
|
2018
2095
|
widgetFiles = imported.widgetFiles;
|
|
2019
2096
|
}
|
|
2020
2097
|
}
|
|
2098
|
+
let discoveredElementEntries = [];
|
|
2099
|
+
const elementsResponse = await runtime.query(ELEMENTS_MANIFEST_PATH);
|
|
2100
|
+
if (elementsResponse.status !== 404) {
|
|
2101
|
+
discoveredElementEntries = await elementsResponse.json();
|
|
2102
|
+
}
|
|
2021
2103
|
let ssrHtmlRouter = null;
|
|
2022
2104
|
let ssrMdRouter = null;
|
|
2023
2105
|
function buildSsrRouters() {
|
|
@@ -2029,22 +2111,23 @@ async function createEmrouteServer(config, runtime) {
|
|
|
2029
2111
|
ssrHtmlRouter = new SsrHtmlRouter(resolver, {
|
|
2030
2112
|
fileReader: (path) => runtime.query(path, { as: "text" }),
|
|
2031
2113
|
moduleLoaders,
|
|
2032
|
-
markdownRenderer: config.markdownRenderer,
|
|
2033
|
-
extendContext: config.extendContext,
|
|
2034
|
-
widgets,
|
|
2114
|
+
...config.markdownRenderer ? { markdownRenderer: config.markdownRenderer } : {},
|
|
2115
|
+
...config.extendContext ? { extendContext: config.extendContext } : {},
|
|
2116
|
+
...widgets ? { widgets } : {},
|
|
2035
2117
|
widgetFiles
|
|
2036
2118
|
});
|
|
2037
2119
|
ssrMdRouter = new SsrMdRouter(resolver, {
|
|
2038
2120
|
fileReader: (path) => runtime.query(path, { as: "text" }),
|
|
2039
2121
|
moduleLoaders,
|
|
2040
|
-
extendContext: config.extendContext,
|
|
2041
|
-
widgets,
|
|
2122
|
+
...config.extendContext ? { extendContext: config.extendContext } : {},
|
|
2123
|
+
...widgets ? { widgets } : {},
|
|
2042
2124
|
widgetFiles
|
|
2043
2125
|
});
|
|
2044
2126
|
}
|
|
2045
2127
|
buildSsrRouters();
|
|
2046
2128
|
const title = config.title ?? "emroute";
|
|
2047
|
-
|
|
2129
|
+
const shellBase = spa === "root" || spa === "only" ? appBase : htmlBase;
|
|
2130
|
+
let shell = await resolveShell(runtime, title, shellBase);
|
|
2048
2131
|
if ((await runtime.query("/main.css")).status !== 404) {
|
|
2049
2132
|
shell = shell.replace("</head>", ' <link rel="stylesheet" href="/main.css">\n</head>');
|
|
2050
2133
|
}
|
|
@@ -2137,6 +2220,9 @@ async function createEmrouteServer(config, runtime) {
|
|
|
2137
2220
|
get widgetEntries() {
|
|
2138
2221
|
return discoveredWidgetEntries;
|
|
2139
2222
|
},
|
|
2223
|
+
get elementEntries() {
|
|
2224
|
+
return discoveredElementEntries;
|
|
2225
|
+
},
|
|
2140
2226
|
get shell() {
|
|
2141
2227
|
return shell;
|
|
2142
2228
|
}
|
|
@@ -2321,20 +2407,36 @@ async function bootEmrouteApp(options) {
|
|
|
2321
2407
|
const routeTree = await routesResponse.json();
|
|
2322
2408
|
const widgetsResponse = await runtime.handle(WIDGETS_MANIFEST_PATH);
|
|
2323
2409
|
const widgetEntries = widgetsResponse.ok ? await widgetsResponse.json() : [];
|
|
2324
|
-
const
|
|
2410
|
+
const elementsResponse = await runtime.handle(ELEMENTS_MANIFEST_PATH);
|
|
2411
|
+
const elementEntries = elementsResponse.ok ? await elementsResponse.json() : [];
|
|
2412
|
+
const moduleLoaders = buildLazyLoaders(routeTree, widgetEntries, elementEntries, runtime);
|
|
2325
2413
|
const widgets = new WidgetRegistry();
|
|
2326
2414
|
for (const entry of widgetEntries) {
|
|
2327
2415
|
ComponentElement.registerLazy(entry.name, entry.files, moduleLoaders[entry.modulePath]);
|
|
2328
2416
|
}
|
|
2417
|
+
for (const entry of elementEntries) {
|
|
2418
|
+
const loader = moduleLoaders[entry.modulePath];
|
|
2419
|
+
if (loader) {
|
|
2420
|
+
loader().then((mod) => {
|
|
2421
|
+
const cls = mod.default;
|
|
2422
|
+
if (typeof cls === "function" && !customElements.get(entry.tagName)) {
|
|
2423
|
+
customElements.define(entry.tagName, cls);
|
|
2424
|
+
}
|
|
2425
|
+
}).catch((e) => {
|
|
2426
|
+
console.error(`[emroute] Failed to load element ${entry.tagName}:`, e);
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
const mdRenderer = MarkdownElement.getConfiguredRenderer();
|
|
2329
2431
|
const server = await createEmrouteServer({
|
|
2330
2432
|
routeTree,
|
|
2331
2433
|
widgets,
|
|
2332
2434
|
moduleLoaders,
|
|
2333
|
-
markdownRenderer:
|
|
2435
|
+
...mdRenderer ? { markdownRenderer: mdRenderer } : {}
|
|
2334
2436
|
}, runtime);
|
|
2335
2437
|
return createEmrouteApp(server, options);
|
|
2336
2438
|
}
|
|
2337
|
-
function buildLazyLoaders(tree, widgetEntries, runtime) {
|
|
2439
|
+
function buildLazyLoaders(tree, widgetEntries, elementEntries, runtime) {
|
|
2338
2440
|
const paths = /* @__PURE__ */ new Set();
|
|
2339
2441
|
function walk(node) {
|
|
2340
2442
|
const modulePath = node.files?.ts ?? node.files?.js;
|
|
@@ -2356,6 +2458,8 @@ function buildLazyLoaders(tree, widgetEntries, runtime) {
|
|
|
2356
2458
|
walk(tree);
|
|
2357
2459
|
for (const entry of widgetEntries)
|
|
2358
2460
|
paths.add(entry.modulePath);
|
|
2461
|
+
for (const entry of elementEntries)
|
|
2462
|
+
paths.add(entry.modulePath);
|
|
2359
2463
|
const loaders = {};
|
|
2360
2464
|
for (const path of paths) {
|
|
2361
2465
|
const absolute = path.startsWith("/") ? path : "/" + path;
|