Vendor axe-core, drop the Skypack CDN dependency#14677
Open
cwickham wants to merge 4 commits into
Open
Conversation
Pins AXE_JS in configuration and adds a download block to update-html-dependencies.ts (axe.min.js plus the MPL-2.0 LICENSE and LICENSE-3RD-PARTY.txt, which must ship alongside it). Files are vendored byte-for-byte from npm via unpkg; the /*! banner in axe.min.js carries the required Deque copyright and MPL notice.
Injects axe.min.js as a classic script ahead of axe-check.js (a deferred module), so window.axe is set before the checker runs. Replaces the pinned Skypack dynamic import, making the axe overlay work offline and removing the CDN request from every reader's browser. Same axe version (4.10.3), identical scan results. Also waits for the window load event before the first scan. Deferred modules run before DOMContentLoaded, but the dashboard layout runs on DOMContentLoaded and keeps <html> hidden until then, so an immediate scan sees only hidden elements and reports nothing. The Skypack import's network latency masked this pre-existing race; removing it exposed the bug (8 dashboard Playwright tests).
Collaborator
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Member
This will be a really nice feature to have! |
cwickham
added a commit
that referenced
this pull request
Jul 13, 2026
The overlay is injected after the page scan, so it never audits itself and nothing else guards against new violations as the report UI grows. Scan it with the vendored axe-core build the page already loaded (window.axe), on the long-report page where the overlay is scrollable. Requires the vendored axe-core from #14677, which this branch is now stacked on. Verified the test fails with scrollable-region-focusable when the tabindex fix is removed.
This was referenced Jul 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Vendors axe-core as a bundled HTML resource, so the
axe:accessibility option stops loading it from the Skypack CDN in the reader's browser.What changes
configurationpinsAXE_JS=4.10.3— exactly the version the CDN import already pinned, so this PR changes where axe comes from, not which axe runs. Scan results are identical. A follow-up PR will bump to current axe-core (4.12.1), keeping "different source" and "different results" in separate diffs.update-html-dependencies.tsdownloadsaxe.min.jsplus axe-core'sLICENSE(MPL-2.0) andLICENSE-3RD-PARTY.txtfrom the npm package via unpkg —https://unpkg.com/axe-core@4.10.3/<file>, the sameupdateUnpkgDependencyhelper used for anchor-js, popper, and tippy — intosrc/resources/formats/html/axe/. To verify the committed bytes,curlthose three URLs and compare (or rerunquarto-bld update-html-dependencies). The files are vendored byte-for-byte: the/*!banner inaxe.min.jscarries the Deque copyright/MPL notice that must ship with every copy. Per-directory LICENSE files follow the existing convention (src/resources/formats/pdf/pdfjs/LICENSE).format-html-axe.tsinjectsaxe.min.jsas a classic script ahead of theaxe-check.jsmodule, sowindow.axeis set before the checker runs.axe-check.jsreplaces the pinned Skypack dynamic import withwindow.axe.Why
cdn.skypack.devfrom every reader's browser.quarto axescanner and the overlay will provably run the same axe version from the same file.A race this exposed (also fixed here):
quarto-dashboard.jskeeps<html>hidden until itsDOMContentLoadedhandler runs, but deferred modules execute before that event — so with the Skypack network latency gone, the first scan ran against a fully hidden document and dashboards reported no violations.axe-check.jsnow waits for the windowloadevent before the first scan. The race predates this PR (a sufficiently fast CDN response could have triggered it); vendoring just made it deterministic.Size: ~573 KB committed across three files (541 KB of it
axe.min.js; ~145 KB gzipped on the wire). Precedent: pdf.js, MathJax, and reveal.js are vendored the same way.Out of scope: any behavior change to scanning, report rendering, or options (#14607
standard, sort order, overlay polish, #14668 rescan) — those are follow-up PRs. This PR produces identical scan results, just sourced locally.Verification
axe-accessibility.spec.ts,axe-code-line-numbers.spec.ts): 27/27 pass on chromium.quarto-bld update-html-dependenciesrerun reproduces the committed axe files byte-for-byte.axe-conformance.test.tsunit tests: 9/9 pass.Checklist
I have (if applicable):
AI-assisted PR