@speedkit/cli 2.32.0 → 2.34.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,18 @@
1
+ # [2.34.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.33.0...v2.34.0) (2024-06-21)
2
+
3
+
4
+ ### Features
5
+
6
+ * **config-wizard:** update prerendering template ([7803e34](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/7803e34a2eb129e9e43badebb0d17414e1e076c8))
7
+
8
+ # [2.33.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.32.0...v2.33.0) (2024-06-19)
9
+
10
+
11
+ ### Features
12
+
13
+ * **customer-config:** extend default query parameter stripping ([f67a696](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/f67a696d427115f3895b7093f7d5d7d3dcfa6b30))
14
+ * **customer-config:** extend default query parameter stripping ([12b643c](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/12b643cbb1d09ed5f751136a03d92adf9d1226b4))
15
+
1
16
  # [2.32.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.31.1...v2.32.0) (2024-06-14)
2
17
 
3
18
 
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/2.32.0 linux-x64 node-v20.14.0
24
+ @speedkit/cli/2.34.0 linux-x64 node-v20.14.0
25
25
  $ sk --help [COMMAND]
26
26
  USAGE
27
27
  $ sk COMMAND
@@ -314,8 +314,8 @@
314
314
  "_ga", // Google Analytics User ID (Universal Analytics)
315
315
  "_gl", // Google Analytics User ID (GA 4+)
316
316
  "_kx", // klaviyo
317
+ "ad_id",
317
318
  "awc", // AWIN (former Zanox Affiliate Network)
318
- "campaign",
319
319
  "cjid",
320
320
  "cto_pld",
321
321
  "dclid", // another Click ID
@@ -323,11 +323,18 @@
323
323
  "epik",
324
324
  "fbclid", // Facebook Click ID
325
325
  "gtm_debug", // Google Tag Manager
326
+ "kgclid",
326
327
  "msclkid", // Microsoft Click ID
328
+ "rtbhc", // RTB House
329
+ "soluteclid", // Solute Click ID
327
330
  "srsltid", // google search engine
331
+ "syclid",
332
+ "tduid", // Tradedoubler UID
333
+ "ttclid", // TikTok Click ID
328
334
  "yclid", // Yandex Click ID
329
335
  "zanpid", // AWIN (former Zanox Affiliate Network)
330
336
  /^[gw]braid$/, // Google's iOS web-to-app campaign measurement
337
+ /^campaign(_id)?$/,
331
338
  /^cq_/,
332
339
  /^et_/,
333
340
  /^gad(w|_source)?$/, // Google Ads
@@ -335,6 +342,7 @@
335
342
  /^klar_/, // getklar.com
336
343
  /^neocom_/, // Neocom.ai
337
344
  /^sc_/, // e.g. Adobe
345
+ /^sv(1|_campaign_id)$/,
338
346
  /^trc_/, // Tracify
339
347
  /^uslk_/, // Userlike Chatbot
340
348
  /^utm[_-]/, // Urchin Tracking Module
@@ -50,6 +50,12 @@ const config = {
50
50
  },
