Why We Stopped Using Lineman.js

Lineman is a node-based command line productivity utility for managing client-side applications. It's basically a wrapper around grunt and testem and also provides a nifty mechanism for faking server-side APIs, allowing parallel development on the client and server. I work for Manta Media Inc., and we were using lineman for frontend development. But after almost a year, we've realized that, for us, it's more like technical debt.

Migration

First, a little backstory on how we came to use lineman in the first place. Manta's website used to be backed by a monolithic Perl application that included, amongst other things, a home-rolled ORM. And it was slow and difficult to maintain and had very few tests. And besides all that - let me reemphasize - it was written in Perl. Perl is fine, really . . . until you start trying to recruit top level developers. Who wants to go work every day on a giant Perl application when you could be working with technologies from this millenium instead? So for those reasons, and because our company in general was in a state of transition, we began a platform migration project to move the entirety of our frontend application into a node.js app powered by express (on the server-side) and angular.js (on the client-side) that talked to Java-based services. Perhaps there will be time, in some future post, to delve into this migration in greater detail. It is, after all, a thrilling tale. A swashbuckler to be sure. But for now, the key point is that we were migrating to new technologies (new to us, I mean), and we needed some help with that.

We knew that we wanted to build something better than the previous application, something maintainable, something covered by tests, something that used well-documented industry-standard tools. Tools like grunt. If you're unfamiliar with grunt (first of all, what?), it's a task runner that automates build processes, such as converting less or sass to css, concatenating and minifying javascript, and running tests. There are hundreds (thousands?) of grunt plugins for just about any task you can think of (including printing "hello world" in your terminal - see grunt-helloworld). But we didn't choose grunt, did we? No, the title gives it away. We chose lineman. Partially, that was due to the fact that we hired testdouble to consult on test-driven development methodologies, and lineman is written and maintained by testdouble, and partially, we just didn't know any better.

But wait, isn't this a post about moving away from lineman? Yes. Yes it is. I was just coming to that.

Lineman: the good parts

Lineman is not a bad tool. It really helped us get off the ground quickly because it wraps a lot of the build process up neatly and abstracts the messy details away so that newbies, as we were at the time, can simply and easily get started. Its single greatest feature is its short learning curve.

On our Perl platform, we had (as it seemed we did with all things) a home-rolled asset concatenation/minification system, so something that just worked out of the box with little to no configuration, setup, overhead, or research (and was maintained by someone else who understood the details) was enticing. Lineman served our purposes nobly at first. We even became somewhat savvy in the technology, enough so that we added our own configuration, changed defaults, installed plugins, wrote custom tasks, overrode the custom task execution order . . . and at some point we realized that the simplicity that originally made lineman so fantastic was actually now working against us.

At last, the reasons we changed

So a few months ago, I expressed in our frontend slack channel the feeling I was having that lineman had sort of outlived its use in our platform and cited the following reasons we should remove the abstraction and just use grunt itself.

1. Lineman is resource intensive.

Lineman is extremely resource intensive. Partly, that's because file watching is expensive if you watch a lot of files. But also, lineman runs its own node server (for the fake API previously mentioned). Which means, when you use it to start your own server, you're actually running two different node servers, in addition to file watching. And that gets expensive. Some of our developers observed 50+% cpu usage.

2. Lineman abstracts a little too much away

Or maybe the abstraction is just done in such a way that it's difficult to hack into. Regardless, we often found that making changes to the lineman config was cumbersome and the outcome often difficult to predict. The abstraction was great at the beginning when we didn't understand what we were doing and only needed the core out-of-the-box functionality. But as we started to change things, to add things, to put things in different orders, it became harder and harder to manage. By the end, I might've been the only one on our team willing to mess with the lineman config because it was just hard to track what was happening. What originally was abstraction, by the end was more like concealment.

3. Lineman is obscenely extensible . . . most of the time

"Obscenely extensible" is lineman's own claim. But the thing about a tool that wraps other tools is that no matter how extensible you try to make it, eventually there is a use case you just did not expect, and then it's actually more difficult for users to do that thing because, not only do they have the initial problem, they have the tool itself preventing them solving the problem because everything is abstracted away. This is actually the issue that made me realize that we had outgrown lineman. I found that I needed two different testem configurations, one for our normal tests, and one to run those tests under coverage and generate a coverage report. Lineman doesn't (as far as I could tell at the time) provide a way to have more than one testem configuration and because testem was actually wrapped up by lineman, it was difficult to figure out if there was a way to hack it to make it do what I wanted.

4. Lineman's config is long and messy

This would also normally be true of traditional Gruntfiles, but at least with Gruntfiles you don't have anything like this:

loadNpmTasks: app.loadNpmTasks.filter(function(task) { return task !== 'grunt-ngmin'; }).concat(['grunt-browserify', 'grunt-jasmine-bundle', 'grunt-env', 'grunt-ng-annotate']  

(That is real line from our lineman configuration.)

However, when we switched, we started using a tool that I had previously built and published for use in my own projects. Task-master lets you break your grunt configuration into multiple files and then loads those pieces, assembles them, and passes the resultant object to grunt.initConfig. It also does a few other handy things, like plug into jit-grunt, permit overriding pieces of configuration, and allow specifying aliases easily and succinctly. This approach has turned out to be really useful because it provides simplicity to task configuration while maintaining readability and coherence.

5. We were dependent on lineman's lifecycle

Lineman is still a project that is growing and improving, which is good thing, but it did mean that we were dependent on other people for functionality we might want. And as you might expect with an open source library, sometimes those changes were fast and sometimes they weren't.

6. It's hard to stay up to date with lineman

Yes . . . we were simultaneously wanting lineman to fix and update more quickly and to just slow down. Sometimes it felt like they could've been competing (probably with Java) for an award for shortest lifecycle. I'd update lineman to the latest version (because again, I was the only willing to mess with it by the end), and a week later we'd be one minor version and four patches behind. On the one hand, you want the libraries you use to be "under development," and you often see comments on github that say things like, "Is this still being maintained? Hasn't been updated in two months." The problem was not knowing whether updating would make something break inexplicably. Well, it was probably excplicable, if it weren't for the abstraction hiding it. Pretty early on in our use of lineman (but after testdouble had stopped consulting with us), we realized we were like eight minor versions behind the current, stable version. Normally, I'd say, "Eh, who cares?" and move on, but I had recently opened an issue on lineman, and when aksed what version we were on, I was told "Latest lineman or it didn't happen." So I started updating it, but it turned out the API had changed somewhat, and I had maybe a week of frustration trying to figure out how to get everything working again with the new version.

7. Grunt is less proprietary

Lineman is open source, but what I mean is that grunt is a well-known, well-documented, stable paradigm in the node.js community. That means that new developers coming on board would likely have more familiarity with it than with lineman and that answers to questions would be easier to find. Case in point, if you search "lineman" on stack overflow, you get 34 hits. If you search "grunt", you get over 10,000. There are less than 30 questions tagged lineman. There are nearly 500 tagged "grunt" or "gruntjs" (plus there are many tagged with specific plugins, like "grunt-contrib-less").

8. We might actual publish grunt plugins

We have some custom grunt tasks, including a task that runs our tests in parallel and produces parallel, readable output (grunt-concurrent will do the first, but not the second). I, personally, have published a number of grunt plugins. Our team, in general, which has it's own npm account and has published modules previously, would be far more likely to open source a grunt plugin than a lineman plugin.

9. We had a lot of duplication between client and server code

Admittedly, we could've solved this problem and kept lineman, but not having lineman made it a lot easier. Our github repository has a server directory, in which our node application resides, and a client directory for all our frontend javascript, as well as our build process code (i.e. lineman configuration). We had separate Gruntfiles in each directory, duplicated tasks (like one to start our express server), and a whole host of duplicated npm modules. As part of our lineman removal, we moved our Gruntfile and package.json up to the root level so that both client and server would have access to the same tasks and third-party code. More on this point in another post.

10. We just weren't in lineman's niche anymore

Lineman is perfect for start up projects because you can get off the ground quickly with reasonable defaults. It's perfect for people who don't know how to use grunt and/or don't want to customize their build process. It's not perfect for large web applications that need a ton of customization and configuration, especially when the developers are highly self-motivated and will learn grunt on their own anyway. And that was clearly us.

Conclusion

In future posts, I'll detail how we went about migrating from lineman to grunt and what we saw as a result, so I don't want to say too much toward that point here. But I will say that, in general, our build process is much more flexible and accessible now that we don't use lineman, and we've really expanded what we can do with grunt tasks.

comments powered by Disqus