wp-rocket icon indicating copy to clipboard operation
wp-rocket copied to clipboard

3.19 Should preload fonts using @import

Open hanna-meda opened this issue 7 months ago • 2 comments

Describe the bug Fonts using @import are not preloaded.

To Reproduce Steps to reproduce the behavior:

  1. Install and activate WPR 3.19.
  2. Enable Preload Fonts & Self-host Google Fonts.
  3. Visit page template using GF above the fold via @import ( i.e. https://rocketlabsqa.ovh/preloadfonts_import/, preloadfonts_import.php template).
  4. Clear the cache and revisit the page.

Expected behavior Above the fold fonts using @import should be preloaded, too.

Note: Issue related to #7241 .

Screenshots If applicable, add screenshots to help explain your problem.

hanna-meda avatar May 06 '25 10:05 hanna-meda

This issue has nothing to do with WP Rocket, but rocket scripts.

Scope a solution

In here, we could edit the function with something like:

getFontFaceRules() {
        const stylesheetFonts = {};

        // Helper to recursively process a stylesheet and its imports
        const processSheet = (sheet, seenSheets = new Set()) => {
            if (!sheet || seenSheets.has(sheet)) return;
            seenSheets.add(sheet);
            let rules;
            try {
                rules = sheet.cssRules || [];
            } catch (e) {
                // CORS or inaccessible stylesheet
                this.logger && this.logger.logMessage(e);
                return;
            }
            for (const rule of rules) {
                // Recursively process @import rules
                if (rule.type === CSSRule.IMPORT_RULE && rule.styleSheet) {
                    processSheet(rule.styleSheet, seenSheets);
                } else 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(document.styleSheets).forEach(sheet => processSheet(sheet));

        Object.values(stylesheetFonts).forEach(fontData => {
            fontData.variations = Array.from(fontData.variations).map(v => JSON.parse(v));
        });

        return stylesheetFonts;
    }

This should ensure to get the @import as well sent to the backend.

Efforts

S

Miraeld avatar May 19 '25 09:05 Miraeld

LGTM Thanks @Miraeld indeed, this is to be tackled on the beacon processing part in getFontFaceRules method, though Implementation might differ from what is proposed in grooming.

jeawhanlee avatar Jun 09 '25 13:06 jeawhanlee

@MathieuLamiot Should we move the issue to Blocked for the moment or just remove it from this sprint ?

Miraeld avatar Jun 20 '25 00:06 Miraeld

Is there a blocker? Otherwise, you can move it to ToDo and to the next sprint.

MathieuLamiot avatar Jun 20 '25 07:06 MathieuLamiot