# JS and CSS rendering Aim of this doc is to share the intuition on how we manage the JS and CSS ("dependencies") associated with components, and how we render them. ## Starting conditions 1. First of all, when we consider a component, it has two kind of dependencies - the "inlined" JS and CSS, and additional linked JS and CSS via `Media.js/css`: ```py from django_components import Component, types class MyTable(Component): # Inlined JS js: types.js = """ console.log(123); """ # Inlined CSS css: types.css = """ .my-table { color: red; } """ # Linked JS / CSS class Media: js = [ "script-one.js", # STATIC file relative to component file "/script-two.js", # URL path "https://example.com/script-three.js", # URL ] css = [ "style-one.css", # STATIC file relative to component file "/style-two.css", # URL path "https://example.com/style-three.css", # URL ] ``` 2. Second thing to keep in mind is that all component's are eventually rendered into a string. And so, if we want to associate extra info with a rendered component, it has to be serialized to a string. This is because a component may be embedded in a Django Template with the `{% component %}` tag, which, when rendered, is turned into a string: ```py template = Template(""" {% load component_tags %}
{% component "my_table" / %}
""") html_str = template.render(Context({})) ``` And for this reason, we take the same approach also when we render a component with `Component.render()` - It returns a string. 3. Thirdly, we also want to add support for JS / CSS variables. That is, that a variable defined on the component would be somehow accessible from within the JS script / CSS style. A simple approach to this would be to modify the inlined JS / CSS directly, and insert them for each component. But if you had extremely large JS / CSS, and e.g. only a single JS / CSS variable that you want to insert, it would be extremely wasteful to copy-paste the JS / CSS for each component instance. So instead, a preferred approach here is to defined and insert the inlined JS / CSS only once, and have some kind of mechanism on how we make correct the JS / CSS variables available only to the correct components. 4. Last important thing is that we want the JS / CSS dependencies to work also with HTML fragments. So normally, e.g. when a user hits URL of a web page, the server renders full HTML document, with ``, ``, ``, and ``. In such case, we know about ALL JS and CSS dependencies at render time, so we can e.g. insert them into `` and `` ourselves. However this renders only the initial state. HTML fragments is a common pattern where interactivity is added to the web page by fetching and replacing bits of HTML on the main HTML document after some user action. In the case of HTML fragments, the HTML is NOT a proper document, but only the HTML that will be inserted somewhere into the DOM. The challenge here is that Django template for the HTML fragment MAY contain components, and these components MAY have inlined or linked JS and CSS. ```py def fragment_view(request): template = Template(""" {% load component_tags %}
{% component "my_table" / %}
""") fragment_str = template.render(Context({})) return HttpResponse(fragment_str, status=200) ``` User may use different libraries to fetch and insert the HTML fragments (e.g. HTMX, AlpineJS, ...). From our perspective, the only thing that we can reliably say is that we expect that the HTML fragment WILL be eventually inserted into the DOM. So to include the corresponding JS and CSS, a simple approach could be to append them to the HTML as ` ``` But this has a number of issues: - The JS scripts would run for each instance of the component. - Bloating of the HTML file, as each inlined JS or CSS would be included fully for each component. - While this sound OK, this could really bloat the HTML files if we used a UI component library for the basic building blocks like buttons, lists, cards, etc. ## Flow So the solution should address all the points above. To achieve that, we manage the JS / CSS dependencies ourselves in the browser. So when a full HTML document is loaded, we keep track of which JS and CSS have been loaded. And when an HTML fragment is inserted, we check which JS / CSS dependencies it has, and load only those that have NOT been loaded yet. This is how we achieve that: 1. When a component is rendered, it inserts an HTML comment containing metadata about the rendered component. So a template like this ```django {% load component_tags %}
{% component "my_table" / %}
{% component "button" %} Click me! {% endcomponent %} ``` May actually render: ```html
...
``` Each `` comment includes comma-separated data - a unique hash for the component class, e.g. `my_table_10bc2c`, and the component ID, e.g. `c020ad`. This way, we or the user can freely pass the rendered around or transform it, treating it as a string to add / remove / replace bits. As long as the `` comments remain in the rendered string, we will be able to deduce which JS and CSS dependencies the component needs. 2. Post-process the rendered HTML, extracting the `` comments, and instead inserting the corresponding JS and CSS dependencies. If we dealt only with JS, then we could get away with processing the `` comments on the client (browser). However, the CSS needs to be processed still on the server, so the browser receives CSS styles already inserted as `