Managing Javascript the easy way in Rails 7
Tags: rails, javascript, babel, rollup
Published: 2022-04-25 16:22:23

---
title: Managing Javascript the easy way in Rails 7
published: true
description: Getting assets to Just Work without fuss
tags: rails, javascript, babel, rollup
//cover_image: https://direct_url_to_image.jpg
---

Rails 7 is here, and with it, comes a whole new way of managing Javscript with ["UJS with import maps plus Turbo and Stimulus from Hotwire as the defaults"](https://world.hey.com/dhh/rails-7-will-have-three-great-answers-to-javascript-in-2021-8d68191b).

I'm a pretty experienced dev, but I must admit, I struggled to wrestle with these concepts, and I had a very short greenfield project that I wanted to get shipped and out the door as quickly as possible. 

I wanted an approach that mirrored what I was used to with Rails 6 and Webpacker, but while still using something that felt new, and wouldn't succumb to bitrot as soon as I shipped it.

After some background research and deliberation with colleagues, I eventually settled on [JSbundling](https://github.com/rails/jsbundling-rails). This allows a similar approach to asset building to the rest of the Javascript world, but with the added magic from the asset pipeline we know and love.

First, add `jsbundling-rails` to the Gemfile:

```ruby
gem "jsbundling-rails", "~> 1.0"
```

And run `bundle install`

Then run the generator (I'm using `rollup`, as it's marginally easier to grasp than Webpack, and is focussed wholly on building JS):

```bash
bin/rails javascript:install:rollup
```

This adds the following to your Rails app:

* An `app/assets/builds` folder - This will contain your built JS, which will then be served via the asset pipeline
* Add `app/javascript/application.js` file - This is the entrypoint for all your application's frontend Javascript
* A `bin/dev` file - This should be how you run your application in development. It runs (and optionally installs if you don't have it) [Foreman](https://github.com/ddollar/foreman), so you can run your server and build Javsacript on the fly
* A `Procfile.dev` file - This tells Foreman what processes to run when you run the `bin/dev` script
* A `rollup.config.js` file - This tells Rollup how to build the Javascript in your `application.js`

In addition, it also adds some extra bits and bobs to your `.gitignore` (so you don't commit the built files to git), and adds the `builds` folder to the `assets/manifest.js` file. It also adds the necessary dependencies to your package.json, as well as an NPM command to build your JS.

When you start the server with `bin/dev`, your application will spin up as usual, whilst also watching your application.js adn rollup.config.js for any changes and building the result.

## Some gotchas

This mainly worked for me off the bat, however, I had some teething problems.

Firstly, I wanted to use [Babel](https://babeljs.io/) to make sure my JS was compatible with older browsers (we still have to support IE11 as we work with Government clients). Adding this to me rollup.config.js seemed to work:

```javascript
import resolve from "@rollup/plugin-node-resolve"
import { babel } from '@rollup/plugin-babel';

export default {
  input: "app/javascript/application.js",
  output: {
    file: "app/assets/builds/application.js",
    format: "es",
    inlineDynamicImports: true,
    sourcemap: true
  },
  plugins: [
    babel({
      babelHelpers: 'runtime',
      exclude: 'node_modules/**',
      presets: [
        [
          '@babel/preset-env', {
            'useBuiltIns': 'usage',
            'corejs': '3'
          }
        ]
      ],
      plugins: ['@babel/plugin-transform-runtime']
    }),
    resolve()
  ]
}
```

(I also had to install `@babel/plugin-transform-runtime` and `@babel/runtime` for this to work).

Secondly, I like to keep my JS nice and modular, with seperate files for each piece of functionality, and `import`ing each file. For this to work, I had to set the rollup `output` format to `iife`(Immediately Invoked Function Expression), which wraps a function around all your code, so it runs as soon as the JS is loaded. Now, my rollup config looks something like this:

```javascript
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs';

import { babel } from '@rollup/plugin-babel';

export default {
  input: 'app/javascript/application.js',
  output: {
    file: 'app/assets/builds/application.js',
    format: 'iife',
    inlineDynamicImports: true,
    sourcemap: true,
  },
  plugins: [
    commonjs(),
    babel({
      babelHelpers: 'runtime',
      exclude: 'node_modules/**',
      presets: [
        [
          '@babel/preset-env', {
            'useBuiltIns': 'usage',
            'corejs': '3'
          }
        ]
      ],
      plugins: ['@babel/plugin-transform-runtime']
    }),
    resolve(),
  ]
}
```

And that's it! Hopefully this will save you hours of painful googling, and prevent you from getting migraine from staring at the same config file for hours on end!