diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cabbd37..83aebc5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Release notes +## v0.141.3 + +#### Feat + +- You no longer need to render the whole page with the `document` strategy to use HTML fragments. + + Previously, if you wanted to insert rendered components as HTML fragments, you had to ensure + that the HTML document it was being inserted into was rendered with the `document` strategy. + + Now, when you render components with `fragment` strategy, they know how to fetch their own JS / CSS dependencies. + +#### Fix + +- Fix compatibility with django-template-partials ([#1322](https://github.com/django-components/django-components/issues/1322)) + ## v0.141.2 #### Fix diff --git a/docs/concepts/advanced/rendering_js_css.md b/docs/concepts/advanced/rendering_js_css.md index 21621cca..ab24048d 100644 --- a/docs/concepts/advanced/rendering_js_css.md +++ b/docs/concepts/advanced/rendering_js_css.md @@ -142,11 +142,12 @@ There are six dependencies strategies: - [`document`](../../advanced/rendering_js_css#document) (default) - Smartly inserts JS / CSS into placeholders or into `
` and `` tags. - - Inserts extra script to allow `fragment` strategy 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`](../../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`](../../advanced/rendering_js_css#simple) - Smartly insert JS / CSS into placeholders or into `` and `` tags. - No extra script loaded. @@ -212,10 +213,6 @@ the page in the browser: - A JS script is injected to manage component dependencies, enabling lazy loading of JS and CSS for HTML fragments. -!!! info - - This strategy is required for fragments to work properly, as it sets up the dependency manager that fragments rely on. - !!! note "How the dependency manager works" The dependency manager is a JS script that keeps track of all the JS and CSS dependencies that have already been loaded. @@ -228,8 +225,7 @@ the page in the browser: ### `fragment` -`deps_strategy="fragment"` is used when rendering a piece of HTML that will be inserted into a page -that has already been rendered with the [`"document"`](#document) strategy: +`deps_strategy="fragment"` is used when rendering a piece of HTML that will be inserted into a page: ```python fragment = MyComponent.render(deps_strategy="fragment") @@ -426,6 +422,6 @@ assert html == html2 Django-components provides a seamless integration with HTML fragments with AJAX ([HTML over the wire](https://hotwired.dev/)), whether you're using jQuery, HTMX, AlpineJS, vanilla JavaScript, or other. -This is achieved by the combination of the [`"document"`](#document) and [`"fragment"`](#fragment) strategies. +This is achieved by the [`"fragment"`](#fragment) strategy. Read more about [HTML fragments](../../advanced/html_fragments). diff --git a/docs/concepts/fundamentals/rendering_components.md b/docs/concepts/fundamentals/rendering_components.md index 38883204..dd66004e 100644 --- a/docs/concepts/fundamentals/rendering_components.md +++ b/docs/concepts/fundamentals/rendering_components.md @@ -349,12 +349,12 @@ There are six dependencies rendering strategies: - [`document`](../../advanced/rendering_js_css#document) (default) - Smartly inserts JS / CSS into placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) or into `` and `` tags. - - Inserts extra script to allow `fragment` components 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`](../../advanced/rendering_js_css#fragment) - A lightweight HTML fragment to be inserted into a document with AJAX. - - Assumes the page was already rendered with `"document"` strategy. - - 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`](../../advanced/rendering_js_css#simple) - Smartly insert JS / CSS into placeholders ([`{% component_js_dependencies %}`](../../../reference/template_tags#component_js_dependencies)) or into `` and `` tags. - No extra script loaded. diff --git a/src/django_components/component.py b/src/django_components/component.py index 11678f46..1e1e43f0 100644 --- a/src/django_components/component.py +++ b/src/django_components/component.py @@ -2697,11 +2697,12 @@ class Component(metaclass=ComponentMeta): - [`"document"`](../../concepts/advanced/rendering_js_css#document) (default) - Smartly inserts JS / CSS into placeholders or into `` and `` 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 `` and `` 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 `` and `` 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 `` and `` tags. - No extra script loaded. diff --git a/src/django_components/dependencies.py b/src/django_components/dependencies.py index 4f4a8830..e575e028 100644 --- a/src/django_components/dependencies.py +++ b/src/django_components/dependencies.py @@ -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 ``. # 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 ")] + final_script_tags = "".join( [ # JS by us diff --git a/src/django_components/static/django_components/django_components.min.js b/src/django_components/static/django_components/django_components.min.js index 89b7f3cf..d3f612b5 100644 --- a/src/django_components/static/django_components/django_components.min.js +++ b/src/django_components/static/django_components/django_components.min.js @@ -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