Generic asset definitions and management

(The following is not a Rails-specific question, but relates to a problem that affects Rails, so I wonder whether anyone on the team has an opinion about it or knows whether a solution is already in development.)

While gem dependencies can be easily managed in a Gemfile by Bundler, when it comes to other assets, you either have to manually copy javascript files into the app/assets or vendor directory or find a gem that someone is keeping up-to-date that contains the Javascript, etc. files that you need that they in turn have to keep up to date with the original project from which the Javascript originates. This is not good for Rails long-term, because gems will end up showing up in rubygems for every Javascript library or framework and some may or may not be kept up-to-date, which just makes developing an application for Rails more difficult.

I happen to notice recently that someone made a request in the AngularUI project asking for them to add an npm for it, but AngularUI is client-side javascript (like Rails’ assets), and npm/npmjs.org is for node, so it is primarily a serverside repo/not meant to be for clientside, so stopped in its tracks: https://github.com/angular-ui/angular-ui/issues/184

There is also CPM which is like NPM but for javascript. However, you have to sign a Dojo CLA that many may not want to sign in order to share an asset with others: https://github.com/kriszyp/cpm

Maybe there is something else out there to package assets (at least javascript assets) generically, or perhaps someone from the Rails community could develop something that would allow Javascript library/framework developers to define a file similar to a gemspec, but that wouldn’t lock it into having to live in the vendor, etc. directory in their project. Then maybe the gem command could be able to process such a file somewhat similar to a gemspec and even lay the project out as part of packaging so that it would have a lib/libraryname/version.rb, etc. and would work as a gem and be injected into the asset pipeline if included?

rails engines are the path to this. Assets in engines will get included in the pipeline.

Yep, and there is a post about it here: http://prioritized.net/blog/gemify-assets-for-rails/

And here is an example of a gem that just includes some javascript files in the asset pipeline: https://rubygems.org/gems/angularjs-rails https://github.com/confuseddesi/angularjs-rails/

I was getting ready to add gems for a few other JS dependencies we had, because versioning and gem-ifying them seemed more manageable than not.

But then I thought about how easy it would be for projects like:

https://github.com/angular/angular.js https://github.com/angular-ui/angular-ui https://github.com/twitter/bootstrap

to just have some .asset file or similar in their root directory that could be used by various web framework implementations that could utilize that to generate a packaged, versioned asset as a gem.

To convert such an asset description file to a gem, you could easily automatically generate the following files:

(1) Create libraryname.gemspec:

# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require 'my_library_name-rails/version'

Gem::Specification.new do |s|

s.name = ‘my_library_name-rails’


s.version = MyLibraryName::Rails::VERSION


s.date = ‘2012-09-04’


s.summary = ‘My Library Name on Rails’


s.description = ‘Injects My Library Name into your asset pipeline.’


s.authors = [“Some Name”]


s.email = ‘some@email.address’


s.files = Dir["{lib,vendor}/**/*"] + ["SOME_LICENSE", "SOME_README"]

s.homepage = 'https://github.com/someone/my_library_name-rails/'
  s.license = 'SOME_LICENSE_TYPE'

end

Perhaps if the asset description file (like package.json) specified dependencies, it would also set the other libraries as gem dependencies, but that would be more involved, since I'm not sure what it would do if those gems didn't exist yet.

(2) Create lib/libraryname/version.rb:

module MyLibraryName

module Rails

VERSION = “1.2.3”

end

end

(3) Create lib/my_library_name.rb:

require "my_library_name-rails/version"

module MyLibraryName

module Rails

class Engine < ::Rails::Engine

end

end

end

(4) Copy assets from locations specified in the asset description file into vendor/assets/javascripts/ (or similar, based on some type defined in the generic asset description) and copy LICENSE from the external project into root.

(5) Generate a basic README that links to the original project for further detail on usage.

Then gem build (gemspec) && gem push (gem) and it would be available to everyone.

The asset file driving the process could either be a package.json file or some yet-to-be-determined suggested standardized format. (JSON seems like it would make sense.)

Creating a custom asset packaging file format and packager seems way more complex than just building a nearly blank Engine.

If I go to every Javascript library developer and put something of the equivalent of package.json or Gemfile in the root of their javascript project, they are a lot more likely to do that than they would be to write a gem.

And if they aren’t writing the gem, then someone else is, and if that someone else gets tired of maintaining the gem, then the gem doesn’t get updated. They you have people in Rails projects using old and perhaps insecure versions of Javascript libraries. Or worse, you have several versions of the some_javascript_library-rails gem in Rubygems, with the more relevantly named one being out of date.

Maybe the answer is to integrate Bundler to use NPM, but NPM is more geared towards Javascript on the server-side for Node, so I don’t think that is the right solution. It really seems like a better option is needed for managing versions and dependencies for client side assets, and doing that in such a way that it would be usable not just by Rails, but other frameworks, would increase the chance of adoption.

In the meantime though, I agree with you that just creating a gem with a blank engine is a workaround. It is just problematic and messy over the long-haul, and doesn’t solve the problem of clientside Javascript libraries, CSS, and other assets being something that should be cross-platform. Rails could take the lead in this area.

Sorry, I mean something that should be packaged and versioned in a platform/framework-agnostic manner.

To follow-up, even though the suggestion was to just create a gem to manage dependencies, it’s worth nothing that there are a number of other solutions to JS package management for clients.

Some discussed here:

Twitter’s Bower (similar to Component, requires Node, but not dependent on GitHub): https://github.com/twitter/bower

GitHub’s Component (requires Node and GitHub-dependent): https://github.com/component/component https://github.com/component/spec/wiki

Jam (requires Node): https://github.com/caolan/jam

Volo (requires Node): https://github.com/volojs/volo

Ender (requires Node): https://github.com/ender-js/Ender

Browserify (requires Node): https://github.com/substack/node-browserify

And not a package manager but an in-browser loader, somewhat similar- RequireJS: http://requirejs.org/

Has this topic advanced at all? I searched the mailing list and don’t see any mention of how Rails plans to handle versioned assets.

Creating gem wrappers for assets is not sustainable and raises potential security issues. This is a problem that is being worked on by the smart folks at Twitter and bower seems to be leading the way. Rails community want to take an opinion on this?

I don’t think this is a Rails problem. The problem is that the “Bundler/Rubygems for (client-side) JavaScript libraries” does not exists yet, and I think having “Rails people” “lead the way” here would be counterproductive.

Bower is making some good progress, and is a promising candidate, but it is far from “THE RubyGems for (client-side) JavaScript libraries”. For example, “jquery” on bower currently points to components/jquery, and all your arguments about why mirroring jquery at rails/jquery-rails is a bad idea/non-sustainable would apply there as well. So again the core issue is that this is still an unsolved problem in the (client-side) JavaScript world, and there is not much we can do from Rails’ perspective until the community agrees on a solution.

However, if you are convinced that Bower is the future fro (client-side) JavaScript package management, or that you think it is “good enough” for consumption today, then all you have to do is to list your Bower dependencies somewhere in your project and write a Ruby client for Bower to fetch the packages and put them into vendor/assets.

In fact, a quick Google search seems to suggest such effort has already been started.