GithubHelp home page GithubHelp logo

whatwg / loader Goto Github PK

View Code? Open in Web Editor NEW
609.0 609.0 45.0 385 KB

Loader Standard

Home Page: https://whatwg.github.io/loader/

License: Creative Commons Zero v1.0 Universal

Makefile 6.14% JavaScript 93.86%

loader's People

Contributors

annevk avatar aretecode avatar arv avatar caridy avatar constellation avatar domenic avatar ericf avatar jeffbcross avatar littledan avatar mikehayesuk avatar probins avatar zcorpan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

loader's Issues

How to access `global` from the module body

From what I can remember, Reflect.global was suppose to provide a way to access the global now that this is undefined in modules, but it is not in the spec for what I can tell. Using new Function() is not an option if you're using CSP, the only option at this point is to cross your fingers and do: typeof global !== 'undefined' ? global : window;.

/cc @ericf

[question] Places where the work on loaders / Loader Pipeline is happening

@dherman sorry for opening an issue to ask a question but wasn't quite sure what is a better way of reaching out to you guys. So once again, apologize for trying here:

basically I'm very much interested in the ESx module loader API, especially it pipeline hooks. I'm rying to piece together various scraps of the info and my current understanding is that:

So here comes my question: is "a new document" being cooked here and drafts published here: http://whatwg.github.io/loader/? Or are there any other places where the discussion happens on this topic? Basically I'm trying to find a place where the work on this topic continues as I'm very much interested in it.

Execute hook

A hook just prior to execution would be extremely helpful in scenarios where you need to alter the universe for a particular module. Consider something like this:

var execute = loader.execute;
loader.execute = function(key){
  var logger = loader.lookup("logger");

  var log = logger.log;
  logger.log = function(msg){
    console.log(key + ":" + msg);
  };

  var mod = execute.apply(this, arguments);
  logger.log = log;
  return mod;
};

In this example a logger module is redefined so that it prints out the key of the module using it.

This won't work if used async but it's still quite useful.

normalize hook

It is great to see the updates here, just had a quick look through it.

The first thing I noticed is there doesn't seem to be the normalize hook?

There is an important feature of the normalize hook, and that is contextual normalization -the ability for the same name to normalize into different canonical names depending on the package you are in, which can be an important feature for allowing jquery to mean different things in different contexts and support things like selective mocking / multi-versions.

Just hope this is still going to be considered!

Iterating over the registry

In this comment @dherman you mentioned that to share registries between loaders you could simply copy over the entries. I agree that this is a good natural way to share. However to do that you would need to be able to iterate over the entries. What do you think about having the Loader implement @@iterator so that this can be done?

Will name resolution dictate forward slashes?

As far as I can gather, the current consensus is for module keys to be absolute URLs. Ignoring the issue of URLs vs module identifiers, is there at least agreement that the paths are separated by forward slashes?

Use moduleIds instead of urls

This responds to the rational of using urls instead of moduleIds and to many of the arguments posted supporting this change in this traceur thread.

whatwg/loader changes relative module identifiers, like "../foo", from being name-normalized to address-normalized. I think this is an incorrect solution because:

  • Most of the use cases address-normalized addresses are not common, the results not expected, or users misunderstand the module namespace.
  • The remaining use case would be better solved with other approaches.
  • It results in non-deterministic moduleNames which create problems for many other use cases.

Background

There are 3 namespaces:

  1. Module specifiers (the string in the declaration) ../foo,
  2. Normalized name, the result of normalize() bar/foo,
  3. Addresses, the result of locate() http://cdn.com/bar/foo.js.

Terms:

  • parent module - the module that is doing the importing.
  • name-normalized - the previous way of normalizing module specifiers based on the parent module's name.
  • address-normalized - the new way of normalizing module specifiers based on the parent module's address.

This change makes the default normalize hook use the "parentAddress" with module specifiers that look like "../" or "./". This will result in normalized names that are urls/paths.

Example

Compare what happens previously to currently with the following example:

System.paths = {
  "a/b": "path/B/b.js",
  "a/c": "path/C/c.js"
}
// in main.js
import b from "a/b"
// in a/b.js
import c from "./c"

Previously:

specifier -> normalized -> address
"a/b"     -> "a/b"      -> "path/B/b.js"
"./c"    -> "a/c"      -> "path/C/c.js"

Current

specifier -> normalized -> address
"a/b"     -> "a/b"      -> "path/B/b.js"
"./c"    -> "path/B/c" -> "path/B/c"

Rationale for the change.

