@lwrjs/client-modules 0.20.0 → 0.20.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.
@@ -1,2 +1,2 @@
1
- import{BOOTSTRAP_END as e,INIT as o,INIT_MODULE as r}from"lwr/metrics";import{logOperationStart as t,logOperationEnd as n}from"lwr/profiler";import{hydrateComponent as i,createElement as c}from"lwc";const l=(()=>{const e=globalThis,{SSREnabled:o}=e.LWR&&e.LWR.env||{};if(!globalThis.performance||!o)return()=>!1;let r=globalThis.performance.now();return()=>{const e=globalThis.performance.now();return e-r>50&&(r=e,!0)}})();function s(e,o){return c(e,{is:o})}function a(e){return e.replace(/\/v\/[a-zA-Z0-9-_.]+$/,"").replace("/","-").replace(/([A-Z])/g,(e=>`-${e.toLowerCase()}`))}const d=/-([a-z])/g;function p(e){return e.replace(d,(e=>e[1].toUpperCase()))}function f(c,d={}){void 0!==globalThis.customElements&&void 0!==globalThis.document?(t({id:o}),(async()=>{let e=0;const o=globalThis.document;for(const[b,g]of c){l()&&await u();const c=++e,w=a(b);if(!o.body.querySelector(w)){t({id:r,specifier:b,specifierIndex:c});const e=s(w,g),i=o.querySelector("[lwr-root]");i?i.appendChild(e):o.body.appendChild(e),n({id:r,specifier:b,specifierIndex:c,metadata:{renderMode:"spa"}});continue}const y=o.querySelectorAll(w);for(const e of y){t({id:r,specifier:b,specifierIndex:c});const o=e.dataset.lwrPropsId;if(o){f=e,m=g,h=d[o]||{},i(f,m,h),n({id:r,specifier:b,specifierIndex:c,metadata:{renderMode:"ssr"}});continue}const l=s(w,g);for(const{name:o,value:r}of e.attributes){l.setAttribute(o,r);const e=p(o);e in l&&(l[e]=r)}for(;e.childNodes.length>0;)l.appendChild(e.childNodes[0]);const a=e.parentElement;a&&a.replaceChild(l,e),n({id:r,specifier:b,specifierIndex:c,metadata:{renderMode:"csr"}})}}var f,m,h})(),n({id:o}),t({id:e})):t({id:e})}async function u(){const e=globalThis.scheduler;return function(e){let o,r=e[0],t=1;for(;t<e.length;){const n=e[t],i=e[t+1];if(t+=2,("optionalAccess"===n||"optionalCall"===n)&&null==r)return;"access"===n||"optionalAccess"===n?(o=r,r=i(r)):"call"!==n&&"optionalCall"!==n||(r=i(((...e)=>r.call(o,...e))),o=void 0)}return r}([e,"optionalAccess",e=>e.yield])?e.yield():new Promise((e=>setTimeout(e,0)))}export{p as getPropFromAttrName,f as init,a as toKebabCase};
1
+ import{BOOTSTRAP_END as e,INIT as o,INIT_MODULE as t}from"lwr/metrics";import{logOperationStart as r,logOperationEnd as n}from"lwr/profiler";import{hydrateComponent as i,createElement as s}from"lwc";const c="lwr:hydrate",l="visible";function a(e,o,t){i(e,o,t)}function d(e,o,t){const r=f||(f=new IntersectionObserver(((e,o)=>{e.forEach((e=>{if(e.isIntersecting){const t=e.target;o.unobserve(t);const r=p.get(t);if(r){p.delete(t);const{ctor:e,props:o}=r;a(t,e,o)}}}))}),{root:null,rootMargin:"100px"}),f);p.set(e,{ctor:o,props:t}),r.observe(e)}const p=new Map;let f;function u(e){return"IntersectionObserver"in globalThis&&e.getAttribute(c)===l}const m=(()=>{const e=globalThis,{SSREnabled:o}=e.LWR&&e.LWR.env||{};if(!globalThis.performance||!o)return()=>!1;let t=globalThis.performance.now();return()=>{const e=globalThis.performance.now();return e-t>50&&(t=e,!0)}})();function b(e,o){return s(e,{is:o})}function h(e){return e.replace(/\/v\/[a-zA-Z0-9-_.]+$/,"").replace("/","-").replace(/([A-Z])/g,(e=>`-${e.toLowerCase()}`))}const g=/-([a-z])/g;function w(e){return e.replace(g,(e=>e[1].toUpperCase()))}function v(i,s={}){void 0!==globalThis.customElements&&void 0!==globalThis.document?(r({id:o}),(async()=>{let e=0;const o=globalThis.document;for(const[c,l]of i){m()&&await y();const i=++e,p=h(c);if(!o.body.querySelector(p)){r({id:t,specifier:c,specifierIndex:i});const e=b(p,l),s=o.querySelector("[lwr-root]");s?s.appendChild(e):o.body.appendChild(e),n({id:t,specifier:c,specifierIndex:i,metadata:{renderMode:"spa"}});continue}const f=o.querySelectorAll(p);for(const e of f){r({id:t,specifier:c,specifierIndex:i});const o=e.dataset.lwrPropsId;if(o){u(e)?d(e,l,s[o]||{}):a(e,l,s[o]||{}),n({id:t,specifier:c,specifierIndex:i,metadata:{renderMode:"ssr"}});continue}const f=b(p,l);for(const{name:o,value:t}of e.attributes){f.setAttribute(o,t);const e=w(o);e in f&&(f[e]=t)}for(;e.childNodes.length>0;)f.appendChild(e.childNodes[0]);const m=e.parentElement;m&&m.replaceChild(f,e),n({id:t,specifier:c,specifierIndex:i,metadata:{renderMode:"csr"}})}}})(),n({id:o}),r({id:e})):r({id:e})}async function y(){const e=globalThis.scheduler;return function(e){let o,t=e[0],r=1;for(;r<e.length;){const n=e[r],i=e[r+1];if(r+=2,("optionalAccess"===n||"optionalCall"===n)&&null==t)return;"access"===n||"optionalAccess"===n?(o=t,t=i(t)):"call"!==n&&"optionalCall"!==n||(t=i(((...e)=>t.call(o,...e))),o=void 0)}return t}([e,"optionalAccess",e=>e.yield])?e.yield():new Promise((e=>setTimeout(e,0)))}export{c as HYDRATE_DIRECTIVE,l as HYDRATE_VISIBLE_VALUE,w as getPropFromAttrName,v as init,h as toKebabCase};
2
2
  //# sourceMappingURL=init.js.map
