Mind Dump, Tech And Life Blog
written by Ivan Alenko
published under license CC4-BY
posted at 01. May '23

How to Fix ViteJS/Rollup, Bootstrap4 and jQuery- cannot find, externalize ignored

Recently I did an upgrade from Webpack 3 (Webpacker, what have you done when you just abandoned it?) to ViteJS which uses Rollup internally. As a Ruby on Rails application with some history, it loads jQuery globally from a gem going through Sprockets instead of using module (import/export) and going through Webpacker or ViteJS.

While ViteJS works all right when configured properly, it has two main pain points - errors and fragility:

  • it does not react predictably for changes in a configuration and code - once it compiles all modules without any problems, then on CI it fails
  • randomly kills a docker container after successful compilation from rake task when using it in the same container with rails application, and always kills docker container when running in a separate container
  • if Vite server does not respond, it just fails randomly without much information and kills a container

So when set up properly, it works ok…but still - I don’t trust it completely and it is a risk which needs to be factored in. Are there better alternatives? No, not really.

Applications with old jQuery code have some specifics:

  • many jQuery plugins or jQuery UI are loaded from gems (sometimes with Rails integration code) or Rails Assets repository - plugins just add themselves to window.jQuery or window.$
  • or they are dropped in app/assets/javascript or vendor/assets/javascript
  • jQuery cannot be added to package.json in addition to a gem, because the latter loaded jQuery will win - race condition and jQuery from package.json don’t have any plugins loaded
  • using just jQuery from package.json and then load plugins from app/assets/ is not possible, because ViteJS will include it as a module <script .... type="module"> which loads asynchronously and also is deferred by default, so even with global export it will work or not randomly, because of random load order - so you’ll get errors like $(selector).autocomplete() is not defined. In theory one could modify ViteJS helper to include synchronously, but what about module format? AMD, UMD, CommonJS, ES6…it is just fucked up and I don’t understand it very well

There are two main configuration options how to force ViteJS code to use global jQuery variable:

  1. External - “jquery” package will be translated to $.
   rollupOptions: {
      external: ["jquery"],
      output: {
        globals: {
          jquery: "$",
        },
      },
    },

Should work, right? Not necessarily. I dug up https://github.com/rollup/rollup/issues/2857 here, that it works only for IIFF UMD:

So if I understand you correctly you expect there to be no imports in the SystemJS file because you used the globals option. The globals option, however, is only observed by IIFE and UMD bundles (and by UMD bundles only if they are not used in a Node or AMD context). All other formats ignore the globals option because in those formats, external means exactly what you see here, i.e. the module is imported by the wrapper. In a way, this is a duplicate of #2374. @eight04 wrote a plugin to address this, check out rollup-plugin-external-globals

I think ES6 modules are not UMD https://github.com/rollup/rollup/issues/2374.

  1. rollup-plugin-external-globals

So I added https://github.com/eight04/rollup-plugin-external-globals, but I’m not sure if it works properly.

While externalize works in a sense it will not complain about undefined $ variable, I had to remove all import $ from 'jquery', because otherwise I saw this:

Error: [vite]: Rollup failed to resolve import "jquery" from "/builds/whatever/app/javascript/entrypoints/application.js".
This is most likely unintended because it can break your application at runtime.
If you do want to externalize this module explicitly add it to
`build.rollupOptions.external`

But I already had externalized. Much frustration.

And I also had to copy Bootstrap 4 from node_modules/src/js/ to app/javascripts/bootstrap4 and remove all import $ from 'jquery'. The weird thing is, I tried to reproduce it today and everything worked without any copying??? Mysteries of JavaScript compilation.

Enough rambling, here is my ViteJS configuration - vite.config.js - it adds Rails plugin, React plugin to remember state when HMR, SVGr plugin to be able to import SVG as a React component (similar to Webpack one) and this external globals plugin which should work, but I’m not sure - since I had to remove those imports. Also there is a setting for HMR to bring a browser to use port 80 so it goes through Nginx proxy and not directly to port 3306:

import { defineConfig } from "vite";
import RailsPlugin from "vite-plugin-rails";
import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr";
import externalGlobals from "rollup-plugin-external-globals";

export default defineConfig({
  build: {
    rollupOptions: {
      // Add _all_ external dependencies here
      external: ["jquery"],
      output: {
        globals: {
          jquery: "$",
        },
      },
    },
  },
  plugins: [
    RailsPlugin(),
    react(),
    svgr(),
    externalGlobals({
      jquery: "$",
    }),
  ],
  server: {
    hmr: {
      clientPort: 80,
    },
  },
});

I feel I need some time to reflect on this topic and maybe later it will be clear why it sometimes throws weird errors.

For new applications, just use ES6 modules. There is already Bootstrap 5 without jQuery, but who knows if there are any developers since Elon Musk fired like 2/3 of engineers from Twitter. Avoid using old packages which do not work with import/export. There are many fancy alternatives. And do not use Sprockets for JavaScript code.

And for fuck sake do not load CSS (or images!) with JavaScript otherwise I need to allow the site in my noscript plugin - I’m looking at you old Phoenix apps and random node/PHP/whatever apps with just basic Webpack support which do import 'some_package/src/application.css'.

That’s all.

Add Comment