The following are examples I've found discussing the reasons for the change. This seemingly is to solve the confusion that moduleNames are a separate namespace from addresses. I think address-normalization will cause more problems than it solves. I will discuss the reasons for this next; however, I will link to those reasons in the following rationale and example sections.

Rationale 1

When you write relative paths in your import you expect these to be relative to the file you are writing and not something else.

It is true that you will commonly expect the file loaded to be relative to the parent module's address. This will happen most of the time with name-normalization. However, the following example shows a case where this does not happen:

Example 1.A

A user expected the following to load "path/to/two.js" instead of "two.js":

System.paths['one'] = 'path/to/one.js';
System.import('one');
// in path/to/one.js
import * from "./two"

With name-normalization, this is a mistake by the user of misunderstanding that moduleNames are not addresses. ./ is swapping out the current module name one for two. They could fix this problem by simply adding:

System.paths['two'] = 'path/to/two.js';

or

System.paths['*'] = 'path/to/*.js';

Example 1.B

Remember that names and URLs are not the same. Assume someone maps a name/a to http://foo.com/a.js and name/a loads ./b but there is a mapping for name/b to http://bar.com/b.js. With the new scheme we would instead load http://foo.com/b.js since we resolve things relative to the URL and not the unresolved name. (ignoring the .js suffix changes)

In this example, the code might look like:

System.paths['name/a'] = "http://foo.com/a.js"
System.paths['name/b'] = "http://bar.com/b.js"
// in http://foo.com/a.js
import "./b"

The result is that name-normalized would load "http://bar.com/b.js" while address-normalized would load "http://foo.com/b.js". I think users would and should expect "http://bar.com/b.js" to load. The address-normalized result will be more difficult to deal with in builds.

Example 2

URLs are unique too. I feel like I am missing something here... Let me think a bit more about it. If I'm not mistaken the issue we showed was related to polyfilling a builtin module that would reside at "js/reflect" for example and this would load "./util.js" but we definitely do not want this to be loaded from "js/reflect/util.js" but from a file relative to the file that implemented the polyfill.

I'm unsure what this use case is exactly. I hope the details of it can be provided. My guess is that a polyfill/reflect/util should be loaded instead of js/reflect/util and that polyfill/reflect/util has its own relative dependencies. The code setup might look like:

// in js/reflect.js
import util from "./reflect/util"
// in js/reflect/util.js
import helpers from "./helpers"
CODE

The idea is to swap js/reflect/util.js with something like the following polyfills/reflect/util.js:

// in polyfills/reflect/util.js
import helpers from "js/reflect/helpers"
import magic from "./magic"
CODE

The problem is that with a paths config like:

System.paths["js/reflect/util"] = "polyfills/reflect/util.js"

... ./magic will normalize to js/reflect/magic.

However, this can be solved better with a map config that is used by RequireJS or SystemJS like:

System.map["js/reflect/util"] = "polyfills/reflect/util"

Conclusions

In short, the problems attempted to be solved by address-normalization are either:

  • not very common
  • not expected
  • can be solved better ways

The following will discuss some very real problems that will be created by address-normalization.

Problems with address-normalization

By incorporating addresses into moduleNames, address-normalization creates non-deterministic moduleNames in all sorts of situations. Deterministic moduleNames are critical for a lot of advanced, but essential functionality.

System extensions that match moduleNames and different environments.

It's pretty common to point something like lodash to a CDN only in production. And, it will be pretty common that some extension might want to run on only certain modules specified by a System config like:

if(ENV === "production") {
  System.paths["lodash"] = "http://cdn.com/lodash/*"
}
System.es6toES5 = ['lodash/*','ember/*'];

However, this will not work because my ES6-ES5 plugin can not simply match moduleNames that start with lodash because if any lodash module uses a relative path, it's module name might be something like: "http://cdn.com/lodash/array.js". address-normalization will force all config to be addressed based, and much more likely to change.

if(ENV === "production") {
  System.paths["lodash"] = "http://cdn.com/lodash/*"
  System..es6toES5 = ["http://cdn.com/lodash/*",'lodash/*','ember/*'];
} else {
  System.es6toES5 = ['lodash/*','ember/*'];
}

Build systems

Build systems typically have to write out a version of a module that is self defining. For instance:

System.define("lodash/array", function(){
  //... lodash/array's code ...
})
System.define("lodash", ["./array"],function(){
  //... lodash's code ...
})

It's very important that the right moduleNames are written out that the client understands and can repeatably locate. address-normalization will make this very hard.

