API
Utils
- alliance_platform.frontend.util.transform_attribute_names(attrs)[source]
Transform HTML attributes to use proper cased names for passing to JSX
Example:
>>> transform_attribute_names({ "class": "item" }) { "className": "item" }
- Parameters:
attrs (dict[str, Any])
- Return type:
dict[str, Any]
Bundler
- class alliance_platform.frontend.bundler.frontend_resource.FrontendResource(path)
Represents a resource to be bundled by the bundler
Previously, we just tracked files that a bundler needed to care about but this limits what you can do. For example, you have to bundle everything at that path, even if you use one export in the case with ES Modules. By storing the specifics about the resource, the bundler can then optimise this and - in the case of ES Modules - only bundle the resources actually used (e.g. perform tree shaking). It also means we can more easily detect what resource we are dealing with without relying on only inferring it from the filename.
- Parameters:
path (Path)
- property content_type: str | None
Get the content type string for this resource
- classmethod from_path(path)
Given a path, return the most appropriate resource for it
- Parameters:
path (Path | str)
- is_substitutable_for(other)
Check if this resource can be used in place of another resource.
- Parameters:
other (FrontendResource)
- Return type:
bool
- path: Path
The path to the resource
- serialize()
Serialize the resource for use by the bundler.
Returns a dict with, at minimum, the following:
type- a string identifying the resource type. Currently, this will be one of “javascript”, “css”, “esmodule”, “image”.path- path to the file for the resource
Then any additional specific to the resource (e.g. the import details for esmodule usage)
This is used by
extract_frontend_resourcesto export the resource for use in the bundler.
- class alliance_platform.frontend.bundler.frontend_resource.JavascriptResource(path)
A plain javascript resource.
See also
ESModuleResource- Parameters:
path (Path)
- class alliance_platform.frontend.bundler.frontend_resource.CssResource(path)
A CSS resource
- Parameters:
path (Path)
- class alliance_platform.frontend.bundler.frontend_resource.ESModuleResource(path, import_name, is_default_import)
An ESModule resource
This allows the bundler to perform tree shaking rather than bundling the whole file and its dependencies.
- Parameters:
path (Path)
import_name (str)
is_default_import (bool)
- class alliance_platform.frontend.bundler.frontend_resource.VanillaExtractResource(path)
A Vanilla Extract CSS resource
This will be a file with extension .css.ts
- Parameters:
path (Path)
- class alliance_platform.frontend.bundler.frontend_resource.ImageResource(path)
An image resource
- Parameters:
path (Path)
- alliance_platform.frontend.bundler.get_bundler()[source]
Get the current bundler instance
This comes from the
BUNDLERsetting- Return type:
- class alliance_platform.frontend.bundler.base.BaseBundler(root_dir, path_resolvers)
- Parameters:
root_dir (Path)
path_resolvers (list[PathResolver])
- check_dev_server()
Check if the dev server is running.
Should return
DevServerCheckwhich should indicate whether the server is running and if so, what the project dir for that server is. This allows detection of when a server is running, but it’s for a different project.- Return type:
- does_asset_exist(filename)
Check if asset exists. By default, looks to filesystem.
Implementations may not rely on the filesystem, e.g. once a project is built and deployed the source may not exist and instead the built files are stored. In Vite, this would be checked by seeing if the asset exists in the manifest.
- Parameters:
filename (Path)
- format_code(code)
Form code for output. Typically, this would only occur in development for performance reasons.
This exists on the bundler so that we can leverage existing dev server to avoid having to shell out to a prettier process each time.
- Parameters:
code (str)
- get_embed_items(resources, resource_type=None)
Generate the necessary
AssetFileEmbedinstances for the specified resource(s).For example, a javascript file for a component could return an AssetFileEmbed instance for its javascript content plus one for it’s CSS content.
- Parameters:
resources (FrontendResource | Iterable[FrontendResource]) – The resource(s) to embed. If you need to embed multiple assets it’s best to do them all together so that any necessary de-duplication can occur.
resource_type (str | type[FrontendResource] | None) – If set only assets of that type will be embedded, otherwise all asset will be. The two common content types are
JavascriptResource. but others like image/png are also possible.
- Returns:
The list of
AssetFileEmbedinstances that will be embedded.- Return type:
list[AssetFileEmbed]
- get_preamble_html()
Return preamble that is included in the HTML head
Defaults to nothing. This can be used for things like setting up hot module reloading.
Is used by the
bundler_preamble()tag.- Return type:
str
- get_ssr_cancel_url()
Return URL for canceling an in-flight SSR request.
Implementations may return
Noneif cancellation is not supported.- Return type:
str | None
- get_ssr_headers()
Return any headers to add to the SSR request
- Return type:
dict[str, str]
- get_ssr_url()
Return the URL to use to perform server side rendering
BundlerAssetContextMiddlewarewillPOSTto this URL in development to generate the HTML for any queued SSR items.Note that production SSR generation works with built files and so the bundler itself isn’t responsible for handling requests, but it may provide a URL that we use (e.g. in PREVIEW mode with Vite).
See Server Side Rendering (SSR) for details on how SSR works.
- get_url(path)
Return the URL to load the specified asset at
path- Parameters:
path (Path | str)
- is_development()
Should return true if running in development mode
- Return type:
bool
- is_ssr_enabled()
Should return true if server side rendering is enabled and supported by this bundler
- Return type:
bool
- path_resolvers: list[PathResolver]
A list of
PathResolverinstances used to resolve paths
- resolve_path(path, context, suffix_whitelist=None, suffix_hint=None, resolve_extensions=None)
Resolve a string to a
Pathbased on specifiedpath_resolversThis allows things like relative paths or resolving relative to a custom directory.
Each resolver in
path_resolversis called in turn until one returns aPathat which point it stops.- Parameters:
path (str | Path) – The path to resolve
context (ResolveContext) – The context
suffix_whitelist (list[str] | None) – (optional) Whitelist of suffixes to accept. Each suffix should include the leading ‘.’. If not specified then all suffixes are supported.
suffix_hint (str | None) – (optional) Hint to display as part of error message if suffix not valid
resolve_extensions (list[str] | None) – (optional) List of extensions to try if the path as specified doesn’t exist.
- Returns:
The resolved absolute
Path- Return type:
Path
- resolve_ssr_import_path(path)
Resolve a path for use in an ES import when server side rendering
This is necessary as in dev importing from the file directly will work (e.g.
import { Button } from 'components/Button') but in production it needs to be the built file (e.g.import { Button } from '/assets/Button-abc123.js').Note that this only applies for code that will be embedded directly in script tags - code that is bundled by the bundler will resolve this imports itself. :param path: The path to transform into a value that can be used in an ES import
- Returns:
A string that can be used in an ES import
- Parameters:
path (Path | str)
- Return type:
Path | str
- root_dir: Path
The root path everything sits under; all other paths are resolved relative to this
- should_check_case_sensitive()
Should return true if the bundler should check for case-sensitive filesystems
Reads from the
BUNDLER_CHECK_CASE_SENSITIVEsetting. Disabled by default as it is slow. Recommended to enable in CI.- Return type:
bool
- validate_path(filename, suffix_whitelist=None, suffix_hint=None, resolve_extensions=None)
Validate a path exists, and optional has a suffix in the whitelist
If
resolve_extensionsare specified the path will be checked with each extension in turn until one matches. If filename doesn’t exist an error is raised.If
suffix_whitelistis specified and the filename doesn’t have a suffix in the whitelist an error is raised.- Parameters:
suffix_whitelist (list[str] | None) – (optional) Whitelist of suffixes to accept. Each suffix should include the leading ‘.’. If not specified then all suffixes are supported.
suffix_hint (str | None) – (optional) Hint to display as part of error message if suffix not valid
resolve_extensions (list[str] | None) – (optional) List of extensions to try if the path as specified doesn’t exist.
filename (str | Path)
- Returns:
The resolved absolute
Pathwith any missing extensions added.- Return type:
Path
- class alliance_platform.frontend.bundler.base.AssetFileEmbed(resource, html_attrs=None)
Represents a file that should be embedded in the HTML
Each
AssetFileEmbedis responsible for generating the HTML to embed the file. This can contain logic for doing things differently in dev vs production, or for different targets (e.g. browser vs PDF).These are created by the bundler when
get_embed_items()is called.If extra HTML attributes are required for the tag, they can be set on the
html_attrsdict.- Parameters:
resource (FrontendResource)
html_attrs (dict[str, str])
- can_embed_head()
Should return
Trueif this file can be embedded in HTML head rather than inline where usedThis is useful to load CSS before HTML renders to avoid un-styled content, and for javascript to start loading before it’s used for performance
Things that display inline cannot do this (e.g. images)
- generate_code(html_target)
Generate the HTML code necessary to embed this file
- Parameters:
html_target (HtmlGenerationTarget)
- get_content_type()
Return the content type of the file to embed
The most common types are text/css and text/javascript. Images will be image/<type>, e.g. image/png
- Return type:
str | None
- get_dependencies()
Return any additional dependencies for this file. For example a JS file may have some CSS associated with it.
- Return type:
list[AssetFileEmbed]
- html_attrs: dict[str, str]
Any HTML attributes to include in the tag
- matches_content_type(content_type)
Return
Trueif this file matches the given content typeIf
content_typeisNonethen it matches anything.- Parameters:
content_type (type[FrontendResource] | str | Pattern | None)
- class alliance_platform.frontend.bundler.base.ResolveContext(root_dir, source_path=None)
Passed to
resolve()to project context for resolving paths- Parameters:
root_dir (Path)
source_path (str | Path | None)
- root_dir: Path
The root dir for the project. Paths should sit under this.
- source_path: str | Path | None = None
The source path for the ‘thing’ path is being resolved for (e.g. a template file). May not be available.
- class alliance_platform.frontend.bundler.base.PathResolver
A class that resolves a path string to a
PathThis can be used to do things like resolve paths relative to a root directory, use path aliases etc.
- resolve(path, context)
Resolve a string
pathto aPath.Should return
Noneif this class was unable to resolve the path, in which case resolving will move onto the next resolver in order.- Parameters:
path (str)
context (ResolveContext)
- Return type:
Path | None
- class alliance_platform.frontend.bundler.base.RegExAliasResolver(find, replace)
Resolves a path by replacing a substring using a regex
- Parameters:
find (str)
replace (str)
- find: str
The regular expression to match
- replace: str
The string to replace the match with
- resolve(path, context)
Resolve a string
pathto aPath.Should return
Noneif this class was unable to resolve the path, in which case resolving will move onto the next resolver in order.- Parameters:
path (str)
context (ResolveContext)
- Return type:
Path | None
- class alliance_platform.frontend.bundler.base.SourceDirResolver(base_dir)
Resolves any non-absolute path relative to
base_dir- Parameters:
base_dir (Path)
- base_dir: Path
The directory to resolve paths relative to
- resolve(path, context)
Resolve a string
pathto aPath.Should return
Noneif this class was unable to resolve the path, in which case resolving will move onto the next resolver in order.- Parameters:
path (str)
context (ResolveContext)
- class alliance_platform.frontend.bundler.base.RelativePathResolver
Resolves strings that begin with
./or../as relative tocontext.source_path- resolve(path, context)
Resolve a string
pathto aPath.Should return
Noneif this class was unable to resolve the path, in which case resolving will move onto the next resolver in order.- Parameters:
path (str)
context (ResolveContext)
- class alliance_platform.frontend.bundler.resource_registry.FrontendResourceRegistry
Stores any extra resources in addition to those found in templates that should be included in the frontend build
This should be populated on startup such that the resources are available when
extract_frontend_resourcesruns.The registry to use is specified by the
FRONTEND_RESOURCE_REGISTRYsetting.These resources are only used when building the frontend in dev. In production, the underlying files do not need to exist - for example if your deployment process excludes the source files.
Warning
In most cases you do not need this.
This is necessary only for resources that can’t be auto-discovered - for example in a dynamic template. If you try and use a resource that cannot be auto-discovered an error will be raised.
Usage:
frontend_resource_registry = FrontendResourceRegistry() frontend_resource_registry.add_resource( // Adding a path will be converted to a resource for you based on the extension settings.PROJECT_DIR / "frontend/src/file1.tsx", // Pass resource directly to have full control ESModuleResource(settings.PROJECT_DIR / "frontend/src/file2.tsx", "MyComponent", True), )
- add_resource(*resources)
Add resource to be included in frontend build
- Parameters:
resources (FrontendResource | Path)
- get_resources_for_bundling()
Get all resources to include in build
- Return type:
set[FrontendResource]
- get_unknown(*resources)
From the specified resource(s) return any that aren’t in the registry
- Parameters:
resources (FrontendResource)
- Return type:
list[FrontendResource]
- alliance_platform.frontend.bundler.asset_registry.FrontendAssetRegistry
alias of
FrontendResourceRegistry
Warning
Deprecated. Use FrontendResourceRegistry directly.
- class alliance_platform.frontend.bundler.base.DevServerCheck(is_running, read_timeout=False, project_dir=None)
Describes status of dev server
- Parameters:
is_running (bool)
read_timeout (bool)
project_dir (Path | None)
- is_ok()
Returns
Trueif server is good to use for this project- Return type:
bool
- is_running: bool
True if dev server is running at the expected location (depends on the bundler)
- is_wrong_server()
Returns
Trueif server is running, but it’s for a different project- Return type:
bool
- project_dir: Path | None = None
The project dir the frontend dev server is running at. This is used to determine if it’s for the same project as Django.
- read_timeout: bool = False
Read timeout
- class alliance_platform.frontend.bundler.context.BundlerAssetContext(*, frontend_resource_registry=None, frontend_asset_registry=None, html_target=HtmlGenerationTarget(label='browser', include_scripts=True, inline_css=False), skip_checks=False, request=None)
Class that acts as container for global context data for React apps and a context manager
When called in a django request and
BundlerAssetContextMiddlewareis used a context always available fromget_current().Usage:
with BundlerAssetContext() as context: # Any templates that contain nodes that extend BundlerAsset will end up in context contents = render_template() # Post process contents to handle SSR & embedding the asset tags contents = context.post_process(contents) # Retrieve the resources for all assets that were added to the context context.get_resources_for_bundling()
Usage in a django view when
BundlerAssetContextMiddlewareis active:context = BundlerAssetContext.get_current() context.get_resources_for_bundling()
- Parameters:
frontend_resource_registry (FrontendResourceRegistry)
frontend_asset_registry (FrontendResourceRegistry | None)
html_target (HtmlGenerationTarget)
skip_checks (bool)
request (HttpRequest | None)
- abort_post_process()
Called when
post_processcan’t be called (e.g. on a request where content-type isn’t text/html)
- add_asset(asset)
Add an asset to the current context
- Parameters:
asset (BundlerAsset)
- assets: list[BundlerAsset]
All the assets that have been added
- embed_item_queue: AssetEmbedFileQueue
All assets that have been queued for embedding in the final HTML
- frontend_resource_registry: FrontendResourceRegistry
The registry to use for resources that can’t be discovered automatically.
- generate_id()
Generate an ID that is guaranteed to be unique for this context
This can be used for generating ID’s to use in HTML, e.g. for container rendering React components into
- Return type:
str
- get_assets(of_type=None)
Get assets that have been added to context, optionally filtered by
of_type- Parameters:
of_type (Type[BundlerAsset] | None) – If provided only assets of this type will be returned
- Return type:
list[BundlerAsset]
- classmethod get_current()
Get the current BundlerAssetContext or raise
NoActiveBundlerAssetContextif noneWhen called in a django request and
BundlerAssetContextMiddlewareis used one will always be available.- Return type:
- get_resources_for_bundling(of_type=None)
Return the paths for assets used by assets added to this context
- Parameters:
of_type (Type[BundlerAsset] | None)
- Return type:
list[FrontendResource]
- html_target: HtmlGenerationTarget
The target HTML is being generated for. This determines things like whether scripts are required at runtime, and whether CSS should be inlined in
styletags or linked from an external file. Defaults tohtml_target_browser.
- post_process(content)
Given the HTML content, post process it to embed assets and render server side rendered components
This is typically called by
BundlerAssetContextMiddleware, but can be called directly if rendering templates of other purposes such as emails of PDFs.contentshould be the full HTML content of the page, including the<head>and<body>tags. This function will handle embedding any necessary assets based on calls toqueue_embed_file()as well as any server side rendered components queued withqueue_ssr().This should be the last step before the HTML is outputted to the client.
- Parameters:
content (str)
- queue_embed_file(item)
Queues an asset to be embedded in the final HTML
The actual embedding and insertion into the HTML happens in
post_process(), which is called byBundlerAssetContextMiddlewarewhich relies onbundler_embed_collected_assets()being used in the template. Typically, this tag would exist in any base templates that are used for all pages.itemis responsible for generating the actual HTML to embed which typically depends on the bundler.- Parameters:
item (AssetFileEmbed)
- queue_ssr(item)
Queue an item for server side rendering
Note that if the current bundler does not support SSR, this will have no effect. You can safely call it still and output the placeholder HTML comment, it just will not be replaced with anything.
- Parameters:
item (SSRItem) – The item to render
- Returns:
A string which is the placeholder that should be outputted in the HTML. It will be replaced by
BundlerAssetContextMiddleware.- Return type:
str
- register_embed_collected_assets_tag()
For use by
bundler_embed_collected_assetsonlyThis is used by the template tag to register that it has been used, and to get the placeholder string to write in the initial HTML that will then be replaced in
post_process.- Return type:
str
- request: HttpRequest | None
Will be set if used within
BundlerAssetContextMiddleware. In other contexts may be unavailable
- requires_post_processing()
Check if
post_processneeds to be called
- skip_checks: bool
If specified, no checks will be done on context exit to ensure all assets have been embedded or post processed. Useful for tests.
- ssr_queue: dict[str, SSRItem]
Contains all SSRItem’s that have been queued for rendering
- class alliance_platform.frontend.bundler.middleware.BundlerAssetContextMiddleware(get_response)
Middleware that wraps requests in a
BundlerAssetContextmanagerWithin a Django request you can call
get_current()and it will return context instance for the current request.BundlerAssetContextdoes this on init.This is currently only used to facilitate server side rendering by collecting all the items that need to be rendered. This works as follows:
SSRItemcan be queued for server side rendering usingqueue_ssr(). The caller (e.g. a template node) must render a placeholder element in the page, e.g.<!-- ___SSR_PLACEHOLDER_0___ -->- this placeholder string is returned byqueue_ssrThe middleware processes the request like normal so all the HTML is generated, including all the necessary placeholders.
It then does the SSR for all requested items.
For each item, it will replace the placeholder in the HTML with the rendered version.
Vite Bundler
Example setup:
import json
import logging
from pathlib import Path
from textwrap import dedent
import time
from django.conf import settings
from alliance_platform.frontend.bundler.base import PathResolver
from alliance_platform.frontend.bundler.base import RegExAliasResolver
from alliance_platform.frontend.bundler.base import RelativePathResolver
from alliance_platform.frontend.bundler.base import ResolveContext
from alliance_platform.frontend.bundler.base import SourceDirResolver
from alliance_platform.frontend.bundler.vite import ViteBundler
from django_site.settings.base import get_env_setting
root_dir = settings.PROJECT_DIR
bundler_mode = settings.VITE_BUNDLER_MODE
# This assumes the Vite server writes these files to the cache directory for you
server_details_path = ap_core_settings.CACHE_DIR / "vite-server-address.json"
server_state_path = ap_core_settings.CACHE_DIR / "vite-server-state.json"
server_details = {}
if server_details_path.exists():
server_details = json.loads(server_details_path.read_text())
if bundler_mode == "development" and server_details:
# Allow switching to 'preview' mode in dev
bundler_mode = server_details["serverType"]
# An example of a custom resolver that will resolve paths starting with @alliancesoftware/ui or @alliancesoftware/icons to
# the node modules directory
class AlliancePlatformPackageResolver(PathResolver):
"""Resolves strings that begin with ``./`` or ``../`` as relative to ``context.source_path``"""
def resolve(self, path: str, context: ResolveContext):
if path.startswith("@alliancesoftware/ui") or path.startswith("@alliancesoftware/icons"):
return ap_frontend_settings.NODE_MODULES_DIR / path
return None
def wait_for_server():
if server_state_path.exists():
server_details = json.loads(server_state_path.read_text())
if server_details.get("status") == "starting":
logger.warning(
"Vite server is starting... web requests will wait until this resolves before loading"
)
warning_logged = False
start = time.time()
while server_details.get("status") == "starting":
time.sleep(0.1)
server_details = json.loads(server_state_path.read_text())
if not warning_logged and (time.time() - start) > 10:
logger.warning(
"Vite appears to be taking a while to build. Check your `yarn dev` or `yarn preview` command is running and has not crashed"
)
warning_logged = True
vite_bundler = ViteBundler(
root_dir=root_dir,
path_resolvers=[
AlliancePlatformPackageResolver(),
RelativePathResolver(),
RegExAliasResolver("^/", str(settings.PROJECT_DIR) + "/"),
# Resolve relative paths from this directory
SourceDirResolver(root_dir / "frontend/src"),
],
mode=bundler_mode,
server_build_dir=settings.PROJECT_DIR / "frontend/server-build",
build_dir=settings.PROJECT_DIR / "frontend/build",
server_host=server_details.get("host", "localhost"),
server_port=server_details.get("port", "5173"),
server_protocol=server_details.get("protocol", "http"),
production_ssr_url=settings.FRONTEND_PRODUCTION_SSR_URL,
wait_for_server=wait_for_server,
)
- class alliance_platform.frontend.bundler.vite.ViteBundler(*, root_dir, path_resolvers, build_dir, server_host, server_port, server_protocol, server_resolve_package_url, mode, wait_for_server=None, disable_ssr=False, server_build_dir=None, production_ssr_url=None, static_url_prefix=None)
Bundler implementation for Vite.
- Parameters:
root_dir (Path) – The root path everything sits under; all other paths are resolved relative to this
path_resolvers (list[PathResolver]) – A list of
PathResolverinstances used to resolve pathsbuild_dir (Path) – The directory client side files are outputted to (see
yarn build:client)server_host (str) – The hostname used for the dev server (e.g.
127.0.0.1)server_port (str) – The port used for the dev server (e.g.
5173)server_protocol (str) – The protocol used for the dev server (
httporhttps)server_resolve_package_url (str) –
The URL, handled by the dev server, to use to resolve package requests in dev mode. This is only used for packages in /node_modules/ so that we can use the optimized versions of packages that Vite generates. Without this, you encounter errors like “Outdated Optimized Deps”. The dev server should handle requests to this URL and redirect to the final package file. Example implementation for fastify, where
server_resolve_package_url="redirect-package-url".server.get('/redirect-package-url/*', async (req, res) => { const packagePath = (req.params as Record<string, string>)['*']; const [, resolvedId] = await vite.moduleGraph.resolveUrl(packagePath); // `resolveId` is an absolute path with query string. Resolve it relative to the project root const relativePath = normalizePath(path.relative(projectDir, resolvedId)); // Convert the filesystem path to a web-accessible path const url = path.join(vite.config.base, relativePath); res.redirect(302, url); });
mode (str) – The mode the bundler is running in; one of
development,productionorpreviewwait_for_server (Callable[[], None] | None) – Function that can be passed that will be called before requesting assets for server. This function should handle waiting until the dev server is ready before returning (e.g. by polling the server status)
disable_ssr (bool) – Set to
Trueto disable SSR entirely. Defaults toFalse.server_build_dir (Path | None) – The directory SSR files are outputted to (see
yarn build:ssr). Required unlessdisable_ssrisTrue.production_ssr_url (str | None) – URL for SSR (only used in production mode). Required unless
disable_ssrisTrue.static_url_prefix (str | None) – Static prefix for assets in production. This should match the prefix specified (if any) in the
STATICFILES_DIRSentry for the frontend build. For example, if the frontend build is outputted to/frontend/buildandSTATICFILES_DIRSis set to(('frontend-dist', '/frontend/build'),), then you should passstatic_url_prefix="frontend-dist".
- build_dir: Path
Directory client side files are compiled to
- build_manifest: ViteManifest
Manifest for client build (only available when in production mode)
- check_dev_server()
Check if the dev server is running.
Should return
DevServerCheckwhich should indicate whether the server is running and if so, what the project dir for that server is. This allows detection of when a server is running, but it’s for a different project.
- disable_ssr: bool = False
Can be set to disable SSR entirely
- does_asset_exist(filename)
In production node_modules might not exist - instead check the manifest file
- Parameters:
filename (Path)
- format_code(code)
In dev format code using /format-code endpoint defined in dev-server.ts
In production this is a no-op for performance reasons.
- Parameters:
code (str)
- get_development_ssr_url()
Returns the URL for SSR rendering used in dev
This assumes the Vite dev server has a ‘ssr/’ endpoint to handle requests.
- get_embed_items(resources, resource_type=None)
Generate the necessary
AssetFileEmbedinstances for the specified asset(s)paths.For example, a javascript file for a component could return an AssetFileEmbed instance for it’s javascript content, and one for it’s CSS content.
Note that in development passing a ‘.css’ file in
pathswill embed as a javascript file as that’s how Vite works with HMR. SeeViteEmbedCssfor where this is handled.Note that if specific items require extra attributes on the tag (e.g.
alt="my alt tag") then this can be attached toitem.html_attrs.TODO: Doing preload need to do testing in browser to work out best approach. In particular script modules - does rel=”preload” work or do you have to use rel=”modulepreload” (which has poor support)?
- Parameters:
resources (FrontendResource | Iterable[FrontendResource]) – The resource(s) to embed. If you need to embed multiple assets it’s best to do them all together so that any necessary de-duplication can occur.
resource_type (type[FrontendResource] | str | Pattern | None) – If set only assets of that type will be embedded, otherwise all asset will be. The two common content types are
JavascriptResourceandCssResource, but others likeImageResourceare also possible.
- Returns:
The list of
AssetFileEmbedinstances that will be embedded.
- get_preamble_html()
In development returns HMR client setup for Vite
NOTE: Does not include react-refresh, use
{% react_refresh_preamble %}for that.
- get_ssr_cancel_url()
Return URL for canceling an in-flight SSR request.
Implementations may return
Noneif cancellation is not supported.
- get_ssr_headers()
In development add an X-SSR-ROOT-DIR header to the request to the dev server.
This lets the dev server check the request came from the same project to avoid trying to SSR requests from a different project which results in confusing errors.
- get_ssr_url()
Returns the URL for SSR rendering
In dev, this uses
get_development_ssr_url().In production
production_ssr_urlis used.
- get_url(path)
Get the URL to load asset as
pathIn development this uses the dev server, in production is served from static files.
- Parameters:
path (Path | str)
- is_development()
Should return true if running in development mode
- is_ssr_enabled()
Should return true if server side rendering is enabled and supported by this bundler
- Return type:
bool
- mode: str
The mode bundler is running in; either ‘development’, ‘production’ or ‘preview’
- production_ssr_url: str | None
URL for SSR (only used in production mode)
- resolve_ssr_import_path(path)
-
- Parameters:
path (Path | str)
- Return type:
Path | str
- resolve_url(path)
Resolve a URL to use to serve the specified
pathIn development this will be served from the dev server and in production from
STATIC_URL- Parameters:
path (Path | str)
- server_build_dir: Path | None
Directory server side rendering (SSR) files are compiled to. May be
Noneifdisable_ssrisTrue.
- server_build_manifest: ViteManifest
Manifest for SSR (only available when in production mode)
- static_url_prefix: str | None
Static prefix for assets in production. This should match the prefix specified (if any) in the
STATICFILES_DIRSentry for the frontend build.
- vite_metadata_path: Path
Path to the vite metdata JSON file that we use in dev to resolve optimised deps
- wait_for_server: Callable[[], None] | None = None
Function that can be passed that will be called before requesting assets for server.
- class alliance_platform.frontend.bundler.vite.ViteEmbed(bundler, resource, html_attrs=None)
A embed item for Vite assets
- Parameters:
bundler (ViteBundler)
resource (FrontendResource)
html_attrs (dict[str, str])
- bundler: ViteBundler
The bundler instance to use to resolve asset paths
- get_content_type()
Return the content type of the file to embed
The most common types are text/css and text/javascript. Images will be image/<type>, e.g. image/png
- Return type:
str | None
- resource: FrontendResource
The resource to embed. This will be used to resolve the final URL to embed (e.g. bundled files in production, dev server URLs in dev)
- class alliance_platform.frontend.bundler.vite.ViteJavaScriptEmbed(bundler, resource, html_attrs=None)
A JavaScript embed
In development this loads from the dev server, in production it loads from the bundled file.
- Parameters:
bundler (ViteBundler)
resource (FrontendResource)
html_attrs (dict[str, str])
- generate_code(html_target)
Generate the HTML code necessary to embed this file
- Parameters:
html_target (HtmlGenerationTarget)
- get_dependencies()
Return any additional dependencies for this file. For example a JS file may have some CSS associated with it.
- Return type:
list[AssetFileEmbed]
- class alliance_platform.frontend.bundler.vite.ViteImageEmbed(bundler, resource, html_attrs=None)
An image embed
This embeds as a
imgtag. In development this loads the image from the dev server, in production it loads from the bundled file. This is useful for cache busting (the bundled file just gets a hash in its name) or for using an image pipeline (see https://kanban.alliancesoftware.com.au/board/75/card/133647/ for possibility of this)- Parameters:
bundler (ViteBundler)
resource (FrontendResource)
html_attrs (dict[str, str])
- can_embed_head()
Should return
Trueif this file can be embedded in HTML head rather than inline where usedThis is useful to load CSS before HTML renders to avoid un-styled content, and for javascript to start loading before it’s used for performance
Things that display inline cannot do this (e.g. images)
- generate_code(html_target)
Generate the HTML code necessary to embed this file
- Parameters:
html_target (HtmlGenerationTarget)
- class alliance_platform.frontend.bundler.vite.ViteManifest(root_dir, manifest_file)
Contains entries from a Vite manifest file
Vite generates a manifest.json file as part of build based on the
build.manifestandbuild.ssrManifestoptions. This files contains a mapping from each built file to the corresponding outputs (e.g. the built javascript file and css files).- Parameters:
root_dir (Path)
manifest_file (Path)
- __init__(root_dir, manifest_file)
- Parameters:
root_dir (Path) – The root that any absolute paths will be resolved relative to
manifest_file (Path) – The path to the manifest file. This will be read as JSON and used to populate
entries
- entries: dict[Path, ViteManifestAsset]
The entries from the file indexed by the source path
- get_asset(path)
Given
pathreturn the matchingViteManifestAssetor raiseViteManifestAssetMissingErrorif none found- Parameters:
path (Path | str)
- manifest_file: Path
The file path entries were read from
- root_dir: Path
The root that any absolute paths will be resolved relative to
- class alliance_platform.frontend.bundler.vite.ViteManifestAsset(manifest, file, is_entry, is_dynamic_entry, src, css, assets, imports, dynamic_imports, vanilla_extract_mapping_file)
See https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/manifest.ts for what this looks like
- Parameters:
manifest (ViteManifest)
file (str)
is_entry (bool)
is_dynamic_entry (bool)
src (str | None)
css (tuple[str])
assets (tuple[str])
imports (tuple[str])
dynamic_imports (tuple[str])
vanilla_extract_mapping_file (str | None)
- assets: tuple[str]
Can exist in the manifest file but currently not used by this
- collect_dependencies()
Collect all dependencies for this asset, including itself.
This is useful to resolve all the files that are needed for a particular asset.
See
AssetDependenciesfor more details- Return type:
- css: tuple[str]
Any CSS file dependencies
- dynamic_imports: tuple[str]
Any other assets this asset imports dynamically (i.e. using
import()
- file: str
The built file (e.g. a js or css file)
- get_content_type()
Get the content type for this asset
For css & vanilla extract files this will be text/css, for typescript/js it will be text/javascript. For other files it will be guessed from the file extension (e.g. image/svg+xml for svg files or image/png for .png files).
- imports: tuple[str]
Any other assets this asset imports
- is_dynamic_entry: bool
Whether this is used as a dynamic entry point (i.e. imported using
import()
- is_entry: bool
Whether this is an entry point. If
Falsethen the file hasn’t been imported directly but is a dependency of another entry point.
- manifest: ViteManifest
The manifest this asset is referenced from
- src: str | None
The source file this was built from. For common chunks this won’t be set.
- vanilla_extract_mapping_file: str | None
File that contains class mappings for a vanilla extract file
- class alliance_platform.frontend.bundler.vite.AssetDependencies(dependencies, dynamic_dependencies)
- Parameters:
dependencies (list[ViteManifestAsset])
dynamic_dependencies (list[ViteManifestAsset])
- dependencies: list[ViteManifestAsset]
Any dependencies not loaded dynamically. This includes any dependencies of dependencies.
- dynamic_dependencies: list[ViteManifestAsset]
Any dependencies loaded dynamically (i.e. by a
import("asset")call). Includes any non-dynamic dependencies of the dependencies so that all requires assets can be preloaded if desired.
- get_css_dependencies()
Returns list of css file names used
- Return type:
list[str]
- get_dynamic_css_dependencies()
Returns list of css file names used by any dynamic imports
- Return type:
list[str]
- get_dynamic_js_dependencies()
Returns list of javascript file names used by any dynamic imports
- Return type:
list[str]
- get_js_dependencies()
Returns list of javascript file names used
- Return type:
list[str]
- merge(deps)
Merge two
AssetDependenciestogether- Parameters:
deps (AssetDependencies)
SSR
- class alliance_platform.frontend.bundler.ssr.SSRSerializable
Mixin for classes which can be serialized for SSR
SSRJsonEncodercan be used to serialize objects that implement thisThe frontend handler, currently
processSSRRequestinssr.ts, will use a custom reviver to convert the serialized objects back into their original form. If it encounters an object with the tag@@AP_SSR_V2, it will trigger custom conversions that allow non-standard JSON objects (e.g. aDateorSet, or any arbitrary object you may define). SeeSSRCustomFormatSerializablefor a base class to implement custom serialization.- serialize(context)
Serialize the object for SSR
This method should return a value that can be JSON serialized. Note that it’s fine to return values that contain
SSRSerializableobjects, as these will be serialized recursively.- Parameters:
context (SSRSerializerContext)
- Return type:
dict | str | list
- class alliance_platform.frontend.bundler.ssr.SSRCustomFormatSerializable
Mixin for classes which can be serialized for SSR using a custom format
serializerconverting the object to the form:["@@AP_SSR_V2", <tag name>, <serialized representation>]
For example, a date object might look like:
[“@@AP_SSR_V2”, “date”, “2023-01-01”]
The frontend will then have a matching reviver to convert this into a date object.
See
SSRJsonEncoderfor how this is handled.- get_representation(context)
Get the representation of the object to be serialized
This can be any JSON serializable value. The matching reviver in
ssrJsonReviviers.tsxwill be passed this value.- Parameters:
context (SSRSerializerContext)
- Return type:
dict | str | list
- get_tag()
Get the tag to identify the type of serializable. This is matched in nodejs for de-serialization.
- class alliance_platform.frontend.bundler.ssr.SSRSerializerContext(bundler)
Context available to
SSRSerializableobjects during serialization- Parameters:
bundler (BaseBundler)
- add_import(definition)
Add the specified import to the list of required imports and return the cache key
This allows us to collect all the imports that are required for a given SSR request and load them all up front, then assign them as part of the JSON parsing.
For example, an import will be serialized as:
[‘@@AP_SSR_V2’, ‘ModuleImport’, ‘abc123’]
abc123is the key returned byadd_import. InprocessSSRRequest, the required imports will be loaded first, then the JSON will be parsed with a custom reviver. This reviver will replace any['@@AP_SSR_V2', 'ModuleImport', 'abc123']with the actual resolved import.This turns out to be a lot faster than traversing the object after JSON parsing and loading any encountered imports.
- Parameters:
definition (ImportDefinition)
- Return type:
str
React
See {% component %} for more information on how to use React components in your templates.
- class alliance_platform.frontend.templatetags.react.ComponentNode(origin, source, props, target_var=None, ssr_disabled=False, omit_if_empty=False, container_tag='dj-component', container_props=None, html_attribute_template_nodes=None)
A template node used by
component()- Parameters:
origin (Origin)
source (ComponentSourceBase)
props (dict[str, Any])
ssr_disabled (bool | FilterExpression)
omit_if_empty (bool | FilterExpression)
container_tag (str | FilterExpression)
container_props (dict[str, Any])
html_attribute_template_nodes (HtmlAttributeTemplateNodeList | None)
- add_dynamic_resource(resource)
Track when a dynamic dependency is used.
A dynamic dependency is JS file that can’t be statically analyzed. For example, if a
ComponentProprequired an import (e.g.useViewModelCacheforViewModelProp) then that would be a dynamic dependency. This lets us identify during dev imports that need to be explicitly included in the created bundle.- Parameters:
resource (FrontendResource)
- dynamic_bundle_resources: list[FrontendResource]
Any extra dependencies for this component. This comes from props used that may require imports, for example DateProp may require the date library be included.
- get_dynamic_resources_for_bundling()
Return a list of resources this asset used at runtime.
This is useful for cases where it can’t be determined statically what dependencies are required (e.g. based on the usage of a component).
This is used by
BundlerAssetContextduring development to determine that any used dependencies will be available in production.- Return type:
list[FrontendResource]
- get_resources_for_bundling()
Return a list of resources this asset requires
These will be included in the build process - see
extract_frontend_resources
- html_attribute_template_nodes: HtmlAttributeTemplateNodeList | None
For tags that are parsed from raw HTML we can have instances where the attributes can’t be resolved until the component is rendered. This is used to store those nodes and resolve them to props later.
- print_debug_tree(props, include_template_origin=True)
Print a debug tree of the component and its children.
This renders to look like JSX with comments indicating which templates are used to render the component:
{ /* django/forms/widgets/select.html */ } <DjangoWidgetWrapper name="is_active"> <Select > {/* django/forms/widgets/select_option.html */ } <Item key="unknown"> -------- </Item> </Select> </DjangoWidgetWrapper>
- Parameters:
props (ComponentProps)
- render(context)
Return the node rendered as a string.
- Parameters:
context (Context)
- resolve_prop(value, context)
Handles resolving values to a type that can be serialized
If you add new
ComponentPropthere must a case here to convert values to the new type.- Parameters:
context (Context)
- resolve_props(context)
Resolve the props for this component to values that can be serialized
To add special handling override the
resolve_prop()method.- Parameters:
context (Context)
- Return type:
ComponentProps
- class alliance_platform.frontend.prop_handlers.ComponentProp(value, node, context)[source]
Represents a prop that requires special conversion to generate the code and perform server side rendering.
Simple props like strings, numbers, lists and dicts can all be converted automatically to the corresponding types in javascript. This works automatically as a) the code generator knows how to convert these types and b) they are JSON serializable so can be sent to the SSR server without any extra processing.
Anything more complex than this needs to be handled by a custom prop handler. There are two parts to this:
Implement
generate_codeto generate the typescript code that will be embedded in a <script> tag whenComponentNodeis rendered.Implement
get_tagandget_representationto return a JSON serializable representation of the prop that can then be revived on the frontend.
A concrete example is
DateProp. This will generate code like:new Date("2020-01-01")
such that the prop passed to the component is a
Dateinstance. Theget_representationmethod will return the string representation “2020-01-01” andget_tagreturns"Date". This gets serialized to["@@AP_SSR_V2", "Date", "2020-01-01"]. The@@AP_SSR_V2tag is used to indicate that the prop needs special “reviving” on the frontend which is detected byprocessSSRRequestinssr.ts, and will call the appropriate “reviver” defined inssrJsonRevivers.tsx.- Parameters:
value (Any)
node (ComponentNode)
context (Context)
- get_representation(context)[source]
Get the representation of the object to be serialized
This can be any JSON serializable value. The matching reviver in
ssrJsonReviviers.tsxwill be passed this value.- Parameters:
context (SSRSerializerContext)
- Return type:
dict | str | list
- classmethod get_resources_for_bundling()[source]
Return any resources that need to be included in bundling to make use of this prop
Note that if this is specified, the paths will _always_ be included in the bundle even if the prop is not used.
- Returns:
A list of
Pathinstances that need to be included in the bundling process.- Return type:
list[FrontendResource]
- get_tag()[source]
Get the tag to identify the type of serializable. This is matched in nodejs for de-serialization.
- serialize(context)[source]
Serialize the object for SSR
This method should return a value that can be JSON serialized. Note that it’s fine to return values that contain
SSRSerializableobjects, as these will be serialized recursively.- Parameters:
context (SSRSerializerContext)
- Return type:
list
- classmethod should_apply(value, node, context)[source]
Return
Trueif this prop handler should be used for the given value.- Parameters:
value (Any) – The value in question
node (ComponentNode) – The
ComponentNodethis is being used incontext (Context) – The current template context
- Returns:
Trueif this prop handler should be used for the given value.
- class alliance_platform.frontend.templatetags.react.ComponentSSRItem(source, props, identifier_prefix)
Represents a component that will be rendered on the server
Provides payload containing necessary import & the props for rendering the component.
See the
ComponentSSRItemtype inssr.tsfor where this is handled.NOTE: Keep in sync with
ssr.ts- Parameters:
source (ComponentSourceBase)
props (ComponentProps)
identifier_prefix (str)
- get_ssr_payload(ssr_context)
Matches
ComponentSSRItemtype inssr.tsFor a common component this looks like:
// {% component "div" %}Hello{% component %} { "component": "div", "props": { "children": ["Hello"] }, "identifierPrefix": "abc123" }
For an imported component source gets serialized as a custom format handled in ssrJsonRevivers.tsx
// {% component "components/Input" value=5 %}{% endcomponent %} { "component": ["@@AP_SSR_V2", "ComponentImport", {"import": "<generated string>", "propertyName": null}], "props": { "value": 5 }, "identifierPrefix": "6169587712__1_1" }
After going through the reviver this becomes:
{ "component": [Function: Input], "props: { "value": 5 }, "identifierPrefix": "6169587712__1_1" }
- Parameters:
ssr_context (SSRSerializerContext)
- get_ssr_type()
Returns the type of SSR which is then interpreted by the frontend
See
ssr.tsfor where this is done
- identifier_prefix: str
Used by React to prefix id’s to guarantee uniqueness when multiple roots rendered on one page
- props: ComponentProps
Props for the component
- source: ComponentSourceBase
Tells the renderer how to resolve the component source - either
CommonComponentSourceorESMAssetSource
Default Prop Handlers
These are the default prop handlers that are used by convert props to the correct format for the frontend when using
the {% component %} tag.
Warning
Currently DateProp, DateTimeProp,
ZonedDateTimeProp and TimeProp
use the @internationalized/date library to
parse and format dates. Make sure this package is installed.
- class alliance_platform.frontend.prop_handlers.SetProp(value, node, context)[source]
Handles passing a python
setto a JSSet- Parameters:
value (set)
node (ComponentNode)
context (Context)
- class alliance_platform.frontend.prop_handlers.DateProp(value, *args, **kwargs)[source]
Convert a python date or datetime to a js Date
- Parameters:
value (date)
- class alliance_platform.frontend.prop_handlers.DateTimeProp(value, *args, **kwargs)[source]
Convert a python datetime
- Parameters:
value (datetime)
- class alliance_platform.frontend.prop_handlers.ZonedDateTimeProp(value, *args, **kwargs)[source]
Convert a python datetime
- Parameters:
value (datetime)
- class alliance_platform.frontend.prop_handlers.TimeProp(value, *args, **kwargs)[source]
Convert a python time to a @internationalized/date time
- Parameters:
value (time)
- class alliance_platform.frontend.prop_handlers.SpecialNumeric(value, node, context)[source]
Special prop that indicates a prop should be passed as NaN or Infinity/-Infinity
This is used whenever a math.inf or math.nan / float(“nan”) is passed as a prop.
- Parameters:
value (float)
node (ComponentNode)
context (Context)