sass-loader icon indicating copy to clipboard operation
sass-loader copied to clipboard

Dart Sass without fibers should use renderSync instead of render

Open filipesilva opened this issue 6 years ago • 18 comments

  • Operating System: Windows
  • Node Version: 10.16.0
  • NPM Version: 6.9.0
  • webpack Version: 4.30.0
  • sass-loader Version: 7.1.0

Expected Behavior

When using Dart Sass and not using fibers, the renderSync method should always be used instead of the render method (see https://github.com/sass/dart-sass#javascript-api, https://github.com/webpack-contrib/sass-loader/issues/435#issuecomment-349450103 and https://github.com/sass/dart-sass/issues/744#issuecomment-507429974 for details). Always using render has severe performance implications.

Actual Behavior

The render method is always used with Dart sass.

Code

https://github.com/webpack-contrib/sass-loader/blob/e279f2a129eee0bd0b624b5acd498f23a81ee35e/lib/loader.js#L137-L144

How Do We Reproduce?

No external reproduction needed. It's in the source code for this package.

filipesilva avatar Jul 02 '19 15:07 filipesilva

Thanks for issue, i will investigate this for next major release, i think i will start work on next release at this week, a lot of other issues

alexander-akait avatar Jul 02 '19 15:07 alexander-akait

SGTM, thanks for looking at it!

filipesilva avatar Jul 02 '19 15:07 filipesilva

We could make it configurable via loader options. This pushes the responsibility/complexity back to the user. The problem is that the sass-loader has no clue if your project uses any custom importer where the async API is necessary.

I should also mention that compiling something synchronously is one of the worst things you can do in a webpack build. In a regular webpack build there are hundreds of async module builds simultaneously. If you do something synchronously, it will pause all other module builds. I'm pretty sure that most people will get worse performance results if they do something synchronously 😁

jhnns avatar Jul 08 '19 09:07 jhnns

I think configurable makes sense if the plugin cannot determine it. I can compare performance numbers if you guide me a little bit on how I can change it in my node modules. I tried to change it myself in https://github.com/sass/dart-sass/issues/744#issuecomment-507245355 but it looks like I was missing something.

filipesilva avatar Jul 08 '19 10:07 filipesilva

We could make it configurable via loader options. This pushes the responsibility/complexity back to the user. The problem is that the sass-loader has no clue if your project uses any custom importer where the async API is necessary.

This would make sense, but the default should still probably be running synchronously (which means WebPack's custom importer should be synchronous as well).

I should also mention that compiling something synchronously is one of the worst things you can do in a webpack build. In a regular webpack build there are hundreds of async module builds simultaneously. If you do something synchronously, it will pause all other module builds. I'm pretty sure that most people will get worse performance results if they do something synchronously :grin:

The logic of compiling Sass—which is by far the bottleneck of Sass compilation—is inherently synchronous. There's nothing about parsing strings or manipulating ASTs that can be done out-of-process, so making it asynchronous doesn't buy you anything in terms of speed (and in fact makes it much slower because it has to bounce back to the event loop all the time). The only reason it has an asynchronous mode at all is to support asynchronous plugins.

nex3 avatar Jul 10 '19 22:07 nex3

This would make sense, but the default should still probably be running synchronously (which means WebPack's custom importer should be synchronous as well).

In theory it is possible https://github.com/webpack/enhanced-resolve#resolve (we need use sync), but here we will have other problem - it is decrease sass compilation time, but increase webpack compilation time, no golden way here

alexander-akait avatar Jul 10 '19 22:07 alexander-akait

The overhead of running Sass asynchronously tends to range between 2x and 3x the total synchronous compilation time for large projects, not to mention the memory overhead that @filipesilva is running into. This dwarfs file IO time, which means it's also likely to dwarf the benefits you'd get from increased parallelism while waiting on file IO.

nex3 avatar Jul 10 '19 23:07 nex3

Hi @nex3 !

Thank you for your work!

1 - Are you considering caching? To increase the performance of file processing 2 - sass.renderSync - Is it recommended? I mean Dart-Sass for JS npm install sass --save-dev.

Yuriy-Svetlov avatar Apr 28 '21 07:04 Yuriy-Svetlov

1 - Are you considering caching? To increase the performance of file processing

Filesystem operations aren't the bottleneck here. The issue, as I explained above, is the overhead of going back to the event loop for every method call in asynchronous mode.

2 - sass.renderSync - Is it recommended? I mean Dart-Sass for JS npm install sass --save-dev.

Yes, renderSync() is recommended for Dart Sass.

nex3 avatar Apr 28 '21 22:04 nex3

@nex3 Thank you!

Can I set your avatar in my plugin?

Yuriy-Svetlov avatar Apr 29 '21 03:04 Yuriy-Svetlov

I'd prefer it if you didn't.

nex3 avatar Apr 29 '21 20:04 nex3

I'd prefer it if you didn't.

@nex3 Ok. I will not add a photo of your avatar. It was just needed for the quote you made. Then I'll just add your nickname.

Here are these quotes

The logic of compiling Sass—which is by far the bottleneck of Sass compilation—is inherently synchronous. There's nothing about parsing strings or manipulating ASTs that can be done out-of-process, so making it asynchronous doesn't buy you anything in terms of speed (and in fact makes it much slower because it has to bounce back to the event loop all the time). The only reason it has an asynchronous mode at all is to support asynchronous plugins.

The overhead of running Sass asynchronously tends to range between 2x and 3x the total synchronous compilation time for large projects, not to mention the memory overhead that @filipesilva is running into. This dwarfs file IO time, which means it's also likely to dwarf the benefits you'd get from increased parallelism while waiting on file IO.

Yuriy-Svetlov avatar Apr 30 '21 01:04 Yuriy-Svetlov

What’s the status of this issue? I thinks what @jhnns proposed—pushing the responsibility/complexity back to the user—is the best way forward. Make sass-loader async by default, but strongly recommend (in documentation) switching to sync API when using Dart Sass.

niksy avatar Sep 07 '21 13:09 niksy

We can't use sync api because we need resolve your @import/@use and etc, it will bring a big losing of perf

alexander-akait avatar Sep 07 '21 14:09 alexander-akait

We can't use sync api because we need resolve your @import/@use and etc, it will bring a big losing of perf

This is still developer/user issue and it’s something that could be mentioned in documentation. Currently there really isn’t any good way forward other than opt-in to sync API and deal with consequences.

As for resolving module at-rules, that’s included inside stats.includedFiles which is provided by Sass render result and it’s not related to any Webpack implementation other than custom Webpack importer.

niksy avatar Sep 07 '21 14:09 niksy

We can't enable sync api, resolver are always async

alexander-akait avatar Sep 07 '21 14:09 alexander-akait

We can't enable sync api, resolver are always async

Do you mean Webpack resolver or Sass resolver? Because Webpack enhanced resolve has sync option IIRC.

niksy avatar Sep 07 '21 14:09 niksy

1 - Are you considering caching? To increase the performance of file processing

Are we not caching the results?

shantanu2307 avatar Oct 31 '23 05:10 shantanu2307