Skip to content

Commit 803c757

Browse files
authored
refactor: remove fast-element sideEffects metadata (#7493)
# Pull Request ## 📖 Description This draft removes the remaining packaging-time side-effect assumptions from `@microsoft/fast-element` and flips the package to `"sideEffects": false`. - make `declarative.js` import-pure by moving declarative runtime setup behind a lazy internal runtime hook - convert `debug.js` to an explicit value-based entrypoint while keeping `index.debug.js` and `index.rollup.debug.js` opt-in debug behavior intact - keep the shared FAST debug-message lookup and idle-callback setup working after the metadata flip - update the directly related docs/tests and package metadata for the final sideEffects cleanup ## 👩‍�� Reviewer Notes The main review points are `declarative/runtime.ts`, the debug entrypoint changes, and the package metadata flip in `package.json`. The important question is whether the package is now honestly safe to ship with `sideEffects: false`; the changed tests/builds are aimed at that exact concern. ## 📑 Test Plan - `npm run build -w @microsoft/fast-element` - `npm run test:chromium -w @microsoft/fast-element` - `node build/biome-changed.mjs` - `node build/biome-changed.mjs check` - `npm run doc:ci -w @microsoft/fast-element` - `npm run doc:exports:ci -w @microsoft/fast-element` - `npm run test:playwright -w @microsoft/fast-element` ## ✅ Checklist ### General - [x] I have included a change request file using `$ npm run change` - [x] I have added tests for my changes. - [x] I have tested my changes. - [x] I have updated the project documentation to reflect my changes. - [x] I have read the [CONTRIBUTING](https://github.com/microsoft/fast/blob/main/CONTRIBUTING.md) documentation and followed the [standards](https://github.com/microsoft/fast/blob/main/CODE_OF_CONDUCT.md#our-standards) for this project.
1 parent 247dfbf commit 803c757

24 files changed

Lines changed: 268 additions & 127 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "major",
3+
"comment": "Make declarative runtime setup lazy and change @microsoft/fast-element/debug.js to require an explicit enableDebug() call.",
4+
"packageName": "@microsoft/fast-element",
5+
"email": "7559015+janechu@users.noreply.github.com",
6+
"dependentChangeType": "none"
7+
}

packages/fast-element/DECLARATIVE_DESIGN.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ a waiting FAST element definition.
7272

7373
### `TemplateParser` — declarative HTML parser
7474

75-
A standalone class that converts declarative HTML template markup into the `strings` and `values` arrays that `ViewTemplate.create()` consumes. It is used by `TemplateElement` internally but can also be used independently for programmatic template compilation. The parsing pipeline is fully synchronous — no promises are allocated during template resolution. A `StringsAccumulator` tracks the running concatenation of preceding HTML, eliminating repeated O(N) `join("")` calls at each binding site.
75+
A standalone class that converts declarative HTML template markup into the `strings` and `values` arrays that `ViewTemplate.create()` consumes. It is used by `TemplateElement` internally but can also be used independently for programmatic template compilation. The parsing pipeline is fully synchronous — no promises are allocated during template resolution. A `StringsAccumulator` tracks the running concatenation of preceding HTML, eliminating repeated O(N) `join("")` calls at each binding site. `createTemplate()` also performs the declarative runtime's lazy installation of hydratable `ViewTemplate` support and declarative debug messages.
7676

7777
### `Schema` — JSON schema builder
7878

@@ -179,7 +179,7 @@ packages/fast-element/
179179
│ └── declarative/
180180
│ ├── index.ts # Declarative barrel export
181181
│ ├── interfaces.ts # Message enum (error codes)
182-
│ ├── debug.ts # Human-readable debug messages registered with FAST
182+
│ ├── debug.ts # Human-readable declarative debug messages
183183
│ ├── template.ts # TemplateElement (<f-template>), lifecycle orchestration, options
184184
│ ├── template-parser.ts # TemplateParser — converts declarative HTML to ViewTemplate strings/values
185185
│ ├── schema.ts # Schema class — JSON schema builder + schemaRegistry
@@ -361,7 +361,7 @@ sequenceDiagram
361361
| Method | Visibility | Role |
362362
|---|---|---|
363363
| `parse()` | public | Entry point: parses declarative HTML into `{ strings, values }`. |
364-
| `createTemplate()` | public | Creates a `ViewTemplate` from resolved strings and values. |
364+
| `createTemplate()` | public | Creates a `ViewTemplate` from resolved strings and values, lazily enabling declarative hydration support. |
365365
| `resolveStringsAndValues()` | private | Creates `strings`/`values` arrays and delegates to `resolveInnerHTML()`. |
366366
| `resolveInnerHTML()` | private | Recursive HTML parser that dispatches to data binding or template directive handlers. |
367367
| `resolveDataBinding()` | private | Thin dispatcher that routes to `resolveContentBinding()`, `resolveAttributeBinding()`, or `resolveAttributeDirectiveBinding()`. |

