Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement experimental support for server side rendering (SSR) #2466

Merged
merged 15 commits into from
Mar 21, 2024
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
Loading