Concatenate and Minify JavaScript on the Fly or at Build Time - ASP.NET MVC

Concatenate and minify JavaScript on the fly OR at build time - ASP.NET MVC

In the appendix of Professional ASP.NET 3.5 Scott Hanselman talks about Packer for .NET. This will integrate with MSBuild and pack javascript files for production deployments etc.

ASP.net MVC Minifying and Bundling - How to minify a single JavaScript file

Yep, currently we don't really expose any methods for you to easily call in to Optimization just to minify a string (you could do it via an instance of JsMinify.Process if you really wanted) i.e.:

string js = "//I am a comment\r\nfoo = bar;\r\nfoo = yes;";
JsMinify jsmin = new JsMinify();
BundleContext context = new BundleContext();
BundleResponse response = new BundleResponse(js, null);
response.Content = js;
jsmin.Process(context, response);
Assert.AreEqual("foo=bar,foo=yes", response.Content);

But what you suggest is probably best (create a bundle of one file).

Creating and minifying JavaScript dynamically in ASP.NET MVC server-side code


Part 1

If you are generating the js at runtime, bundling isn't possible (at least not efficiently). You would have to create a new bundle for every request which isn't terribly quick. Plus, you wouldn't be able to cache the regular, constant script bundle.

EDIT: While bundling server-generated js isn't practical, rendering the values into a script tag in the page can achieve the same benefit of bundling, fewer HTTP calls. See the edit in Part 3 for more.

Minifying the server generated js however, is totally possible. This question should have the answer you're looking for. However, I'd recommend you cache this on the server if possible, as the minification process itself could take longer than simply sending down the extra bits.

Part 2

In most minifiers, global variables (those accessible on the window object) are skipped during the name mangling. With the same respect, variables that are accessed in other files that are not defined within that file are not renamed.

For example, if you have the following file...

// outside of a closure, so globally accessible
var foo = 1;

function bar() {
// within a closure, and defined with `var`, not globally accessible
var bar;

// reference to variable declared in another file
baz = null;
}

