@speedkit/cli 3.9.1 → 3.10.0

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,24 @@
1
+ # [3.10.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.9.1...v3.10.0) (2025-10-15)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **wizard:** ssr ([7f48214](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/7f48214e04aae0183b4027e1d0b103f22bbb8499))
7
+ * **wizard:** ssr ([a848bb2](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/a848bb26f3fcafd79d12d7acbf4b9b4a32105ff4))
8
+
9
+
10
+ ### Features
11
+
12
+ * **customer-config:** improve root-referencing style handling by parallelizing css fetching ([907d487](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/907d4875ed738ac6ba773562907a947a529ac198))
13
+ * **customer-config:** move css processing from dh to lambda ([409da9b](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/409da9ba8c542ddcdc45feb5f25c06b3d98d37a3))
14
+ * **customer-config:** optimise ssr handling + provide dom-parsing dh as default ([ac0c2fa](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/ac0c2fab72f926d8a818d21dd8c0a67c736d033d))
15
+ * **customer-config:** prevent ssr shadow root container from being accessible by screen readers ([0743a91](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/0743a91a111ed1207fa3e8079c1a0f22166bc67c))
16
+ * **customer-config:** remove ssr tv blacklisting due to deprecation ([5a7e6c2](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/5a7e6c2bfb12678153d1f7dd2b16790a47c27484))
17
+ * **customer-config:** remove unnecessary ssr tablet disabling ([a48582f](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/a48582f65bb0f55019cc77476998c206f415d0ae))
18
+ * **customer-config:** scope shadow app root container positioning to pre-rendered pages ([46e970b](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/46e970b35295794b9e05c588bf34fc80470a2f84))
19
+ * **customer-config:** update ssr variation detection ([56a79b5](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/56a79b55f49b12628682bfbe9a23c403a39ebdba))
20
+ * **customer-config:** update ssr variation detection ([8744274](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/87442749b5a3dc730195d5f009316501d80ed95c))
21
+
1
22
  ## [3.9.1](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.9.0...v3.9.1) (2025-10-15)
2
23
 
3
24
 
package/README.md CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @speedkit/cli
21
21
  $ sk COMMAND
22
22
  running command...
23
23
  $ sk (--version)
24
- @speedkit/cli/3.9.1 linux-x64 node-v20.19.5
24
+ @speedkit/cli/3.10.0 linux-x64 node-v20.19.5
25
25
  $ sk --help [COMMAND]
26
26
  USAGE
27
27
  $ sk COMMAND
@@ -29,11 +29,9 @@ export interface CustomerConfigSettings {
29
29
  includeServiceWorker?: string;
30
30
  activateImageOptimisation?: boolean;
31
31
  removeLazyLoading?: boolean;
32
- addPreRendering?: boolean;
33
- addUADetection?: boolean;
34
- supportShadowDomInPreRendering?: boolean;
35
- shadowDomCustomElementPrefix?: string;
36
- activateRumTracking?: boolean;
32
+ addSSR?: boolean;
33
+ addSSRInnerShadowDomSupport?: boolean;
34
+ addDeviceDetection?: boolean;
37
35
  useGATracking?: boolean;
38
36
  useScrapingBee?: boolean;
39
37
  withGoogleOptimize?: boolean;
@@ -136,7 +136,7 @@ class CustomerConfigService {
136
136
  return true;
137
137
  }
138
138
  async getConfigSettings() {
139
- let { production, staging, activateScopedDeployments, subRouteScope, includeServiceWorker, activateImageOptimisation, removeLazyLoading, addPreRendering, addUADetection, supportShadowDomInPreRendering, shadowDomCustomElementPrefix, activateRumTracking, useGATracking, useScrapingBee, withGoogleOptimize, activateCfRocketLoaderWorkaround, hasSoftNavigations, isShopify, isShopware, isSalesforce, isOxid, isPlentymarkets, isPOV, } = {};
139
+ let { production, staging, activateScopedDeployments, subRouteScope, includeServiceWorker, activateImageOptimisation, removeLazyLoading, addSSR, addSSRInnerShadowDomSupport, addDeviceDetection, useGATracking, useScrapingBee, withGoogleOptimize, activateCfRocketLoaderWorkaround, hasSoftNavigations, isShopify, isShopware, isSalesforce, isOxid, isPlentymarkets, isPOV, } = {};
140
140
  this.appName = await this.cli.prompt(`[App Name] Enter desired SK app name:`, {
141
141
  validator: (input) => /^[\da-z]+(?:-[\da-z]+)*$/.test(input) ? "" : "No valid kebab-case",
142
142
  defaultAnswer: this.appName,
@@ -181,29 +181,23 @@ class CustomerConfigService {
181
181
  if (await this.cli.confirm("[Service Worker] Is there a Service Worker which needs to be included?", false)) {
182
182
  includeServiceWorker = await this.cli.prompt("[Service Worker] Enter path of Service Worker to be included:", { defaultAnswer: "/sw.js" });
183
183
  }
184
- addUADetection = await this.cli.confirm("[Variation handling] Do mobile and desktop versions of the website differ in their HTML structure, which would require us to add variations?", false);
185
184
  activateImageOptimisation = await this.cli.confirm("[IO] Should image optimisation be activated?", false);
186
- removeLazyLoading = await this.cli.confirm("[Lazy-Loading] Does the shop require us to resolve lazy-loaded content?");
187
- if (await this.cli.confirm("[Lambda-SSR] Add server-side-rendering? This requires a set-up Lambda function to work. Also requires usage of the DOM-based DocumentHandler.", false)) {
188
- addPreRendering = true;
189
- if (await this.cli.confirm("[Lambda-SSR] Does the customer utilize Shadow DOMs? If so additional handling for this will be added.", false)) {
190
- supportShadowDomInPreRendering = true;
191
- shadowDomCustomElementPrefix = await this.cli.prompt("[Lambda-SSR] Please specify the prefix of the custom elements used (e.g. o2 uses tef-): ", { defaultAnswer: "custom-" });
185
+ removeLazyLoading = await this.cli.confirm("[Lazy Loading] Does the shop require us to resolve lazy-loaded content?");
186
+ if (await this.cli.confirm("[Lambda SSR] Add server-side-rendering? This requires a set-up Lambda function to work.", false)) {
187
+ addSSR = true;
188
+ if (await this.cli.confirm("[Lambda SSR] Does the customer utilize Shadow DOMs? If so additional handling for this will be added.", false)) {
189
+ addSSRInnerShadowDomSupport = true;
192
190
  }
193
- await this.cli.confirm("Confirm that you increased the Node memory limit to at least 1024 as the SSR setup is memory intensive." +
194
- ' BBQ App -> Data -> App -> Search for App -> Addons -> "nodeMemLimit": 1024', false);
195
191
  }
192
+ // ask for variation handling only if adding Lambda SSR is not needed, as it will come with device detection anyway
193
+ addDeviceDetection = addSSR
194
+ ? true
195
+ : await this.cli.confirm("[Device Detection] Do mobile and desktop versions of the website differ in their HTML structure, which would require us to add variations?", false);
196
196
  useScrapingBee = await this.cli.confirm("[ScrapingBee] Do customer responses differ geo-location-based which would require ScrapingBee API to fetch documents?", false);
197
- activateRumTracking = true;
198
- // activateRumTracking = await this.cli.confirm(
199
- // "[Tracking] Activate RUM Tracking?",
200
- // );
201
- // if (activateRumTracking) {
202
197
  if (!isShopify) {
203
198
  useGATracking = await this.cli.confirm("[Tracking] Add tracking based on Google Analytics' DataLayer?");
204
199
  }
205
200
  hasSoftNavigations = await this.cli.confirm("[Tracking] Add tracking for Soft Navigations?", false);
206
- // }
207
201
  isPOV = await this.cli.confirm("[Tracking] Is a POV (proof of value) anticipated, so that additional tracking is needed?", false);
208
202
  withGoogleOptimize = await this.cli.confirm("[Services] Is Google Optimize used?", false);
209
203
  activateCfRocketLoaderWorkaround = isShopify
@@ -218,11 +212,9 @@ class CustomerConfigService {
218
212
  includeServiceWorker,
219
213
  activateImageOptimisation,
220
214
  removeLazyLoading,
221
- addPreRendering,
222
- addUADetection,
223
- supportShadowDomInPreRendering,
224
- shadowDomCustomElementPrefix,
225
- activateRumTracking,
215
+ addSSR,
216
+ addSSRInnerShadowDomSupport,
217
+ addDeviceDetection,
226
218
  useGATracking,
227
219
  useScrapingBee,
228
220
  withGoogleOptimize,
@@ -25,30 +25,6 @@
25
25
  var ENABLED_HOSTS = getEnabledHosts(ENABLED_HOSTS_CONFIGS);
26
26
  var CURRENT_HOST_CONFIG = getCurrentHostConfig(ENABLED_HOSTS_CONFIGS);
27
27
 
28
- {{#if addPreRendering}}
29
- // Lambda SSR specific: disable SK for tablets as they are not covered in lambda
30
- function isTablet() {
31
- var userAgent = window.navigator.userAgent.toLowerCase();
32
- var tabletUAs = [
33
- /tablet/,
34
- /ipad/,
35
- /nexus 7/,
36
- /nexus 10/,
37
- /silk\//,
38
- /\bgt-[pn]\d{4}\b/,
39
- /\bsm-t\d{3}\b/,
40
- /^.*android(?!.*mobile|.*smart-tv|.*smarttv|.*aft[tm]|.*crkey).*$/,
41
- ];
42
-
43
- if ("ontouchend" in document && userAgent.indexOf("Macintosh") > -1) {
44
- return true;
45
- }
46
- return tabletUAs.some(function (ua) {
47
- return ua.test(userAgent);
48
- });
49
- }
50
- {{/if}}
51
-
52
28
  /**
53
29
  * @type {SpeedKitConfig}
54
30
  */
@@ -66,22 +42,36 @@
66
42
  includeServiceWorker: "{{includeServiceWorker}}",
67
43
  {{/if}}
68
44
  preloadDynamicBlocks: true,
69
- rumTracking: {{activateRumTracking}},
70
- {{#if addPreRendering}}
71
- // Lambda SSR specific: detect device to cover different resolutions when rendered in lambda
72
- userAgentDetection: true,
73
- {{else if addUADetection}}
74
- userAgentDetection: true,
45
+ rumTracking: true,
46
+ {{#if addDeviceDetection}}
47
+ {{#unless addSSR}}
48
+ userAgentDetection: true,
49
+ {{/unless}}
50
+ detectDevice: function(doc) {
51
+ {{#if addSSR}}
52
+ // SSR specific – identify device based on screen width:
53
+ // TODO: adjust devices + breakpoints to your needs
54
+ var width = window?.screen?.width || 1024;
55
+ if (width < 768) return "mobile";
56
+ return "desktop";
57
+ {{else}}
58
+ // TODO: adjust condition to your needs
59
+ return doc.querySelector('<mobile-specific-element>') ? "mobile" : "desktop";
60
+ {{/if}}
61
+ },
75
62
  {{/if}}
76
- {{#if addUADetection}}
77
- detectDevice: function(doc) {
78
- // TODO adjust condition to your needs
79
- return doc.querySelector('<mobile-specific-element>') ? "mobile" : "desktop";
80
- },
63
+ {{#if addSSR}}
64
+ customVariation: [
65
+ {
66
+ variationFunction: function (request, device, cookies) {
67
+ // SSR specific – don't report a variation for specific page types NOT covered by SSR:
68
+ // TODO: adjust and activate if needed
69
+ // if (request.url.indexOf("<url-page-type-idenfier>") > -1) return "desktop";
70
+ return device;
71
+ },
72
+ },
73
+ ],
81
74
  {{/if}}
82
- split: CURRENT_HOST_CONFIG.split,
83
- splitTestId: CURRENT_HOST_CONFIG.splitTestId,
84
- disabled: !CURRENT_HOST_CONFIG.enabled{{#if addPreRendering}} || isTablet(){{/if}},
85
75
  {{#if useScrapingBee}}
86
76
  customVariation: [
87
77
  {
@@ -94,6 +84,9 @@
94
84
  },
95
85
  ],
96
86
  {{/if}}
87
+ split: CURRENT_HOST_CONFIG.split,
88
+ splitTestId: CURRENT_HOST_CONFIG.splitTestId,
89
+ disabled: !CURRENT_HOST_CONFIG.enabled,
97
90
  enabledSites: [
98
91
  {
99
92
  pathname: [
@@ -150,12 +143,6 @@
150
143
  {{/if}}
151
144
  ],
152
145
  blacklist: [
153
- {{#if addPreRendering}}
154
- // blacklist tv variations
155
- {
156
- tv: true,
157
- },
158
- {{/if}}
159
146
  {
160
147
  pathname: [
161
148
  // TODO: adjust/replace this exemplary list
@@ -1,56 +1,46 @@
1
1
  {
2
- "production": {
3
- "_meta": {
4
- "shopSystem":
5
- {{#if isShopify}}
6
- "Shopify"
7
- {{else if isShopware}}
8
- "Shopware"
9
- {{else if isSalesforce}}
10
- "Salesforce Commerce Cloud"
11
- {{else if isOxid}}
12
- "OXID eShop"
13
- {{else if isPlentymarkets}}
14
- "Plentymarkets"
15
- {{else}}
16
- "TODO: enter name of system in use"
17
- {{/if}}
18
- },
19
- "app": "{{appName}}",
20
- "domain": "{{production.host}}",
21
- "origins": [
22
- "https://{{production.host}}"
23
- ],
24
- {{#if isShopify}}
25
- "swPath": "/apps/speed-kit/sw.js",
26
- {{else}}
27
- "swPath": "/wrapper-sw.js",
28
- {{/if}}
29
- {{#if subRouteScope}}
30
- "scope": "/{{subRouteScope}}/",
31
- {{else}}
32
- "scope": "/",
33
- {{/if}}
34
- "forceInstall": {{production.forceInstall}}
35
- }
36
- {{#if staging.host}},
37
- "staging": {
38
- "name": "staging",
39
- "domain": "{{staging.host}}",
40
- "origins": [
41
- "https://{{staging.host}}"
42
- ],
43
- "forceInstall": {{staging.forceInstall}},
44
- {{#if isShopify}}
45
- "swPath": "/apps/speed-kit/sw.js",
46
- {{else}}
47
- "swPath": "/wrapper-sw.js",
48
- {{/if}}
49
- {{#if subRouteScope}}
50
- "scope": "/{{subRouteScope}}/"
51
- {{else}}
52
- "scope": "/"
53
- {{/if}}
54
- }
55
- {{/if}}
2
+ "production": {
3
+ "_meta": {
4
+ "shopSystem":
5
+ {{#if isShopify}}
6
+ "Shopify"
7
+ {{else if isShopware}}
8
+ "Shopware"
9
+ {{else if isSalesforce}}
10
+ "Salesforce Commerce Cloud"
11
+ {{else if isOxid}}
12
+ "OXID eShop"
13
+ {{else if isPlentymarkets}}
14
+ "Plentymarkets"
15
+ {{else}}
16
+ "TODO: enter name of system in use"
17
+ {{/if}}
18
+ },
19
+ "app": "{{appName}}",
20
+ "domain": "{{production.host}}",
21
+ "origins": [
22
+ "https://{{production.host}}"
23
+ ],
24
+ {{#if isShopify}}
25
+ "swPath": "/apps/speed-kit/sw.js",
26
+ {{else}}
27
+ "swPath": "/wrapper-sw.js",
28
+ {{/if}}
29
+ {{#if subRouteScope}}
30
+ "scope": "/{{subRouteScope}}/",
31
+ {{else}}
32
+ "scope": "/",
33
+ {{/if}}
34
+ "forceInstall": {{production.forceInstall}}
35
+ }
36
+ {{#if staging.host}},
37
+ "staging": {
38
+ "name": "staging",
39
+ "domain": "{{staging.host}}",
40
+ "origins": [
41
+ "https://{{staging.host}}"
42
+ ],
43
+ "forceInstall": {{staging.forceInstall}},
44
+ }
45
+ {{/if}}
56
46
  }
@@ -1,53 +1,51 @@
1
- {{#unless addPreRendering}}
2
- {{#if removeLazyLoading}}
3
- const lazyLoadDefaultConfig = {
4
- attributes: [
5
- // TODO adjust based on if original attribute should be preserved or renamed
6
- // when using find and set the original attribute is preserved
7
- // when using find and rename the original attribute is renamed
8
- {find: 'data-src', set: 'src'},
9
- {find: 'data-srcset', set: 'srcset'},
10
- ],
11
- lazyClass: 'lazyload',
12
- lazyLoad: false,
13
- preload: true // preload lazy-removed images in head
14
- }
1
+ {{#if removeLazyLoading}}
2
+ const lazyLoadDefaultConfig = {
3
+ attributes: [
4
+ // TODO adjust based on if original attribute should be preserved or renamed
5
+ // when using find and set the original attribute is preserved
6
+ // when using find and rename the original attribute is renamed
7
+ {find: 'data-src', set: 'src'},
8
+ {find: 'data-srcset', set: 'srcset'},
9
+ ],
10
+ lazyClass: 'lazyload',
11
+ lazyLoad: false,
12
+ preload: true // preload lazy-removed images in head
13
+ }
15
14
 
16
- const lazyLoadList = [
17
- // TODO: adjust/replace this exemplary list
15
+ const lazyLoadList = [
16
+ // TODO: adjust/replace this exemplary list
18
17
 
19
- // Content page's stage image:
20
- {
21
- ...lazyLoadDefaultConfig,
22
- parentClasses: [
23
- 'image-with-text-overlay__banner',
24
- ],
25
- limit: 1
26
- },
18
+ // Content page's stage image:
19
+ {
20
+ ...lazyLoadDefaultConfig,
21
+ parentClasses: [
22
+ 'image-with-text-overlay__banner',
23
+ ],
24
+ limit: 1
25
+ },
27
26
 
28
- // PLP product images:
29
- {
30
- ...lazyLoadDefaultConfig,
31
- parentClasses: [
32
- 'product-image__wrapper',
33
- ],
34
- limit: 4
35
- },
27
+ // PLP product images:
28
+ {
29
+ ...lazyLoadDefaultConfig,
30
+ parentClasses: [
31
+ 'product-image__wrapper',
32
+ ],
33
+ limit: 4
34
+ },
36
35
 
37
- // PDP hero image:
38
- {
39
- ...lazyLoadDefaultConfig,
40
- parentClasses: [
41
- 'product-gallery__image',
42
- ],
43
- limit: 1
44
- },
45
- ];
36
+ // PDP hero image:
37
+ {
38
+ ...lazyLoadDefaultConfig,
39
+ parentClasses: [
40
+ 'product-gallery__image',
41
+ ],
42
+ limit: 1
43
+ },
44
+ ];
46
45
 
47
- {{/if}}
48
- {{/unless}}
46
+ {{/if}}
49
47
  const config = {
50
- rejectHTML({db, {{#if addPreRendering}}document{{else}}html{{/if}}, variation, url, headers}) {
48
+ rejectHTML({db, document, variation, url, headers}) {
51
49
  /* Use to configure guards to prevent e.g. error and captcha pages from being cached */
52
50
  {{#if isPlentymarkets}}
53
51
  // we don't want to cache CSR pages
@@ -59,59 +57,58 @@ const config = {
59
57
  return false;
60
58
  {{/if}}
61
59
  },
62
- blacklistHTML({db, {{#if addPreRendering}}document{{else}}html{{/if}}, variation, url, headers}) {
60
+ blacklistHTML({db, document, variation, url, headers}) {
63
61
  /* Use to configure guards to black-list e.g. specific page types */
64
- {{#if addPreRendering}}
65
- // dynamically blacklist unsupported variations as they are not covered in lambda
66
- if (variation.toUpperCase() === "TABLET") {
67
- return `Tablet viewport is unsupported: ${url}`;
68
- }
69
-
70
- {{/if}}
71
62
  {{#if useScrapingBee}}
72
- // dynamically blacklist not 200 status codes from ScrapingBee
73
- const spbStatusCode = headers["Spb-initial-status-code"];
74
- if (spbStatusCode && spbStatusCode !== "200") {
75
- return `initial status code unequal to 200 received from scrapingbee: ${url} ${spbStatusCode}`;
76
- }
63
+
64
+ // dynamically blacklist not 200 status codes from ScrapingBee
65
+ const spbStatusCode = headers["Spb-initial-status-code"];
66
+ if (spbStatusCode && spbStatusCode !== "200") {
67
+ return `initial status code unequal to 200 received from scrapingbee: ${url} ${spbStatusCode}`;
68
+ }
77
69
  {{/if}}
70
+
78
71
  return false;
79
72
  },
80
- shouldTransform({db, {{#if addPreRendering}}document{{else}}html{{/if}}, variation, url, headers}) {
73
+ shouldTransform({db, document, variation, url, headers}) {
81
74
  return true;
82
75
  },
83
- shouldDelayDom({db, {{#if addPreRendering}}document{{else}}html{{/if}}, variation, url, headers}) {
76
+ shouldDelayDom({db, document, variation, url, headers}) {
84
77
  {{#if isPlentymarkets}}
85
78
  return false; // Plentymarkets specific
86
79
  {{else}}
87
80
  return true;
88
81
  {{/if}}
89
82
  },
90
- async preTransform({db, {{#if addPreRendering}}document{{else}}html{{/if}}, variation, url, headers}) {
91
- {{#unless addPreRendering}}
92
- return html;
93
- {{else}}
83
+ async preTransform({db, document, variation, url, headers}) {
84
+ {{#if addPreRendering}}
85
+ // SSR specific:
94
86
  generateMainShadowRoot(document);
95
- {{/unless}}
96
- },
97
- async postTransform({db, {{#if addPreRendering}}document{{else}}html{{/if}}, variation, url, headers}) {
98
- {{#unless addPreRendering}}
99
- {{#if includeServiceWorker}}
100
- html = injectServiceWorkerScript(html, '{{includeServiceWorker}}');
101
- {{/if}}
102
- return html;
103
87
  {{else}}
88
+ // do nothing
89
+ {{/if}}
90
+ },
91
+ async postTransform({db, document, variation, url, headers}) {
92
+ {{#if includeServiceWorker}}
93
+ injectServiceWorkerScript(document, '{{includeServiceWorker}}');
94
+
95
+ {{/if}}
96
+ {{#if addSSR}}
97
+ // SSR specific:
104
98
  createPreloadsForInnerStyleLinks(document);
105
99
  removeInnerScriptNodes(document);
106
- assignStylesToShadowDom(document);
100
+ assignDynamicStylesToShadowDom(document);
107
101
  addDeclarativeShadowDOMPolyfill(document);
102
+ wrapPreRenderedElementsWithCustomRootTags(document);
108
103
  addPolyFillAfterMainPreRenderTemplate(document);
109
- hideCustomShadowDom(document);
110
- showCustomShadowDom(document);
111
- {{/unless}}
112
- {{#if supportShadowDomInPreRendering}}
113
- overrideCustomElementDefine(document);
114
- walkPreRenderedElements(document);
104
+ triggerShadowRendering(document);
105
+ {{#if addSSRInnerShadowDomSupport}}
106
+
107
+ overrideCustomElementDefine(document);
108
+ walkPreRenderedElements(document);
109
+ {{/if}}
110
+ {{else}}
111
+ // do nothing
115
112
  {{/if}}
116
113
  },
117
114
  {{#if isPlentymarkets}}
@@ -130,11 +127,12 @@ const config = {
130
127
  {{else}}
131
128
  executeDeploymentDetection: true,
132
129
  {{/if}}
133
- {{#unless addPreRendering}}
134
- {{#if removeLazyLoading}}
135
- lazyLoadList,
136
- {{/if}}
137
- {{else}}
130
+ {{#if removeLazyLoading}}
131
+ lazyLoadList,
132
+ {{/if}}
133
+ {{#if addSSR}}
134
+
135
+ // SSR specific:
138
136
  preRender: {
139
137
  lambdaUrl: '<generate-new-lambda-function-and-paste-url-endpoint-here>',
140
138
  elements: [
@@ -147,11 +145,11 @@ const config = {
147
145
  "body > style",
148
146
  "body > link[rel='stylesheet'][href]",
149
147
  ].join(","),
150
- appendTo: ".sk-container template",
148
+ appendTo: "sk-shadow-app-root > template",
151
149
  },
152
150
  // this will be the main element to extract from the rendered DOM
153
151
  // TODO change selector to the main element of your page
154
- { selector: "body > #app", appendTo: ".sk-container template" },
152
+ { selector: "body > #app", appendTo: "sk-shadow-app-root > template" },
155
153
  ],
156
154
  guards: [
157
155
  {
@@ -160,7 +158,7 @@ const config = {
160
158
  // e.g. wait for first 4 images to be loaded
161
159
  '.products-list-page__products-list .products-list__tiles__element:nth-child(-n+4) img[src=""]',
162
160
  ],
163
- {{#if supportShadowDomInPreRendering}}
161
+ {{#if addSSRInnerShadowDomSupport}}
164
162
  waitForCallback: waitForCallback.toString(),
165
163
  {{/if}}
166
164
  },
@@ -182,10 +180,10 @@ const config = {
182
180
  ],
183
181
  },
184
182
  },
185
- {{/unless}}
183
+ {{/if}}
186
184
  }
187
185
 
188
- const documentHandler = new {{#if addPreRendering}}DomDocumentHandler{{else}}DocumentHandler{{/if}}({
186
+ const documentHandler = new DomDocumentHandler({
189
187
  ...config,
190
188
  app: APP,
191
189
  dynamicFetcher: DYNAMIC_FETCHER,
@@ -196,44 +194,42 @@ const documentHandler = new {{#if addPreRendering}}DomDocumentHandler{{else}}Doc
196
194
  export const post = async (db, req, res) => {
197
195
  await documentHandler.transformAndSend(db, req, res);
198
196
  }
199
- {{#unless addPreRendering}}
200
- {{#if includeServiceWorker}}
201
-
202
- //================================================================================
203
- // Utility Functions
204
- //================================================================================
205
-
206
- function prepend(string, searchString, prependString) {
207
- const index = string.lastIndexOf(searchString);
208
- return string.slice(0, index) + prependString + string.slice(index);
209
- }
197
+ {{#if includeServiceWorker}}
210
198
 
211
- function injectServiceWorkerScript(html, searchedSwPath) {
212
- return prepend(html, '</head>', `
213
- <script class="speed-kit-service-worker">
214
- var serviceWorkerRegister = ServiceWorkerContainer.prototype.register;
215
- ServiceWorkerContainer.prototype.register = function (swPath, options) {
199
+ /**
200
+ * Injects a script into the document to override the service worker registration.
201
+ *
202
+ * @param {Document} document - The document object where the script will be injected.
203
+ * @param {string} searchedSwPath - The path of the service worker to be overridden.
204
+ */
205
+ function injectServiceWorkerScript(document, searchedSwPath) {
206
+ const swScript = document.createElement("script");
207
+ swScript.setAttribute("id", "speed-kit-service-worker");
208
+ swScript.type = 'text/javascript';
209
+ swScript.innerHTML = `
210
+ var serviceWorkerRegister = ServiceWorkerContainer.prototype.register;
211
+ ServiceWorkerContainer.prototype.register = function (swPath, options) {
216
212
  if (swPath.indexOf('${searchedSwPath}') === 0 && SpeedKit.group === 'A') {
217
- return navigator.serviceWorker.getRegistration();
213
+ return navigator.serviceWorker.getRegistration();
218
214
  }
219
215
  return serviceWorkerRegister.call(this, swPath, options);
220
- };
221
- </script>
222
- `);
223
- }
224
- {{/if}}
225
- {{/unless}}
216
+ };`;
217
+
218
+ document.head.appendChild(swScript);
219
+ }
220
+ {{/if}}
221
+ {{#if addSSR}}
226
222
 
227
- {{#if addPreRendering}}
228
223
  /**
229
224
  * Generates the main shadow root for the given DOM.
230
225
  * This is the parent element of the extracted pre-rendered content.
226
+ *
231
227
  * @param document
232
228
  */
233
229
  function generateMainShadowRoot(document) {
234
- const customContainer = document.createElement("div");
235
- customContainer.classList.add("sk-container");
236
- customContainer.classList.add("speed-kit-shadow-app-root");
230
+ const customContainer = document.createElement("sk-shadow-app-root");
231
+ customContainer.setAttribute("aria-hidden", "true"); // hide from screen readers
232
+
237
233
  const template = document.createElement("template");
238
234
  template.setAttribute("shadowrootmode", "open");
239
235
  customContainer.appendChild(template);
@@ -245,21 +241,22 @@ export const post = async (db, req, res) => {
245
241
  * Creates preloads for inner style links in the given document.
246
242
  *
247
243
  * @param {Document} document - The document object to search for inner style links.
248
- *
249
244
  * @return {void}
250
245
  */
251
246
  function createPreloadsForInnerStyleLinks(document) {
252
- const innerStyleLinks = document.querySelectorAll(
253
- '.speed-kit-pre-rendered-block-added[rel="stylesheet"][href]',
254
- );
255
- const head = document.querySelector("head");
256
- innerStyleLinks.forEach((styleLink) => {
257
- const link = document.createElement("link");
258
- link.setAttribute("rel", "preload");
259
- link.setAttribute("as", "stylesheet");
260
- link.setAttribute("href", styleLink.getAttribute("href"));
261
- head.append(link);
262
- });
247
+ const customShadowRoots = getCustomShadowElements(document);
248
+ for (const customShadowRoot of customShadowRoots) {
249
+ const innerStyleLinks = customShadowRoot.querySelectorAll('link[rel="stylesheet"][href]');
250
+
251
+ const head = document.querySelector("head");
252
+ innerStyleLinks.forEach((styleLink) => {
253
+ const link = document.createElement("link");
254
+ link.setAttribute("rel", "preload");
255
+ link.setAttribute("as", "stylesheet");
256
+ link.setAttribute("href", styleLink.getAttribute("href"));
257
+ head.append(link);
258
+ });
259
+ }
263
260
  }
264
261
 
265
262
  /**
@@ -269,25 +266,28 @@ export const post = async (db, req, res) => {
269
266
  */
270
267
  function removeInnerScriptNodes(document) {
271
268
  // remove all innerShadowDom script, as they would be executed again
272
- Array.from(
273
- document.querySelectorAll(".speed-kit-pre-rendered-block-added script"),
274
- ).forEach((scriptNode) => scriptNode.remove());
269
+ const customShadowRoots = getCustomShadowElements(document);
270
+ for (const customShadowRoot of customShadowRoots) {
271
+ Array.from(customShadowRoot.querySelectorAll("script")).forEach((scriptNode) => scriptNode.remove());
272
+ }
275
273
  }
276
274
 
277
275
  /**
278
- * Assigns styles to the Shadow DOM of specified document.
276
+ * Assigns dynamic styles to the Shadow DOM of specified document.
279
277
  *
280
278
  * @param {Document} document - The document object to which styles are to be assigned.
281
279
  * @throws {Abort} Throws an error if `skStyles` is not found.
282
280
  */
283
- function assignStylesToShadowDom(document) {
284
- const skStyles = document.querySelector('[id="speed-kit-df-styles"]');
281
+ function assignDynamicStylesToShadowDom(document) {
282
+ const skStyles = document.querySelector("#speed-kit-df-styles");
285
283
 
286
284
  if (!skStyles) {
287
- throw new Abort("skStyles not found");
285
+ throw new Abort("#speed-kit-df-styles not found");
288
286
  }
289
287
  const clonedStyles = document.createElement("style");
290
- clonedStyles.classList.add("speed-kit-df-styles");
288
+ for (const attr of skStyles.attributes) {
289
+ clonedStyles.setAttribute(attr.name, attr.value);
290
+ }
291
291
  clonedStyles.textContent = skStyles.textContent;
292
292
 
293
293
  const customShadowElements = getCustomShadowElements(document);
@@ -298,15 +298,17 @@ export const post = async (db, req, res) => {
298
298
 
299
299
  /**
300
300
  * Helper function to select all template elements.
301
+ *
301
302
  * @param document
302
303
  * @returns {*}
303
304
  */
304
305
  function getCustomShadowElements(document) {
305
- return document.querySelectorAll(".sk-container > template");
306
+ return document.querySelectorAll("sk-shadow-app-root > template");
306
307
  }
307
308
 
308
309
  /**
309
310
  * Adds the declarative shadow dom polyfill to the head of the document.
311
+ *
310
312
  * @param document
311
313
  */
312
314
  function addDeclarativeShadowDOMPolyfill(document) {
@@ -324,7 +326,6 @@ export const post = async (db, req, res) => {
324
326
  * https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill
325
327
  *
326
328
  * @param {HTMLTemplateElement} template - The template element to be transformed into a Shadow Root.
327
- *
328
329
  * @constructor
329
330
  */
330
331
  function SKShadowRootsPolyfill(template) {
@@ -344,49 +345,92 @@ export const post = async (db, req, res) => {
344
345
  }
345
346
 
346
347
  /**
347
- * Calls the polyfill for shadowRoots after the main pre-rendered template.
348
+ * Wraps pre-rendered elements with custom sk-html and sk-body tags.
349
+ *
348
350
  * @param document
349
351
  */
350
- function addPolyFillAfterMainPreRenderTemplate(document) {
351
- let topLevelPreRenderElements = document.querySelectorAll(
352
- ".speed-kit-pre-rendered-block-added",
353
- );
354
- topLevelPreRenderElements.forEach((element) => {
355
- let topTemplate = element.querySelector('template[shadowrootmode="open"]');
356
- if (!topTemplate) {
357
- return;
352
+ function wrapPreRenderedElementsWithCustomRootTags(document) {
353
+ const customShadowRoots = getCustomShadowElements(document);
354
+
355
+ function createCustomElement(tagName, sourceSelector) {
356
+ const el = document.createElement(tagName);
357
+ const source = document.querySelector(sourceSelector);
358
+ if (source) {
359
+ for (const attr of source.attributes) {
360
+ el.setAttribute(attr.name, attr.value);
361
+ }
358
362
  }
363
+ return el;
364
+ }
359
365
 
360
- // polyfill for shadowRoots
361
- let polyfillScript = document.createElement("script");
362
- polyfillScript.textContent = `SKShadowRootsPolyfill(document?.currentScript?.previousElementSibling)`;
363
- topTemplate.after(polyfillScript);
364
- });
366
+ for (const customShadowRoot of customShadowRoots) {
367
+ const skHtml = createCustomElement("sk-html", "html");
368
+ const skBody = createCustomElement("sk-body", "body");
369
+
370
+ // move all children from the shadow root element into sk-body
371
+ while (customShadowRoot.firstChild) {
372
+ // remove class which is no longer needed
373
+ customShadowRoot.firstChild.classList.remove("speed-kit-pre-rendered-block-added");
374
+
375
+ // move element to sk-body
376
+ skBody.appendChild(customShadowRoot.firstChild);
377
+ }
378
+
379
+ // append sk-body inside sk-html
380
+ skHtml.appendChild(skBody);
381
+
382
+ // append sk-html into the shadow root element
383
+ customShadowRoot.appendChild(skHtml);
384
+ }
365
385
  }
366
386
 
367
- function hideCustomShadowDom(document) {
387
+ /**
388
+ * Calls the polyfill for shadowRoots after the main pre-rendered template.
389
+ *
390
+ * @param document
391
+ */
392
+ function addPolyFillAfterMainPreRenderTemplate(document) {
368
393
  const customShadowRoots = getCustomShadowElements(document);
394
+ for (const customShadowRoot of customShadowRoots) {
395
+ let topLevelPreRenderElements = customShadowRoot.querySelectorAll("sk-body > *");
396
+ topLevelPreRenderElements.forEach((element) => {
397
+ let topTemplate = element.querySelector('template[shadowrootmode="open"]');
398
+ if (!topTemplate) {
399
+ return;
400
+ }
369
401
 
370
- customShadowRoots.forEach((element) => {
371
- const style = document.createElement("style");
372
- style.textContent = `.speed-kit-pre-rendered-block-added{visibility:hidden;}`;
373
- element.prepend(style);
374
- });
402
+ // polyfill for shadowRoots
403
+ let polyfillScript = document.createElement("script");
404
+ polyfillScript.textContent = `SKShadowRootsPolyfill(document?.currentScript?.previousElementSibling)`;
405
+ topTemplate.after(polyfillScript);
406
+ });
407
+ }
375
408
  }
376
409
 
377
- function showCustomShadowDom(document) {
410
+ /**
411
+ * Forces rendering of elements within custom shadow DOMs by briefly toggling
412
+ * their visibility via injected style rules.
413
+ *
414
+ * @param document
415
+ */
416
+ function triggerShadowRendering(document) {
378
417
  const customShadowRoots = getCustomShadowElements(document);
379
-
380
- customShadowRoots.forEach((element) => {
381
- const style = document.createElement("style");
382
- style.textContent = `.speed-kit-pre-rendered-block-added{visibility:visible;}`;
383
- element.appendChild(style);
384
- });
418
+ for (const customShadowRoot of customShadowRoots) {
419
+ const hidingStyle = document.createElement("style");
420
+ hidingStyle.textContent = `sk-html{visibility:hidden;}`;
421
+ customShadowRoot.prepend(hidingStyle);
422
+
423
+ const visibilityStyle = document.createElement("style");
424
+ visibilityStyle.textContent = `sk-html{visibility:visible;}`;
425
+ customShadowRoot.appendChild(visibilityStyle);
426
+ }
385
427
  }
386
- {{#if supportShadowDomInPreRendering}}
428
+ {{#if addSSRInnerShadowDomSupport}}
429
+
387
430
  //================================================================================
388
- // PuppeteerCallback
431
+ // Puppeteer Callback
389
432
  //================================================================================
433
+
390
434
  /**
391
435
  * Waits for the callback when custom elements are defined on the document.
392
436
  *
@@ -465,7 +509,7 @@ export const post = async (db, req, res) => {
465
509
  return instance;
466
510
  };
467
511
 
468
- // create a dynamic proxy class which si overwriting all lifecycle hooks of the original class
512
+ // create a dynamic proxy class which is overwriting all lifecycle hooks of the original class
469
513
  skProxy.prototype = Object.create(o.prototype, {
470
514
  constructor: {
471
515
  value: skProxy,
@@ -495,7 +539,7 @@ export const post = async (db, req, res) => {
495
539
 
496
540
  Object.setPrototypeOf(skProxy, o);
497
541
 
498
- const skContainer = document.querySelector(".speed-kit-shadow-app-root");
542
+ const skContainer = document.querySelector("sk-shadow-app-root");
499
543
  let skContainerParentNode;
500
544
  let skContainerNextNode;
501
545
  if (skContainer) {
@@ -560,6 +604,5 @@ export const post = async (db, req, res) => {
560
604
  }
561
605
  });
562
606
  }
563
- {{else}}
564
607
  {{/if}}
565
608
  {{/if}}
@@ -4,20 +4,21 @@
4
4
  */
5
5
  window.dynamicBlockConfig = {
6
6
  dynamicScriptExecution: true,
7
- {{#if addPreRendering}}
8
- // ssr container
9
- prerendered: [{ selector: "body > .speed-kit-shadow-app-root" }],
10
- {{/if}}
11
- {{#if addUADetection}}
12
- detectDevice: (doc) => {
13
- // TODO adjust condition to your needs
14
- return doc.querySelector('<mobile-specific-element>') ? "mobile" : "desktop";
15
- },
7
+ {{#if addDeviceDetection}}
8
+ {{#unless addSSR}}
9
+ detectDevice: (doc) => {
10
+ // keep only if you need to check the DOM – for screen width checks the SK config's detectDevice is sufficient
11
+
12
+ // TODO: adjust condition to your needs
13
+ return doc.querySelector('<mobile-specific-element>') ? "mobile" : "desktop";
14
+ },
15
+ {{/unless}}
16
16
  {{/if}}
17
17
  blocks: [
18
18
  // TODO: adjust/replace this exemplary list
19
19
  {
20
20
  selector: "body",
21
+ {{#unless addSSR}}
21
22
  detectChanges: [
22
23
  // Main Headline:
23
24
  {
@@ -56,12 +57,14 @@
56
57
  compare: ["src"],
57
58
  },
58
59
  ],
60
+ {{/unless}}
59
61
  },
60
62
  {{#if isShopify}}
63
+
61
64
  // Shopify specific:
62
65
  {
63
66
  selector: "#shopify-section-announcement-bar",
64
- {{#unless addPreRendering}}
67
+ {{#unless addSSR}}
65
68
  merge: false, // TODO: remove if body is not the main block
66
69
  {{/unless}}
67
70
  detectChanges: [{
@@ -110,31 +113,55 @@
110
113
  { selector: 'meta[property]', idAttribute: "property"},
111
114
  { selector: 'script[type="application/ld+json"]'},
112
115
  ],
113
- {{#if addPreRendering}}
116
+ {{#if addSSR}}
117
+
118
+ // SSR specific:
119
+ prerendered: [{ selector: "body > sk-shadow-app-root" }],
114
120
  preRenderedRootConfig: [
115
121
  // TODO: adjust/replace this exemplary list
116
122
  {
117
- selector: ".prerendered-top-level-element",
118
- detectChanges: [
119
- {
120
- // header area
121
- find: "#page-root > div > .aem-container > div:nth-of-type(1)",
122
- detectChanges: [
123
- // main categories
124
- {
125
- find: 'button[id^="menu-button"] p',
126
- compare: ["text"],
127
- },
128
- // logo
129
- {
130
- find: "img[src]",
131
- compare: ["src"],
132
- },
133
- ],
134
- },
123
+ selector: "sk-body",
124
+ remoteSelector: "body",
125
+ detectChanges: [
126
+ // Main Headline:
127
+ {
128
+ find: "h1",
129
+ compare: ["text"],
130
+ },
131
+
132
+ // Top Banner:
133
+ {
134
+ find: ".header .top-banner",
135
+ compare: ["text"],
136
+ },
137
+
138
+ // First-Level Items of Header/Main Navigation:
139
+ {
140
+ find: ".header .main-navigation .first-level-item",
141
+ compare: ["text"],
142
+ },
143
+
144
+ // Stage Teaser:
145
+ {
146
+ find: ".stage-teaser source",
147
+ compare: ["srcset"],
148
+ },
149
+
150
+ // PLP Items:
151
+ {
152
+ find: ".product-grid .product img",
153
+ limit: 9, // only compare the first 9 products
154
+ compare: ["src"],
155
+ },
156
+
157
+ // PDP Hero Image:
158
+ {
159
+ find: ".product-detail .slide.main img",
160
+ compare: ["src"],
161
+ },
135
162
  ],
136
163
  },
137
- ],
138
- {{/if}}
164
+ ],
165
+ {{/if}}
139
166
  };
140
167
  })();
@@ -24,20 +24,36 @@
24
24
  background-position: calc(100% + 400px) 0;
25
25
  }
26
26
  }
27
+ {{#if addSSR}}
28
+
29
+ /* SSR specific – positioning for pre-rendered container */
30
+ .speed-kit-pre-rendered sk-shadow-app-root {
31
+ position: absolute;
32
+ top: 0;
33
+ width: 100%;
34
+ left: 0;
35
+ right: 0;
36
+ z-index: 99999;
37
+ background: #fff;
38
+ }
39
+
40
+ /*
41
+ SSR specific:
42
+ remember to use sk-html/sk-body as additional selectors if you reference html/body to also cover the SSR case
43
+ */
44
+ {{/if}}
27
45
  {{#if withGoogleOptimize}}
28
46
 
29
47
  /* Google Optimize Flicker Fix: */
48
+ {{#if addSSR}}
49
+ sk-html > sk-body,
50
+ {{/if}}
30
51
  html > body {
31
52
  opacity: 1 !important;
32
53
  }
33
54
  {{/if}}
34
55
 
35
56
  .speed-kit-dynamic-loading {
36
- {{#if addPreRendering}}
37
- /* TODO: use the following wrapping selector if you want to apply those styles to the pre-rendered block instead: */
38
- /* .speed-kit-pre-rendered-block-added */
39
- {{/if}}
40
-
41
57
  .dummy-price-class,
42
58
  .dummy-availability-class {
43
59
  visibility: hidden;
@@ -75,7 +91,6 @@ html > body {
75
91
  }
76
92
  }
77
93
 
78
-
79
94
  /* Prevent Add2Cart clicks: */
80
95
  .dummy-add2cart-class {
81
96
  pointer-events: none !important;
@@ -111,16 +126,3 @@ html > body {
111
126
  mask-repeat: no-repeat;
112
127
  }
113
128
  }
114
- {{#if addPreRendering}}
115
-
116
- /* Positioning for pre-rendered containers */
117
- html.speed-kit-pre-rendered body > .speed-kit-shadow-app-root {
118
- position: absolute;
119
- top: 0;
120
- width: 100%;
121
- left: 0;
122
- right: 0;
123
- z-index: 99999;
124
- background: #fff;
125
- }
126
- {{/if}}
@@ -732,5 +732,5 @@
732
732
  ]
733
733
  }
734
734
  },
735
- "version": "3.9.1"
735
+ "version": "3.10.0"
736
736
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@speedkit/cli",
3
3
  "description": "Speed Kit CLI",
4
- "version": "3.9.1",
4
+ "version": "3.10.0",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"