Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frontend speed improvements - cuts landing page load time by 55% and DOMready time by 35% #4826

Closed
wants to merge 22 commits into from

Conversation

owocki
Copy link
Contributor

@owocki owocki commented Jul 18, 2019

Description

up until now, my efforts on site speed have been mostly oriented around HTML generation time. nwo that we've gotten the HTML gen time down, i'm turning my efforts towards focusing more on front-end optimizations.

this is a PR that takes the landing page load time on gitcoin from

Screen Shot 2019-07-12 at 5 39 35 PM

to

Screen Shot 2019-07-12 at 5 35 14 PM

on the intial page paint, thats:

  • 146 to 30 requests
  • 4.8MB to 2.2MB in total page size
  • 1.88s to DOMReady to 1.2s
  • 2.5s to Load to 1.57s

on a cold cache.

on a warmed cache itll take us from

  • (same requests)
  • (same size)
  • 930ms to DOMReady to 730ms
  • 1.65s to Load to 1.14s

how

  • uses django-compress to combine all the css and js files on a page into one http request response
  • uses jquery-unveil to defer any below-the-fold images until after the user scrolls down
  • uses AWS_HEADERS to set far future expires headers on AWS
  • uses django-statici18n to serve the i18n information along with the script js (instead of being another XHTTP request that blocked painting of text)
  • removes old trackcing scripts that we dont use any more (facebook + twitter pixel)
  • compressing all the images on the landing page ( these images are uploaded to https://github.com/gitcoinco/web/tree/kevin/site-perf2 ; there are 1000s of them, so i did not included them in this PR)

further TODOs

  • many of these things are just done on the landing page, and will need to be applied across the site too.
Testing

tested landing page locally

@owocki
Copy link
Contributor Author

owocki commented Jul 18, 2019

updated from #4791

Copy link
Contributor

@octavioamu octavioamu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bootstrap is already loaded in footer_scripts.js or footer_scripts_lite.js maybe adding compress js inside that files will be better

@@ -100,6 +100,13 @@ <h1 class="text-center title">{% trans "Submit Work" %}</h1>
{% include 'shared/footer.html' %}
{% include 'shared/messages.html' %}
</body>

<!-- jQuery -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is already loaded in footer_scripts.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -201,6 +201,13 @@ <h5>{% trans 'Payout Preview' %}</h5>
{% include 'shared/footer.html' %}
{% include 'shared/messages.html' %}
</body>

<!-- jQuery -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is already loaded in footer_scripts.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it; ill factor it back into footer_scripts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool I will wait until you finish to review it, ping me when ready

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just updated 1eb3739

@@ -109,8 +109,15 @@ <h3>{% trans "No results found." %}</h3>
{% include 'shared/footer_scripts.html' %}
{% include 'shared/footer.html' %}
{% include 'shared/messages.html' %}

{% compress js %}
<script src="{% static "v2/js/lib/popper.min.js" %}"></script>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is already loaded in footer_scripts.js

@@ -251,7 +251,18 @@ <h6 class="font-weight-bold mb-3">Invite User to Bounty</h6>
{% include 'shared/footer.html' %}
{% include 'shared/messages.html' %}

{% compress js %}
<script src="{% static "v2/js/lib/popper.min.js" %}"></script>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is already loaded in footer_scripts.js

<script>
let bootstrapTooltip = $.fn.tooltip.noConflict()
$.fn.runTooltip = bootstrapTooltip;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also declared in footer_scripts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app/dashboard/templates/bounty/details.html Outdated Show resolved Hide resolved
@owocki
Copy link
Contributor Author

owocki commented Jul 20, 2019

check the footer of master, is ordered in a way don't conflict with decencies over the site https://github.com/gitcoinco/web/blob/master/app/retail/templates/shared/footer_scripts.html

which dependancies are you worried about? crawling the site now, not seeing any conflicts

@owocki
Copy link
Contributor Author

owocki commented Jul 20, 2019

i think i got everyones feedback so far, but lmk if not





Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we remove the extra lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yessir done





Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we remove extra lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes done





Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya done

@@ -82,3 +82,8 @@ sentry-sdk
websocket-client
bleach
python-magic
rcssmin --install-option="--without-c-extensions"
rjsmin --install-option="--without-c-extensions"
django-compressor --upgrade
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the --upgrade option?
An auto-upgrade might mess things up if there ever is a breaking upgrade for compressor

Also travis fails

ERROR: Invalid requirement: django-compressor --upgrade
pip: error: no such option: --upgrade
The command "pip install -r requirements/test.txt" exited with 1.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree - having auto-upgrade is a bit scary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thelostone-mc
Copy link
Member

thelostone-mc commented Jul 20, 2019

@owocki A couple of other concerns

rcssmin + rjsmin are essentially bundlers from what I gather (kinda like webpack)
And this effectively would compress those our css into 1 file + js into 1 file

This would make debugging a lil tricker but the performance boost outweighs it 🙌
@octavioamu & me wondered if it would make more sense to split it as 2 bundles for both css + js

aka all library css files would be it's own bundle and all custom would be it's own bundle
same for JS.

I'm assuming this can be with something as simple as

 {% compress css %}
     <link...> // library css files
 {% endcompress %}

 {% compress css %}
     <link...> // custom css files
 {% endcompress %}

@thelostone-mc
Copy link
Member

^ @owocki on the off chance you didn't see this

@owocki
Copy link
Contributor Author

owocki commented Jul 25, 2019

This would make debugging a lil tricker but the performance boost outweighs it 🙌

you can toggle this in your environment by setting TEMPLATE_DEBUG flag. so on your local it won't be any harder to debug than before!

or were you worried about prod debugging?

aka all library css files would be it's own bundle and all custom would be it's own bundle

whats the difference between 'library' css files and custom? is the former dependancies like jquery / bootstrap, and the the latter is our own css files?

@thelostone-mc
Copy link
Member

thelostone-mc commented Jul 25, 2019

@owocki
Yup I was worried about prod debugging

And yes former == dependencies
And latter == our own CSS files!

@oritwoen
Copy link
Contributor

@owocki @thelostone-mc Funny thing. You are worried about debugging on production and I will be happy to see it and I will be glad if such production debugging appears. HACKING ❤️

But you're right, you really have to be careful about it. Sometimes very sensitive information is found in such debugging.

@owocki
Copy link
Contributor Author

owocki commented Jul 25, 2019

Yup I was worried about prod debugging

got it. whats your existing prod debugging workflow? i assume its sentry > js errors > repro locally > submit patch? how often do we go through this workflow?

@octavioamu
Copy link
Contributor

about production debugging the answer is "source maps" but not sure if that django tool have it
https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/

@owocki
Copy link
Contributor Author

owocki commented Jul 25, 2019

sourcemaps seems super powerful! seems like that solves the production debugging issue, no?

i will update the css/js builds to make them into dependancy/gitcoin packages and then re-submit the PR. does that sounds good? seems like thats where the consensus is going here..

@thelostone-mc
Copy link
Member

thelostone-mc commented Jul 27, 2019

@owocki that works. Once that PR is ready we can close this out 🙌
@ririen Appreciate you chiming in and thanks for creating the issues on GitHub

Copy link
Contributor Author

@owocki owocki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just fixed everything up and responded to code review

@@ -82,3 +82,8 @@ sentry-sdk
websocket-client
bleach
python-magic
rcssmin --install-option="--without-c-extensions"
rjsmin --install-option="--without-c-extensions"
django-compressor --upgrade
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -301,7 +301,7 @@ class Bounty(SuperModel):
canceled_bounty_reason = models.TextField(default='', blank=True, verbose_name=_('Cancelation reason'))
project_type = models.CharField(max_length=50, choices=PROJECT_TYPES, default='traditional', db_index=True)
permission_type = models.CharField(max_length=50, choices=PERMISSION_TYPES, default='permissionless', db_index=True)
bounty_categories = ArrayField(models.CharField(max_length=50, choices=BOUNTY_CATEGORIES), default=list)
bounty_categories = ArrayField(models.CharField(max_length=50, choices=BOUNTY_CATEGORIES), default=list, blank=True)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm i can't seem to generate one

bash-4.4# ./manage.py makemigrations
No changes detected

i thikn ebecause master has this change (and a subssequent migration) also





Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yessir done





Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes done





Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya done

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
{% elif user.is_authenticated %}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think well be running ./manage.py compress on our local, so youll still be able to debug on your local with the local one

<script src="{% static "v2/js/lib/popper.min.js" %}"></script>
<script src="{% static "v2/js/lib/bootstrap.min.js" %}" crossorigin="anonymous"></script>
<script src="{% static "jsi18n/en/djangojs.js" %}"></script>
<script src="{% static "v2/js/lib/vue.js" %}"></script>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i favor bringing vue inside of the compress; but @octavioamu let me know if u disagree

@owocki
Copy link
Contributor Author

owocki commented Aug 28, 2019

ok this is ready for re-review

You might want to revert that later for windows compatibility, or add a flag for each OS
@danlipert
Copy link
Contributor

@owocki I can't get the django-compressor library to install in my image, here's the output:

docker-compose exec web pip3 install django-compressor==2.3
WARNING: The TEST_MNEMONIC variable is not set. Defaulting to a blank string.
WARNING: Some services (db, ipfs, redis, testrpc, web) use the 'deploy' key, which will be ignored. Compose does not support 'deploy' configuration - use `docker stack deploy` to deploy to a swarm.
Collecting django-compressor==2.3
  Downloading https://files.pythonhosted.org/packages/43/e2/50e21afd7e13b522f7d1a29cf0a6dd65e19835950dce3be3c5928e668846/django_compressor-2.3-py2.py3-none-any.whl (124kB)
     |████████████████████████████████| 133kB 3.2MB/s 
Collecting rjsmin==1.1.0 (from django-compressor==2.3)
  Downloading https://files.pythonhosted.org/packages/a2/ba/0fa30f7ec949714b8397e80ee2057d1a7e77b3a9f1b94c1ece93586cf34f/rjsmin-1.1.0.tar.gz (412kB)
     |████████████████████████████████| 419kB 1.4MB/s 
Collecting rcssmin==1.0.6 (from django-compressor==2.3)
  Downloading https://files.pythonhosted.org/packages/e2/5f/852be8aa80d1c24de9b030cdb6532bc7e7a1c8461554f6edbe14335ba890/rcssmin-1.0.6.tar.gz (582kB)
     |████████████████████████████████| 583kB 1.7MB/s 
Collecting django-appconf>=1.0 (from django-compressor==2.3)
  Downloading https://files.pythonhosted.org/packages/f6/b3/fcec63afcf323581c4919f21e90ef8c8200034108a6a0ab47a6bf6a9327b/django_appconf-1.0.3-py2.py3-none-any.whl
Requirement already satisfied: six in /usr/local/lib/python3.7/site-packages (from django-appconf>=1.0->django-compressor==2.3) (1.12.0)
Requirement already satisfied: django in /usr/local/lib/python3.7/site-packages (from django-appconf>=1.0->django-compressor==2.3) (2.2.4)
Requirement already satisfied: sqlparse in /usr/local/lib/python3.7/site-packages (from django->django-appconf>=1.0->django-compressor==2.3) (0.3.0)
Requirement already satisfied: pytz in /usr/local/lib/python3.7/site-packages (from django->django-appconf>=1.0->django-compressor==2.3) (2019.2)
Building wheels for collected packages: rjsmin, rcssmin
  Building wheel for rjsmin (setup.py) ... done
  Stored in directory: /root/.cache/pip/wheels/2d/df/c3/08c02c2363af2c6d7fc29d3c303d1b90c648ac3252d1733a0e
  Building wheel for rcssmin (setup.py) ... error
  ERROR: Complete output from command /usr/local/bin/python -u -c 'import setuptools, tokenize;__file__='"'"'/tmp/pip-install-cf1wkn9e/rcssmin/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' bdist_wheel -d /tmp/pip-wheel-aejqkl7q --python-tag cp37:
  ERROR: running bdist_wheel
  running build
  running build_py
  creating build
  creating build/lib.linux-x86_64-3.7
  copying ./rcssmin.py -> build/lib.linux-x86_64-3.7
  warning: build_py: byte-compiling is disabled, skipping.
  
  running build_ext
  building '_rcssmin' extension
  creating build/temp.linux-x86_64-3.7
  gcc -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -DTHREAD_STACK_SIZE=0x100000 -fPIC -DEXT_MODULE=_rcssmin -UEXT_PACKAGE -I_setup/include -I/usr/local/include/python3.7m -c rcssmin.c -o build/temp.linux-x86_64-3.7/rcssmin.o
  unable to execute 'gcc': No such file or directory
  error: command 'gcc' failed with exit status 1
  ----------------------------------------
  ERROR: Failed building wheel for rcssmin
  Running setup.py clean for rcssmin
Successfully built rjsmin
Failed to build rcssmin
Installing collected packages: rjsmin, rcssmin, django-appconf, django-compressor
  Running setup.py install for rcssmin ... error
    ERROR: Complete output from command /usr/local/bin/python -u -c 'import setuptools, tokenize;__file__='"'"'/tmp/pip-install-cf1wkn9e/rcssmin/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-ecoe5ho5/install-record.txt --single-version-externally-managed --compile:
    ERROR: running install
    running build
    running build_py
    creating build
    creating build/lib.linux-x86_64-3.7
    copying ./rcssmin.py -> build/lib.linux-x86_64-3.7
    warning: build_py: byte-compiling is disabled, skipping.
    
    running build_ext
    building '_rcssmin' extension
    creating build/temp.linux-x86_64-3.7
    gcc -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -DTHREAD_STACK_SIZE=0x100000 -fPIC -DEXT_MODULE=_rcssmin -UEXT_PACKAGE -I_setup/include -I/usr/local/include/python3.7m -c rcssmin.c -o build/temp.linux-x86_64-3.7/rcssmin.o
    unable to execute 'gcc': No such file or directory
    error: command 'gcc' failed with exit status 1
    ----------------------------------------
ERROR: Command "/usr/local/bin/python -u -c 'import setuptools, tokenize;__file__='"'"'/tmp/pip-install-cf1wkn9e/rcssmin/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-ecoe5ho5/install-record.txt --single-version-externally-managed --compile" failed with error code 1 in /tmp/pip-install-cf1wkn9e/rcssmin/
WARNING: You are using pip version 19.1.1, however version 19.2.3 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

@owocki
Copy link
Contributor Author

owocki commented Sep 4, 2019

@danlipert checkout the requirements/base file ( https://github.com/gitcoinco/web/pull/4826/files#diff-7fe11226cff646f5d9f35faa76217059 ) if you install rcssmin with the flags in there itll work

Kevin/site-perf-3 Improve loading speed with django compressor
@codecov
Copy link

codecov bot commented Sep 4, 2019

Codecov Report

Merging #4826 into master will decrease coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4826      +/-   ##
==========================================
- Coverage   30.86%   30.86%   -0.01%     
==========================================
  Files         219      219              
  Lines       17656    17623      -33     
  Branches     2435     2427       -8     
==========================================
- Hits         5450     5439      -11     
+ Misses      11981    11959      -22     
  Partials      225      225
Impacted Files Coverage Δ
app/app/settings.py 79.34% <100%> (+0.38%) ⬆️
app/retail/templatetags/matches.py 62.5% <0%> (-37.5%) ⬇️
app/retail/templatetags/is_in_list.py 66.66% <0%> (-33.34%) ⬇️
app/kudos/test_views.py 73.07% <0%> (-26.93%) ⬇️
app/kudos/views.py 20.71% <0%> (-0.89%) ⬇️
app/kudos/models.py 54.26% <0%> (-0.45%) ⬇️
app/marketing/views.py 11.85% <0%> (-0.05%) ⬇️
app/app/urls.py 89.36% <0%> (ø) ⬆️
app/retail/emails.py 23.57% <0%> (ø) ⬆️
app/retail/utils.py 10.18% <0%> (+0.05%) ⬆️
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 96021fc...956d551. Read the comment docs.

1 similar comment
@codecov
Copy link

codecov bot commented Sep 4, 2019

Codecov Report

Merging #4826 into master will decrease coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4826      +/-   ##
==========================================
- Coverage   30.86%   30.86%   -0.01%     
==========================================
  Files         219      219              
  Lines       17656    17623      -33     
  Branches     2435     2427       -8     
==========================================
- Hits         5450     5439      -11     
+ Misses      11981    11959      -22     
  Partials      225      225
Impacted Files Coverage Δ
app/app/settings.py 79.34% <100%> (+0.38%) ⬆️
app/retail/templatetags/matches.py 62.5% <0%> (-37.5%) ⬇️
app/retail/templatetags/is_in_list.py 66.66% <0%> (-33.34%) ⬇️
app/kudos/test_views.py 73.07% <0%> (-26.93%) ⬇️
app/kudos/views.py 20.71% <0%> (-0.89%) ⬇️
app/kudos/models.py 54.26% <0%> (-0.45%) ⬇️
app/marketing/views.py 11.85% <0%> (-0.05%) ⬇️
app/app/urls.py 89.36% <0%> (ø) ⬆️
app/retail/emails.py 23.57% <0%> (ø) ⬆️
app/retail/utils.py 10.18% <0%> (+0.05%) ⬆️
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 96021fc...956d551. Read the comment docs.

@thelostone-mc
Copy link
Member

@MajorTomSec got your PR in but that breaks a lot of this when I pull this in locally !

Could you test out all your changes and raise a new PR to the branch ensuring none of your changes break the existing behavior ? If there are stuff which you can't test / figure out why it's breaking -> it might make more sense to not compress them at all!

cc @owocki

I can't get this in unless the we are sure the PR doesn't break existing functionality

@MajorTomSec
Copy link
Contributor

@MajorTomSec got your PR in but that breaks a lot of this when I pull this in locally !

Could you test out all your changes and raise a new PR to the branch ensuring none of your changes break the existing behavior ? If there are stuff which you can't test / figure out why it's breaking -> it might make more sense to not compress them at all!

I had no issue testing it locally (even though I could very well have missed things !), at least from what I could see. Can you point out issues you're having so I can try to reproduce them ?

{% include 'shared/sentry.html' %}
<script src="{% static "v2/js/lib/popper.min.js" %}"></script>
<script src="{% static "v2/js/lib/bootstrap.min.js" %}" crossorigin="anonymous"></script>
<script>
const bootstrapModal = $.fn.modal.noConflict()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is going to give $.fn.modal is undefined as bootstrap is loaded after this declaration.

<script src="{% static "v2/js/lib/jquery-ui.js" %}"></script>
<script src="{% static "v2/js/lib/tooltip.js" %}"></script>
{% endif %}
<script src="{% static "v2/js/lib/jquery-ui.js" %}"></script>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will break a lot of places using modals and popovers from bootstrap, that the main reason of slim, also as we are trying to totally remove this from the product

@thelostone-mc
Copy link
Member

Closing this out cause @octavioamu and @androolloyd were talking about redoing this with compress and @octavioamu already has a open PR we can use as a starting point

@thelostone-mc thelostone-mc deleted the kevin/site-perf-3 branch June 27, 2020 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants