David Bashford

A web dev blog from a Washington DC based Dad, UI architect, web developer, and creator of Mimosa.

    

Mimosa 2.1.4, require.js support improvements, thx r.js + esprima

Mimosa v2.1.4 brings with it something I've procrastinaed on for awhile thinking it would be much harder than it was. v2.1.4 includes mimosa-require v2.0.0 which vastly improves how that module determines your JavaScript code's define and require dependencies/config.

How does it improve it?

Backstory

The functionality that is mimosa-require's require.js support was one of a few key reasons I built Mimosa in the first place (starting almost 2 years ago!). Brunch didn't support AMD/require. Grunt was young, hard to configure (still is) and...just no. And there really wasn't anything else.

I wanted something that would:

  • validate dependency paths
  • validate requirejs.config.paths
  • map paths, shim and shim dependency paths, etc
  • let me know a path is bad the second it is bad

This validation alone is a huge time saver. And it really comes in handy when reorganizing your codebase. Move folderX to folderY then work your way through all the pathing errors mimosa-require informs you of and you are set.

I also wanted something that could determine the "main" require.js files, build out a r.js config, run it and output the results. Ideally it would do this without a single line of config, just by being smart about how require.js works and by programmitically learning things about the code as it was processed.

But the way mimosa-require was determining your dependencies and requirejs config was, well, it was evil.

The Hack

mimosa-require used eval.

And I feel shame.

Rather than try and tackle any sort of complicated parsing or regex, I decided, maybe correctly at the time, that a better, quicker way to get the information mimosa-require needed was to eval your JavaScript code.

mimosa-require defined in-scope versions of define, require and requirejs, then evaled your JavaScript. The local version of, for instance, define would capture the dependency array and off it would go. It would even treat the callback function as a string and search for embedded require calls in the event you were using require.js' commonjs wrapper.

The fact that eval was there never caused problems in and of itself, but treating the code that way would.

Everything would work great if you all your code was wrapped in define and require, and it would work great if any code outside those function calls didn't attempt to access some scope that mimosa-require's server-side eval wouldn't know anything about.

But, if your code looked like this? Trouble.

var uA = window.navigator.userAgent;  
define(["a","b"], function(a, b) {  
  ...
});

mimosa-require would define a window, just to cover a lot of global variable cases, but window.navigator would not exist in-scope. So window.navigator.userAgent would throw an error and mimosa-require would be unable to do anything with this file.

Bummer.

It would be an even bigger bummer if this file was one of your "main" files. mimosa-require would now not know it was a main file and it would not build the combined file.

Use Esprima

I've had "require + esprima" on my ToDo list for a very long time, but I knew it would be a huge effort. So much to parse, so many edge cases. Ugh.

Then an issue was logged against mimosa-require that had this problem at its root and I finally decided to look into it.

While I love writing esprima AST parsing code -- for reals, its legit awesome to write -- I was not going to love writing this.

r.js to the Rescue

I remember shortly after I released Mimosa and mimosa-require that James Burke the creator of require.js switched how require.js itself parses code to use Esprima. Certainly the tool mimosa-require is trying to help you use -- and that has to do all the things that mimosa-require does, just not interactively -- can provide some guidance.

An API for this maybe? No such luck. And why would there be.

But there is a parse.js sitting in the r.js repo. And it does have a parse function. And it does have a findConfig function.

Well, that's awesome.

It's not exposed, and it doesn't quite do what I need it to, but some copy paste, some minor hacking and day after I started I had ripped out the eval BS and replaced it with r.js' legit esprima parsing.

Ugh

Seriously. That's all it took. Copy, paste, 75 lines of code changed/removed. I let the evil fester for an exceptionally long time because I knew it would be 1000+ lines of code to do it properly and I had a lot of other higher priorities. For whatever reason it didn't occur to me that those 1000+ lines of code already existed.

Win

So now there are far fewer caveats to the require.js support. mimosa-require won't run your code and it won't trip over anything. It's using the same parsing mechanism r.js uses, so all should be right.

So try it out.

comments powered by Disqus