JavaScript intercept module import
Asked Answered
C

1

14

I have a SPA (in Aurelia / TypeScript but that should not matter) which uses SystemJS. Let's say it runs at http://spa:5000/app.

It sometimes loads JavaScript modules like waterservice/external.js on demand from an external URL like http://otherhost:5002/fetchmodule?moduleId=waterservice.external.js. I use SystemJS.import(url) to do this and it works fine.

But when this external module wants to import another module with a simple import { OtherClass } from './other-class'; this (comprehensiblely) does not work. When loaded by the SPA it looks at http://spa:5000/app/other-class.js. In this case I have to intercept the path/location to redirect it to http://otherhost:5002/fetchmodule?moduleId=other-class.js.

Note: The Typescript compilation for waterservice/external.ts works find because the typescript compiler can find ./other-class.ts easily. Obviously I cannot use an absolute URL for the import.

How can I intercept the module loading inside a module I am importing with SystemJS?

One approach I already tested is to add a mapping in the SystemJS configuration. If I import it like import { OtherClass } from 'other-class'; and add a mapping like "other-class": "http://otherhost:5002/fetchmodule?moduleId=other-class" it works. But if this approach is good, how can I add mapping dynamically at runtime?

Other approaches like a generic load url interception are welcome too.


Update

I tried to intercept SystemJS as suggest by artem like this

var systemLoader = SystemJS;
var defaultNormalize = systemLoader.normalize;
systemLoader.normalize = function(name, parentName) {
    console.error("Intercepting", name, parentName);
    return defaultNormalize(name, parentName);
}

This would normally not change anything but produce some console output to see what is going on. Unfortunately this seems to do change something as I get an error Uncaught (in promise) TypeError: this.has is not a function inside system.js.

Then I tried to add mappings with SystemJS.config({map: ...});. Surprisingly this function works incremental, so when I call it, it does not loose the already provided mappings. So I can do:

System.config({map: {
   "other-class": `http://otherhost:5002/fetchModule?moduleId=other-class.js`
}});

This does not work with relative paths (those which start with . or ..) but if I put the shared ones in the root this works out.

I would still prefer to intercept the loading to be able to handle more scenarios but at the moment I have no idea which has function is missing in the above approach.

Chablis answered 4/8, 2016 at 12:3 Comment(0)
S
3

how can I add mapping dynamically at runtime?

AFAIK SystemJS can be configured at any time just by calling

SystemJS.config({ map: { additional-mappings-here ... }});

If it does not work for you, you can override loader.normalize and add your own mapping from module ids to URLs there. Something along these lines:

// assuming you have one global SystemJS instance
var loader = SystemJS;

var defaultNormalize = loader.normalize;
loader.normalize = function(name, parentName) {
    if (parentName == 'your-external-module' && name == 'your-external-submodule') {
        return Promise.resolve('your-submodule-url');
    } else {
        return defaultNormalize.call(loader, name, parentName);
    }
}

I have no idea if this will work with typescript or not. Also, you will have to figure out what names exactly are passed to loader.normalize in your case.

Also, if you use systemjs builder to bundle your code, you will need to add that override to the loader used by builder (and that's whole another story).

Satem answered 12/8, 2016 at 19:0 Comment(3)
Because of a public holiday I did not have time to look at this suggestion right now but will do so. Awarding the bounty because it otherwise would expire. I might come back with questions though ;)Chablis
Thanks for your insight. I have updated the question with both attempts. Do you have an idea why the interception of normalize produces an error in my case?Chablis
Oh sorry my mistake - defaultNormalize needs this argument: defaultNoramlize.call(loader, name, parentName);Satem

© 2022 - 2024 — McMap. All rights reserved.