whatwg / loader

Loader Standard
https://whatwg.github.io/loader/
Creative Commons Zero v1.0 Universal
609 stars 45 forks source link

How are named exports passed to hooks when Loader.import is called? #128

Closed RGBboy closed 8 years ago

RGBboy commented 8 years ago

Unless I am mistaken you can't express named imports via the loader API in an efficient way that allows hooks to optimize (e.g. tree shake).

Currently there are 3 kinds of imports with the ES6 module syntax:

import * from 'test.js' // 1. module
import test from 'test.js' // 2. default
import { x } from 'test.js' // 3. named

One of the benefits this new syntax gives is the ability to just import part of a given module as shown in 3.

I can see that currently 1 can be expressed in the loader import syntax with

Loader.import('test.js').then(m => ...)

2 and 3 can not without importing the entire module first (along with all dependencies):

Loader.import('test.js').then(m => m.default).then(...) // 2.
Loader.import('test.js').then(m => m.x).then(...) // 3.

The downside of this is that the hooks only will not know exactly what a user is asking for, therefore can't make optimizations (e.g. tree shake), and is forced to load the entire module.

Is one of the goals of this project to allow the ES6 module syntax to be expressed in terms of the Loader API? If so, should named exports be passed to each hook? If not, is there another way to achieve this?

caridy commented 8 years ago

@RGBboy the reflective API to import modules via loader is not intended to be statically analyzable, in fact, it is very hard to do any analysis on an imperative form IMO, e.g.: "test.js" could be produced by a function call, and there is nothing you can do to guess what value that expression will output.

RGBboy commented 8 years ago

@caridy but you could achieve such by allowing an API to declare this. E.g.

Loader.importNamed(['x'], 'test.js').then(...)

Then each hook has this information to do optimizations if they wish.

I was just under the impression that Loader would one day be the lower level API behind the module syntax. The end game would be to enable the browser/engine to do this optimization, and if they end up using Loader they would need this via this API no? Otherwise they would need to implement their own loading system without Loader.

Another alternative would be to specify it in the load string, which would allow hooks to see it:

Loader.import('test.js?x').then(...)
matthewp commented 8 years ago

Then each hook has this information to do optimizations if they wish.

What if some other code (later) depends on having the entire namespace. If you've already tree-shaken away part of the module then what? Maybe I am misunderstanding your request.

RGBboy commented 8 years ago

Perhaps tree shaking is the wrong term. I am under the assumption that it would be loaded. Say for example I make two imports for different named exports:

Loader.importNamed(['x'], 'test.js').then('...') // like import { x } from 'test.js';

// ... later

Loader.importNamed(['y'], 'test.js').then('...') // like import { y } from 'test.js';

x and y would be gradually resolved when they are needed. You would load the whole of test.js but you would not resolve all of its imports as you only need x in the first case. Later y would be resolved as it is requested.

I was under the impression this is how Loader would need to work and that the Registry be incrementally resolved as the tree of dependencies are imported.

caridy commented 8 years ago

no, that's not the intent, and that's not the way modules are implemented in 262. modules are evaluated to completion by the engine, which doesn't take into consideration who consume the module, and how it is consumed. any sort of optimization, and tree shaking, should happen before delivering a code to be evaluated by the engine.

charypar commented 8 years ago

I think the question is quite valid... Why is there a difference in the level of expressiveness between the static and dynamic APIs?

In the static one, it's possible to express the intent of loading a certain named export of a module, therefore whoever is facilitating the loading of the module can make informed decisions about loading dependencies of the module being loaded (which is especially interesting when using the proposed export _ from _ semantics.

The dynamic version can only decide to use a part of the module after it has been loaded, there is no way to declare the intent of using only a part of the module.

This is an important case in my mind for things like relatively large function libraries (e.g. lodash) where the top level module is just a composition of other modules (a sort of a "fanout" scenario) and its named exports are statically analyzable (they use the export syntax). Having the information about the intent to use just a single export out of many from the library provides the ability to exclude all the dependencies of the other exports when loading the module.

I was also under the impression that this is one of the major goals of ES6 modules, so it's interesting that the potential of doing something similar wasn't included in the loader standard.

Is there any elaboration on that decision one could read?