mirror of
https://github.com/django-components/django-components.git
synced 2025-10-25 12:57:19 +00:00
feat: Add support for HTML fragments (#845)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
6681fc0085
commit
4dab940db8
26 changed files with 1225 additions and 246 deletions
|
|
@ -1,22 +1,14 @@
|
|||
/** This file defines the API of the JS code. */
|
||||
import { createComponentsManager } from './manager';
|
||||
import { unescapeJs } from './utils';
|
||||
|
||||
export type * from './manager';
|
||||
|
||||
export const Components = (() => {
|
||||
const manager = createComponentsManager();
|
||||
|
||||
/** Unescape JS that was escaped in Django side with `escape_js` */
|
||||
const unescapeJs = (escapedJs: string) => {
|
||||
return new DOMParser().parseFromString(escapedJs, 'text/html').documentElement.textContent;
|
||||
};
|
||||
|
||||
return {
|
||||
manager,
|
||||
createComponentsManager,
|
||||
unescapeJs,
|
||||
};
|
||||
})();
|
||||
export const Components = {
|
||||
manager: createComponentsManager(),
|
||||
createComponentsManager,
|
||||
unescapeJs,
|
||||
};
|
||||
|
||||
// In browser, this is accessed as `Components.manager`, etc
|
||||
globalThis.Components = Components;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
/** The actual code of the JS dependency manager */
|
||||
import { callWithAsyncErrorHandling } from './errorHandling';
|
||||
import { observeScriptTag } from './mutationObserver';
|
||||
import { unescapeJs } from './utils';
|
||||
|
||||
type MaybePromise<T> = Promise<T> | T;
|
||||
|
||||
|
|
@ -233,24 +235,35 @@ export const createComponentsManager = () => {
|
|||
toLoadCssTags: string[];
|
||||
toLoadJsTags: string[];
|
||||
}) => {
|
||||
const loadedCssUrls = inputs.loadedCssUrls.map((s) => atob(s));
|
||||
const loadedJsUrls = inputs.loadedJsUrls.map((s) => atob(s));
|
||||
const toLoadCssTags = inputs.toLoadCssTags.map((s) => atob(s));
|
||||
const toLoadJsTags = inputs.toLoadJsTags.map((s) => atob(s));
|
||||
|
||||
// Mark as loaded the CSS that WAS inlined into the HTML.
|
||||
inputs.loadedCssUrls.forEach((s) => markScriptLoaded("css", s));
|
||||
inputs.loadedJsUrls.forEach((s) => markScriptLoaded("js", s));
|
||||
loadedCssUrls.forEach((s) => markScriptLoaded("css", s));
|
||||
loadedJsUrls.forEach((s) => markScriptLoaded("js", s));
|
||||
|
||||
// Load CSS that was not inlined into the HTML
|
||||
// NOTE: We don't need to wait for CSS to load
|
||||
Promise
|
||||
.all(inputs.toLoadCssTags.map((s) => loadCss(s)))
|
||||
.all(toLoadCssTags.map((s) => loadCss(s)))
|
||||
.catch(console.error);
|
||||
|
||||
// Load JS that was not inlined into the HTML
|
||||
const jsScriptsPromise = Promise
|
||||
// NOTE: Interestingly enough, when we insert scripts into the DOM programmatically,
|
||||
// the order of execution is the same as the order of insertion.
|
||||
.all(inputs.toLoadJsTags.map((s) => loadJs(s)))
|
||||
.all(toLoadJsTags.map((s) => loadJs(s)))
|
||||
.catch(console.error);
|
||||
};
|
||||
|
||||
// Initialise the MutationObserver that watches for `<script>` tags with `data-djc` attribute
|
||||
observeScriptTag((script) => {
|
||||
const data = JSON.parse(script.text);
|
||||
_loadComponentScripts(data);
|
||||
});
|
||||
|
||||
return {
|
||||
callComponent,
|
||||
registerComponent,
|
||||
|
|
@ -258,6 +271,5 @@ export const createComponentsManager = () => {
|
|||
loadJs,
|
||||
loadCss,
|
||||
markScriptLoaded,
|
||||
_loadComponentScripts,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
27
src/django_components_js/src/mutationObserver.ts
Normal file
27
src/django_components_js/src/mutationObserver.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/** Set up MutationObserver that watches for `<script>` tags with `data-djc` attribute */
|
||||
export const observeScriptTag = (onScriptTag: (node: HTMLScriptElement) => void) => {
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === "childList") {
|
||||
// Check added nodes
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (
|
||||
node.nodeName === "SCRIPT" &&
|
||||
(node as HTMLElement).hasAttribute("data-djc")
|
||||
) {
|
||||
onScriptTag(node as HTMLScriptElement);
|
||||
}
|
||||
});
|
||||
2;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Observe the entire document
|
||||
observer.observe(document, {
|
||||
childList: true,
|
||||
subtree: true, // To detect nodes added anywhere in the DOM
|
||||
});
|
||||
|
||||
return observer;
|
||||
};
|
||||
|
|
@ -1,10 +1,19 @@
|
|||
// Helper functions taken from @vue/shared
|
||||
/** Unescape JS that was escaped in Django side with `escape_js` */
|
||||
export const unescapeJs = (escapedJs: string) => {
|
||||
const doc = new DOMParser().parseFromString(escapedJs, "text/html")
|
||||
return doc.documentElement.textContent as string;
|
||||
};
|
||||
|
||||
// ////////////////////////////////////////////////////////
|
||||
// Helper functions below were taken from @vue/shared
|
||||
// See https://github.com/vuejs/core/blob/91112520427ff55941a1c759d7d60a0811ff4a61/packages/shared/src/general.ts#L105
|
||||
// ////////////////////////////////////////////////////////
|
||||
|
||||
export const isArray = Array.isArray;
|
||||
export const isFunction = (val: unknown): val is Function => typeof val === 'function';
|
||||
export const isFunction = (val: unknown): val is Function =>
|
||||
typeof val === "function";
|
||||
export const isObject = (val: unknown): val is Record<any, any> => {
|
||||
return val !== null && typeof val === 'object';
|
||||
return val !== null && typeof val === "object";
|
||||
};
|
||||
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue