mirror of
https://github.com/django-components/django-components.git
synced 2025-11-17 21:57:07 +00:00
feat: render fragments without document strategy (#1339)
This commit is contained in:
parent
aa14e3698d
commit
c72fed8255
11 changed files with 217 additions and 26 deletions
|
|
@ -2697,11 +2697,12 @@ class Component(metaclass=ComponentMeta):
|
|||
|
||||
- [`"document"`](../../concepts/advanced/rendering_js_css#document) (default)
|
||||
- Smartly inserts JS / CSS into placeholders or into `<head>` and `<body>` tags.
|
||||
- Inserts extra script to allow `fragment` types to work.
|
||||
- Assumes the HTML will be rendered in a JS-enabled browser.
|
||||
- Requires the HTML to be rendered in a JS-enabled browser.
|
||||
- Inserts extra script for managing fragments.
|
||||
- [`"fragment"`](../../concepts/advanced/rendering_js_css#fragment)
|
||||
- A lightweight HTML fragment to be inserted into a document with AJAX.
|
||||
- No JS / CSS included.
|
||||
- Fragment will fetch its own JS / CSS dependencies when inserted into the page.
|
||||
- Requires the HTML to be rendered in a JS-enabled browser.
|
||||
- [`"simple"`](../../concepts/advanced/rendering_js_css#simple)
|
||||
- Smartly insert JS / CSS into placeholders or into `<head>` and `<body>` tags.
|
||||
- No extra script loaded.
|
||||
|
|
@ -3186,11 +3187,12 @@ class Component(metaclass=ComponentMeta):
|
|||
|
||||
- [`"document"`](../../concepts/advanced/rendering_js_css#document) (default)
|
||||
- Smartly inserts JS / CSS into placeholders or into `<head>` and `<body>` tags.
|
||||
- Inserts extra script to allow `fragment` types to work.
|
||||
- Assumes the HTML will be rendered in a JS-enabled browser.
|
||||
- Requires the HTML to be rendered in a JS-enabled browser.
|
||||
- Inserts extra script for managing fragments.
|
||||
- [`"fragment"`](../../concepts/advanced/rendering_js_css#fragment)
|
||||
- A lightweight HTML fragment to be inserted into a document with AJAX.
|
||||
- No JS / CSS included.
|
||||
- Fragment will fetch its own JS / CSS dependencies when inserted into the page.
|
||||
- Requires the HTML to be rendered in a JS-enabled browser.
|
||||
- [`"simple"`](../../concepts/advanced/rendering_js_css#simple)
|
||||
- Smartly insert JS / CSS into placeholders or into `<head>` and `<body>` tags.
|
||||
- No extra script loaded.
|
||||
|
|
|
|||
|
|
@ -476,6 +476,28 @@ def render_dependencies(content: TContent, strategy: DependenciesStrategy = "doc
|
|||
_render_dependencies = render_dependencies
|
||||
|
||||
|
||||
def _pre_loader_js() -> str:
|
||||
"""
|
||||
This script checks if our dependency manager script is already loaded on the page,
|
||||
and loads the manager if not yet.
|
||||
|
||||
This script is included with every "fragment", so that the "fragments" can be rendered
|
||||
even on pages that weren't rendered with the "document" strategy.
|
||||
"""
|
||||
manager_url = static("django_components/django_components.min.js")
|
||||
return f"""
|
||||
(() => {{
|
||||
if (!globalThis.Components) {{
|
||||
const s = document.createElement('script');
|
||||
s.src = "{manager_url}";
|
||||
document.head.appendChild(s);
|
||||
}}
|
||||
// Remove this loader script
|
||||
if (document.currentScript) document.currentScript.remove();
|
||||
}})();
|
||||
"""
|
||||
|
||||
|
||||
# Overview of this function:
|
||||
# 1. We extract all HTML comments like `<!-- _RENDERED table_10bac31,1234-->`.
|
||||
# 2. We look up the corresponding component classes
|
||||
|
|
@ -647,6 +669,20 @@ def _process_dep_declarations(content: bytes, strategy: DependenciesStrategy) ->
|
|||
js=[static("django_components/django_components.min.js")] if strategy == "document" else [],
|
||||
).render_js()
|
||||
|
||||
# Core scripts without which the rest wouldn't work
|
||||
core_script_tags = []
|
||||
if strategy == "document":
|
||||
# For full documents, load manager as a normal external <script src="...">
|
||||
core_script_tags = Media(js=[static("django_components/django_components.min.js")]).render_js()
|
||||
elif strategy == "fragment":
|
||||
# For fragments, inline a script that conditionally injects the dependency manager
|
||||
# if it's not already loaded.
|
||||
#
|
||||
# TODO: Eventually we want to parametrize how the `<script>` tag is rendered
|
||||
# (e.g. to use `type="module"`, `defer`, or csp nonce) based on which component
|
||||
# it was defined in.
|
||||
core_script_tags = [mark_safe(f"<script>{_pre_loader_js()}</script>")]
|
||||
|
||||
final_script_tags = "".join(
|
||||
[
|
||||
# JS by us
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
(()=>{var x=o=>new DOMParser().parseFromString(o,"text/html").documentElement.textContent,E=Array.isArray,m=o=>typeof o=="function",H=o=>o!==null&&typeof o=="object",S=o=>(H(o)||m(o))&&m(o.then)&&m(o.catch);function N(o,i){try{return i?o.apply(null,i):o()}catch(s){L(s)}}function g(o,i){if(m(o)){let s=N(o,i);return s&&S(s)&&s.catch(c=>{L(c)}),[s]}if(E(o)){let s=[];for(let c=0;c<o.length;c++)s.push(g(o[c],i));return s}else console.warn(`[Components] Invalid value type passed to callWithAsyncErrorHandling(): ${typeof o}`)}function L(o){console.error(o)}var M=o=>{let i=new MutationObserver(s=>{for(let c of s)c.type==="childList"&&c.addedNodes.forEach(d=>{d.nodeName==="SCRIPT"&&d.hasAttribute("data-djc")&&o(d)})});return i.observe(document,{childList:!0,subtree:!0}),i};var y=()=>{let o=new Set,i=new Set,s={},c={},d=t=>{let e=new DOMParser().parseFromString(t,"text/html").querySelector("script");if(!e)throw Error("[Components] Failed to extract <script> tag. Make sure that the string contains <script><\/script> and is a valid HTML");return e},F=t=>{let e=new DOMParser().parseFromString(t,"text/html").querySelector("link");if(!e)throw Error("[Components] Failed to extract <link> tag. Make sure that the string contains <link></link> and is a valid HTML");return e},T=t=>{let e=document.createElement(t.tagName);e.innerHTML=t.innerHTML;for(let r of t.attributes)e.setAttributeNode(r.cloneNode());return e},f=t=>{let e=d(t),r=e.getAttribute("src");if(!r||C("js",r))return;p("js",r);let a=T(e),l=e.getAttribute("async")!=null||e.getAttribute("defer")!=null||e.getAttribute("type")==="module";a.async=l;let u=new Promise((n,b)=>{a.onload=()=>{n()},globalThis.document.body.append(a)});return{el:a,promise:u}},h=t=>{let e=F(t),r=e.getAttribute("href");if(!r||C("css",r))return;let a=T(e);return globalThis.document.head.append(a),p("css",r),{el:a,promise:Promise.resolve()}},p=(t,e)=>{if(t!=="js"&&t!=="css")throw Error(`[Components] markScriptLoaded received invalid script type '${t}'. Must be one of 'js', 'css'`);(t==="js"?o:i).add(e)},C=(t,e)=>{if(t!=="js"&&t!=="css")throw Error(`[Components] isScriptLoaded received invalid script type '${t}'. Must be one of 'js', 'css'`);return(t==="js"?o:i).has(e)},w=(t,e)=>{s[t]=e},j=(t,e,r)=>{let a=`${t}:${e}`;c[a]=r},A=(t,e,r)=>{let a=s[t];if(!a)throw Error(`[Components] '${t}': No component registered for that name`);let l=Array.from(document.querySelectorAll(`[data-djc-id-${e}]`));if(!l.length)throw Error(`[Components] '${t}': No elements with component ID '${e}' found`);let u=`${t}:${r}`,n=c[u];if(!n)throw Error(`[Components] '${t}': Cannot find input for hash '${r}'`);let b=n(),v={name:t,id:e,els:l},[P]=g(a,[b,v]);return P},k=async t=>{let e=t.loadedCssUrls.map(n=>atob(n)),r=t.loadedJsUrls.map(n=>atob(n)),a=t.toLoadCssTags.map(n=>atob(n)),l=t.toLoadJsTags.map(n=>atob(n));e.forEach(n=>p("css",n)),r.forEach(n=>p("js",n)),Promise.all(a.map(n=>h(n))).catch(console.error);let u=Promise.all(l.map(n=>f(n))).catch(console.error)};return M(t=>{let e=JSON.parse(t.text);k(e)}),{callComponent:A,registerComponent:w,registerComponentData:j,loadJs:f,loadCss:h,markScriptLoaded:p}};var $={manager:y(),createComponentsManager:y,unescapeJs:x};globalThis.Components=$;})();
|
||||
(()=>{var x=o=>new DOMParser().parseFromString(o,"text/html").documentElement.textContent,E=Array.isArray,m=o=>typeof o=="function",N=o=>o!==null&&typeof o=="object",L=o=>(N(o)||m(o))&&m(o.then)&&m(o.catch);function $(o,i){try{return i?o.apply(null,i):o()}catch(s){M(s)}}function g(o,i){if(m(o)){let s=$(o,i);return s&&L(s)&&s.catch(c=>{M(c)}),[s]}if(E(o)){let s=[];for(let c=0;c<o.length;c++)s.push(g(o[c],i));return s}else console.warn(`[Components] Invalid value type passed to callWithAsyncErrorHandling(): ${typeof o}`)}function M(o){console.error(o)}var j=o=>{let i=new MutationObserver(s=>{for(let c of s)c.type==="childList"&&c.addedNodes.forEach(p=>{p.nodeName==="SCRIPT"&&p.hasAttribute("data-djc")&&o(p)})});return i.observe(document,{childList:!0,subtree:!0}),i};var y=()=>{let o=new Set,i=new Set,s={},c={},p=t=>{let e=new DOMParser().parseFromString(t,"text/html").querySelector("script");if(!e)throw Error("[Components] Failed to extract <script> tag. Make sure that the string contains <script><\/script> and is a valid HTML");return e},F=t=>{let e=new DOMParser().parseFromString(t,"text/html").querySelector("link");if(!e)throw Error("[Components] Failed to extract <link> tag. Make sure that the string contains <link></link> and is a valid HTML");return e},T=t=>{let e=document.createElement(t.tagName);e.innerHTML=t.innerHTML;for(let r of t.attributes)e.setAttributeNode(r.cloneNode());return e},f=t=>{let e=p(t),r=e.getAttribute("src");if(!r||C("js",r))return;d("js",r);let a=T(e),l=e.getAttribute("async")!=null||e.getAttribute("defer")!=null||e.getAttribute("type")==="module";a.async=l;let u=new Promise((n,b)=>{a.onload=()=>{n()},globalThis.document.body.append(a)});return{el:a,promise:u}},h=t=>{let e=F(t),r=e.getAttribute("href");if(!r||C("css",r))return;let a=T(e);return globalThis.document.head.append(a),d("css",r),{el:a,promise:Promise.resolve()}},d=(t,e)=>{if(t!=="js"&&t!=="css")throw Error(`[Components] markScriptLoaded received invalid script type '${t}'. Must be one of 'js', 'css'`);(t==="js"?o:i).add(e)},C=(t,e)=>{if(t!=="js"&&t!=="css")throw Error(`[Components] isScriptLoaded received invalid script type '${t}'. Must be one of 'js', 'css'`);return(t==="js"?o:i).has(e)},w=(t,e)=>{s[t]=e},A=(t,e,r)=>{let a=`${t}:${e}`;c[a]=r},H=(t,e,r)=>{let a=s[t];if(!a)throw Error(`[Components] '${t}': No component registered for that name`);let l=Array.from(document.querySelectorAll(`[data-djc-id-${e}]`));if(!l.length)throw Error(`[Components] '${t}': No elements with component ID '${e}' found`);let u=`${t}:${r}`,n=c[u];if(!n)throw Error(`[Components] '${t}': Cannot find input for hash '${r}'`);let b=n(),v={name:t,id:e,els:l},[P]=g(a,[b,v]);return P},k=async t=>{let e=t.loadedCssUrls.map(n=>atob(n)),r=t.loadedJsUrls.map(n=>atob(n)),a=t.toLoadCssTags.map(n=>atob(n)),l=t.toLoadJsTags.map(n=>atob(n));e.forEach(n=>d("css",n)),r.forEach(n=>d("js",n)),Promise.all(a.map(n=>h(n))).catch(console.error);let u=Promise.all(l.map(n=>f(n))).catch(console.error)},S=t=>{let e=JSON.parse(t.text);k(e)};return j(S),document.querySelectorAll("script[data-djc]").forEach(S),{callComponent:H,registerComponent:w,registerComponentData:A,loadJs:f,loadCss:h,markScriptLoaded:d}};var J={manager:y(),createComponentsManager:y,unescapeJs:x};globalThis.Components=J;})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue