Skip to content

Commit

Permalink
feat: implement experimental support for server side rendering (SSR) (#…
Browse files Browse the repository at this point in the history
…2466)

This PR adds the experimental possibility to use server side rendering with `@sbb-esta/lyne-components`. Most components have been verified to work with SSR, but our testing setup is not yet complete and will be finished in a future PR.

---------

Co-authored-by: Jeremias Peier <[email protected]>
  • Loading branch information
kyubisation and jeripeierSBB authored Mar 21, 2024
1 parent 6d4e9c2 commit 3abcc68
Show file tree
Hide file tree
Showing 268 changed files with 4,151 additions and 2,417 deletions.
31 changes: 28 additions & 3 deletions config/lit-ssr-worker.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { createHash } from 'crypto';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { dirname, join, relative, resolve } from 'path';
import { parentPort, workerData } from 'worker_threads';

import { render } from '@lit-labs/ssr';
import { build } from 'vite';
import { createHash } from 'crypto';
import { dirname, join, relative, resolve } from 'path';
import * as esbuild from 'esbuild';
import { sassPlugin } from 'esbuild-sass-plugin';

if (parentPort === null) {
throw new Error('worker.js must only be run in a worker thread');
Expand Down Expand Up @@ -50,6 +52,28 @@ async function buildModules(buildCacheOutDir) {
.join('\n');
writeFileSync(entry, importStatement, 'utf8');

await esbuild.build({
entryPoints: [entry],
outdir: buildCacheOutDir.pathname,
bundle: true,
format: 'esm',
platform: 'node',
target: 'node20',
external: Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }),
logLevel: 'warning',
plugins: [
sassPlugin({
type: 'lit-css',
loadPaths: [
new URL('../', import.meta.url).pathname,
new URL('../node_modules/', import.meta.url).pathname,
],
}),
],
});

/*
// Vite build config. It is slower by about a factor of 3.
await build({
root: new URL('..', import.meta.url).pathname,
mode: 'development',
Expand All @@ -66,6 +90,7 @@ async function buildModules(buildCacheOutDir) {
sourcemap: 'inline',
},
});
*/
}

/** Generate a hash from the contents of the given modules and their import chain. */
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"chromatic": "11.2.0",
"custom-elements-manifest": "^2.0.0",
"date-fns": "3.6.0",
"esbuild-sass-plugin": "^3.1.0",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "npm:eslint-plugin-i@latest",
Expand Down Expand Up @@ -123,6 +124,7 @@
},
"resolutions": {
"@types/node": "20.11.30",
"@webcomponents/template-shadowroot": "0.2.1",
"jackspeak": "2.1.1",
"lit": "3.1.2"
},
Expand Down
3 changes: 2 additions & 1 deletion src/components/accordion/accordion.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { assert, expect } from '@open-wc/testing';
import { nothing } from 'lit';
import { html } from 'lit/static-html.js';

import { waitForCondition, waitForLitRender, EventSpy, fixture, isSsr } from '../core/testing';
import { waitForCondition, waitForLitRender, EventSpy, isSsr } from '../core/testing';
import { fixture } from '../core/testing/private';
import { SbbExpansionPanelElement, type SbbExpansionPanelHeaderElement } from '../expansion-panel';

import { SbbAccordionElement } from './accordion';
Expand Down
6 changes: 3 additions & 3 deletions src/components/accordion/accordion.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { expect, fixture } from '@open-wc/testing';
import { expect } from '@open-wc/testing';
import { html } from 'lit/static-html.js';

import { waitForLitRender } from '../core/testing';
import { testA11yTreeSnapshot } from '../core/testing/a11y-tree-snapshot';
import { fixture, testA11yTreeSnapshot } from '../core/testing/private';

import type { SbbAccordionElement } from './accordion';
import './accordion';
import '../expansion-panel';

