@emkodev/emroute 1.10.0 → 1.12.0-beta.2

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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/core/component/widget.component.ts +3 -8
  3. package/core/pipeline/pipeline.ts +13 -5
  4. package/core/util/html.util.ts +1 -1
  5. package/core/util/js.util.ts +8 -0
  6. package/core/util/widget-resolve.util.ts +4 -2
  7. package/dist/core/component/widget.component.js +3 -7
  8. package/dist/core/component/widget.component.js.map +1 -1
  9. package/dist/core/pipeline/pipeline.d.ts +1 -0
  10. package/dist/core/pipeline/pipeline.js +12 -5
  11. package/dist/core/pipeline/pipeline.js.map +1 -1
  12. package/dist/core/util/html.util.js +1 -1
  13. package/dist/core/util/html.util.js.map +1 -1
  14. package/dist/core/util/js.util.d.ts +5 -0
  15. package/dist/core/util/js.util.js +8 -0
  16. package/dist/core/util/js.util.js.map +1 -0
  17. package/dist/core/util/widget-resolve.util.js +4 -2
  18. package/dist/core/util/widget-resolve.util.js.map +1 -1
  19. package/dist/emroute.js +120 -32
  20. package/dist/emroute.js.map +11 -10
  21. package/dist/runtime/abstract.runtime.d.ts +13 -1
  22. package/dist/runtime/abstract.runtime.js +34 -22
  23. package/dist/runtime/abstract.runtime.js.map +1 -1
  24. package/dist/runtime/bun/fs/bun-fs.runtime.d.ts +4 -0
  25. package/dist/runtime/bun/fs/bun-fs.runtime.js +20 -1
  26. package/dist/runtime/bun/fs/bun-fs.runtime.js.map +1 -1
  27. package/dist/server/build.util.d.ts +9 -15
  28. package/dist/server/build.util.js +9 -148
  29. package/dist/server/build.util.js.map +1 -1
  30. package/dist/server/dev.server.d.ts +25 -0
  31. package/dist/server/dev.server.js +81 -0
  32. package/dist/server/dev.server.js.map +1 -0
  33. package/dist/src/element/component.element.d.ts +12 -0
  34. package/dist/src/element/component.element.js +55 -3
  35. package/dist/src/element/component.element.js.map +1 -1
  36. package/dist/src/util/html.util.d.ts +5 -0
  37. package/dist/src/util/html.util.js +38 -1
  38. package/dist/src/util/html.util.js.map +1 -1
  39. package/package.json +2 -2
  40. package/runtime/abstract.runtime.ts +38 -23
  41. package/runtime/bun/fs/bun-fs.runtime.ts +23 -1
  42. package/server/build.util.ts +9 -169
  43. package/src/element/component.element.ts +63 -3
  44. package/src/util/html.util.ts +46 -1
package/dist/emroute.js CHANGED
@@ -15,8 +15,10 @@ function unescapeHtml(text) {
15
15
  return text.replaceAll("&#96;", "`").replaceAll("&#39;", "'").replaceAll("&quot;", '"').replaceAll("&gt;", ">").replaceAll("&lt;", "<").replaceAll("&amp;", "&");
16
16
  }