Build systems often load files in different paths and have different addresses. The client might find "lodash/array" at http://localhost/utils/lodash/array.js, but the server might see it at: /user/jbm/dev/project/shared/utils/lodash/array.js. This file can not be written out like:

System.define("/user/jbm/dev/project/shared/utils/lodash/array", function(){
  //... lodash/array's code ...
})
System.define("lodash", ["./array"],function(){
  //... lodash's code ...
})

Or even:

System.define("/user/jbm/dev/project/shared/utils/lodash/array", function(){
  //... lodash/array's code ...
})
System.define("lodash", ["/user/jbm/dev/project/shared/utils/lodash/array"],function(){
  //... lodash's code ...
})

The reason is that the client might want to dynamically import "lodash/superArray" which might look like:

import "./array"

The client would see "lodash/superArray" at http://localhost/utils/lodash/array/superArray.js and would load http://localhost/utils/lodash/array/array.js twice.

Recommendations

It's highly likely I am missing something. But if I'm not, this seems like a step in the wrong direction. I recommend reverting to name-normalization and adding a "map" specification.

Define "Normal Module Record"

The stand uses this assert several places:

Assert: _instance_ is a Normal Module Record.

But the right hand side is not defined.

Sites packages rules

Currently the sites packages examples seem to map into folders, but don't specify the exact JS file to use:

site({
  "jquery":  "https://cdn.example.com/jquery/v/2.1.1"
});

Does this mean that import $ from 'jquery' would this result in a request to:

  • https://cdn.example.com/jquery/v/2.1.1
  • https://cdn.example.com/jquery/v/2.1.1.js
  • https://cdn.example.com/jquery/v/2.1.1/index.js

If the latter, are we enforcing a main style?

Further, can we import subpaths within the package like import sizzle from 'jquery/sizzle.js' or import sizzle from 'jquery/sizzle'?

Needs to specify set state to max

There are a few alg steps like these:

Set _entry_.[[State]] to the max of _entry_.[[State]] and "translate".

We need to specify what this means.

How about:

GetStateValue(state)

  1. If state is the string "fetch" return 0.
  2. If state is the string "translate" return 1.
  3. If state is the string "instantiate" return 2.
  4. If state is the string "link" return 3.
  5. If state is the string "ready" return 4.

SetStateToMax(entry, newState)

  1. Let state be entry.[[State]].
  2. Let stateValue be GetStateValue(state).
  3. Let newStateValue be GetStateValue(newState).
  4. If newStateValue is larger than stateValue, set entry.[[State]] to newState.

Updated Feedback

I've just updated our implementation to match the latest changes. Feedback below.

  • 6.3.1 Should call requestInstantiateAll to ensure optimal loading (early dependency loading)
  • 6.5.2. Reflect.Loader.install - we should probably throw here if module is not a module object.
  • 4.1.6 Can call commitInstantiated when entry.[[Instantiate]] is undefined. Advisable to merge with 4.1.5 and just call fulfillInstantiate.
  • It is possible to use the fulfillX functions at the end of each requestX function instead of repeating between the two: "Set entry.[[state]] to the max of entry.[[state]] and translate/fetch"
  • 4.2.4 depEntry not defined when checking depEntry.[[State]], worth using a manual call to ensureRegistered at the top like all the others.
  • 4.2.4 Circular references still don't have a stop condition here. Some type of extra state is needed, perhaps an INSTANTIATE_ALL that fits in after instantiate, which then gets marked as LINK within instantiateAll before hitting the circular references.

Remote resource imports

Does or will the spec support remote resource imports as in System.import('http://example.com/remote.js'), equivalent to dynamic script tag insertion (or if not, to say eval(CORSResponse) or eval(SandboxedXHRResponse))?

Underscores not parsed as italics?

This just seemed weird to me, such as this in 2.2.1:


Algorithm steps that say


1. RejectIfAbrupt(_x_).

mean the same thing as:


1. If _x_ is an abrupt completion, the return a promise rejected with _x_.[[value]].
1. Else if _x_ is a Completion Record, then let _x_ be _x_.[[value]].

Shouldn't it look like this?


Algorithm steps that say


1. RejectIfAbrupt(x).

mean the same thing as:


1. If x is an abrupt completion, the return a promise rejected with x.[[value]].
1. Else if x is a Completion Record, then let x be x.[[value]].

This is throughout the spec, by the way.

CommonJS and System.import

We're typically defining CommonJS modules as the default export for interop.

This means if I use System.import to load a CommonJS module, I will need to manually access the default property:

System.import('cjs-module').then(function(m) {
  var cjsModule = m.default;
});

It would be nice to be able to somehow indicate to the loader that this module is just a default export, and hence that System.import can return the default export directly.

It's possible to do this by overriding System.import with a wrapper function, but I'm not sure if that would carry through to the contextual import as well.

Alternatively if users will just be expected to access the default property that is fine too, but it would be good to know.

Goals of this spec

A few questions ...

What are the goals of this spec?
What is it trying to accomplish?
What mechanisms are being used to determine if one proposal is better than another?
Is there a roadmap?

Dependency Loading Bug

Currently in 4.2.4 RequestLink it calls requestInstantiate on all dependencies. This in turn will fulfill when all the dependencies have been driven to the LINK state. But just because the dependencies are in the link state does not mean that their dependencies in turn are. Thus third-level dependencies can be skipped out.

Let me know if that describes the issue in enough detail.

8.1. Site Packages should not be Browser specific (or even called site-packages just packages)

This feature maps package names to root paths for resolving modules. Nothing about this functionality is specific to Browsers.

Developers want to be able to write source code that uses packages then import that code into any JS environment, whether it is browser, node, or future runtimes. Package name mapping separates the specification of the module-in-the-package from the specification of the path-to-the-package. The latter then can be configured when the packages are combined into an application.

In other words, developers need this feature to be independent of the runtime. No technical issue that I know about prevents such use. Really if you look at the API is just a Loader-blessed dictionary. Nothing about browsers involved.

System.install to update module export references

If I have a module, the standard scenario with hot-reloading is that I want to alter the dependency and then have that new reference automatically pushed out to all modules that may have been loading it.

Because modules naturally share this export reference, if System.install can not just set the module in the registry, but update the export references in dependent modules so that it pushes out to all modules already using the old dependency, it would be a really nice use case. It's exactly what assignment to an export variable does already. Code not based on side-effects (ala React) would effectively get natural hot-reloading support for modules (components).

implement error state

  • All error conditions need to set the [[Error]] field.
  • Implement the .error() method.
  • Clear out the [[Error]] field when installing.

Loader Hooks and State

Loader Hooks and State

Currently, we have the following possible states:

  • fetch
  • translate
  • instantiate
  • link
  • ready

We also have the following hooks:

  • resolve
  • fetch
  • translate
  • instantiate

This is certainly not my area of expertise, but there's a bit of a mismatch here that bothers me. Of particular concern to me is that the entire process of loading a resource is not accessible via hooks. The hooks seem to cut off at the instantiate phase, whereas I believe they should follow through to the ready phase. There's also a bit of an inconsistency at the beginning, with a missing resolve state.

I believe there was recently some talk around removing the locate hook, so it seems there's still issues to be discussed around this topic and I think it's pretty critical that we get this right.

Note: I realize stuff has only recently been moved over here and there's still lots of work to do. I just wanted to jump in early on an area that's particularly important to me and my community.

My personal interest is in having a ready hook which would allow me to receive the fully executed module instance along with its metadata. There are a number of important scenarios that this would open up:

  • Insight - Track loads, load times, runtime usage patterns, tracing, etc.
  • Instrumentation and AOP - Dynamically instrument or transform code based on the runtime context.
  • Metaprogramming - Use metadata defined in instances to generate code on the fly.
  • Registries - Application-Specific optimized catalogues of loaded modules for use by frameworks/libraries.

I'm sure there are other uses as well. One technique I've used repeatedly across multiple module loaders for a number of years now is to tag exports with their module id of origin, thus enabling convention-based programming, ala Rails. It's not possible to do this reliably 100% of the time without a ready hook of some sort.

I've also attempted to proxy module.execute but this technique doesn't work for real ES6 modules. So, it's not adequate...not to mention that it seems like a hack. Issue #9 is related to this. @matthewp shows a technique for using instantiate. He's got a different scenario, but hits some similar types of inconsistencies.

So, I'd love to see additional refinement in this area. And, as I've mentioned, I'm particularly interested in a ready hook. I'm also pretty sure I've heard this requested by others in different forums including @jrburke and @guybedford (Though I could be wrong, because it was quite some time ago.)

Looking forward to working through this and very excited to see the renewed effort on the loader.

3.1. Module Registry [[State]] vs [[Error]]

[[Error]]:
'An error that was encountered during loading, linking, or evaluation;"
[[State]] "fetch", "translate", "instantiate", "link", "ready"

This confuses me: I expect that errors would occur in the phases described by [[State]]. Only 'link' seems to overlap.

Lifecycle experiment with limited async steps

I have been working on the core for a loader that is inspired by some previous sketches of the ES module-related loader, details at amodro-lifecycle. It is not a module system on its own, but a piece that can be wrapped by a module system.

While I do not expect it to be useful as-is for this repo, I wanted to share some aspects of its design in the hopes it helps the effort here.

Namely, it only has two async steps in the loading lifecyle, fetch and depend. The rest of the steps are functions that return their values synchronously.

This resolved some tensions I felt with previous ES-related loader sketches: supporting concepts like AMD loader plugins, but also providing synchronous methods on the AMD module meta for things like normalize and locate.

It also felt like it could bridge between node modules and ES modules well given the limited async steps that might be avoided in some node loading scenarios.

The other design note is the normalize step in the lifecycle, for things like loader plugin IDs that may not be paths, and module bundles that are likely more portable than an approach that uses URLs for the IDs in the bundles.

I put more details in the design forces section of the README.

The goal is to use that core in a new AMD module loader, currently being constructed and tested in the impl/amd directory in that repo. The tests are pulled from the tests used by the other AMD loaders I maintain. So far it is feeling pretty good, the code is operational: loader plugin support, config for ID and location resolution.

Still more work to do, but promising, so it seemed worth passing along the design parts.

Why a locate hook is unnecessary

In creating the upgrade path in SystemJS for permitting URLs as module identifiers, it has turned out best to deprecate the locate hook. This may be over-explaining the obvious or dwelling on decisions already made, but coming to this conclusion has taken me a surprising amount of consideration so I'd like to describe the reasoning behind this here to retain some reference for the decision and attempt to leave somewhat reasoned feedback.

The question that started this was whether we should re-introduce the locate hook into the specification. Re-introducing the locate hook will enable normalize to normalize module names into a custom schema that can be defined by the loader implementation and form the string names that are stored in the module registry. Locate then handles the final resolution into URLs that can be fetched.

The justification for considering this was to retain compatibility with AMD-style module loading where we have a baseURL-schema. In this schema, modules names are stored in the registry always as plain names relative to some baseURL. jspm also uses its own schema in the registry to refer to modules such as npm:[email protected].

There is a draw to having the sense of storing these universal schema names inside of the module registry as a portable naming system but I'd argue this lure is mostly one of elegance as opposed to practicality.

I've implemented the baseURL-schema normalization in the current SystemJS, and have been experimenting recently with at least three different complete implementations of normalization of a custom schema alongside URLs (the new requirements of the spec, which completely make sense).

In the end, trying to make a custom schema work alongside URLs in the same registry space, ends up causing more issues, for no practical gain.

Dot Normalization

As soon as we allow both AMD-style module IDs relative to some baseURL alongside URLs, the first issue we hit is the need to define "dot normalization". This basically means that relative normalization needs to be defined for the subset of both URLs and non-URLs.

It's not a lot of code, but it is the first sign here that we're duplicating work.

Non-uniqueness

The next issue we have is the non-uniqueness of our schema. This issue here is that import '/local/path.js' is now distinct and separate to the module at import 'local/path.js' in the scenario where baseURL='/' (one resolves as a name and the other as a URL). This will cause confusion as we are allowing the same unique module to be referred to by two different possible names breaking a key principle of the registry being unique.

Having two ways to refer to the same module is a bug waiting to happen, causing problems for configuration (which variation do we configure?), creating the possibility of a module being executed twice, and interfering with bundling workflows.

Expecting the user to know that they should write import('x') instead of import('./x') arbitrarily is a hard ask.

This leads down a road of trying to catch these uniqueness issues in the normalization pipeline itself, which then ends up becoming URL normalization, followed by a reverse normalization into the schema.

All schemas have the non-uniques problem

Schema non-uniqueness with URLs applies to any custom schema chosen that maps to URLs, not just the baseURL system. Even if we come up with the perfect custom naming schema, as soon as we want that schema to co-exist alongside URL requests we hit these issues.

In order to retain unique identification and configuration of modules, one ends up normalizing from schema space into URL space, and then reverse-normalizing back into schema space at the end of normalize, before resolving back into URLs from the schema in locate, just in order to have our perfect schema names stored in the registry.

Add to this the idea of a configuration space consisting of both schema and URL identifiers as well, and this compounds the problem even further.

One ends up swapping between spaces in such a way that URLs become the primary space anyway, and we're just pretending that the schema is the primary space.

Beyond the baseURL-schema

