diff --git a/packages/beasties/README.md b/packages/beasties/README.md index f7fa9d1..710bab3 100644 --- a/packages/beasties/README.md +++ b/packages/beasties/README.md @@ -120,6 +120,7 @@ All optional. Pass them to `new Beasties({ ... })`. - `path` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Base path location of the CSS files _(default: `''`)_ - `publicPath` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Public path of the CSS resources. This prefix is removed from the href _(default: `''`)_ - `external` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Inline styles from external stylesheets _(default: `true`)_ +- `remote` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Download and inline remote stylesheets (http://, https://, //) _(default: `false`)_ - `inlineThreshold` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** Inline external stylesheets smaller than a given size _(default: `0`)_ - `minimumExternalSize` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** If the non-critical external stylesheet would be below this size, just inline it _(default: `0`)_ - `pruneSource` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Remove inlined rules from the external stylesheet _(default: `false`)_ diff --git a/packages/beasties/src/index.d.ts b/packages/beasties/src/index.d.ts index 10076f6..bda8754 100644 --- a/packages/beasties/src/index.d.ts +++ b/packages/beasties/src/index.d.ts @@ -50,6 +50,7 @@ export interface Options { path?: string publicPath?: string external?: boolean + remote?: boolean inlineThreshold?: number minimumExternalSize?: number pruneSource?: boolean diff --git a/packages/beasties/src/index.ts b/packages/beasties/src/index.ts index ab61eb0..ca63d8d 100644 --- a/packages/beasties/src/index.ts +++ b/packages/beasties/src/index.ts @@ -205,8 +205,28 @@ export default class Beasties { .replace(/^\//, '') } - // Ignore remote stylesheets - if (/^https?:\/\//.test(normalizedPath) || href.startsWith('//')) { + // Handle remote stylesheets + const isRemote = /^https?:\/\//.test(href) || href.startsWith('//') + if (isRemote) { + if (this.options.remote === true) { + try { + // Normalize protocol-relative URLs + const absoluteUrl = href.startsWith('//') ? `https:${href}` : href + + const response = await fetch(absoluteUrl) + + if (!response.ok) { + this.logger.warn?.(`Failed to fetch ${absoluteUrl} (${response.status})`) + return undefined + } + + return await response.text() + } + catch (error) { + this.logger.warn?.(`Error fetching ${href}: ${(error as Error).message}`) + return undefined + } + } return undefined } @@ -275,7 +295,9 @@ export default class Beasties { const href = link.getAttribute('href') // skip filtered resources, or network resources if no filter is provided - if (!href?.endsWith('.css')) { + // Strip query params and hashes before checking extension + const pathname = href?.split('?')[0]?.split('#')[0] + if (!pathname?.endsWith('.css')) { return undefined } diff --git a/packages/beasties/src/types.ts b/packages/beasties/src/types.ts index a52bdc0..c3301e1 100644 --- a/packages/beasties/src/types.ts +++ b/packages/beasties/src/types.ts @@ -40,6 +40,10 @@ export interface Options { * Inline styles from external stylesheets _(default: `true`)_ */ external?: boolean + /** + * Download and inline remote stylesheets _(default: `false`)_ + */ + remote?: boolean /** * Inline external stylesheets smaller than a given size _(default: `0`)_ */ diff --git a/packages/beasties/test/beasties.test.ts b/packages/beasties/test/beasties.test.ts index 7ff0010..183a472 100644 --- a/packages/beasties/test/beasties.test.ts +++ b/packages/beasties/test/beasties.test.ts @@ -497,4 +497,158 @@ describe('beasties', () => { expect(loggerWarnSpy).not.toHaveBeenCalled() expect(result).toMatchSnapshot() }) + + it('ignores remote stylesheets by default', async () => { + const beasties = new Beasties({ + reduceInlineStyles: false, + }) + + const result = await beasties.process(trim` + +
+ + + +