Skip to content

Pass the modern style Sass option instead of the legacy outputStyle - #1528

Merged
Kocal merged 1 commit into
symfony:mainfrom
X-Coder264:fix-sass-modern-style-option
Aug 9, 2026
Merged

Kocal merged 1 commit into
symfony:mainfrom
X-Coder264:fix-sass-modern-style-option

Conversation

@X-Coder264

Copy link
Copy Markdown
Contributor
Q A
Bug fix? yes
New feature? no
Deprecations? no
Issues Fix #1527
License MIT

lib/loaders/sass.ts passes sassOptions: { outputStyle: 'expanded' }, but outputStyle belongs to the legacy Sass JS API (render() / renderSync()). sass-loader 16 and 17 — the range Encore declares (^16.0.1 || ^17.0.0) — only support the modern API, where the option is named style, and unknown keys are ignored by compileStringAsync(). Encore's value therefore had no effect and sass-loader's own production default applied instead (src/utils.js#L285-L288):

// opt.outputStyle
if (!sassOptions.style && isProductionLikeMode(loaderContext)) {
  sassOptions.style = "compressed";
}

So in encore production Sass minified the CSS itself, which is what the comment (and #638 / #639) says Encore leaves to the CSS minimizer. This PR just uses the option name the modern API actually reads.

Why it matters beyond output formatting

Dart Sass prepends a UTF-8 BOM (instead of @charset "UTF-8") when compressed output contains non-ASCII characters — common in practice, e.g. any project importing bootstrap-sass glyphicons. Until postcss 8.5.23 that BOM was silently dropped when enablePostCssLoader() was used; postcss 8.5.24 changed to "Preserve the BOM after the processing", so it now reaches the CSS minimizer, and lightningcss cannot parse a leading BOM (parcel-bundler/lightningcss#338) — it lexes U+FEFF as an ident, so BOM + license comment + first selector fails with Unexpected token Ident("html"). That breaks configureCssMinimizerPlugin((options, MinimizerPlugin) => { options.minify = MinimizerPlugin.lightningCssMinify; }), which UPGRADE.md recommends as the Encore 7 replacement for the removed default CSS minifier.

With this change Sass emits @charset "UTF-8"; instead of the BOM, which every CSS minifier parses:

$ od -c build/app.css   # h2 { content: "→"; } compiled with encore production
0000000   @   c   h   a   r   s   e   t       "   U   T   F   -   8   "
0000020   ;  \n   h   2       {  \n           c   o   n   t   e   n   t
0000040   :       " 342 206 222   "   ;  \n   }  \n

Behaviour change worth flagging

Projects that have not opted into CSS minification will now genuinely ship unminified CSS from Sass entries in production, where Sass used to compress it for them by accident. That matches what UPGRADE.md already documents for 7.0 ("CSS minification is no longer enabled by default … otherwise CSS is not minified in production"), so I have treated it as the bug rather than as intended behaviour — but tell me if you would like a CHANGELOG.md entry and/or an UPGRADE.md note and I will add it.

Two things I deliberately left out, happy to add either if you disagree:

  • charset: false — projects that want a minifier to keep leading /*! … */ license banners need it, because lightningcss only preserves loud comments at the very start of the file and @charset now sits in front of them. That felt like a user-side choice rather than an Encore default, since it drops the encoding declaration for everyone.
  • Any deprecation path for outputStyle — since sass-loader 16 dropped the legacy API, there is no supported configuration in which the old name did anything, so there is nothing to deprecate.

Tests

  • test/loaders/sass.js — the existing options-callback assertion is updated, and getLoaders() basic usage now asserts the default sassOptions explicitly, so a legacy option name cannot silently come back.
  • test/functional.js — new production-mode test asserting Sass does not minify the CSS itself (h2 { survives in the emitted asset).

pnpm vitest run test/loaders/sass.js, pnpm type-check, pnpm lint and pnpm fmt:check all pass locally. I could not run test/functional.js in my environment (no Chrome for the Puppeteer beforeAll), so I verified that test's expectation by building the fixture through the patched Encore directly: h2 { … } expanded before this change was h2{…}.

@carsonbot carsonbot added Status: Reviewed Has been reviewed by a maintainer and removed Status: Needs Review Needs to be reviewed labels Aug 1, 2026
Comment thread lib/loaders/sass.ts Outdated
outputStyle: 'expanded',
// CSS minification is handled by the CSS minimizer, not by Sass.
// `style` is the modern Sass API option, the legacy `outputStyle`
// is ignored by sass-loader.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The part about outputStyle should be removed. A comment in the code is meant to explain the code, not the diff producing it (explaining the diff is the job of the commit message)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, done — trimmed the comment down to just what the code does, leaving the diff rationale to the commit message.

`outputStyle` belongs to the legacy Sass JS API. sass-loader 16 and 17 only
support the modern API, where that option is named `style`, so the value was
silently ignored and sass-loader applied its own production default of
`style: compressed` instead. Sass therefore minified the CSS itself in
production, which is exactly what the comment says Encore leaves to the CSS
minimizer, and it made Dart Sass prefix bundles containing non-ASCII
characters with a UTF-8 BOM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@X-Coder264
X-Coder264 force-pushed the fix-sass-modern-style-option branch from 28aa8a2 to cb808c1 Compare August 6, 2026 16:52
@Kocal
Kocal merged commit 9d2120d into symfony:main Aug 9, 2026
43 of 73 checks passed
@Kocal

Kocal commented Aug 9, 2026

Copy link
Copy Markdown
Member

Thanks @X-Coder264 !

@X-Coder264
X-Coder264 deleted the fix-sass-modern-style-option branch August 10, 2026 11:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Bug Fix Status: Reviewed Has been reviewed by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sass-loader receives the legacy outputStyle option, so production Sass is compiled compressed (and BOM-prefixed)

4 participants