17
17
  function scopeWidgetCss(css, widgetName) {
18
- return `@scope (widget-${widgetName}) {
18
+ return `@layer emroute {
19
+ @scope (widget-${widgetName}) {
19
20
  ${css}
21
+ }
20
22
  }`;
21
23
  }
22
24
  var STATUS_MESSAGES = {
@@ -27,14 +29,34 @@ var STATUS_MESSAGES = {
27
29
  };
28
30
 
29
31
  // dist/src/util/html.util.js
32
+ class SsrCSSStyleSheet {
33
+ cssText = "";
34
+ replaceSync(css) {
35
+ this.cssText = css;
36
+ }
37
+ replace(css) {
38
+ this.cssText = css;
39
+ return Promise.resolve(this);
40
+ }
41
+ }
42
+ var CSSStyleSheetBase = globalThis.CSSStyleSheet ?? SsrCSSStyleSheet;
43
+
30
44
  class SsrShadowRoot {
31
45
  host;
32
46
  _innerHTML = "";
47
+ _adoptedStyleSheets = [];
33
48
  constructor(host) {
34
49
  this.host = host;
35
50
  }
51
+ get adoptedStyleSheets() {
52
+ return this._adoptedStyleSheets;
53
+ }
54
+ set adoptedStyleSheets(sheets) {
55
+ this._adoptedStyleSheets = sheets;
56
+ }
36
57
  get innerHTML() {
37
- return this._innerHTML;
58
+ const adopted = this._adoptedStyleSheets.filter((s) => s.cssText).map((s) => `<style>${s.cssText}</style>`).join("");
59
+ return adopted + this._innerHTML;
38
60
  }
39
61
  set innerHTML(value) {
40
62
  this._innerHTML = value;
@@ -57,6 +79,10 @@ class SsrShadowRoot {
57
79
  }
58
80
  }
59
81
 
82
+ class SsrElementInternals {
83
+ states = new Set;
84
+ }
85
+
60
86
  class SsrHTMLElement {
61
87
  _innerHTML = "";
62
88
  _shadowRoot = null;
@@ -97,6 +123,9 @@ class SsrHTMLElement {
97
123
  this._shadowRoot = new SsrShadowRoot(this);
98
124
  return this._shadowRoot;
99
125
  }
126
+ attachInternals() {
127
+ return new SsrElementInternals;
128
+ }
100
129
  getAttribute(name) {
101
130
  return this._attributes.get(name) ?? null;
102
131
  }
@@ -212,10 +241,12 @@ function filterUndefined(obj) {
212
241
 
213
242
  class ComponentElement extends HTMLElementBase {
214
243
  static lazyLoaders = new Map;
244
+ static sheetCache = new Map;
215
245
  static extendContext;
216
246
  static setContextProvider(provider) {
217
247
  ComponentElement.extendContext = provider;
218
248
  }
249
+ static CUSTOM_STATES = ["lazy", "loading", "hydrating", "ready", "error"];
219
250
  component;
220
251
  effectiveFiles;
221
252
  params = null;
@@ -226,15 +257,23 @@ class ComponentElement extends HTMLElementBase {
226
257
  deferred = null;
227
258
  abortController = null;
228
259
  intersectionObserver = null;
260
+ internals;
229
261
  dataPromise = null;
230
262
  constructor(component, files) {
231
263
  super();
232
264
  this.component = component;
233
265
  this.effectiveFiles = files;
266
+ this.internals = this.attachInternals();
234
267
  if (!this.shadowRoot) {
235
268
  this.attachShadow({ mode: "open" });
236
269
  }
237
270
  }
271
+ setCustomState(next) {
272
+ for (const s of ComponentElement.CUSTOM_STATES) {
273
+ this.internals.states.delete(s);
274
+ }
275
+ this.internals.states.add(next);
276
+ }
238
277
  static register(component, files) {
239
278
  const tagName = `widget-${component.name}`;
240
279
  if (!globalThis.customElements || customElements.get(tagName)) {
@@ -316,7 +355,6 @@ class ComponentElement extends HTMLElementBase {
316
355
  }
317
356
  }
318
357
  this.component.element = this;
319
- this.style.contentVisibility = "auto";
320
358
  this.abortController = new AbortController;
321
359
  const signal = this.abortController.signal;
322
360
  const params = {};
@@ -351,8 +389,10 @@ class ComponentElement extends HTMLElementBase {
351
389
  ...filteredFiles ? { files: filteredFiles } : {}
352
390
  };
353
391
  this.context = ComponentElement.extendContext ? ComponentElement.extendContext(base) : base;
392
+ this.adoptCss();
354
393
  if (this.hasAttribute(SSR_ATTR)) {
355
394
  this.removeAttribute(SSR_ATTR);
395
+ this.setCustomState("hydrating");
356
396
  const lightText = this.textContent?.trim();
357
397
  if (lightText) {
358
398
  try {
@@ -365,12 +405,16 @@ class ComponentElement extends HTMLElementBase {
365
405
  const args = { data: this.data, params: this.params, context: this.context };
366
406
  queueMicrotask(() => {
367
407
  this.component.hydrate(args);
408
+ this.setCustomState("ready");
368
409
  });
410
+ } else {
411
+ this.setCustomState("ready");
369
412
  }
370
413
  this.signalReady();
371
414
  return;
372
415
  }
373
416
  if (this.hasAttribute(LAZY_ATTR)) {
417
+ this.setCustomState("lazy");
374
418
  this.intersectionObserver = new IntersectionObserver((entries) => {
375
419
  const entry = entries[0];
376
420
  if (entry.isIntersecting) {
@@ -392,6 +436,9 @@ class ComponentElement extends HTMLElementBase {
392
436
  this.abortController?.abort();
393
437
  this.abortController = null;
394
438
  this.state = "idle";
439
+ for (const s of ComponentElement.CUSTOM_STATES) {
440
+ this.internals.states.delete(s);
441
+ }
395
442
  this.data = null;
396
443
  this.context = undefined;
397
444
  this.dataPromise = null;
@@ -409,11 +456,36 @@ class ComponentElement extends HTMLElementBase {
409
456
  async loadFiles() {
410
457
  return this.effectiveFiles ?? {};
411
458
  }
459
+ static baseSheet = null;
460
+ static getBaseSheet() {
461
+ if (!ComponentElement.baseSheet) {
462
+ ComponentElement.baseSheet = new CSSStyleSheetBase;
463
+ ComponentElement.baseSheet.replaceSync(":host { container-type: inline-size; content-visibility: auto; }");
464
+ }
465
+ return ComponentElement.baseSheet;
466
+ }
467
+ adoptCss() {
468
+ const css = this.effectiveFiles?.css;
469
+ const base = ComponentElement.getBaseSheet();
470
+ if (!css) {
471
+ this.shadowRoot.adoptedStyleSheets = [base];
472
+ return;
473
+ }
474
+ const name = this.component.name;
475
+ let sheet = ComponentElement.sheetCache.get(name);
476
+ if (!sheet) {
477
+ sheet = new CSSStyleSheetBase;
478
+ sheet.replaceSync(scopeWidgetCss(css, name));
479
+ ComponentElement.sheetCache.set(name, sheet);
480
+ }
481
+ this.shadowRoot.adoptedStyleSheets = [base, sheet];
482
+ }
412
483
  async loadData() {
413
484
  if (this.params === null)
414
485
  return;
415
486
  const signal = this.abortController?.signal;
416
487
  this.state = "loading";
488
+ this.setCustomState("loading");
417
489
  this.render();
418
490
  try {
419
491
  const promise = this.component.getData({
@@ -426,6 +498,7 @@ class ComponentElement extends HTMLElementBase {
426
498
  if (signal?.aborted)
427
499
  return;
428
500
  this.state = "ready";
501
+ this.setCustomState("ready");
429
502
  } catch (e) {
430
503
  if (e instanceof DOMException && e.name === "AbortError")
431
504
  return;
@@ -439,6 +512,7 @@ class ComponentElement extends HTMLElementBase {
439
512
  }
440
513
  setError(message) {
441
514
  this.state = "error";
515
+ this.setCustomState("error");
442
516
  this.errorMessage = message;
443
517
  this.render();
444
518
  this.signalReady();
@@ -696,21 +770,26 @@ class Pipeline {
696
770
  return hierarchy;
697
771
  }
698
772
  async findWidgetModulePath(name) {
773
+ return (await this.findWidgetEntry(name))?.modulePath;
774
+ }
775
+ async findWidgetEntry(name) {
699
776
  const response = await this.runtime.query(WIDGETS_MANIFEST_PATH);
700
777
  if (response.status === 404)
701
778
  return;
702
779
  const entries = await response.json();
703
- return entries.find((e) => e.name === name)?.modulePath;
780
+ return entries.find((e) => e.name === name);
704
781
  }
705
782
  async loadWidgetModule(name) {
706
- const path = await this.findWidgetModulePath(name);
707
- if (!path)
783
+ const entry = await this.findWidgetEntry(name);
784
+ if (!entry)
708
785
  return;
709
- const mod = await this.loadModule(path);
786
+ const mod = await this.loadModule(entry.modulePath);
710
787
  const component = this.extractWidgetComponent(mod);
711
788
  if (!component)
712
789
  return;
713
- return { component, files: this.getModuleFiles(mod) ?? {} };
790
+ const inlined = this.getModuleFiles(mod);
791
+ const files = inlined ?? (entry.files ? await this.loadFiles(entry.files) : {});
792
+ return { component, files };
714
793
  }
715
794
  async loadWidget(name) {
716
795
  return (await this.loadWidgetModule(name))?.component;
@@ -847,7 +926,9 @@ function resolveWidgetTags(html, getWidget, routeInfo, contextProvider, logger =
847
926
  };
848
927
  const context = contextProvider ? contextProvider(baseContext) : baseContext;
849
928
  const data = await widget.getData({ params, context });
850
- const rendered = widget.renderHTML({ data, params, context });
929
+ const hostStyle = "<style>:host { container-type: inline-size; content-visibility: auto; }</style>";
930
+ const cssStyle = files?.css ? `<style>${scopeWidgetCss(files.css, widgetName)}</style>` : "";
931
+ const rendered = hostStyle + cssStyle + widget.renderHTML({ data, params, context });
851
932
  wrappers.set(match, {
852
933
  tagName: `widget-${widgetName}`,
853
934
  attrs: attrsString ? ` ${attrsString}` : "",
@@ -1605,6 +1686,11 @@ function resolveTargetNode(node, name, isRoot) {
1605
1686
  return node.children[name];
1606
1687
  }
1607
1688
 
1689
+ // dist/core/util/js.util.js
1690
+ function escapeTemplateLiteral(s) {
1691
+ return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
1692
+ }
1693
+
1608
1694
  // dist/runtime/abstract.runtime.js
1609
1695
  var CONTENT_TYPES = new Map([
1610
1696
  [".html", "text/html; charset=utf-8"],
@@ -1912,26 +1998,12 @@ class Runtime {
1912
1998
  } catch {
1913
1999
  return;
1914
2000
  }
1915
- const companionExts = kind === "element" ? [] : ["html", "md", "css"];
1916
- const files = {};
1917
- for (const ext of companionExts) {
1918
- const companionPath = tsPath.replace(/\.ts$/, `.${ext}`);
1919
- try {
1920
- files[ext] = await this.query(companionPath, { as: "text" });
1921
- } catch {}
1922
- }
1923
2001
  let jsCode;
1924
2002
  try {
1925
- jsCode = await this.transpile(tsSource);
2003
+ jsCode = await this.transpileModule(tsPath, tsSource);
1926
2004
  } catch {
1927
2005
  return;
1928
2006
  }
1929
- if (Object.keys(files).length > 0) {
1930
- const entries = Object.entries(files).map(([k, v]) => `${k}: \`${v.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$")}\``).join(", ");
1931
- jsCode += `
1932
- export const __files = { ${entries} };
1933
- `;
1934
- }
1935
2007
  await this.handle(jsPath, { method: "PUT", body: jsCode });
