Closed insidewhy closed 7 years ago
I've used jspm bundles a lot but the way webpack handles bundles is superior as it calculates the optimal bundles by scanning the code for dynamic and static imports calculating the minimum number of chunked bundles necessary. To do this manually can be untenable for huge projects.
I'm not really a fan of webpack but this one feature is indispensable for large projects.
It's not really hard with aurelia's bundler, you simply supply globs to tell it what should be in the different bundles. If you organize your app by features or by user scenarios, then that makes it even easier.
It isn't hard in simple apps but when you have many external dependencies per each of your code modules then calculating the optimal bundles becomes much less simple. In huge apps making a mega bundle of third party dependencies such as in the Aurelian skeleton app will lead to a very suboptimal user experience while they wait for that huge bundle to compile. With webpack these are non-issues.
@rosenfeld and me recently had a discussion where I was arguing that jspm's approach was good enough but he won me over with some very convincing arguments, he has some great blog articles on how to optimise page load times.
I believe you are discussing different subjects. I employ both techniques in the application I maintain but they are not necessarily related to each other. The technique described by @EisenbergEffect is commonly referred to as creating separate bundles, like vendor and app, both of which must be loaded for the application to load. This is handled in webpack by the CommonsChunkPlugin.
Even though it's also explained in the code-splitting section of webpack documentation, the lazy code loading technique described by @ohjames is another feature related to code-splitting, but a completely different one from creating a vendor bundle. They can be combined (which I recommend) but this is not required.
Lazy code loading is the hardest part to implement transparently, like webpack does, but it allows you to load only the code you need for the initial rendering of the page and then download more code as you need. By doing so you can speed up the page loading by reducing the size of your initial JS.
Webpack is smart enough to understand which modules are already available in the initial load as it assumes both the commons chunk and the app chunk will be loaded upon initialization. So if some part of the code will call require(['lazy-module', 'another-one'], function(){...})
webpack understands that it should pack those modules and their dependencies and exclude any modules which are already part of the commons and app bundles. This is a very powerful and interesting feature I only found in webpack so far...
The bundles created using system.builder or the aurelia bundler do not have to be loaded up front. They can be loaded on demand. SystemJS knows what modules reside in which bundles. As a result, it only downloads the bundle when a module that resides inside that bundle is needed. It does not download it otherwise. This is basically the same capability as you are describing with Webpack. What it doesn't do is automatically determine how to break your app up into these different downloadable bundles. For that, you have to tell it what modules should go in which bundle. You can use globs so it's easy to define. We could add something in the future to have the aurelia bundler try to automatically determine an optimal deploy...but in reality the developer usually knows best because they understand the actual usage patterns of the application.
I haven't tried webpack yet, if it works as fine as @rosenfeld and @ohjames I'll have to consider its usage. I'm implementing lazy load via aurelia bundles, I do have a bundle that contains minimal stuff to show a login screen to the user. Meanwhile user is logging in I'm importing some module contained in the following bundle to load (inspecting the url I can know which view user is navigating to once he logs in) so I can start the bundle download. App has just 3 bundles, It took me a while configuring the bundles (via Globs and excludes) but it wasn't a nightmare.
@EisenbergEffect I'd love to better understand how this works since I'm interested in webpack alternatives which would support persistent caching so that it could be useful for incremental builds to speed up the deploy process.
Suppose you want to generate 3 bundles, Vendor, App and OnDemand. Suppose both Vendor, App and OnDemand depend on jQuery. How does SystemJS know that Vendor and App should be considered to be loaded upon initialization so that it only includes jQuery in Vendor and not in App and OnDemand? Also, what if both App and OnDemand depend on Common module? How would SystemJS know that Common should be included only in the App module?
Basically, you tell it what goes into each bundle. Then it generates a manifest of the bundle contents so it knows what is where. Whenever jQuery is request is simply ensures that the bundle that contains jQuery gets loaded.
@EisenbergEffect, if I understood it correctly, you basically tell the build system that modules in the Vendor bundle shouldn't be included in the App bundle and that neither Vendor, App and their dependencies should be included in the Common bundle and the build system is able to manage this correctly to remove any duplicates, is that correct? For example, if all bundles require jQuery neither App nor Common bundles would include it because they were told not to include Vendor or any of their dependencies and jQuery is one of them.
What is the exact tool that manages this? SystemJS? jspm?
This is certainly manageable, although not as much transparent and convenient as the webpack approach... Anyway, if it supports incremental build persistent caching I'd certainly consider it as an alternative to my current build system. Does it support persistent caching for incremental builds?
Could you provide an example showing how Vendor, App and Common would be configured using your solution?
Also, how do you manage the lazy load behavior? Does that system already provide you the required tools to ask for the module and run a callback once it's loaded? Or do you have to manage this part with your own custom code?
For each bundle there's an entry in the config file that lists the modules contained. Whenever app imports a module SystemJS knows if it's included in a bundle, if so SystemJS first downloads the bundle before resolving the import, bundles are only download once. A bundle is not downloaded until threre's an import asking for a module contained in.
@dpinart, I'll take your words to build a pseudo example.
"For each bundle there's an entry in the config file that lists the modules contained."
Here's the config:
var config = {
entries: {
Vendor: ['jquery', 'knockout'],
App: ['./app.js'], // app.js requires jquery and ./common.js
OnDemand: ['./on-demand.js'] // on-demand.js requires jquery and ./common.js
}
}
How do you tell the config that all dependencies (and their dependencies) included in Vendor should not be part of the App bundle and that all dependencies from App (including the ones bundled in Vendor) should not be part of the OnDemand bundle? You haven't explained that part.
How does SystemJS downloads missing bundles? Is this implemented by SystemJS itself when generating the bundles, transparently?
Also, how does SystemJS know that both Vendor and App have been included in the head section? For example, suppose you include two async script tags in the head, one for App and another for Vendor and suppose Vendor is much bigger than App so that App would usually be downloaded first for the first access (no cache). Since App depends on Vendor, how would SystemJS know that Vendor is already being downloaded and wait for it to finish rather than requesting the vendor bundle again and then prevent it to be downloaded and executed twice?
The Aurelia bundler is built upon the SystemJS Builder. The Aurelia Bundler Documentation is currently here: http://aurelia.io/docs.html#/aurelia/framework/1.0.0-beta.1.1.4/doc/article/bundling-your-app-for-deploy. It lays out the answers to all of your configuration questions.
When the SystemJS Builder builds up the bundles (based upon the configuration passed to it by the Aurelia Bundler tooling), it writes out, in the app's config.js
file, a list of exactly what files are contained within each bundle. SystemJS reads this configuration information at runtime. Using this information, it would know, for example, that app.js
is included in the App
bundle.
how does SystemJS know that both Vendor and App have been included in the head section?
That's not how the bundler works. You do not include script tags for any of the bundles. You rely on the module loader to load the bundles on demand.
@rosenfeld You have to be aware to avoid modules being duplicated. Aurelia bundler does not warrants this point. This task is a bit tedious and it requires some time. I ended watching in the debugger what modules were being downloaded in order to show the login view and I set up the "login" bundle with these modules. Then I built the "app" bundle excluding all the modules that were present in "login" bundle and finally I built the third bundle 'app-ext'... At least for me, it was a bit tedious and manually task, although it wasn't a nightmare.
The advantage I can see in webpack is that it automatically ensures a module is not present in more than a bundle. I don't know how you configure different bundles...
Hi @dpinart, this is what I was trying to understand.
I've detailed how I use webpack to create two bundles (vendor and app) and load them async and how to load more code lazily in this article:
Webpack consider each chunk in an special way. Take a look at "Chunk types" in its documentation to understand them:
https://webpack.github.io/docs/code-splitting.html
There is the entry chunk, an initial chunk (non-entry) which is achieved using the CommonsChunkPlugin (like the vendor chunk) and normal chunks which are loaded on demand.
You only specify the entry chunks and you can use the CommonsChunkPlugin to create initial non-entry chunks. The normal chunks are created automatically by webpack by looking at the async requires and bundling together everything which is not included in the initial chunks (entry or not). You can optionally provide a name for this async require so that you would be able to include other modules as well in other places by giving the same name.
I'm not sure if that plugin would support more than one initial chunk besides the entry chunk. You can look at more details about the plugin here:
https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
There's a downside though that webpack assumes you will ensure the commons chunks (Vendor) will be executed before the entry chunk (App). That means you can't load them asynchronously, so I had to add some custom logic to the chunks to be able to handle them async and I also had to create a small plugin to change the initial chunk a little bit, which is demonstrated in the referred article in case you are curious. It would be awesome if webpack allowed you to inform that those chunks will be loaded async so that it could handle that itself when generating the chunks...
Now, if you were asking about multiple entry points, then webpack supports that pretty well.
@rosenfeld Could you please give more details on how to separate aurelia and app code into two bundles? I'm having an exception when try to do that, although one bundle app works fine. I managed to trace it to an undefined Element
injection. I feel that it has something to do with in which bundle aurelia-pal-browser
is located but couldn't get to the bottom of it.
Hi @MaximBalaganskiy, I don't actually use Aurelia myself. I was brought to this discussion by someone while we were discussing some performance issues related to the build tool and just contributed to this specific subject. I can't give you details on how that applies to an aurelia application. In my application I bundle all libraries in a vendor JS and the application code in another JS. I also load some code on demand. But I don't know much of Aurelia to help you understanding how you can split your application and help you debugging any issues, sorry.
Using the latest Aurelia with a jspm configuration, the loading spinner is still shown for 3 seconds even on a Samsung Galaxy S6. It's too much :( Could switching to webpack help this?
Site is at http://kchomp.co and code is fully bundled. The only non-aurelia deps I'm using are lodash and blbuebird.
Just try not using system.js for starters.
@EisenbergEffect I was under the impression that jspm needed system.js. I imagined that system.js' job just became much smaller for a bundled app. I'd have no idea how to remove system.js without also removing jspm. Do you think this slowdown is called by system.js then?
Neither one really needs the other, it's just more convenient to use them together. You could use npm instead of jspm for packages and still use system.js or you could use npm with require.js or npm with webpack.
Thanks, I will assume this is an issue with system.js for now then and port to webpack. Will reopen if it doesn't help or open a bug against system.js if it does. I loved system.js as a concept but this much load time is obviously not good enough, I can only assume from your switch to webpack that you guys have also found systemjs to be inadequate.
We are moving to provide multiple alternatives to system.js. We've also discovered that Babel generates code that creates runtime checks in class constructors. We're working on removing that, which should improve startup time as well (and reduce size a bit more). Performance is an ongoing project for us and we're always investigating different options. After our v1 release, we'll also begin work on server-render too.
Thanks, you're the man. Our team really appreciates all of the advice and help you've given and have had a great time building with Aurelia! We'll committing some more work back besides my animator fixes soon :)
A member of my team has determined that 75% of the time delay is due to system.js, so Aurelia is off the hook. <3 aurelia.
We moved to webpack a couple of months ago and we could observe a significative startup time improvement. Anyway, on mobile devices (android 5.1 +) startup time is annoyingly long, about 15/20 seconds. Even deploying the app within an apache cordova application results in very long startup times. It seems the bottleneck is at aurelia plugins/feature initialization (main.js). Once app is launched performance is quite acceptable. There's no significative difference amongst webpack or systemjs on mobile devices
@dpinart Is that for bundled applications?
I tested the Aurelia docs app today on my phone (Nexus 6p) It loads in 2-3 seconds.
Hi that's the case for me already Im using aurelia enhance feature that helps me to extend my server side template engine and use aurelia just for interactive components that's great and appreciable. check out my website here (language is Persian): http://otex.ir really it works like a monster on desktop browsers and take too long for startup on android + windows phone (about 5-10 sec) vice versa it's weird that on Iphone everything works fast and smoothly that's weird for me because safari is also contains a webkit engine what's the big deal between android and IOS? have to mentioned that site is bundled and I'm using systemjs + jspm + js + aurelia and systemjs latest builds
My team member was wrong to lay the blame on system.js... this startup time is still such a big problem for us that we have to port to angular 2.
Ah it seems like this is a duplicate of #136.
@ohjames I'm not sure that is going to solve any problem for you. In the videos I've seen, the ng2 startup time is actually longer than Aurelia. I'd love to help you with this but we haven't experienced any severe issues. There are usually many ways to handle these types of problems if you encounter them but it does depend on the details of your app.
I'd love to help you with this but we haven't experienced any severe issues.
If I reload the majority of the websites linked from http://builtwithaurelia.com then I get 3+ seconds of loading on every reload. I've switched phones recently (to a comparatively much more powerful Xiaomi Redmi Note 2) so it rules out my previous phone being the issue. Am stuck on Android 5.1 though, but using the latest Chrome.
There are a few examples linked that do load within a second after the first reload though, I'd really like to know what is different about those.
There's only about 10 or so links to go through on that website, it wouldn't take long to verify the long load. One notable example is the "North winds".
Northwind takes like 6 seconds to load on my Galaxy S6 and around 5 seconds in my PC. The longest "wait times" are loading something from the breeze controller, though. Especially "Metadata" which takes nearly a second.
There seems to by a relatively huge gap (around 1 second) between loading google analytics and index.js
.
That being said, I think it uses SystemJS. That's kinda slow if not everything is bundled (at least materialize and views is not).
JavaScriptKicks loads instantly.
http://kchomp.co/ takes around 3.5 seconds with a 2 second gap between bluebird.js
and app.js
.
@ohjames
My team member was wrong to lay the blame on system.js...
Would you mind to share any details on this conclusion? As far as I know, SystemJS uses a promise-based approach which essentially means there's overhead (waiting for resolves). With a lot of files, this is a lot of overhead.
I suspect the issue is with systemjs being slow after doing some of my own analysis, although I doubt its use of promises are exclusively to blame.
I just upgraded to the latest aurelia-bundler and now kchomp.co takes a lot longer to load on mobile. Oh no ;)
SystemJS is just super slow even when bundled, I've hacked the loader a little and made it much faster. My approach isn't suitable for a PR though.
SystemJS is just super slow even when bundled
well, this is not what we see in our environment. While the web site is definitely slower on a mobile, on the desktop it's almost instantaneous.
well, this is not what we see in our environment
Every single aurelia website deployed with systemjs, bundled or otherwise, spends at least 3 seconds bootstrapping on mobile. Bet you can't find a single example to the contrary.
on the desktop it's almost instantaneous.
That's true, but this issue is all about mobile performance which is terrible due to systemjs.
After several hours porting the build system to webpack I've brought mobile load times down to less than a second with pretty much the same code as before.
@ohjames, I'm glad to hear Aurelia isn't the problem.
Well it's opened up a whole other can of worms (https://github.com/aurelia/loader-webpack/issues/24 !?) but at least I managed to hack the build enough and peg dependencies just so that my build stopped throwing unhelpful error messages enough to work. The lack of documentation and bugs were a huge source of pain though.
Loads in the blink of an eye on desktop browser. Takes 7-10 seconds to load the page on my Moto E using Chrome on Android 5.1. This is a reasonably powered (if budget) smartphone. I was surprised to have to wait so long and that it is the same no matter the state of the cache.
I'd love to use Aurelia for a project I'm working on but I can't consider it with that mobile performance. Is there any likelihood this will considerably improve within the next few months?