/// <summary> /// Resolve all script dependencies in the bound CQ document. Scripts that cotain a "data- /// location='head'" attribute will be moved to the head. /// </summary> /// /// <param name="doc"> /// The document to resolve. /// </param> public void ResolveScriptDependencies(CQ doc) { string scriptSelector = "script[src][type='text/javascript'], script[src]:not([type]), link[type='text/css']"; CQ scripts = doc[scriptSelector]; if (scripts.Length == 0) { return; } // move scripts first // TODO: Optimize using a query caching mechanism so foreach (var item in scripts.Filter("[data-moveto]")) { var target = doc.Select(item["data-moveto"]); if (target.Length > 0) { target.First().Append(item); item.RemoveAttribute("data-moveto"); } } // resolve dependencies ScriptCollection coll = new ScriptCollection(ScriptEnvironment); coll.Options = Options; // identify the insertion point for the script bundle. AddFromCq returns the first script with dependencies, // so scripts should be added right before that one. Otherwise they should be added // at the end of head. var firstScriptEl = coll.AddFromCq(scripts); CQ firstScript = null; if (firstScriptEl != null) { firstScript = firstScriptEl.Cq(); } string bundleUrl; List <ScriptRef> dependencies = coll.GetDependencies() .Where(item => !coll.Contains(item)) .ToList(); // Now add scripts directly for dependencies marked as NoCombine. var inlineScripts = Options.HasFlag(ViewEngineOptions.NoBundle) ? dependencies : dependencies.Where(item => item.NoCombine); foreach (var item in inlineScripts) { var script = GetScriptHtml(item.Path, item.ScriptHash); if (firstScript != null) { firstScript.Before(script); } else { firstScript = script; doc["body"].Append(script); } } // Before creating the bundle, remove any duplicates of the same script on the page if (!Options.HasFlag(ViewEngineOptions.NoBundle)) { bool hasBundle = Bundles.TryGetValue(coll, out bundleUrl); if (hasBundle) { // when nocache is set, we will regenerate the bundle, but not change the script ID. The v= // flag will be changed by BundleTable. if (Options.HasFlag(ViewEngineOptions.NoCache)) { string removeUrl = "~" + bundleUrl.Before("?"); BundleTable.Bundles.Remove(BundleTable.Bundles.GetBundleFor(removeUrl)); hasBundle = false; ScriptID++; // this code attempts to un-cache the bundle, it doesn't work. // leaving it here until some permanent solution is found as a reminder // // http://stackoverflow.com/questions/12317391/how-to-force-bundlecollection-to-flush-cached-script-bundles-in-mvc4 // //var bundleList = BundleTable.Bundles.ToList(); //BundleTable.Bundles.Clear(); //BundleTable.Bundles.ResetAll(); //BundleTable.EnableOptimizations = false; //foreach (var oldBundle in bundleList) //{ // BundleTable.Bundles.Add(oldBundle); //} } } else { ScriptID++; } if (!hasBundle) { var activeDependencies = dependencies.Where(item => !item.NoCombine).ToList(); if (activeDependencies.Count > 0) { string bundleAlias = "~/cqbundle" + ScriptID; var bundle = GetScriptBundle(bundleAlias); foreach (var item in activeDependencies) { bundle.Include(item.Path); } BundleTable.Bundles.Add(bundle); if (HttpContext.Current != null) { bundleUrl = BundleTable.Bundles.ResolveBundleUrl(bundleAlias, true); } else { bundleUrl = bundleAlias + "_no_http_context"; } Bundles[coll] = bundleUrl; } } var scriptPlaceholder = scripts.First(); // add bundle after all noncombined scripts if (!String.IsNullOrEmpty(bundleUrl)) { firstScript.Before(GetScriptHtml(bundleUrl)); } } }