`;
+ }
+}
diff --git a/blocks/button/button.css b/blocks/button/button.css
index 7358e7cb..e3a5c958 100644
--- a/blocks/button/button.css
+++ b/blocks/button/button.css
@@ -1,8 +1,12 @@
raqn-button {
width: 100%;
display: grid;
+ align-content: center;
+ justify-content: center;
+ align-items: center;
+ justify-items: var(--scope-justify, start);
- & > div {
+ & > * {
background-color: var(--scope-accent-background, #000);
color: var(--scope-accent-color, #fff);
text-transform: none;
@@ -19,7 +23,7 @@ raqn-button {
}
raqn-button a {
- color: currentcolor;
+ color: var(--scope-accent-color, currentcolor);
padding: 10px 20px;
text-decoration: none;
}
diff --git a/blocks/card/card.css b/blocks/card/card.css
index db0d2da7..7e9f1bb5 100644
--- a/blocks/card/card.css
+++ b/blocks/card/card.css
@@ -1,10 +1,41 @@
raqn-card {
- background-color: var(--scope-background, red);
- display: grid;
- grid-template-columns: var(--card-columns, 1fr);
- gap: var(--scope-gap, 20px);
+ background-color: var(--scope-background, red);
+ display: grid;
+ position: relative;
+ grid-template-columns: var(--card-columns, 1fr);
+ gap: var(--scope-gap, 20px);
+ padding: var(--scope-padding, 20px 0);
+}
+
+raqn-card a {
+ font-size: var(--raqn-font-size-3, 1.2em);
+ font-weight: bold;
}
raqn-card > picture {
- grid-column: span var(--card-columns, 1fr);
-}
\ No newline at end of file
+ grid-column: span var(--card-columns, 1fr);
+}
+
+raqn-card > div {
+ position: relative;
+}
+
+raqn-card:not(.inner-background) > div {
+ background-color: var(--scope-background, #fff);
+ color: var(--scope-color, #000);
+}
+
+raqn-card > div div:last-child > a {
+ position: absolute;
+ inset-inline-start: 0;
+ inset-block-start: 0;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+ text-indent: -10000px;
+}
+
+raqn-card raqn-icon {
+ width: 100%;
+ display: flex;
+}
diff --git a/blocks/card/card.js b/blocks/card/card.js
index cd9b1026..9c69b120 100644
--- a/blocks/card/card.js
+++ b/blocks/card/card.js
@@ -3,17 +3,32 @@ import { eagerImage } from '../../scripts/libs.js';
export default class Card extends ComponentBase {
static get observedAttributes() {
- return ['columns', 'ratio', 'eager'];
+ return ['columns', 'ratio', 'eager', 'background', 'button'];
}
connected() {
+ if (this.getAttribute('button') === 'true') {
+ Array.from(this.querySelectorAll('a')).forEach((a) =>
+ this.convertLink(a),
+ );
+ }
this.eager = parseInt(this.getAttribute('eager') || 0, 10);
+ this.ratio = this.getAttribute('ratio') || '4/3';
+ this.style.setProperty('--card-ratio', this.ratio);
+ this.classList.add('inner');
this.setupColumns(this.getAttribute('columns'));
if (this.eager) {
eagerImage(this, this.eager);
}
}
+ convertLink(a) {
+ const button = document.createElement('raqn-button');
+ const content = a.outerHTML;
+ button.innerHTML = content;
+ a.replaceWith(button);
+ }
+
setupColumns(columns) {
if (!columns) {
return;
@@ -24,19 +39,4 @@ export default class Card extends ComponentBase {
.join(' ');
this.style.setProperty('--card-columns', this.area);
}
-
- attributeChangedCallback(name, oldValue, newValue) {
- if (oldValue !== newValue) {
- switch (name) {
- case 'columns':
- this.setupColumns(newValue);
- break;
- case 'ratio':
- this.style.setProperty('--card-ratio', newValue);
- break;
- default:
- break;
- }
- }
- }
}
diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css
index 23d16ae2..46c3a0f2 100644
--- a/blocks/footer/footer.css
+++ b/blocks/footer/footer.css
@@ -1,14 +1,38 @@
footer {
- padding: 2rem;
- background-color: var(--overlay-background-color);
- font-size: var(--body-font-size-s);
+ background-color: var(--scope-background-color);
+ width: var(--scope-max-width);
+ margin: 0 auto;
}
-footer .footer {
- max-width: 1200px;
- margin: auto;
-}
+raqn-footer {
+ background-color: var(--scope-background-color);
+ border-top: 1px solid var(--scope-color);
+ display: grid;
+ grid-template-columns: auto 20vw;
+
+ & :last-child {
+ justify-self: end;
+ align-self: center;
+ }
+
+ ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+
+ li {
+ margin: 2em 0;
+
+ a {
+ padding: 10px 1.2em;
+ color: var(--scope-color);
+ border-inline-end: 1px solid var(--scope-color);
+ }
-footer .footer p {
- margin: 0;
+ &:last-child a {
+ border-inline-end: none;
+ }
+ }
+ }
}
diff --git a/blocks/footer/footer.js b/blocks/footer/footer.js
index c8ff6b12..8c39f0f3 100644
--- a/blocks/footer/footer.js
+++ b/blocks/footer/footer.js
@@ -5,4 +5,12 @@ export default class Footer extends ComponentBase {
super();
this.external = '/footer.plain.html';
}
+
+ ready() {
+ const child = this.children[0];
+ child.replaceWith(...child.children);
+ this.nav = this.querySelector('ul');
+ this.nav.setAttribute('role', 'navigation');
+ this.classList.add('full-width');
+ }
}
diff --git a/blocks/hero/hero.css b/blocks/hero/hero.css
index 0483f623..55c0ffa3 100644
--- a/blocks/hero/hero.css
+++ b/blocks/hero/hero.css
@@ -1,23 +1,23 @@
/* block specific CSS goes here */
raqn-hero {
- --raqn-background-color: var(--scope-background, transparent);
- --raqn-color: var(--scope-color, transparent);
- --raqn-grid-template-columns: 0.6fr 0.4fr;
+ --hero-background-color: var(--scope-background, black);
+ --hero-color: var(--scope-color, white);
+ --hero-grid-template-columns: 0.6fr 0.4fr;
+ --hero-hero-order: 0;
+ --hero-padding-block: 40px;
- background-color: var(--raqn-background-color);
- color: var(--raqn-color);
+ background-color: var(--hero-background-color);
+ color: var(--hero-color);
align-items: center;
+ grid-template-columns: var(--hero-grid-template-columns, 1fr);
+ padding-block: var(--hero-padding-block);
@media screen and (max-width: 768px) {
- --raqn-grid-template-columns: 1fr;
+ --hero-grid-template-columns: 1fr;
}
- grid-template-columns: var(--raqn-grid-template-columns, 1fr);
-
- @media screen and (min-width: 768px) {
- & > div:first-child {
- max-width: 30vw;
- }
+ & > div:first-child {
+ order: var(--hero-hero-order);
}
}
diff --git a/blocks/hero/hero.js b/blocks/hero/hero.js
index 67c2f5e6..660419c4 100644
--- a/blocks/hero/hero.js
+++ b/blocks/hero/hero.js
@@ -1,12 +1,16 @@
import ComponentBase from '../../scripts/component-base.js';
export default class Hero extends ComponentBase {
+ get observedAttributes() {
+ return ['order'];
+ }
+
connected() {
const child = this.children[0];
child.replaceWith(...child.children);
this.classList.add('full-width');
this.setAttribute('role', 'banner');
this.setAttribute('aria-label', 'hero');
- this.style.setProperty('--hero-columns', this.getAttribute('height'));
+ this.style.setProperty('--hero-hero-order', this.getAttribute('order'));
}
}
diff --git a/blocks/icon/icon.css b/blocks/icon/icon.css
index bd707aa0..e4ac4b87 100644
--- a/blocks/icon/icon.css
+++ b/blocks/icon/icon.css
@@ -2,9 +2,10 @@ raqn-icon {
display: inline-flex;
font-size: 1em;
line-height: 1em;
+ text-align: center;
min-width: var(--scope-icon-size, 1em);
min-height: var(--scope-icon-size, 1em);
- text-align: center;
+ justify-content: var(--scope-icon-align, start);
text-transform: none;
vertical-align: middle;
-webkit-font-smoothing: antialiased;
@@ -13,8 +14,8 @@ raqn-icon {
raqn-icon svg {
display: inline-block;
- max-height: 100%;
- max-width: 100%;
+ max-width: var(--scope-icon-size, 1em);
+ max-height: var(--scope-icon-size, 1em);
fill: currentcolor;
overflow: hidden;
vertical-align: middle;
diff --git a/blocks/icon/icon.js b/blocks/icon/icon.js
index b1ab2434..08e6e075 100644
--- a/blocks/icon/icon.js
+++ b/blocks/icon/icon.js
@@ -16,7 +16,7 @@ export default class Icon extends ComponentBase {
}
get iconUrl() {
- return `assets/icons/${this.iconName}.svg`;
+ return `/assets/icons/${this.iconName}.svg`;
}
get cache() {
diff --git a/blocks/navigation/navigation.css b/blocks/navigation/navigation.css
index 26f5ed24..bea390f7 100644
--- a/blocks/navigation/navigation.css
+++ b/blocks/navigation/navigation.css
@@ -97,6 +97,17 @@ raqn-navigation {
padding: var(--padding-vertical, 20px) var(--padding-horizontal, 20px);
}
+ .level-2,
+ .level-2 > ul {
+ display: inline-flex;
+ flex-direction: column;
+ }
+
+ .level-2 > ul {
+ list-style: none;
+ padding: 0;
+ }
+
.level-1 > ul {
display: flex;
clip-path: inset(0% -100vw 100% -100vw);
diff --git a/blocks/navigation/navigation.js b/blocks/navigation/navigation.js
index 210e2651..25e93704 100644
--- a/blocks/navigation/navigation.js
+++ b/blocks/navigation/navigation.js
@@ -17,7 +17,7 @@ export default class Navigation extends Column {
return button;
}
- render() {
+ ready() {
this.list = this.querySelector('ul');
this.nav = document.createElement('nav');
this.nav.append(this.list);
diff --git a/blocks/theme/theme.js b/blocks/theme/theme.js
index c7ad257a..4a977ac3 100644
--- a/blocks/theme/theme.js
+++ b/blocks/theme/theme.js
@@ -7,7 +7,26 @@ export default class Theme extends ComponentBase {
constructor() {
super();
this.external = '/theme.json';
- this.toTags = ['font-size', 'font-weight', 'font-family'];
+ this.skip = ['tag', 'font-face'];
+ this.themeVariables = [
+ 'color',
+ 'background',
+ 'link-color',
+ 'link-color-hover',
+ 'accent-color',
+ 'accent-background',
+ 'accent-color-hover',
+ 'accent-background-hover',
+ 'accent2-color',
+ 'accent2-background',
+ 'accent2-color-hover',
+ 'accent2-background-hover',
+ 'header-height',
+ 'header-background',
+ 'header-color',
+ 'max-width',
+ ];
+ this.toTags = ['font-size', 'font-weight', 'font-family', 'line-height'];
this.fontFace = '';
this.atomic = '';
}
@@ -54,10 +73,6 @@ export default class Theme extends ComponentBase {
this.fontFace += this.fontFaceTemplate(value);
} else {
variable = `\n--raqn-${key}-${row}: ${value};\n`;
- if (row === 'default') {
- variable += `\n--scope-${key}: ${value};\n`;
- }
-
this.atomic += `\n.${key}-${row} {\n--scope-${key}: var(--raqn-${key}-${row}, ${value}); \n}\n`;
}
}
@@ -71,6 +86,9 @@ export default class Theme extends ComponentBase {
(ac, item, i) =>
keys.reduce((acc, key) => {
delete item.key;
+ if (!this.themesKeys) {
+ this.themesKeys = k(item);
+ }
const ind = keys.indexOf(key);
if (i === ind) {
acc[key] = item;
@@ -84,7 +102,24 @@ export default class Theme extends ComponentBase {
.map((index) => this.fontTags(t, index))
.join('\n\n');
+ // full scoped theme classes
+ this.themes = this.themesKeys
+ .map(
+ (theme) => `\n.theme-${theme} {\n${k(t)
+ .filter((key) => this.themeVariables.includes(key))
+ .map((key) =>
+ t[key][theme]
+ ? `--scope-${key}: var(--raqn-${key}-${theme}, ${t[key][theme]});`
+ : '',
+ )
+ .filter((v) => v !== '')
+ .join('\n')}
+ }\n`,
+ )
+ .join('');
+
this.variables = k(t)
+ .filter((key) => !this.skip.includes(key))
.map((key) => {
const rows = k(t[key]);
return rows.map((row) => this.renderVariables(key, row, t)).join('');
@@ -94,8 +129,9 @@ export default class Theme extends ComponentBase {
styles() {
const style = document.createElement('style');
- style.innerHTML = `${this.fontFace}\n\nbody {\n${this.variables}\n}\n\n${this.tags}\n\n${this.atomic}`;
+ style.innerHTML = `${this.fontFace}\n\nbody {\n${this.variables}\n}\n\n${this.tags}\n\n${this.atomic}\n${this.themes}`;
document.head.appendChild(style);
+ document.body.classList.add('theme-default');
document.body.style.display = 'block';
}
diff --git a/docs/assets/adobelive-slow3g.png b/docs/assets/adobelive-slow3g.png
new file mode 100644
index 00000000..05889ad7
Binary files /dev/null and b/docs/assets/adobelive-slow3g.png differ
diff --git a/docs/assets/aemlive1.png b/docs/assets/aemlive1.png
new file mode 100644
index 00000000..654b4d0e
Binary files /dev/null and b/docs/assets/aemlive1.png differ
diff --git a/docs/assets/aemlive2.png b/docs/assets/aemlive2.png
new file mode 100644
index 00000000..c678cd9c
Binary files /dev/null and b/docs/assets/aemlive2.png differ
diff --git a/docs/assets/franklin-regular-network.png b/docs/assets/franklin-regular-network.png
new file mode 100644
index 00000000..e1883b91
Binary files /dev/null and b/docs/assets/franklin-regular-network.png differ
diff --git a/docs/assets/franklin-slow3g-network.png b/docs/assets/franklin-slow3g-network.png
new file mode 100644
index 00000000..1e16e152
Binary files /dev/null and b/docs/assets/franklin-slow3g-network.png differ
diff --git a/docs/assets/hero-example.png b/docs/assets/hero-example.png
new file mode 100644
index 00000000..b7a25459
Binary files /dev/null and b/docs/assets/hero-example.png differ
diff --git a/docs/assets/hero-mobile-param-preview.png b/docs/assets/hero-mobile-param-preview.png
new file mode 100644
index 00000000..9887fbbe
Binary files /dev/null and b/docs/assets/hero-mobile-param-preview.png differ
diff --git a/docs/assets/hero-mobile-param.png b/docs/assets/hero-mobile-param.png
new file mode 100644
index 00000000..bda56951
Binary files /dev/null and b/docs/assets/hero-mobile-param.png differ
diff --git a/docs/assets/hero-order-param-0.png b/docs/assets/hero-order-param-0.png
new file mode 100644
index 00000000..ce82d1fc
Binary files /dev/null and b/docs/assets/hero-order-param-0.png differ
diff --git a/docs/assets/hero-param-1.png b/docs/assets/hero-param-1.png
new file mode 100644
index 00000000..cdb95768
Binary files /dev/null and b/docs/assets/hero-param-1.png differ
diff --git a/docs/assets/hero-param.png b/docs/assets/hero-param.png
new file mode 100644
index 00000000..c5441453
Binary files /dev/null and b/docs/assets/hero-param.png differ
diff --git a/docs/assets/hero.png b/docs/assets/hero.png
new file mode 100644
index 00000000..da4fd4bc
Binary files /dev/null and b/docs/assets/hero.png differ
diff --git a/docs/assets/netcentricbiz.png b/docs/assets/netcentricbiz.png
new file mode 100644
index 00000000..e2559bd4
Binary files /dev/null and b/docs/assets/netcentricbiz.png differ
diff --git a/docs/assets/raqn-slow3g-await.png b/docs/assets/raqn-slow3g-await.png
new file mode 100644
index 00000000..513226ac
Binary files /dev/null and b/docs/assets/raqn-slow3g-await.png differ
diff --git a/docs/assets/raqn-slow3g-regular.png b/docs/assets/raqn-slow3g-regular.png
new file mode 100644
index 00000000..217bc647
Binary files /dev/null and b/docs/assets/raqn-slow3g-regular.png differ
diff --git a/docs/assets/raqn-wifi-await.png b/docs/assets/raqn-wifi-await.png
new file mode 100644
index 00000000..078480f6
Binary files /dev/null and b/docs/assets/raqn-wifi-await.png differ
diff --git a/docs/assets/raqn-wifi-regular.png b/docs/assets/raqn-wifi-regular.png
new file mode 100644
index 00000000..fb98ec76
Binary files /dev/null and b/docs/assets/raqn-wifi-regular.png differ
diff --git a/docs/assets/theme-concept-excel.png b/docs/assets/theme-concept-excel.png
new file mode 100644
index 00000000..6533a9a6
Binary files /dev/null and b/docs/assets/theme-concept-excel.png differ
diff --git a/docs/edge/client.md b/docs/edge/client.md
index b2d3fbd2..68e0b76b 100644
--- a/docs/edge/client.md
+++ b/docs/edge/client.md
@@ -1,15 +1,74 @@
-# Everything else as client side javascript
+# Everything as client side javascript
+
+As mentioned and for what is rendered at serverside.
+
+Things that can be done without javascript:
+
+1. Style basic html set on the page.
+2. Predefine sizes on LCP (althout even the header is not available SSR)
+3. Set the scripts to and things to preload
+
+Most features depends on javascript to transform dom and hidratate it.
+
+1. load and priorize resorces that are not in head.html
+2. inicially hide body
+3. parse dom
+4. compile rendering blocking styles and javascripts
+5. Load header.plain.html
+6. load other non eager components
+
+The only server side injection available is by head.html.
Requirements
- Clientside Hidratation dom transformation "decoration"
- Manual focus on FCP and LCP
-- blocking resources before render
+- queue and blocking each resources before loading and render next
Boiler plate approach
-1. Performance
+1. Performance by avoiding visible rendering before loading required
2. Decoration by feature
-3. Manualy defining priority
+3. Manualy defining priority of required
4. Eager and defered
-5. Semantical content used as component functional content (metadata)
+5. Semantical content used as component functional content (metadata) among others
+
+## Defining blocks and features
+
+### Default script behavior - Non block features
+
+1. Setup SampleRun
+2. Eager loading
+ 1. decorateTemplateAndTheme (Required once, add classes to body)
+ 2. Load external header, add css variables using metadata (required to avoid CLS and must be defined used styles.css)
+ 3. decorateMain (Required once, but need to run always that a new dom is loaded or added) includes:
+ 1. decorate buttons, icons, blocks, Section, Images, grid.
+ 4. if desktop loads fonts
+3. Lazy and delayed 4. Load blocks (by order of apearance) 5. Load footer and decorate it manually 6. Load fonts 7. Load lazy styles 8. Observe for delayed etcs
+
+### Block features
+
+A example of a block
+
+```javascript
+export default async function decorate(block) {
+ // add feature to a element (block)
+ // Eg add events transform it etcs
+}
+```
+
+Here is some examples:
+(Example HEADER)[https://github.com/adobe/aem-boilerplate/blob/main/blocks/header/header.js]
+
+Advances:
+1 - Simple
+2 - Functional approch good for testing
+3 - usign modules allow importing from other libraries
+
+Disavantages
+1 - Blocks need to be executed manually in every element
+2 - Any new dom needs be decorated manually
+3 - Limited reusability of a "component"
+4 - Too opniated allows to free style coding / no code best practices enforcing
+5 - No clear life cicle of a block
+6 - Uses semantical data as parameters and passing options
diff --git a/docs/edge/perfomance.md b/docs/edge/perfomance.md
new file mode 100644
index 00000000..ecd0a347
--- /dev/null
+++ b/docs/edge/perfomance.md
@@ -0,0 +1,111 @@
+# Edge Perfomance
+
+As it states
+
+```
+If you start your project with the Boilerplate as in the Developer Tutorial, you will get a very stable Lighthouse score that is 100. On every component of the lighthouse score there is some buffer for the project code to use and still be within the boundaries of a perfect 100 score.
+```
+
+Main point to focus here is
+Please check
+[Three-Phase Loading](https://www.aem.live/developer/keeping-it-100#three-phase-loading-e-l-d)
+
+Edge defines as 3 fases
+
+- Phase E (Eager): This contains everything that's needed to get to the largest contentful paint (LCP).
+- Phase L (Lazy): This contains everything that is controlled by the project and largely served from the same origin.
+- Phase D (Delayed): This contains everything else such as third-party tags or assets that are not material to experience.
+
+The good:
+
+1. Good backend perfomance
+2. LCP Blocks
+3. Proper Eager first LCP image.
+4. Deliver minimal for LCP
+5. Font fallback plugin
+
+Could improve
+
+1. Header
+2. no mention of preloading
+3. fixed code required to configure LCP_BLOCKS
+
+Problems
+
+1. Queue / Priorization with async await code
+2. Icons as inicial decoration script
+
+# Good
+
+## Good backend perfomnce
+
+Very good backend perfomance OOB and very small footprint under CDN.
+This seems the most impacting perfomance, at CDN in several sites, content and assets can reach amazing speed.
+
+### Production sites with server response under 40ms
+
+Examples
+
+- [https://www.netcentric.biz/](https://www.netcentric.biz/)
+- [https://www.aem.live/home](https://www.aem.live/home)
+
+## LCP blocks
+
+One great point is to be able to configure what blocks represents LCP
+
+```javascript
+// at their script they allow a LCP_BLOCKS
+const LCP_BLOCKS = ['hero', 'logo-wall'];
+```
+
+## Eager first LCP image
+
+Althoult backend renders first image as `loading="lazy"` they transform it to `loading="eager"` at blocking level
+Kepping it as good as it were preloaded.
+
+## fonts
+
+They offer a plugin to proper check and setup custom fonts to avoid CLS [font fallback plugin](https://www.aem.live/developer/font-fallback)
+
+# Could improve
+
+## header
+
+although they enforce that header is not LCP, that definetly is the case in several henkel sites where logo is the main LCP or header impacts CLS. So a non priorized header can easily impact CLS and some cases even LCP
+
+## Some assets could be preloaded.
+
+In some cases preload some assets would offer advantages on LCP and FP
+
+## fixed code required to configure LCP_BLOCKS
+
+That also can be a issue if you have diferent blocks on diferent pages
+
+# Problems
+
+Well althould is actually a well performing script, it's relying a lot on queue and async await.
+
+- Async function if you actually await became basicly syncronous code.
+
+Queue will always offer a step by step loading and reduce the concurrency increasing the time to load overrall and can lead to longer load times.
+
+## This repo on OOB script on regular wifi network no CDN
+
+![OOB script on regular wifi network no CDN](../assets/franklin-regular-network.png)
+
+## This repo on OOB script on slow 3G
+
+![OOB script on regular wifi network no CDN](../assets/franklin-slow3g-network.png)
+get's clear the async await / queue impact
+
+## At netcentric.biz
+
+![Netcentric.biz](../assets/netcentricbiz.png)
+
+## At aem.live
+
+![Netcentric.biz](../assets/aemlive1.png)
+
+# Here anothe issue due to icons loaded before since they are defined at incial script
+
+![Netcentric.biz](../assets/aemlive2.png)
diff --git a/docs/raqn/components.md b/docs/raqn/components.md
new file mode 100644
index 00000000..f6b0b6ab
--- /dev/null
+++ b/docs/raqn/components.md
@@ -0,0 +1,207 @@
+# Component Base Development with custom elements
+
+We want to be able to add features as reusable components instead of a composition of functions.
+
+That allows us to:
+
+1. Have a clear component life cicle
+2. Enforce good code practices
+3. Easy Reusability
+
+# Defining concern and features in Component-Based Architecture
+
+For better separation of concern we should define feaures as components.
+
+Let's have a example
+Let's check some concerns defined on the main OOB script for EDS
+1 - Decorate buttons
+2 - Placeholders
+3 - Load icons
+4 - Restructure Headings
+
+Those concerns should not be at general inicial script
+
+- A button should be the concern of it's component not a convetion based on beeing inside of a div
+- Placeholders should be concern of sites that require placeholders
+- Icons, so not every site or page will require icons, so icon is the concern of icons not all pages.
+- Headings can be defined by authors, and semantical meaning should also be concern of authors or a component.
+
+We should be able to set components and features on the fly without obligation as components.
+
+## Simple component loader
+
+Instead of several decoration functions we setup a simple component loader to "decorate and prepare"
+
+It allows:
+
+1. Still keep the "blocks" as functional scripts
+2. Allow to use custom elements intead
+3. Proper setup data and params from class intead of semantical content data.
+4. Params can be setup per breakpoint
+
+## Component loader
+
+Component setup params and custom elements
+
+Document example
+![Hero](../assets/hero-example.png)
+
+Then with the example our component loader will:
+
+1. Try to load a hero component or block
+2. Will check if the loaded default has a name and not fn named decorate
+3. Read and create element attributes based on class names
+4. If not it will create a custom element based on the name of the component
+5. if is a decorate function keep as EDS
+
+## Base Custom Element Component
+
+A standard use custom elements as our definition of a component
+
+That will
+
+1. Extends HTMLElement
+2. Pre define some extra life cicle
+ 1. predefine `connectedCallback`
+ 2. Load a external content if `external` property exist
+ 3. Run Component loader on it's content if external
+ 4. Add a life cicle for process external html as `processExternal`
+ 5. Add a `connected` after previous setup and externals are loaded
+ 6. Add a ready life cicle to setup things everythings is ready.
+3. Keep all other custom element features
+
+here:
+[component-base](../../scripts/component-base.js)
+
+With component loader that will be rendered as:
+
+```html
+
+
+
+
+
+
Get started
+
+ Learn the basics: how to best get started and create a page. And how to
+ transfer your brand theme to the new capabilities of RAQN web.
+
+
+
+```
+
+## Custom element example
+
+So let's bring that custom element to life
+
+Let's check this simple example
+
+```javascript
+import ComponentBase from '../../scripts/component-base.js';
+
+export default class Hero extends ComponentBase {
+ static observedAttributes = ['order'];
+
+ ready() {
+ this.order = this.getAttribute('order');
+ // add some extra classes
+ this.classList.add('full-width');
+ this.setAttribute('role', 'banner');
+ // setup a css variable
+ this.style.setProperty('--hero-hero-order', this.getAttribute('order'));
+ }
+}
+```
+
+this example:
+1 - Use the ready callback when the custom element is defined and added to the page.
+2 - Setup some classe, attribute and set a css variable.
+
+### Passing attributes to your component
+
+Let's the use the document to pass the param
+
+![Order](../assets/hero-order-param-0.png)
+
+Then with this change you will pass a param to your component
+
+```html
+
+
+
+
+
+
+
+
+```
+
+So let's add a little style at hero.css
+
+```css
+/* block specific CSS goes here */
+raqn-hero {
+ --hero-background-color: var(--scope-background, black);
+ --hero-color: var(--scope-color, white);
+ --hero-grid-template-columns: 0.6fr 0.4fr;
+ --hero-hero-order: 0;
+
+ background-color: var(--hero-background-color);
+ color: var(--hero-color);
+ align-items: center;
+ grid-template-columns: var(--hero-grid-template-columns, 1fr);
+
+ @media screen and (max-width: 768px) {
+ --hero-grid-template-columns: 1fr;
+ }
+
+ & > div:first-child {
+ order: var(--hero-hero-order);
+ }
+}
+```
+
+Now we should have something like
+(Apart from theme definitions (see [theme](theme.md)))
+![Hero 1](../assets/hero.png)
+
+### Changing the params by document
+
+![Hero param 1](../assets/hero-param.png)
+
+These change will:
+1 - Set the param to 1
+2 - Setting the variable order to css
+
+them we'll see changes like:
+
+![Hero applyed](../assets/hero-param-1.png)
+
+A param is set to all viewports
+
+## Setting Param only to a viewport
+
+For setting a param only to a specific viewport just prefix with the viewport key
+
+1. **s**: 0 to 767,
+2. **m**: 768 to 1023,
+3. **l**: 1024 to 1279,
+4. **xl**: 1280 to 1919,
+5. **xxl**: 1920,
+
+So let's set the order param to apply only on S (0 to 767) viewport
+
+![Mobile param](../assets/hero-mobile-param.png)
+
+Now the param is only set at S viewports
+
+![Mobile param preview](../assets/hero-mobile-param-preview.png)
+
+Where:
+
+1. regular params will be set to all viewports
+2. Prefixed params will be apply only to the specific viewport overriding the general one.
diff --git a/docs/raqn/reasoning.md b/docs/raqn/reasoning.md
new file mode 100644
index 00000000..801eaa8c
--- /dev/null
+++ b/docs/raqn/reasoning.md
@@ -0,0 +1,42 @@
+# Reasoning
+
+We want to take advantage of Edge Delivery cababilities but improving some key features we embrace in RAQN web
+
+1. Components Based Development
+2. Authoring theming capabilityes
+3. Fine Grained Perfomance by authoring
+
+For proof of concept we are recreating https://guide.raqn.io/ in EDS
+
+## Component Based Development
+
+We want to be able to add features as reusable components instead of a composition of functions.
+
+That allows us to:
+
+1. Have a clear component life cicle
+2. Enforce good code practices
+3. Easy Reusability
+
+## Authoring Theme Capabilityes
+
+We want authors to be able to create fast and quick simple websites without having a development team available
+
+this means same code can:
+
+1. Change colors
+2. Change composition (grid, margins, borders)
+3. Change Icons
+4. Change fonts
+5. Apply specific styles
+
+## Fine Grained Perfomance
+
+As the same concept of themeing we want to be able to setup same code base with diferent params related to perfomance.
+
+not all websites have the same set of components order / layout definitions therefore we need to be able to change components and elements priorization **without development** to be able to still get 100% perfomance.
+
+Those are the minimal requirements
+
+1. Allow editorial setup what components are LCP per page
+2. Allow to setup what images are eager loaded per page
diff --git a/docs/raqn/theming.md b/docs/raqn/theming.md
new file mode 100644
index 00000000..6358a122
--- /dev/null
+++ b/docs/raqn/theming.md
@@ -0,0 +1 @@
+# Theming
diff --git a/docs/readme.md b/docs/readme.md
index a1336eb9..b3ac892b 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -4,12 +4,14 @@ This project is a RAQN option for using edge deliver.
Summary
-1. Introduction
- 1. Introdution to Edge Delivery Services
- 2. [EDS - Server rendering](edge/server.md)
- 3. [EDS - Client side features](edge/client.md)
-2. RAQN EDS
- 1. Reasoning
- 2. Component loader
- 3. Custom elements
- 4. Defining Priority Components
+1. Edge Delivery OOB
+
+ 1. [Server rendering](edge/server.md)
+ 2. [Client side features](edge/client.md)
+ 3. [Perfomance](edge/perfomance.md)
+
+2. RAQN POC
+ 1. [Reasoning](raqn/reasoning.md)
+ 2. [Component base development](raqn/components.md)
+ 3. [Theming and authoring](./raqn/theming.md)
+ 4. [Perfomance](./raqn/perfomance.md)
diff --git a/head.html b/head.html
index 68292790..6f07eff5 100644
--- a/head.html
+++ b/head.html
@@ -1,8 +1,5 @@
-
-
-
{
if (!document.querySelector(`head > link[href="${href}"]`)) {
@@ -31,9 +27,6 @@ export default class ComponentLoader {
});
}
- /**
- * Parse extra params from classList
- */
setParams() {
const mediaParams = {};
this.params = {
@@ -66,13 +59,6 @@ export default class ComponentLoader {
};
}
- /**
- * Set the configuration for the given block, and also passes
- * the config through all custom patching helpers added to the project.
- *
- * @param {Element} block The block element
- * @returns {Object} The block config (blockName, cssPath and jsPath)
- */
setBlockPaths() {
this.cssPath = `/blocks/${this.blockName}/${this.blockName}.css`;
this.jsPath = `/blocks/${this.blockName}/${this.blockName}.js`;
@@ -83,7 +69,6 @@ export default class ComponentLoader {
const element = document.createElement(elementName);
element.append(...this.block.children);
Object.keys(this.params).forEach((key) => {
- // @TODO sanitize
const value = Array.isArray(this.params[key])
? this.params[key].join(' ')
: this.params[key];
diff --git a/scripts/init.js b/scripts/init.js
index ebc9b3d9..df817e84 100644
--- a/scripts/init.js
+++ b/scripts/init.js
@@ -1,7 +1,7 @@
import ComponentLoader from './component-loader.js';
import { config, debounce, eagerImage, getBreakPoint } from './libs.js';
-export function retriveDataFrom(blocks) {
+export function getInfos(blocks) {
return blocks.map((block) => {
let el = block;
const tagName = el.tagName.toLowerCase();
@@ -9,6 +9,7 @@ export function retriveDataFrom(blocks) {
if (!config.elementBlocks.includes(tagName)) {
[name] = Array.from(el.classList);
} else {
+ // allow original way of defining blocks
el = document.createElement('div');
block.append(el);
}
@@ -19,24 +20,22 @@ export function retriveDataFrom(blocks) {
});
}
-function lcpPriority() {
- const eagerImages = document.querySelector('meta[name="lcp"]');
- if (eagerImages) {
- const length = parseInt(eagerImages.getAttribute('content'), 10);
- eagerImage(document, length);
+function getMeta(name) {
+ const meta = document.querySelector(`meta[name="${name}"]`);
+ if (!meta) {
+ return null;
}
+ return meta.content;
+}
- const lcp = document.querySelector('meta[name="lcp"]');
- if (!lcp) {
- return window.raqnLCP || [];
+function lcpPriority() {
+ const eagerImages = getMeta('eager-images');
+ if (eagerImages) {
+ const length = parseInt(eagerImages, 10);
+ eagerImage(document.body, length);
}
- window.raqnLCP =
- window.raqnLCP ||
- lcp
- .getAttribute('content')
- .split(',')
- .map((name) => ({ name }));
- return window.raqnLCP;
+ const lcp = getMeta('lcp');
+ window.raqnLCP = lcp ? lcp.split(',').map((name) => ({ name })) : [];
}
export async function init(node = document) {
@@ -48,29 +47,33 @@ export async function init(node = document) {
blocks = [header, ...blocks, footer];
}
- const data = retriveDataFrom(blocks);
+ const data = getInfos(blocks);
const lcp = window.raqnLCP;
- const prio = data.slice(0, 2);
- const rest = data.slice(2);
+ const delay = window.raqnLCPDelay || [];
+ const priority = data.filter(({ name }) => lcp.includes(name));
+ const rest = data.filter(
+ ({ name }) => !lcp.includes(name) && !delay.includes(name),
+ );
const start = ({ name, el }) => {
const loader = new ComponentLoader(name, el);
return loader.decorate();
};
+
+ // start with lcp and priority
Promise.all([
...lcp.map(({ name, el }) => start({ name, el })),
- ...prio.map(({ name, el }) => start({ name, el })),
+ ...priority.map(({ name, el }) => start({ name, el })),
]);
- setTimeout(() => {
- Promise.all(
- rest.map(({ name, el }) => setTimeout(() => start({ name, el }))),
- );
- });
- // reload on breakpoint change
+
+ // timeout for the rest to proper prioritize in case of stalled loading
+ rest.map(({ name, el }) => setTimeout(() => start({ name, el })));
+
+ // reload on breakpoint change to reset params and variables
window.raqnBreakpoint = getBreakPoint();
window.addEventListener(
'resize',
debounce(() => {
- // only on width changes
+ // only on width / breakpoint changes
if (window.raqnBreakpoint !== getBreakPoint()) {
window.location.reload();
}
diff --git a/scripts/lib-franklin.js b/scripts/lib-franklin.js
deleted file mode 100644
index d365f1bd..00000000
--- a/scripts/lib-franklin.js
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * Copyright 2022 Adobe. All rights reserved.
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License. You may obtain a copy
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
- * OF ANY KIND, either express or implied. See the License for the specific language
- * governing permissions and limitations under the License.
- */
-
-/**
- * log RUM if part of the sample.
- * @param {string} checkpoint identifies the checkpoint in funnel
- * @param {Object} data additional data for RUM sample
- */
-export function sampleRUM(checkpoint, data = {}) {
- sampleRUM.defer = sampleRUM.defer || [];
- const defer = (fnname) => {
- sampleRUM[fnname] = sampleRUM[fnname]
- || ((...args) => sampleRUM.defer.push({ fnname, args }));
- };
- sampleRUM.drain = sampleRUM.drain
- || ((dfnname, fn) => {
- sampleRUM[dfnname] = fn;
- sampleRUM.defer
- .filter(({ fnname }) => dfnname === fnname)
- .forEach(({ fnname, args }) => sampleRUM[fnname](...args));
- });
- sampleRUM.always = sampleRUM.always || [];
- sampleRUM.always.on = (chkpnt, fn) => {
- sampleRUM.always[chkpnt] = fn;
- };
- sampleRUM.on = (chkpnt, fn) => {
- sampleRUM.cases[chkpnt] = fn;
- };
- defer('observe');
- defer('cwv');
- try {
- window.hlx = window.hlx || {};
- if (!window.hlx.rum) {
- const usp = new URLSearchParams(window.location.search);
- const weight = usp.get('rum') === 'on' ? 1 : 100; // with parameter, weight is 1. Defaults to 100.
- // eslint-disable-next-line no-bitwise
- const hashCode = (s) => s.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0);
- const id = `${hashCode(
- window.location.href,
- )}-${new Date().getTime()}-${Math.random().toString(16).substr(2, 14)}`;
- const random = Math.random();
- const isSelected = random * weight < 1;
- const urlSanitizers = {
- full: () => window.location.href,
- origin: () => window.location.origin,
- path: () => window.location.href.replace(/\?.*$/, ''),
- };
- // eslint-disable-next-line object-curly-newline, max-len
- window.hlx.rum = {
- weight,
- id,
- random,
- isSelected,
- sampleRUM,
- sanitizeURL: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'],
- };
- }
- const { weight, id } = window.hlx.rum;
- if (window.hlx && window.hlx.rum && window.hlx.rum.isSelected) {
- const sendPing = (pdata = data) => {
- // eslint-disable-next-line object-curly-newline, max-len, no-use-before-define
- const body = JSON.stringify({
- weight,
- id,
- referer: window.hlx.rum.sanitizeURL(),
- checkpoint,
- ...data,
- });
- const url = `https://rum.hlx.page/.rum/${weight}`;
- // eslint-disable-next-line no-unused-expressions
- navigator.sendBeacon(url, body);
- // eslint-disable-next-line no-console
- console.debug(`ping:${checkpoint}`, pdata);
- };
- sampleRUM.cases = sampleRUM.cases || {
- cwv: () => sampleRUM.cwv(data) || true,
- lazy: () => {
- // use classic script to avoid CORS issues
- const script = document.createElement('script');
- script.src = 'https://rum.hlx.page/.rum/@adobe/helix-rum-enhancer@^1/src/index.js';
- document.head.appendChild(script);
- return true;
- },
- };
- sendPing(data);
- if (sampleRUM.cases[checkpoint]) {
- sampleRUM.cases[checkpoint]();
- }
- }
- if (sampleRUM.always[checkpoint]) {
- sampleRUM.always[checkpoint](data);
- }
- } catch (error) {
- // something went wrong
- }
-}
-
-/**
- * Loads a CSS file.
- * @param {string} href URL to the CSS file
- */
-export async function loadCSS(href) {
- return new Promise((resolve, reject) => {
- if (!document.querySelector(`head > link[href="${href}"]`)) {
- const link = document.createElement('link');
- link.rel = 'stylesheet';
- link.href = href;
- link.onload = resolve;
- link.onerror = reject;
- document.head.append(link);
- } else {
- resolve();
- }
- });
-}
-
-/**
- * Loads a non module JS file.
- * @param {string} src URL to the JS file
- * @param {Object} attrs additional optional attributes
- */
-
-export async function loadScript(src, attrs) {
- return new Promise((resolve, reject) => {
- if (!document.querySelector(`head > script[src="${src}"]`)) {
- const script = document.createElement('script');
- script.src = src;
- if (attrs) {
- // eslint-disable-next-line no-restricted-syntax, guard-for-in
- for (const attr in attrs) {
- script.setAttribute(attr, attrs[attr]);
- }
- }
- script.onload = resolve;
- script.onerror = reject;
- document.head.append(script);
- } else {
- resolve();
- }
- });
-}
-
-/**
- * Retrieves the content of metadata tags.
- * @param {string} name The metadata name (or property)
- * @returns {string} The metadata value(s)
- */
-export function getMetadata(name) {
- const attr = name && name.includes(':') ? 'property' : 'name';
- const meta = [...document.head.querySelectorAll(`meta[${attr}="${name}"]`)]
- .map((m) => m.content)
- .join(', ');
- return meta || '';
-}
-
-/**
- * Sanitizes a string for use as class name.
- * @param {string} name The unsanitized string
- * @returns {string} The class name
- */
-export function toClassName(name) {
- return typeof name === 'string'
- ? name
- .toLowerCase()
- .replace(/[^0-9a-z]/gi, '-')
- .replace(/-+/g, '-')
- .replace(/^-|-$/g, '')
- : '';
-}
-
-/**
- * Sanitizes a string for use as a js property name.
- * @param {string} name The unsanitized string
- * @returns {string} The camelCased name
- */
-export function toCamelCase(name) {
- return toClassName(name).replace(/-([a-z])/g, (g) => g[1].toUpperCase());
-}
-
-const ICONS_CACHE = {};
-/**
- * Replace icons with inline SVG and prefix with codeBasePath.
- * @param {Element} [element] Element containing icons
- */
-export async function decorateIcons(element) {
- // Prepare the inline sprite
- let svgSprite = document.getElementById('franklin-svg-sprite');
- if (!svgSprite) {
- const div = document.createElement('div');
- div.innerHTML = '';
- svgSprite = div.firstElementChild;
- document.body.append(div.firstElementChild);
- }
-
- // Download all new icons
- const icons = [...element.querySelectorAll('span.icon')];
- await Promise.all(
- icons.map(async (span) => {
- const iconName = Array.from(span.classList)
- .find((c) => c.startsWith('icon-'))
- .substring(5);
- if (!ICONS_CACHE[iconName]) {
- ICONS_CACHE[iconName] = true;
- try {
- const response = await fetch(
- `${window.hlx.iconsPath}/${iconName}.svg`,
- );
- if (!response.ok) {
- ICONS_CACHE[iconName] = false;
- return;
- }
- // Styled icons don't play nice with the sprite approach because of shadow dom isolation
- // and same for internal references
- const svg = await response.text();
- if (svg.match(/(