From 3add5a375f7996baec82d59fa4ef3a01dbce74cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Robin?= Date: Thu, 15 May 2025 04:54:05 +0200 Subject: [PATCH 1/6] Implement inlineGoogleFonts method to fetch and inline Google Fonts CSS, avoiding CORS issues --- src/BeaconPreloadFonts.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/BeaconPreloadFonts.js b/src/BeaconPreloadFonts.js index 127b4ec..d28cc60 100644 --- a/src/BeaconPreloadFonts.js +++ b/src/BeaconPreloadFonts.js @@ -68,6 +68,39 @@ class BeaconPreloadFonts { } } + /** + * Inlines Google Fonts CSS to avoid CORS issues before parsing stylesheets. + * + * @returns {Promise} A promise that resolves when inlining is complete. + */ + async inlineGoogleFonts() { + // Find and dedupe Google Fonts stylesheet links. + const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')) + .filter(link => link.href.includes('fonts.googleapis.com')); + const hrefs = [...new Set(links.map(l => l.href))]; + if (!hrefs.length) return; + // Fetch all CSS in parallel. + const cssContents = await Promise.all( + hrefs.map(href => + fetch(href) + .then(res => (res.ok ? res.text() : '')) + .catch(() => '') + ) + ); + // Batch insert inlined styles. + const frag = document.createDocumentFragment(); + cssContents.forEach(text => { + if (text) { + const styleEl = document.createElement('style'); + styleEl.textContent = text; + frag.appendChild(styleEl); + } + }); + document.head.appendChild(frag); + // Remove external link elements. + links.forEach(link => link.remove()); + } + /** * Retrieves a map of network-loaded fonts. * @@ -173,6 +206,8 @@ class BeaconPreloadFonts { * @returns {Promise} A promise that resolves when the analysis is complete. */ async run() { + // Inline Google Fonts CSS to avoid CORS issues + await this.inlineGoogleFonts(); // Wait for fonts to be loaded await document.fonts.ready; const networkLoadedFonts = this.getNetworkLoadedFonts(); From d5f72f0e7343a2fa88542493a952fe46236225e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Robin?= Date: Fri, 16 May 2025 03:15:38 +0200 Subject: [PATCH 2/6] Add tests for inlineGoogleFonts method to validate fetching and inlining of Google Fonts --- test/BeaconPreloadFonts.test.js | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/BeaconPreloadFonts.test.js b/test/BeaconPreloadFonts.test.js index 3be4ebd..d766f3b 100644 --- a/test/BeaconPreloadFonts.test.js +++ b/test/BeaconPreloadFonts.test.js @@ -467,4 +467,51 @@ describe('BeaconPreloadFonts', () => { assert.strictEqual(result['Duplicate'].variations.length, 2, 'Should have two variations'); }); }); + + describe('inlineGoogleFonts', () => { + let beacon; + let fetchStub; + beforeEach(() => { + beacon = new BeaconPreloadFonts({}, { logMessage: () => {} }); + fetchStub = sinon.stub(global, 'fetch'); + }); + afterEach(() => { + sinon.restore(); + }); + + it('should return early when no Google Fonts links are present', async () => { + sinon.stub(document, 'querySelectorAll').returns([]); + await beacon.inlineGoogleFonts(); + sinon.assert.notCalled(fetchStub); + }); + + it('should fetch CSS, inline styles, and remove link elements', async () => { + // Prepare fake link elements + const linkA = { href: 'https://fonts.googleapis.com/a', remove: sinon.spy() }; + const linkB = { href: 'https://fonts.googleapis.com/b', remove: sinon.spy() }; + sinon.stub(document, 'querySelectorAll').returns([linkA, linkB]); + // Stub fetch responses + fetchStub.withArgs('https://fonts.googleapis.com/a').resolves({ ok: true, text: () => Promise.resolve('CSS_A') }); + fetchStub.withArgs('https://fonts.googleapis.com/b').resolves({ ok: true, text: () => Promise.resolve('CSS_B') }); + // Prepare a fragment and override document.createDocumentFragment + const frag = { children: [], appendChild(node) { this.children.push(node); } }; + document.createDocumentFragment = () => frag; + // Stub head.appendChild + document.head = { appendChild: sinon.spy() }; + + await beacon.inlineGoogleFonts(); + // Ensure fetch called for each href + sinon.assert.calledWith(fetchStub, 'https://fonts.googleapis.com/a'); + sinon.assert.calledWith(fetchStub, 'https://fonts.googleapis.com/b'); + // Two style nodes in fragment + assert.strictEqual(frag.children.length, 2); + assert.strictEqual(frag.children[0].textContent, 'CSS_A'); + assert.strictEqual(frag.children[1].textContent, 'CSS_B'); + // Single append to head + sinon.assert.calledOnce(document.head.appendChild); + // Links removed + sinon.assert.calledOnce(linkA.remove); + sinon.assert.calledOnce(linkB.remove); + }); + }); }); \ No newline at end of file From 3ea55dec4dd902b3d3fdc5a9a48b89d016f20f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Robin?= Date: Mon, 19 May 2025 03:47:02 +0200 Subject: [PATCH 3/6] Refactor font handling: Implement externalStylesheetsDoc method to fetch and parse external font stylesheets, and update _initializeExternalFontSheets to manage parsed results. --- src/BeaconPreloadFonts.js | 244 ++++++++++++++++++++++++++++---- test/BeaconPreloadFonts.test.js | 129 ++++++++++++----- 2 files changed, 309 insertions(+), 64 deletions(-) diff --git a/src/BeaconPreloadFonts.js b/src/BeaconPreloadFonts.js index d28cc60..77051d2 100644 --- a/src/BeaconPreloadFonts.js +++ b/src/BeaconPreloadFonts.js @@ -69,36 +69,227 @@ class BeaconPreloadFonts { } /** - * Inlines Google Fonts CSS to avoid CORS issues before parsing stylesheets. + * Fetches external stylesheet links from known font providers, retrieves their CSS, + * parses them into in-memory CSSStyleSheet objects, and extracts font-family/font-face + * information into a structured object. * - * @returns {Promise} A promise that resolves when inlining is complete. + * @async + * @function externalStylesheetsDoc + * @returns {Promise<{styleSheets: CSSStyleSheet[], fontPairs: Object}>} An object containing: + * - styleSheets: Array of parsed CSSStyleSheet objects (not attached to the DOM). + * - fontPairs: An object mapping font URLs to arrays of font variation objects + * ({family, weight, style}). + * + * @example + * const { styleSheets, fontPairs } = await externalStylesheetsDoc(); + * this.logger.logMessage(fontPairs); */ - async inlineGoogleFonts() { - // Find and dedupe Google Fonts stylesheet links. - const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')) - .filter(link => link.href.includes('fonts.googleapis.com')); - const hrefs = [...new Set(links.map(l => l.href))]; - if (!hrefs.length) return; - // Fetch all CSS in parallel. - const cssContents = await Promise.all( - hrefs.map(href => - fetch(href) - .then(res => (res.ok ? res.text() : '')) - .catch(() => '') - ) + async externalStylesheetsDoc() { + function generateFontPairsFromStyleSheets(styleSheetsArray) { + const fontPairs = {}; + + /** + * Extracts the first URL from a CSS `src` value string. + * + * @param {string} srcValue - The CSS `src` value containing one or more `url(...)` references. + * @returns {string|null} The first extracted URL if found, otherwise `null`. + */ + function _extractFirstUrlFromSrc(srcValue) { + if (!srcValue) return null; + const urlMatch = srcValue.match(/url\s*\(\s*(['"]?)(.+?)\1\s*\)/); + return urlMatch ? urlMatch[2] : null; + } + + /** + * Removes leading and trailing single or double quotes from a font family name and trims whitespace. + * + * @param {string} fontFamilyValue - The font family name to clean. + * @returns {string} The cleaned font family name without surrounding quotes and trimmed whitespace. + */ + function _cleanFontFamilyName(fontFamilyValue) { + if (!fontFamilyValue) return ''; + return fontFamilyValue.replace(/^['"]+|['"]+$/g, '').trim(); + } + + if (!styleSheetsArray || !Array.isArray(styleSheetsArray)) { + console.warn( + 'generateFontPairsFromStyleSheets: Input is not a valid array. Received:', + styleSheetsArray + ); + return fontPairs; + } + if (styleSheetsArray.length === 0) { + return fontPairs; + } + + styleSheetsArray.forEach((sheet) => { + if (sheet && sheet.cssRules) { + try { + for (const rule of sheet.cssRules) { + if (rule.type === CSSRule.FONT_FACE_RULE) { + const cssFontFaceRule = rule; + const fontFamily = _cleanFontFamilyName( + cssFontFaceRule.style.getPropertyValue('font-family') + ); + const fontWeight = + cssFontFaceRule.style.getPropertyValue('font-weight') || + 'normal'; + const fontStyle = + cssFontFaceRule.style.getPropertyValue('font-style') || + 'normal'; + const src = cssFontFaceRule.style.getPropertyValue('src'); + const fontUrl = _extractFirstUrlFromSrc(src); + + if (fontFamily && fontUrl) { + const variation = { + family: fontFamily, + weight: fontWeight, + style: fontStyle, + }; + if (!fontPairs[fontUrl]) fontPairs[fontUrl] = []; + const variationExists = fontPairs[fontUrl].some( + (v) => + v.family === variation.family && + v.weight === variation.weight && + v.style === variation.style + ); + if (!variationExists) fontPairs[fontUrl].push(variation); + } + } + } + } catch (e) { + console.warn( + 'Error processing CSS rules from a stylesheet:', + e, + sheet + ); + } + } else if (sheet && !sheet.cssRules) { + console.warn( + 'Skipping a stylesheet as its cssRules are not accessible or it is empty:', + sheet + ); + } + }); + return fontPairs; + } + + // --- Main logic for fetching Google Fonts --- + const externalFontsProviders = [ + 'fonts.googleapis.com', + 'fonts.gstatic.com', + 'use.typekit.net', + 'fonts.adobe.com', + 'cdn.fonts.net', + // Add more known external font domains as needed + ]; + + const links = [ + ...document.querySelectorAll('link[rel="stylesheet"]'), + ].filter((link) => + externalFontsProviders.some((domain) => link.href.includes(domain)) + ); + + if (links.length === 0) { + this.logger.logMessage('No external CSS links found to process.'); + return { + // Consistent return structure + styleSheets: [], // The retrievable CSSStyleSheet objects + fontPairs: {}, // Processed data from these sheets + }; + } + + const fetchedCssPromises = links.map((linkElement) => + fetch(linkElement.href, { mode: 'cors' }) + .then((response) => { + if (response.ok) { + return response.text(); + } + console.warn( + `Failed to fetch external CSS from ${linkElement.href}: ${response.status} ${response.statusText}` + ); + return null; + }) + .catch((error) => { + console.error( + `Network error fetching external CSS from ${linkElement.href}:`, + error + ); + return null; + }) ); - // Batch insert inlined styles. - const frag = document.createDocumentFragment(); - cssContents.forEach(text => { - if (text) { - const styleEl = document.createElement('style'); - styleEl.textContent = text; - frag.appendChild(styleEl); + + const cssTexts = await Promise.all(fetchedCssPromises); + const temporaryStyleSheets = []; // These will hold the CSSStyleSheet objects + + cssTexts.forEach((txt) => { + if (txt && txt.trim() !== '') { + try { + const sheet = new CSSStyleSheet(); // Create a new CSSStyleSheet object + sheet.replaceSync(txt); // Parse the CSS text into it + temporaryStyleSheets.push(sheet); // Add to our array + // These sheets exist in memory only and are not applied to the document. + } catch (error) { + console.error( + 'Could not parse fetched CSS into a stylesheet:', + error, + `\nCSS (first 200 chars): ${txt.substring(0, 200)}...` + ); + } } }); - document.head.appendChild(frag); - // Remove external link elements. - links.forEach(link => link.remove()); + + // At this point, `temporaryStyleSheets` contains CSSStyleSheet objects. + if (temporaryStyleSheets.length > 0) { + this.logger.logMessage( + `[Beacon] ${temporaryStyleSheets.length} stylesheet(s) fetched and parsed into CSSStyleSheet objects.` + ); + } else { + this.logger.logMessage( + '[Beacon] No stylesheets were successfully parsed from the fetched CSS.' + ); + } + + // You can now process these in-memory sheets (e.g., to get font pairs) + // This demonstrates their "processability". + const processedFontPairs = + generateFontPairsFromStyleSheets(temporaryStyleSheets); + + // Return the array of CSSStyleSheet objects and any processed data. + // This makes them "retrievable". + return { + styleSheets: temporaryStyleSheets, + fontPairs: processedFontPairs, + }; + } + + /** + * Asynchronously initializes and parses external font stylesheets. + * + * Fetches external font stylesheets and font pairs using `externalStylesheetsDoc`, + * then stores the parsed results in `externalParsedSheets` and `externalParsedPairs`. + * Logs the process and handles errors by resetting `externalParsedSheets` to an empty array. + * + * @async + * @returns {Promise} Resolves when external font stylesheets have been initialized. + */ + async _initializeExternalFontSheets() { + this.logger.logMessage('Initializing external font stylesheets...'); + try { + // Assuming externalStylesheetsDoc is available in this scope + const result = await this.externalStylesheetsDoc(); + this.externalParsedSheets = result.styleSheets || []; + this.externalParsedPairs = result.fontPairs || []; + this.logger.logMessage( + `Successfully parsed ${this.externalParsedSheets.length} external font stylesheets.` + ); + } catch (error) { + this.logger.logMessage( + 'Error initializing external font stylesheets:', + error + ); + this.externalParsedSheets = []; // Ensure it's an array even on error + } } /** @@ -206,10 +397,9 @@ class BeaconPreloadFonts { * @returns {Promise} A promise that resolves when the analysis is complete. */ async run() { - // Inline Google Fonts CSS to avoid CORS issues - await this.inlineGoogleFonts(); // Wait for fonts to be loaded await document.fonts.ready; + await this._initializeExternalFontSheets(); const networkLoadedFonts = this.getNetworkLoadedFonts(); const stylesheetFonts = this.getFontFaceRules(); const hostedFonts = new Map(); diff --git a/test/BeaconPreloadFonts.test.js b/test/BeaconPreloadFonts.test.js index d766f3b..ee10d2f 100644 --- a/test/BeaconPreloadFonts.test.js +++ b/test/BeaconPreloadFonts.test.js @@ -468,50 +468,105 @@ describe('BeaconPreloadFonts', () => { }); }); - describe('inlineGoogleFonts', () => { - let beacon; - let fetchStub; + describe('_initializeExternalFontSheets', () => { + it('should set externalParsedSheets and externalParsedPairs from externalStylesheetsDoc', async () => { + const mockSheets = [{ cssRules: [] }]; + const mockPairs = { 'https://fonts.example.com/font.woff2': [{ family: 'MockFont', weight: '400', style: 'normal' }] }; + const externalStylesheetsDocStub = sinon.stub(beaconPreloadFonts, 'externalStylesheetsDoc').resolves({ + styleSheets: mockSheets, + fontPairs: mockPairs + }); + + await beaconPreloadFonts._initializeExternalFontSheets(); + + assert.deepStrictEqual(beaconPreloadFonts.externalParsedSheets, mockSheets, 'externalParsedSheets should be set'); + assert.deepStrictEqual(beaconPreloadFonts.externalParsedPairs, mockPairs, 'externalParsedPairs should be set'); + assert.ok(loggerMock.logMessage.calledWith('Initializing external font stylesheets...')); + assert.ok(loggerMock.logMessage.calledWith('Successfully parsed 1 external font stylesheets.')); + + externalStylesheetsDocStub.restore(); + }); + + it('should handle errors and reset externalParsedSheets to empty array', async () => { + const error = new Error('Test error'); + const externalStylesheetsDocStub = sinon.stub(beaconPreloadFonts, 'externalStylesheetsDoc').rejects(error); + + await beaconPreloadFonts._initializeExternalFontSheets(); + + assert.deepStrictEqual(beaconPreloadFonts.externalParsedSheets, [], 'externalParsedSheets should be empty array on error'); + assert.ok(loggerMock.logMessage.calledWith('Error initializing external font stylesheets:', error)); + + externalStylesheetsDocStub.restore(); + }); + }); + + describe('externalStylesheetsDoc', () => { + let originalQuerySelectorAll; + let originalFetch; + let originalCSSStyleSheet; beforeEach(() => { - beacon = new BeaconPreloadFonts({}, { logMessage: () => {} }); - fetchStub = sinon.stub(global, 'fetch'); + // Mock CSSRule.FONT_FACE_RULE + global.CSSRule = { FONT_FACE_RULE: 5 }; + // Mock document.querySelectorAll to return fake link elements + originalQuerySelectorAll = global.document.querySelectorAll; + global.document.querySelectorAll = sinon.stub().returns([ + { href: 'https://fonts.googleapis.com/css?family=Roboto', rel: 'stylesheet' } + ]); + + // Mock fetch to return a fake CSS response + originalFetch = global.fetch; + global.fetch = sinon.stub().resolves({ + ok: true, + text: () => Promise.resolve('@font-face { font-family: "Roboto"; src: url("https://fonts.gstatic.com/s/roboto.woff2"); font-weight: 400; font-style: normal; }') + }); + + // Mock CSSStyleSheet and replaceSync + originalCSSStyleSheet = global.CSSStyleSheet; + global.CSSStyleSheet = function() { + this.rules = []; + this.replaceSync = function(cssText) { + // Simulate parsing CSS text into cssRules + this.cssRules = [{ + type: 5, // CSSRule.FONT_FACE_RULE + style: { + getPropertyValue: (prop) => { + if (prop === 'font-family') return 'Roboto'; + if (prop === 'src') return 'url("https://fonts.gstatic.com/s/roboto.woff2")'; + if (prop === 'font-weight') return '400'; + if (prop === 'font-style') return 'normal'; + return ''; + } + } + }]; + }; + }; }); + afterEach(() => { - sinon.restore(); + global.document.querySelectorAll = originalQuerySelectorAll; + global.fetch = originalFetch; + global.CSSStyleSheet = originalCSSStyleSheet; }); - it('should return early when no Google Fonts links are present', async () => { - sinon.stub(document, 'querySelectorAll').returns([]); - await beacon.inlineGoogleFonts(); - sinon.assert.notCalled(fetchStub); + it('should fetch, parse, and return external font CSS as styleSheets and fontPairs', async () => { + const result = await beaconPreloadFonts.externalStylesheetsDoc(); + // Should have one stylesheet with cssRules + assert.strictEqual(result.styleSheets.length, 1); + assert.ok(Array.isArray(result.styleSheets)); + // Should have fontPairs with the correct structure + assert.ok(result.fontPairs['https://fonts.gstatic.com/s/roboto.woff2']); + assert.deepStrictEqual(result.fontPairs['https://fonts.gstatic.com/s/roboto.woff2'][0], { + family: 'Roboto', + weight: '400', + style: 'normal' + }); }); - it('should fetch CSS, inline styles, and remove link elements', async () => { - // Prepare fake link elements - const linkA = { href: 'https://fonts.googleapis.com/a', remove: sinon.spy() }; - const linkB = { href: 'https://fonts.googleapis.com/b', remove: sinon.spy() }; - sinon.stub(document, 'querySelectorAll').returns([linkA, linkB]); - // Stub fetch responses - fetchStub.withArgs('https://fonts.googleapis.com/a').resolves({ ok: true, text: () => Promise.resolve('CSS_A') }); - fetchStub.withArgs('https://fonts.googleapis.com/b').resolves({ ok: true, text: () => Promise.resolve('CSS_B') }); - // Prepare a fragment and override document.createDocumentFragment - const frag = { children: [], appendChild(node) { this.children.push(node); } }; - document.createDocumentFragment = () => frag; - // Stub head.appendChild - document.head = { appendChild: sinon.spy() }; - - await beacon.inlineGoogleFonts(); - // Ensure fetch called for each href - sinon.assert.calledWith(fetchStub, 'https://fonts.googleapis.com/a'); - sinon.assert.calledWith(fetchStub, 'https://fonts.googleapis.com/b'); - // Two style nodes in fragment - assert.strictEqual(frag.children.length, 2); - assert.strictEqual(frag.children[0].textContent, 'CSS_A'); - assert.strictEqual(frag.children[1].textContent, 'CSS_B'); - // Single append to head - sinon.assert.calledOnce(document.head.appendChild); - // Links removed - sinon.assert.calledOnce(linkA.remove); - sinon.assert.calledOnce(linkB.remove); + it('should return empty arrays/objects if no external links are found', async () => { + global.document.querySelectorAll = sinon.stub().returns([]); + const result = await beaconPreloadFonts.externalStylesheetsDoc(); + assert.deepStrictEqual(result.styleSheets, []); + assert.deepStrictEqual(result.fontPairs, {}); }); }); }); \ No newline at end of file From 662128f20043dde9512841da96f10a48486ed9ef Mon Sep 17 00:00:00 2001 From: WordPressFan Date: Mon, 19 May 2025 08:45:37 +0300 Subject: [PATCH 4/6] remove external urls from stylesheets --- src/BeaconPreloadFonts.js | 80 ++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/BeaconPreloadFonts.js b/src/BeaconPreloadFonts.js index 77051d2..83682c7 100644 --- a/src/BeaconPreloadFonts.js +++ b/src/BeaconPreloadFonts.js @@ -323,46 +323,48 @@ class BeaconPreloadFonts { getFontFaceRules() { const stylesheetFonts = {}; - Array.from(document.styleSheets).forEach((sheet) => { - try { - Array.from(sheet.cssRules || []).forEach((rule) => { - if (rule instanceof CSSFontFaceRule) { - const src = rule.style.getPropertyValue('src'); - const fontFamily = rule.style.getPropertyValue('font-family') - .replace(/['"]+/g, '') - .trim(); - const weight = rule.style.getPropertyValue('font-weight') || '400'; - const style = rule.style.getPropertyValue('font-style') || 'normal'; - - if (!stylesheetFonts[fontFamily]) { - stylesheetFonts[fontFamily] = { - urls: [], - variations: new Set() - }; - } - - const urls = src.match(/url\(['"]?([^'"]+)['"]?\)/g) || []; - urls.forEach((urlMatch) => { - let rawUrl = urlMatch.match(/url\(['"]?([^'"]+)['"]?\)/)[1]; - // Reconstruct url to absolute if stylesheet is not internal. - if (sheet.href) { - rawUrl = new URL(rawUrl, sheet.href).href; - } - const normalizedUrl = this.cleanUrl(rawUrl); - if (!stylesheetFonts[fontFamily].urls.includes(normalizedUrl)) { - stylesheetFonts[fontFamily].urls.push(normalizedUrl); - stylesheetFonts[fontFamily].variations.add(JSON.stringify({ - weight, - style - })); + Array.from(Array.from(document.styleSheets)) + .filter(sheet => !sheet.href || new URL(sheet.href).origin === location.origin) + .forEach((sheet) => { + try { + Array.from(sheet.cssRules || []).forEach((rule) => { + if (rule instanceof CSSFontFaceRule) { + const src = rule.style.getPropertyValue('src'); + const fontFamily = rule.style.getPropertyValue('font-family') + .replace(/['"]+/g, '') + .trim(); + const weight = rule.style.getPropertyValue('font-weight') || '400'; + const style = rule.style.getPropertyValue('font-style') || 'normal'; + + if (!stylesheetFonts[fontFamily]) { + stylesheetFonts[fontFamily] = { + urls: [], + variations: new Set() + }; } - }); - } - }); - } catch (e) { - this.logger.logMessage(e); - } - }); + + const urls = src.match(/url\(['"]?([^'"]+)['"]?\)/g) || []; + urls.forEach((urlMatch) => { + let rawUrl = urlMatch.match(/url\(['"]?([^'"]+)['"]?\)/)[1]; + // Reconstruct url to absolute if stylesheet is not internal. + if (sheet.href) { + rawUrl = new URL(rawUrl, sheet.href).href; + } + const normalizedUrl = this.cleanUrl(rawUrl); + if (!stylesheetFonts[fontFamily].urls.includes(normalizedUrl)) { + stylesheetFonts[fontFamily].urls.push(normalizedUrl); + stylesheetFonts[fontFamily].variations.add(JSON.stringify({ + weight, + style + })); + } + }); + } + }); + } catch (e) { + this.logger.logMessage(e); + } + }); Object.values(stylesheetFonts).forEach(fontData => { fontData.variations = Array.from(fontData.variations).map(v => JSON.parse(v)); From 0a94a7c4554bd5c54cb2bdf68c09cde6c096198c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Robin?= Date: Tue, 20 May 2025 03:49:10 +0200 Subject: [PATCH 5/6] fix test --- test/BeaconPreloadFonts.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/BeaconPreloadFonts.test.js b/test/BeaconPreloadFonts.test.js index ee10d2f..065dd31 100644 --- a/test/BeaconPreloadFonts.test.js +++ b/test/BeaconPreloadFonts.test.js @@ -365,7 +365,7 @@ describe('BeaconPreloadFonts', () => { const mockCSSFontFaceRule3 = createMockFontFaceRule('Open Sans', 'url("fonts/opensans.woff2")', '400', 'normal'); const mockStyleSheet = { - href: 'https://example.com/styles.css', + href: null, cssRules: [ mockCSSFontFaceRule1, { type: 1 }, // Some other rule type From e93c42b8b91f46bfc81e61ee570e1eff759e1927 Mon Sep 17 00:00:00 2001 From: WordPressFan Date: Thu, 22 May 2025 09:07:31 +0300 Subject: [PATCH 6/6] add Adame's suggestion and it is working --- src/BeaconPreloadFonts.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/BeaconPreloadFonts.js b/src/BeaconPreloadFonts.js index 984ca97..67cf22e 100644 --- a/src/BeaconPreloadFonts.js +++ b/src/BeaconPreloadFonts.js @@ -433,8 +433,7 @@ class BeaconPreloadFonts { const networkLoadedFonts = this.getNetworkLoadedFonts(); const stylesheetFonts = this.getFontFaceRules(); const hostedFonts = new Map(); - const externalFontPairs = this.config.font_data; - const externalFontsResults = await this.processExternalFonts(externalFontPairs); + const externalFontsResults = await this.processExternalFonts(this.externalParsedPairs); const elements = Array.from(document.getElementsByTagName('*')) .filter(el => this.isElementAboveFold(el));