At today’s dev discussion in Boston, we discussed alternatives to the Rails asset pipeline for JavaScript apps. My notes follow - please feel free to add to the disucssion!
Rails Asset Pipeline
Pros:
- It “just works”
- CoffeeScript and Sass out of the box
- Can chain on ERB to inject things like environment variables at compile time.
- Fingerprinting for free - so long as you use the right helpers for images, css, etc.
- version your JavaScript dependencies with asset gems
** Cons**:
- Asset gems are terrible. Often lag official release, often have unnecessary dependencies. Consider railsassets.org which fronts bower.
- Smashes all your JavaScript together in the global namespace.
JavaScript Apps
If you’re writing an app that doesn’t use Rails for the front end, you should consider using something other than the Rails Asset pipeline for compilation. This is certainly the case if your backend doesn’t use rails at all. Unfortunately, the JavaScript community doesn’t really have a canonical, do-it-all solution. There are several decisions you need to make.
Package Managers
There are two, slightly incompatible package managers that are typically used for this.
- NPM - mostly stuff you can use in Node but also has many (most? all?) typical front end dependencies available as well
- Bower - front end focused. It’s not clear to us why this needs to exist in addition to NPM.
You will probably want a command line tool to actually run the compilation, so you’re probably going to need Node anyway. You also need to install bower via npm. Both package managers lack support for something as thorough as Gemfile.lock, so you should probably check these dependencies in, vendored.
Task Runners/Compilers
Lots of different options here.
- Grunt - oldest. Configuration based, reliant on each step outputting files. Tends to get unwieldy and is slower than newer runners.
- Gulp - Seems to be winning favor in the JS community. Things are done in code rather than described with JSON. It works with streams, so there are no intermediate files. Fast.
- Webpack - New(ish). Not a general purpose task runner. No one had much experience with this. Seemed to be used with React?
- Broccoli - Not a general purpose task runner.Exists exclusively to be an asset pipeline. Used by ember_cli.
Module Formats
Using something that isn’t sprockets for your asset pipeline means you can take advantage of various JS module systems.
- CommonJS - like what node uses:
require('./path/to/file')
. Use theexports
object to expose things. - AMD - requireJS is the canonical implementation here
- ECMAScript 6 Modules - Coming soon to a browser near you. A middle ground hoping to please both AMD and CJS users. Seems most similar to CJS.
- UMD - something @christoomey said and was maybe making up. You never can tell.
- Angular - angular has its own module system.
Browserify allows you to use common-js style require’s in your browser code. It also lets you use any (probably a lie) node module there. If you’re using browserify, you should probably stick to NPM. You can use packages from bower if you need to with a shim. That shim takes your browser code, npm-ifies it so it works in NPM, then browserifies it so you can require
it in the browser. Right…
Sass
When compiling Sass with JS, you’re probably using libsass. The advantage here is speed. It’s significantly faster than sass compilation in ruby. Unfortunately, it’s not 100% compatible with Ruby Sass, which is still the canonical implementation.
What about…
- Individual files in development/concatenated in production
- Adding digests to the file names
There are various packages that do this. Usemin, for instance. Sounded like we needed more experience to make decisions here…
Recommendations
- If you’re writing an ember app with a rails back end (or otherwise) consider splitting it into two repositories. Use
ember_cli
andbroccoli
for your asset pipeline needs. - If you’re using angular, there’s less of a reason to do this as you still need to use its module system.
There was some feeling that separating the front end as its own repository promoted it to a first class citizen rather than being rooted down in app/assets. It also lets you more readily use whatever conventions arise around your framework of choice.