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