1936
2008
  }
1937
2009
  loadModule(_path) {
@@ -1940,6 +2012,27 @@ export const __files = { ${entries} };
1940
2012
  transpile(_source) {
1941
2013
  throw new Error(`transpile not implemented for ${this.constructor.name}`);
1942
2014
  }
2015
+ async transpileModule(path, source) {
2016
+ let js = await this.transpile(source);
2017
+ const basePath = path.replace(/\.ts$/, "");
2018
+ const companions = ["html", "md", "css"];
2019
+ const entries = [];
2020
+ for (const ext of companions) {
2021
+ try {
2022
+ const content = await this.query(basePath + "." + ext, { as: "text" });
2023
+ entries.push(` ${ext}: \`${escapeTemplateLiteral(content)}\``);
2024
+ } catch {}
2025
+ }
2026
+ if (entries.length > 0) {
2027
+ js += `
2028
+ export const __files = {
2029
+ ${entries.join(`,
2030
+ `)}
2031
+ };
2032
+ `;
2033
+ }
2034
+ return js;
2035
+ }
1943
2036
  async mergeWidgetIntoManifest(filePath, widgetsDir) {
1944
2037
  const relativePath = filePath.slice(widgetsDir.length + 1);
1945
2038
  const parts = relativePath.split("/");
@@ -2448,16 +2541,11 @@ function buildLazyLoaders(tree, widgetEntries, elementEntries, runtime) {
2448
2541
  class WidgetComponent extends Component {
2449
2542
  renderHTML(args) {
2450
2543
  const files = args.context.files;
2451
- const style = files?.css ? `<style>${scopeWidgetCss(files.css, this.name)}</style>
2452
- ` : "";
2453
2544
  if (files?.html) {
2454
- return style + files.html;
2545
+ return files.html;
2455
2546
  }
2456
2547
  if (files?.md) {
2457
- return `${style}<mark-down>${escapeHtml(files.md)}</mark-down>`;
2458
- }
2459
- if (style) {
2460
- return style + super.renderHTML(args);
2548
+ return `<mark-down>${escapeHtml(files.md)}</mark-down>`;
2461
2549
  }
2462
2550
  return super.renderHTML(args);
2463
2551
  }
@@ -3467,5 +3555,5 @@ export {
3467
3555
  BreadcrumbWidget
3468
3556
  };
3469
3557
 
3470
- //# debugId=422168580B2E75DD64756E2164756E21
3558
+ //# debugId=9D7A4A39ADB77FE864756E2164756E21
3471
3559
  //# sourceMappingURL=emroute.js.map