Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Microsoft Bundle output JavaScript not HTML script tags

I am building a ASP.NET MVC4 based jQuery plug-in and I would like to use the standard Microsoft Bundler.

Now normally when I use the bundler the final usage is this:

@Scripts.Render(BundleConfig.jsBundleFile)

With a output like this in debug:

<script src="/Scripts/..."></script>
<script src="/Scripts/..."></script>
<script src="/Scripts/..."></script>
<script src="/Scripts/..."></script>
<script src="/Scripts/..."></script>
<script src="/Scripts/..."></script>

And a single tag to the minified and combined file in the official release.

However, because we are building a jQuery plug-in I want to use the minifier to insert the actual JavaScript from the various files i.e. in DEBUG, when I have Object1 defined in one file and Object2 defined in another then I get this:

/* Content from file #1 */
var Object1 = function() { /* Un-minified content of Object1 */ };
/* Content from file #2 */
var Object2 = function() { /* Un-minified content of Object2 */ };

and in the release I would get the minified content.

From the MSDN I saw that the bundler, by default can't do that. But I am wondering is there an extension that can do this or another minifing library that can? Though I'd prefer to stay with the Microsoft Bundler.

In my own research on how to extend the System.Web.Optimizer I found the CodePlex project of System.Web.Optimization, where it is said it's not open source as of yet, with makes the extending kinda difficult to anyone not really working on it.

EDIT : So knowing that I can't really expand the System.Web.Optimizer I went with a compromise solution and this is what I ended up using.

In the Controller:

public JavaScriptResult jQueryComponent()
{
    JavaScriptResult ret = new JavaScriptResult();
    ClientSettings Model = new ClientSettings();

#if DEBUG
    List<string> jsFiles = App_Start.BundleConfig.Main.FilesToBeBundledJS;
    StringBuilder bundleBuilder = new StringBuilder();

    foreach (string file in jsFiles)
    {
        bundleBuilder.Append(System.IO.File.ReadAllText(Server.MapPath(file)));
    }

    ViewBag.bundledJS = bundleBuilder.ToString();
#else
    StringBuilder urlBuilder = new StringBuilder("http://localhost");

    int port = Request.Url.Port;
    if (port != -1) {
        urlBuilder.Append(':');
        urlBuilder.Append(port);
    }

    urlBuilder.Append(Scripts.Url(App_Start.BundleConfig.Main.jsBundleFile));

    WebClient wc = new WebClient();
    byte[] raw = wc.DownloadData(urlBuilder.ToString());
    string bundledJS = System.Text.Encoding.UTF8.GetString(raw);

    ViewBag.bundledJS = bundledJS;
#endif

    ret.Script = RenderRazorViewToString("~/Views/Main/jQueryComponent.cshtml", Model);

    return ret;
}

private string RenderRazorViewToString(string viewName, object model)
{
    ViewData.Model = model;
    using (StringWriter sw = new StringWriter())
    {
        ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
        ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);
        viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
        return sw.GetStringBuilder().ToString();
    }
}

And in the View (jQueryComponent.cshtml):

@{
    Layout = null;
}

if (typeof jQuery != 'function') {
    throw "This is a jQuery plugin i.e. jQuery needs to be defined first";
}

(function ($) {

    //Adds the bundled JS into the plug-in
    @Html.Raw(ViewBag.bundledJS)

    $.fn.jsPlugIn = function() {
    /*
        Creates a new instance of [MainObject] with is defined in the bundle.
    */    
        //uses the bundled JS
        var Main = new MainObject();
        Main.doSomething();

        return this;
    };

}(jQuery));

Any ideas how to improve this are welcome alongside the proper extention to the bundler.

Although works, this is far from ideal and I am well aware of this.
Please do not criticise this approach unless you have an alternative.

like image 568
Idra Avatar asked Nov 29 '13 11:11

Idra


People also ask

Does JavaScript need script tag?

JavaScript became the default language for HTML5 and modern browsers. Therefore, now adding text/javascript isn't required in <script> tag.

Are script tags blocking?

SCRIPT tags have a negative impact on page performance because of their blocking behavior. While scripts are being downloaded and executed, most browsers won't download anything else.

Why is it a good idea to put your JavaScript either with script tags or an external file link near the end of the HTML body instead of in the head of the HTML?

Placing scripts in external files has some advantages: It separates HTML and code. It makes HTML and JavaScript easier to read and maintain. Cached JavaScript files can speed up page loads.

Why is JavaScript not working?

In the "Preferences" window select the "Security" tab. In the "Security" tab section "Web content" mark the "Enable JavaScript" checkbox. Click on the "Reload the current page" button of the web browser to refresh the page.


1 Answers

You can do what the BundleHandler does: Get a bundle based on the virtual path (same one you would use for @Script.Render), generate the response and write the contents.

@{
    var context = new BundleContext(this.Context, BundleTable.Bundles, string.Empty);
    var bundle = BundleTable.Bundles.GetBundleFor("~/VIRTUALPATH");
    var response = bundle.GenerateBundleResponse(context);
    var content = response.Content;

    this.WriteLiteral(content);
}

Or in short:

@Html.Raw(BundleTable.Bundles.GetBundleFor("~/VIRTUALPATH").GenerateBundleResponse(new BundleContext(this.Context, BundleTable.Bundles, string.Empty)).Content)

EDIT: To get unminified content in debug, we can do what Scripts does: Get the bundle again, but instead of generating the response, we iterate its files and write their contents (delimited).

@{
    var context = new BundleContext(this.Context, BundleTable.Bundles, string.Empty);
    var bundle = BundleTable.Bundles.GetBundleFor("~/VIRTUALPATH");

    if (BundleTable.EnableOptimizations)
    {
        var response = bundle.GenerateBundleResponse(context);
        var content = response.Content;

        this.WriteLiteral(content);
    }
    else
    {
        var files = bundle.EnumerateFiles(context);

        foreach (var file in files)
        {
            var stream = file.VirtualFile.Open();

            using (var reader = new StreamReader(stream))
            {
                this.Output.Write("{0}{1}", reader.ReadToEnd(), bundle.ConcatenationToken);
            }
        }
    }
}
like image 86
Samu Lang Avatar answered Oct 17 '22 01:10

Samu Lang