Using `window.jQuery` instead of yarn version

Viewed 65

I'm trying to covert to Vite 3 with Vite Ruby (on Rails) from Webpacker & Webpack. One of the main blockers I've noticed is that my system is a CMS. Some of our older customers have some jQuery code directly within <script> tags in <head>. I can't easily change this, and because importing jQuery within Vite and assigning it to window with something like:

import jQuery from 'jquery'
window.$ = window.jQuery = jQuery

happens in a deferred manner with type="module" jquery isn't initialised when the code in the <head> runs. I've resorted to loading jQuery via a CDN <script> tag so that it's loaded synchronously before the <head> code executes. No big deal.

The problem I've encountered from here is when trying to use the jstree jQuery plugin. This is able to be imported, but it seems to automatically load the node_modules jQuery dependency, then mount itself on to that. When then subsequently trying to find $(element).jstree, we're referring to the window copy of jQuery and jstree doesn't exist on that. I can fix this by import $ from 'jquery' just before importing jstree, but then I'm essentially getting the user to download jQuery twice.

What I'd like to do is have the window version of jQuery provided any time a package tries to import 'jQuery'. I've tried rollup's external configuration but that didn't seem to work. Keen to give anything a go really. For now I've resorted to loading jstree from a CDN too but that feels less clean.

I've tried the following config so far:

import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'

export default defineConfig({
  plugins: [
    RubyPlugin(),
  ],
  server: {
    hmr: {
      host: 'vitedevserver.test',
      clientPort: 443
    }
  },
  build: {
    rollupOptions: {
      external: ['jquery'],
      output: {
        globals: {
          jquery: '$'
        }
      }
    }
  }
})
1 Answers

So there's quite a bit that I needed to understand about this issue first.

Vite uses esbuild for "Dependency Pre-Bundling" in development. So it's esbuild that changes require('jquery') into a full code injection of the jQuery library within the file that included it. We need to use a plugin at the esbuild level to override this behaviour in the case of jQuery and just point to the copy of jQuery that already exists on window. Here's my final config. I needed to install the following plugin too:

$ yarn add @fal-works/esbuild-plugin-global-externals
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import { globalExternals } from "@fal-works/esbuild-plugin-global-externals";

export default defineConfig({
  plugins: [
    RubyPlugin()
  ],
  server: {
    hmr: {
      host: 'vitedevserver.test',
      clientPort: 443
    }
  },
  optimizeDeps: {
    esbuildOptions: {
      plugins: [globalExternals({ jquery: { type: "cjs", varName: "$" } })]
    }
  }
})

It's important to target cjs or the global still didn't work in the case of jstree.

Whether this will be a problem in the production build that uses @rollup/plugin-commonjs remains to be seen. I assume I might need to use a plugin in that environment for this too.

Related