-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rails 5.1 Webpacker Cache Support #892
Conversation
This would fix #654 |
@michaelherold This is ready for review again. |
It all makes sense to me, but I think it requires review from one of the owners of the repo. I was commenting merely because our app wouldn't receive the benefit of this change if we excluded 6.0.0.rc1. 😁 |
…emove old versions, container image keeps growing
@kpheasey I could also greatly use this, I've got pretty much the exact same problem and build times as you do. |
You can use it straight from github. |
Would also love to see this PR merged, as we currently have deploy times of 5 to 10 minutes. |
Hello. I previously closed a similar PR due to my concerns here: #876 (comment). TLDR is that webpack does not ship with caching and is not generally cache aware (i.e. cache invalidation is not a "solved" problem in webpack). I do not want to bake in caching support for a framework that does not directly provide caching support. In general, I would say that i'm willing to cache whatever heroku/nodejs is willing to cache, so node modules seems fair game, I'm less familiar with the yarn cache. I would guess that caching
In my experience, caching node modules would save about 60 seconds, but the other 4-9 minutes might be coming from webpack configuration. People who are reporting webpack build times of 30-45 minutes (rare but it happens) are definetly in this camp. Any thoughts here @jmorrell |
You make a valid argument here and in the previous comments. That being said, build times are crippling workflows for anyone building a modern Rails application, and something needs to be done about it. This PR simply utilizes existing caching features as much as possible in order to reduce build times and slug size. It's not meant to improve caching in Webpacker, Sprockets, or any other tool.
I understand that this solution isn't perfect, but it does reduce build times and reduce slug size. It does not reinvent the wheel or try to solve caching problems for tools that don't provide caching. Yarn and Webpacker both have caching support in some way.
Webpacker uses yarn as the package manager and the benefits of doing that are lost without including the yarn cache. https://yarnpkg.com/lang/en/docs/cli/cache/
The node_modules cache saves the package download time, however, if the yarn cache isn't saved yarn will re-download everything because nothing is linked. The other time saved is during assets:precompile, because webpacker has a cache to build from. Without the webpacker cache, the precompile starts from scratch. This is equivalent to |
I noticed that the nodejs buildpack respects my package.json's cacheDirectories list for what folders to cache; could the Ruby buildpack do the same, so we can choose our folders? Instead of having no caching, or a stalemate over what folders should be hard coded in. |
I found this PR from this blog post. I'd love to see this problem solved in the official buildpack! |
This is great but it should load based on the presence of webpacker instead of the Rails version. |
Agreed |
Ideally I want to get out of the node/webpack game. One way to do that would be to delegate responsibility to the node buildpack, but that's been "on the table" for the last few years and we've made no progress. I think the best intermediate option is to get together with the node maintainer and try to reach some kind of a parity, or alternatively figure out a way to better let the node buildpack do more weight lifting for people who want to use it. Right now if you have the node buildpack and then the ruby buildpack we install your dependencies twice. The only way I could avoid that is by detecting it somehow and then telling the |
…es not remove old versions, container image keeps growing" This reverts commit a9781db.
# Conflicts: # lib/language_pack.rb
That's worth looking into more. When sprockets introduced the same feature it took a full Rails version release to get the bugs out. I'll have to dig in some more. Next on my plate is shipping CNB branch and some other housekeeping and performance fixes. Also observability work is queued up. Ideally, I would still like to not have this problem. I.e. delegate to the Node buildpack instead of i both of us reimplementing logic. We got a new Node maintainer who is also focused on shipping and refining CNB. We've gone down this path before but didn't make much room. There's a number of edge cases to consider such as how to stop webpacker from re-running it's own Short termThat's all long term. I think the best chance in getting this functionality short term might look like this:
So anyone who wanted this perf bump could take two steps: Add a buildpack, add a gem. And that's it. I know it's not as nice as natively supported code in the Ruby buildpack, but at least it's something people could use today rather than being blocked by my official blessing and support. TimelineI can't forecast when i'll be able to prioritize the webpacker cache work. I do appreciate the effort that has gone in here and also being updated with developments in webpacker. If you or someone else on this thread wants to be able to move forward my suggestion would be to use existing supported interfaces to hook into Heroku to get the functionality you want today. |
I would like to share a short-term solution we have been using which doesn't require a fork of the buildpack or a wrapper gem. We use these buildpacks to provide caching: and this one to cleanup: These are very simple buildpacks which we forked and customised the cacheload/save to allow for caching files outside of the project directory (but not really necessary). If you add these buildpacks in this order:
And add these files to your project:
That should provide you with webpacker cache support and you can customise it for other things. We for example use Elm which requires caching more directories as well. |
@PChambino that's great, thanks for sharing. I like the nice simple buildpacks. One thing to note is yarn defaults to always installing packages from the Internet, even if the package is available in the cache. With Rails default To read from the cache first, and then fallback to online, you have to use the
|
@PChambino thanks for sharing. I see from your .slugcleanup file that you're removing the node_modules directory. How do you handle binaries required while running though, since the node_modules/.bin directory gets cleaned up? Heroku's .slugcleanup doesn't allow us exclude folders |
Thanks again @PChambino for these build pack references, it's worked really well for us.
namespace :yarn do
task :install do
# don't run if webpacker doesn't need to run
next unless Webpacker::Instance.new.compiler.stale?
# These yarn settings come from the nodejs buildpack
# https://github.com/heroku/heroku-buildpack-nodejs/blob/02af461e42c37e7d10120e94cb1f2fa8508cb2a5/lib/dependencies.sh
yarn_flags = "--production=false --frozen-lockfile --ignore-engines --prefer-offline --cache-folder=~/.cache/yarn"
puts "*** [yarn:install] Configuring yarn with all build dependencies"
command = "yarn install #{yarn_flags}"
puts "Running: #{command}"
system command
end
end
This has reduced our slug size by a couple hundred megabytes, even with the extra cache directory from (By the way, Bootsnap loosely and unofficially recommends that you clean the bootsnap cache with some regularity.) |
@0duaht We don't rely on any binaries during runtime only during build, so that is not an issue for us. There are other clean buildpacks that can do a bit more complex things, so you can look for one that works for you or change that one. Or just keep the node_modules directory, it is mostly for reducing slug size but usually that is not really required. |
@PChambino thanks, yeah for now, we've had to keep the directory and just clear out other directories |
inspired by heroku#892
@PChambino & @bbugh thank you so much for sharing these! |
I'm not going to merge this. I've suggested in a prior comment a way to expose the build cache if you wanted to implement something like this yourself. I'm working towards a re-write of the ruby buildpack and going forward i'm going to rely on the node buildpack to install and cache node and yarn dependencies. I'll also look to them for finding out how generally safe caching webpack assets is. |
In terms of caching build-related products, specifically, the |
Parallelize Cypress There is a build in parallel functionality with cypress: See https://github.com/cypress-io/github-action#parallel But we can't use this without paying for a cypress.io account (originally tried it and we blew through the limit of free usage in 1 day. Instead, add a new command that randomly splits feature files based on the number of nodes available. We get diminishing returns the more nodes we add, so 6 seems like a good sweet spot. Parallelize rspec Adds a new command that randomly splits tests randomly, similar to cypress Additional js improvements: - Improve yarn install command use flags based off the node heroku buildpack: heroku/heroku-buildpack-ruby#892 (comment) This seems to prevent the occasional times that yarn was just ignoring the cache and taking 5 minutes to install rather than 30s - Remove existing caching steps These are already covered by the action we are using - Remove yarn installation step Yarn is preinstalled - Remove Misc step definition meta tests This is a test for a single step that isn't used in any test and takes ages to run
I was thinking this was mostly around build performance and speed, but I'm now wondering how webpack expects to be used regarding multiple assets over time. Sprockets will retain the last 3 asset versions, Heroku supports this via the cache, but we do not cache I'm surprised if that's the case, that this hasn't come up before. |
@schneems Yes, the I worked around the missing build cache by using the |
Rails 5.1 includes Webpacker by default, but heroku-buildback-ruby only has the most basic support for calling
yarn install
. Currently, builds are taking a long time and are very bulky. This PR aims to complete the webpacker integration by adding the necessary caching and cleanup operations to allow for quick and trim builds.Features
Defines new Rails51 langauge pack that does the following:
node_modules
~/.yarn-cache
~/.cache/yarn
tmp/cache/webpacker
public/packs
node_modules
tmp/cache/webpacker
Rails51#load_asset_cache
method that is called beforeassets:precompile
is runRails51#store_asset_cache
method that is called afterassets:precompile
is runNotes
If you are using webpacker 4.1 or older, the slug size will keep increasing because the old packs are not removed. Install the clean-webpack-plugin to delete old packs.
With our Rails 5.2 application, we saw the following improvements:
UPDATE 2019-11-14
Refactored the way we define cache paths and included the updated yarn cache path.
UPDATE 2019-09-18
Added
public/packs
back. Included note on installingclean-webpack-plugin
.UPDATE 2019-07-25
Removed
public/packs
from the asset cache directories because there was no way to remove old assets.rake assets:clean
only removes old assets frompublic/assets
, there is an open webpack request for this same functionality, rails/webpacker#1410.This change increased build time from ~3 minutes to ~5 minutes. It's still faster and smaller.