systemjs / systemjs

Dynamic ES module loader

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Conflicting SystemJS instances — how to resolve?

halostatue opened this issue · comments

  • SystemJS Version: 6.14.12
  • Which library are you using?
    • system.js
    • s.js
    • system-node.cjs
  • Which extras are you using? None.
  • Are you using any custom hooks? No.

Question

We create a widget that is loaded through a single loader script that we provide to our clients. Our previous version of the script was hand-written and worked well for the webpack/vue widget we had. We have since moved to Vite / Rollup and Svelte and switched to the s.js version of SystemJS and format: 'system' output for Rollup. This worked nicely until our most recent client who has a different version of SystemJS already installed (digging finds that it is 0.19.46).

Our initial version did an import 'systemjs/s.js and was packed into an IIFE for the URL we provided to clients. The only configuration performed after that was to (a) add the anchor element and (b) System.import(entry_url) (where entry_url is the specific URL provided by the client). When that happens with our most recent client, our loaded version of s.js overwrites their SystemJS configuration and they can no longer load many of the modules already in place.

After the better part of a week's investigation, I now have a version that does if (window.System == null) { /* create script element for systemjs/s.js and then run loadWidget on script.onload, then append to the end of boddy */ } else { /* run loadWidget */ }. The loadWidget function does both (a) and (b) described above. To ensure stability, our build process copies s.min.js from node_modules into the destination directory and always loads that when there is no existing window.System.

In the standalone case, it works fine (as expected). However, now that we are trying to reuse the client's version of SystemJS, our widget does not load with Module https://our-widget-js interpreted as global module format, but calling System.register.

I can state with 99% certainty that telling our client that they need to rework their use of SystemJS into something more modern is a non-starter. I also don't want to build smarts into our code to try to figure out how to clone their configuration, and I don't know how compatible 0.19.46 is with 6.14.12 from a rollup format: 'system' perspective. It does not appear that there is a version of SystemJS that I can bundle which would allow me to have a completely isolated version of SystemJS for versioning, and the SFX bundles mentioned in #561 appear to be no longer possible — and I can't see how the options mentioned in #2194 would be executed because my only access into the client site is through my singular loader script.

Any assistance here in resolving this would be appreciated.

I was exploring new System.constructor(), but that does not help me as it appears to be a full clone of the parent SystemJS (with configuration) instead of being a fresh unconfigured instance that I could then call mySystem.import(entry_url) and have reasonable assurance that it would work since that is how we are using our SJS.

Although hacky and unsupported, you can make your own custom build of systemjs. The main thing of interest is that SystemJS installs itself to envGlobal https://github.com/systemjs/systemjs/blob/main/dist/s.js (line 14 and 504) which is defined as the unqualified variable self if present.

var envGlobal = hasSelf ? self : global;
.
.
.
envGlobal.System = new SystemJS();

So in an IIFE you can define your own self variable and SystemJS will install itself into that.

(function() {
var self = {};

// copy s.js to here

window.MySystemJS = self;

})();

Now you can call MySystemJS.System.import(...) and be completely isolated

Now you can call MySystemJS.System.import(...) and be completely isolated

As far as I can tell, I would need to also modify my bundles (created by Rollup using format: system) to refer to MySystemJS.System.register. Am I correct about that, or will the System.register that the downloaded bundles see be scoped to MySystemJS.System?

As far as I can tell, I would need to also modify my bundles (created by Rollup using format: system) to refer to MySystemJS.System.register. Am I correct about that, or will the System.register that the downloaded bundles see be scoped to MySystemJS.System?

Yes, unfortunately you are right about that (most of my modules are globals, so I wasn't thinking of System.register()). Some (not very good choices) I can think of:

  1. As you suggested, change all your System.register to MySystemJS.System.register in your modules (could probably be a sed script you run on your output).

  2. If you only have to worry about System being defined, but not amd methods (define), instruct rollup to produce amd modules and use the amd compatibility mode of SystemJS. You'd also need to copy the define method from MySystemJS to window at the bottom of the code above. Semantics of amd are a bit different than SystemJS, so may or may not work depending on how complex your imports are.

  3. If it's safe to target only browsers implementing fetch, you can put SystemJS in its fetch-eval mode (override the shouldFetch method to always return true). Then you make a custom eval (which your version of System would use) where you swap with the global System and then eval:

(function() {
var self = {};

// note: redefining eval like this is not supported in strict mode, but will work in a `loose` function
var eval = function(code) {
  var backupSystem = window.System;
  try{
    window.System = self.System;
    window.eval(code); // indirect call of eval -- will always execute in the global scope and is much safer
  } finally {
    window.System = backupSystem;
 }
};

// copy s.js to here

self.System.constructor.prototype.shouldFetch = function () { return true; };
window.MySystemJS = self; // or maybe `window.MySystemJS = self.System` to be a little simpler

})();

Can add a fetch polyfill too if needed. Loaded scripts need to be samesite or CORS enabled.

Thank you. You can assume that I have a rant that I don’t want to have to think about any of this stuff, just be able to deploy and have it work without conflicts. It doesn't help that the client is on an ancient version of SystemJS that does not work like modern versions of SystemJS at all, so I think that I was always going to end up having to do something … "unsafe" like this.

I'll see if I can do some text-replace magic in the bundles &c. to see if the concept will work and try from there.

This works, but it is as ugly as it sounds.