Another common issue with baseURLs is that when back-tracking below the baseURL, we end up with "normalized" paths looking like "../../module.js", which is really not acceptable for a naming system either.

If we return to the question of what AMD's baseURL schema is really trying to accomplish, the core principle is one of portability of modules, which is completely in agreement with what we should be aiming for. URLs are obviously not a portable naming system for modules (modules can move between environments and hence change URL), so the question is simply how to maintain portability of modules in spite of using URLs?

URLs are the schema

It turns out to be very simple to do this - normalization is seen as the process of converting a "portable module name" into an "environment-specific name". And the most environment-specific name is the URL which we store in the module registry.

The concept that we need to have a registry based on our perfect portable schema is flawed. We still keep our schema if we like - which we can bundle into just the same:

System.register('custom:portable/schema', ...);

Where the name above name is normalized into a resolved name of http://www.site.com/packages/custom/portable/schema.js by the loader when being processed and stored in the registry (bundle names are now treated as unnormalized).

There is no big loss that the registry now contains this value under an environment-specific URL instead of the schema. One can just accept that any lookup into the registry must pass through a normalization phase first:

// lookup a module by its schema name by passing through a simple URL-normalization first
Reflect.loader.lookup(Reflect.loader.schemaToURL('custom:portable/schema'));

If an implementor really wants to use a custom schema, make the schema URL-based and add the implementation to the fetch hook so everything works out well anyway:

import 'custom:///portable/schema'

The other consequence of using URLs is that configuration then always goes through a normalization phase itself:

Reflect.loader.configure({
  module: {
    './some/local/module.js': {
      moduleFormat: 'CommonJS'
    }
  }
});

The above would normalize the above configuration into http://site.com/local/path/some/local/module.js.

The benefit of this is that users don't need to understand the special naming schema - they can just reference modules as URLs exactly as they expect and correctly configure things without needing to have studied the system in detail.

One implication here for implementors is that build systems wanting to use portable naming system schemas need to reverse-map the schemas at build time from URLs in the registry, but that is a very minimal cost and a straightforward 1-1 mapping.

I've yet to hear a single use case that is lost by enforcing that the registry is only to store URLs - the justifications for allowing the registry to store a custom schema seem to cling to dated models due to history, while there are many benefits as described to both implementors and users in enforcing URLs as the schema and keeping the locate hook deprecated.

3.1. Module Registry row [[State]] column description

[[State]] "fetch", "translate", "instantiate", "link", "ready" The metadata object passed through the pipeline.

Maybe

[[State]] "fetch", "translate", "instantiate", "link", "ready" A constant set at the end of observable phases of loading indicating which phase this entry has completed.

switch to ecmark{up,down}

Once the tools are ready for use we'll want to use ecmarkup and ecmarkdown to do the algorithm steps.

Loader.install should resolve pending imports

This was a bug in the old spec and appears to still be present. Consider this code:

loader.import("main.js");

setTimeout(function(){
  loader.install("main.js", value);
}, 5);

This creates a race condition where if main.js passes through resolve before loader.install is called it will continue through the loading steps and (I believe) fail an assertion in linking.

Is loader.cancel intended to fix this issue?

names for Loader constructor and default loader

Summary

Decide on names for two things: the Loader constructor and the default loader instance.

Background

Originally we had global System and Loader but that was when we expected System.get() to be a common use case. Once we worked out dynamic relative import, the default loader became a much more low-level API: it's meant for frameworks and loader customization logic, not for everyday use cases. So IMO giving it an attractive global name like System is inappropriate. Also, it was kind of competing with window, navigator, and process as Yet Another representation of the top level application state.

IMO Reflect.Loader is good for the constructor, but this still leaves open the question of where the default loader should live. Suggestions:

  • another name in Reflect:
    • Reflect.System
    • Reflect.LOADER
  • a static property or method of Reflect.Loader:
    • Reflect.Loader.DEFAULT
    • Reflect.Loader.current
    • Reflect.Loader.current()
  • Reflect.Loader itself is both the constructor and the instance

My preference is a Reflect.Loader.current getter. ALLCAPS feels more appropriate for immutable constants like Math.PI rather than a mutable object, and a current() method feels like overkill for just accessing a singleton object. But I like the idea that it's conceptually associated with the Loader type but it's a static singleton instance.

Drawbacks

Systemjs already has named itself after System. But it's still a pretty cool name for the project, and this just gives it a more fun historical etymology. :)

5.1.1 HostResolveImportedModule somehow needs to find module's registry entry

The current text says to look up the entry as a field of module, [[RegistryEntry]]. But this is the only reference to [[RegistryEntry]] in the whole spec. Presumably it needs to be lookup up in some loader's registry. But which loader?

I'm not at all sure of the right solution here, but the current text clearly doesn't work.

Specify loading metadata

In the old spec this was useful as a way to share information about a module between loader hooks (SystemJS for example had a load.metadata object). You can maintain your own hashtable for this but this is inherently less shareable. It was also nice just for the simplicity of every hook having the same signature (except normalize).

Was this intentionally removed for a particular reason or was it just not thought to be needed?

moduleName, __modulename or __dirname

We use node __dirname in Traceur and I'd like to substitute something that would work under EcmaScript modules.

In node, module source code is wrapped in a function:

'(function (exports, require, module, __filename, __dirname) { ',
    '\n});'

that, in effect, extends the language with module-specific operations and values. The ES module solution provides alternatives for the first three arguments. The last two are redundant and in other discussions we've used __moduleName as a substitute, where the value is set to the normalized name (and we assume the normalized name is a value path on the system we are running).

What shall we be doing for this case? Do we need to justify adding this feature?

RFE: Ability to tell the loader not to block onload in Browsers

I'd like to see a standardized way to tell a loader to load assets (scripts mainly) into a browser window in a way where the download starts early in the page (eg: in the HEAD), but essentially before onload fires, but does not block the onload event from firing.

eg timeline:

------------------------------------------------------------------------------------>
document start -------------[Other Assets]---------------->document onload   ^
            |                                             ^                  |
            V                                             |                  |
       loader start                                       |                  |
               |                                          |                  |
               +-> blocking script d/l -------------------+                  |
               |                                                             |
               +-> non blocking script d/l ----------------------------------+

This allows downloading of third party scripts that are useful but not critical to the page, so these scripts do not add latency to page load, and in the event of third party server failure, the host page does not block waiting for the request to timeout.

An implementation is documented here: http://www.lognormal.com/blog/2012/12/12/the-script-loader-pattern/ but it would be useful to have a standard method for consumers of script loaders to pass this intent onto the loader.

Discussion and reasoning behind initial draft

Where can I find discussion and reasoning behind Loader API introduced first commit?

I can't find anything like it in last ES6 draft that included Loader API (August 24, 2014 Draft Rev 27). The Loader API in this repository seems quite different from what I found in it.

I can't find any discussion about this on mailing list too.

baseURL

I know it's no longer strictly necessary, but consider running the browser loader in NodeJS for example - it can be useful to be able to set what the base path should be for normalizing unnormalized URLs.

It can also be nice to be able to do the following in the browser:

System.baseURL = '/js/';
System.import('blah');

And to be able to control that resolution. I think it's an important feature to keep here.

Issues in RequestInstantiate / RequestInstantiateAll

Currently RequestInstantiate() calls both RequestFetch() and RequestTranslate(), which itself calls RequestFetch(). It looks like it should just call the RequestTranslate().

Also RequestInstantiateAll() refers to depEntry which is not defined anywhere.

Naming inline modules

I was referred here to discuss this. Apologies if I'm going about it wrong. :)

The idea is to allow inline modules to be cached within the loader just like any other module by giving <module> a name attribute.

Suppose you have some inline module within HTML like so:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
    <title>Example</title>
  </head>
  <body>
    <module name="main">

        import App from 'some/dynamic/location';
        var ImmediateBlob = {"maybe": "some JSON stringified by server upon request"};

        App.doSomething();
        export { App, ImmediateBlob };

    </module>
    <script src="some/static/script.js"></script>
  </body>
</html>

And some/static/script.js might look for the main module:

import main from 'main';

main.App.looksLikeADuck();

if (main.ImmediateBlob.quacks == 'like a duck') {
    console.log('It is a duck!');
}

Two other relevant discussions:
ModuleLoader/es-module-loader#343
jorendorff/js-loaders#83 (comment)

reflection: imperative equivalence for declarative import

as today, we only have loader.import('./foo.js').then(m => {...}), which is reflective to import * as m from "./foo.js", as we discussed earlier today, we might need to provide reflection for the other two options (default and named exports import declarations).

@dherman suggested:

local.import("./foo.js").then((def, named) => ...)
local.importNamespace("./foo.js").then(m => ...)

Providing trace information for best development experience

The best development experience with the loader is to use module hot-reloading, without a full page refresh. This way, just the module that changes can be run through loader.set to be updated.

In order for this to work in a dependency tree requires refreshing all the dependencies of the module as well so that they get the new reference to the new module.

