From 454b22179bb076ecb496dfc57a694c375e15cb7b Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:17:48 -0700 Subject: [PATCH 1/2] Add webui-todo-app example with declarative FAST HTML and webui prerendering Adds a new example in examples/webui-todo-app that: - Converts the todo-app to use declarative FAST HTML syntax (f-template, f-repeat, {{}}/{} bindings) via @microsoft/fast-html - Prerenders the DOM content using @microsoft/webui (build + render) - The Express server calls build() at startup to compile the HTML template into a binary protocol, then render() on each request to inject prerendered todo items and component templates as raw HTML - Client-side hydration uses RenderableFASTElement and TemplateElement from @microsoft/fast-html, seeded with window.__INITIAL_STATE__ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- examples/webui-todo-app/package.json | 31 ++++ examples/webui-todo-app/server.js | 127 +++++++++++++++ examples/webui-todo-app/src/index.html | 68 ++++++++ examples/webui-todo-app/src/main.ts | 24 +++ examples/webui-todo-app/src/todo-app.ts | 6 + examples/webui-todo-app/src/todo-form.ts | 12 ++ examples/webui-todo-app/src/todo-list.ts | 69 ++++++++ examples/webui-todo-app/todo-data.json | 5 + examples/webui-todo-app/tsconfig.json | 19 +++ examples/webui-todo-app/vite.config.ts | 19 +++ package-lock.json | 191 +++++++++++++++-------- package.json | 3 +- 12 files changed, 504 insertions(+), 70 deletions(-) create mode 100644 examples/webui-todo-app/package.json create mode 100644 examples/webui-todo-app/server.js create mode 100644 examples/webui-todo-app/src/index.html create mode 100644 examples/webui-todo-app/src/main.ts create mode 100644 examples/webui-todo-app/src/todo-app.ts create mode 100644 examples/webui-todo-app/src/todo-form.ts create mode 100644 examples/webui-todo-app/src/todo-list.ts create mode 100644 examples/webui-todo-app/todo-data.json create mode 100644 examples/webui-todo-app/tsconfig.json create mode 100644 examples/webui-todo-app/vite.config.ts diff --git a/examples/webui-todo-app/package.json b/examples/webui-todo-app/package.json new file mode 100644 index 00000000000..2fd42c5e69e --- /dev/null +++ b/examples/webui-todo-app/package.json @@ -0,0 +1,31 @@ +{ + "name": "@microsoft/fast-webui-todo-app-example", + "version": "1.0.0", + "description": "", + "main": "dist/exports.js", + "type": "module", + "private": true, + "scripts": { + "build": "vite build", + "start": "npm run build && node server.js", + "test": "npm run build" + }, + "author": { + "name": "Microsoft", + "url": "https://discord.gg/FcSNfg4" + }, + "homepage": "https://www.fast.design/", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/Microsoft/fast.git", + "directory": "examples/webui-todo-app" + }, + "dependencies": { + "@microsoft/fast-element": "^2.10.2", + "@microsoft/fast-html": "*", + "@microsoft/webui": "^0.0.2", + "express": "4.22.1", + "tslib": "^2.6.3" + } +} diff --git a/examples/webui-todo-app/server.js b/examples/webui-todo-app/server.js new file mode 100644 index 00000000000..87755ed7f31 --- /dev/null +++ b/examples/webui-todo-app/server.js @@ -0,0 +1,127 @@ +import fs from "node:fs"; +import { build, render } from "@microsoft/webui"; +import express from "express"; + +const app = express(); +const port = 8081; + +// Build templates into a binary protocol at startup (build-time compilation) +const result = build({ appDir: "./src", plugin: "fast" }); + +// The f-template elements define the declarative FAST component templates for +// client-side hydration. They use single-brace {} bindings so the SSR step +// does not attempt to fill them in — FAST-html resolves them in the browser. +const COMPONENT_TEMPLATES = ` + + + + + + +`; + +function escapeHtml(str) { + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +function renderTodoItems(todos) { + if (todos.length === 0) return ""; + return todos + .map( + todo => ` +
  • + + ${escapeHtml(todo.description)} + +
  • `, + ) + .join(""); +} + +app.use(express.static("./www")); + +app.get("/", (req, res) => { + const todoData = JSON.parse(fs.readFileSync("./todo-data.json").toString()); + + // Render the page template using webui, injecting prerendered todo items and + // the declarative component template definitions as raw HTML + const html = render(result.protocol, { + prerenderedItems: renderTodoItems(todoData), + componentTemplates: COMPONENT_TEMPLATES, + }); + + // Inject initial state so the client-side FAST components can hydrate with + // the same data that was used for prerendering + const withState = html.replace( + "", + ``, + ); + + res.send(withState); +}); + +app.listen(port, () => { + console.log(`WebUI Todo app listening on port ${port}`); +}); diff --git a/examples/webui-todo-app/src/index.html b/examples/webui-todo-app/src/index.html new file mode 100644 index 00000000000..849c9bfdace --- /dev/null +++ b/examples/webui-todo-app/src/index.html @@ -0,0 +1,68 @@ + + + + + FAST Todos + + + + + + {{{componentTemplates}}} + + + diff --git a/examples/webui-todo-app/src/main.ts b/examples/webui-todo-app/src/main.ts new file mode 100644 index 00000000000..b6a18a3097c --- /dev/null +++ b/examples/webui-todo-app/src/main.ts @@ -0,0 +1,24 @@ +import { RenderableFASTElement, TemplateElement } from "@microsoft/fast-html"; +import { TodoApp } from "./todo-app.js"; +import { TodoForm } from "./todo-form.js"; +import { DefaultTodoList, TodoList } from "./todo-list.js"; + +const SSRState = (window as any).__INITIAL_STATE__ || []; +TodoList.provide(document, new DefaultTodoList(SSRState)); + +RenderableFASTElement(TodoApp).defineAsync({ + name: "todo-app", + templateOptions: "defer-and-hydrate", +}); + +RenderableFASTElement(TodoForm).defineAsync({ + name: "todo-form", + templateOptions: "defer-and-hydrate", +}); + +TemplateElement.options({ + "todo-app": { observerMap: "all" }, + "todo-form": { observerMap: "all" }, +}).define({ + name: "f-template", +}); diff --git a/examples/webui-todo-app/src/todo-app.ts b/examples/webui-todo-app/src/todo-app.ts new file mode 100644 index 00000000000..621a7a1cc10 --- /dev/null +++ b/examples/webui-todo-app/src/todo-app.ts @@ -0,0 +1,6 @@ +import { FASTElement } from "@microsoft/fast-element"; +import { TodoList } from "./todo-list.js"; + +export class TodoApp extends FASTElement { + @TodoList todos!: TodoList; +} diff --git a/examples/webui-todo-app/src/todo-form.ts b/examples/webui-todo-app/src/todo-form.ts new file mode 100644 index 00000000000..273915f852d --- /dev/null +++ b/examples/webui-todo-app/src/todo-form.ts @@ -0,0 +1,12 @@ +import { FASTElement, observable } from "@microsoft/fast-element"; +import { TodoList } from "./todo-list.js"; + +export class TodoForm extends FASTElement { + @observable public description: string = ""; + @TodoList todos!: TodoList; + + public submitTodo() { + this.description && this.todos.add(this.description); + this.description = ""; + } +} diff --git a/examples/webui-todo-app/src/todo-list.ts b/examples/webui-todo-app/src/todo-list.ts new file mode 100644 index 00000000000..71e15c1679e --- /dev/null +++ b/examples/webui-todo-app/src/todo-list.ts @@ -0,0 +1,69 @@ +import { Observable, observable, volatile } from "@microsoft/fast-element"; +import { Context } from "@microsoft/fast-element/context.js"; +import { reactive } from "@microsoft/fast-element/state.js"; + +export type Todo = { description: string; done: boolean }; +export type TodoListFilter = "all" | "active" | "completed"; +export const TodoList = Context.create("TodoList"); +export interface TodoList { + activeFilter: TodoListFilter; + readonly filtered: readonly Todo[]; + add(description: string): void; + remove(todo: Todo): void; +} + +export class DefaultTodoList { + @observable private _todos: Todo[] = []; + @observable public activeFilter: TodoListFilter = "all"; + + public get all() { + return this._todos; + } + + @volatile + public get filtered(): readonly Todo[] { + // This property is decorated with @volatile because the exact + // observable dependencies of the property can change between + // invocations. Normally, FAST assumes that the dependencies of + // a binding are the same across invocations, for optimization + // purposes. So, in this case, we need to tell the system not to + // make that assumption. + + switch (this.activeFilter) { + case "active": + return this._todos.filter(x => !x.done); + case "completed": + return this._todos.filter(x => x.done); + default: + return this._todos; + } + } + + constructor(todos?: Todo[]) { + if (todos) { + this._todos = todos.map(x => reactive(x)); + } + } + + public add(description: string) { + this.splice(this._todos.length, 0, reactive({ description, done: false })); + } + + public remove(todo: Todo) { + const index = this._todos.indexOf(todo); + index !== -1 && this.splice(index, 1); + } + + /** + * This method centralizes all updates to the internal array so that we + * can guarantee that observers are notified in the appropriate cases. + */ + private splice(index: number, removeCount: number, ...newItem: Todo[]) { + this._todos.splice(index, removeCount, ...newItem); + + // Because the filtered property returns different arrays depending + // on the filter, we need to notify FAST that the dependent _todos + // observable has changed whenever we splice the internal data structure. + this.activeFilter !== "all" && Observable.notify(this, "_todos"); + } +} diff --git a/examples/webui-todo-app/todo-data.json b/examples/webui-todo-app/todo-data.json new file mode 100644 index 00000000000..81e6d00f2a1 --- /dev/null +++ b/examples/webui-todo-app/todo-data.json @@ -0,0 +1,5 @@ +[ + { "description": "Buy groceries", "done": false }, + { "description": "Walk the dog", "done": true }, + { "description": "Read a book", "done": false } +] diff --git a/examples/webui-todo-app/tsconfig.json b/examples/webui-todo-app/tsconfig.json new file mode 100644 index 00000000000..beed49471b4 --- /dev/null +++ b/examples/webui-todo-app/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "pretty": true, + "target": "ES2015", + "module": "ES2015", + "moduleResolution": "bundler", + "importHelpers": true, + "experimentalDecorators": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "noEmitOnError": true, + "strict": true, + "outDir": "dist", + "rootDir": "src", + "lib": ["dom", "esnext"] + }, + "include": ["src"] +} diff --git a/examples/webui-todo-app/vite.config.ts b/examples/webui-todo-app/vite.config.ts new file mode 100644 index 00000000000..0515149cbcf --- /dev/null +++ b/examples/webui-todo-app/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "www", + emptyOutDir: true, + sourcemap: true, + rollupOptions: { + input: "src/main.ts", + output: { + entryFileNames: "bundle.js", + }, + }, + }, + server: { + port: 9001, + open: !process.env.CI, + }, +}); diff --git a/package-lock.json b/package-lock.json index 19001c883e6..f197715c46b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "packages/*", "sites/website", "examples/todo-app", - "examples/ssr" + "examples/ssr", + "examples/webui-todo-app" ], "devDependencies": { "@ast-grep/cli": "0.37.0", @@ -71,6 +72,18 @@ "@genesiscommunitysuccess/custom-elements-lsp": "5.0.3" } }, + "examples/webui-todo-app": { + "name": "@microsoft/fast-webui-todo-app-example", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@microsoft/fast-element": "^2.10.2", + "@microsoft/fast-html": "*", + "@microsoft/webui": "^0.0.2", + "express": "4.22.1", + "tslib": "^2.6.3" + } + }, "node_modules/@11ty/dependency-tree": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-4.0.2.tgz", @@ -1772,6 +1785,10 @@ "resolved": "examples/todo-app", "link": true }, + "node_modules/@microsoft/fast-webui-todo-app-example": { + "resolved": "examples/webui-todo-app", + "link": true + }, "node_modules/@microsoft/tsdoc": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", @@ -1790,6 +1807,105 @@ "resolve": "~1.22.2" } }, + "node_modules/@microsoft/webui": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/webui/-/webui-0.0.2.tgz", + "integrity": "sha512-29nzo2P8X5bd4Xt7iOBS/7csQ19d4PJk6pxoDCoCqFXOWK1NpqqKAbjkK/uaSWw9p+zN3uOXVV3tQUeYNKpW7w==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "webui": "bin/webui" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@microsoft/webui-darwin-arm64": "0.0.2", + "@microsoft/webui-darwin-x64": "0.0.2", + "@microsoft/webui-linux-arm64": "0.0.2", + "@microsoft/webui-linux-x64": "0.0.2", + "@microsoft/webui-win32-arm64": "0.0.2", + "@microsoft/webui-win32-x64": "0.0.2" + } + }, + "node_modules/@microsoft/webui-darwin-arm64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/webui-darwin-arm64/-/webui-darwin-arm64-0.0.2.tgz", + "integrity": "sha512-9y2ohH9w9VRHG6+oqgThE9SQYczesM/0YTjifLyfpwoVZqYHi2smDyzyZZGmLh9OOJ8XsNpZZhSaZpbIUuKsRw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@microsoft/webui-darwin-x64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/webui-darwin-x64/-/webui-darwin-x64-0.0.2.tgz", + "integrity": "sha512-tj/BIk4RMAs/XIbxj5RUuSKJqn1x/6ozmTvX6CQbvTcWYeFCqUClP2d6fa7OHBRClqX0tXtd7MoPFSdxFvCmgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@microsoft/webui-linux-arm64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/webui-linux-arm64/-/webui-linux-arm64-0.0.2.tgz", + "integrity": "sha512-4Nw0D8QAWl+GKnnEKDOoQ4PtgPLaAK1cjxDk1XGN44XVAXjuiHu5F+FQnN5SUkDOJ/4+4sN6ebyuhU2tP3XyFw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@microsoft/webui-linux-x64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/webui-linux-x64/-/webui-linux-x64-0.0.2.tgz", + "integrity": "sha512-8qkHmAErGOQohC8Qqwe3MkWlqlkWdrl92qzak6MIviMZ4siXkMlJGJTrhK5sZmFTtX8D/WH5vj4UVNkURO3svg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@microsoft/webui-win32-arm64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/webui-win32-arm64/-/webui-win32-arm64-0.0.2.tgz", + "integrity": "sha512-hmAVWuu5ehAPS1Shj6HoYa7yktLaA3Dra1vMS29rdQZny6AYik9dLUPDuXc2T3IXPZsFcgkkGFAzQH+0388J+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@microsoft/webui-win32-x64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/webui-win32-x64/-/webui-win32-x64-0.0.2.tgz", + "integrity": "sha512-qVjrNhmdXghqcdZKeZQ9aphUbdsAtyc2d8WUslFaF0N30nSxFKy3Zn9mTWSsTR31Q+Y/FOyO8pFiFCCQBQU3CQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3123,7 +3239,6 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -3318,8 +3433,7 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/array-union": { "version": "2.1.0", @@ -3477,7 +3591,6 @@ "version": "1.20.4", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "dev": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -3502,7 +3615,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "dependencies": { "ms": "2.0.0" } @@ -3511,7 +3623,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, "license": "MIT", "dependencies": { "depd": "~2.0.0", @@ -3531,14 +3642,12 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/body-parser/node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -3685,7 +3794,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -3797,7 +3905,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3811,7 +3918,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4073,7 +4179,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -4085,7 +4190,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -4094,7 +4198,6 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4103,8 +4206,7 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -4224,7 +4326,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -4320,7 +4421,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4358,7 +4458,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -4442,7 +4541,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4452,7 +4550,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -4461,7 +4558,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4617,7 +4713,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -4664,7 +4759,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "dependencies": { "ms": "2.0.0" } @@ -4673,7 +4767,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -4682,8 +4775,7 @@ "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/extend-shallow": { "version": "2.0.1", @@ -4872,7 +4964,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -4881,7 +4972,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4948,7 +5038,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4973,7 +5062,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5140,7 +5228,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5215,7 +5302,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5369,7 +5455,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -5422,7 +5507,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -5562,7 +5646,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, "engines": { "node": ">= 0.10" } @@ -6126,7 +6209,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6169,7 +6251,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -6178,7 +6259,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6203,7 +6283,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -6226,7 +6305,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -6235,7 +6313,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -6437,7 +6514,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -6767,7 +6843,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6981,7 +7056,6 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, "license": "MIT" }, "node_modules/path-type": { @@ -7202,7 +7276,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -7230,7 +7303,6 @@ "version": "6.14.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -7274,7 +7346,6 @@ "version": "2.5.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "dev": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -7290,7 +7361,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, "license": "MIT", "dependencies": { "depd": "~2.0.0", @@ -7311,7 +7381,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -7540,7 +7609,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -7559,8 +7627,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/section-matter": { "version": "1.0.0", @@ -7584,7 +7651,6 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -7609,7 +7675,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -7619,14 +7684,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, "license": "MIT" }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, "license": "MIT", "bin": { "mime": "cli.js" @@ -7639,7 +7702,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/serialize-javascript": { @@ -7656,7 +7718,6 @@ "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", @@ -7672,7 +7733,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -7721,7 +7781,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7741,7 +7800,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7758,7 +7816,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7777,7 +7834,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8321,7 +8377,6 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -8419,7 +8474,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, "engines": { "node": ">= 0.4.0" } @@ -8449,7 +8503,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, "engines": { "node": ">= 0.8" } diff --git a/package.json b/package.json index ca498bd7850..730dff4c0a8 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "packages/*", "sites/website", "examples/todo-app", - "examples/ssr" + "examples/ssr", + "examples/webui-todo-app" ], "engines": { "npm": ">=10.0.0", From 10d6237a36a7f4db70f8a4d9239a0714cc0b59e9 Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:39:46 -0700 Subject: [PATCH 2/2] Potential fix for code scanning alert no. 964: Missing rate limiting Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- examples/webui-todo-app/package.json | 3 ++- examples/webui-todo-app/server.js | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/webui-todo-app/package.json b/examples/webui-todo-app/package.json index 2fd42c5e69e..99f54809fd4 100644 --- a/examples/webui-todo-app/package.json +++ b/examples/webui-todo-app/package.json @@ -26,6 +26,7 @@ "@microsoft/fast-html": "*", "@microsoft/webui": "^0.0.2", "express": "4.22.1", - "tslib": "^2.6.3" + "tslib": "^2.6.3", + "express-rate-limit": "^8.3.2" } } diff --git a/examples/webui-todo-app/server.js b/examples/webui-todo-app/server.js index 87755ed7f31..baa7b749dbd 100644 --- a/examples/webui-todo-app/server.js +++ b/examples/webui-todo-app/server.js @@ -1,10 +1,16 @@ import fs from "node:fs"; import { build, render } from "@microsoft/webui"; import express from "express"; +import rateLimit from "express-rate-limit"; const app = express(); const port = 8081; +const todoRateLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // limit each IP to 100 requests per windowMs +}); + // Build templates into a binary protocol at startup (build-time compilation) const result = build({ appDir: "./src", plugin: "fast" }); @@ -102,7 +108,7 @@ function renderTodoItems(todos) { app.use(express.static("./www")); -app.get("/", (req, res) => { +app.get("/", todoRateLimiter, (req, res) => { const todoData = JSON.parse(fs.readFileSync("./todo-data.json").toString()); // Render the page template using webui, injecting prerendered todo items and