getting rails webpacker to apply javascripts (both yarn-managed and not) only in specific views

Viewed 119

In migrating a legacy rails application to Rails 6

3 javascript libraries are used only on two pages and given the size of one, the goal is to call them only on those pages.

yarn add grapheme-splitter

ran properly, but the other two libraries are not listed through yarn. Thus application.js contains:

require("grapheme-splitter")
import("../src/segments_calculator")
import("../src/segments_viewer")

However when invoking these scripts

<%= javascript_pack_tag 'grapheme_splitter' %>
[...]
<%= javascript_pack_tag 'segments_calculator' %>

Webpacker can't find grapheme_splitter in /Volumes/sjt/r/fidel/public/packs/manifest.json. Possible causes: [...]
Webpacker can't find segments_calculator in /Volumes/sjt/r/fidel/public/packs/manifest.json. Possible causes: [...]

Of all the initial causes, webpacker is configured to compile (& I've seen it do so) and There is no indication this file was ever modified (same creation and modification date)

development:
  <<: *default
  compile: true

Why would the fourth explanation then be valid your webpack configuration is not creating a manifest.? I do see it is missing form the manifest, but so are other libraries that are used only on specific pages (and running).

How can one get pages running where only the specific javascripts are to be loaded, both yarn-managed and not) ?

update

{
  "application.js": "/packs/js/application-3eebddca0d1bd59ab625.js",
  "application.js.map": "/packs/js/application-3eebddca0d1bd59ab625.js.map",
  "entrypoints": {
    "application": {
      "js": [
        "/packs/js/application-3eebddca0d1bd59ab625.js"
      ],
      "js.map": [
        "/packs/js/application-3eebddca0d1bd59ab625.js.map"
      ]
    },
    "foundation-datepicker": {
      "js": [
        "/packs/js/foundation-datepicker-a70417b8f24614757c48.js"
      ],
      "js.map": [
        "/packs/js/foundation-datepicker-a70417b8f24614757c48.js.map"
      ]
    },
    "municipals": {
      "js": [
        "/packs/js/municipals-ba2af3f728fdf4b1104e.js"
      ],
      "js.map": [
        "/packs/js/municipals-ba2af3f728fdf4b1104e.js.map"
      ]
    },
    "what-input": {
      "js": [
        "/packs/js/what-input-f10be0b1923799dfc64d.js"
      ],
      "js.map": [
        "/packs/js/what-input-f10be0b1923799dfc64d.js.map"
      ]
    }
  },
  "foundation-datepicker.js": "/packs/js/foundation-datepicker-a70417b8f24614757c48.js",
  "foundation-datepicker.js.map": "/packs/js/foundation-datepicker-a70417b8f24614757c48.js.map",
  "municipals.js": "/packs/js/municipals-ba2af3f728fdf4b1104e.js",
  "municipals.js.map": "/packs/js/municipals-ba2af3f728fdf4b1104e.js.map",
  "what-input.js": "/packs/js/what-input-f10be0b1923799dfc64d.js",
  "what-input.js.map": "/packs/js/what-input-f10be0b1923799dfc64d.js.map"
}
1 Answers

Background

The contents of your "grapheme-splitter" and "segments_calculator" modules are contained in your "application.js" bundled file.

When webpack compiles, it creates one or more dependency graphs. By Webpack-er convention, the "entry points" to your dependency graph(s) are the files contained in the app/javascript/packs/ directory. Each of these "pack" files gets compiled into the public/packs/ directory as output (assuming you haven't configured webpack to share modules across packs).

Quick Fix

So by putting these lines in your "application.js" pack file:

require("grapheme-splitter")
import("../src/segments_calculator")
import("../src/segments_viewer")

you're saying: include these modules in the "application.js" file that gets output into the public/packs/ directory. To render these modules on a given page then, you'd need to use <%= javascript_pack_tag 'application' %>. Otherwise, you could put these modules in a different pack file and render that instead. This should be good enough to get you started.

Better Fix

However, you asked about JavaScript in specific views. The recommended approach for "page-specific" JavaScript with modern JavaScript bundlers like webpack is to use dynamic imports and a single (or very few) pack files instead. Currently, it looks like you already have four packs— "application", "foundation-datepicker", "municipals", "what-input"—which (in my opinion and generally speaking) is too many for a small application. You're running the risk of duplicating modules and creating performance bottlenecks, as described in this post.

With dynamic imports, you can defer rendering parts of your dependency graph and use an event listener or a function call to download and execute small chunks at a time. An example might look like:

window.addEventListener('DOMContentLoaded', () => {
  if (document.getElementById("graphene-page")) {
    import('../src/graphene-page') // <-- dynamic import
  }
});
Related