Source code for next.static.scripts

"""Pluggable builder for the `next.min.js` preload, script, and init tags.

The builder produces the three HTML fragments that wire `window.Next`
into the rendered page. The first fragment is a preload hint injected
before `</head>` so the browser starts downloading during HTML parsing.
The second fragment is a blocking script tag for the compiled runtime.
The third fragment is an inline script that feeds the serialized JS
context into `Next._init`.

Every template is an instance attribute, so users can override any
single tag without subclassing. An injection policy controls whether
the static manager emits those tags at all.
"""

from __future__ import annotations

import enum
import json
from typing import TYPE_CHECKING, Any, ClassVar, Final

from .serializers import resolve_serializer


if TYPE_CHECKING:
    from collections.abc import Mapping

    from .serializers import JsContextSerializer


NEXT_JS_STATIC_PATH: Final = "next/next.min.js"


[docs] class ScriptInjectionPolicy(enum.Enum): """Controls whether `next.min.js` is automatically injected. The `AUTO` value is the default. Under `AUTO` the static manager emits the preload hint, the `<script>` tag, and the `Next._init` call into every rendered page. The `DISABLED` value skips injection entirely and is useful when a page does not need `window.Next`, for example a raw API response rendered through the page machinery. The `MANUAL` value skips automatic injection but still builds the fragments on request so users can emit the tags themselves from a template. """ AUTO = "auto" DISABLED = "disabled" MANUAL = "manual"
[docs] class NextScriptBuilder: """Builds the preload hint, script tag, and init script for `window.Next`. The `next_js_url` argument is the public URL of the compiled `next.min.js` asset. The optional `preload_template`, `script_tag_template`, and `init_template` arguments override the defaults. The preload and script templates must contain the `{url}` placeholder. The init template must contain the `{payload}` placeholder, which receives the JSON-serialized JS context. The `policy` argument is consulted by the static manager before injection and defaults to `ScriptInjectionPolicy.AUTO`. """ DEFAULT_PRELOAD: ClassVar[str] = '<link rel="preload" as="script" href="{url}">' DEFAULT_SCRIPT_TAG: ClassVar[str] = '<script src="{url}"></script>' DEFAULT_INIT: ClassVar[str] = "<script>Next._init({payload});</script>"
[docs] def __init__( self, next_js_url: str, *, preload_template: str | None = None, script_tag_template: str | None = None, init_template: str | None = None, policy: ScriptInjectionPolicy = ScriptInjectionPolicy.AUTO, ) -> None: """Store the URL, tag templates, and injection policy.""" self._url = next_js_url self._preload_template = preload_template or self.DEFAULT_PRELOAD self._script_tag_template = script_tag_template or self.DEFAULT_SCRIPT_TAG self._init_template = init_template or self.DEFAULT_INIT self._policy = policy
@property def policy(self) -> ScriptInjectionPolicy: """Return the configured script injection policy.""" return self._policy @property def url(self) -> str: """Return the resolved `next.min.js` URL.""" return self._url
[docs] def script_tag(self) -> str: """Return the blocking script tag that executes `next.min.js`.""" return self._script_tag_template.format(url=self._url)
[docs] def init_script( self, js_context: Mapping[str, Any], *, key_serializers: Mapping[str, JsContextSerializer] | None = None, ) -> str: """Return the inline script that passes the context to `Next._init`. Delegates serialisation to the configured `JsContextSerializer` so the init payload honours the same encoding rules as values registered through `StaticCollector.add_js_context`. When `key_serializers` is supplied, each top-level key listed there is encoded with its dedicated serializer and the rest fall back to the global default. """ if not key_serializers: payload = resolve_serializer().dumps(dict(js_context)) else: default = resolve_serializer() fragments: list[str] = [] for k, v in js_context.items(): key_serializer = key_serializers.get(k, default) encoded_key = json.dumps(k, separators=(",", ":")) encoded_val = key_serializer.dumps(v) fragments.append(f"{encoded_key}:{encoded_val}") payload = "{" + ",".join(fragments) + "}" return self._init_template.format(payload=payload)
[docs] @classmethod def from_options( cls, next_js_url: str, options: Mapping[str, Any] | None = None, ) -> NextScriptBuilder: """Build a script builder from an options mapping. The recognised keys are `preload_template`, `script_tag_template`, `init_template`, and `policy`. The `policy` value may be a `ScriptInjectionPolicy` member or the string value of one of its members. Any other value raises `ValueError`. """ options = options or {} raw_policy = options.get("policy", ScriptInjectionPolicy.AUTO) if isinstance(raw_policy, ScriptInjectionPolicy): policy = raw_policy else: try: policy = ScriptInjectionPolicy(raw_policy) except ValueError as e: allowed = ", ".join(repr(p.value) for p in ScriptInjectionPolicy) msg = ( f"Invalid NextScriptBuilder policy {raw_policy!r}. " f"Expected one of {allowed}" ) raise ValueError(msg) from e return cls( next_js_url, preload_template=options.get("preload_template"), script_tag_template=options.get("script_tag_template"), init_template=options.get("init_template"), policy=policy, )