mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
perf(website): serve fonts and the avatar locally, load comments on demand
Every page pulled its three typefaces from fonts.googleapis.com and fonts.gstatic.com, each blog post fetched the author avatar from githubusercontent.com, and the giscus client script ran on page load. That is four outside origins contacted before a reader does anything, each costing a DNS lookup and a TLS handshake on the critical path. Fonts now come from the @fontsource packages the app already uses and are emitted as .woff2 beside the site; the avatar is a 3 KB file in public/; and the giscus embed is created by a "Show comments" button that carries the configuration as data attributes, so the script is only built when a reader asks for it. A delivered page now makes no third-party request at all, verified across the whole build. The variable Bricolage package names itself "Bricolage Grotesque Variable", so that exact name leads the display stack in the Tailwind config. The giscus test now checks the button configuration and asserts the client script is absent from the delivered HTML. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7d1265d566
commit
61ac15372c
10 files changed
+162
-40
No files matched your search
@@ -7,6 +7,14 @@ The website is an Astro static site deployed to GitHub Pages at `https://4gray.g
|
|||||||
Blog posts render Giscus comments from `apps/website/src/components/GiscusComments.astro`.
|
Blog posts render Giscus comments from `apps/website/src/components/GiscusComments.astro`.
|
||||||
Giscus stores comments in GitHub Discussions for `4gray/iptvnator` and maps each page to a discussion by `pathname`, including the GitHub Pages base path such as `/iptvnator/blog/why-external-players-help/`.
|
Giscus stores comments in GitHub Discussions for `4gray/iptvnator` and maps each page to a discussion by `pathname`, including the GitHub Pages base path such as `/iptvnator/blog/why-external-players-help/`.
|
||||||
|
|
||||||
|
The embed is **click-to-load**: the configuration sits as `data-*` attributes on a
|
||||||
|
"Show comments" button and the `giscus.app` script is created only when a reader
|
||||||
|
presses it, so opening a post requests nothing from giscus.app or github.com.
|
||||||
|
Keep it that way — pasting the upstream `<script src="https://giscus.app/client.js">`
|
||||||
|
snippet back into the component would contact both for every reader, and
|
||||||
|
`tools/testing/website-giscus-comments.test.mjs` fails when that script tag
|
||||||
|
appears in the delivered HTML.
|
||||||
|
|
||||||
The embed is wired to the dedicated `Blog comments` discussion category:
|
The embed is wired to the dedicated `Blog comments` discussion category:
|
||||||
|
|
||||||
- Repository id: `MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg=`
|
- Repository id: `MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg=`
|
||||||
@@ -27,6 +35,24 @@ Then update `data-category-id` in `GiscusComments.astro`.
|
|||||||
|
|
||||||
Moderation happens in GitHub Discussions. Maintainers can hide, delete, lock, or move discussions and comments from the repository Discussions UI.
|
Moderation happens in GitHub Discussions. Maintainers can hide, delete, lock, or move discussions and comments from the repository Discussions UI.
|
||||||
|
|
||||||
|
## Fonts And Third-Party Requests
|
||||||
|
|
||||||
|
A delivered page must make **no third-party request**. The build is checked
|
||||||
|
against this: fonts come from the `@fontsource` packages imported in
|
||||||
|
`src/layouts/BaseLayout.astro` and are emitted as `.woff2` beside the site (never
|
||||||
|
from a font CDN), the author avatar is `public/author-4gray.jpg` rather than a
|
||||||
|
`githubusercontent.com` URL, and comments are click-to-load as described above.
|
||||||
|
|
||||||
|
The display family is the **variable** package, which declares itself as
|
||||||
|
`Bricolage Grotesque Variable` — that exact name leads the `display` stack in
|
||||||
|
`tailwind.config.mjs`, with the static `Bricolage Grotesque` kept behind it as a
|
||||||
|
fallback. Adding a weight or style means adding its `@fontsource` import;
|
||||||
|
nothing is fetched at runtime.
|
||||||
|
|
||||||
|
Before adding any embed (analytics, a video, a widget, a webfont), check what it
|
||||||
|
loads. Keeping the page free of outside requests is what keeps it fast and keeps
|
||||||
|
the site from needing a consent banner.
|
||||||
|
|
||||||
## Download Pages
|
## Download Pages
|
||||||
|
|
||||||
`/download/` plus `/download/windows/`, `/download/macos/`, `/download/linux/`
|
`/download/` plus `/download/windows/`, `/download/macos/`, `/download/linux/`
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
@@ -48,14 +48,6 @@ const schemaPayload = jsonLd
|
|||||||
<meta name="format-detection" content="telephone=no" />
|
<meta name="format-detection" content="telephone=no" />
|
||||||
{keywords.length > 0 && <meta name="keywords" content={keywords.join(', ')} />}
|
{keywords.length > 0 && <meta name="keywords" content={keywords.join(', ')} />}
|
||||||
|
|
||||||
<!-- Fonts: Bricolage Grotesque (display), DM Sans (body), IBM Plex Mono (code) -->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,600;12..96,700;12..96,800&family=DM+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=IBM+Plex+Mono:wght@400;500&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Canonical URL -->
|
<!-- Canonical URL -->
|
||||||
<link rel="canonical" href={canonicalURL.toString()} />
|
<link rel="canonical" href={canonicalURL.toString()} />
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,28 @@
|
|||||||
|
---
|
||||||
|
/**
|
||||||
|
* Comments live in GitHub Discussions and are embedded through giscus, which
|
||||||
|
* loads from `giscus.app` and `github.com`. Embedding it on page load would
|
||||||
|
* contact both for every reader of every post, so the embed is created only
|
||||||
|
* after an explicit click: until then the page requests nothing third-party.
|
||||||
|
*
|
||||||
|
* The configuration is carried as `data-*` attributes on the button and copied
|
||||||
|
* onto the real script, so it stays in one place and is visible in the HTML.
|
||||||
|
*/
|
||||||
|
const giscusConfig = {
|
||||||
|
'data-repo': '4gray/iptvnator',
|
||||||
|
'data-repo-id': 'MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg=',
|
||||||
|
'data-category': 'Blog comments',
|
||||||
|
'data-category-id': 'DIC_kwDODLUX8s4C9eBJ',
|
||||||
|
'data-mapping': 'pathname',
|
||||||
|
'data-strict': '1',
|
||||||
|
'data-reactions-enabled': '1',
|
||||||
|
'data-emit-metadata': '0',
|
||||||
|
'data-input-position': 'bottom',
|
||||||
|
'data-theme': 'transparent_dark',
|
||||||
|
'data-lang': 'en',
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
<section class="mt-16 border-t border-surface-700/60 pt-10" aria-labelledby="blog-comments-heading">
|
<section class="mt-16 border-t border-surface-700/60 pt-10" aria-labelledby="blog-comments-heading">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h2 id="blog-comments-heading" class="font-display text-2xl font-semibold tracking-tight text-surface-100">
|
<h2 id="blog-comments-heading" class="font-display text-2xl font-semibold tracking-tight text-surface-100">
|
||||||
@@ -5,23 +30,58 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="giscus min-h-40"></div>
|
<div class="giscus min-h-40" data-giscus-target></div>
|
||||||
<script
|
|
||||||
is:inline
|
<div data-giscus-prompt class="rounded-xl border border-surface-800 bg-surface-900/40 p-5">
|
||||||
src="https://giscus.app/client.js"
|
<button
|
||||||
data-repo="4gray/iptvnator"
|
type="button"
|
||||||
data-repo-id="MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg="
|
data-giscus-loader
|
||||||
data-category="Blog comments"
|
data-script-src="https://giscus.app/client.js"
|
||||||
data-category-id="DIC_kwDODLUX8s4C9eBJ"
|
{...giscusConfig}
|
||||||
data-mapping="pathname"
|
class="inline-flex items-center gap-2 rounded-lg border border-surface-700 bg-surface-900 px-4 py-2 font-mono text-xs uppercase tracking-[0.1em] text-accent-400 transition-colors hover:border-accent-500/60 hover:text-accent-300"
|
||||||
data-strict="1"
|
>
|
||||||
data-reactions-enabled="1"
|
Show comments
|
||||||
data-emit-metadata="0"
|
</button>
|
||||||
data-input-position="bottom"
|
<p class="mt-3 text-sm leading-relaxed text-surface-500">
|
||||||
data-theme="transparent_dark"
|
Comments are hosted in GitHub Discussions. Loading them contacts giscus.app and github.com;
|
||||||
data-lang="en"
|
nothing is requested from them until you choose to.
|
||||||
crossorigin="anonymous"
|
</p>
|
||||||
loading="lazy"
|
</div>
|
||||||
async
|
|
||||||
></script>
|
<script is:inline>
|
||||||
|
(() => {
|
||||||
|
const button = document.querySelector('[data-giscus-loader]');
|
||||||
|
const prompt = document.querySelector('[data-giscus-prompt]');
|
||||||
|
const target = document.querySelector('[data-giscus-target]');
|
||||||
|
|
||||||
|
if (!button || !prompt || !target) return;
|
||||||
|
|
||||||
|
button.addEventListener(
|
||||||
|
'click',
|
||||||
|
() => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
|
||||||
|
script.src = button.dataset.scriptSrc;
|
||||||
|
script.async = true;
|
||||||
|
script.crossOrigin = 'anonymous';
|
||||||
|
script.setAttribute('loading', 'lazy');
|
||||||
|
|
||||||
|
// Every giscus option except the two that drive this loader.
|
||||||
|
for (const attribute of button.attributes) {
|
||||||
|
if (
|
||||||
|
attribute.name.startsWith('data-') &&
|
||||||
|
attribute.name !== 'data-script-src' &&
|
||||||
|
attribute.name !== 'data-giscus-loader'
|
||||||
|
) {
|
||||||
|
script.setAttribute(attribute.name, attribute.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt.remove();
|
||||||
|
target.appendChild(script);
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</section>
|
</section>
|
||||||
@@ -2,6 +2,17 @@
|
|||||||
import BaseHead from '../components/BaseHead.astro';
|
import BaseHead from '../components/BaseHead.astro';
|
||||||
import Header from '../components/Header.astro';
|
import Header from '../components/Header.astro';
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
|
// Fonts ship with the site instead of being fetched from a font CDN, so a
|
||||||
|
// page load makes no third-party request and the faces are already cached
|
||||||
|
// with the rest of the assets.
|
||||||
|
import '@fontsource-variable/bricolage-grotesque/wght.css';
|
||||||
|
import '@fontsource/dm-sans/400.css';
|
||||||
|
import '@fontsource/dm-sans/400-italic.css';
|
||||||
|
import '@fontsource/dm-sans/500.css';
|
||||||
|
import '@fontsource/dm-sans/600.css';
|
||||||
|
import '@fontsource/dm-sans/700.css';
|
||||||
|
import '@fontsource/ibm-plex-mono/400.css';
|
||||||
|
import '@fontsource/ibm-plex-mono/500.css';
|
||||||
import '../styles/global.css';
|
import '../styles/global.css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ const formattedUpdatedDate = updatedDate?.toLocaleDateString('en-US', {
|
|||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
});
|
});
|
||||||
|
|
||||||
const AUTHOR_AVATAR_URL = 'https://avatars.githubusercontent.com/u/1503032';
|
/** Served from the site: an avatar fetched from GitHub would report every reader to them. */
|
||||||
|
const AUTHOR_AVATAR_URL = '/iptvnator/author-4gray.jpg';
|
||||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site).toString();
|
const canonicalURL = new URL(Astro.url.pathname, Astro.site).toString();
|
||||||
const blogPostSchema = {
|
const blogPostSchema = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
|
|||||||
@@ -41,7 +41,13 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
display: ['"Bricolage Grotesque"', '"DM Sans"', 'system-ui', 'sans-serif'],
|
display: [
|
||||||
|
'"Bricolage Grotesque Variable"',
|
||||||
|
'"Bricolage Grotesque"',
|
||||||
|
'"DM Sans"',
|
||||||
|
'system-ui',
|
||||||
|
'sans-serif',
|
||||||
|
],
|
||||||
body: ['"DM Sans"', 'system-ui', 'sans-serif'],
|
body: ['"DM Sans"', 'system-ui', 'sans-serif'],
|
||||||
mono: ['"IBM Plex Mono"', 'monospace'],
|
mono: ['"IBM Plex Mono"', 'monospace'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -161,8 +161,10 @@
|
|||||||
"@eslint/eslintrc": "3.3.7",
|
"@eslint/eslintrc": "3.3.7",
|
||||||
"@eslint/js": "^9.38.0",
|
"@eslint/js": "^9.38.0",
|
||||||
"@faker-js/faker": "10.6.0",
|
"@faker-js/faker": "10.6.0",
|
||||||
|
"@fontsource-variable/bricolage-grotesque": "5.3.0",
|
||||||
"@fontsource/crimson-pro": "5.3.0",
|
"@fontsource/crimson-pro": "5.3.0",
|
||||||
"@fontsource/dm-sans": "5.3.0",
|
"@fontsource/dm-sans": "5.3.0",
|
||||||
|
"@fontsource/ibm-plex-mono": "5.3.0",
|
||||||
"@fontsource/jetbrains-mono": "5.3.0",
|
"@fontsource/jetbrains-mono": "5.3.0",
|
||||||
"@fontsource/roboto": "5.3.0",
|
"@fontsource/roboto": "5.3.0",
|
||||||
"@ngrx/eslint-plugin": "^21.1.1",
|
"@ngrx/eslint-plugin": "^21.1.1",
|
||||||
|
|||||||
Generated
+16
-1
@@ -241,12 +241,18 @@ importers:
|
|||||||
'@faker-js/faker':
|
'@faker-js/faker':
|
||||||
specifier: 10.6.0
|
specifier: 10.6.0
|
||||||
version: 10.6.0
|
version: 10.6.0
|
||||||
|
'@fontsource-variable/bricolage-grotesque':
|
||||||
|
specifier: 5.3.0
|
||||||
|
version: 5.3.0
|
||||||
'@fontsource/crimson-pro':
|
'@fontsource/crimson-pro':
|
||||||
specifier: 5.3.0
|
specifier: 5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
'@fontsource/dm-sans':
|
'@fontsource/dm-sans':
|
||||||
specifier: 5.3.0
|
specifier: 5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
|
'@fontsource/ibm-plex-mono':
|
||||||
|
specifier: 5.3.0
|
||||||
|
version: 5.3.0
|
||||||
'@fontsource/jetbrains-mono':
|
'@fontsource/jetbrains-mono':
|
||||||
specifier: 5.3.0
|
specifier: 5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
@@ -2272,12 +2278,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-3RQHgEtvL1Frl/d1cSreo7qhJ3Gk1OdNUai/CtZ8G+wYeRQnJih3s9xJ9/kgYekPQRdwgh0HXRPqMlzWGwivIQ==}
|
resolution: {integrity: sha512-3RQHgEtvL1Frl/d1cSreo7qhJ3Gk1OdNUai/CtZ8G+wYeRQnJih3s9xJ9/kgYekPQRdwgh0HXRPqMlzWGwivIQ==}
|
||||||
engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'}
|
engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'}
|
||||||
|
|
||||||
|
'@fontsource-variable/bricolage-grotesque@5.3.0':
|
||||||
|
resolution: {integrity: sha512-TLi9Q4hJjS2UvoTMRSS2nHu6c4R56lAw60NR9QYtVRCHn0XtsFpiEhNffZ8Glsoxu6wEEwLKBP8lb94J52PNBA==}
|
||||||
|
|
||||||
'@fontsource/crimson-pro@5.3.0':
|
'@fontsource/crimson-pro@5.3.0':
|
||||||
resolution: {integrity: sha512-PXQH0NGma2wwskZAySBH+s/ezAYBwyDH0I4oRURBu9u81nNUCm5DcNCJaUS9H0UR1y8qlslLfVRU6mnDBpffrA==}
|
resolution: {integrity: sha512-PXQH0NGma2wwskZAySBH+s/ezAYBwyDH0I4oRURBu9u81nNUCm5DcNCJaUS9H0UR1y8qlslLfVRU6mnDBpffrA==}
|
||||||
|
|
||||||
'@fontsource/dm-sans@5.3.0':
|
'@fontsource/dm-sans@5.3.0':
|
||||||
resolution: {integrity: sha512-lYJtMXXO28q1z+yz+z8XKd0s4hXaa9QdkETzkyD760sidCv5heI86weYA0sx0Nc4pAMAQTUuyf4gO44cYKKS9g==}
|
resolution: {integrity: sha512-lYJtMXXO28q1z+yz+z8XKd0s4hXaa9QdkETzkyD760sidCv5heI86weYA0sx0Nc4pAMAQTUuyf4gO44cYKKS9g==}
|
||||||
|
|
||||||
|
'@fontsource/ibm-plex-mono@5.3.0':
|
||||||
|
resolution: {integrity: sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q==}
|
||||||
|
|
||||||
'@fontsource/jetbrains-mono@5.3.0':
|
'@fontsource/jetbrains-mono@5.3.0':
|
||||||
resolution: {integrity: sha512-fqDfB5I9f1p1TV486aUgB9t8zP84P0O1FtQR5Ol9vjwPy+S+EIGlVYm1cvj2W5shcZMTg2nZFdVMoH5wFu8a1A==}
|
resolution: {integrity: sha512-fqDfB5I9f1p1TV486aUgB9t8zP84P0O1FtQR5Ol9vjwPy+S+EIGlVYm1cvj2W5shcZMTg2nZFdVMoH5wFu8a1A==}
|
||||||
|
|
||||||
@@ -10255,7 +10267,6 @@ packages:
|
|||||||
|
|
||||||
shaka-player@5.2.4:
|
shaka-player@5.2.4:
|
||||||
resolution: {integrity: sha512-vf81av2EIcb03jRpeeZBhrPT3PMyggXnZA9k4sxGBxpr/H6v0iEL6b51T2Kwz5YNrQG/yDYoadgBW+oW40LeoA==}
|
resolution: {integrity: sha512-vf81av2EIcb03jRpeeZBhrPT3PMyggXnZA9k4sxGBxpr/H6v0iEL6b51T2Kwz5YNrQG/yDYoadgBW+oW40LeoA==}
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
shallow-clone@3.0.1:
|
shallow-clone@3.0.1:
|
||||||
resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
|
resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
|
||||||
@@ -13617,10 +13628,14 @@ snapshots:
|
|||||||
|
|
||||||
'@faker-js/faker@10.6.0': {}
|
'@faker-js/faker@10.6.0': {}
|
||||||
|
|
||||||
|
'@fontsource-variable/bricolage-grotesque@5.3.0': {}
|
||||||
|
|
||||||
'@fontsource/crimson-pro@5.3.0': {}
|
'@fontsource/crimson-pro@5.3.0': {}
|
||||||
|
|
||||||
'@fontsource/dm-sans@5.3.0': {}
|
'@fontsource/dm-sans@5.3.0': {}
|
||||||
|
|
||||||
|
'@fontsource/ibm-plex-mono@5.3.0': {}
|
||||||
|
|
||||||
'@fontsource/jetbrains-mono@5.3.0': {}
|
'@fontsource/jetbrains-mono@5.3.0': {}
|
||||||
|
|
||||||
'@fontsource/roboto@5.3.0': {}
|
'@fontsource/roboto@5.3.0': {}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import assert from 'node:assert/strict';
|
|||||||
const postHtmlPath = new URL('../../dist/apps/website/blog/why-external-players-help/index.html', import.meta.url);
|
const postHtmlPath = new URL('../../dist/apps/website/blog/why-external-players-help/index.html', import.meta.url);
|
||||||
|
|
||||||
const expectedGiscusAttributes = {
|
const expectedGiscusAttributes = {
|
||||||
src: 'https://giscus.app/client.js',
|
|
||||||
'data-repo': '4gray/iptvnator',
|
'data-repo': '4gray/iptvnator',
|
||||||
'data-repo-id': 'MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg=',
|
'data-repo-id': 'MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg=',
|
||||||
'data-category': 'Blog comments',
|
'data-category': 'Blog comments',
|
||||||
@@ -19,18 +18,28 @@ const expectedGiscusAttributes = {
|
|||||||
'data-lang': 'en',
|
'data-lang': 'en',
|
||||||
};
|
};
|
||||||
|
|
||||||
test('published blog posts include the Giscus comments embed configuration', async () => {
|
test('published blog posts carry the Giscus configuration on a click-to-load button', async () => {
|
||||||
const html = await readFile(postHtmlPath, 'utf8');
|
const html = await readFile(postHtmlPath, 'utf8');
|
||||||
const scriptMatch = html.match(/<script\b[^>]*giscus\.app\/client\.js[^>]*>/);
|
const buttonMatch = html.match(/<button\b[^>]*data-giscus-loader[^>]*>/);
|
||||||
|
|
||||||
assert.ok(scriptMatch, 'Expected the blog post HTML to include the Giscus client script.');
|
assert.ok(buttonMatch, 'Expected the blog post HTML to include the Giscus loader button.');
|
||||||
|
|
||||||
const scriptTag = scriptMatch[0];
|
const buttonTag = buttonMatch[0];
|
||||||
for (const [name, value] of Object.entries(expectedGiscusAttributes)) {
|
for (const [name, value] of Object.entries(expectedGiscusAttributes)) {
|
||||||
assert.match(scriptTag, new RegExp(`${name}="${value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`));
|
assert.match(buttonTag, new RegExp(`${name}="${value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
assert.match(scriptTag, /\bloading="lazy"/);
|
|
||||||
assert.match(scriptTag, /\bcrossorigin="anonymous"/);
|
test('a blog post loads nothing from a third party before the reader asks for comments', async () => {
|
||||||
assert.match(scriptTag, /\basync/);
|
const html = await readFile(postHtmlPath, 'utf8');
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
!/<script\b[^>]*\bsrc="https:\/\/giscus\.app/.test(html),
|
||||||
|
'The giscus client must not be a script tag in the delivered HTML — it is created on click.'
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
html,
|
||||||
|
/data-script-src="https:\/\/giscus\.app\/client\.js"/,
|
||||||
|
'The loader button must carry the giscus client URL for its click handler.'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user