From 8f6143c5e4ff3f102f2724147fd42b63f23281cb Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Fri, 11 Feb 2022 17:35:52 -0800 Subject: [PATCH 1/6] Add the fast-style web component --- packages/web-components/fast-ssr/package.json | 6 +- .../web-components/fast-ssr/server/server.ts | 47 ++ .../src/fast-style/index.fixture.html | 436 ++++++++++++++++++ .../fast-ssr/src/fast-style/index.ts | 77 ++++ .../fast-ssr/test/fast-style.spec.ts | 62 +++ .../fast-ssr/webpack.fast-style.config.cjs | 36 ++ 6 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 packages/web-components/fast-ssr/src/fast-style/index.fixture.html create mode 100644 packages/web-components/fast-ssr/src/fast-style/index.ts create mode 100644 packages/web-components/fast-ssr/test/fast-style.spec.ts create mode 100644 packages/web-components/fast-ssr/webpack.fast-style.config.cjs diff --git a/packages/web-components/fast-ssr/package.json b/packages/web-components/fast-ssr/package.json index cdcb62fa1ec..9168f33f0e8 100644 --- a/packages/web-components/fast-ssr/package.json +++ b/packages/web-components/fast-ssr/package.json @@ -16,6 +16,7 @@ }, "scripts": { "build": "tsc -b --clean src && tsc -b src", + "build:fast-style": "webpack --config webpack.fast-style.config.cjs", "build-server": "tsc -b server", "pretest": "npm run build-server", "test": "playwright test -c test", @@ -35,6 +36,9 @@ "@types/express": "^4.17.13", "@types/node": "^17.0.17", "express": "^4.17.1", - "typescript": "^3.8.3" + "ts-loader": "^9.2.6", + "typescript": "^3.8.3", + "webpack": "^5.68.0", + "webpack-cli": "^4.9.2" } } diff --git a/packages/web-components/fast-ssr/server/server.ts b/packages/web-components/fast-ssr/server/server.ts index e47e01fad16..833f9a53ff2 100644 --- a/packages/web-components/fast-ssr/server/server.ts +++ b/packages/web-components/fast-ssr/server/server.ts @@ -1,6 +1,9 @@ import { Readable } from "stream"; import express, { Request, Response } from "express"; +import fs from "fs"; +import path from "path"; +const __dirname = path.resolve(path.dirname("")); const PORT = 8080; function handleRequest(req: Request, res: Response) { res.set("Content-Type", "text/html"); @@ -20,6 +23,50 @@ function handleRequest(req: Request, res: Response) { }); } +function handleStyleRequest(req: Request, res: Response) { + res.set("Content-Type", "text/html"); + fs.readFile( + path.resolve(__dirname, "./src/fast-style/index.fixture.html"), + { encoding: "utf8" }, + (err, data) => { + const stream = (Readable as any).from(data); + stream.on("readable", function (this: any) { + while ((data = this.read())) { + res.write(data); + } + }); + stream.on("close", () => res.end()); + stream.on("error", (e: Error) => { + console.error(e); + process.exit(1); + }); + } + ); +} + +function handleStyleScriptRequest(req: Request, res: Response) { + res.set("Content-Type", "text/plain"); + fs.readFile( + path.resolve(__dirname, "./dist/fast-style.min.js"), + { encoding: "utf8" }, + (err, data) => { + const stream = (Readable as any).from(data); + stream.on("readable", function (this: any) { + while ((data = this.read())) { + res.write(data); + } + }); + stream.on("close", () => res.end()); + stream.on("error", (e: Error) => { + console.error(e); + process.exit(1); + }); + } + ); +} + const app = express(); app.get("/", handleRequest); +app.get("/fast-style", handleStyleRequest); +app.get("/fast-style.min.js", handleStyleScriptRequest); app.listen(PORT); diff --git a/packages/web-components/fast-ssr/src/fast-style/index.fixture.html b/packages/web-components/fast-ssr/src/fast-style/index.fixture.html new file mode 100644 index 00000000000..78b1da25b7b --- /dev/null +++ b/packages/web-components/fast-ssr/src/fast-style/index.fixture.html @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web-components/fast-ssr/src/fast-style/index.ts b/packages/web-components/fast-ssr/src/fast-style/index.ts new file mode 100644 index 00000000000..a987e351dc6 --- /dev/null +++ b/packages/web-components/fast-ssr/src/fast-style/index.ts @@ -0,0 +1,77 @@ +import { DOM, StyleTarget } from "@microsoft/fast-element"; + +interface StyleCache { + [key: string]: CSSStyleSheet | string; +} + +/** + * The FASTStyle web component that takes attributes: + * - css - a string version of the CSS to be applied to the parent web component, this can be omitted if the data-style-id has been used in the DOM as the cached value will be fetched + * - data-style-id - a dataset attribute used as an identifier for the CSS attr string + */ +export default class FASTStyle extends HTMLElement { + static cache: StyleCache = {}; + static hashIdDataSetName: string = "data-style-id"; + + constructor() { + super(); + + const hashId: string = this.getAttribute(FASTStyle.hashIdDataSetName) as string; + const css: string = this.getAttribute("css") as string; + this.registerStyles(hashId, css); + } + + /** + * Register styles if they are not part of the cache and attach them + */ + registerStyles = (hashId: string, css: string): void => { + if (DOM.supportsAdoptedStyleSheets) { + if (!(hashId in FASTStyle.cache)) { + this.memoizeAdoptedStylesheetStyles(hashId, css); + } + this.attachAdoptedStylesheetStyles(this.parentNode as StyleTarget, hashId); + } else { + if (!(hashId in FASTStyle.cache)) { + this.memoizeStyleElementStyles(hashId, css); + } + this.attachStyleElementStyles(this.parentNode as StyleTarget, hashId); + } + }; + + /** + * Memoize CSSStyleSheets + */ + memoizeAdoptedStylesheetStyles(hashId: string, css: string) { + const sheet = new CSSStyleSheet(); + (sheet as any).replaceSync(css); + FASTStyle.cache[hashId] = sheet; + } + + /** + * Attach CSSStyleSheets + */ + attachAdoptedStylesheetStyles(shadowRoot: StyleTarget, hashId: string) { + shadowRoot.adoptedStyleSheets = [ + ...shadowRoot.adoptedStyleSheets!, + FASTStyle.cache[hashId] as CSSStyleSheet, + ]; + } + + /** + * Memoize css strings + */ + memoizeStyleElementStyles(hashId: string, css: string) { + FASTStyle.cache[hashId] = css; + } + + /** + * Attach style elements + */ + attachStyleElementStyles(shadowRoot: StyleTarget, hashId: string) { + const element = document.createElement("style"); + element.innerHTML = FASTStyle.cache[hashId] as string; + shadowRoot.append(element); + } +} + +customElements.define("fast-style", FASTStyle); diff --git a/packages/web-components/fast-ssr/test/fast-style.spec.ts b/packages/web-components/fast-ssr/test/fast-style.spec.ts new file mode 100644 index 00000000000..08f74b18e39 --- /dev/null +++ b/packages/web-components/fast-ssr/test/fast-style.spec.ts @@ -0,0 +1,62 @@ +import { expect, test } from "@playwright/test" + +test("Check that the first element has styles assigned", async ({ page }) => { + await page.goto("/fast-style"); + + const cards = page.locator("fast-card"); + const styles = await cards.evaluateAll((cardList) => { + return cardList.map((card) => { + return window.getComputedStyle(card, null).getPropertyValue("background-color"); + }); + }); + + expect(styles[0]).toEqual("rgb(26, 26, 26)"); +}); +test("Check that the nested element in the first element has styles assigned", async ({ page }) => { + await page.goto("/fast-style"); + + const cards = page.locator("fast-card"); + const styles = await cards.evaluateAll((cardList) => { + return cardList.map((card) => { + return window.getComputedStyle( + (card.shadowRoot?.querySelector("fast-button") as Element), null + ).getPropertyValue("background-color"); + }); + }); + + expect(styles[0]).toEqual("rgb(43, 43, 43)"); +}); +test("Check that all elements have styles assigned", async ({ page }) => { + await page.goto("/fast-style"); + + const cards = page.locator("fast-card"); + const styles = await cards.evaluateAll((cardList) => { + return cardList.map((card) => { + return window.getComputedStyle(card, null).getPropertyValue("background-color"); + }); + }); + + expect(styles).toHaveLength(10); + + styles.forEach((style) => { + expect(style).toEqual("rgb(26, 26, 26)"); + }); +}); +test("Check that all nested elements have styles assigned", async ({ page}) => { + await page.goto("/fast-style"); + + const cards = page.locator("fast-card"); + const styles = await cards.evaluateAll((cardList) => { + return cardList.map((card) => { + return window.getComputedStyle( + (card.shadowRoot?.querySelector("fast-button") as Element), null + ).getPropertyValue("background-color"); + }); + }); + + expect(styles).toHaveLength(10); + + styles.forEach((style) => { + expect(style).toEqual("rgb(43, 43, 43)"); + }); +}); diff --git a/packages/web-components/fast-ssr/webpack.fast-style.config.cjs b/packages/web-components/fast-ssr/webpack.fast-style.config.cjs new file mode 100644 index 00000000000..91fa9f3dd3a --- /dev/null +++ b/packages/web-components/fast-ssr/webpack.fast-style.config.cjs @@ -0,0 +1,36 @@ +const path = require("path"); +const srcDir = path.resolve(__dirname, "./src"); +const outDir = path.resolve(__dirname, "./dist"); + +/** + * This file exports the minified fast-style web component + */ +module.exports = { + entry: { + "fast-style": path.resolve(srcDir, "./fast-style/index.ts"), + }, + output: { + path: outDir, + filename: "[name].min.js", + }, + mode: "production", + module: { + rules: [ + { + test: /.ts$/, + use: [ + { + loader: "ts-loader", + options: { + onlyCompileBundledFiles: true, + configFile: "tsconfig.json", + }, + }, + ], + }, + ], + }, + resolve: { + extensions: [".js", ".ts", ".json"], + }, +}; From 99c7c6bb2e5f5ac4297857fe8e76b8df72f3487c Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Mon, 14 Feb 2022 15:37:18 -0800 Subject: [PATCH 2/6] privatize methods and static vars --- .../fast-ssr/src/fast-style/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/web-components/fast-ssr/src/fast-style/index.ts b/packages/web-components/fast-ssr/src/fast-style/index.ts index a987e351dc6..215c5958c7e 100644 --- a/packages/web-components/fast-ssr/src/fast-style/index.ts +++ b/packages/web-components/fast-ssr/src/fast-style/index.ts @@ -10,8 +10,8 @@ interface StyleCache { * - data-style-id - a dataset attribute used as an identifier for the CSS attr string */ export default class FASTStyle extends HTMLElement { - static cache: StyleCache = {}; - static hashIdDataSetName: string = "data-style-id"; + private static cache: StyleCache = {}; + private static hashIdDataSetName: string = "data-style-id"; constructor() { super(); @@ -24,7 +24,7 @@ export default class FASTStyle extends HTMLElement { /** * Register styles if they are not part of the cache and attach them */ - registerStyles = (hashId: string, css: string): void => { + private registerStyles = (hashId: string, css: string): void => { if (DOM.supportsAdoptedStyleSheets) { if (!(hashId in FASTStyle.cache)) { this.memoizeAdoptedStylesheetStyles(hashId, css); @@ -41,7 +41,7 @@ export default class FASTStyle extends HTMLElement { /** * Memoize CSSStyleSheets */ - memoizeAdoptedStylesheetStyles(hashId: string, css: string) { + private memoizeAdoptedStylesheetStyles(hashId: string, css: string) { const sheet = new CSSStyleSheet(); (sheet as any).replaceSync(css); FASTStyle.cache[hashId] = sheet; @@ -50,7 +50,7 @@ export default class FASTStyle extends HTMLElement { /** * Attach CSSStyleSheets */ - attachAdoptedStylesheetStyles(shadowRoot: StyleTarget, hashId: string) { + private attachAdoptedStylesheetStyles(shadowRoot: StyleTarget, hashId: string) { shadowRoot.adoptedStyleSheets = [ ...shadowRoot.adoptedStyleSheets!, FASTStyle.cache[hashId] as CSSStyleSheet, @@ -60,14 +60,14 @@ export default class FASTStyle extends HTMLElement { /** * Memoize css strings */ - memoizeStyleElementStyles(hashId: string, css: string) { + private memoizeStyleElementStyles(hashId: string, css: string) { FASTStyle.cache[hashId] = css; } /** * Attach style elements */ - attachStyleElementStyles(shadowRoot: StyleTarget, hashId: string) { + private attachStyleElementStyles(shadowRoot: StyleTarget, hashId: string) { const element = document.createElement("style"); element.innerHTML = FASTStyle.cache[hashId] as string; shadowRoot.append(element); From 3e7fc9e0c7bc7c756ade3a2af1965b9d0b27e181 Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Mon, 14 Feb 2022 15:45:22 -0800 Subject: [PATCH 3/6] move attr inspection to connectedCallback --- packages/web-components/fast-ssr/src/fast-style/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/web-components/fast-ssr/src/fast-style/index.ts b/packages/web-components/fast-ssr/src/fast-style/index.ts index 215c5958c7e..93988026103 100644 --- a/packages/web-components/fast-ssr/src/fast-style/index.ts +++ b/packages/web-components/fast-ssr/src/fast-style/index.ts @@ -13,9 +13,10 @@ export default class FASTStyle extends HTMLElement { private static cache: StyleCache = {}; private static hashIdDataSetName: string = "data-style-id"; - constructor() { - super(); - + /** + * @internal + */ + public connectedCallback(): void { const hashId: string = this.getAttribute(FASTStyle.hashIdDataSetName) as string; const css: string = this.getAttribute("css") as string; this.registerStyles(hashId, css); From 0e0d7f156437930f995b032d66d9b96a2a9b2b9b Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Mon, 14 Feb 2022 16:55:01 -0800 Subject: [PATCH 4/6] remove DOM dependency --- .../fast-ssr/src/fast-style/index.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/web-components/fast-ssr/src/fast-style/index.ts b/packages/web-components/fast-ssr/src/fast-style/index.ts index 93988026103..cb480301651 100644 --- a/packages/web-components/fast-ssr/src/fast-style/index.ts +++ b/packages/web-components/fast-ssr/src/fast-style/index.ts @@ -1,5 +1,3 @@ -import { DOM, StyleTarget } from "@microsoft/fast-element"; - interface StyleCache { [key: string]: CSSStyleSheet | string; } @@ -12,6 +10,9 @@ interface StyleCache { export default class FASTStyle extends HTMLElement { private static cache: StyleCache = {}; private static hashIdDataSetName: string = "data-style-id"; + private static supportsAdoptedStyleSheets: boolean = + Array.isArray((document as any).adoptedStyleSheets) && + "replace" in CSSStyleSheet.prototype; /** * @internal @@ -26,16 +27,16 @@ export default class FASTStyle extends HTMLElement { * Register styles if they are not part of the cache and attach them */ private registerStyles = (hashId: string, css: string): void => { - if (DOM.supportsAdoptedStyleSheets) { + if (FASTStyle.supportsAdoptedStyleSheets) { if (!(hashId in FASTStyle.cache)) { this.memoizeAdoptedStylesheetStyles(hashId, css); } - this.attachAdoptedStylesheetStyles(this.parentNode as StyleTarget, hashId); + this.attachAdoptedStylesheetStyles(this.parentNode as ShadowRoot, hashId); } else { if (!(hashId in FASTStyle.cache)) { this.memoizeStyleElementStyles(hashId, css); } - this.attachStyleElementStyles(this.parentNode as StyleTarget, hashId); + this.attachStyleElementStyles(this.parentNode as ShadowRoot, hashId); } }; @@ -51,9 +52,9 @@ export default class FASTStyle extends HTMLElement { /** * Attach CSSStyleSheets */ - private attachAdoptedStylesheetStyles(shadowRoot: StyleTarget, hashId: string) { - shadowRoot.adoptedStyleSheets = [ - ...shadowRoot.adoptedStyleSheets!, + private attachAdoptedStylesheetStyles(shadowRoot: ShadowRoot, hashId: string) { + (shadowRoot as any).adoptedStyleSheets = [ + ...(shadowRoot as any).adoptedStyleSheets!, FASTStyle.cache[hashId] as CSSStyleSheet, ]; } @@ -68,7 +69,7 @@ export default class FASTStyle extends HTMLElement { /** * Attach style elements */ - private attachStyleElementStyles(shadowRoot: StyleTarget, hashId: string) { + private attachStyleElementStyles(shadowRoot: ShadowRoot, hashId: string) { const element = document.createElement("style"); element.innerHTML = FASTStyle.cache[hashId] as string; shadowRoot.append(element); From 2155d9fc213cced1bbdaa017763004680b38f274 Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Mon, 14 Feb 2022 17:06:48 -0800 Subject: [PATCH 5/6] remove webpack, use compiled ts as module with mimetype for es6 --- packages/web-components/fast-ssr/package.json | 6 +----- packages/web-components/fast-ssr/server/server.ts | 6 +++--- .../fast-ssr/src/fast-style/index.fixture.html | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/web-components/fast-ssr/package.json b/packages/web-components/fast-ssr/package.json index 9168f33f0e8..cdcb62fa1ec 100644 --- a/packages/web-components/fast-ssr/package.json +++ b/packages/web-components/fast-ssr/package.json @@ -16,7 +16,6 @@ }, "scripts": { "build": "tsc -b --clean src && tsc -b src", - "build:fast-style": "webpack --config webpack.fast-style.config.cjs", "build-server": "tsc -b server", "pretest": "npm run build-server", "test": "playwright test -c test", @@ -36,9 +35,6 @@ "@types/express": "^4.17.13", "@types/node": "^17.0.17", "express": "^4.17.1", - "ts-loader": "^9.2.6", - "typescript": "^3.8.3", - "webpack": "^5.68.0", - "webpack-cli": "^4.9.2" + "typescript": "^3.8.3" } } diff --git a/packages/web-components/fast-ssr/server/server.ts b/packages/web-components/fast-ssr/server/server.ts index 833f9a53ff2..866fa10b5e8 100644 --- a/packages/web-components/fast-ssr/server/server.ts +++ b/packages/web-components/fast-ssr/server/server.ts @@ -45,9 +45,9 @@ function handleStyleRequest(req: Request, res: Response) { } function handleStyleScriptRequest(req: Request, res: Response) { - res.set("Content-Type", "text/plain"); + res.set("Content-Type", "application/javascript"); fs.readFile( - path.resolve(__dirname, "./dist/fast-style.min.js"), + path.resolve(__dirname, "./dist/fast-style/index.js"), { encoding: "utf8" }, (err, data) => { const stream = (Readable as any).from(data); @@ -68,5 +68,5 @@ function handleStyleScriptRequest(req: Request, res: Response) { const app = express(); app.get("/", handleRequest); app.get("/fast-style", handleStyleRequest); -app.get("/fast-style.min.js", handleStyleScriptRequest); +app.get("/fast-style.js", handleStyleScriptRequest); app.listen(PORT); diff --git a/packages/web-components/fast-ssr/src/fast-style/index.fixture.html b/packages/web-components/fast-ssr/src/fast-style/index.fixture.html index 78b1da25b7b..92820fa5ab9 100644 --- a/packages/web-components/fast-ssr/src/fast-style/index.fixture.html +++ b/packages/web-components/fast-ssr/src/fast-style/index.fixture.html @@ -121,7 +121,7 @@ display: flex; " > - +