Support for asynchronous loading (and not packing everything in one file) #58

Closed
arnaud-lb opened this Issue Aug 20, 2011 · 33 comments

Projects

None yet
@arnaud-lb

Packing everying in one file causes some problems:

  • You have to re-pack the whole thing everytime you modify a single file
  • When an error is reported during execution, it does not reports the line number and file name of the real source file
  • Multiple browserified modules can't share dependencies: they are packed independently in each module; as a result they are downloaded twice.

This is a feature request to add support (as a browserify option) for using asynchronous loading instead of packing everything in a single file.

This would solve all the problems described above: dev becomes easier, bandwidth is saved, and pages load faster (because scripts are loaded asynchronously).

Most node modules are currently using synchronous loading, but it would be possible to convert a script like that:

var http = require('http');

function doRequest() {
    var url = require('url');
    ....
}

exports.doRequest = doRequest;

To something like this:

define(['http', 'url'], function(http, url) {
    function doRequest() {
        ....
    }

    exports.doRequest = doRequest;
    return exports;
});

Or even easier (just wrap the unmodified script with a header and footer):

define(['http', 'url'], function() {

    // now that http and url modules are loaded, require('http') and require('url') can be used directly
    // no need to modify the script :-)

    var http = require('http');

    function doRequest() {
        var url = require('url');
        ....
    }

    exports.doRequest = doRequest;
    return exports;
});

Optimization

Such asynchronous modules can still be packed together in a single javascript file for reducing the number of HTTP requests in production.

This is done by just adding the module name as first argument of define (define('moduleName', ['dependency'], func)) and concatenating all modules.

@substack
Owner

1: wrapping a whole file in a function block is ugly

2: node modules use synchronous requires

3: browserify's goal is to let code written for node run in the browser

@substack substack closed this Aug 20, 2011
@arnaud-lb

1: wrapping a whole file in a function block is ugly

That's a quite common pattern in Javascript. This avoids poluting the global namespace, this allows to close some variables private to the module.

That's called the module pattern.

2: node modules use synchronous requires

That's not a problem:

Once a module is loaded you can synchronously require() it (as per the spec):

// this loads 'foo' and 'bar' before calling the function
define(['foo', 'bar'], function() {
    // this synchronous require() works because 'foo' is already loaded:
   var foo = require('foo');
});

See the example code below "Or even easier (just wrap the unmodified script with a header and footer)"

So a node module using synchronous requires can be made asynchronous by just wrapping it in a define([dependencies...], functino() { ... })

3: browserify's goal is to let code written for node run in the browser

In the browser, that's why the ability to make them asynchronous at the same time would be awesome

@substack
Owner

This issue completely misses the point of browserify. If you want that stuff go use requirejs.

@arnaud-lb
  1. Nodejs modules are CommonJS modules.
  2. There is already a way to load CommonJS modules in the browser, called asynchronous module definition.
  3. It is very easy to convert synchronous modules to asynchronous modules.
  4. Multiple javascript loaders for the browser use this spec to do asynchronous loading, and are able to load CommonJS modules. requirejs is only one of them.

So converting nodejs modules to asynchronous modules is a quite obvious way to load them in the browser. Doing something else is completely missing the point.

@substack
Owner

We accept pull requests.

@arnaud-lb

Great! Could you reopen the issue then ?

@substack substack reopened this Aug 21, 2011
@medikoo
medikoo commented Aug 24, 2011

@arnaud-ib
CommonJS modules (1.x) are not asynchronous
Spec you are referring is probably AMD which conceptually is more about loading packages than modules (if you look from node.js point of view)

Why don't you use requirejs ?

@arnaud-lb

CommonJS modules (1.x) are not asynchronous

They can by defined in a way that they can be asynchronously loaded (see http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition ).

As I shown in the issue, converting a synchronous module to an asynchronous one is very easy.

Spec you are referring is probably AMD

Yes, this is a draft that now is maintained on the AMD project, which stands for Asynchronous Module Definition:

From the page: The Asynchronous Module Definition (AMD) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded.

This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems.

which conceptually is more about loading packages than modules (if you look from node.js point of view)

It is a way to reliably load multiple inter-dependent javascript files asynchronously.

Why don't you use requirejs ?

requirejs only provides define() and require() implementations; it's only an asynchronous javascript loader, it does not rewrites scripts. Script have to use asynchronous module definition.

@medikoo
medikoo commented Aug 24, 2011

They can by defined in a way that they can be asynchronously loaded (see http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition ).

It's not part of Modules 1.x

You also said:

Most node modules are currently using synchronous loading

Fact is, that modules in Node are only synchronous and there are no plans even in far future for asynchronous support . Browserify is closely tied to Node and share same approach.

Running modules asynchronously will just slow down page load and will still need kind of file wrapping, I'm not sure what's the added value.

@arnaud-lb

Please read earlier answers :)

It's not part of Modules 1.x

Why is this a problem ? All script loaders conform to this spec.

Even node implements it. Try it yourself:

// foo.js

define(function() {
    return {yes: "node implements asynchronous module definition too"};
});

// bar.js

foo = require('./foo');
console.log(foo.yes);

Browserify is closely tied to Node and share same approach

What is node's approach ? To concatenate the world in a single file ? To do things synchronously ?

Node's approch is to load scripts on demand. The let's load scripts synchronously approch is based on the fact that node can do it, and with guarantees on the order of execution of required scripts. BTW that's probably the only synchronous by default thing in node.

Browserify is porting node modules to the web.

In a web environment you can't load multiple scripts synchronously in a portable manner, and most importantly, you can't guarantee the order of execution. That means you can't guarantee that module A is executed before module B, and this is bad if module B depends on module A.

That's why CommonJS for the web uses asynchronous loading.

That's still follows node's approach to load scripts on demand, and to keep modules in a separate file.

Browserify wraps and packs everything in a single file. Nothing avoid it from converting everything to asynchronously-loadable modules. That's just cleaner and follows node's philosophy.

Running modules asynchronously will just slow down page load

  • Asynchronous means that the browser can continue loading and rendering the page even if the scripts aren't loaded yet.
  • Synchronous loading means that when the browser sees a <script> tag, it stops rendering the page until the script is downloaded and executed.

So the page will actually appear to load faster to the user with asynchronous loading.

This also means that you can load some modules only when needed, for example you can load your form validation module only when the user actually uses a form.

Any optimisation tips you read these days will recommend you to load your scripts asynchronously.

and will still need kind of file wrapping, I'm not sure what's the added value.

  • The page will load faster.
  • Your scripts can load other modules on demand, just like you do in node, and they are not downloaded until you ask it (if I understand browserify loads everything, everytime)

With current browserify, you get the following problems:

  • If you have two separate browserified modules each depending on the same modules, you download the modules twice.
  • You have to re-pack the whole thing everytime you modify a single file
  • Packing all in a single file changes line-numbers; this doesn't makes debugging easy
@itay
Contributor
itay commented Aug 24, 2011

I don't think the "async loading" vs "all in one file" approach is at all settled. Look at the discussion here:

https://github.com/paulirish/html5-boilerplate/issues/28

In general, the browserify approach is great for what it tries to do. I use it not to emulate node.js modules, but rather to allow for me to package my node.js module (so I get code sharing) as something that is suitable to the browser.

Given what you want, it seems that browserify isn't the project for you. You'd be better served by something like requireJS/yabble/etc.

@arnaud-lb

You'd be better served by something like requireJS/yabble/etc.

Doesn't anyone read my answers before replying ?

This is what I want ! Using browserify to produce asynchronously defined modules, and using requireJS/yabble/etc to load the produced modules.

Producing scripts and loading them are two different things.

@itay
Contributor
itay commented Aug 25, 2011

I apologize - I did read your answers, but your intent wasn't clear to me at all. I understand what you're looking for now.

Personally, as a user of browserify, I'm much more in the camp of "single file 'melding'", as I find it much easier to code and deploy that way. I also haven't seen convincing evidence that async loading of scripts is faster than a single, gzipped file.

So to answer your concerns:

  1. Duplication of modules due to single files: in general, I want my application code to be in one file that is downloaded once. I find it both clearer in terms of coding (so that I dont have to ensure that the other script is loaded) and just as good in terms of perf.
  2. Needing to rebuild the "big file" on every change: I have no idea how long your browserify tool chain takes - for me it is pretty much instantaneous.
  3. Debugging: even though the line numbers change, unless you minify the code, then the source code is the same, and it's trivial to do the mapping from melded-file to real-file. If it's minified, all bets are off regardless of whether it is one file or many async-loaded files.
@medikoo
medikoo commented Aug 25, 2011

