Skip to content

Commit 8f9dac0

Browse files
janechuCopilot
andcommitted
feat: modularize FASTElement for smaller bundle size
Breaking changes to reduce FASTElement bundle size: - Remove globalThis.FAST singleton — FAST is now a module-scoped export, no side-effecting global mutation - Remove FAST.getById(), FASTGlobal type, KernelServiceId enum - Remove requestIdleCallback polyfill (native in all modern browsers) - Move HydrationView to separate module (hydration-view.ts) - Move renderPrerendered to pluggable hydration hook in enable-hydration.ts — ElementController has zero hydration imports - Move CSS/styles to ./styles.js subpath export - Move array observation to ./arrays.js subpath export - Move deferHydrationAttribute to ./hydration.js subpath (retained for intersection observer viewport hydration rendering) - ElementController uses composition instead of inheritance for PropertyChangeNotifier - Update DESIGN.md, README.md, migration guide, 3.x docs - Fix fast-router and todo-app imports for new subpaths - Update platform.pw.spec.ts and debug.pw.spec.ts for module FAST Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8a5f201 commit 8f9dac0

128 files changed

Lines changed: 1025 additions & 6197 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/todo-app/src/todo-app.styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { css } from "@microsoft/fast-element";
1+
import { css } from "@microsoft/fast-element/styles.js";
22

