feat: Migrate SCSS to plain CSS with PostCSS, replace Stylelint with Biome#9584
Open
feat: Migrate SCSS to plain CSS with PostCSS, replace Stylelint with Biome#9584
Conversation
✅ Deploy Preview for label-studio-docs-new-theme canceled.
|
✅ Deploy Preview for label-studio-playground canceled.
|
✅ Deploy Preview for heartex-docs canceled.
|
✅ Deploy Preview for label-studio-storybook canceled.
|
Replace sass-loader with postcss-nested in the webpack build pipeline. This removes the Sass compiler dependency and processes all .scss files through PostCSS instead. Changes: - Remove sass-loader from SCSS webpack rules - Add postcss-nested plugin for Sass-compatible & nesting - Add explicit .scss extensions to all @import statements (required by postcss-import) - Replace @humansignal/ui alias imports with relative paths (postcss-import doesn't use webpack aliases) - Convert remaining // comments to /* */ block comments - Fix missing semicolons before nested rules Made-with: Cursor
Rename all CSS module files from .module.scss to .module.css and update all import references. These files no longer contain any SCSS syntax and are processed purely by PostCSS. 72 files renamed, 83 import paths updated. Made-with: Cursor
Rename all non-module .scss files to .prefix.css and update all import references. Update webpack config to match .prefix.css files with the existing SCSS processing rules (prefix class names), and exclude them from the regular CSS rule to prevent double-processing. Also apply custom CSS module localIdentName to the CSS rule for .module.css files. 171 files renamed, 214 import paths updated. Made-with: Cursor
- Switch stylelint from SCSS configs to standard CSS configs - Update lint script glob from *.scss to *.css - Update CI workflow stylelint glob - Update pre-commit hook for CSS files - Update TypeScript CSS modules plugin matcher - Update Storybook webpack config for .prefix.css handling Made-with: Cursor
The Nx-generated rule combines CSS and SCSS into one rule with oneOf. Keep .css in the outer test so .module.css and .css files are processed. Add exclude on the non-module .css oneOf rules to prevent .prefix.css from being matched before the SCSS/prefix oneOf. Production build CSS output verified byte-for-byte identical to baseline. Made-with: Cursor
- Remove sass, sass-loader, stylelint-config-standard-scss as direct deps - Add resolutions to stub out sass, sass-embedded, stylus, less, less-loader - Rewrite webpack config to find CSS rules directly (no SCSS detection) - Fix css-loader matching bug (postcss-loader contains "css-loader" substring) - Change nx.json generators from style: scss to style: css - Update jest configs, gitignore, pre-commit hooks, README, storybook config - Remove obsolete SCSS tools (validate-scss-transform, fix-bare-nesting) - Update design-tokens-converter output path to .prefix.css Made-with: Cursor
- Remove stylelint and stylelint-config-tailwindcss packages - Delete .stylelintrc.json and .github/workflows/stylelint.yml - Remove stylelint from CI pipelines (cicd_pipeline, cicd_pipeline_develop) - Remove stylelint from pre-commit hooks - Enable Biome CSS linter with cssModules parser support - Disable noisy CSS rules (noUnknownTypeSelector, noDescendingSpecificity) - Downgrade existing-code CSS issues to warnings for incremental fixing - Fix minor CSS parse errors (missing semicolon, stray semicolon, comma in :global) - Update lint-css script to use biome instead of stylelint Made-with: Cursor
70e5852 to
d8d91c0
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Complete migration from SCSS (Sass) to plain CSS with PostCSS, eliminating the Sass compiler dependency from the build pipeline and replacing Stylelint with Biome for CSS linting.
Impact at a Glance
yarn.locklines removedsass,sass-loader,stylelint,stylelint-config-standard-scss,stylelint-config-tailwindcss)resolutions→empty-npm-packagestylelint.yml— 36 lines)validate-scss-transform.js,fix-bare-nesting.js— 616 lines).stylelintrc.json,.stylelintignore,.github/workflows/stylelint.yml).module.scss→.module.css, 168.scss→.prefix.css)sass-loaderDeprecation Warnings)Build Warnings: 6 → 0
On
develop, every production build emits 6 webpack warnings — all fromsass-loader:On this branch the build compiles with 0 warnings —
sass-loaderis gone, so the deprecation warnings are gone with it.Build Pipeline
sass-loaderforpostcss-nested: All.scssfiles are now plain CSS processed by PostCSS with thepostcss-nestedplugin for Sass-compatible&nestingsass-loader,stylus-loader, andless-loaderfrom any Nx-generated rulescss-loaderdetection bug: Changedincludes("css-loader")toincludes("/css-loader/")becausepostcss-loader's path contains the substring "css-loader", which causedmodulesoptions to be incorrectly applied to PostCSS loaderFile Renames
.module.scss→.module.css: CSS Modules files (hashedlocalIdentName).scss→.prefix.css: BEM-prefixed files (predictablelsf-[local]class names)Dependency Removal
sass,sass-loader,stylelint,stylelint-config-standard-scss,stylelint-config-tailwindcsspostcss-nested(lightweight — single PostCSS plugin)resolutions:sass,sass-embedded,stylus,less,less-loader,stylelint,stylelint-config-tailwindcss,stylelint-config-standard→npm:[email protected]Linting: Stylelint → Biome
.stylelintrc.json,.stylelintignore,.github/workflows/stylelint.ymlcicd_pipeline.yml,cicd_pipeline_develop.yml), pre-commit hookscssModulesparser supportnoUnknownTypeSelector,noUnknownPseudoClass,noDescendingSpecificity(off)noUnknownMediaFeatureName,noUnknownProperty,noDuplicateProperties,noShorthandPropertyOverridesPersonalAccessToken.module.css, stray semicolon inTaxonomy.module.css, comma inside:global()inTaxonomy.prefix.cssTooling & Config Updates
nx.json:style: scss→style: cssfor all generatorsjest.config: Module name mappers updated fromscss|sass|lesstocss.gitignore: Removed.sass-cacheand.scss-snapshots/design-tokens-converter: Output path changed fromtokens.scsstotokens.prefix.cssvalidate-scss-transform.js(296 lines),fix-bare-nesting.js(320 lines)extract-antd-no-reset.mjs: Removedstylelint-disablefrom generated headercss-loaderdetection fix appliedpostcss.config.js: Addedpostcss-nestedpluginDocumentation
.cursor/rules/:design.mdc,tailwind.mdc,react.mdcupdated (SCSS → CSS,.module.scss→.module.css,tokens.scss→tokens.prefix.css, stylelint → biome)DESIGN.md: All SCSS references updated, code blocks changed fromscsstocssweb/libs/ui/README.md: SCSS → CSS in migration instructionsweb/README.md:yarn lint-scss→yarn lint-cssHow CSS Output Equivalence Was Validated
A rigorous multi-step process confirmed that the compiled CSS output is functionally identical before and after the migration:
Step 1: Baseline Capture (before changes)
git stash npx nx reset npx nx build labelstudio --skip-nx-cache cp dist/apps/labelstudio/static/css/*.css /tmp/css-before/Step 2: Post-Migration Build
git stash pop npx nx reset npx nx build labelstudio --skip-nx-cache cp dist/apps/labelstudio/static/css/*.css /tmp/css-after/Step 3: Normalized Comparison
CSS output differs in chunk hashes (webpack content-hashing changes when file extensions change), so a normalization step was applied:
Step 4: Diff Analysis
Result: Zero functional differences. The only cosmetic differences found were:
rgba(r,g,b,1)vs PostCSS outputsrgb(r,g,b)— functionally identical per CSS spec:not()selector: Sass expands:not(.a.b)to:not(.a):not(.b)vs PostCSS preserves:not(.a.b)— functionally identical per CSS Selectors Level 4\a0escape vs PostCSS outputs literalU+00A0byte — confirmed identical viaxxdhex dumpStep 5: Hash Verification
After normalization, MD5 hashes of all CSS bundles were compared and matched.
Everyday Developer Impact
What changes for you
.module.scssfor component styles.module.css— same syntax, nesting still works viapostcss-nested.scssfor BEM-prefixed styles.prefix.css— samelsf-[local]prefixing behavioryarn lint-scssyarn lint-css(uses Biome now).stylelintrc.jsonbiome.jsonundercsssection@importwith aliases@import(postcss-import doesn't resolve webpack aliases)What stays the same
@applyTailwind directives work identicallyimport styles from './foo.module.css') work identicallyimport './foo.prefix.css'withcn("block")) works identically:global()pseudo-class works identically (it's part of the CSS Modules spec, not Sass)&works identically (viapostcss-nested)Test Plan
npx nx build labelstudiosucceeds with zero warnings (except expected svg export warning)npx biome check .passes with zero errors.scssor.sassfiles remain in the repository:global()pseudo-class continues working (CSS Modules spec, not Sass)