lasso-js / lasso

Advanced JavaScript module bundler, asset pipeline and optimizer
581 stars 75 forks source link

Add support for System.import()/import() #179

Open kristoferjoseph opened 7 years ago

kristoferjoseph commented 7 years ago

Is there currently a way to asynchronously load a module without having to have lasso leak into app source?

philidem commented 7 years ago

This is a good ideas. Do you have any suggestions for what this new syntax will look like?

Currently, we walk the parsed AST for required dependencies to find the the require('lasso-loader').async(...) calls and then rewrite them. Perhaps we should look for something else? Maybe something like LOAD_ASYNC(...). We would still probably rewrite it to use lasso-loader under the hood but we don't need to reveal this implementation detail.

mlrawlings commented 7 years ago

We need some way to define those split points, request the async bundle and ensure it exists before running the code.

ES6+/201X does define System.import for the purpose of asynchronously loading a module. We could probably use that and at least it would be somewhat standard. Webpack2 is using this for their split point.

patrick-steele-idem commented 7 years ago

I'm not opposed to coming up with an alternative that doesn't leak "lasso" into the code. The reason I was okay with "lasso-loader" being leaked is that lasso-loader is an actual module that can be used to load JS and CSS files asynchronously and it can be used independent of lasso.

In addition, in the case that lasso-loader is running on the server we could make the lasso-loader module work on the server since we control the code for lasso-loader.

Here are some ideas:

Option 1) generic name for lasso-loader

Rename lasso-loader to something like "lazy-loader" that is more generic and does not have "lasso" in its name (assuming the name is also available on npm)


Option 2) Use System.import

Switch to System.import and require that the lazy function be split into a separate module:

Old:

client.js:

require('lasso-loader').async(function() {
    var foo = require('./foo');
    var bar = require('./bar');

    // ...
});

New:

client.js:

System.import('./lazy')
    .then(function(lazy) {
        lazy(); // Invoke the lazy function when ready
    })
    .catch(function() {
        // ...
    });

lazy.js:

// NOTE: We export a function that is invoked when the dependencies are fully loaded

module.exports = function lazy() {
    var foo = require('./foo');
    var bar = require('./bar');

    // ...
});

It would be a challenge to make System.import work on the server since it would require System polyfill. If System is a true global then it would not have access to the local require syntax. Here are some ideas on how to make System.import work on the server:

Server-side Option 1) Require a fully resolved path

System.import(require.resolve('./lazy'))
    .then(function(lazy) {
        lazy(); // Invoke the lazy function when ready
    })
    .catch(function() {
        // ...
    });

Server-side Option 2) Hack the Node.js module loader to introduce a local System variable in every loaded module


Personally, I like the System.import solution. We can continue to support lasso-loader for backwards compatibility, but I think it is reasonable for lasso to transform the System.import code such that it works in the browser. Through some hacks we could probably make System.import work on the server, but hopefully that would be temporary until System.import is natively supported.

Thoughts?

mlrawlings commented 7 years ago

Looks like simply import(path) : Promise has been proposed: https://github.com/tc39/proposal-dynamic-import

kristoferjoseph commented 7 years ago

Was thinking a lot about this while implementing my own lasso middleware and if you are already rewriting the lasso-require calls is it too far of a stretch to instead look for require('modle-name') since lasso already needs users to define what modules are being asynchronously loaded?

patrick-steele-idem commented 7 years ago

@kristoferjoseph FYI, we still want to implement this, but we have been focused on the Marko v4 release that just went out recently. I'm going to reopen this :)

ghost commented 7 years ago

I think what is most desirable is to help push the following spec forward: https://github.com/tc39/proposal-dynamic-import

Doing so will force the hand of Node to make some concrete decisions Re: ES6 Modules Interop.

Next step is to get the tests defined for implementations. I've opened an issue for that in the Compat Table here: https://github.com/kangax/compat-table/issues/1052

Please note some Node-goers seem vehemently opposed to dynamic imports. But despite how they feel the Web needs them to help drive ES modules forward.

As a stop gap you may find some value in Fetch Inject, which implements Fetch Injection and will work alongside Service Workers for offline support in PWAs.

I'm interested in understanding how HTTP/2 multiplexing will play into all of this, as mentioned in the comments section of Rich Harris' bundler post on Medium this week.