33
export const styles = css`
44
:host {

examples/todo-app/src/todo-form.styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { css } from "@microsoft/fast-element";
1+
import { css } from "@microsoft/fast-element/styles.js";
22

33
export const styles = css`
44
form {

packages/fast-element/SIZES.md

Lines changed: 14 additions & 14 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.72 KB | 19.53 KB | 17.50 KB |
8-
| FASTElement | 25.31 KB | 7.88 KB | 7.13 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 |
18-
| when | 1.99 KB | 771 B | 637 B |
19-
| html | 26.86 KB | 8.79 KB | 7.89 KB |
20-
| repeat | 30.51 KB | 9.71 KB | 8.75 KB |
7+
| CDN Rollup Bundle | 63.66 KB | 19.00 KB | 17.01 KB |
8+
| FASTElement | 22.83 KB | 7.19 KB | 6.49 KB |
9+
| Updates | 473 B | 337 B | 287 B |
10+
| Observable | 6.70 KB | 2.49 KB | 2.22 KB |
11+
| observable | 6.74 KB | 2.50 KB | 2.24 KB |
12+
| attr | 477 B | 288 B | 244 B |
13+
| children | 4.81 KB | 1.86 KB | 1.64 KB |
14+
| css | 0 B | 20 B | 1 B |
15+
| ref | 3.78 KB | 1.53 KB | 1.33 KB |
16+
| slotted | 4.60 KB | 1.79 KB | 1.58 KB |
17+
| volatile | 6.79 KB | 2.53 KB | 2.25 KB |
18+
| when | 1.82 KB | 712 B | 568 B |
19+
| html | 25.92 KB | 8.50 KB | 7.61 KB |
20+
| repeat | 29.57 KB | 9.42 KB | 8.47 KB |

packages/fast-element/docs/api-report.api.md

Lines changed: 19 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,6 @@ export interface Accessor {
1414
// @public
1515
export type AddViewBehaviorFactory = (factory: ViewBehaviorFactory) => string;
1616

17-
// @public
18-
export interface ArrayObserver extends SubscriberSet {
19-
addSort(sort: Sort): void;
20-
addSplice(splice: Splice): void;
21-
flush(): void;
22-
readonly lengthObserver: LengthObserver;
23-
reset(oldCollection: any[] | undefined): void;
24-
readonly sortObserver: SortObserver;
25-
strategy: SpliceStrategy | null;
26-
}
27-
28-
// @public
29-
export const ArrayObserver: Readonly<{
30-
readonly sorted: 0;
31-
readonly enable: () => void;
32-
}>;
33-
3417
// @public
3518
export interface Aspected {
3619
aspectType: DOMAspect;
@@ -151,19 +134,11 @@ export const Compiler: {
151134
aggregate(parts: (string | ViewBehaviorFactory)[], policy?: DOMPolicy): ViewBehaviorFactory;
152135
};
153136

154-
// @public
155-
export type ComposableStyles = string | ElementStyles | CSSStyleSheet;
156-
157137
// @public
158138
export type Constructable<T = {}> = {
159139
new (...args: any[]): T;
160140
};
161141

162-
// @public
163-
export type ConstructibleStyleStrategy = {
164-
new (styles: (string | CSSStyleSheet)[]): StyleStrategy;
165-
};
166-
167142
// @public
168143
export interface ContentTemplate {
169144
create(): ContentView;
@@ -179,46 +154,12 @@ export interface ContentView {
179154
unbind(): void;
180155
}
181156

182-
// @public
183-
export const css: CSSTemplateTag;
184-
185-
// @public
186-
export interface CSSDirective {
187-
createCSS(): ComposableStyles;
188-
}
189-
190-
// @public
191-
export const CSSDirective: Readonly<{
192-
getForInstance: (object: any) => CSSDirectiveDefinition<Constructable<CSSDirective>> | undefined;
193-
getByType: (key: Function) => CSSDirectiveDefinition<Constructable<CSSDirective>> | undefined;
194-
define<TType extends Constructable<CSSDirective>>(type: any): TType;
195-
}>;
196-
197-
// @public
198-
export function cssDirective(): (type: Constructable<CSSDirective>) => void;
199-
200-
// @public
201-
export interface CSSDirectiveDefinition<TType extends Constructable<CSSDirective> = Constructable<CSSDirective>> {
202-
readonly type: TType;
203-
}
204-
205-
// @public
206-
export type CSSTemplateTag = ((strings: TemplateStringsArray, ...values: CSSValue[]) => ElementStyles) & {
207-
partial(strings: TemplateStringsArray, ...values: CSSValue[]): CSSDirective;
208-
};
209-
210-
// @public
211-
export type CSSValue = ComposableStyles | CSSDirective;
212-
213157
// @public
214158
export function customElement(nameOrDef: string | PartialFASTElementDefinition): (type: Constructable<HTMLElement>) => void;
215159

216160
// @public
217161
export type DecoratorAttributeConfiguration = Omit<AttributeConfiguration, "property">;
218162

219-
// @beta
220-
export const deferHydrationAttribute = "defer-hydration";
221-
222163
// @public
223164
export interface Disposable {
224165
dispose(): void;
@@ -255,12 +196,15 @@ export interface DOMPolicy {
255196
// @public
256197
export type DOMSink = (target: Node, aspectName: string, value: any, ...args: any[]) => void;
257198

199+
// Warning: (ae-forgotten-export) The symbol "HostController" needs to be exported by the entry point index.d.ts
200+
//
258201
// @public
259-
export class ElementController<TElement extends HTMLElement = HTMLElement> extends PropertyChangeNotifier implements HostController<TElement> {
202+
export class ElementController<TElement extends HTMLElement = HTMLElement> implements Notifier, HostController<TElement> {
260203
// @internal
261204
constructor(element: TElement, definition: FASTElementDefinition);
262205
addBehavior(behavior: HostBehavior<TElement>): void;
263206
addStyles(styles: ElementStyles | HTMLStyleElement | null | undefined): void;
207+
// Warning: (ae-forgotten-export) The symbol "HostBehavior" needs to be exported by the entry point index.d.ts
264208
protected behaviors: Map<HostBehavior<TElement>, number> | null;
265209
protected bindObservables(): void;
266210
protected captureBoundObservables(): void;
@@ -271,16 +215,19 @@ export class ElementController<TElement extends HTMLElement = HTMLElement> exten
271215
disconnect(): void;
272216
protected disconnectBehaviors(): void;
273217
emit(type: string, detail?: any, options?: Omit<CustomEventInit, "detail">): void | boolean;
274-
static enableHydration(tracker: HydrationTracker): void;
275218
static forCustomElement(element: HTMLElement, override?: boolean): ElementController;
276219
protected hasExistingShadowRoot: boolean;
220+
// @internal
221+
static installHydrationHook(hook: (controller: ElementController, template: ElementViewTemplate, element: HTMLElement, host: Node) => boolean): void;
277222
get isBound(): boolean;
278223
get isConnected(): boolean;
279224
readonly isHydrated: Promise<boolean>;
280225
readonly isPrerendered: Promise<boolean>;
226+
// Warning: (ae-forgotten-export) The symbol "ElementStyles" needs to be exported by the entry point index.d.ts
281227
get mainStyles(): ElementStyles | null;
282228
set mainStyles(value: ElementStyles | null);
283229
protected needsInitialization: boolean;
230+
notify(args: any): void;
284231
protected observeLateAttributes(): void;
285232
onAttributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
286233
onUnbind(behavior: {
@@ -295,9 +242,12 @@ export class ElementController<TElement extends HTMLElement = HTMLElement> exten
295242
readonly source: TElement;
296243
get sourceLifetime(): SourceLifetime | undefined;
297244
protected stage: Stages;
245+
get subject(): TElement;
246+
subscribe(subscriber: Subscriber, propertyToWatch?: any): void;
298247
protected syncLateAttributes(): void;
299248
get template(): ElementViewTemplate<TElement> | null;
300249
set template(value: ElementViewTemplate<TElement> | null);
250+
unsubscribe(subscriber: Subscriber, propertyToUnwatch?: any): void;
301251
readonly view: ElementView<TElement> | null;
302252
}
303253

@@ -313,24 +263,6 @@ export const elements: (selector?: string) => ElementsFilter;
313263
// @public
314264
export type ElementsFilter = (value: Node, index?: number, array?: Node[]) => boolean;
315265

316-
// @public
317-
export class ElementStyles {
318-
constructor(styles: ReadonlyArray<ComposableStyles>);
319-
// @internal (undocumented)
320-
addStylesTo(target: StyleTarget): void;
321-
// @internal (undocumented)
322-
isAttachedTo(target: StyleTarget): boolean;
323-
static normalize(styles: ComposableStyles | ComposableStyles[] | undefined): ElementStyles | undefined;
324-
// @internal (undocumented)
325-
removeStylesFrom(target: StyleTarget): void;
326-
static setDefaultStrategy(Strategy: ConstructibleStyleStrategy): void;
327-
get strategy(): StyleStrategy;
328-
// (undocumented)
329-
readonly styles: ReadonlyArray<ComposableStyles>;
330-
static readonly supportsAdoptedStyleSheets: boolean;
331-
withStrategy(Strategy: ConstructibleStyleStrategy): this;
332-
}
333-
334266
// @public
335267
export interface ElementView<TSource = any, TParent = any> extends View<TSource, TParent> {
336268
appendTo(node: Node): void;
@@ -402,7 +334,11 @@ export interface ExpressionObserver<TSource = any, TReturn = any, TParent = any>
402334
}
403335

404336
// @public
405-
export const FAST: FASTGlobal;
337+
export const FAST: {
338+
warn(_code: number, _values?: Record<string, any>): void;
339+
error(code: number, _values?: Record<string, any>): Error;
340+
addMessages(messages: Record<number, string>): void;
341+
};
406342

407343
// @public
408344
export interface FASTElement extends HTMLElement {
@@ -457,34 +393,6 @@ export const fastElementRegistry: TypeRegistry<FASTElementDefinition>;
457393
// @public
458394
export type FASTElementTemplateResolver<TType extends Constructable<HTMLElement> = Constructable<HTMLElement>> = (definition: FASTElementDefinition<TType>) => ElementViewTemplate<InstanceType<TType>> | Promise<ElementViewTemplate<InstanceType<TType>>>;
459395

460-
// @public
461-
export interface FASTGlobal {
462-
addMessages(messages: Record<number, string>): void;
463-
error(code: number, values?: Record<string, any>): Error;
464-
getById<T>(id: string | number): T | null;
465-
// (undocumented)
466-
getById<T>(id: string | number, initialize: () => T): T;
467-
warn(code: number, values?: Record<string, any>): void;
468-
}
469-
470-
// @public
471-
export interface HostBehavior<TSource = any> {
472-
addedCallback?(controller: HostController<TSource>): void;
473-
connectedCallback?(controller: HostController<TSource>): void;
474-
disconnectedCallback?(controller: HostController<TSource>): void;
475-
removedCallback?(controller: HostController<TSource>): void;
476-
}
477-
478-
// @public
479-
export interface HostController<TSource = any> extends ExpressionController<TSource> {
480-
addBehavior(behavior: HostBehavior<TSource>): void;
481-
addStyles(styles: ElementStyles | HTMLStyleElement | null | undefined): void;
482-
readonly isConnected: boolean;
483-
mainStyles: ElementStyles | null;
484-
removeBehavior(behavior: HostBehavior<TSource>, force?: boolean): void;
485-
removeStyles(styles: ElementStyles | HTMLStyleElement | null | undefined): void;
486-
}
487-
488396
// @public
489397
export const html: HTMLTemplateTag;
490398

@@ -641,14 +549,6 @@ export function isHydratable<TSource = any, TParent = any>(template: ElementView
641549
// @beta (undocumented)
642550
export function isHydratable(template: ContentTemplate): template is HydratableContentTemplate;
643551

644-
// @public
645-
export interface LengthObserver extends Subscriber {
646-
length: number;
647-
}
648-
649-
// @public
650-
export function lengthOf<T>(array: readonly T[]): number;
651-
652552
// @public
653553
export function listener<T = any>(expression: Expression<T>, options?: AddEventListenerOptions): Binding<T>;
654554

@@ -738,6 +638,7 @@ export interface PartialFASTElementDefinition<TType extends Constructable<HTMLEl
738638
readonly name: string;
739639
readonly registry?: CustomElementRegistry;
740640
readonly shadowOptions?: Partial<ShadowRootOptions> | null;
641+
// Warning: (ae-forgotten-export) The symbol "ComposableStyles" needs to be exported by the entry point index.d.ts
741642
readonly styles?: ComposableStyles | ComposableStyles[];
742643
readonly template?: ElementViewTemplate<InstanceType<TType>> | FASTElementTemplateResolver<TType>;
743644
}
@@ -798,6 +699,8 @@ export function repeat<TSource = any, TArray extends ReadonlyArray<any> = Readon
798699
export class RepeatBehavior<TSource = any> implements ViewBehavior, Subscriber {
799700
constructor(directive: RepeatDirective);
800701
bind(controller: ViewController): void;
702+
// Warning: (ae-forgotten-export) The symbol "Splice" needs to be exported by the entry point index.d.ts
703+
// Warning: (ae-forgotten-export) The symbol "Sort" needs to be exported by the entry point index.d.ts
801704
handleChange(source: any, args: Splice[] | Sort[] | ExpressionObserver): void;
802705
unbind(): void;
803706
// @internal (undocumented)
@@ -846,21 +749,6 @@ export class SlottedDirective extends NodeObservationDirective<SlottedDirectiveO
846749
export interface SlottedDirectiveOptions<T = any> extends NodeBehaviorOptions<T>, AssignedNodesOptions {
847750
}
848751

849-
// @public
850-
export class Sort {
851-
constructor(sorted?: number[] | undefined);
852-
// (undocumented)
853-
sorted?: number[] | undefined;
854-
}
855-
856-
// @public
857-
export function sortedCount<T>(array: readonly T[]): number;
858-
859-
// @public
860-
export interface SortObserver extends Subscriber {
861-
sorted: number;
862-
}
863-
864752
// @public
865753
export const SourceLifetime: Readonly<{
866754
readonly unknown: undefined;
@@ -870,48 +758,6 @@ export const SourceLifetime: Readonly<{
870758
// @public
871759
export type SourceLifetime = (typeof SourceLifetime)[keyof typeof SourceLifetime];
872760

873-
// @public
874-
export class Splice {
875-
constructor(index: number, removed: any[], addedCount: number);
876-
// (undocumented)
877-
addedCount: number;
878-
adjustTo(array: any[]): this;
879-
// (undocumented)
880-
index: number;
881-
// (undocumented)
882-
removed: any[];
883-
reset?: boolean;
884-
}
885-
886-
// @public
887-
export interface SpliceStrategy {
888-
normalize(previous: unknown[] | undefined, current: unknown[], changes: Splice[] | undefined): readonly Splice[];
889-
pop(array: any[], observer: ArrayObserver, pop: typeof Array.prototype.pop, args: any[]): any;
890-
push(array: any[], observer: ArrayObserver, push: typeof Array.prototype.push, args: any[]): any;
891-
reverse(array: any[], observer: ArrayObserver, reverse: typeof Array.prototype.reverse, args: any[]): any;
892-
shift(array: any[], observer: ArrayObserver, shift: typeof Array.prototype.shift, args: any[]): any;
893-
sort(array: any[], observer: ArrayObserver, sort: typeof Array.prototype.sort, args: any[]): any[];
894-
splice(array: any[], observer: ArrayObserver, splice: typeof Array.prototype.splice, args: any[]): any;
895-
readonly support: SpliceStrategySupport;
896-
unshift(array: any[], observer: ArrayObserver, unshift: typeof Array.prototype.unshift, args: any[]): any[];
897-
}
898-
899-
// @public
900-
export const SpliceStrategy: Readonly<{
901-
readonly reset: Splice[];
902-
readonly setDefaultStrategy: (strategy: SpliceStrategy) => void;
903-
}>;
904-
905-
// @public
906-
export const SpliceStrategySupport: Readonly<{
907-
readonly reset: 1;
908-
readonly splice: 2;
909-
readonly optimized: 3;
910-
}>;
911-
912-
// @public
913-
export type SpliceStrategySupport = (typeof SpliceStrategySupport)[keyof typeof SpliceStrategySupport];
914-
915761
// @public
916762
export const enum Stages {
917763
connected = 1,
@@ -930,20 +776,6 @@ export abstract class StatelessAttachedAttributeDirective<TOptions> implements H
930776
protected options: TOptions;
931777
}
932778

933-
// @public
934-
export interface StyleStrategy {
935-
addStylesTo(target: StyleTarget): void;
936-
removeStylesFrom(target: StyleTarget): void;
937-
}
938-
939-
// @public
940-
export interface StyleTarget extends Pick<Node, "getRootNode"> {
941-
adoptedStyleSheets?: CSSStyleSheet[];
942-
append(styles: HTMLStyleElement): void;
943-
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
944-
removeChild(styles: HTMLStyleElement): void;
945-
}
946-
947779
// @public
948780
export interface Subscriber {
949781
handleChange(subject: any, args: any): void;

packages/fast-element/docs/hydration/api-report.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
55
```ts
66

7+
// @beta
8+
export const deferHydrationAttribute = "defer-hydration";
9+
710
// @public
811
export function enableHydration(options?: HydrationOptions): void;
912

0 commit comments

Comments
 (0)