Skip to content

Theming: Default Values Codegen #4263

Description

@JasonVMo

Summary

Generate alias, shadow, and Flex default values from the Fluent design token
packages instead of maintaining literal tables by hand. Extend the existing
codegen and shared-constant factoring so values are de-duplicated across both
platforms and appearances and remain tree-shakeable.

The repository already has the required prebuild entry point and
de-duplication machinery for global tokens; this task applies that model to the
remaining theme data.

Goal

Generate the default theme values from the Fluent design tokens instead of
hand-maintaining them, and de-duplicate values that are shared across themes so
each value is declared once and unused values can be dropped from a bundle.

Stage

Stages 2 and 3. Stage 2 replaces the hand-authored defaults with generated
ones; Stage 3 completes cross-appearance de-duplication and records the measured
bundle result.

Why it matters

  • Observed. The Flex defaults are hand-authored.
    defaultTokens.ts
    is 473 lines and contains 249 color literals with only 80 distinct values,
    because the color, color.hover, and color.pressed maps each restate the
    full token list even when the value does not change between states.
  • Observed. Only one appearance is generated at all. codegen.cts reads
    android/light, ios/light, macos/light, win32/colorful, and
    windows/light
    (scripts/codegen.cts),
    while the installed token packages ship more variants: macOS provides light,
    dark, hclight, and hcdark, and win32 provides colorful, darkgray,
    hc, and black (Observed in the resolved
    @fluentui-react-native/design-tokens-macos@0.53.0 and
    @fluentui-react-native/design-tokens-win32@0.53.0 packages; the catalog pins
    ^0.53.0 in .yarnrc.yml).
  • Observed. Non-generated theme values are loaded as whole JSON objects at
    module scope. theme-tokens/src/getTokens.ts
    imports the windows light and dark alias and shadow JSON files plus the win32
    high-contrast shadow JSON, then selects between them at runtime. For macOS the
    on-disk sizes are 6,088 bytes for light/tokens-aliases.json and 6,089 bytes
    for dark/tokens-aliases.json, with byte-identical 22,681-byte
    tokens-global.json files in both directories (Observed in the resolved
    package).
  • Inferred. Whole-object JSON imports cannot be tree-shaken per token, so
    every consumer pays for every alias value of every bundled appearance. The
    existing constant-per-token codegen output does not have that property, which
    is why global.generated.ts states in its header that it "allows references
    to direct constants which can be minified and statically analyzed and removed
    if unused."

Observed current state

  • Observed. codegen.cts runs as the design package's prebuild script
    (package.json) and calls
    processPlatformJsonFiles and outputCodegenFile from
    @fluentui-react-native/scripts.
  • Observed. scripts/src/codegen/json.ts
    implements the de-duplication: it flattens nested JSON into prefixed constant
    names, treats win32 as the default platform written to the un-suffixed file,
    pulls constants common to all platforms into the common entry with
    extractCommonFromPlatforms, and factors group-shared constants into
    desktop, mobile, win, and apple subset files with
    extractConstsUpstream, "so each value is declared once."
  • Observed. The generated output is
    src/tokens/global.generated.ts
    (879 lines of export const color, size, radius, stroke, and font constants)
    plus src/tokens/generated
    containing global.platform.ts, four global.platform.<platform>.ts files,
    and four global.subset.<group>.ts files that re-export shared values.
  • Observed. global.generated.ts is exposed publicly through the
    ./tokens/global export condition.
  • Observed. defaultTokens.ts mixes two styles: non-color groups reference
    the generated constants (cornerRadius20, size40, fontLineHeight300),
    while colors are inline literals. nonFluentFlexTokens holds the values that
    have no Fluent source and is layered under defaultFlexTokens.
  • Observed. flex-token-map.yaml records, per Flex token, both the generic
    source and the Fluent source (global.*, alias.*, shadow.* prefixes
    against @fluentui-react-native/design-tokens-win32/colorful), and documents
    the matching rules used for spacing, radius, stroke, font size, line height,
    font weight, and font family
    (flex-token-map.yaml).
    It also states that nonFluentFlexTokens must be hand-synchronized with the
    unmapped destinations.

Upstream de-duplication model (x3-design/fluent-design at d334acf)

  • Observed. x3 solves the exact problem this task names, by layering rather
    than by per-theme value tables. dev/web/flex-tokens/mappings.json holds 640
    primitives (574 distinct values) shared by every mode, and 264 generics that
    reference them by name.
  • Observed. Of those 264 generics, 142 carry a single mode-invariant
    primitive reference and 116 carry a primitive_light/primitive_dark pair;
    6 are layered composites. No generic stores a literal color per mode, so a
    value used by both light and dark exists once in the primitive table.
  • Observed. Interaction values are the one place upstream does store a
    per-mode literal pair: mappings.interaction has 118 entries, each with a
    light and a dark hex, and they are emitted only into the opt-in
    interaction-fallback*.css files rather than into the base theme.
  • Inferred. A FURN generator that emits a shared primitive constant table
    plus per-appearance reference tables reproduces the upstream structure and
    satisfies the "shared across themes are not duplicated" requirement, and it
    composes with the platform and platform-group factoring that
    scripts/src/codegen/json.ts already performs.

Scope

  • Extend the codegen to alias and shadow token JSON in addition to global token
    JSON.
  • Generate the Flex default token sets from the Fluent token packages using the
    mapping recorded in flex-token-map.yaml, replacing the hand-authored literal
    tables in defaultTokens.ts.
  • Add appearance as a de-duplication axis so a value shared by light, dark, and
    high contrast is declared once and referenced from each appearance file,
    matching the existing platform and platform-group behavior.
  • Keep emitting individual export const bindings rather than object literals
    wherever a value can be referenced directly, so unused values can be dropped.
  • Preserve the documented provenance: generated files must remain traceable to
    the mapping file and the source token package.
  • Record a bundle-size baseline before the change and the resulting size after.

Out of scope

Deliverables

  1. Extended codegen in
    packages/agentic/design/scripts/codegen.cts
    and, where the logic is shared, in
    scripts/src/codegen, covering alias and
    shadow tokens and multiple appearances.
  2. Generated Flex default value modules replacing the hand-authored literal
    tables in defaultTokens.ts.
  3. Appearance-aware de-duplication that emits shared values once.
  4. Tests for the codegen de-duplication behavior, including an appearance case.
  5. A recorded before-and-after size measurement for a representative bundle.
  6. Changesets for the design package and, if changed,
    @fluentui-react-native/scripts.

Acceptance criteria

  • yarn prebuild in the design package regenerates every default value
    module, and re-running it produces no diff.
  • No hand-maintained color literal table remains in
    packages/agentic/design/src/tokens; values that genuinely have no Fluent
    source are either generated from a checked-in source file or explicitly
    listed with a recorded reason.
  • A value shared by two or more appearances is declared once and re-exported
    or referenced, verified by a codegen test.
  • Generated modules expose individually named constants that a bundler can
    drop when unused.
  • The resolved Flex token values for the current default appearance are
    unchanged, verified by existing design and component tests.
  • A bundle-size measurement is recorded for the same scenario before and
    after, using yarn bundle:repo or another cited repeatable command.
  • yarn build, yarn lage test, and yarn lage lint pass at the
    repository root, and changesets are present.

Dependencies and ordering

Risks and open decisions

  • Open decision. Which appearances to generate. Observed: the installed
    packages ship macOS light/dark/hclight/hcdark and win32
    colorful/darkgray/hc/black, and AppearanceOptions in
    Theme.types.ts
    names light, dark, darkElevated, and highContrast; the two sets do not
    correspond one to one.
  • Open decision. Whether the generated defaults keep win32 colorful as the
    default platform and appearance. Observed: json.ts hard-codes
    defaultPlatform = 'win32', and AGENTS.md directs
    using Win32, macOS, or Windows as the canonical cross-platform reference
    rather than iOS.
  • Open decision. Whether theme-tokens should be re-pointed at the
    generated constants as part of this task or left to
    Package Consolidation. Its module-scope JSON
    imports are the main remaining whole-object load.
  • Risk. Generating from flex-token-map.yaml requires the mapping to be
    complete and current; the file itself documents that some destinations have no
    Fluent equivalent (null entries, letterSpacing, serif font family) and that
    synchronization is manual today.
  • Risk. Aggressive de-duplication across appearances can produce re-export
    chains that are correct at type level but confusing to read; the existing
    subset files are the precedent to follow for readability.
  • Risk. Upstream is alpha and pins are exact by instruction
    (@x3-design/flex-tokens@0.9.0, @x3-design/flex-themes@0.8.0). If FURN
    generates from upstream structure, the source commit should be recorded so
    drift is detectable; see the
    suggested addition on upstream drift.
  • Risk. Bundle-size claims are unverifiable without a baseline. If no
    repeatable measurement is established, the production criterion cannot be
    closed.

Evidence and references

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions