@podium/podlet 5.2.0-next.2 → 5.2.0-next.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [5.2.0-next.3](https://github.com/podium-lib/podlet/compare/v5.2.0-next.2...v5.2.0-next.3) (2024-09-10)
2
+
3
+
4
+ ### Features
5
+
6
+ * add DSD shadow DOM encapsulation support ([02c9a64](https://github.com/podium-lib/podlet/commit/02c9a64a6bb2f460c035416e43405ae380cb6d1c))
7
+
1
8
  # [5.2.0-next.2](https://github.com/podium-lib/podlet/compare/v5.2.0-next.1...v5.2.0-next.2) (2024-09-06)
2
9
 
3
10
 
package/README.md CHANGED
@@ -79,16 +79,17 @@ const podlet = new Podlet(options);
79
79
 
80
80
  ### options
81
81
 
82
- | option | type | default | required |
83
- | ----------- | --------- | ---------------- | -------- |
84
- | name | `string` | | ✓ |
85
- | version | `string` | | ✓ |
86
- | pathname | `string` | | ✓ |
87
- | manifest | `string` | `/manifest.json` | |
88
- | content | `string` | `/` | |
89
- | fallback | `string` | | |
90
- | logger | `object` | | |
91
- | development | `boolean` | `false` | |
82
+ | option | type | default | required |
83
+ | ------------ | --------- | ---------------- | -------- |
84
+ | name | `string` | | ✓ |
85
+ | version | `string` | | ✓ |
86
+ | pathname | `string` | | ✓ |
87
+ | manifest | `string` | `/manifest.json` | |
88
+ | content | `string` | `/` | |
89
+ | fallback | `string` | | |
90
+ | logger | `object` | | |
91
+ | development | `boolean` | `false` | |
92
+ | useShadowDOM | `boolean` | `false` | |
92
93
 
93
94
  #### name
94
95
 
@@ -257,6 +258,52 @@ further details.
257
258
 
258
259
  Turns development mode on or off. See the section about development mode.
259
260
 
261
+ #### useShadowDOM
262
+
263
+ Turns declarative shadow DOM encapsulation on for the podlet. When enabled, the podlet content will be wrapped inside a declarative shadow DOM wrapper to isolate it from the rest of whichever
264
+ page it is being included on.
265
+
266
+ ```js
267
+ const podlet = new Podlet({ ..., useShadowDOM: true });
268
+ ```
269
+
270
+ Please note the following caveats when using this feature:
271
+ 1. You must name your podlet following custom element naming conventions as explained here: https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define#valid_custom_element_names
272
+ 2. In order to style your content, you will need to include your CSS inside the shadow DOM wrapper. You can do this using one of the following 2 options:
273
+
274
+ You can include a `<style>` tag before your content
275
+
276
+ ```js
277
+ res.podiumSend(`
278
+ <style>
279
+ ...styles here...
280
+ </style>
281
+ <div>...content here...</div>
282
+ `);
283
+ ```
284
+
285
+ You can have your podlet CSS included for you by using the "shadow-dom" scope
286
+
287
+ ```js
288
+ podlet.css({ value: '/path/to/css', scope: 'shadow-dom' });
289
+ res.podiumSend(`
290
+ <div>...content here...</div>
291
+ `);
292
+ ```
293
+
294
+ For more fine grained control, you can use the `podlet.wrapWithShadowDOM` method directly
295
+
296
+ ```js
297
+ const podlet = new Podlet({ ..., useShadowDOM: false });
298
+ ```
299
+
300
+ ```js
301
+ const wrapped = podlet.wrapWithShadowDOM(`<div>...content here...</div>`);
302
+ res.podiumSend(`
303
+ ${wrapped}
304
+ `);
305
+ ```
306
+
260
307
  ## Podlet Instance
261
308
 
262
309
  The podlet instance has the following API:
package/lib/podlet.js CHANGED
@@ -15,6 +15,10 @@ import { join, dirname } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
16
  // @ts-ignore
17
17
  import fs from 'node:fs';
18
+ import assert from 'node:assert';
19
+
20
+ const customElementRegex =
21
+ /^(?!(annotation-xml|color-profile|font-face|font-face-src|font-face-uri|font-face-format|font-face-name|missing-glyph)$)[a-z][a-z0-9.-]*-[a-z0-9.-]*$/;
18
22
 
19
23
  const currentDirectory = dirname(fileURLToPath(import.meta.url));
20
24
  const pkgJson = fs.readFileSync(
@@ -30,16 +34,17 @@ const { template } = utils;
30
34
  * @property {string} name - (required) podlet name
31
35
  * @property {string} version - (required) podlet version
32
36
  * @property {string} pathname - (required) podlet pathname
33
- * @property {string} [manifest] - path where the podlet manifest file is served from (default '/manifest.json')
34
- * @property {string} [content] - path where the podlet content HTML markup is served from (default '/')
35
- * @property {string} [fallback] - path where the podlet fallback HTML markup is served from (default '/fallback')
36
- * @property {boolean} [development] - a boolean flag that, when true, enables additional development setup (default false)
37
+ * @property {string} [manifest='/manifest.json'] - path where the podlet manifest file is served from (default '/manifest.json')
38
+ * @property {string} [content='/'] - path where the podlet content HTML markup is served from (default '/')
39
+ * @property {string} [fallback=''] - path where the podlet fallback HTML markup is served from (default '/fallback')
40
+ * @property {boolean} [development=false] - a boolean flag that, when true, enables additional development setup (default false)
41
+ * @property {boolean} [useShadowDOM=false] - a boolean flag that, when true, enables the use of ShadowDOM (default false)
37
42
  * @property {import('abslog').AbstractLoggerOptions} [logger] - a logger to use when provided. Can be the console object if console logging is desired but can also be any Log4j compatible logging object as well. Nothing is logged if no logger is provided. (default null)
38
43
  * @property {import("@podium/proxy").PodiumProxyOptions} [proxy] - options that can be provided to configure the @podium/proxy instance used by the podlet. See that module for details.
39
44
  *
40
45
  * @typedef {{ debug: 'true' | 'false', locale: string, deviceType: string, requestedBy: string, mountOrigin: string, mountPathname: string, publicPathname: string }} PodletContext
41
- * @typedef {{ as?: string | false | null, crossorigin?: string | null | boolean, disabled?: boolean | '' | null, hreflang?: string | false | null, title?: string | false | null, media?: string | false | null, rel?: string | false | null, type?: string | false | null, value: string | false | null, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all", [key: string]: any }} AssetCssLike
42
- * @typedef {{ value: string | null, crossorigin?: string | null | boolean, type?: string | null | false, integrity?: string | null | false, referrerpolicy?: string | null | false, nomodule?: boolean | null | '', async?: boolean | null | '', defer?: boolean | null | '', data?: {[key: string]: string} | Array<{ key: string; value: string }>, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all", [key: string]: any }} AssetJsLike
46
+ * @typedef {{ as?: string | false | null, crossorigin?: string | null | boolean, disabled?: boolean | '' | null, hreflang?: string | false | null, title?: string | false | null, media?: string | false | null, rel?: string | false | null, type?: string | false | null, value: string | false | null, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all" | "shadow-dom", [key: string]: any }} AssetCssLike
47
+ * @typedef {{ value: string | null, crossorigin?: string | null | boolean, type?: string | null | false, integrity?: string | null | false, referrerpolicy?: string | null | false, nomodule?: boolean | null | '', async?: boolean | null | '', defer?: boolean | null | '', data?: {[key: string]: string} | Array<{ key: string; value: string }>, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all" | "shadow-dom", [key: string]: any }} AssetJsLike
43
48
  */
44
49
 
45
50
  export default class PodiumPodlet {
@@ -283,6 +288,11 @@ export default class PodiumPodlet {
283
288
  */
284
289
  metrics = new Metrics();
285
290
 
291
+ /**
292
+ * Boolean flag that, when true, enables the use of declarative ShadowDOM in the podlet.
293
+ */
294
+ #useShadowDOM = false;
295
+
286
296
  /**
287
297
  * Creates a new instance of a Podium podlet which can be used in conjunction with your framework of choice to build podlet server apps.
288
298
  * `name`, `version` and `pathname` constructor arguments are required. All other options are optional.
@@ -294,6 +304,7 @@ export default class PodiumPodlet {
294
304
  * * `content` - path where the podlet content HTML markup is served from (**default** `'/'`)
295
305
  * * `fallback` - path where the podlet fallback HTML markup is served from (**default** `'/fallback'`)
296
306
  * * `development` - a boolean flag that, when true, enables additional development setup (**default** `false`)
307
+ * * `useShadowDOM` - a boolean flag that, when true, enables the use of declarative ShadowDOM in the podlet (**default** `false`)
297
308
  * * `logger` - a logger to use when provided. Can be the console object if console logging is desired but can also be any Log4j compatible logging object as well. Nothing is logged if no logger is provided. (**default** `null`)
298
309
  * * `proxy` - options that can be provided to configure the @podium/proxy instance used by the podlet. See that module for details. (**default**: `{}`)
299
310
  *
@@ -317,6 +328,7 @@ export default class PodiumPodlet {
317
328
  logger = undefined,
318
329
  development = false,
319
330
  proxy = {},
331
+ useShadowDOM = false,
320
332
  }) {
321
333
  if (schema.name(name).error)
322
334
  throw new Error(
@@ -374,6 +386,7 @@ export default class PodiumPodlet {
374
386
  this.name,
375
387
  ),
376
388
  };
389
+ this.#useShadowDOM = useShadowDOM;
377
390
 
378
391
  // Skip a tick to ensure the metric stream has been consumed
379
392
  setImmediate(() => {
@@ -811,10 +824,50 @@ export default class PodiumPodlet {
811
824
  * @returns {string}
812
825
  */
813
826
  render(incoming, data, ...args) {
827
+ const markup = this.#useShadowDOM ? this.wrapWithShadowDOM(data) : data;
814
828
  if (!incoming.development) {
815
- return data;
829
+ return markup;
816
830
  }
817
- return this.#view(incoming, data, ...args);
831
+ return this.#view(incoming, markup, ...args);
832
+ }
833
+
834
+ /**
835
+ * Function that takes in the podlet content,wraps it in declarative shadow DOM and returns it.
836
+ * @param {string} data - the podlet content to wrap in declarative shadow DOM as an HTML markup string
837
+ *
838
+ * @example
839
+ * ```js
840
+ * const content = podlet.wrapWithShadowDOM('<div>content to render</div>');
841
+ * ```
842
+ */
843
+ wrapWithShadowDOM(data) {
844
+ assert.ok(
845
+ customElementRegex.test(this.name),
846
+ 'When using the constructor argument "useShadowDOM", podlet.name must conform to custom element naming conventions: https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define#valid_custom_element_names',
847
+ );
848
+
849
+ const styles = this.cssRoute
850
+ .filter((css) => css.scope === 'shadow-dom')
851
+ .map((css) => css.toHTML());
852
+
853
+ const scripts = this.jsRoute
854
+ .filter((js) => js.scope === 'shadow-dom')
855
+ .map((js) => js.toHTML());
856
+
857
+ // Wrap the markup in DSD.
858
+ return `
859
+ <${this.name}>
860
+ <template shadowrootmode="open">
861
+ ${styles.join('\n')}
862
+ ${data}
863
+ ${scripts.join('\n')}
864
+ </template>
865
+ </${this.name}>
866
+ <script>(()=>{function e(d){HTMLTemplateElement.prototype.hasOwnProperty("shadowRootMode")||d.querySelectorAll("template[shadowrootmode]").forEach(o=>{let
867
+ n=o.getAttribute("shadowrootmode"),s=o.hasAttribute("shadowrootdelegatesfocus"),t=o.parentNode.attachShadow({mode:n,delegatesFocus:s});t.appendChild(o.content),o.remove(),e(t)})}var
868
+ r;(r=document.currentScript)!=null&&r.previousElementSibling&&e(document.currentScript.previousElementSibling);})();
869
+ </script>
870
+ `;
818
871
  }
819
872
 
820
873
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@podium/podlet",
3
- "version": "5.2.0-next.2",
3
+ "version": "5.2.0-next.3",
4
4
  "type": "module",
5
5
  "description": "Module for building page fragment servers in a micro frontend architecture.",
6
6
  "license": "MIT",
package/types/podlet.d.ts CHANGED
@@ -30,16 +30,17 @@ declare global {
30
30
  * @property {string} name - (required) podlet name
31
31
  * @property {string} version - (required) podlet version
32
32
  * @property {string} pathname - (required) podlet pathname
33
- * @property {string} [manifest] - path where the podlet manifest file is served from (default '/manifest.json')
34
- * @property {string} [content] - path where the podlet content HTML markup is served from (default '/')
35
- * @property {string} [fallback] - path where the podlet fallback HTML markup is served from (default '/fallback')
36
- * @property {boolean} [development] - a boolean flag that, when true, enables additional development setup (default false)
33
+ * @property {string} [manifest='/manifest.json'] - path where the podlet manifest file is served from (default '/manifest.json')
34
+ * @property {string} [content='/'] - path where the podlet content HTML markup is served from (default '/')
35
+ * @property {string} [fallback=''] - path where the podlet fallback HTML markup is served from (default '/fallback')
36
+ * @property {boolean} [development=false] - a boolean flag that, when true, enables additional development setup (default false)
37
+ * @property {boolean} [useShadowDOM=false] - a boolean flag that, when true, enables the use of ShadowDOM (default false)
37
38
  * @property {import('abslog').AbstractLoggerOptions} [logger] - a logger to use when provided. Can be the console object if console logging is desired but can also be any Log4j compatible logging object as well. Nothing is logged if no logger is provided. (default null)
38
39
  * @property {import("@podium/proxy").PodiumProxyOptions} [proxy] - options that can be provided to configure the @podium/proxy instance used by the podlet. See that module for details.
39
40
  *
40
41
  * @typedef {{ debug: 'true' | 'false', locale: string, deviceType: string, requestedBy: string, mountOrigin: string, mountPathname: string, publicPathname: string }} PodletContext
41
- * @typedef {{ as?: string | false | null, crossorigin?: string | null | boolean, disabled?: boolean | '' | null, hreflang?: string | false | null, title?: string | false | null, media?: string | false | null, rel?: string | false | null, type?: string | false | null, value: string | false | null, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all", [key: string]: any }} AssetCssLike
42
- * @typedef {{ value: string | null, crossorigin?: string | null | boolean, type?: string | null | false, integrity?: string | null | false, referrerpolicy?: string | null | false, nomodule?: boolean | null | '', async?: boolean | null | '', defer?: boolean | null | '', data?: {[key: string]: string} | Array<{ key: string; value: string }>, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all", [key: string]: any }} AssetJsLike
42
+ * @typedef {{ as?: string | false | null, crossorigin?: string | null | boolean, disabled?: boolean | '' | null, hreflang?: string | false | null, title?: string | false | null, media?: string | false | null, rel?: string | false | null, type?: string | false | null, value: string | false | null, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all" | "shadow-dom", [key: string]: any }} AssetCssLike
43
+ * @typedef {{ value: string | null, crossorigin?: string | null | boolean, type?: string | null | false, integrity?: string | null | false, referrerpolicy?: string | null | false, nomodule?: boolean | null | '', async?: boolean | null | '', defer?: boolean | null | '', data?: {[key: string]: string} | Array<{ key: string; value: string }>, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all" | "shadow-dom", [key: string]: any }} AssetJsLike
43
44
  */
44
45
  export default class PodiumPodlet {
45
46
  /**
@@ -53,6 +54,7 @@ export default class PodiumPodlet {
53
54
  * * `content` - path where the podlet content HTML markup is served from (**default** `'/'`)
54
55
  * * `fallback` - path where the podlet fallback HTML markup is served from (**default** `'/fallback'`)
55
56
  * * `development` - a boolean flag that, when true, enables additional development setup (**default** `false`)
57
+ * * `useShadowDOM` - a boolean flag that, when true, enables the use of declarative ShadowDOM in the podlet (**default** `false`)
56
58
  * * `logger` - a logger to use when provided. Can be the console object if console logging is desired but can also be any Log4j compatible logging object as well. Nothing is logged if no logger is provided. (**default** `null`)
57
59
  * * `proxy` - options that can be provided to configure the @podium/proxy instance used by the podlet. See that module for details. (**default**: `{}`)
58
60
  *
@@ -66,7 +68,7 @@ export default class PodiumPodlet {
66
68
  * const podlet = new Podlet({ name: 'foo', version: '1.0.0', pathname: '/' });
67
69
  * ```
68
70
  */
69
- constructor({ name, version, pathname, manifest, fallback, content, logger, development, proxy, }: PodletOptions);
71
+ constructor({ name, version, pathname, manifest, fallback, content, logger, development, proxy, useShadowDOM, }: PodletOptions);
70
72
  /**
71
73
  * The name that the podlet identifies itself by. (set in the constructor) This is used internally for things like metrics but can also be used by a layout server.
72
74
  * This value must be in camelCase.
@@ -545,6 +547,16 @@ export default class PodiumPodlet {
545
547
  render<T extends {
546
548
  [key: string]: unknown;
547
549
  }>(incoming: HttpIncoming<T>, data: string, ...args: any[]): string;
550
+ /**
551
+ * Function that takes in the podlet content,wraps it in declarative shadow DOM and returns it.
552
+ * @param {string} data - the podlet content to wrap in declarative shadow DOM as an HTML markup string
553
+ *
554
+ * @example
555
+ * ```js
556
+ * const content = podlet.wrapWithShadowDOM('<div>content to render</div>');
557
+ * ```
558
+ */
559
+ wrapWithShadowDOM(data: string): string;
548
560
  /**
549
561
  * Method for processing an incoming HTTP request. This method is intended to be used to implement support for multiple HTTP frameworks and in most cases will not need to be used directly by podlet developers when creating podlet servers.
550
562
  *
@@ -611,6 +623,10 @@ export type PodletOptions = {
611
623
  * - a boolean flag that, when true, enables additional development setup (default false)
612
624
  */
613
625
  development?: boolean;
626
+ /**
627
+ * - a boolean flag that, when true, enables the use of ShadowDOM (default false)
628
+ */
629
+ useShadowDOM?: boolean;
614
630
  /**
615
631
  * - a logger to use when provided. Can be the console object if console logging is desired but can also be any Log4j compatible logging object as well. Nothing is logged if no logger is provided. (default null)
616
632
  */
@@ -640,7 +656,7 @@ export type AssetCssLike = {
640
656
  type?: string | false | null;
641
657
  value: string | false | null;
642
658
  strategy?: "beforeInteractive" | "afterInteractive" | "lazy";
643
- scope?: "content" | "fallback" | "all";
659
+ scope?: "content" | "fallback" | "all" | "shadow-dom";
644
660
  [key: string]: any;
645
661
  };
646
662
  export type AssetJsLike = {
@@ -659,7 +675,7 @@ export type AssetJsLike = {
659
675
  value: string;
660
676
  }>;
661
677
  strategy?: "beforeInteractive" | "afterInteractive" | "lazy";
662
- scope?: "content" | "fallback" | "all";
678
+ scope?: "content" | "fallback" | "all" | "shadow-dom";
663
679
  [key: string]: any;
664
680
  };
665
681
  import Proxy from '@podium/proxy';