From 4241f8c8c0761344eeccf219d125324b0b232f6f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 21 Aug 2022 18:30:14 +0530 Subject: [PATCH] perf: simpler/faster preload header computation We parse entire response to find preload headers, instead just use include_style and include_script to include assets directly into preload headers. This shaves off ~13% overhead in response. --- frappe/__init__.py | 1 + frappe/utils/jinja_globals.py | 24 ++++++++++++++++++++++-- frappe/website/utils.py | 30 ++++++++++-------------------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index a796db9a83..53bc1018da 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -240,6 +240,7 @@ def init(site: str, sites_path: str = ".", new_site: bool = False) -> None: local.document_cache = {} local.meta_cache = {} local.form_dict = _dict() + local.preload_assets = {"style": [], "script": []} local.session = _dict() local.dev_server = _dev_server local.qb = get_query_builder(local.conf.db_type or "mariadb") diff --git a/frappe/utils/jinja_globals.py b/frappe/utils/jinja_globals.py index 44835be352..1265bd4d42 100644 --- a/frappe/utils/jinja_globals.py +++ b/frappe/utils/jinja_globals.py @@ -95,13 +95,33 @@ def get_dom_id(seed=None): return "id-" + generate_hash(seed, 12) -def include_script(path): +def include_script(path, preload=True): + """Get path of bundled script files. + + If preload is specified the path will be added to preload headers so browsers can prefetch + assets.""" path = bundled_asset(path) + + if preload: + import frappe + + frappe.local.preload_assets["script"].append(path) + return f'' -def include_style(path, rtl=None): +def include_style(path, rtl=None, preload=True): + """Get path of bundled style files. + + If preload is specified the path will be added to preload headers so browsers can prefetch + assets.""" path = bundled_asset(path) + + if preload: + import frappe + + frappe.local.preload_assets["style"].append(path) + return f'' diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 508026f064..bfb600a8c4 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -527,7 +527,8 @@ def build_response(path, data, http_status_code, headers: dict | None = None): response.headers["X-Page-Name"] = path.encode("ascii", errors="xmlcharrefreplace") response.headers["X-From-Cache"] = frappe.local.response.from_cache or False - add_preload_headers(response) + add_preload_for_bundled_assets(response) + if headers: for key, val in headers.items(): response.headers[key] = val.encode("ascii", errors="xmlcharrefreplace") @@ -557,29 +558,18 @@ def set_content_type(response, data, path): return data -def add_preload_headers(response): - from bs4 import BeautifulSoup, SoupStrainer +def add_preload_for_bundled_assets(response): - try: - preload = [] - strainer = SoupStrainer(re.compile("script|link")) - soup = BeautifulSoup(response.data, "html.parser", parse_only=strainer) - for elem in soup.find_all("script", src=re.compile(".*")): - preload.append(("script", elem.get("src"))) + links = [] - for elem in soup.find_all("link", rel="stylesheet"): - preload.append(("style", elem.get("href"))) + for css in frappe.local.preload_assets["style"]: + links.append(f"<{css}>; rel=preload; as=style") - links = [] - for _type, link in preload: - links.append(f"<{link}>; rel=preload; as={_type}") + for js in frappe.local.preload_assets["script"]: + links.append(f"<{js}>; rel=preload; as=script") - if links: - response.headers["Link"] = ",".join(links) - except Exception: - import traceback - - traceback.print_exc() + if links: + response.headers["Link"] = ",".join(links) @lru_cache