This can only be implemented by having dependency information about what modules a given module depends on.

Exposing this dependency information to the loader is thus critical to the development experience.

4.2.3 5 RequestTranslate directly

From the feedback previously:

  • 4.2.3 5 Should RequestTranslate directly, don't need RequestFetch as it is implicit in RequestTranslate

The commit in fe60901 makes this valid again despite the previous revert.

//cc @caridy.

Implementation feedback

I've got a working implementation at https://github.com/ModuleLoader/es6-module-loader/blob/1.0/src/loader.js (PR ModuleLoader/es-module-loader#317).

Here are some notes of the minor corrections from the process:

  • 4.1.5 1 Instantiation must be called with a loader argument.
  • 4.1.5 1 instance is both an argument and a local variable.
  • 4.1.5 4 Resolve entry.[[Instantiate]] with instance. We're resolving the promise from outside of the scope of the promise which practically, means storing the instantiateResolve function on the entry. It can be easier to move this out to a return entry.module in 4.2.3 at the instantiate promise return and a Promise.resolve in the last line of 4.1.4.
  • 4.1.5 6 b For each dep in instance.[[ImportedModules]] is this a ReflectiveModuleRecord property? I couldn't see it defined anywhere.
  • 4.2.1 6 use payload instead of v for consistency?
  • 4.2.3 5 Should RequestTranslate directly, don't need RequestFetch as it is implicit in RequestTranslate
  • 4.2.3 Let p1 be the result of promise-calling hook(key, payload, source)
    Surely we just need source and not payload?
  • 4.2.4 entry intead of entry0
  • 4.2.4 Resolve is not defined anywhere? loader.[[resolve]]?
  • 4.2.4 Before running RequestInstantiate for dependencies, we need to do the check:
    _depEntry_ = ensureRegistered(loader, depKey, metadata); if _depEntry_.[[state]] is "ready" then use _depEntry_ for _dep_
    Otherwise requestInstantiate throws if the module is already defined
  • 4.2.5 module.Evaluate is not defined?
    Resolve is not defined?
  • 5.2.1 Let status be module.Instantiate(). I'm wondering about using the module object as both the execute function and the module instance?
  • 6.3.1 To confirm - if stage is fetch or translate, it never proceeds to next stage?
  • 6.4.2 Not sure how loader.prototype.error is to be implemented.
  • 6.5.2 Should probably set error property to null for installed entry.
  • 6.5.3 Reflect.loader.uninstall(key) not Reflect.loader.uninstall(module)

I also implemented the metadata piping. Having metadata available as soon as the resolve hook runs would really be the ideal here, but does mean that the first resolution takes precedence, which I know may not be ideal for the spec process.

To describe the implementation (for what its worth):

  • We add a third metadata argument to resolve, and pass it through to the request* calls
  • 4.1.1 Added metadata last argument to ensureRegistered. If already existing, it ignores, otherwise it sets on the entry if given, or an empty object
  • 4.2.1 - 4.2.5 all take metadata last argument and pass to ensureRegistered
  • 6.3.1 May also have to consider metadata option for Loader.load
  • 6.5.1 Lookup to return metadata as well.

If it would help to provide any of the above as a PR I'd be more than happy to assist, but would rather have the suggestions reviewed at a higher level first.

Accept header

Just to bring up the question - is it worth altering the accept header for ES6 module file requests?

Proposal: Hook registration with url-scopes/path patterns

The System loader may well be used to load a great many different things that are not simply JavaScript modules. It seems that in the spirit of The Extensible Web it would be best to describe what hooks get used in a way that is universal and can be extended by users at the lowest level. Ideally without HAVING to sub-class.

I would propose that something similar to what is being considered for ServiceWorkers => url-scopes/path-expressions be used in an api for registering hooks to handle loading. This would allow, for example, a translate hook be registered for "*.coffee" etc.

Repeated calls to requestInstantiateAll, requestLink, requestReady

requestReady is called on import, which in turn calls requestLink and requestInstantiateAll.

None of these store their promises if they have already been run, unlike all the other requestX calls.

So simultaneous imports can cause duplicated work:

  System.import('a');
  System.import('a');
  System.import('a');

The above would trigger three calls to requestReady, requestLink and requestInstantiateAll for the same module, running all the logic in each one.

I haven't been able to create any conflict conditions, but I think it might be possible for these to occur.

It may be worth storing the promises for these requestReady, requestLink and requestInstantiateAll functions on the entry just like all the other requestX functions?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.