packages/fast-element/DECLARATIVE_HTML.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@ server-side rendering, see the
2020

2121
## Template Structure
2222

23-
After registering the declarative entrypoint as shown in the README, templates
24-
are associated with an element through
23+
After importing the declarative APIs as shown in the README, templates are
24+
associated with an element through
2525
`<f-template name="[custom-element-name]"><template>...</template></f-template>`.
2626
The host custom element should be defined with
2727
`template: declarativeTemplate()`. This automatically defines `<f-template>` in
2828
the relevant registry and waits for the matching declarative template when it is
2929
already present or inserted later.
3030

31+
The `@microsoft/fast-element/declarative.js` entrypoint itself remains
32+
side-effect free at import time. The hydratable `ViewTemplate` runtime is
33+
installed lazily when `TemplateParser`, `TemplateElement`, or
34+
`declarativeTemplate()` first create a declarative template.
35+
3136
Example:
3237
```html
3338
<my-custom-element greeting="Hello world">

packages/fast-element/DESIGN.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ engines must install their own `globalThis` polyfill before FAST loads.
6767

6868
- `FAST.getById(id, initializer)` – shared kernel slot registry (used to share the update queue and observable system across FAST instances)
6969
- `FAST.warn(code, values)` / `FAST.error(code, values)` – structured diagnostic messages
70-
- `FAST.addMessages(dict)` – registers human-readable debug messages (imported by `src/debug.ts`)
70+
- `FAST.addMessages(dict)` – registers human-readable debug messages used by `enableDebug()` and declarative runtime diagnostics
7171

7272
The `KernelServiceId` enum provides the fixed numeric keys used for shared
7373
services on the `FAST` global. These stable IDs let FAST instances on the same
@@ -344,9 +344,10 @@ the imperative `html` API:
344344
- `ObserverMap` and `AttributeMap` layer on top of the core observable and
345345
attribute-definition systems.
346346

347-
The `src/declarative.ts` entrypoint owns the declarative-only side effects:
348-
registering debug messages and installing hydratable view templates. This keeps
349-
the root `@microsoft/fast-element` barrel free of declarative side effects and
347+
The `src/declarative.ts` entrypoint is pure at module evaluation time. The
348+
declarative runtime installs its debug messages and hydratable `ViewTemplate`
349+
hooks lazily from `TemplateParser.createTemplate()`, keeping the root
350+
`@microsoft/fast-element` barrel free of declarative side effects and
350351
utility-subpath collisions. See [`DECLARATIVE_DESIGN.md`](./DECLARATIVE_DESIGN.md)
351352
for the detailed architecture.
352353

@@ -510,12 +511,12 @@ Below is a conceptual map of the major subsystems and their relationships:
510511
src/
511512
├── interfaces.ts # Core types: Callable, Constructable, FASTGlobal, Message codes
512513
├── platform.ts # FAST global initialisation, KernelServiceId, TypeRegistry
513-
├── declarative.ts # Declarative entrypoint (debug messages + hydratable view install)
514+
├── declarative.ts # Pure declarative entrypoint
514515
├── dom.ts # DOMAspect enum, DOMPolicy, DOMSink
515516
├── dom-policy.ts # Default DOM security policy (TrustedTypes integration)
516517
├── metadata.ts # Reflect-based metadata helpers
517518
├── utilities.ts # UnobservableMutationObserver and other helpers
518-
├── debug.ts # Adds human-readable error messages to FAST global
519+
├── debug.ts # Exports enableDebug() for human-readable FAST errors
519520
├── observation/
520521
│ ├── observable.ts # Observable, @observable, ExpressionNotifier, ExecutionContext
521522
│ ├── notifier.ts # Subscriber, Notifier, SubscriberSet, PropertyChangeNotifier

packages/fast-element/MIGRATION.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ removed `@microsoft/fast-html` package.
5757
3. Keep importing core FAST Element APIs (for example `FASTElement`, `attr`,
5858
`observable`) from `@microsoft/fast-element`.
5959
4. Do not switch to the root `@microsoft/fast-element` barrel for declarative
60-
APIs; the declarative entrypoint owns the debug-message and hydratable-view
61-
side effects.
60+
APIs; the declarative entrypoint owns the declarative runtime and installs
61+
hydration support lazily when declarative templates are created.
6262

6363
## `TemplateOptions` removal (v3)
6464

@@ -72,14 +72,33 @@ removed `@microsoft/fast-html` package.
7272

7373
### Changed behavior
7474

75-
- `FASTElement.define()` no longer uses `templateOptions` to delay platform definition or connection.
76-
- Elements can still be defined before a template is attached; a later `FASTElementDefinition.template` update notifies connected elements so they can render or hydrate with the new template.
75+
- `FASTElement.define()` no longer uses `templateOptions` to delay platform
76+
definition or connection.
77+
- Elements can still be defined before a template is attached; a later
78+
`FASTElementDefinition.template` update notifies connected elements so they
79+
can render or hydrate with the new template.
7780

7881
### Migration steps
7982

8083
1. Remove `templateOptions` from element definitions.
81-
2. Continue calling `define({ name })` when a definition needs to exist before its template is attached.
82-
3. If a template is supplied later, assign `FASTElementDefinition.template` (or use the declarative runtime that does so for you).
84+
2. Continue calling `define({ name })` when a definition needs to exist before
85+
its template is attached.
86+
3. If a template is supplied later, assign `FASTElementDefinition.template` (or
87+
use the declarative runtime that does so for you).
88+
89+
## Debug entrypoint explicit enablement (v3)
90+
91+
### Import changes
92+
93+
| Before | After |
94+
|---|---|
95+
| `import "@microsoft/fast-element/debug.js";` | `import { enableDebug } from "@microsoft/fast-element/debug.js"; enableDebug();` |
96+
97+
### Migration steps
98+
99+
1. Replace setup-only `debug.js` imports with an explicit `enableDebug()` call.
100+
2. Keep using the root package `development` export or debug rollup bundle when
101+
you want debug behavior enabled automatically.
83102

84103
## Declarative event handler `e` removal (v3)
85104

@@ -119,8 +138,8 @@ data source.
119138
| `@microsoft/fast-element/install-hydration.js` | No replacement needed — prerendered path is built into `ElementController` |
120139

121140
The `install-hydratable-view-templates.js` side-effect import is still
122-
available and is applied automatically by
123-
`@microsoft/fast-element/declarative.js` for hydration marker support.
141+
available, but `@microsoft/fast-element/declarative.js` now installs the
142+
hydration runtime lazily when declarative APIs create a template.
124143

125144
### Changed behavior
126145

packages/fast-element/README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ internally, but it no longer patches `globalThis` for older engines. If you
6161
need to support an environment without `globalThis`, load that polyfill before
6262
importing `@microsoft/fast-element`.
6363

64+
## Debug entrypoint
65+
66+
`@microsoft/fast-element/debug.js` exports `enableDebug()` instead of
67+
configuring FAST at import time. The development root export and debug rollup
68+
bundle still enable debug behavior automatically.
69+
70+
```ts
71+
import { enableDebug } from "@microsoft/fast-element/debug.js";
72+
73+
enableDebug();
74+
```
75+
6476
## Export Sizes
6577

6678
Bundle sizes for each tree-shakeable export are tracked in [`SIZES.md`](./SIZES.md) and regenerated on every build. See the [Export Sizes](https://www.fast.design/docs/3.x/resources/export-sizes/) documentation page for the latest numbers.
@@ -82,8 +94,9 @@ controller when styles need to change.
8294
FAST Element also publishes a declarative HTML runtime from
8395
`@microsoft/fast-element/declarative.js`. This entrypoint exports
8496
`declarativeTemplate()`, `TemplateElement`, `TemplateParser`, `Schema`,
85-
`ObserverMap`, and `AttributeMap`, and installs the hydratable `ViewTemplate`
86-
behavior without adding those side effects to the root
97+
`ObserverMap`, and `AttributeMap`. The entrypoint stays pure at import time and
98+
installs the hydratable `ViewTemplate` behavior lazily when declarative APIs
99+
actually create a template, without adding those side effects to the root
87100
`@microsoft/fast-element` import.
88101

89102
```ts

packages/fast-element/SIZES.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ Bundle sizes for `@microsoft/fast-element` exports.
44

55
| Export | Minified | Gzip | Brotli |
66
|--------|----------|------|--------|
7-
| CDN Rollup Bundle | 65.39 KB | 19.42 KB | 17.43 KB |
8-
| FASTElement | 25.59 KB | 7.93 KB | 7.19 KB |
9-
| Updates | 3.03 KB | 1.25 KB | 1.06 KB |
10-
| Observable | 7.55 KB | 2.76 KB | 2.48 KB |
11-
| observable | 7.58 KB | 2.77 KB | 2.50 KB |
12-
| attr | 2.98 KB | 1.18 KB | 1011 B |
13-
| children | 5.58 KB | 2.15 KB | 1.90 KB |
14-
| css | 4.06 KB | 1.60 KB | 1.39 KB |
15-
| ref | 4.55 KB | 1.81 KB | 1.60 KB |
16-
| slotted | 5.36 KB | 2.08 KB | 1.83 KB |
17-
| volatile | 7.64 KB | 2.79 KB | 2.51 KB |
7+
| CDN Rollup Bundle | 65.46 KB | 19.45 KB | 17.44 KB |
8+
| FASTElement | 25.69 KB | 7.98 KB | 7.22 KB |
9+
| Updates | 3.13 KB | 1.28 KB | 1.09 KB |
10+
| Observable | 7.65 KB | 2.79 KB | 2.51 KB |
11+
| observable | 7.68 KB | 2.81 KB | 2.53 KB |
12+
| attr | 1.28 KB | 634 B | 552 B |
13+
| children | 5.68 KB | 2.19 KB | 1.95 KB |
14+
| css | 2.36 KB | 1.05 KB | 964 B |
15+
| ref | 4.65 KB | 1.86 KB | 1.63 KB |
16+
| slotted | 5.46 KB | 2.12 KB | 1.87 KB |
17+
| volatile | 7.74 KB | 2.83 KB | 2.54 KB |
1818
| when | 1.99 KB | 770 B | 625 B |
19-
| html | 26.71 KB | 8.74 KB | 7.84 KB |
20-
| repeat | 30.37 KB | 9.67 KB | 8.70 KB |
19+
| html | 26.82 KB | 8.77 KB | 7.88 KB |
20+
| repeat | 30.48 KB | 9.70 KB | 8.74 KB |

