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 toMySystemJS.System.register
. Am I correct about that, or will theSystem.register
that the downloaded bundles see be scoped toMySystemJS.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:
-
As you suggested, change all your
System.register
toMySystemJS.System.register
in your modules (could probably be ased
script you run on your output). -
If you only have to worry about
System
being defined, but notamd
methods (define
), instruct rollup to produceamd
modules and use the amd compatibility mode of SystemJS. You'd also need to copy thedefine
method fromMySystemJS
towindow
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. -
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 customeval
(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.