@@ -1,5 +1,7 @@
1
1
  import type { LightningElement } from 'lwc';
2
2
  import type { ServerData } from '@lwrjs/types';
3
+ export declare const HYDRATE_DIRECTIVE = "lwr:hydrate";
4
+ export declare const HYDRATE_VISIBLE_VALUE = "visible";
3
5
  /**
4
6
  * Convert a module specifier into a valid CustomElement registry name:
5
7
  * - remove any version linking
@@ -7,10 +7,76 @@ import { createElement } from 'lwc';
7
7
  // Note: a build step uses these comments to strip the code for core.
8
8
  // eslint-disable-next-line lwr/only-allowed-imports
9
9
  import { hydrateComponent } from 'lwc';
10
+ // hydration directive + value constants
11
+ // must align with the constants in @lwrjs/shared-utils/src/html-meta.ts
12
+ export const HYDRATE_DIRECTIVE = 'lwr:hydrate';
13
+ export const HYDRATE_VISIBLE_VALUE = 'visible';
10
14
  function hydrateComponentProxy(customElement, Ctor, props) {
11
15
  hydrateComponent(customElement, Ctor, props);
12
16
  }
13
17
  // </hydrateComponentProxy>
18
+ /**
19
+ * Hydrate the custom element only when it is visible.
20
+ * @param customElement - The custom element to hydrate
21
+ * @param ctor - The constructor of the custom element
22
+ * @param props - The properties of the custom element
23
+ */
24
+ function hydrateComponentOnVisible(customElement, ctor, props) {
25
+ // Use IntersectionObserver for visibility-based hydration
26
+ const observer = createVisibilityObserver();
27
+ // add the element to the pending hydrations and observe it
28
+ pendingHydrations.set(customElement, { ctor, props });
29
+ observer.observe(customElement);
30
+ }
31
+ // store component metadata for pending hydrations
32
+ const pendingHydrations = new Map();
33
+ // store the visibility observer so that we don't create a new one each time
34
+ let visibilityObserver;
35
+ /**
36
+ * Determines if a component should be hydrated when it becomes visible in the viewport.
37
+ * This requires IntersectionObserver to be available and the hydrate directive to be set to 'visible'.
38
+ * @param element - The element to check for visibility-based hydration
39
+ * @returns True if the component should be hydrated when visible, false otherwise
40
+ */
41
+ function shouldHydrateComponentWhenVisible(element) {
42
+ return ('IntersectionObserver' in globalThis &&
43
+ element.getAttribute(HYDRATE_DIRECTIVE) === HYDRATE_VISIBLE_VALUE);
44
+ }
45
+ /**
46
+ * Create an intersection observer for hydrating islands when visible, if one doesn't already exist.
47
+ * @returns An intersection observer that will hydrate the island when visible
48
+ */
49
+ function createVisibilityObserver() {
50
+ // return the existing observer if it already exists
51
+ if (visibilityObserver)
52
+ return visibilityObserver;
53
+ // create a new observer if it doesn't already exist
54
+ visibilityObserver = new IntersectionObserver((entries, observer) => {
55
+ // for each observed element, check if it is intersecting with the viewport
56
+ entries.forEach((entry) => {
57
+ // if intersecting, hydrate the island
58
+ if (entry.isIntersecting) {
59
+ const element = entry.target;
60
+ // stop observing the element
61
+ observer.unobserve(element);
62
+ // get the hydration data for the element
63
+ const hydrationData = pendingHydrations.get(element);
64
+ if (hydrationData) {
65
+ // delete the hydration data for the element
66
+ pendingHydrations.delete(element);
67
+ const { ctor, props } = hydrationData;
68
+ // hydrate the island
69
+ hydrateComponentProxy(element, ctor, props);
70
+ }
71
+ }
72
+ });
73
+ }, {
74
+ root: null,
75
+ // adds a buffer to the intersection observer to hydrate the island slightly before it enters the viewport
76
+ rootMargin: '100px',
77
+ });
78
+ return visibilityObserver;
79
+ }
14
80
  const shouldYield = (() => {
15
81
  const globalThisLWR = globalThis;
16
82
  const { SSREnabled } = (globalThisLWR.LWR && globalThisLWR.LWR.env) || {};
@@ -112,7 +178,15 @@ export function init(rootModules, serverData = {}) {
112
178
  const propsId = element.dataset.lwrPropsId;
113
179
  // hydrate SSR'd components
114
180
  if (propsId) {
115
- hydrateComponentProxy(element, ctor, serverData[propsId] || {});
181
+ // check if the element is a hydration visible island
182
+ if (shouldHydrateComponentWhenVisible(element)) {
183
+ // hydrate the island when visible
184
+ hydrateComponentOnVisible(element, ctor, serverData[propsId] || {});
185
+ }
186
+ else {
187
+ // hydrate the island immediately
188
+ hydrateComponentProxy(element, ctor, serverData[propsId] || {});
189
+ }
116
190
  logOperationEnd({
117
191
  id: INIT_MODULE,
118
192
  specifier,
@@ -13,11 +13,90 @@ import { createElement } from 'lwc';
13
13
  // Note: a build step uses these comments to strip the code for core.
14
14
  // eslint-disable-next-line lwr/only-allowed-imports
15
15
  import { hydrateComponent } from 'lwc';
16
+
17
+ // hydration directive + value constants
18
+ // must align with the constants in @lwrjs/shared-utils/src/html-meta.ts
19
+ export const HYDRATE_DIRECTIVE = 'lwr:hydrate';
20
+ export const HYDRATE_VISIBLE_VALUE = 'visible';
16
21
  function hydrateComponentProxy(customElement, Ctor, props) {
17
22
  hydrateComponent(customElement, Ctor, props);
18
23
  }
19
24
  // </hydrateComponentProxy>
20
25
 
26
+ /**
27
+ * Hydrate the custom element only when it is visible.
28
+ * @param customElement - The custom element to hydrate
29
+ * @param ctor - The constructor of the custom element
30
+ * @param props - The properties of the custom element
31
+ */
32
+ function hydrateComponentOnVisible(customElement, ctor, props) {
33
+ // Use IntersectionObserver for visibility-based hydration
34
+ const observer = createVisibilityObserver();
35
+
36
+ // add the element to the pending hydrations and observe it
37
+ pendingHydrations.set(customElement, {
38
+ ctor,
39
+ props
40
+ });
41
+ observer.observe(customElement);
42
+ }
43
+
44
+ // store component metadata for pending hydrations
45
+ const pendingHydrations = new Map();
46
+
47
+ // store the visibility observer so that we don't create a new one each time
48
+ let visibilityObserver;
49
+
50
+ /**
51
+ * Determines if a component should be hydrated when it becomes visible in the viewport.
52
+ * This requires IntersectionObserver to be available and the hydrate directive to be set to 'visible'.
53
+ * @param element - The element to check for visibility-based hydration
54
+ * @returns True if the component should be hydrated when visible, false otherwise
55
+ */
56
+ function shouldHydrateComponentWhenVisible(element) {
57
+ return 'IntersectionObserver' in globalThis && element.getAttribute(HYDRATE_DIRECTIVE) === HYDRATE_VISIBLE_VALUE;
58
+ }
59
+
60
+ /**
61
+ * Create an intersection observer for hydrating islands when visible, if one doesn't already exist.
62
+ * @returns An intersection observer that will hydrate the island when visible
63
+ */
64
+ function createVisibilityObserver() {
65
+ // return the existing observer if it already exists
66
+ if (visibilityObserver) return visibilityObserver;
67
+
68
+ // create a new observer if it doesn't already exist
69
+ visibilityObserver = new IntersectionObserver((entries, observer) => {
70
+ // for each observed element, check if it is intersecting with the viewport
71
+ entries.forEach(entry => {
72
+ // if intersecting, hydrate the island
73
+ if (entry.isIntersecting) {
74
+ const element = entry.target;
75
+
76
+ // stop observing the element
77
+ observer.unobserve(element);
78
+
79
+ // get the hydration data for the element
80
+ const hydrationData = pendingHydrations.get(element);
81
+ if (hydrationData) {
82
+ // delete the hydration data for the element
83
+ pendingHydrations.delete(element);
84
+ const {
85
+ ctor,
86
+ props
87
+ } = hydrationData;
88
+ // hydrate the island
89
+ hydrateComponentProxy(element, ctor, props);
90
+ }
91
+ }
92
+ });
93
+ }, {
94
+ root: null,
95
+ // adds a buffer to the intersection observer to hydrate the island slightly before it enters the viewport
96
+ rootMargin: '100px'
97
+ });
98
+ return visibilityObserver;
99
+ }
21
100
  const shouldYield = (() => {
22
101
  const globalThisLWR = globalThis;
23
102
  const {
@@ -143,7 +222,14 @@ export function init(rootModules, serverData = {}) {
143
222
 
144
223
  // hydrate SSR'd components
145
224
  if (propsId) {
146
- hydrateComponentProxy(element, ctor, serverData[propsId] || {});
225
+ // check if the element is a hydration visible island
226
+ if (shouldHydrateComponentWhenVisible(element)) {
227
+ // hydrate the island when visible
228
+ hydrateComponentOnVisible(element, ctor, serverData[propsId] || {});
229
+ } else {
230
+ // hydrate the island immediately
231
+ hydrateComponentProxy(element, ctor, serverData[propsId] || {});
232
+ }
147
233
  logOperationEnd({
148
234
  id: INIT_MODULE,
149
235
  specifier,
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.20.0",
7
+ "version": "0.20.2",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
@@ -34,10 +34,10 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@locker/sandbox": "0.25.3",
37
- "@lwrjs/shared-utils": "0.20.0"
37
+ "@lwrjs/shared-utils": "0.20.2"
38
38
  },
39
39
  "devDependencies": {
40
- "@lwrjs/types": "0.20.0",
40
+ "@lwrjs/types": "0.20.2",
41
41
  "@rollup/plugin-node-resolve": "^15.2.3",
42
42
  "@rollup/plugin-sucrase": "^5.0.2",
43
43
  "@rollup/plugin-terser": "^0.4.4",
@@ -70,5 +70,5 @@
70
70
  "volta": {
71
71
  "extends": "../../../package.json"
72
72
  },
73
- "gitHead": "d2632661c89b535cc33b131edf04b7a75ca9469c"
73
+ "gitHead": "be82aca54f9a3b6cd18a4aac86f2f96c0184a930"
74
74
  }