@speedkit/cli 2.33.0 → 2.35.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,17 @@
1
+ # [2.35.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.34.0...v2.35.0) (2024-06-21)
2
+
3
+
4
+ ### Features
5
+
6
+ * **customer-config:** add ignore assets ([8774d78](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/8774d78d042ee80937c13cdd38acff84ca5b7e4f))
7
+
8
+ # [2.34.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.33.0...v2.34.0) (2024-06-21)
9
+
10
+
11
+ ### Features
12
+
13
+ * **config-wizard:** update prerendering template ([7803e34](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/7803e34a2eb129e9e43badebb0d17414e1e076c8))
14
+
1
15
  # [2.33.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.32.0...v2.33.0) (2024-06-19)
2
16
 
3
17
 
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.33.0 linux-x64 node-v20.14.0
24
+ @speedkit/cli/2.35.0 linux-x64 node-v20.14.0
25
25
  $ sk --help [COMMAND]
26
26
  USAGE
27
27
  $ sk COMMAND
@@ -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,17 +82,32 @@ 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
- {{#if isPlentymarkets}}
83
- stripComments: false, // Plentymarkets specific
84
- {{/if}}
85
- {{#if isShopify}}
86
- delayScriptPath: "/apps/speed-kit/speed-kit-dom-ready.js", // Shopify specific
87
- {{else}}
88
- delayScriptPath: "/speed-kit-dom-ready.js",
89
- {{/if}}
96
+ {{#if isPlentymarkets}}
97
+ stripComments: false, // Plentymarkets specific
98
+ {{/if}}
99
+ {{#if addPreRendering}}
100
+ ignoreAssets: [
101
+ // keep aligned with dynamicBlockConfig.assetChangeDetection.ignoreAssets
102
+ ],
103
+ {{else}}
104
+ ignoreAssets: [],
105
+ {{/if}}
106
+ {{#if isShopify}}
107
+ delayScriptPath: "/apps/speed-kit/speed-kit-dom-ready.js", // Shopify specific
108
+ {{else}}
109
+ delayScriptPath: "/speed-kit-dom-ready.js",
110
+ {{/if}}
90
111
  executeDeploymentDetection: true,
91
112
  {{#unless addPreRendering}}
92
113
  {{#if removeLazyLoading}}
@@ -94,19 +115,30 @@ const config = {
94
115
  {{/if}}
95
116
  {{else}}
96
117
  preRender: {
97
- lambdaUrl: 'https://pw5wfiqnr2kqdzzc45tu3rdjue0zildl.lambda-url.eu-central-1.on.aws',
118
+ lambdaUrl: '<generate-new-lambda-function-and-paste-url-endpoint-here>',
98
119
  elements: [
99
- // app shell e.g. vue often uses #app
100
120
  // this will be the main element to extract from the rendered DOM
101
- {selector: '#app'}
121
+ { selector: "body > #app", appendTo: ".sk-container template" },
122
+ // append all extracted styles to all pre-rendered elements (as they are capsuled in shadow doms)
123
+ {
124
+ selector: 'head > link[href*="css"]',
125
+ appendTo: ".sk-container template",
126
+ },
127
+ {
128
+ selector: "head > style",
129
+ appendTo: ".sk-container template",
130
+ },
102
131
  ],
103
132
  guards: [
104
- { // plp
105
- whenPresent: '.products-list-page', // check wait for selectors, when this is present on current Page
133
+ {
134
+ whenPresent: 'body > #app',
106
135
  waitFor: [
107
136
  // e.g. wait for first 4 images to be loaded
108
137
  '.products-list-page__products-list .products-list__tiles__element:nth-child(-n+4) img[src=""]',
109
138
  ],
139
+ {{#if supportShadowDomInPreRendering}}
140
+ waitForCallback: waitForCallback.toString(),
141
+ {{/if}}
110
142
  },
111
143
  ],
112
144
  rules: {
@@ -116,11 +148,13 @@ const config = {
116
148
  ],
117
149
  resourceType: [
118
150
  // only resource types listed here will be fetched
119
- 'xhr',
120
- 'font',
121
- 'script',
122
- 'document',
123
- 'stylesheet',
151
+ "xhr",
152
+ "fetch",
153
+ "font",
154
+ "script",
155
+ "document",
156
+ "stylesheet",
157
+ "image",
124
158
  ],
125
159
  },
126
160
  },
@@ -165,3 +199,303 @@ export const post = async (db, req, res) => {
165
199
  }
166
200
  {{/if}}
167
201
  {{/unless}}
202
+
203
+ {{#if addPreRendering}}
204
+ /**
205
+ * Generates the main shadow root for the given DOM.
206
+ * This is the parent element of the extracted pre-rendered content.
207
+ * @param document
208
+ */
209
+ function generateMainShadowRoot(document) {
210
+ const customContainer = document.createElement("div");
211
+ customContainer.classList.add("sk-container");
212
+ customContainer.classList.add("speed-kit-shadow-app-root");
213
+ const template = document.createElement("template");
214
+ template.setAttribute("shadowrootmode", "open");
215
+ customContainer.appendChild(template);
216
+
217
+ document.querySelector("body").prepend(customContainer);
218
+ }
219
+
220
+ /**
221
+ * Creates preloads for inner style links in the given document.
222
+ *
223
+ * @param {Document} document - The document object to search for inner style links.
224
+ *
225
+ * @return {void}
226
+ */
227
+ function createPreloadsForInnerStyleLinks(document) {
228
+ const innerStyleLinks = document.querySelectorAll(
229
+ '.speed-kit-pre-rendered-block-added[rel="stylesheet"][href]',
230
+ );
231
+ const head = document.querySelector("head");
232
+ innerStyleLinks.forEach((styleLink) => {
233
+ const link = document.createElement("link");
234
+ link.setAttribute("rel", "preload");
235
+ link.setAttribute("as", "stylesheet");
236
+ link.setAttribute("href", styleLink.getAttribute("href"));
237
+ head.append(link);
238
+ });
239
+ }
240
+
241
+ /**
242
+ * Removes all inner script nodes from the given document.
243
+ *
244
+ * @param {Document} document - The document object from which to remove script nodes.
245
+ */
246
+ function removeInnerScriptNodes(document) {
247
+ // remove all innerShadowDom script, as they would be executed again
248
+ Array.from(
249
+ document.querySelectorAll(".speed-kit-pre-rendered-block-added script"),
250
+ ).forEach((scriptNode) => scriptNode.remove());
251
+ }
252
+
253
+ /**
254
+ * Assigns styles to the Shadow DOM of specified document.
255
+ *
256
+ * @param {Document} document - The document object to which styles are to be assigned.
257
+ * @throws {Abort} Throws an error if `skStyles` is not found.
258
+ */
259
+ function assignStylesToShadowDom(document) {
260
+ const skStyles = document.querySelector('[id="speed-kit-df-styles"]');
261
+
262
+ if (!skStyles) {
263
+ throw new Abort("skStyles not found");
264
+ }
265
+ const clonedStyles = document.createElement("style");
266
+ clonedStyles.classList.add("speed-kit-df-styles");
267
+ clonedStyles.textContent = skStyles.textContent;
268
+
269
+ const customShadowElements = getCustomShadowElements(document);
270
+ customShadowElements.forEach((shadowElement) => {
271
+ shadowElement.prepend(clonedStyles);
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Helper function to select all template elements.
277
+ * @param document
278
+ * @returns {*}
279
+ */
280
+ function getCustomShadowElements(document) {
281
+ return document.querySelectorAll(".sk-container > template");
282
+ }
283
+
284
+ /**
285
+ * Adds the declarative shadow dom polyfill to the head of the document.
286
+ * @param document
287
+ */
288
+ function addDeclarativeShadowDOMPolyfill(document) {
289
+ const head = document.querySelector("head");
290
+ if (head) {
291
+ const script = document.createElement("script");
292
+ script.textContent = SKShadowRootsPolyfill.toString();
293
+ head.append(script);
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Polyfills the creation of Shadow Roots for browsers that do not support Shadow DOM.
299
+ * If the browser supports Shadow DOM, this method does nothing.
300
+ * https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill
301
+ *
302
+ * @param {HTMLTemplateElement} template - The template element to be transformed into a Shadow Root.
303
+ *
304
+ * @constructor
305
+ */
306
+ function SKShadowRootsPolyfill(template) {
307
+ // If the browser supports Shadow DOM, do nothing, as template is already transformed to a shadowRoot.
308
+ if (!template) {
309
+ return;
310
+ }
311
+ const mode = template.getAttribute("shadowrootmode");
312
+ const shadowRoot = template.parentNode.attachShadow({ mode });
313
+ shadowRoot.appendChild(template.content);
314
+ template.remove();
315
+ shadowRoot
316
+ .querySelectorAll("template[shadowrootmode]")
317
+ .forEach((template) => {
318
+ SKShadowRootsPolyfill(template);
319
+ });
320
+ }
321
+
322
+ /**
323
+ * Calls the polyfill for shadowRoots after the main pre-rendered template.
324
+ * @param document
325
+ */
326
+ function addPolyFillAfterMainPreRenderTemplate(document) {
327
+ let topLevelPreRenderElements = document.querySelectorAll(
328
+ ".speed-kit-pre-rendered-block-added",
329
+ );
330
+ topLevelPreRenderElements.forEach((element) => {
331
+ let topTemplate = element.querySelector('template[shadowrootmode="open"]');
332
+ if (!topTemplate) {
333
+ return;
334
+ }
335
+
336
+ // polyfill for shadowRoots
337
+ let polyfillScript = document.createElement("script");
338
+ polyfillScript.textContent = `SKShadowRootsPolyfill(document?.currentScript?.previousElementSibling)`;
339
+ topTemplate.after(polyfillScript);
340
+ });
341
+ }
342
+ {{#if supportShadowDomInPreRendering}}
343
+ //================================================================================
344
+ // PuppeteerCallback
345
+ //================================================================================
346
+ /**
347
+ * Waits for the callback when custom elements are defined on the document.
348
+ *
349
+ * @return {Promise} - A promise that resolves when all custom elements are defined, or if a timeout of 5 seconds occurs.
350
+ */
351
+ async function waitForCallback() {
352
+ const undefinedElements = document.querySelectorAll(":not(:defined)");
353
+
354
+ const promises = [...undefinedElements]
355
+ .filter((button) => button.localName.startsWith("{{shadowDomCustomElementPrefix}}"))
356
+ .map((button) => {
357
+ // eslint-disable-next-line no-undef
358
+ return customElements.whenDefined(button.localName);
359
+ });
360
+
361
+ return await Promise.race([
362
+ Promise.all(promises),
363
+ new Promise((resolve) => {
364
+ setTimeout(resolve, 5000);
365
+ }),
366
+ ]);
367
+ }
368
+
369
+ //================================================================================
370
+ // INLINE SCRIPTS
371
+ //================================================================================
372
+
373
+ /**
374
+ * Overrides the window.customElements.define method to add custom code before calling the original function.
375
+ */
376
+ function overrideDefine() {
377
+ // Store the original function
378
+ // eslint-disable-next-line no-undef
379
+ let originalDefine = window.customElements.define;
380
+
381
+ // Create new function
382
+ // eslint-disable-next-line no-undef
383
+ window.customElements.define = function (e, o, options) {
384
+ // Execute your custom code
385
+ // Create a class for the element
386
+ let skProxy = class extends o {
387
+ constructor() {
388
+ super();
389
+ }
390
+
391
+ connectedCallback() {
392
+ if (this.hasAttribute("data-sk-static")) {
393
+ return;
394
+ }
395
+ // normal functionality
396
+ super.connectedCallback();
397
+ }
398
+
399
+ disconnectedCallback() {
400
+ // for sk-shadow-root don't do anything
401
+ if (this.hasAttribute("data-sk-static")) {
402
+ return;
403
+ }
404
+ // normal functionality
405
+ super.disconnectedCallback();
406
+ }
407
+
408
+ adoptedCallback() {
409
+ // for sk-shadow-root don't do anything
410
+ if (this.hasAttribute("data-sk-static")) {
411
+ return;
412
+ }
413
+ // normal functionality
414
+ super.adoptedCallback();
415
+ }
416
+
417
+ attributeChangedCallback(name, oldValue, newValue) {
418
+ // for sk-shadow-root don't do anything
419
+ if (this.hasAttribute("data-sk-static")) {
420
+ return;
421
+ }
422
+ // normal functionality
423
+ super.attributeChangedCallback(name, oldValue, newValue);
424
+ }
425
+
426
+ attachShadow(mode) {
427
+ if (this.hasAttribute("data-sk-static")) {
428
+ return null;
429
+ }
430
+ return super.attachShadow(mode);
431
+ }
432
+ };
433
+
434
+ // Call the original function with modified arguments
435
+ // eslint-disable-next-line no-undef
436
+ originalDefine.call(window.customElements, e, skProxy, options);
437
+ };
438
+ }
439
+
440
+ /**
441
+ * Overrides the custom element definition in the given document.
442
+ *
443
+ * @param {Document} document - The document object where the custom element is defined.
444
+ *
445
+ * @return {void} - This method does not return anything.
446
+ */
447
+ function overrideCustomElementDefine(document) {
448
+ const script = document.createElement("script");
449
+ script.setAttribute("type", "text/javascript");
450
+ script.classList.add("sk-shadowroot-extension");
451
+ script.textContent = `(${overrideDefine.toString()})()`;
452
+ document.querySelector("head").appendChild(script);
453
+ }
454
+
455
+ /**
456
+ * Walks through pre-rendered elements and performs specific actions on each element.
457
+ *
458
+ * @param {Document} document - The HTML document object.
459
+ */
460
+ function walkPreRenderedElements(document) {
461
+ const customShadowElements = getCustomShadowElements(document);
462
+ walkElements(customShadowElements, (element) => {
463
+ addStaticContextForPrerenderedElements(element);
464
+ // TODO extend if shadow-doms should be modified, as access to the shadow dom is not possible via querySelectors from the outside
465
+ });
466
+ }
467
+
468
+ /**
469
+ * Recursively walks through a tree of elements and applies a callback function to each element.
470
+ *
471
+ * @param {Array} elements - The array of elements to walk through. Each element should be an object with a 'children' property.
472
+ * @param {Function} callback - The callback function to apply to each element. It should take a single argument, which will be the current element.
473
+ *
474
+ * @return {undefined}
475
+ */
476
+ function walkElements(elements, callback) {
477
+ elements.forEach((element) => {
478
+ callback(element);
479
+ if (element.children && element.children.length > 0) {
480
+ walkElements(element.children, callback);
481
+ }
482
+ });
483
+ }
484
+
485
+ /**
486
+ * Adds static context for prerendered elements.
487
+ *
488
+ * @param {Element} element The element to add static context for.
489
+ */
490
+ function addStaticContextForPrerenderedElements(element) {
491
+ if (
492
+ "name" in element &&
493
+ element.name !== undefined &&
494
+ element.name.startsWith("{{shadowDomCustomElementPrefix}}")
495
+ ) {
496
+ element.setAttribute("data-sk-static", "1");
497
+ }
498
+ }
499
+ {{else}}
500
+ {{/if}}
501
+ {{/if}}
@@ -37,6 +37,14 @@
37
37
  dynamicScriptExecution: true, // Shopify specific
38
38
  // => as some async external scripts are injected more or less randomly into the html, destroying the order of local & remote scripts
39
39
  {{/if}}
40
+ {{#if addPreRendering}}
41
+ assetChangeDetection: {
42
+ ignore: [
43
+ // keep aligned with DocumentHandler's config.ignoreAssets
44
+ ],
45
+ },
46
+ {{/if}}
47
+
40
48
  blocks: [
41
49
  // TODO: adjust/replace this exemplary list
42
50
  {{#if addPreRendering}}
@@ -681,5 +681,5 @@
681
681
  ]
682
682
  }
683
683
  },
684
- "version": "2.33.0"
684
+ "version": "2.35.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.33.0",
4
+ "version": "2.35.0",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"