packages/fast-element/package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,7 @@
7979
"./package.json": "./package.json"
8080
},
8181
"unpkg": "dist/fast-element.min.js",
82-
"sideEffects": [
83-
"./dist/esm/debug.js",
84-
"./dist/esm/declarative.js",
85-
"./dist/esm/polyfills.js",
86-
"./dist/esm/interfaces.js"
87-
],
82+
"sideEffects": false,
8883
"scripts": {
8984
"clean": "clean dist temp test-results",
9085
"doc": "api-extractor run --local",

packages/fast-element/src/debug.pw.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { expect, test } from "@playwright/test";
2-
import "./debug.js";
2+
import { enableDebug } from "./debug.js";
33
import type { FASTGlobal } from "./interfaces.js";
44

55
declare const FAST: FASTGlobal;
66

7+
enableDebug();
8+
79
test.describe("The debug module", () => {
810
let keyBase = 1111111111;
911

packages/fast-element/src/debug.ts

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
1-
import type { FASTGlobal } from "./interfaces.js";
1+
import { FAST, getDebugMessageLookup } from "./platform.js";
22

3-
if (globalThis.FAST === void 0) {
4-
Reflect.defineProperty(globalThis, "FAST", {
5-
value: Object.create(null),
6-
configurable: false,
7-
enumerable: false,
8-
writable: false,
9-
});
10-
}
11-
12-
const FAST: FASTGlobal = globalThis.FAST;
13-
14-
const debugMessages = {
3+
const baseDebugMessages = {
154
[1101 /* needsArrayObservation */]:
165
"Must call ArrayObserver.enable() before observing arrays.",
176
[1201 /* onlySetDOMPolicyOnce */]: "The DOM Policy can only be set once.",
@@ -82,16 +71,26 @@ function formatMessage(message: string, values: Record<string, any>) {
8271
.join("");
8372
}
8473

85-
Object.assign(FAST, {
86-
addMessages(messages: Record<number, string>) {
87-
Object.assign(debugMessages, messages);
88-
},
89-
warn(code: number, values: Record<string, any> = noValues) {
90-
const message = debugMessages[code] ?? "Unknown Warning";
91-
console.warn(formatMessage(message, values));
92-
},
93-
error(code: number, values: Record<string, any> = noValues) {
94-
const message = debugMessages[code] ?? "Unknown Error";
95-
return new Error(formatMessage(message, values));
96-
},
97-
});
74+
/**
75+
* Enables human-readable FAST debug messages.
76+
* @public
77+
*/
78+
export function enableDebug(): void {
79+
const debugMessages = getDebugMessageLookup();
80+
81+
Object.assign(debugMessages, baseDebugMessages);
82+
83+
Object.assign(FAST, {
84+
addMessages(messages: Record<number, string>) {
85+
Object.assign(debugMessages, messages);
86+
},
87+
warn(code: number, values: Record<string, any> = noValues) {
88+
const message = debugMessages[code] ?? "Unknown Warning";
89+
console.warn(formatMessage(message, values));
90+
},
91+
error(code: number, values: Record<string, any> = noValues) {
92+
const message = debugMessages[code] ?? "Unknown Error";
93+
return new Error(formatMessage(message, values));
94+
},
95+
});
96+
}

0 commit comments

Comments
 (0)