-
Notifications
You must be signed in to change notification settings - Fork 45
Multiple instances of Loader #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
👍 Agreed, multiple loaders is very important. |
Maybe bringing back the Reflect.Loader constructor and System being only an instance, like how the API was at first in ES6 before getting pulled out into its own spec, would be the easiest way to resolve this. |
Multiple loaders could mean a few different things. Having separated out the Realm API from the Loader API makes it less clear to me how much of the functionality of multiple loaders should be achievable via For your use cases, do you only want different loading policies, but a shared registry between the two loaders? Or do you want to completely distinct registries? It seems reasonable to me to achieve distinct registries by creating new Realms, although maybe that's too hard to polyfill. |
The main use case for multiple loaders comes up when doing a build in Node. It is nice to not tie the build itself to the global environment it is running in, and be able to easily create and dispose loaders during the process. Being able to spin up a loading context without worry of clashes among other dependencies is very useful here. @matthewp can probably describe these use cases better as well. |
What @guybedford said, but also Loaders are useful whenever you need to load stuff that's not modules. For example Less.js has it's own loading pipeline which could be implemented as a custom Loader. This would make it more easily hackable. Eventually it would be interesting if HTML tags used custom Loaders to do the work. For example if all |
I haven't seen a good use-case that requires sharing the registry. I think spinning up a new instance in a new realm could keep things easy and simple. We have a pretty basic (and small) Realm polyfills that we can probably adjust to support this today. |
This seems like it's going to make creating custom loaders much more difficult. If I'm a module loader author I now have to tell my users to create a new Realm? The class-based approach as @sheerun suggested seems natural in this case. It makes extending far more elegant than writing wrappers around a singleton. But I admittedly missed the conversations, why would a Loader be tied to a Realm anyways? |
Another reason for multiple loaders is testing. The test driver code needs to use a solid Loader while allowing mocks and fakes to be loaded for code being tested. Forcing test drivers to work through Realms would be a big barrier. |
After sleeping on it I don't really see why I was confused. While in the olden days the Loader constructor would give you a separate registry and a separate realm, it makes sense to think of a loader as encapsulating a registry but simply being tied to a realm. IOW the Realm constructor lets you create a realm and the Loader constructor lets you create a registry. This all seems like a pretty natural separation of concerns. It also seems clear that fresh loaders should have distinct registries that start out empty. If you want to share between registries you can manually copy entries from one registry into another. (I guess one question would be whether there'd be use cases for actually keeping two registries in sync... maybe you could express that via the hooks?) To sum up:
Seem good? Dave |
👍 |
Sounds good! Will have to look it over when you update the spec. Being able to extend the default loader (whatever System is becoming) would be important as well. Assuming it's an instance of |
👍 |
@dherman I do have a use case in mind for keeping separate registries in sync. Doing this would require a way to iterate over the registry which I don't think is currently specced. I'll create a new issue to discuss this. |
(lurking) Is there still a default/global loader at some level? I.e., where do I go to |
To follow up on this - does this mean the class would set the initial hooks in the constructor itself: class MyLoader extends System.constructor {
constructor() {
super();
this.hook('fetch', function(url) {
// ... custom fetch implementation ...
});
}
} That seems to work out quite nicely I think, but just wanted to verify. |
@nevir Filed #34 to discuss that. @guybedford TBH I haven't thought carefully enough about subclassing. I'm interested to collect constraints; perhaps @johnjbarton has some too based on what Traceur is doing. I assume you did |
Related to this, why is |
Hm, I'm a little confused.
Well, it both gets and sets each hook.
It does define the pipeline, though, or at least it defines the extensible parts of the pipeline. It's how you can specify custom logic for hooking into the various steps of loading a module: how to fetch the payload, how/whether to translate the payload into source, and whether to pre-instantiate the module via custom logic.
I don't really understand this. |
Let me rephrase more clearly; what is the value in a function that simply gets/sets a property? Why would I want to use // Get
var translate = loader.translate;
// Set
loader.translate = myTranslate; |
@matthewp oic. It's not that important, except that since they are values-that-happen-to-be-functions, it avoids them looking like methods. Alternatively, it could be a sub-object, like: var translate = loader.hooks.translate;
loader.hooks.translate = function(...) { ... translate(...) ... }; |
Wait, is the value of |
Not as the spec is written, no, but it could change. I'm guessing this touches on your expectations around subclassing. I think the question here is what is the most ergonomic way to make something where you can mutate the hooks but also subclass a loader class. And maybe what you're saying is that the most ergonomic way in JS for this is simply to make them inherited methods: you inherit default behavior from the prototype, you can override the default behavior in a subclass: class MyLoader extends Reflect.Loader {
fetch(key) { ... Reflect.Loader.prototype.fetch.call(this, key) ... }
} and you can modify the behavior of an existing loader by putting a new method on the instance: Reflect.Loader.current.fetch = function(key) {
... Reflect.Loader.prototype.fetch.call(this, key) ...
}; Is that your preference? |
I think the only thing that bothers me about this is that you have methods that indicate external-facing operations you can do on a loader ( |
Yes, exactly, this is a perfect place to use subclassing. But even if we didn't go this way you absolutely need to have access to the loader from within hooks. It's not uncommon to want to call other hooks (fetch is common) within a hook. And for complex extensions you need a place to store loader-specific data. |
Calling the hooks externally could be useful when you want the all of the extension behavior to be applied. Calling |
Sure, but there are plenty of ways to have access to the loader via lexical scope. IINM the only question is whether you want to make use of inheritance e.g. to reuse a particular hook implementation without having to explicitly parameterize it over a loader; you get the loader for free via
Hm, maybe. I mean I could just not worry about it; there are only four hooks. But there are is one name collision ( |
This won't work if someone has
I do think this is the best way. In the old spec I thought things were leaning this way. |
OK sounds good. I've moved subclassability to issue #35. |
@dherman about the naming conflict - surely the Also surely |
@guybedford Good point. We could just unify the two
Well it's also meant for frameworks that want to control when and how much individual modules are loaded, but it's true that it's very low-level. I need to think about alternate names. |
On the subject of multiple instance of the loader: in experimenting with Will the spec say anything about this issue? |
@johnjbarton there was a baseURL discussion in #23 if that helps. |
Solved by #65 |
subclassing is now supported via: class MyLoader extends Reflect.Loader {} hooks are now defined thru symbols: System.loader[Reflect.Loader.resolve] = function (name, referrer) {
// implement your custom resolve on the default loader
};
// or
let myLoader = new MyLoader();
myLoader[Reflect.Loader.resolve] = function (name, referrer) {
// implement resolve on your custom loader instance
}; |
👍 I'm assuming class MyLoader extends System.loader.constructor {} Should work as well, to extend what System.loader provides in addition to the base Loader? |
@matthewp yes! at this point we don't have a strong reason to prevent that :) |
Just heads up, it's important for some developers to be able to instantiate Loader multiple times:
systemjs/systemjs#60 (comment)
Please don't make System a singleton, but only a default instance. Also class-based approach is desired.
The text was updated successfully, but these errors were encountered: