diff --git a/src/core/a11y.js b/src/core/a11y.js index cd50a69ba8..139c3d04bd 100644 --- a/src/core/a11y.js +++ b/src/core/a11y.js @@ -15,13 +15,32 @@ const DISABLED_RULES = [ "region", ]; +export async function prepare(conf) { + if (!conf.a11y) { + return; + } + conf.state[name] = { + axeImportPromise: importAxe(), + }; +} + export async function run(conf) { if (!conf.a11y) { return; } + /** @type {typeof window.axe} */ + let axe; + try { + axe = await conf.state[name].axeImportPromise; + } catch (error) { + const msg = `Failed to load a11y linter. ${error.msg}`; + showError(msg, name); + return; + } + const options = conf.a11y === true ? {} : conf.a11y; - const violations = await getViolations(options); + const violations = await getViolations(axe, options); for (const violation of violations) { /** * We're grouping by failureSummary as it contains hints to fix the issue. @@ -49,9 +68,10 @@ export async function run(conf) { } /** + * @param {typeof window.axe} axe * @param {object} opts Options as described at https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter */ -async function getViolations(opts) { +async function getViolations(axe, opts) { const { rules, ...otherOptions } = opts; const options = { rules: { @@ -64,16 +84,6 @@ async function getViolations(opts) { reporter: "v1", // v1 includes a `failureSummary` }; - let axe; - try { - axe = await importAxe(); - } catch (error) { - const msg = "Failed to load a11y linter."; - showError(msg, name); - console.error(error); - return []; - } - try { const result = await axe.run(document, options); return result.violations; diff --git a/src/core/algorithms.js b/src/core/algorithms.js index a693248ca9..9dbec98025 100644 --- a/src/core/algorithms.js +++ b/src/core/algorithms.js @@ -7,14 +7,11 @@ import { fetchAsset } from "./text-loader.js"; export const name = "core/algorithms"; -const cssPromise = loadStyle(); - -async function loadStyle() { - try { - return (await import("text!../../assets/algorithms.css")).default; - } catch { - return fetchAsset("algorithms.css"); - } +export async function prepare() { + const style = document.createElement("style"); + style.id = "respec-css-algorithms"; + style.textContent = await loadStyle(); + document.head.appendChild(style); } export async function run() { @@ -22,9 +19,15 @@ export async function run() { elements .filter(li => li.textContent.trim().startsWith("Assert: ")) .forEach(li => li.classList.add("assert")); - if (document.querySelector(".assert")) { - const style = document.createElement("style"); - style.textContent = await cssPromise; - document.head.appendChild(style); + if (!document.querySelector(".assert")) { + document.getElementById("respec-css-algorithms").remove(); + } +} + +async function loadStyle() { + try { + return (await import("text!../../assets/algorithms.css")).default; + } catch { + return fetchAsset("algorithms.css"); } } diff --git a/src/core/biblio.js b/src/core/biblio.js index 77217f65ce..9cec7e7116 100644 --- a/src/core/biblio.js +++ b/src/core/biblio.js @@ -8,9 +8,6 @@ import { biblioDB } from "./biblio-db.js"; import { createResourceHint } from "./utils.js"; -/** @type {Conf['biblio']} */ -export const biblio = {}; - // for backward compatibity export { wireReference, stringifyReference } from "./render-biblio.js"; @@ -18,19 +15,20 @@ export const name = "core/biblio"; const bibrefsURL = new URL("https://specref.herokuapp.com/bibrefs?refs="); -// Opportunistically dns-prefetch to bibref server, as we don't know yet -// if we will actually need to download references yet. -const link = createResourceHint({ - hint: "dns-prefetch", - href: bibrefsURL.origin, -}); -document.head.appendChild(link); -let doneResolver; - -/** @type {Promise} */ -const done = new Promise(resolve => { - doneResolver = resolve; -}); +export async function prepare(conf) { + // Opportunistically dns-prefetch to bibref server, as we don't know yet + // if we will actually need to download references yet. + const link = createResourceHint({ + hint: "dns-prefetch", + href: bibrefsURL.origin, + }); + document.head.appendChild(link); + + conf.state[name] = { + /** @type {Conf['biblio']} */ + biblio: {}, + }; +} export async function updateFromNetwork( refs, @@ -62,17 +60,17 @@ export async function updateFromNetwork( } /** + * @param {Conf['biblio']} biblio * @param {string} key - * @returns {Promise} + * @returns {BiblioData} */ -export async function resolveRef(key) { - const biblio = await done; +export function resolveRef(biblio, key) { if (!biblio.hasOwnProperty(key)) { return null; } const entry = biblio[key]; if (entry.aliasOf) { - return await resolveRef(entry.aliasOf); + return resolveRef(biblio, entry.aliasOf); } return entry; } @@ -130,12 +128,11 @@ export class Plugin { } async run() { - const finish = () => { - doneResolver(this.conf.biblio); - }; if (!this.conf.localBiblio) { this.conf.localBiblio = {}; } + /** @type {Conf["biblio"]} */ + const biblio = this.conf.state[name].biblio; this.conf.biblio = biblio; const localAliases = Object.keys(this.conf.localBiblio) .filter(key => this.conf.localBiblio[key].hasOwnProperty("aliasOf")) @@ -169,6 +166,5 @@ export class Plugin { Object.assign(biblio, data); } Object.assign(biblio, this.conf.localBiblio); - finish(); } } diff --git a/src/core/data-cite.js b/src/core/data-cite.js index 1e7e34f579..e596591c11 100644 --- a/src/core/data-cite.js +++ b/src/core/data-cite.js @@ -9,13 +9,13 @@ * `data-cite` to `href` attributes. `data-cite` attributes are added to markup * directly by the author as well as via other modules like core/xref. */ -import { biblio, resolveRef, updateFromNetwork } from "./biblio.js"; import { refTypeFromContext, showError, showWarning, wrapInner, } from "./utils.js"; +import { resolveRef, updateFromNetwork } from "./biblio.js"; import { sub } from "./pubsubhub.js"; export const name = "core/data-cite"; @@ -26,9 +26,10 @@ export const name = "core/data-cite"; export const THIS_SPEC = "__SPEC__"; /** + * @param {Conf["biblio"]} biblio * @param {CiteDetails} citeDetails */ -async function getLinkProps(citeDetails) { +function getLinkProps(biblio, citeDetails) { const { key, frag, path } = citeDetails; let href = ""; let title = ""; @@ -37,7 +38,7 @@ async function getLinkProps(citeDetails) { href = document.location.href; } else { // Let's go look it up in spec ref... - const entry = await resolveRef(key); + const entry = resolveRef(biblio, key); if (!entry) { return null; } @@ -156,18 +157,20 @@ export function toCiteDetails(elem) { return details; } -export async function run() { +export async function run(conf) { + /** @type {Conf["biblio"]} */ + const biblio = conf.state["core/biblio"].biblio; /** @type {NodeListOf} */ const elems = document.querySelectorAll( "dfn[data-cite]:not([data-cite='']), a[data-cite]:not([data-cite=''])" ); - await updateBiblio([...elems]); + await updateBiblio(biblio, [...elems]); for (const elem of elems) { const originalKey = elem.dataset.cite; const citeDetails = toCiteDetails(elem); - const linkProps = await getLinkProps(citeDetails); + const linkProps = getLinkProps(biblio, citeDetails); if (linkProps) { linkElem(elem, linkProps, citeDetails); } else { @@ -181,14 +184,14 @@ export async function run() { /** * Fetch and update `biblio` with entries corresponding to given elements + * @param {Conf["biblio"]} biblio * @param {HTMLElement[]} elems */ -async function updateBiblio(elems) { - const promisesForBibEntries = elems.map(toCiteDetails).map(async entry => { - const result = await resolveRef(entry.key); +async function updateBiblio(biblio, elems) { + const bibEntries = elems.map(toCiteDetails).map(entry => { + const result = resolveRef(biblio, entry.key); return { entry, result }; }); - const bibEntries = await Promise.all(promisesForBibEntries); const missingBibEntries = bibEntries .filter(({ result }) => result === null) diff --git a/src/core/data-type.js b/src/core/data-type.js index 25742a0827..60011482c9 100644 --- a/src/core/data-type.js +++ b/src/core/data-type.js @@ -9,14 +9,12 @@ import { fetchAsset } from "./text-loader.js"; export const name = "core/data-type"; -const tooltipStylePromise = loadStyle(); - -async function loadStyle() { - try { - return (await import("text!../../assets/datatype.css")).default; - } catch { - return fetchAsset("datatype.css"); - } +export async function prepare(conf) { + if (!conf.highlightVars) return; + const style = document.createElement("style"); + style.id = "respec-css-data-type"; + style.textContent = await loadStyle(); + document.head.appendChild(style); } export async function run(conf) { @@ -24,10 +22,6 @@ export async function run(conf) { return; } - const style = document.createElement("style"); - style.textContent = await tooltipStylePromise; - document.head.appendChild(style); - let section = null; const varMap = new Map(); /** @type {NodeListOf} */ @@ -46,3 +40,11 @@ export async function run(conf) { if (type) varElem.dataset.type = type; } } + +async function loadStyle() { + try { + return (await import("text!../../assets/datatype.css")).default; + } catch { + return fetchAsset("datatype.css"); + } +} diff --git a/src/core/examples.js b/src/core/examples.js index 7fa1a4f489..2908c0b061 100644 --- a/src/core/examples.js +++ b/src/core/examples.js @@ -39,8 +39,6 @@ const localizationStrings = { const l10n = getIntlData(localizationStrings); -const cssPromise = loadStyle(); - async function loadStyle() { try { return (await import("text!../../assets/examples.css")).default; @@ -80,7 +78,7 @@ export async function run() { ); if (!examples.length) return; - const css = await cssPromise; + const css = await loadStyle(); document.head.insertBefore( html``; + document.head.appendChild(style); +} + export async function run(conf) { // Nothing to highlight if (conf.noHighlightCSS) return; @@ -96,16 +103,12 @@ export async function run(conf) { ); // Nothing to highlight if (!highlightables.length) { + document.getElementById("respec-css-highlight").remove(); return; } const promisesToHighlight = highlightables .filter(elem => elem.textContent.trim()) .map(highlightElement); - const ghCss = await ghCssPromise; - document.head.appendChild( - html`` - ); + await Promise.all(promisesToHighlight); } diff --git a/src/core/issues-notes.js b/src/core/issues-notes.js index 6c97e350bc..67f69d29a6 100644 --- a/src/core/issues-notes.js +++ b/src/core/issues-notes.js @@ -78,8 +78,6 @@ const localizationStrings = { }, }; -const cssPromise = loadStyle(); - async function loadStyle() { try { return (await import("text!../../assets/issues-notes.css")).default; @@ -377,22 +375,24 @@ async function fetchAndStoreGithubIssues(github) { return new Map(Object.entries(issues)); } +export async function prepare() { + document.head.insertBefore( + html``, + document.head.querySelector("link") + ); +} + export async function run(conf) { const query = ".issue, .note, .warning, .ednote"; /** @type {NodeListOf} */ const issuesAndNotes = document.querySelectorAll(query); if (!issuesAndNotes.length) { + document.getElementById("respec-css-issues-notes").remove(); return; // nothing to do. } const ghIssues = await fetchAndStoreGithubIssues(conf.github); - const css = await cssPromise; - const { head: headElem } = document; - headElem.insertBefore( - html``, - headElem.querySelector("link") - ); handleIssues(issuesAndNotes, ghIssues, conf); const ednotes = document.querySelectorAll(".ednote"); ednotes.forEach(ednote => { diff --git a/src/core/render-biblio.js b/src/core/render-biblio.js index 4adb2cb530..42d184db4c 100644 --- a/src/core/render-biblio.js +++ b/src/core/render-biblio.js @@ -3,7 +3,6 @@ // renders the biblio data pre-processed in core/biblio import { addId, getIntlData, showError } from "./utils.js"; -import { biblio } from "./biblio.js"; import { html } from "./import-maps.js"; export const name = "core/render-biblio"; @@ -73,6 +72,8 @@ const endWithDot = endNormalizer("."); /** @param {Conf} conf */ export function run(conf) { + /** @type {Conf["biblio"]} */ + const biblio = conf.state["core/biblio"].biblio; const informs = Array.from(conf.informativeReferences); const norms = Array.from(conf.normativeReferences); @@ -90,11 +91,11 @@ export function run(conf) { refSection.classList.add("appendix"); if (norms.length) { - const sec = createReferencesSection(norms, l10n.norm_references); + const sec = createReferencesSection(biblio, norms, l10n.norm_references); refSection.appendChild(sec); } if (informs.length) { - const sec = createReferencesSection(informs, l10n.info_references); + const sec = createReferencesSection(biblio, informs, l10n.info_references); refSection.appendChild(sec); } @@ -102,12 +103,15 @@ export function run(conf) { } /** + * @param {Conf["biblio"]} biblio * @param {string[]} refs * @param {string} title * @returns {HTMLElement} */ -function createReferencesSection(refs, title) { - const { goodRefs, badRefs } = groupRefs(refs.map(toRefContent)); +function createReferencesSection(biblio, refs, title) { + const { goodRefs, badRefs } = groupRefs( + refs.map(ref => toRefContent(biblio, ref)) + ); const uniqueRefs = getUniqueRefs(goodRefs); const refsToShow = uniqueRefs @@ -132,10 +136,11 @@ function createReferencesSection(refs, title) { /** * returns refcontent and unique key for a reference among its aliases * and warns about circular references + * @param {Conf["biblio"]} biblio * @param {String} ref * @typedef {ReturnType} Ref */ -function toRefContent(ref) { +function toRefContent(biblio, ref) { let refcontent = biblio[ref]; let key = ref; const circular = new Set([key]); diff --git a/src/core/style.js b/src/core/style.js index 5dece26b14..b085e54d5a 100644 --- a/src/core/style.js +++ b/src/core/style.js @@ -12,9 +12,6 @@ import { fetchAsset } from "./text-loader.js"; export const name = "core/style"; -// Opportunistically inserts the style, with the chance to reduce some FOUC -const styleElement = insertStyle(); - async function loadStyle() { try { return (await import("text!../../assets/respec.css")).default; @@ -31,8 +28,13 @@ async function insertStyle() { return styleElement; } -export async function run(conf) { - if (conf.noReSpecCSS) { - (await styleElement).remove(); +export async function prepare(conf) { + if (!conf.noReSpecCSS) { + // Insert style early to reduce FOUC + await insertStyle(); } } + +export async function run(_conf) { + /** nothing to do */ +} diff --git a/src/core/webidl.js b/src/core/webidl.js index fc137f8bd9..bc09d75900 100644 --- a/src/core/webidl.js +++ b/src/core/webidl.js @@ -377,8 +377,6 @@ export function addIDLHeader(pre) { addCopyIDLButton(header); } -const cssPromise = loadStyle(); - async function loadStyle() { try { return (await import("text!../../assets/webidl.css")).default; @@ -387,19 +385,20 @@ async function loadStyle() { } } +export async function prepare() { + document.querySelector("head link, head :last-child").before( + html`` + ); +} + export async function run() { const idls = document.querySelectorAll("pre.idl, pre.webidl"); if (!idls.length) { + document.getElementById("respec-css-webidl").remove(); return; } - if (!document.querySelector(".idl:not(pre), .webidl:not(pre)")) { - const link = document.querySelector("head link"); - if (link) { - const style = document.createElement("style"); - style.textContent = await cssPromise; - link.before(style); - } - } const astArray = [...idls].map(renderWebIDL); diff --git a/src/core/xref.js b/src/core/xref.js index aea0f7abcf..aafdaff620 100644 --- a/src/core/xref.js +++ b/src/core/xref.js @@ -35,14 +35,16 @@ const profiles = { export const API_URL = "https://respec.org/xref/"; -if ( - !document.querySelector("link[rel='preconnect'][href='https://respec.org']") -) { - const link = createResourceHint({ - hint: "preconnect", - href: "https://respec.org", - }); - document.head.appendChild(link); +export function prepare() { + if ( + !document.querySelector("link[rel='preconnect'][href='https://respec.org']") + ) { + const link = createResourceHint({ + hint: "preconnect", + href: "https://respec.org", + }); + document.head.appendChild(link); + } } /** diff --git a/src/type-helper.d.ts b/src/type-helper.d.ts index a4a2da8092..1b5b19b6d2 100644 --- a/src/type-helper.d.ts +++ b/src/type-helper.d.ts @@ -121,6 +121,7 @@ interface Conf { localBiblio?: Record; biblio: Record; shortName: string; + state: Record>; } type ResourceHintOption = { diff --git a/src/w3c/seo.js b/src/w3c/seo.js index d0ad71544d..0379fba456 100644 --- a/src/w3c/seo.js +++ b/src/w3c/seo.js @@ -69,6 +69,9 @@ export async function run(conf) { } async function addJSONLDInfo(conf, doc) { + /** @type {Conf["biblio"]} */ + const biblio = conf.state["core/biblio"].biblio; + // Content for JSON const type = ["TechArticle"]; if (conf.rdfStatus) type.push(conf.rdfStatus); @@ -133,9 +136,7 @@ async function addJSONLDInfo(conf, doc) { ...conf.normativeReferences, ...conf.informativeReferences, ]; - const citationContents = await Promise.all( - citationIds.map(ref => resolveRef(ref)) - ); + const citationContents = citationIds.map(ref => resolveRef(biblio, ref)); jsonld.citation = citationContents .filter(ref => typeof ref === "object") .map(addRef);