4

Problem

Our deployed application has development dependencies in it. We have a lot of development dependencies. This increases the artifact size and the memory consumption in production, as all those dependencies are require'd. Most instances are deployed in the cloud, so more memory = more money for larger instances. We would like to reduce the size/memory and make a more clear separation between the deployed artifact and the development environment. A particular focus is the need for therubyrhino in production environments even though our assets are precompiled.

Context

This question has some extremely highly upvoted comments that are asking the same thing (see this one and this one), but I don't actually see any answers.

Looking at the Rails upgrade guide, the following is suggested: enter image description here

As well, asset precompilation is done with the following command:
RAILS_ENV=production rake assets:precompile

As discussed in the linked question, this means that all gems are required in production. Fundamentally this doesn't make sense to me, and I feel like I'm missing something obvious. The whole point of asset precompilation is that we're avoiding doing it in production, so this command (so far as I understand it) should be something along the lines of:
RAILS_ENV=development RAILS_ENV_TARGET=production rake assets:precompile
Or some business.

I've read the discussion on an old Rails ticket here, and it seems to leave the question unanswered - how do we get development dependencies out of the production environment? One user in particular sums up the same problem here

It still strikes me that memory-bloat-by-default with things like therubyracer in production is poor, particularly if precompiling is core's recommendation and a widely-held best practice at this point. Many people likely never stop to consider that that's even happening if they came to Rails after the assets group removal or never gave much thought to it serving that purpose -- at least a suggestive comment in the generated Gemfile might be a good idea.

It's now a yak shave for developers to work around this for gems they know are unneeded in production web or worker processes since loading the group was removed from the precompile task. I'm basically now including this as boilerplate in new apps: namespace :assets do # Override sprockets-rails task to put back assets group require, so as to # avoid memory bloat in web processes :-/ task :environment do Bundler.require(:assets) Rake::Task['environment'].invoke end end
plus restoring Bundler.require(*Rails.groups(assets: %w[development test])) to config/application.rb. What a mess.

FYI, du reports therubyracer as 17MB on my machine, and it doesn't use autoload. We're not using any CoffeeScript view templates.

The author of that comment suggests a workaround, but later on in the thread deficiencies are discussed with that strategy, which makes me nervous.

tl;dr:

How do we remove development dependencies from production run-time? Alternatively, what am I missing as to why this ability would be desirable/the default?

Community
  • 1
  • 1
Toby Murray
  • 521
  • 3
  • 14

1 Answers1

4

How do we remove development dependencies from production run-time?

This comment in the thread you referenced has instructions for enabling the old :assets group behavior:

Change Bundler.require(*Rails.groups) to Bundler.require(*Rails.groups(assets: %w[development test])) in config/application.rb, and add this to your rake tasks:

namespace :assets do
  # Override sprockets-rails task to put back assets group require, so as to
  # avoid memory bloat in web processes :-/
  task :environment do
    Bundler.require(:assets)
    Rake::Task['environment'].invoke
  end
end

Alternatively, what am I missing as to why this ability would be desirable/the default?

So, strictly speaking, these aren't development dependencies. You shouldn't really think of them that way, because asset precompilation should happen in a production environment, even if it's just during deploy. You definitely shouldn't be precompiling on your developer machine.

On top of that, the line between asset-specific gems and production gems has become more blurred since the earlier versions of the asset pipeline. For instance, many gems now expect a javascript interpreter to be available. Also, many Rails apps now use .coffee templates in views (instead of .js.erb), and because those can't be precompiled, coffeescript must be available in production.

Basically, as rails contributors started removing more and more gems from the :assets group, they realized that it didn't really need to exist anymore, and would simplify things if it just went away. It had only existed in the first place so to avoid unintended compilation-on-demand, but the asset pipeline was updated in Rails 4 to expect to serve only static assets by default.

In the end it may not have been the most memory-optimal decision (since you're requireing a bunch of gems you don't ever use) but it was the most universally compatible.

Edit: Also refer to this question for more discussion/answers to this question.

Community
  • 1
  • 1
smudge
  • 801
  • 1
  • 6
  • 21
  • what's the difference between precompiling where the Rails environment is set to "production" and an actual production environment? We precompile our assets on our CI server, then then bundle them up with the rest of the app and drop that in production. If you're able to precompile for production in a development environment, why wouldn't you? It'll be interesting to see where this goes with respect to the blessing of "API only" Rails, even more reason to Rails development dependencies. – Toby Murray Sep 29 '16 at 13:48
  • Also, thanks for the thorough answer! Interesting to hear that this was somewhat of an intentional decision. It still strikes me as odd, Rails doesn't bundle PostgreSQL just because many people use it - it's installed seperately on the host. Why is a JS interpreter different? Or is it not, it's just a common enough use case that it seemed easier this way? I'm having a hard time thinking of anything other than a JS interpreter that would make sense to deploy for applications where assets are entirely precompiled - any examples of what else is driving the current design decision? – Toby Murray Sep 29 '16 at 13:53
  • I don't think this really solves the problem completely. It would get the gems out of memory, but they would still have to be present on the production machine because things like `rake db:migrate` will hit the `environment` task as well, wouldn't they? It's sounding like the most viable way to really separate development and production environments is to avoid `Bundler.require`... – Toby Murray Sep 29 '16 at 20:34
  • When it comes to deploys, your CI server *is* part of your production environment. You want to be able to test for deployment issues the same way you'd test your app itself. So maybe it makes more sense to compare "production" to "staging" -- your staging environment should allow you to catch and reproduce precompilation issues in an environment that mimics the real production deploy. – smudge Sep 30 '16 at 21:22
  • That's also why I don't consider these dependencies "development," and why it's not a good idea to precompile on your local machine. Because on a team of, say, 10 people, if someone's assets are precompiling incorrectly and they don't realize it, how are you going to detect it before they break your production deploy? And even if you detect it, how can you reproduce and debug it without borrowing their laptop? – smudge Sep 30 '16 at 21:25