ligershark / WebOptimizer.Sass

Apache License 2.0
63 stars 26 forks source link

Changes to partial files do not bust cached bundle #3

Open zbecknell opened 6 years ago

zbecknell commented 6 years ago

Currently, including a .scss file with a partial file will compile correctly but will not re-compile with changes only to the partial. So the following will only recompile when changes to site.scss are made but not to _partial.scss which it is referencing:

pipeline.AddScssBundle("~/css/site.css",
    "css/site.scss");

A workaround is to include the partial in the bundle list:

pipeline.AddScssBundle("~/css/site.css",
    "css/_partial.scss",
    "css/site.scss");

This does bust the cache when changes to the partial are made and causes the full site.scss to recompile as well, but results in both files being served in the bundle and duplicate css appearing.

Less than ideal solution:

Manually include the partials, as in the workaround, and use them only for purposes of cache-busting but do not duplicate them in the bundle.

More ideal solution:

Automatically include partials for purposes of cache-busting and do not duplicate them in the bundle. We can find the partials quite easily by generating the source map during conversion, but after that it would be a matter of associating them appropriately and evaluating when they've been changed or not.

I'm not familiar enough with the code to know whether this would require major architectural changes or not, but it seems like this should be the long term solution.

JordanMunroe commented 6 years ago

This workaround works okay, except you can't use variables from _partial.scss in site.scss defeating the purpose of the entire project if I have to put everything in one class or mirror variables across files.

Sure, I can modify the main file every time I change something in a partial file but that's a huge pain in the ass and a good way to get me pulling my hair out when I forget about it and can't figure out why a div's style won't update.

lucasBertola commented 6 years ago

I think this lib is unusable without this feature.

and i try to use enableCaching to false for solve this.; but it don't recompile each time. (so it use cache....)

jbeanky commented 6 years ago

It appears that setting enableCaching to false does not add any additional header information. Only when enableCaching is set to true, does it inject headers to support caching the output.

I was successful compiling multiple source files and having it reload after a change to any of them by controlling the cache mechanism on my own, which I often do anyway, using either tag helpers or NWebsec.

I applied the asp-append-version tag helper to my stylesheet reference in HTML layout.

<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />

And then included the bundle files as follows:

services.AddWebOptimizer(pipeline =>
{
     pipeline.AddScssBundle("/css/site.css", "/Styles/*.scss").UseContentRoot();
});

Changing any of the partial files and doing a refresh of the page renders a new CSS file.

jbeanky commented 6 years ago

I missed this at first in the documentation. If you import the WebOptimization.Core TagHelpers, then CSS links are automatically versioned. The addition of the asp-append-version tag is irrelevant.

Include this in your _ViewImports.cshtml file.

@addTagHelper *, WebOptimizer.Core
zbecknell commented 6 years ago

@jbeanky Do you have your partials in the /Styles/ folder? If so, they are probably being bundled into site.css and therefor are being referenced twice. In this case I've found that they will indeed cause the cache be busted, but that's only because the partials are being explicitly included (in a glob in your case).

wernerb90 commented 6 years ago

Is there any update to resolve this issue?

oculys-andrew commented 6 years ago

I've found an alternative workaround to this, which is basically to use custom WebOptimizer bundling logic instead of SCSS' own @import functionality.
Pros:

Cons:

services.AddWebOptimizer(pipeline =>
{
    pipeline
        .AddBundle("/scss-bundle.css", "text/css; charset=UTF-8",
            "/scss/base-file.scss",
            "/scss/partials/_partial-1.scss",
            "/scss/partials/_partial-2.scss")
        .AdjustRelativePaths()
        .Concatenate()
        .CompileScss()
        .InlineImages()
        .FingerprintUrls()
        .MinifyCss();
}

The main trick is just adding the files in the order you want them appended to your base SCSS file, and then calling .Concatenate() before calling .CompileScss(). Also, if you currently do an @import in the middle of an SCSS file, you'll need to break that file into two files, one for the content before the @import and one for the content after. Then define your bundle with them in that order: the 'before import' file, the file to import, then the 'after import' file.

This obviously isn't ideal, but after a quick look at the WebOptimizer code I didn't see an obvious first-class fix for the problem, and this works well enough for my scenario (basically just setting different SCSS variable values for different clients in a multi-tenant application).

To mitigate debugging headaches caused by not being able to disable bundling in dev, in my actual implementation for this, I check IHostingEnvironment.IsDevelopment() and only append the .MinifyCss() call on my bundles when the app is not running in dev. That way, while the files are bundled, they're at least readable when I'm trying to debug stuff. (If you want to do that, keep in mind that you can't inject an IHostingEnvironment object into Startup.ConfigureServices() directly; you have to inject it into the Startup constructor and store a reference to it on the Startup class object, then reference that.)

mikaelliljedahl commented 4 years ago

I guess this issue cannot be resolved unless the Core project is updated as well.

WebOptimizer.Core/Taghelpers/BaseTagHelper.cs contains this method:

protected void AddToCache(string cacheKey, string value, IFileProvider fileProvider, params string[] files)
        {
            var cacheOptions = new MemoryCacheEntryOptions();

            foreach (string file in files)
            {
                cacheOptions.AddExpirationToken(fileProvider.Watch(file));
            }

            Cache.Set(cacheKey, value, cacheOptions);
        }

Because this method files parameter is only the entry scss this results in the only file watched is the "css/site.scss"-file.

The only part of this WebOptimizer.Sass project aware of the containing scss files is the compiler (which is a separate library). However the Compiler.cs could call ConvertToCss using GenerateSourceMap = true (which it is not). Then the sourcemap could be used to retrieve the underlying files and push the list of files all the way down to the AddToCache method (I haven't figured that part out, but maybe someone here could).