Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
557e291
feat(web-components): update dependencies and add SSR build scripts
radium-v May 22, 2026
230bf46
update fast-test-harness dependency to version 0.3.0
radium-v May 24, 2026
39ac840
add SSR entry modules and HTML template for testing
radium-v May 22, 2026
eb9d85f
add DSD modules for accordion and accordion-item
radium-v May 22, 2026
f482514
add DSD modules for anchor-button
radium-v May 22, 2026
a998a3c
add DSD modules for avatar
radium-v May 24, 2026
3272a3e
add DSD modules for badge
radium-v May 24, 2026
e168e22
add DSD modules for button
radium-v May 24, 2026
d29a066
add DSD modules for checkbox
radium-v May 24, 2026
c1c5f62
add DSD modules for compound-button
radium-v May 24, 2026
199bd60
add DSD modules for counter-badge
radium-v May 24, 2026
4f029f9
add DSD modules for dialog and dialog-body
radium-v May 24, 2026
746c0fc
add DSD modules for divider
radium-v May 24, 2026
0e594c9
add DSD modules for drawer and drawer-body
radium-v May 24, 2026
92aa7d8
add DSD modules for dropdown, listbox, and option
radium-v May 24, 2026
a9f66f8
add DSD modules for field
radium-v May 24, 2026
bc6ea6e
add DSD modules for image
radium-v May 25, 2026
2956f84
add DSD modules for label
radium-v May 25, 2026
74a14d8
add DSD modules for link
radium-v May 25, 2026
d51875e
add DSD modules for menu
radium-v May 25, 2026
317ebdd
add DSD modules for menu-button
radium-v May 25, 2026
0a4d80a
add DSD modules for menu-item
radium-v May 25, 2026
91729b4
add DSD modules for menu-list
radium-v May 25, 2026
cb73195
add DSD modules for message-bar
radium-v May 25, 2026
84f14e4
add DSD modules for progress-bar
radium-v May 25, 2026
ee00a68
add DSD modules for radio
radium-v May 25, 2026
2417a00
add DSD modules for radio-group
radium-v May 25, 2026
62faef2
add DSD modules for rating-display
radium-v May 25, 2026
34f1968
add DSD modules for slider
radium-v May 25, 2026
8fdbf83
add DSD modules for spinner
radium-v May 25, 2026
f2b6fee
add DSD modules for switch
radium-v May 25, 2026
77fa800
add DSD modules for tab
radium-v May 25, 2026
f7c474e
add DSD modules for tablist
radium-v May 25, 2026
0c87661
add DSD modules for text
radium-v May 25, 2026
57c4a16
add DSD modules for text-input
radium-v May 25, 2026
7e5457d
add DSD modules for textarea
radium-v May 25, 2026
0771f9c
skip setTheme tests in SSR
radium-v May 25, 2026
7f862e9
add DSD modules for toggle-button
radium-v May 25, 2026
1c85fe3
add DSD modules for tooltip
radium-v May 25, 2026
adf3a10
update tooltip anchor positioning polyfill handling
radium-v May 26, 2026
898d6ce
add DSD modules for tree and tree-item
radium-v May 25, 2026
7a8fb3a
add changefile
radium-v May 26, 2026
d9b43de
address PR feedback
radium-v May 26, 2026
c80688c
add SSR support to Playwright configuration and streamline e2e test e…
radium-v May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Comment thread
radium-v marked this conversation as resolved.
"type": "prerelease",
"comment": "feat(web-components): add SSR support via Declarative Shadow DOM modules",
"packageName": "@fluentui/web-components",
"email": "863023+radium-v@users.noreply.github.com",
"dependentChangeType": "patch"
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@
"@microsoft/eslint-plugin-sdl": "1.0.1",
"@microsoft/fast-build": "0.6.0",
"@microsoft/fast-element": "2.10.4",
"@microsoft/fast-test-harness": "0.1.0",
"@microsoft/focusgroup-polyfill": "^1.4.1",
"@microsoft/fast-html": "1.0.0-alpha.53",
"@microsoft/fast-test-harness": "0.3.0",
"@microsoft/focusgroup-polyfill": "1.5.0",
"@microsoft/load-themed-styles": "1.10.26",
"@microsoft/loader-load-themed-styles": "2.0.17",
"@microsoft/tsdoc": "0.15.1",
Expand Down
16 changes: 15 additions & 1 deletion packages/web-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,21 @@
"./theme/*.js": "./dist/esm/theme/*.js",
"./*/base.js": "./dist/esm/*/*.base.js",
"./*/define.js": "./dist/esm/*/define.js",
"./*/define-async.js": "./dist/esm/*/define-async.js",
"./*/definition.js": "./dist/esm/*/*.definition.js",
"./*/options.js": "./dist/esm/*/*.options.js",
"./*/styles.js": "./dist/esm/*/*.styles.js",
"./*/styles.css": "./dist/esm/*/*.styles.css",
"./*/template.js": "./dist/esm/*/*.template.js",
"./*/template.html": "./dist/esm/*/*.template.html",
"./*/index.js": "./dist/esm/*/index.js",
"./*.js": "./dist/esm/*/define.js",
"./custom-elements.json": "./custom-elements.json",
"./package.json": "./package.json"
},
"sideEffects": [
"define.*",
"define-async.*",
"define-all.*",
"index-rollup.*",
"index-all-rollup.*",
Expand All @@ -75,7 +79,11 @@
"compile:benchmark": "rollup -c rollup.bench.js",
"clean": "node ./scripts/clean dist",
"generate-api": "api-extractor run --local",
"build": "yarn compile && yarn rollup -c && yarn generate-api && yarn analyze",
"build": "yarn compile && yarn build:rollup && yarn build:ssr && yarn generate-api && yarn analyze",
"build:ssr:templates": "fast-test-harness generate-templates --tag-prefix=fluent",
"build:ssr:styles": "fast-test-harness generate-stylesheets",
"build:ssr": "yarn build:ssr:templates && yarn build:ssr:styles",
"build:rollup": "rollup -c",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"format": "prettier -w src/**/*.{ts,html} src/*.{ts,html} --ignore-path ../../.prettierignore",
Expand Down Expand Up @@ -106,8 +114,14 @@
},
"peerDependencies": {
"@microsoft/fast-element": "^2.0.0",
"@microsoft/fast-html": "^1.0.0-alpha.53",
"@microsoft/focusgroup-polyfill": "^1.4.1"
},
"peerDependenciesMeta": {
"@microsoft/fast-html": {
"optional": true
}
},
"beachball": {
"disallowedChangeTypes": [
"major",
Expand Down
34 changes: 33 additions & 1 deletion packages/web-components/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,42 @@
import defaultConfig from '@microsoft/fast-test-harness/playwright.config.mjs';
import { defineConfig } from '@playwright/test';
import { defineConfig, devices } from '@playwright/test';

const CI = process.env.CI === 'true';

// Duplicate each browser project across CSR and SSR rendering modes so a
// single `playwright test` run exercises both. Per-test overrides
// (e.g. `test.use({ ssr: true })`) still take precedence over the
// project-level value.
//
// TODO: Remove this local axis once the equivalent change lands in
// @microsoft/fast-test-harness/playwright.config.mjs.
const browsers = [
{ name: 'chromium', use: devices['Desktop Chrome'] },
{ name: 'firefox', use: devices['Desktop Firefox'] },
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
deviceScaleFactor: 1,
},
},
];

const modes = [
{ suffix: 'csr', ssr: false },
{ suffix: 'ssr', ssr: true },
];

const projects = browsers.flatMap(browser =>
modes.map(mode => ({
name: `${browser.name}-${mode.suffix}`,
use: { ...browser.use, ssr: mode.ssr },
})),
);

export default defineConfig({
...defaultConfig,
projects,
reporter: CI ? 'github' : 'list',
testMatch: 'src/**/*.spec.ts',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { PartialFASTElementDefinition } from '@microsoft/fast-element';
import { tagName } from './accordion-item.options.js';

/**
* The async definition configuration for the fluent-accordion-item element.
*
* @public
* @remarks
* This is used in server-side rendering (SSR) scenarios where the template
* is provided as a deferred option to be hydrated later.
*/
export const definition: PartialFASTElementDefinition = {
name: tagName,
templateOptions: 'defer-and-hydrate',
};
5 changes: 5 additions & 0 deletions packages/web-components/src/accordion-item/define-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { RenderableFASTElement } from '@microsoft/fast-html';
import { definition } from './accordion-item.definition-async.js';
import { AccordionItem } from './accordion-item.js';

RenderableFASTElement(AccordionItem).defineAsync(definition);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type PartialFASTElementDefinition } from '@microsoft/fast-element';
import { tagName } from './accordion.options.js';

/**
* The async definition configuration for the fluent-accordion element.
*
* @public
* @remarks
* This is used in server-side rendering (SSR) scenarios where the template
* is provided as a deferred option to be hydrated later.
*/
export const declarativeDefinition: PartialFASTElementDefinition = {
Comment thread
radium-v marked this conversation as resolved.
name: tagName,
templateOptions: 'defer-and-hydrate',
} as const;
2 changes: 1 addition & 1 deletion packages/web-components/src/accordion/accordion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class Accordion extends FASTElement {
*/
private setItems = (): void => {
waitForConnectedDescendants(this, () => {
if (this.slottedAccordionItems.length === 0) {
if (!this.slottedAccordionItems?.length) {
return;
}

Expand Down
5 changes: 5 additions & 0 deletions packages/web-components/src/accordion/define-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { RenderableFASTElement } from '@microsoft/fast-html';
import { declarativeDefinition } from './accordion.definition-async.js';
import { Accordion } from './accordion.js';

RenderableFASTElement(Accordion).defineAsync(declarativeDefinition);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { PartialFASTElementDefinition } from '@microsoft/fast-element';
import { tagName } from './anchor-button.options.js';

/**
* The async definition configuration for the fluent-anchor-button element.
*
* @public
* @remarks
* This is used in server-side rendering (SSR) scenarios where the template
* is provided as a deferred option to be hydrated later.
*/
export const definition: PartialFASTElementDefinition = {
name: tagName,
templateOptions: 'defer-and-hydrate',
};
24 changes: 18 additions & 6 deletions packages/web-components/src/anchor-button/anchor-button.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,11 @@ test.describe('Anchor Button', () => {

await fastPage.setTemplate({ attributes: { href: expectedUrl } });

expect(page.url()).not.toMatch(expectedUrl);

await element.click();

await expect(page).toHaveURL(expectedUrl);
expect(page.url()).toMatch(expectedUrl);
});

test('should emit a single click event when clicked', async ({ fastPage, page }) => {
Expand All @@ -197,8 +199,9 @@ test.describe('Anchor Button', () => {
});

test('should navigate to the provided url when clicked while pressing the `Control` key on Windows or `Meta` on Mac', async ({
fastPage,
context,
fastPage,
page,
}) => {
const { element } = fastPage;

Expand All @@ -212,7 +215,11 @@ test.describe('Anchor Button', () => {

const newPage = await newPagePromise;

await expect(newPage).toHaveURL(expectedUrl);
expect(page.url()).not.toMatch(expectedUrl);

expect(newPage.url()).toMatch(expectedUrl);

await newPage.close();
});

test('should navigate to the provided url when `Enter` is pressed via keyboard', async ({ fastPage, page }) => {
Expand All @@ -226,12 +233,13 @@ test.describe('Anchor Button', () => {

await element.press('Enter');

await expect(page).toHaveURL(expectedUrl);
expect(page.url()).toMatch(expectedUrl);
});

test('should navigate to the provided url when `ctrl` and `Enter` are pressed via keyboard', async ({
fastPage,
context,
fastPage,
page,
}) => {
const { element } = fastPage;

Expand All @@ -249,6 +257,10 @@ test.describe('Anchor Button', () => {

const newPage = await newPagePromise;

await expect(newPage).toHaveURL(expectedUrl);
expect(page.url()).not.toMatch(expectedUrl);

expect(newPage.url()).toMatch(expectedUrl);

await newPage.close();
});
});
5 changes: 5 additions & 0 deletions packages/web-components/src/anchor-button/define-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { RenderableFASTElement } from '@microsoft/fast-html';
import { definition } from './anchor-button.definition-async.js';
import { AnchorButton } from './anchor-button.js';

RenderableFASTElement(AnchorButton).defineAsync(definition);
15 changes: 15 additions & 0 deletions packages/web-components/src/avatar/avatar.definition-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { PartialFASTElementDefinition } from '@microsoft/fast-element';
import { tagName } from './avatar.options.js';

/**
* The async definition configuration for the fluent-avatar element.
*
* @public
* @remarks
* This is used in server-side rendering (SSR) scenarios where the template
* is provided as a deferred option to be hydrated later.
*/
export const definition: PartialFASTElementDefinition = {
name: tagName,
templateOptions: 'defer-and-hydrate',
};
1 change: 1 addition & 0 deletions packages/web-components/src/avatar/avatar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ test.describe('Avatar', () => {

// eslint-disable-next-line playwright/no-conditional-in-test
if (color !== AvatarColor.colorful) {
// eslint-disable-next-line playwright/no-conditional-expect
await expect.soft(element).toHaveAttribute('data-color', color);
}
});
Expand Down
5 changes: 5 additions & 0 deletions packages/web-components/src/avatar/define-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { RenderableFASTElement } from '@microsoft/fast-html';
import { definition } from './avatar.definition-async.js';
import { Avatar } from './avatar.js';

RenderableFASTElement(Avatar).defineAsync(definition);
15 changes: 15 additions & 0 deletions packages/web-components/src/badge/badge.definition-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { PartialFASTElementDefinition } from '@microsoft/fast-element';
import { tagName } from './badge.options.js';

/**
* The async definition configuration for the fluent-badge element.
*
* @public
* @remarks
* This is used in server-side rendering (SSR) scenarios where the template
* is provided as a deferred option to be hydrated later.
*/
export const definition: PartialFASTElementDefinition = {
name: tagName,
templateOptions: 'defer-and-hydrate',
};
5 changes: 5 additions & 0 deletions packages/web-components/src/badge/define-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { RenderableFASTElement } from '@microsoft/fast-html';
import { definition } from './badge.definition-async.js';
import { Badge } from './badge.js';

RenderableFASTElement(Badge).defineAsync(definition);
15 changes: 15 additions & 0 deletions packages/web-components/src/button/button.definition-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { PartialFASTElementDefinition } from '@microsoft/fast-element';
import { tagName } from './button.options.js';

/**
* The async definition configuration for the fluent-button element.
*
* @public
* @remarks
* This is used in server-side rendering (SSR) scenarios where the template
* is provided as a deferred option to be hydrated later.
*/
export const definition: PartialFASTElementDefinition = {
name: tagName,
templateOptions: 'defer-and-hydrate',
};
12 changes: 10 additions & 2 deletions packages/web-components/src/button/button.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { InitialTemplateAttributes } from '@microsoft/fast-test-harness/fixtures/csr-fixture.js';
import { expect, test } from '../../test/playwright/index.js';
import { tagName } from './button.options.js';

Expand Down Expand Up @@ -262,10 +263,17 @@ test.describe('Button', () => {
await expect(notFocusable).not.toBeFocused();
});

test('should focus the element when the `autofocus` attribute is present', async ({ fastPage }) => {
test('should focus the element when the `autofocus` attribute is present', async ({ fastPage, ssr }) => {
const { element } = fastPage;

await fastPage.setTemplate({ attributes: { autofocus: true } });
const attributes: InitialTemplateAttributes = { autofocus: true };

if (ssr) {
// the host element needs to be focusable for autofocus to work on the server, so we need to set tabindex="0"
attributes.tabindex = '0';
}

await fastPage.setTemplate({ attributes });

await expect(element).toBeFocused();
});
Expand Down
5 changes: 5 additions & 0 deletions packages/web-components/src/button/define-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { RenderableFASTElement } from '@microsoft/fast-html';
import { definition } from './button.definition-async.js';
import { Button } from './button.js';

RenderableFASTElement(Button).defineAsync(definition);
Loading
Loading