1

Premise: I need to make a transparently reusable widget that uses jQuery and can work on any page (even if the page loads and uses its own version of jQuery); the widget will be loaded via a single <script="widget.js"> tag on the hosting page.

I am aware of the noConflict() proposed solution such as https://stackoverflow.com/a/528251/1710180

I have two concerns:

  1. as the widget itself is loaded via a JS tag, any <script src="jquery.js"> tag it will insert in the document to load the jQuery library will be loaded async (AFAIK even if using document.write()); therefore, the 2-step noConflict() resolution would be unreliable at best.
  2. the widget also would need to load various jQuery plugins; once jQuery would be resolved without conflict to, say, a jQuery_widget global, how could I make these plugins bind against jQuery_widget as opposed to jQuery ?

Best solution I could come up with is compile and package the whole mess using an ES6 processor; that would at least fix me the jQuery name conflict in the global namespace; however, some of the plugins I would use are not ES6-ready so point 2) would still be not addressed by this solution since they won't use an ES6 import.

Another dirty solution that comes to mind would be to load everything in an iframe and use iframe.contentWindow.$ to manipulate the host window; I don't know how reliable jQuery would be cross-frame though, I have never tried something like this.

Dinu
  • 1,374
  • 8
  • 21

1 Answers1

0

You have at least two options:

  1. Do what you're describing, which will involve your widget outputting script tags to the target page, triggering HTTP requests for each resource (jQuery, plugins, your code) that can't be predicted by the browser and thus cannot be pre-fetched. This will not be good for performance.

  2. Bundle up the code.

Let's take those in order.

Doing what your'e describing

...the widget will be loaded via a single <script="widget.js"> tag on the hosting page.

This part is critical to this working. If someone uses defer or async, it won't work. Because of the load time impact, I'd suggest advising users of your widget to put that script tag at the very end of their document, just prior to the closing </body> tag.

Using document.write, have your widget.js output your scripts in this order:

  1. Your copy of jQuery. This will temporarily take over the $ and jQuery global identifiers.
  2. All of the jQuery plugins you need. These will register themselves with jQuery via the jQuery global (if they're written properly).
  3. A script with your main code, which starts with (inside a scoping function):
    (function() {
        const $ = jQuery.noConflict(true);
        // ...the main body of your code here...
    })();
    
    ...which will return the $ and jQuery globals to what they were prior to Step #1 and give you a $ local within that scoping function to use in your code.

Bundling up the code

But, you can probably avoid that. Bundlers existed long before ES2015, but even if you can't find one, you can do it yourself: Literally just concatenate your various scripts in the order given above, with a ; in between them, into a single widget.js file and run it through any decent minifier. That will leave any pre-loaded jQuery on the page unaffected for the reasons described above, while requiring only a single HTTP request. It also lets your users use async or defer, since your script doesn't have to use document.write.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks, are you positive `document.write` loads ` – Dinu Dec 16 '20 at 11:19
  • For the ES6 processor, I've had this thought myself, however some of the plugins are not ES6-ready... but making my own custom packager as you suggested might just do the trick, coming to think of it... just wrapping everything in a sort of artificial global ns closure. Looks promising. – Dinu Dec 16 '20 at 11:25
  • @Dinu - *"Thanks, are you positive document.write loads – T.J. Crowder Dec 16 '20 at 11:27
  • @Dinu - *"... just wrapping everything in a sort of artificial global ns closure..."* Only **your** code, not jQuery or the widgets. jQuery isn't written to be wrapped in that way, and widgets may or may not be. – T.J. Crowder Dec 16 '20 at 11:27
  • (Actually, I haven't looked at jQuery in years, it may well be okay being wrapped in a scoping function. But there isn't any reason to given `noConflict(true)`.) – T.J. Crowder Dec 16 '20 at 11:36