Why is this a problem ? All script loaders conform to this spec.

Even node implements it. Try it yourself:

Node never implemented it, there was just a brief while when Node (in unstable branch) allowed 'define' construct to require Modules, but it was working same as regular 'require' - synchronously. Shortly after it was added it was taken out, it's not longer there, and won't be put back. Check following links:

https://groups.google.com/d/msg/nodejs-dev/wITGE8TLk2M/ewajnrAF5BkJ
nodejs/node-v0.x-archive@703a1ff

Asynchronous script loading that you are talking about can be optimal when we talk about few files and not tens of them, (see discussion that itay linked), thing is when we talk about modules we usually talk about tens of files.

Currently I'm working on web application (written purely in JavaScript), which at the moment consists of 103 modules from 11 packages, most of those modules are needed to show initial page, I can't imagine loading them each by each asynchronously, it will significantly slow down page load. Basically I've been there before, few years ago, in development I used to load each file separately, but after some time I switch to joined file load as growing number of files made my page load slow.

So conclusion is, that it might be good to separate JS code base into few separate files but it won't work great at modules level, where we may have hundreds of files, it may work fine for packages (that's why I said AMD is conceptually more about packages not modules) but I think best option would be if Browserify knowing what's most optimal will prepare number of files that will result in fastest load in Browser - and such option will definitely be appreciated

@arnaud-lb

@itay

so that I dont have to ensure that the other script is loaded

That's exactly why you want to use require() (sync or async)

@medikoo

Node never implemented it

I must have checked out the revision where define() was implemented

optimal when we talk about few files and not tens of them

Obviously.

You can still concatenate some modules together to do less requests. define() allows this, as long as you specify the module name (define('module-name', dependencies, function)) and you concatenate the modules in the correct order, this just works.

But do not pack everything. Have a big "core.js" in which you put things that every page on you site depends on. And a few set of other files for things that are not always required.

The thing is that you'll still be able to load at least some parts asynchronously, and the site will load faster.

And during development you won't have to pack the files files, all modules will be loaded independently, and transparently. Dev will be easier.

Currently I'm working on web application (written purely in JavaScript)

You can load the core of the app in a <script> tag if even the rendering of the page depends on it. But after that you can load separate modules or packages asynchronously as the user navigates the app or use some features. The app will take less time to load.

On a website, when javascript is not absolutly required to render the page, you can easily load everything but the loader asynchronously, and have the page rendered quickly.

@medikoo
medikoo commented Aug 25, 2011

@arnaud-lb
We're drifting away from main point. Main concern is: Do Browserify need to support AMD ? I don't think so, mind that Browserify is tool for porting node modules to browser, and AMD modules doesn't work in Node.

@noonat
noonat commented Aug 26, 2011

As an observer, I am having a hard time understanding why @arnaud-lb is getting so much blow-back for this feature request. It's likely a minimally invasive change, given that Browserify already needs to track this dependency information, and offers significant benefits for someone who has a large set of shared dependencies between two sets of unrelated code. It could be optional, so it need not affect people who don't care about it.

If @arnaud-lb is willing to do the coding work, and Browserify is willing to accept pull requests, can we just stop belabouring the point until there is code to look at?

@medikoo
medikoo commented Aug 26, 2011

@noonat you're totally right.
I probably drifted this discussion away as I was wondering why @arnaud-lb is so much after this feature, that's it. Sorry if it didn't sound as nice as it should be.

@tauren
tauren commented Oct 15, 2011

Browserify sounded great at first, but once I looked into it deeper, I decided it wasn't yet the tool for me. Here's why:

  • I want to load my core.js on page load, but only include code commonly used throughout my app
  • I do not want to load everything on page load, as there are many heavyweight features of my app that rarely get used. * I want to asynchronously lazy-load extra packageX.js files on demand
  • I don't want the extra packageX.js files to duplicate any of the code that I already loaded in core.js
  • I don't want to bundle common resources like jquery, but instead load it from a CDN (google, etc.)
  • Yet, I still want a tool that can create my core.js and package.js files by walking the AST to determine dependencies

For these reasons, I completely agree with @arnaud-lb. If Browserify allowed me to do the things above, I would not hesitate in the least to use it over RequireJS or others.

Initial page load time is very important for my apps, and there are many things that can be done using async loading techniques to really make an application snappy. I don't like the idea of stalling the page render so that one massive browserified script can get loaded.

