What's Changed
Modify the JS and CSS tags generated from components, JS code in Component.js no longer pollutes global scope, and using deps_strategy="ignore" inside get_template_data() is now optional.
Feat
-
JS from
Component.jsis now scoped by defaultUntil now, if you assigned a variable or declared a function in your
Component.jscode, it would be available in the global scope. This could cause conflicts with other scripts on the page.Now, the JS code is scoped, so you can't accidentally assign global variables or functions.
To define global variables or functions, you should instead use the
globalThiskeyword:globalThis.myGlobalVariable = "Hello, world!"; globalThis.myGlobalFunction = () => { console.log("Hello, world!"); };
If you want to keep the original behaviour, set
Script.wrap = False.Function wrapping does NOT apply to JS modules (
type="module"). -
Component.on_dependencieshook to override JS/CSS renderingYou can override
Component.on_dependencies(a classmethod) to modify the JS/CSS dependencies emitted by that component only - for example to inject a CSP nonce, change attributes, or wrap inline JS.The hook receives lists of
ScriptandStyleobjects for this component (fromComponent.js/Component.css, JS/CSS variables, and Media). Return(new_scripts, new_styles)to replace them, orNoneto leave them unchanged.To modify all dependencies for the whole page, use the extension hook instead.
Example:
from django_components import Component, Script, Style class MyButton(Component): @classmethod def on_dependencies(cls, scripts, styles): # Add a nonce to every inline style for this component for style in styles: if style.content and "nonce" not in style.attrs: style.attrs["nonce"] = get_current_nonce() return (scripts, styles)
-
ComponentExtension.on_dependencieshook to override JS/CSS renderingSay you want to add a CSP nonce to all scripts, or render scripts as
type="module".Before, you had to subclass a
Mediaclass to intercept how JS and CSS scripts are rendered.
But this did not capture ALL JS and CSS scripts that are rendered.Now, there is a new
on_dependenciesextension hook that you can use to modify JS and CSS scripts before they are rendered.This hook exposes JS/CSS scripts as
ScriptandStyleobjects, so you can modify them before they are rendered.You can add or remove attributes, add or remove entire scripts, and more. See Modifying JS / CSS scripts for more details.
from django_components import ComponentExtension, OnDependenciesContext, Script, Style class MyExtension(ComponentExtension): def on_dependencies(self, ctx: OnDependenciesContext): scripts = list(ctx.scripts) styles = list(ctx.styles) # Set nonce attribute on all JS scripts for script in scripts: script.attrs["nonce"] = "1234567890" return (scripts, styles)
-
Dependency,Script, andStylehelper classesInstead of modifying the JS and CSS scripts as raw strings like
<script>and<style>,
the JS and CSS dependencies are represented by helper objects: -
Use
ScriptandStyleobjects inComponent.Media.js/Component.Media.cssYou can now put
ScriptandStyleobjects directly inComponent.Media.jsandComponent.Media.css.from django_components import Component, Script, Style, register @register("calendar") class Calendar(Component): class Media: js = [ Script(content="console.log('inline');") ] css = [ Style(content=".x { color: red; }") ]
Fix
- Fix race condition where
ComponentMedia._templateremainsUNSET. See #1588
Refactor
-
Component.Media.js/cssnow render BEFOREComponent.js/css, instead of after. -
Rendering components in Python is now simpler: No explicit
deps_strategyneeded when nestedWhen you pre-render a component in Python, and pass it into another component's
get_template_data(),
you should passdeps_strategy="ignore"to the render function to avoid rendering the dependencies twice.django-components now makes this easier for you.
When you call
Component.render()from Python inside another component (e.g. inget_template_data()),
you no longer need to passdeps_strategy="ignore"to the inner Component. This is set automatically be default.Top-level renders still default to
"document".See issue #1463.
Before:
class Outer(Component): def get_template_data(self, args, kwargs, slots, context): content = Inner.render(deps_strategy="ignore") return {"content": content}
After:
class Outer(Component): def get_template_data(self, args, kwargs, slots, context): content = Inner.render() # no deps_strategy needed! return {"content": content}
Full Changelog: 0.147.0...0.148.0