Add icon sprite caching via local storage (#6243)

Improve Wagtail admin page load performance by caching SVG icons sprite in localstorage

Co-authored-by: Thibaud Colas <thibaudcolas@gmail.com>
pull/6402/head
Coen van der Kamp 2020-09-24 21:12:32 +02:00 zatwierdzone przez GitHub
rodzic 3300e3b851
commit 0338cc37f7
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
7 zmienionych plików z 111 dodań i 18 usunięć

Wyświetl plik

@ -14,6 +14,7 @@ Changelog
* Add `render` helper to `RoutablePageMixin` to support serving template responses according to Wagtail conventions (Andy Babic)
* Specify minimum Python version in setup.py (Vince Salvino)
* Show user's full name in report views (Matt Westcott)
* Improve Wagtail admin page load performance by caching SVG icons sprite in localstorage (Coen van der Kamp)
* Fix: Make page-level actions accessible to keyboard users in page listing tables (Jesse Menn)
* Fix: `WAGTAILFRONTENDCACHE_LANGUAGES` was being interpreted incorrectly. It now accepts a list of strings, as documented (Karl Hobley)
* Fix: Update oEmbed endpoints to use https where available (Matt Westcott)

Wyświetl plik

@ -23,6 +23,7 @@ Other features
* Extend treebeard's ``fix_tree`` method with the ability to non-destructively fix path issues and add a --full option to apply path fixes (Matt Westcott)
* Add support for hierarchical/nested Collections (Robert Rollins)
* Show user's full name in report views (Matt Westcott)
* Improve Wagtail admin page load performance by caching SVG icons sprite in localstorage (Coen van der Kamp)
Bug fixes

Wyświetl plik

@ -19,6 +19,56 @@
{% block branding_favicon %}{% endblock %}
</head>
<body id="wagtail" class="{% block bodyclass %}{% endblock %} {% if messages %}has-messages{% endif %} focus-outline-on">
<div data-sprite></div>
<script>
function loadIconSprite() {
var spriteURL = '{% url "wagtailadmin_sprite" %}';
var revisionKey = 'wagtail:spriteRevision';
var dataKey = 'wagtail:spriteData';
var isLocalStorage = 'localStorage' in window && typeof window.localStorage !== 'undefined';
var insertIt = function (data) {
var spriteContainer = document.body.querySelector('[data-sprite]');
spriteContainer.innerHTML = data;
}
var insert = function (data) {
if (document.body) {
insertIt(data)
} else {
document.addEventListener('DOMContentLoaded', insertIt.bind(null, data));
}
}
if (isLocalStorage && localStorage.getItem(revisionKey) === spriteURL) {
var data = localStorage.getItem(dataKey);
if (data) {
insert(data);
return true;
}
}
try {
var request = new XMLHttpRequest();
request.open('GET', spriteURL, true);
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
data = request.responseText;
insert(data);
if (isLocalStorage) {
localStorage.setItem(dataKey, data);
localStorage.setItem(revisionKey, spriteURL);
}
}
}
request.send();
} catch (e) {
console.error(e);
}
}
loadIconSprite();
</script>
<!--[if lt IE 9]>
<p class="capabilitymessage">{% blocktrans %}You are using an <strong>outdated</strong> browser not supported by this software. Please <a href="https://browsehappy.com/">upgrade your browser</a>.{% endblocktrans %}</p>
<![endif]-->
@ -29,8 +79,6 @@
{% endblocktrans %}
</noscript>
{% icons %}
{% block js %}{% endblock %}
<a class="skiplink button" href="#main" data-skiplink>{% trans 'Skip to main content' %}</a>

Wyświetl plik

@ -1,4 +1,3 @@
import itertools
import json
import warnings
from datetime import datetime
@ -548,20 +547,6 @@ def icon(name=None, class_name='icon', title=None, wrapped=False):
}
_icons_html = None
@register.simple_tag
def icons():
global _icons_html
if _icons_html is None:
icon_hooks = hooks.get_hooks('register_icons')
icons = sorted(itertools.chain.from_iterable(hook([]) for hook in icon_hooks))
_icons_html = render_to_string("wagtailadmin/shared/icons.html", {'icons': icons})
return _icons_html
@register.filter()
def timesince_simple(d):
"""

Wyświetl plik

@ -0,0 +1,24 @@
import re
from django.test import TestCase
from django.urls import reverse
from wagtail.admin.urls import get_sprite_hash, sprite_hash
class TestIconSprite(TestCase):
def test_get_sprite_hash(self):
result = get_sprite_hash()
self.assertTrue(bool(re.match(r"^[a-z0-9]{8}$", result)))
def test_hash_var(self):
self.assertTrue(isinstance(sprite_hash, str))
self.assertTrue(len(sprite_hash) == 8)
def test_url(self):
url = reverse("wagtailadmin_sprite")
self.assertEqual(url[:14], "/admin/sprite-")
def test_view(self):
response = self.client.get(reverse("wagtailadmin_sprite"))
self.assertTrue("Content-Type: text/html; charset=utf-8" in str(response.serialize_headers()))

Wyświetl plik

@ -1,5 +1,7 @@
import functools
import hashlib
from django.conf import settings
from django.urls import include, path, re_path
from django.views.decorators.cache import never_cache
from django.views.generic import TemplateView
@ -84,9 +86,23 @@ for fn in hooks.get_hooks('register_admin_urls'):
# Add "wagtailadmin.access_admin" permission check
urlpatterns = decorate_urlpatterns(urlpatterns, require_admin_access)
sprite_hash = None
def get_sprite_hash():
global sprite_hash
if not sprite_hash:
content = str(home.sprite(None).content, "utf-8")
sprite_hash = hashlib.sha1(
(content + settings.SECRET_KEY).encode("utf-8")
).hexdigest()[:8]
return sprite_hash
# These url patterns do not require an authenticated admin user
urlpatterns += [
path(f"sprite-{get_sprite_hash()}/", home.sprite, name="wagtailadmin_sprite"),
path('login/', account.LoginView.as_view(), name='wagtailadmin_login'),
# These two URLs have the "permission_required" decorator applied directly

Wyświetl plik

@ -1,9 +1,11 @@
import itertools
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import permission_required
from django.db import connection
from django.db.models import Max, Q
from django.http import Http404
from django.http import Http404, HttpResponse
from django.template.loader import render_to_string
from django.template.response import TemplateResponse
@ -191,3 +193,19 @@ def default(request):
redirected to the login page.
"""
raise Http404
_icons_html = None
def icons():
global _icons_html
if _icons_html is None:
icon_hooks = hooks.get_hooks('register_icons')
all_icons = sorted(itertools.chain.from_iterable(hook([]) for hook in icon_hooks))
_icons_html = render_to_string("wagtailadmin/shared/icons.html", {'icons': all_icons})
return _icons_html
def sprite(request):
return HttpResponse(icons())