Also, consider that the iPhone 3 limits cached elements to 15K. If I use browserify to combine my app and all of its dependencies into one file, then that poor iphone 3 user out there is loading the entire thing every time.

Anyway, I understand that the point of browserify is for synchronous loading. But I believe it would get a lot more interest and traction if it was extended to have async features. I hope any pull requests that add async features would be welcome.

@substack
Owner

Many bikes were shed.

@substack substack closed this Mar 11, 2012
@azer
azer commented Feb 7, 2013

For those who needs this feature; https://github.com/azer/onejs#multiple

@rybesh rybesh referenced this issue in editorsnotes/editorsnotes-server Apr 26, 2013
Closed

JS organization #145

@fresheneesz

@substack can you please reopen this issue? I see agreement that this is:

  • a minimally invasive feature
  • would be incredibly helpful for people who don't want to have a package that holds all of their javascript (which is obviously suboptimal)

I assume you closed this thinking that there was no traction. Well this is still a problem despite the fact that no one has fixed it (I'd hope this is obvious ; ). I'd also like to hear from @arnaud-lb why he went dark, if he's willing to revisit this issue, and/or how he worked around it in his projects.

@substack
Owner
substack commented Aug 1, 2013

I don't care if there is traction or not. browserify is for loading node-style modules in the browser. Asynchronous loading is not something that browserify does. If you want that, there are lots of alternatives such as requirejs that do asynchronous loading. This is not something that browserify will ever support.

@fresheneesz

What changed your mind since you originally reopened the issue after saying "We accept pull requests."?

@jaydson
jaydson commented Sep 12, 2013

browserify is for loading node-style modules in the browser

Well, the fact is that node ecosystem is completely different from the Browser, maybe the Browserify "mission" is just nice, but doesn't work for the web.
Don't get me wrong, i really like Browserify, and i'm using in production, but i would like if Browserify support the asynchronous way to load dependencies.
If you have a huge application, and have to deal with a large amount of JavaScript files, this "node-style" is not what you want, but at the same time, you'll need a tool like the excelent Browserify.
The truth is that we don't have(yet) an optimal solution for modules in the Browser.
Browserify will help some kind of applications, AMD will help others, but we still need to workaround.

@epeli
Contributor
epeli commented Sep 12, 2013

we still need to workaround

Here's mine https://github.com/epeli/browserify-externalize

@fresheneesz

I've been working on my own solution as well: https://github.com/fresheneesz/asyncify

Its purpose is to make commonJS code just work with browsers, as long as you have control over the server-side (which you probably do if you care about this kind of thing). Browsers can request a set of modules, and the server will package up those modules and all their nested dependencies that haven't been already requested by the browser (no load duplication) and sends em down. It uses detective (just like browserify) to find dependencies and will also use browser-builtins to serve browser-side versions of node.js builtins.

Its pretty close to being in a working state, and I'll probably try to put up a working version sometime in the next month (i'm pretty busy at the moment). Let me know if anyone is interested in helping me speed up this process.

@sokra
sokra commented Sep 12, 2013

Here's mine: https://github.com/webpack/webpack

It uses CommonJS and AMD. CommonJS and AMD define are loaded sync (packed into the same file), AMD require is loaded async (packed into another file). This way the code base is automatically splitted into multiple chunks, which are loaded on demand. It generates static files so no server-side component is required.

@thlorenz
Collaborator

Although you should think twice about asynchronously loading anything from a remote url, etc., you can use bromote for that use case.
It integrates with browserify and is very simple to setup.

Using this together with browserify --standalone also allows you to externalize part of your dependencies and load them asynchronously (on demand).

However I agree 100% that this functionality should not be part of browserify itself.

@jhnns
Contributor
jhnns commented Sep 14, 2013

I'm using webpack because it offers a bundle-feature that allows me to split the entire code into separate bundles that can be loaded on demand. They can also be cached more efficiently.

Don't get me wrong, I don't want to advertise another module in this issue. But I think that this feature is very useful.

@fresheneesz

I just wanted to note that I've since tried webpack and have to say it handles asynchronous loading exceptionally well using the commonjs require.ensure proposal. Webpack is definitely the best of require.js with the best of browserify.

@tnrich
tnrich commented Mar 4, 2015

+1 to @arnaud-lb's ideas

@substack substack locked and limited conversation to collaborators Mar 4, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.