From 78d1c9bb242a6b6afb0208353dd8e17850cf6689 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 25 May 2026 15:13:13 +0000 Subject: [PATCH] Inject jQuery for legacy plugins Legacy jQuery plugins need to attach themselves to the same jQuery instance that application code imports from ESM. Wrap enjoyhint and tablesorter with that instance while they load so precompiled assets do not route their UMD wrappers through a separate CommonJS jQuery copy. --- frontend/esbuild/plugins.ts | 67 +++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/frontend/esbuild/plugins.ts b/frontend/esbuild/plugins.ts index 282c8959d0c..be23182ae79 100644 --- a/frontend/esbuild/plugins.ts +++ b/frontend/esbuild/plugins.ts @@ -29,6 +29,8 @@ */ import type { Plugin } from 'esbuild'; +import * as fs from 'fs'; +import * as path from 'node:path'; const customConfigPlugin:Plugin = { name: 'custom-config', @@ -36,7 +38,68 @@ const customConfigPlugin:Plugin = { if (options.chunkNames === '[name]-[hash]') { // named chunks options.chunkNames = '[dir]/[name]-[hash]'; } - } + }, +}; + +const jqueryInjectionPlugin:Plugin = { + name: 'jquery-injection', + setup(build) { + // Intercept legacy jQuery plugins that need the ESM jQuery instance. + build.onResolve({ filter: /^(core-vendor\/enjoyhint|tablesorter)$/ }, (args) => { + return { + path: args.path, + namespace: 'jquery-wrapper', + }; + }); + + // Provide the wrapper content + build.onLoad({ filter: /.*/, namespace: 'jquery-wrapper' }, async (args) => { + const workingDir = build.initialOptions.absWorkingDir ?? process.cwd(); + const modulePath = args.path === 'tablesorter' + ? path.join(workingDir, 'node_modules', 'tablesorter', 'dist', 'js', 'jquery.tablesorter.combined.js') + : path.join(workingDir, 'src', 'vendor', 'enjoyhint.js'); + const contents = await fs.promises.readFile(modulePath, 'utf8'); + + // Wrap with jQuery import + const wrappedCode = ` +import jQuery from 'jquery'; +import 'jquery-migrate'; + +const previousJQuery = window.jQuery; +const previousDollar = window.$; +const hadPreviousJQuery = 'jQuery' in window; +const hadPreviousDollar = '$' in window; + +// Legacy jQuery plugins expect global jQuery while they load. +window.jQuery = jQuery; +window.$ = jQuery; + +const define = undefined; +const module = undefined; +const exports = undefined; + +${contents} + +if (hadPreviousJQuery) { + window.jQuery = previousJQuery; +} else { + delete window.jQuery; } -export default [customConfigPlugin]; +if (hadPreviousDollar) { + window.$ = previousDollar; +} else { + delete window.$; +} +`; + + return { + contents: wrappedCode, + loader: 'js', + resolveDir: path.join(workingDir, 'src'), + }; + }); + }, +}; + +export default [customConfigPlugin, jqueryInjectionPlugin];