-
-
Notifications
You must be signed in to change notification settings - Fork 431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sass-loader performance #296
Comments
I experienced great improvements after switching to node-sass. But doing that would go in opposite direction with component-based styling. @mmase do you have lots of variables and mixins that you import with |
"Also, are you importing .sass files multiple times, i.e, every component imports its own sass file?" would doing this improve or worsen the compiliation speed? |
Related to: #307 |
@wildeyes i think so. I have 2 sass files, variables.sass and componentA.sass. The variables file is used by multiple components. Incremental rebuild speed for 700 components took 8 secs on a variable.sass change while 2 secs for componentA.sass. |
Based on this discussion, I started profiling an example project yesterday and had some interesting insights. First of all, the sass-loader performance (49.2s) is completely unacceptable compared to fast-sass-loader (5.29s) or even bare node-sass (2.25s) from the command line. All three toolchains compile the same source code and produce similar output. We can expect some overhead coming from webpack since we need to initialize it, set up the loader pipeline, etc., but even the fast-sass-loader is unacceptable slow compared to bare node-sass in my opinion. OverviewSo let's take a look at the flame graphs: First of all, as you can see, node-sass is spending most of the time outside of Node.js which is what we expected, since the hard work is done by Libsass. Now, let's take a look at both sass-loaders. The execution can roughly be divided into five phases (indicated as black boxes in the graph) that I will discuss in detail later:
So, the first surprise here is that we have two separate compilations going on. This is due to the extract-text-webpack-plugin which initiates a child compilation. Tbh, I'm not sure why this is necessary at all. @sokra can give us more insights here. The second surprise is, that the second compilation doesn't use the cache from the first compilation. In the sass-loader graph, you can clearly see that the second pass takes almost exactly the same amount of time. This could be improved a little bit because the sass-loader is currently not using webpack's fs cache, but that would only reduce the second sass-loader execution time (4.), which is not where most of the time is spent. The most time, however, is spent—and that's the third surprise—inside the css-loader. You can clearly see that in both graphs. It takes up half of the total time in the fast-sass-loader setup, and even more in the sass-loader setup. It might also be surprising that node-sass and webpack + fast-sass-loader take almost the same amount of time to compile. In the flame graphs, you can clearly see that node-sass finishes almost at the same time where the 2. block of the fast-sass-loader graph ends. However, we need to remember that the fast-sass-loader is deduping all files before compilation, so there's far less to compile. Now, let's take a look at all the phases in detail. Phase 1: Webpack initialization & loader execution startNo big surprise here: The initialization of both setups takes almost the same amount of time. Differences here are probably due to I/O or general OS flakyness because there is no actual difference between both loaders here. You can clearly see that during the first 1000ms, most of the time is spent requiring modules. That's why webpack almost always takes at least 1s to compile—even with the simplest setup. Maybe we could win something here with lazy requiring ala lazy-req. Phase 2: Import resolving & sass compilation (1. compilation)Now that looks different! First, let's look at the sass-loader: The sass-loader registers a custom importer and kicks off the compilation. Now, we're essentially waiting for Libsass to call back. You can clearly see that there is a lot of back-and-forth going on with recurring breaks (grey bars) where the process is just sitting and waiting for Libsass. This graph led me to the assumption that Libsass might be doing sequential I/O. Concurrent I/O would look different, I suppose. After all the resolving is done, Libsass is performing the actual compilation and we're just waiting for Libsass to call back. Nothing to optimize here. Now, let's look at the fast-sass-loader: The fast-sass-loader preparses the source code with regexps for import statements and passes all imports to webpack's resolver. After the import paths have been resolved, the imported files are read from disk via node's The actual Sass compilation is also a lot faster since a lot of imports have been deduped. It's just that Libsass has less to parse. Phase 3: css-loader (1. compilation)This phase is the most interesting one because:
The only difference is that the amount of data is very different. The fast-sass-loader produces a string with There are two things that I found surprising:
Phase 4: sass-loader & css-loader (2. compilation)The sass-loader performs exactly the same compilation again. No results from the first compilation are re-used. The fast-sass-loader skips the second Sass compilation because it uses its own cache. That's a nice shortcut, but this should be fixed in webpack. I don't understand why we need a second compilation. And if we need the second one, can't we just skip the first one? This seems redundant. Conclusion
Here are both CPU profiles. Load them into your Chrome Developer Tools if you want to take a look for yourself. This was bothering me for a long time because I think we can do a lot better than that 😁 |
As comparison, the sass-loader setup with [email protected] takes 10.1s and looks like this: |
As @sokra pointed out: With the new ExtractTextPlugin({
filename: '[name].css',
allChunks: true
}) the second compilation can be skipped: thus reducing the built time to 23.9s |
@jhnns Hi jhnns, may I ask what tools you were using to generate those profiling screenshots? I need to troubleshoot webpack issues as well, I need a tool to help me pinpoint the problem. |
Run node --inspect-brk ./node_modules/.bin/webpack to start the debugging process. The process will halt at the first line and print a debugger URL. Copy the debugger URL into the Chrome Browser and the developers tools will initialize. Then go to the JavaScript profiler tab and start profiling :) If you don't want to copy debugger URLs around, you can also use the NIM chrome extension. It discovers debuggable node processes automatically. |
@jhnns thank you very much!!! |
Not directly related to this thread, but also interesting: Looks like the most recent v8 version (with their new optimizing compiler TurboFan) gives a startup performance boost of 160% (1150ms vs 720ms). Using v8/node, phase 1 looks like this: The overall build time decreased from 49.2s to 41.35s (~119% faster). TurboFan doesn't bail out on try/catch which will probably improve the startup performance of all node applications because node is using try/catch to initialize the modules. |
awesome 👍 |
Postcss issue about source map: webpack-contrib/postcss-loader#195 |
After disabling generation of source maps using my PR webpack-contrib/css-loader#478 i get about |
hello friends. Just as an FYI, I'm looking into this. With any luck, we'll see if there is any low hanging fruit to go after besides for the |
any new updates? @mikesherov |
@levin-du, several of css-loaders dependencies are extremely slow but have been rewritten to be much much faster, but are semver major. css-loader is currently undergoing a rewrite along with the rest of the CSS module loading architecture, so I asked if a patch would be accepted. Due to the fact that it's a major bump, I was asked to wait for the rewrite to be completed instead. |
That's a shame, @mikesherov, but thanks for the update! |
Maybe dart-sass #435 is faster. I need to try that out. |
I quickly put together a proof of concept loader for first class node module imports. It'll probably need some API changes to work within the webpack context but it's worth a look. https://www.npmjs.com/package/@node-sass/node-module-importer |
Hey folks 👋 |
@manniL no, We already cache many things and need improve speed on |
years gone, my projects still get ~15s to re-compile on watch |
@Grawl try move imports from scss to js (as many as you can). In my case it speeded up my build by more than 5 times (10+ if turn off source maps). |
@DimaGashko can you create minimum reproducible test repo? |
@DimaGashko my mixins, variables and placeholder selectors will work as usual after this changes? |
@Grawl you can create file @import "variables";
@import "mixins/mixinA";
@import "mixins/mixinB";
@import "somePlaceholderSelectors"; And import it in each entry style file. loader: 'sass-loader',
options: {
// Of cource, you have to use aliases
data: '@import "~@/styles/disappearing" ';
. . .
} |
Are we still favoring |
@rodoabad did you ever get an answer on this (one year ago today) question? |
@simeyla no I have not. |
Should we revive this topic? 2023 and my extra big project takes 9 mins, 24.091 secs of which SCSS compilation takes 8 mins, 21.86 secs
|
@DzmVasileusky If you can give me access to the project I can profile, I don't think we have a problem with sass-loader itself here |
@alexander-akait I will try to make a big enough project to play with, can't share our enterprise app's code. BTW, removing all the SCSS from the app decreased the build time from 9min 24s to 3min 33s:
|
Do you use Also let's try to check this - try to compile sass code without webpack, like you have one big file and check perf, thank you |
@alexander-akait I have found the biggest issue. |
@DzmVasileusky Great, yeah, most of problems with sass perfomance are not in sass-loader, we just wrapper with custom resolver, it has some overhead, but it is very very little and we cache mostly everything |
But how we can use
|
@DiazAilan You can ask |
@DiazAilan sorry to hear it. but you can do the following _colors.scss
theme.scss
any-component.scss
|
This works like a charm! Thanks!
|
|
We have a fairly expansive suite of sass files (around 600 files total). When running our suite directly with
libsass
, behindnode-sass
, it takes less than 5 seconds. Yet, sass-loader through webpack takes over 15 seconds.I am wondering if others have run into this or if we can debug this to find where the performance lag lies.
The text was updated successfully, but these errors were encountered: