kopia lustrzana https://github.com/wagtail/wagtail
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
rodzic
3300e3b851
commit
0338cc37f7
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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()))
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
Ładowanie…
Reference in New Issue