How To Add Javascript To Ruby On Rails
Rails 7 and JavaScript
Posted on September 8, 2021
Or: Rails and JavaScript, Part 5
A quick programme note: If you lot like this newsletter, you might like my contempo books: "Modernistic Front-End Evolution for Rail" (Ebook) (Amazon) and "Mod CSS With Tailwind" (Ebook) (Amazon). If you've already read and enjoyed either book, I would greatly capeesh your help by giving a rating on Amazon. Thanks!
If you are really interested in how we got here, I wrote about the history of Runway and JavaScript, read Part 1, Role 2, Office iii, and Part iv.
Over the final few weeks, DHH and the Rails core squad take appear a number of different JavaScript build tools for use with Rails 7. As I was writing this very mail, DHH wrote his own post outlining the strategy for Runway seven and JavaScript.
I'm going to try and sort out what it all means for yous every bit as a potential Rails programmer.
A couple of warnings:
- This is all unreleased and new, it is guaranteed to change, though I call back that at present that DHH has appear the whole strategy, it might have stabilized some. All the relevant tools are however being updated, though and the integrations and implications are nevertheless not clear.
- I've been testing these by upgrading Elmer, my project tracking tool. I like Elmer, but it'southward kind of small and it uses Hotwire, then it doesn't have many JavaScript dependencies. Larger projects with more complex JS dependencies will probably accept other bug.
Hither goes.
Rails 7 offers most v different of means to collaborate client-side lawmaking from a Rail app, depending on how you count. Omakase, baby!
- The classic asset pipeline route via Sprockets and manifest files yet will work every bit far as I tin tell, simply I think you might want to look at a newer tool.
- Webpacker is still nether active evolution, and should release a new version more or less simultaneously with Rails 7. I definitely accept from DHH'southward post, though, that Webpacker is soft-deprecated in favor of the JS Bundling approach.
- Rails 7 will back up "JavaScript Bundling" as of literally ten minutes ago as I started this post. The JavaScript bundling tool uses the existing Yarn and
package.jsontooling, but places the parcel into the nugget pipeline. For you lot your bundling took you can use Webpack, esbuild, a webpack replacement that bills itself equally "An extremely fast JavaScript bundler", which I suppose is better than a only fast one, or a slow one, or Rollup. Rollup doesn't have a fancy marketing slogan, but information technology's besides a JavaScript module bundler. - The default Rails vii tooling is called "import maps", which is a browser tool that lets you map a logical name to a downloaded module directly in the browser without needing to practise further bundling on the server for the browser, and a Rails wrapper to manage that mapping from your codes.
- Finally, y'all tin can just apply Rails as an API, and manage your customer side code as a separate project using whatever tooling you want.
As an aside, this is a example where information technology'd be real dainty if Rails had a more formalized RFP or road map process, then nosotros mere mortals had some sense of what the Rails core squad is actually planning and recommending, rather than having to scramble through DHH's Twitter feed or wait for an announcement on Hey World. The strategy here is interesting, it would have been useful to see information technology laid out in advance.
Simply Tell Me Which 1 I Should Use!
Earlier I write a gazillion words on this topic, let me summarize the options, Wirecutter fashion.
Best Option for Most Projects: I call up information technology'll exist the jsbundling-rail gem, or whatsoever they wind upward naming it. The combination of the flexibility of a JavaScript bundler and the Rails convenience of using the asset pipeline – information technology'south early on days, but this seems like a combination that volition work for a lot of different projects.
If I was redoing the book right now, I think I'd build the project using jsbundling-rails considering I'd be able to support the Stimulus and React code and not have to spend two chapters explaining webpack, which is less fun than it sounds.
Too Groovy if You lot are Using Hotwire: The Importmap-rail solution avoids any build step at all which seems like it'd easier to develop with. From my brief experience, import maps are super fast in development.
Importmap as a Rails tool seems a little limited (it was pointed out to me that it's not clear how to deal with JS libraries that likewise bundle CSS). I call up, but I'yard non sure, it'd accept transitive dependency issues where NPM allows multiple versions of dependent projects. Also, by blueprint, no transpiling, which ways no TypeScript, and potential JS versioning issues. Just import map seems like a corking approach if you can stay inside its lines.
I will probably endeavor to keep my Elmer project on import maps, considering it was the easiest to go working and I'm curious to work with them.
Also Nevertheless There If You Demand That Level Of Complexity: Webpacker, which I would at present generally recommend if you really demand the webpack ecosystem and plugins for functioning or if you lot were doing something that you just can't do in esbuild or Rollup.
I'm not at all sure I would recommend a new project jump on the Webpacker train, and I'd suggest that existing Webpacker 5 projects consider the jsbundling-rails route instead of upgrading to Webpacker 6 – they seem like similar levels of endeavour.
Also there: Using Rails equally an API only. I'one thousand glad the option is at that place, but all I really take to say about the API-only road is that I probably wouldn't pick information technology on my projects without a very, very strong reason, only that I bet a lot of people will attempt it.
What's the problem Rails is trying to solve?
Someone suggested that the problem is that DHH hates JavaScript, but I don't recollect that's quite it. I think that DHH doesn't like being tied to the JavaScript ecosystem, but hey, I don't like being tied to it either. Similar many Cherry developers, I find the Cherry-red ecosystem to be easier to manage.
Specifically, webpack feels big, complicated, and fragile. It feels, to me, like it breaks every time I await at it cross-eyed, and I take used Webpacker since information technology came out and I literally wrote a book on it. I think that Webpacker has non been as successful as hoped at making webpack feel easier and more Rail-like.
I made an incorrect prediction here – when Webpacker came out, I thought the Rail community would write a lot of Ruby extensions that would make Webpacker even easier to deal with, just that didn't happen at all.
Now, I think browsers have defenseless upward with developers to a point where it'south possible for projects to have much smaller build overheads than earlier, and Rails is trying to allow projects to take advantage of that if they can.
Runway 7
To get these new features working fully, you demand to update to Rails vii, which currently just lives in the main branch on GitHub. I don't recommend running your product app off of Rails main, though Basecamp does, and then if you have that level of control over your dependencies, I suppose go for it.
Simply quickly, here's how I do a Rails upgrade like this:
- Update the Rails version in the Gemfile to
gem "rails", github: "rails/rails". - Run the
rails app:upgradetask. I take all the file changes when information technology asks for them. - Then I become through all the changes in my git browser, and more granularly check them. Unremarkably what I'chiliad looking for here is custom changes on my office that have been removed and which I need to put back.
- The Rails Guide on updates often has specific actress changes you lot need to make, at this betoken I don't think in that location was annihilation new. (I fifty-fifty wound upwardly deleting the defaults file and simply changing the version in the configuration)
- Runway 7 no longer updates the Gemfile as part of
app:upgrade, so I went to the actual Runway code for the template and checked for changes. This resulted in me removinglistenandjumpneither of which are part of Rails 7 default.
I did take a couple of hiccups. The Devise precious stone has a couple of compatibility PR'south that haven't been merged yet, gem "devise", github: "strobilomyces/devise", branch: "patch-1" is a temporary branch that has the changes incorporated. I had to work around another jewel temporarily.
CSS
One of the specific goals of getting off Webpacker is non doing CSS through webpack, which I retrieve a lot of people found especially confusing. So one goal here is to move people to asset pipeline CSS if you were not already doing so.
Sass is no longer a default. Instead, Rail 7 will have a new cssbundling-rails gem that allows you to cull between Tailwind (using the tailwind-css gem to install Tailwind into the asset pipeline), PostCSS, or Dart Sass as your CSS processor of option.
For Elmer, all I needed to do was follow the cssbundling-track installation instructions, choose Tailwind, delete Sass, and move my very small number of CSS selectors to the asset pipeline root at app/avails/stylesheets/application.css and everything continued to work.
I did, however, move image files out of the webpacker managing director and back to public/images, where the regular Rail image_tag could be used to display them. Probably, though I should put SVG files into Track helpers and so that they tin be better styled.
JavaScript
A common feature of all the Rails 7 JavaScript build tools is a new default directory construction. (Webpacker hasn't caught upwards yet, simply will likely by the time you read this).
The new organisation puts all JavaScript lawmaking in app/javascript (singular – I know that sprockets used to pluralize it). JavaScript files at the top level are considered to be entry points, and I recollect the idea is that optimally there should be exactly one entrypoint at app/javascript/application.js.
For Elmer, this involved moving my file in app/packs/entrypoint (the Webpacker RC4 directory) to app/javascript/application.js, so moving my other directories (generally Stimulus controllers) underneath app/javascript.
Using jsbundling
I'll but go through jsbundling with one of the supported bundlers – esbuild, because it was released first.
The idea behind jsbundling is that yous still use Yarn and the parcel.json file, but the congenital bundle goes into the asset pipeline download rather than being controlled by webpack. (And, I call up, that you simply packet JavaScript, not CSS, and non static files).
Here'southward what I did:
- Started with a Rails seven upgrade, and the CSS changes above.
- Moved all my JavaScript code to
app/javascript. - Add together
jsbundling-railto the Gemfile, removingwebpacker. - Cleared Tailwind and css stuff from the
package.jsonfile. Also cleared Webpack from information technology, making thepackage.jsonfile a lot simpler. - Ran
bundle installand./bin/rails javascript:install:esbuild(or replaceesbuildwithrolluporwebpack. The installer- Creates an
app/assets/builddirectory, appends that directory to the asset pipeline manifest atapp/assets/config/manifest.jsand.gitignoresouthward information technology. - Adds a
javascript_include_tag "awarding"to theawarding.html.erblayout. - Offers to override
package.json, merely the just thing it adds is a script"build": "esbuild app/javascript/*.* --package --outdir=app/assets/builds"– fifty-fifty if y'all keep yourpacket.json, you still need to add the script. - Runs
yarn add together esbuild.
- Creates an
- Re-ran
hotwire-railinstall, because the Stimulusindex.jsneeds to exist aware of this. This actually gave me the Stimulus 3.0 beta.
The default Stimulus installation uses require and a glob to autoload Stimulus controllers. You can't exercise that in esbuild, and then I needed to manually import all my Stimulus controllers with a for each controller line like: import("./css_controller").and then(c => application.register("css", c.default)) – I'1000 quite positive this will be addressed soon.
At this point the recommended course of action is to run the evolution Track server in one terminal and esbuild in another with yarn build --watch.
And this worked. JS file changes triggered a reasonably fast rebuild and the bundle was distributed to the page.
Import Maps
Import maps are a unlike way of thinking virtually sending JavaScript to the browser.
If I can oversimplify, the webpack family unit of products let you lot to reference other files or external modules inside your code because they resolve all the references at packaging fourth dimension, and send a single file to the browser with all the references, and then the browser merely finds code in that file.
Once you lot are already bundling your JavaScript lawmaking, all kinds of boosted characteristic become desirable, such as minifying lawmaking. And in one case you've got a big bundle, all kinds of other things attach to it and down that route, y'all get to webpack.
What import maps ask, is what if you didn't exercise any of that?
Instead, what y'all do is send downward all the individual files separately, and also send a text map relating the logical names yous use in the code with the physical download URLs.
From the importmap-rails readme:
This frees you from needing Webpack, Yarn, npm, or any other part of the JavaScript toolchain. All you demand is the asset pipeline that'due south already included in Rail.
With this approach y'all'll ship many small JavaScript files instead of one big JavaScript file. Thanks to HTTP2 that no longer carries a material performance penalty during the initial transport, and in fact offers substantial benefits over the long run due to better caching dynamics. Whereas before any change to any JavaScript file included in your big parcel would invalidate the cache for the the whole parcel, now only the cache for that single file is invalidated.
The immediate trouble is how to manage sending an accurate map of files downwardly to the browser, which is where the importmap-rail gem comes in. The gem provides a view helper to add the import map to your header, and a control line tool to manage the dependencies that go into the import map.
Here's what I did to make this work, starting dorsum from the Rails 6 version of Elmer:
- Upgraded to Rails seven. The Rails standard JavaScript libraries are updated in Rails 7 to work with import maps
- Moved all my JavaScript code to
app/javascript. - Add
importmap-railto the Gemfile, removingwebpacker. - Run
parcel installand./bin/rail importmap:install.
The installer adds the new javascript_import_tags helper into the application layout, adds app/javascript/awarding.js to the classic asset pipeline manifest in app/assets/config/manifest.js, sets upwards an initial importmap.rb file and adds an importmap command line tool.
- I updated the
hotwire-runwayinstall by rerunning that install command – it changes the Stimulus installation to use importmaps to place Stimulus controllers. The name for the bodily Stimulus distribution inverse to@hotwired/stimulus, then all my references needed to update.
I changed my application.js to remove the Webpacker epitome path and besides ActionCable and ActiveSupport references that I'grand not currently using, leaving me with simply this:
import "@hotwired/turbo-rail" ; import "controllers" ; Rail UJS is at present soft-deprecated, so I'k non including information technology. Elmer didn't use its behavior much, so I think I'm ok. (I had to alter some link_to helpers to button_to.)
The head of my layout file now looks like this:
<head> <championship>Elmer</title> <meta proper name="viewport" content="width=device-width,initial-scale=i"> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag "inter-font", "information-turbo-runway": "reload" %> <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> <%= stylesheet_link_tag "awarding", media: "all" %> <%= javascript_importmap_tags %> </caput> The peak two stylesheet tags were added by tailwind-rail and the bottom 1 is the classic nugget pipeline that's going to take hold of app/assets/stylesheets/application.css.
The javascript_importmap_tags came from importmap-rails.
Nosotros're so close to this working, only I do accept some small external JavaScript dependencies, I have a grade polyfill and a tool that I'yard using for drag and drop lists.
The importmap control line tool lets united states of america manage these external dependencies. The website JSPM provides CDN hosting for NPM modules and an API for generating a listing of dependencies for NPM modules. The command line tool lets united states of america "pin" dependencies into the importmap using this API.
So, from the command line:
$ bin/importmap pin form-asking-submit-polyfill $ bin/importmap pin sortablejs If I wanted to remove them I'd use importmap unpin <the affair>.
Which results in an importmap.rb file like this:
pin "application" pin "@hotwired/stimulus" , to : "stimulus.js" pin "@hotwired/stimulus-importmap-autoloader" , to : "stimulus-importmap-autoloader.js" pin_all_from "app/javascript/controllers" , under : "controllers" pin "@hotwired/turbo-rails" , to : "turbo.js" pin "form-request-submit-polyfill" , to : "https://ga.jspm.io/npm:form-asking-submit-polyfill@2.0.0/form-request-submit-polyfill.js" pivot "sortablejs" , to : "https://ga.jspm.io/npm:sortablejs@1.14.0/modular/sortable.esm.js" The first line is pinning my entry point at awarding.js, the next three pivot lines are mapping Stimulus and Turbo locations to actual modules that are downloaded every bit part of the hotwire-rails precious stone. The pin_all_from is mapping everything in the app/javascript/controllers directory to the logical proper noun controllers – the browser will still load the index.js in that directory when it's referenced. And the final two lines are mapping my external dependencies to a CDN that can provide them to the browser (ga.jspm.io is a CDN that exists precisely for this purposed, maintained by the same people that manage the import map specification)
This works. Turbo loads, Stimulus loads, Tailwind loads. My Cypress tests work (I'll need to keep a minimal bundle.json file around for them), and in fact are quite a bit faster to offset run because they aren't getting the overhead of a webpack build before the test run. (They might also be more stable, only I'm not sure about that even so).
The HTML page source gives me this
And then that's a big long listing of imports mapping logical names to locations, and then a big long list of link tags downloading those locations – notice that the external dependencies are coming from the CDN (there is a manner to download and vendor them if you lot'd rather not have that dependency).
That's an beauteous amount of Merely Working, I got this going with relatively fiddling frustration, and information technology does feel stable and it's nice to not have a build tool – development feels very responsive. I definitely had the feeling of being able to write and display JS code rapidly that I hadn't really had in several years.
Wrapping Upwards
As the dust settles, there's pretty good story here:
- Apply importmap if you lot are not using much JS, you'll go a great programmer feel at the cost of some limited interaction with the JS globe.
- Use 1 of the jsbundler tools if you lot desire more than interaction with the JS world.
- Utilize Rails equally an API if you lot really have a Single Page App that is very big and very circuitous.
I'm excited about it, I think information technology's going to exist a amend developer feel.
Comments
How To Add Javascript To Ruby On Rails,
Source: https://noelrappin.com/blog/2021/09/rails-7-and-javascript/
Posted by: pascarellafehe1948.blogspot.com

0 Response to "How To Add Javascript To Ruby On Rails"
Post a Comment