describe('sbb-accordion', () => {
describe(`sbb-accordion`, () => {
let element: SbbAccordionElement;

beforeEach(async () => {
Expand Down
45 changes: 24 additions & 21 deletions src/components/action-group/action-group.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
import { assert, expect, fixture } from '@open-wc/testing';
import { assert, expect } from '@open-wc/testing';
import { html } from 'lit/static-html.js';

import type { SbbSecondaryButtonElement } from '../button';
import { waitForLitRender } from '../core/testing';
import { fixture } from '../core/testing/private';
import type { SbbBlockLinkElement } from '../link';
import '../button/secondary-button';
import '../link/block-link';

import { SbbActionGroupElement } from './action-group';

describe('sbb-action-group', () => {
import '../button/secondary-button';
import '../link/block-link';

describe(`sbb-action-group with ${fixture.name}`, () => {
let element: SbbActionGroupElement;

beforeEach(async () => {
element = await fixture(html`
<sbb-action-group align-group="start" orientation="horizontal">
<sbb-secondary-button>Button</sbb-secondary-button>
<sbb-block-link
icon-name="chevron-small-left-small"
icon-placement="start"
href="https://github.com/lyne-design-system/lyne-components"
>
Link
</sbb-block-link>
</sbb-action-group>
`);
element = await fixture(
html`
<sbb-action-group align-group="start" orientation="horizontal">
<sbb-secondary-button>Button</sbb-secondary-button>
<sbb-block-link
icon-name="chevron-small-left-small"
icon-placement="start"
href="https://github.com/lyne-design-system/lyne-components"
>
Link
</sbb-block-link>
</sbb-action-group>
`,
{ modules: ['./action-group.ts', '../button/index.ts', '../link/index.ts'] },
);
await waitForLitRender(element);
});

Expand All @@ -35,7 +40,7 @@ describe('sbb-action-group', () => {
describe('property sync', () => {
it('should sync default size with sbb-button', async () => {
const buttons = Array.from(
document.querySelectorAll('sbb-action-group sbb-secondary-button'),
element.querySelectorAll('sbb-secondary-button'),
) as SbbSecondaryButtonElement[];
expect(buttons.length).to.be.greaterThan(0);
expect(buttons.every((l) => l.size === 'l')).to.be.ok;
Expand All @@ -45,7 +50,7 @@ describe('sbb-action-group', () => {
element.setAttribute('button-size', 'm');
await waitForLitRender(element);
const buttons = Array.from(
document.querySelectorAll('sbb-action-group sbb-secondary-button'),
element.querySelectorAll('sbb-secondary-button'),
) as SbbSecondaryButtonElement[];
expect(buttons.length).to.be.greaterThan(0);
expect(buttons.every((l) => l.size === 'm')).to.be.ok;
Expand All @@ -54,9 +59,7 @@ describe('sbb-action-group', () => {
it('should update attributes with link-size="s"', async () => {
element.setAttribute('link-size', 's');
await waitForLitRender(element);
const links = Array.from(
document.querySelectorAll('sbb-action-group sbb-block-link'),
) as SbbBlockLinkElement[];
const links = Array.from(element.querySelectorAll('sbb-block-link')) as SbbBlockLinkElement[];
expect(links.length).to.be.greaterThan(0);
expect(links.every((l) => l.size === 's')).to.be.ok;
});
Expand Down
6 changes: 3 additions & 3 deletions src/components/action-group/action-group.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { expect, fixture } from '@open-wc/testing';
import { expect } from '@open-wc/testing';
import { html } from 'lit/static-html.js';

import type { SbbSecondaryButtonElement } from '../button';
import { waitForLitRender } from '../core/testing';
import { testA11yTreeSnapshot } from '../core/testing/a11y-tree-snapshot';
import { fixture, testA11yTreeSnapshot } from '../core/testing/private';

import type { SbbActionGroupElement } from './action-group';
import './action-group';
import '../button/secondary-button';
import '../link/block-link';

describe('sbb-action-group', () => {
describe(`sbb-action-group`, () => {
describe('renders', () => {
let element: SbbActionGroupElement;

Expand Down
29 changes: 17 additions & 12 deletions src/components/alert/alert-group/alert-group.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { expect, fixture } from '@open-wc/testing';
import { expect } from '@open-wc/testing';
import { html } from 'lit/static-html.js';

import type { SbbTransparentButtonElement } from '../../button';
import { waitForCondition, EventSpy, waitForLitRender } from '../../core/testing';
import { fixture } from '../../core/testing/private';
import type { SbbAlertElement } from '../alert';

import { SbbAlertGroupElement } from './alert-group';

import '../alert';

describe('sbb-alert-group', () => {
describe(`sbb-alert-group with ${fixture.name}`, () => {
let element: SbbAlertGroupElement;

it('should handle events ond states on interacting with alerts', async () => {
Expand All @@ -18,16 +19,19 @@ describe('sbb-alert-group', () => {
const accessibilityTitleLevel = '3';

// Given sbb-alert-group with two alerts
element = await fixture(html`
<sbb-alert-group
id="${alertGroupId}"
accessibility-title="${accessibilityTitle}"
accessibility-title-level="${accessibilityTitleLevel}"
>
<sbb-alert title-content="Interruption" href="www.sbb.ch">First</sbb-alert>
<sbb-alert title-content="Interruption" href="www.sbb.ch">Second</sbb-alert>
</sbb-alert-group>
`);
element = await fixture(
html`
<sbb-alert-group
id="${alertGroupId}"
accessibility-title="${accessibilityTitle}"
accessibility-title-level="${accessibilityTitleLevel}"
>
<sbb-alert title-content="Interruption" href="www.sbb.ch">First</sbb-alert>
<sbb-alert title-content="Interruption" href="www.sbb.ch">Second</sbb-alert>
</sbb-alert-group>
`,
{ modules: ['./alert-group.ts', '../alert/index.ts'] },
);
const didDismissAlertSpy = new EventSpy(SbbAlertGroupElement.events.didDismissAlert);
const emptySpy = new EventSpy(SbbAlertGroupElement.events.empty);

Expand Down Expand Up @@ -97,6 +101,7 @@ describe('sbb-alert-group', () => {
// Given empty sbb-alert-group
element = await fixture(
html`<sbb-alert-group accessibility-title="Disruptions"></sbb-alert-group>`,
{ modules: ['./alert-group.ts'] },
);
const emptySpy = new EventSpy(SbbAlertGroupElement.events.empty);

Expand Down
6 changes: 3 additions & 3 deletions src/components/alert/alert-group/alert-group.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { expect, fixture } from '@open-wc/testing';
import { expect } from '@open-wc/testing';
import { html } from 'lit/static-html.js';

import { waitForLitRender } from '../../core/testing';
import { testA11yTreeSnapshot } from '../../core/testing/a11y-tree-snapshot';
import { fixture, testA11yTreeSnapshot } from '../../core/testing/private';

import type { SbbAlertGroupElement } from './alert-group';
import './alert-group';
import '../alert';

describe('sbb-alert-group', () => {
describe(`sbb-alert-group`, () => {
describe('should render', () => {
let root: SbbAlertGroupElement;

Expand Down
5 changes: 4 additions & 1 deletion src/components/alert/alert-group/alert-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ export class SbbAlertGroupElement extends LitElement {

private _slotChanged(event: Event): void {
const hadAlerts = this._hasAlerts;
this._hasAlerts = (event.target as HTMLSlotElement).assignedElements().length > 0;
this._hasAlerts =
(event.target as HTMLSlotElement)
.assignedElements()
.filter((e) => e instanceof Element && e.localName === 'sbb-alert').length > 0;
if (!this._hasAlerts && hadAlerts) {
this._empty.emit();
}
Expand Down
12 changes: 8 additions & 4 deletions src/components/alert/alert/alert.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { assert, expect, fixture } from '@open-wc/testing';
import { assert, expect } from '@open-wc/testing';
import { html } from 'lit/static-html.js';

import { waitForCondition, EventSpy } from '../../core/testing';
import { fixture } from '../../core/testing/private';

import { SbbAlertElement } from './alert';

describe('sbb-alert', () => {
describe(`sbb-alert with ${fixture.name}`, () => {
let alert: SbbAlertElement;

it('renders', async () => {
alert = await fixture(html`<sbb-alert></sbb-alert>`);
alert = await fixture(html`<sbb-alert></sbb-alert>`, { modules: ['./alert.ts'] });
assert.instanceOf(alert, SbbAlertElement);
});

it('should fire animation events', async () => {
const willOpenSpy = new EventSpy(SbbAlertElement.events.willOpen);
const didOpenSpy = new EventSpy(SbbAlertElement.events.didOpen);

await fixture(html`<sbb-alert title-content="disruption">Interruption</sbb-alert>`);
await fixture(html`<sbb-alert title-content="disruption">Interruption</sbb-alert>`, {
modules: ['./alert.ts'],
});

await waitForCondition(() => willOpenSpy.events.length === 1);
expect(willOpenSpy.count).to.be.equal(1);
Expand All @@ -28,6 +31,7 @@ describe('sbb-alert', () => {
it('should hide close button in readonly mode', async () => {
alert = await fixture(
html`<sbb-alert title-content="Interruption" readonly>Alert content</sbb-alert>`,
{ modules: ['./alert.ts'] },
);

expect(alert.shadowRoot!.querySelector('.sbb-alert__close-button-wrapper')).to.be.null;
Expand Down
6 changes: 3 additions & 3 deletions src/components/alert/alert/alert.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { expect, fixture } from '@open-wc/testing';
import { expect } from '@open-wc/testing';
import { html } from 'lit/static-html.js';

import { waitForLitRender } from '../../core/testing';
import { testA11yTreeSnapshot } from '../../core/testing/a11y-tree-snapshot';
import { fixture, testA11yTreeSnapshot } from '../../core/testing/private';

import type { SbbAlertElement } from './alert';

import './alert';

describe('sbb-alert', () => {
describe(`sbb-alert`, () => {
let element: SbbAlertElement;

it('should render default properties', async () => {
Expand Down
30 changes: 17 additions & 13 deletions src/components/autocomplete/autocomplete.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
import { assert, expect, fixture } from '@open-wc/testing';
import { assert, expect } from '@open-wc/testing';
import { sendKeys, sendMouse } from '@web/test-runner-commands';
import { html } from 'lit/static-html.js';

import { waitForCondition, waitForLitRender, EventSpy } from '../core/testing';
import { fixture } from '../core/testing/private';
import { SbbFormFieldElement } from '../form-field';
import { SbbOptionElement } from '../option';

import { SbbAutocompleteElement } from './autocomplete';

describe('sbb-autocomplete', () => {
describe(`sbb-autocomplete with ${fixture.name}`, () => {
let element: SbbAutocompleteElement, formField: SbbFormFieldElement, input: HTMLInputElement;

beforeEach(async () => {
formField = await fixture(html`
<sbb-form-field>
<input />
<sbb-autocomplete id="myAutocomplete" disable-animation>
<sbb-option id="option-1" value="1">1</sbb-option>
<sbb-option id="option-2" value="2">2</sbb-option>
<sbb-option id="option-3" value="3">3</sbb-option>
</sbb-autocomplete>
</sbb-form-field>
<button>Use this for backdrop click</button>
`);
formField = await fixture(
html`
<sbb-form-field>
<input />
<sbb-autocomplete id="myAutocomplete" disable-animation>
<sbb-option id="option-1" value="1">1</sbb-option>
<sbb-option id="option-2" value="2">2</sbb-option>
<sbb-option id="option-3" value="3">3</sbb-option>
</sbb-autocomplete>
</sbb-form-field>
<button>Use this for backdrop click</button>
`,
{ modules: ['../form-field/index.ts', './autocomplete.ts', '../option/index.ts'] },
);
input = formField.querySelector<HTMLInputElement>('input')!;
element = formField.querySelector<SbbAutocompleteElement>('sbb-autocomplete')!;
});
Expand Down
Loading

0 comments on commit 3abcc68

Please sign in to comment.