it would be minified as follows (with whitespace included for readability

var foo = 1;

function bar() {
var b;

baz = null;
}

This is one reason it is important to always declare your variables using the var keyword, otherwise they are assumed to be references to global variables and will not be minified.

Also, JSON (not Javascript object literals!!!) will never be distorted by minifiers, because it consists of string literals for all keys, and all values that aren't of another literal type.

Part 3

Not a bad way, and at my job we do use this approach. For small files though, or simple config values, we have transitioned to rendering server values in a script tag using ASP.NET in the actual view. i.e.

Default.aspx

<script> window.globals = <%= JsonConvert.SerializeObject(new AppGlobals(currentUser)) %>; </script>

We rip this out into a code behind, but the premise is the same.

EDIT:

Server-Generated JS (at it's own uri)

  • Pros

    • Cacheable by browser (if fresh values aren't needed on every request)
  • Cons

    • Extra round trip
  • Use when:

    • Your generated files are large, but rarely change or are the same for multiple users. These scripts can be treated the same as other static assets. To give an example, we serve a js file containing all the text in our app for localization purposes. We can serve a different js file based on the language set in the user's settings, but these values only change once at most with every release, so we can set aggressive cache headers and use a hash in the uri, along with a query string for the locale, to leverage browser caching and download each language file only once per client. Plus, if this file is going to be the same for every user accessing the same uri, you can cache it at the web server (IIS, Apache, etc.).

      Ex: /api/language.v1-0-0.js?locale=en

    • Your js is independent from the rest of your app and not having it won't delay rendering. In this case, you can add the async attribute to your script tag, and this file will be downloaded asynchronously and executed when it is received without preventing the execution of other javascript.

Server-Rendered JS (within the page in a script tag)

  • Pros

    • No extra HTTP calls
  • Cons

    • Can add extra weight to your HTML, which may not be cacheable or minified depending on your circumstances
  • Use when:

    • Your values change often. The weight added to the page should be negligible unless you have a huge number of values (in that case, you might consider splitting them up and adding API endpoints for these values, and only getting them when you need them). With this, you can cut out the extra HTTP call as the js is injected into a script tag on a page the user would already have to retrieve.

But...

Don't waste too much time worrying about it. The differences in these two approaches is almost always negligible. If it becomes a problem, try both and use the better option for your case.

How can I automatically compress and minimize JavaScript files in an ASP.NET MVC app?

I personally think that keeping the files separate during development is invaluable and that during production is when something like this counts. So I modified my deployment script in order to do that above.

I have a section that reads:

<Target Name="BeforeDeploy">

<ReadLinesFromFile File="%(JsFile.Identity)">
<Output TaskParameter="Lines" ItemName="JsLines"/>
</ReadLinesFromFile>

<WriteLinesToFile File="Scripts\all.js" Lines="@(JsLines)" Overwrite="true"/>

<Exec Command="java -jar tools\yuicompressor-2.4.2.jar Scripts\all.js -o Scripts\all-min.js"></Exec>

</Target>

And in my master page file I use:

if (HttpContext.Current.IsDebuggingEnabled)
{%>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/jquery-1.3.2.js")%>"></script>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/jquery-ui-1.7.2.min.js")%>"></script>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/jquery.form.js")%>"></script>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/jquery.metadata.js")%>"></script>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/jquery.validate.js")%>"></script>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/additional-methods.js")%>"></script>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/form-interaction.js")%>"></script>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/morevalidation.js")%>"></script>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/showdown.js") %>"></script>
<%
} else {%>
<script type="text/javascript" src="<%=Url.UrlLoadScript("~/Scripts/all-min.js")%>"></script>
<% } %>

The build script takes all the files in the section and combines them all together. Then I use YUI's minifier to get a minified version of the javascript. Because this is served by IIS, I would rather turn on compression in IIS to get gzip compression.
**** Added ****
My deployment script is an MSBuild script. I also use the excellent MSBuild community tasks (http://msbuildtasks.tigris.org/) to help deploy an application.

I'm not going to post my entire script file here, but here are some relevant lines to should demonstrate most of what it does.

The following section will run the build in asp.net compiler to copy the application over to the destination drive. (In a previous step I just run exec net use commands and map a network share drive).

<Target Name="Precompile" DependsOnTargets="build;remoteconnect;GetTime">

<MakeDir Directories="%(WebApplication.SharePath)\$(buildDate)" />

<Message Text="Precompiling Website to %(WebApplication.SharePath)\$(buildDate)" />

<AspNetCompiler
VirtualPath="/%(WebApplication.VirtualDirectoryPath)"
PhysicalPath="%(WebApplication.PhysicalPath)"
TargetPath="%(WebApplication.SharePath)\$(buildDate)"
Force="true"
Updateable="true"
Debug="$(Debug)"
/>
<Message Text="copying the correct configuration files over" />

<Exec Command="xcopy $(ConfigurationPath) %(WebApplication.SharePath)\$(buildDate) /S /E /Y" />

</Target>

After all of the solution projects are copied over I run this:

    <Target Name="_deploy">
<Message Text="Removing Old Virtual Directory" />
<WebDirectoryDelete
VirtualDirectoryName="%(WebApplication.VirtualDirectoryPath)"
ServerName="$(IISServer)"
ContinueOnError="true"
Username="$(username)"
HostHeaderName="$(HostHeader)"
/>

<Message Text="Creating New Virtual Directory" />

<WebDirectoryCreate
VirtualDirectoryName="%(WebApplication.VirtualDirectoryPath)"
VirtualDirectoryPhysicalPath="%(WebApplication.IISPath)\$(buildDate)"
ServerName="$(IISServer)"
EnableDefaultDoc="true"
DefaultDoc="%(WebApplication.DefaultDocument)"
Username="$(username)"
HostHeaderName="$(HostHeader)"
/>
</Target>

That should be enough to get you started on automating deployment. I put all this stuff in a separate file called Aspnetdeploy.msbuild. I just msbuild /t:Target whenever I need to deploy to an environment.

Combine and minimize css with title and rel attributes

well, really you should be loading the CSS files on demand.

So that if the user chooses pink, the pink.css <link> would be inserted as the last element in <head>.



Related Topics



Leave a reply



Submit