diff --git a/index.html b/index.html index ae31978..7fd4e44 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,8 @@ defer > + + diff --git a/src/fetch.ts b/src/fetch.ts index 7d414e8..ed974a5 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -2,6 +2,8 @@ import { nanoid } from 'nanoid/non-secure'; import { type StyleData } from './utils.js'; +const INVALID_MIME_TYPE_ERROR = 'InvalidMimeType'; + export function isStyleLink(link: HTMLLinkElement): link is HTMLLinkElement { return Boolean( (link.type === 'text/css' || link.rel === 'stylesheet') && link.href, @@ -18,17 +20,35 @@ function getStylesheetUrl(link: HTMLLinkElement): URL | undefined { async function fetchLinkedStylesheets( sources: Partial[], ): Promise { - return Promise.all( + const results = await Promise.all( sources.map(async (data) => { if (!data.url) { return data as StyleData; } // fetch css and add to array - const response = await fetch(data.url.toString()); - const css = await response.text(); - return { ...data, css } as StyleData; + try { + const response = await fetch(data.url.toString()); + const type = response.headers.get('content-type'); + if (!type?.startsWith('text/css')) { + const error = new Error( + `Error loading ${data.url}: expected content-type "text/css", got "${type}".`, + ); + error.name = INVALID_MIME_TYPE_ERROR; + throw error; + } + const css = await response.text(); + return { ...data, css } as StyleData; + } catch (error) { + if (error instanceof Error && error.name === INVALID_MIME_TYPE_ERROR) { + // eslint-disable-next-line no-console + console.warn(error); + return null; + } + throw error; + } }), ); + return results.filter((loaded) => loaded !== null); } // Searches for all elements with inline style attributes that include `anchor`. diff --git a/tests/helpers.ts b/tests/helpers.ts index 29688cd..da1718c 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -25,3 +25,8 @@ export function cascadeCSSForTest(css: string) { cascadeCSS([styleObj]); return styleObj.css; } + +export const requestWithCSSType = (css: string) => ({ + body: css, + headers: { 'Content-Type': 'text/css' }, +}); diff --git a/tests/unit/fetch.test.ts b/tests/unit/fetch.test.ts index 6bfb1f8..105df49 100644 --- a/tests/unit/fetch.test.ts +++ b/tests/unit/fetch.test.ts @@ -1,7 +1,7 @@ import fetchMock from 'fetch-mock'; import { fetchCSS } from '../../src/fetch.js'; -import { getSampleCSS } from '../helpers.js'; +import { getSampleCSS, requestWithCSSType } from '../helpers.js'; describe('fetch stylesheet', () => { beforeAll(() => { @@ -22,7 +22,7 @@ describe('fetch stylesheet', () => { it('fetches CSS', async () => { const css = getSampleCSS('anchor-positioning'); - fetchMock.getOnce('end:sample.css', css); + fetchMock.getOnce('end:sample.css', requestWithCSSType(css)); const styleData = await fetchCSS(); expect(styleData).toHaveLength(2); @@ -81,7 +81,7 @@ describe('fetch inline styles', () => { it('fetch returns inline CSS', async () => { const css = getSampleCSS('anchor-positioning'); - fetchMock.getOnce('end:sample.css', css); + fetchMock.getOnce('end:sample.css', requestWithCSSType(css)); const styleData = await fetchCSS(); expect(styleData).toHaveLength(4);