RequireJS – Shooting Yourself in the Foot Made Simple

I’ve run across a few JavaScript dependency issues when creating pages, where you have to ensure the right libraries are loaded in the right order before the page-specific stuff is loaded.

RequireJS seems like the solution to this problem as you can define requirements in each JavaScript file. It uses the AMD pattern and TypeScript supports AMD, so it looked like the obvious choice.

I’ve tried looking at RequireJS before briefly. A quick look was enough to make me realise this wasn’t a simple implementation, so until this weekend I had not made a serious attempt to learn it. Having tried to do this over a weekend, I’ve begun to realise why it’s not more widely used.

Introduction

On the face of it, it seems that it should be simple. You load the require.js script and give it a config file in the data-main attribute, to set it up. When you write JavaScript code, you do so using AMD specifications so it can be loaded asynchronously and in the correct order.

For TypeScript users like myself, this presents a big barrier to entry (not RequireJS’s fault), as you need to specify the –module AMD flag on the compiler. This means all TypeScript code in the application is now compiled with AMD support, so existing non-requireJS pages won’t work. You have to migrate all in one go.

Setting Up a Test App

I created a simple ASP.NET web application with MVC to test requireJS, and followed the (many) tutorials, which explain how to configure the baseURL, paths etc. I then add a script tag to the _Layout.cshtml template to load and configure requireJS:

<script data-main="/Scripts/Config.js"

src="~/Scripts/require.js"></script>

Seems simple enough, doesn’t it? In fact I’d just shot myself in the foot: I just didn’t realise it.

Since my original layout loaded jQuery and Bootstrap, I needed to replicate that, so I added the following code:

    <script>
        require(['jquery', 'bootstrap'], function ($, bootstrap) {
        });
    </script>

This would tell requireJS to look for a javascript file called /Scripts/jquery.js, but since I loaded jQuery using Nuget the file is actually /Scripts/jquery-2.1.1.min.js

I obviously don’t want to bake that version number into every call or require() statement, so requireJS supports an ‘aliasing’ feature. In the config you can specify a path, in this format:

    paths: {
        "jquery": "jquery-2.1.1.min",
        "bootstrap": "bootstrap.min"
    }

Requesting the ‘jquery’ module should now actually ensure that jquery-2.1.1.min.js is loaded.

Except that it doesn’t… most of the time.

Loading the page on both IE and Chrome with developer tools, I can see that mostly that require.js is trying to load /Scripts/jquery.js – what gives‽ Changing the settings and trying different combinations seems to have bearing on what actually happens.

I felt like a kid who’d picked up the wrong remote control for his toy car: it didn’t matter what buttons I pressed, the car seemed to ignore me.

StackOverflow to the Rescue (again)

I don’t like going to StackOverflow too quickly. Often, even just writing the question prompts enough ideas to try other solutions and you find the answer yourself.

In this case I’d spent part of my weekend and two days, hunting through lots of tutorials and existing StackOverflow answers, trying to figure out WTF was going on. Finally a kind soul pointed me to the right solution:

A config/main file specified in the in the data-main attribute of the require.js script tag is loaded asynchronously.

To be fair, requireJS does try to make this reasonably clear here http://requirejs.org/docs/api.html#data-main – but this is just one little warning in the sea of information you are trying to learn.

This means that when you call require(…) you can’t be certain that the configuration is actually loaded or not. When I called require(..) just after the script tag for requireJS it had not been run, so it was using a default configuration.

What requireJS intended was that you load your modules just after setting the configuration, inside the config.js file. However, this approach only works if every page is running the same scripts.

So far the only way to get this to work has been to remove the data-main attribute and load Config.js as a separate script tag. At that point we can be sure the config has been applied and can specify dependencies and use aliases.

TypeScript files not compiling in VS2013 project

I created a C# class library project to hold some product-related code, and wanted to emit some JavaScript from this DLL, compiled from a TypeScript file. I added the .ts file and compiled the project, but no .js file was created.

The first thing to check is the Build Action is set to “TypeScriptCompile”, which it was – so why no .js output?

It seems the VS 2013 update 2 which is supposed to incorporate TypeScript compilation is not adding the required Import section to the project file.

If you add this line

  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" />

Just after the line

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Then the TypeScript compiler will be invoked.

My thanks to my colleague Sri for figuring this one out!

Reported as a bug on Microsoft Connect: https://connect.microsoft.com/VisualStudio/feedback/details/934285/adding-typescript-files-to-vs-2013-update-2-library-does-not-compile-typescript