Closed pheara closed 7 years ago
This is really a showstopper for many people.
An first step that we should take before starting with service-workers, would be to generate a hash of every built bundle.js
and append that to it's name and statically generate index.html
with <script src="bundle-a7ad875aef.js"></script>
. Then we can set the caching-policy with which it's served to "forever"
.
This would also have the nice benefit of fixing caching issues when debugging.
We could add the clojure-compiler to the build. Among other things it does limited tree shaking and minification. For a different project I'm currently working on, this reduced the app-bundle size from 2.2MB to 550kB (140kB after gzipping). Dunno if we can't also get babel/jspm to minify/tree-shake, though.
Also, we should go over our javascript dependencies, check their file-size and try to replace the larger ones with slimmer alternatives.
Related issues:
As for minification: there's a jspm-option for that. Just add minify: true
to the call to gulp_jspm(..)
in gulpfile.js
. This will cause some compilation problems though, that need to be fixed (e.g. missing semicolons).
jspm allows us to split bundle. If angular allows adding components later at run-time, we could pull the landing page it's dependencies into a seperate bundle and load the rest of the app in a second pass. Dunno how we'd react to people entering via the create-need or post-detail pages? have an initial-deferred pair for every possible entry-point?
The worst part is loading all the data. Possible approaches:
Some additional enhancement-possibilities that came up during today's jour fixe:
screenshots of the state of things at 69de16c, loading the app with throttling set to "fast 3g" (1Mb/s down, 750kb/s up, 40ms RTT):
on the landingpage without an account (12.5s without, 16s with fonts):
note that the map loads tiles towards the end for about 4s, even though it's not visible by default. performance wise this is actually good, should the user continue with a view with a visible map, but has the downside of occuring additional data-transmission (which should be avoided due to causing additional charges)
roughly 12.5s (excluding fonts) till the app has finished loading, 16s with them, most of which is spent loading data over the network (as there's almost no gaps between the requests)
landinpage, w/o account, gzip enabled (8.5s w/o fonts, 14s with them): (ADDED LATER)
As you can see, page-load has improved from 12.5s to about 8.5s (without fonts; 16s to 14s with them), thus yielding roughly a 4s improvement (32% of load-time) to first meaningful render (i.e. till text and buttons of the landing page are visible, even if just with a backup-font)
on the feed, signed in, with 5 owned needs (18.5s):
loading the needs and connections incurs an additional 6.5s (onto a slightly lower base of 12s) for a total of around 18.5s in this case. Most of that time is spend on the round-trips (the actual data-transfer has neglible impact). Note, that the requests for the needs and connections are somewhat cascaded, as first the owned needs, then their connections and then their counterparts are fetched.
Roughly 2s of the app-bundle's load time are spent waiting for a free tcp socket, as it's listed last. By preloading it via <link prefetch...>
(for a usage-example see number seven here) we could (probably) shave those off.
Note:
For a legend and explanation of the color-coding, see here
also rather interesting,
on the feed, signed in, with 5 owned needs, repeated visit (22s):
The sprite and the <script>
-included dependencies are cached now. For some reason however, it redownloads them -- sequentially -- thus causing the even higher load time.
To summarize possible approaches, for general page-load performance we have:
<link prefetch...>
, see number 7 here). Update: link-preload/prefetch had no effect, however it was possible to bundle mediumjs/rangy/undojs and thus have the bundle load in the first round of ressources after the html is parsed.<script>
-includesgetDefaultWonNodeUri
; e.g. via config.js
or a jsp-template. Ideally this should be taken from the owner-config in conf.local
.npm:redux
, see below)ad inlining: as David Gilbertson points out in number six here, index.html should stay below 14kB to avoid running into flow-control. We could still try it though, as we spent roughly 0.5s per request on TTFB and our first html isn't too meaningful anyway before the javascript has finished initializing everything (unless of course we go down the path of server-side prerendering).
For repeated visits we could do the following:
First, I'd like to diagnose how much size the dependencies in our package json and our app-folder are contributing to the bundle though.
For when we go super-perfomance, https://hpbn.co/is a good ressource to read. Though, at this point of time, most of it is rather neglectable for us, compared to the bundle-size and the time required to fetch needs and everythin attached to them.
For analyzing our dependencies contribution to bundle-size there's this nice treeview-app (it would require setting up an alternative, webpack-based build though)
I've listed the sorted the sizes of all javascript-files in ./jspm_packages/*
here. However this doesn't tell us which versions (for packages containing multiple variants), how large they are minified (unless they already are) and to what dependencies of ours they contribute to (e.g. we don't use lodash, one of the worse offenders, directly, but it's included via npm:redux
), as such we don't know how much each direct dependency ultimatively adds to the bundle atm.
hier sind die top punkte komprimierung von js, aufspalten und parallel laden von js und js reduzieren: https://developers.google.com/speed/pagespeed/insights/?url=www.matchat.org wobei man sich warscheinlich vor der komprimierung gedanken über aufspalten, reduzieren und paralelisieren machen muss, komprimieren kann man dann immer
One of our larger dependencies and the only dependency that's larger than our app is angular, that we mostly use as a rendering layer. In this capacity we could replace it with just about anything else and save significantly in size (e.g. react, preact, riot, etc). For switching to react (the largest of the alternatives), this would reduce our bundle-size by about 10% and thus have roughly the same effect as reducing our the size of the scripts in our app/
-folder by about 40% (assuming the minified sizes listed below). However, it would require quite some work -- mostly refactoring the component's boilerplate code and switching out the router -- and thus shouldn't be the highest priority in my opinion.
8276 preact.min.js
20772 preact.js
23040 react.min.js
145032 react.js
168828 angular.min.js
380532 app/ (rough minification estimate*)
962560 app/ (unminified)
1270864 angular.js
1473863 app_jspm.bundle.js (minified)
5016465 app_jspm.bundle.js (not minified)
* using the more realistic factor in this quick and rough estimation calculation
One of the other big-offenders, that we really don't need is lodash, that we get indirectly via npm:redux
and npm:ng-redux
that depends npm:redux
and several lodash-plugins. We should look into alternatives or write our own. Per se the redux dispatcher should be rather straightfoward/small and possible to implement without lodash (but i'm probably mistaken in the first regard). Maybe there's also a lodash-free variant or alternative. In any way, we'd also need to replace the utilities gained from ng-redux. Doing this successfully, would reduce our bundle-size by about 9%.
5782 jspm_packages/npm/redux@3.7.2/dist/redux.min.js
26736 jspm_packages/npm/redux@3.7.2/dist/redux.js
134829 jspm_packages/npm/lodash@4.17.4/lodash.min.js
213073 jspm_packages/npm/lodash@4.17.4/lodash.js
@heikofriedrich ah, this answers the question whether or not our server uses gzip -- apparently it doesn't! That should be a rather low hanging fruit indeed we've got there :)
yeah, this google develeoper performance site looks quite useful to me, maybe we could use this to asses how successful our performance optimizations were in the end => current measurement: 30/100 (mobile), 37/100 (desktop)
As for tree-shaking: this page of the jspm-0.17-beta documentation mentions tree-shaking. I've openened a branch (feat__treeshaking-with-bower
, named to avoid clashing with a failed branch off of refac__removed-bower
). In that branch I added jspm-0.17 to the repo and in the powershell (necessary to see the color-highlighting in the output) bundled the app via
jspm build app/app_jspm.js generated/build.js --minify
This was done using the CLI, as gulp-jspm doesn't support jspm-0.17 yet and thus our bundlejs
-task fails.
Tree-shaking works, as a function I added to redux-utils.js
-- one of the files inlined by rollup (as is indicated by the intendation and color-highlighting) and thus one of the files benefitting from tree-shaking. The function wasn't contained in the output.
However, the resulting build.js
is bigger than the app_jspm.bundle.js
build on master. It appears, that we're already using tree-shaking (the same test works as well in redux-utils.js
on master
) and for some reason the jspm-0.17-beta output is about 1M bigger, as can be seen here:
-rw-r--r-- 1 ksinger 1049089 1475004 Sep 12 14:53 app_jspm.bundle.js
-rw-r--r-- 1 ksinger 1049089 6425584 Sep 12 14:53 app_jspm.bundle.js.map
-rw-r--r-- 1 ksinger 1049089 2512155 Sep 12 17:14 build.js
-rw-r--r-- 1 ksinger 1049089 3161453 Sep 12 17:14 build.js.map
Update: I'm not sure anymore that we're using tree-shaking with the old jspm version -- I can't repeat the experiment (having a function removed) anymore, unless I build with the newer jspm-version in the changed build-setup/config environment of eat__treeshaking-with-bower
. What I do know however, is that the output generated by jspm-0.17 is bigger than with our current setup, so I'd advise against changing right now.
For measuring the actual size-impacts of our dependencies (including their dependencies), we could simply change jspm_config.js
to point to an empty module export for each of these in turn, build the bundle and check the size differences. This should even be scriptable without too much effort.
landinpage, w/o account, after enhancements (4.5s w/o fonts, 5.5s with fonts):
With the following enhancements merged:
- {x} #1201(pr): serve the minified css (we already generate it)
- {x} #1203(pr): make sure bundle starts loading asap (e.g. via
<link prefetch...>
, see number 7 here). Update: link-preload/prefetch had no effect, however it was possible to bundle mediumjs/rangy/undojs and thus have the bundle load in the first round of ressources after the html is parsed.- {x} #1198: configure server to use gzip (expected size-reduction of bundle of 1M/ by 73%)
- {x} #1199(pr): use gzip-compression for fonts
- {x} #1205(pr): remove unused font-weights
- {x} #1205(pr): non-blocking font-loading / using fall-back fonts
- {x} #1205(pr): woff/woff2 fonts (~10% of the size)
- {x} #1210(pr): gzip svgs
- {x} #1203(pr): inline* or bundle
<script>
-includes
We're now down to 4.5s (w/o fonts) and 5.5s (with fonts) on "fast 3G"
As for in-browser-caching: Chrome seems to have troubles caching won.min.css
and occassionally OpenSans-*.woff2
and icon-sprite.svg
. The only difference to e.g. the request for app_jspm.bundle.js
is that these requests don't contain cache-control: no-cache
and the responses to them contain expires: <now+1day>
(while the response with the bundle doesn't). However I don't see how to add this field in the <link...>
-tag, i.e. afaict we could only add this by loading the ressources via js or by intercepting the requests and adding the fields.
Other ways to fine-tune the caching:
max-age
to one year (controlled via an ExpiresFilter
in the owner-application's web.xml
: see here) and use revving, i.e. add the a version-number or the build-date to the file-names of the ressources and to the links in index.html
. A lighter-weight but hackier variant would be something like: /style.css?modified=20012009
. This only affects re-visits more than a day apart (our current default cache-timeout), though. This should be done for all static ressources (i.e. all html, js, css).max-age
or even the new immutable
flag.cache-control: public
for all ressources but events. This would allow shared-caches (e.g. businesses web-proxies) to cache the ressources. Conversely, cache-control: private
should be used for events.can we close this item for the time being as we did plenty of performance enhancement as is, and we can open new specific items whenever we feel like it anyway
Here's a few enhancements we could make to drastically increase the page-load duration, roughly sorted by how much effort they are to implement: