Should @rails npm packages be locked to exact versions?

We were recently upgrading a Rails 6.1 app to Rails 7.0, one that uses shakapacker/webpack. As part of the

Change the versions for Rails JavaScript packages in package.json

step in the general Moving between versions section of the Upgrade guide we updated the @rails/actiontext, @rails/activestorage and @rails/ujs packages from ^6.1.710 to ^7.0.806. We were surprised that the versions actually installed as a result were a mix of 7.2.x and 7.1.x package versions.

The reason for newer versions being installed is obvious given the meaning of the ^ (caret) symbol in package.json, but this led to a question:

Should @rails npm packages be locked to an exact version that matches the Rails gem?

I couldn’t find a definitive answer to this online, so thought it was worth asking here in the hope of a clear answer for future searchers.

Off the top of my head I can think of some arguments both for and against.

Arguments in favour of matching the versions exactly:

  • The ruby and JS code lives in the same repository, and has likely only ever been tested with the versions matched. Using out-of-sync versions is like mixing files from different commits and expecting it to always work.
  • A matching npm version is released with every version of the ruby gem. I think this happens even if the JS wasn’t changed, which suggests an expectation that the versions are matched exactly.

Arguments in favour of a more relaxed approach:

  • Rails commands that add JS packages seem to add them with a ^ caret and doesn’t specify an exact version. In fact, running bin/rails action_text:install using Rails 7.0.8.6 adds "@rails/actiontext": "^8.0.0" to package.json, which is a different major version entirely!
  • Technically if there were any breaking changes in the JS package, then it shouldn’t really be released under the same major version anyway.

Could anyone help to clarify what is the Rails Way? Dropping the caret to ensure you get an exact version, or making use of newer versions of JS packages even when using older ruby gems?

1 Like