csstwitter-bootstraplessbundlesquishit

Reference variables in a separate less file using SquishIt (support dynamic LESS content)?


We are using bootstrap for our project in MVC4. So far, we were referencing the bootstrap.less file in our main layout page and it worked great. However, a new requirement has come along that requires us to have customized look for each of our department pages (each department has its own layout that use the main layout)

bootstrap.less has this structure:

@import variables.less // defines all the variables

@import others // all imports like mixins.less, reset.less etc

since we need to inject our variable override, we created another less file:

bootstrap-without-variables.less //contains all the imports without the variables.less from bootstrap.less

The reason for this separation is to inject our variable override so that we can customize the bootstrap styles for our pages.

We are using SquishIt to bundle the less files into a bundle.

Here is the Bundle:

    Bundle.Css()
    .Add("~/bootstrap/variables.less")
    .Add("~/variable-override.less") // custom override
    .Add("~/bootstrap/bootstrap-without-variables.less")
    .MvcRender("styles_combined_#.js");

This does not work at all. If I remove the variables.less and reference that in bootstrap-without-variables.less (which now becomes similar to bootstrap.less), it works perfectly fine.

The issue, I think, is that each file is evaluated and converted to css independently before combining them together.

Is there a way to tell the bundler to first bundle the files into one and then to evaluate and convert to css or a better solution to this problem?


Solution

  • Like mentioned by AlexCuse, there was no way for me to do what I mentioned above using only SquishIt. However, as mentioned in https://github.com/jetheredge/SquishIt/issues/170, there is an overload to AddString() that lets you add dynamic less content.

    For example,

    .AddString("LessString",".less") // .less being the extension

    This works perfectly fine as long as the LessString does not contain any imports (@import). So, I downloaded the source from https://github.com/jetheredge/SquishIt and started diving into the code. Going through the code I found that the content loaded through AddString() has the CurrentDirectory set to the path of my IIS ("c:\windows\system32\inetsrv"). As a result of which, the imports were throwing

    FileNotFoundException (You are importing a file ending in .less that cannot be found.)

    So, I needed a way to set the current directory (reference location from where my imports will be searched)

    Here is what I did:

    STEP1: Extended Asset to have a property called CurrentDirectory

      internal string CurrentDirectory { get; set; }
    

    STEP2: Added a third optional parameter to the AddString() overload

    public T AddString(string content, string extension, string currentDirectory = null)
        {
            return AddString(content, extension, true, currentDirectory);
        }
    

    STEP3: Updated the AddString() to add the current directory to the Asset

     T AddString(string content, string extension, bool minify, string currentDirectory = null)
        {
            if (bundleState.Assets.All(ac => ac.Content != content))
                bundleState.Assets.Add(new Asset { Content = content, Extension = extension, Minify = minify, CurrentDirectory = currentDirectory });
            return (T)this;
        }
    

    STEP4: Modify the PreprocessArbitary (For Release) on BundleBase to set the current directory

    protected string PreprocessArbitrary(Asset asset)
        {
            if (!asset.IsArbitrary) throw new InvalidOperationException("PreprocessArbitrary can only be called on Arbitrary assets.");
    
            var filename = "dummy." + (asset.Extension ?? defaultExtension);
            var preprocessors = FindPreprocessors(filename);
            return asset.CurrentDirectory != null ?
                        directoryWrapper.ExecuteInDirectory(asset.CurrentDirectory, () => MinifyIfNeeded(PreprocessContent(filename, preprocessors, asset.Content), asset.Minify)) :
                        MinifyIfNeeded(PreprocessContent(filename, preprocessors, asset.Content), asset.Minify);
        }
    

    For Debug, modify the RenderDebug to set the current directory

     if (asset.IsArbitrary)
                {
                    var filename = "dummy" + asset.Extension;
                    var preprocessors = FindPreprocessors(filename);
                    var processedContent = asset.CurrentDirectory != null ?
                            directoryWrapper.ExecuteInDirectory(asset.CurrentDirectory, () => PreprocessContent(filename, preprocessors, asset.Content)) :
                            PreprocessContent(filename, preprocessors, asset.Content);
                    sb.AppendLine(string.Format(tagFormat, processedContent));
                }
    

    Here is my how I add dynamic or static less files now:

    .AddString("@import 'content/bootstrap/variables.less';", ".less", AppDomain.CurrentDomain.BaseDirectory)
    

    For my above requirement, I read the variables.less into a string builder, then add my variable-override.less and finally add the bootstrap-without-variables.less to the string builder.

    It has worked for me so far. I tested following scenarios:

    1. normal less files with imports, e.g. .Add("~/content/styles.less")

    2. inline less without imports, e.g. .AddString(LessString, ".less")

    3. dynamic less files with imports, e.g. .AddString("@import content/bootstrap/variables.less';", ".less", AppDomain.CurrentDomain.BaseDirectory)

      I'll try to do a pull request soon. I hope this helps people looking to support dynamic LESS content with imports.