51
51
  blacklistHTML({db, {{#if addPreRendering}}document{{else}}html{{/if}}, variation, url, headers}) {
52
52
  /* Use to configure guards to black-list e.g. specific page types */
53
+ {{#if addPreRendering}}
54
+ // dynamically blacklist unsupported variations as they are not covered in lambda
55
+ if (variation.toUpperCase() === "TABLET") {
56
+ return `Tablet viewport is unsupported: ${url}`;
57
+ }
58
+ {{/if}}
53
59
  return false;
54
60
  },
55
61
  shouldTransform({db, {{#if addPreRendering}}document{{else}}html{{/if}}, variation, url, headers}) {
@@ -66,7 +72,7 @@ const config = {
66
72
  {{#unless addPreRendering}}
67
73
  return html;
68
74
  {{else}}
69
- // do nothing
75
+ generateMainShadowRoot(document);
70
76
  {{/unless}}
71
77
  },
72
78
  postTransform({db, {{#if addPreRendering}}document{{else}}html{{/if}}, variation, url, headers}) {
@@ -76,8 +82,16 @@ const config = {
76
82
  {{/if}}
77
83
  return html;
78
84
  {{else}}
79
- // do nothing
85
+ createPreloadsForInnerStyleLinks(document);
86
+ removeInnerScriptNodes(document);
87
+ assignStylesToShadowDom(document);
88
+ addDeclarativeShadowDOMPolyfill(document);
89
+ addPolyFillAfterMainPreRenderTemplate(document);
80
90
  {{/unless}}
91
+ {{#if supportShadowDomInPreRendering}}
92
+ overrideCustomElementDefine(document);
93
+ walkPreRenderedElements(document);
94
+ {{/if}}
81
95
  },
82
96
  {{#if isPlentymarkets}}
83
97
  stripComments: false, // Plentymarkets specific
@@ -94,19 +108,30 @@ const config = {
94
108
  {{/if}}
95
109
  {{else}}
96
110
  preRender: {
97
- lambdaUrl: 'https://pw5wfiqnr2kqdzzc45tu3rdjue0zildl.lambda-url.eu-central-1.on.aws',
111
+ lambdaUrl: '<generate-new-lambda-function-and-paste-url-endpoint-here>',
98
112
  elements: [
99
- // app shell e.g. vue often uses #app
100
113
  // this will be the main element to extract from the rendered DOM
101
- {selector: '#app'}
114
+ { selector: "body > #app", appendTo: ".sk-container template" },
115
+ // append all extracted styles to all pre-rendered elements (as they are capsuled in shadow doms)
116
+ {
117
+ selector: 'head > link[href*="css"]',
118
+ appendTo: ".sk-container template",
119
+ },
120
+ {
121
+ selector: "head > style",
122
+ appendTo: ".sk-container template",
123
+ },
102
124
  ],
103
125
  guards: [
104
- { // plp
105
- whenPresent: '.products-list-page', // check wait for selectors, when this is present on current Page
126
+ {
127
+ whenPresent: 'body > #app',
106
128
  waitFor: [
107
129
  // e.g. wait for first 4 images to be loaded
108
130
  '.products-list-page__products-list .products-list__tiles__element:nth-child(-n+4) img[src=""]',
109
131
  ],
132
+ {{#if supportShadowDomInPreRendering}}
133
+ waitForCallback: waitForCallback.toString(),
134
+ {{/if}}
110
135
  },
111
136
  ],
112
137
  rules: {
@@ -116,11 +141,13 @@ const config = {
116
141
  ],
117
142
  resourceType: [
118
143
  // only resource types listed here will be fetched
119
- 'xhr',
120
- 'font',
121
- 'script',
122
- 'document',
123
- 'stylesheet',
144
+ "xhr",
145
+ "fetch",
146
+ "font",
147
+ "script",
148
+ "document",
149
+ "stylesheet",
150
+ "image",
124
151
  ],
125
152
  },
126
153
  },
@@ -165,3 +192,303 @@ export const post = async (db, req, res) => {
165
192
  }
166
193
  {{/if}}
167
194
  {{/unless}}
195
+
196
+ {{#if addPreRendering}}
197
+ /**
198
+ * Generates the main shadow root for the given DOM.
199
+ * This is the parent element of the extracted pre-rendered content.
200
+ * @param document
201
+ */
202
+ function generateMainShadowRoot(document) {
203
+ const customContainer = document.createElement("div");
204
+ customContainer.classList.add("sk-container");
205
+ customContainer.classList.add("speed-kit-shadow-app-root");
206
+ const template = document.createElement("template");
207
+ template.setAttribute("shadowrootmode", "open");
208
+ customContainer.appendChild(template);
209
+
210
+ document.querySelector("body").prepend(customContainer);
211
+ }
212
+
213
+ /**
214
+ * Creates preloads for inner style links in the given document.
215
+ *
216
+ * @param {Document} document - The document object to search for inner style links.
217
+ *
218
+ * @return {void}
219
+ */
220
+ function createPreloadsForInnerStyleLinks(document) {
221
+ const innerStyleLinks = document.querySelectorAll(
222
+ '.speed-kit-pre-rendered-block-added[rel="stylesheet"][href]',
223
+ );
224
+ const head = document.querySelector("head");
225
+ innerStyleLinks.forEach((styleLink) => {
226
+ const link = document.createElement("link");
227
+ link.setAttribute("rel", "preload");
228
+ link.setAttribute("as", "stylesheet");
229
+ link.setAttribute("href", styleLink.getAttribute("href"));
230
+ head.append(link);
231
+ });
232
+ }
233
+
234
+ /**
235
+ * Removes all inner script nodes from the given document.
236
+ *
237
+ * @param {Document} document - The document object from which to remove script nodes.
238
+ */
239
+ function removeInnerScriptNodes(document) {
240
+ // remove all innerShadowDom script, as they would be executed again
241
+ Array.from(
242
+ document.querySelectorAll(".speed-kit-pre-rendered-block-added script"),
243
+ ).forEach((scriptNode) => scriptNode.remove());
244
+ }
245
+
246
+ /**
247
+ * Assigns styles to the Shadow DOM of specified document.
248
+ *
249
+ * @param {Document} document - The document object to which styles are to be assigned.
250
+ * @throws {Abort} Throws an error if `skStyles` is not found.
251
+ */
252
+ function assignStylesToShadowDom(document) {
253
+ const skStyles = document.querySelector('[id="speed-kit-df-styles"]');
254
+
255
+ if (!skStyles) {
256
+ throw new Abort("skStyles not found");
257
+ }
258
+ const clonedStyles = document.createElement("style");
259
+ clonedStyles.classList.add("speed-kit-df-styles");
260
+ clonedStyles.textContent = skStyles.textContent;
261
+
262
+ const customShadowElements = getCustomShadowElements(document);
263
+ customShadowElements.forEach((shadowElement) => {
264
+ shadowElement.prepend(clonedStyles);
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Helper function to select all template elements.
270
+ * @param document
271
+ * @returns {*}
272
+ */
273
+ function getCustomShadowElements(document) {
274
+ return document.querySelectorAll(".sk-container > template");
275
+ }
276
+
277
+ /**
278
+ * Adds the declarative shadow dom polyfill to the head of the document.
279
+ * @param document
280
+ */
281
+ function addDeclarativeShadowDOMPolyfill(document) {
282
+ const head = document.querySelector("head");
283
+ if (head) {
284
+ const script = document.createElement("script");
285
+ script.textContent = SKShadowRootsPolyfill.toString();
286
+ head.append(script);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Polyfills the creation of Shadow Roots for browsers that do not support Shadow DOM.
292
+ * If the browser supports Shadow DOM, this method does nothing.
293
+ * https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill
294
+ *
295
+ * @param {HTMLTemplateElement} template - The template element to be transformed into a Shadow Root.
296
+ *
297
+ * @constructor
298
+ */
299
+ function SKShadowRootsPolyfill(template) {
300
+ // If the browser supports Shadow DOM, do nothing, as template is already transformed to a shadowRoot.
301
+ if (!template) {
302
+ return;
303
+ }
304
+ const mode = template.getAttribute("shadowrootmode");
305
+ const shadowRoot = template.parentNode.attachShadow({ mode });
306
+ shadowRoot.appendChild(template.content);
307
+ template.remove();
308
+ shadowRoot
309
+ .querySelectorAll("template[shadowrootmode]")
310
+ .forEach((template) => {
311
+ SKShadowRootsPolyfill(template);
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Calls the polyfill for shadowRoots after the main pre-rendered template.
317
+ * @param document
318
+ */
319
+ function addPolyFillAfterMainPreRenderTemplate(document) {
320
+ let topLevelPreRenderElements = document.querySelectorAll(
321
+ ".speed-kit-pre-rendered-block-added",
322
+ );
323
+ topLevelPreRenderElements.forEach((element) => {
324
+ let topTemplate = element.querySelector('template[shadowrootmode="open"]');
325
+ if (!topTemplate) {
326
+ return;
327
+ }
328
+
329
+ // polyfill for shadowRoots
330
+ let polyfillScript = document.createElement("script");
331
+ polyfillScript.textContent = `SKShadowRootsPolyfill(document?.currentScript?.previousElementSibling)`;
332
+ topTemplate.after(polyfillScript);
333
+ });
334
+ }
335
+ {{#if supportShadowDomInPreRendering}}
336
+ //================================================================================
337
+ // PuppeteerCallback
338
+ //================================================================================
339
+ /**
340
+ * Waits for the callback when custom elements are defined on the document.
341
+ *
342
+ * @return {Promise} - A promise that resolves when all custom elements are defined, or if a timeout of 5 seconds occurs.
343
+ */
344
+ async function waitForCallback() {
345
+ const undefinedElements = document.querySelectorAll(":not(:defined)");
346
+
347
+ const promises = [...undefinedElements]
348
+ .filter((button) => button.localName.startsWith("{{shadowDomCustomElementPrefix}}"))
349
+ .map((button) => {
350
+ // eslint-disable-next-line no-undef
351
+ return customElements.whenDefined(button.localName);
352
+ });
353
+
354
+ return await Promise.race([
355
+ Promise.all(promises),
356
+ new Promise((resolve) => {
357
+ setTimeout(resolve, 5000);
358
+ }),
359
+ ]);
360
+ }
361
+
362
+ //================================================================================
363
+ // INLINE SCRIPTS
364
+ //================================================================================
365
+
366
+ /**
367
+ * Overrides the window.customElements.define method to add custom code before calling the original function.
368
+ */
369
+ function overrideDefine() {
370
+ // Store the original function
371
+ // eslint-disable-next-line no-undef
372
+ let originalDefine = window.customElements.define;
373
+
374
+ // Create new function
375
+ // eslint-disable-next-line no-undef
376
+ window.customElements.define = function (e, o, options) {
377
+ // Execute your custom code
378
+ // Create a class for the element
379
+ let skProxy = class extends o {
380
+ constructor() {
381
+ super();
382
+ }
383
+
384
+ connectedCallback() {
385
+ if (this.hasAttribute("data-sk-static")) {
386
+ return;
387
+ }
388
+ // normal functionality
389
+ super.connectedCallback();
390
+ }
391
+
392
+ disconnectedCallback() {
393
+ // for sk-shadow-root don't do anything
394
+ if (this.hasAttribute("data-sk-static")) {
395
+ return;
396
+ }
397
+ // normal functionality
398
+ super.disconnectedCallback();
399
+ }
400
+
401
+ adoptedCallback() {
402
+ // for sk-shadow-root don't do anything
403
+ if (this.hasAttribute("data-sk-static")) {
404
+ return;
405
+ }
406
+ // normal functionality
407
+ super.adoptedCallback();
408
+ }
409
+
410
+ attributeChangedCallback(name, oldValue, newValue) {
411
+ // for sk-shadow-root don't do anything
412
+ if (this.hasAttribute("data-sk-static")) {
413
+ return;
414
+ }
415
+ // normal functionality
416
+ super.attributeChangedCallback(name, oldValue, newValue);
417
+ }
418
+
419
+ attachShadow(mode) {
420
+ if (this.hasAttribute("data-sk-static")) {
421
+ return null;
422
+ }
423
+ return super.attachShadow(mode);
424
+ }
425
+ };
426
+
427
+ // Call the original function with modified arguments
428
+ // eslint-disable-next-line no-undef
429
+ originalDefine.call(window.customElements, e, skProxy, options);
430
+ };
431
+ }
432
+
433
+ /**
434
+ * Overrides the custom element definition in the given document.
435
+ *
436
+ * @param {Document} document - The document object where the custom element is defined.
437
+ *
438
+ * @return {void} - This method does not return anything.
439
+ */
440
+ function overrideCustomElementDefine(document) {
441
+ const script = document.createElement("script");
442
+ script.setAttribute("type", "text/javascript");
443
+ script.classList.add("sk-shadowroot-extension");
444
+ script.textContent = `(${overrideDefine.toString()})()`;
445
+ document.querySelector("head").appendChild(script);
446
+ }
447
+
448
+ /**
449
+ * Walks through pre-rendered elements and performs specific actions on each element.
450
+ *
451
+ * @param {Document} document - The HTML document object.
452
+ */
453
+ function walkPreRenderedElements(document) {
454
+ const customShadowElements = getCustomShadowElements(document);
455
+ walkElements(customShadowElements, (element) => {
456
+ addStaticContextForPrerenderedElements(element);
457
+ // TODO extend if shadow-doms should be modified, as access to the shadow dom is not possible via querySelectors from the outside
458
+ });
459
+ }
460
+
461
+ /**
462
+ * Recursively walks through a tree of elements and applies a callback function to each element.
463
+ *
464
+ * @param {Array} elements - The array of elements to walk through. Each element should be an object with a 'children' property.
465
+ * @param {Function} callback - The callback function to apply to each element. It should take a single argument, which will be the current element.
466
+ *
467
+ * @return {undefined}
468
+ */
469
+ function walkElements(elements, callback) {
470
+ elements.forEach((element) => {
471
+ callback(element);
472
+ if (element.children && element.children.length > 0) {
473
+ walkElements(element.children, callback);
474
+ }
475
+ });
476
+ }
477
+
478
+ /**
479
+ * Adds static context for prerendered elements.
480
+ *
481
+ * @param {Element} element The element to add static context for.
482
+ */
483
+ function addStaticContextForPrerenderedElements(element) {
484
+ if (
485
+ "name" in element &&
486
+ element.name !== undefined &&
487
+ element.name.startsWith("{{shadowDomCustomElementPrefix}}")
488
+ ) {
489
+ element.setAttribute("data-sk-static", "1");
490
+ }
491
+ }
492
+ {{else}}
493
+ {{/if}}
494
+ {{/if}}
@@ -681,5 +681,5 @@
681
681
  ]
682
682
  }
683
683
  },
684
- "version": "2.32.0"
684
+ "version": "2.34.0"
685
685
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@speedkit/cli",
3
3
  "description": "Speed Kit CLI",
4
- "version": "2.32.0",
4
+ "version": "2.34.0",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"