sass / dart-sass

The reference implementation of Sass, written in Dart.
https://sass-lang.com/dart-sass
MIT License
3.9k stars 351 forks source link

Unused code elimination and deduplication is not applied when using api: 'legacy' #2221

Closed AndrewJDR closed 4 months ago

AndrewJDR commented 4 months ago

I just wanted to document this for others in the future, because I wasn't able to find this particular solution when I searched. TLDR: The solution is to use the api: "modern" setting when using sass-loader with dart-sass if you want deduplication/dead code elimination on your sass/css. Because this involves an interaction between webpack and sass, I also opened an issue on the webpack project - https://github.com/webpack/webpack/issues/18333

Here is further information in case you are interested:

I use webpack 5 on a project, and recently switched from node-sass/libsass to dart-sass. The project imports fontawesome sass files in a .ts files in this manner:

import "@fortawesome/fontawesome-pro/scss/fontawesome.scss"
import "@fortawesome/fontawesome-pro/scss/brands.scss"
import "@fortawesome/fontawesome-pro/scss/solid.scss"
import "@fortawesome/fontawesome-pro/scss/regular.scss"
import "@fortawesome/fontawesome-pro/scss/light.scss"
import "@fortawesome/fontawesome-pro/scss/v4-shims.scss"
import "@fortawesome/fontawesome-pro/scss/thin.scss"

After the changeover from node-sass to dart-sass, the parsed webpack bundle size was much larger than before. It went from 5.64MB to 7.74MB.

After some digging around, I found that specifying the modern API in the webpack configuration for the sass-loader reduced the bundle size back down to 5.64 MB.

            {
              loader: "sass-loader",
              options: {
                api: "modern",
              }
            }

If the api setting is left unspecified, it defaults to "legacy", and fontawesome and other sass imports don't seem to receive any "dead-code elimination" or deduplication, leading to larger bundle sizes. Specifically, I observed with webpack-bundle-analyzer that each imported sass file like "brands.scss" and "solid.scss" consume nearly 400KB in the bundle because they're duplicating many of the same sass file imports that were already imported by prior file imports like fontawesome.scss. Setting api to "modern" seems to ensure that some form of "dead code elimination" and deduplication is applied, and files like "brands.scss" and "solid.scss" only contribute a few KB to the bundle size, and even fontawesome.scss (the first import) contributes around 250KB rather than about 600KB.

Snippets of the webpack bundle analyzer for the fontawesome sass files are shown below.

Using api: "modern": image

Using api: "legacy" (the default): image

ntkme commented 4 months ago

Sounds like an issue or question for webpack sass-loader rather than for dart-sass, because:

  1. Sass (libsass or dart-sass) does not know how to process an import from .ts file. Such import statements are processed by webpack.
  2. Sass itself does not do any deduplications of import at all, deduplication of ts/js imports if any, again is done by webpack
AndrewJDR commented 4 months ago

Perhaps, but will the webpack folks have the requisite knowledge to understand why changing the sass api setting would impact this?

nex3 commented 4 months ago

We certainly don't have the requisite knowledge, since we have no expertise in webpack's code elimination at all. If you can track this down to a case where the Sass API is behaving contrary to documentation or in a way that's clearly incorrect, please let us know, but as it stands there's not really any action we can take here.

AndrewJDR commented 4 months ago

@ntkme @nex3 Yeah, I'll be more specific about what I mean. Does anyone on the dart-sass project know anything about how its api: "modern" setting works vs its api: "legacy" setting that might impact the operation of a deduplicator? For example, is "legacy" more aggressive than "modern" in mangling variable names or something like that that would make it trickier for a dedpulicator to work? I'm not asking for anyone to spend hours digging, but if someone familiar with the dart-sass code happens to know of some key differences in this regard between legacy and modern, I'd have more to tell the webpack folks when I file an issue over there.

AndrewJDR commented 4 months ago

Actually, reading here, I see that the legacy API is going to be unsupported once this project reaches 2.0. Since this is no longer an issue when using modern, it does seem like a waste of everyone's time to try to track this down. I may file a bug over on webpack and close it just so others can find this info.

Thanks.