Skip to content

fix(web-components): use lib replacement for web types#32129

Closed
radium-v wants to merge 9 commits intomicrosoft:masterfrom
radium-v:users/radium-v/web-types
Closed

fix(web-components): use lib replacement for web types#32129
radium-v wants to merge 9 commits intomicrosoft:masterfrom
radium-v:users/radium-v/web-types

Conversation

@radium-v
Copy link
Contributor

@radium-v radium-v commented Jul 26, 2024

Previous Behavior

The type declaration files generated for the @fluentui/web-components package are incorrectly including reference tags (/// <reference types="web" />), which creates issues for projects which use @fluentui/web-components as a dev dependency.

Many new browser features we're using (like the Popover API) are sometimes too new to rely on the DOM types provided with the version of TypeScript in the repository, but these types are expected to be included with TypeScript over time, so we don't want to include them as a types reference.

New Behavior

This PR uses lib replacement to replace the built-in @typescript/lib-dom package with @types/web, which prevents /// <reference types="web" /> tags from being included in declaration files. The replaced types package is only included in the web-components/package.json and is added to the nohoist list to prevent it from being referenced outside of the web-components package.

Additional changes:

  • Fixed a minor type issue with the MatchMediaStyleSheetBehavior utility class.

@radium-v radium-v requested review from a team as code owners July 26, 2024 18:39
@fabricteam
Copy link
Collaborator

fabricteam commented Jul 26, 2024

🕵 fluentui-web-components-v3 No visual regressions between this PR and main

@fabricteam
Copy link
Collaborator

fabricteam commented Jul 26, 2024

Perf Analysis (@fluentui/react-components)

No significant results to display.

All results

Scenario Render type Master Ticks PR Ticks Iterations Status
Avatar mount 658 635 5000
Button mount 309 319 5000
Field mount 1142 1151 5000
FluentProvider mount 725 693 5000
FluentProviderWithTheme mount 88 87 10
FluentProviderWithTheme virtual-rerender 32 34 10
FluentProviderWithTheme virtual-rerender-with-unmount 84 78 10
MakeStyles mount 871 861 50000
Persona mount 1779 1753 5000
SpinButton mount 1418 1384 5000
SwatchPicker mount 1701 1737 5000

@fabricteam
Copy link
Collaborator

fabricteam commented Jul 26, 2024

🕵 fluentuiv8 No visual regressions between this PR and main

@fabricteam
Copy link
Collaborator

fabricteam commented Jul 26, 2024

📊 Bundle size report

✅ No changes found

@fabricteam
Copy link
Collaborator

fabricteam commented Jul 26, 2024

🕵 FluentUIV0 No visual regressions between this PR and main

@fabricteam
Copy link
Collaborator

fabricteam commented Jul 26, 2024

Perf Analysis (@fluentui/react-northstar)

Perf tests with no regressions
Scenario Current PR Ticks Baseline Ticks Ratio
AttachmentMinimalPerf.default 91 78 1.17:1
AvatarMinimalPerf.default 114 103 1.11:1
PortalMinimalPerf.default 93 85 1.09:1
RadioGroupMinimalPerf.default 282 258 1.09:1
FlexMinimalPerf.default 166 154 1.08:1
FormMinimalPerf.default 235 219 1.07:1
SegmentMinimalPerf.default 203 189 1.07:1
CardMinimalPerf.default 321 304 1.06:1
DividerMinimalPerf.default 208 196 1.06:1
PopupMinimalPerf.default 367 347 1.06:1
TableMinimalPerf.default 239 225 1.06:1
CarouselMinimalPerf.default 269 255 1.05:1
ChatMinimalPerf.default 462 440 1.05:1
ChatWithPopoverPerf.default 209 199 1.05:1
ListWith60ListItems.default 364 348 1.05:1
DialogMinimalPerf.default 469 451 1.04:1
ImageMinimalPerf.default 234 226 1.04:1
LabelMinimalPerf.default 221 213 1.04:1
AnimationMinimalPerf.default 307 297 1.03:1
AttachmentSlotsPerf.default 638 617 1.03:1
DropdownMinimalPerf.default 1454 1408 1.03:1
ProviderMergeThemesPerf.default 670 651 1.03:1
SliderMinimalPerf.default 760 741 1.03:1
SplitButtonMinimalPerf.default 2281 2225 1.03:1
StatusMinimalPerf.default 400 387 1.03:1
IconMinimalPerf.default 385 373 1.03:1
CustomToolbarPrototype.default 1477 1435 1.03:1
AlertMinimalPerf.default 161 158 1.02:1
ButtonSlotsPerf.default 301 296 1.02:1
CheckboxMinimalPerf.default 1158 1140 1.02:1
TextAreaMinimalPerf.default 293 288 1.02:1
DatepickerMinimalPerf.default 3576 3556 1.01:1
DropdownManyItemsPerf.default 399 396 1.01:1
HeaderSlotsPerf.default 478 472 1.01:1
LayoutMinimalPerf.default 202 200 1.01:1
RosterPerf.default 1647 1623 1.01:1
ButtonOverridesMissPerf.default 656 653 1:1
GridMinimalPerf.default 189 189 1:1
ItemLayoutMinimalPerf.default 721 719 1:1
ListNestedPerf.default 330 329 1:1
ProviderMinimalPerf.default 205 204 1:1
ToolbarMinimalPerf.default 537 537 1:1
VideoMinimalPerf.default 435 434 1:1
ChatDuplicateMessagesPerf.default 149 150 0.99:1
EmbedMinimalPerf.default 1850 1868 0.99:1
InputMinimalPerf.default 546 549 0.99:1
SkeletonMinimalPerf.default 195 197 0.99:1
TableManyItemsPerf.default 1107 1116 0.99:1
TextMinimalPerf.default 194 196 0.99:1
ListMinimalPerf.default 308 313 0.98:1
MenuMinimalPerf.default 501 509 0.98:1
ReactionMinimalPerf.default 210 215 0.98:1
TooltipMinimalPerf.default 1298 1321 0.98:1
BoxMinimalPerf.default 191 197 0.97:1
AccordionMinimalPerf.default 85 89 0.96:1
MenuButtonMinimalPerf.default 951 988 0.96:1
TreeMinimalPerf.default 482 502 0.96:1
ListCommonPerf.default 381 405 0.94:1
LoaderMinimalPerf.default 188 201 0.94:1
RefMinimalPerf.default 102 108 0.94:1
HeaderMinimalPerf.default 193 208 0.93:1
ButtonMinimalPerf.default 84 91 0.92:1
TreeWith60ListItems.default 81 92 0.88:1

@fabricteam
Copy link
Collaborator

fabricteam commented Jul 26, 2024

Perf Analysis (@fluentui/react)

No significant results to display.

All results

Scenario Render type Master Ticks PR Ticks Iterations Status
BaseButton mount 636 667 5000
Breadcrumb mount 1742 1689 1000
Checkbox mount 1695 1752 5000
CheckboxBase mount 1530 1477 5000
ChoiceGroup mount 3007 2968 5000
ComboBox mount 691 661 1000
CommandBar mount 6685 6641 1000
ContextualMenu mount 13166 13051 1000
DefaultButton mount 779 801 5000
DetailsRow mount 2233 2267 5000
DetailsRowFast mount 2233 2282 5000
DetailsRowNoStyles mount 2046 2052 5000
Dialog mount 2812 2791 1000
DocumentCardTitle mount 246 239 1000
Dropdown mount 2062 2028 5000
FocusTrapZone mount 1168 1168 5000
FocusZone mount 1133 1112 5000
GroupedList mount 38746 43398 2
GroupedList virtual-rerender 20936 21152 2
GroupedList virtual-rerender-with-unmount 53484 53013 2
GroupedListV2 mount 239 251 2
GroupedListV2 virtual-rerender 225 225 2
GroupedListV2 virtual-rerender-with-unmount 239 237 2
IconButton mount 1182 1169 5000
Label mount 341 358 5000
Layer mount 2837 2779 5000
Link mount 420 407 5000
MenuButton mount 975 998 5000
MessageBar mount 22242 22219 5000
Nav mount 2062 2080 1000
OverflowSet mount 832 807 5000
Panel mount 1876 1815 1000
Persona mount 754 760 1000
Pivot mount 947 913 1000
PrimaryButton mount 949 953 5000
Rating mount 4732 4881 5000
SearchBox mount 950 977 5000
Shimmer mount 1908 1955 5000
Slider mount 1358 1328 5000
SpinButton mount 3021 2995 5000
Spinner mount 409 408 5000
SplitButton mount 1943 1940 5000
Stack mount 438 432 5000
StackWithIntrinsicChildren mount 874 897 5000
StackWithTextChildren mount 2822 2825 5000
SwatchColorPicker mount 6536 6559 5000
TagPicker mount 1489 1486 5000
Text mount 398 408 5000
TextField mount 955 952 5000
ThemeProvider mount 884 886 5000
ThemeProvider virtual-rerender 604 593 5000
ThemeProvider virtual-rerender-with-unmount 1332 1340 5000
Toggle mount 644 649 5000
buttonNative mount 203 207 5000

@radium-v radium-v requested a review from TristanWatanabe July 29, 2024 20:31
@tudorpopams tudorpopams requested a review from Hotell July 30, 2024 12:02
Copy link
Contributor

@Hotell Hotell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

package are incorrectly including reference tags (/// ), which creates issues for projects which use @fluentui/web-components as a dev dependency.

this seems to me like issue in user land. folks should use skipLibCheck:true in their TS configs

the upgrade from TypeScript 4 to 5 exposed a tsconfig configuration error with the moduleResolution field.

what kind of error ? AFAIR moduleResolution node16 follows same resolution as
nodeNext


I already provided review on this approach some time ago #31807 (review)

this introduces another complex setup to our repo which should be avoided.

if you are ok with shipping leaking global types without proper type definitions to your users I'd suggest to write a simple script that removes those type references after build step from .d.ts files.

Note: it seems menu and menu-item expose these web types to public API surface

quickly checking the implementation code indeed those types do leak:
image

Summary:

  • we should not land these monorepo setup changes
  • there are multiple patterns how to go around these limitations

@davatron5000
Copy link
Contributor

Created issue for leaking types #32194

@radium-v radium-v force-pushed the users/radium-v/web-types branch from 645195e to 66ee4b8 Compare August 2, 2024 02:30
@radium-v
Copy link
Contributor Author

radium-v commented Aug 2, 2024

Hi @Hotell,

Using skipLibCheck to resolve conflicts with the TypeScript standard library is discouraged, as stated in the documentation for skipLibCheck. It's recommended to use lib replacement.

I reverted the moduleResolution changes shortly after creating the PR (I missed a reference which I have now reverted as well, and updated the PR comment to remove that item). It's been a recurring issue that the module and moduleResolution fields show misconfiguration errors in my editor. I'm sorry for any confusion there.

We're not okay with shipping leaking types, which is what this PR is specifically intended to address.

Writing a custom script to do what a few simple, standard, and recommended configuration adjustments already do, in a built-in way, doesn't make sense to me.

Comment on lines -368 to +371
"@fluentui/web-components/@storybook/html"
"@fluentui/web-components/@storybook/html",
"@fluentui/web-components/@typescript/lib-dom"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @typescript/lib-dom package is included in nohoist, since @types/web breaks in packages which target ES5. This ensures that this types override is scoped only to the web components package.

https://github.com/microsoft/TypeScript-DOM-Lib-Generator?tab=readme-ov-file#typeslib-minimum-target
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this - makes sense!

@radium-v radium-v force-pushed the users/radium-v/web-types branch from 66ee4b8 to 1d6e88d Compare August 2, 2024 19:53
@radium-v
Copy link
Contributor Author

radium-v commented Aug 2, 2024

I reduced the changes down to only those related to the lib replacement, and fixing the type error in a utility class.

@davatron5000
Copy link
Contributor

I started looking into the leaking types issue and couldn't quite replicate your red squiggles, but after talking with @radium-v it seems that this PR would help fix the issue with ToggleEvent (a new but valid web platform event object type).

As mentioned in the initial issue...

  • /// <reference types="web" /> is causing some issues in downstream consumers.
  • Downstream consumers could add @types/web to their tsconfigs, but if they're using newer versions of TS (5.5) they'll run into duplicate type errors.
  • We can't tell consumers to use skipLibCheck
  • The skipLibCheck documentation seems to push people away from using skipLibCheck to solve this usecase.
  • The skipLibCheck documentation is pushing us towards using the lib replacement strategy
  • The @types/web documentation is also pushing us towards the lib replacement strategy

Could explain more about the concerns you have for the lib-replacement strategy landing in the web-components package? I'd like to fully understand the problems and perceived complexity.

Copy link
Member

@chrisdholt chrisdholt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple questions to confirm

Comment on lines -368 to +371
"@fluentui/web-components/@storybook/html"
"@fluentui/web-components/@storybook/html",
"@fluentui/web-components/@typescript/lib-dom"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this - makes sense!

@Hotell
Copy link
Contributor

Hotell commented Aug 9, 2024

folks, quick tip: if you wanna another review please make sure to hit the refresh button, otherwise reviewers will never get notification that there has been some movement. ty !
image

@Hotell
Copy link
Contributor

Hotell commented Aug 9, 2024

/// is causing some issues in downstream consumers

yes I'm aware what problem are we trying to solve.

my proposal how to mitigate this is postprocessing within build step -> simple script to remove those reference pragmas, instead going route against monorepo setup and practices set in place ( NOTE: it's ok to opt out short term, if there is no clear workaround - which is not this case )

🐞 NOTE: as I mentioned my(your) proposal will not mitigate leaking invalid public API problem.

We can't tell consumers to use skipLibCheck

true, but I'd suggest you to start encourage them to do so. Not using skipLibCheck:true in applications/feature teams adds performance penalty of invoking tsc without gaining most of the time any benefits.

To add, our repo uses skipLibCheck in all packages. only use-case where this might matter to you is when you are authoring and shipping a library which has TS type emit as public api output and you commit to follow semver with it ( not application ) - which is our case ( we disable skipLibCheck when we generate public api interfaces -> .d.ts to make sure we don't ship something that might break another library that builds on ours )

seems to push people away

I guess everyone might perceive it differently, I don't see any "push aways" in those official docs. It would be nice to mention the perf and general recommendations in these docs indeed.

The leaking types issue:

  • we remove ref=types pragmas
  • some extension of wc library install fluent package
  • that team is not using skipLibCheck and they use some older version of typescript that doesn't ship the new DOM api interfaces as part standard lib
  • they gonna import in their production code one of the WC api that use those NEW DOM interfaces as part of its public API
// NOTE: super contrived example
import {Menu} from '@fluentui/web-components'

const menu = new Menu()

// 🚨 TS error
menu.toggleHandler(....)
  • tsc will fail

hope this answers your questions @davatron5000 . happy to discuss further if needed. ty

@davatron5000
Copy link
Contributor

I can "fix" the ToggleEvent issue by rewriting the toggleHandler to not use the appropriate type and just use Event. It sort of defeats the entire purpose of TypeScript, but hopefully that unblocks us there.

That said, the web-components project will always be a bit ahead of the curve on web platform feature we use, so the ToggleEvent issue a convenient example of where we're ahead of the current monorepo version of TS in terms of supported web APIs.

Per our hours of investigation into the issue...

  • The types: ["web"] method is a mostly undocumented way of extending web types for TS <=4.4. (see docs)
    • The TS <=4.4 docs are hidden in a dropdown and don't mention the types array technique, but instead recommend using lib: ['SomeESTarget'] or removing lib: ["dom"] if lib already exists.
  • Since TS 4.5+, TypeScript and @types/web recommends using the lib-replacement method for extending web types.

We did some experiments yesterday and made the exact same demo repro you posted today and verified the tsc compiler couldn't build in the downstream project. Unfortunately, post-processing removing the /// references pragma from all the .d.ts files only half-solved our problem. Downstream consumers were getting duplicate type errors for all types (web-components' types/web + local-repo's lib-dom types). The easiest way for us to resolve that issue was to use lib-replacement. @radium-v and I can work up a demo/video if that would help demonstrate the issue.

With lib-replacement, the leaking types issue only surfaces if a downstream consumer tries to overrride/extend a function that uses newer types. At that point they are using newer types and would need to upgrade their lib-dom types to web-types. For downstream consumers in that situation (TS <= 5.4) we would not recommend skipLibCheck (per the docs) and recommend the lib-replacement strategy (per the docs). So for us, all paths lead us to lib-replacement.

If the web-component package's use of lib-replacement for web types with nohoist guards in effect breaks other systems in the monorepo in some way, please let us know. But we've done our due diligence here and are trying abide in the one-version policy while not impacting other projects but still allowing us to leverage new web technologies.

@Hotell
Copy link
Contributor

Hotell commented Aug 14, 2024

types or dependency alias?:

whatever those docs say or encourage, its not completely "up to date/accurate" (also note those docs are 3 years old).

In typescript, if you're dealing with global overrides/extensions , you should use types setting. That's the main purpose of this configuration we have set in place for whole monorepo. If you don't specify types explicitly, typescript will include in the Program anything that lives within node_modules including node_modules/@types.

global leaking types:

That menu event is only one case, there are other occurences in your source code.

Related to this, the proper API contract that types enforces here is actually giving library authors ( us ) proper public API surface information in terms of what are we shipping to users.

💡 If your source code had no issues with leaking these global types, that are not supported in every version of typescript, those reference pragmas would not be emitted at all = your .d.ts contract is solid.

🚨 If you use dependency alias (as you propose in this PR) you'll never know about this problem (only if consumers would start creating issues).

solution/recap:

  • we (v-build) don't wanna maintain unnecessary nohoist
  • you can recommend any kind of typescript configuration practices to your users that you see fit for your packages
  • you can use any kind of @types/web as needed
    • this package should live in root (single version policy -> removed from web-components dependencies), unless other packages need something different ( probably this will never happen )
    • to enable web DOM api overrides, explicit usage of types is needed across any monorepo project IFF necessary
  • to mitigate shipping leaking types a simple checker should be build that would fail build if global overrides pragmas referencing @types/web are present in public API surface of .d.ts

happy to talk further

@chrisdholt
Copy link
Member

Closing this in favor of #32308

@chrisdholt chrisdholt closed this Aug 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants