Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ignoring files in MVC Bundles

We are using feature flags (set in Web.Config) to toggle the visibility in the UI of certain features that aren't yet complete. I'd also like my MVC bundles to NOT include the related JS files since they would just be useless files the client would have to download when the feature isn't enabled.

So far I've found IgnoreList.Ignore but I can't seem to get it to work. This is basically what I'm doing:

public static void RegisterBundles(BundleCollection bundles)
{
    if (!appConfiguration.FeatureXEnabled)
    {
        //Skip these files if the Feature X is not enabled!
        //When this flag is removed, these lines can be deleted and the files will be included like normal
        bundles.IgnoreList.Ignore("~/App/Organization/SomeCtrl.js", OptimizationMode.Always);
        bundles.IgnoreList.Ignore("~/App/Filters/SomeFilter.js", OptimizationMode.Always);
    }

    var globalBundle = new ScriptBundle("~/bundles/app-global").Include(
        "~/App/RootCtrl.js",
        "~/App/Directives/*.js",
        "~/App/Services/*.js",
        "~/App/Application.js"
    );
    bundles.Add(globalBundle);

    var appOrgBundle = new ScriptBundle("~/bundles/app-org");
    appOrgBundle.Include(
        "~/App/Filters/*.js",
        "~/App/Organization/*.js"
    );

    bundles.Add(appOrgBundle);
}

With the above code, the files in the ignore list are still being included! What am I doing wrong here?

like image 630
FiniteLooper Avatar asked May 11 '15 15:05

FiniteLooper


3 Answers

I have fought the ignore list when using expressions as well. A simple work around I have found is to implement an IBundleOrderer that will exclude the files I do not want, and apply some ordering to how the files get included. Although this is not really its intended use, I find it fills the gap.

The IBundleOrderer gets access to the full list of files (expression expanded to what files it matched).

public class ApplicationOrderer : IBundleOrderer {
    public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
    {
        if (!AppSettings.FeatureFlag_ServiceIntegrationsEnabled)
        {
            //Skip these files if the Service Integrations Feature is not enabled!
            //When this flag is removed, these lines can be deleted and the files will be included like normal
            var serviceIntegrationPathsToIgnore = new[]
            {
                "/App/ServiceIntegrations/IntegrationSettingsModel.js",
                "/App/ServiceIntegrations/IntegrationSettingsService.js",
                "/App/ServiceIntegrations/ServiceIntegrationsCtrl.js"
            };
            files = files.Where(x => !serviceIntegrationPathsToIgnore.Contains(x.VirtualFile.VirtualPath));
        }

        return files;
    }
}

Example Usage:

public static void RegisterBundles(BundleCollection bundles)
{
        var appBundle = new ScriptBundle("~/bundles/app")
            .IncludeDirectory("~/App/", "*.js", true)
        appBundle.Orderer = new ApplicationOrderer();
        bundles.Add(appBundle);
}
like image 166
Gent Avatar answered Nov 15 '22 06:11

Gent


I think that the bundling code only happens once - during the initialisation of the website. If you are switching the appConfiguration.FeatureXEnabled flag without restarting/republishing the website then your change will be ignored.

One solution would be to create 2 separate bundles and then use Razor to display the correct bundle reference in your HTML.

e.g.

@{if (appConfiguration.FeatureXEnabled){
    <script type="text/javascript" src="~/bundles/bundle-large.js"></script>
}else{
    <script type="text/javascript" src="~/bundles/bundle-small.js"></script>
}
like image 32
Ulric Avatar answered Nov 15 '22 06:11

Ulric


I upvoted Gent's answer above because it helped me accomplish what I wanted.

But then I expanded upon it and created a generic IBundleOrderer that uses lambdas to exclude whatever items you want to exclude very simply.

Note the excludes parameter is a params list so you are not limited to a single exclusion pattern. This doesn't do any ordering, but it could be easily added with another parameter.

public class ExcludeItemsOrderer : IBundleOrderer
{
    private Func<BundleFile, bool>[] _excludes;

    public ExcludeItemsOrderer(params Func<BundleFile, bool>[] excludes)
    {
        _excludes = excludes;
    }

    public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
    {
        if(_excludes == null || _excludes.Length == 0)
        {
            return files;
        }

        foreach(var exclude in _excludes)
        {
            var exclusions = files.Where(exclude);
            files = files.Except(exclusions);
        }

        return files;
    }
}

And then I created an extension method to simplify using it:

public static class BundleExtensions
{
    public static Bundle AsIsOrderExcluding(this Bundle bundle, params Func<BundleFile, bool>[] excludes)
    {
        bundle.Orderer = new ExcludeItemsOrderer(excludes);
        return bundle;
    }
}

So that it can be easily applied as you create your bundles, like this:

bundles.Add(
            new ScriptBundle("~/Bundles/App/js")
                .IncludeDirectory("~/App", "*.js", true)
                .AsIsOrderExcluding(bf => bf.VirtualFile.VirtualPath.IndexOf("/skip_me/") >= 0)
            );
like image 31
Brian S Avatar answered Nov 15 '22 07:11

Brian S