Skip to content

Inline scripts in page content run before preceding external scripts on instant navigation and lose their attributes #190

Description

@michaelweinold

Context

We migrated the documentation of our tool from
Material for MkDocs to Zensical. The homepage embeds an interactive Plotly figure generated at build time by markdown-exec's documented pattern (fig.to_html(full_html=False, include_plotlyjs="cdn")), which emits an external <script src="https://cdn.plot.ly/..."> followed by an inline <script>Plotly.newPlot(...)</script> in the page content. After the migration the figure was reported as "gone" — it turned out to render fine on hard loads but to disappear whenever the page was reached through instant navigation.

Bug description

[thank you to Claude for helping me write this up]

When instant navigation injects a new page, author scripts inside the content container are re-evaluated by cloning each <script> element (inject() in src/assets/javascripts/integrations/instant/index.ts). Two defects in that block break common content patterns:

  1. Execution order is not preserved. The script list is piped through concat(...).pipe(switchMap(...)). Because the list is emitted synchronously, switchMap unsubscribes the script.onload completion of an external (src) script the moment the next element is emitted — and a dynamically inserted script loads asynchronously anyway. A following inline script therefore always executes before a preceding external script has loaded (deterministically so, since the external script cannot run until the current task yields). For the Plotly pattern above this throws ReferenceError: Plotly is not defined and leaves an empty figure.

  2. Inline clones lose all attributes. The src branch copies attributes onto the clone, but the inline branch copies only textContent. Inert data payloads such as <script type="application/json">…</script> are therefore re-injected as plain executable scripts: the JSON is parsed as JavaScript and throws right inside inject() (Failed to execute 'replaceWith' on 'Element': Unexpected token ':'), and selectors like script[type="application/json"] no longer match after the swap.

Both defects only manifest on instant navigation; a hard load of the same page behaves correctly, which makes the breakage easy to miss and confusing to reproduce ("sometimes the chart is gone").

Related links

Reproduction

zensical-repro.zip

Steps to reproduce

  1. Unzip the attached reproduction (the three files above), pip install zensical==0.0.50 (zensical-ui 0.0.22), zensical serve.
  2. Open http://localhost:8000/chart/ directly (hard load): the Plotly chart renders, console is clean.
  3. Open http://localhost:8000/ and click Chart in the navigation (instant navigation): the chart is missing and the console shows ReferenceError: Plotly is not defined (defect 1) and Failed to execute 'replaceWith' on 'Element': Unexpected token ':' (defect 2, from the application/json script losing its type).

Browser

No response

Before submitting

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIssue reports a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions