feat(core): add support for CSS modules to dev server + lib build #7650
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Don't be fooled by the line/file count, this PR was the product of a full 1.5 days of discovery and glorious learning.
At the most recent UI SIG meeting, we aligned around the idea of experimenting with CSS Modules in a manner that would limit the blast radius of changes in the event we decided to change course after experimentation. The current thinking is that we'll:
@spinnaker/core
(but not across all packages)This PR accomplishes 1 in what I hope is a manner that will work as desired even as we move into other packages and potentially share styles between packages. The two primary components we need are the ability to parse CSS Module files and load them properly, and the ability to have TypeScript know about the CSS Modules being imported.
For loading CSS Modules, the standard webpack
css-loader
is the preferred method and works great. You just turn it on and it does the thing. So that's what I did.For TypeScript support, the story is less straightforward. Some folks only go as far as declaring a blanket type of
{ [className: string]: string }
for all CSS module files, which means you don't get autocomplete and TS can't alert you to broken usages of class names when things change (bad). The most popular way of getting real TS support is to emit.d.ts
files for each CSS file, but in order for TS to properly handle the declaration files they have to be co-located next to source files. Some folks take the stance of generating the files and checking them into source, then verifying they're correct in a CI step because they want to parallelize TS + webpack, but that seems pretty heavy and like overkill for us. Create React App takes the approach of using a TypeScript plugin which is great, but plugins only work at the local dev flow level and don't have any impact during actual compilation.The approach I took instead was to generate declaration files as part of the dev server build so things work during dev, and use the corresponding CLI tool to output defs in the library build before TypeScript runs so it has the defs available when type checking. With both of those in place it's possible to put the declaration files in
.gitignore
, so while they will show up in your local dev environment they won't make diffs noisy or require a confusing generate + verify workflow. A couple other things:amazon
still build properly now that core has module shenanigans going on? Yes, because the library build ofcore
does all the bundling, nothing really changes from the perspective of other packages. There's still the usual compiled CSS (which will end up having some auto-generated class names), and the magic strings that come with CSS Module imports are bundled into thelib.js
file inline. The CSS Module files themselves are omitted from the built output just like our existing less/css files today.core
from a package likeamazon
— will that work in the future if we want it? Yes, because in order to consume something fromcore
it has to be explicitly exported incore
. So if I wanted to share some reusable class names outsidecore
, webpack would take the bundled auto-generated class names and make them available incore
's exports for other code to consume and the TS compiler would take the types from the.d.ts
file and use them to define the type of the export. e.g.:Modal.tsx (source):
Modal.d.ts (built type def):